kitty-0.41.1/0000775000175000017510000000000014773370543012307 5ustar nileshnileshkitty-0.41.1/.editorconfig0000664000175000017510000000056514773370543014772 0ustar nileshnileshroot = true [*] indent_style = space indent_size = 4 end_of_line = lf trim_trailing_whitespace = true [{Makefile,*.terminfo,*.go}] indent_style = tab # Autogenerated files with tabs below this line. [kitty/{unicode-data.c,emoji.h,wcwidth-std.h}] indent_style = tab [kittens/unicode_input/names.h] indent_style = tab [glfw/wayland-*-protocol.{c,h}] indent_style = tab kitty-0.41.1/.gitattributes0000664000175000017510000000256214773370543015207 0ustar nileshnileshkitty/char-props-data.h linguist-generated=true kitty_tests/GraphemeBreakTest.json linguist-generated=true kitty/charsets.c linguist-generated=true kitty/key_encoding.py linguist-generated=true kitty/rowcolumn-diacritics.c linguist-generated=true kitty/rgb.py linguist-generated=true kitty/srgb_gamma.* linguist-generated=true kitty/gl-wrapper.* linguist-generated=true kitty/glfw-wrapper.* linguist-generated=true kitty/parse-graphics-command.h linguist-generated=true kitty/parse-multicell-command.h linguist-generated=true kitty/options/types.py linguist-generated=true kitty/options/parse.py linguist-generated=true kitty/options/to-c-generated.h linguist-generated=true kittens/diff/options/types.py linguist-generated=true kittens/diff/options/parse.py linguist-generated=true glfw/*.c linguist-vendored=true glfw/*.h linguist-vendored=true 3rdparty/** linguist-vendored=true kittens/unicode_input/names.h linguist-generated=true tools/wcswidth/char-props-data.go linguist-generated=true tools/unicode_names/names.txt linguist-generated=true terminfo/kitty.term* linguist-generated=true terminfo/x/* linguist-generated=true *_generated.h linguist-generated=true *_generated.go linguist-generated=true *_generated_test.go linguist-generated=true *_generated_test.s linguist-generated=true *_generated.s linguist-generated=true *.py text diff=python *.m text diff=objc *.go text diff=go kitty-0.41.1/.gitignore0000664000175000017510000000065314773370543014303 0ustar nileshnilesh*.so *.pyc *.pyo *.bin *_stub.pyi *_generated.go *_generated.s *_generated_test.go *_generated_test.s *_generated.h /.dmypy.json /dependencies /tags /build/ /fonts/ /linux-package/ /kitty.app/ /glad/out/ /kitty/launcher/kitt* /*.dSYM/ __pycache__/ /glfw/wayland-*-client-protocol.[ch] /docs/_build/ /docs/generated/ /tools/simdstring/simdstring.test /.mypy_cache /.ruff_cache .DS_Store .cache bypy/b bypy/virtual-machines.conf kitty-0.41.1/.ignore0000664000175000017510000000003614773370543013572 0ustar nileshnileshkittens/unicode_input/names.h kitty-0.41.1/3rdparty/0000775000175000017510000000000014773370543014057 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/0000775000175000017510000000000014773370543015143 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/LICENSE0000664000175000017510000000261614773370543016155 0ustar nileshnileshCopyright (c) 2005-2007, Nick Galbreath Copyright (c) 2015-2018, Wojciech Muła Copyright (c) 2016-2017, Matthieu Darbois Copyright (c) 2013-2022, Alfred Klomp All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. kitty-0.41.1/3rdparty/base64/README.md0000664000175000017510000004605314773370543016432 0ustar nileshnilesh# Fast Base64 stream encoder/decoder [![Build Status](https://github.com/aklomp/base64/actions/workflows/test.yml/badge.svg)](https://github.com/aklomp/base64/actions/workflows/test.yml) This is an implementation of a base64 stream encoding/decoding library in C99 with SIMD (AVX2, AVX512, NEON, AArch64/NEON, SSSE3, SSE4.1, SSE4.2, AVX) and [OpenMP](http://www.openmp.org) acceleration. It also contains wrapper functions to encode/decode simple length-delimited strings. This library aims to be: - FAST; - easy to use; - elegant. On x86, the library does runtime feature detection. The first time it's called, the library will determine the appropriate encoding/decoding routines for the machine. It then remembers them for the lifetime of the program. If your processor supports AVX2, SSSE3, SSE4.1, SSE4.2 or AVX instructions, the library will pick an optimized codec that lets it encode/decode 12 or 24 bytes at a time, which gives a speedup of four or more times compared to the "plain" bytewise codec. AVX512 support is only for encoding at present, utilizing the AVX512 VL and VBMI instructions. Decoding part reused AVX2 implementations. For CPUs later than Cannonlake (manufactured in 2018) supports these instructions. NEON support is hardcoded to on or off at compile time, because portable runtime feature detection is unavailable on ARM. Even if your processor does not support SIMD instructions, this is a very fast library. The fallback routine can process 32 or 64 bits of input in one round, depending on your processor's word width, which still makes it significantly faster than naive bytewise implementations. On some 64-bit machines, the 64-bit routines even outperform the SSSE3 ones. To the author's knowledge, at the time of original release, this was the only Base64 library to offer SIMD acceleration. The author wrote [an article](http://www.alfredklomp.com/programming/sse-base64) explaining one possible SIMD approach to encoding/decoding Base64. The article can help figure out what the code is doing, and why. Notable features: - Really fast on x86 and ARM systems by using SIMD vector processing; - Can use [OpenMP](http://www.openmp.org) for even more parallel speedups; - Really fast on other 32 or 64-bit platforms through optimized routines; - Reads/writes blocks of streaming data; - Does not dynamically allocate memory; - Valid C99 that compiles with pedantic options on; - Re-entrant and threadsafe; - Unit tested; - Uses Duff's Device. ## Acknowledgements The original AVX2, NEON and Aarch64/NEON codecs were generously contributed by [Inkymail](https://github.com/inkymail/base64), who, in their fork, also implemented some additional features. Their work is slowly being backported into this project. The SSSE3 and AVX2 codecs were substantially improved by using some very clever optimizations described by Wojciech Muła in a [series](http://0x80.pl/notesen/2016-01-12-sse-base64-encoding.html) of [articles](http://0x80.pl/notesen/2016-01-17-sse-base64-decoding.html). His own code is [here](https://github.com/WojciechMula/toys/tree/master/base64). The AVX512 encoder is based on code from Wojciech Muła's [base64simd](https://github.com/WojciechMula/base64simd) library. The OpenMP implementation was added by Ferry Toth (@htot) from [Exalon Delft](http://www.exalondelft.nl). ## Building The `lib` directory contains the code for the actual library. Typing `make` in the toplevel directory will build `lib/libbase64.o` and `bin/base64`. The first is a single, self-contained object file that you can link into your own project. The second is a standalone test binary that works similarly to the `base64` system utility. The matching header file needed to use this library is in `include/libbase64.h`. To compile just the "plain" library without SIMD codecs, type: ```sh make lib/libbase64.o ``` Optional SIMD codecs can be included by specifying the `AVX2_CFLAGS`, `AVX512_CFLAGS`, `NEON32_CFLAGS`, `NEON64_CFLAGS`, `SSSE3_CFLAGS`, `SSE41_CFLAGS`, `SSE42_CFLAGS` and/or `AVX_CFLAGS` environment variables. A typical build invocation on x86 looks like this: ```sh AVX2_CFLAGS=-mavx2 SSSE3_CFLAGS=-mssse3 SSE41_CFLAGS=-msse4.1 SSE42_CFLAGS=-msse4.2 AVX_CFLAGS=-mavx make lib/libbase64.o ``` ### AVX2 To build and include the AVX2 codec, set the `AVX2_CFLAGS` environment variable to a value that will turn on AVX2 support in your compiler, typically `-mavx2`. Example: ```sh AVX2_CFLAGS=-mavx2 make ``` ### AVX512 To build and include the AVX512 codec, set the `AVX512_CFLAGS` environment variable to a value that will turn on AVX512 support in your compiler, typically `-mavx512vl -mavx512vbmi`. Example: ```sh AVX512_CFLAGS="-mavx512vl -mavx512vbmi" make ``` The codec will only be used if runtime feature detection shows that the target machine supports AVX2. ### SSSE3 To build and include the SSSE3 codec, set the `SSSE3_CFLAGS` environment variable to a value that will turn on SSSE3 support in your compiler, typically `-mssse3`. Example: ```sh SSSE3_CFLAGS=-mssse3 make ``` The codec will only be used if runtime feature detection shows that the target machine supports SSSE3. ### NEON This library includes two NEON codecs: one for regular 32-bit ARM and one for the 64-bit AArch64 with NEON, which has double the amount of SIMD registers and can do full 64-byte table lookups. These codecs encode in 48-byte chunks and decode in massive 64-byte chunks, so they had to be augmented with an uint32/64 codec to stay fast on smaller inputs! Use LLVM/Clang for compiling the NEON codecs. The code generation of at least GCC 4.6 (the version shipped with Raspbian and used for testing) contains a bug when compiling `vstq4_u8()`, and the generated assembly code is of low quality. NEON intrinsics are a known weak area of GCC. Clang does a better job. NEON support can unfortunately not be portably detected at runtime from userland (the `mrc` instruction is privileged), so the default value for using the NEON codec is determined at compile-time. But you can do your own runtime detection. You can include the NEON codec and make it the default, then do a runtime check if the CPU has NEON support, and if not, force a downgrade to non-NEON with `BASE64_FORCE_PLAIN`. These are your options: 1. Don't include NEON support; 2. build NEON support and make it the default, but build all other code without NEON flags so that you can override the default at runtime with `BASE64_FORCE_PLAIN`; 3. build everything with NEON support and make it the default; 4. build everything with NEON support, but don't make it the default (which makes no sense). For option 1, simply don't specify any NEON-specific compiler flags at all, like so: ```sh CC=clang CFLAGS="-march=armv6" make ``` For option 2, keep your `CFLAGS` plain, but set the `NEON32_CFLAGS` environment variable to a value that will build NEON support. The line below, for instance, will build all the code at ARMv6 level, except for the NEON codec, which is built at ARMv7. It will also make the NEON codec the default. For ARMv6 platforms, override that default at runtime with the `BASE64_FORCE_PLAIN` flag. No ARMv7/NEON code will then be touched. ```sh CC=clang CFLAGS="-march=armv6" NEON32_CFLAGS="-march=armv7 -mfpu=neon" make ``` For option 3, put everything in your `CFLAGS` and use a stub, but non-empty, `NEON32_CFLAGS`. This example works for the Raspberry Pi 2B V1.1, which has NEON support: ```sh CC=clang CFLAGS="-march=armv7 -mtune=cortex-a7" NEON32_CFLAGS="-mfpu=neon" make ``` To build and include the NEON64 codec, use `CFLAGS` as usual to define the platform and set `NEON64_CFLAGS` to a nonempty stub. (The AArch64 target has mandatory NEON64 support.) Example: ```sh CC=clang CFLAGS="--target=aarch64-linux-gnu -march=armv8-a" NEON64_CFLAGS=" " make ``` ### OpenMP To enable OpenMP on GCC you need to build with `-fopenmp`. This can be by setting the `OPENMP` environment variable to `1`. Example: ```sh OPENMP=1 make ``` This will let the compiler define `_OPENMP`, which in turn will include the OpenMP optimized `lib_openmp.c` into `lib.c`. By default the number of parallel threads will be equal to the number of cores of the processor. On a quad core with hyperthreading eight cores will be detected, but hyperthreading will not increase the performance. To get verbose information about OpenMP start the program with `OMP_DISPLAY_ENV=VERBOSE`, for instance ```sh OMP_DISPLAY_ENV=VERBOSE test/benchmark ``` To put a limit on the number of threads, start the program with `OMP_THREAD_LIMIT=n`, for instance ```sh OMP_THREAD_LIMIT=2 test/benchmark ``` An example of running a benchmark with OpenMP, SSSE3 and AVX2 enabled: ```sh make clean && OPENMP=1 SSSE3_CFLAGS=-mssse3 AVX2_CFLAGS=-mavx2 make && OPENMP=1 make -C test ``` ## API reference Strings are represented as a pointer and a length; they are not zero-terminated. This was a conscious design decision. In the decoding step, relying on zero-termination would make no sense since the output could contain legitimate zero bytes. In the encoding step, returning the length saves the overhead of calling `strlen()` on the output. If you insist on the trailing zero, you can easily add it yourself at the given offset. ### Flags Some API calls take a `flags` argument. That argument can be used to force the use of a specific codec, even if that codec is a no-op in the current build. Mainly there for testing purposes, this is also useful on ARM where the only way to do runtime NEON detection is to ask the OS if it's available. The following constants can be used: - `BASE64_FORCE_AVX2` - `BASE64_FORCE_AVX512` - `BASE64_FORCE_NEON32` - `BASE64_FORCE_NEON64` - `BASE64_FORCE_PLAIN` - `BASE64_FORCE_SSSE3` - `BASE64_FORCE_SSE41` - `BASE64_FORCE_SSE42` - `BASE64_FORCE_AVX` Set `flags` to `0` for the default behavior, which is runtime feature detection on x86, a compile-time fixed codec on ARM, and the plain codec on other platforms. ### Encoding #### base64_encode ```c void base64_encode ( const char *src , size_t srclen , char *out , size_t *outlen , int flags ) ; ``` Wrapper function to encode a plain string of given length. Output is written to `out` without trailing zero. Output length in bytes is written to `outlen`. The buffer in `out` has been allocated by the caller and is at least 4/3 the size of the input. #### base64_stream_encode_init ```c void base64_stream_encode_init ( struct base64_state *state , int flags ) ; ``` Call this before calling `base64_stream_encode()` to init the state. #### base64_stream_encode ```c void base64_stream_encode ( struct base64_state *state , const char *src , size_t srclen , char *out , size_t *outlen ) ; ``` Encodes the block of data of given length at `src`, into the buffer at `out`. Caller is responsible for allocating a large enough out-buffer; it must be at least 4/3 the size of the in-buffer, but take some margin. Places the number of new bytes written into `outlen` (which is set to zero when the function starts). Does not zero-terminate or finalize the output. #### base64_stream_encode_final ```c void base64_stream_encode_final ( struct base64_state *state , char *out , size_t *outlen ) ; ``` Finalizes the output begun by previous calls to `base64_stream_encode()`. Adds the required end-of-stream markers if appropriate. `outlen` is modified and will contain the number of new bytes written at `out` (which will quite often be zero). ### Decoding #### base64_decode ```c int base64_decode ( const char *src , size_t srclen , char *out , size_t *outlen , int flags ) ; ``` Wrapper function to decode a plain string of given length. Output is written to `out` without trailing zero. Output length in bytes is written to `outlen`. The buffer in `out` has been allocated by the caller and is at least 3/4 the size of the input. Returns `1` for success, and `0` when a decode error has occured due to invalid input. Returns `-1` if the chosen codec is not included in the current build. #### base64_stream_decode_init ```c void base64_stream_decode_init ( struct base64_state *state , int flags ) ; ``` Call this before calling `base64_stream_decode()` to init the state. #### base64_stream_decode ```c int base64_stream_decode ( struct base64_state *state , const char *src , size_t srclen , char *out , size_t *outlen ) ; ``` Decodes the block of data of given length at `src`, into the buffer at `out`. Caller is responsible for allocating a large enough out-buffer; it must be at least 3/4 the size of the in-buffer, but take some margin. Places the number of new bytes written into `outlen` (which is set to zero when the function starts). Does not zero-terminate the output. Returns 1 if all is well, and 0 if a decoding error was found, such as an invalid character. Returns -1 if the chosen codec is not included in the current build. Used by the test harness to check whether a codec is available for testing. ## Examples A simple example of encoding a static string to base64 and printing the output to stdout: ```c #include /* fwrite */ #include "libbase64.h" int main () { char src[] = "hello world"; char out[20]; size_t srclen = sizeof(src) - 1; size_t outlen; base64_encode(src, srclen, out, &outlen, 0); fwrite(out, outlen, 1, stdout); return 0; } ``` A simple example (no error checking, etc) of stream encoding standard input to standard output: ```c #include #include "libbase64.h" int main () { size_t nread, nout; char buf[12000], out[16000]; struct base64_state state; // Initialize stream encoder: base64_stream_encode_init(&state, 0); // Read contents of stdin into buffer: while ((nread = fread(buf, 1, sizeof(buf), stdin)) > 0) { // Encode buffer: base64_stream_encode(&state, buf, nread, out, &nout); // If there's output, print it to stdout: if (nout) { fwrite(out, nout, 1, stdout); } // If an error occurred, exit the loop: if (feof(stdin)) { break; } } // Finalize encoding: base64_stream_encode_final(&state, out, &nout); // If the finalizing resulted in extra output bytes, print them: if (nout) { fwrite(out, nout, 1, stdout); } return 0; } ``` Also see `bin/base64.c` for a simple re-implementation of the `base64` utility. A file or standard input is fed through the encoder/decoder, and the output is written to standard output. ## Tests See `tests/` for a small test suite. Testing is automated with [GitHub Actions](https://github.com/aklomp/base64/actions), which builds and tests the code across various architectures. ## Benchmarks Benchmarks can be run with the built-in benchmark program as follows: ```sh make -C test benchmark && test/benchmark ``` It will run an encoding and decoding benchmark for all of the compiled-in codecs. The tables below contain some results on random machines. All numbers measured with a 10MB buffer in MB/sec, rounded to the nearest integer. \*: Update needed x86 processors | Processor | Plain enc | Plain dec | SSSE3 enc | SSSE3 dec | AVX enc | AVX dec | AVX2 enc | AVX2 dec | |-------------------------------------------|----------:|----------:|----------:|----------:|--------:|--------:|---------:|---------:| | i7-4771 @ 3.5 GHz | 833\* | 1111\* | 3333\* | 4444\* | TBD | TBD | 4999\* | 6666\* | | i7-4770 @ 3.4 GHz DDR1600 | 1790\* | 3038\* | 4899\* | 4043\* | 4796\* | 5709\* | 4681\* | 6386\* | | i7-4770 @ 3.4 GHz DDR1600 OPENMP 1 thread | 1784\* | 3041\* | 4945\* | 4035\* | 4776\* | 5719\* | 4661\* | 6294\* | | i7-4770 @ 3.4 GHz DDR1600 OPENMP 2 thread | 3401\* | 5729\* | 5489\* | 7444\* | 5003\* | 8624\* | 5105\* | 8558\* | | i7-4770 @ 3.4 GHz DDR1600 OPENMP 4 thread | 4884\* | 7099\* | 4917\* | 7057\* | 4799\* | 7143\* | 4902\* | 7219\* | | i7-4770 @ 3.4 GHz DDR1600 OPENMP 8 thread | 5212\* | 8849\* | 5284\* | 9099\* | 5289\* | 9220\* | 4849\* | 9200\* | | i7-4870HQ @ 2.5 GHz | 1471\* | 3066\* | 6721\* | 6962\* | 7015\* | 8267\* | 8328\* | 11576\* | | i5-4590S @ 3.0 GHz | 3356 | 3197 | 4363 | 6104 | 4243\* | 6233 | 4160\* | 6344 | | Xeon X5570 @ 2.93 GHz | 2161 | 1508 | 3160 | 3915 | - | - | - | - | | Pentium4 @ 3.4 GHz | 896 | 740 | - | - | - | - | - | - | | Atom N270 | 243 | 266 | 508 | 387 | - | - | - | - | | AMD E-450 | 645 | 564 | 625 | 634 | - | - | - | - | | Intel Edison @ 500 MHz | 79\* | 92\* | 152\* | 172\* | - | - | - | - | | Intel Edison @ 500 MHz OPENMP 2 thread | 158\* | 184\* | 300\* | 343\* | - | - | - | - | | Intel Edison @ 500 MHz (x86-64) | 162 | 119 | 209 | 164 | - | - | - | - | | Intel Edison @ 500 MHz (x86-64) 2 thread | 319 | 237 | 412 | 329 | - | - | - | - | ARM processors | Processor | Plain enc | Plain dec | NEON32 enc | NEON32 dec | NEON64 enc | NEON64 dec | |-------------------------------------------|----------:|----------:|-----------:|-----------:|-----------:|-----------:| | Raspberry PI B+ V1.2 | 46\* | 40\* | - | - | - | - | | Raspberry PI 2 B V1.1 | 85 | 141 | 300 | 225 | - | - | | Apple iPhone SE armv7 | 1056\* | 895\* | 2943\* | 2618\* | - | - | | Apple iPhone SE arm64 | 1061\* | 1239\* | - | - | 4098\* | 3983\* | PowerPC processors | Processor | Plain enc | Plain dec | |-------------------------------------------|----------:|----------:| | PowerPC E6500 @ 1.8GHz | 270\* | 265\* | Benchmarks on i7-4770 @ 3.4 GHz DDR1600 with varrying buffer sizes: ![Benchmarks](base64-benchmarks.png) Note: optimal buffer size to take advantage of the cache is in the range of 100 kB to 1 MB, leading to 12x faster AVX encoding/decoding compared to Plain, or a throughput of 24/27GB/sec. Also note the performance degradation when the buffer size is less than 10 kB due to thread creation overhead. To prevent this from happening `lib_openmp.c` defines `OMP_THRESHOLD 20000`, requiring at least a 20000 byte buffer to enable multithreading. ## License This repository is licensed under the [BSD 2-clause License](http://opensource.org/licenses/BSD-2-Clause). See the LICENSE file. kitty-0.41.1/3rdparty/base64/config.h0000664000175000017510000000000014773370543016547 0ustar nileshnileshkitty-0.41.1/3rdparty/base64/include/0000775000175000017510000000000014773370543016566 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/include/libbase64.h0000664000175000017510000001126514773370543020517 0ustar nileshnilesh#ifndef LIBBASE64_H #define LIBBASE64_H #include /* size_t */ #if defined(_WIN32) || defined(__CYGWIN__) #define BASE64_SYMBOL_IMPORT __declspec(dllimport) #define BASE64_SYMBOL_EXPORT __declspec(dllexport) #define BASE64_SYMBOL_PRIVATE #elif __GNUC__ >= 4 #define BASE64_SYMBOL_IMPORT __attribute__ ((visibility ("default"))) #define BASE64_SYMBOL_EXPORT __attribute__ ((visibility ("default"))) #define BASE64_SYMBOL_PRIVATE __attribute__ ((visibility ("hidden"))) #else #define BASE64_SYMBOL_IMPORT #define BASE64_SYMBOL_EXPORT #define BASE64_SYMBOL_PRIVATE #endif #if defined(BASE64_STATIC_DEFINE) #define BASE64_EXPORT #define BASE64_NO_EXPORT #else #if defined(BASE64_EXPORTS) // defined if we are building the shared library #define BASE64_EXPORT BASE64_SYMBOL_EXPORT #else #define BASE64_EXPORT BASE64_SYMBOL_IMPORT #endif #define BASE64_NO_EXPORT BASE64_SYMBOL_PRIVATE #endif #ifdef __cplusplus extern "C" { #endif /* These are the flags that can be passed in the `flags` argument. The values * below force the use of a given codec, even if that codec is a no-op in the * current build. Used in testing. Set to 0 for the default behavior, which is * runtime feature detection on x86, a compile-time fixed codec on ARM, and * the plain codec on other platforms: */ #define BASE64_FORCE_AVX2 (1 << 0) #define BASE64_FORCE_NEON32 (1 << 1) #define BASE64_FORCE_NEON64 (1 << 2) #define BASE64_FORCE_PLAIN (1 << 3) #define BASE64_FORCE_SSSE3 (1 << 4) #define BASE64_FORCE_SSE41 (1 << 5) #define BASE64_FORCE_SSE42 (1 << 6) #define BASE64_FORCE_AVX (1 << 7) #define BASE64_FORCE_AVX512 (1 << 8) struct base64_state { int eof; int bytes; int flags; unsigned char carry; }; /* Wrapper function to encode a plain string of given length. Output is written * to *out without trailing zero. Output length in bytes is written to *outlen. * The buffer in `out` has been allocated by the caller and is at least 4/3 the * size of the input. See above for `flags`; set to 0 for default operation: */ void BASE64_EXPORT base64_encode ( const char *src , size_t srclen , char *out , size_t *outlen , int flags ) ; /* Call this before calling base64_stream_encode() to init the state. See above * for `flags`; set to 0 for default operation: */ void BASE64_EXPORT base64_stream_encode_init ( struct base64_state *state , int flags ) ; /* Encodes the block of data of given length at `src`, into the buffer at * `out`. Caller is responsible for allocating a large enough out-buffer; it * must be at least 4/3 the size of the in-buffer, but take some margin. Places * the number of new bytes written into `outlen` (which is set to zero when the * function starts). Does not zero-terminate or finalize the output. */ void BASE64_EXPORT base64_stream_encode ( struct base64_state *state , const char *src , size_t srclen , char *out , size_t *outlen ) ; /* Finalizes the output begun by previous calls to `base64_stream_encode()`. * Adds the required end-of-stream markers if appropriate. `outlen` is modified * and will contain the number of new bytes written at `out` (which will quite * often be zero). */ void BASE64_EXPORT base64_stream_encode_final ( struct base64_state *state , char *out , size_t *outlen ) ; /* Wrapper function to decode a plain string of given length. Output is written * to *out without trailing zero. Output length in bytes is written to *outlen. * The buffer in `out` has been allocated by the caller and is at least 3/4 the * size of the input. See above for `flags`, set to 0 for default operation: */ int BASE64_EXPORT base64_decode ( const char *src , size_t srclen , char *out , size_t *outlen , int flags ) ; /* Call this before calling base64_stream_decode() to init the state. See above * for `flags`; set to 0 for default operation: */ void BASE64_EXPORT base64_stream_decode_init ( struct base64_state *state , int flags ) ; /* Decodes the block of data of given length at `src`, into the buffer at * `out`. Caller is responsible for allocating a large enough out-buffer; it * must be at least 3/4 the size of the in-buffer, but take some margin. Places * the number of new bytes written into `outlen` (which is set to zero when the * function starts). Does not zero-terminate the output. Returns 1 if all is * well, and 0 if a decoding error was found, such as an invalid character. * Returns -1 if the chosen codec is not included in the current build. Used by * the test harness to check whether a codec is available for testing. */ int BASE64_EXPORT base64_stream_decode ( struct base64_state *state , const char *src , size_t srclen , char *out , size_t *outlen ) ; #ifdef __cplusplus } #endif #endif /* LIBBASE64_H */ kitty-0.41.1/3rdparty/base64/lib/0000775000175000017510000000000014773370543015711 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/lib/arch/0000775000175000017510000000000014773370543016626 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/lib/arch/avx/0000775000175000017510000000000014773370543017424 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/lib/arch/avx/codec.c0000664000175000017510000000255014773370543020647 0ustar nileshnilesh#include #include #include #include "../../../include/libbase64.h" #include "../../tables/tables.h" #include "../../codecs.h" #include "config.h" #include "../../env.h" #if HAVE_AVX #include // Only enable inline assembly on supported compilers and on 64-bit CPUs. #ifndef BASE64_AVX_USE_ASM # if (defined(__GNUC__) || defined(__clang__)) && BASE64_WORDSIZE == 64 # define BASE64_AVX_USE_ASM 1 # else # define BASE64_AVX_USE_ASM 0 # endif #endif #include "../ssse3/dec_reshuffle.c" #include "../ssse3/dec_loop.c" #if BASE64_AVX_USE_ASM # include "enc_loop_asm.c" #else # include "../ssse3/enc_translate.c" # include "../ssse3/enc_reshuffle.c" # include "../ssse3/enc_loop.c" #endif #endif // HAVE_AVX BASE64_ENC_FUNCTION(avx) { #if HAVE_AVX #include "../generic/enc_head.c" // For supported compilers, use a hand-optimized inline assembly // encoder. Otherwise fall back on the SSSE3 encoder, but compiled with // AVX flags to generate better optimized AVX code. #if BASE64_AVX_USE_ASM enc_loop_avx(&s, &slen, &o, &olen); #else enc_loop_ssse3(&s, &slen, &o, &olen); #endif #include "../generic/enc_tail.c" #else BASE64_ENC_STUB #endif } BASE64_DEC_FUNCTION(avx) { #if HAVE_AVX #include "../generic/dec_head.c" dec_loop_ssse3(&s, &slen, &o, &olen); #include "../generic/dec_tail.c" #else BASE64_DEC_STUB #endif } kitty-0.41.1/3rdparty/base64/lib/arch/avx/enc_loop_asm.c0000664000175000017510000002214214773370543022227 0ustar nileshnilesh// Apologies in advance for combining the preprocessor with inline assembly, // two notoriously gnarly parts of C, but it was necessary to avoid a lot of // code repetition. The preprocessor is used to template large sections of // inline assembly that differ only in the registers used. If the code was // written out by hand, it would become very large and hard to audit. // Generate a block of inline assembly that loads register R0 from memory. The // offset at which the register is loaded is set by the given round. #define LOAD(R0, ROUND) \ "vlddqu ("#ROUND" * 12)(%[src]), %["R0"] \n\t" // Generate a block of inline assembly that deinterleaves and shuffles register // R0 using preloaded constants. Outputs in R0 and R1. #define SHUF(R0, R1, R2) \ "vpshufb %[lut0], %["R0"], %["R1"] \n\t" \ "vpand %["R1"], %[msk0], %["R2"] \n\t" \ "vpand %["R1"], %[msk2], %["R1"] \n\t" \ "vpmulhuw %["R2"], %[msk1], %["R2"] \n\t" \ "vpmullw %["R1"], %[msk3], %["R1"] \n\t" \ "vpor %["R1"], %["R2"], %["R1"] \n\t" // Generate a block of inline assembly that takes R0 and R1 and translates // their contents to the base64 alphabet, using preloaded constants. #define TRAN(R0, R1, R2) \ "vpsubusb %[n51], %["R1"], %["R0"] \n\t" \ "vpcmpgtb %[n25], %["R1"], %["R2"] \n\t" \ "vpsubb %["R2"], %["R0"], %["R0"] \n\t" \ "vpshufb %["R0"], %[lut1], %["R2"] \n\t" \ "vpaddb %["R1"], %["R2"], %["R0"] \n\t" // Generate a block of inline assembly that stores the given register R0 at an // offset set by the given round. #define STOR(R0, ROUND) \ "vmovdqu %["R0"], ("#ROUND" * 16)(%[dst]) \n\t" // Generate a block of inline assembly that generates a single self-contained // encoder round: fetch the data, process it, and store the result. Then update // the source and destination pointers. #define ROUND() \ LOAD("a", 0) \ SHUF("a", "b", "c") \ TRAN("a", "b", "c") \ STOR("a", 0) \ "add $12, %[src] \n\t" \ "add $16, %[dst] \n\t" // Define a macro that initiates a three-way interleaved encoding round by // preloading registers a, b and c from memory. // The register graph shows which registers are in use during each step, and // is a visual aid for choosing registers for that step. Symbol index: // // + indicates that a register is loaded by that step. // | indicates that a register is in use and must not be touched. // - indicates that a register is decommissioned by that step. // x indicates that a register is used as a temporary by that step. // V indicates that a register is an input or output to the macro. // #define ROUND_3_INIT() /* a b c d e f */ \ LOAD("a", 0) /* + */ \ SHUF("a", "d", "e") /* | + x */ \ LOAD("b", 1) /* | + | */ \ TRAN("a", "d", "e") /* | | - x */ \ LOAD("c", 2) /* V V V */ // Define a macro that translates, shuffles and stores the input registers A, B // and C, and preloads registers D, E and F for the next round. // This macro can be arbitrarily daisy-chained by feeding output registers D, E // and F back into the next round as input registers A, B and C. The macro // carefully interleaves memory operations with data operations for optimal // pipelined performance. #define ROUND_3(ROUND, A,B,C,D,E,F) /* A B C D E F */ \ LOAD(D, (ROUND + 3)) /* V V V + */ \ SHUF(B, E, F) /* | | | | + x */ \ STOR(A, (ROUND + 0)) /* - | | | | */ \ TRAN(B, E, F) /* | | | - x */ \ LOAD(E, (ROUND + 4)) /* | | | + */ \ SHUF(C, A, F) /* + | | | | x */ \ STOR(B, (ROUND + 1)) /* | - | | | */ \ TRAN(C, A, F) /* - | | | x */ \ LOAD(F, (ROUND + 5)) /* | | | + */ \ SHUF(D, A, B) /* + x | | | | */ \ STOR(C, (ROUND + 2)) /* | - | | | */ \ TRAN(D, A, B) /* - x V V V */ // Define a macro that terminates a ROUND_3 macro by taking pre-loaded // registers D, E and F, and translating, shuffling and storing them. #define ROUND_3_END(ROUND, A,B,C,D,E,F) /* A B C D E F */ \ SHUF(E, A, B) /* + x V V V */ \ STOR(D, (ROUND + 3)) /* | - | | */ \ TRAN(E, A, B) /* - x | | */ \ SHUF(F, C, D) /* + x | | */ \ STOR(E, (ROUND + 4)) /* | - | */ \ TRAN(F, C, D) /* - x | */ \ STOR(F, (ROUND + 5)) /* - */ // Define a type A round. Inputs are a, b, and c, outputs are d, e, and f. #define ROUND_3_A(ROUND) \ ROUND_3(ROUND, "a", "b", "c", "d", "e", "f") // Define a type B round. Inputs and outputs are swapped with regard to type A. #define ROUND_3_B(ROUND) \ ROUND_3(ROUND, "d", "e", "f", "a", "b", "c") // Terminating macro for a type A round. #define ROUND_3_A_LAST(ROUND) \ ROUND_3_A(ROUND) \ ROUND_3_END(ROUND, "a", "b", "c", "d", "e", "f") // Terminating macro for a type B round. #define ROUND_3_B_LAST(ROUND) \ ROUND_3_B(ROUND) \ ROUND_3_END(ROUND, "d", "e", "f", "a", "b", "c") // Suppress clang's warning that the literal string in the asm statement is // overlong (longer than the ISO-mandated minimum size of 4095 bytes for C99 // compilers). It may be true, but the goal here is not C99 portability. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Woverlength-strings" static inline void enc_loop_avx (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { // For a clearer explanation of the algorithm used by this function, // please refer to the plain (not inline assembly) implementation. This // function follows the same basic logic. if (*slen < 16) { return; } // Process blocks of 12 bytes at a time. Input is read in blocks of 16 // bytes, so "reserve" four bytes from the input buffer to ensure that // we never read beyond the end of the input buffer. size_t rounds = (*slen - 4) / 12; *slen -= rounds * 12; // 12 bytes consumed per round *olen += rounds * 16; // 16 bytes produced per round // Number of times to go through the 36x loop. size_t loops = rounds / 36; // Number of rounds remaining after the 36x loop. rounds %= 36; // Lookup tables. const __m128i lut0 = _mm_set_epi8( 10, 11, 9, 10, 7, 8, 6, 7, 4, 5, 3, 4, 1, 2, 0, 1); const __m128i lut1 = _mm_setr_epi8( 65, 71, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -19, -16, 0, 0); // Temporary registers. __m128i a, b, c, d, e, f; __asm__ volatile ( // If there are 36 rounds or more, enter a 36x unrolled loop of // interleaved encoding rounds. The rounds interleave memory // operations (load/store) with data operations (table lookups, // etc) to maximize pipeline throughput. " test %[loops], %[loops] \n\t" " jz 18f \n\t" " jmp 36f \n\t" " \n\t" ".balign 64 \n\t" "36: " ROUND_3_INIT() " " ROUND_3_A( 0) " " ROUND_3_B( 3) " " ROUND_3_A( 6) " " ROUND_3_B( 9) " " ROUND_3_A(12) " " ROUND_3_B(15) " " ROUND_3_A(18) " " ROUND_3_B(21) " " ROUND_3_A(24) " " ROUND_3_B(27) " " ROUND_3_A_LAST(30) " add $(12 * 36), %[src] \n\t" " add $(16 * 36), %[dst] \n\t" " dec %[loops] \n\t" " jnz 36b \n\t" // Enter an 18x unrolled loop for rounds of 18 or more. "18: cmp $18, %[rounds] \n\t" " jl 9f \n\t" " " ROUND_3_INIT() " " ROUND_3_A(0) " " ROUND_3_B(3) " " ROUND_3_A(6) " " ROUND_3_B(9) " " ROUND_3_A_LAST(12) " sub $18, %[rounds] \n\t" " add $(12 * 18), %[src] \n\t" " add $(16 * 18), %[dst] \n\t" // Enter a 9x unrolled loop for rounds of 9 or more. "9: cmp $9, %[rounds] \n\t" " jl 6f \n\t" " " ROUND_3_INIT() " " ROUND_3_A(0) " " ROUND_3_B_LAST(3) " sub $9, %[rounds] \n\t" " add $(12 * 9), %[src] \n\t" " add $(16 * 9), %[dst] \n\t" // Enter a 6x unrolled loop for rounds of 6 or more. "6: cmp $6, %[rounds] \n\t" " jl 55f \n\t" " " ROUND_3_INIT() " " ROUND_3_A_LAST(0) " sub $6, %[rounds] \n\t" " add $(12 * 6), %[src] \n\t" " add $(16 * 6), %[dst] \n\t" // Dispatch the remaining rounds 0..5. "55: cmp $3, %[rounds] \n\t" " jg 45f \n\t" " je 3f \n\t" " cmp $1, %[rounds] \n\t" " jg 2f \n\t" " je 1f \n\t" " jmp 0f \n\t" "45: cmp $4, %[rounds] \n\t" " je 4f \n\t" // Block of non-interlaced encoding rounds, which can each // individually be jumped to. Rounds fall through to the next. "5: " ROUND() "4: " ROUND() "3: " ROUND() "2: " ROUND() "1: " ROUND() "0: \n\t" // Outputs (modified). : [rounds] "+r" (rounds), [loops] "+r" (loops), [src] "+r" (*s), [dst] "+r" (*o), [a] "=&x" (a), [b] "=&x" (b), [c] "=&x" (c), [d] "=&x" (d), [e] "=&x" (e), [f] "=&x" (f) // Inputs (not modified). : [lut0] "x" (lut0), [lut1] "x" (lut1), [msk0] "x" (_mm_set1_epi32(0x0FC0FC00)), [msk1] "x" (_mm_set1_epi32(0x04000040)), [msk2] "x" (_mm_set1_epi32(0x003F03F0)), [msk3] "x" (_mm_set1_epi32(0x01000010)), [n51] "x" (_mm_set1_epi8(51)), [n25] "x" (_mm_set1_epi8(25)) // Clobbers. : "cc", "memory" ); } #pragma GCC diagnostic pop kitty-0.41.1/3rdparty/base64/lib/arch/avx2/0000775000175000017510000000000014773370543017506 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/lib/arch/avx2/codec.c0000664000175000017510000000206714773370543020734 0ustar nileshnilesh#include #include #include #include "../../../include/libbase64.h" #include "../../tables/tables.h" #include "../../codecs.h" #include "config.h" #include "../../env.h" #if HAVE_AVX2 #include // Only enable inline assembly on supported compilers and on 64-bit CPUs. #ifndef BASE64_AVX2_USE_ASM # if (defined(__GNUC__) || defined(__clang__)) && BASE64_WORDSIZE == 64 # define BASE64_AVX2_USE_ASM 1 # else # define BASE64_AVX2_USE_ASM 0 # endif #endif #include "dec_reshuffle.c" #include "dec_loop.c" #if BASE64_AVX2_USE_ASM # include "enc_loop_asm.c" #else # include "enc_translate.c" # include "enc_reshuffle.c" # include "enc_loop.c" #endif #endif // HAVE_AVX2 BASE64_ENC_FUNCTION(avx2) { #if HAVE_AVX2 #include "../generic/enc_head.c" enc_loop_avx2(&s, &slen, &o, &olen); #include "../generic/enc_tail.c" #else BASE64_ENC_STUB #endif } BASE64_DEC_FUNCTION(avx2) { #if HAVE_AVX2 #include "../generic/dec_head.c" dec_loop_avx2(&s, &slen, &o, &olen); #include "../generic/dec_tail.c" #else BASE64_DEC_STUB #endif } kitty-0.41.1/3rdparty/base64/lib/arch/avx2/dec_loop.c0000664000175000017510000000622014773370543021436 0ustar nileshnileshstatic inline int dec_loop_avx2_inner (const uint8_t **s, uint8_t **o, size_t *rounds) { const __m256i lut_lo = _mm256_setr_epi8( 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x13, 0x1A, 0x1B, 0x1B, 0x1B, 0x1A, 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x13, 0x1A, 0x1B, 0x1B, 0x1B, 0x1A); const __m256i lut_hi = _mm256_setr_epi8( 0x10, 0x10, 0x01, 0x02, 0x04, 0x08, 0x04, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x01, 0x02, 0x04, 0x08, 0x04, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10); const __m256i lut_roll = _mm256_setr_epi8( 0, 16, 19, 4, -65, -65, -71, -71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 19, 4, -65, -65, -71, -71, 0, 0, 0, 0, 0, 0, 0, 0); const __m256i mask_2F = _mm256_set1_epi8(0x2F); // Load input: __m256i str = _mm256_loadu_si256((__m256i *) *s); // See the SSSE3 decoder for an explanation of the algorithm. const __m256i hi_nibbles = _mm256_and_si256(_mm256_srli_epi32(str, 4), mask_2F); const __m256i lo_nibbles = _mm256_and_si256(str, mask_2F); const __m256i hi = _mm256_shuffle_epi8(lut_hi, hi_nibbles); const __m256i lo = _mm256_shuffle_epi8(lut_lo, lo_nibbles); if (!_mm256_testz_si256(lo, hi)) { return 0; } const __m256i eq_2F = _mm256_cmpeq_epi8(str, mask_2F); const __m256i roll = _mm256_shuffle_epi8(lut_roll, _mm256_add_epi8(eq_2F, hi_nibbles)); // Now simply add the delta values to the input: str = _mm256_add_epi8(str, roll); // Reshuffle the input to packed 12-byte output format: str = dec_reshuffle(str); // Store the output: _mm256_storeu_si256((__m256i *) *o, str); *s += 32; *o += 24; *rounds -= 1; return 1; } static inline void dec_loop_avx2 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { if (*slen < 45) { return; } // Process blocks of 32 bytes per round. Because 8 extra zero bytes are // written after the output, ensure that there will be at least 13 // bytes of input data left to cover the gap. (11 data bytes and up to // two end-of-string markers.) size_t rounds = (*slen - 13) / 32; *slen -= rounds * 32; // 32 bytes consumed per round *olen += rounds * 24; // 24 bytes produced per round do { if (rounds >= 8) { if (dec_loop_avx2_inner(s, o, &rounds) && dec_loop_avx2_inner(s, o, &rounds) && dec_loop_avx2_inner(s, o, &rounds) && dec_loop_avx2_inner(s, o, &rounds) && dec_loop_avx2_inner(s, o, &rounds) && dec_loop_avx2_inner(s, o, &rounds) && dec_loop_avx2_inner(s, o, &rounds) && dec_loop_avx2_inner(s, o, &rounds)) { continue; } break; } if (rounds >= 4) { if (dec_loop_avx2_inner(s, o, &rounds) && dec_loop_avx2_inner(s, o, &rounds) && dec_loop_avx2_inner(s, o, &rounds) && dec_loop_avx2_inner(s, o, &rounds)) { continue; } break; } if (rounds >= 2) { if (dec_loop_avx2_inner(s, o, &rounds) && dec_loop_avx2_inner(s, o, &rounds)) { continue; } break; } dec_loop_avx2_inner(s, o, &rounds); break; } while (rounds > 0); // Adjust for any rounds that were skipped: *slen += rounds * 32; *olen -= rounds * 24; } kitty-0.41.1/3rdparty/base64/lib/arch/avx2/dec_reshuffle.c0000664000175000017510000000241314773370543022450 0ustar nileshnileshstatic inline __m256i dec_reshuffle (const __m256i in) { // in, lower lane, bits, upper case are most significant bits, lower // case are least significant bits: // 00llllll 00kkkkLL 00jjKKKK 00JJJJJJ // 00iiiiii 00hhhhII 00ggHHHH 00GGGGGG // 00ffffff 00eeeeFF 00ddEEEE 00DDDDDD // 00cccccc 00bbbbCC 00aaBBBB 00AAAAAA const __m256i merge_ab_and_bc = _mm256_maddubs_epi16(in, _mm256_set1_epi32(0x01400140)); // 0000kkkk LLllllll 0000JJJJ JJjjKKKK // 0000hhhh IIiiiiii 0000GGGG GGggHHHH // 0000eeee FFffffff 0000DDDD DDddEEEE // 0000bbbb CCcccccc 0000AAAA AAaaBBBB __m256i out = _mm256_madd_epi16(merge_ab_and_bc, _mm256_set1_epi32(0x00011000)); // 00000000 JJJJJJjj KKKKkkkk LLllllll // 00000000 GGGGGGgg HHHHhhhh IIiiiiii // 00000000 DDDDDDdd EEEEeeee FFffffff // 00000000 AAAAAAaa BBBBbbbb CCcccccc // Pack bytes together in each lane: out = _mm256_shuffle_epi8(out, _mm256_setr_epi8( 2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, -1, -1, -1, -1, 2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, -1, -1, -1, -1)); // 00000000 00000000 00000000 00000000 // LLllllll KKKKkkkk JJJJJJjj IIiiiiii // HHHHhhhh GGGGGGgg FFffffff EEEEeeee // DDDDDDdd CCcccccc BBBBbbbb AAAAAAaa // Pack lanes: return _mm256_permutevar8x32_epi32(out, _mm256_setr_epi32(0, 1, 2, 4, 5, 6, -1, -1)); } kitty-0.41.1/3rdparty/base64/lib/arch/avx2/enc_loop.c0000664000175000017510000000433314773370543021453 0ustar nileshnileshstatic inline void enc_loop_avx2_inner_first (const uint8_t **s, uint8_t **o) { // First load is done at s - 0 to not get a segfault: __m256i src = _mm256_loadu_si256((__m256i *) *s); // Shift by 4 bytes, as required by enc_reshuffle: src = _mm256_permutevar8x32_epi32(src, _mm256_setr_epi32(0, 0, 1, 2, 3, 4, 5, 6)); // Reshuffle, translate, store: src = enc_reshuffle(src); src = enc_translate(src); _mm256_storeu_si256((__m256i *) *o, src); // Subsequent loads will be done at s - 4, set pointer for next round: *s += 20; *o += 32; } static inline void enc_loop_avx2_inner (const uint8_t **s, uint8_t **o) { // Load input: __m256i src = _mm256_loadu_si256((__m256i *) *s); // Reshuffle, translate, store: src = enc_reshuffle(src); src = enc_translate(src); _mm256_storeu_si256((__m256i *) *o, src); *s += 24; *o += 32; } static inline void enc_loop_avx2 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { if (*slen < 32) { return; } // Process blocks of 24 bytes at a time. Because blocks are loaded 32 // bytes at a time an offset of -4, ensure that there will be at least // 4 remaining bytes after the last round, so that the final read will // not pass beyond the bounds of the input buffer: size_t rounds = (*slen - 4) / 24; *slen -= rounds * 24; // 24 bytes consumed per round *olen += rounds * 32; // 32 bytes produced per round // The first loop iteration requires special handling to ensure that // the read, which is done at an offset, does not underflow the buffer: enc_loop_avx2_inner_first(s, o); rounds--; while (rounds > 0) { if (rounds >= 8) { enc_loop_avx2_inner(s, o); enc_loop_avx2_inner(s, o); enc_loop_avx2_inner(s, o); enc_loop_avx2_inner(s, o); enc_loop_avx2_inner(s, o); enc_loop_avx2_inner(s, o); enc_loop_avx2_inner(s, o); enc_loop_avx2_inner(s, o); rounds -= 8; continue; } if (rounds >= 4) { enc_loop_avx2_inner(s, o); enc_loop_avx2_inner(s, o); enc_loop_avx2_inner(s, o); enc_loop_avx2_inner(s, o); rounds -= 4; continue; } if (rounds >= 2) { enc_loop_avx2_inner(s, o); enc_loop_avx2_inner(s, o); rounds -= 2; continue; } enc_loop_avx2_inner(s, o); break; } // Add the offset back: *s += 4; } kitty-0.41.1/3rdparty/base64/lib/arch/avx2/enc_loop_asm.c0000664000175000017510000002432514773370543022316 0ustar nileshnilesh// Apologies in advance for combining the preprocessor with inline assembly, // two notoriously gnarly parts of C, but it was necessary to avoid a lot of // code repetition. The preprocessor is used to template large sections of // inline assembly that differ only in the registers used. If the code was // written out by hand, it would become very large and hard to audit. // Generate a block of inline assembly that loads register R0 from memory. The // offset at which the register is loaded is set by the given round and a // constant offset. #define LOAD(R0, ROUND, OFFSET) \ "vlddqu ("#ROUND" * 24 + "#OFFSET")(%[src]), %["R0"] \n\t" // Generate a block of inline assembly that deinterleaves and shuffles register // R0 using preloaded constants. Outputs in R0 and R1. #define SHUF(R0, R1, R2) \ "vpshufb %[lut0], %["R0"], %["R1"] \n\t" \ "vpand %["R1"], %[msk0], %["R2"] \n\t" \ "vpand %["R1"], %[msk2], %["R1"] \n\t" \ "vpmulhuw %["R2"], %[msk1], %["R2"] \n\t" \ "vpmullw %["R1"], %[msk3], %["R1"] \n\t" \ "vpor %["R1"], %["R2"], %["R1"] \n\t" // Generate a block of inline assembly that takes R0 and R1 and translates // their contents to the base64 alphabet, using preloaded constants. #define TRAN(R0, R1, R2) \ "vpsubusb %[n51], %["R1"], %["R0"] \n\t" \ "vpcmpgtb %[n25], %["R1"], %["R2"] \n\t" \ "vpsubb %["R2"], %["R0"], %["R0"] \n\t" \ "vpshufb %["R0"], %[lut1], %["R2"] \n\t" \ "vpaddb %["R1"], %["R2"], %["R0"] \n\t" // Generate a block of inline assembly that stores the given register R0 at an // offset set by the given round. #define STOR(R0, ROUND) \ "vmovdqu %["R0"], ("#ROUND" * 32)(%[dst]) \n\t" // Generate a block of inline assembly that generates a single self-contained // encoder round: fetch the data, process it, and store the result. Then update // the source and destination pointers. #define ROUND() \ LOAD("a", 0, -4) \ SHUF("a", "b", "c") \ TRAN("a", "b", "c") \ STOR("a", 0) \ "add $24, %[src] \n\t" \ "add $32, %[dst] \n\t" // Define a macro that initiates a three-way interleaved encoding round by // preloading registers a, b and c from memory. // The register graph shows which registers are in use during each step, and // is a visual aid for choosing registers for that step. Symbol index: // // + indicates that a register is loaded by that step. // | indicates that a register is in use and must not be touched. // - indicates that a register is decommissioned by that step. // x indicates that a register is used as a temporary by that step. // V indicates that a register is an input or output to the macro. // #define ROUND_3_INIT() /* a b c d e f */ \ LOAD("a", 0, -4) /* + */ \ SHUF("a", "d", "e") /* | + x */ \ LOAD("b", 1, -4) /* | + | */ \ TRAN("a", "d", "e") /* | | - x */ \ LOAD("c", 2, -4) /* V V V */ // Define a macro that translates, shuffles and stores the input registers A, B // and C, and preloads registers D, E and F for the next round. // This macro can be arbitrarily daisy-chained by feeding output registers D, E // and F back into the next round as input registers A, B and C. The macro // carefully interleaves memory operations with data operations for optimal // pipelined performance. #define ROUND_3(ROUND, A,B,C,D,E,F) /* A B C D E F */ \ LOAD(D, (ROUND + 3), -4) /* V V V + */ \ SHUF(B, E, F) /* | | | | + x */ \ STOR(A, (ROUND + 0)) /* - | | | | */ \ TRAN(B, E, F) /* | | | - x */ \ LOAD(E, (ROUND + 4), -4) /* | | | + */ \ SHUF(C, A, F) /* + | | | | x */ \ STOR(B, (ROUND + 1)) /* | - | | | */ \ TRAN(C, A, F) /* - | | | x */ \ LOAD(F, (ROUND + 5), -4) /* | | | + */ \ SHUF(D, A, B) /* + x | | | | */ \ STOR(C, (ROUND + 2)) /* | - | | | */ \ TRAN(D, A, B) /* - x V V V */ // Define a macro that terminates a ROUND_3 macro by taking pre-loaded // registers D, E and F, and translating, shuffling and storing them. #define ROUND_3_END(ROUND, A,B,C,D,E,F) /* A B C D E F */ \ SHUF(E, A, B) /* + x V V V */ \ STOR(D, (ROUND + 3)) /* | - | | */ \ TRAN(E, A, B) /* - x | | */ \ SHUF(F, C, D) /* + x | | */ \ STOR(E, (ROUND + 4)) /* | - | */ \ TRAN(F, C, D) /* - x | */ \ STOR(F, (ROUND + 5)) /* - */ // Define a type A round. Inputs are a, b, and c, outputs are d, e, and f. #define ROUND_3_A(ROUND) \ ROUND_3(ROUND, "a", "b", "c", "d", "e", "f") // Define a type B round. Inputs and outputs are swapped with regard to type A. #define ROUND_3_B(ROUND) \ ROUND_3(ROUND, "d", "e", "f", "a", "b", "c") // Terminating macro for a type A round. #define ROUND_3_A_LAST(ROUND) \ ROUND_3_A(ROUND) \ ROUND_3_END(ROUND, "a", "b", "c", "d", "e", "f") // Terminating macro for a type B round. #define ROUND_3_B_LAST(ROUND) \ ROUND_3_B(ROUND) \ ROUND_3_END(ROUND, "d", "e", "f", "a", "b", "c") // Suppress clang's warning that the literal string in the asm statement is // overlong (longer than the ISO-mandated minimum size of 4095 bytes for C99 // compilers). It may be true, but the goal here is not C99 portability. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Woverlength-strings" static inline void enc_loop_avx2 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { // For a clearer explanation of the algorithm used by this function, // please refer to the plain (not inline assembly) implementation. This // function follows the same basic logic. if (*slen < 32) { return; } // Process blocks of 24 bytes at a time. Because blocks are loaded 32 // bytes at a time an offset of -4, ensure that there will be at least // 4 remaining bytes after the last round, so that the final read will // not pass beyond the bounds of the input buffer. size_t rounds = (*slen - 4) / 24; *slen -= rounds * 24; // 24 bytes consumed per round *olen += rounds * 32; // 32 bytes produced per round // Pre-decrement the number of rounds to get the number of rounds // *after* the first round, which is handled as a special case. rounds--; // Number of times to go through the 36x loop. size_t loops = rounds / 36; // Number of rounds remaining after the 36x loop. rounds %= 36; // Lookup tables. const __m256i lut0 = _mm256_set_epi8( 10, 11, 9, 10, 7, 8, 6, 7, 4, 5, 3, 4, 1, 2, 0, 1, 14, 15, 13, 14, 11, 12, 10, 11, 8, 9, 7, 8, 5, 6, 4, 5); const __m256i lut1 = _mm256_setr_epi8( 65, 71, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -19, -16, 0, 0, 65, 71, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -19, -16, 0, 0); // Temporary registers. __m256i a, b, c, d, e; // Temporary register f doubles as the shift mask for the first round. __m256i f = _mm256_setr_epi32(0, 0, 1, 2, 3, 4, 5, 6); __asm__ volatile ( // The first loop iteration requires special handling to ensure // that the read, which is normally done at an offset of -4, // does not underflow the buffer. Load the buffer at an offset // of 0 and permute the input to achieve the same effect. LOAD("a", 0, 0) "vpermd %[a], %[f], %[a] \n\t" // Perform the standard shuffling and translation steps. SHUF("a", "b", "c") TRAN("a", "b", "c") // Store the result and increment the source and dest pointers. "vmovdqu %[a], (%[dst]) \n\t" "add $24, %[src] \n\t" "add $32, %[dst] \n\t" // If there are 36 rounds or more, enter a 36x unrolled loop of // interleaved encoding rounds. The rounds interleave memory // operations (load/store) with data operations (table lookups, // etc) to maximize pipeline throughput. " test %[loops], %[loops] \n\t" " jz 18f \n\t" " jmp 36f \n\t" " \n\t" ".balign 64 \n\t" "36: " ROUND_3_INIT() " " ROUND_3_A( 0) " " ROUND_3_B( 3) " " ROUND_3_A( 6) " " ROUND_3_B( 9) " " ROUND_3_A(12) " " ROUND_3_B(15) " " ROUND_3_A(18) " " ROUND_3_B(21) " " ROUND_3_A(24) " " ROUND_3_B(27) " " ROUND_3_A_LAST(30) " add $(24 * 36), %[src] \n\t" " add $(32 * 36), %[dst] \n\t" " dec %[loops] \n\t" " jnz 36b \n\t" // Enter an 18x unrolled loop for rounds of 18 or more. "18: cmp $18, %[rounds] \n\t" " jl 9f \n\t" " " ROUND_3_INIT() " " ROUND_3_A(0) " " ROUND_3_B(3) " " ROUND_3_A(6) " " ROUND_3_B(9) " " ROUND_3_A_LAST(12) " sub $18, %[rounds] \n\t" " add $(24 * 18), %[src] \n\t" " add $(32 * 18), %[dst] \n\t" // Enter a 9x unrolled loop for rounds of 9 or more. "9: cmp $9, %[rounds] \n\t" " jl 6f \n\t" " " ROUND_3_INIT() " " ROUND_3_A(0) " " ROUND_3_B_LAST(3) " sub $9, %[rounds] \n\t" " add $(24 * 9), %[src] \n\t" " add $(32 * 9), %[dst] \n\t" // Enter a 6x unrolled loop for rounds of 6 or more. "6: cmp $6, %[rounds] \n\t" " jl 55f \n\t" " " ROUND_3_INIT() " " ROUND_3_A_LAST(0) " sub $6, %[rounds] \n\t" " add $(24 * 6), %[src] \n\t" " add $(32 * 6), %[dst] \n\t" // Dispatch the remaining rounds 0..5. "55: cmp $3, %[rounds] \n\t" " jg 45f \n\t" " je 3f \n\t" " cmp $1, %[rounds] \n\t" " jg 2f \n\t" " je 1f \n\t" " jmp 0f \n\t" "45: cmp $4, %[rounds] \n\t" " je 4f \n\t" // Block of non-interlaced encoding rounds, which can each // individually be jumped to. Rounds fall through to the next. "5: " ROUND() "4: " ROUND() "3: " ROUND() "2: " ROUND() "1: " ROUND() "0: \n\t" // Outputs (modified). : [rounds] "+r" (rounds), [loops] "+r" (loops), [src] "+r" (*s), [dst] "+r" (*o), [a] "=&x" (a), [b] "=&x" (b), [c] "=&x" (c), [d] "=&x" (d), [e] "=&x" (e), [f] "+x" (f) // Inputs (not modified). : [lut0] "x" (lut0), [lut1] "x" (lut1), [msk0] "x" (_mm256_set1_epi32(0x0FC0FC00)), [msk1] "x" (_mm256_set1_epi32(0x04000040)), [msk2] "x" (_mm256_set1_epi32(0x003F03F0)), [msk3] "x" (_mm256_set1_epi32(0x01000010)), [n51] "x" (_mm256_set1_epi8(51)), [n25] "x" (_mm256_set1_epi8(25)) // Clobbers. : "cc", "memory" ); } #pragma GCC diagnostic pop kitty-0.41.1/3rdparty/base64/lib/arch/avx2/enc_reshuffle.c0000664000175000017510000000521514773370543022465 0ustar nileshnileshstatic inline __m256i enc_reshuffle (const __m256i input) { // Translation of the SSSE3 reshuffling algorithm to AVX2. This one // works with shifted (4 bytes) input in order to be able to work // efficiently in the two 128-bit lanes. // Input, bytes MSB to LSB: // 0 0 0 0 x w v u t s r q p o n m // l k j i h g f e d c b a 0 0 0 0 const __m256i in = _mm256_shuffle_epi8(input, _mm256_set_epi8( 10, 11, 9, 10, 7, 8, 6, 7, 4, 5, 3, 4, 1, 2, 0, 1, 14, 15, 13, 14, 11, 12, 10, 11, 8, 9, 7, 8, 5, 6, 4, 5)); // in, bytes MSB to LSB: // w x v w // t u s t // q r p q // n o m n // k l j k // h i g h // e f d e // b c a b const __m256i t0 = _mm256_and_si256(in, _mm256_set1_epi32(0x0FC0FC00)); // bits, upper case are most significant bits, lower case are least // significant bits. // 0000wwww XX000000 VVVVVV00 00000000 // 0000tttt UU000000 SSSSSS00 00000000 // 0000qqqq RR000000 PPPPPP00 00000000 // 0000nnnn OO000000 MMMMMM00 00000000 // 0000kkkk LL000000 JJJJJJ00 00000000 // 0000hhhh II000000 GGGGGG00 00000000 // 0000eeee FF000000 DDDDDD00 00000000 // 0000bbbb CC000000 AAAAAA00 00000000 const __m256i t1 = _mm256_mulhi_epu16(t0, _mm256_set1_epi32(0x04000040)); // 00000000 00wwwwXX 00000000 00VVVVVV // 00000000 00ttttUU 00000000 00SSSSSS // 00000000 00qqqqRR 00000000 00PPPPPP // 00000000 00nnnnOO 00000000 00MMMMMM // 00000000 00kkkkLL 00000000 00JJJJJJ // 00000000 00hhhhII 00000000 00GGGGGG // 00000000 00eeeeFF 00000000 00DDDDDD // 00000000 00bbbbCC 00000000 00AAAAAA const __m256i t2 = _mm256_and_si256(in, _mm256_set1_epi32(0x003F03F0)); // 00000000 00xxxxxx 000000vv WWWW0000 // 00000000 00uuuuuu 000000ss TTTT0000 // 00000000 00rrrrrr 000000pp QQQQ0000 // 00000000 00oooooo 000000mm NNNN0000 // 00000000 00llllll 000000jj KKKK0000 // 00000000 00iiiiii 000000gg HHHH0000 // 00000000 00ffffff 000000dd EEEE0000 // 00000000 00cccccc 000000aa BBBB0000 const __m256i t3 = _mm256_mullo_epi16(t2, _mm256_set1_epi32(0x01000010)); // 00xxxxxx 00000000 00vvWWWW 00000000 // 00uuuuuu 00000000 00ssTTTT 00000000 // 00rrrrrr 00000000 00ppQQQQ 00000000 // 00oooooo 00000000 00mmNNNN 00000000 // 00llllll 00000000 00jjKKKK 00000000 // 00iiiiii 00000000 00ggHHHH 00000000 // 00ffffff 00000000 00ddEEEE 00000000 // 00cccccc 00000000 00aaBBBB 00000000 return _mm256_or_si256(t1, t3); // 00xxxxxx 00wwwwXX 00vvWWWW 00VVVVVV // 00uuuuuu 00ttttUU 00ssTTTT 00SSSSSS // 00rrrrrr 00qqqqRR 00ppQQQQ 00PPPPPP // 00oooooo 00nnnnOO 00mmNNNN 00MMMMMM // 00llllll 00kkkkLL 00jjKKKK 00JJJJJJ // 00iiiiii 00hhhhII 00ggHHHH 00GGGGGG // 00ffffff 00eeeeFF 00ddEEEE 00DDDDDD // 00cccccc 00bbbbCC 00aaBBBB 00AAAAAA } kitty-0.41.1/3rdparty/base64/lib/arch/avx2/enc_translate.c0000664000175000017510000000232614773370543022477 0ustar nileshnileshstatic inline __m256i enc_translate (const __m256i in) { // A lookup table containing the absolute offsets for all ranges: const __m256i lut = _mm256_setr_epi8( 65, 71, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -19, -16, 0, 0, 65, 71, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -19, -16, 0, 0); // Translate values 0..63 to the Base64 alphabet. There are five sets: // # From To Abs Index Characters // 0 [0..25] [65..90] +65 0 ABCDEFGHIJKLMNOPQRSTUVWXYZ // 1 [26..51] [97..122] +71 1 abcdefghijklmnopqrstuvwxyz // 2 [52..61] [48..57] -4 [2..11] 0123456789 // 3 [62] [43] -19 12 + // 4 [63] [47] -16 13 / // Create LUT indices from the input. The index for range #0 is right, // others are 1 less than expected: __m256i indices = _mm256_subs_epu8(in, _mm256_set1_epi8(51)); // mask is 0xFF (-1) for range #[1..4] and 0x00 for range #0: const __m256i mask = _mm256_cmpgt_epi8(in, _mm256_set1_epi8(25)); // Subtract -1, so add 1 to indices for range #[1..4]. All indices are // now correct: indices = _mm256_sub_epi8(indices, mask); // Add offsets to input values: return _mm256_add_epi8(in, _mm256_shuffle_epi8(lut, indices)); } kitty-0.41.1/3rdparty/base64/lib/arch/avx512/0000775000175000017510000000000014773370543017654 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/lib/arch/avx512/codec.c0000664000175000017510000000146414773370543021102 0ustar nileshnilesh#include #include #include #include "../../../include/libbase64.h" #include "../../tables/tables.h" #include "../../codecs.h" #include "config.h" #include "../../env.h" #if HAVE_AVX512 #include #include "../avx2/dec_reshuffle.c" #include "../avx2/dec_loop.c" #include "enc_reshuffle_translate.c" #include "enc_loop.c" #endif // HAVE_AVX512 BASE64_ENC_FUNCTION(avx512) { #if HAVE_AVX512 #include "../generic/enc_head.c" enc_loop_avx512(&s, &slen, &o, &olen); #include "../generic/enc_tail.c" #else BASE64_ENC_STUB #endif } // Reuse AVX2 decoding. Not supporting AVX512 at present BASE64_DEC_FUNCTION(avx512) { #if HAVE_AVX512 #include "../generic/dec_head.c" dec_loop_avx2(&s, &slen, &o, &olen); #include "../generic/dec_tail.c" #else BASE64_DEC_STUB #endif } kitty-0.41.1/3rdparty/base64/lib/arch/avx512/enc_loop.c0000664000175000017510000000272514773370543021624 0ustar nileshnileshstatic inline void enc_loop_avx512_inner (const uint8_t **s, uint8_t **o) { // Load input. __m512i src = _mm512_loadu_si512((__m512i *) *s); // Reshuffle, translate, store. src = enc_reshuffle_translate(src); _mm512_storeu_si512((__m512i *) *o, src); *s += 48; *o += 64; } static inline void enc_loop_avx512 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { if (*slen < 64) { return; } // Process blocks of 48 bytes at a time. Because blocks are loaded 64 // bytes at a time, ensure that there will be at least 24 remaining // bytes after the last round, so that the final read will not pass // beyond the bounds of the input buffer. size_t rounds = (*slen - 24) / 48; *slen -= rounds * 48; // 48 bytes consumed per round *olen += rounds * 64; // 64 bytes produced per round while (rounds > 0) { if (rounds >= 8) { enc_loop_avx512_inner(s, o); enc_loop_avx512_inner(s, o); enc_loop_avx512_inner(s, o); enc_loop_avx512_inner(s, o); enc_loop_avx512_inner(s, o); enc_loop_avx512_inner(s, o); enc_loop_avx512_inner(s, o); enc_loop_avx512_inner(s, o); rounds -= 8; continue; } if (rounds >= 4) { enc_loop_avx512_inner(s, o); enc_loop_avx512_inner(s, o); enc_loop_avx512_inner(s, o); enc_loop_avx512_inner(s, o); rounds -= 4; continue; } if (rounds >= 2) { enc_loop_avx512_inner(s, o); enc_loop_avx512_inner(s, o); rounds -= 2; continue; } enc_loop_avx512_inner(s, o); break; } } kitty-0.41.1/3rdparty/base64/lib/arch/avx512/enc_reshuffle_translate.c0000664000175000017510000000433114773370543024706 0ustar nileshnilesh// AVX512 algorithm is based on permutevar and multishift. The code is based on // https://github.com/WojciechMula/base64simd which is under BSD-2 license. static inline __m512i enc_reshuffle_translate (const __m512i input) { // 32-bit input // [ 0 0 0 0 0 0 0 0|c1 c0 d5 d4 d3 d2 d1 d0| // b3 b2 b1 b0 c5 c4 c3 c2|a5 a4 a3 a2 a1 a0 b5 b4] // output order [1, 2, 0, 1] // [b3 b2 b1 b0 c5 c4 c3 c2|c1 c0 d5 d4 d3 d2 d1 d0| // a5 a4 a3 a2 a1 a0 b5 b4|b3 b2 b1 b0 c3 c2 c1 c0] const __m512i shuffle_input = _mm512_setr_epi32(0x01020001, 0x04050304, 0x07080607, 0x0a0b090a, 0x0d0e0c0d, 0x10110f10, 0x13141213, 0x16171516, 0x191a1819, 0x1c1d1b1c, 0x1f201e1f, 0x22232122, 0x25262425, 0x28292728, 0x2b2c2a2b, 0x2e2f2d2e); // Reorder bytes // [b3 b2 b1 b0 c5 c4 c3 c2|c1 c0 d5 d4 d3 d2 d1 d0| // a5 a4 a3 a2 a1 a0 b5 b4|b3 b2 b1 b0 c3 c2 c1 c0] const __m512i in = _mm512_permutexvar_epi8(shuffle_input, input); // After multishift a single 32-bit lane has following layout // [c1 c0 d5 d4 d3 d2 d1 d0|b1 b0 c5 c4 c3 c2 c1 c0| // a1 a0 b5 b4 b3 b2 b1 b0|d1 d0 a5 a4 a3 a2 a1 a0] // (a = [10:17], b = [4:11], c = [22:27], d = [16:21]) // 48, 54, 36, 42, 16, 22, 4, 10 const __m512i shifts = _mm512_set1_epi64(0x3036242a1016040alu); __m512i shuffled_in = _mm512_multishift_epi64_epi8(shifts, in); // Translate immediatedly after reshuffled. const __m512i lookup = _mm512_loadu_si512(base64_table_enc_6bit); // Translation 6-bit values to ASCII. return _mm512_permutexvar_epi8(shuffled_in, lookup); } kitty-0.41.1/3rdparty/base64/lib/arch/generic/0000775000175000017510000000000014773370543020242 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/lib/arch/generic/32/0000775000175000017510000000000014773370543020466 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/lib/arch/generic/32/dec_loop.c0000664000175000017510000000423514773370543022422 0ustar nileshnileshstatic inline int dec_loop_generic_32_inner (const uint8_t **s, uint8_t **o, size_t *rounds) { const uint32_t str = base64_table_dec_32bit_d0[(*s)[0]] | base64_table_dec_32bit_d1[(*s)[1]] | base64_table_dec_32bit_d2[(*s)[2]] | base64_table_dec_32bit_d3[(*s)[3]]; #if BASE64_LITTLE_ENDIAN // LUTs for little-endian set MSB in case of invalid character: if (str & UINT32_C(0x80000000)) { return 0; } #else // LUTs for big-endian set LSB in case of invalid character: if (str & UINT32_C(1)) { return 0; } #endif // Store the output: memcpy(*o, &str, sizeof (str)); *s += 4; *o += 3; *rounds -= 1; return 1; } static inline void dec_loop_generic_32 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { if (*slen < 8) { return; } // Process blocks of 4 bytes per round. Because one extra zero byte is // written after the output, ensure that there will be at least 4 bytes // of input data left to cover the gap. (Two data bytes and up to two // end-of-string markers.) size_t rounds = (*slen - 4) / 4; *slen -= rounds * 4; // 4 bytes consumed per round *olen += rounds * 3; // 3 bytes produced per round do { if (rounds >= 8) { if (dec_loop_generic_32_inner(s, o, &rounds) && dec_loop_generic_32_inner(s, o, &rounds) && dec_loop_generic_32_inner(s, o, &rounds) && dec_loop_generic_32_inner(s, o, &rounds) && dec_loop_generic_32_inner(s, o, &rounds) && dec_loop_generic_32_inner(s, o, &rounds) && dec_loop_generic_32_inner(s, o, &rounds) && dec_loop_generic_32_inner(s, o, &rounds)) { continue; } break; } if (rounds >= 4) { if (dec_loop_generic_32_inner(s, o, &rounds) && dec_loop_generic_32_inner(s, o, &rounds) && dec_loop_generic_32_inner(s, o, &rounds) && dec_loop_generic_32_inner(s, o, &rounds)) { continue; } break; } if (rounds >= 2) { if (dec_loop_generic_32_inner(s, o, &rounds) && dec_loop_generic_32_inner(s, o, &rounds)) { continue; } break; } dec_loop_generic_32_inner(s, o, &rounds); break; } while (rounds > 0); // Adjust for any rounds that were skipped: *slen += rounds * 4; *olen -= rounds * 3; } kitty-0.41.1/3rdparty/base64/lib/arch/generic/32/enc_loop.c0000664000175000017510000000357714773370543022444 0ustar nileshnileshstatic inline void enc_loop_generic_32_inner (const uint8_t **s, uint8_t **o) { uint32_t src; // Load input: memcpy(&src, *s, sizeof (src)); // Reorder to 32-bit big-endian, if not already in that format. The // workset must be in big-endian, otherwise the shifted bits do not // carry over properly among adjacent bytes: src = BASE64_HTOBE32(src); // Two indices for the 12-bit lookup table: const size_t index0 = (src >> 20) & 0xFFFU; const size_t index1 = (src >> 8) & 0xFFFU; // Table lookup and store: memcpy(*o + 0, base64_table_enc_12bit + index0, 2); memcpy(*o + 2, base64_table_enc_12bit + index1, 2); *s += 3; *o += 4; } static inline void enc_loop_generic_32 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { if (*slen < 4) { return; } // Process blocks of 3 bytes at a time. Because blocks are loaded 4 // bytes at a time, ensure that there will be at least one remaining // byte after the last round, so that the final read will not pass // beyond the bounds of the input buffer: size_t rounds = (*slen - 1) / 3; *slen -= rounds * 3; // 3 bytes consumed per round *olen += rounds * 4; // 4 bytes produced per round do { if (rounds >= 8) { enc_loop_generic_32_inner(s, o); enc_loop_generic_32_inner(s, o); enc_loop_generic_32_inner(s, o); enc_loop_generic_32_inner(s, o); enc_loop_generic_32_inner(s, o); enc_loop_generic_32_inner(s, o); enc_loop_generic_32_inner(s, o); enc_loop_generic_32_inner(s, o); rounds -= 8; continue; } if (rounds >= 4) { enc_loop_generic_32_inner(s, o); enc_loop_generic_32_inner(s, o); enc_loop_generic_32_inner(s, o); enc_loop_generic_32_inner(s, o); rounds -= 4; continue; } if (rounds >= 2) { enc_loop_generic_32_inner(s, o); enc_loop_generic_32_inner(s, o); rounds -= 2; continue; } enc_loop_generic_32_inner(s, o); break; } while (rounds > 0); } kitty-0.41.1/3rdparty/base64/lib/arch/generic/64/0000775000175000017510000000000014773370543020473 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/lib/arch/generic/64/enc_loop.c0000664000175000017510000000410314773370543022433 0ustar nileshnileshstatic inline void enc_loop_generic_64_inner (const uint8_t **s, uint8_t **o) { uint64_t src; // Load input: memcpy(&src, *s, sizeof (src)); // Reorder to 64-bit big-endian, if not already in that format. The // workset must be in big-endian, otherwise the shifted bits do not // carry over properly among adjacent bytes: src = BASE64_HTOBE64(src); // Four indices for the 12-bit lookup table: const size_t index0 = (src >> 52) & 0xFFFU; const size_t index1 = (src >> 40) & 0xFFFU; const size_t index2 = (src >> 28) & 0xFFFU; const size_t index3 = (src >> 16) & 0xFFFU; // Table lookup and store: memcpy(*o + 0, base64_table_enc_12bit + index0, 2); memcpy(*o + 2, base64_table_enc_12bit + index1, 2); memcpy(*o + 4, base64_table_enc_12bit + index2, 2); memcpy(*o + 6, base64_table_enc_12bit + index3, 2); *s += 6; *o += 8; } static inline void enc_loop_generic_64 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { if (*slen < 8) { return; } // Process blocks of 6 bytes at a time. Because blocks are loaded 8 // bytes at a time, ensure that there will be at least 2 remaining // bytes after the last round, so that the final read will not pass // beyond the bounds of the input buffer: size_t rounds = (*slen - 2) / 6; *slen -= rounds * 6; // 6 bytes consumed per round *olen += rounds * 8; // 8 bytes produced per round do { if (rounds >= 8) { enc_loop_generic_64_inner(s, o); enc_loop_generic_64_inner(s, o); enc_loop_generic_64_inner(s, o); enc_loop_generic_64_inner(s, o); enc_loop_generic_64_inner(s, o); enc_loop_generic_64_inner(s, o); enc_loop_generic_64_inner(s, o); enc_loop_generic_64_inner(s, o); rounds -= 8; continue; } if (rounds >= 4) { enc_loop_generic_64_inner(s, o); enc_loop_generic_64_inner(s, o); enc_loop_generic_64_inner(s, o); enc_loop_generic_64_inner(s, o); rounds -= 4; continue; } if (rounds >= 2) { enc_loop_generic_64_inner(s, o); enc_loop_generic_64_inner(s, o); rounds -= 2; continue; } enc_loop_generic_64_inner(s, o); break; } while (rounds > 0); } kitty-0.41.1/3rdparty/base64/lib/arch/generic/codec.c0000664000175000017510000000137214773370543021466 0ustar nileshnilesh#include #include #include #include "../../../include/libbase64.h" #include "../../tables/tables.h" #include "../../codecs.h" #include "config.h" #include "../../env.h" #if BASE64_WORDSIZE == 32 # include "32/enc_loop.c" #elif BASE64_WORDSIZE == 64 # include "64/enc_loop.c" #endif #if BASE64_WORDSIZE >= 32 # include "32/dec_loop.c" #endif BASE64_ENC_FUNCTION(plain) { #include "enc_head.c" #if BASE64_WORDSIZE == 32 enc_loop_generic_32(&s, &slen, &o, &olen); #elif BASE64_WORDSIZE == 64 enc_loop_generic_64(&s, &slen, &o, &olen); #endif #include "enc_tail.c" } BASE64_DEC_FUNCTION(plain) { #include "dec_head.c" #if BASE64_WORDSIZE >= 32 dec_loop_generic_32(&s, &slen, &o, &olen); #endif #include "dec_tail.c" } kitty-0.41.1/3rdparty/base64/lib/arch/generic/dec_head.c0000664000175000017510000000142714773370543022126 0ustar nileshnileshint ret = 0; const uint8_t *s = (const uint8_t *) src; uint8_t *o = (uint8_t *) out; uint8_t q; // Use local temporaries to avoid cache thrashing: size_t olen = 0; size_t slen = srclen; struct base64_state st; st.eof = state->eof; st.bytes = state->bytes; st.carry = state->carry; // If we previously saw an EOF or an invalid character, bail out: if (st.eof) { *outlen = 0; ret = 0; // If there was a trailing '=' to check, check it: if (slen && (st.eof == BASE64_AEOF)) { state->bytes = 0; state->eof = BASE64_EOF; ret = ((base64_table_dec_8bit[*s++] == 254) && (slen == 1)) ? 1 : 0; } return ret; } // Turn four 6-bit numbers into three bytes: // out[0] = 11111122 // out[1] = 22223333 // out[2] = 33444444 // Duff's device again: switch (st.bytes) { for (;;) { case 0: kitty-0.41.1/3rdparty/base64/lib/arch/generic/dec_tail.c0000664000175000017510000000335614773370543022161 0ustar nileshnilesh if (slen-- == 0) { ret = 1; break; } if ((q = base64_table_dec_8bit[*s++]) >= 254) { st.eof = BASE64_EOF; // Treat character '=' as invalid for byte 0: break; } st.carry = q << 2; st.bytes++; // Deliberate fallthrough: BASE64_FALLTHROUGH case 1: if (slen-- == 0) { ret = 1; break; } if ((q = base64_table_dec_8bit[*s++]) >= 254) { st.eof = BASE64_EOF; // Treat character '=' as invalid for byte 1: break; } *o++ = st.carry | (q >> 4); st.carry = q << 4; st.bytes++; olen++; // Deliberate fallthrough: BASE64_FALLTHROUGH case 2: if (slen-- == 0) { ret = 1; break; } if ((q = base64_table_dec_8bit[*s++]) >= 254) { st.bytes++; // When q == 254, the input char is '='. // Check if next byte is also '=': if (q == 254) { if (slen-- != 0) { st.bytes = 0; // EOF: st.eof = BASE64_EOF; q = base64_table_dec_8bit[*s++]; ret = ((q == 254) && (slen == 0)) ? 1 : 0; break; } else { // Almost EOF st.eof = BASE64_AEOF; ret = 1; break; } } // If we get here, there was an error: break; } *o++ = st.carry | (q >> 2); st.carry = q << 6; st.bytes++; olen++; // Deliberate fallthrough: BASE64_FALLTHROUGH case 3: if (slen-- == 0) { ret = 1; break; } if ((q = base64_table_dec_8bit[*s++]) >= 254) { st.bytes = 0; st.eof = BASE64_EOF; // When q == 254, the input char is '='. Return 1 and EOF. // When q == 255, the input char is invalid. Return 0 and EOF. ret = ((q == 254) && (slen == 0)) ? 1 : 0; break; } *o++ = st.carry | q; st.carry = 0; st.bytes = 0; olen++; } } state->eof = st.eof; state->bytes = st.bytes; state->carry = st.carry; *outlen = olen; return ret; kitty-0.41.1/3rdparty/base64/lib/arch/generic/enc_head.c0000664000175000017510000000111114773370543022126 0ustar nileshnilesh// Assume that *out is large enough to contain the output. // Theoretically it should be 4/3 the length of src. const uint8_t *s = (const uint8_t *) src; uint8_t *o = (uint8_t *) out; // Use local temporaries to avoid cache thrashing: size_t olen = 0; size_t slen = srclen; struct base64_state st; st.bytes = state->bytes; st.carry = state->carry; // Turn three bytes into four 6-bit numbers: // in[0] = 00111111 // in[1] = 00112222 // in[2] = 00222233 // in[3] = 00333333 // Duff's device, a for() loop inside a switch() statement. Legal! switch (st.bytes) { for (;;) { case 0: kitty-0.41.1/3rdparty/base64/lib/arch/generic/enc_tail.c0000664000175000017510000000117514773370543022170 0ustar nileshnilesh if (slen-- == 0) { break; } *o++ = base64_table_enc_6bit[*s >> 2]; st.carry = (*s++ << 4) & 0x30; st.bytes++; olen += 1; // Deliberate fallthrough: BASE64_FALLTHROUGH case 1: if (slen-- == 0) { break; } *o++ = base64_table_enc_6bit[st.carry | (*s >> 4)]; st.carry = (*s++ << 2) & 0x3C; st.bytes++; olen += 1; // Deliberate fallthrough: BASE64_FALLTHROUGH case 2: if (slen-- == 0) { break; } *o++ = base64_table_enc_6bit[st.carry | (*s >> 6)]; *o++ = base64_table_enc_6bit[*s++ & 0x3F]; st.bytes = 0; olen += 2; } } state->bytes = st.bytes; state->carry = st.carry; *outlen = olen; kitty-0.41.1/3rdparty/base64/lib/arch/neon32/0000775000175000017510000000000014773370543017732 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/lib/arch/neon32/codec.c0000664000175000017510000000351414773370543021156 0ustar nileshnilesh#include #include #include #include "../../../include/libbase64.h" #include "../../tables/tables.h" #include "../../codecs.h" #include "config.h" #include "../../env.h" #ifdef __arm__ # if (defined(__ARM_NEON__) || defined(__ARM_NEON)) && HAVE_NEON32 # define BASE64_USE_NEON32 # endif #endif #ifdef BASE64_USE_NEON32 #include // Only enable inline assembly on supported compilers. #if defined(__GNUC__) || defined(__clang__) #define BASE64_NEON32_USE_ASM #endif static inline uint8x16_t vqtbl1q_u8 (const uint8x16_t lut, const uint8x16_t indices) { // NEON32 only supports 64-bit wide lookups in 128-bit tables. Emulate // the NEON64 `vqtbl1q_u8` intrinsic to do 128-bit wide lookups. uint8x8x2_t lut2; uint8x8x2_t result; lut2.val[0] = vget_low_u8(lut); lut2.val[1] = vget_high_u8(lut); result.val[0] = vtbl2_u8(lut2, vget_low_u8(indices)); result.val[1] = vtbl2_u8(lut2, vget_high_u8(indices)); return vcombine_u8(result.val[0], result.val[1]); } #include "../generic/32/dec_loop.c" #include "../generic/32/enc_loop.c" #include "dec_loop.c" #include "enc_reshuffle.c" #include "enc_translate.c" #include "enc_loop.c" #endif // BASE64_USE_NEON32 // Stride size is so large on these NEON 32-bit functions // (48 bytes encode, 32 bytes decode) that we inline the // uint32 codec to stay performant on smaller inputs. BASE64_ENC_FUNCTION(neon32) { #ifdef BASE64_USE_NEON32 #include "../generic/enc_head.c" enc_loop_neon32(&s, &slen, &o, &olen); enc_loop_generic_32(&s, &slen, &o, &olen); #include "../generic/enc_tail.c" #else BASE64_ENC_STUB #endif } BASE64_DEC_FUNCTION(neon32) { #ifdef BASE64_USE_NEON32 #include "../generic/dec_head.c" dec_loop_neon32(&s, &slen, &o, &olen); dec_loop_generic_32(&s, &slen, &o, &olen); #include "../generic/dec_tail.c" #else BASE64_DEC_STUB #endif } kitty-0.41.1/3rdparty/base64/lib/arch/neon32/dec_loop.c0000664000175000017510000000546314773370543021672 0ustar nileshnileshstatic inline int is_nonzero (const uint8x16_t v) { uint64_t u64; const uint64x2_t v64 = vreinterpretq_u64_u8(v); const uint32x2_t v32 = vqmovn_u64(v64); vst1_u64(&u64, vreinterpret_u64_u32(v32)); return u64 != 0; } static inline uint8x16_t delta_lookup (const uint8x16_t v) { const uint8x8_t lut = { 0, 16, 19, 4, (uint8_t) -65, (uint8_t) -65, (uint8_t) -71, (uint8_t) -71, }; return vcombine_u8( vtbl1_u8(lut, vget_low_u8(v)), vtbl1_u8(lut, vget_high_u8(v))); } static inline uint8x16_t dec_loop_neon32_lane (uint8x16_t *lane) { // See the SSSE3 decoder for an explanation of the algorithm. const uint8x16_t lut_lo = { 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x13, 0x1A, 0x1B, 0x1B, 0x1B, 0x1A }; const uint8x16_t lut_hi = { 0x10, 0x10, 0x01, 0x02, 0x04, 0x08, 0x04, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10 }; const uint8x16_t mask_0F = vdupq_n_u8(0x0F); const uint8x16_t mask_2F = vdupq_n_u8(0x2F); const uint8x16_t hi_nibbles = vshrq_n_u8(*lane, 4); const uint8x16_t lo_nibbles = vandq_u8(*lane, mask_0F); const uint8x16_t eq_2F = vceqq_u8(*lane, mask_2F); const uint8x16_t hi = vqtbl1q_u8(lut_hi, hi_nibbles); const uint8x16_t lo = vqtbl1q_u8(lut_lo, lo_nibbles); // Now simply add the delta values to the input: *lane = vaddq_u8(*lane, delta_lookup(vaddq_u8(eq_2F, hi_nibbles))); // Return the validity mask: return vandq_u8(lo, hi); } static inline void dec_loop_neon32 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { if (*slen < 64) { return; } // Process blocks of 64 bytes per round. Unlike the SSE codecs, no // extra trailing zero bytes are written, so it is not necessary to // reserve extra input bytes: size_t rounds = *slen / 64; *slen -= rounds * 64; // 64 bytes consumed per round *olen += rounds * 48; // 48 bytes produced per round do { uint8x16x3_t dec; // Load 64 bytes and deinterleave: uint8x16x4_t str = vld4q_u8(*s); // Decode each lane, collect a mask of invalid inputs: const uint8x16_t classified = dec_loop_neon32_lane(&str.val[0]) | dec_loop_neon32_lane(&str.val[1]) | dec_loop_neon32_lane(&str.val[2]) | dec_loop_neon32_lane(&str.val[3]); // Check for invalid input: if any of the delta values are // zero, fall back on bytewise code to do error checking and // reporting: if (is_nonzero(classified)) { break; } // Compress four bytes into three: dec.val[0] = vorrq_u8(vshlq_n_u8(str.val[0], 2), vshrq_n_u8(str.val[1], 4)); dec.val[1] = vorrq_u8(vshlq_n_u8(str.val[1], 4), vshrq_n_u8(str.val[2], 2)); dec.val[2] = vorrq_u8(vshlq_n_u8(str.val[2], 6), str.val[3]); // Interleave and store decoded result: vst3q_u8(*o, dec); *s += 64; *o += 48; } while (--rounds > 0); // Adjust for any rounds that were skipped: *slen += rounds * 64; *olen -= rounds * 48; } kitty-0.41.1/3rdparty/base64/lib/arch/neon32/enc_loop.c0000664000175000017510000001102514773370543021673 0ustar nileshnilesh#ifdef BASE64_NEON32_USE_ASM static inline void enc_loop_neon32_inner_asm (const uint8_t **s, uint8_t **o) { // This function duplicates the functionality of enc_loop_neon32_inner, // but entirely with inline assembly. This gives a significant speedup // over using NEON intrinsics, which do not always generate very good // code. The logic of the assembly is directly lifted from the // intrinsics version, so it can be used as a guide to this code. // Temporary registers, used as scratch space. uint8x16_t tmp0, tmp1, tmp2, tmp3; uint8x16_t mask0, mask1, mask2, mask3; // A lookup table containing the absolute offsets for all ranges. const uint8x16_t lut = { 65U, 71U, 252U, 252U, 252U, 252U, 252U, 252U, 252U, 252U, 252U, 252U, 237U, 240U, 0U, 0U }; // Numeric constants. const uint8x16_t n51 = vdupq_n_u8(51); const uint8x16_t n25 = vdupq_n_u8(25); const uint8x16_t n63 = vdupq_n_u8(63); __asm__ ( // Load 48 bytes and deinterleave. The bytes are loaded to // hard-coded registers q12, q13 and q14, to ensure that they // are contiguous. Increment the source pointer. "vld3.8 {d24, d26, d28}, [%[src]]! \n\t" "vld3.8 {d25, d27, d29}, [%[src]]! \n\t" // Reshuffle the bytes using temporaries. "vshr.u8 %q[t0], q12, #2 \n\t" "vshr.u8 %q[t1], q13, #4 \n\t" "vshr.u8 %q[t2], q14, #6 \n\t" "vsli.8 %q[t1], q12, #4 \n\t" "vsli.8 %q[t2], q13, #2 \n\t" "vand.u8 %q[t1], %q[t1], %q[n63] \n\t" "vand.u8 %q[t2], %q[t2], %q[n63] \n\t" "vand.u8 %q[t3], q14, %q[n63] \n\t" // t0..t3 are the reshuffled inputs. Create LUT indices. "vqsub.u8 q12, %q[t0], %q[n51] \n\t" "vqsub.u8 q13, %q[t1], %q[n51] \n\t" "vqsub.u8 q14, %q[t2], %q[n51] \n\t" "vqsub.u8 q15, %q[t3], %q[n51] \n\t" // Create the mask for range #0. "vcgt.u8 %q[m0], %q[t0], %q[n25] \n\t" "vcgt.u8 %q[m1], %q[t1], %q[n25] \n\t" "vcgt.u8 %q[m2], %q[t2], %q[n25] \n\t" "vcgt.u8 %q[m3], %q[t3], %q[n25] \n\t" // Subtract -1 to correct the LUT indices. "vsub.u8 q12, %q[m0] \n\t" "vsub.u8 q13, %q[m1] \n\t" "vsub.u8 q14, %q[m2] \n\t" "vsub.u8 q15, %q[m3] \n\t" // Lookup the delta values. "vtbl.u8 d24, {%q[lut]}, d24 \n\t" "vtbl.u8 d25, {%q[lut]}, d25 \n\t" "vtbl.u8 d26, {%q[lut]}, d26 \n\t" "vtbl.u8 d27, {%q[lut]}, d27 \n\t" "vtbl.u8 d28, {%q[lut]}, d28 \n\t" "vtbl.u8 d29, {%q[lut]}, d29 \n\t" "vtbl.u8 d30, {%q[lut]}, d30 \n\t" "vtbl.u8 d31, {%q[lut]}, d31 \n\t" // Add the delta values. "vadd.u8 q12, %q[t0] \n\t" "vadd.u8 q13, %q[t1] \n\t" "vadd.u8 q14, %q[t2] \n\t" "vadd.u8 q15, %q[t3] \n\t" // Store 64 bytes and interleave. Increment the dest pointer. "vst4.8 {d24, d26, d28, d30}, [%[dst]]! \n\t" "vst4.8 {d25, d27, d29, d31}, [%[dst]]! \n\t" // Outputs (modified). : [src] "+r" (*s), [dst] "+r" (*o), [t0] "=&w" (tmp0), [t1] "=&w" (tmp1), [t2] "=&w" (tmp2), [t3] "=&w" (tmp3), [m0] "=&w" (mask0), [m1] "=&w" (mask1), [m2] "=&w" (mask2), [m3] "=&w" (mask3) // Inputs (not modified). : [lut] "w" (lut), [n25] "w" (n25), [n51] "w" (n51), [n63] "w" (n63) // Clobbers. : "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31", "cc", "memory" ); } #endif static inline void enc_loop_neon32_inner (const uint8_t **s, uint8_t **o) { #ifdef BASE64_NEON32_USE_ASM enc_loop_neon32_inner_asm(s, o); #else // Load 48 bytes and deinterleave: uint8x16x3_t src = vld3q_u8(*s); // Reshuffle: uint8x16x4_t out = enc_reshuffle(src); // Translate reshuffled bytes to the Base64 alphabet: out = enc_translate(out); // Interleave and store output: vst4q_u8(*o, out); *s += 48; *o += 64; #endif } static inline void enc_loop_neon32 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { size_t rounds = *slen / 48; *slen -= rounds * 48; // 48 bytes consumed per round *olen += rounds * 64; // 64 bytes produced per round while (rounds > 0) { if (rounds >= 8) { enc_loop_neon32_inner(s, o); enc_loop_neon32_inner(s, o); enc_loop_neon32_inner(s, o); enc_loop_neon32_inner(s, o); enc_loop_neon32_inner(s, o); enc_loop_neon32_inner(s, o); enc_loop_neon32_inner(s, o); enc_loop_neon32_inner(s, o); rounds -= 8; continue; } if (rounds >= 4) { enc_loop_neon32_inner(s, o); enc_loop_neon32_inner(s, o); enc_loop_neon32_inner(s, o); enc_loop_neon32_inner(s, o); rounds -= 4; continue; } if (rounds >= 2) { enc_loop_neon32_inner(s, o); enc_loop_neon32_inner(s, o); rounds -= 2; continue; } enc_loop_neon32_inner(s, o); break; } } kitty-0.41.1/3rdparty/base64/lib/arch/neon32/enc_reshuffle.c0000664000175000017510000000171114773370543022706 0ustar nileshnileshstatic inline uint8x16x4_t enc_reshuffle (uint8x16x3_t in) { uint8x16x4_t out; // Input: // in[0] = a7 a6 a5 a4 a3 a2 a1 a0 // in[1] = b7 b6 b5 b4 b3 b2 b1 b0 // in[2] = c7 c6 c5 c4 c3 c2 c1 c0 // Output: // out[0] = 00 00 a7 a6 a5 a4 a3 a2 // out[1] = 00 00 a1 a0 b7 b6 b5 b4 // out[2] = 00 00 b3 b2 b1 b0 c7 c6 // out[3] = 00 00 c5 c4 c3 c2 c1 c0 // Move the input bits to where they need to be in the outputs. Except // for the first output, the high two bits are not cleared. out.val[0] = vshrq_n_u8(in.val[0], 2); out.val[1] = vshrq_n_u8(in.val[1], 4); out.val[2] = vshrq_n_u8(in.val[2], 6); out.val[1] = vsliq_n_u8(out.val[1], in.val[0], 4); out.val[2] = vsliq_n_u8(out.val[2], in.val[1], 2); // Clear the high two bits in the second, third and fourth output. out.val[1] = vandq_u8(out.val[1], vdupq_n_u8(0x3F)); out.val[2] = vandq_u8(out.val[2], vdupq_n_u8(0x3F)); out.val[3] = vandq_u8(in.val[2], vdupq_n_u8(0x3F)); return out; } kitty-0.41.1/3rdparty/base64/lib/arch/neon32/enc_translate.c0000664000175000017510000000406714773370543022727 0ustar nileshnileshstatic inline uint8x16x4_t enc_translate (const uint8x16x4_t in) { // A lookup table containing the absolute offsets for all ranges: const uint8x16_t lut = { 65U, 71U, 252U, 252U, 252U, 252U, 252U, 252U, 252U, 252U, 252U, 252U, 237U, 240U, 0U, 0U }; const uint8x16_t offset = vdupq_n_u8(51); uint8x16x4_t indices, mask, delta, out; // Translate values 0..63 to the Base64 alphabet. There are five sets: // # From To Abs Index Characters // 0 [0..25] [65..90] +65 0 ABCDEFGHIJKLMNOPQRSTUVWXYZ // 1 [26..51] [97..122] +71 1 abcdefghijklmnopqrstuvwxyz // 2 [52..61] [48..57] -4 [2..11] 0123456789 // 3 [62] [43] -19 12 + // 4 [63] [47] -16 13 / // Create LUT indices from input: // the index for range #0 is right, others are 1 less than expected: indices.val[0] = vqsubq_u8(in.val[0], offset); indices.val[1] = vqsubq_u8(in.val[1], offset); indices.val[2] = vqsubq_u8(in.val[2], offset); indices.val[3] = vqsubq_u8(in.val[3], offset); // mask is 0xFF (-1) for range #[1..4] and 0x00 for range #0: mask.val[0] = vcgtq_u8(in.val[0], vdupq_n_u8(25)); mask.val[1] = vcgtq_u8(in.val[1], vdupq_n_u8(25)); mask.val[2] = vcgtq_u8(in.val[2], vdupq_n_u8(25)); mask.val[3] = vcgtq_u8(in.val[3], vdupq_n_u8(25)); // Subtract -1, so add 1 to indices for range #[1..4], All indices are // now correct: indices.val[0] = vsubq_u8(indices.val[0], mask.val[0]); indices.val[1] = vsubq_u8(indices.val[1], mask.val[1]); indices.val[2] = vsubq_u8(indices.val[2], mask.val[2]); indices.val[3] = vsubq_u8(indices.val[3], mask.val[3]); // Lookup delta values: delta.val[0] = vqtbl1q_u8(lut, indices.val[0]); delta.val[1] = vqtbl1q_u8(lut, indices.val[1]); delta.val[2] = vqtbl1q_u8(lut, indices.val[2]); delta.val[3] = vqtbl1q_u8(lut, indices.val[3]); // Add delta values: out.val[0] = vaddq_u8(in.val[0], delta.val[0]); out.val[1] = vaddq_u8(in.val[1], delta.val[1]); out.val[2] = vaddq_u8(in.val[2], delta.val[2]); out.val[3] = vaddq_u8(in.val[3], delta.val[3]); return out; } kitty-0.41.1/3rdparty/base64/lib/arch/neon64/0000775000175000017510000000000014773370543017737 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/lib/arch/neon64/codec.c0000664000175000017510000000425114773370543021162 0ustar nileshnilesh#include #include #include #include "../../../include/libbase64.h" #include "../../tables/tables.h" #include "../../codecs.h" #include "config.h" #include "../../env.h" #ifdef __aarch64__ # if (defined(__ARM_NEON__) || defined(__ARM_NEON)) && HAVE_NEON64 # define BASE64_USE_NEON64 # endif #endif #ifdef BASE64_USE_NEON64 #include // Only enable inline assembly on supported compilers. #if defined(__GNUC__) || defined(__clang__) #define BASE64_NEON64_USE_ASM #endif static inline uint8x16x4_t load_64byte_table (const uint8_t *p) { #ifdef BASE64_NEON64_USE_ASM // Force the table to be loaded into contiguous registers. GCC will not // normally allocate contiguous registers for a `uint8x16x4_t'. These // registers are chosen to not conflict with the ones in the enc loop. register uint8x16_t t0 __asm__ ("v8"); register uint8x16_t t1 __asm__ ("v9"); register uint8x16_t t2 __asm__ ("v10"); register uint8x16_t t3 __asm__ ("v11"); __asm__ ( "ld1 {%[t0].16b, %[t1].16b, %[t2].16b, %[t3].16b}, [%[src]], #64 \n\t" : [src] "+r" (p), [t0] "=w" (t0), [t1] "=w" (t1), [t2] "=w" (t2), [t3] "=w" (t3) ); return (uint8x16x4_t) { .val[0] = t0, .val[1] = t1, .val[2] = t2, .val[3] = t3, }; #else return vld1q_u8_x4(p); #endif } #include "../generic/32/dec_loop.c" #include "../generic/64/enc_loop.c" #include "dec_loop.c" #ifdef BASE64_NEON64_USE_ASM # include "enc_loop_asm.c" #else # include "enc_reshuffle.c" # include "enc_loop.c" #endif #endif // BASE64_USE_NEON64 // Stride size is so large on these NEON 64-bit functions // (48 bytes encode, 64 bytes decode) that we inline the // uint64 codec to stay performant on smaller inputs. BASE64_ENC_FUNCTION(neon64) { #ifdef BASE64_USE_NEON64 #include "../generic/enc_head.c" enc_loop_neon64(&s, &slen, &o, &olen); enc_loop_generic_64(&s, &slen, &o, &olen); #include "../generic/enc_tail.c" #else BASE64_ENC_STUB #endif } BASE64_DEC_FUNCTION(neon64) { #ifdef BASE64_USE_NEON64 #include "../generic/dec_head.c" dec_loop_neon64(&s, &slen, &o, &olen); dec_loop_generic_32(&s, &slen, &o, &olen); #include "../generic/dec_tail.c" #else BASE64_DEC_STUB #endif } kitty-0.41.1/3rdparty/base64/lib/arch/neon64/dec_loop.c0000664000175000017510000001212514773370543021670 0ustar nileshnilesh// The input consists of five valid character sets in the Base64 alphabet, // which we need to map back to the 6-bit values they represent. // There are three ranges, two singles, and then there's the rest. // // # From To LUT Characters // 1 [0..42] [255] #1 invalid input // 2 [43] [62] #1 + // 3 [44..46] [255] #1 invalid input // 4 [47] [63] #1 / // 5 [48..57] [52..61] #1 0..9 // 6 [58..63] [255] #1 invalid input // 7 [64] [255] #2 invalid input // 8 [65..90] [0..25] #2 A..Z // 9 [91..96] [255] #2 invalid input // 10 [97..122] [26..51] #2 a..z // 11 [123..126] [255] #2 invalid input // (12) Everything else => invalid input // The first LUT will use the VTBL instruction (out of range indices are set to // 0 in destination). static const uint8_t dec_lut1[] = { 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 255U, 62U, 255U, 255U, 255U, 63U, 52U, 53U, 54U, 55U, 56U, 57U, 58U, 59U, 60U, 61U, 255U, 255U, 255U, 255U, 255U, 255U, }; // The second LUT will use the VTBX instruction (out of range indices will be // unchanged in destination). Input [64..126] will be mapped to index [1..63] // in this LUT. Index 0 means that value comes from LUT #1. static const uint8_t dec_lut2[] = { 0U, 255U, 0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 11U, 12U, 13U, 14U, 15U, 16U, 17U, 18U, 19U, 20U, 21U, 22U, 23U, 24U, 25U, 255U, 255U, 255U, 255U, 255U, 255U, 26U, 27U, 28U, 29U, 30U, 31U, 32U, 33U, 34U, 35U, 36U, 37U, 38U, 39U, 40U, 41U, 42U, 43U, 44U, 45U, 46U, 47U, 48U, 49U, 50U, 51U, 255U, 255U, 255U, 255U, }; // All input values in range for the first look-up will be 0U in the second // look-up result. All input values out of range for the first look-up will be // 0U in the first look-up result. Thus, the two results can be ORed without // conflicts. // // Invalid characters that are in the valid range for either look-up will be // set to 255U in the combined result. Other invalid characters will just be // passed through with the second look-up result (using the VTBX instruction). // Since the second LUT is 64 bytes, those passed-through values are guaranteed // to have a value greater than 63U. Therefore, valid characters will be mapped // to the valid [0..63] range and all invalid characters will be mapped to // values greater than 63. static inline void dec_loop_neon64 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { if (*slen < 64) { return; } // Process blocks of 64 bytes per round. Unlike the SSE codecs, no // extra trailing zero bytes are written, so it is not necessary to // reserve extra input bytes: size_t rounds = *slen / 64; *slen -= rounds * 64; // 64 bytes consumed per round *olen += rounds * 48; // 48 bytes produced per round const uint8x16x4_t tbl_dec1 = load_64byte_table(dec_lut1); const uint8x16x4_t tbl_dec2 = load_64byte_table(dec_lut2); do { const uint8x16_t offset = vdupq_n_u8(63U); uint8x16x4_t dec1, dec2; uint8x16x3_t dec; // Load 64 bytes and deinterleave: uint8x16x4_t str = vld4q_u8((uint8_t *) *s); // Get indices for second LUT: dec2.val[0] = vqsubq_u8(str.val[0], offset); dec2.val[1] = vqsubq_u8(str.val[1], offset); dec2.val[2] = vqsubq_u8(str.val[2], offset); dec2.val[3] = vqsubq_u8(str.val[3], offset); // Get values from first LUT: dec1.val[0] = vqtbl4q_u8(tbl_dec1, str.val[0]); dec1.val[1] = vqtbl4q_u8(tbl_dec1, str.val[1]); dec1.val[2] = vqtbl4q_u8(tbl_dec1, str.val[2]); dec1.val[3] = vqtbl4q_u8(tbl_dec1, str.val[3]); // Get values from second LUT: dec2.val[0] = vqtbx4q_u8(dec2.val[0], tbl_dec2, dec2.val[0]); dec2.val[1] = vqtbx4q_u8(dec2.val[1], tbl_dec2, dec2.val[1]); dec2.val[2] = vqtbx4q_u8(dec2.val[2], tbl_dec2, dec2.val[2]); dec2.val[3] = vqtbx4q_u8(dec2.val[3], tbl_dec2, dec2.val[3]); // Get final values: str.val[0] = vorrq_u8(dec1.val[0], dec2.val[0]); str.val[1] = vorrq_u8(dec1.val[1], dec2.val[1]); str.val[2] = vorrq_u8(dec1.val[2], dec2.val[2]); str.val[3] = vorrq_u8(dec1.val[3], dec2.val[3]); // Check for invalid input, any value larger than 63: const uint8x16_t classified = vcgtq_u8(str.val[0], vdupq_n_u8(63)) | vcgtq_u8(str.val[1], vdupq_n_u8(63)) | vcgtq_u8(str.val[2], vdupq_n_u8(63)) | vcgtq_u8(str.val[3], vdupq_n_u8(63)); // Check that all bits are zero: if (vmaxvq_u8(classified) != 0U) { break; } // Compress four bytes into three: dec.val[0] = vshlq_n_u8(str.val[0], 2) | vshrq_n_u8(str.val[1], 4); dec.val[1] = vshlq_n_u8(str.val[1], 4) | vshrq_n_u8(str.val[2], 2); dec.val[2] = vshlq_n_u8(str.val[2], 6) | str.val[3]; // Interleave and store decoded result: vst3q_u8((uint8_t *) *o, dec); *s += 64; *o += 48; } while (--rounds > 0); // Adjust for any rounds that were skipped: *slen += rounds * 64; *olen -= rounds * 48; } kitty-0.41.1/3rdparty/base64/lib/arch/neon64/enc_loop.c0000664000175000017510000000346414773370543021710 0ustar nileshnileshstatic inline void enc_loop_neon64_inner (const uint8_t **s, uint8_t **o, const uint8x16x4_t tbl_enc) { // Load 48 bytes and deinterleave: uint8x16x3_t src = vld3q_u8(*s); // Divide bits of three input bytes over four output bytes: uint8x16x4_t out = enc_reshuffle(src); // The bits have now been shifted to the right locations; // translate their values 0..63 to the Base64 alphabet. // Use a 64-byte table lookup: out.val[0] = vqtbl4q_u8(tbl_enc, out.val[0]); out.val[1] = vqtbl4q_u8(tbl_enc, out.val[1]); out.val[2] = vqtbl4q_u8(tbl_enc, out.val[2]); out.val[3] = vqtbl4q_u8(tbl_enc, out.val[3]); // Interleave and store output: vst4q_u8(*o, out); *s += 48; *o += 64; } static inline void enc_loop_neon64 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { size_t rounds = *slen / 48; *slen -= rounds * 48; // 48 bytes consumed per round *olen += rounds * 64; // 64 bytes produced per round // Load the encoding table: const uint8x16x4_t tbl_enc = load_64byte_table(base64_table_enc_6bit); while (rounds > 0) { if (rounds >= 8) { enc_loop_neon64_inner(s, o, tbl_enc); enc_loop_neon64_inner(s, o, tbl_enc); enc_loop_neon64_inner(s, o, tbl_enc); enc_loop_neon64_inner(s, o, tbl_enc); enc_loop_neon64_inner(s, o, tbl_enc); enc_loop_neon64_inner(s, o, tbl_enc); enc_loop_neon64_inner(s, o, tbl_enc); enc_loop_neon64_inner(s, o, tbl_enc); rounds -= 8; continue; } if (rounds >= 4) { enc_loop_neon64_inner(s, o, tbl_enc); enc_loop_neon64_inner(s, o, tbl_enc); enc_loop_neon64_inner(s, o, tbl_enc); enc_loop_neon64_inner(s, o, tbl_enc); rounds -= 4; continue; } if (rounds >= 2) { enc_loop_neon64_inner(s, o, tbl_enc); enc_loop_neon64_inner(s, o, tbl_enc); rounds -= 2; continue; } enc_loop_neon64_inner(s, o, tbl_enc); break; } } kitty-0.41.1/3rdparty/base64/lib/arch/neon64/enc_loop_asm.c0000664000175000017510000001276114773370543022550 0ustar nileshnilesh// Apologies in advance for combining the preprocessor with inline assembly, // two notoriously gnarly parts of C, but it was necessary to avoid a lot of // code repetition. The preprocessor is used to template large sections of // inline assembly that differ only in the registers used. If the code was // written out by hand, it would become very large and hard to audit. // Generate a block of inline assembly that loads three user-defined registers // A, B, C from memory and deinterleaves them, post-incrementing the src // pointer. The register set should be sequential. #define LOAD(A, B, C) \ "ld3 {"A".16b, "B".16b, "C".16b}, [%[src]], #48 \n\t" // Generate a block of inline assembly that takes three deinterleaved registers // and shuffles the bytes. The output is in temporary registers t0..t3. #define SHUF(A, B, C) \ "ushr %[t0].16b, "A".16b, #2 \n\t" \ "ushr %[t1].16b, "B".16b, #4 \n\t" \ "ushr %[t2].16b, "C".16b, #6 \n\t" \ "sli %[t1].16b, "A".16b, #4 \n\t" \ "sli %[t2].16b, "B".16b, #2 \n\t" \ "and %[t1].16b, %[t1].16b, %[n63].16b \n\t" \ "and %[t2].16b, %[t2].16b, %[n63].16b \n\t" \ "and %[t3].16b, "C".16b, %[n63].16b \n\t" // Generate a block of inline assembly that takes temporary registers t0..t3 // and translates them to the base64 alphabet, using a table loaded into // v8..v11. The output is in user-defined registers A..D. #define TRAN(A, B, C, D) \ "tbl "A".16b, {v8.16b-v11.16b}, %[t0].16b \n\t" \ "tbl "B".16b, {v8.16b-v11.16b}, %[t1].16b \n\t" \ "tbl "C".16b, {v8.16b-v11.16b}, %[t2].16b \n\t" \ "tbl "D".16b, {v8.16b-v11.16b}, %[t3].16b \n\t" // Generate a block of inline assembly that interleaves four registers and // stores them, post-incrementing the destination pointer. #define STOR(A, B, C, D) \ "st4 {"A".16b, "B".16b, "C".16b, "D".16b}, [%[dst]], #64 \n\t" // Generate a block of inline assembly that generates a single self-contained // encoder round: fetch the data, process it, and store the result. #define ROUND() \ LOAD("v12", "v13", "v14") \ SHUF("v12", "v13", "v14") \ TRAN("v12", "v13", "v14", "v15") \ STOR("v12", "v13", "v14", "v15") // Generate a block of assembly that generates a type A interleaved encoder // round. It uses registers that were loaded by the previous type B round, and // in turn loads registers for the next type B round. #define ROUND_A() \ SHUF("v2", "v3", "v4") \ LOAD("v12", "v13", "v14") \ TRAN("v2", "v3", "v4", "v5") \ STOR("v2", "v3", "v4", "v5") // Type B interleaved encoder round. Same as type A, but register sets swapped. #define ROUND_B() \ SHUF("v12", "v13", "v14") \ LOAD("v2", "v3", "v4") \ TRAN("v12", "v13", "v14", "v15") \ STOR("v12", "v13", "v14", "v15") // The first type A round needs to load its own registers. #define ROUND_A_FIRST() \ LOAD("v2", "v3", "v4") \ ROUND_A() // The last type B round omits the load for the next step. #define ROUND_B_LAST() \ SHUF("v12", "v13", "v14") \ TRAN("v12", "v13", "v14", "v15") \ STOR("v12", "v13", "v14", "v15") // Suppress clang's warning that the literal string in the asm statement is // overlong (longer than the ISO-mandated minimum size of 4095 bytes for C99 // compilers). It may be true, but the goal here is not C99 portability. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Woverlength-strings" static inline void enc_loop_neon64 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { size_t rounds = *slen / 48; if (rounds == 0) { return; } *slen -= rounds * 48; // 48 bytes consumed per round. *olen += rounds * 64; // 64 bytes produced per round. // Number of times to go through the 8x loop. size_t loops = rounds / 8; // Number of rounds remaining after the 8x loop. rounds %= 8; // Temporary registers, used as scratch space. uint8x16_t tmp0, tmp1, tmp2, tmp3; __asm__ volatile ( // Load the encoding table into v8..v11. " ld1 {v8.16b-v11.16b}, [%[tbl]] \n\t" // If there are eight rounds or more, enter an 8x unrolled loop // of interleaved encoding rounds. The rounds interleave memory // operations (load/store) with data operations to maximize // pipeline throughput. " cbz %[loops], 4f \n\t" // The SIMD instructions do not touch the flags. "88: subs %[loops], %[loops], #1 \n\t" " " ROUND_A_FIRST() " " ROUND_B() " " ROUND_A() " " ROUND_B() " " ROUND_A() " " ROUND_B() " " ROUND_A() " " ROUND_B_LAST() " b.ne 88b \n\t" // Enter a 4x unrolled loop for rounds of 4 or more. "4: cmp %[rounds], #4 \n\t" " b.lt 30f \n\t" " " ROUND_A_FIRST() " " ROUND_B() " " ROUND_A() " " ROUND_B_LAST() " sub %[rounds], %[rounds], #4 \n\t" // Dispatch the remaining rounds 0..3. "30: cbz %[rounds], 0f \n\t" " cmp %[rounds], #2 \n\t" " b.eq 2f \n\t" " b.lt 1f \n\t" // Block of non-interlaced encoding rounds, which can each // individually be jumped to. Rounds fall through to the next. "3: " ROUND() "2: " ROUND() "1: " ROUND() "0: \n\t" // Outputs (modified). : [loops] "+r" (loops), [src] "+r" (*s), [dst] "+r" (*o), [t0] "=&w" (tmp0), [t1] "=&w" (tmp1), [t2] "=&w" (tmp2), [t3] "=&w" (tmp3) // Inputs (not modified). : [rounds] "r" (rounds), [tbl] "r" (base64_table_enc_6bit), [n63] "w" (vdupq_n_u8(63)) // Clobbers. : "v2", "v3", "v4", "v5", "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15", "cc", "memory" ); } #pragma GCC diagnostic pop kitty-0.41.1/3rdparty/base64/lib/arch/neon64/enc_reshuffle.c0000664000175000017510000000171714773370543022721 0ustar nileshnileshstatic inline uint8x16x4_t enc_reshuffle (const uint8x16x3_t in) { uint8x16x4_t out; // Input: // in[0] = a7 a6 a5 a4 a3 a2 a1 a0 // in[1] = b7 b6 b5 b4 b3 b2 b1 b0 // in[2] = c7 c6 c5 c4 c3 c2 c1 c0 // Output: // out[0] = 00 00 a7 a6 a5 a4 a3 a2 // out[1] = 00 00 a1 a0 b7 b6 b5 b4 // out[2] = 00 00 b3 b2 b1 b0 c7 c6 // out[3] = 00 00 c5 c4 c3 c2 c1 c0 // Move the input bits to where they need to be in the outputs. Except // for the first output, the high two bits are not cleared. out.val[0] = vshrq_n_u8(in.val[0], 2); out.val[1] = vshrq_n_u8(in.val[1], 4); out.val[2] = vshrq_n_u8(in.val[2], 6); out.val[1] = vsliq_n_u8(out.val[1], in.val[0], 4); out.val[2] = vsliq_n_u8(out.val[2], in.val[1], 2); // Clear the high two bits in the second, third and fourth output. out.val[1] = vandq_u8(out.val[1], vdupq_n_u8(0x3F)); out.val[2] = vandq_u8(out.val[2], vdupq_n_u8(0x3F)); out.val[3] = vandq_u8(in.val[2], vdupq_n_u8(0x3F)); return out; } kitty-0.41.1/3rdparty/base64/lib/arch/sse41/0000775000175000017510000000000014773370543017565 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/lib/arch/sse41/codec.c0000664000175000017510000000217114773370543021007 0ustar nileshnilesh#include #include #include #include "../../../include/libbase64.h" #include "../../tables/tables.h" #include "../../codecs.h" #include "config.h" #include "../../env.h" #if HAVE_SSE41 #include // Only enable inline assembly on supported compilers and on 64-bit CPUs. #ifndef BASE64_SSE41_USE_ASM # if (defined(__GNUC__) || defined(__clang__)) && BASE64_WORDSIZE == 64 # define BASE64_SSE41_USE_ASM 1 # else # define BASE64_SSE41_USE_ASM 0 # endif #endif #include "../ssse3/dec_reshuffle.c" #include "../ssse3/dec_loop.c" #if BASE64_SSE41_USE_ASM # include "../ssse3/enc_loop_asm.c" #else # include "../ssse3/enc_translate.c" # include "../ssse3/enc_reshuffle.c" # include "../ssse3/enc_loop.c" #endif #endif // HAVE_SSE41 BASE64_ENC_FUNCTION(sse41) { #if HAVE_SSE41 #include "../generic/enc_head.c" enc_loop_ssse3(&s, &slen, &o, &olen); #include "../generic/enc_tail.c" #else BASE64_ENC_STUB #endif } BASE64_DEC_FUNCTION(sse41) { #if HAVE_SSE41 #include "../generic/dec_head.c" dec_loop_ssse3(&s, &slen, &o, &olen); #include "../generic/dec_tail.c" #else BASE64_DEC_STUB #endif } kitty-0.41.1/3rdparty/base64/lib/arch/sse42/0000775000175000017510000000000014773370543017566 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/lib/arch/sse42/codec.c0000664000175000017510000000217114773370543021010 0ustar nileshnilesh#include #include #include #include "../../../include/libbase64.h" #include "../../tables/tables.h" #include "../../codecs.h" #include "config.h" #include "../../env.h" #if HAVE_SSE42 #include // Only enable inline assembly on supported compilers and on 64-bit CPUs. #ifndef BASE64_SSE42_USE_ASM # if (defined(__GNUC__) || defined(__clang__)) && BASE64_WORDSIZE == 64 # define BASE64_SSE42_USE_ASM 1 # else # define BASE64_SSE42_USE_ASM 0 # endif #endif #include "../ssse3/dec_reshuffle.c" #include "../ssse3/dec_loop.c" #if BASE64_SSE42_USE_ASM # include "../ssse3/enc_loop_asm.c" #else # include "../ssse3/enc_translate.c" # include "../ssse3/enc_reshuffle.c" # include "../ssse3/enc_loop.c" #endif #endif // HAVE_SSE42 BASE64_ENC_FUNCTION(sse42) { #if HAVE_SSE42 #include "../generic/enc_head.c" enc_loop_ssse3(&s, &slen, &o, &olen); #include "../generic/enc_tail.c" #else BASE64_ENC_STUB #endif } BASE64_DEC_FUNCTION(sse42) { #if HAVE_SSE42 #include "../generic/dec_head.c" dec_loop_ssse3(&s, &slen, &o, &olen); #include "../generic/dec_tail.c" #else BASE64_DEC_STUB #endif } kitty-0.41.1/3rdparty/base64/lib/arch/ssse3/0000775000175000017510000000000014773370543017666 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/lib/arch/ssse3/codec.c0000664000175000017510000000231714773370543021112 0ustar nileshnilesh#include #include #include #include "../../../include/libbase64.h" #include "../../tables/tables.h" #include "../../codecs.h" #include "config.h" #include "../../env.h" #if HAVE_SSSE3 #include // Only enable inline assembly on supported compilers and on 64-bit CPUs. // 32-bit CPUs with SSSE3 support, such as low-end Atoms, only have eight XMM // registers, which is not enough to run the inline assembly. #ifndef BASE64_SSSE3_USE_ASM # if (defined(__GNUC__) || defined(__clang__)) && BASE64_WORDSIZE == 64 # define BASE64_SSSE3_USE_ASM 1 # else # define BASE64_SSSE3_USE_ASM 0 # endif #endif #include "dec_reshuffle.c" #include "dec_loop.c" #if BASE64_SSSE3_USE_ASM # include "enc_loop_asm.c" #else # include "enc_reshuffle.c" # include "enc_translate.c" # include "enc_loop.c" #endif #endif // HAVE_SSSE3 BASE64_ENC_FUNCTION(ssse3) { #if HAVE_SSSE3 #include "../generic/enc_head.c" enc_loop_ssse3(&s, &slen, &o, &olen); #include "../generic/enc_tail.c" #else BASE64_ENC_STUB #endif } BASE64_DEC_FUNCTION(ssse3) { #if HAVE_SSSE3 #include "../generic/dec_head.c" dec_loop_ssse3(&s, &slen, &o, &olen); #include "../generic/dec_tail.c" #else BASE64_DEC_STUB #endif } kitty-0.41.1/3rdparty/base64/lib/arch/ssse3/dec_loop.c0000664000175000017510000001533614773370543021626 0ustar nileshnilesh// The input consists of six character sets in the Base64 alphabet, which we // need to map back to the 6-bit values they represent. There are three ranges, // two singles, and then there's the rest. // // # From To Add Characters // 1 [43] [62] +19 + // 2 [47] [63] +16 / // 3 [48..57] [52..61] +4 0..9 // 4 [65..90] [0..25] -65 A..Z // 5 [97..122] [26..51] -71 a..z // (6) Everything else => invalid input // // We will use lookup tables for character validation and offset computation. // Remember that 0x2X and 0x0X are the same index for _mm_shuffle_epi8, this // allows to mask with 0x2F instead of 0x0F and thus save one constant // declaration (register and/or memory access). // // For offsets: // Perfect hash for lut = ((src >> 4) & 0x2F) + ((src == 0x2F) ? 0xFF : 0x00) // 0000 = garbage // 0001 = / // 0010 = + // 0011 = 0-9 // 0100 = A-Z // 0101 = A-Z // 0110 = a-z // 0111 = a-z // 1000 >= garbage // // For validation, here's the table. // A character is valid if and only if the AND of the 2 lookups equals 0: // // hi \ lo 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 // LUT 0x15 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x13 0x1A 0x1B 0x1B 0x1B 0x1A // // 0000 0x10 char NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI // andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 // // 0001 0x10 char DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US // andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 // // 0010 0x01 char ! " # $ % & ' ( ) * + , - . / // andlut 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x00 0x01 0x01 0x01 0x00 // // 0011 0x02 char 0 1 2 3 4 5 6 7 8 9 : ; < = > ? // andlut 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x02 0x02 0x02 0x02 0x02 // // 0100 0x04 char @ A B C D E F G H I J K L M N O // andlut 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 // // 0101 0x08 char P Q R S T U V W X Y Z [ \ ] ^ _ // andlut 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x08 0x08 0x08 0x08 0x08 // // 0110 0x04 char ` a b c d e f g h i j k l m n o // andlut 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 // 0111 0x08 char p q r s t u v w x y z { | } ~ // andlut 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x08 0x08 0x08 0x08 0x08 // // 1000 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 // 1001 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 // 1010 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 // 1011 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 // 1100 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 // 1101 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 // 1110 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 // 1111 0x10 andlut 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 0x10 static inline int dec_loop_ssse3_inner (const uint8_t **s, uint8_t **o, size_t *rounds) { const __m128i lut_lo = _mm_setr_epi8( 0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x13, 0x1A, 0x1B, 0x1B, 0x1B, 0x1A); const __m128i lut_hi = _mm_setr_epi8( 0x10, 0x10, 0x01, 0x02, 0x04, 0x08, 0x04, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10); const __m128i lut_roll = _mm_setr_epi8( 0, 16, 19, 4, -65, -65, -71, -71, 0, 0, 0, 0, 0, 0, 0, 0); const __m128i mask_2F = _mm_set1_epi8(0x2F); // Load input: __m128i str = _mm_loadu_si128((__m128i *) *s); // Table lookups: const __m128i hi_nibbles = _mm_and_si128(_mm_srli_epi32(str, 4), mask_2F); const __m128i lo_nibbles = _mm_and_si128(str, mask_2F); const __m128i hi = _mm_shuffle_epi8(lut_hi, hi_nibbles); const __m128i lo = _mm_shuffle_epi8(lut_lo, lo_nibbles); // Check for invalid input: if any "and" values from lo and hi are not // zero, fall back on bytewise code to do error checking and reporting: if (_mm_movemask_epi8(_mm_cmpgt_epi8(_mm_and_si128(lo, hi), _mm_setzero_si128())) != 0) { return 0; } const __m128i eq_2F = _mm_cmpeq_epi8(str, mask_2F); const __m128i roll = _mm_shuffle_epi8(lut_roll, _mm_add_epi8(eq_2F, hi_nibbles)); // Now simply add the delta values to the input: str = _mm_add_epi8(str, roll); // Reshuffle the input to packed 12-byte output format: str = dec_reshuffle(str); // Store the output: _mm_storeu_si128((__m128i *) *o, str); *s += 16; *o += 12; *rounds -= 1; return 1; } static inline void dec_loop_ssse3 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { if (*slen < 24) { return; } // Process blocks of 16 bytes per round. Because 4 extra zero bytes are // written after the output, ensure that there will be at least 8 bytes // of input data left to cover the gap. (6 data bytes and up to two // end-of-string markers.) size_t rounds = (*slen - 8) / 16; *slen -= rounds * 16; // 16 bytes consumed per round *olen += rounds * 12; // 12 bytes produced per round do { if (rounds >= 8) { if (dec_loop_ssse3_inner(s, o, &rounds) && dec_loop_ssse3_inner(s, o, &rounds) && dec_loop_ssse3_inner(s, o, &rounds) && dec_loop_ssse3_inner(s, o, &rounds) && dec_loop_ssse3_inner(s, o, &rounds) && dec_loop_ssse3_inner(s, o, &rounds) && dec_loop_ssse3_inner(s, o, &rounds) && dec_loop_ssse3_inner(s, o, &rounds)) { continue; } break; } if (rounds >= 4) { if (dec_loop_ssse3_inner(s, o, &rounds) && dec_loop_ssse3_inner(s, o, &rounds) && dec_loop_ssse3_inner(s, o, &rounds) && dec_loop_ssse3_inner(s, o, &rounds)) { continue; } break; } if (rounds >= 2) { if (dec_loop_ssse3_inner(s, o, &rounds) && dec_loop_ssse3_inner(s, o, &rounds)) { continue; } break; } dec_loop_ssse3_inner(s, o, &rounds); break; } while (rounds > 0); // Adjust for any rounds that were skipped: *slen += rounds * 16; *olen -= rounds * 12; } kitty-0.41.1/3rdparty/base64/lib/arch/ssse3/dec_reshuffle.c0000664000175000017510000000212114773370543022624 0ustar nileshnileshstatic inline __m128i dec_reshuffle (const __m128i in) { // in, bits, upper case are most significant bits, lower case are least significant bits // 00llllll 00kkkkLL 00jjKKKK 00JJJJJJ // 00iiiiii 00hhhhII 00ggHHHH 00GGGGGG // 00ffffff 00eeeeFF 00ddEEEE 00DDDDDD // 00cccccc 00bbbbCC 00aaBBBB 00AAAAAA const __m128i merge_ab_and_bc = _mm_maddubs_epi16(in, _mm_set1_epi32(0x01400140)); // 0000kkkk LLllllll 0000JJJJ JJjjKKKK // 0000hhhh IIiiiiii 0000GGGG GGggHHHH // 0000eeee FFffffff 0000DDDD DDddEEEE // 0000bbbb CCcccccc 0000AAAA AAaaBBBB const __m128i out = _mm_madd_epi16(merge_ab_and_bc, _mm_set1_epi32(0x00011000)); // 00000000 JJJJJJjj KKKKkkkk LLllllll // 00000000 GGGGGGgg HHHHhhhh IIiiiiii // 00000000 DDDDDDdd EEEEeeee FFffffff // 00000000 AAAAAAaa BBBBbbbb CCcccccc // Pack bytes together: return _mm_shuffle_epi8(out, _mm_setr_epi8( 2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, -1, -1, -1, -1)); // 00000000 00000000 00000000 00000000 // LLllllll KKKKkkkk JJJJJJjj IIiiiiii // HHHHhhhh GGGGGGgg FFffffff EEEEeeee // DDDDDDdd CCcccccc BBBBbbbb AAAAAAaa } kitty-0.41.1/3rdparty/base64/lib/arch/ssse3/enc_loop.c0000664000175000017510000000300014773370543021621 0ustar nileshnileshstatic inline void enc_loop_ssse3_inner (const uint8_t **s, uint8_t **o) { // Load input: __m128i str = _mm_loadu_si128((__m128i *) *s); // Reshuffle: str = enc_reshuffle(str); // Translate reshuffled bytes to the Base64 alphabet: str = enc_translate(str); // Store: _mm_storeu_si128((__m128i *) *o, str); *s += 12; *o += 16; } static inline void enc_loop_ssse3 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { if (*slen < 16) { return; } // Process blocks of 12 bytes at a time. Because blocks are loaded 16 // bytes at a time, ensure that there will be at least 4 remaining // bytes after the last round, so that the final read will not pass // beyond the bounds of the input buffer: size_t rounds = (*slen - 4) / 12; *slen -= rounds * 12; // 12 bytes consumed per round *olen += rounds * 16; // 16 bytes produced per round do { if (rounds >= 8) { enc_loop_ssse3_inner(s, o); enc_loop_ssse3_inner(s, o); enc_loop_ssse3_inner(s, o); enc_loop_ssse3_inner(s, o); enc_loop_ssse3_inner(s, o); enc_loop_ssse3_inner(s, o); enc_loop_ssse3_inner(s, o); enc_loop_ssse3_inner(s, o); rounds -= 8; continue; } if (rounds >= 4) { enc_loop_ssse3_inner(s, o); enc_loop_ssse3_inner(s, o); enc_loop_ssse3_inner(s, o); enc_loop_ssse3_inner(s, o); rounds -= 4; continue; } if (rounds >= 2) { enc_loop_ssse3_inner(s, o); enc_loop_ssse3_inner(s, o); rounds -= 2; continue; } enc_loop_ssse3_inner(s, o); break; } while (rounds > 0); } kitty-0.41.1/3rdparty/base64/lib/arch/ssse3/enc_loop_asm.c0000664000175000017510000002213614773370543022474 0ustar nileshnilesh// Apologies in advance for combining the preprocessor with inline assembly, // two notoriously gnarly parts of C, but it was necessary to avoid a lot of // code repetition. The preprocessor is used to template large sections of // inline assembly that differ only in the registers used. If the code was // written out by hand, it would become very large and hard to audit. // Generate a block of inline assembly that loads register R0 from memory. The // offset at which the register is loaded is set by the given round. #define LOAD(R0, ROUND) \ "lddqu ("#ROUND" * 12)(%[src]), %["R0"] \n\t" // Generate a block of inline assembly that deinterleaves and shuffles register // R0 using preloaded constants. Outputs in R0 and R1. #define SHUF(R0, R1) \ "pshufb %[lut0], %["R0"] \n\t" \ "movdqa %["R0"], %["R1"] \n\t" \ "pand %[msk0], %["R0"] \n\t" \ "pand %[msk2], %["R1"] \n\t" \ "pmulhuw %[msk1], %["R0"] \n\t" \ "pmullw %[msk3], %["R1"] \n\t" \ "por %["R1"], %["R0"] \n\t" // Generate a block of inline assembly that takes R0 and R1 and translates // their contents to the base64 alphabet, using preloaded constants. #define TRAN(R0, R1, R2) \ "movdqa %["R0"], %["R1"] \n\t" \ "movdqa %["R0"], %["R2"] \n\t" \ "psubusb %[n51], %["R1"] \n\t" \ "pcmpgtb %[n25], %["R2"] \n\t" \ "psubb %["R2"], %["R1"] \n\t" \ "movdqa %[lut1], %["R2"] \n\t" \ "pshufb %["R1"], %["R2"] \n\t" \ "paddb %["R2"], %["R0"] \n\t" // Generate a block of inline assembly that stores the given register R0 at an // offset set by the given round. #define STOR(R0, ROUND) \ "movdqu %["R0"], ("#ROUND" * 16)(%[dst]) \n\t" // Generate a block of inline assembly that generates a single self-contained // encoder round: fetch the data, process it, and store the result. Then update // the source and destination pointers. #define ROUND() \ LOAD("a", 0) \ SHUF("a", "b") \ TRAN("a", "b", "c") \ STOR("a", 0) \ "add $12, %[src] \n\t" \ "add $16, %[dst] \n\t" // Define a macro that initiates a three-way interleaved encoding round by // preloading registers a, b and c from memory. // The register graph shows which registers are in use during each step, and // is a visual aid for choosing registers for that step. Symbol index: // // + indicates that a register is loaded by that step. // | indicates that a register is in use and must not be touched. // - indicates that a register is decommissioned by that step. // x indicates that a register is used as a temporary by that step. // V indicates that a register is an input or output to the macro. // #define ROUND_3_INIT() /* a b c d e f */ \ LOAD("a", 0) /* + */ \ SHUF("a", "d") /* | + */ \ LOAD("b", 1) /* | + | */ \ TRAN("a", "d", "e") /* | | - x */ \ LOAD("c", 2) /* V V V */ // Define a macro that translates, shuffles and stores the input registers A, B // and C, and preloads registers D, E and F for the next round. // This macro can be arbitrarily daisy-chained by feeding output registers D, E // and F back into the next round as input registers A, B and C. The macro // carefully interleaves memory operations with data operations for optimal // pipelined performance. #define ROUND_3(ROUND, A,B,C,D,E,F) /* A B C D E F */ \ LOAD(D, (ROUND + 3)) /* V V V + */ \ SHUF(B, E) /* | | | | + */ \ STOR(A, (ROUND + 0)) /* - | | | | */ \ TRAN(B, E, F) /* | | | - x */ \ LOAD(E, (ROUND + 4)) /* | | | + */ \ SHUF(C, A) /* + | | | | */ \ STOR(B, (ROUND + 1)) /* | - | | | */ \ TRAN(C, A, F) /* - | | | x */ \ LOAD(F, (ROUND + 5)) /* | | | + */ \ SHUF(D, A) /* + | | | | */ \ STOR(C, (ROUND + 2)) /* | - | | | */ \ TRAN(D, A, B) /* - x V V V */ // Define a macro that terminates a ROUND_3 macro by taking pre-loaded // registers D, E and F, and translating, shuffling and storing them. #define ROUND_3_END(ROUND, A,B,C,D,E,F) /* A B C D E F */ \ SHUF(E, A) /* + V V V */ \ STOR(D, (ROUND + 3)) /* | - | | */ \ TRAN(E, A, B) /* - x | | */ \ SHUF(F, C) /* + | | */ \ STOR(E, (ROUND + 4)) /* | - | */ \ TRAN(F, C, D) /* - x | */ \ STOR(F, (ROUND + 5)) /* - */ // Define a type A round. Inputs are a, b, and c, outputs are d, e, and f. #define ROUND_3_A(ROUND) \ ROUND_3(ROUND, "a", "b", "c", "d", "e", "f") // Define a type B round. Inputs and outputs are swapped with regard to type A. #define ROUND_3_B(ROUND) \ ROUND_3(ROUND, "d", "e", "f", "a", "b", "c") // Terminating macro for a type A round. #define ROUND_3_A_LAST(ROUND) \ ROUND_3_A(ROUND) \ ROUND_3_END(ROUND, "a", "b", "c", "d", "e", "f") // Terminating macro for a type B round. #define ROUND_3_B_LAST(ROUND) \ ROUND_3_B(ROUND) \ ROUND_3_END(ROUND, "d", "e", "f", "a", "b", "c") // Suppress clang's warning that the literal string in the asm statement is // overlong (longer than the ISO-mandated minimum size of 4095 bytes for C99 // compilers). It may be true, but the goal here is not C99 portability. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Woverlength-strings" static inline void enc_loop_ssse3 (const uint8_t **s, size_t *slen, uint8_t **o, size_t *olen) { // For a clearer explanation of the algorithm used by this function, // please refer to the plain (not inline assembly) implementation. This // function follows the same basic logic. if (*slen < 16) { return; } // Process blocks of 12 bytes at a time. Input is read in blocks of 16 // bytes, so "reserve" four bytes from the input buffer to ensure that // we never read beyond the end of the input buffer. size_t rounds = (*slen - 4) / 12; *slen -= rounds * 12; // 12 bytes consumed per round *olen += rounds * 16; // 16 bytes produced per round // Number of times to go through the 36x loop. size_t loops = rounds / 36; // Number of rounds remaining after the 36x loop. rounds %= 36; // Lookup tables. const __m128i lut0 = _mm_set_epi8( 10, 11, 9, 10, 7, 8, 6, 7, 4, 5, 3, 4, 1, 2, 0, 1); const __m128i lut1 = _mm_setr_epi8( 65, 71, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -19, -16, 0, 0); // Temporary registers. __m128i a, b, c, d, e, f; __asm__ volatile ( // If there are 36 rounds or more, enter a 36x unrolled loop of // interleaved encoding rounds. The rounds interleave memory // operations (load/store) with data operations (table lookups, // etc) to maximize pipeline throughput. " test %[loops], %[loops] \n\t" " jz 18f \n\t" " jmp 36f \n\t" " \n\t" ".balign 64 \n\t" "36: " ROUND_3_INIT() " " ROUND_3_A( 0) " " ROUND_3_B( 3) " " ROUND_3_A( 6) " " ROUND_3_B( 9) " " ROUND_3_A(12) " " ROUND_3_B(15) " " ROUND_3_A(18) " " ROUND_3_B(21) " " ROUND_3_A(24) " " ROUND_3_B(27) " " ROUND_3_A_LAST(30) " add $(12 * 36), %[src] \n\t" " add $(16 * 36), %[dst] \n\t" " dec %[loops] \n\t" " jnz 36b \n\t" // Enter an 18x unrolled loop for rounds of 18 or more. "18: cmp $18, %[rounds] \n\t" " jl 9f \n\t" " " ROUND_3_INIT() " " ROUND_3_A(0) " " ROUND_3_B(3) " " ROUND_3_A(6) " " ROUND_3_B(9) " " ROUND_3_A_LAST(12) " sub $18, %[rounds] \n\t" " add $(12 * 18), %[src] \n\t" " add $(16 * 18), %[dst] \n\t" // Enter a 9x unrolled loop for rounds of 9 or more. "9: cmp $9, %[rounds] \n\t" " jl 6f \n\t" " " ROUND_3_INIT() " " ROUND_3_A(0) " " ROUND_3_B_LAST(3) " sub $9, %[rounds] \n\t" " add $(12 * 9), %[src] \n\t" " add $(16 * 9), %[dst] \n\t" // Enter a 6x unrolled loop for rounds of 6 or more. "6: cmp $6, %[rounds] \n\t" " jl 55f \n\t" " " ROUND_3_INIT() " " ROUND_3_A_LAST(0) " sub $6, %[rounds] \n\t" " add $(12 * 6), %[src] \n\t" " add $(16 * 6), %[dst] \n\t" // Dispatch the remaining rounds 0..5. "55: cmp $3, %[rounds] \n\t" " jg 45f \n\t" " je 3f \n\t" " cmp $1, %[rounds] \n\t" " jg 2f \n\t" " je 1f \n\t" " jmp 0f \n\t" "45: cmp $4, %[rounds] \n\t" " je 4f \n\t" // Block of non-interlaced encoding rounds, which can each // individually be jumped to. Rounds fall through to the next. "5: " ROUND() "4: " ROUND() "3: " ROUND() "2: " ROUND() "1: " ROUND() "0: \n\t" // Outputs (modified). : [rounds] "+r" (rounds), [loops] "+r" (loops), [src] "+r" (*s), [dst] "+r" (*o), [a] "=&x" (a), [b] "=&x" (b), [c] "=&x" (c), [d] "=&x" (d), [e] "=&x" (e), [f] "=&x" (f) // Inputs (not modified). : [lut0] "x" (lut0), [lut1] "x" (lut1), [msk0] "x" (_mm_set1_epi32(0x0FC0FC00)), [msk1] "x" (_mm_set1_epi32(0x04000040)), [msk2] "x" (_mm_set1_epi32(0x003F03F0)), [msk3] "x" (_mm_set1_epi32(0x01000010)), [n51] "x" (_mm_set1_epi8(51)), [n25] "x" (_mm_set1_epi8(25)) // Clobbers. : "cc", "memory" ); } #pragma GCC diagnostic pop kitty-0.41.1/3rdparty/base64/lib/arch/ssse3/enc_reshuffle.c0000664000175000017510000000273514773370543022651 0ustar nileshnileshstatic inline __m128i enc_reshuffle (__m128i in) { // Input, bytes MSB to LSB: // 0 0 0 0 l k j i h g f e d c b a in = _mm_shuffle_epi8(in, _mm_set_epi8( 10, 11, 9, 10, 7, 8, 6, 7, 4, 5, 3, 4, 1, 2, 0, 1)); // in, bytes MSB to LSB: // k l j k // h i g h // e f d e // b c a b const __m128i t0 = _mm_and_si128(in, _mm_set1_epi32(0x0FC0FC00)); // bits, upper case are most significant bits, lower case are least significant bits // 0000kkkk LL000000 JJJJJJ00 00000000 // 0000hhhh II000000 GGGGGG00 00000000 // 0000eeee FF000000 DDDDDD00 00000000 // 0000bbbb CC000000 AAAAAA00 00000000 const __m128i t1 = _mm_mulhi_epu16(t0, _mm_set1_epi32(0x04000040)); // 00000000 00kkkkLL 00000000 00JJJJJJ // 00000000 00hhhhII 00000000 00GGGGGG // 00000000 00eeeeFF 00000000 00DDDDDD // 00000000 00bbbbCC 00000000 00AAAAAA const __m128i t2 = _mm_and_si128(in, _mm_set1_epi32(0x003F03F0)); // 00000000 00llllll 000000jj KKKK0000 // 00000000 00iiiiii 000000gg HHHH0000 // 00000000 00ffffff 000000dd EEEE0000 // 00000000 00cccccc 000000aa BBBB0000 const __m128i t3 = _mm_mullo_epi16(t2, _mm_set1_epi32(0x01000010)); // 00llllll 00000000 00jjKKKK 00000000 // 00iiiiii 00000000 00ggHHHH 00000000 // 00ffffff 00000000 00ddEEEE 00000000 // 00cccccc 00000000 00aaBBBB 00000000 return _mm_or_si128(t1, t3); // 00llllll 00kkkkLL 00jjKKKK 00JJJJJJ // 00iiiiii 00hhhhII 00ggHHHH 00GGGGGG // 00ffffff 00eeeeFF 00ddEEEE 00DDDDDD // 00cccccc 00bbbbCC 00aaBBBB 00AAAAAA } kitty-0.41.1/3rdparty/base64/lib/arch/ssse3/enc_translate.c0000664000175000017510000000220614773370543022654 0ustar nileshnileshstatic inline __m128i enc_translate (const __m128i in) { // A lookup table containing the absolute offsets for all ranges: const __m128i lut = _mm_setr_epi8( 65, 71, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -19, -16, 0, 0 ); // Translate values 0..63 to the Base64 alphabet. There are five sets: // # From To Abs Index Characters // 0 [0..25] [65..90] +65 0 ABCDEFGHIJKLMNOPQRSTUVWXYZ // 1 [26..51] [97..122] +71 1 abcdefghijklmnopqrstuvwxyz // 2 [52..61] [48..57] -4 [2..11] 0123456789 // 3 [62] [43] -19 12 + // 4 [63] [47] -16 13 / // Create LUT indices from the input. The index for range #0 is right, // others are 1 less than expected: __m128i indices = _mm_subs_epu8(in, _mm_set1_epi8(51)); // mask is 0xFF (-1) for range #[1..4] and 0x00 for range #0: __m128i mask = _mm_cmpgt_epi8(in, _mm_set1_epi8(25)); // Subtract -1, so add 1 to indices for range #[1..4]. All indices are // now correct: indices = _mm_sub_epi8(indices, mask); // Add offsets to input values: return _mm_add_epi8(in, _mm_shuffle_epi8(lut, indices)); } kitty-0.41.1/3rdparty/base64/lib/codec_choose.c0000664000175000017510000001613014773370543020473 0ustar nileshnilesh#include #include #include #include #include #include "../include/libbase64.h" #include "codecs.h" #include "config.h" #include "env.h" #if (__x86_64__ || __i386__ || _M_X86 || _M_X64) #define BASE64_X86 #if (HAVE_SSSE3 || HAVE_SSE41 || HAVE_SSE42 || HAVE_AVX || HAVE_AVX2 || HAVE_AVX512) #define BASE64_X86_SIMD #endif #endif #ifdef BASE64_X86 #ifdef _MSC_VER #include #define __cpuid_count(__level, __count, __eax, __ebx, __ecx, __edx) \ { \ int info[4]; \ __cpuidex(info, __level, __count); \ __eax = info[0]; \ __ebx = info[1]; \ __ecx = info[2]; \ __edx = info[3]; \ } #define __cpuid(__level, __eax, __ebx, __ecx, __edx) \ __cpuid_count(__level, 0, __eax, __ebx, __ecx, __edx) #else #include #if HAVE_AVX512 || HAVE_AVX2 || HAVE_AVX #if ((__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 2) || (__clang_major__ >= 3)) static inline uint64_t _xgetbv (uint32_t index) { uint32_t eax, edx; __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index)); return ((uint64_t)edx << 32) | eax; } #else #error "Platform not supported" #endif #endif #endif #ifndef bit_AVX512vl #define bit_AVX512vl (1 << 31) #endif #ifndef bit_AVX512vbmi #define bit_AVX512vbmi (1 << 1) #endif #ifndef bit_AVX2 #define bit_AVX2 (1 << 5) #endif #ifndef bit_SSSE3 #define bit_SSSE3 (1 << 9) #endif #ifndef bit_SSE41 #define bit_SSE41 (1 << 19) #endif #ifndef bit_SSE42 #define bit_SSE42 (1 << 20) #endif #ifndef bit_AVX #define bit_AVX (1 << 28) #endif #define bit_XSAVE_XRSTORE (1 << 27) #ifndef _XCR_XFEATURE_ENABLED_MASK #define _XCR_XFEATURE_ENABLED_MASK 0 #endif #define _XCR_XMM_AND_YMM_STATE_ENABLED_BY_OS 0x6 #endif // Function declarations: #define BASE64_CODEC_FUNCS(arch) \ BASE64_ENC_FUNCTION(arch); \ BASE64_DEC_FUNCTION(arch); \ BASE64_CODEC_FUNCS(avx512) BASE64_CODEC_FUNCS(avx2) BASE64_CODEC_FUNCS(neon32) BASE64_CODEC_FUNCS(neon64) BASE64_CODEC_FUNCS(plain) BASE64_CODEC_FUNCS(ssse3) BASE64_CODEC_FUNCS(sse41) BASE64_CODEC_FUNCS(sse42) BASE64_CODEC_FUNCS(avx) static bool codec_choose_forced (struct codec *codec, int flags) { // If the user wants to use a certain codec, // always allow it, even if the codec is a no-op. // For testing purposes. if (!(flags & 0xFFFF)) { return false; } if (flags & BASE64_FORCE_AVX2) { codec->enc = base64_stream_encode_avx2; codec->dec = base64_stream_decode_avx2; return true; } if (flags & BASE64_FORCE_NEON32) { codec->enc = base64_stream_encode_neon32; codec->dec = base64_stream_decode_neon32; return true; } if (flags & BASE64_FORCE_NEON64) { codec->enc = base64_stream_encode_neon64; codec->dec = base64_stream_decode_neon64; return true; } if (flags & BASE64_FORCE_PLAIN) { codec->enc = base64_stream_encode_plain; codec->dec = base64_stream_decode_plain; return true; } if (flags & BASE64_FORCE_SSSE3) { codec->enc = base64_stream_encode_ssse3; codec->dec = base64_stream_decode_ssse3; return true; } if (flags & BASE64_FORCE_SSE41) { codec->enc = base64_stream_encode_sse41; codec->dec = base64_stream_decode_sse41; return true; } if (flags & BASE64_FORCE_SSE42) { codec->enc = base64_stream_encode_sse42; codec->dec = base64_stream_decode_sse42; return true; } if (flags & BASE64_FORCE_AVX) { codec->enc = base64_stream_encode_avx; codec->dec = base64_stream_decode_avx; return true; } if (flags & BASE64_FORCE_AVX512) { codec->enc = base64_stream_encode_avx512; codec->dec = base64_stream_decode_avx512; return true; } return false; } static bool codec_choose_arm (struct codec *codec) { #if (defined(__ARM_NEON__) || defined(__ARM_NEON)) && ((defined(__aarch64__) && HAVE_NEON64) || HAVE_NEON32) // Unfortunately there is no portable way to check for NEON // support at runtime from userland in the same way that x86 // has cpuid, so just stick to the compile-time configuration: #if defined(__aarch64__) && HAVE_NEON64 codec->enc = base64_stream_encode_neon64; codec->dec = base64_stream_decode_neon64; #else codec->enc = base64_stream_encode_neon32; codec->dec = base64_stream_decode_neon32; #endif return true; #else (void)codec; return false; #endif } static bool codec_choose_x86 (struct codec *codec) { #ifdef BASE64_X86_SIMD unsigned int eax, ebx = 0, ecx = 0, edx; unsigned int max_level; #ifdef _MSC_VER int info[4]; __cpuidex(info, 0, 0); max_level = info[0]; #else max_level = __get_cpuid_max(0, NULL); #endif #if HAVE_AVX512 || HAVE_AVX2 || HAVE_AVX // Check for AVX/AVX2/AVX512 support: // Checking for AVX requires 3 things: // 1) CPUID indicates that the OS uses XSAVE and XRSTORE instructions // (allowing saving YMM registers on context switch) // 2) CPUID indicates support for AVX // 3) XGETBV indicates the AVX registers will be saved and restored on // context switch // // Note that XGETBV is only available on 686 or later CPUs, so the // instruction needs to be conditionally run. if (max_level >= 1) { __cpuid_count(1, 0, eax, ebx, ecx, edx); if (ecx & bit_XSAVE_XRSTORE) { uint64_t xcr_mask; xcr_mask = _xgetbv(_XCR_XFEATURE_ENABLED_MASK); if ((xcr_mask & _XCR_XMM_AND_YMM_STATE_ENABLED_BY_OS) == _XCR_XMM_AND_YMM_STATE_ENABLED_BY_OS) { // check multiple bits at once #if HAVE_AVX512 if (max_level >= 7) { __cpuid_count(7, 0, eax, ebx, ecx, edx); if ((ebx & bit_AVX512vl) && (ecx & bit_AVX512vbmi)) { codec->enc = base64_stream_encode_avx512; codec->dec = base64_stream_decode_avx512; return true; } } #endif #if HAVE_AVX2 if (max_level >= 7) { __cpuid_count(7, 0, eax, ebx, ecx, edx); if (ebx & bit_AVX2) { codec->enc = base64_stream_encode_avx2; codec->dec = base64_stream_decode_avx2; return true; } } #endif #if HAVE_AVX __cpuid_count(1, 0, eax, ebx, ecx, edx); if (ecx & bit_AVX) { codec->enc = base64_stream_encode_avx; codec->dec = base64_stream_decode_avx; return true; } #endif } } } #endif #if HAVE_SSE42 // Check for SSE42 support: if (max_level >= 1) { __cpuid(1, eax, ebx, ecx, edx); if (ecx & bit_SSE42) { codec->enc = base64_stream_encode_sse42; codec->dec = base64_stream_decode_sse42; return true; } } #endif #if HAVE_SSE41 // Check for SSE41 support: if (max_level >= 1) { __cpuid(1, eax, ebx, ecx, edx); if (ecx & bit_SSE41) { codec->enc = base64_stream_encode_sse41; codec->dec = base64_stream_decode_sse41; return true; } } #endif #if HAVE_SSSE3 // Check for SSSE3 support: if (max_level >= 1) { __cpuid(1, eax, ebx, ecx, edx); if (ecx & bit_SSSE3) { codec->enc = base64_stream_encode_ssse3; codec->dec = base64_stream_decode_ssse3; return true; } } #endif #else (void)codec; #endif return false; } void codec_choose (struct codec *codec, int flags) { // User forced a codec: if (codec_choose_forced(codec, flags)) { return; } // Runtime feature detection: if (codec_choose_arm(codec)) { return; } if (codec_choose_x86(codec)) { return; } codec->enc = base64_stream_encode_plain; codec->dec = base64_stream_decode_plain; } kitty-0.41.1/3rdparty/base64/lib/codecs.h0000664000175000017510000000256014773370543017325 0ustar nileshnilesh#include #include #include "../include/libbase64.h" #include "config.h" // Function parameters for encoding functions: #define BASE64_ENC_PARAMS \ ( struct base64_state *state \ , const char *src \ , size_t srclen \ , char *out \ , size_t *outlen \ ) // Function parameters for decoding functions: #define BASE64_DEC_PARAMS \ ( struct base64_state *state \ , const char *src \ , size_t srclen \ , char *out \ , size_t *outlen \ ) // Function signature for encoding functions: #define BASE64_ENC_FUNCTION(arch) \ void \ base64_stream_encode_ ## arch \ BASE64_ENC_PARAMS // Function signature for decoding functions: #define BASE64_DEC_FUNCTION(arch) \ int \ base64_stream_decode_ ## arch \ BASE64_DEC_PARAMS // Cast away unused variable, silence compiler: #define UNUSED(x) ((void)(x)) // Stub function when encoder arch unsupported: #define BASE64_ENC_STUB \ UNUSED(state); \ UNUSED(src); \ UNUSED(srclen); \ UNUSED(out); \ \ *outlen = 0; // Stub function when decoder arch unsupported: #define BASE64_DEC_STUB \ UNUSED(state); \ UNUSED(src); \ UNUSED(srclen); \ UNUSED(out); \ UNUSED(outlen); \ \ return -1; struct codec { void (* enc) BASE64_ENC_PARAMS; int (* dec) BASE64_DEC_PARAMS; }; extern void codec_choose (struct codec *, int flags); kitty-0.41.1/3rdparty/base64/lib/env.h0000664000175000017510000000404214773370543016652 0ustar nileshnilesh#ifndef BASE64_ENV_H #define BASE64_ENV_H // This header file contains macro definitions that describe certain aspects of // the compile-time environment. Compatibility and portability macros go here. // Define machine endianness. This is for GCC: #if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) # define BASE64_LITTLE_ENDIAN 1 #else # define BASE64_LITTLE_ENDIAN 0 #endif // This is for Clang: #ifdef __LITTLE_ENDIAN__ # define BASE64_LITTLE_ENDIAN 1 #endif #ifdef __BIG_ENDIAN__ # define BASE64_LITTLE_ENDIAN 0 #endif // MSVC++ needs intrin.h for _byteswap_uint64 (issue #68): #if BASE64_LITTLE_ENDIAN && defined(_MSC_VER) # include #endif // Endian conversion functions: #if BASE64_LITTLE_ENDIAN # ifdef _MSC_VER // Microsoft Visual C++: # define BASE64_HTOBE32(x) _byteswap_ulong(x) # define BASE64_HTOBE64(x) _byteswap_uint64(x) # else // GCC and Clang: # define BASE64_HTOBE32(x) __builtin_bswap32(x) # define BASE64_HTOBE64(x) __builtin_bswap64(x) # endif #else // No conversion needed: # define BASE64_HTOBE32(x) (x) # define BASE64_HTOBE64(x) (x) #endif // Detect word size: #if defined (__x86_64__) // This also works for the x32 ABI, which has a 64-bit word size. # define BASE64_WORDSIZE 64 #elif defined (_INTEGRAL_MAX_BITS) # define BASE64_WORDSIZE _INTEGRAL_MAX_BITS #elif defined (__WORDSIZE) # define BASE64_WORDSIZE __WORDSIZE #elif defined (__SIZE_WIDTH__) # define BASE64_WORDSIZE __SIZE_WIDTH__ #else # error BASE64_WORDSIZE_NOT_DEFINED #endif // End-of-file definitions. // Almost end-of-file when waiting for the last '=' character: #define BASE64_AEOF 1 // End-of-file when stream end has been reached or invalid input provided: #define BASE64_EOF 2 // GCC 7 defaults to issuing a warning for fallthrough in switch statements, // unless the fallthrough cases are marked with an attribute. As we use // fallthrough deliberately, define an alias for the attribute: #if __GNUC__ >= 7 # define BASE64_FALLTHROUGH __attribute__((fallthrough)); #else # define BASE64_FALLTHROUGH #endif #endif // BASE64_ENV_H kitty-0.41.1/3rdparty/base64/lib/exports.txt0000664000175000017510000000022514773370543020155 0ustar nileshnileshbase64_encode base64_stream_encode base64_stream_encode_init base64_stream_encode_final base64_decode base64_stream_decode base64_stream_decode_init kitty-0.41.1/3rdparty/base64/lib/lib.c0000664000175000017510000000632114773370543016625 0ustar nileshnilesh#include #include #ifdef _OPENMP #include #endif #include "../include/libbase64.h" #include "tables/tables.h" #include "codecs.h" #include "env.h" // These static function pointers are initialized once when the library is // first used, and remain in use for the remaining lifetime of the program. // The idea being that CPU features don't change at runtime. static struct codec codec = { NULL, NULL }; void base64_stream_encode_init (struct base64_state *state, int flags) { // If any of the codec flags are set, redo choice: if (codec.enc == NULL || flags & 0xFF) { codec_choose(&codec, flags); } state->eof = 0; state->bytes = 0; state->carry = 0; state->flags = flags; } void base64_stream_encode ( struct base64_state *state , const char *src , size_t srclen , char *out , size_t *outlen ) { codec.enc(state, src, srclen, out, outlen); } void base64_stream_encode_final ( struct base64_state *state , char *out , size_t *outlen ) { uint8_t *o = (uint8_t *)out; if (state->bytes == 1) { *o++ = base64_table_enc_6bit[state->carry]; *o++ = '='; *o++ = '='; *outlen = 3; return; } if (state->bytes == 2) { *o++ = base64_table_enc_6bit[state->carry]; *o++ = '='; *outlen = 2; return; } *outlen = 0; } void base64_stream_decode_init (struct base64_state *state, int flags) { // If any of the codec flags are set, redo choice: if (codec.dec == NULL || flags & 0xFFFF) { codec_choose(&codec, flags); } state->eof = 0; state->bytes = 0; state->carry = 0; state->flags = flags; } int base64_stream_decode ( struct base64_state *state , const char *src , size_t srclen , char *out , size_t *outlen ) { return codec.dec(state, src, srclen, out, outlen); } #ifdef _OPENMP // Due to the overhead of initializing OpenMP and creating a team of // threads, we require the data length to be larger than a threshold: #define OMP_THRESHOLD 20000 // Conditionally include OpenMP-accelerated codec implementations: #include "lib_openmp.c" #endif void base64_encode ( const char *src , size_t srclen , char *out , size_t *outlen , int flags ) { size_t s; size_t t; struct base64_state state; #ifdef _OPENMP if (srclen >= OMP_THRESHOLD) { base64_encode_openmp(src, srclen, out, outlen, flags); return; } #endif // Init the stream reader: base64_stream_encode_init(&state, flags); // Feed the whole string to the stream reader: base64_stream_encode(&state, src, srclen, out, &s); // Finalize the stream by writing trailer if any: base64_stream_encode_final(&state, out + s, &t); // Final output length is stream length plus tail: *outlen = s + t; } int base64_decode ( const char *src , size_t srclen , char *out , size_t *outlen , int flags ) { int ret; struct base64_state state; #ifdef _OPENMP if (srclen >= OMP_THRESHOLD) { return base64_decode_openmp(src, srclen, out, outlen, flags); } #endif // Init the stream reader: base64_stream_decode_init(&state, flags); // Feed the whole string to the stream reader: ret = base64_stream_decode(&state, src, srclen, out, outlen); // If when decoding a whole block, we're still waiting for input then fail: if (ret && (state.bytes == 0)) { return ret; } return 0; } kitty-0.41.1/3rdparty/base64/lib/lib_openmp.c0000664000175000017510000001055114773370543020203 0ustar nileshnilesh// This code makes some assumptions on the implementation of // base64_stream_encode_init(), base64_stream_encode() and base64_stream_decode(). // Basically these assumptions boil down to that when breaking the src into // parts, out parts can be written without side effects. // This is met when: // 1) base64_stream_encode() and base64_stream_decode() don't use globals; // 2) the shared variables src and out are not read or written outside of the // bounds of their parts, i.e. when base64_stream_encode() reads a multiple // of 3 bytes, it must write no more then a multiple of 4 bytes, not even // temporarily; // 3) the state flag can be discarded after base64_stream_encode() and // base64_stream_decode() on the parts. static inline void base64_encode_openmp ( const char *src , size_t srclen , char *out , size_t *outlen , int flags ) { size_t s; size_t t; size_t sum = 0, len, last_len; struct base64_state state, initial_state; int num_threads, i; // Request a number of threads but not necessarily get them: #pragma omp parallel { // Get the number of threads used from one thread only, // as num_threads is a shared var: #pragma omp single { num_threads = omp_get_num_threads(); // Split the input string into num_threads parts, each // part a multiple of 3 bytes. The remaining bytes will // be done later: len = srclen / (num_threads * 3); len *= 3; last_len = srclen - num_threads * len; // Init the stream reader: base64_stream_encode_init(&state, flags); initial_state = state; } // Single has an implicit barrier for all threads to wait here // for the above to complete: #pragma omp for firstprivate(state) private(s) reduction(+:sum) schedule(static,1) for (i = 0; i < num_threads; i++) { // Feed each part of the string to the stream reader: base64_stream_encode(&state, src + i * len, len, out + i * len * 4 / 3, &s); sum += s; } } // As encoding should never fail and we encode an exact multiple // of 3 bytes, we can discard state: state = initial_state; // Encode the remaining bytes: base64_stream_encode(&state, src + num_threads * len, last_len, out + num_threads * len * 4 / 3, &s); // Finalize the stream by writing trailer if any: base64_stream_encode_final(&state, out + num_threads * len * 4 / 3 + s, &t); // Final output length is stream length plus tail: sum += s + t; *outlen = sum; } static inline int base64_decode_openmp ( const char *src , size_t srclen , char *out , size_t *outlen , int flags ) { int num_threads, result = 0, i; size_t sum = 0, len, last_len, s; struct base64_state state, initial_state; // Request a number of threads but not necessarily get them: #pragma omp parallel { // Get the number of threads used from one thread only, // as num_threads is a shared var: #pragma omp single { num_threads = omp_get_num_threads(); // Split the input string into num_threads parts, each // part a multiple of 4 bytes. The remaining bytes will // be done later: len = srclen / (num_threads * 4); len *= 4; last_len = srclen - num_threads * len; // Init the stream reader: base64_stream_decode_init(&state, flags); initial_state = state; } // Single has an implicit barrier to wait here for the above to // complete: #pragma omp for firstprivate(state) private(s) reduction(+:sum, result) schedule(static,1) for (i = 0; i < num_threads; i++) { int this_result; // Feed each part of the string to the stream reader: this_result = base64_stream_decode(&state, src + i * len, len, out + i * len * 3 / 4, &s); sum += s; result += this_result; } } // If `result' equals `-num_threads', then all threads returned -1, // indicating that the requested codec is not available: if (result == -num_threads) { return -1; } // If `result' does not equal `num_threads', then at least one of the // threads hit a decode error: if (result != num_threads) { return 0; } // So far so good, now decode whatever remains in the buffer. Reuse the // initial state, since we are at a 4-byte boundary: state = initial_state; result = base64_stream_decode(&state, src + num_threads * len, last_len, out + num_threads * len * 3 / 4, &s); sum += s; *outlen = sum; // If when decoding a whole block, we're still waiting for input then fail: if (result && (state.bytes == 0)) { return result; } return 0; } kitty-0.41.1/3rdparty/base64/lib/tables/0000775000175000017510000000000014773370543017163 5ustar nileshnileshkitty-0.41.1/3rdparty/base64/lib/tables/.gitignore0000664000175000017510000000002014773370543021143 0ustar nileshnileshtable_generator kitty-0.41.1/3rdparty/base64/lib/tables/Makefile0000664000175000017510000000043414773370543020624 0ustar nileshnilesh.PHONY: all clean TARGETS := table_dec_32bit.h table_enc_12bit.h table_generator all: $(TARGETS) clean: $(RM) $(TARGETS) table_dec_32bit.h: table_generator ./$^ > $@ table_enc_12bit.h: table_enc_12bit.py ./$^ > $@ table_generator: table_generator.c $(CC) $(CFLAGS) -o $@ $^ kitty-0.41.1/3rdparty/base64/lib/tables/table_dec_32bit.h0000664000175000017510000006125214773370543022247 0ustar nileshnilesh#include #define CHAR62 '+' #define CHAR63 '/' #define CHARPAD '=' #if BASE64_LITTLE_ENDIAN /* SPECIAL DECODE TABLES FOR LITTLE ENDIAN (INTEL) CPUS */ const uint32_t base64_table_dec_32bit_d0[256] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x000000f8, 0xffffffff, 0xffffffff, 0xffffffff, 0x000000fc, 0x000000d0, 0x000000d4, 0x000000d8, 0x000000dc, 0x000000e0, 0x000000e4, 0x000000e8, 0x000000ec, 0x000000f0, 0x000000f4, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000004, 0x00000008, 0x0000000c, 0x00000010, 0x00000014, 0x00000018, 0x0000001c, 0x00000020, 0x00000024, 0x00000028, 0x0000002c, 0x00000030, 0x00000034, 0x00000038, 0x0000003c, 0x00000040, 0x00000044, 0x00000048, 0x0000004c, 0x00000050, 0x00000054, 0x00000058, 0x0000005c, 0x00000060, 0x00000064, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000068, 0x0000006c, 0x00000070, 0x00000074, 0x00000078, 0x0000007c, 0x00000080, 0x00000084, 0x00000088, 0x0000008c, 0x00000090, 0x00000094, 0x00000098, 0x0000009c, 0x000000a0, 0x000000a4, 0x000000a8, 0x000000ac, 0x000000b0, 0x000000b4, 0x000000b8, 0x000000bc, 0x000000c0, 0x000000c4, 0x000000c8, 0x000000cc, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }; const uint32_t base64_table_dec_32bit_d1[256] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x0000e003, 0xffffffff, 0xffffffff, 0xffffffff, 0x0000f003, 0x00004003, 0x00005003, 0x00006003, 0x00007003, 0x00008003, 0x00009003, 0x0000a003, 0x0000b003, 0x0000c003, 0x0000d003, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00001000, 0x00002000, 0x00003000, 0x00004000, 0x00005000, 0x00006000, 0x00007000, 0x00008000, 0x00009000, 0x0000a000, 0x0000b000, 0x0000c000, 0x0000d000, 0x0000e000, 0x0000f000, 0x00000001, 0x00001001, 0x00002001, 0x00003001, 0x00004001, 0x00005001, 0x00006001, 0x00007001, 0x00008001, 0x00009001, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x0000a001, 0x0000b001, 0x0000c001, 0x0000d001, 0x0000e001, 0x0000f001, 0x00000002, 0x00001002, 0x00002002, 0x00003002, 0x00004002, 0x00005002, 0x00006002, 0x00007002, 0x00008002, 0x00009002, 0x0000a002, 0x0000b002, 0x0000c002, 0x0000d002, 0x0000e002, 0x0000f002, 0x00000003, 0x00001003, 0x00002003, 0x00003003, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }; const uint32_t base64_table_dec_32bit_d2[256] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00800f00, 0xffffffff, 0xffffffff, 0xffffffff, 0x00c00f00, 0x00000d00, 0x00400d00, 0x00800d00, 0x00c00d00, 0x00000e00, 0x00400e00, 0x00800e00, 0x00c00e00, 0x00000f00, 0x00400f00, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00400000, 0x00800000, 0x00c00000, 0x00000100, 0x00400100, 0x00800100, 0x00c00100, 0x00000200, 0x00400200, 0x00800200, 0x00c00200, 0x00000300, 0x00400300, 0x00800300, 0x00c00300, 0x00000400, 0x00400400, 0x00800400, 0x00c00400, 0x00000500, 0x00400500, 0x00800500, 0x00c00500, 0x00000600, 0x00400600, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00800600, 0x00c00600, 0x00000700, 0x00400700, 0x00800700, 0x00c00700, 0x00000800, 0x00400800, 0x00800800, 0x00c00800, 0x00000900, 0x00400900, 0x00800900, 0x00c00900, 0x00000a00, 0x00400a00, 0x00800a00, 0x00c00a00, 0x00000b00, 0x00400b00, 0x00800b00, 0x00c00b00, 0x00000c00, 0x00400c00, 0x00800c00, 0x00c00c00, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }; const uint32_t base64_table_dec_32bit_d3[256] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x003e0000, 0xffffffff, 0xffffffff, 0xffffffff, 0x003f0000, 0x00340000, 0x00350000, 0x00360000, 0x00370000, 0x00380000, 0x00390000, 0x003a0000, 0x003b0000, 0x003c0000, 0x003d0000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00010000, 0x00020000, 0x00030000, 0x00040000, 0x00050000, 0x00060000, 0x00070000, 0x00080000, 0x00090000, 0x000a0000, 0x000b0000, 0x000c0000, 0x000d0000, 0x000e0000, 0x000f0000, 0x00100000, 0x00110000, 0x00120000, 0x00130000, 0x00140000, 0x00150000, 0x00160000, 0x00170000, 0x00180000, 0x00190000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x001a0000, 0x001b0000, 0x001c0000, 0x001d0000, 0x001e0000, 0x001f0000, 0x00200000, 0x00210000, 0x00220000, 0x00230000, 0x00240000, 0x00250000, 0x00260000, 0x00270000, 0x00280000, 0x00290000, 0x002a0000, 0x002b0000, 0x002c0000, 0x002d0000, 0x002e0000, 0x002f0000, 0x00300000, 0x00310000, 0x00320000, 0x00330000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }; #else /* SPECIAL DECODE TABLES FOR BIG ENDIAN (IBM/MOTOROLA/SUN) CPUS */ const uint32_t base64_table_dec_32bit_d0[256] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xf8000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xfc000000, 0xd0000000, 0xd4000000, 0xd8000000, 0xdc000000, 0xe0000000, 0xe4000000, 0xe8000000, 0xec000000, 0xf0000000, 0xf4000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x04000000, 0x08000000, 0x0c000000, 0x10000000, 0x14000000, 0x18000000, 0x1c000000, 0x20000000, 0x24000000, 0x28000000, 0x2c000000, 0x30000000, 0x34000000, 0x38000000, 0x3c000000, 0x40000000, 0x44000000, 0x48000000, 0x4c000000, 0x50000000, 0x54000000, 0x58000000, 0x5c000000, 0x60000000, 0x64000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x68000000, 0x6c000000, 0x70000000, 0x74000000, 0x78000000, 0x7c000000, 0x80000000, 0x84000000, 0x88000000, 0x8c000000, 0x90000000, 0x94000000, 0x98000000, 0x9c000000, 0xa0000000, 0xa4000000, 0xa8000000, 0xac000000, 0xb0000000, 0xb4000000, 0xb8000000, 0xbc000000, 0xc0000000, 0xc4000000, 0xc8000000, 0xcc000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }; const uint32_t base64_table_dec_32bit_d1[256] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x03e00000, 0xffffffff, 0xffffffff, 0xffffffff, 0x03f00000, 0x03400000, 0x03500000, 0x03600000, 0x03700000, 0x03800000, 0x03900000, 0x03a00000, 0x03b00000, 0x03c00000, 0x03d00000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00100000, 0x00200000, 0x00300000, 0x00400000, 0x00500000, 0x00600000, 0x00700000, 0x00800000, 0x00900000, 0x00a00000, 0x00b00000, 0x00c00000, 0x00d00000, 0x00e00000, 0x00f00000, 0x01000000, 0x01100000, 0x01200000, 0x01300000, 0x01400000, 0x01500000, 0x01600000, 0x01700000, 0x01800000, 0x01900000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x01a00000, 0x01b00000, 0x01c00000, 0x01d00000, 0x01e00000, 0x01f00000, 0x02000000, 0x02100000, 0x02200000, 0x02300000, 0x02400000, 0x02500000, 0x02600000, 0x02700000, 0x02800000, 0x02900000, 0x02a00000, 0x02b00000, 0x02c00000, 0x02d00000, 0x02e00000, 0x02f00000, 0x03000000, 0x03100000, 0x03200000, 0x03300000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }; const uint32_t base64_table_dec_32bit_d2[256] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x000f8000, 0xffffffff, 0xffffffff, 0xffffffff, 0x000fc000, 0x000d0000, 0x000d4000, 0x000d8000, 0x000dc000, 0x000e0000, 0x000e4000, 0x000e8000, 0x000ec000, 0x000f0000, 0x000f4000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00004000, 0x00008000, 0x0000c000, 0x00010000, 0x00014000, 0x00018000, 0x0001c000, 0x00020000, 0x00024000, 0x00028000, 0x0002c000, 0x00030000, 0x00034000, 0x00038000, 0x0003c000, 0x00040000, 0x00044000, 0x00048000, 0x0004c000, 0x00050000, 0x00054000, 0x00058000, 0x0005c000, 0x00060000, 0x00064000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00068000, 0x0006c000, 0x00070000, 0x00074000, 0x00078000, 0x0007c000, 0x00080000, 0x00084000, 0x00088000, 0x0008c000, 0x00090000, 0x00094000, 0x00098000, 0x0009c000, 0x000a0000, 0x000a4000, 0x000a8000, 0x000ac000, 0x000b0000, 0x000b4000, 0x000b8000, 0x000bc000, 0x000c0000, 0x000c4000, 0x000c8000, 0x000cc000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }; const uint32_t base64_table_dec_32bit_d3[256] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00003e00, 0xffffffff, 0xffffffff, 0xffffffff, 0x00003f00, 0x00003400, 0x00003500, 0x00003600, 0x00003700, 0x00003800, 0x00003900, 0x00003a00, 0x00003b00, 0x00003c00, 0x00003d00, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000100, 0x00000200, 0x00000300, 0x00000400, 0x00000500, 0x00000600, 0x00000700, 0x00000800, 0x00000900, 0x00000a00, 0x00000b00, 0x00000c00, 0x00000d00, 0x00000e00, 0x00000f00, 0x00001000, 0x00001100, 0x00001200, 0x00001300, 0x00001400, 0x00001500, 0x00001600, 0x00001700, 0x00001800, 0x00001900, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00001a00, 0x00001b00, 0x00001c00, 0x00001d00, 0x00001e00, 0x00001f00, 0x00002000, 0x00002100, 0x00002200, 0x00002300, 0x00002400, 0x00002500, 0x00002600, 0x00002700, 0x00002800, 0x00002900, 0x00002a00, 0x00002b00, 0x00002c00, 0x00002d00, 0x00002e00, 0x00002f00, 0x00003000, 0x00003100, 0x00003200, 0x00003300, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }; #endif kitty-0.41.1/3rdparty/base64/lib/tables/table_enc_12bit.h0000664000175000017510000022215214773370543022255 0ustar nileshnilesh#include const uint16_t base64_table_enc_12bit[] = { #if BASE64_LITTLE_ENDIAN 0x4141U, 0x4241U, 0x4341U, 0x4441U, 0x4541U, 0x4641U, 0x4741U, 0x4841U, 0x4941U, 0x4A41U, 0x4B41U, 0x4C41U, 0x4D41U, 0x4E41U, 0x4F41U, 0x5041U, 0x5141U, 0x5241U, 0x5341U, 0x5441U, 0x5541U, 0x5641U, 0x5741U, 0x5841U, 0x5941U, 0x5A41U, 0x6141U, 0x6241U, 0x6341U, 0x6441U, 0x6541U, 0x6641U, 0x6741U, 0x6841U, 0x6941U, 0x6A41U, 0x6B41U, 0x6C41U, 0x6D41U, 0x6E41U, 0x6F41U, 0x7041U, 0x7141U, 0x7241U, 0x7341U, 0x7441U, 0x7541U, 0x7641U, 0x7741U, 0x7841U, 0x7941U, 0x7A41U, 0x3041U, 0x3141U, 0x3241U, 0x3341U, 0x3441U, 0x3541U, 0x3641U, 0x3741U, 0x3841U, 0x3941U, 0x2B41U, 0x2F41U, 0x4142U, 0x4242U, 0x4342U, 0x4442U, 0x4542U, 0x4642U, 0x4742U, 0x4842U, 0x4942U, 0x4A42U, 0x4B42U, 0x4C42U, 0x4D42U, 0x4E42U, 0x4F42U, 0x5042U, 0x5142U, 0x5242U, 0x5342U, 0x5442U, 0x5542U, 0x5642U, 0x5742U, 0x5842U, 0x5942U, 0x5A42U, 0x6142U, 0x6242U, 0x6342U, 0x6442U, 0x6542U, 0x6642U, 0x6742U, 0x6842U, 0x6942U, 0x6A42U, 0x6B42U, 0x6C42U, 0x6D42U, 0x6E42U, 0x6F42U, 0x7042U, 0x7142U, 0x7242U, 0x7342U, 0x7442U, 0x7542U, 0x7642U, 0x7742U, 0x7842U, 0x7942U, 0x7A42U, 0x3042U, 0x3142U, 0x3242U, 0x3342U, 0x3442U, 0x3542U, 0x3642U, 0x3742U, 0x3842U, 0x3942U, 0x2B42U, 0x2F42U, 0x4143U, 0x4243U, 0x4343U, 0x4443U, 0x4543U, 0x4643U, 0x4743U, 0x4843U, 0x4943U, 0x4A43U, 0x4B43U, 0x4C43U, 0x4D43U, 0x4E43U, 0x4F43U, 0x5043U, 0x5143U, 0x5243U, 0x5343U, 0x5443U, 0x5543U, 0x5643U, 0x5743U, 0x5843U, 0x5943U, 0x5A43U, 0x6143U, 0x6243U, 0x6343U, 0x6443U, 0x6543U, 0x6643U, 0x6743U, 0x6843U, 0x6943U, 0x6A43U, 0x6B43U, 0x6C43U, 0x6D43U, 0x6E43U, 0x6F43U, 0x7043U, 0x7143U, 0x7243U, 0x7343U, 0x7443U, 0x7543U, 0x7643U, 0x7743U, 0x7843U, 0x7943U, 0x7A43U, 0x3043U, 0x3143U, 0x3243U, 0x3343U, 0x3443U, 0x3543U, 0x3643U, 0x3743U, 0x3843U, 0x3943U, 0x2B43U, 0x2F43U, 0x4144U, 0x4244U, 0x4344U, 0x4444U, 0x4544U, 0x4644U, 0x4744U, 0x4844U, 0x4944U, 0x4A44U, 0x4B44U, 0x4C44U, 0x4D44U, 0x4E44U, 0x4F44U, 0x5044U, 0x5144U, 0x5244U, 0x5344U, 0x5444U, 0x5544U, 0x5644U, 0x5744U, 0x5844U, 0x5944U, 0x5A44U, 0x6144U, 0x6244U, 0x6344U, 0x6444U, 0x6544U, 0x6644U, 0x6744U, 0x6844U, 0x6944U, 0x6A44U, 0x6B44U, 0x6C44U, 0x6D44U, 0x6E44U, 0x6F44U, 0x7044U, 0x7144U, 0x7244U, 0x7344U, 0x7444U, 0x7544U, 0x7644U, 0x7744U, 0x7844U, 0x7944U, 0x7A44U, 0x3044U, 0x3144U, 0x3244U, 0x3344U, 0x3444U, 0x3544U, 0x3644U, 0x3744U, 0x3844U, 0x3944U, 0x2B44U, 0x2F44U, 0x4145U, 0x4245U, 0x4345U, 0x4445U, 0x4545U, 0x4645U, 0x4745U, 0x4845U, 0x4945U, 0x4A45U, 0x4B45U, 0x4C45U, 0x4D45U, 0x4E45U, 0x4F45U, 0x5045U, 0x5145U, 0x5245U, 0x5345U, 0x5445U, 0x5545U, 0x5645U, 0x5745U, 0x5845U, 0x5945U, 0x5A45U, 0x6145U, 0x6245U, 0x6345U, 0x6445U, 0x6545U, 0x6645U, 0x6745U, 0x6845U, 0x6945U, 0x6A45U, 0x6B45U, 0x6C45U, 0x6D45U, 0x6E45U, 0x6F45U, 0x7045U, 0x7145U, 0x7245U, 0x7345U, 0x7445U, 0x7545U, 0x7645U, 0x7745U, 0x7845U, 0x7945U, 0x7A45U, 0x3045U, 0x3145U, 0x3245U, 0x3345U, 0x3445U, 0x3545U, 0x3645U, 0x3745U, 0x3845U, 0x3945U, 0x2B45U, 0x2F45U, 0x4146U, 0x4246U, 0x4346U, 0x4446U, 0x4546U, 0x4646U, 0x4746U, 0x4846U, 0x4946U, 0x4A46U, 0x4B46U, 0x4C46U, 0x4D46U, 0x4E46U, 0x4F46U, 0x5046U, 0x5146U, 0x5246U, 0x5346U, 0x5446U, 0x5546U, 0x5646U, 0x5746U, 0x5846U, 0x5946U, 0x5A46U, 0x6146U, 0x6246U, 0x6346U, 0x6446U, 0x6546U, 0x6646U, 0x6746U, 0x6846U, 0x6946U, 0x6A46U, 0x6B46U, 0x6C46U, 0x6D46U, 0x6E46U, 0x6F46U, 0x7046U, 0x7146U, 0x7246U, 0x7346U, 0x7446U, 0x7546U, 0x7646U, 0x7746U, 0x7846U, 0x7946U, 0x7A46U, 0x3046U, 0x3146U, 0x3246U, 0x3346U, 0x3446U, 0x3546U, 0x3646U, 0x3746U, 0x3846U, 0x3946U, 0x2B46U, 0x2F46U, 0x4147U, 0x4247U, 0x4347U, 0x4447U, 0x4547U, 0x4647U, 0x4747U, 0x4847U, 0x4947U, 0x4A47U, 0x4B47U, 0x4C47U, 0x4D47U, 0x4E47U, 0x4F47U, 0x5047U, 0x5147U, 0x5247U, 0x5347U, 0x5447U, 0x5547U, 0x5647U, 0x5747U, 0x5847U, 0x5947U, 0x5A47U, 0x6147U, 0x6247U, 0x6347U, 0x6447U, 0x6547U, 0x6647U, 0x6747U, 0x6847U, 0x6947U, 0x6A47U, 0x6B47U, 0x6C47U, 0x6D47U, 0x6E47U, 0x6F47U, 0x7047U, 0x7147U, 0x7247U, 0x7347U, 0x7447U, 0x7547U, 0x7647U, 0x7747U, 0x7847U, 0x7947U, 0x7A47U, 0x3047U, 0x3147U, 0x3247U, 0x3347U, 0x3447U, 0x3547U, 0x3647U, 0x3747U, 0x3847U, 0x3947U, 0x2B47U, 0x2F47U, 0x4148U, 0x4248U, 0x4348U, 0x4448U, 0x4548U, 0x4648U, 0x4748U, 0x4848U, 0x4948U, 0x4A48U, 0x4B48U, 0x4C48U, 0x4D48U, 0x4E48U, 0x4F48U, 0x5048U, 0x5148U, 0x5248U, 0x5348U, 0x5448U, 0x5548U, 0x5648U, 0x5748U, 0x5848U, 0x5948U, 0x5A48U, 0x6148U, 0x6248U, 0x6348U, 0x6448U, 0x6548U, 0x6648U, 0x6748U, 0x6848U, 0x6948U, 0x6A48U, 0x6B48U, 0x6C48U, 0x6D48U, 0x6E48U, 0x6F48U, 0x7048U, 0x7148U, 0x7248U, 0x7348U, 0x7448U, 0x7548U, 0x7648U, 0x7748U, 0x7848U, 0x7948U, 0x7A48U, 0x3048U, 0x3148U, 0x3248U, 0x3348U, 0x3448U, 0x3548U, 0x3648U, 0x3748U, 0x3848U, 0x3948U, 0x2B48U, 0x2F48U, 0x4149U, 0x4249U, 0x4349U, 0x4449U, 0x4549U, 0x4649U, 0x4749U, 0x4849U, 0x4949U, 0x4A49U, 0x4B49U, 0x4C49U, 0x4D49U, 0x4E49U, 0x4F49U, 0x5049U, 0x5149U, 0x5249U, 0x5349U, 0x5449U, 0x5549U, 0x5649U, 0x5749U, 0x5849U, 0x5949U, 0x5A49U, 0x6149U, 0x6249U, 0x6349U, 0x6449U, 0x6549U, 0x6649U, 0x6749U, 0x6849U, 0x6949U, 0x6A49U, 0x6B49U, 0x6C49U, 0x6D49U, 0x6E49U, 0x6F49U, 0x7049U, 0x7149U, 0x7249U, 0x7349U, 0x7449U, 0x7549U, 0x7649U, 0x7749U, 0x7849U, 0x7949U, 0x7A49U, 0x3049U, 0x3149U, 0x3249U, 0x3349U, 0x3449U, 0x3549U, 0x3649U, 0x3749U, 0x3849U, 0x3949U, 0x2B49U, 0x2F49U, 0x414AU, 0x424AU, 0x434AU, 0x444AU, 0x454AU, 0x464AU, 0x474AU, 0x484AU, 0x494AU, 0x4A4AU, 0x4B4AU, 0x4C4AU, 0x4D4AU, 0x4E4AU, 0x4F4AU, 0x504AU, 0x514AU, 0x524AU, 0x534AU, 0x544AU, 0x554AU, 0x564AU, 0x574AU, 0x584AU, 0x594AU, 0x5A4AU, 0x614AU, 0x624AU, 0x634AU, 0x644AU, 0x654AU, 0x664AU, 0x674AU, 0x684AU, 0x694AU, 0x6A4AU, 0x6B4AU, 0x6C4AU, 0x6D4AU, 0x6E4AU, 0x6F4AU, 0x704AU, 0x714AU, 0x724AU, 0x734AU, 0x744AU, 0x754AU, 0x764AU, 0x774AU, 0x784AU, 0x794AU, 0x7A4AU, 0x304AU, 0x314AU, 0x324AU, 0x334AU, 0x344AU, 0x354AU, 0x364AU, 0x374AU, 0x384AU, 0x394AU, 0x2B4AU, 0x2F4AU, 0x414BU, 0x424BU, 0x434BU, 0x444BU, 0x454BU, 0x464BU, 0x474BU, 0x484BU, 0x494BU, 0x4A4BU, 0x4B4BU, 0x4C4BU, 0x4D4BU, 0x4E4BU, 0x4F4BU, 0x504BU, 0x514BU, 0x524BU, 0x534BU, 0x544BU, 0x554BU, 0x564BU, 0x574BU, 0x584BU, 0x594BU, 0x5A4BU, 0x614BU, 0x624BU, 0x634BU, 0x644BU, 0x654BU, 0x664BU, 0x674BU, 0x684BU, 0x694BU, 0x6A4BU, 0x6B4BU, 0x6C4BU, 0x6D4BU, 0x6E4BU, 0x6F4BU, 0x704BU, 0x714BU, 0x724BU, 0x734BU, 0x744BU, 0x754BU, 0x764BU, 0x774BU, 0x784BU, 0x794BU, 0x7A4BU, 0x304BU, 0x314BU, 0x324BU, 0x334BU, 0x344BU, 0x354BU, 0x364BU, 0x374BU, 0x384BU, 0x394BU, 0x2B4BU, 0x2F4BU, 0x414CU, 0x424CU, 0x434CU, 0x444CU, 0x454CU, 0x464CU, 0x474CU, 0x484CU, 0x494CU, 0x4A4CU, 0x4B4CU, 0x4C4CU, 0x4D4CU, 0x4E4CU, 0x4F4CU, 0x504CU, 0x514CU, 0x524CU, 0x534CU, 0x544CU, 0x554CU, 0x564CU, 0x574CU, 0x584CU, 0x594CU, 0x5A4CU, 0x614CU, 0x624CU, 0x634CU, 0x644CU, 0x654CU, 0x664CU, 0x674CU, 0x684CU, 0x694CU, 0x6A4CU, 0x6B4CU, 0x6C4CU, 0x6D4CU, 0x6E4CU, 0x6F4CU, 0x704CU, 0x714CU, 0x724CU, 0x734CU, 0x744CU, 0x754CU, 0x764CU, 0x774CU, 0x784CU, 0x794CU, 0x7A4CU, 0x304CU, 0x314CU, 0x324CU, 0x334CU, 0x344CU, 0x354CU, 0x364CU, 0x374CU, 0x384CU, 0x394CU, 0x2B4CU, 0x2F4CU, 0x414DU, 0x424DU, 0x434DU, 0x444DU, 0x454DU, 0x464DU, 0x474DU, 0x484DU, 0x494DU, 0x4A4DU, 0x4B4DU, 0x4C4DU, 0x4D4DU, 0x4E4DU, 0x4F4DU, 0x504DU, 0x514DU, 0x524DU, 0x534DU, 0x544DU, 0x554DU, 0x564DU, 0x574DU, 0x584DU, 0x594DU, 0x5A4DU, 0x614DU, 0x624DU, 0x634DU, 0x644DU, 0x654DU, 0x664DU, 0x674DU, 0x684DU, 0x694DU, 0x6A4DU, 0x6B4DU, 0x6C4DU, 0x6D4DU, 0x6E4DU, 0x6F4DU, 0x704DU, 0x714DU, 0x724DU, 0x734DU, 0x744DU, 0x754DU, 0x764DU, 0x774DU, 0x784DU, 0x794DU, 0x7A4DU, 0x304DU, 0x314DU, 0x324DU, 0x334DU, 0x344DU, 0x354DU, 0x364DU, 0x374DU, 0x384DU, 0x394DU, 0x2B4DU, 0x2F4DU, 0x414EU, 0x424EU, 0x434EU, 0x444EU, 0x454EU, 0x464EU, 0x474EU, 0x484EU, 0x494EU, 0x4A4EU, 0x4B4EU, 0x4C4EU, 0x4D4EU, 0x4E4EU, 0x4F4EU, 0x504EU, 0x514EU, 0x524EU, 0x534EU, 0x544EU, 0x554EU, 0x564EU, 0x574EU, 0x584EU, 0x594EU, 0x5A4EU, 0x614EU, 0x624EU, 0x634EU, 0x644EU, 0x654EU, 0x664EU, 0x674EU, 0x684EU, 0x694EU, 0x6A4EU, 0x6B4EU, 0x6C4EU, 0x6D4EU, 0x6E4EU, 0x6F4EU, 0x704EU, 0x714EU, 0x724EU, 0x734EU, 0x744EU, 0x754EU, 0x764EU, 0x774EU, 0x784EU, 0x794EU, 0x7A4EU, 0x304EU, 0x314EU, 0x324EU, 0x334EU, 0x344EU, 0x354EU, 0x364EU, 0x374EU, 0x384EU, 0x394EU, 0x2B4EU, 0x2F4EU, 0x414FU, 0x424FU, 0x434FU, 0x444FU, 0x454FU, 0x464FU, 0x474FU, 0x484FU, 0x494FU, 0x4A4FU, 0x4B4FU, 0x4C4FU, 0x4D4FU, 0x4E4FU, 0x4F4FU, 0x504FU, 0x514FU, 0x524FU, 0x534FU, 0x544FU, 0x554FU, 0x564FU, 0x574FU, 0x584FU, 0x594FU, 0x5A4FU, 0x614FU, 0x624FU, 0x634FU, 0x644FU, 0x654FU, 0x664FU, 0x674FU, 0x684FU, 0x694FU, 0x6A4FU, 0x6B4FU, 0x6C4FU, 0x6D4FU, 0x6E4FU, 0x6F4FU, 0x704FU, 0x714FU, 0x724FU, 0x734FU, 0x744FU, 0x754FU, 0x764FU, 0x774FU, 0x784FU, 0x794FU, 0x7A4FU, 0x304FU, 0x314FU, 0x324FU, 0x334FU, 0x344FU, 0x354FU, 0x364FU, 0x374FU, 0x384FU, 0x394FU, 0x2B4FU, 0x2F4FU, 0x4150U, 0x4250U, 0x4350U, 0x4450U, 0x4550U, 0x4650U, 0x4750U, 0x4850U, 0x4950U, 0x4A50U, 0x4B50U, 0x4C50U, 0x4D50U, 0x4E50U, 0x4F50U, 0x5050U, 0x5150U, 0x5250U, 0x5350U, 0x5450U, 0x5550U, 0x5650U, 0x5750U, 0x5850U, 0x5950U, 0x5A50U, 0x6150U, 0x6250U, 0x6350U, 0x6450U, 0x6550U, 0x6650U, 0x6750U, 0x6850U, 0x6950U, 0x6A50U, 0x6B50U, 0x6C50U, 0x6D50U, 0x6E50U, 0x6F50U, 0x7050U, 0x7150U, 0x7250U, 0x7350U, 0x7450U, 0x7550U, 0x7650U, 0x7750U, 0x7850U, 0x7950U, 0x7A50U, 0x3050U, 0x3150U, 0x3250U, 0x3350U, 0x3450U, 0x3550U, 0x3650U, 0x3750U, 0x3850U, 0x3950U, 0x2B50U, 0x2F50U, 0x4151U, 0x4251U, 0x4351U, 0x4451U, 0x4551U, 0x4651U, 0x4751U, 0x4851U, 0x4951U, 0x4A51U, 0x4B51U, 0x4C51U, 0x4D51U, 0x4E51U, 0x4F51U, 0x5051U, 0x5151U, 0x5251U, 0x5351U, 0x5451U, 0x5551U, 0x5651U, 0x5751U, 0x5851U, 0x5951U, 0x5A51U, 0x6151U, 0x6251U, 0x6351U, 0x6451U, 0x6551U, 0x6651U, 0x6751U, 0x6851U, 0x6951U, 0x6A51U, 0x6B51U, 0x6C51U, 0x6D51U, 0x6E51U, 0x6F51U, 0x7051U, 0x7151U, 0x7251U, 0x7351U, 0x7451U, 0x7551U, 0x7651U, 0x7751U, 0x7851U, 0x7951U, 0x7A51U, 0x3051U, 0x3151U, 0x3251U, 0x3351U, 0x3451U, 0x3551U, 0x3651U, 0x3751U, 0x3851U, 0x3951U, 0x2B51U, 0x2F51U, 0x4152U, 0x4252U, 0x4352U, 0x4452U, 0x4552U, 0x4652U, 0x4752U, 0x4852U, 0x4952U, 0x4A52U, 0x4B52U, 0x4C52U, 0x4D52U, 0x4E52U, 0x4F52U, 0x5052U, 0x5152U, 0x5252U, 0x5352U, 0x5452U, 0x5552U, 0x5652U, 0x5752U, 0x5852U, 0x5952U, 0x5A52U, 0x6152U, 0x6252U, 0x6352U, 0x6452U, 0x6552U, 0x6652U, 0x6752U, 0x6852U, 0x6952U, 0x6A52U, 0x6B52U, 0x6C52U, 0x6D52U, 0x6E52U, 0x6F52U, 0x7052U, 0x7152U, 0x7252U, 0x7352U, 0x7452U, 0x7552U, 0x7652U, 0x7752U, 0x7852U, 0x7952U, 0x7A52U, 0x3052U, 0x3152U, 0x3252U, 0x3352U, 0x3452U, 0x3552U, 0x3652U, 0x3752U, 0x3852U, 0x3952U, 0x2B52U, 0x2F52U, 0x4153U, 0x4253U, 0x4353U, 0x4453U, 0x4553U, 0x4653U, 0x4753U, 0x4853U, 0x4953U, 0x4A53U, 0x4B53U, 0x4C53U, 0x4D53U, 0x4E53U, 0x4F53U, 0x5053U, 0x5153U, 0x5253U, 0x5353U, 0x5453U, 0x5553U, 0x5653U, 0x5753U, 0x5853U, 0x5953U, 0x5A53U, 0x6153U, 0x6253U, 0x6353U, 0x6453U, 0x6553U, 0x6653U, 0x6753U, 0x6853U, 0x6953U, 0x6A53U, 0x6B53U, 0x6C53U, 0x6D53U, 0x6E53U, 0x6F53U, 0x7053U, 0x7153U, 0x7253U, 0x7353U, 0x7453U, 0x7553U, 0x7653U, 0x7753U, 0x7853U, 0x7953U, 0x7A53U, 0x3053U, 0x3153U, 0x3253U, 0x3353U, 0x3453U, 0x3553U, 0x3653U, 0x3753U, 0x3853U, 0x3953U, 0x2B53U, 0x2F53U, 0x4154U, 0x4254U, 0x4354U, 0x4454U, 0x4554U, 0x4654U, 0x4754U, 0x4854U, 0x4954U, 0x4A54U, 0x4B54U, 0x4C54U, 0x4D54U, 0x4E54U, 0x4F54U, 0x5054U, 0x5154U, 0x5254U, 0x5354U, 0x5454U, 0x5554U, 0x5654U, 0x5754U, 0x5854U, 0x5954U, 0x5A54U, 0x6154U, 0x6254U, 0x6354U, 0x6454U, 0x6554U, 0x6654U, 0x6754U, 0x6854U, 0x6954U, 0x6A54U, 0x6B54U, 0x6C54U, 0x6D54U, 0x6E54U, 0x6F54U, 0x7054U, 0x7154U, 0x7254U, 0x7354U, 0x7454U, 0x7554U, 0x7654U, 0x7754U, 0x7854U, 0x7954U, 0x7A54U, 0x3054U, 0x3154U, 0x3254U, 0x3354U, 0x3454U, 0x3554U, 0x3654U, 0x3754U, 0x3854U, 0x3954U, 0x2B54U, 0x2F54U, 0x4155U, 0x4255U, 0x4355U, 0x4455U, 0x4555U, 0x4655U, 0x4755U, 0x4855U, 0x4955U, 0x4A55U, 0x4B55U, 0x4C55U, 0x4D55U, 0x4E55U, 0x4F55U, 0x5055U, 0x5155U, 0x5255U, 0x5355U, 0x5455U, 0x5555U, 0x5655U, 0x5755U, 0x5855U, 0x5955U, 0x5A55U, 0x6155U, 0x6255U, 0x6355U, 0x6455U, 0x6555U, 0x6655U, 0x6755U, 0x6855U, 0x6955U, 0x6A55U, 0x6B55U, 0x6C55U, 0x6D55U, 0x6E55U, 0x6F55U, 0x7055U, 0x7155U, 0x7255U, 0x7355U, 0x7455U, 0x7555U, 0x7655U, 0x7755U, 0x7855U, 0x7955U, 0x7A55U, 0x3055U, 0x3155U, 0x3255U, 0x3355U, 0x3455U, 0x3555U, 0x3655U, 0x3755U, 0x3855U, 0x3955U, 0x2B55U, 0x2F55U, 0x4156U, 0x4256U, 0x4356U, 0x4456U, 0x4556U, 0x4656U, 0x4756U, 0x4856U, 0x4956U, 0x4A56U, 0x4B56U, 0x4C56U, 0x4D56U, 0x4E56U, 0x4F56U, 0x5056U, 0x5156U, 0x5256U, 0x5356U, 0x5456U, 0x5556U, 0x5656U, 0x5756U, 0x5856U, 0x5956U, 0x5A56U, 0x6156U, 0x6256U, 0x6356U, 0x6456U, 0x6556U, 0x6656U, 0x6756U, 0x6856U, 0x6956U, 0x6A56U, 0x6B56U, 0x6C56U, 0x6D56U, 0x6E56U, 0x6F56U, 0x7056U, 0x7156U, 0x7256U, 0x7356U, 0x7456U, 0x7556U, 0x7656U, 0x7756U, 0x7856U, 0x7956U, 0x7A56U, 0x3056U, 0x3156U, 0x3256U, 0x3356U, 0x3456U, 0x3556U, 0x3656U, 0x3756U, 0x3856U, 0x3956U, 0x2B56U, 0x2F56U, 0x4157U, 0x4257U, 0x4357U, 0x4457U, 0x4557U, 0x4657U, 0x4757U, 0x4857U, 0x4957U, 0x4A57U, 0x4B57U, 0x4C57U, 0x4D57U, 0x4E57U, 0x4F57U, 0x5057U, 0x5157U, 0x5257U, 0x5357U, 0x5457U, 0x5557U, 0x5657U, 0x5757U, 0x5857U, 0x5957U, 0x5A57U, 0x6157U, 0x6257U, 0x6357U, 0x6457U, 0x6557U, 0x6657U, 0x6757U, 0x6857U, 0x6957U, 0x6A57U, 0x6B57U, 0x6C57U, 0x6D57U, 0x6E57U, 0x6F57U, 0x7057U, 0x7157U, 0x7257U, 0x7357U, 0x7457U, 0x7557U, 0x7657U, 0x7757U, 0x7857U, 0x7957U, 0x7A57U, 0x3057U, 0x3157U, 0x3257U, 0x3357U, 0x3457U, 0x3557U, 0x3657U, 0x3757U, 0x3857U, 0x3957U, 0x2B57U, 0x2F57U, 0x4158U, 0x4258U, 0x4358U, 0x4458U, 0x4558U, 0x4658U, 0x4758U, 0x4858U, 0x4958U, 0x4A58U, 0x4B58U, 0x4C58U, 0x4D58U, 0x4E58U, 0x4F58U, 0x5058U, 0x5158U, 0x5258U, 0x5358U, 0x5458U, 0x5558U, 0x5658U, 0x5758U, 0x5858U, 0x5958U, 0x5A58U, 0x6158U, 0x6258U, 0x6358U, 0x6458U, 0x6558U, 0x6658U, 0x6758U, 0x6858U, 0x6958U, 0x6A58U, 0x6B58U, 0x6C58U, 0x6D58U, 0x6E58U, 0x6F58U, 0x7058U, 0x7158U, 0x7258U, 0x7358U, 0x7458U, 0x7558U, 0x7658U, 0x7758U, 0x7858U, 0x7958U, 0x7A58U, 0x3058U, 0x3158U, 0x3258U, 0x3358U, 0x3458U, 0x3558U, 0x3658U, 0x3758U, 0x3858U, 0x3958U, 0x2B58U, 0x2F58U, 0x4159U, 0x4259U, 0x4359U, 0x4459U, 0x4559U, 0x4659U, 0x4759U, 0x4859U, 0x4959U, 0x4A59U, 0x4B59U, 0x4C59U, 0x4D59U, 0x4E59U, 0x4F59U, 0x5059U, 0x5159U, 0x5259U, 0x5359U, 0x5459U, 0x5559U, 0x5659U, 0x5759U, 0x5859U, 0x5959U, 0x5A59U, 0x6159U, 0x6259U, 0x6359U, 0x6459U, 0x6559U, 0x6659U, 0x6759U, 0x6859U, 0x6959U, 0x6A59U, 0x6B59U, 0x6C59U, 0x6D59U, 0x6E59U, 0x6F59U, 0x7059U, 0x7159U, 0x7259U, 0x7359U, 0x7459U, 0x7559U, 0x7659U, 0x7759U, 0x7859U, 0x7959U, 0x7A59U, 0x3059U, 0x3159U, 0x3259U, 0x3359U, 0x3459U, 0x3559U, 0x3659U, 0x3759U, 0x3859U, 0x3959U, 0x2B59U, 0x2F59U, 0x415AU, 0x425AU, 0x435AU, 0x445AU, 0x455AU, 0x465AU, 0x475AU, 0x485AU, 0x495AU, 0x4A5AU, 0x4B5AU, 0x4C5AU, 0x4D5AU, 0x4E5AU, 0x4F5AU, 0x505AU, 0x515AU, 0x525AU, 0x535AU, 0x545AU, 0x555AU, 0x565AU, 0x575AU, 0x585AU, 0x595AU, 0x5A5AU, 0x615AU, 0x625AU, 0x635AU, 0x645AU, 0x655AU, 0x665AU, 0x675AU, 0x685AU, 0x695AU, 0x6A5AU, 0x6B5AU, 0x6C5AU, 0x6D5AU, 0x6E5AU, 0x6F5AU, 0x705AU, 0x715AU, 0x725AU, 0x735AU, 0x745AU, 0x755AU, 0x765AU, 0x775AU, 0x785AU, 0x795AU, 0x7A5AU, 0x305AU, 0x315AU, 0x325AU, 0x335AU, 0x345AU, 0x355AU, 0x365AU, 0x375AU, 0x385AU, 0x395AU, 0x2B5AU, 0x2F5AU, 0x4161U, 0x4261U, 0x4361U, 0x4461U, 0x4561U, 0x4661U, 0x4761U, 0x4861U, 0x4961U, 0x4A61U, 0x4B61U, 0x4C61U, 0x4D61U, 0x4E61U, 0x4F61U, 0x5061U, 0x5161U, 0x5261U, 0x5361U, 0x5461U, 0x5561U, 0x5661U, 0x5761U, 0x5861U, 0x5961U, 0x5A61U, 0x6161U, 0x6261U, 0x6361U, 0x6461U, 0x6561U, 0x6661U, 0x6761U, 0x6861U, 0x6961U, 0x6A61U, 0x6B61U, 0x6C61U, 0x6D61U, 0x6E61U, 0x6F61U, 0x7061U, 0x7161U, 0x7261U, 0x7361U, 0x7461U, 0x7561U, 0x7661U, 0x7761U, 0x7861U, 0x7961U, 0x7A61U, 0x3061U, 0x3161U, 0x3261U, 0x3361U, 0x3461U, 0x3561U, 0x3661U, 0x3761U, 0x3861U, 0x3961U, 0x2B61U, 0x2F61U, 0x4162U, 0x4262U, 0x4362U, 0x4462U, 0x4562U, 0x4662U, 0x4762U, 0x4862U, 0x4962U, 0x4A62U, 0x4B62U, 0x4C62U, 0x4D62U, 0x4E62U, 0x4F62U, 0x5062U, 0x5162U, 0x5262U, 0x5362U, 0x5462U, 0x5562U, 0x5662U, 0x5762U, 0x5862U, 0x5962U, 0x5A62U, 0x6162U, 0x6262U, 0x6362U, 0x6462U, 0x6562U, 0x6662U, 0x6762U, 0x6862U, 0x6962U, 0x6A62U, 0x6B62U, 0x6C62U, 0x6D62U, 0x6E62U, 0x6F62U, 0x7062U, 0x7162U, 0x7262U, 0x7362U, 0x7462U, 0x7562U, 0x7662U, 0x7762U, 0x7862U, 0x7962U, 0x7A62U, 0x3062U, 0x3162U, 0x3262U, 0x3362U, 0x3462U, 0x3562U, 0x3662U, 0x3762U, 0x3862U, 0x3962U, 0x2B62U, 0x2F62U, 0x4163U, 0x4263U, 0x4363U, 0x4463U, 0x4563U, 0x4663U, 0x4763U, 0x4863U, 0x4963U, 0x4A63U, 0x4B63U, 0x4C63U, 0x4D63U, 0x4E63U, 0x4F63U, 0x5063U, 0x5163U, 0x5263U, 0x5363U, 0x5463U, 0x5563U, 0x5663U, 0x5763U, 0x5863U, 0x5963U, 0x5A63U, 0x6163U, 0x6263U, 0x6363U, 0x6463U, 0x6563U, 0x6663U, 0x6763U, 0x6863U, 0x6963U, 0x6A63U, 0x6B63U, 0x6C63U, 0x6D63U, 0x6E63U, 0x6F63U, 0x7063U, 0x7163U, 0x7263U, 0x7363U, 0x7463U, 0x7563U, 0x7663U, 0x7763U, 0x7863U, 0x7963U, 0x7A63U, 0x3063U, 0x3163U, 0x3263U, 0x3363U, 0x3463U, 0x3563U, 0x3663U, 0x3763U, 0x3863U, 0x3963U, 0x2B63U, 0x2F63U, 0x4164U, 0x4264U, 0x4364U, 0x4464U, 0x4564U, 0x4664U, 0x4764U, 0x4864U, 0x4964U, 0x4A64U, 0x4B64U, 0x4C64U, 0x4D64U, 0x4E64U, 0x4F64U, 0x5064U, 0x5164U, 0x5264U, 0x5364U, 0x5464U, 0x5564U, 0x5664U, 0x5764U, 0x5864U, 0x5964U, 0x5A64U, 0x6164U, 0x6264U, 0x6364U, 0x6464U, 0x6564U, 0x6664U, 0x6764U, 0x6864U, 0x6964U, 0x6A64U, 0x6B64U, 0x6C64U, 0x6D64U, 0x6E64U, 0x6F64U, 0x7064U, 0x7164U, 0x7264U, 0x7364U, 0x7464U, 0x7564U, 0x7664U, 0x7764U, 0x7864U, 0x7964U, 0x7A64U, 0x3064U, 0x3164U, 0x3264U, 0x3364U, 0x3464U, 0x3564U, 0x3664U, 0x3764U, 0x3864U, 0x3964U, 0x2B64U, 0x2F64U, 0x4165U, 0x4265U, 0x4365U, 0x4465U, 0x4565U, 0x4665U, 0x4765U, 0x4865U, 0x4965U, 0x4A65U, 0x4B65U, 0x4C65U, 0x4D65U, 0x4E65U, 0x4F65U, 0x5065U, 0x5165U, 0x5265U, 0x5365U, 0x5465U, 0x5565U, 0x5665U, 0x5765U, 0x5865U, 0x5965U, 0x5A65U, 0x6165U, 0x6265U, 0x6365U, 0x6465U, 0x6565U, 0x6665U, 0x6765U, 0x6865U, 0x6965U, 0x6A65U, 0x6B65U, 0x6C65U, 0x6D65U, 0x6E65U, 0x6F65U, 0x7065U, 0x7165U, 0x7265U, 0x7365U, 0x7465U, 0x7565U, 0x7665U, 0x7765U, 0x7865U, 0x7965U, 0x7A65U, 0x3065U, 0x3165U, 0x3265U, 0x3365U, 0x3465U, 0x3565U, 0x3665U, 0x3765U, 0x3865U, 0x3965U, 0x2B65U, 0x2F65U, 0x4166U, 0x4266U, 0x4366U, 0x4466U, 0x4566U, 0x4666U, 0x4766U, 0x4866U, 0x4966U, 0x4A66U, 0x4B66U, 0x4C66U, 0x4D66U, 0x4E66U, 0x4F66U, 0x5066U, 0x5166U, 0x5266U, 0x5366U, 0x5466U, 0x5566U, 0x5666U, 0x5766U, 0x5866U, 0x5966U, 0x5A66U, 0x6166U, 0x6266U, 0x6366U, 0x6466U, 0x6566U, 0x6666U, 0x6766U, 0x6866U, 0x6966U, 0x6A66U, 0x6B66U, 0x6C66U, 0x6D66U, 0x6E66U, 0x6F66U, 0x7066U, 0x7166U, 0x7266U, 0x7366U, 0x7466U, 0x7566U, 0x7666U, 0x7766U, 0x7866U, 0x7966U, 0x7A66U, 0x3066U, 0x3166U, 0x3266U, 0x3366U, 0x3466U, 0x3566U, 0x3666U, 0x3766U, 0x3866U, 0x3966U, 0x2B66U, 0x2F66U, 0x4167U, 0x4267U, 0x4367U, 0x4467U, 0x4567U, 0x4667U, 0x4767U, 0x4867U, 0x4967U, 0x4A67U, 0x4B67U, 0x4C67U, 0x4D67U, 0x4E67U, 0x4F67U, 0x5067U, 0x5167U, 0x5267U, 0x5367U, 0x5467U, 0x5567U, 0x5667U, 0x5767U, 0x5867U, 0x5967U, 0x5A67U, 0x6167U, 0x6267U, 0x6367U, 0x6467U, 0x6567U, 0x6667U, 0x6767U, 0x6867U, 0x6967U, 0x6A67U, 0x6B67U, 0x6C67U, 0x6D67U, 0x6E67U, 0x6F67U, 0x7067U, 0x7167U, 0x7267U, 0x7367U, 0x7467U, 0x7567U, 0x7667U, 0x7767U, 0x7867U, 0x7967U, 0x7A67U, 0x3067U, 0x3167U, 0x3267U, 0x3367U, 0x3467U, 0x3567U, 0x3667U, 0x3767U, 0x3867U, 0x3967U, 0x2B67U, 0x2F67U, 0x4168U, 0x4268U, 0x4368U, 0x4468U, 0x4568U, 0x4668U, 0x4768U, 0x4868U, 0x4968U, 0x4A68U, 0x4B68U, 0x4C68U, 0x4D68U, 0x4E68U, 0x4F68U, 0x5068U, 0x5168U, 0x5268U, 0x5368U, 0x5468U, 0x5568U, 0x5668U, 0x5768U, 0x5868U, 0x5968U, 0x5A68U, 0x6168U, 0x6268U, 0x6368U, 0x6468U, 0x6568U, 0x6668U, 0x6768U, 0x6868U, 0x6968U, 0x6A68U, 0x6B68U, 0x6C68U, 0x6D68U, 0x6E68U, 0x6F68U, 0x7068U, 0x7168U, 0x7268U, 0x7368U, 0x7468U, 0x7568U, 0x7668U, 0x7768U, 0x7868U, 0x7968U, 0x7A68U, 0x3068U, 0x3168U, 0x3268U, 0x3368U, 0x3468U, 0x3568U, 0x3668U, 0x3768U, 0x3868U, 0x3968U, 0x2B68U, 0x2F68U, 0x4169U, 0x4269U, 0x4369U, 0x4469U, 0x4569U, 0x4669U, 0x4769U, 0x4869U, 0x4969U, 0x4A69U, 0x4B69U, 0x4C69U, 0x4D69U, 0x4E69U, 0x4F69U, 0x5069U, 0x5169U, 0x5269U, 0x5369U, 0x5469U, 0x5569U, 0x5669U, 0x5769U, 0x5869U, 0x5969U, 0x5A69U, 0x6169U, 0x6269U, 0x6369U, 0x6469U, 0x6569U, 0x6669U, 0x6769U, 0x6869U, 0x6969U, 0x6A69U, 0x6B69U, 0x6C69U, 0x6D69U, 0x6E69U, 0x6F69U, 0x7069U, 0x7169U, 0x7269U, 0x7369U, 0x7469U, 0x7569U, 0x7669U, 0x7769U, 0x7869U, 0x7969U, 0x7A69U, 0x3069U, 0x3169U, 0x3269U, 0x3369U, 0x3469U, 0x3569U, 0x3669U, 0x3769U, 0x3869U, 0x3969U, 0x2B69U, 0x2F69U, 0x416AU, 0x426AU, 0x436AU, 0x446AU, 0x456AU, 0x466AU, 0x476AU, 0x486AU, 0x496AU, 0x4A6AU, 0x4B6AU, 0x4C6AU, 0x4D6AU, 0x4E6AU, 0x4F6AU, 0x506AU, 0x516AU, 0x526AU, 0x536AU, 0x546AU, 0x556AU, 0x566AU, 0x576AU, 0x586AU, 0x596AU, 0x5A6AU, 0x616AU, 0x626AU, 0x636AU, 0x646AU, 0x656AU, 0x666AU, 0x676AU, 0x686AU, 0x696AU, 0x6A6AU, 0x6B6AU, 0x6C6AU, 0x6D6AU, 0x6E6AU, 0x6F6AU, 0x706AU, 0x716AU, 0x726AU, 0x736AU, 0x746AU, 0x756AU, 0x766AU, 0x776AU, 0x786AU, 0x796AU, 0x7A6AU, 0x306AU, 0x316AU, 0x326AU, 0x336AU, 0x346AU, 0x356AU, 0x366AU, 0x376AU, 0x386AU, 0x396AU, 0x2B6AU, 0x2F6AU, 0x416BU, 0x426BU, 0x436BU, 0x446BU, 0x456BU, 0x466BU, 0x476BU, 0x486BU, 0x496BU, 0x4A6BU, 0x4B6BU, 0x4C6BU, 0x4D6BU, 0x4E6BU, 0x4F6BU, 0x506BU, 0x516BU, 0x526BU, 0x536BU, 0x546BU, 0x556BU, 0x566BU, 0x576BU, 0x586BU, 0x596BU, 0x5A6BU, 0x616BU, 0x626BU, 0x636BU, 0x646BU, 0x656BU, 0x666BU, 0x676BU, 0x686BU, 0x696BU, 0x6A6BU, 0x6B6BU, 0x6C6BU, 0x6D6BU, 0x6E6BU, 0x6F6BU, 0x706BU, 0x716BU, 0x726BU, 0x736BU, 0x746BU, 0x756BU, 0x766BU, 0x776BU, 0x786BU, 0x796BU, 0x7A6BU, 0x306BU, 0x316BU, 0x326BU, 0x336BU, 0x346BU, 0x356BU, 0x366BU, 0x376BU, 0x386BU, 0x396BU, 0x2B6BU, 0x2F6BU, 0x416CU, 0x426CU, 0x436CU, 0x446CU, 0x456CU, 0x466CU, 0x476CU, 0x486CU, 0x496CU, 0x4A6CU, 0x4B6CU, 0x4C6CU, 0x4D6CU, 0x4E6CU, 0x4F6CU, 0x506CU, 0x516CU, 0x526CU, 0x536CU, 0x546CU, 0x556CU, 0x566CU, 0x576CU, 0x586CU, 0x596CU, 0x5A6CU, 0x616CU, 0x626CU, 0x636CU, 0x646CU, 0x656CU, 0x666CU, 0x676CU, 0x686CU, 0x696CU, 0x6A6CU, 0x6B6CU, 0x6C6CU, 0x6D6CU, 0x6E6CU, 0x6F6CU, 0x706CU, 0x716CU, 0x726CU, 0x736CU, 0x746CU, 0x756CU, 0x766CU, 0x776CU, 0x786CU, 0x796CU, 0x7A6CU, 0x306CU, 0x316CU, 0x326CU, 0x336CU, 0x346CU, 0x356CU, 0x366CU, 0x376CU, 0x386CU, 0x396CU, 0x2B6CU, 0x2F6CU, 0x416DU, 0x426DU, 0x436DU, 0x446DU, 0x456DU, 0x466DU, 0x476DU, 0x486DU, 0x496DU, 0x4A6DU, 0x4B6DU, 0x4C6DU, 0x4D6DU, 0x4E6DU, 0x4F6DU, 0x506DU, 0x516DU, 0x526DU, 0x536DU, 0x546DU, 0x556DU, 0x566DU, 0x576DU, 0x586DU, 0x596DU, 0x5A6DU, 0x616DU, 0x626DU, 0x636DU, 0x646DU, 0x656DU, 0x666DU, 0x676DU, 0x686DU, 0x696DU, 0x6A6DU, 0x6B6DU, 0x6C6DU, 0x6D6DU, 0x6E6DU, 0x6F6DU, 0x706DU, 0x716DU, 0x726DU, 0x736DU, 0x746DU, 0x756DU, 0x766DU, 0x776DU, 0x786DU, 0x796DU, 0x7A6DU, 0x306DU, 0x316DU, 0x326DU, 0x336DU, 0x346DU, 0x356DU, 0x366DU, 0x376DU, 0x386DU, 0x396DU, 0x2B6DU, 0x2F6DU, 0x416EU, 0x426EU, 0x436EU, 0x446EU, 0x456EU, 0x466EU, 0x476EU, 0x486EU, 0x496EU, 0x4A6EU, 0x4B6EU, 0x4C6EU, 0x4D6EU, 0x4E6EU, 0x4F6EU, 0x506EU, 0x516EU, 0x526EU, 0x536EU, 0x546EU, 0x556EU, 0x566EU, 0x576EU, 0x586EU, 0x596EU, 0x5A6EU, 0x616EU, 0x626EU, 0x636EU, 0x646EU, 0x656EU, 0x666EU, 0x676EU, 0x686EU, 0x696EU, 0x6A6EU, 0x6B6EU, 0x6C6EU, 0x6D6EU, 0x6E6EU, 0x6F6EU, 0x706EU, 0x716EU, 0x726EU, 0x736EU, 0x746EU, 0x756EU, 0x766EU, 0x776EU, 0x786EU, 0x796EU, 0x7A6EU, 0x306EU, 0x316EU, 0x326EU, 0x336EU, 0x346EU, 0x356EU, 0x366EU, 0x376EU, 0x386EU, 0x396EU, 0x2B6EU, 0x2F6EU, 0x416FU, 0x426FU, 0x436FU, 0x446FU, 0x456FU, 0x466FU, 0x476FU, 0x486FU, 0x496FU, 0x4A6FU, 0x4B6FU, 0x4C6FU, 0x4D6FU, 0x4E6FU, 0x4F6FU, 0x506FU, 0x516FU, 0x526FU, 0x536FU, 0x546FU, 0x556FU, 0x566FU, 0x576FU, 0x586FU, 0x596FU, 0x5A6FU, 0x616FU, 0x626FU, 0x636FU, 0x646FU, 0x656FU, 0x666FU, 0x676FU, 0x686FU, 0x696FU, 0x6A6FU, 0x6B6FU, 0x6C6FU, 0x6D6FU, 0x6E6FU, 0x6F6FU, 0x706FU, 0x716FU, 0x726FU, 0x736FU, 0x746FU, 0x756FU, 0x766FU, 0x776FU, 0x786FU, 0x796FU, 0x7A6FU, 0x306FU, 0x316FU, 0x326FU, 0x336FU, 0x346FU, 0x356FU, 0x366FU, 0x376FU, 0x386FU, 0x396FU, 0x2B6FU, 0x2F6FU, 0x4170U, 0x4270U, 0x4370U, 0x4470U, 0x4570U, 0x4670U, 0x4770U, 0x4870U, 0x4970U, 0x4A70U, 0x4B70U, 0x4C70U, 0x4D70U, 0x4E70U, 0x4F70U, 0x5070U, 0x5170U, 0x5270U, 0x5370U, 0x5470U, 0x5570U, 0x5670U, 0x5770U, 0x5870U, 0x5970U, 0x5A70U, 0x6170U, 0x6270U, 0x6370U, 0x6470U, 0x6570U, 0x6670U, 0x6770U, 0x6870U, 0x6970U, 0x6A70U, 0x6B70U, 0x6C70U, 0x6D70U, 0x6E70U, 0x6F70U, 0x7070U, 0x7170U, 0x7270U, 0x7370U, 0x7470U, 0x7570U, 0x7670U, 0x7770U, 0x7870U, 0x7970U, 0x7A70U, 0x3070U, 0x3170U, 0x3270U, 0x3370U, 0x3470U, 0x3570U, 0x3670U, 0x3770U, 0x3870U, 0x3970U, 0x2B70U, 0x2F70U, 0x4171U, 0x4271U, 0x4371U, 0x4471U, 0x4571U, 0x4671U, 0x4771U, 0x4871U, 0x4971U, 0x4A71U, 0x4B71U, 0x4C71U, 0x4D71U, 0x4E71U, 0x4F71U, 0x5071U, 0x5171U, 0x5271U, 0x5371U, 0x5471U, 0x5571U, 0x5671U, 0x5771U, 0x5871U, 0x5971U, 0x5A71U, 0x6171U, 0x6271U, 0x6371U, 0x6471U, 0x6571U, 0x6671U, 0x6771U, 0x6871U, 0x6971U, 0x6A71U, 0x6B71U, 0x6C71U, 0x6D71U, 0x6E71U, 0x6F71U, 0x7071U, 0x7171U, 0x7271U, 0x7371U, 0x7471U, 0x7571U, 0x7671U, 0x7771U, 0x7871U, 0x7971U, 0x7A71U, 0x3071U, 0x3171U, 0x3271U, 0x3371U, 0x3471U, 0x3571U, 0x3671U, 0x3771U, 0x3871U, 0x3971U, 0x2B71U, 0x2F71U, 0x4172U, 0x4272U, 0x4372U, 0x4472U, 0x4572U, 0x4672U, 0x4772U, 0x4872U, 0x4972U, 0x4A72U, 0x4B72U, 0x4C72U, 0x4D72U, 0x4E72U, 0x4F72U, 0x5072U, 0x5172U, 0x5272U, 0x5372U, 0x5472U, 0x5572U, 0x5672U, 0x5772U, 0x5872U, 0x5972U, 0x5A72U, 0x6172U, 0x6272U, 0x6372U, 0x6472U, 0x6572U, 0x6672U, 0x6772U, 0x6872U, 0x6972U, 0x6A72U, 0x6B72U, 0x6C72U, 0x6D72U, 0x6E72U, 0x6F72U, 0x7072U, 0x7172U, 0x7272U, 0x7372U, 0x7472U, 0x7572U, 0x7672U, 0x7772U, 0x7872U, 0x7972U, 0x7A72U, 0x3072U, 0x3172U, 0x3272U, 0x3372U, 0x3472U, 0x3572U, 0x3672U, 0x3772U, 0x3872U, 0x3972U, 0x2B72U, 0x2F72U, 0x4173U, 0x4273U, 0x4373U, 0x4473U, 0x4573U, 0x4673U, 0x4773U, 0x4873U, 0x4973U, 0x4A73U, 0x4B73U, 0x4C73U, 0x4D73U, 0x4E73U, 0x4F73U, 0x5073U, 0x5173U, 0x5273U, 0x5373U, 0x5473U, 0x5573U, 0x5673U, 0x5773U, 0x5873U, 0x5973U, 0x5A73U, 0x6173U, 0x6273U, 0x6373U, 0x6473U, 0x6573U, 0x6673U, 0x6773U, 0x6873U, 0x6973U, 0x6A73U, 0x6B73U, 0x6C73U, 0x6D73U, 0x6E73U, 0x6F73U, 0x7073U, 0x7173U, 0x7273U, 0x7373U, 0x7473U, 0x7573U, 0x7673U, 0x7773U, 0x7873U, 0x7973U, 0x7A73U, 0x3073U, 0x3173U, 0x3273U, 0x3373U, 0x3473U, 0x3573U, 0x3673U, 0x3773U, 0x3873U, 0x3973U, 0x2B73U, 0x2F73U, 0x4174U, 0x4274U, 0x4374U, 0x4474U, 0x4574U, 0x4674U, 0x4774U, 0x4874U, 0x4974U, 0x4A74U, 0x4B74U, 0x4C74U, 0x4D74U, 0x4E74U, 0x4F74U, 0x5074U, 0x5174U, 0x5274U, 0x5374U, 0x5474U, 0x5574U, 0x5674U, 0x5774U, 0x5874U, 0x5974U, 0x5A74U, 0x6174U, 0x6274U, 0x6374U, 0x6474U, 0x6574U, 0x6674U, 0x6774U, 0x6874U, 0x6974U, 0x6A74U, 0x6B74U, 0x6C74U, 0x6D74U, 0x6E74U, 0x6F74U, 0x7074U, 0x7174U, 0x7274U, 0x7374U, 0x7474U, 0x7574U, 0x7674U, 0x7774U, 0x7874U, 0x7974U, 0x7A74U, 0x3074U, 0x3174U, 0x3274U, 0x3374U, 0x3474U, 0x3574U, 0x3674U, 0x3774U, 0x3874U, 0x3974U, 0x2B74U, 0x2F74U, 0x4175U, 0x4275U, 0x4375U, 0x4475U, 0x4575U, 0x4675U, 0x4775U, 0x4875U, 0x4975U, 0x4A75U, 0x4B75U, 0x4C75U, 0x4D75U, 0x4E75U, 0x4F75U, 0x5075U, 0x5175U, 0x5275U, 0x5375U, 0x5475U, 0x5575U, 0x5675U, 0x5775U, 0x5875U, 0x5975U, 0x5A75U, 0x6175U, 0x6275U, 0x6375U, 0x6475U, 0x6575U, 0x6675U, 0x6775U, 0x6875U, 0x6975U, 0x6A75U, 0x6B75U, 0x6C75U, 0x6D75U, 0x6E75U, 0x6F75U, 0x7075U, 0x7175U, 0x7275U, 0x7375U, 0x7475U, 0x7575U, 0x7675U, 0x7775U, 0x7875U, 0x7975U, 0x7A75U, 0x3075U, 0x3175U, 0x3275U, 0x3375U, 0x3475U, 0x3575U, 0x3675U, 0x3775U, 0x3875U, 0x3975U, 0x2B75U, 0x2F75U, 0x4176U, 0x4276U, 0x4376U, 0x4476U, 0x4576U, 0x4676U, 0x4776U, 0x4876U, 0x4976U, 0x4A76U, 0x4B76U, 0x4C76U, 0x4D76U, 0x4E76U, 0x4F76U, 0x5076U, 0x5176U, 0x5276U, 0x5376U, 0x5476U, 0x5576U, 0x5676U, 0x5776U, 0x5876U, 0x5976U, 0x5A76U, 0x6176U, 0x6276U, 0x6376U, 0x6476U, 0x6576U, 0x6676U, 0x6776U, 0x6876U, 0x6976U, 0x6A76U, 0x6B76U, 0x6C76U, 0x6D76U, 0x6E76U, 0x6F76U, 0x7076U, 0x7176U, 0x7276U, 0x7376U, 0x7476U, 0x7576U, 0x7676U, 0x7776U, 0x7876U, 0x7976U, 0x7A76U, 0x3076U, 0x3176U, 0x3276U, 0x3376U, 0x3476U, 0x3576U, 0x3676U, 0x3776U, 0x3876U, 0x3976U, 0x2B76U, 0x2F76U, 0x4177U, 0x4277U, 0x4377U, 0x4477U, 0x4577U, 0x4677U, 0x4777U, 0x4877U, 0x4977U, 0x4A77U, 0x4B77U, 0x4C77U, 0x4D77U, 0x4E77U, 0x4F77U, 0x5077U, 0x5177U, 0x5277U, 0x5377U, 0x5477U, 0x5577U, 0x5677U, 0x5777U, 0x5877U, 0x5977U, 0x5A77U, 0x6177U, 0x6277U, 0x6377U, 0x6477U, 0x6577U, 0x6677U, 0x6777U, 0x6877U, 0x6977U, 0x6A77U, 0x6B77U, 0x6C77U, 0x6D77U, 0x6E77U, 0x6F77U, 0x7077U, 0x7177U, 0x7277U, 0x7377U, 0x7477U, 0x7577U, 0x7677U, 0x7777U, 0x7877U, 0x7977U, 0x7A77U, 0x3077U, 0x3177U, 0x3277U, 0x3377U, 0x3477U, 0x3577U, 0x3677U, 0x3777U, 0x3877U, 0x3977U, 0x2B77U, 0x2F77U, 0x4178U, 0x4278U, 0x4378U, 0x4478U, 0x4578U, 0x4678U, 0x4778U, 0x4878U, 0x4978U, 0x4A78U, 0x4B78U, 0x4C78U, 0x4D78U, 0x4E78U, 0x4F78U, 0x5078U, 0x5178U, 0x5278U, 0x5378U, 0x5478U, 0x5578U, 0x5678U, 0x5778U, 0x5878U, 0x5978U, 0x5A78U, 0x6178U, 0x6278U, 0x6378U, 0x6478U, 0x6578U, 0x6678U, 0x6778U, 0x6878U, 0x6978U, 0x6A78U, 0x6B78U, 0x6C78U, 0x6D78U, 0x6E78U, 0x6F78U, 0x7078U, 0x7178U, 0x7278U, 0x7378U, 0x7478U, 0x7578U, 0x7678U, 0x7778U, 0x7878U, 0x7978U, 0x7A78U, 0x3078U, 0x3178U, 0x3278U, 0x3378U, 0x3478U, 0x3578U, 0x3678U, 0x3778U, 0x3878U, 0x3978U, 0x2B78U, 0x2F78U, 0x4179U, 0x4279U, 0x4379U, 0x4479U, 0x4579U, 0x4679U, 0x4779U, 0x4879U, 0x4979U, 0x4A79U, 0x4B79U, 0x4C79U, 0x4D79U, 0x4E79U, 0x4F79U, 0x5079U, 0x5179U, 0x5279U, 0x5379U, 0x5479U, 0x5579U, 0x5679U, 0x5779U, 0x5879U, 0x5979U, 0x5A79U, 0x6179U, 0x6279U, 0x6379U, 0x6479U, 0x6579U, 0x6679U, 0x6779U, 0x6879U, 0x6979U, 0x6A79U, 0x6B79U, 0x6C79U, 0x6D79U, 0x6E79U, 0x6F79U, 0x7079U, 0x7179U, 0x7279U, 0x7379U, 0x7479U, 0x7579U, 0x7679U, 0x7779U, 0x7879U, 0x7979U, 0x7A79U, 0x3079U, 0x3179U, 0x3279U, 0x3379U, 0x3479U, 0x3579U, 0x3679U, 0x3779U, 0x3879U, 0x3979U, 0x2B79U, 0x2F79U, 0x417AU, 0x427AU, 0x437AU, 0x447AU, 0x457AU, 0x467AU, 0x477AU, 0x487AU, 0x497AU, 0x4A7AU, 0x4B7AU, 0x4C7AU, 0x4D7AU, 0x4E7AU, 0x4F7AU, 0x507AU, 0x517AU, 0x527AU, 0x537AU, 0x547AU, 0x557AU, 0x567AU, 0x577AU, 0x587AU, 0x597AU, 0x5A7AU, 0x617AU, 0x627AU, 0x637AU, 0x647AU, 0x657AU, 0x667AU, 0x677AU, 0x687AU, 0x697AU, 0x6A7AU, 0x6B7AU, 0x6C7AU, 0x6D7AU, 0x6E7AU, 0x6F7AU, 0x707AU, 0x717AU, 0x727AU, 0x737AU, 0x747AU, 0x757AU, 0x767AU, 0x777AU, 0x787AU, 0x797AU, 0x7A7AU, 0x307AU, 0x317AU, 0x327AU, 0x337AU, 0x347AU, 0x357AU, 0x367AU, 0x377AU, 0x387AU, 0x397AU, 0x2B7AU, 0x2F7AU, 0x4130U, 0x4230U, 0x4330U, 0x4430U, 0x4530U, 0x4630U, 0x4730U, 0x4830U, 0x4930U, 0x4A30U, 0x4B30U, 0x4C30U, 0x4D30U, 0x4E30U, 0x4F30U, 0x5030U, 0x5130U, 0x5230U, 0x5330U, 0x5430U, 0x5530U, 0x5630U, 0x5730U, 0x5830U, 0x5930U, 0x5A30U, 0x6130U, 0x6230U, 0x6330U, 0x6430U, 0x6530U, 0x6630U, 0x6730U, 0x6830U, 0x6930U, 0x6A30U, 0x6B30U, 0x6C30U, 0x6D30U, 0x6E30U, 0x6F30U, 0x7030U, 0x7130U, 0x7230U, 0x7330U, 0x7430U, 0x7530U, 0x7630U, 0x7730U, 0x7830U, 0x7930U, 0x7A30U, 0x3030U, 0x3130U, 0x3230U, 0x3330U, 0x3430U, 0x3530U, 0x3630U, 0x3730U, 0x3830U, 0x3930U, 0x2B30U, 0x2F30U, 0x4131U, 0x4231U, 0x4331U, 0x4431U, 0x4531U, 0x4631U, 0x4731U, 0x4831U, 0x4931U, 0x4A31U, 0x4B31U, 0x4C31U, 0x4D31U, 0x4E31U, 0x4F31U, 0x5031U, 0x5131U, 0x5231U, 0x5331U, 0x5431U, 0x5531U, 0x5631U, 0x5731U, 0x5831U, 0x5931U, 0x5A31U, 0x6131U, 0x6231U, 0x6331U, 0x6431U, 0x6531U, 0x6631U, 0x6731U, 0x6831U, 0x6931U, 0x6A31U, 0x6B31U, 0x6C31U, 0x6D31U, 0x6E31U, 0x6F31U, 0x7031U, 0x7131U, 0x7231U, 0x7331U, 0x7431U, 0x7531U, 0x7631U, 0x7731U, 0x7831U, 0x7931U, 0x7A31U, 0x3031U, 0x3131U, 0x3231U, 0x3331U, 0x3431U, 0x3531U, 0x3631U, 0x3731U, 0x3831U, 0x3931U, 0x2B31U, 0x2F31U, 0x4132U, 0x4232U, 0x4332U, 0x4432U, 0x4532U, 0x4632U, 0x4732U, 0x4832U, 0x4932U, 0x4A32U, 0x4B32U, 0x4C32U, 0x4D32U, 0x4E32U, 0x4F32U, 0x5032U, 0x5132U, 0x5232U, 0x5332U, 0x5432U, 0x5532U, 0x5632U, 0x5732U, 0x5832U, 0x5932U, 0x5A32U, 0x6132U, 0x6232U, 0x6332U, 0x6432U, 0x6532U, 0x6632U, 0x6732U, 0x6832U, 0x6932U, 0x6A32U, 0x6B32U, 0x6C32U, 0x6D32U, 0x6E32U, 0x6F32U, 0x7032U, 0x7132U, 0x7232U, 0x7332U, 0x7432U, 0x7532U, 0x7632U, 0x7732U, 0x7832U, 0x7932U, 0x7A32U, 0x3032U, 0x3132U, 0x3232U, 0x3332U, 0x3432U, 0x3532U, 0x3632U, 0x3732U, 0x3832U, 0x3932U, 0x2B32U, 0x2F32U, 0x4133U, 0x4233U, 0x4333U, 0x4433U, 0x4533U, 0x4633U, 0x4733U, 0x4833U, 0x4933U, 0x4A33U, 0x4B33U, 0x4C33U, 0x4D33U, 0x4E33U, 0x4F33U, 0x5033U, 0x5133U, 0x5233U, 0x5333U, 0x5433U, 0x5533U, 0x5633U, 0x5733U, 0x5833U, 0x5933U, 0x5A33U, 0x6133U, 0x6233U, 0x6333U, 0x6433U, 0x6533U, 0x6633U, 0x6733U, 0x6833U, 0x6933U, 0x6A33U, 0x6B33U, 0x6C33U, 0x6D33U, 0x6E33U, 0x6F33U, 0x7033U, 0x7133U, 0x7233U, 0x7333U, 0x7433U, 0x7533U, 0x7633U, 0x7733U, 0x7833U, 0x7933U, 0x7A33U, 0x3033U, 0x3133U, 0x3233U, 0x3333U, 0x3433U, 0x3533U, 0x3633U, 0x3733U, 0x3833U, 0x3933U, 0x2B33U, 0x2F33U, 0x4134U, 0x4234U, 0x4334U, 0x4434U, 0x4534U, 0x4634U, 0x4734U, 0x4834U, 0x4934U, 0x4A34U, 0x4B34U, 0x4C34U, 0x4D34U, 0x4E34U, 0x4F34U, 0x5034U, 0x5134U, 0x5234U, 0x5334U, 0x5434U, 0x5534U, 0x5634U, 0x5734U, 0x5834U, 0x5934U, 0x5A34U, 0x6134U, 0x6234U, 0x6334U, 0x6434U, 0x6534U, 0x6634U, 0x6734U, 0x6834U, 0x6934U, 0x6A34U, 0x6B34U, 0x6C34U, 0x6D34U, 0x6E34U, 0x6F34U, 0x7034U, 0x7134U, 0x7234U, 0x7334U, 0x7434U, 0x7534U, 0x7634U, 0x7734U, 0x7834U, 0x7934U, 0x7A34U, 0x3034U, 0x3134U, 0x3234U, 0x3334U, 0x3434U, 0x3534U, 0x3634U, 0x3734U, 0x3834U, 0x3934U, 0x2B34U, 0x2F34U, 0x4135U, 0x4235U, 0x4335U, 0x4435U, 0x4535U, 0x4635U, 0x4735U, 0x4835U, 0x4935U, 0x4A35U, 0x4B35U, 0x4C35U, 0x4D35U, 0x4E35U, 0x4F35U, 0x5035U, 0x5135U, 0x5235U, 0x5335U, 0x5435U, 0x5535U, 0x5635U, 0x5735U, 0x5835U, 0x5935U, 0x5A35U, 0x6135U, 0x6235U, 0x6335U, 0x6435U, 0x6535U, 0x6635U, 0x6735U, 0x6835U, 0x6935U, 0x6A35U, 0x6B35U, 0x6C35U, 0x6D35U, 0x6E35U, 0x6F35U, 0x7035U, 0x7135U, 0x7235U, 0x7335U, 0x7435U, 0x7535U, 0x7635U, 0x7735U, 0x7835U, 0x7935U, 0x7A35U, 0x3035U, 0x3135U, 0x3235U, 0x3335U, 0x3435U, 0x3535U, 0x3635U, 0x3735U, 0x3835U, 0x3935U, 0x2B35U, 0x2F35U, 0x4136U, 0x4236U, 0x4336U, 0x4436U, 0x4536U, 0x4636U, 0x4736U, 0x4836U, 0x4936U, 0x4A36U, 0x4B36U, 0x4C36U, 0x4D36U, 0x4E36U, 0x4F36U, 0x5036U, 0x5136U, 0x5236U, 0x5336U, 0x5436U, 0x5536U, 0x5636U, 0x5736U, 0x5836U, 0x5936U, 0x5A36U, 0x6136U, 0x6236U, 0x6336U, 0x6436U, 0x6536U, 0x6636U, 0x6736U, 0x6836U, 0x6936U, 0x6A36U, 0x6B36U, 0x6C36U, 0x6D36U, 0x6E36U, 0x6F36U, 0x7036U, 0x7136U, 0x7236U, 0x7336U, 0x7436U, 0x7536U, 0x7636U, 0x7736U, 0x7836U, 0x7936U, 0x7A36U, 0x3036U, 0x3136U, 0x3236U, 0x3336U, 0x3436U, 0x3536U, 0x3636U, 0x3736U, 0x3836U, 0x3936U, 0x2B36U, 0x2F36U, 0x4137U, 0x4237U, 0x4337U, 0x4437U, 0x4537U, 0x4637U, 0x4737U, 0x4837U, 0x4937U, 0x4A37U, 0x4B37U, 0x4C37U, 0x4D37U, 0x4E37U, 0x4F37U, 0x5037U, 0x5137U, 0x5237U, 0x5337U, 0x5437U, 0x5537U, 0x5637U, 0x5737U, 0x5837U, 0x5937U, 0x5A37U, 0x6137U, 0x6237U, 0x6337U, 0x6437U, 0x6537U, 0x6637U, 0x6737U, 0x6837U, 0x6937U, 0x6A37U, 0x6B37U, 0x6C37U, 0x6D37U, 0x6E37U, 0x6F37U, 0x7037U, 0x7137U, 0x7237U, 0x7337U, 0x7437U, 0x7537U, 0x7637U, 0x7737U, 0x7837U, 0x7937U, 0x7A37U, 0x3037U, 0x3137U, 0x3237U, 0x3337U, 0x3437U, 0x3537U, 0x3637U, 0x3737U, 0x3837U, 0x3937U, 0x2B37U, 0x2F37U, 0x4138U, 0x4238U, 0x4338U, 0x4438U, 0x4538U, 0x4638U, 0x4738U, 0x4838U, 0x4938U, 0x4A38U, 0x4B38U, 0x4C38U, 0x4D38U, 0x4E38U, 0x4F38U, 0x5038U, 0x5138U, 0x5238U, 0x5338U, 0x5438U, 0x5538U, 0x5638U, 0x5738U, 0x5838U, 0x5938U, 0x5A38U, 0x6138U, 0x6238U, 0x6338U, 0x6438U, 0x6538U, 0x6638U, 0x6738U, 0x6838U, 0x6938U, 0x6A38U, 0x6B38U, 0x6C38U, 0x6D38U, 0x6E38U, 0x6F38U, 0x7038U, 0x7138U, 0x7238U, 0x7338U, 0x7438U, 0x7538U, 0x7638U, 0x7738U, 0x7838U, 0x7938U, 0x7A38U, 0x3038U, 0x3138U, 0x3238U, 0x3338U, 0x3438U, 0x3538U, 0x3638U, 0x3738U, 0x3838U, 0x3938U, 0x2B38U, 0x2F38U, 0x4139U, 0x4239U, 0x4339U, 0x4439U, 0x4539U, 0x4639U, 0x4739U, 0x4839U, 0x4939U, 0x4A39U, 0x4B39U, 0x4C39U, 0x4D39U, 0x4E39U, 0x4F39U, 0x5039U, 0x5139U, 0x5239U, 0x5339U, 0x5439U, 0x5539U, 0x5639U, 0x5739U, 0x5839U, 0x5939U, 0x5A39U, 0x6139U, 0x6239U, 0x6339U, 0x6439U, 0x6539U, 0x6639U, 0x6739U, 0x6839U, 0x6939U, 0x6A39U, 0x6B39U, 0x6C39U, 0x6D39U, 0x6E39U, 0x6F39U, 0x7039U, 0x7139U, 0x7239U, 0x7339U, 0x7439U, 0x7539U, 0x7639U, 0x7739U, 0x7839U, 0x7939U, 0x7A39U, 0x3039U, 0x3139U, 0x3239U, 0x3339U, 0x3439U, 0x3539U, 0x3639U, 0x3739U, 0x3839U, 0x3939U, 0x2B39U, 0x2F39U, 0x412BU, 0x422BU, 0x432BU, 0x442BU, 0x452BU, 0x462BU, 0x472BU, 0x482BU, 0x492BU, 0x4A2BU, 0x4B2BU, 0x4C2BU, 0x4D2BU, 0x4E2BU, 0x4F2BU, 0x502BU, 0x512BU, 0x522BU, 0x532BU, 0x542BU, 0x552BU, 0x562BU, 0x572BU, 0x582BU, 0x592BU, 0x5A2BU, 0x612BU, 0x622BU, 0x632BU, 0x642BU, 0x652BU, 0x662BU, 0x672BU, 0x682BU, 0x692BU, 0x6A2BU, 0x6B2BU, 0x6C2BU, 0x6D2BU, 0x6E2BU, 0x6F2BU, 0x702BU, 0x712BU, 0x722BU, 0x732BU, 0x742BU, 0x752BU, 0x762BU, 0x772BU, 0x782BU, 0x792BU, 0x7A2BU, 0x302BU, 0x312BU, 0x322BU, 0x332BU, 0x342BU, 0x352BU, 0x362BU, 0x372BU, 0x382BU, 0x392BU, 0x2B2BU, 0x2F2BU, 0x412FU, 0x422FU, 0x432FU, 0x442FU, 0x452FU, 0x462FU, 0x472FU, 0x482FU, 0x492FU, 0x4A2FU, 0x4B2FU, 0x4C2FU, 0x4D2FU, 0x4E2FU, 0x4F2FU, 0x502FU, 0x512FU, 0x522FU, 0x532FU, 0x542FU, 0x552FU, 0x562FU, 0x572FU, 0x582FU, 0x592FU, 0x5A2FU, 0x612FU, 0x622FU, 0x632FU, 0x642FU, 0x652FU, 0x662FU, 0x672FU, 0x682FU, 0x692FU, 0x6A2FU, 0x6B2FU, 0x6C2FU, 0x6D2FU, 0x6E2FU, 0x6F2FU, 0x702FU, 0x712FU, 0x722FU, 0x732FU, 0x742FU, 0x752FU, 0x762FU, 0x772FU, 0x782FU, 0x792FU, 0x7A2FU, 0x302FU, 0x312FU, 0x322FU, 0x332FU, 0x342FU, 0x352FU, 0x362FU, 0x372FU, 0x382FU, 0x392FU, 0x2B2FU, 0x2F2FU, #else 0x4141U, 0x4142U, 0x4143U, 0x4144U, 0x4145U, 0x4146U, 0x4147U, 0x4148U, 0x4149U, 0x414AU, 0x414BU, 0x414CU, 0x414DU, 0x414EU, 0x414FU, 0x4150U, 0x4151U, 0x4152U, 0x4153U, 0x4154U, 0x4155U, 0x4156U, 0x4157U, 0x4158U, 0x4159U, 0x415AU, 0x4161U, 0x4162U, 0x4163U, 0x4164U, 0x4165U, 0x4166U, 0x4167U, 0x4168U, 0x4169U, 0x416AU, 0x416BU, 0x416CU, 0x416DU, 0x416EU, 0x416FU, 0x4170U, 0x4171U, 0x4172U, 0x4173U, 0x4174U, 0x4175U, 0x4176U, 0x4177U, 0x4178U, 0x4179U, 0x417AU, 0x4130U, 0x4131U, 0x4132U, 0x4133U, 0x4134U, 0x4135U, 0x4136U, 0x4137U, 0x4138U, 0x4139U, 0x412BU, 0x412FU, 0x4241U, 0x4242U, 0x4243U, 0x4244U, 0x4245U, 0x4246U, 0x4247U, 0x4248U, 0x4249U, 0x424AU, 0x424BU, 0x424CU, 0x424DU, 0x424EU, 0x424FU, 0x4250U, 0x4251U, 0x4252U, 0x4253U, 0x4254U, 0x4255U, 0x4256U, 0x4257U, 0x4258U, 0x4259U, 0x425AU, 0x4261U, 0x4262U, 0x4263U, 0x4264U, 0x4265U, 0x4266U, 0x4267U, 0x4268U, 0x4269U, 0x426AU, 0x426BU, 0x426CU, 0x426DU, 0x426EU, 0x426FU, 0x4270U, 0x4271U, 0x4272U, 0x4273U, 0x4274U, 0x4275U, 0x4276U, 0x4277U, 0x4278U, 0x4279U, 0x427AU, 0x4230U, 0x4231U, 0x4232U, 0x4233U, 0x4234U, 0x4235U, 0x4236U, 0x4237U, 0x4238U, 0x4239U, 0x422BU, 0x422FU, 0x4341U, 0x4342U, 0x4343U, 0x4344U, 0x4345U, 0x4346U, 0x4347U, 0x4348U, 0x4349U, 0x434AU, 0x434BU, 0x434CU, 0x434DU, 0x434EU, 0x434FU, 0x4350U, 0x4351U, 0x4352U, 0x4353U, 0x4354U, 0x4355U, 0x4356U, 0x4357U, 0x4358U, 0x4359U, 0x435AU, 0x4361U, 0x4362U, 0x4363U, 0x4364U, 0x4365U, 0x4366U, 0x4367U, 0x4368U, 0x4369U, 0x436AU, 0x436BU, 0x436CU, 0x436DU, 0x436EU, 0x436FU, 0x4370U, 0x4371U, 0x4372U, 0x4373U, 0x4374U, 0x4375U, 0x4376U, 0x4377U, 0x4378U, 0x4379U, 0x437AU, 0x4330U, 0x4331U, 0x4332U, 0x4333U, 0x4334U, 0x4335U, 0x4336U, 0x4337U, 0x4338U, 0x4339U, 0x432BU, 0x432FU, 0x4441U, 0x4442U, 0x4443U, 0x4444U, 0x4445U, 0x4446U, 0x4447U, 0x4448U, 0x4449U, 0x444AU, 0x444BU, 0x444CU, 0x444DU, 0x444EU, 0x444FU, 0x4450U, 0x4451U, 0x4452U, 0x4453U, 0x4454U, 0x4455U, 0x4456U, 0x4457U, 0x4458U, 0x4459U, 0x445AU, 0x4461U, 0x4462U, 0x4463U, 0x4464U, 0x4465U, 0x4466U, 0x4467U, 0x4468U, 0x4469U, 0x446AU, 0x446BU, 0x446CU, 0x446DU, 0x446EU, 0x446FU, 0x4470U, 0x4471U, 0x4472U, 0x4473U, 0x4474U, 0x4475U, 0x4476U, 0x4477U, 0x4478U, 0x4479U, 0x447AU, 0x4430U, 0x4431U, 0x4432U, 0x4433U, 0x4434U, 0x4435U, 0x4436U, 0x4437U, 0x4438U, 0x4439U, 0x442BU, 0x442FU, 0x4541U, 0x4542U, 0x4543U, 0x4544U, 0x4545U, 0x4546U, 0x4547U, 0x4548U, 0x4549U, 0x454AU, 0x454BU, 0x454CU, 0x454DU, 0x454EU, 0x454FU, 0x4550U, 0x4551U, 0x4552U, 0x4553U, 0x4554U, 0x4555U, 0x4556U, 0x4557U, 0x4558U, 0x4559U, 0x455AU, 0x4561U, 0x4562U, 0x4563U, 0x4564U, 0x4565U, 0x4566U, 0x4567U, 0x4568U, 0x4569U, 0x456AU, 0x456BU, 0x456CU, 0x456DU, 0x456EU, 0x456FU, 0x4570U, 0x4571U, 0x4572U, 0x4573U, 0x4574U, 0x4575U, 0x4576U, 0x4577U, 0x4578U, 0x4579U, 0x457AU, 0x4530U, 0x4531U, 0x4532U, 0x4533U, 0x4534U, 0x4535U, 0x4536U, 0x4537U, 0x4538U, 0x4539U, 0x452BU, 0x452FU, 0x4641U, 0x4642U, 0x4643U, 0x4644U, 0x4645U, 0x4646U, 0x4647U, 0x4648U, 0x4649U, 0x464AU, 0x464BU, 0x464CU, 0x464DU, 0x464EU, 0x464FU, 0x4650U, 0x4651U, 0x4652U, 0x4653U, 0x4654U, 0x4655U, 0x4656U, 0x4657U, 0x4658U, 0x4659U, 0x465AU, 0x4661U, 0x4662U, 0x4663U, 0x4664U, 0x4665U, 0x4666U, 0x4667U, 0x4668U, 0x4669U, 0x466AU, 0x466BU, 0x466CU, 0x466DU, 0x466EU, 0x466FU, 0x4670U, 0x4671U, 0x4672U, 0x4673U, 0x4674U, 0x4675U, 0x4676U, 0x4677U, 0x4678U, 0x4679U, 0x467AU, 0x4630U, 0x4631U, 0x4632U, 0x4633U, 0x4634U, 0x4635U, 0x4636U, 0x4637U, 0x4638U, 0x4639U, 0x462BU, 0x462FU, 0x4741U, 0x4742U, 0x4743U, 0x4744U, 0x4745U, 0x4746U, 0x4747U, 0x4748U, 0x4749U, 0x474AU, 0x474BU, 0x474CU, 0x474DU, 0x474EU, 0x474FU, 0x4750U, 0x4751U, 0x4752U, 0x4753U, 0x4754U, 0x4755U, 0x4756U, 0x4757U, 0x4758U, 0x4759U, 0x475AU, 0x4761U, 0x4762U, 0x4763U, 0x4764U, 0x4765U, 0x4766U, 0x4767U, 0x4768U, 0x4769U, 0x476AU, 0x476BU, 0x476CU, 0x476DU, 0x476EU, 0x476FU, 0x4770U, 0x4771U, 0x4772U, 0x4773U, 0x4774U, 0x4775U, 0x4776U, 0x4777U, 0x4778U, 0x4779U, 0x477AU, 0x4730U, 0x4731U, 0x4732U, 0x4733U, 0x4734U, 0x4735U, 0x4736U, 0x4737U, 0x4738U, 0x4739U, 0x472BU, 0x472FU, 0x4841U, 0x4842U, 0x4843U, 0x4844U, 0x4845U, 0x4846U, 0x4847U, 0x4848U, 0x4849U, 0x484AU, 0x484BU, 0x484CU, 0x484DU, 0x484EU, 0x484FU, 0x4850U, 0x4851U, 0x4852U, 0x4853U, 0x4854U, 0x4855U, 0x4856U, 0x4857U, 0x4858U, 0x4859U, 0x485AU, 0x4861U, 0x4862U, 0x4863U, 0x4864U, 0x4865U, 0x4866U, 0x4867U, 0x4868U, 0x4869U, 0x486AU, 0x486BU, 0x486CU, 0x486DU, 0x486EU, 0x486FU, 0x4870U, 0x4871U, 0x4872U, 0x4873U, 0x4874U, 0x4875U, 0x4876U, 0x4877U, 0x4878U, 0x4879U, 0x487AU, 0x4830U, 0x4831U, 0x4832U, 0x4833U, 0x4834U, 0x4835U, 0x4836U, 0x4837U, 0x4838U, 0x4839U, 0x482BU, 0x482FU, 0x4941U, 0x4942U, 0x4943U, 0x4944U, 0x4945U, 0x4946U, 0x4947U, 0x4948U, 0x4949U, 0x494AU, 0x494BU, 0x494CU, 0x494DU, 0x494EU, 0x494FU, 0x4950U, 0x4951U, 0x4952U, 0x4953U, 0x4954U, 0x4955U, 0x4956U, 0x4957U, 0x4958U, 0x4959U, 0x495AU, 0x4961U, 0x4962U, 0x4963U, 0x4964U, 0x4965U, 0x4966U, 0x4967U, 0x4968U, 0x4969U, 0x496AU, 0x496BU, 0x496CU, 0x496DU, 0x496EU, 0x496FU, 0x4970U, 0x4971U, 0x4972U, 0x4973U, 0x4974U, 0x4975U, 0x4976U, 0x4977U, 0x4978U, 0x4979U, 0x497AU, 0x4930U, 0x4931U, 0x4932U, 0x4933U, 0x4934U, 0x4935U, 0x4936U, 0x4937U, 0x4938U, 0x4939U, 0x492BU, 0x492FU, 0x4A41U, 0x4A42U, 0x4A43U, 0x4A44U, 0x4A45U, 0x4A46U, 0x4A47U, 0x4A48U, 0x4A49U, 0x4A4AU, 0x4A4BU, 0x4A4CU, 0x4A4DU, 0x4A4EU, 0x4A4FU, 0x4A50U, 0x4A51U, 0x4A52U, 0x4A53U, 0x4A54U, 0x4A55U, 0x4A56U, 0x4A57U, 0x4A58U, 0x4A59U, 0x4A5AU, 0x4A61U, 0x4A62U, 0x4A63U, 0x4A64U, 0x4A65U, 0x4A66U, 0x4A67U, 0x4A68U, 0x4A69U, 0x4A6AU, 0x4A6BU, 0x4A6CU, 0x4A6DU, 0x4A6EU, 0x4A6FU, 0x4A70U, 0x4A71U, 0x4A72U, 0x4A73U, 0x4A74U, 0x4A75U, 0x4A76U, 0x4A77U, 0x4A78U, 0x4A79U, 0x4A7AU, 0x4A30U, 0x4A31U, 0x4A32U, 0x4A33U, 0x4A34U, 0x4A35U, 0x4A36U, 0x4A37U, 0x4A38U, 0x4A39U, 0x4A2BU, 0x4A2FU, 0x4B41U, 0x4B42U, 0x4B43U, 0x4B44U, 0x4B45U, 0x4B46U, 0x4B47U, 0x4B48U, 0x4B49U, 0x4B4AU, 0x4B4BU, 0x4B4CU, 0x4B4DU, 0x4B4EU, 0x4B4FU, 0x4B50U, 0x4B51U, 0x4B52U, 0x4B53U, 0x4B54U, 0x4B55U, 0x4B56U, 0x4B57U, 0x4B58U, 0x4B59U, 0x4B5AU, 0x4B61U, 0x4B62U, 0x4B63U, 0x4B64U, 0x4B65U, 0x4B66U, 0x4B67U, 0x4B68U, 0x4B69U, 0x4B6AU, 0x4B6BU, 0x4B6CU, 0x4B6DU, 0x4B6EU, 0x4B6FU, 0x4B70U, 0x4B71U, 0x4B72U, 0x4B73U, 0x4B74U, 0x4B75U, 0x4B76U, 0x4B77U, 0x4B78U, 0x4B79U, 0x4B7AU, 0x4B30U, 0x4B31U, 0x4B32U, 0x4B33U, 0x4B34U, 0x4B35U, 0x4B36U, 0x4B37U, 0x4B38U, 0x4B39U, 0x4B2BU, 0x4B2FU, 0x4C41U, 0x4C42U, 0x4C43U, 0x4C44U, 0x4C45U, 0x4C46U, 0x4C47U, 0x4C48U, 0x4C49U, 0x4C4AU, 0x4C4BU, 0x4C4CU, 0x4C4DU, 0x4C4EU, 0x4C4FU, 0x4C50U, 0x4C51U, 0x4C52U, 0x4C53U, 0x4C54U, 0x4C55U, 0x4C56U, 0x4C57U, 0x4C58U, 0x4C59U, 0x4C5AU, 0x4C61U, 0x4C62U, 0x4C63U, 0x4C64U, 0x4C65U, 0x4C66U, 0x4C67U, 0x4C68U, 0x4C69U, 0x4C6AU, 0x4C6BU, 0x4C6CU, 0x4C6DU, 0x4C6EU, 0x4C6FU, 0x4C70U, 0x4C71U, 0x4C72U, 0x4C73U, 0x4C74U, 0x4C75U, 0x4C76U, 0x4C77U, 0x4C78U, 0x4C79U, 0x4C7AU, 0x4C30U, 0x4C31U, 0x4C32U, 0x4C33U, 0x4C34U, 0x4C35U, 0x4C36U, 0x4C37U, 0x4C38U, 0x4C39U, 0x4C2BU, 0x4C2FU, 0x4D41U, 0x4D42U, 0x4D43U, 0x4D44U, 0x4D45U, 0x4D46U, 0x4D47U, 0x4D48U, 0x4D49U, 0x4D4AU, 0x4D4BU, 0x4D4CU, 0x4D4DU, 0x4D4EU, 0x4D4FU, 0x4D50U, 0x4D51U, 0x4D52U, 0x4D53U, 0x4D54U, 0x4D55U, 0x4D56U, 0x4D57U, 0x4D58U, 0x4D59U, 0x4D5AU, 0x4D61U, 0x4D62U, 0x4D63U, 0x4D64U, 0x4D65U, 0x4D66U, 0x4D67U, 0x4D68U, 0x4D69U, 0x4D6AU, 0x4D6BU, 0x4D6CU, 0x4D6DU, 0x4D6EU, 0x4D6FU, 0x4D70U, 0x4D71U, 0x4D72U, 0x4D73U, 0x4D74U, 0x4D75U, 0x4D76U, 0x4D77U, 0x4D78U, 0x4D79U, 0x4D7AU, 0x4D30U, 0x4D31U, 0x4D32U, 0x4D33U, 0x4D34U, 0x4D35U, 0x4D36U, 0x4D37U, 0x4D38U, 0x4D39U, 0x4D2BU, 0x4D2FU, 0x4E41U, 0x4E42U, 0x4E43U, 0x4E44U, 0x4E45U, 0x4E46U, 0x4E47U, 0x4E48U, 0x4E49U, 0x4E4AU, 0x4E4BU, 0x4E4CU, 0x4E4DU, 0x4E4EU, 0x4E4FU, 0x4E50U, 0x4E51U, 0x4E52U, 0x4E53U, 0x4E54U, 0x4E55U, 0x4E56U, 0x4E57U, 0x4E58U, 0x4E59U, 0x4E5AU, 0x4E61U, 0x4E62U, 0x4E63U, 0x4E64U, 0x4E65U, 0x4E66U, 0x4E67U, 0x4E68U, 0x4E69U, 0x4E6AU, 0x4E6BU, 0x4E6CU, 0x4E6DU, 0x4E6EU, 0x4E6FU, 0x4E70U, 0x4E71U, 0x4E72U, 0x4E73U, 0x4E74U, 0x4E75U, 0x4E76U, 0x4E77U, 0x4E78U, 0x4E79U, 0x4E7AU, 0x4E30U, 0x4E31U, 0x4E32U, 0x4E33U, 0x4E34U, 0x4E35U, 0x4E36U, 0x4E37U, 0x4E38U, 0x4E39U, 0x4E2BU, 0x4E2FU, 0x4F41U, 0x4F42U, 0x4F43U, 0x4F44U, 0x4F45U, 0x4F46U, 0x4F47U, 0x4F48U, 0x4F49U, 0x4F4AU, 0x4F4BU, 0x4F4CU, 0x4F4DU, 0x4F4EU, 0x4F4FU, 0x4F50U, 0x4F51U, 0x4F52U, 0x4F53U, 0x4F54U, 0x4F55U, 0x4F56U, 0x4F57U, 0x4F58U, 0x4F59U, 0x4F5AU, 0x4F61U, 0x4F62U, 0x4F63U, 0x4F64U, 0x4F65U, 0x4F66U, 0x4F67U, 0x4F68U, 0x4F69U, 0x4F6AU, 0x4F6BU, 0x4F6CU, 0x4F6DU, 0x4F6EU, 0x4F6FU, 0x4F70U, 0x4F71U, 0x4F72U, 0x4F73U, 0x4F74U, 0x4F75U, 0x4F76U, 0x4F77U, 0x4F78U, 0x4F79U, 0x4F7AU, 0x4F30U, 0x4F31U, 0x4F32U, 0x4F33U, 0x4F34U, 0x4F35U, 0x4F36U, 0x4F37U, 0x4F38U, 0x4F39U, 0x4F2BU, 0x4F2FU, 0x5041U, 0x5042U, 0x5043U, 0x5044U, 0x5045U, 0x5046U, 0x5047U, 0x5048U, 0x5049U, 0x504AU, 0x504BU, 0x504CU, 0x504DU, 0x504EU, 0x504FU, 0x5050U, 0x5051U, 0x5052U, 0x5053U, 0x5054U, 0x5055U, 0x5056U, 0x5057U, 0x5058U, 0x5059U, 0x505AU, 0x5061U, 0x5062U, 0x5063U, 0x5064U, 0x5065U, 0x5066U, 0x5067U, 0x5068U, 0x5069U, 0x506AU, 0x506BU, 0x506CU, 0x506DU, 0x506EU, 0x506FU, 0x5070U, 0x5071U, 0x5072U, 0x5073U, 0x5074U, 0x5075U, 0x5076U, 0x5077U, 0x5078U, 0x5079U, 0x507AU, 0x5030U, 0x5031U, 0x5032U, 0x5033U, 0x5034U, 0x5035U, 0x5036U, 0x5037U, 0x5038U, 0x5039U, 0x502BU, 0x502FU, 0x5141U, 0x5142U, 0x5143U, 0x5144U, 0x5145U, 0x5146U, 0x5147U, 0x5148U, 0x5149U, 0x514AU, 0x514BU, 0x514CU, 0x514DU, 0x514EU, 0x514FU, 0x5150U, 0x5151U, 0x5152U, 0x5153U, 0x5154U, 0x5155U, 0x5156U, 0x5157U, 0x5158U, 0x5159U, 0x515AU, 0x5161U, 0x5162U, 0x5163U, 0x5164U, 0x5165U, 0x5166U, 0x5167U, 0x5168U, 0x5169U, 0x516AU, 0x516BU, 0x516CU, 0x516DU, 0x516EU, 0x516FU, 0x5170U, 0x5171U, 0x5172U, 0x5173U, 0x5174U, 0x5175U, 0x5176U, 0x5177U, 0x5178U, 0x5179U, 0x517AU, 0x5130U, 0x5131U, 0x5132U, 0x5133U, 0x5134U, 0x5135U, 0x5136U, 0x5137U, 0x5138U, 0x5139U, 0x512BU, 0x512FU, 0x5241U, 0x5242U, 0x5243U, 0x5244U, 0x5245U, 0x5246U, 0x5247U, 0x5248U, 0x5249U, 0x524AU, 0x524BU, 0x524CU, 0x524DU, 0x524EU, 0x524FU, 0x5250U, 0x5251U, 0x5252U, 0x5253U, 0x5254U, 0x5255U, 0x5256U, 0x5257U, 0x5258U, 0x5259U, 0x525AU, 0x5261U, 0x5262U, 0x5263U, 0x5264U, 0x5265U, 0x5266U, 0x5267U, 0x5268U, 0x5269U, 0x526AU, 0x526BU, 0x526CU, 0x526DU, 0x526EU, 0x526FU, 0x5270U, 0x5271U, 0x5272U, 0x5273U, 0x5274U, 0x5275U, 0x5276U, 0x5277U, 0x5278U, 0x5279U, 0x527AU, 0x5230U, 0x5231U, 0x5232U, 0x5233U, 0x5234U, 0x5235U, 0x5236U, 0x5237U, 0x5238U, 0x5239U, 0x522BU, 0x522FU, 0x5341U, 0x5342U, 0x5343U, 0x5344U, 0x5345U, 0x5346U, 0x5347U, 0x5348U, 0x5349U, 0x534AU, 0x534BU, 0x534CU, 0x534DU, 0x534EU, 0x534FU, 0x5350U, 0x5351U, 0x5352U, 0x5353U, 0x5354U, 0x5355U, 0x5356U, 0x5357U, 0x5358U, 0x5359U, 0x535AU, 0x5361U, 0x5362U, 0x5363U, 0x5364U, 0x5365U, 0x5366U, 0x5367U, 0x5368U, 0x5369U, 0x536AU, 0x536BU, 0x536CU, 0x536DU, 0x536EU, 0x536FU, 0x5370U, 0x5371U, 0x5372U, 0x5373U, 0x5374U, 0x5375U, 0x5376U, 0x5377U, 0x5378U, 0x5379U, 0x537AU, 0x5330U, 0x5331U, 0x5332U, 0x5333U, 0x5334U, 0x5335U, 0x5336U, 0x5337U, 0x5338U, 0x5339U, 0x532BU, 0x532FU, 0x5441U, 0x5442U, 0x5443U, 0x5444U, 0x5445U, 0x5446U, 0x5447U, 0x5448U, 0x5449U, 0x544AU, 0x544BU, 0x544CU, 0x544DU, 0x544EU, 0x544FU, 0x5450U, 0x5451U, 0x5452U, 0x5453U, 0x5454U, 0x5455U, 0x5456U, 0x5457U, 0x5458U, 0x5459U, 0x545AU, 0x5461U, 0x5462U, 0x5463U, 0x5464U, 0x5465U, 0x5466U, 0x5467U, 0x5468U, 0x5469U, 0x546AU, 0x546BU, 0x546CU, 0x546DU, 0x546EU, 0x546FU, 0x5470U, 0x5471U, 0x5472U, 0x5473U, 0x5474U, 0x5475U, 0x5476U, 0x5477U, 0x5478U, 0x5479U, 0x547AU, 0x5430U, 0x5431U, 0x5432U, 0x5433U, 0x5434U, 0x5435U, 0x5436U, 0x5437U, 0x5438U, 0x5439U, 0x542BU, 0x542FU, 0x5541U, 0x5542U, 0x5543U, 0x5544U, 0x5545U, 0x5546U, 0x5547U, 0x5548U, 0x5549U, 0x554AU, 0x554BU, 0x554CU, 0x554DU, 0x554EU, 0x554FU, 0x5550U, 0x5551U, 0x5552U, 0x5553U, 0x5554U, 0x5555U, 0x5556U, 0x5557U, 0x5558U, 0x5559U, 0x555AU, 0x5561U, 0x5562U, 0x5563U, 0x5564U, 0x5565U, 0x5566U, 0x5567U, 0x5568U, 0x5569U, 0x556AU, 0x556BU, 0x556CU, 0x556DU, 0x556EU, 0x556FU, 0x5570U, 0x5571U, 0x5572U, 0x5573U, 0x5574U, 0x5575U, 0x5576U, 0x5577U, 0x5578U, 0x5579U, 0x557AU, 0x5530U, 0x5531U, 0x5532U, 0x5533U, 0x5534U, 0x5535U, 0x5536U, 0x5537U, 0x5538U, 0x5539U, 0x552BU, 0x552FU, 0x5641U, 0x5642U, 0x5643U, 0x5644U, 0x5645U, 0x5646U, 0x5647U, 0x5648U, 0x5649U, 0x564AU, 0x564BU, 0x564CU, 0x564DU, 0x564EU, 0x564FU, 0x5650U, 0x5651U, 0x5652U, 0x5653U, 0x5654U, 0x5655U, 0x5656U, 0x5657U, 0x5658U, 0x5659U, 0x565AU, 0x5661U, 0x5662U, 0x5663U, 0x5664U, 0x5665U, 0x5666U, 0x5667U, 0x5668U, 0x5669U, 0x566AU, 0x566BU, 0x566CU, 0x566DU, 0x566EU, 0x566FU, 0x5670U, 0x5671U, 0x5672U, 0x5673U, 0x5674U, 0x5675U, 0x5676U, 0x5677U, 0x5678U, 0x5679U, 0x567AU, 0x5630U, 0x5631U, 0x5632U, 0x5633U, 0x5634U, 0x5635U, 0x5636U, 0x5637U, 0x5638U, 0x5639U, 0x562BU, 0x562FU, 0x5741U, 0x5742U, 0x5743U, 0x5744U, 0x5745U, 0x5746U, 0x5747U, 0x5748U, 0x5749U, 0x574AU, 0x574BU, 0x574CU, 0x574DU, 0x574EU, 0x574FU, 0x5750U, 0x5751U, 0x5752U, 0x5753U, 0x5754U, 0x5755U, 0x5756U, 0x5757U, 0x5758U, 0x5759U, 0x575AU, 0x5761U, 0x5762U, 0x5763U, 0x5764U, 0x5765U, 0x5766U, 0x5767U, 0x5768U, 0x5769U, 0x576AU, 0x576BU, 0x576CU, 0x576DU, 0x576EU, 0x576FU, 0x5770U, 0x5771U, 0x5772U, 0x5773U, 0x5774U, 0x5775U, 0x5776U, 0x5777U, 0x5778U, 0x5779U, 0x577AU, 0x5730U, 0x5731U, 0x5732U, 0x5733U, 0x5734U, 0x5735U, 0x5736U, 0x5737U, 0x5738U, 0x5739U, 0x572BU, 0x572FU, 0x5841U, 0x5842U, 0x5843U, 0x5844U, 0x5845U, 0x5846U, 0x5847U, 0x5848U, 0x5849U, 0x584AU, 0x584BU, 0x584CU, 0x584DU, 0x584EU, 0x584FU, 0x5850U, 0x5851U, 0x5852U, 0x5853U, 0x5854U, 0x5855U, 0x5856U, 0x5857U, 0x5858U, 0x5859U, 0x585AU, 0x5861U, 0x5862U, 0x5863U, 0x5864U, 0x5865U, 0x5866U, 0x5867U, 0x5868U, 0x5869U, 0x586AU, 0x586BU, 0x586CU, 0x586DU, 0x586EU, 0x586FU, 0x5870U, 0x5871U, 0x5872U, 0x5873U, 0x5874U, 0x5875U, 0x5876U, 0x5877U, 0x5878U, 0x5879U, 0x587AU, 0x5830U, 0x5831U, 0x5832U, 0x5833U, 0x5834U, 0x5835U, 0x5836U, 0x5837U, 0x5838U, 0x5839U, 0x582BU, 0x582FU, 0x5941U, 0x5942U, 0x5943U, 0x5944U, 0x5945U, 0x5946U, 0x5947U, 0x5948U, 0x5949U, 0x594AU, 0x594BU, 0x594CU, 0x594DU, 0x594EU, 0x594FU, 0x5950U, 0x5951U, 0x5952U, 0x5953U, 0x5954U, 0x5955U, 0x5956U, 0x5957U, 0x5958U, 0x5959U, 0x595AU, 0x5961U, 0x5962U, 0x5963U, 0x5964U, 0x5965U, 0x5966U, 0x5967U, 0x5968U, 0x5969U, 0x596AU, 0x596BU, 0x596CU, 0x596DU, 0x596EU, 0x596FU, 0x5970U, 0x5971U, 0x5972U, 0x5973U, 0x5974U, 0x5975U, 0x5976U, 0x5977U, 0x5978U, 0x5979U, 0x597AU, 0x5930U, 0x5931U, 0x5932U, 0x5933U, 0x5934U, 0x5935U, 0x5936U, 0x5937U, 0x5938U, 0x5939U, 0x592BU, 0x592FU, 0x5A41U, 0x5A42U, 0x5A43U, 0x5A44U, 0x5A45U, 0x5A46U, 0x5A47U, 0x5A48U, 0x5A49U, 0x5A4AU, 0x5A4BU, 0x5A4CU, 0x5A4DU, 0x5A4EU, 0x5A4FU, 0x5A50U, 0x5A51U, 0x5A52U, 0x5A53U, 0x5A54U, 0x5A55U, 0x5A56U, 0x5A57U, 0x5A58U, 0x5A59U, 0x5A5AU, 0x5A61U, 0x5A62U, 0x5A63U, 0x5A64U, 0x5A65U, 0x5A66U, 0x5A67U, 0x5A68U, 0x5A69U, 0x5A6AU, 0x5A6BU, 0x5A6CU, 0x5A6DU, 0x5A6EU, 0x5A6FU, 0x5A70U, 0x5A71U, 0x5A72U, 0x5A73U, 0x5A74U, 0x5A75U, 0x5A76U, 0x5A77U, 0x5A78U, 0x5A79U, 0x5A7AU, 0x5A30U, 0x5A31U, 0x5A32U, 0x5A33U, 0x5A34U, 0x5A35U, 0x5A36U, 0x5A37U, 0x5A38U, 0x5A39U, 0x5A2BU, 0x5A2FU, 0x6141U, 0x6142U, 0x6143U, 0x6144U, 0x6145U, 0x6146U, 0x6147U, 0x6148U, 0x6149U, 0x614AU, 0x614BU, 0x614CU, 0x614DU, 0x614EU, 0x614FU, 0x6150U, 0x6151U, 0x6152U, 0x6153U, 0x6154U, 0x6155U, 0x6156U, 0x6157U, 0x6158U, 0x6159U, 0x615AU, 0x6161U, 0x6162U, 0x6163U, 0x6164U, 0x6165U, 0x6166U, 0x6167U, 0x6168U, 0x6169U, 0x616AU, 0x616BU, 0x616CU, 0x616DU, 0x616EU, 0x616FU, 0x6170U, 0x6171U, 0x6172U, 0x6173U, 0x6174U, 0x6175U, 0x6176U, 0x6177U, 0x6178U, 0x6179U, 0x617AU, 0x6130U, 0x6131U, 0x6132U, 0x6133U, 0x6134U, 0x6135U, 0x6136U, 0x6137U, 0x6138U, 0x6139U, 0x612BU, 0x612FU, 0x6241U, 0x6242U, 0x6243U, 0x6244U, 0x6245U, 0x6246U, 0x6247U, 0x6248U, 0x6249U, 0x624AU, 0x624BU, 0x624CU, 0x624DU, 0x624EU, 0x624FU, 0x6250U, 0x6251U, 0x6252U, 0x6253U, 0x6254U, 0x6255U, 0x6256U, 0x6257U, 0x6258U, 0x6259U, 0x625AU, 0x6261U, 0x6262U, 0x6263U, 0x6264U, 0x6265U, 0x6266U, 0x6267U, 0x6268U, 0x6269U, 0x626AU, 0x626BU, 0x626CU, 0x626DU, 0x626EU, 0x626FU, 0x6270U, 0x6271U, 0x6272U, 0x6273U, 0x6274U, 0x6275U, 0x6276U, 0x6277U, 0x6278U, 0x6279U, 0x627AU, 0x6230U, 0x6231U, 0x6232U, 0x6233U, 0x6234U, 0x6235U, 0x6236U, 0x6237U, 0x6238U, 0x6239U, 0x622BU, 0x622FU, 0x6341U, 0x6342U, 0x6343U, 0x6344U, 0x6345U, 0x6346U, 0x6347U, 0x6348U, 0x6349U, 0x634AU, 0x634BU, 0x634CU, 0x634DU, 0x634EU, 0x634FU, 0x6350U, 0x6351U, 0x6352U, 0x6353U, 0x6354U, 0x6355U, 0x6356U, 0x6357U, 0x6358U, 0x6359U, 0x635AU, 0x6361U, 0x6362U, 0x6363U, 0x6364U, 0x6365U, 0x6366U, 0x6367U, 0x6368U, 0x6369U, 0x636AU, 0x636BU, 0x636CU, 0x636DU, 0x636EU, 0x636FU, 0x6370U, 0x6371U, 0x6372U, 0x6373U, 0x6374U, 0x6375U, 0x6376U, 0x6377U, 0x6378U, 0x6379U, 0x637AU, 0x6330U, 0x6331U, 0x6332U, 0x6333U, 0x6334U, 0x6335U, 0x6336U, 0x6337U, 0x6338U, 0x6339U, 0x632BU, 0x632FU, 0x6441U, 0x6442U, 0x6443U, 0x6444U, 0x6445U, 0x6446U, 0x6447U, 0x6448U, 0x6449U, 0x644AU, 0x644BU, 0x644CU, 0x644DU, 0x644EU, 0x644FU, 0x6450U, 0x6451U, 0x6452U, 0x6453U, 0x6454U, 0x6455U, 0x6456U, 0x6457U, 0x6458U, 0x6459U, 0x645AU, 0x6461U, 0x6462U, 0x6463U, 0x6464U, 0x6465U, 0x6466U, 0x6467U, 0x6468U, 0x6469U, 0x646AU, 0x646BU, 0x646CU, 0x646DU, 0x646EU, 0x646FU, 0x6470U, 0x6471U, 0x6472U, 0x6473U, 0x6474U, 0x6475U, 0x6476U, 0x6477U, 0x6478U, 0x6479U, 0x647AU, 0x6430U, 0x6431U, 0x6432U, 0x6433U, 0x6434U, 0x6435U, 0x6436U, 0x6437U, 0x6438U, 0x6439U, 0x642BU, 0x642FU, 0x6541U, 0x6542U, 0x6543U, 0x6544U, 0x6545U, 0x6546U, 0x6547U, 0x6548U, 0x6549U, 0x654AU, 0x654BU, 0x654CU, 0x654DU, 0x654EU, 0x654FU, 0x6550U, 0x6551U, 0x6552U, 0x6553U, 0x6554U, 0x6555U, 0x6556U, 0x6557U, 0x6558U, 0x6559U, 0x655AU, 0x6561U, 0x6562U, 0x6563U, 0x6564U, 0x6565U, 0x6566U, 0x6567U, 0x6568U, 0x6569U, 0x656AU, 0x656BU, 0x656CU, 0x656DU, 0x656EU, 0x656FU, 0x6570U, 0x6571U, 0x6572U, 0x6573U, 0x6574U, 0x6575U, 0x6576U, 0x6577U, 0x6578U, 0x6579U, 0x657AU, 0x6530U, 0x6531U, 0x6532U, 0x6533U, 0x6534U, 0x6535U, 0x6536U, 0x6537U, 0x6538U, 0x6539U, 0x652BU, 0x652FU, 0x6641U, 0x6642U, 0x6643U, 0x6644U, 0x6645U, 0x6646U, 0x6647U, 0x6648U, 0x6649U, 0x664AU, 0x664BU, 0x664CU, 0x664DU, 0x664EU, 0x664FU, 0x6650U, 0x6651U, 0x6652U, 0x6653U, 0x6654U, 0x6655U, 0x6656U, 0x6657U, 0x6658U, 0x6659U, 0x665AU, 0x6661U, 0x6662U, 0x6663U, 0x6664U, 0x6665U, 0x6666U, 0x6667U, 0x6668U, 0x6669U, 0x666AU, 0x666BU, 0x666CU, 0x666DU, 0x666EU, 0x666FU, 0x6670U, 0x6671U, 0x6672U, 0x6673U, 0x6674U, 0x6675U, 0x6676U, 0x6677U, 0x6678U, 0x6679U, 0x667AU, 0x6630U, 0x6631U, 0x6632U, 0x6633U, 0x6634U, 0x6635U, 0x6636U, 0x6637U, 0x6638U, 0x6639U, 0x662BU, 0x662FU, 0x6741U, 0x6742U, 0x6743U, 0x6744U, 0x6745U, 0x6746U, 0x6747U, 0x6748U, 0x6749U, 0x674AU, 0x674BU, 0x674CU, 0x674DU, 0x674EU, 0x674FU, 0x6750U, 0x6751U, 0x6752U, 0x6753U, 0x6754U, 0x6755U, 0x6756U, 0x6757U, 0x6758U, 0x6759U, 0x675AU, 0x6761U, 0x6762U, 0x6763U, 0x6764U, 0x6765U, 0x6766U, 0x6767U, 0x6768U, 0x6769U, 0x676AU, 0x676BU, 0x676CU, 0x676DU, 0x676EU, 0x676FU, 0x6770U, 0x6771U, 0x6772U, 0x6773U, 0x6774U, 0x6775U, 0x6776U, 0x6777U, 0x6778U, 0x6779U, 0x677AU, 0x6730U, 0x6731U, 0x6732U, 0x6733U, 0x6734U, 0x6735U, 0x6736U, 0x6737U, 0x6738U, 0x6739U, 0x672BU, 0x672FU, 0x6841U, 0x6842U, 0x6843U, 0x6844U, 0x6845U, 0x6846U, 0x6847U, 0x6848U, 0x6849U, 0x684AU, 0x684BU, 0x684CU, 0x684DU, 0x684EU, 0x684FU, 0x6850U, 0x6851U, 0x6852U, 0x6853U, 0x6854U, 0x6855U, 0x6856U, 0x6857U, 0x6858U, 0x6859U, 0x685AU, 0x6861U, 0x6862U, 0x6863U, 0x6864U, 0x6865U, 0x6866U, 0x6867U, 0x6868U, 0x6869U, 0x686AU, 0x686BU, 0x686CU, 0x686DU, 0x686EU, 0x686FU, 0x6870U, 0x6871U, 0x6872U, 0x6873U, 0x6874U, 0x6875U, 0x6876U, 0x6877U, 0x6878U, 0x6879U, 0x687AU, 0x6830U, 0x6831U, 0x6832U, 0x6833U, 0x6834U, 0x6835U, 0x6836U, 0x6837U, 0x6838U, 0x6839U, 0x682BU, 0x682FU, 0x6941U, 0x6942U, 0x6943U, 0x6944U, 0x6945U, 0x6946U, 0x6947U, 0x6948U, 0x6949U, 0x694AU, 0x694BU, 0x694CU, 0x694DU, 0x694EU, 0x694FU, 0x6950U, 0x6951U, 0x6952U, 0x6953U, 0x6954U, 0x6955U, 0x6956U, 0x6957U, 0x6958U, 0x6959U, 0x695AU, 0x6961U, 0x6962U, 0x6963U, 0x6964U, 0x6965U, 0x6966U, 0x6967U, 0x6968U, 0x6969U, 0x696AU, 0x696BU, 0x696CU, 0x696DU, 0x696EU, 0x696FU, 0x6970U, 0x6971U, 0x6972U, 0x6973U, 0x6974U, 0x6975U, 0x6976U, 0x6977U, 0x6978U, 0x6979U, 0x697AU, 0x6930U, 0x6931U, 0x6932U, 0x6933U, 0x6934U, 0x6935U, 0x6936U, 0x6937U, 0x6938U, 0x6939U, 0x692BU, 0x692FU, 0x6A41U, 0x6A42U, 0x6A43U, 0x6A44U, 0x6A45U, 0x6A46U, 0x6A47U, 0x6A48U, 0x6A49U, 0x6A4AU, 0x6A4BU, 0x6A4CU, 0x6A4DU, 0x6A4EU, 0x6A4FU, 0x6A50U, 0x6A51U, 0x6A52U, 0x6A53U, 0x6A54U, 0x6A55U, 0x6A56U, 0x6A57U, 0x6A58U, 0x6A59U, 0x6A5AU, 0x6A61U, 0x6A62U, 0x6A63U, 0x6A64U, 0x6A65U, 0x6A66U, 0x6A67U, 0x6A68U, 0x6A69U, 0x6A6AU, 0x6A6BU, 0x6A6CU, 0x6A6DU, 0x6A6EU, 0x6A6FU, 0x6A70U, 0x6A71U, 0x6A72U, 0x6A73U, 0x6A74U, 0x6A75U, 0x6A76U, 0x6A77U, 0x6A78U, 0x6A79U, 0x6A7AU, 0x6A30U, 0x6A31U, 0x6A32U, 0x6A33U, 0x6A34U, 0x6A35U, 0x6A36U, 0x6A37U, 0x6A38U, 0x6A39U, 0x6A2BU, 0x6A2FU, 0x6B41U, 0x6B42U, 0x6B43U, 0x6B44U, 0x6B45U, 0x6B46U, 0x6B47U, 0x6B48U, 0x6B49U, 0x6B4AU, 0x6B4BU, 0x6B4CU, 0x6B4DU, 0x6B4EU, 0x6B4FU, 0x6B50U, 0x6B51U, 0x6B52U, 0x6B53U, 0x6B54U, 0x6B55U, 0x6B56U, 0x6B57U, 0x6B58U, 0x6B59U, 0x6B5AU, 0x6B61U, 0x6B62U, 0x6B63U, 0x6B64U, 0x6B65U, 0x6B66U, 0x6B67U, 0x6B68U, 0x6B69U, 0x6B6AU, 0x6B6BU, 0x6B6CU, 0x6B6DU, 0x6B6EU, 0x6B6FU, 0x6B70U, 0x6B71U, 0x6B72U, 0x6B73U, 0x6B74U, 0x6B75U, 0x6B76U, 0x6B77U, 0x6B78U, 0x6B79U, 0x6B7AU, 0x6B30U, 0x6B31U, 0x6B32U, 0x6B33U, 0x6B34U, 0x6B35U, 0x6B36U, 0x6B37U, 0x6B38U, 0x6B39U, 0x6B2BU, 0x6B2FU, 0x6C41U, 0x6C42U, 0x6C43U, 0x6C44U, 0x6C45U, 0x6C46U, 0x6C47U, 0x6C48U, 0x6C49U, 0x6C4AU, 0x6C4BU, 0x6C4CU, 0x6C4DU, 0x6C4EU, 0x6C4FU, 0x6C50U, 0x6C51U, 0x6C52U, 0x6C53U, 0x6C54U, 0x6C55U, 0x6C56U, 0x6C57U, 0x6C58U, 0x6C59U, 0x6C5AU, 0x6C61U, 0x6C62U, 0x6C63U, 0x6C64U, 0x6C65U, 0x6C66U, 0x6C67U, 0x6C68U, 0x6C69U, 0x6C6AU, 0x6C6BU, 0x6C6CU, 0x6C6DU, 0x6C6EU, 0x6C6FU, 0x6C70U, 0x6C71U, 0x6C72U, 0x6C73U, 0x6C74U, 0x6C75U, 0x6C76U, 0x6C77U, 0x6C78U, 0x6C79U, 0x6C7AU, 0x6C30U, 0x6C31U, 0x6C32U, 0x6C33U, 0x6C34U, 0x6C35U, 0x6C36U, 0x6C37U, 0x6C38U, 0x6C39U, 0x6C2BU, 0x6C2FU, 0x6D41U, 0x6D42U, 0x6D43U, 0x6D44U, 0x6D45U, 0x6D46U, 0x6D47U, 0x6D48U, 0x6D49U, 0x6D4AU, 0x6D4BU, 0x6D4CU, 0x6D4DU, 0x6D4EU, 0x6D4FU, 0x6D50U, 0x6D51U, 0x6D52U, 0x6D53U, 0x6D54U, 0x6D55U, 0x6D56U, 0x6D57U, 0x6D58U, 0x6D59U, 0x6D5AU, 0x6D61U, 0x6D62U, 0x6D63U, 0x6D64U, 0x6D65U, 0x6D66U, 0x6D67U, 0x6D68U, 0x6D69U, 0x6D6AU, 0x6D6BU, 0x6D6CU, 0x6D6DU, 0x6D6EU, 0x6D6FU, 0x6D70U, 0x6D71U, 0x6D72U, 0x6D73U, 0x6D74U, 0x6D75U, 0x6D76U, 0x6D77U, 0x6D78U, 0x6D79U, 0x6D7AU, 0x6D30U, 0x6D31U, 0x6D32U, 0x6D33U, 0x6D34U, 0x6D35U, 0x6D36U, 0x6D37U, 0x6D38U, 0x6D39U, 0x6D2BU, 0x6D2FU, 0x6E41U, 0x6E42U, 0x6E43U, 0x6E44U, 0x6E45U, 0x6E46U, 0x6E47U, 0x6E48U, 0x6E49U, 0x6E4AU, 0x6E4BU, 0x6E4CU, 0x6E4DU, 0x6E4EU, 0x6E4FU, 0x6E50U, 0x6E51U, 0x6E52U, 0x6E53U, 0x6E54U, 0x6E55U, 0x6E56U, 0x6E57U, 0x6E58U, 0x6E59U, 0x6E5AU, 0x6E61U, 0x6E62U, 0x6E63U, 0x6E64U, 0x6E65U, 0x6E66U, 0x6E67U, 0x6E68U, 0x6E69U, 0x6E6AU, 0x6E6BU, 0x6E6CU, 0x6E6DU, 0x6E6EU, 0x6E6FU, 0x6E70U, 0x6E71U, 0x6E72U, 0x6E73U, 0x6E74U, 0x6E75U, 0x6E76U, 0x6E77U, 0x6E78U, 0x6E79U, 0x6E7AU, 0x6E30U, 0x6E31U, 0x6E32U, 0x6E33U, 0x6E34U, 0x6E35U, 0x6E36U, 0x6E37U, 0x6E38U, 0x6E39U, 0x6E2BU, 0x6E2FU, 0x6F41U, 0x6F42U, 0x6F43U, 0x6F44U, 0x6F45U, 0x6F46U, 0x6F47U, 0x6F48U, 0x6F49U, 0x6F4AU, 0x6F4BU, 0x6F4CU, 0x6F4DU, 0x6F4EU, 0x6F4FU, 0x6F50U, 0x6F51U, 0x6F52U, 0x6F53U, 0x6F54U, 0x6F55U, 0x6F56U, 0x6F57U, 0x6F58U, 0x6F59U, 0x6F5AU, 0x6F61U, 0x6F62U, 0x6F63U, 0x6F64U, 0x6F65U, 0x6F66U, 0x6F67U, 0x6F68U, 0x6F69U, 0x6F6AU, 0x6F6BU, 0x6F6CU, 0x6F6DU, 0x6F6EU, 0x6F6FU, 0x6F70U, 0x6F71U, 0x6F72U, 0x6F73U, 0x6F74U, 0x6F75U, 0x6F76U, 0x6F77U, 0x6F78U, 0x6F79U, 0x6F7AU, 0x6F30U, 0x6F31U, 0x6F32U, 0x6F33U, 0x6F34U, 0x6F35U, 0x6F36U, 0x6F37U, 0x6F38U, 0x6F39U, 0x6F2BU, 0x6F2FU, 0x7041U, 0x7042U, 0x7043U, 0x7044U, 0x7045U, 0x7046U, 0x7047U, 0x7048U, 0x7049U, 0x704AU, 0x704BU, 0x704CU, 0x704DU, 0x704EU, 0x704FU, 0x7050U, 0x7051U, 0x7052U, 0x7053U, 0x7054U, 0x7055U, 0x7056U, 0x7057U, 0x7058U, 0x7059U, 0x705AU, 0x7061U, 0x7062U, 0x7063U, 0x7064U, 0x7065U, 0x7066U, 0x7067U, 0x7068U, 0x7069U, 0x706AU, 0x706BU, 0x706CU, 0x706DU, 0x706EU, 0x706FU, 0x7070U, 0x7071U, 0x7072U, 0x7073U, 0x7074U, 0x7075U, 0x7076U, 0x7077U, 0x7078U, 0x7079U, 0x707AU, 0x7030U, 0x7031U, 0x7032U, 0x7033U, 0x7034U, 0x7035U, 0x7036U, 0x7037U, 0x7038U, 0x7039U, 0x702BU, 0x702FU, 0x7141U, 0x7142U, 0x7143U, 0x7144U, 0x7145U, 0x7146U, 0x7147U, 0x7148U, 0x7149U, 0x714AU, 0x714BU, 0x714CU, 0x714DU, 0x714EU, 0x714FU, 0x7150U, 0x7151U, 0x7152U, 0x7153U, 0x7154U, 0x7155U, 0x7156U, 0x7157U, 0x7158U, 0x7159U, 0x715AU, 0x7161U, 0x7162U, 0x7163U, 0x7164U, 0x7165U, 0x7166U, 0x7167U, 0x7168U, 0x7169U, 0x716AU, 0x716BU, 0x716CU, 0x716DU, 0x716EU, 0x716FU, 0x7170U, 0x7171U, 0x7172U, 0x7173U, 0x7174U, 0x7175U, 0x7176U, 0x7177U, 0x7178U, 0x7179U, 0x717AU, 0x7130U, 0x7131U, 0x7132U, 0x7133U, 0x7134U, 0x7135U, 0x7136U, 0x7137U, 0x7138U, 0x7139U, 0x712BU, 0x712FU, 0x7241U, 0x7242U, 0x7243U, 0x7244U, 0x7245U, 0x7246U, 0x7247U, 0x7248U, 0x7249U, 0x724AU, 0x724BU, 0x724CU, 0x724DU, 0x724EU, 0x724FU, 0x7250U, 0x7251U, 0x7252U, 0x7253U, 0x7254U, 0x7255U, 0x7256U, 0x7257U, 0x7258U, 0x7259U, 0x725AU, 0x7261U, 0x7262U, 0x7263U, 0x7264U, 0x7265U, 0x7266U, 0x7267U, 0x7268U, 0x7269U, 0x726AU, 0x726BU, 0x726CU, 0x726DU, 0x726EU, 0x726FU, 0x7270U, 0x7271U, 0x7272U, 0x7273U, 0x7274U, 0x7275U, 0x7276U, 0x7277U, 0x7278U, 0x7279U, 0x727AU, 0x7230U, 0x7231U, 0x7232U, 0x7233U, 0x7234U, 0x7235U, 0x7236U, 0x7237U, 0x7238U, 0x7239U, 0x722BU, 0x722FU, 0x7341U, 0x7342U, 0x7343U, 0x7344U, 0x7345U, 0x7346U, 0x7347U, 0x7348U, 0x7349U, 0x734AU, 0x734BU, 0x734CU, 0x734DU, 0x734EU, 0x734FU, 0x7350U, 0x7351U, 0x7352U, 0x7353U, 0x7354U, 0x7355U, 0x7356U, 0x7357U, 0x7358U, 0x7359U, 0x735AU, 0x7361U, 0x7362U, 0x7363U, 0x7364U, 0x7365U, 0x7366U, 0x7367U, 0x7368U, 0x7369U, 0x736AU, 0x736BU, 0x736CU, 0x736DU, 0x736EU, 0x736FU, 0x7370U, 0x7371U, 0x7372U, 0x7373U, 0x7374U, 0x7375U, 0x7376U, 0x7377U, 0x7378U, 0x7379U, 0x737AU, 0x7330U, 0x7331U, 0x7332U, 0x7333U, 0x7334U, 0x7335U, 0x7336U, 0x7337U, 0x7338U, 0x7339U, 0x732BU, 0x732FU, 0x7441U, 0x7442U, 0x7443U, 0x7444U, 0x7445U, 0x7446U, 0x7447U, 0x7448U, 0x7449U, 0x744AU, 0x744BU, 0x744CU, 0x744DU, 0x744EU, 0x744FU, 0x7450U, 0x7451U, 0x7452U, 0x7453U, 0x7454U, 0x7455U, 0x7456U, 0x7457U, 0x7458U, 0x7459U, 0x745AU, 0x7461U, 0x7462U, 0x7463U, 0x7464U, 0x7465U, 0x7466U, 0x7467U, 0x7468U, 0x7469U, 0x746AU, 0x746BU, 0x746CU, 0x746DU, 0x746EU, 0x746FU, 0x7470U, 0x7471U, 0x7472U, 0x7473U, 0x7474U, 0x7475U, 0x7476U, 0x7477U, 0x7478U, 0x7479U, 0x747AU, 0x7430U, 0x7431U, 0x7432U, 0x7433U, 0x7434U, 0x7435U, 0x7436U, 0x7437U, 0x7438U, 0x7439U, 0x742BU, 0x742FU, 0x7541U, 0x7542U, 0x7543U, 0x7544U, 0x7545U, 0x7546U, 0x7547U, 0x7548U, 0x7549U, 0x754AU, 0x754BU, 0x754CU, 0x754DU, 0x754EU, 0x754FU, 0x7550U, 0x7551U, 0x7552U, 0x7553U, 0x7554U, 0x7555U, 0x7556U, 0x7557U, 0x7558U, 0x7559U, 0x755AU, 0x7561U, 0x7562U, 0x7563U, 0x7564U, 0x7565U, 0x7566U, 0x7567U, 0x7568U, 0x7569U, 0x756AU, 0x756BU, 0x756CU, 0x756DU, 0x756EU, 0x756FU, 0x7570U, 0x7571U, 0x7572U, 0x7573U, 0x7574U, 0x7575U, 0x7576U, 0x7577U, 0x7578U, 0x7579U, 0x757AU, 0x7530U, 0x7531U, 0x7532U, 0x7533U, 0x7534U, 0x7535U, 0x7536U, 0x7537U, 0x7538U, 0x7539U, 0x752BU, 0x752FU, 0x7641U, 0x7642U, 0x7643U, 0x7644U, 0x7645U, 0x7646U, 0x7647U, 0x7648U, 0x7649U, 0x764AU, 0x764BU, 0x764CU, 0x764DU, 0x764EU, 0x764FU, 0x7650U, 0x7651U, 0x7652U, 0x7653U, 0x7654U, 0x7655U, 0x7656U, 0x7657U, 0x7658U, 0x7659U, 0x765AU, 0x7661U, 0x7662U, 0x7663U, 0x7664U, 0x7665U, 0x7666U, 0x7667U, 0x7668U, 0x7669U, 0x766AU, 0x766BU, 0x766CU, 0x766DU, 0x766EU, 0x766FU, 0x7670U, 0x7671U, 0x7672U, 0x7673U, 0x7674U, 0x7675U, 0x7676U, 0x7677U, 0x7678U, 0x7679U, 0x767AU, 0x7630U, 0x7631U, 0x7632U, 0x7633U, 0x7634U, 0x7635U, 0x7636U, 0x7637U, 0x7638U, 0x7639U, 0x762BU, 0x762FU, 0x7741U, 0x7742U, 0x7743U, 0x7744U, 0x7745U, 0x7746U, 0x7747U, 0x7748U, 0x7749U, 0x774AU, 0x774BU, 0x774CU, 0x774DU, 0x774EU, 0x774FU, 0x7750U, 0x7751U, 0x7752U, 0x7753U, 0x7754U, 0x7755U, 0x7756U, 0x7757U, 0x7758U, 0x7759U, 0x775AU, 0x7761U, 0x7762U, 0x7763U, 0x7764U, 0x7765U, 0x7766U, 0x7767U, 0x7768U, 0x7769U, 0x776AU, 0x776BU, 0x776CU, 0x776DU, 0x776EU, 0x776FU, 0x7770U, 0x7771U, 0x7772U, 0x7773U, 0x7774U, 0x7775U, 0x7776U, 0x7777U, 0x7778U, 0x7779U, 0x777AU, 0x7730U, 0x7731U, 0x7732U, 0x7733U, 0x7734U, 0x7735U, 0x7736U, 0x7737U, 0x7738U, 0x7739U, 0x772BU, 0x772FU, 0x7841U, 0x7842U, 0x7843U, 0x7844U, 0x7845U, 0x7846U, 0x7847U, 0x7848U, 0x7849U, 0x784AU, 0x784BU, 0x784CU, 0x784DU, 0x784EU, 0x784FU, 0x7850U, 0x7851U, 0x7852U, 0x7853U, 0x7854U, 0x7855U, 0x7856U, 0x7857U, 0x7858U, 0x7859U, 0x785AU, 0x7861U, 0x7862U, 0x7863U, 0x7864U, 0x7865U, 0x7866U, 0x7867U, 0x7868U, 0x7869U, 0x786AU, 0x786BU, 0x786CU, 0x786DU, 0x786EU, 0x786FU, 0x7870U, 0x7871U, 0x7872U, 0x7873U, 0x7874U, 0x7875U, 0x7876U, 0x7877U, 0x7878U, 0x7879U, 0x787AU, 0x7830U, 0x7831U, 0x7832U, 0x7833U, 0x7834U, 0x7835U, 0x7836U, 0x7837U, 0x7838U, 0x7839U, 0x782BU, 0x782FU, 0x7941U, 0x7942U, 0x7943U, 0x7944U, 0x7945U, 0x7946U, 0x7947U, 0x7948U, 0x7949U, 0x794AU, 0x794BU, 0x794CU, 0x794DU, 0x794EU, 0x794FU, 0x7950U, 0x7951U, 0x7952U, 0x7953U, 0x7954U, 0x7955U, 0x7956U, 0x7957U, 0x7958U, 0x7959U, 0x795AU, 0x7961U, 0x7962U, 0x7963U, 0x7964U, 0x7965U, 0x7966U, 0x7967U, 0x7968U, 0x7969U, 0x796AU, 0x796BU, 0x796CU, 0x796DU, 0x796EU, 0x796FU, 0x7970U, 0x7971U, 0x7972U, 0x7973U, 0x7974U, 0x7975U, 0x7976U, 0x7977U, 0x7978U, 0x7979U, 0x797AU, 0x7930U, 0x7931U, 0x7932U, 0x7933U, 0x7934U, 0x7935U, 0x7936U, 0x7937U, 0x7938U, 0x7939U, 0x792BU, 0x792FU, 0x7A41U, 0x7A42U, 0x7A43U, 0x7A44U, 0x7A45U, 0x7A46U, 0x7A47U, 0x7A48U, 0x7A49U, 0x7A4AU, 0x7A4BU, 0x7A4CU, 0x7A4DU, 0x7A4EU, 0x7A4FU, 0x7A50U, 0x7A51U, 0x7A52U, 0x7A53U, 0x7A54U, 0x7A55U, 0x7A56U, 0x7A57U, 0x7A58U, 0x7A59U, 0x7A5AU, 0x7A61U, 0x7A62U, 0x7A63U, 0x7A64U, 0x7A65U, 0x7A66U, 0x7A67U, 0x7A68U, 0x7A69U, 0x7A6AU, 0x7A6BU, 0x7A6CU, 0x7A6DU, 0x7A6EU, 0x7A6FU, 0x7A70U, 0x7A71U, 0x7A72U, 0x7A73U, 0x7A74U, 0x7A75U, 0x7A76U, 0x7A77U, 0x7A78U, 0x7A79U, 0x7A7AU, 0x7A30U, 0x7A31U, 0x7A32U, 0x7A33U, 0x7A34U, 0x7A35U, 0x7A36U, 0x7A37U, 0x7A38U, 0x7A39U, 0x7A2BU, 0x7A2FU, 0x3041U, 0x3042U, 0x3043U, 0x3044U, 0x3045U, 0x3046U, 0x3047U, 0x3048U, 0x3049U, 0x304AU, 0x304BU, 0x304CU, 0x304DU, 0x304EU, 0x304FU, 0x3050U, 0x3051U, 0x3052U, 0x3053U, 0x3054U, 0x3055U, 0x3056U, 0x3057U, 0x3058U, 0x3059U, 0x305AU, 0x3061U, 0x3062U, 0x3063U, 0x3064U, 0x3065U, 0x3066U, 0x3067U, 0x3068U, 0x3069U, 0x306AU, 0x306BU, 0x306CU, 0x306DU, 0x306EU, 0x306FU, 0x3070U, 0x3071U, 0x3072U, 0x3073U, 0x3074U, 0x3075U, 0x3076U, 0x3077U, 0x3078U, 0x3079U, 0x307AU, 0x3030U, 0x3031U, 0x3032U, 0x3033U, 0x3034U, 0x3035U, 0x3036U, 0x3037U, 0x3038U, 0x3039U, 0x302BU, 0x302FU, 0x3141U, 0x3142U, 0x3143U, 0x3144U, 0x3145U, 0x3146U, 0x3147U, 0x3148U, 0x3149U, 0x314AU, 0x314BU, 0x314CU, 0x314DU, 0x314EU, 0x314FU, 0x3150U, 0x3151U, 0x3152U, 0x3153U, 0x3154U, 0x3155U, 0x3156U, 0x3157U, 0x3158U, 0x3159U, 0x315AU, 0x3161U, 0x3162U, 0x3163U, 0x3164U, 0x3165U, 0x3166U, 0x3167U, 0x3168U, 0x3169U, 0x316AU, 0x316BU, 0x316CU, 0x316DU, 0x316EU, 0x316FU, 0x3170U, 0x3171U, 0x3172U, 0x3173U, 0x3174U, 0x3175U, 0x3176U, 0x3177U, 0x3178U, 0x3179U, 0x317AU, 0x3130U, 0x3131U, 0x3132U, 0x3133U, 0x3134U, 0x3135U, 0x3136U, 0x3137U, 0x3138U, 0x3139U, 0x312BU, 0x312FU, 0x3241U, 0x3242U, 0x3243U, 0x3244U, 0x3245U, 0x3246U, 0x3247U, 0x3248U, 0x3249U, 0x324AU, 0x324BU, 0x324CU, 0x324DU, 0x324EU, 0x324FU, 0x3250U, 0x3251U, 0x3252U, 0x3253U, 0x3254U, 0x3255U, 0x3256U, 0x3257U, 0x3258U, 0x3259U, 0x325AU, 0x3261U, 0x3262U, 0x3263U, 0x3264U, 0x3265U, 0x3266U, 0x3267U, 0x3268U, 0x3269U, 0x326AU, 0x326BU, 0x326CU, 0x326DU, 0x326EU, 0x326FU, 0x3270U, 0x3271U, 0x3272U, 0x3273U, 0x3274U, 0x3275U, 0x3276U, 0x3277U, 0x3278U, 0x3279U, 0x327AU, 0x3230U, 0x3231U, 0x3232U, 0x3233U, 0x3234U, 0x3235U, 0x3236U, 0x3237U, 0x3238U, 0x3239U, 0x322BU, 0x322FU, 0x3341U, 0x3342U, 0x3343U, 0x3344U, 0x3345U, 0x3346U, 0x3347U, 0x3348U, 0x3349U, 0x334AU, 0x334BU, 0x334CU, 0x334DU, 0x334EU, 0x334FU, 0x3350U, 0x3351U, 0x3352U, 0x3353U, 0x3354U, 0x3355U, 0x3356U, 0x3357U, 0x3358U, 0x3359U, 0x335AU, 0x3361U, 0x3362U, 0x3363U, 0x3364U, 0x3365U, 0x3366U, 0x3367U, 0x3368U, 0x3369U, 0x336AU, 0x336BU, 0x336CU, 0x336DU, 0x336EU, 0x336FU, 0x3370U, 0x3371U, 0x3372U, 0x3373U, 0x3374U, 0x3375U, 0x3376U, 0x3377U, 0x3378U, 0x3379U, 0x337AU, 0x3330U, 0x3331U, 0x3332U, 0x3333U, 0x3334U, 0x3335U, 0x3336U, 0x3337U, 0x3338U, 0x3339U, 0x332BU, 0x332FU, 0x3441U, 0x3442U, 0x3443U, 0x3444U, 0x3445U, 0x3446U, 0x3447U, 0x3448U, 0x3449U, 0x344AU, 0x344BU, 0x344CU, 0x344DU, 0x344EU, 0x344FU, 0x3450U, 0x3451U, 0x3452U, 0x3453U, 0x3454U, 0x3455U, 0x3456U, 0x3457U, 0x3458U, 0x3459U, 0x345AU, 0x3461U, 0x3462U, 0x3463U, 0x3464U, 0x3465U, 0x3466U, 0x3467U, 0x3468U, 0x3469U, 0x346AU, 0x346BU, 0x346CU, 0x346DU, 0x346EU, 0x346FU, 0x3470U, 0x3471U, 0x3472U, 0x3473U, 0x3474U, 0x3475U, 0x3476U, 0x3477U, 0x3478U, 0x3479U, 0x347AU, 0x3430U, 0x3431U, 0x3432U, 0x3433U, 0x3434U, 0x3435U, 0x3436U, 0x3437U, 0x3438U, 0x3439U, 0x342BU, 0x342FU, 0x3541U, 0x3542U, 0x3543U, 0x3544U, 0x3545U, 0x3546U, 0x3547U, 0x3548U, 0x3549U, 0x354AU, 0x354BU, 0x354CU, 0x354DU, 0x354EU, 0x354FU, 0x3550U, 0x3551U, 0x3552U, 0x3553U, 0x3554U, 0x3555U, 0x3556U, 0x3557U, 0x3558U, 0x3559U, 0x355AU, 0x3561U, 0x3562U, 0x3563U, 0x3564U, 0x3565U, 0x3566U, 0x3567U, 0x3568U, 0x3569U, 0x356AU, 0x356BU, 0x356CU, 0x356DU, 0x356EU, 0x356FU, 0x3570U, 0x3571U, 0x3572U, 0x3573U, 0x3574U, 0x3575U, 0x3576U, 0x3577U, 0x3578U, 0x3579U, 0x357AU, 0x3530U, 0x3531U, 0x3532U, 0x3533U, 0x3534U, 0x3535U, 0x3536U, 0x3537U, 0x3538U, 0x3539U, 0x352BU, 0x352FU, 0x3641U, 0x3642U, 0x3643U, 0x3644U, 0x3645U, 0x3646U, 0x3647U, 0x3648U, 0x3649U, 0x364AU, 0x364BU, 0x364CU, 0x364DU, 0x364EU, 0x364FU, 0x3650U, 0x3651U, 0x3652U, 0x3653U, 0x3654U, 0x3655U, 0x3656U, 0x3657U, 0x3658U, 0x3659U, 0x365AU, 0x3661U, 0x3662U, 0x3663U, 0x3664U, 0x3665U, 0x3666U, 0x3667U, 0x3668U, 0x3669U, 0x366AU, 0x366BU, 0x366CU, 0x366DU, 0x366EU, 0x366FU, 0x3670U, 0x3671U, 0x3672U, 0x3673U, 0x3674U, 0x3675U, 0x3676U, 0x3677U, 0x3678U, 0x3679U, 0x367AU, 0x3630U, 0x3631U, 0x3632U, 0x3633U, 0x3634U, 0x3635U, 0x3636U, 0x3637U, 0x3638U, 0x3639U, 0x362BU, 0x362FU, 0x3741U, 0x3742U, 0x3743U, 0x3744U, 0x3745U, 0x3746U, 0x3747U, 0x3748U, 0x3749U, 0x374AU, 0x374BU, 0x374CU, 0x374DU, 0x374EU, 0x374FU, 0x3750U, 0x3751U, 0x3752U, 0x3753U, 0x3754U, 0x3755U, 0x3756U, 0x3757U, 0x3758U, 0x3759U, 0x375AU, 0x3761U, 0x3762U, 0x3763U, 0x3764U, 0x3765U, 0x3766U, 0x3767U, 0x3768U, 0x3769U, 0x376AU, 0x376BU, 0x376CU, 0x376DU, 0x376EU, 0x376FU, 0x3770U, 0x3771U, 0x3772U, 0x3773U, 0x3774U, 0x3775U, 0x3776U, 0x3777U, 0x3778U, 0x3779U, 0x377AU, 0x3730U, 0x3731U, 0x3732U, 0x3733U, 0x3734U, 0x3735U, 0x3736U, 0x3737U, 0x3738U, 0x3739U, 0x372BU, 0x372FU, 0x3841U, 0x3842U, 0x3843U, 0x3844U, 0x3845U, 0x3846U, 0x3847U, 0x3848U, 0x3849U, 0x384AU, 0x384BU, 0x384CU, 0x384DU, 0x384EU, 0x384FU, 0x3850U, 0x3851U, 0x3852U, 0x3853U, 0x3854U, 0x3855U, 0x3856U, 0x3857U, 0x3858U, 0x3859U, 0x385AU, 0x3861U, 0x3862U, 0x3863U, 0x3864U, 0x3865U, 0x3866U, 0x3867U, 0x3868U, 0x3869U, 0x386AU, 0x386BU, 0x386CU, 0x386DU, 0x386EU, 0x386FU, 0x3870U, 0x3871U, 0x3872U, 0x3873U, 0x3874U, 0x3875U, 0x3876U, 0x3877U, 0x3878U, 0x3879U, 0x387AU, 0x3830U, 0x3831U, 0x3832U, 0x3833U, 0x3834U, 0x3835U, 0x3836U, 0x3837U, 0x3838U, 0x3839U, 0x382BU, 0x382FU, 0x3941U, 0x3942U, 0x3943U, 0x3944U, 0x3945U, 0x3946U, 0x3947U, 0x3948U, 0x3949U, 0x394AU, 0x394BU, 0x394CU, 0x394DU, 0x394EU, 0x394FU, 0x3950U, 0x3951U, 0x3952U, 0x3953U, 0x3954U, 0x3955U, 0x3956U, 0x3957U, 0x3958U, 0x3959U, 0x395AU, 0x3961U, 0x3962U, 0x3963U, 0x3964U, 0x3965U, 0x3966U, 0x3967U, 0x3968U, 0x3969U, 0x396AU, 0x396BU, 0x396CU, 0x396DU, 0x396EU, 0x396FU, 0x3970U, 0x3971U, 0x3972U, 0x3973U, 0x3974U, 0x3975U, 0x3976U, 0x3977U, 0x3978U, 0x3979U, 0x397AU, 0x3930U, 0x3931U, 0x3932U, 0x3933U, 0x3934U, 0x3935U, 0x3936U, 0x3937U, 0x3938U, 0x3939U, 0x392BU, 0x392FU, 0x2B41U, 0x2B42U, 0x2B43U, 0x2B44U, 0x2B45U, 0x2B46U, 0x2B47U, 0x2B48U, 0x2B49U, 0x2B4AU, 0x2B4BU, 0x2B4CU, 0x2B4DU, 0x2B4EU, 0x2B4FU, 0x2B50U, 0x2B51U, 0x2B52U, 0x2B53U, 0x2B54U, 0x2B55U, 0x2B56U, 0x2B57U, 0x2B58U, 0x2B59U, 0x2B5AU, 0x2B61U, 0x2B62U, 0x2B63U, 0x2B64U, 0x2B65U, 0x2B66U, 0x2B67U, 0x2B68U, 0x2B69U, 0x2B6AU, 0x2B6BU, 0x2B6CU, 0x2B6DU, 0x2B6EU, 0x2B6FU, 0x2B70U, 0x2B71U, 0x2B72U, 0x2B73U, 0x2B74U, 0x2B75U, 0x2B76U, 0x2B77U, 0x2B78U, 0x2B79U, 0x2B7AU, 0x2B30U, 0x2B31U, 0x2B32U, 0x2B33U, 0x2B34U, 0x2B35U, 0x2B36U, 0x2B37U, 0x2B38U, 0x2B39U, 0x2B2BU, 0x2B2FU, 0x2F41U, 0x2F42U, 0x2F43U, 0x2F44U, 0x2F45U, 0x2F46U, 0x2F47U, 0x2F48U, 0x2F49U, 0x2F4AU, 0x2F4BU, 0x2F4CU, 0x2F4DU, 0x2F4EU, 0x2F4FU, 0x2F50U, 0x2F51U, 0x2F52U, 0x2F53U, 0x2F54U, 0x2F55U, 0x2F56U, 0x2F57U, 0x2F58U, 0x2F59U, 0x2F5AU, 0x2F61U, 0x2F62U, 0x2F63U, 0x2F64U, 0x2F65U, 0x2F66U, 0x2F67U, 0x2F68U, 0x2F69U, 0x2F6AU, 0x2F6BU, 0x2F6CU, 0x2F6DU, 0x2F6EU, 0x2F6FU, 0x2F70U, 0x2F71U, 0x2F72U, 0x2F73U, 0x2F74U, 0x2F75U, 0x2F76U, 0x2F77U, 0x2F78U, 0x2F79U, 0x2F7AU, 0x2F30U, 0x2F31U, 0x2F32U, 0x2F33U, 0x2F34U, 0x2F35U, 0x2F36U, 0x2F37U, 0x2F38U, 0x2F39U, 0x2F2BU, 0x2F2FU, #endif }; kitty-0.41.1/3rdparty/base64/lib/tables/table_enc_12bit.py0000775000175000017510000000214414773370543022456 0ustar nileshnilesh#!/usr/bin/python3 def tr(x): """Translate a 6-bit value to the Base64 alphabet.""" s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \ + 'abcdefghijklmnopqrstuvwxyz' \ + '0123456789' \ + '+/' return ord(s[x]) def table(fn): """Generate a 12-bit lookup table.""" ret = [] for n in range(0, 2**12): pre = "\n\t" if n % 8 == 0 else " " pre = "\t" if n == 0 else pre ret.append("{}0x{:04X}U,".format(pre, fn(n))) return "".join(ret) def table_be(): """Generate a 12-bit big-endian lookup table.""" return table(lambda n: (tr(n & 0x3F) << 0) | (tr(n >> 6) << 8)) def table_le(): """Generate a 12-bit little-endian lookup table.""" return table(lambda n: (tr(n >> 6) << 0) | (tr(n & 0x3F) << 8)) def main(): """Entry point.""" lines = [ "#include ", "", "const uint16_t base64_table_enc_12bit[] = {", "#if BASE64_LITTLE_ENDIAN", table_le(), "#else", table_be(), "#endif", "};" ] for line in lines: print(line) if __name__ == "__main__": main() kitty-0.41.1/3rdparty/base64/lib/tables/table_generator.c0000664000175000017510000001204614773370543022467 0ustar nileshnilesh/** * * Copyright 2005, 2006 Nick Galbreath -- nickg [at] modp [dot] com * Copyright 2017 Matthieu Darbois * All rights reserved. * * http://modp.com/release/base64 * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ /****************************/ #include #include #include #include #include static uint8_t b64chars[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; static uint8_t padchar = '='; static void printStart(void) { printf("#include \n"); printf("#define CHAR62 '%c'\n", b64chars[62]); printf("#define CHAR63 '%c'\n", b64chars[63]); printf("#define CHARPAD '%c'\n", padchar); } static void clearDecodeTable(uint32_t* ary) { int i = 0; for (i = 0; i < 256; ++i) { ary[i] = 0xFFFFFFFF; } } /* dump uint32_t as hex digits */ void uint32_array_to_c_hex(const uint32_t* ary, size_t sz, const char* name) { size_t i = 0; printf("const uint32_t %s[%d] = {\n", name, (int)sz); for (;;) { printf("0x%08" PRIx32, ary[i]); ++i; if (i == sz) break; if (i % 6 == 0) { printf(",\n"); } else { printf(", "); } } printf("\n};\n"); } int main(int argc, char** argv) { uint32_t x; uint32_t i = 0; uint32_t ary[256]; /* over-ride standard alphabet */ if (argc == 2) { uint8_t* replacements = (uint8_t*)argv[1]; if (strlen((char*)replacements) != 3) { fprintf(stderr, "input must be a string of 3 characters '-', '.' or '_'\n"); exit(1); } fprintf(stderr, "fusing '%s' as replacements in base64 encoding\n", replacements); b64chars[62] = replacements[0]; b64chars[63] = replacements[1]; padchar = replacements[2]; } printStart(); printf("\n\n#if BASE64_LITTLE_ENDIAN\n"); printf("\n\n/* SPECIAL DECODE TABLES FOR LITTLE ENDIAN (INTEL) CPUS */\n\n"); clearDecodeTable(ary); for (i = 0; i < 64; ++i) { x = b64chars[i]; ary[x] = i << 2; } uint32_array_to_c_hex(ary, sizeof(ary) / sizeof(uint32_t), "base64_table_dec_32bit_d0"); printf("\n\n"); clearDecodeTable(ary); for (i = 0; i < 64; ++i) { x = b64chars[i]; ary[x] = ((i & 0x30) >> 4) | ((i & 0x0F) << 12); } uint32_array_to_c_hex(ary, sizeof(ary) / sizeof(uint32_t), "base64_table_dec_32bit_d1"); printf("\n\n"); clearDecodeTable(ary); for (i = 0; i < 64; ++i) { x = b64chars[i]; ary[x] = ((i & 0x03) << 22) | ((i & 0x3c) << 6); } uint32_array_to_c_hex(ary, sizeof(ary) / sizeof(uint32_t), "base64_table_dec_32bit_d2"); printf("\n\n"); clearDecodeTable(ary); for (i = 0; i < 64; ++i) { x = b64chars[i]; ary[x] = i << 16; } uint32_array_to_c_hex(ary, sizeof(ary) / sizeof(uint32_t), "base64_table_dec_32bit_d3"); printf("\n\n"); printf("#else\n"); printf("\n\n/* SPECIAL DECODE TABLES FOR BIG ENDIAN (IBM/MOTOROLA/SUN) CPUS */\n\n"); clearDecodeTable(ary); for (i = 0; i < 64; ++i) { x = b64chars[i]; ary[x] = i << 26; } uint32_array_to_c_hex(ary, sizeof(ary) / sizeof(uint32_t), "base64_table_dec_32bit_d0"); printf("\n\n"); clearDecodeTable(ary); for (i = 0; i < 64; ++i) { x = b64chars[i]; ary[x] = i << 20; } uint32_array_to_c_hex(ary, sizeof(ary) / sizeof(uint32_t), "base64_table_dec_32bit_d1"); printf("\n\n"); clearDecodeTable(ary); for (i = 0; i < 64; ++i) { x = b64chars[i]; ary[x] = i << 14; } uint32_array_to_c_hex(ary, sizeof(ary) / sizeof(uint32_t), "base64_table_dec_32bit_d2"); printf("\n\n"); clearDecodeTable(ary); for (i = 0; i < 64; ++i) { x = b64chars[i]; ary[x] = i << 8; } uint32_array_to_c_hex(ary, sizeof(ary) / sizeof(uint32_t), "base64_table_dec_32bit_d3"); printf("\n\n"); printf("#endif\n"); return 0; } kitty-0.41.1/3rdparty/base64/lib/tables/tables.c0000664000175000017510000000400314773370543020576 0ustar nileshnilesh#include "tables.h" const uint8_t base64_table_enc_6bit[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "+/"; // In the lookup table below, note that the value for '=' (character 61) is // 254, not 255. This character is used for in-band signaling of the end of // the datastream, and we will use that later. The characters A-Z, a-z, 0-9 // and + / are mapped to their "decoded" values. The other bytes all map to // the value 255, which flags them as "invalid input". const uint8_t base64_table_dec_8bit[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 0..15 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 16..31 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, // 32..47 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 254, 255, 255, // 48..63 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64..79 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, // 80..95 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96..111 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255, // 112..127 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 128..143 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, }; #if BASE64_WORDSIZE >= 32 # include "table_dec_32bit.h" # include "table_enc_12bit.h" #endif kitty-0.41.1/3rdparty/base64/lib/tables/tables.h0000664000175000017510000000130014773370543020600 0ustar nileshnilesh#ifndef BASE64_TABLES_H #define BASE64_TABLES_H #include #include "../env.h" // These tables are used by all codecs for fallback plain encoding/decoding: extern const uint8_t base64_table_enc_6bit[]; extern const uint8_t base64_table_dec_8bit[]; // These tables are used for the 32-bit and 64-bit generic decoders: #if BASE64_WORDSIZE >= 32 extern const uint32_t base64_table_dec_32bit_d0[]; extern const uint32_t base64_table_dec_32bit_d1[]; extern const uint32_t base64_table_dec_32bit_d2[]; extern const uint32_t base64_table_dec_32bit_d3[]; // This table is used by the 32 and 64-bit generic encoders: extern const uint16_t base64_table_enc_12bit[]; #endif #endif // BASE64_TABLES_H kitty-0.41.1/3rdparty/ringbuf/0000775000175000017510000000000014773370543015513 5ustar nileshnileshkitty-0.41.1/3rdparty/ringbuf/ringbuf.c0000664000175000017510000002250014773370543017312 0ustar nileshnilesh/* * ringbuf.c - C ring buffer (FIFO) implementation. * * Written in 2011 by Drew Hess . * * To the extent possible under law, the author(s) have dedicated all * copyright and related and neighboring rights to this software to * the public domain worldwide. This software is distributed without * any warranty. * * You should have received a copy of the CC0 Public Domain Dedication * along with this software. If not, see * . */ #ifndef KITTY_DEBUG_BUILD #define NDEBUG 1 #endif #include "ringbuf.h" #include #include #include #include #include #include #include static size_t size_t_min(size_t x, size_t y) { return x > y ? y : x; } /* * The code is written for clarity, not cleverness or performance, and * contains many assert()s to enforce invariant assumptions and catch * bugs. Feel free to optimize the code and to remove asserts for use * in your own projects, once you're comfortable that it functions as * intended. */ struct ringbuf_t { uint8_t *buf; uint8_t *head, *tail; size_t size; }; ringbuf_t ringbuf_new(size_t capacity) { ringbuf_t rb = malloc(sizeof(struct ringbuf_t)); if (rb) { /* One byte is used for detecting the full condition. */ rb->size = capacity + 1; rb->buf = malloc(rb->size); if (rb->buf) ringbuf_reset(rb); else { free(rb); return 0; } } return rb; } size_t ringbuf_buffer_size(const struct ringbuf_t *rb) { return rb->size; } void ringbuf_reset(ringbuf_t rb) { rb->head = rb->tail = rb->buf; } void ringbuf_free(ringbuf_t *rb) { assert(rb && *rb); free((*rb)->buf); free(*rb); *rb = 0; } size_t ringbuf_capacity(const struct ringbuf_t *rb) { return ringbuf_buffer_size(rb) - 1; } /* * Return a pointer to one-past-the-end of the ring buffer's * contiguous buffer. You shouldn't normally need to use this function * unless you're writing a new ringbuf_* function. */ static const uint8_t * ringbuf_end(const struct ringbuf_t *rb) { return rb->buf + ringbuf_buffer_size(rb); } size_t ringbuf_bytes_free(const struct ringbuf_t *rb) { if (rb->head >= rb->tail) return ringbuf_capacity(rb) - (rb->head - rb->tail); else return rb->tail - rb->head - 1; } size_t ringbuf_bytes_used(const struct ringbuf_t *rb) { return ringbuf_capacity(rb) - ringbuf_bytes_free(rb); } int ringbuf_is_full(const struct ringbuf_t *rb) { return ringbuf_bytes_free(rb) == 0; } int ringbuf_is_empty(const struct ringbuf_t *rb) { return ringbuf_bytes_free(rb) == ringbuf_capacity(rb); } const void * ringbuf_tail(const struct ringbuf_t *rb) { return rb->tail; } const void * ringbuf_head(const struct ringbuf_t *rb) { return rb->head; } /* * Given a ring buffer rb and a pointer to a location within its * contiguous buffer, return the a pointer to the next logical * location in the ring buffer. */ static uint8_t * ringbuf_nextp(ringbuf_t rb, const uint8_t *p) { /* * The assert guarantees the expression (++p - rb->buf) is * non-negative; therefore, the modulus operation is safe and * portable. */ assert((p >= rb->buf) && (p < ringbuf_end(rb))); return rb->buf + ((++p - rb->buf) % ringbuf_buffer_size(rb)); } size_t ringbuf_findchr(const struct ringbuf_t *rb, int c, size_t offset) { const uint8_t *bufend = ringbuf_end(rb); size_t bytes_used = ringbuf_bytes_used(rb); if (offset >= bytes_used) return bytes_used; const uint8_t *start = rb->buf + (((rb->tail - rb->buf) + offset) % ringbuf_buffer_size(rb)); assert(bufend > start); size_t n = size_t_min(bufend - start, bytes_used - offset); const uint8_t *found = memchr(start, c, n); if (found) return offset + (found - start); else return ringbuf_findchr(rb, c, offset + n); } size_t ringbuf_memset(ringbuf_t dst, int c, size_t len) { const uint8_t *bufend = ringbuf_end(dst); size_t nwritten = 0; size_t count = size_t_min(len, ringbuf_buffer_size(dst)); int overflow = count > ringbuf_bytes_free(dst); while (nwritten != count) { /* don't copy beyond the end of the buffer */ assert(bufend > dst->head); size_t n = size_t_min(bufend - dst->head, count - nwritten); memset(dst->head, c, n); dst->head += n; nwritten += n; /* wrap? */ if (dst->head == bufend) dst->head = dst->buf; } if (overflow) { dst->tail = ringbuf_nextp(dst, dst->head); assert(ringbuf_is_full(dst)); } return nwritten; } void * ringbuf_memcpy_into(ringbuf_t dst, const void *src, size_t count) { const uint8_t *u8src = src; const uint8_t *bufend = ringbuf_end(dst); int overflow = count > ringbuf_bytes_free(dst); size_t nread = 0; while (nread != count) { /* don't copy beyond the end of the buffer */ assert(bufend > dst->head); size_t n = size_t_min(bufend - dst->head, count - nread); memcpy(dst->head, u8src + nread, n); dst->head += n; nread += n; /* wrap? */ if (dst->head == bufend) dst->head = dst->buf; } if (overflow) { dst->tail = ringbuf_nextp(dst, dst->head); assert(ringbuf_is_full(dst)); } return dst->head; } ssize_t ringbuf_read(int fd, ringbuf_t rb, size_t count) { const uint8_t *bufend = ringbuf_end(rb); size_t nfree = ringbuf_bytes_free(rb); /* don't write beyond the end of the buffer */ assert(bufend > rb->head); count = size_t_min(bufend - rb->head, count); ssize_t n = read(fd, rb->head, count); if (n > 0) { assert(rb->head + n <= bufend); rb->head += n; /* wrap? */ if (rb->head == bufend) rb->head = rb->buf; /* fix up the tail pointer if an overflow occurred */ if ((size_t)n > nfree) { rb->tail = ringbuf_nextp(rb, rb->head); assert(ringbuf_is_full(rb)); } } return n; } void * ringbuf_memmove_from(void *dst, ringbuf_t src, size_t count) { size_t bytes_used = ringbuf_bytes_used(src); if (count > bytes_used) return 0; uint8_t *u8dst = dst; const uint8_t *bufend = ringbuf_end(src); size_t nwritten = 0; while (nwritten != count) { assert(bufend > src->tail); size_t n = size_t_min(bufend - src->tail, count - nwritten); memcpy(u8dst + nwritten, src->tail, n); src->tail += n; nwritten += n; /* wrap ? */ if (src->tail == bufend) src->tail = src->buf; } assert(count + ringbuf_bytes_used(src) == bytes_used); return src->tail; } unsigned char ringbuf_move_char(ringbuf_t src) { assert(!ringbuf_is_empty(src)); const uint8_t *bufend = ringbuf_end(src); assert(bufend > src->tail); uint8_t ans = *src->tail; src->tail += 1; if (src->tail == bufend) src->tail = src->buf; return ans; } size_t ringbuf_memcpy_from(void *dst, const ringbuf_t src, size_t count) { size_t bytes_used = ringbuf_bytes_used(src); if (count > bytes_used) count = bytes_used; uint8_t *u8dst = dst; const uint8_t *bufend = ringbuf_end(src); size_t nwritten = 0; const uint8_t* tail = src->tail; while (nwritten != count) { assert(bufend > tail); size_t n = size_t_min(bufend - tail, count - nwritten); memcpy(u8dst + nwritten, tail, n); tail += n; nwritten += n; /* wrap ? */ if (tail == bufend) tail = src->buf; } assert(ringbuf_bytes_used(src) == bytes_used); return count; } ssize_t ringbuf_write(int fd, ringbuf_t rb, size_t count) { size_t bytes_used = ringbuf_bytes_used(rb); if (count > bytes_used) return 0; const uint8_t *bufend = ringbuf_end(rb); assert(bufend > rb->head); count = size_t_min(bufend - rb->tail, count); ssize_t n = write(fd, rb->tail, count); if (n > 0) { assert(rb->tail + n <= bufend); rb->tail += n; /* wrap? */ if (rb->tail == bufend) rb->tail = rb->buf; assert(n + ringbuf_bytes_used(rb) == bytes_used); } return n; } void * ringbuf_copy(ringbuf_t dst, ringbuf_t src, size_t count) { size_t src_bytes_used = ringbuf_bytes_used(src); if (count > src_bytes_used) return 0; int overflow = count > ringbuf_bytes_free(dst); const uint8_t *src_bufend = ringbuf_end(src); const uint8_t *dst_bufend = ringbuf_end(dst); size_t ncopied = 0; while (ncopied != count) { assert(src_bufend > src->tail); size_t nsrc = size_t_min(src_bufend - src->tail, count - ncopied); assert(dst_bufend > dst->head); size_t n = size_t_min(dst_bufend - dst->head, nsrc); memcpy(dst->head, src->tail, n); src->tail += n; dst->head += n; ncopied += n; /* wrap ? */ if (src->tail == src_bufend) src->tail = src->buf; if (dst->head == dst_bufend) dst->head = dst->buf; } assert(count + ringbuf_bytes_used(src) == src_bytes_used); if (overflow) { dst->tail = ringbuf_nextp(dst, dst->head); assert(ringbuf_is_full(dst)); } return dst->head; } kitty-0.41.1/3rdparty/ringbuf/ringbuf.h0000664000175000017510000002232314773370543017322 0ustar nileshnilesh#pragma once /* * ringbuf.h - C ring buffer (FIFO) interface. * * Written in 2011 by Drew Hess . * * To the extent possible under law, the author(s) have dedicated all * copyright and related and neighboring rights to this software to * the public domain worldwide. This software is distributed without * any warranty. * * You should have received a copy of the CC0 Public Domain Dedication * along with this software. If not, see * . */ /* * A byte-addressable ring buffer FIFO implementation. * * The ring buffer's head pointer points to the starting location * where data should be written when copying data *into* the buffer * (e.g., with ringbuf_read). The ring buffer's tail pointer points to * the starting location where data should be read when copying data * *from* the buffer (e.g., with ringbuf_write). */ #include #include typedef struct ringbuf_t *ringbuf_t; /* * Create a new ring buffer with the given capacity (usable * bytes). Note that the actual internal buffer size may be one or * more bytes larger than the usable capacity, for bookkeeping. * * Returns the new ring buffer object, or 0 if there's not enough * memory to fulfill the request for the given capacity. */ ringbuf_t ringbuf_new(size_t capacity); /* * The size of the internal buffer, in bytes. One or more bytes may be * unusable in order to distinguish the "buffer full" state from the * "buffer empty" state. * * For the usable capacity of the ring buffer, use the * ringbuf_capacity function. */ size_t ringbuf_buffer_size(const struct ringbuf_t *rb); /* * Deallocate a ring buffer, and, as a side effect, set the pointer to * 0. */ void ringbuf_free(ringbuf_t *rb); /* * Reset a ring buffer to its initial state (empty). */ void ringbuf_reset(ringbuf_t rb); /* * The usable capacity of the ring buffer, in bytes. Note that this * value may be less than the ring buffer's internal buffer size, as * returned by ringbuf_buffer_size. */ size_t ringbuf_capacity(const struct ringbuf_t *rb); /* * The number of free/available bytes in the ring buffer. This value * is never larger than the ring buffer's usable capacity. */ size_t ringbuf_bytes_free(const struct ringbuf_t *rb); /* * The number of bytes currently being used in the ring buffer. This * value is never larger than the ring buffer's usable capacity. */ size_t ringbuf_bytes_used(const struct ringbuf_t *rb); int ringbuf_is_full(const struct ringbuf_t *rb); int ringbuf_is_empty(const struct ringbuf_t *rb); /* * Const access to the head and tail pointers of the ring buffer. */ const void * ringbuf_tail(const struct ringbuf_t *rb); const void * ringbuf_head(const struct ringbuf_t *rb); /* * Locate the first occurrence of character c (converted to an * unsigned char) in ring buffer rb, beginning the search at offset * bytes from the ring buffer's tail pointer. The function returns the * offset of the character from the ring buffer's tail pointer, if * found. If c does not occur in the ring buffer, the function returns * the number of bytes used in the ring buffer. * * Note that the offset parameter and the returned offset are logical * offsets from the tail pointer, not necessarily linear offsets. */ size_t ringbuf_findchr(const struct ringbuf_t *rb, int c, size_t offset); /* * Beginning at ring buffer dst's head pointer, fill the ring buffer * with a repeating sequence of len bytes, each of value c (converted * to an unsigned char). len can be as large as you like, but the * function will never write more than ringbuf_buffer_size(dst) bytes * in a single invocation, since that size will cause all bytes in the * ring buffer to be written exactly once each. * * Note that if len is greater than the number of free bytes in dst, * the ring buffer will overflow. When an overflow occurs, the state * of the ring buffer is guaranteed to be consistent, including the * head and tail pointers; old data will simply be overwritten in FIFO * fashion, as needed. However, note that, if calling the function * results in an overflow, the value of the ring buffer's tail pointer * may be different than it was before the function was called. * * Returns the actual number of bytes written to dst: len, if * len < ringbuf_buffer_size(dst), else ringbuf_buffer_size(dst). */ size_t ringbuf_memset(ringbuf_t dst, int c, size_t len); /* * Copy n bytes from a contiguous memory area src into the ring buffer * dst. Returns the ring buffer's new head pointer. * * It is possible to copy more data from src than is available in the * buffer; i.e., it's possible to overflow the ring buffer using this * function. When an overflow occurs, the state of the ring buffer is * guaranteed to be consistent, including the head and tail pointers; * old data will simply be overwritten in FIFO fashion, as * needed. However, note that, if calling the function results in an * overflow, the value of the ring buffer's tail pointer may be * different than it was before the function was called. */ void * ringbuf_memcpy_into(ringbuf_t dst, const void *src, size_t count); /* * This convenience function calls read(2) on the file descriptor fd, * using the ring buffer rb as the destination buffer for the read, * and returns the value returned by read(2). It will only call * read(2) once, and may return a short count. * * It is possible to read more data from the file descriptor than is * available in the buffer; i.e., it's possible to overflow the ring * buffer using this function. When an overflow occurs, the state of * the ring buffer is guaranteed to be consistent, including the head * and tail pointers: old data will simply be overwritten in FIFO * fashion, as needed. However, note that, if calling the function * results in an overflow, the value of the ring buffer's tail pointer * may be different than it was before the function was called. */ ssize_t ringbuf_read(int fd, ringbuf_t rb, size_t count); /* * Copy n bytes from the ring buffer src, starting from its tail * pointer, into a contiguous memory area dst. Returns the value of * src's tail pointer after the copy is finished. * * Note that this copy is destructive with respect to the ring buffer: * the n bytes copied from the ring buffer are no longer available in * the ring buffer after the copy is complete, and the ring buffer * will have n more free bytes than it did before the function was * called. * * This function will *not* allow the ring buffer to underflow. If * count is greater than the number of bytes used in the ring buffer, * no bytes are copied, and the function will return 0. */ void * ringbuf_memmove_from(void *dst, ringbuf_t src, size_t count); /* ringbuf_memmove_from() optimized for a single character. * Must only be called if the ringbuf is not empty */ unsigned char ringbuf_move_char(ringbuf_t src); /* * Same as ringbuf_memmove_from() except that it does not change the ringbuffer * and returns the actual number of bytes copied, which is the minimum of ringbuf_bytes_used * and count. */ size_t ringbuf_memcpy_from(void *dst, const ringbuf_t src, size_t count); /* * This convenience function calls write(2) on the file descriptor fd, * using the ring buffer rb as the source buffer for writing (starting * at the ring buffer's tail pointer), and returns the value returned * by write(2). It will only call write(2) once, and may return a * short count. * * Note that this copy is destructive with respect to the ring buffer: * any bytes written from the ring buffer to the file descriptor are * no longer available in the ring buffer after the copy is complete, * and the ring buffer will have N more free bytes than it did before * the function was called, where N is the value returned by the * function (unless N is < 0, in which case an error occurred and no * bytes were written). * * This function will *not* allow the ring buffer to underflow. If * count is greater than the number of bytes used in the ring buffer, * no bytes are written to the file descriptor, and the function will * return 0. */ ssize_t ringbuf_write(int fd, ringbuf_t rb, size_t count); /* * Copy count bytes from ring buffer src, starting from its tail * pointer, into ring buffer dst. Returns dst's new head pointer after * the copy is finished. * * Note that this copy is destructive with respect to the ring buffer * src: any bytes copied from src into dst are no longer available in * src after the copy is complete, and src will have 'count' more free * bytes than it did before the function was called. * * It is possible to copy more data from src than is available in dst; * i.e., it's possible to overflow dst using this function. When an * overflow occurs, the state of dst is guaranteed to be consistent, * including the head and tail pointers; old data will simply be * overwritten in FIFO fashion, as needed. However, note that, if * calling the function results in an overflow, the value dst's tail * pointer may be different than it was before the function was * called. * * It is *not* possible to underflow src; if count is greater than the * number of bytes used in src, no bytes are copied, and the function * returns 0. */ void * ringbuf_copy(ringbuf_t dst, ringbuf_t src, size_t count); kitty-0.41.1/3rdparty/verstable.h0000664000175000017510000023604214773370543016226 0ustar nileshnilesh/*------------------------------------------------- VERSTABLE v2.1.1 --------------------------------------------------- Verstable is a C99-compatible, open-addressing hash table using quadratic probing and the following additions: * All keys that hash (i.e. "belong") to the same bucket (their "home bucket") are linked together by an 11-bit integer specifying the quadratic displacement, relative to that bucket, of the next key in the chain. * If a chain of keys exists for a given bucket, then it always begins at that bucket. To maintain this policy, a 1-bit flag is used to mark whether the key occupying a bucket belongs there. When inserting a new key, if the bucket it belongs to is occupied by a key that does not belong there, then the occupying key is evicted and the new key takes the bucket. * A 4-bit fragment of each key's hash code is also stored. * The aforementioned metadata associated with each bucket (the 4-bit hash fragment, the 1-bit flag, and the 11-bit link to the next key in the chain) are stored together in a uint16_t array rather than in the bucket alongside the key and (optionally) the value. One way to conceptualize this scheme is as a chained hash table in which overflowing keys are stored not in separate memory allocations but in otherwise unused buckets. In this regard, it shares similarities with Malte Skarupke's Bytell hash table (https://www.youtube.com/watch?v=M2fKMP47slQ) and traditional "coalesced hashing". Advantages of this scheme include: * Fast lookups impervious to load factor: If the table contains any key belonging to the lookup key's home bucket, then that bucket contains the first in a traversable chain of all keys belonging to it. Hence, only the home bucket and other buckets containing keys belonging to it are ever probed. Moreover, the stored hash fragments allow skipping most non-matching keys in the chain without accessing the actual buckets array or calling the (potentially expensive) key comparison function. * Fast insertions: Insertions are faster than they are in other schemes that move keys around (e.g. Robin Hood) because they only move, at most, one existing key. * Fast, tombstone-free deletions: Deletions, which usually require tombstones in quadratic-probing hash tables, are tombstone-free and only move, at most, one existing key. * Fast iteration: The separate metadata array allows keys in sparsely populated tables to be found without incurring the frequent cache misses that would result from traversing the buckets array. Usage example: +---------------------------------------------------------+----------------------------------------------------------+ | Using the generic macro API (C11 and later): | Using the prefixed functions API (C99 and later): | |---------------------------------------------------------+----------------------------------------------------------+ | #include | #include | | | | | // Instantiating a set template. | // Instantiating a set template. | | #define NAME int_set | #define NAME int_set | | #define KEY_TY int | #define KEY_TY int | | #include "verstable.h" | #define HASH_FN vt_hash_integer | | | #define CMPR_FN vt_cmpr_integer | | // Instantiating a map template. | #include "verstable.h" | | #define NAME int_int_map | | | #define KEY_TY int | // Instantiating a map template. | | #define VAL_TY int | #define NAME int_int_map | | #include "verstable.h" | #define KEY_TY int | | | #define VAL_TY int | | int main( void ) | #define HASH_FN vt_hash_integer | | { | #define CMPR_FN vt_cmpr_integer | | // Set. | #include "verstable.h" | | | | | int_set our_set; | int main( void ) | | vt_init( &our_set ); | { | | | // Set. | | // Inserting keys. | | | for( int i = 0; i < 10; ++i ) | int_set our_set; | | { | int_set_init( &our_set ); | | int_set_itr itr = vt_insert( &our_set, i ); | | | if( vt_is_end( itr ) ) | // Inserting keys. | | { | for( int i = 0; i < 10; ++i ) | | // Out of memory, so abort. | { | | vt_cleanup( &our_set ); | int_set_itr itr = | | return 1; | int_set_insert( &our_set, i ); | | } | if( int_set_is_end( itr ) ) | | } | { | | | // Out of memory, so abort. | | // Erasing keys. | int_set_cleanup( &our_set ); | | for( int i = 0; i < 10; i += 3 ) | return 1; | | vt_erase( &our_set, i ); | } | | | } | | // Retrieving keys. | | | for( int i = 0; i < 10; ++i ) | // Erasing keys. | | { | for( int i = 0; i < 10; i += 3 ) | | int_set_itr itr = vt_get( &our_set, i ); | int_set_erase( &our_set, i ); | | if( !vt_is_end( itr ) ) | | | printf( "%d ", itr.data->key ); | // Retrieving keys. | | } | for( int i = 0; i < 10; ++i ) | | // Printed: 1 2 4 5 7 8 | { | | | int_set_itr itr = int_set_get( &our_set, i ); | | // Iteration. | if( !int_set_is_end( itr ) ) | | for( | printf( "%d ", itr.data->key ); | | int_set_itr itr = vt_first( &our_set ); | } | | !vt_is_end( itr ); | // Printed: 1 2 4 5 7 8 | | itr = vt_next( itr ) | | | ) | // Iteration. | | printf( "%d ", itr.data->key ); | for( | | // Printed: 2 4 7 1 5 8 | int_set_itr itr = | | | int_set_first( &our_set ); | | vt_cleanup( &our_set ); | !int_set_is_end( itr ); | | | itr = int_set_next( itr ) | | // Map. | ) | | | printf( "%d ", itr.data->key ); | | int_int_map our_map; | // Printed: 2 4 7 1 5 8 | | vt_init( &our_map ); | | | | int_set_cleanup( &our_set ); | | // Inserting keys and values. | | | for( int i = 0; i < 10; ++i ) | // Map. | | { | | | int_int_map_itr itr = | int_int_map our_map; | | vt_insert( &our_map, i, i + 1 ); | int_int_map_init( &our_map ); | | if( vt_is_end( itr ) ) | | | { | // Inserting keys and values. | | // Out of memory, so abort. | for( int i = 0; i < 10; ++i ) | | vt_cleanup( &our_map ); | { | | return 1; | int_int_map_itr itr = | | } | int_int_map_insert( &our_map, i, i + 1 ); | | } | if( int_int_map_is_end( itr ) ) | | | { | | // Erasing keys and values. | // Out of memory, so abort. | | for( int i = 0; i < 10; i += 3 ) | int_int_map_cleanup( &our_map ); | | vt_erase( &our_map, i ); | return 1; | | | } | | // Retrieving keys and values. | } | | for( int i = 0; i < 10; ++i ) | | | { | // Erasing keys and values. | | int_int_map_itr itr = vt_get( &our_map, i ); | for( int i = 0; i < 10; i += 3 ) | | if( !vt_is_end( itr ) ) | int_int_map_erase( &our_map, i ); | | printf( | | | "%d:%d ", | // Retrieving keys and values. | | itr.data->key, | for( int i = 0; i < 10; ++i ) | | itr.data->val | { | | ); | int_int_map_itr itr = | | } | int_int_map_get( &our_map, i ); | | // Printed: 1:2 2:3 4:5 5:6 7:8 8:9 | if( !int_int_map_is_end( itr ) ) | | | printf( | | // Iteration. | "%d:%d ", | | for( | itr.data->key, | | int_int_map_itr itr = vt_first( &our_map ); | itr.data->val | | !vt_is_end( itr ); | ); | | itr = vt_next( itr ) | } | | ) | // Printed: 1:2 2:3 4:5 5:6 7:8 8:9 | | printf( | | | "%d:%d ", | // Iteration. | | itr.data->key, | for( | | itr.data->val | int_int_map_itr itr = | | ); | int_int_map_first( &our_map ); | | // Printed: 2:3 4:5 7:8 1:2 5:6 8:9 | !int_int_map_is_end( itr ); | | | itr = int_int_map_next( itr ) | | vt_cleanup( &our_map ); | ) | | } | printf( | | | "%d:%d ", | | | itr.data->key, | | | itr.data->val | | | ); | | | // Printed: 2:3 4:5 7:8 1:2 5:6 8:9 | | | | | | int_int_map_cleanup( &our_map ); | | | } | | | | +---------------------------------------------------------+----------------------------------------------------------+ API: Instantiating a hash table template: Create a new hash table type in the following manner: #define NAME #define KEY_TY #include "verstable.h" The NAME macro specifies the name of hash table type that the library will declare, the prefix for the functions associated with it, and the prefix for the associated iterator type. The KEY_TY macro specifies the key type. In C99, it is also always necessary to define HASH_FN and CMPR_FN (see below) before including the header. The following macros may also be defined before including the header: #define VAL_TY The type of the value associated with each key. If this macro is defined, the hash table acts as a map associating keys with values. Otherwise, it acts as a set containing only keys. #define HASH_FN The name of the existing function used to hash each key. The function should have the signature uint64_t ( KEY_TY key ) and return a 64-bit hash code. For best performance, the hash function should provide a high level of entropy across all bits. There are two default hash functions: vt_hash_integer for all integer types up to 64 bits in size, and vt_hash_string for NULL-terminated strings (i.e. char *). When KEY_TY is one of such types and the compiler is in C11 mode or later, HASH_FN may be left undefined, in which case the appropriate default function is inferred from KEY_TY. Otherwise, HASH_FN must be defined. #define CMPR_FN The name of the existing function used to compare two keys. The function should have the signature bool ( KEY_TY key_1, KEY_TY key_2 ) and return true if the two keys are equal. There are two default comparison functions: vt_cmpr_integer for all integer types up to 64 bits in size, and vt_cmpr_string for NULL-terminated strings (i.e. char *). As with the default hash functions, in C11 or later the appropriate default comparison function is inferred if KEY_TY is one of such types and CMPR_FN is left undefined. Otherwise, CMPR_FN must be defined. #define MAX_LOAD The floating-point load factor at which the hash table automatically doubles the size of its internal buckets array. The default is 0.9, i.e. 90%. #define KEY_DTOR_FN The name of the existing destructor function, with the signature void ( KEY_TY key ), called on a key when it is erased from the table or replaced by a newly inserted key. The API functions that may call the key destructor are NAME_insert, NAME_erase, NAME_erase_itr, NAME_clear, and NAME_cleanup. #define VAL_DTOR_FN The name of the existing destructor function, with the signature void ( VAL_TY val ), called on a value when it is erased from the table or replaced by a newly inserted value. The API functions that may call the value destructor are NAME_insert, NAME_erase, NAME_erase_itr, NAME_clear, and NAME_cleanup. #define CTX_TY The type of the hash table type's ctx (context) member. This member only exists if CTX_TY was defined. It is intended to be used in conjunction with MALLOC_FN and FREE_FN (see below). #define MALLOC_FN The name of the existing function used to allocate memory. If CTX_TY was defined, the signature should be void *( size_t size, CTX_TY *ctx ), where size is the number of bytes to allocate and ctx points to the table's ctx member. Otherwise, the signature should be void *( size_t size ). The default wraps stdlib.h's malloc. #define FREE_FN The name of the existing function used to free memory. If CTX_TY was defined, the signature should be void ( void *ptr, size_t size, CTX_TY *ctx ), where ptr points to the memory to free, size is the number of bytes that were allocated, and ctx points to the table's ctx member. Otherwise, the signature should be void ( void *ptr, size_t size ). The default wraps stdlib.h's free. #define HEADER_MODE #define IMPLEMENTATION_MODE By default, all hash table functions are defined as static inline functions, the intent being that a given hash table template should be instantiated once per translation unit; for best performance, this is the recommended way to use the library. However, it is also possible separate the struct definitions and function declarations from the function definitions such that one implementation can be shared across all translation units (as in a traditional header and source file pair). In that case, instantiate a template wherever it is needed by defining HEADER_MODE, along with only NAME, KEY_TY, and (optionally) VAL_TY, CTX_TY, and header guards, and including the library, e.g.: #ifndef INT_INT_MAP_H #define INT_INT_MAP_H #define NAME int_int_map #define KEY_TY int #define VAL_TY int #define HEADER_MODE #include "verstable.h" #endif In one source file, define IMPLEMENTATION_MODE, along with NAME, KEY_TY, and any of the aforementioned optional macros, and include the library, e.g.: #define NAME int_int_map #define KEY_TY int #define VAL_TY int #define HASH_FN vt_hash_integer // C99. #define CMPR_FN vt_cmpr_integer // C99. #define MAX_LOAD 0.8 #define IMPLEMENTATION_MODE #include "verstable.h" Including the library automatically undefines all the aforementioned macros after they have been used to instantiate the template. Functions: The functions associated with a hash table type are all prefixed with the name the user supplied via the NAME macro. In C11 and later, the generic "vt_"-prefixed macros may be used to automatically select the correct version of the specified function based on the arguments. void NAME_init( NAME *table ) void NAME_init( NAME *table, CTX_TY ctx ) // C11 generic macro: vt_init. Initializes the table for use. If CTX_TY was defined, ctx sets the table's ctx member. bool NAME_init_clone( NAME *table, NAME *source ) bool NAME_init_clone( NAME *table, NAME *source, CTX_TY ctx ) // C11 generic macro: vt_init_clone. Initializes the table as a shallow copy of the specified source table. If CTX_TY was defined, ctx sets the table's ctx member. Returns false in the case of memory allocation failure. size_t NAME_size( NAME *table ) // C11 generic macro: vt_size. Returns the number of keys currently in the table. size_t NAME_bucket_count( NAME *table ) // C11 generic macro: vt_bucket_count. Returns the table's current bucket count. NAME_itr NAME_insert( NAME *table, KEY_TY key ) NAME_itr NAME_insert( NAME *table, KEY_TY key, VAL_TY val ) // C11 generic macro: vt_insert. Inserts the specified key (and value, if VAL_TY was defined) into the hash table. If the same key already exists, then the new key (and value) replaces the existing key (and value). Returns an iterator to the new key, or an end iterator in the case of memory allocation failure. NAME_itr NAME_get_or_insert( NAME *table, KEY_TY key ) NAME_itr NAME_get_or_insert( NAME *table, KEY_TY key, VAL_TY val ) // C11 generic macro: vt_get_or_insert. Inserts the specified key (and value, if VAL_TY was defined) if it does not already exist in the table. Returns an iterator to the new key if it was inserted, or an iterator to the existing key, or an end iterator if the key did not exist but the new key could not be inserted because of memory allocation failure. Determine whether the key was inserted by comparing the table's size before and after the call. NAME_itr NAME_get( NAME *table, KEY_TY key ) // C11 generic macro: vt_get. Returns a iterator to the specified key, or an end iterator if no such key exists. bool NAME_erase( NAME *table, KEY_TY key ) // C11 generic macro: vt_erase. Erases the specified key (and associated value, if VAL_TY was defined), if it exists. Returns true if a key was erased. NAME_itr NAME_erase_itr( NAME *table, NAME_itr itr ) // C11 generic macro: vt_erase_itr. Erases the key (and associated value, if VAL_TY was defined) pointed to by the specified iterator. Returns an iterator to the next key in the table, or an end iterator if the erased key was the last one. bool NAME_reserve( NAME *table, size_t size ) // C11 generic macro: vt_reserve. Ensures that the bucket count is large enough to support the specified key count (i.e. size) without rehashing. Returns false if unsuccessful due to memory allocation failure. bool NAME_shrink( NAME *table ) // C11 generic macro: vt_shrink. Shrinks the bucket count to best accommodate the current size. Returns false if unsuccessful due to memory allocation failure. NAME_itr NAME_first( NAME *table ) // C11 generic macro: vt_first. Returns an iterator to the first key in the table, or an end iterator if the table is empty. bool NAME_is_end( NAME *table, NAME_itr itr ) // C11 generic macro: vt_is_end. Returns true if the iterator is an end iterator. NAME_itr NAME_next( NAME_itr itr ) // C11 generic macro: vt_next. Returns an iterator to the key after the one pointed to by the specified iterator, or an end iterator if the specified iterator points to the last key in the table. void NAME_clear( NAME *table ) // C11 generic macro: vt_clear. Erases all keys (and values, if VAL_TY was defined) in the table. void NAME_cleanup( NAME *table ) // C11 generic macro: vt_cleanup. Erases all keys (and values, if VAL_TY was defined) in the table, frees all memory associated with it, and initializes it for reuse. Iterators: Access the key (and value, if VAL_TY was defined) that an iterator points to using the NAME_itr struct's data member: itr.data->key itr.data->val Functions that may insert new keys (NAME_insert and NAME_get_or_insert), erase keys (NAME_erase and NAME_erase_itr), or reallocate the internal bucket array (NAME_reserve and NAME_shrink) invalidate all exiting iterators. To delete keys during iteration and resume iterating, use the return value of NAME_erase_itr. Version history: 18/06/2024 2.1.1: Fixed a bug affecting iteration on big-endian platforms under MSVC. 27/05/2024 2.1.0: Replaced the Murmur3 mixer with the fast-hash mixer as the default integer hash function. Fixed a bug that could theoretically cause a crash on rehash (triggerable in testing using NAME_shrink with a maximum load factor significantly higher than 1.0). 06/02/2024 2.0.0: Improved custom allocator support by introducing the CTX_TY option and allowing user-supplied free functions to receive the allocation size. Improved documentation. Introduced various optimizations, including storing the buckets-array size mask instead of the bucket count, eliminating empty-table checks, combining the buckets memory and metadata memory into one allocation, and adding branch prediction macros. Fixed a bug that caused a key to be used after destruction during erasure. 12/12/2023 1.0.0: Initial release. License (MIT): Copyright (c) 2023-2024 Jackson L. Allan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*--------------------------------------------------------------------------------------------------------------------*/ /* Common header section */ /*--------------------------------------------------------------------------------------------------------------------*/ #ifndef VERSTABLE_H #define VERSTABLE_H #include #include #include #include #include #include // Two-way concatenation macro. #define VT_CAT_( a, b ) a##b #define VT_CAT( a, b ) VT_CAT_( a, b ) // Branch optimization macros. #ifdef __GNUC__ #define VT_LIKELY( expression ) __builtin_expect( (bool)( expression ), true ) #define VT_UNLIKELY( expression ) __builtin_expect( (bool)( expression ), false ) #else #define VT_LIKELY( expression ) ( expression ) #define VT_UNLIKELY( expression ) ( expression ) #endif // Masks for manipulating and extracting data from a bucket's uint16_t metadatum. #define VT_EMPTY 0x0000 #define VT_HASH_FRAG_MASK 0xF000 // 0b1111000000000000. #define VT_IN_HOME_BUCKET_MASK 0x0800 // 0b0000100000000000. #define VT_DISPLACEMENT_MASK 0x07FF // 0b0000011111111111, also denotes the displacement limit. Set to VT_LOAD to 1.0 // to test proper handling of encroachment on the displacement limit during // inserts. // Extracts a hash fragment from a uint64_t hash code. // We take the highest four bits so that keys that map (via modulo) to the same bucket have distinct hash fragments. static inline uint16_t vt_hashfrag( uint64_t hash ) { return ( hash >> 48 ) & VT_HASH_FRAG_MASK; } // Standard quadratic probing formula that guarantees that all buckets are visited when the bucket count is a power of // two (at least in theory, because the displacement limit could terminate the search early when the bucket count is // high). static inline size_t vt_quadratic( uint16_t displacement ) { return ( (size_t)displacement * displacement + displacement ) / 2; } #define VT_MIN_NONZERO_BUCKET_COUNT 8 // Must be a power of two. // Function to find the left-most non-zero uint16_t in a uint64_t. // This function is used when we scan four buckets at a time while iterating and relies on compiler intrinsics wherever // possible. #if defined( __GNUC__ ) && ULLONG_MAX == 0xFFFFFFFFFFFFFFFF static inline int vt_first_nonzero_uint16( uint64_t val ) { const uint16_t endian_checker = 0x0001; if( *(const char *)&endian_checker ) // Little-endian (the compiler will optimize away the check at -O1 and above). return __builtin_ctzll( val ) / 16; return __builtin_clzll( val ) / 16; } #elif defined( _MSC_VER ) && ( defined( _M_X64 ) || defined( _M_ARM64 ) ) #include #pragma intrinsic(_BitScanForward64) #pragma intrinsic(_BitScanReverse64) static inline int vt_first_nonzero_uint16( uint64_t val ) { unsigned long result; const uint16_t endian_checker = 0x0001; if( *(const char *)&endian_checker ) _BitScanForward64( &result, val ); else { _BitScanReverse64( &result, val ); result = 63 - result; } return result / 16; } #else static inline int vt_first_nonzero_uint16( uint64_t val ) { int result = 0; uint32_t half; memcpy( &half, &val, sizeof( uint32_t ) ); if( !half ) result += 2; uint16_t quarter; memcpy( &quarter, (char *)&val + result * sizeof( uint16_t ), sizeof( uint16_t ) ); if( !quarter ) result += 1; return result; } #endif // When the bucket count is zero, setting the metadata pointer to point to a VT_EMPTY placeholder, rather than NULL, // allows us to avoid checking for a zero bucket count during insertion and lookup. static const uint16_t vt_empty_placeholder_metadatum = VT_EMPTY; // Default hash and comparison functions. // Fast-hash, as described by https://jonkagstrom.com/bit-mixer-construction and // https://code.google.com/archive/p/fast-hash. // In testing, this hash function provided slightly better performance than the Murmur3 mixer. static inline uint64_t vt_hash_integer( uint64_t key ) { key ^= key >> 23; key *= 0x2127599bf4325c37ull; key ^= key >> 47; return key; } // FNV-1a. static inline uint64_t vt_hash_string( const char *key ) { uint64_t hash = 0xcbf29ce484222325ull; while( *key ) hash = ( (unsigned char)*key++ ^ hash ) * 0x100000001b3ull; return hash; } static inline bool vt_cmpr_integer( uint64_t key_1, uint64_t key_2 ) { return key_1 == key_2; } static inline bool vt_cmpr_string( const char *key_1, const char *key_2 ) { return strcmp( key_1, key_2 ) == 0; } // Default allocation and free functions. static inline void *vt_malloc( size_t size ) { return malloc( size ); } static inline void vt_free( void *ptr, size_t size ) { (void)size; free( ptr ); } static inline void *vt_malloc_with_ctx( size_t size, void *ctx ) { (void)ctx; return malloc( size ); } static inline void vt_free_with_ctx( void *ptr, size_t size, void *ctx ) { (void)size; (void)ctx; free( ptr ); } // The rest of the common header section pertains to the C11 generic macro API. // This interface is based on the extendible-_Generic mechanism documented in detail at // https://github.com/JacksonAllan/CC/blob/main/articles/Better_C_Generics_Part_1_The_Extendible_Generic.md. // In summary, instantiating a template also defines wrappers for the template's types and functions with names in the // pattern of vt_table_NNNN and vt_init_NNNN, where NNNN is an automatically generated integer unique to the template // instance in the current translation unit. // These wrappers plug in to _Generic-based API macros, which use preprocessor magic to automatically generate _Generic // slots for every existing template instance. #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined( VT_NO_C11_GENERIC_API ) // Octal counter that supports up to 511 hash table templates. #define VT_TEMPLATE_COUNT_D1 0 // Digit 1, i.e. least significant digit. #define VT_TEMPLATE_COUNT_D2 0 #define VT_TEMPLATE_COUNT_D3 0 // Four-way concatenation macro. #define VT_CAT_4_( a, b, c, d ) a##b##c##d #define VT_CAT_4( a, b, c, d ) VT_CAT_4_( a, b, c, d ) // Provides the current value of the counter as a three-digit octal number preceded by 0. #define VT_TEMPLATE_COUNT VT_CAT_4( 0, VT_TEMPLATE_COUNT_D3, VT_TEMPLATE_COUNT_D2, VT_TEMPLATE_COUNT_D1 ) // _Generic-slot generation macros. #define VT_GENERIC_SLOT( ty, fn, n ) , VT_CAT( ty, n ): VT_CAT( fn, n ) #define VT_R1_0( ty, fn, d3, d2 ) #define VT_R1_1( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 0 ) ) #define VT_R1_2( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 1 ) ) VT_R1_1( ty, fn, d3, d2 ) #define VT_R1_3( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 2 ) ) VT_R1_2( ty, fn, d3, d2 ) #define VT_R1_4( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 3 ) ) VT_R1_3( ty, fn, d3, d2 ) #define VT_R1_5( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 4 ) ) VT_R1_4( ty, fn, d3, d2 ) #define VT_R1_6( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 5 ) ) VT_R1_5( ty, fn, d3, d2 ) #define VT_R1_7( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 6 ) ) VT_R1_6( ty, fn, d3, d2 ) #define VT_R1_8( ty, fn, d3, d2 ) VT_GENERIC_SLOT( ty, fn, VT_CAT_4( 0, d3, d2, 7 ) ) VT_R1_7( ty, fn, d3, d2 ) #define VT_R2_0( ty, fn, d3 ) #define VT_R2_1( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 0 ) #define VT_R2_2( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 1 ) VT_R2_1( ty, fn, d3 ) #define VT_R2_3( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 2 ) VT_R2_2( ty, fn, d3 ) #define VT_R2_4( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 3 ) VT_R2_3( ty, fn, d3 ) #define VT_R2_5( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 4 ) VT_R2_4( ty, fn, d3 ) #define VT_R2_6( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 5 ) VT_R2_5( ty, fn, d3 ) #define VT_R2_7( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 6 ) VT_R2_6( ty, fn, d3 ) #define VT_R2_8( ty, fn, d3 ) VT_R1_8( ty, fn, d3, 7 ) VT_R2_7( ty, fn, d3 ) #define VT_R3_0( ty, fn ) #define VT_R3_1( ty, fn ) VT_R2_8( ty, fn, 0 ) #define VT_R3_2( ty, fn ) VT_R2_8( ty, fn, 1 ) VT_R3_1( ty, fn ) #define VT_R3_3( ty, fn ) VT_R2_8( ty, fn, 2 ) VT_R3_2( ty, fn ) #define VT_R3_4( ty, fn ) VT_R2_8( ty, fn, 3 ) VT_R3_3( ty, fn ) #define VT_R3_5( ty, fn ) VT_R2_8( ty, fn, 4 ) VT_R3_4( ty, fn ) #define VT_R3_6( ty, fn ) VT_R2_8( ty, fn, 5 ) VT_R3_5( ty, fn ) #define VT_R3_7( ty, fn ) VT_R2_8( ty, fn, 6 ) VT_R3_6( ty, fn ) #define VT_GENERIC_SLOTS( ty, fn ) \ VT_CAT( VT_R1_, VT_TEMPLATE_COUNT_D1 )( ty, fn, VT_TEMPLATE_COUNT_D3, VT_TEMPLATE_COUNT_D2 ) \ VT_CAT( VT_R2_, VT_TEMPLATE_COUNT_D2 )( ty, fn, VT_TEMPLATE_COUNT_D3 ) \ VT_CAT( VT_R3_, VT_TEMPLATE_COUNT_D3 )( ty, fn ) \ // Actual generic API macros. // vt_init must be handled as a special case because it could take one or two arguments, depending on whether CTX_TY // was defined. #define VT_ARG_3( _1, _2, _3, ... ) _3 #define vt_init( ... ) VT_ARG_3( __VA_ARGS__, vt_init_with_ctx, vt_init_without_ctx, )( __VA_ARGS__ ) #define vt_init_without_ctx( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_init_ ) )( table ) #define vt_init_with_ctx( table, ... ) _Generic( *( table ) \ VT_GENERIC_SLOTS( vt_table_, vt_init_ ) \ )( table, __VA_ARGS__ ) \ #define vt_init_clone( table, ... ) _Generic( *( table ) \ VT_GENERIC_SLOTS( vt_table_, vt_init_clone_ ) \ )( table, __VA_ARGS__ ) \ #define vt_size( table )_Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_size_ ) )( table ) #define vt_bucket_count( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_bucket_count_ ) )( table ) #define vt_is_end( itr ) _Generic( itr VT_GENERIC_SLOTS( vt_table_itr_, vt_is_end_ ) )( itr ) #define vt_insert( table, ... ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_insert_ ) )( table, __VA_ARGS__ ) #define vt_get_or_insert( table, ... ) _Generic( *( table ) \ VT_GENERIC_SLOTS( vt_table_, vt_get_or_insert_ ) \ )( table, __VA_ARGS__ ) \ #define vt_get( table, ... ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_get_ ) )( table, __VA_ARGS__ ) #define vt_erase( table, ... ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_erase_ ) )( table, __VA_ARGS__ ) #define vt_next( itr ) _Generic( itr VT_GENERIC_SLOTS( vt_table_itr_, vt_next_ ) )( itr ) #define vt_erase_itr( table, ... ) _Generic( *( table ) \ VT_GENERIC_SLOTS( vt_table_, vt_erase_itr_ ) \ )( table, __VA_ARGS__ ) \ #define vt_reserve( table, ... ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_reserve_ ) )( table, __VA_ARGS__ ) #define vt_shrink( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_shrink_ ) )( table ) #define vt_first( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_first_ ) )( table ) #define vt_clear( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_clear_ ) )( table ) #define vt_cleanup( table ) _Generic( *( table ) VT_GENERIC_SLOTS( vt_table_, vt_cleanup_ ) )( table ) #endif #endif /*--------------------------------------------------------------------------------------------------------------------*/ /* Prefixed structs */ /*--------------------------------------------------------------------------------------------------------------------*/ #ifndef IMPLEMENTATION_MODE typedef struct { KEY_TY key; #ifdef VAL_TY VAL_TY val; #endif } VT_CAT( NAME, _bucket ); typedef struct { VT_CAT( NAME, _bucket ) *data; uint16_t *metadatum; uint16_t *metadata_end; // Iterators carry an internal end pointer so that NAME_is_end does not need the table to be // passed in as an argument. // This also allows for the zero-bucket-count check to occur once in NAME_first, rather than // repeatedly in NAME_is_end. size_t home_bucket; // SIZE_MAX if home bucket is unknown. } VT_CAT( NAME, _itr ); typedef struct { size_t key_count; size_t buckets_mask; // Rather than storing the bucket count directly, we store the bit mask used to reduce a hash // code or displacement-derived bucket index to the buckets array, i.e. the bucket count minus // one. // Consequently, a zero bucket count (i.e. when .metadata points to the placeholder) constitutes // a special case, represented by all bits unset (i.e. zero). VT_CAT( NAME, _bucket ) *buckets; uint16_t *metadata; // As described above, each metadatum consists of a 4-bit hash-code fragment (X), a 1-bit flag // indicating whether the key in this bucket begins a chain associated with the bucket (Y), and // an 11-bit value indicating the quadratic displacement of the next key in the chain (Z): // XXXXYZZZZZZZZZZZ. #ifdef CTX_TY CTX_TY ctx; #endif } NAME; #endif /*--------------------------------------------------------------------------------------------------------------------*/ /* Function prototypes */ /*--------------------------------------------------------------------------------------------------------------------*/ #if defined( HEADER_MODE ) || defined( IMPLEMENTATION_MODE ) #define VT_API_FN_QUALIFIERS #else #define VT_API_FN_QUALIFIERS static inline #endif #ifndef IMPLEMENTATION_MODE VT_API_FN_QUALIFIERS void VT_CAT( NAME, _init )( NAME * #ifdef CTX_TY , CTX_TY #endif ); VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _init_clone )( NAME *, NAME * #ifdef CTX_TY , CTX_TY #endif ); VT_API_FN_QUALIFIERS size_t VT_CAT( NAME, _size )( const NAME * ); VT_API_FN_QUALIFIERS size_t VT_CAT( NAME, _bucket_count )( const NAME * ); VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _is_end )( VT_CAT( NAME, _itr ) ); VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _insert )( NAME *, KEY_TY #ifdef VAL_TY , VAL_TY #endif ); VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _get_or_insert )( NAME *, KEY_TY #ifdef VAL_TY , VAL_TY #endif ); VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _get )( NAME *table, KEY_TY key ); VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _erase )( NAME *, KEY_TY ); VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _next )( VT_CAT( NAME, _itr ) ); VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _reserve )( NAME *, size_t ); VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _shrink )( NAME * ); VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _first )( NAME * ); VT_API_FN_QUALIFIERS void VT_CAT( NAME, _clear )( NAME * ); VT_API_FN_QUALIFIERS void VT_CAT( NAME, _cleanup )( NAME * ); // Not an API function, but must be prototyped anyway because it is called by the inline NAME_erase_itr below. VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _erase_itr_raw ) ( NAME *, VT_CAT( NAME, _itr ) ); // Erases the key pointed to by itr and returns an iterator to the next key in the table. // This function must be inlined to ensure that the compiler optimizes away the NAME_fast_forward call if the returned // iterator is discarded. #ifdef __GNUC__ static inline __attribute__((always_inline)) #elif defined( _MSC_VER ) static __forceinline #else static inline #endif VT_CAT( NAME, _itr ) VT_CAT( NAME, _erase_itr )( NAME *table, VT_CAT( NAME, _itr ) itr ) { if( VT_CAT( NAME, _erase_itr_raw )( table, itr ) ) return VT_CAT( NAME, _next )( itr ); return itr; } #endif /*--------------------------------------------------------------------------------------------------------------------*/ /* Function implementations */ /*--------------------------------------------------------------------------------------------------------------------*/ #ifndef HEADER_MODE // Default settings. #ifndef MAX_LOAD #define MAX_LOAD 0.9 #endif #if !defined( MALLOC ) || !defined( FREE ) #include #endif #ifndef MALLOC_FN #ifdef CTX_TY #define MALLOC_FN vt_malloc_with_ctx #else #define MALLOC_FN vt_malloc #endif #endif #ifndef FREE_FN #ifdef CTX_TY #define FREE_FN vt_free_with_ctx #else #define FREE_FN vt_free #endif #endif #ifndef HASH_FN #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #ifdef _MSC_VER // In MSVC, the compound literal in the _Generic triggers a warning about unused local variables at /W4. #define HASH_FN \ _Pragma( "warning( push )" ) \ _Pragma( "warning( disable: 4189 )" ) \ _Generic( ( KEY_TY ){ 0 }, char *: vt_hash_string, const char*: vt_hash_string, default: vt_hash_integer ) \ _Pragma( "warning( pop )" ) #else #define HASH_FN _Generic( ( KEY_TY ){ 0 }, char *: vt_hash_string, const char*: vt_hash_string, default: vt_hash_integer ) #endif #else #error Hash function inference is only available in C11 and later. In C99, you need to define HASH_FN manually to \ vt_hash_integer, vt_hash_string, or your own custom function with the signature uint64_t ( KEY_TY ). #endif #endif #ifndef CMPR_FN #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #ifdef _MSC_VER #define CMPR_FN \ _Pragma( "warning( push )" ) \ _Pragma( "warning( disable: 4189 )" ) \ _Generic( ( KEY_TY ){ 0 }, char *: vt_cmpr_string, const char*: vt_cmpr_string, default: vt_cmpr_integer ) \ _Pragma( "warning( pop )" ) #else #define CMPR_FN _Generic( ( KEY_TY ){ 0 }, char *: vt_cmpr_string, const char*: vt_cmpr_string, default: vt_cmpr_integer ) #endif #else #error Comparison function inference is only available in C11 and later. In C99, you need to define CMPR_FN manually \ to vt_cmpr_integer, vt_cmpr_string, or your own custom function with the signature bool ( KEY_TY, KEY_TY ). #endif #endif VT_API_FN_QUALIFIERS void VT_CAT( NAME, _init )( NAME *table #ifdef CTX_TY , CTX_TY ctx #endif ) { table->key_count = 0; table->buckets_mask = 0x0000000000000000ull; table->buckets = NULL; table->metadata = (uint16_t *)&vt_empty_placeholder_metadatum; #ifdef CTX_TY table->ctx = ctx; #endif } // For efficiency, especially in the case of a small table, the buckets array and metadata share the same dynamic memory // allocation: // +-----------------------------+-----+----------------+--------+ // | Buckets | Pad | Metadata | Excess | // +-----------------------------+-----+----------------+--------+ // Any allocated metadata array requires four excess elements to ensure that iteration functions, which read four // metadata at a time, never read beyond the end of it. // This function returns the offset of the beginning of the metadata, i.e. the size of the buckets array plus the // (usually zero) padding. // It assumes that the bucket count is not zero. static inline size_t VT_CAT( NAME, _metadata_offset )( NAME *table ) { // Use sizeof, rather than alignof, for C99 compatibility. return ( ( ( table->buckets_mask + 1 ) * sizeof( VT_CAT( NAME, _bucket ) ) + sizeof( uint16_t ) - 1 ) / sizeof( uint16_t ) ) * sizeof( uint16_t ); } // Returns the total allocation size, including the buckets array, padding, metadata, and excess metadata. // As above, this function assumes that the bucket count is not zero. static inline size_t VT_CAT( NAME, _total_alloc_size )( NAME *table ) { return VT_CAT( NAME, _metadata_offset )( table ) + ( table->buckets_mask + 1 + 4 ) * sizeof( uint16_t ); } VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _init_clone )( NAME *table, NAME *source #ifdef CTX_TY , CTX_TY ctx #endif ) { table->key_count = source->key_count; table->buckets_mask = source->buckets_mask; #ifdef CTX_TY table->ctx = ctx; #endif if( !source->buckets_mask ) { table->metadata = (uint16_t *)&vt_empty_placeholder_metadatum; table->buckets = NULL; return true; } void *allocation = MALLOC_FN( VT_CAT( NAME, _total_alloc_size )( table ) #ifdef CTX_TY , &table->ctx #endif ); if( VT_UNLIKELY( !allocation ) ) return false; table->buckets = (VT_CAT( NAME, _bucket ) *)allocation; table->metadata = (uint16_t *)( (unsigned char *)allocation + VT_CAT( NAME, _metadata_offset )( table ) ); memcpy( allocation, source->buckets, VT_CAT( NAME, _total_alloc_size )( table ) ); return true; } VT_API_FN_QUALIFIERS size_t VT_CAT( NAME, _size )( const NAME *table ) { return table->key_count; } VT_API_FN_QUALIFIERS size_t VT_CAT( NAME, _bucket_count )( const NAME *table ) { // If the bucket count is zero, buckets_mask will be zero, not the bucket count minus one. // We account for this special case by adding (bool)buckets_mask rather than one. return table->buckets_mask + (bool)table->buckets_mask; } VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _is_end )( VT_CAT( NAME, _itr ) itr ) { return itr.metadatum == itr.metadata_end; } // Finds the earliest empty bucket in which a key belonging to home_bucket can be placed, assuming that home_bucket // is already occupied. // The reason to begin the search at home_bucket, rather than the end of the existing chain, is that keys deleted from // other chains might have freed up buckets that could fall in this chain before the final key. // Returns true if an empty bucket within the range of the displacement limit was found, in which case the final two // pointer arguments contain the index of the empty bucket and its quadratic displacement from home_bucket. static inline bool VT_CAT( NAME, _find_first_empty )( NAME *table, size_t home_bucket, size_t *empty, uint16_t *displacement ) { *displacement = 1; size_t linear_dispacement = 1; while( true ) { *empty = ( home_bucket + linear_dispacement ) & table->buckets_mask; if( table->metadata[ *empty ] == VT_EMPTY ) return true; if( VT_UNLIKELY( ++*displacement == VT_DISPLACEMENT_MASK ) ) return false; linear_dispacement += *displacement; } } // Finds the key in the chain beginning in home_bucket after which to link a new key with displacement_to_empty // quadratic displacement and returns the index of the bucket containing that key. // Although the new key could simply be linked to the end of the chain, keeping the chain ordered by displacement // theoretically improves cache locality during lookups. static inline size_t VT_CAT( NAME, _find_insert_location_in_chain )( NAME *table, size_t home_bucket, uint16_t displacement_to_empty ) { size_t candidate = home_bucket; while( true ) { uint16_t displacement = table->metadata[ candidate ] & VT_DISPLACEMENT_MASK; if( displacement > displacement_to_empty ) return candidate; candidate = ( home_bucket + vt_quadratic( displacement ) ) & table->buckets_mask; } } // Frees up a bucket occupied by a key not belonging there so that a new key belonging there can be placed there as the // beginning of a new chain. // This requires: // * Finding the previous key in the chain to which the occupying key belongs by rehashing it and then traversing the // chain. // * Disconnecting the key from the chain. // * Finding the appropriate empty bucket to which to move the key. // * Moving the key (and value) data to the empty bucket. // * Re-linking the key to the chain. // Returns true if the eviction succeeded, or false if no empty bucket to which to evict the occupying key could be // found within the displacement limit. static inline bool VT_CAT( NAME, _evict )( NAME *table, size_t bucket ) { // Find the previous key in chain. size_t home_bucket = HASH_FN( table->buckets[ bucket ].key ) & table->buckets_mask; size_t prev = home_bucket; while( true ) { size_t next = ( home_bucket + vt_quadratic( table->metadata[ prev ] & VT_DISPLACEMENT_MASK ) ) & table->buckets_mask; if( next == bucket ) break; prev = next; } // Disconnect the key from chain. table->metadata[ prev ] = ( table->metadata[ prev ] & ~VT_DISPLACEMENT_MASK ) | ( table->metadata[ bucket ] & VT_DISPLACEMENT_MASK ); // Find the empty bucket to which to move the key. size_t empty; uint16_t displacement; if( VT_UNLIKELY( !VT_CAT( NAME, _find_first_empty )( table, home_bucket, &empty, &displacement ) ) ) return false; // Find the key in the chain after which to link the moved key. prev = VT_CAT( NAME, _find_insert_location_in_chain )( table, home_bucket, displacement ); // Move the key (and value) data. table->buckets[ empty ] = table->buckets[ bucket ]; // Re-link the key to the chain from its new bucket. table->metadata[ empty ] = ( table->metadata[ bucket ] & VT_HASH_FRAG_MASK ) | ( table->metadata[ prev ] & VT_DISPLACEMENT_MASK ); table->metadata[ prev ] = ( table->metadata[ prev ] & ~VT_DISPLACEMENT_MASK ) | displacement; return true; } // Returns an end iterator, i.e. any iterator for which .metadatum == .metadata_end. // This function just cleans up the library code in functions that return an end iterator as a failure indicator. static inline VT_CAT( NAME, _itr ) VT_CAT( NAME, _end_itr )( void ) { VT_CAT( NAME, _itr ) itr = { NULL, NULL, NULL, 0 }; return itr; } // Inserts a key, optionally replacing the existing key if it already exists. // There are two main cases that must be handled: // * If the key's home bucket is empty or occupied by a key that does not belong there, then the key is inserted there, // evicting the occupying key if there is one. // * Otherwise, the chain of keys beginning at the home bucket is (if unique is false) traversed in search of a matching // key. // If none is found, then the new key is inserted at the earliest available bucket, per quadratic probing from the // home bucket, and then linked to the chain in a manner that maintains its quadratic order. // The unique argument tells the function whether to skip searching for the key before inserting it (on rehashing, this // step is unnecessary). // The replace argument tells the function whether to replace an existing key. // If replace is true, the function returns an iterator to the inserted key, or an end iterator if the key was not // inserted because of the maximum load factor or displacement limit constraints. // If replace is false, then the return value is as described above, except that if the key already exists, the function // returns an iterator to the existing key. static inline VT_CAT( NAME, _itr ) VT_CAT( NAME, _insert_raw )( NAME *table, KEY_TY key, #ifdef VAL_TY VAL_TY *val, #endif bool unique, bool replace ) { uint64_t hash = HASH_FN( key ); uint16_t hashfrag = vt_hashfrag( hash ); size_t home_bucket = hash & table->buckets_mask; // Case 1: The home bucket is empty or contains a key that doesn't belong there. // This case also implicitly handles the case of a zero bucket count, since home_bucket will be zero and metadata[ 0 ] // will be the empty placeholder. // In that scenario, the zero buckets_mask triggers the below load-factor check. if( !( table->metadata[ home_bucket ] & VT_IN_HOME_BUCKET_MASK ) ) { if( // Load-factor check. VT_UNLIKELY( table->key_count + 1 > VT_CAT( NAME, _bucket_count )( table ) * MAX_LOAD ) || // Vacate the home bucket if it contains a key. ( table->metadata[ home_bucket ] != VT_EMPTY && VT_UNLIKELY( !VT_CAT( NAME, _evict )( table, home_bucket ) ) ) ) return VT_CAT( NAME, _end_itr )(); table->buckets[ home_bucket ].key = key; #ifdef VAL_TY table->buckets[ home_bucket ].val = *val; #endif table->metadata[ home_bucket ] = hashfrag | VT_IN_HOME_BUCKET_MASK | VT_DISPLACEMENT_MASK; ++table->key_count; VT_CAT( NAME, _itr ) itr = { table->buckets + home_bucket, table->metadata + home_bucket, table->metadata + table->buckets_mask + 1, // Iteration stopper (i.e. the first of the four excess metadata). home_bucket }; return itr; } // Case 2: The home bucket contains the beginning of a chain. // Optionally, check the existing chain. if( !unique ) { size_t bucket = home_bucket; while( true ) { if( ( table->metadata[ bucket ] & VT_HASH_FRAG_MASK ) == hashfrag && VT_LIKELY( CMPR_FN( table->buckets[ bucket ].key, key ) ) ) { if( replace ) { #ifdef KEY_DTOR_FN KEY_DTOR_FN( table->buckets[ bucket ].key ); #endif table->buckets[ bucket ].key = key; #ifdef VAL_TY #ifdef VAL_DTOR_FN VAL_DTOR_FN( table->buckets[ bucket ].val ); #endif table->buckets[ bucket ].val = *val; #endif } VT_CAT( NAME, _itr ) itr = { table->buckets + bucket, table->metadata + bucket, table->metadata + table->buckets_mask + 1, home_bucket }; return itr; } uint16_t displacement = table->metadata[ bucket ] & VT_DISPLACEMENT_MASK; if( displacement == VT_DISPLACEMENT_MASK ) break; bucket = ( home_bucket + vt_quadratic( displacement ) ) & table->buckets_mask; } } size_t empty; uint16_t displacement; if( VT_UNLIKELY( // Load-factor check. table->key_count + 1 > VT_CAT( NAME, _bucket_count )( table ) * MAX_LOAD || // Find the earliest empty bucket, per quadratic probing. !VT_CAT( NAME, _find_first_empty )( table, home_bucket, &empty, &displacement ) ) ) return VT_CAT( NAME, _end_itr )(); // Insert the new key (and value) in the empty bucket and link it to the chain. size_t prev = VT_CAT( NAME, _find_insert_location_in_chain )( table, home_bucket, displacement ); table->buckets[ empty ].key = key; #ifdef VAL_TY table->buckets[ empty ].val = *val; #endif table->metadata[ empty ] = hashfrag | ( table->metadata[ prev ] & VT_DISPLACEMENT_MASK ); table->metadata[ prev ] = ( table->metadata[ prev ] & ~VT_DISPLACEMENT_MASK ) | displacement; ++table->key_count; VT_CAT( NAME, _itr ) itr = { table->buckets + empty, table->metadata + empty, table->metadata + table->buckets_mask + 1, home_bucket }; return itr; } // Resizes the bucket array. // This function assumes that bucket_count is a power of two and large enough to accommodate all keys without violating // the maximum load factor. // Returns false in the case of allocation failure. // As this function is called very rarely in _insert and _get_or_insert, ideally it should not be inlined into those // functions. // In testing, the no-inline approach showed a performance benefit when inserting existing keys (i.e. replacing). #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wattributes" // Silence warning about combining noinline with static inline. __attribute__((noinline)) static inline #elif defined( _MSC_VER ) __declspec(noinline) static inline #else static inline #endif bool VT_CAT( NAME, _rehash )( NAME *table, size_t bucket_count ) { // The attempt to resize the bucket array and rehash the keys must occur inside a loop that incrementally doubles the // target bucket count because a failure could theoretically occur at any load factor due to the displacement limit. while( true ) { NAME new_table = { 0, bucket_count - 1, NULL, NULL #ifdef CTX_TY , table->ctx #endif }; void *allocation = MALLOC_FN( VT_CAT( NAME, _total_alloc_size )( &new_table ) #ifdef CTX_TY , &new_table.ctx #endif ); if( VT_UNLIKELY( !allocation ) ) return false; new_table.buckets = (VT_CAT( NAME, _bucket ) *)allocation; new_table.metadata = (uint16_t *)( (unsigned char *)allocation + VT_CAT( NAME, _metadata_offset )( &new_table ) ); memset( new_table.metadata, 0x00, ( bucket_count + 4 ) * sizeof( uint16_t ) ); // Iteration stopper at the end of the actual metadata array (i.e. the first of the four excess metadata). new_table.metadata[ bucket_count ] = 0x01; for( size_t bucket = 0; bucket < VT_CAT( NAME, _bucket_count )( table ); ++bucket ) if( table->metadata[ bucket ] != VT_EMPTY ) { VT_CAT( NAME, _itr ) itr = VT_CAT( NAME, _insert_raw )( &new_table, table->buckets[ bucket ].key, #ifdef VAL_TY &table->buckets[ bucket ].val, #endif true, false ); if( VT_UNLIKELY( VT_CAT( NAME, _is_end )( itr ) ) ) break; } // If a key could not be reinserted due to the displacement limit, double the bucket count and retry. if( VT_UNLIKELY( new_table.key_count < table->key_count ) ) { FREE_FN( new_table.buckets, VT_CAT( NAME, _total_alloc_size )( &new_table ) #ifdef CTX_TY , &new_table.ctx #endif ); bucket_count *= 2; continue; } if( table->buckets_mask ) FREE_FN( table->buckets, VT_CAT( NAME, _total_alloc_size )( table ) #ifdef CTX_TY , &table->ctx #endif ); *table = new_table; return true; } } #ifdef __GNUC__ #pragma GCC diagnostic pop #endif // Inserts a key, replacing the existing key if it already exists. // This function wraps insert_raw in a loop that handles growing and rehashing the table if a new key cannot be inserted // because of the maximum load factor or displacement limit constraints. // Returns an iterator to the inserted key, or an end iterator in the case of allocation failure. VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _insert )( NAME *table, KEY_TY key #ifdef VAL_TY , VAL_TY val #endif ) { while( true ) { VT_CAT( NAME, _itr ) itr = VT_CAT( NAME, _insert_raw )( table, key, #ifdef VAL_TY &val, #endif false, true ); if( // Lookup succeeded, in which case itr points to the found key. VT_LIKELY( !VT_CAT( NAME, _is_end )( itr ) ) || // Lookup failed and rehash also fails, in which case itr is an end iterator. VT_UNLIKELY( !VT_CAT( NAME, _rehash )( table, table->buckets_mask ? VT_CAT( NAME, _bucket_count )( table ) * 2 : VT_MIN_NONZERO_BUCKET_COUNT ) ) ) return itr; } } // Same as NAME_insert, except that if the key already exists, no insertion occurs and the function returns an iterator // to the existing key. VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _get_or_insert )( NAME *table, KEY_TY key #ifdef VAL_TY , VAL_TY val #endif ) { while( true ) { VT_CAT( NAME, _itr ) itr = VT_CAT( NAME, _insert_raw )( table, key, #ifdef VAL_TY &val, #endif false, false ); if( // Lookup succeeded, in which case itr points to the found key. VT_LIKELY( !VT_CAT( NAME, _is_end )( itr ) ) || // Lookup failed and rehash also fails, in which case itr is an end iterator. VT_UNLIKELY( !VT_CAT( NAME, _rehash )( table, table->buckets_mask ? VT_CAT( NAME, _bucket_count )( table ) * 2 : VT_MIN_NONZERO_BUCKET_COUNT ) ) ) return itr; } } // Returns an iterator pointing to the specified key, or an end iterator if the key does not exist. VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _get )( NAME *table, KEY_TY key ) { uint64_t hash = HASH_FN( key ); size_t home_bucket = hash & table->buckets_mask; // If the home bucket is empty or contains a key that does not belong there, then our key does not exist. // This check also implicitly handles the case of a zero bucket count, since home_bucket will be zero and // metadata[ 0 ] will be the empty placeholder. if( !( table->metadata[ home_bucket ] & VT_IN_HOME_BUCKET_MASK ) ) return VT_CAT( NAME, _end_itr )(); // Traverse the chain of keys belonging to the home bucket. uint16_t hashfrag = vt_hashfrag( hash ); size_t bucket = home_bucket; while( true ) { if( ( table->metadata[ bucket ] & VT_HASH_FRAG_MASK ) == hashfrag && VT_LIKELY( CMPR_FN( table->buckets[ bucket ].key, key ) ) ) { VT_CAT( NAME, _itr ) itr = { table->buckets + bucket, table->metadata + bucket, table->metadata + table->buckets_mask + 1, home_bucket }; return itr; } uint16_t displacement = table->metadata[ bucket ] & VT_DISPLACEMENT_MASK; if( displacement == VT_DISPLACEMENT_MASK ) return VT_CAT( NAME, _end_itr )(); bucket = ( home_bucket + vt_quadratic( displacement ) ) & table->buckets_mask; } } // Erases the key pointed to by the specified iterator. // The erasure always occurs at the end of the chain to which the key belongs. // If the key to be erased is not the last in the chain, it is swapped with the last so that erasure occurs at the end. // This helps keep a chain's keys close to their home bucket for the sake of cache locality. // Returns true if, in the case of iteration from first to end, NAME_next should now be called on the iterator to find // the next key. // This return value is necessary because at the iterator location, the erasure could result in an empty bucket, a // bucket containing a moved key already visited during the iteration, or a bucket containing a moved key not yet // visited. VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _erase_itr_raw )( NAME *table, VT_CAT( NAME, _itr ) itr ) { --table->key_count; size_t itr_bucket = itr.metadatum - table->metadata; // For now, we only call the value's destructor because the key may need to be hashed below to determine the home // bucket. #ifdef VAL_DTOR_FN VAL_DTOR_FN( table->buckets[ itr_bucket ].val ); #endif // Case 1: The key is the only one in its chain, so just remove it. if( table->metadata[ itr_bucket ] & VT_IN_HOME_BUCKET_MASK && ( table->metadata[ itr_bucket ] & VT_DISPLACEMENT_MASK ) == VT_DISPLACEMENT_MASK ) { #ifdef KEY_DTOR_FN KEY_DTOR_FN( table->buckets[ itr_bucket ].key ); #endif table->metadata[ itr_bucket ] = VT_EMPTY; return true; } // Case 2 and 3 require that we know the key's home bucket, which the iterator may not have recorded. if( itr.home_bucket == SIZE_MAX ) { if( table->metadata[ itr_bucket ] & VT_IN_HOME_BUCKET_MASK ) itr.home_bucket = itr_bucket; else itr.home_bucket = HASH_FN( table->buckets[ itr_bucket ].key ) & table->buckets_mask; } // The key can now be safely destructed for cases 2 and 3. #ifdef KEY_DTOR_FN KEY_DTOR_FN( table->buckets[ itr_bucket ].key ); #endif // Case 2: The key is the last in a multi-key chain. // Traverse the chain from the beginning and find the penultimate key. // Then disconnect the key and erase. if( ( table->metadata[ itr_bucket ] & VT_DISPLACEMENT_MASK ) == VT_DISPLACEMENT_MASK ) { size_t bucket = itr.home_bucket; while( true ) { uint16_t displacement = table->metadata[ bucket ] & VT_DISPLACEMENT_MASK; size_t next = ( itr.home_bucket + vt_quadratic( displacement ) ) & table->buckets_mask; if( next == itr_bucket ) { table->metadata[ bucket ] |= VT_DISPLACEMENT_MASK; table->metadata[ itr_bucket ] = VT_EMPTY; return true; } bucket = next; } } // Case 3: The chain has multiple keys, and the key is not the last one. // Traverse the chain from the key to be erased and find the last and penultimate keys. // Disconnect the last key from the chain, and swap it with the key to erase. size_t bucket = itr_bucket; while( true ) { size_t prev = bucket; bucket = ( itr.home_bucket + vt_quadratic( table->metadata[ bucket ] & VT_DISPLACEMENT_MASK ) ) & table->buckets_mask; if( ( table->metadata[ bucket ] & VT_DISPLACEMENT_MASK ) == VT_DISPLACEMENT_MASK ) { table->buckets[ itr_bucket ] = table->buckets[ bucket ]; table->metadata[ itr_bucket ] = ( table->metadata[ itr_bucket ] & ~VT_HASH_FRAG_MASK ) | ( table->metadata[ bucket ] & VT_HASH_FRAG_MASK ); table->metadata[ prev ] |= VT_DISPLACEMENT_MASK; table->metadata[ bucket ] = VT_EMPTY; // Whether the iterator should be advanced depends on whether the key moved to the iterator bucket came from // before or after that bucket. // In the former case, the iteration would already have hit the moved key, so the iterator should still be // advanced. if( bucket > itr_bucket ) return false; return true; } } } // Erases the specified key, if it exists. // Returns true if a key was erased. VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _erase )( NAME *table, KEY_TY key ) { VT_CAT( NAME, _itr ) itr = VT_CAT( NAME, _get)( table, key ); if( VT_CAT( NAME, _is_end )( itr ) ) return false; VT_CAT( NAME, _erase_itr_raw )( table, itr ); return true; } // Finds the first occupied bucket at or after the bucket pointed to by itr. // This function scans four buckets at a time, ideally using intrinsics. static inline void VT_CAT( NAME, _fast_forward )( VT_CAT( NAME, _itr ) *itr ) { while( true ) { uint64_t metadata; memcpy( &metadata, itr->metadatum, sizeof( uint64_t ) ); if( metadata ) { int offset = vt_first_nonzero_uint16( metadata ); itr->data += offset; itr->metadatum += offset; itr->home_bucket = SIZE_MAX; return; } itr->data += 4; itr->metadatum += 4; } } VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _next )( VT_CAT( NAME, _itr ) itr ) { ++itr.data; ++itr.metadatum; VT_CAT( NAME, _fast_forward )( &itr ); return itr; } // Returns the minimum bucket count required to accommodate a certain number of keys, which is governed by the maximum // load factor. static inline size_t VT_CAT( NAME, _min_bucket_count_for_size )( size_t size ) { if( size == 0 ) return 0; // Round up to a power of two. size_t bucket_count = VT_MIN_NONZERO_BUCKET_COUNT; while( size > bucket_count * MAX_LOAD ) bucket_count *= 2; return bucket_count; } VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _reserve )( NAME *table, size_t size ) { size_t bucket_count = VT_CAT( NAME, _min_bucket_count_for_size )( size ); if( bucket_count <= VT_CAT( NAME, _bucket_count )( table ) ) return true; return VT_CAT( NAME, _rehash )( table, bucket_count ); } VT_API_FN_QUALIFIERS bool VT_CAT( NAME, _shrink )( NAME *table ) { size_t bucket_count = VT_CAT( NAME, _min_bucket_count_for_size )( table->key_count ); if( bucket_count == VT_CAT( NAME, _bucket_count )( table ) ) // Shrink unnecessary. return true; if( bucket_count == 0 ) { FREE_FN( table->buckets, VT_CAT( NAME, _total_alloc_size )( table ) #ifdef CTX_TY , &table->ctx #endif ); table->buckets_mask = 0x0000000000000000ull; table->metadata = (uint16_t *)&vt_empty_placeholder_metadatum; return true; } return VT_CAT( NAME, _rehash )( table, bucket_count ); } VT_API_FN_QUALIFIERS VT_CAT( NAME, _itr ) VT_CAT( NAME, _first )( NAME *table ) { if( !table->key_count ) return VT_CAT( NAME, _end_itr )(); VT_CAT( NAME, _itr ) itr = { table->buckets, table->metadata, table->metadata + table->buckets_mask + 1, SIZE_MAX }; VT_CAT( NAME, _fast_forward )( &itr ); return itr; } VT_API_FN_QUALIFIERS void VT_CAT( NAME, _clear )( NAME *table ) { if( !table->key_count ) return; for( size_t i = 0; i < VT_CAT( NAME, _bucket_count )( table ); ++i ) { if( table->metadata[ i ] != VT_EMPTY ) { #ifdef KEY_DTOR_FN KEY_DTOR_FN( table->buckets[ i ].key ); #endif #ifdef VAL_DTOR_FN VAL_DTOR_FN( table->buckets[ i ].val ); #endif } table->metadata[ i ] = VT_EMPTY; } table->key_count = 0; } VT_API_FN_QUALIFIERS void VT_CAT( NAME, _cleanup )( NAME *table ) { if( !table->buckets_mask ) return; #if defined( KEY_DTOR_FN ) || defined( VAL_DTOR_FN ) VT_CAT( NAME, _clear )( table ); #endif FREE_FN( table->buckets, VT_CAT( NAME, _total_alloc_size )( table ) #ifdef CTX_TY , &table->ctx #endif ); VT_CAT( NAME, _init )( table #ifdef CTX_TY , table->ctx #endif ); } #endif /*--------------------------------------------------------------------------------------------------------------------*/ /* Wrapper types and functions for the C11 generic API */ /*--------------------------------------------------------------------------------------------------------------------*/ #if defined(__STDC_VERSION__) && \ __STDC_VERSION__ >= 201112L && \ !defined( IMPLEMENTATION_MODE ) && \ !defined( VT_NO_C11_GENERIC_API ) \ typedef NAME VT_CAT( vt_table_, VT_TEMPLATE_COUNT ); typedef VT_CAT( NAME, _itr ) VT_CAT( vt_table_itr_, VT_TEMPLATE_COUNT ); static inline void VT_CAT( vt_init_, VT_TEMPLATE_COUNT )( NAME *table #ifdef CTX_TY , CTX_TY ctx #endif ) { VT_CAT( NAME, _init )( table #ifdef CTX_TY , ctx #endif ); } static inline bool VT_CAT( vt_init_clone_, VT_TEMPLATE_COUNT )( NAME *table, NAME* source #ifdef CTX_TY , CTX_TY ctx #endif ) { return VT_CAT( NAME, _init_clone )( table, source #ifdef CTX_TY , ctx #endif ); } static inline size_t VT_CAT( vt_size_, VT_TEMPLATE_COUNT )( const NAME *table ) { return VT_CAT( NAME, _size )( table ); } static inline size_t VT_CAT( vt_bucket_count_, VT_TEMPLATE_COUNT )( const NAME *table ) { return VT_CAT( NAME, _bucket_count )( table ); } static inline bool VT_CAT( vt_is_end_, VT_TEMPLATE_COUNT )( VT_CAT( NAME, _itr ) itr ) { return VT_CAT( NAME, _is_end )( itr ); } static inline VT_CAT( NAME, _itr ) VT_CAT( vt_insert_, VT_TEMPLATE_COUNT )( NAME *table, KEY_TY key #ifdef VAL_TY , VAL_TY val #endif ) { return VT_CAT( NAME, _insert )( table, key #ifdef VAL_TY , val #endif ); } static inline VT_CAT( NAME, _itr ) VT_CAT( vt_get_or_insert_, VT_TEMPLATE_COUNT )( NAME *table, KEY_TY key #ifdef VAL_TY , VAL_TY val #endif ) { return VT_CAT( NAME, _get_or_insert )( table, key #ifdef VAL_TY , val #endif ); } static inline VT_CAT( NAME, _itr ) VT_CAT( vt_get_, VT_TEMPLATE_COUNT )( NAME *table, KEY_TY key ) { return VT_CAT( NAME, _get )( table, key ); } static inline bool VT_CAT( vt_erase_, VT_TEMPLATE_COUNT )( NAME *table, KEY_TY key ) { return VT_CAT( NAME, _erase )( table, key ); } static inline VT_CAT( NAME, _itr ) VT_CAT( vt_next_, VT_TEMPLATE_COUNT )( VT_CAT( NAME, _itr ) itr ) { return VT_CAT( NAME, _next )( itr ); } static inline VT_CAT( NAME, _itr ) VT_CAT( vt_erase_itr_, VT_TEMPLATE_COUNT )( NAME *table, VT_CAT( NAME, _itr ) itr ) { return VT_CAT( NAME, _erase_itr )( table, itr ); } static inline bool VT_CAT( vt_reserve_, VT_TEMPLATE_COUNT )( NAME *table, size_t bucket_count ) { return VT_CAT( NAME, _reserve )( table, bucket_count ); } static inline bool VT_CAT( vt_shrink_, VT_TEMPLATE_COUNT )( NAME *table ) { return VT_CAT( NAME, _shrink )( table ); } static inline VT_CAT( NAME, _itr ) VT_CAT( vt_first_, VT_TEMPLATE_COUNT )( NAME *table ) { return VT_CAT( NAME, _first )( table ); } static inline void VT_CAT( vt_clear_, VT_TEMPLATE_COUNT )( NAME *table ) { VT_CAT( NAME, _clear )( table ); } static inline void VT_CAT( vt_cleanup_, VT_TEMPLATE_COUNT )( NAME *table ) { VT_CAT( NAME, _cleanup )( table ); } // Increment the template counter. #if VT_TEMPLATE_COUNT_D1 == 0 #undef VT_TEMPLATE_COUNT_D1 #define VT_TEMPLATE_COUNT_D1 1 #elif VT_TEMPLATE_COUNT_D1 == 1 #undef VT_TEMPLATE_COUNT_D1 #define VT_TEMPLATE_COUNT_D1 2 #elif VT_TEMPLATE_COUNT_D1 == 2 #undef VT_TEMPLATE_COUNT_D1 #define VT_TEMPLATE_COUNT_D1 3 #elif VT_TEMPLATE_COUNT_D1 == 3 #undef VT_TEMPLATE_COUNT_D1 #define VT_TEMPLATE_COUNT_D1 4 #elif VT_TEMPLATE_COUNT_D1 == 4 #undef VT_TEMPLATE_COUNT_D1 #define VT_TEMPLATE_COUNT_D1 5 #elif VT_TEMPLATE_COUNT_D1 == 5 #undef VT_TEMPLATE_COUNT_D1 #define VT_TEMPLATE_COUNT_D1 6 #elif VT_TEMPLATE_COUNT_D1 == 6 #undef VT_TEMPLATE_COUNT_D1 #define VT_TEMPLATE_COUNT_D1 7 #elif VT_TEMPLATE_COUNT_D1 == 7 #undef VT_TEMPLATE_COUNT_D1 #define VT_TEMPLATE_COUNT_D1 0 #if VT_TEMPLATE_COUNT_D2 == 0 #undef VT_TEMPLATE_COUNT_D2 #define VT_TEMPLATE_COUNT_D2 1 #elif VT_TEMPLATE_COUNT_D2 == 1 #undef VT_TEMPLATE_COUNT_D2 #define VT_TEMPLATE_COUNT_D2 2 #elif VT_TEMPLATE_COUNT_D2 == 2 #undef VT_TEMPLATE_COUNT_D2 #define VT_TEMPLATE_COUNT_D2 3 #elif VT_TEMPLATE_COUNT_D2 == 3 #undef VT_TEMPLATE_COUNT_D2 #define VT_TEMPLATE_COUNT_D2 4 #elif VT_TEMPLATE_COUNT_D2 == 4 #undef VT_TEMPLATE_COUNT_D2 #define VT_TEMPLATE_COUNT_D2 5 #elif VT_TEMPLATE_COUNT_D2 == 5 #undef VT_TEMPLATE_COUNT_D2 #define VT_TEMPLATE_COUNT_D2 6 #elif VT_TEMPLATE_COUNT_D2 == 6 #undef VT_TEMPLATE_COUNT_D2 #define VT_TEMPLATE_COUNT_D2 7 #elif VT_TEMPLATE_COUNT_D2 == 7 #undef VT_TEMPLATE_COUNT_D2 #define VT_TEMPLATE_COUNT_D2 0 #if VT_TEMPLATE_COUNT_D3 == 0 #undef VT_TEMPLATE_COUNT_D3 #define VT_TEMPLATE_COUNT_D3 1 #elif VT_TEMPLATE_COUNT_D3 == 1 #undef VT_TEMPLATE_COUNT_D3 #define VT_TEMPLATE_COUNT_D3 2 #elif VT_TEMPLATE_COUNT_D3 == 2 #undef VT_TEMPLATE_COUNT_D3 #define VT_TEMPLATE_COUNT_D3 3 #elif VT_TEMPLATE_COUNT_D3 == 3 #undef VT_TEMPLATE_COUNT_D3 #define VT_TEMPLATE_COUNT_D3 4 #elif VT_TEMPLATE_COUNT_D3 == 4 #undef VT_TEMPLATE_COUNT_D3 #define VT_TEMPLATE_COUNT_D3 5 #elif VT_TEMPLATE_COUNT_D3 == 5 #undef VT_TEMPLATE_COUNT_D3 #define VT_TEMPLATE_COUNT_D3 6 #elif VT_TEMPLATE_COUNT_D3 == 6 #undef VT_TEMPLATE_COUNT_D3 #define VT_TEMPLATE_COUNT_D3 7 #elif VT_TEMPLATE_COUNT_D3 == 7 #error Sorry, the number of template instances is limited to 511. Define VT_NO_C11_GENERIC_API globally and use the \ C99 prefixed function API to circumvent this restriction. #endif #endif #endif #endif #undef NAME #undef KEY_TY #undef VAL_TY #undef HASH_FN #undef CMPR_FN #undef MAX_LOAD #undef KEY_DTOR_FN #undef VAL_DTOR_FN #undef CTX_TY #undef MALLOC_FN #undef FREE_FN #undef HEADER_MODE #undef IMPLEMENTATION_MODE #undef VT_API_FN_QUALIFIERS kitty-0.41.1/Brewfile0000664000175000017510000000016414773370543013772 0ustar nileshnileshbrew "zlib" brew "xxhash" brew "simde" brew "python" brew "imagemagick" brew "harfbuzz" brew "sphinx-doc" brew "go" kitty-0.41.1/CHANGELOG.rst0000664000175000017510000000005714773370543014332 0ustar nileshnileshSee https://sw.kovidgoyal.net/kitty/changelog/ kitty-0.41.1/CONTRIBUTING.md0000664000175000017510000000243514773370543014544 0ustar nileshnilesh### Reporting bugs Please first search existing bug reports (especially closed ones) for a report that matches your issue. When reporting a bug, provide full details of your environment, that means, at a minimum, kitty version, OS and OS version, kitty config (ideally a minimal config to reproduce the issue with). Note that bugs and feature requests are often closed quickly as they are either fixed or deemed wontfix/invalid. In my experience, this is the only scalable way to manage a bug tracker. Feel free to continue to post to a closed bug report if you would like to discuss the issue further. Being closed does not mean you will not get any more responses. ### Contributing code Install [the dependencies](https://sw.kovidgoyal.net/kitty/build/#dependencies) using your favorite package manager. Build and run kitty [from source](https://sw.kovidgoyal.net/kitty/build/#install-and-run-from-source). Make a fork, submit your Pull Request. If it's a large/controversial change, open an issue beforehand to discuss it, so that you don't waste your time making a pull request that gets rejected. If the code you are submitting is reasonably easily testable, please contribute tests as well (see the `kitty_tests/` sub-directory for existing tests, which can be run with `./test.py`). That's it. kitty-0.41.1/INSTALL.md0000664000175000017510000000017614773370543013743 0ustar nileshnilesh[To build from source](https://sw.kovidgoyal.net/kitty/build/) [Pre-built binaries](https://sw.kovidgoyal.net/kitty/binary/) kitty-0.41.1/LICENSE0000664000175000017510000010451514773370543013322 0ustar nileshnilesh GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . kitty-0.41.1/Makefile0000664000175000017510000000202614773370543013747 0ustar nileshnileshifdef V VVAL=--verbose endif ifdef VERBOSE VVAL=--verbose endif ifdef FAIL_WARN export FAIL_WARN endif all: python3 setup.py $(VVAL) test: python3 setup.py $(VVAL) test clean: python3 setup.py $(VVAL) clean # A debug build debug: python3 setup.py build $(VVAL) --debug debug-event-loop: python3 setup.py build $(VVAL) --debug --extra-logging=event-loop # Build with the ASAN and UBSAN sanitizers asan: python3 setup.py build $(VVAL) --debug --sanitize profile: python3 setup.py build $(VVAL) --profile app: python3 setup.py kitty.app $(VVAL) linux-package: FORCE rm -rf linux-package python3 setup.py linux-package FORCE: man: $(MAKE) -C docs man html: $(MAKE) -C docs html dirhtml: $(MAKE) -C docs dirhtml linkcheck: $(MAKE) -C docs linkcheck website: ./publish.py --only website docs: man html develop-docs: $(MAKE) -C docs develop-docs prepare-for-cross-compile: clean all python3 setup.py $(VVAL) clean --clean-for-cross-compile cross-compile: python3 setup.py linux-package --skip-code-generation kitty-0.41.1/README.asciidoc0000664000175000017510000000136714773370543014753 0ustar nileshnilesh= kitty - the fast, feature-rich, cross-platform, GPU based terminal See https://sw.kovidgoyal.net/kitty/[the kitty website]. image:https://github.com/kovidgoyal/kitty/workflows/CI/badge.svg["Build status", link="https://github.com/kovidgoyal/kitty/actions?query=workflow%3ACI"] https://sw.kovidgoyal.net/kitty/faq/[Frequently Asked Questions] To ask other questions about kitty usage, use either the https://github.com/kovidgoyal/kitty/discussions/[discussions on GitHub] or the https://www.reddit.com/r/KittyTerminal[Reddit community] Packaging status in various repositories: image:https://repology.org/badge/vertical-allrepos/kitty-terminal.svg?columns=3&header=kitty["Packaging status", link="https://repology.org/project/kitty-terminal/versions"] kitty-0.41.1/SECURITY.md0000664000175000017510000000056714773370543014110 0ustar nileshnilesh# Security Policy ## Supported Versions There are no security specific releases of kitty. Security bugs are fixed and released just like all other bugs. ## Reporting a vulnerability Preferably send an email to kovid at kovidgoyal.net or open an issue in the GitHub repository, though the latter means you are disclosing the vulnerability publicly before it can be fixed. kitty-0.41.1/__main__.py0000664000175000017510000000025714773370543014405 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2015, Kovid Goyal if __name__ == '__main__': from kitty.entry_points import main main() kitty-0.41.1/benchmark.py0000775000175000017510000000515014773370543014617 0ustar nileshnilesh#!./kitty/launcher/kitty +launch # License: GPL v3 Copyright: 2016, Kovid Goyal import fcntl import io import os import select import signal import struct import sys import termios import time from pty import CHILD, fork from kitty.constants import kitten_exe from kitty.fast_data_types import Screen, safe_pipe from kitty.utils import read_screen_size def run_parsing_benchmark(cell_width: int = 10, cell_height: int = 20, scrollback: int = 20000) -> None: isatty = sys.stdout.isatty() if isatty: sz = read_screen_size() columns, rows = sz.cols, sz.rows else: columns, rows = 80, 25 child_pid, master_fd = fork() is_child = child_pid == CHILD argv = [kitten_exe(), '__benchmark__', '--with-scrollback'] if is_child: while read_screen_size().width != columns * cell_width: time.sleep(0.01) signal.pthread_sigmask(signal.SIG_SETMASK, ()) os.execvp(argv[0], argv) # os.set_blocking(master_fd, False) x_pixels = columns * cell_width y_pixels = rows * cell_height s = struct.pack('HHHH', rows, columns, x_pixels, y_pixels) fcntl.ioctl(master_fd, termios.TIOCSWINSZ, s) write_buf = b'' r_pipe, w_pipe = safe_pipe(True) class ToChild: def write(self, x: bytes | str) -> None: nonlocal write_buf if isinstance(x, str): x = x.encode() write_buf += x os.write(w_pipe, b'1') screen = Screen(None, rows, columns, scrollback, cell_width, cell_height, 0, ToChild()) def parse_bytes(data: bytes) -> None: data = memoryview(data) while data: dest = screen.test_create_write_buffer() s = screen.test_commit_write_buffer(data, dest) data = data[s:] screen.test_parse_written_data() while True: rd, wd, _ = select.select([master_fd, r_pipe], [master_fd] if write_buf else [], []) if r_pipe in rd: os.read(r_pipe, 256) if master_fd in rd: try: data = os.read(master_fd, io.DEFAULT_BUFFER_SIZE) except OSError: data = b'' if not data: break parse_bytes(data) if master_fd in wd: n = os.write(master_fd, write_buf) write_buf = write_buf[n:] if isatty: lines: list[str] = [] screen.linebuf.as_ansi(lines.append) sys.stdout.write(''.join(lines)) else: sys.stdout.write(str(screen.linebuf)) def main() -> None: run_parsing_benchmark() if __name__ == '__main__': main() kitty-0.41.1/build-terminfo0000775000175000017510000000371714773370543015165 0ustar nileshnilesh#!/usr/bin/env python # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2019, Kovid Goyal import glob import os import shutil import subprocess import sys import tempfile def compile_terminfo(base): with tempfile.TemporaryDirectory() as tdir: proc = subprocess.run(['tic', '-x', f'-o{tdir}', 'terminfo/kitty.terminfo'], capture_output=True) if proc.returncode != 0: sys.stderr.buffer.write(proc.stderr) raise SystemExit(proc.returncode) tfiles = glob.glob(os.path.join(tdir, '*', 'xterm-kitty')) if not tfiles: raise SystemExit('tic failed to output the compiled kitty terminfo file') tfile = tfiles[0] directory, xterm_kitty = os.path.split(tfile) _, directory = os.path.split(directory) odir = os.path.join(base, directory) os.makedirs(odir, exist_ok=True) ofile = os.path.join(odir, xterm_kitty) shutil.move(tfile, ofile) return ofile def generate_terminfo(): base = os.path.dirname(os.path.abspath(__file__)) os.chdir(base) sys.path.insert(0, base) from kitty.terminfo import generate_terminfo with open('terminfo/kitty.terminfo', 'w') as f: f.write(generate_terminfo()) proc = subprocess.run(['tic', '-CrT0', 'terminfo/kitty.terminfo'], capture_output=True) if proc.returncode != 0: sys.stderr.buffer.write(proc.stderr) raise SystemExit(proc.returncode) tcap = proc.stdout.decode('utf-8').splitlines()[-1] with open('terminfo/kitty.termcap', 'w') as f: f.write(tcap) dbfile = compile_terminfo(os.path.join(base, 'terminfo')) with open(dbfile, 'rb') as f: data = f.read() with open('kitty/terminfo.h', 'w') as f: print(f'static const uint8_t terminfo_data[{len(data)}] = ''{', file=f) for b in data: print(b, end=', ', file=f) print('};', file=f) if __name__ == '__main__': generate_terminfo() kitty-0.41.1/bypy/0000775000175000017510000000000014773370543013272 5ustar nileshnileshkitty-0.41.1/bypy/devenv.go0000664000175000017510000002666614773370543015130 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package main import ( "bufio" "bytes" "errors" "flag" "fmt" "io" "io/fs" "net/http" "os" "os/exec" "path/filepath" "regexp" "runtime" "strings" ) const ( folder = "dependencies" fonts_folder = "fonts" macos_prefix = "/Users/Shared/kitty-build/sw/sw" macos_python = "python/Python.framework/Versions/Current/bin/python3" macos_python_framework = "python/Python.framework/Versions/Current/Python" macos_python_framework_exe = "python/Python.framework/Versions/Current/Resources/Python.app/Contents/MacOS/Python" NERD_URL = "https://github.com/ryanoasis/nerd-fonts/releases/latest/download/NerdFontsSymbolsOnly.tar.xz" ) func root_dir() string { f, e := filepath.Abs(filepath.Join(folder, runtime.GOOS+"-"+runtime.GOARCH)) if e != nil { exit(e) } return f } func fonts_dir() string { f, e := filepath.Abs(fonts_folder) if e != nil { exit(e) } return f } var _ = fmt.Print func exit(x any) { switch v := x.(type) { case error: if v == nil { os.Exit(0) } var ee *exec.ExitError if errors.As(v, &ee) { os.Exit(ee.ExitCode()) } case string: if v == "" { os.Exit(0) } case int: os.Exit(v) } fmt.Fprintf(os.Stderr, "\x1b[31mError\x1b[m: %s\n", x) os.Exit(1) } // download deps {{{ type dependency struct { path string basename string is_id bool } func lines(exe string, cmd ...string) []string { c := exec.Command(exe, cmd...) c.Stderr = os.Stderr out, err := c.Output() if err != nil { exit(fmt.Errorf("Failed to run '%s' with error: %w", strings.Join(append([]string{exe}, cmd...), " "), err)) } ans := []string{} for s := bufio.NewScanner(bytes.NewReader(out)); s.Scan(); { ans = append(ans, s.Text()) } return ans } func get_dependencies(path string) (ans []dependency) { a := lines("otool", "-D", path) install_name := strings.TrimSpace(a[len(a)-1]) for _, line := range lines("otool", "-L", path) { line = strings.TrimSpace(line) if strings.Contains(line, "compatibility") && !strings.HasSuffix(line, ":") { idx := strings.IndexByte(line, '(') dep := strings.TrimSpace(line[:idx]) ans = append(ans, dependency{path: dep, is_id: dep == install_name}) } } return } func get_local_dependencies(path string) (ans []dependency) { for _, dep := range get_dependencies(path) { for _, y := range []string{filepath.Join(macos_prefix, "lib") + "/", filepath.Join(macos_prefix, "python", "Python.framework") + "/", "@rpath/"} { if strings.HasPrefix(dep.path, y) { if y == "@rpath/" { dep.basename = "lib/" + dep.path[len(y):] } else { y = macos_prefix + "/" dep.basename = dep.path[len(y):] } ans = append(ans, dep) break } } } return } func change_dep(path string, dep dependency) { cmd := []string{} fid := filepath.Join(root_dir(), dep.basename) if dep.is_id { cmd = append(cmd, "-id", fid) } else { cmd = append(cmd, "-change", dep.path, fid) } cmd = append(cmd, path) c := exec.Command("install_name_tool", cmd...) c.Stdout = os.Stdout c.Stderr = os.Stderr if err := c.Run(); err != nil { exit(fmt.Errorf("Failed to run command '%s' with error: %w", strings.Join(c.Args, " "), err)) } } func fix_dependencies_in_lib(path string) { path, err := filepath.EvalSymlinks(path) if err != nil { exit(err) } if s, err := os.Stat(path); err != nil { exit(err) } else if err := os.Chmod(path, s.Mode().Perm()|0o200); err != nil { exit(err) } for _, dep := range get_local_dependencies(path) { change_dep(path, dep) } if ldeps := get_local_dependencies(path); len(ldeps) > 0 { exit(fmt.Errorf("Failed to fix local dependencies in: %s", path)) } } func cached_download(url string) string { fname := filepath.Base(url) fmt.Println("Downloading", fname) req, err := http.NewRequest("GET", url, nil) if err != nil { exit(err) } etag_file := filepath.Join(folder, fname+".etag") if etag, err := os.ReadFile(etag_file); err == nil { if _, err := os.Stat(filepath.Join(folder, fname)); err == nil { req.Header.Add("If-None-Match", string(etag)) } } client := &http.Client{} resp, err := client.Do(req) if err != nil { exit(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { if resp.StatusCode == http.StatusNotModified { return filepath.Join(folder, fname) } exit(fmt.Errorf("The server responded with the HTTP error: %s", resp.Status)) } f, err := os.Create(filepath.Join(folder, fname)) if err != nil { exit(err) } defer f.Close() if _, err := io.Copy(f, resp.Body); err != nil { exit(fmt.Errorf("Failed to download file with error: %w", err)) } if etag := resp.Header.Get("ETag"); etag != "" { if err := os.WriteFile(etag_file, []byte(etag), 0o644); err != nil { exit(err) } } return f.Name() } func relocate_pkgconfig(path, old_prefix, new_prefix string) error { raw, err := os.ReadFile(path) if err != nil { return err } nraw := bytes.ReplaceAll(raw, []byte(old_prefix), []byte(new_prefix)) return os.WriteFile(path, nraw, 0o644) } func chdir_to_base() { _, filename, _, _ := runtime.Caller(0) base_dir := filepath.Dir(filepath.Dir(filename)) if err := os.Chdir(base_dir); err != nil { exit(err) } } func dependencies_for_docs() { fmt.Println("Downloading get-pip.py") rq, err := http.Get("https://bootstrap.pypa.io/get-pip.py") if err != nil { exit(err) } defer rq.Body.Close() if rq.StatusCode != http.StatusOK { exit(fmt.Errorf("Server responded with HTTP error: %s", rq.Status)) } gp, err := os.Create(filepath.Join(folder, "get-pip.py")) if err != nil { exit(err) } defer gp.Close() if _, err = io.Copy(gp, rq.Body); err != nil { exit(err) } python := setup_to_run_python() run := func(exe string, args ...string) { c := exec.Command(exe, args...) c.Stdout = os.Stdout c.Stderr = os.Stderr if err := c.Run(); err != nil { exit(err) } } run(python, gp.Name()) run(python, "-m", "pip", "install", "-r", "docs/requirements.txt") } func dependencies(args []string) { chdir_to_base() nf := flag.NewFlagSet("deps", flag.ExitOnError) docsptr := nf.Bool("for-docs", false, "download the dependencies needed to build the documentation") if err := nf.Parse(args); err != nil { exit(err) } if *docsptr { dependencies_for_docs() fmt.Println("Dependencies needed to generate documentation have been installed. Build docs with ./dev.sh docs") exit(0) } data, err := os.ReadFile(".github/workflows/ci.py") if err != nil { exit(err) } pat := regexp.MustCompile("BUNDLE_URL = '(.+?)'") prefix := "/sw/sw" var url string if m := pat.FindStringSubmatch(string(data)); len(m) < 2 { exit("Failed to find BUNDLE_URL in ci.py") } else { url = m[1] } var which string switch runtime.GOOS { case "darwin": prefix = macos_prefix which = "macos" case "linux": which = "linux" if runtime.GOARCH != "amd64" { exit("Pre-built dependencies are only available for the amd64 CPU architecture") } } if which == "" { exit("Prebuilt dependencies are only available for Linux and macOS") } url = strings.Replace(url, "{}", which, 1) if err := os.RemoveAll(root_dir()); err != nil { exit(err) } if err := os.MkdirAll(folder, 0o755); err != nil { exit(err) } tarfile, _ := filepath.Abs(cached_download(url)) root := root_dir() if err := os.MkdirAll(root, 0o755); err != nil { exit(err) } cmd := exec.Command("tar", "xf", tarfile) cmd.Dir = root cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err = cmd.Run(); err != nil { exit(err) } if runtime.GOOS == "darwin" { fix_dependencies_in_lib(filepath.Join(root, macos_python)) fix_dependencies_in_lib(filepath.Join(root, macos_python_framework)) fix_dependencies_in_lib(filepath.Join(root, macos_python_framework_exe)) } if err = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.Type().IsRegular() { name := d.Name() ext := filepath.Ext(name) if ext == ".pc" || (ext == ".py" && strings.HasPrefix(name, "_sysconfigdata_")) { err = relocate_pkgconfig(path, prefix, root) } // remove libfontconfig so that we use the system one because // different distros stupidly use different fontconfig configuration dirs if strings.HasPrefix(name, "libfontconfig.so") { os.Remove(path) } if runtime.GOOS == "darwin" { if ext == ".so" || ext == ".dylib" { fix_dependencies_in_lib(path) } } } return err }); err != nil { exit(err) } tarfile, _ = filepath.Abs(cached_download(NERD_URL)) root = fonts_dir() if err := os.MkdirAll(root, 0o755); err != nil { exit(err) } cmd = exec.Command("tar", "xf", tarfile, "SymbolsNerdFontMono-Regular.ttf") cmd.Dir = root cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err = cmd.Run(); err != nil { exit(err) } fmt.Println(`Dependencies downloaded. Now build kitty with: ./dev.sh build`) } // }}} func prepend(env_var, path string) { val := os.Getenv(env_var) if val != "" { val = string(filepath.ListSeparator) + val } os.Setenv(env_var, path+val) } func setup_to_run_python() (python string) { root := root_dir() for _, x := range os.Environ() { if strings.HasPrefix(x, "PYTHON") { a, _, _ := strings.Cut(x, "=") os.Unsetenv(a) } } switch runtime.GOOS { case "linux": prepend("LD_LIBRARY_PATH", filepath.Join(root, "lib")) os.Setenv("PYTHONHOME", root) python = filepath.Join(root, "bin", "python") case `darwin`: python = filepath.Join(root, macos_python) default: exit("Building is only supported on Linux and macOS") } return } func build(args []string) { chdir_to_base() if _, err := os.Stat(folder); err != nil { dependencies(nil) } root := root_dir() os.Setenv("DEVELOP_ROOT", root) prepend("PKG_CONFIG_PATH", filepath.Join(root, "lib", "pkgconfig")) if runtime.GOOS == "darwin" { os.Setenv("PKGCONFIG_EXE", filepath.Join(root, "bin", "pkg-config")) } python := setup_to_run_python() args = append([]string{"setup.py", "develop"}, args...) cmd := exec.Command(python, args...) cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr if err := cmd.Run(); err != nil { fmt.Fprintln(os.Stderr, "The following build command failed:", python, strings.Join(args, " ")) exit(err) } fmt.Println("Build successful. Run kitty as: kitty/launcher/kitty") } func docs(args []string) { setup_to_run_python() nf := flag.NewFlagSet("deps", flag.ExitOnError) livereload := nf.Bool("live-reload", false, "build the docs and make them available via s local server with live reloading for ease of development") failwarn := nf.Bool("fail-warn", false, "make warnings fatal when building the docs") if err := nf.Parse(args); err != nil { exit(err) } exe := filepath.Join(root_dir(), "bin", "sphinx-build") aexe := filepath.Join(root_dir(), "bin", "sphinx-autobuild") target := "docs" if *livereload { target = "develop-docs" } cmd := []string{target, "SPHINXBUILD=" + exe, "SPHINXAUTOBUILD=" + aexe} if *failwarn { cmd = append(cmd, "FAILWARN=1") } c := exec.Command("make", cmd...) c.Stdout = os.Stdout c.Stderr = os.Stderr err := c.Run() if err != nil { exit(err) } fmt.Println("docs successfully built") } func main() { if len(os.Args) < 2 { exit(`Expected "deps" or "build" subcommands`) } switch os.Args[1] { case "deps": dependencies(os.Args[2:]) case "build": build(os.Args[2:]) case "docs": docs(os.Args[2:]) case "-h", "--help": fmt.Fprintln(os.Stderr, "Usage: ./dev.sh [build|deps|docs] [options...]") default: exit(`Expected "deps" or "build" subcommands`) } } kitty-0.41.1/bypy/init_env.py0000664000175000017510000001070314773370543015460 0ustar nileshnilesh#!/usr/bin/env python # vim:fileencoding=utf-8 # License: GPLv3 Copyright: 2020, Kovid Goyal import os import re import shlex import shutil import subprocess import sys import tempfile from contextlib import suppress from bypy.constants import LIBDIR, PREFIX, PYTHON, ismacos, worker_env from bypy.constants import SRC as KITTY_DIR from bypy.utils import run_shell, walk def read_src_file(name): with open(os.path.join(KITTY_DIR, 'kitty', name), 'rb') as f: return f.read().decode('utf-8') def initialize_constants(): kitty_constants = {} src = read_src_file('constants.py') nv = re.search(r'Version\((\d+), (\d+), (\d+)\)', src) kitty_constants['version'] = f'{nv.group(1)}.{nv.group(2)}.{nv.group(3)}' kitty_constants['appname'] = re.search( r'appname: str\s+=\s+(u{0,1})[\'"]([^\'"]+)[\'"]', src ).group(2) kitty_constants['cacerts_url'] = 'https://curl.haxx.se/ca/cacert.pem' return kitty_constants def run(*args, **extra_env): env = os.environ.copy() env.update(worker_env) env.update(extra_env) env['SW'] = PREFIX env['LD_LIBRARY_PATH'] = LIBDIR if ismacos: env['PKGCONFIG_EXE'] = os.path.join(PREFIX, 'bin', 'pkg-config') cwd = env.pop('cwd', KITTY_DIR) print(' '.join(map(shlex.quote, args)), flush=True) return subprocess.call(list(args), env=env, cwd=cwd) SETUP_CMD = [PYTHON, 'setup.py'] def build_frozen_launcher(extra_include_dirs): inc_dirs = [f'--extra-include-dirs={x}' for x in extra_include_dirs] cmd = SETUP_CMD + ['--prefix', build_frozen_launcher.prefix] + inc_dirs + ['build-frozen-launcher'] if run(*cmd, cwd=build_frozen_launcher.writeable_src_dir) != 0: print('Building of frozen kitty launcher failed', file=sys.stderr) os.chdir(KITTY_DIR) run_shell() raise SystemExit('Building of kitty launcher failed') return build_frozen_launcher.writeable_src_dir def run_tests(kitty_exe): with tempfile.TemporaryDirectory() as tdir: uenv = { 'KITTY_CONFIG_DIRECTORY': os.path.join(tdir, 'conf'), 'KITTY_CACHE_DIRECTORY': os.path.join(tdir, 'cache') } [os.mkdir(x) for x in uenv.values()] env = os.environ.copy() env.update(uenv) cmd = [kitty_exe, '+runpy', 'from kitty_tests.main import run_tests; run_tests(report_env=True)'] print(*map(shlex.quote, cmd), flush=True) if subprocess.call(cmd, env=env, cwd=build_frozen_launcher.writeable_src_dir) != 0: print('Checking of kitty build failed, in directory:', build_frozen_launcher.writeable_src_dir, file=sys.stderr) os.chdir(os.path.dirname(kitty_exe)) run_shell() raise SystemExit('Checking of kitty build failed') def build_frozen_tools(kitty_exe): cmd = SETUP_CMD + ['--prefix', os.path.dirname(kitty_exe)] + ['build-frozen-tools'] if run(*cmd, cwd=build_frozen_launcher.writeable_src_dir) != 0: print('Building of frozen kitten failed', file=sys.stderr) os.chdir(KITTY_DIR) run_shell() raise SystemExit('Building of kitten launcher failed') def sanitize_source_folder(path: str) -> None: for q in walk(path): if os.path.splitext(q)[1] not in ('.py', '.glsl', '.ttf', '.otf', '.json'): os.unlink(q) def build_c_extensions(ext_dir, args): writeable_src_dir = os.path.join(ext_dir, 'src') build_frozen_launcher.writeable_src_dir = writeable_src_dir shutil.copytree( KITTY_DIR, writeable_src_dir, symlinks=True, ignore=shutil.ignore_patterns('b', 'build', 'dist', '*_commands.json', '*.o', '*.so', '*.dylib', '*.pyd')) with suppress(FileNotFoundError): os.unlink(os.path.join(writeable_src_dir, 'kitty', 'launcher', 'kitty')) cmd = SETUP_CMD + ['macos-freeze' if ismacos else 'linux-freeze'] if args.dont_strip: cmd.append('--debug') if args.extra_program_data: cmd.append(f'--vcs-rev={args.extra_program_data}') dest = kitty_constants['appname'] + ('.app' if ismacos else '') dest = build_frozen_launcher.prefix = os.path.join(ext_dir, dest) cmd += ['--prefix', dest, '--full'] if run(*cmd, cwd=writeable_src_dir) != 0: print('Building of kitty package failed', file=sys.stderr) os.chdir(writeable_src_dir) run_shell() raise SystemExit('Building of kitty package failed') return ext_dir if __name__ == 'program': kitty_constants = initialize_constants() kitty-0.41.1/bypy/linux.conf0000664000175000017510000000041414773370543015277 0ustar nileshnileshimage 'https://cloud-images.ubuntu.com/releases/bionic/release/ubuntu-18.04-server-cloudimg-{}.img' deps 'bison flex libxcursor-dev libxrandr-dev libxi-dev libxinerama-dev libgl1-mesa-dev libx11-xcb-dev libxcb-xkb-dev libfontconfig1-dev libdbus-1-dev libsystemd-dev' kitty-0.41.1/bypy/linux/0000775000175000017510000000000014773370543014431 5ustar nileshnileshkitty-0.41.1/bypy/linux/__main__.py0000664000175000017510000002076614773370543016536 0ustar nileshnilesh#!/usr/bin/env python # vim:fileencoding=utf-8 # License: GPLv3 Copyright: 2020, Kovid Goyal import errno import os import shutil import stat import subprocess import tarfile import time from bypy.constants import OUTPUT_DIR, PREFIX, python_major_minor_version from bypy.freeze import extract_extension_modules, freeze_python, path_to_freeze_dir from bypy.utils import get_dll_path, mkdtemp, py_compile, walk j = os.path.join machine = (os.uname()[4] or '').lower() self_dir = os.path.dirname(os.path.abspath(__file__)) py_ver = '.'.join(map(str, python_major_minor_version())) iv = globals()['init_env'] kitty_constants = iv['kitty_constants'] def binary_includes(): return tuple(map(get_dll_path, ( 'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16', 'lcms2', 'ssl', 'crypto', 'crypt', 'iconv', 'pcre2-8', 'graphite2', 'glib-2.0', 'freetype', 'xxhash', 'pixman-1', 'cairo', 'harfbuzz', 'xkbcommon', 'xkbcommon-x11', # fontconfig is not bundled because in typical brain dead Linux # distro fashion, different distros use different default config # paths for fontconfig. 'ncursesw', 'readline', 'brotlicommon', 'brotlienc', 'brotlidec', 'wayland-client', 'wayland-cursor', ))) + ( get_dll_path('bz2', 2), get_dll_path(f'python{py_ver}', 2), ) class Env: def __init__(self, package_dir): self.base = package_dir self.lib_dir = j(self.base, 'lib') self.py_dir = j(self.lib_dir, f'python{py_ver}') os.makedirs(self.py_dir) self.bin_dir = j(self.base, 'bin') self.obj_dir = mkdtemp('launchers-') def ignore_in_lib(base, items, ignored_dirs=None): ans = [] if ignored_dirs is None: ignored_dirs = {'.svn', '.bzr', '.git', 'test', 'tests', 'testing'} for name in items: path = j(base, name) if os.path.isdir(path): if name in ignored_dirs or not os.path.exists(j(path, '__init__.py')): if name != 'plugins': ans.append(name) else: if name.rpartition('.')[-1] not in ('so', 'py'): ans.append(name) return ans def import_site_packages(srcdir, dest): if not os.path.exists(dest): os.mkdir(dest) for x in os.listdir(srcdir): ext = x.rpartition('.')[-1] f = j(srcdir, x) if ext in ('py', 'so'): shutil.copy2(f, dest) elif ext == 'pth' and x != 'setuptools.pth': for line in open(f): src = os.path.abspath(j(srcdir, line)) if os.path.exists(src) and os.path.isdir(src): import_site_packages(src, dest) elif os.path.exists(j(f, '__init__.py')): shutil.copytree(f, j(dest, x), ignore=ignore_in_lib) def copy_libs(env): print('Copying libs...') for x in binary_includes(): dest = env.bin_dir if '/bin/' in x else env.lib_dir shutil.copy2(x, dest) dest = os.path.join(dest, os.path.basename(x)) subprocess.check_call(['chrpath', '-d', dest]) def add_ca_certs(env): print('Downloading CA certs...') from urllib.request import urlopen cdata = urlopen(kitty_constants['cacerts_url']).read() dest = os.path.join(env.lib_dir, 'cacert.pem') with open(dest, 'wb') as f: f.write(cdata) def copy_python(env): print('Copying python...') srcdir = j(PREFIX, f'lib/python{py_ver}') for x in os.listdir(srcdir): y = j(srcdir, x) ext = os.path.splitext(x)[1] if os.path.isdir(y) and x not in ('test', 'hotshot', 'distutils', 'tkinter', 'turtledemo', 'site-packages', 'idlelib', 'lib2to3', 'dist-packages'): shutil.copytree(y, j(env.py_dir, x), ignore=ignore_in_lib) if os.path.isfile(y) and ext in ('.py', '.so'): shutil.copy2(y, env.py_dir) srcdir = j(srcdir, 'site-packages') import_site_packages(srcdir, env.py_dir) pdir = os.path.join(env.lib_dir, 'kitty-extensions') os.makedirs(pdir, exist_ok=True) kitty_dir = os.path.join(env.lib_dir, 'kitty') bases = ('kitty', 'kittens', 'kitty_tests') for x in bases: dest = os.path.join(env.py_dir, x) os.rename(os.path.join(kitty_dir, x), dest) if x == 'kitty': shutil.rmtree(os.path.join(dest, 'launcher')) os.rename(os.path.join(kitty_dir, '__main__.py'), os.path.join(env.py_dir, 'kitty_main.py')) shutil.rmtree(os.path.join(kitty_dir, '__pycache__')) print('Extracting extension modules from', env.py_dir, 'to', pdir) ext_map = extract_extension_modules(env.py_dir, pdir) shutil.copy(os.path.join(os.path.dirname(self_dir), 'site.py'), os.path.join(env.py_dir, 'site.py')) for x in bases: iv['sanitize_source_folder'](os.path.join(env.py_dir, x)) py_compile(env.py_dir) freeze_python(env.py_dir, pdir, env.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM', remove_pyc_files=True) shutil.rmtree(env.py_dir) def build_launcher(env): iv['build_frozen_launcher']([path_to_freeze_dir(), env.obj_dir]) def is_elf(path): with open(path, 'rb') as f: return f.read(4) == b'\x7fELF' def fix_permissions(files): for path in files: os.chmod(path, 0o755) STRIPCMD = ['strip'] def find_binaries(env): files = {j(env.bin_dir, x) for x in os.listdir(env.bin_dir)} | { x for x in { j(os.path.dirname(env.bin_dir), x) for x in os.listdir(env.bin_dir)} if os.path.exists(x)} for x in walk(env.lib_dir): x = os.path.realpath(x) if x not in files and is_elf(x): files.add(x) return files def strip_files(files, argv_max=(256 * 1024)): """ Strip a list of files """ while files: cmd = list(STRIPCMD) pathlen = sum(len(s) + 1 for s in cmd) while pathlen < argv_max and files: f = files.pop() cmd.append(f) pathlen += len(f) + 1 if len(cmd) > len(STRIPCMD): all_files = cmd[len(STRIPCMD):] unwritable_files = tuple(filter(None, (None if os.access(x, os.W_OK) else (x, os.stat(x).st_mode) for x in all_files))) [os.chmod(x, stat.S_IWRITE | old_mode) for x, old_mode in unwritable_files] subprocess.check_call(cmd) [os.chmod(x, old_mode) for x, old_mode in unwritable_files] def strip_binaries(files): print(f'Stripping {len(files)} files...') before = sum(os.path.getsize(x) for x in files) strip_files(files) after = sum(os.path.getsize(x) for x in files) print('Stripped {:.1f} MB'.format((before - after) / (1024 * 1024.))) def create_tarfile(env, compression_level='9'): print('Creating archive...') base = OUTPUT_DIR arch = 'arm64' if 'arm64' in os.environ['BYPY_ARCH'] else ('i686' if 'i386' in os.environ['BYPY_ARCH'] else 'x86_64') try: shutil.rmtree(base) except OSError as err: if err.errno not in (errno.ENOENT, errno.EBUSY): # EBUSY when the directory is mountpoint raise os.makedirs(base, exist_ok=True) dist = os.path.join(base, f'{kitty_constants["appname"]}-{kitty_constants["version"]}-{arch}.tar') with tarfile.open(dist, mode='w', format=tarfile.PAX_FORMAT) as tf: cwd = os.getcwd() os.chdir(env.base) try: for x in os.listdir('.'): tf.add(x) finally: os.chdir(cwd) print('Compressing archive...') ans = f'{dist.rpartition(".")[0]}.txz' start_time = time.time() threads = 4 if arch == 'i686' else 0 subprocess.check_call(['xz', '--verbose', f'--threads={threads}', '-f', f'-{compression_level}', dist]) secs = time.time() - start_time print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60)) os.rename(f'{dist}.xz', ans) print('Archive {} created: {:.2f} MB'.format( os.path.basename(ans), os.stat(ans).st_size / (1024.**2))) def main(): args = globals()['args'] ext_dir = globals()['ext_dir'] env = Env(os.path.join(ext_dir, kitty_constants['appname'])) copy_libs(env) copy_python(env) build_launcher(env) files = find_binaries(env) fix_permissions(files) add_ca_certs(env) kitty_exe = os.path.join(env.base, 'bin', 'kitty') iv['build_frozen_tools'](kitty_exe) if not args.dont_strip: strip_binaries(files) if not args.skip_tests: iv['run_tests'](kitty_exe) create_tarfile(env, args.compression_level) if __name__ == '__main__': main() kitty-0.41.1/bypy/macos.conf0000664000175000017510000000034514773370543015245 0ustar nileshnilesh# Requires installation of XCode >= 10.3 and go 1.23 and Python 3 and # python3 -m pip install certifi vm_name 'macos-kitty' root '/Users/Shared/kitty-build' python '/usr/local/bin/python3' universal 'true' deploy_target '11.0' kitty-0.41.1/bypy/macos/0000775000175000017510000000000014773370543014374 5ustar nileshnileshkitty-0.41.1/bypy/macos/__main__.py0000664000175000017510000004275114773370543016477 0ustar nileshnilesh#!/usr/bin/env python # vim:fileencoding=utf-8 # License: GPLv3 Copyright: 2020, Kovid Goyal import glob import json import os import shutil import stat import subprocess import sys import tempfile import zipfile from bypy.constants import PREFIX, PYTHON, SW, python_major_minor_version from bypy.freeze import extract_extension_modules, freeze_python, path_to_freeze_dir from bypy.macos_sign import codesign, create_entitlements_file, make_certificate_useable, notarize_app, verify_signature from bypy.utils import current_dir, mkdtemp, py_compile, run_shell, timeit, walk iv = globals()['init_env'] kitty_constants = iv['kitty_constants'] self_dir = os.path.dirname(os.path.abspath(__file__)) join = os.path.join basename = os.path.basename dirname = os.path.dirname abspath = os.path.abspath APPNAME = kitty_constants['appname'] VERSION = kitty_constants['version'] py_ver = '.'.join(map(str, python_major_minor_version())) def flush(func): def ff(*args, **kwargs): sys.stdout.flush() sys.stderr.flush() ret = func(*args, **kwargs) sys.stdout.flush() sys.stderr.flush() return ret return ff def flipwritable(fn, mode=None): """ Flip the writability of a file and return the old mode. Returns None if the file is already writable. """ if os.access(fn, os.W_OK): return None old_mode = os.stat(fn).st_mode os.chmod(fn, stat.S_IWRITE | old_mode) return old_mode STRIPCMD = ('/usr/bin/strip', '-x', '-S', '-') def strip_files(files, argv_max=(256 * 1024)): """ Strip a list of files """ tostrip = [(fn, flipwritable(fn)) for fn in files if os.path.exists(fn)] while tostrip: cmd = list(STRIPCMD) flips = [] pathlen = sum(len(s) + 1 for s in cmd) while pathlen < argv_max: if not tostrip: break added, flip = tostrip.pop() pathlen += len(added) + 1 cmd.append(added) flips.append((added, flip)) else: cmd.pop() tostrip.append(flips.pop()) os.spawnv(os.P_WAIT, cmd[0], cmd) for args in flips: flipwritable(*args) def files_in(folder): for record in os.walk(folder): for f in record[-1]: yield join(record[0], f) def expand_dirs(items, exclude=lambda x: x.endswith('.so')): items = set(items) dirs = set(x for x in items if os.path.isdir(x)) items.difference_update(dirs) for x in dirs: items.update({y for y in files_in(x) if not exclude(y)}) return items def do_sign(app_dir): with current_dir(join(app_dir, 'Contents')): # Sign all .so files so_files = {x for x in files_in('.') if x.endswith('.so')} codesign(so_files) # Sign everything else in Frameworks with current_dir('Frameworks'): fw = set(glob.glob('*.framework')) codesign(fw) items = set(os.listdir('.')) - fw codesign(expand_dirs(items)) # Sign kitten with current_dir('MacOS'): codesign('kitten') # Now sign the main app codesign(app_dir) verify_signature(app_dir) def sign_app(app_dir, notarize): # Copied from iTerm2: https://github.com/gnachman/iTerm2/blob/master/iTerm2.entitlements create_entitlements_file({ 'com.apple.security.automation.apple-events': True, 'com.apple.security.cs.allow-jit': True, 'com.apple.security.device.audio-input': True, 'com.apple.security.device.camera': True, 'com.apple.security.personal-information.addressbook': True, 'com.apple.security.personal-information.calendars': True, 'com.apple.security.personal-information.location': True, 'com.apple.security.personal-information.photos-library': True, }) with make_certificate_useable(): do_sign(app_dir) if notarize: notarize_app(app_dir, 'kitty') class Freeze(object): FID = '@executable_path/../Frameworks' def __init__(self, build_dir, dont_strip=False, sign_installers=False, notarize=False, skip_tests=False): self.build_dir = build_dir self.skip_tests = skip_tests self.sign_installers = sign_installers self.notarize = notarize self.dont_strip = dont_strip self.contents_dir = join(self.build_dir, 'Contents') self.resources_dir = join(self.contents_dir, 'Resources') self.frameworks_dir = join(self.contents_dir, 'Frameworks') self.to_strip = [] self.warnings = [] self.py_ver = py_ver self.python_stdlib = join(self.resources_dir, 'Python', 'lib', f'python{self.py_ver}') self.site_packages = self.python_stdlib # hack to avoid needing to add site-packages to path self.obj_dir = mkdtemp('launchers-') self.run() def run_shell(self): with current_dir(self.contents_dir): run_shell() def run(self): ret = 0 self.add_python_framework() self.add_site_packages() self.add_stdlib() self.add_misc_libraries() self.freeze_python() self.add_ca_certs() self.build_frozen_tools() if not self.dont_strip: self.strip_files() if not self.skip_tests: self.run_tests() # self.run_shell() ret = self.makedmg(self.build_dir, f'{APPNAME}-{VERSION}') return ret @flush def add_ca_certs(self): print('\nDownloading CA certs...') from urllib.request import urlopen cdata = None for i in range(5): try: cdata = urlopen(kitty_constants['cacerts_url']).read() break except Exception as e: print(f'Downloading CA certs failed with error: {e}, retrying...') if cdata is None: raise SystemExit('Downloading C certs failed, giving up') dest = join(self.contents_dir, 'Resources', 'cacert.pem') with open(dest, 'wb') as f: f.write(cdata) @flush def strip_files(self): print('\nStripping files...') strip_files(self.to_strip) @flush def run_tests(self): iv['run_tests'](join(self.contents_dir, 'MacOS', 'kitty')) @flush def set_id(self, path_to_lib, new_id): old_mode = flipwritable(path_to_lib) subprocess.check_call( ['install_name_tool', '-id', new_id, path_to_lib]) if old_mode is not None: flipwritable(path_to_lib, old_mode) @flush def get_dependencies(self, path_to_lib): install_name = subprocess.check_output( ['otool', '-D', path_to_lib]).decode('utf-8').splitlines()[-1].strip() raw = subprocess.check_output(['otool', '-L', path_to_lib]).decode('utf-8') for line in raw.splitlines(): if 'compatibility' not in line or line.strip().endswith(':'): continue idx = line.find('(') path = line[:idx].strip() yield path, path == install_name @flush def get_local_dependencies(self, path_to_lib): for x, is_id in self.get_dependencies(path_to_lib): for y in (f'{PREFIX}/lib/', f'{PREFIX}/python/Python.framework/', '@rpath/'): if x.startswith(y): if y == f'{PREFIX}/python/Python.framework/': y = f'{PREFIX}/python/' yield x, x[len(y):], is_id break @flush def change_dep(self, old_dep, new_dep, is_id, path_to_lib): cmd = ['-id', new_dep] if is_id else ['-change', old_dep, new_dep] subprocess.check_call(['install_name_tool'] + cmd + [path_to_lib]) @flush def fix_dependencies_in_lib(self, path_to_lib): self.to_strip.append(path_to_lib) old_mode = flipwritable(path_to_lib) for dep, bname, is_id in self.get_local_dependencies(path_to_lib): ndep = f'{self.FID}/{bname}' self.change_dep(dep, ndep, is_id, path_to_lib) ldeps = list(self.get_local_dependencies(path_to_lib)) if ldeps: print('\nFailed to fix dependencies in', path_to_lib) print('Remaining local dependencies:', ldeps) raise SystemExit(1) if old_mode is not None: flipwritable(path_to_lib, old_mode) @flush def add_python_framework(self): print('\nAdding Python framework') src = join(f'{PREFIX}/python', 'Python.framework') x = join(self.frameworks_dir, 'Python.framework') curr = os.path.realpath(join(src, 'Versions', 'Current')) currd = join(x, 'Versions', basename(curr)) rd = join(currd, 'Resources') os.makedirs(rd) shutil.copy2(join(curr, 'Resources', 'Info.plist'), rd) shutil.copy2(join(curr, 'Python'), currd) self.set_id( join(currd, 'Python'), f'{self.FID}/Python.framework/Versions/{basename(curr)}/Python') # The following is needed for codesign with current_dir(x): os.symlink(basename(curr), 'Versions/Current') for y in ('Python', 'Resources'): os.symlink(f'Versions/Current/{y}', y) @flush def install_dylib(self, path, set_id=True): shutil.copy2(path, self.frameworks_dir) if set_id: self.set_id( join(self.frameworks_dir, basename(path)), f'{self.FID}/{basename(path)}') self.fix_dependencies_in_lib(join(self.frameworks_dir, basename(path))) @flush def add_misc_libraries(self): for x in ( 'sqlite3.0', 'z.1', 'harfbuzz.0', 'png16.16', 'lcms2.2', 'crypto.3', 'ssl.3', 'xxhash.0', ): print('\nAdding', x) x = f'lib{x}.dylib' src = join(PREFIX, 'lib', x) shutil.copy2(src, self.frameworks_dir) dest = join(self.frameworks_dir, x) self.set_id(dest, f'{self.FID}/{x}') self.fix_dependencies_in_lib(dest) @flush def add_package_dir(self, x, dest=None): def ignore(root, files): ans = [] for y in files: ext = os.path.splitext(y)[1] if ext not in ('', '.py', '.so') or \ (not ext and not os.path.isdir(join(root, y))): ans.append(y) return ans if dest is None: dest = self.site_packages dest = join(dest, basename(x)) shutil.copytree(x, dest, symlinks=True, ignore=ignore) for f in walk(dest): if f.endswith('.so'): self.fix_dependencies_in_lib(f) @flush def add_stdlib(self): print('\nAdding python stdlib') src = f'{PREFIX}/python/Python.framework/Versions/Current/lib/python{self.py_ver}' dest = self.python_stdlib if not os.path.exists(dest): os.makedirs(dest) for x in os.listdir(src): if x in ('site-packages', 'config', 'test', 'lib2to3', 'lib-tk', 'lib-old', 'idlelib', 'plat-mac', 'plat-darwin', 'site.py', 'distutils', 'turtledemo', 'tkinter'): continue x = join(src, x) if os.path.isdir(x): self.add_package_dir(x, dest) elif os.path.splitext(x)[1] in ('.so', '.py'): shutil.copy2(x, dest) dest2 = join(dest, basename(x)) if dest2.endswith('.so'): self.fix_dependencies_in_lib(dest2) @flush def freeze_python(self): print('\nFreezing python') kitty_dir = join(self.resources_dir, 'kitty') bases = ('kitty', 'kittens', 'kitty_tests') for x in bases: dest = join(self.python_stdlib, x) os.rename(join(kitty_dir, x), dest) if x == 'kitty': shutil.rmtree(join(dest, 'launcher')) os.rename(join(kitty_dir, '__main__.py'), join(self.python_stdlib, 'kitty_main.py')) shutil.rmtree(join(kitty_dir, '__pycache__')) pdir = join(dirname(self.python_stdlib), 'kitty-extensions') os.mkdir(pdir) print('Extracting extension modules from', self.python_stdlib, 'to', pdir) ext_map = extract_extension_modules(self.python_stdlib, pdir) shutil.copy(join(os.path.dirname(self_dir), 'site.py'), join(self.python_stdlib, 'site.py')) for x in bases: iv['sanitize_source_folder'](join(self.python_stdlib, x)) self.compile_py_modules() freeze_python(self.python_stdlib, pdir, self.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM', remove_pyc_files=True) shutil.rmtree(self.python_stdlib) iv['build_frozen_launcher']([path_to_freeze_dir(), self.obj_dir]) os.rename(join(dirname(self.contents_dir), 'bin', 'kitty'), join(self.contents_dir, 'MacOS', 'kitty')) shutil.rmtree(join(dirname(self.contents_dir), 'bin')) self.fix_dependencies_in_lib(join(self.contents_dir, 'MacOS', 'kitty')) for f in walk(pdir): if f.endswith('.so') or f.endswith('.dylib'): self.fix_dependencies_in_lib(f) @flush def build_frozen_tools(self): iv['build_frozen_tools'](join(self.contents_dir, 'MacOS', 'kitty')) @flush def add_site_packages(self): print('\nAdding site-packages') os.makedirs(self.site_packages) sys_path = json.loads(subprocess.check_output([ PYTHON, '-c', 'import sys, json; json.dump(sys.path, sys.stdout)'])) paths = reversed(tuple(map(abspath, [x for x in sys_path if x.startswith('/') and not x.startswith('/Library/')]))) upaths = [] for x in paths: if x not in upaths and (x.endswith('.egg') or x.endswith('/site-packages')): upaths.append(x) for x in upaths: print('\t', x) tdir = None try: if not os.path.isdir(x): zf = zipfile.ZipFile(x) tdir = tempfile.mkdtemp() zf.extractall(tdir) x = tdir self.add_modules_from_dir(x) self.add_packages_from_dir(x) finally: if tdir is not None: shutil.rmtree(tdir) self.remove_bytecode(self.site_packages) @flush def add_modules_from_dir(self, src): for x in glob.glob(join(src, '*.py')) + glob.glob(join(src, '*.so')): shutil.copy2(x, self.site_packages) if x.endswith('.so'): self.fix_dependencies_in_lib(x) @flush def add_packages_from_dir(self, src): for x in os.listdir(src): x = join(src, x) if os.path.isdir(x) and os.path.exists(join(x, '__init__.py')): if self.filter_package(basename(x)): continue self.add_package_dir(x) @flush def filter_package(self, name): return name in ('Cython', 'modulegraph', 'macholib', 'py2app', 'bdist_mpkg', 'altgraph') @flush def remove_bytecode(self, dest): for x in os.walk(dest): root = x[0] for f in x[-1]: if os.path.splitext(f) == '.pyc': os.remove(join(root, f)) @flush def compile_py_modules(self): self.remove_bytecode(join(self.resources_dir, 'Python')) py_compile(join(self.resources_dir, 'Python')) @flush def makedmg(self, d, volname, format='ULMO'): ''' Copy a directory d into a dmg named volname ''' print('\nMaking dmg...') sys.stdout.flush() destdir = join(SW, 'dist') try: shutil.rmtree(destdir) except FileNotFoundError: pass os.mkdir(destdir) dmg = join(destdir, f'{volname}.dmg') if os.path.exists(dmg): os.unlink(dmg) tdir = tempfile.mkdtemp() appdir = join(tdir, os.path.basename(d)) shutil.copytree(d, appdir, symlinks=True) if self.sign_installers: with timeit() as times: sign_app(appdir, self.notarize) print('Signing completed in {} minutes {} seconds'.format(*times)) os.symlink('/Applications', join(tdir, 'Applications')) size_in_mb = int( subprocess.check_output(['du', '-s', '-k', tdir]).decode('utf-8') .split()[0]) / 1024. cmd = [ '/usr/bin/hdiutil', 'create', '-srcfolder', tdir, '-volname', volname, '-format', format ] if 190 < size_in_mb < 250: # We need -size 255m because of a bug in hdiutil. When the size of # srcfolder is close to 200MB hdiutil fails with # diskimages-helper: resize request is above maximum size allowed. cmd += ['-size', '255m'] print('\nCreating dmg...') with timeit() as times: subprocess.check_call(cmd + [dmg]) print('dmg created in {} minutes and {} seconds'.format(*times)) shutil.rmtree(tdir) size = os.stat(dmg).st_size / (1024 * 1024.) print(f'\nInstaller size: {size:.2f}MB\n') return dmg def main(): args = globals()['args'] ext_dir = globals()['ext_dir'] Freeze( join(ext_dir, f'{kitty_constants["appname"]}.app'), dont_strip=args.dont_strip, sign_installers=args.sign_installers, notarize=args.notarize, skip_tests=args.skip_tests ) if __name__ == '__main__': main() kitty-0.41.1/bypy/rsync.conf0000664000175000017510000000022414773370543015275 0ustar nileshnileshto_vm_excludes '/dependencies /build /dist /kitty/launcher/kitty* /.build-cache /tags __pycache__ /*_commands.json *.so *.pyd *.pyc *_generated.go' kitty-0.41.1/bypy/site.py0000664000175000017510000000122214773370543014605 0ustar nileshnilesh#!/usr/bin/env python # vim:fileencoding=utf-8 # License: GPLv3 Copyright: 2021, Kovid Goyal import _sitebuiltins import builtins import sys def set_quit() -> None: eof = 'Ctrl-D (i.e. EOF)' builtins.quit = _sitebuiltins.Quitter('quit', eof) builtins.exit = _sitebuiltins.Quitter('exit', eof) def set_helper() -> None: builtins.help = _sitebuiltins._Helper() def main() -> None: sys.argv[0] = sys.calibre_basename set_helper() set_quit() mod = __import__(sys.calibre_module, fromlist=[1]) func = getattr(mod, sys.calibre_function) return func() if __name__ == '__main__': main() kitty-0.41.1/bypy/sources.json0000664000175000017510000002410514773370543015652 0ustar nileshnilesh[ { "name": "zlib", "unix": { "filename": "zlib-1.3.1.tar.xz", "hash": "sha256:38ef96b8dfe510d42707d9c781877914792541133e1870841463bfa73f883e32", "urls": ["https://zlib.net/{filename}"] } }, { "name": "bzip2", "os": "linux", "unix": { "filename": "bzip2-1.0.8.tar.gz", "hash": "sha256:ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269", "urls": ["https://www.sourceware.org/pub/bzip2/bzip2-latest.tar.gz"] } }, { "name": "pkg-config", "os": "macos", "unix": { "filename": "pkg-config-0.29.2.tar.gz", "hash": "sha256:6fc69c01688c9458a57eb9a1664c9aba372ccda420a02bf4429fe610e7e7d591", "urls": ["https://pkg-config.freedesktop.org/releases/{filename}"] } }, { "name": "openssl", "unix": { "filename": "openssl-3.3.0.tar.gz", "hash": "sha256:53e66b043322a606abf0087e7699a0e033a37fa13feb9742df35c3a33b18fb02", "urls": ["https://www.openssl.org/source/{filename}"] } }, { "name": "cmake", "os": "macos", "unix": { "filename": "cmake-3.29.3.tar.gz", "hash": "sha256:252aee1448d49caa04954fd5e27d189dd51570557313e7b281636716a238bccb", "urls": ["https://cmake.org/files/v3.19/{filename}"] } }, { "name": "expat", "unix": { "filename": "expat-2.6.2.tar.xz", "hash": "sha256:ee14b4c5d8908b1bec37ad937607eab183d4d9806a08adee472c3c3121d27364", "urls": ["https://github.com/libexpat/libexpat/releases/download/R_2_6_2/{filename}"] } }, { "name": "libxml2", "unix": { "filename": "libxml2-2.12.7.tar.xz", "hash": "sha256:24ae78ff1363a973e6d8beba941a7945da2ac056e19b53956aeb6927fd6cfb56", "urls": ["https://download.gnome.org/sources/libxml2/2.12/{filename}"] } }, { "name": "xkbcommon", "os": "linux", "unix": { "filename": "libxkbcommon-1.7.0.tar.xz", "hash": "sha256:65782f0a10a4b455af9c6baab7040e2f537520caa2ec2092805cdfd36863b247", "urls": ["https://xkbcommon.org/download/{filename}"] } }, { "name": "sqlite", "unix": { "filename": "sqlite-autoconf-3450300.tar.gz", "hash": "sha256:b2809ca53124c19c60f42bf627736eae011afdcc205bb48270a5ee9a38191531", "urls": ["https://www.sqlite.org/2024/{filename}"] } }, { "name": "libffi", "os": "linux", "unix": { "filename": "libffi-3.4.6.tar.gz", "hash": "sha256:b0dea9df23c863a7a50e825440f3ebffabd65df1497108e5d437747843895a4e", "urls": ["https://github.com/libffi/libffi/releases/download/v3.4.6/{filename}"] } }, { "name": "ncurses", "os": "linux", "unix": { "filename": "ncurses-6.5.tar.gz", "hash": "sha256:136d91bc269a9a5785e5f9e980bc76ab57428f604ce3e5a5a90cebc767971cc6", "urls": ["https://ftp.gnu.org/gnu/ncurses/{filename}"] } }, { "name": "readline", "os": "linux", "unix": { "filename": "readline-8.2.tar.gz", "hash": "sha256:3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35", "urls": ["https://ftp.gnu.org/gnu/readline/{filename}"] } }, { "name": "xz", "unix": { "filename": "xz-5.2.5.tar.gz", "hash": "sha256:f6f4910fd033078738bd82bfba4f49219d03b17eb0794eb91efbae419f4aba10", "urls": ["https://tukaani.org/xz/{filename}"] } }, { "name": "libxxhash", "unix": { "filename": "xxHash-0.8.2.tar.gz", "hash": "sha256:baee0c6afd4f03165de7a4e67988d16f0f2b257b51d0e3cb91909302a26a79c4", "urls": ["https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.2.tar.gz"] } }, { "name": "xcrypt", "os": "linux", "unix": { "filename": "xcrypt-4.4.36.tar.gz", "hash": "sha256:b979838d5f1f238869d467484793b72b8bca64c4eae696fdbba0a9e0b6c28453", "urls": ["https://github.com/besser82/libxcrypt/archive/v4.4.36.tar.gz"] } }, { "name": "python", "unix": { "filename": "Python-3.12.3.tar.xz", "hash": "sha256:56bfef1fdfc1221ce6720e43a661e3eb41785dd914ce99698d8c7896af4bdaa1", "urls": ["https://www.python.org/ftp/python/3.12.3/{filename}"] } }, { "name": "libpng", "unix": { "filename": "libpng-1.6.43.tar.xz", "hash": "sha256:6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c", "urls": ["https://downloads.sourceforge.net/sourceforge/libpng/{filename}"] } }, { "name": "lcms2", "unix": { "filename": "lcms2-2.16.tar.gz", "hash": "sha256:d873d34ad8b9b4cea010631f1a6228d2087475e4dc5e763eb81acc23d9d45a51", "urls": ["https://github.com/mm2/Little-CMS/archive/2.16/{filename}"] } }, { "name": "graphite", "os": "linux", "unix": { "filename": "graphite2-1.3.14.tgz", "hash": "sha256:f99d1c13aa5fa296898a181dff9b82fb25f6cc0933dbaa7a475d8109bd54209d", "urls": ["https://downloads.sourceforge.net/silgraphite/{filename}"] } }, { "name": "pcre", "os": "linux", "unix": { "filename": "pcre2-10.43.tar.bz2", "hash": "sha256:e2a53984ff0b07dfdb5ae4486bbb9b21cca8e7df2434096cc9bf1b728c350bcb", "urls": ["https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.43/{filename}"] } }, { "name": "iconv", "os": "linux", "unix": { "filename": "libiconv-1.17.tar.gz", "hash": "sha256:8f74213b56238c85a50a5329f77e06198771e70dd9a739779f4c02f65d971313", "urls": ["https://ftp.gnu.org/pub/gnu/libiconv/{filename}"] } }, { "name": "installer", "comment": "Needed infrastructure for installing pure python packages (wheels)", "unix": { "filename": "installer-0.7.0-py3-none-any.whl", "hash": "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", "urls": ["pypi"] } }, { "name": "packaging", "comment": "Needed for glib for some absurd reason", "unix": { "filename": "packaging-23.1-py3-none-any.whl", "hash": "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", "urls": ["pypi"] } }, { "name": "glib", "os": "linux", "unix": { "filename": "glib-2.80.2.tar.xz", "hash": "sha256:b9cfb6f7a5bd5b31238fd5d56df226b2dda5ea37611475bf89f6a0f9400fe8bd", "urls": ["https://download.gnome.org/sources/glib/2.80/{filename}"] } }, { "name": "brotli", "os": "linux", "unix": { "filename": "brotli-1.1.0.tar.gz", "hash": "sha256:e720a6ca29428b803f4ad165371771f5398faba397edf6778837a18599ea13ff", "urls": ["https://github.com/google/brotli/archive/v1.1.0/{filename}"] } }, { "name": "pixman", "os": "linux", "unix": { "filename": "pixman-0.44.2.tar.xz", "hash": "sha256:50baf820dde0c5ff9714d03d2df4970f606a3d3b1024f5404c0398a9821cc4b0", "urls": ["https://www.cairographics.org/releases/pixman-0.44.2.tar.xz"] } }, { "name": "freetype", "os": "linux", "unix": { "filename": "freetype-2.13.2.tar.xz", "hash": "sha256:12991c4e55c506dd7f9b765933e62fd2be2e06d421505d7950a132e4f1bb484d", "urls": ["https://download.savannah.gnu.org/releases/freetype/{filename}"] } }, { "name": "fontconfig", "os": "linux", "unix": { "filename": "fontconfig-2.13.1.tar.bz2", "hash": "sha256:f655dd2a986d7aa97e052261b36aa67b0a64989496361eca8d604e6414006741", "urls": ["https://www.fontconfig.org/release/{filename}"] } }, { "name": "cairo", "os": "linux", "unix": { "filename": "cairo-1.18.2.tar.xz", "hash": "sha256:a62b9bb42425e844cc3d6ddde043ff39dbabedd1542eba57a2eb79f85889d45a", "urls": ["https://www.cairographics.org/releases/cairo-1.18.2.tar.xz"] } }, { "name": "harfbuzz", "unix": { "filename": "harfbuzz-8.5.0.tar.xz", "hash": "sha256:77e4f7f98f3d86bf8788b53e6832fb96279956e1c3961988ea3d4b7ca41ddc27", "urls": ["https://github.com/harfbuzz/harfbuzz/releases/download/8.5.0/{filename}"] } }, { "name": "simde", "comment": "Cannot update till gcc in the build VM is updated as simde 0.8 requires newer gcc", "unix": { "filename": "simde-amalgamated-0.7.6.tar.xz", "hash": "sha256:703eac1f2af7de1f7e4aea2286130b98e1addcc0559426e78304c92e2b4eb5e1", "urls": ["https://github.com/simd-everywhere/simde/releases/download/v0.7.6/{filename}"] } }, { "name": "wayland", "os": "linux", "unix": { "filename": "wayland-1.23.0.tar.xz", "hash": "sha256:05b3e1574d3e67626b5974f862f36b5b427c7ceeb965cb36a4e6c2d342e45ab2", "urls": ["https://gitlab.freedesktop.org/wayland/wayland/-/releases/1.23.0/downloads/{filename}"] } }, { "name": "wayland-protocols", "os": "linux", "unix": { "filename": "wayland-protocols-1.41.tar.xz", "hash": "sha256:2786b6b1b79965e313f2c289c12075b9ed700d41844810c51afda10ee329576b", "urls": ["https://gitlab.freedesktop.org/wayland/wayland-protocols/-/releases/1.41/downloads/{filename}"] } } ] kitty-0.41.1/count-lines-of-code0000775000175000017510000000136714773370543016016 0ustar nileshnilesh#!/usr/bin/env python import subprocess ls_files = subprocess.check_output([ 'git', 'ls-files']).decode('utf-8') all_files = set(ls_files.splitlines()) all_files.discard('') for attr in ('linguist-generated', 'linguist-vendored'): cp = subprocess.run(['git', 'check-attr', attr, '--stdin'], check=True, stdout=subprocess.PIPE, input='\n'.join(all_files).encode('utf-8')) for line in cp.stdout.decode().splitlines(): if line.endswith(' true'): fname = line.split(':', 1)[0] all_files.discard(fname) all_files -= {'gen/nerd-fonts-glyphs.txt', 'gen/rowcolumn-diacritics.txt'} cp = subprocess.run(['cloc', '--list-file', '-'], input='\n'.join(all_files).encode()) raise SystemExit(cp.returncode) kitty-0.41.1/dev.sh0000775000175000017510000000024514773370543013425 0ustar nileshnilesh#!/bin/sh # # dev.sh # Copyright (C) 2023 Kovid Goyal # # Distributed under terms of the GPLv3 license. # exec go run bypy/devenv.go "$@" kitty-0.41.1/docs/0000775000175000017510000000000014773370543013237 5ustar nileshnileshkitty-0.41.1/docs/Makefile0000664000175000017510000000157514773370543014707 0ustar nileshnilesh# Minimal makefile for Sphinx documentation # ifdef FAIL_WARN override FAIL_WARN=-W endif # You can set these variables from the command line. SPHINXOPTS = -n -q -j auto -T $(FAIL_WARN) $(OPTS) SPHINXBUILD = sphinx-build SPHINXAUTOBUILD = sphinx-autobuild SPHINXPROJ = kitty SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) develop-docs: $(SPHINXAUTOBUILD) --ignore "$(abspath $(SOURCEDIR))/generated/*" --watch ../kitty --watch ../kittens -b dirhtml "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) kitty-0.41.1/docs/_static/0000775000175000017510000000000014773370543014665 5ustar nileshnileshkitty-0.41.1/docs/_static/custom.css0000664000175000017510000000116714773370543016716 0ustar nileshnilesh/* * custom.css * Copyright (C) 2018 Kovid Goyal * * Distributed under terms of the MIT license. */ .italic { font-style: italic; } .sidebar-logo { height: 128px; } .major-features li { margin-bottom: 0.75ex; margin-top: 0.75ex; } .sidebar-tree a.current { font-style: italic; } details > summary { color: var(--color-link); cursor: pointer; text-decoration-color: var(--color-link-underline); text-decoration-line: underline; } /* pygments adds an underline to some white-space, this is particularly visible in * dark mode. Remove it. */ .highlight .w { text-decoration: none } kitty-0.41.1/docs/_static/custom.js0000664000175000017510000000356614773370543016547 0ustar nileshnilesh/* vim:fileencoding=utf-8 * * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPLv3 license */ /*jshint esversion: 6 */ (function() { "use strict"; function get_sidebar_tree() { return document.querySelector('.sidebar-tree'); } function scroll_sidebar_node_into_view(a) { var ss = get_sidebar_tree().closest('.sidebar-scroll'); if (!ss || !a) return; ss.style.position = 'relative'; var pos = 0; while (true) { pos += a.offsetTop; a = a.offsetParent; if (!a || a == ss) break; } ss.scrollTo({top: pos, behavior: 'instant'}); } function mark_current_link(sidebar_tree, a, onload) { var li = a.closest('li.has-children'); while (li) { li.querySelector('input[type=checkbox]').setAttribute('checked', 'checked'); li = li.parentNode.closest('li.has-children'); } sidebar_tree.querySelectorAll('.current').forEach(function (elem) { elem.classList.remove('current'); }); if (onload) scroll_sidebar_node_into_view(a); a.classList.add('current'); } function show_hash_in_sidebar(onload) { const sidebar_tree = get_sidebar_tree(); if (document.location.hash.length > 1) { var a = sidebar_tree.querySelector('a[href="' + document.location.hash + '"]'); if (a) mark_current_link(sidebar_tree, a, onload); } else { if (onload) scroll_sidebar_node_into_view(sidebar_tree.querySelector('.current-page a')); } } function init_sidebar() { const sidebar_tree = document.querySelector('.sidebar-tree'); if (!sidebar_tree || sidebar_tree.dataset.inited === 'true') return; sidebar_tree.dataset.inited = 'true'; show_hash_in_sidebar(true); window.addEventListener('hashchange', show_hash_in_sidebar.bind(null, false)); } document.addEventListener("DOMContentLoaded", init_sidebar); init_sidebar(); }()); kitty-0.41.1/docs/_static/poster.png0000664000175000017510000022767114773370543016726 0ustar nileshnileshPNG  IHDRem/IDATxػJ`aoɠ̒IW]$ buPi\, H{qJ : -)yxҐ6iœ$I$>@$IT1aa0ca0YCz6wm:ȯ$Id c K$cc$ccX$Òd c K$cXaaId K11,IaI21%I1,I0ư$I2%vea\^^\?0pxr419V*VVUcXU)c8jв{-%gqB[X\-J2$!x0>=;5 %IZ7(ڟV\6slfv&<>?%i4u)F?yU;+c6;[}?k >'nEArp|ɾqL@zW6E9Phm_4JQM1zֶ+ЇOɒnhਉW !=8v@ ~:xr"CZofmcy`*JNSD7yIߍ>;LY(roD$׋Iә0?<5ȃ1ۅKihw5ϾzWw;%/+pZ c?V;Nc%-ЦRy߄9A9Mװ*KJAI t6Xc뫂 1A7 &&'K<OJ:nR'sLfQy:z"0mXХAmZL6{m ;p]≸Q[@=>"&;qVxY&Dw& ?E0̃-") %%o9}>_cTϞץav*h+]>? -8`hL$^% G"&(8 |چK 0nc'z<^XVrʋY}  j{CZq- ccө#a8}7So.J7{֚Fo/UD%'SHiEh%AJ[B1_L$RBB,L8˚mv 99\=ZHzj Cx51 '\?s{2c A8_ cuo -bdb{pt(02+_oo6p@jwNע2 YYjdE@ sjMs㑝߾TjLp? Z  786M|_bqĝ OM]\tXe SYLÄr`L $݇_rkIߓ5$,؃XvAhx -ϡf?r0$|H_*MHJ͐ aNpr+p*fNXX'|ܰH4BbHTmu/P$SIqR~@ a j46 0֠?FASd3 u`9g9bGbY2޼+K2㔝V,jSq #$03T.&Cp`y /R V^u"& 2E sl|⚃ã>oX0 kld3Ju;a38GRx%xÐzJGB7uL~ȥFVb nlPh*uD1d {ʜܠIfuP($4U77n"뱿[-|2⡨kQBx yyQ9 'EiQ9^Ԑ&(hQׂauKmO֧T)!fQyER 4$mV &3M"R 1*,QJIg8԰Pa{"^FFl(p>hK5db цN{g#Gq7|v#R ل\ 6!1&v 1 /vXA M"þjzjLOoRLLw:x4s$Ts { ?U ?j'NLx|aWA({c EӜdj:/w?E6q"g6^|| 3g8P8w?kd86g%À)J3EBC PUBȻc< Rl2lmk\~M˃!!}jΌG"gPF onmQ.o yl~$a?%l$(1\&f<^'3aV!`XE\ E-rI"0)@BUXeDJ[!w7gCSzA&|]dX^(j2SQ|*p X.0-VL7?l8r#~a]&*T`ڹWF%Ak&C9wH0v$3FL.1ťEB2ITԫEUeT-:o2Gz(R^U|6(s;Pes(&^\ kzoU9 2$TBIy(x3v%C Y+/ i?R5fƨ?Z0N*h}Uq$e]\xaxr\%j'ޙǑ_qm=|fjʡE' õhQ2Yxqk{h!=%nisf:UO(bN.U)M>2 Anӧ8KA,k%><ld݁L!Tg33着ISG^EOM\{o>C Hq szQLL7--xDV7+92 !H`;o^ kì^5~5WLZ䬖|Ճnqfy;/2̷at鷲ղ1D@ VnGZ>>ÒFq&F7!"-:Bʛ$BT*GV0Џ 5mr\+yT99Z&];Kp*nri̫š|Uީ# )e7i0 ? I LZ"a ͍ݿ1A`HN+p{y'9~WDTKOd?+$ÔTbpQb,SNr*bS EBA Q$jdKaaF R ;d-JX4!'Ztd؏dðCUNO(5Z`H(qN2U0p3U >6Mbt\0<({`@e iqnhzʑi2eɨl9{ +immdX.VYݗhbY.9=5姚jJeo𶊀YZ°!&85e-0T kzwb lT/x1R0JpZX)$ڼ_g~Ja WE=0`.)Z鸇.G:"h$܃r2A)8Im*M^ [^REjjp*^PGqˊtBPeCIJ1_oF"E: :cwdqXf 8ft[TUexn_v/'R2l;01vr&$9$SM~Iy9-9Sm7(Y{t(^d(V3,PA*%ʮdP]uy92 6"q0O V~[6P?4`0_zE&XFZ X^ ƭ=EGA,8JRr2 QOܿb TIdj/<aN2,WU@Ztd2\MWT9z-#=Nڄ @Ή|W3#=DAWbf'rpJC<6JAsXV%{eS6K Al+PK51 cpʝB.$hjk'%3ŠX N *>>-´mʞډc'? f>6!#DLwellNOk!g}!Z2,7A/lPn߅n/Z [*+-8<1NtEB)s$2L|/ .q<{ɰ~vh p}ZWF@A&#whљa?C;7?Bؗx9Q_(,X[t+NRĮwUsU0pKٿ;dhs]|hehFR#A~& Mh)47s[7ael-Pg#3\Aa)\7eǥ`|/S]% N\6kenؤ)=ԙG8. `AjhK(o>ðIhB[2]Px) 3nxΨZړlW>Xn%mu z^ /e[)@Tƾ 30#JzB  ڽ/cP|N3ױ븠,bvhpuZ 7^xرcjТs&~':79HU̻P_UMmb獝7o Q~BQ1>enQx`D,M DTFwQr>r$stt&^T;Vh,a%J$JU[P1qd͑aGo}X8ؔȰPRdXZgx\-5qΌA1pjSdd45:%w ȰC'ACꁜwgB*Ң8z*(%K-:+2Bz=⍉:dޅ*2tAXW^5O2amȱGB!My-UjΜ=C8~ _ -)G{_80B0Y7l$o*2Ó NHYϸpu`SRM,퐖Bo=@Zѓ?}# m(Dh{-ѐ=M[/bWKW!Z6 0NYP:8E(_?,CJSbG6Lt!ʹaqdXI(^zr`"sˀu dĐ+8o2Lqn8b#Ȱ_cmrTWE=,Xv+P T%;Ȱ饤QU̻P_~ URR2#,: We i0ڟ@y@Ld. eU\ Lo^>8AAĚMAaJ4ohz !M,(S8\bAPZ*cU\@>v9HRTk :8.43 r@An %AUb$&xr/C:hHT6*cI'e-i.x}y'nKE⥋jx0]ߵ݂A2 2iimdd3)SC /wo5/[W_ga::;LJ}C#55IqXO'>T36@V2F]OWTV8h*d3)2 pcSi)--T/X0L} +p2 g#]),,{5)ҋJL} +pIeym#O7ۭS3SRߓogs+Y}CÛ_S᱑ڛLӿ\9UתοDNYYY])ff߅x;ɤ([ teJs+?x0><>W_ WupL|g\z 8M&m]ҭ y(bhS%. U 0Ʉ@bE33s{s=uu @!ϟ6,5nOcoϯ޼lT PbCFrO uYv+%>ŕ%f ;b-ءS^|yf#s3XNu1+ kGstμςo1maUqY?>N JaN>*@7r#;2:~ \ϖ- ߫Nj|(ak<(*S襋LnBdy-[3 0jR -W]U/C߫}UUёcGU47}ry7921m[@0# w)6 ?l~z\St1͔aĠR=_8r)o`_˟siE(ⱚ7ol1*+_596lpkꡆ̇H1yZ8DQ3B^mfҨ櫹%<0 eg%H2Yޔ!\P*Y>|s=^}s{X*BBda'fny)C޹ "~C:4\2tF9֯__q& }6Ṙe |dƫe{MhKF[ 0T5?8x e z6~%g?MLfrCzw?iyM:E8Ds\(#;_GWi^!~]dć iTU5jhϥޟ2Ju|rEBvU5t>F Lłj-K^afҨ>Gaˢ hRjT ߻?ÕMRG1DᲙzV+L* AƎY؊oص+Z幡\@&%hM0 Px̊r9H]$&%ʦMVL֩S G?9e8ՎήNr#ld}+DU5t>FpQ Wν&%SVstaFp.ksaDr`!N6I*g2K=a5>Q\iyR5KH9F(]=۶o9l& K@E WrP̟3T(ٺʰN ;vH(Վ \Oe)_c ɞ7O& }6Ṙ- B*^yfڒVE:0 c%a ~-TF2ڙTpfyYYbinO= Uڠ[J@k+=95%7+1=zQ<1u+:5to߾=?/2/h:l-5u'W]P)3vԻ֔ʹWلdjjn0 è^Cï 5`\qF=Ua#mI%ٞg;djN3IY_EǕ%e1 PJD.6 )neXNy qc ʰ0Ra]_:yNjZ@k>I:j#|X*!Piqx_:3ЖLZ-a+ &F2\Ib넞NJUPc~)?SpYd.|GSKOOvtthyekGxh =A{MhKF[ 0]{v]]]MeM,A.\b7Olj?cέؚ2[̓\1?^=q]w]e?.ueCdj(Ո[I8~%&3qDQR2֯<7jp@ɡC}}}zE V>J^k$V jVuj蔇\q=5{jP"ҡW_+X^X#cc(XˇW-P)ZP(W{|X.@j_23ЖLZ-a.R& Xѝ \6W-M=a~Lk׮M8y3NF&zj!@EB277Q&^!BI}0Js g|] 5tկjP"ҡ GZ,<2#C^QCC}ԇz-hœ~Uν̦QWs ya2*$ȫDrM.^rw)! tj}({KI, ٍ&Scǭ{Ds Tj?-?==N4hU ٚҘp&?̍HQ)œ'Cgkxd8RVWWLAF K>oP Ý5x =ȵU5T>Gp;J<-s]N O_:j3֖Fv^-a/ "'Vb/0 u@\ SajaV 0 0 0 ʰaaaaX6 0 {v% 7$n!EBv4#^t.RVeK {RjZDa/w;ʹ 9< ,JpoGtlcWa/۶2]9:UBb%tuخvM =i _^G>:L.^NgΖڪfpk1 ~}DO%U|Fo|C^ Ɂ_Y>id seߧp=_uDC[7W]JW b <ә ~# áᡴٞbrKKa %JiMJ7U+Ku" hL4^A71"\x5ngnUݤE:IAG kX5L^E 0ԛ)@2Rҋs&/u¡I3h[nlnHs&|ꋺ/a"""""Rꖽ; KKksgE)XP@PQD. ˆJ EFgjU@QEEaiKckb-1i⓵CMmk_9 3Nys̽^=° ;j[w9? g7V}Ml%YF^ۼƌM"P߂Z[[JIMIPPeg@`@؞xER&'ggE$I{z '1{ a. j1eZ"'Jh]:z:PF0]RM MN(GGE' Uvc2.a00|Pdx1QMO11H5)2%J?tðq! yak?NmRɀLsՁÚ5kJK)z5,J<< Ɵt úDž0 c@id)S-n/- ',>Ԅw=!Ɠ[K+<1 N<1蠪.XP\`aXpaҰ9ғ(y`,ۯ=^xzzJYك|qcZ@{ Lߏ+ȯk;4*L7 0K ηt/SB>jΊ`3\#\ig.0EPʹi "92}ОOlf뢒⼂3i/:YU[maXq! a̧(M<6g`d51~Fu m3b|gμ]슱֫(͔fƣ制k+sNpOk/F\`^Ӥ{N _пq"/¤I__֩F7~d6fGM(=2-~vTFo#M|6|\`a80_T* UaVO^6e6Ղ"qb#%ǻ\hii7Wic[R%BCK#OllJ'βmhn|i?wpðBÚ:ar0buy+:l+)7KLڰTES\e+++6 F)JߞyTËAiYkU]\\h.߶꘍L>m-ðBs&-i#Iu[/a癋M#>d:v~8t.luB6?9ʼFFFJR6:Ԟ^٫$"[ uʲܷĆKHϗ񶵵n!Y9Y/yFǧt'92_}놉 a% S ήH2Lu䅻:;Nһu :aE4u)_]Y~{(֩j7߲VZEϦݳ#<"<*:l+W=^Ǡ>buu3fie9q! Ya Q/<@-_t۴yEqBGrez3WAyG\+.>6/S=~=}EUe×>?Bv]T /)=njAWNtq a+ A“qz%RH$-4i00Lbׇy/0 `a8ٳl1E{Mb~D _aÜ5YӪ4a_v%8(4!E")03_4Ydbh`pBE^VC!_ҠOqBWDtzg.SK!J'ߏdx`oyh,j%1 aF a@ #0a @ 1 b18{8liE@ S;^OǫŸ8n~Kq4$ Ֆ>]xΑ=6)[OӝaPmuT\9X\׺WÎvdm\K23v p͸D԰Z?df0 3վ}JoC]6irW6`uMM=##7fܿ[x4<)p~>)w*=;ukQԫde|pe1=cxhxr72'7$0Y]]b Y,DhJk_^vl5ƴ65XmzC/z4*I-ɒ?!1T$ _{7?U鍽~H&dL&x:H^$W~9e6- LLL6)2inL6= _fS$oON݊t ުZAAabZCgK+Â29ʿ N i|_l|b=̱#9ʍ0GF^jFu,LZ\Ngs zK}}dOa .t Oad;HF(L Ae6AAe4A=xZ~P.l:) w S>5vrtiuB_Ch6g/ܟ8'vJ2רa t*\DY%m؝(,e p8  HW˰2P퍭\!U;aPXe_ipUq颎[}jIB  !@Ȟ (AB"EC]gi;q3~CnПy 朹a|~Nssgna&__ ŷ]l1BY{W?<.Β(D&`>g .=}jUueVbA̘ZRRR?]((.'|BN 7tKk5vi{ѥNPfbĝv_b#EXn, 9м"͚"ӗM=ߊ¥}EoY8ޮK?Y)f(a7bKز4#zes暯Y:6ubc iH6'gqXt[vv}IqC{[φSKLvX;/Tw&{y`'Giۣ8V믗((+b[ߋ8 W dz! NEU,Y)XY%DKrJܬub/RX`t!tނ|-{^%a_`fԩN3Y@V->g{a~̼1i20#ɠcWF_zyo>u>Mk6'h+)-1iX ^Oj8sK\Q>T[ I!" k|ѹ8 "=yoLJ`BbCJJ%/f}?X84/IR-^saTB}oE. L&T_MDBܹKQEQI1ld8z%m{޻UO<8+;KjNJC$OP=qDΰLֶ48/+]6#:`\#Ѵl06WU,V$LgR$h!"1JJ+W?M4id˹vu-4x%P7{1h G慼KreU%{r_vN:ږ˗W_/EQEQW0*df-ktRGIGiMmKr/lk&42 -{2.&xBio\㭮tC Ss7+ӧ{>t7rkajD2a0|փq 3e9!ˆlai͍;/ lo@;kX[p<)bq߳.^^))1NJ1VbخN, aqFfŰH#FaBf̘13hh2 KdܟWQNFGkʔ)X@jjWKmgO_/EQEQW0dJ!HXlʾ/Dap<A%\_dkl\e9AQr Jaصî~ػOԟQs}hru%&dI(sSg+'09ø]`\gOC,=Fd99bwEiցU hإ`eA[M*5zhYŪ!ՖɄ &ګjc_/EQEQWİXk)-╗ȆJ31lUupC{[\\þz$EݻL48dZYEdY07ȼ"JWkP}6b8/&:H츛HH*\bc{pҔ$q'^4\1lKq39#sVpsZ˃782d =k֬ F2rG?uKKd2 ߩy,&d B %B_ӧO(g㚓MEQEQ;v z 1lh2A /C{FCӁ\VOF c"&5X?ÏlE]I~ideo M1`|GX$ύWMD{F"+XeCbӦMfpt6mzmʊJ)^m&I8+2::#b|J:+s;؎8d8N[MSY`k^mmqt>m#l0v wOiy͟ezqS=:?ˉPx̲K$IdV5«吏"l:rTc͋׮}tâ]6c$IaXqz0kJ,~HoƟSnb2}!chIEjY8$IaI-P1iuuTKK7>e$I2 %I$IaI$I Ò$I$%I$I2 K$Id$I$0,I;I# 0OqĀK*4U)KS"E;}IBAs_ @0 a07ΎIs;}ee <]ZWh4E BQ;>~G7CAhea0`-QL63NOmTz3$0 \~ jklssSruQ;O>Iۉ-V ORI`YKn]zURq8tz~mmm}}]ޑ0 Äa)I`Y3gӒX&{_ ᩷lOp${40LaF0Q*?Yi3Gpkd^)v6k[M BRWjڛ}CBj@NB|Ȱanfw:)8/7&0VxiL</WCĎb]U>IJgҰV=<¸IQ=. !DGQ u/~1E!X( {U[ϋac'9::Fw@xZNkyJŇٹ215S ;WYX(bo?B Jb_a$1bzNr""mt;lָ`4z`}OD"dHXڦOS—"U>a9UvEkpl e6qgpF8BI2? ֋g302֞V#LG2W u/~1M %XU&@~( -xd*ẍ́N0l6 w2fhX,5Mw?;џwdxT%|<+(I`9O@!P(>R80s -@ '~-ni5rje/{EE?Jr+D)Zh@E>@_|kݵ=i3 ۜg3[?U~uw:-Ql7za,շ9`V(d\NUXgظcZxM)Lw ?wwnmoaW!Ƹ^ 6s9ފ2a!QC[gT; yz񽚹)ʗ狗.4ʟ O}j`x ^ [D#fe^6D-NȮ 0 0A؍tm!JNVab5߮_x좥Ƹ^˴6s9ڊ44a!Q>5|0\wn">G3lx*>ZTD%rY-э]g=sn T&Ie"t2-@ pثZkǨ;;ʼ2DS> * J$mH*͂޽=3OOFޑT jlHO  2:{ `8 .`88 g # _{ÔঔeŨ<}wP$ {` 7•)aYt%ʗ WlEa?)٥)- Sy%=nv\IYw;d rZӰlsn0=wt߸^K6s9Ԋ44a!Q>5|(X%$). n0<PqTm όMv_+IԜ8uT3v&@d lbIAJGHEm=,8͋<&Ru֎X@d:RÃ3*T|) !CH'ƍ> b"vK fYUR7jPaق5粿SU.r֦|.ZQ5 ]6$ʠF,~"NGa *Aq+_bRێ?) (Ke ={ Tww0!k `={40km~_I7G177]KZנ9?`\ K/>S @h5 8ԞKy 3RCNjQsY~c'SaB5粿Jr`1޽ .mmrihPeOrj篔BQ0lzz>>@QX0/'c!CȴDɟWȰ(b0eq֕ ȃae9 6=v{VBU alƩqˆO+H6P[MՊ TqIކFI/, Zz\&J;:@B )>& 7/&r֦|.ZQ5 ]$*F`8~D  Or0TU I FK\m %lJ` mH*|LO*!=@1({[cKK×.eZK/>S٠٦ |N1\'uA`ƎzuƷKJ_!\A-XS|.-C hL2ջ墭M\> jKTF`8~p;7 ^@0TUI}M-SPM7(FgR&&z߰-ޮ6e"&|ݨY(|tɣ^XUDS> BBKrtA%vmmUw02%mKN\S؀)}+dXhM7DZddg/E[jE}0uٗDp%jK6<=*$aD/~X:bm;еHCBQi`X߿gG_eJo"Iސ!NMȐVWW:,s =^k Ql7z8YAh='E^*jj;ׯ_>WVV諴ئaق5Gn('$c\F/ZjE}0uٗDpET8 e Dv2p^>/3#Im #pl>tj)VקіGHummjs#÷nxɅ@尥%fa\[Jm&ۉ4WV'8?l<{* ĄMv7kx)Uݽ]PΝ[UDaPFS=[qJ9( iGE_ntQߴ)i5䲿ne1!4i1 .omrihPe_5 WL>s6 e Dv0O%CkBDPt8cV1x`=4a,S+gL 1'tS|v`aԟwcˉɪZJ/e#r,,|5zɓ:Y4GĹ,(ölsGJW/ǹlep\kS>㭨A0tِgsth@ҙP8ʕaӳ %Wv< c;q2U06H0%tمB$Y et6$:*L܇%GiLތ L?ק>c;`}녌APDDf0bvjLroI!L[[2>xk+Ql˗ %+P~a.p ml= Aca%^g6\KdZB:]6e2\)[Q0tِ(Nj1( O\C TU~Cm0xUV[m6QjxmR$54|Wr?KqMAy0whYf\P]:Ӣ[CT$aC{)/AEtkԞ6 s śa!1Kҁk~H+@?eYHBaX9w~2F=cׯ.Mrg` @&Ƌ'|>N{ ư1 1 0$v O$Is+6%I26%I26%I2v$cX$` K$c?Zym4^f$I27sWTG:՞p0 ` zjy~kce{uO\|f$I27NG\˼<ڛڪp$ ÇD;3_Dq-[eJP8l%zB?+M r]]]7nL$Ntusr$jYxs8<>* 6Oo>}髗gf?}2PL|O/ǵLD}Z\WfEZYŸ+WkWgaHдݙ{}}]4H1ClcNiiӆ5P5Bf540kQj{)ӧ3a!Cj XgiexrpN$ w1g7x%WG Ҭx~/{}oZ . wvq611QSS۾{onkФ HAK[[+ :chqQ4F;tIvEe? :A@)}ˀ2,! q@0HKՖmuv+&d:[[+ I_Ch3bWS=~uθ0 תzٹi`Z[ss>#IХ(6;aVmm"׺FSǎ;onea<98 2dȀပi=FOY=ů``mwl?mnqƢސNQjTV 鄮1BLV->_Q0&N觓b NF`aNwqes׼moTL7WWiPZዏ~2ܭav6$7>G%UF{퉈`aaa/|yb`ý{tz2dȐ+K&^ g7~<)GfmE'tw޷v̅t`X"?mz\|J""#Vl-jjxOO#*Y׏Px>B| k{ھ\D9SvrM0$ ^8qتqrl s7b Xq3g~2zh?ɸx8Zb<ÉbUQU <|h;V~tfn߲EFFië˳m5} U9j\(7[|UcKRR\یEL殱 pLsn{R.` F⩼rFƀoK5:lϝ=y &&\n\tt_8 T;ITP)0emAS4ώ6|ؓ˥#OeV11cJLsg|or_qm,;V&Yٯ E4Fdy:CYȥ 'xfnIJXj!F@4=Ƽ4L~F%˖:?SP8Dk藤I4BersS6Xg ȉ1liӾxƂx0xk6i0Þ ܬv9@R%z)e掝9?;g9ogߔ ! 3e(V9>e.emПzU&"wW nqjqRS0 !wTTtbr 2dȐþӛ#?:~4g 08ˈ.EC4'l6v9JT>'KOvá*'3gAZ^-Z*#`z& ,hlԘY5c7x{ $CuDh$~.g劙fUfN:-YvN6ݵj*I[{NDj pmr_^2 2hKRJeѣ(^tTdW=Un5u֨qNi|3'^/Аq\ a8lW/@+I$6T2m*kb9D΃  a{8;&@eةNazjѿ׽8s(Bx4~ +m4/vJaL&杶nJu'Ig%v5dȐ!},%]DWΎ @aZ݃":<e!C|4F"=i$@rc k=4h %4[~ q+:~Tr;" G)/B R $&%qMoR턗,[g#OrJ-%a\P B5׼02xBN9 ]{/{R HȱyYhzY!~_7^ou#`#=IH8T`$H!لZU/^Xކ kȐ!C RĿd #:+ [,'[W@bKhQ}[qҰE/':7V!ٛE@N Oq^MPE7 A5s zYMnƨFEsN=htVL ⓢ_0)+_M/^rr- Er˜kw/?Rv}z 6v ߬;c  l{as]7>u_jKe$=NuH.悅AJ9w!o#M";wtp7,!C 0K kv/zTuЕ2tGd-^" :a6A@(CUG@y #Ŀp\4rߊSΟA5y gE"M J-zfd4bH[ ۟BT?am>.bbc]0_`x SPb=)kߴ~cF{]v=;]-:PH燭.Yoq&qb|r ϋI+?LL%iXXC 2`{wڈ̤_ש=Dvwop Tu?]n V3+{Q* :S'6*mO`kCR⽬*#D ]!Mre֘5|cN/߽zc-V=s%ڶ~0IwWqHj;$V 5 XQ1a:{N~)JJ`?ak&ʝ4= *}s%TL0YVC뚟*=M|D_w1 0?/2T !.P̹)H%P2#ZS= kȐ!C {/ٹ6Q =AtZ3wlCT4Q֩#ʅ p xV 6ĹT F@Pr,=h5v}Q%* ҼGIY0*18Yr ]3e%.4] LBaЈ"s]1Q\DqUT]⟑meh0 ]aPtlS*.VW^sߞ.=$(w`x§N=GեirSr7wW-#B6 (V} 0?/'> "ـta‡ \"2dȐ-ĩ{zfompϱo}b7YlS&lМN)L`F'08T\hVJ k0LT3` U6 Xƌ>`Xc^א FErtXu.AjDL\ej @v2~yaJ8^,ڤv6,M* =ضS;*–.FSQUqgwힼG>2M :0;NGJ//k/v+L|YCӆҊ,e 0?/) q jȐ!C D\{tC77ܮXIԾyqaƄUr].5~l1mm;QG [* 4/bLO[DK ֟5$t KOzZC~Ǎ+(ΑNa] 0,Vp*=m1/E_ID .8>~a">Zeo-Qb'RYEQ]LN_kdLM6k<*&@C=x&1ؐ!C )#|}m 7]Y O!*PM9Е۹kbƈ>TS/PVU}YL=Ns־=vŊEJ#ELi.kU}O&ǢM ҬaYq!ō&=_!"蟲 ܒ_5dȐ /JYzWgݙ))oVq$f-3"H{"^1;?.9#Qx nۘɨ 2`g|\Dd>~Æ ^6T2ŝw~~Yfh*lը.BCsgpަׅG=7ib`(eJ$f9b[>v"*#lԭ:C NmEpVR<{>IL$@zc\a0!,4@V"NƼRJϝ7Q\y95Ss%L[b hK0 ,H>.:6E6**;M%O,[tW H->gw%yw]j.@2ЅYaA-Zhg.~b6 NKq}%EMg:FXm =91cx9"sÿ2dg0,L-5E5qq= R0;(8߫S7f "Faau.av`v$Qԭ"!D"bХCəlc$DBXчQ\j3{ٔ,YAaeWz73xM1M9(ӅXiUq|2eB =5zZ@_8ځ.㪺$P[ Q^G2}%aMD:E|+kMBsk} S/Abdl4]]w?WmXh ?Rh*A`@x# {QSY]ܢm(ྯZBA>yTkq.#n./ / X -u.k/.Z6TUup}Ϟޞ- a79 ȅ\.bR9j0 {l|۰v4Qs9" q.]k>؏QUU5 BKs|nә㛷7m_r9^&ӸٞmCe;2JsCmHU0 ;;3.:}vw)1x!>*~NaxMt2H wQن|뉝9ǚV6byCwFZ2ylppLѥQB|k|IptzZUUUpp;,or3kevw2mpv TUU 띃|[p#6^$22pb|p@?,c êaðaXUU0,""b6 EDDq9DDDDDD0,""""""b1 'vUqtwĆ)ɇfF:a L&KMdKS4sjhdړS>dD -$]-~xN:# ~?䌞99s`aZB%Fi, UI8T@քᙾ'd/P嚄CdG2e}Ҫ1iJ~k*u;.vH< ') 4-XZagϺ3ǞjHLq4Rax\D0|v֫G?[dIvax+Z^/;hZzn*+W 0z{W]ܥ8SZuNZ;~t:[J䙹 y ͙}={yosmU=Rf!BT 97ipnoOo u> l_v_^pL|l"8T|ۢ令A9hںD˖TϩfbxҔ O-qU=A2B<NԢ7O4O&ψN|꬜Yu62ԯw@̣w'/(bxђE(p|333]"\aI|-qGroܽw QЭ`M=oH,o}"=!׾C~};þſM{މ20DB!EB OWY§2|Dgf<}lP $]86!cƍlEv#N$E=z:S#FKw_Q^ķ˦&FoW 0{QGD5sf5ҵO/W!jfĜ w _-~zp 903_~SOEU.0BEFa!g>grb7PLͫCrssQq&^m$aKH0v{;fʸNkJ(3@ {l8D}3&KWY!i,:>cX[.C*#1tBv !91WMW] lce$ V)6F j+txX~A^RےZSPUf6}iZ^X!zrCc'U,{,nkTgɁخqǹt5* !"bo6g{G-Q U1œU8xชabmJ%|NJaK4fc]ze`ƥkSY|aqL_ؖ.ǚuknlSˋ5m~eݴcN͉Z?B!NH[ApN.;o/|P_p4D^^^03bj!E!I?n O;}g{7o<(4Ebcy|Ra&쒀8[V',SZz={eTͪ.zX<5G,Hd!BW %|s.gnN>q nv0@627OUA^8p/Y/L2m.\RB>4ϰqgk]6 #UkVS@YV9~87𭍧fMzw0]Hxn5 Ic`smqb+40-бZ=B!D|pA(aBI}h{ڴiuuujjԆcՌ9+!2  ֯@(^HbFZ 1߮`K<*-7t#fJ`dF؈/ 1CVLLc;WS JPŰ&6b:&[nݜ?FD)ޮ D an {,B'fيJCro7s|5Eݻ ґw~瞰f'la{Q UQƦfIW:7K3,Сon؂I ȞLELXiɓ! J G/O ӿo$=Ҷ l.\-l,v1c!BQ  ^Mqqqmmu6ˬߧ5S-O2~q 5qm7(],N%TETK7錤 Z],+GЧDr.ao0R \j PGe.p&W裿?)^ dWyXW7`ab"o"pgc,BxNiS棄mQԫKV+8Rc 6~!] NCl8P:X칳]r%heÚU HgTU@ Gm3Yt4OaaKB7U^K .i0O1Za_b8q+_`"^mr5n 19:xa,VS[GJc!BH gf]%ZJy];9/tl_t$Jֹot졷9DǗL8PR1-D*uJ1|5n 2!N{ؐ SlHB)PZ RmAmF(piON z^_aE޾{@9=(Q-dsG bFE oK,BֽT~nC#%jB0:O?M8s{֯N)E~}cnv޽{ߟa#7׍o7ﺣC;EF>vs}wld)N%TET2l@3'sgǎq_~\ŋ,3auK%mZĘYSW^ղX_c1]"Ĥ5~C`_(**r!rss9L"S͝/T^үݰXJ-CfAU=+p+_>_n >Cd )BAS UQ!զbsCVFo^/A gcAA0l?0+;kƗ5`s>2k$? 񄼴S ɷ3rE^*t@k͛i2;<ь-tӕrLİ]s{? j7NV,\s鑟JLQa'ҵv%n bgX!ÄfbѵwmpyҢrss.4&@JQJ ֥lJ̋c"}ҩᩚU3!-+X d4.]S_=~}WrV N8:.&g1LO&ja mun>Hl=kǣ记w}0$VЍH G7&. 堘$-u* lywϯa?Kj9vˀgΜ avr d8PRK=DCeȈvU Vtߜ]{w`#K͕S/B؈9ӽ"=uzc}oM QszS!&YTлOosuۅB!1ܒIi_>g:cp'˶ W$Jf|1+o r Qk>gkY&M^B!pBƔ^#&}/IИR`!@Y-ۈR@ _Z.B!pRAi$6 qBqTά$*CձbVaaK[cNN4x`B!İB!B!1,B!BH !B!B!B!İB!B!1,KSal'FZ9-¨qќwxӍPIAf]Tċ *-Bbo;ƶamy<0aa0 @.-]ʦH ,*b]y`?TD \aQ%JڏyYr&$U.CMGso2sոOzkmk-l.Feit7;q/f֚wj&*Zpxw)O_e cGx>Ifڵs}\d.uZK7vأt\ BʇttdኼT 8KW/l}C}y*g#T.l. ØGn,_/ 7mP_T| hX]2$~Ovζ'+ddR7.h h"jiK!E-mֺT"Gy^} &Fc—k0%Vh?';sϙ1?gnldɏ47B5$ SL4Z罫O׿v^qNd!*Mʘ`9wxei=yM$Oޚ"]]s|ht"M(/''[&f&{jztQ2켬#; bG<$fc}˯hςOÃIkKJK,w-8|p8r~CWEC2t?WOhs2 ϧ>uIt\/mPߢt.]\ܢ"rssUn6`17uL.]MNk3Cv.2IGnjfOM,^N?пXdb\~樗ͯn۰xuŖb}۲%>@ ɰ7Ma‘?vPMx0ۧ㵇_~Av<׾EHp&bNGYFɞ=tvwHJ쟿ZRB6,gp.$:VuL#c#8hϞ 3t~2{CzcCcq: :4ovnBI˨oe QeDCC p3OL/~ .Gc^eԅW?if٣ũ^Lپ4+>|z557AuCy'b(nF d83s}LcGcdx%RDeiHRY)WB$x :&_D=Ҳ2N9Rw^]+b&=7'msgLD64.TN2FAp[%B)F龜qw-RphS"U{'!ØGE"jd6dX/;!nvƍ*_)VvJa-8&n21&?$xSK(f,+ 5{~ 0q5(+XXXXXXZ)k̍<^ &pq_!'~Ga4.hwʕ++++ [>q(&KRE#Æ&]K#ێtd;by>1|J!s N2~ %vQщj{EɌ+G3nZ= œE.%޹q7̉y+ ׹[ӿ+f%"Tрf4^^=xrs7o.//O=B|}RG[TRRRSsO(rHo;,BS Yұ dspQ0E2&/j1m1>a+fYo{cXuW ۉ(K-,,,,,.ۼ[w-!tL8rF(q>|/7o }yg9Ps c67PH :xrOZ߼PR!n^p껫!C'c/@XP:cdza}焥}æ~ɰ_1乓Ac'ф(guE ܆vѭy,O.uaÑX1f2 tcNu71Uɴ3DZ<Ǹ,dnc;vv,#VV/;ZXPP[WWu}hF3sCg;yƍ3,[1>Ϝ 43,|"DP %, W7nڼh1dڼ=x jiiT#Ȱ5Q]@[5E"~)!Br'h)Ajб4&@.ҬnS]wwATNbɱhSq8*|ժLK? _=3,n(߳@mқEGH6ѭy,O.uav&3F¼hmo@(̙D OVI 9.HXbe~Տ!%<J >rHm݊dbrLHh^^yw0SC(Z"Q8/'Ց̥Y?xw[ |7 N;7̓\բ,?*uy(@Vq]pC)Ɣ6B菮2 $'Z7ZK3h1z"{*Ι㪣z~\-COǢB=kSQI X dIS  ѡnjHA?_@7g?BzHz }~A缍Yy5b)Nq$Zueaa͖WagO9X{gr%v禅+o/I9ȖͯC~̌ewn 2H \'ɛk/w 勐+]}]ݝ lYOmlMw&%ql=ʥG`*90F##Zƀ^h09Yn-syu-^Of໧ZbA ð$I%-×WUb|i)-SB襷D"YW캴35MgY>Gj*9ڐ$IkaQNlYKW.KɻO}:F0,IOpZ֝'VWlt-2ҌWYu Ò$IZݐTAkH͙$3L丕#%I C4rvB͆ 7{qM'6 aVzYj Ò$IaI$I Ò$I$%I$I2 K$Id$I$0,I$IaX$Iͮ4qK:6 i5P" 5JYUB!]hԺ)B L1@iMM̊"袧{bFKѵg6x8v9ρ}Äaa0awyՖeңBk3amHevI%*T&Pfe}{!b`$Stn{0pqeI jLjNo-.Fn)>5xmh|r|ƦFdϟJwDL L#=0^=9쀆9Uru4+)6;oZcw֪2kq1rE6KƏh>T߸ul|>ߟdNo-.n54;{pdK[-|Y767T|5:{3zʄὺ_a0| 5uRo# 0 V Uʸ ]o%%.Lb_V>qx/v}J—GBe5XP>٫JwcA[m@M}BMLO'#TD,kD@UAXACZ9<8ʱ x!bAAԨ"k[jƚ6M6R5aH?q[uN̈́3<3HhˆoYofedf77/U@%䕴x52T:+p/N/MҖd1y.F|1d86gx1𐿙 sR~KWQ'pGW'й芩aWuSM]6%dX/w>^^^h2!B}dt[q#f8?aWkY+o]~%q#v^9kn7-*2s#E K"ݝJ|X%3VH#WhWǯڌZ oV?[xϱx7ꤛ ƒ:/Vσ>ǽ><΅ectwwlK:t~KFjׂ4 ޹.'ɰz]2޳ycґc8ji9^.|2CՈ4׸y0]aGM``x70g=;7g=LF4>NLl.lȍ&kd8_/A_X ܠ 4(=^V'LL5RT^z?Qa(ȏU9>x8ߧby`k.ljyuHQsE֐Ԝ@Ig̜-L_8if^ {k t92.z,_cׂ]Zw3A0g!eB%@ρ'Ʋˑ<ў0DŽ dX.x{:ʍ86!Z_/L B[ BBfuL *Hxyf+޸!-=O,ڵq9RMAjz3}2#P3.lPdfTTtq!YU,:Dd @We3M"1R YfC.Rjlw,yM"+OxcF-<թYuuZ?DlC\ z\pb2漣%DXW_e[+Н_2W ^]+w9FCv)I~j<~eT{̘C"L2B Q1s<1%<@늋++}"OZ?3>XL* /3e^SCQ('TYS%C!YӨ'Lڥn.pbi[VăҪ5 $)(*h|kj(@.v& $$"; Wњ&ADPJՍ EBDMRv%JVwAc r2Iw2i^H޼d2wބ{޹N'yή/<1,/!g@u 0~07 C hl Bp%7,Hot3dx/c)X@k? FPzEo7_|-&s|>ޤ .ZBl- _L&c4/暩STE [ < q٧WW6xHY QR'Oă %!cO5! FΔg ZJCޗ|W iT hEQ܆ԀM`EBuԳQ( • 2|ȓGglih$/Ixh a}N-Kcfhx oL+#6<qv{wZ'5Y^0%͗uj5A;I Kn|!` B:s Vc׉DgH#JCޗ|+`hICſ_F@M/k-Ϗ6LR!k( BD=LFMΐG{~N^uzff<$|rd!~0 |٭98ۏ0/G݊="36#Vdx͕ v*j,2,܆FVʳ3fۻەK.d] +Ey_byusk W xHRT,TZ-҂-/҂OV ,PDD PBiZERi4Fb"?Y333NaN3Yk28 GXiGG{C5VXMCy1ıFd{/pUdP( ](s9A3g3i9t*_==0?7 se-XylY4E4hIdnqDID$X%T 0U\jJd9բz^~>wt[IN2l/1ܺ*y`h.JfG BWX,ys] <Ȱ^SND^0cW7!}Ȼ!J?lnQ%BP( Vsd  o }Zᑮџ E\z`ፗ~y}'k-C ;2cCKGB?G{N#4)\HF2%%%ɇ~FKJKąEC4(, WL+GRQ 6dؾ_c(Fiɰ$h\#>OT7.꼈2UQ( ‰ OM$;y| ?8\ԿǺq픯}3; 8v ?^=@S4BS4H4B\BX2Q32\\R,ϜU%qۆ'O"& @x-F }-W_}rS<9 } ,p5 sv  OEb yc=ۭyYN+2l?_;8? Wtn#>999 `XVBP(Nd<[5ܾ`"c }աmaL&(fRFhJ6{c&F ϱą?~q\8F_ܟbڊ $ s1"k%Xc3˘MgbR2l/1u<浴 [u) ճk'ݚ"SK|F>xigDȻ!s eSY%BP(Ȱ{.+К}@?[38_guG&(ֳ"*T߀Ňt8gd^S/*|8"lTѡ$Lʦ-OS'%xg~Kr(ʧ ~ 8Ţ58\\ b]C+<5!]ּ,/8WW<y14d!bp߽XDεu[+ڹiDpD BP22\TTFۼGzFZaO;s0-'5$>drʐ3r0N#4%A,Ìkg̹n"96mrK ">Ǫ9މhKq*KT"%Ya<<-0 }4[1[M1}يe~>FOhb"'4%mVTVpqz[rza_Q819r$%S8X9vy12ObS |x`*)P{<ɹ+ãx"BP( 5FexѾ5pZ{M9NZ8ӝZFY/I^nkߎ s$˓V>^'H(L-T!LLD .(8qe>g)R,=M+M(IBj;s$Ƚ0~QhFe0-arC?O *~vP"e ~o9`-f9=Ȱ|ٯ('1R2U5gubBOry1au 98Jxt1$ܐgy7AWHP(JS,!VkO72c]"0b h D]/hMS>ft   >N.bĤ>Ul6 S7FipMm 0 0 0 0°0 0 0hH׉$J*Q8TM0.~Yvu (I:pوǫ`*)[,yG'/_2qܣՕ鼞MZKkkA}>F].9;5`F^(7$pY6m(IaD\gWg}:Sgѱهa*6ۄaOO̶2NIλLic|H',GIFycd565 7S;gD;93O !ULk ڈFqPJ,B%-XOlRb ܹ 5Lއo1sdf`ay!8~2.GTP^\g\f8cy쐕Rԓ{)ü6$jfeYW] B!-Zs>wxUuwk˭|u7ҧcr1F6Ft!iHF Q] %چiS?o|ucZVGp,+aƱhOF ("Ϛw(äkNr2L! Rz}]m1 S'^>w怋XrBҐ\/ J(\anP/ SVx[DUU,)Gm0׵A&B2|6 *ڎZluoeiUm$ÉbJWߗFO%:W}`+<2Ymz0OeB!dW.3#|Bl+ߪ+猻^U!({dhPk"Rq 98&./B2έ; hR,*.b/H_yëjΞp8y.ywΥ db$$ .W-P $h ("Q)*>lAAt ?Q/Lrsvh&fܹgg9 ^[yEɳ_S&Imh`Y[W(UO]80`QVa8je9Iqc|c*gjp@ |o.MDB;q; |f*}+$%%̝16 7n3W?8F6 W300000008s 2nUAġlA-ΐEܟ"<d'NQB7KR-V82`[avss͢|amH &<:@%z ^,TFawGGdƄ,.-  ,+Qbհ7X&Bn[N}YXPr&M(e 4:kFª([^ ™U db~ J=!óU!w;ܯ^Xd'BQh =q7h<IIo|<kV#YpWJv] s Le Rm1X uxL|{zE)`D1.v!]],m. A\]laB*Tw{0Kּ7ZsNfS, U p+Cˬ>,B kWFU0 |oesnۈ޹DqqzISgpSv]-`UPAAn " U.ZPV@m%hҦ/M4>4izO_rYvc_&=sffϙg^oCppaabؚ7bhc?r++\J!/M,Y*V E3L$׺i\hATC(Dz'#+@Y L&' /;퓕;6,  WTxuY&^2~$8 ވ%ٍ49X߹ څy8)9Iq2zBoC aaF8ñR B"]EqN+ Qm%iKSU -FJC﯋XMl{Lm4|viĆ. nd'W!x'"i|I<=rǛtd&/`i7Ҷm׭_G?EOaW aa/QF86V2cKYFsmS߷W^rqRk̸m ;7`M EO#p6j ܰtto QgIt@jVzUMka6(6a' ] S;#Rrz>,4,TOG=j  &q Vr=aaFC ka8+=LA!S:=&:g]h)EcǍnY۱+s:mS UR8<J1~\p1uq̦&)]@#RC}JٶwaZzlU#[*4"Pܔ5ݻ$Jl6{V +R0 0 g1,Y4S.!QKzq?U:;jX0{섄$T@6..eBV^8|R^"@0xV?.YLHa*YJ Ut8Ex+#3dG loHw Arn"K6h2zF )a6 -_a[v14oX( L0-HZ:(EFF g 4gŰ+y 0 0ai"VಸfMuuuUUթ? /L~ut׾F2UnL-?]{Σwl9BJbk]HLP#0IAT@+^ ?<{+Űq!| R>10ҐMd7+?>fluLŒbZ"(%B#Cp z,,Z7bX("訨($+o'OAұ(yQ"j?t0|WdSabX,+Þ . ۏ5Vx%߳/T(M=+_)|ccaQ& 渀ӫƐ) WJ̳:|wp:2Vb)uyAUq57QTd)NSkb³5p1~\ptbu&S>/1Zg"~`+sW"aN {~6(c͢/X[%oǿebz=_XPlaW KbaaŰTQg8KĶ&JMK͡$V+B22ґFRWr vtLƌRm̑M>dOfp>܎< n.S8!0t?7n^ VDxxWSg$w]. `s䦦4Br9&kjmɋQp22bXɸp: h[IoP 0EwAu\&PMŘ%)KRŐedwCbXYV =7ソ}̩ei_bzs9 qHw`\3[./^)_)|cayW.[L)P_4T.]n詁=ԑ3QT,=)UTK"uinmevx@7xJgWˆTZ |GFȍEC{]%X]>-) jzXi7zu%G'_yFW:iJhNZa{lo% vagx/KkR"]$KDZ;~'mX]HMQ=?xs{-5l~nwg?Mz(wU?'RoywUTe溮FcBo4,It=~56 iϥ%4GfÓ_ 73 J8V0]@!VcD`ma|[O@)wk[E`8|9t hvGzsbvGh]X4J`a(b+tT"zʊV֎3<\70a @0 @0 a?-q⨢V*`ńOSߤ(;*j XaXiVQ֖W9&aZXUbTrҗ>!5kkž'uvuD{oV*<[fRzF>!}G@ᖗ({-wi+-fWEU5eAܡ|B"adW =6a@N4 oۜ6~SׁR3?-Ŵ뱚2ȰLjVk<233GG]5s0:N~dcܩ⧊FhY.e_;04T|u#yZ^aX\MII é0 óx0#vSc6Û~ᅰi'~~}jb\֞/x)ݔFO*聞ElVUTu=USun޺xy4k[8c &x?!;FἼ+s\m~-a={ ]kE%Q1A.^JLH! ވE-yP!FB􆆶@Q5cƒ o>~dL5yΜ33{vv{ΥDa7]L>rl11ZkMȧw0ۯ{`-ۖFO#EPT72l`*Xe% 2`tݏy"D @ N%#9MfI$P : #?0R Jaa!YX_0?ipx/DLw݃@IOr&|O3,glv?o@^oōDz]P2L`s䠪+vdbjUuwTge==w"KQ,#645#kqE5lvM^|8R^p~Df{m]- 765s44| 5>$sf;rs3/z x $Pq \bD 4M㾵X `0pywY$xR W4 <)r0aۙ6T=z4wj o6y# 9~q-4!̾V q$A"=O@  @-E c[Z1}sC`EuR HcL`sQUpEEl'@JleʟaL3(X[1c>O{F&L6kj@\޹$aHN2(-2L#FZ-I- aEdzAǹ~3s♻Vgeu% r_ Op%vMߐKj˔ $hD;D A򜞝Qpp|H|!_^'SIj$lc%ݍd/7%''>iuwNUE[eY:C ^'<6Ŝϵ>"]\͟/~悯8$$9dh2utrA߯5_k!`l } b#1!TFj(3E.f)%=cOS3qdVH"-xLj=E  !YX8xcb`<1,Vȣ]&GAЮ/IhwW lL@f"T Pv!@ȸHlS|x':YtL.*98&BY~R0MkGߖ%xGgGOo/'TYejDe{RORUQ_O|{}Z:9gΜϷE'è2fKw3fΐ UYdHRцq5ucMh:r(8Az)&F8&08pMa_ehRU?UkVܗΞ;.m2C/Y߲=j-BuckR^U07rZ].W4Hl5Z% K-p1,Ȱ ,_=|7/&peSz .281$|S 6fgRQZ.2{bpl0T0i99h:NwSj5%%'gXXT(1l%V㋗.Dnnn^G a/fuY/6Ke F" [S$D83AhN]C8VhE˼>s (baR 2/|1ΉµwJCt2L-%ã R}9ȶ*c M}^@Ω/'pZax >YCq"]"he_ 2lޗ"2rwZZj C`/(KǏT~݄ G˼>NNIs$Z Fj]ƝV<= (ɼy/= %;$Ix-s%vppppp LV"VzK؍+y>o;~d8M ŀ aB^%[ AGV'bYj%Z8vTfhݳJKVv*ܘ ȰQ6b?*C(vdu7$*|ŏਇƄN%b7ơ׳H&0a b&9a-dؼ)N<2ټ-QKs1wwyPy."@M}&O<0Iq+t GÐ[FAwWr#ʖ?UAzwkU_\5U,̶ev& Mta P Z>E5MHt# #*lࠁvfHȴoEqY. Ô zF"djxRKYaNbɀϜdXT֐Rƒ-xH4uLX$RB̹5۶,7.DZ[CGd|zȰ἟9+*f]Hm-Ò> ]O>g2)~wuҫ]+ʻJ w$EmfG80'P؋ CaX 2ŗfY{^) _ ,3C1 Â&dEVa>i`N9%ãŮ )"cRQUgt1xanܯ\ei2ힽ{RaYnHUKe rPHcN4!~ @me^ æ8Pe񎲖([g`/('f\_晆T8\M%굢 w åu> 5A0l'{!?| 9SN>׆_~y;F~OoY'ӤGW?On #a<~d/Ğdؤ4|]/y ?_i?ؠH[hFȄ>-ՅV(%%նTȰ}!GzL z:vo0W .!iސg;x dX_ɰT< (d񎲗(<^ˎQxȷ!J2oX" 4־Vwʐ#ÕIBw'&yOIV=% q$gNLbc9_pĠ^ӛ_u؃ߌikv|^ȄӝA*~u͒l7Ǜ ME_lvTf8./O_gW EieFov\[cx)aa "ìu$V{Fe#׮b2r_*kHfH^F \L}9B>ޑi{W /i;S83JM( N"ˮ'.\TE‡C6?lM>aSC!ȣǎp>~sF>qITN,PzJ2r_*kPz/:%6S9\+Z3 b'OY ˼>拀<1$Q x9XT$J>HԴT6~He~'#IV)C Wv<w˾ҔAYJ`l= dee^JxsQ/^0q}M:͙aB0@vvvH@Li,͞3'̖0H5y/#o#~C=ftWaRYCEp!"%۰nE3fΘo-N!kf2-)әٙVGBAN%9J+7yq2'(SfA-#e3:60Pma6" OU(wCw*Öh0KX[M E>ST$-'߳`8碂|gNW2Br"c'xN$ q@vvez'ɝ&^#ǜDRKie5TQC렾ٜ'(ee{@Q;^Oƻ򠹌W,(LQt}f}",URvR*psmr#)]JqA^`4=>z UH악 )\Y9y5#< =[yެ^$tL>lazMT| {a ` 0@0 `p ~o/Mqڠ =MӔinnSBfB]!Ahͬn4G(By>|9睜sp S"* y [[ xnsC>ǥ(# [UllnW_m;Ӧ(_KcFrOJ)ߐOzT";Q+_ˣwys;DrD*).OSpt\jCߞAg9vۅEvWT ^W?ab&ت6exk-+Rφa{ ;>1v7fP[Nw;xR/K͢JA4~ y C ԉHFGR2r흗h  "2'_- YML?_+{~ , ~?`0(XeZ5Ղ؋Ia y=LtsɉΎ ryy^~+)Hc]:졒щ1<\*: GCeRAx; d %beeoፀ|Jvv8P2 -~^mr7 _=Q08ie2 r'eMЛb*q;THf4m~`c8βiݶi~<]AC5 (ok}*jAZ1DQiɶiMӤ4niڦi[~0wƻwoɉs97+&F vlGܓ6ait˨=AAAXP5|B!SPNH tS1& ǵO3> Ϙb)|E]񤤦^*Pi{/ /ֆ=c]q _,h<0*7<<^İ'H0è[U]]-]5v!..np*zܿQ03f-.-+mjnNUƁ%t)[+4{[{.W߰ȥ13 CiI]}݁] y m=柴U*t˨=O!}7^]ȰLȄB!Ĕb=Jؾs2Hǖ{b 1`*G^(IzxשN.+V;ٞsz/E¨;F0Dpuw\ho,X\f~r0fb.J + g۵w, UL [VffCe"/wH8|0'B!dbx01 'Q^w}O(Z 1R>p (e<ŋ2@iN rS{.!sw 9s UP\F2UEaC潭ɒL#K1ʥ?|B!3ƎD(-{R}.kvfVQ6<,Go쬱#SL G zxܹdc]ǔIC-:s1 ,S(,@&:aDzK À))[Z[DGGˢ4bXaCb吃O^B!b1dNJ QkXeqT{Jf}coS·:45p<^n2VUyC YDfQjX\L@Dd$!8VL*ٕ<øS(f-K[4lo.aaa۰a"1|1Fg8)aҿ/e ɜ%B!&@ okMP^hݺ:&hB ?eګ~?hg?Kc(LC]]Y &΄MTlFZK6,0b{1IJömcbXܪ7W+Wt;c֔;¯cx|B}K-" 8[5iİuCv~ Ë#Gr0<|B!s!b^ÑƤ;W:p?G4gO&d9Ѭ+G5jh1 S0D,E+Wd,̐3a=Vn2dbXM!a$RK UbX[} QH!!1A "[nqW]N~ZqM#Kd GR.(aN}a]omoK!B!75Zw%Z Ryy9`?yl?K3-"\&17 _kIKKޔj@5X5(S$Z9{L{g14].MF9(**y(A]p2*GR J1F!|~|L#KeJVB!L/b@ F+A ŋ g:~zϣKDj~__ ?pJ8==Q'uY[ !BpQe pgOJxEy}O&~d;zfy5_ $JiFi+VJ ]7oFDD(e($u7'Q'Ձ5v/a+\ GEGݺs[ E(=hR0^(x₎T^eޡ)s}/K]3J!BL,rrpM'ay 8{G £s1 V\!zةVŊ.Jdr(:ԂRR"K{ɋ/ =;{1]P$xC;<) !BYpPIfkkkkjl{OK$إ懆.O>|؛[XXP^^VUU9bF$EE *n*E 8)zM^~JJy5dUWlh ])ta tCibX@uVHz,7lXۑGA`a;3n vKX?#))Ig]r2 G=&&Y w+tŰuC%հ ݼUWeega7%B!&Ò:{%(@y{ak+7O⇯8}NpmP뮐edž#uuw43(Gx˥V#96]cL&,){a1_Z) q`ʪ[w=n_]]956VwQzvpXa:9֥{x}38tU;DҧK!B^v1dڹ'sΌkL.ͱ8.1\F 99rEp&l8&$9%fRؓGFAٷ'idHpUjMMpUEQYL!ڛGqIC}ZGo_:İ8!k,HvZDƢXAѴ坅аںZL4ԾrqѰPzvPz_ кejmtK6sVcB!2cBGm{[.3f`G[G00,?ɀAjRXM;1{H!B(_"1 BA/0$$ukapU !B!+ʀO,m+i2H][.pd ^E0!B!bX %0 U*ɥ 6  8!B!P Vg| 0P2 Ia B!B( !B!aB!zi*8MΌS6 *da-"̴̂. ȴQkc&vKhmܵ#؋103?9a @ @ a0a @ /=O?^ujb߄a{Fal|jLxu7Ғϵx2026Zy=3$_9ݍdx%l̞Fۑ"7Z>^΂ ot.iUtb݉h u{vϹR fFJ**.kimЋXl!9KUu**ǩ UܖTՎm}e Vaّڼh^osSs_x4O\?r&'&| _n j}v& h$hY%<iG|p*4~ngiYGkÇvdⲭKbQtz&3$gVfzD-,ʄ"3R'E%.+}Y.vV׷ EY\=\|p~qY"<:5Q8QrŽUj:_uY+B_#-^A>Xf4Tz/fy%={CA뛑a${d߁ GFq\biQAA2\! aNXn=iXM1 [*fL:xl1 yÃf>0h$wP\n("6굳P>>AAA W;+*X -ٙAܬhw|f/O_{,XDk$yѢb2 E%NU;DQ?w?Nd4ɦ榍M(w#0*W, OHF|jֶ1l6 -F6[H&rHv{'H-fܬuղV/]i$ X #c]H|xx莽i"7Iײ Ih/kM[߈h)_0$FH`HDш'O$}C6-SJ!9tݙ%M1cEeTǏA8&}z%/WuiW  p4 b*\$'Tx5Ad Sm/|s Tw4 D**lF^~ʏ?Q,!17_?\1Ns}'']`c6 ؘ@ؠU,#WA.P;9{QtTE?m_UYOTTjo{wY2F> 9Կg~0:"ߞ  Ύ[ۤ+3'e9V=ͥ+׻%Nt^k>7&uU S+:H p\]O#v2IۭT%Φk$jyS\k1~`ͷ% sSw0Z_y8 <+~g. TYKڇC˰.e\h;xsbi<d71ҟxwch7u"F-33F[eϸ޹Tyqvb7M:S@"rED.*"(]ԊPԉR]ٲ%[,ۚ,KmٲdvM}巽yN ;~?ybyyfǺt!8TwX.oE 7x{ B!,8CE53\"D%gg< aҊD&cQvi*< "18', DςT&hi(U;p*c島m/:'Tq͢ZNC9p\fi,g(v<8@!QC^;wĺߣ Z֥kH<*+`_B!1[+-D%H_JZ7}<׭5x3IJ+.˖^ɝe8 z zb$ vp-DDžNSb82*Rօ`<TO&-U}ppp^?4.CT4l$Ku 1l^=D`1K2׍c~B!70Щ0w?V(e&9It*/#wVbi6靣/y"o?7~i$Gp 2c L *M۝!yTC(ڮvc :0 )ɺP*F1l4Eiaè4&0ز*z)X="*``[(KιT G;0\Ư>{۽[PB!PbxvOS%Xu]B!F% l+s}p{.xҙP_ܱߨmQ0UUU0tWoGY"BN(A^#|v,12uuse]dhхlg-ˑtBR+M$I; 03gGֲ.-{(a(a~EB!K 'gN&ê/Gs8dT]vtuwGwl|gZv7к[W5cw<0a,k*B]@b:$ xQ X*v1\Z^:x;vF1OYH`V6DVjـ ^MW/5bX˺auM$!ByİHtꋐpWÈR16nLӍEm)uzpjŌݖ6L4i] 0!0 FUF.ŋW.KQ ױ, ia1_ћr !PԮh8FMc&%SQ~~~61Jy-*,K]!EI!B1l݈DfH ݮ %ꝏo^e7fzZ0S0J5rV";Tw2-퐈I0K Y­ s~d0Z%zz\ᕋ`BJ1E^PLW/ I/)KBK#bB!<bk>WdLwAzĹ%~81$4UŮE{öފNbxR~WV־q襄q kܿYØ#VfupR1o 'Eʊֳ]o]"ZC@ am ࠟfOph/m3ϰkk׭rM|}|%za$o9wp?\nˑ!=1ӑ#pa]v8&5=}B6@ !BaQ^;Jj"޾? 7z'Դ4xZw?^u~o~}QׯO9JaԳ$8$8K>Ҁ!Ds@?$e88꾮ZN?0П֘5;tLYb62Ebq"H/(Fe\ܲs+1[>3axmEF;_;G!Jiia]v(Fgǻq3Gk%~B!ÏTU;~̞wݮsƍ?l.^J~33(|q"AĩJ\`ࠖKpg.+B!w1,J?1{N-w]!t(Gխj+޸ *!B!CfsDt͡On *owN_5i;vk3BXצwvnNɮGlXqI^UB!B1l7KW:}xb8SI9k]dy]uv1.(qEB!B(b"TF^ୣ[KKbHLYԸ.Q[!B!bB!B&B!B( !Bص&{w!ڠT"%GJEX%HQ Vf -8mEHco)bCԄ~?{罃{oy(P @2 e0W~dy|6@1$g7-Xmˬ:ղO[ 2)/ZņZ ,zTOm"=7<5sP|gP%GqPqԥb k&ZCյGD'LrNﻔ O܋dKٶ.l5_}EPLa-BuPt+3]]# ,yTw#qdNg߭x}XT+uvv<;cG(ýɶsѩ+L[ ~6ۍOrH 9;21A&:ԏ:b kP1 KxŦXatlTgO; RZ([^'v{B6 3۽f:l2 yѩyPU34!Nl=¾O@1jDqyܐE Z}a㎶5uaab/@_Xk]Tѱ>$ y{ ([dzF0oSGn1F т$1=՚dpB;e08T\ ?aaaƥN\WwsvdnAI47q4g}<#D: :;.1D $R7 'WAՁjye%'Nrԙg8ݿ\dUÑFXfde.]!SYSi qgLߞʚ*19%˽ӧ 'a zryΚZܓ'e5VZRXR$dN{nK6h5Gaaa1lQZ1 rᲄ#,AX%g-BQVij4 ]{ \ :;J">$3ӕF777岶"+tjڸypDK{=Kn^n@@31s,)1ԩojPZ.A N kAۥB*s΅˾r)NY@p.d%e daaX S k(br'16CHbn՚p-*8ڬ\rL&S#g*T. NHx y­:$#gFR_x,$ ຤в raqǎI+܆Y,/K5z^ {gdYv󒰆C΁0 0 ðae.:%$3C(nށΗo3|zkSD4wXӽnLz 4Z%ēKWAݞV/OD;ԩ`*B]Q9v|(Up]FΜI~lnj6v`:u}eKKYTCcf7J5aaa1,@ٟnp F|i JHH0\5 jh_TnV^|U-NߞskrCa@ +7*RC\< )&N|oKk_ȟ4xY1R .RAb2.&91/zPh<ӉX -%J`l| ޼u 4-+#SiEJia-:c裡2%1ΰ29iAj/1}^RP{_CaawgJ%]TTd6[ {|ҽu]u{W?{'5u).ʃW\ccbp!lXZ8yEH j* =H S1vP\.\s~fA&hB4{o- \(= 1n=&1z֖ꭳIGV "Z 1İyIYC! aaNAb0(a@}K'e7,?;ɓt$tP2(6B-]0€VHb4C@?P_N)=rP M>tÌвpdl xfL2Тݭd)%e ?v 0 0 a/]fApo^H~5|7`2'{^%=ғPF܂`l~R1'/&+y)ͮbefMAn9pZKhr,aT[jQ]DcF zc 7-c1,e^RPaaaF-MyaPuw!1a󭋾PJ6NKΈwI[Bu4 A0İBK.!9GFVX\fde[զW\|6Tp]>XؔPXJ$mm a XʙNiurg)@ HX K5{0 0 0ZŰ.ͩe P{kLPB>ީII+rWhZu'@CLzITpFܲ1Fw 0 BH/Drq:9L,]F>T҅ ^E4NVxxxs[ "#c_767ѐ2~*B+B6k:ء//c^ ]q=R K5Y3 0 0#İ2dt+vb_?aܞ%q19(z{zz*x+˅8Ed@cMLGF%1#lBae4%.C#p"? 쯪 B%2~S„lz4>Y[F͎2J_ rLԢR-^ʏ3/k8? 0 0 o w' =(q6'Zm'~mPQ>ecJE|r;]=7)':Nƾ`o-of3>]K~*.;CGL ORVCwEug?ӡBQڸT_0/k8? 0 0 o>3(1%;Řp\om{Qo"ΑwMEaDXOoA T؊B%":AGRX,.".N~'7A" jl/m2 { if (loc.dataset.inited === 'true') return; loc.dataset.inited = 'true'; const container_id = loc.id; const dl = loc.querySelector('dl'); dl.querySelectorAll('dt').forEach(dt => { dt.innerHTML = ''; dt.style.display = 'inline'; }); dl.addEventListener('click', handle_timestamp_click.bind(null, container_id)); }); } function handle_timestamp_click(container_id, e) { if (e.target.tagName.toUpperCase() === 'TIME') { const timestamp = e.target.textContent; if (timestamp) { const [minutes, seconds] = timestamp.split(':'); const total_seconds = parseInt(minutes) * 60 + parseInt(seconds); const video = document.querySelector('#' + container_id + ' video'); video.currentTime = total_seconds; video.play(); } } } init_timestamps(); document.addEventListener('DOMContentloaded', init_timestamps); })(); kitty-0.41.1/docs/_templates/0000775000175000017510000000000014773370543015374 5ustar nileshnileshkitty-0.41.1/docs/_templates/base.html0000664000175000017510000000074514773370543017202 0ustar nileshnilesh{% extends "!base.html" %} {% block extrahead %} {{ super() }} {%- if analytics_id %} {% endif -%} {% endblock %} kitty-0.41.1/docs/actions.rst0000664000175000017510000000060414773370543015431 0ustar nileshnileshMappable actions ----------------------- .. highlight:: conf The actions described below can be mapped to any key press or mouse action using the ``map`` and ``mouse_map`` directives in :file:`kitty.conf`. For configuration examples, see the default shortcut links for each action. To read about keyboard mapping in more detail, see :doc:`mapping`. .. include:: /generated/actions.rst kitty-0.41.1/docs/basic.rst0000664000175000017510000001674114773370543015063 0ustar nileshnileshTabs and Windows ------------------- |kitty| is capable of running multiple programs organized into tabs and windows. The top level of organization is the :term:`OS window `. Each OS window consists of one or more :term:`tabs `. Each tab consists of one or more :term:`kitty windows `. The kitty windows can be arranged in multiple different :term:`layouts `, like windows are organized in a tiling window manager. The keyboard controls (which are :ref:`all customizable `) for tabs and windows are: Scrolling ~~~~~~~~~~~~~~ ========================= ======================= Action Shortcut ========================= ======================= Line up :sc:`scroll_line_up` (also :kbd:`⌥+⌘+⇞` and :kbd:`⌘+↑` on macOS) Line down :sc:`scroll_line_down` (also :kbd:`⌥+⌘+⇟` and :kbd:`⌘+↓` on macOS) Page up :sc:`scroll_page_up` (also :kbd:`⌘+⇞` on macOS) Page down :sc:`scroll_page_down` (also :kbd:`⌘+⇟` on macOS) Top :sc:`scroll_home` (also :kbd:`⌘+↖` on macOS) Bottom :sc:`scroll_end` (also :kbd:`⌘+↘` on macOS) Previous shell prompt :sc:`scroll_to_previous_prompt` (see :ref:`shell_integration`) Next shell prompt :sc:`scroll_to_next_prompt` (see :ref:`shell_integration`) Browse scrollback in less :sc:`show_scrollback` Browse last cmd output :sc:`show_last_command_output` (see :ref:`shell_integration`) ========================= ======================= The scroll actions only take effect when the terminal is in the main screen. When the alternate screen is active (for example when using a full screen program like an editor) the key events are instead passed to program running in the terminal. Tabs ~~~~~~~~~~~ ======================== ======================= Action Shortcut ======================== ======================= New tab :sc:`new_tab` (also :kbd:`⌘+t` on macOS) Close tab :sc:`close_tab` (also :kbd:`⌘+w` on macOS) Next tab :sc:`next_tab` (also :kbd:`⇧+⌃+⇥` and :kbd:`⇧+⌘+]` on macOS) Previous tab :sc:`previous_tab` (also :kbd:`⇧+⌃+⇥` and :kbd:`⇧+⌘+[` on macOS) Next layout :sc:`next_layout` Move tab forward :sc:`move_tab_forward` Move tab backward :sc:`move_tab_backward` Set tab title :sc:`set_tab_title` (also :kbd:`⇧+⌘+i` on macOS) ======================== ======================= Windows ~~~~~~~~~~~~~~~~~~ ======================== ======================= Action Shortcut ======================== ======================= New window :sc:`new_window` (also :kbd:`⌘+↩` on macOS) New OS window :sc:`new_os_window` (also :kbd:`⌘+n` on macOS) Close window :sc:`close_window` (also :kbd:`⇧+⌘+d` on macOS) Resize window :sc:`start_resizing_window` (also :kbd:`⌘+r` on macOS) Next window :sc:`next_window` Previous window :sc:`previous_window` Move window forward :sc:`move_window_forward` Move window backward :sc:`move_window_backward` Move window to top :sc:`move_window_to_top` Visually focus window :sc:`focus_visible_window` Visually swap window :sc:`swap_with_window` Focus specific window :sc:`first_window`, :sc:`second_window` ... :sc:`tenth_window` (also :kbd:`⌘+1`, :kbd:`⌘+2` ... :kbd:`⌘+9` on macOS) (clockwise from the top-left) ======================== ======================= Additionally, you can define shortcuts in :file:`kitty.conf` to focus neighboring windows and move windows around (similar to window movement in :program:`vim`):: map ctrl+left neighboring_window left map shift+left move_window right map ctrl+down neighboring_window down map shift+down move_window up ... You can also define a shortcut to switch to the previously active window:: map ctrl+p nth_window -1 :ac:`nth_window` will focus the nth window for positive numbers (starting from zero) and the previously active windows for negative numbers. To switch to the nth OS window, you can define :ac:`nth_os_window`. Only positive numbers are accepted, starting from one. .. _detach_window: You can define shortcuts to detach the current window and move it to another tab or another OS window:: # moves the window into a new OS window map ctrl+f2 detach_window # moves the window into a new tab map ctrl+f3 detach_window new-tab # moves the window into the previously active tab map ctrl+f3 detach_window tab-prev # moves the window into the tab at the left of the active tab map ctrl+f3 detach_window tab-left # moves the window into a new tab created to the left of the active tab map ctrl+f3 detach_window new-tab-left # asks which tab to move the window into map ctrl+f4 detach_window ask Similarly, you can detach the current tab, with:: # moves the tab into a new OS window map ctrl+f2 detach_tab # asks which OS Window to move the tab into map ctrl+f4 detach_tab ask Finally, you can define a shortcut to close all windows in a tab other than the currently active window:: map f9 close_other_windows_in_tab Other keyboard shortcuts ---------------------------------- The full list of actions that can be mapped to key presses is available :doc:`here `. To learn how to do more sophisticated keyboard mappings, such as modal mappings, per application mappings, etc. see :doc:`mapping`. ================================== ======================= Action Shortcut ================================== ======================= Show this help :sc:`show_kitty_doc` Copy to clipboard :sc:`copy_to_clipboard` (also :kbd:`⌘+c` on macOS) Paste from clipboard :sc:`paste_from_clipboard` (also :kbd:`⌘+v` on macOS) Paste from selection :sc:`paste_from_selection` Pass selection to program :sc:`pass_selection_to_program` Increase font size :sc:`increase_font_size` (also :kbd:`⌘++` on macOS) Decrease font size :sc:`decrease_font_size` (also :kbd:`⌘+-` on macOS) Restore font size :sc:`reset_font_size` (also :kbd:`⌘+0` on macOS) Toggle fullscreen :sc:`toggle_fullscreen` (also :kbd:`⌃+⌘+f` on macOS) Toggle maximized :sc:`toggle_maximized` Input Unicode character :sc:`input_unicode_character` (also :kbd:`⌃+⌘+space` on macOS) Open URL in web browser :sc:`open_url` Reset the terminal :sc:`reset_terminal` (also :kbd:`⌥+⌘+r` on macOS) Edit :file:`kitty.conf` :sc:`edit_config_file` (also :kbd:`⌘+,` on macOS) Reload :file:`kitty.conf` :sc:`reload_config_file` (also :kbd:`⌃+⌘+,` on macOS) Debug :file:`kitty.conf` :sc:`debug_config` (also :kbd:`⌥+⌘+,` on macOS) Open a |kitty| shell :sc:`kitty_shell` Increase background opacity :sc:`increase_background_opacity` Decrease background opacity :sc:`decrease_background_opacity` Full background opacity :sc:`full_background_opacity` Reset background opacity :sc:`reset_background_opacity` ================================== ======================= kitty-0.41.1/docs/binary.rst0000664000175000017510000001137114773370543015260 0ustar nileshnileshInstall kitty ======================== Binary install ---------------- .. highlight:: sh You can install pre-built binaries of |kitty| if you are on macOS or Linux using the following simple command: .. code-block:: sh _kitty_install_cmd The binaries will be installed in the standard location for your OS, :file:`/Applications/kitty.app` on macOS and :file:`~/.local/kitty.app` on Linux. The installer only touches files in that directory. To update kitty, simply re-run the command. .. warning:: **Do not** copy the kitty binary out of the installation folder. If you want to add it to your :envvar:`PATH`, create a symlink in :file:`~/.local/bin` or :file:`/usr/bin` or wherever. You should create a symlink for the :file:`kitten` binary as well. Whichever folder you choose to create the symlink in should be in the **systemwide** PATH, a folder added to the PATH in your shell rc files will not work when running kitty from your desktop environment. Manually installing --------------------- If something goes wrong or you simply do not want to run the installer, you can manually download and install |kitty| from the `GitHub releases page `__. If you are on macOS, download the :file:`.dmg` and install as normal. If you are on Linux, download the tarball and extract it into a directory. The |kitty| executable will be in the :file:`bin` sub-directory. Desktop integration on Linux -------------------------------- If you want the kitty icon to appear in the taskbar and an entry for it to be present in the menus, you will need to install the :file:`kitty.desktop` file. The details of the following procedure may need to be adjusted for your particular desktop, but it should work for most major desktop environments. .. code-block:: sh # Create symbolic links to add kitty and kitten to PATH (assuming ~/.local/bin is in # your system-wide PATH) ln -sf ~/.local/kitty.app/bin/kitty ~/.local/kitty.app/bin/kitten ~/.local/bin/ # Place the kitty.desktop file somewhere it can be found by the OS cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications/ # If you want to open text files and images in kitty via your file manager also add the kitty-open.desktop file cp ~/.local/kitty.app/share/applications/kitty-open.desktop ~/.local/share/applications/ # Update the paths to the kitty and its icon in the kitty desktop file(s) sed -i "s|Icon=kitty|Icon=$(readlink -f ~)/.local/kitty.app/share/icons/hicolor/256x256/apps/kitty.png|g" ~/.local/share/applications/kitty*.desktop sed -i "s|Exec=kitty|Exec=$(readlink -f ~)/.local/kitty.app/bin/kitty|g" ~/.local/share/applications/kitty*.desktop # Make xdg-terminal-exec (and hence desktop environments that support it use kitty) echo 'kitty.desktop' > ~/.config/xdg-terminals.list .. note:: In :file:`kitty-open.desktop`, kitty is registered to handle some supported MIME types. This will cause kitty to take precedence on some systems where the default apps are not explicitly set. For example, if you expect to use other GUI file managers to open dir paths when using commands such as :program:`xdg-open`, you should configure the default opener for the MIME type ``inode/directory``:: xdg-mime default org.kde.dolphin.desktop inode/directory .. note:: If you use the venerable `stow `__ command to manage your manual installations, the following takes care of the above for you (use with :code:`dest=~/.local/stow`):: cd ~/.local/stow stow -v kitty.app Customizing the installation -------------------------------- .. _nightly: * You can install the latest nightly kitty build with ``installer``: .. code-block:: sh _kitty_install_cmd \ installer=nightly If you want to install it in parallel to the released kitty specify a different install locations with ``dest``: .. code-block:: sh _kitty_install_cmd \ installer=nightly dest=/some/other/location * You can specify a specific version to install, with: .. code-block:: sh _kitty_install_cmd \ installer=version-0.35.2 * You can tell the installer not to launch |kitty| after installing it with ``launch=n``: .. code-block:: sh _kitty_install_cmd \ launch=n * You can use a previously downloaded dmg/tarball, with ``installer``: .. code-block:: sh _kitty_install_cmd \ installer=/path/to/dmg or tarball Uninstalling ---------------- All the installer does is copy the kitty files into the install directory. To uninstall, simply delete that directory. Building from source ------------------------ |kitty| is easy to build from source, follow the :doc:`instructions `. kitty-0.41.1/docs/build.rst0000664000175000017510000001703014773370543015071 0ustar nileshnileshBuild from source ================== .. image:: https://github.com/kovidgoyal/kitty/workflows/CI/badge.svg :alt: Build status :target: https://github.com/kovidgoyal/kitty/actions?query=workflow%3ACI .. note:: If you just want to test the latest changes to kitty, you don't need to build from source. Instead install the :ref:`latest nightly build `. .. highlight:: sh |kitty| is designed to run from source, for easy hack-ability. All you need to get started is a C compiler and the `go compiler `__ (on Linux, the :ref:`X11 development libraries ` as well). After installing those, run the following commands:: git clone https://github.com/kovidgoyal/kitty.git && cd kitty ./dev.sh build That's it, kitty will be built from source, magically. You can run it as :file:`kitty/launcher/kitty`. This works, because the :code:`./dev.sh build` command downloads all the major dependencies of kitty as pre-built binaries for your platform and builds kitty to use these rather than system libraries. The few required system libraries are X11 and DBUS on Linux. If you make changes to kitty code, simply re-run :code:`./dev.sh build` to build kitty with your changes. .. note:: If you plan to run kitty from source long-term, there are a couple of caveats to be aware of. You should occasionally run ``./dev.sh deps`` to have the dependencies re-downloaded as they are updated periodically. Also, the built kitty executable assumes it will find source in whatever directory you first ran :code:`./dev.sh build` in. If you move/rename the directory, run :code:`make clean && ./dev.sh build`. You should also create symlinks to the :file:`kitty` and :file:`kitten` binaries from somewhere in your PATH so that they can be conveniently launched. .. note:: On macOS, you can use :file:`kitty/launcher/kitty.app` to run kitty as well, but note that this is an unsigned kitty.app so some functionality such as notifications will not work as Apple disallows this. If you need this functionality, you can try signing the built :file:`kitty.app` with a self signed certificate, see for example, `here `__. Building in debug mode ^^^^^^^^^^^^^^^^^^^^^^^^^^ The following will build with debug symbols:: ./dev.sh build --debug To build with sanitizers and debug symbols:: ./dev.sh build --debug --sanitize For more help on the various options supported by the build script:: ./dev.sh build -h Building the documentation ------------------------------------- To have the kitty documentation available locally, run:: ./dev.sh deps -for-docs && ./dev.sh docs To develop the docs, with live reloading, use:: ./dev.sh deps -for-docs && ./dev.sh docs -live-reload Dependencies ---------------- These dependencies are needed when building against system libraries only. Run-time dependencies: * ``python`` >= 3.8 * ``harfbuzz`` >= 2.2.0 * ``zlib`` * ``libpng`` * ``liblcms2`` * ``libxxhash`` * ``openssl`` * ``pixman`` (not needed on macOS) * ``cairo`` (not needed on macOS) * ``freetype`` (not needed on macOS) * ``fontconfig`` (not needed on macOS) * ``libcanberra`` (not needed on macOS) * ``libsystemd`` (optional, not needed on non systemd systems) * ``ImageMagick`` (optional, needed to display uncommon image formats in the terminal) Build-time dependencies: * ``gcc`` or ``clang`` * ``simde`` * ``go`` >= _build_go_version (see :file:`go.mod` for go packages used during building) * ``pkg-config`` * Symbols NERD Font Mono either installed system-wide or placed in :file:`fonts/SymbolsNerdFontMono-Regular.ttf` * For building on Linux in addition to the above dependencies you might also need to install the following packages, if they are not already installed by your distro: - ``liblcms2-dev`` - ``libfontconfig-dev`` - ``libssl-dev`` - ``libpython3-dev`` - ``libxxhash-dev`` - ``libsimde-dev`` - ``libcairo2-dev`` .. _x11-dev-libs: Also, the X11 development libraries: - ``libdbus-1-dev`` - ``libxcursor-dev`` - ``libxrandr-dev`` - ``libxi-dev`` - ``libxinerama-dev`` - ``libgl1-mesa-dev`` - ``libxkbcommon-x11-dev`` - ``libfontconfig-dev`` - ``libx11-xcb-dev`` Build and run from source with Nix ------------------------------------------- On NixOS or any other Linux or macOS system with the Nix package manager installed, execute `nix-shell `__ to create the correct environment to build kitty or use ``nix-shell --pure`` instead to eliminate most of the influence of the outside system, e.g. globally installed packages. ``nix-shell`` will automatically fetch all required dependencies and make them available in the newly spawned shell. Then proceed with ``make`` or ``make app`` according to the platform specific instructions above. .. _packagers: Notes for Linux/macOS packagers ---------------------------------- The released |kitty| source code is available as a `tarball`_ from `the GitHub releases page `__. While |kitty| does use Python, it is not a traditional Python package, so please do not install it in site-packages. Instead run:: make linux-package This will install |kitty| into the directory :file:`linux-package`. You can run |kitty| with :file:`linux-package/bin/kitty`. All the files needed to run kitty will be in :file:`linux-package/lib/kitty`. The terminfo file will be installed into :file:`linux-package/share/terminfo`. Simply copy these files into :file:`/usr` to install |kitty|. In other words, :file:`linux-package` is the staging area into which |kitty| is installed. You can choose a different staging area, by passing the ``--prefix`` argument to :file:`setup.py`. You should probably split |kitty| into three packages: :code:`kitty-terminfo` Installs the terminfo file :code:`kitty-shell-integration` Installs the shell integration scripts (the contents of the shell-integration directory in the kitty source code), probably to :file:`/usr/share/kitty/shell-integration` :code:`kitty` Installs the main program This allows users to install the terminfo and shell integration files on servers into which they ssh, without needing to install all of |kitty|. The shell integration files **must** still be present in :file:`lib/kitty/shell-integration` when installing the kitty main package as the kitty program expects to find them there. .. note:: You need a couple of extra dependencies to build linux-package. :file:`tic` to compile terminfo files, usually found in the development package of :file:`ncurses`. Also, if you are building from a git checkout instead of the released source code tarball, you will need to install the dependencies from :file:`docs/requirements.txt` to build the kitty documentation. They can be installed most easily with ``python -m pip -r docs/requirements.txt``. This applies to creating packages for |kitty| for macOS package managers such as Homebrew or MacPorts as well. Cross compilation ------------------- While cross compilation is neither officially supported, nor recommended, as it means the test suite cannot be run for the cross compiled build, there is some support for cross compilation. Basically, run:: make prepare-for-cross-compile Then setup the cross compile environment (CC, CFLAGS, PATH, etc.) and run:: make cross-compile This will create the cross compiled build in the :file:`linux-package` directory. kitty-0.41.1/docs/changelog.rst0000664000175000017510000055214114773370543015730 0ustar nileshnileshChangelog ============== |kitty| is a feature-rich, cross-platform, *fast*, GPU based terminal. To update |kitty|, :doc:`follow the instructions `. .. recent major features {{{ Recent major new features --------------------------- Multiple sized text [0.40] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ kitty is the first major terminal to introduce the concept of multiple sized text. Terminal programs running in kitty can now opt-in to use and display text in multiple font sizes both larger and smaller than the base font size. This is done in a backwards compatible, opt-in way that does not affect how traditional terminal programs work at all. For details on the new feature and how to use it, see :doc:`text-sizing-protocol`. Cursor trails [0.37] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Show an animated trail when the text cursor makes large jumps making it easy to follow cursor movements. Inspired by the similar feature in neovide, but works with terminal multiplexers and kitty windows as well. See :pull:`the pull request <7970>` for a demonstration video. This feature is optional and must be turned on by the :opt:`cursor_trail` option in :file:`kitty.conf`. Variable font support [0.36] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Terminal aficionados spend all day staring at text, so getting text rendering just right is very important. In that spirit, kitty now supports `OpenType Variable fonts `__. These allow precise customisation of font characteristics, such as weight and spacing. Not only that, kitty now has a new :doc:`choose-fonts ` kitten that provides a UI for choosing fonts with support for font features, variable fonts and previews of how the font will look. This is in addition to its existing best-in-class font customization abilities, such as: :opt:`symbol_map`, :opt:`text_composition_strategy`, :opt:`font_features` and :opt:`modify_font`. kitty knows text rendering is important, and goes the extra mile for it. Desktop notifications [0.36] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |kitty| now has a :doc:`notify ` kitten that can be used to display desktop notifications from the command line, even over SSH. It has support for icons, buttons, updating notifications, waiting till the notification is closed, etc. The underlying :doc:`desktop-notifications` protocol has been expanded to support all these features. Wayland goodies [0.34] ~~~~~~~~~~~~~~~~~~~~~~~ Wayland users should rejoice as kitty now comes with major Wayland quality-of-life improvements: * Draw GPU accelerated :doc:`desktop panels and background ` running arbitrary terminal programs. For example, run `btop `__ as your desktop background * Background blur for transparent windows is now supported under KDE using a custom KDE specific protocol * The kitty window decorations in GNOME are now fully functional with buttons and they follow system dark/light mode automatically * kitty now supports fractional scaling in Wayland which means pixel perfect rendering when you use a fractional scale with no wasted performance on resizing an overdrawn pixmap in the compositor With this release kitty's Wayland support is now on par with X11, provided you use a decent Wayland compositor. Cheetah speed 🐆 [0.33] ~~~~~~~~~~~~~~~~~~~~~~~~~ kitty has grown up and become a cheetah. It now parses data it receives in parallel :iss:`using SIMD vector CPU instructions <7005>` for a 2x speedup in benchmarks and a 10%-50% real world speedup depending on workload. There is a new benchmarking kitten ``kitten __benchmark__`` that can be used to measure terminal throughput. There is also :ref:`a table ` showing kitty is much faster than other terminal emulators based on the benchmark kitten. While kitty was already so fast that its performance was never a bottleneck, this improvement makes it even faster and more importantly reduces the energy consumption to do the same tasks. .. }}} Detailed list of changes ------------------------------------- 0.41.1 [2025-04-03] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Fix a regression in the previous release that caused rendering of emoji using the VS16 variation selector to fail with some fonts (:iss:`8495`) - Fix a regression in 0.40.0 that caused tab bar margins to not be properly blanked when the tab bar is at the bottom (:iss:`8494`) - Wayland: panel kitten: Fix incorrect initial font size on compositors such as Hyprland that set scale late in the window creation process (:iss:`8496`) - Fix a regression in 0.40.1 that caused hyperlink underline on hover to remain on screen when the screen is scrolled 0.41.0 [2025-03-29] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new mode of operation for :opt:`text_fg_override_threshold` to override foreground colors so as to maintain a minimum contrast between foreground and background text colors. Works in a perceptual color space for best color accuracy (:pull:`8420`) - A 15% improvement in throughput when processing text thanks to using a multi-stage table for Unicode property lookups - :ref:`kitty +open `: Ask for confirmation by default when running executables to work around some badly designed programs that try to open links in documents that point to executable files. Can be overridden by specifying your own :file:`launch-actions.conf`. - Fix a regression in version 0.40.0 causing a crash when the underline thickness of the font is zero (:iss:`8443`) - Fix a regression in version 0.40.0 causing a hang on resizing with a wide character at the right edge of a line that needs to be moved onto the next line (:iss:`8464`) - Fix a regression in 0.40.1 that caused copying to clipboard via OSC 52 from applications that don't specify a destination in the escape code not working (:iss:`8459`) - Wayland: Fix a regression in the previous release that caused crashes on compositors that don't support the xdg-toplevel-icon protocol and the user has set a custom kitty icon (:iss:`8471`) 0.40.1 [2025-03-18] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Do not count background processes by default for :opt:`confirm_os_window_close` (:iss:`8358`) - A new option :opt:`clear_selection_on_clipboard_loss` to clear selections when they no longer reflect the contents of the clipboard - Fix a regression in the previous release that caused empty lines to be skipped when copying text from a selection (:iss:`8435`) - Fix flickering of hyperlink underline when client program continuously redraws on mouse movement (:iss:`8414`) - Wayland: Allow overriding the kitty OS Window icon on compositors that implement the xdg-toplevel-icon protocol - macOS: When the program running in kitty reports progress information for a task, show a progress bar on the kitty dock icon - macOS: Fix a regression causing a crash when using :opt:`focus_follows_mouse` (:iss:`8437`) - OSC 52: Fix specifying both clipboard and primary in OSC 52 requests not supported 0.40.0 [2025-03-08] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :doc:`Allow terminal programs to use text in different font sizes ` (:iss:`8226`) - When rendering underlines add gaps around text descenders (parts of the text that overlap with the underline). Controlled by the new option :opt:`underline_exclusion` (:iss:`8226`) - Finally fix the issue of text-width mismatches that has been plaguing the terminal ecosystem for decades by allowing terminal programs to specify how many cells to render a piece of text in (:iss:`8226`) - **Behavior change**: The :opt:`notify_on_cmd_finish` option now uses OS Window visibility instead of focus state when set to ``invisible`` on platforms that support querying OS window visibility (:iss:`8320`) - launch: Add options :option:`launch --source-window` and :option:`launch --next-to` to allow specifying which window is used as the data source and destination location independently of the currently active window (:iss:`8295`) - Linux: Add support for `COLRv1 `__ fonts. These are typically emoji fonts that use vector images for emoji - Add support for the octant box-drawing characters - Speed up rendering of box drawing characters by moving the implementation to native code - When confirming if a window should be closed consider it active if it has running background processes (:iss:`8358`) - Remote control: `kitten @ scroll-window`: Allow scrolling to previous/next prompt - macOS: Fix fallback font rendering for bold/italic text not working for some symbols that are present in the Menlo regular face but not the bold/italic faces (:iss:`8282`) - XTGETTCAP: Fix response invalid for empty string capabilities (:pull:`8304`) - ssh kitten: Fix incorrect copying of data files when using the python interpreter and also fix incorrect hard link detection (:disc:`8308`) - Fix a regression in the previous release that broke setting of nullable colors - Fix a regression in 0.39.0 that caused a crash on invalid Unicode with a large number of combining characters in a single cell (:iss:`8318`) - Fix ``--hold`` always restoring cursor to block shape instead of respecting the value of :opt:`cursor_shape` (:disc:`8344`) - When dragging in rectangle select mode use a crosshair mouse cursor configurable via :opt:`pointer_shape_when_dragging` - macOS: notify kitten: Fix waiting for result from desktop notification not working (:disc:`8379`) - Wayland: Fix mouse pointer position update not being sent when focus regained (:iss`8397`, :iss:`8398`) - Fix cursor blink animation when :opt:`background_opacity` is less than one (:iss:`8401`) - Wayland: panel kitten: Add a :code:`center` mode for creating panels to ease creation of centered popups in Wayland (:pull:`8411`) 0.39.1 [2025-02-01] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Splits layout: Allow setting the bias of the current split using ``layout_action bias`` (:iss:`8222`) - hints kitten: Workaround for some broken light color themes that make the hints text color too low contrast to read (:iss:`7330`) - Wayland niri: Fix 250ms delay on startup when using scale 1 (:iss:`8236`) - :ref:`Watchers `: Add a new event ``on_color_scheme_preference_change`` (:iss:`8246`) 0.39.0 [2025-01-16] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :doc:`diff kitten `: Automatically use dark/light color scheme based on the color scheme of the parent terminal. Can be controlled via the new :opt:`kitten-diff.color_scheme` option. Note that this is a **default behavior change** (:iss:`8170`) - Allow dynamically generating configuration by running an arbitrary program using the new :code:`geninclude` directive in :file:`kitty.conf` - When a program running in kitty reports progress of a task display it as a percentage in the tab title. Controlled by the :opt:`tab_title_template` option - When mapping a custom kitten allow using shell escaping for the kitten path (:iss:`8178`) - Fix border colors not being changed by auto light/dark themes at startup (:iss:`8180`) - ssh kitten: Fix kitten not being on PATH when SSHing into Debian systems (:iss:`7160`) - diff kitten: Abort when run inside a terminal that does not support the kitty keyboard protocol (:iss:`8185`) - :doc:`query kitten `: Add support for reporting name of the OS the terminal emulator is running on (:iss:`8201`) - macOS: Allow using the Passwords app to autofill passwords via the Edit->Autofill menu mimicking other macOS applications (:pull:`8195`) - macOS: Add menu items to the Edit menu to clear the screen and scrollback - Fix the :ac:`clear_terminal scrollback ` action also clearing screen, not just the scrollback - When reloading configuration fix auto color themes not being re-applied (:iss:`8203`) 0.38.1 [2024-12-26] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - macOS: Fix a regression in the previous release that broke rendering of Emoji using the VS16 variation selector (:iss:`8130`) - When automatically changing colors based on OS color preference, first reset all colors to default before applying the new theme so that even colors not specified in the theme are correct (:iss:`8124`) - Graphics: Fix deleted but not freed images without any placements being incorrectly freed on a subsequent delete command (:disc:`8129`) - Graphics: Fix deletion of images by id not working for images with no placements (:disc:`8129`) - Add support for `escape code protocol `__ for notifying applications on dark/light color scheme change - Cursor trails: Fix pure vertical movement sometimes not triggering a trail and holding down a key in nvim causing the trail to be glitchy (:pull:`8152`, :pull:`8153`) - macOS: Fix mouse cursor shape not always being reset to text cursor when mouse re-enters kitty (:iss:`8155`) - clone-in-kitty: Fix :envvar:`KITTY_WINDOW_ID` being cloned and thus having incorrect value (:iss:`8161`) 0.38.0 [2024-12-15] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Allow :ref:`specifying individual color themes ` to use so that kitty changes colors automatically following the OS dark/light mode - :opt:`notify_on_cmd_finish`: Automatically remove notifications when the window gains focus or the next notification is shown. Clearing behavior can be configured (:pull:`8100`) - Discard OSC 9 notifications that start with :code:`4;` because some misguided software is using it for "progress reporting" (:iss:`8011`) - Wayland GNOME: Workaround bug in mutter causing double tap on titlebar to not always work (:iss:`8054`) - clipboard kitten: Fix a bug causing kitten to hang in filter mode when input data size is not divisible by 3 and larger than 8KB (:iss:`8059`) - Wayland: Fix an abort when a client program tries to set an invalid title containing interleaved escape codes and UTF-8 multi-byte characters (:iss:`8067`) - Graphics protocol: Fix delete by number not deleting newest image with the specified number (:iss:`8071`) - Fix dashed and dotted underlines not being drawn at the same y position as straight underlines at all font sizes (:iss:`8074`) - panel kitten: Allow creating floating and on-top panels with arbitrary placement and size on Wayland (:pull:`8068`) - :opt:`remote_control_password`: Fix using a password without any actions not working (:iss:`8082`) - Fix enlarging window when a long line is wrapped between the first line of the scrollback buffer and the screen inserting a spurious newline (:iss:`7033`) - When re-attaching a detached tab preserve internal layout state such as biases and orientations (:iss:`8106`) - hints/unicode_input kittens: Do not lose keypresses that are sent very rapidly via an automation tool immediately after the kitten is launched (:iss:`7089`) 0.37.0 [2024-10-30] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new optional :opt:`text cursor movement animation ` that shows a "trail" following the movement of the cursor making it easy to follow large cursor jumps (:pull:`7970`) - Custom kittens: Add :ref:`a framework ` for easily and securely using remote control from within a kitten's :code:`main()` function - kitten icat: Fix the :option:`kitty +kitten icat --no-trailing-newline` not working when using unicode placeholders (:iss:`7948`) - :opt:`tab_title_template` allow using the 256 terminal colors for formatting (:disc:`7976`) - Fix resizing window when alternate screen is active does not preserve trailing blank output line in the main screen (:iss:`7978`) - Wayland: Fix :opt:`background_opacity` less than one causing flicker on startup when the Wayland compositor supports single pixel buffers (:iss:`7987`) - Fix background image flashing when closing a tab (:iss:`7999`) - When running a kitten that modifies the kitty config file if no config file exists create a commented out default config file and then modify it (:iss:`7991`) 0.36.4 [2024-09-27] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Fix a regression in the previous release that caused window padding to be rendered opaque even when :opt:`background_opacity` is less than 1 (:iss:`7895`) - Wayland GNOME: Fix a crash when using multiple monitors with different scales and starting on or moving to the monitor with lower scale (:iss:`7894`) - macOS: Fix a regression in the previous release that caused junk to be rendered in font previews in the choose fonts kitten and crash on Intel macs (:iss:`7892`) 0.36.3 [2024-09-25] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - The option ``second_transparent_bg`` has been removed and replaced by :opt:`transparent_background_colors` which allows setting up to seven additional colors that will be transparent, with individual opacities per color (:iss:`7646`) - Fix a regression in the previous release that broke use of the ``cd`` command in session files (:iss:`7829`) - macOS: Fix shortcuts that become entries in the global menubar being reported as removed shortcuts in the debug output - macOS: Fix :opt:`macos_option_as_alt` not working when :kbd:`caps lock` is engaged (:iss:`7836`) - Fix a regression when tinting of background images was introduced that caused window borders to have :opt:`background_opacity` applied to them (:iss:`7850`) - Fix a regression that broke writing to the clipboard using the OSC 5522 protocol (:iss:`7858`) - macOS: Fix a regression in the previous release that caused kitty to fail to run after an unclean shutdown/crash when using --single-instance (:iss:`7846`) - kitten @ ls: Fix the ``--self`` flag not working (:iss:`7864`) - Remote control: Fix ``--match state:self`` not working (:disc:`7886`) - Splits layout: Allow setting the ``split_axis`` option to ``auto`` so that all new windows have their split axis chosen automatically unless explicitly specified in the launch command (:iss:`7887`) 0.36.2 [2024-09-06] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Linux: Fix a regression in 0.36.0 that caused font features defined via fontconfig to be ignored (:iss:`7773`) - :ac:`goto_tab`: Allow numbers less than ``-1`` to go to the Nth previously active tab - Wayland: Fix for upcoming explicit sync changes in Wayland compositors breaking kitty (:iss:`7767`) - Remote control: When listening on a UNIX domain socket only allow connections from processes having the same user id (:pull:`7777`) - kitten @: Fix a regression connecting to TCP sockets using plain IP addresses rather than hostnames (:iss:`7794`) - diff kitten: Fix a regression that broke diffing against remote files (:iss:`7797`) 0.36.1 [2024-08-24] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Allow specifying that the :opt:`cursor shape for unfocused windows ` should remain unchanged (:pull:`7728`) - MacOS Intel: Fix a crash in the choose-fonts kitten when displaying previews of variable fonts (:iss:`7734`) - Remote control: Fix a regression causing an escape code to leak when using @ launch with ``--no-response`` over the TTY (:iss:`7752`) - OSC 52: Fix a regression in the previous release that broke handling of invalid base64 encoded data in OSC 52 requests (:iss:`7757`) - macOS: Fix a regression in the previous release that caused :option:`kitty --single-instance` to not work when using :file:`macos-launch-services-cmdline` 0.36.0 [2024-08-17] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Support `OpenType Variable fonts `__ (:iss:`3711`) - A new :doc:`choose-fonts ` kitten that provides a UI with font previews to ease selection of fonts. Also has support for font features and variable fonts - Allow animating the blinking of the cursor. See :opt:`cursor_blink_interval` for how to configure it - Add NERD fonts builtin so that users don't have to install them to use NERD symbols in kitty. The builtin font is used only if the symbols are not available in some system font - launch command: A new :option:`launch --bias` option to adjust the size of newly created windows declaratively (:iss:`7634`) - A new option :opt:`transparent_background_colors` to make a second background color semi-transparent via :opt:`background_opacity`. Useful for things like cursor line highlight in editors (:iss:`7646`) - A new :doc:`notify ` kitten to show desktop notifications from the command line with support for icons, buttons and more. - Desktop notifications protocol: Add support for icons, buttons, closing of notifications, expiry of notifications, updating of notifications and querying if the terminal emulator supports the protocol (:iss:`7657`, :iss:`7658`, :iss:`7659`) - A new option :opt:`filter_notification` to filter out or perform arbitrary actions on desktop notifications based on sophisticated criteria (:iss:`7670`) - A new protocol to allow terminal applications to change colors in the terminal more robustly than with the legacy XTerm protocol (:ref:`color_control`) - Sessions: A new command ``focus_matching_window`` to shift focus to a specific window, useful when creating complex layouts with splits (:disc:`7635`) - Speed up loading of large background images by caching the decoded image data. Also allow using images in JPEG/WEBP/TIFF/GIF/BMP formats in addition to PNG - Wayland: Allow fractional scales less than one (:pull:`7549`) - Wayland: Fix specifying the output name for the panel kitten not working (:iss:`7573`) - icat kitten: Add an option :option:`kitty +kitten icat --no-trailing-newline` to leave the cursor to the right of the image (:iss:`7574`) - Speed up ``kitty --version`` and ``kitty --single-instance`` (for all subsequent instances). They are now the fastest of all terminal emulators with similar functionality - macOS: Fix rendering of the unicode hyphen (U+2010) character when using a font that does not include a glyph for it (:iss:`7525`) - macOS 15: Handle Fn modifier when detecting global shortcuts (:iss:`7582`) - Dispatch any clicks waiting for :opt:`click_interval` on key events (:iss:`7601`) - ``kitten run-shell``: Automatically add the directory containing the kitten binary to PATH if needed. Controlled via the ``--inject-self-onto-path`` option (`disc`:7668`) - Wayland: Fix an issue with mouse selections not being stopped when there are multiple OS windows (:iss:`7381`) - Splits layout: Fix the ``move_to_screen_edge`` action breaking when only a single window is present (:iss:`7621`) - Add support for in-band window resize notifications (:iss:`7642`) - Allow controlling the easing curves used for :opt:`visual_bell_duration` - New special rendering for font symbols useful in drawing commit graphs (:pull:`7681`) - diff kitten: Add bindings to jump to next and previous file (:pull:`7683`) - Wayland GNOME: Fix the font size in the OS Window title bar changing with the size of the text in the window (:disc:`7677`) - Wayland GNOME: Fix a small rendering artifact when docking a window at a screen edge or maximizing it (:iss:`7701`) - When :opt:`shell` is set to ``.`` respect the SHELL environment variable in the environment in which kitty is launched (:pull:`7714`) - macOS: Bump the minimum required macOS version to Catalina released five years ago. - Fix a regression in :opt:`notify_on_cmd_finish` that caused notifications to appear for every command after the first (:iss:`7725`) 0.35.2 [2024-06-22] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new option, :opt:`window_logo_scale` to specify how window logos are scaled with respect to the size of the window containing the logo (:pull:`7534`) - A new option, :opt:`cursor_shape_unfocused` to specify the shape of the text cursor in unfocused OS windows (:pull:`7544`) - Remote control: Fix empty password not working (:iss:`7538`) - Wayland: Fix regression in 0.34.0 causing flickering on window resize on NVIDIA drivers (:iss:`7493`) - Wayland labwc: Fix kitty timing out waiting for compositor to quit fucking around with scales on labwc (:iss:`7540`) - Fix :opt:`scrollback_indicator_opacity` not actually controlling the opacity (:iss:`7557`) - URL detection: Fix IPv6 hostnames breaking URL detection (:iss:`7565`) 0.35.1 [2024-05-31] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Wayland: Fix a regression in 0.34 that caused the tab bar to not render in second and subsequent OS Windows under Hyprland (:iss:`7413`) - Fix a regression in the previous release that caused horizontal scrolling via touchpad in fullscreen applications to be reversed on non-Wayland platforms (:iss:`7475`, :iss:`7481`) - Fix a regression in the previous release causing an error when setting background_opacity to zero (:iss:`7483`) - Image display: Fix cursor movement and image hit region incorrect for image placements that specify only a number of rows or columns to display in (:iss:`7479`) 0.35.0 [2024-05-25] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - kitten @ run: A new remote control command to run a process on the machine kitty is running on and get its output (:iss:`7429`) - :opt:`notify_on_cmd_finish`: Show the actual command that was finished (:iss:`7420`) - hints kitten: Allow clicking on matched text to select it in addition to typing the hint - Shell integration: Make the currently executing cmdline available as a window variable in kitty - :opt:`paste_actions`: Fix ``replace-newline`` not working with ``confirm`` (:iss:`7374`) - Graphics: Fix aspect ratio of images not being preserved when only a single dimension of the destination rectangle is specified (:iss:`7380`) - :ac:`focus_visible_window`: Fix selecting with mouse click leaving keyboard in unusable state (:iss:`7390`) - Wayland: Fix infinite loop causing bad performance when using IME via fcitx5 due to a change in fcitx5 (:iss:`7396`) - Desktop notifications protocol: Add support for specifying urgency - Improve rendering of Unicode shade character to avoid Moire patterns (:pull:`7401`) - kitten @ send-key: Fix some keys being sent in kitty keyboard protocol encoding when not using socket for remote control - Dont clear selections on erase in screen commands unless the erased region intersects a selection (:iss:`7408`) - Wayland: save energy by not rendering "suspended" windows on compositors that support that - Allow more types of alignment for :opt:`placement_strategy` (:pull:`7419`) - Add some more box-drawing characters from the "Geometric shapes" Unicode block (:iss:`7433`) - Linux: Run all child processes in their own systemd scope to prevent the OOM killer from harvesting kitty when a child process misbehaves (:iss:`7427`) - Mouse reporting: Fix horizontal scroll events inverted (:iss:`7439`) - Remote control: @ action: Fix some actions being performed on the active window instead of the matched window (:iss:`7438`) - Scrolling with mouse wheel when a selection is active should update the selection (:iss:`7453`) - Fix kitten @ set-background-opacity limited to min opacity of 0.1 instead of 0 (:iss:`7463`) - launch --hold: Fix hold not working if kernel signals process group with SIGINT (:iss:`7466`) - macOS: Fix --start-as=fullscreen not working when another window is already fullscreen (:iss:`7448`) - Add option :option:`kitten @ detach-window --stay-in-tab` to keep focus in the currently active tab when moving windows (:iss:`7468`) - macOS: Fix changing window chrome/colors while in traditional fullscreen causing the titlebar to become visible (:iss:`7469`) 0.34.1 [2024-04-19] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Wayland KDE: Fix window background blur not adapting when window is grown. Also fix turning it on and off not working. (:iss:`7351`) - Wayland GNOME: Draw the titlebar buttons without using a font (:iss:`7349`) - Fix a regression in the previous release that caused incorrect font selection when using variable fonts on Linux (:iss:`7361`) 0.34.0 [2024-04-15] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Wayland: :doc:`panel kitten `: Add support for drawing desktop background and bars using the panel kitten for all compositors that support the `requisite Wayland protocol `__ which is practically speaking all of them but GNOME (:pull:`2590`) - Show a small :opt:`scrollback indicator ` along the right window edge when viewing the scrollback to keep track of scroll position (:iss:`2502`) - Wayland: Support fractional scales so that there is no wasted drawing at larger scale followed by resizing in the compositor - Wayland KDE: Support :opt:`background_blur` - Wayland GNOME: The window titlebar now has buttons to minimize/maximize/close the window - Wayland GNOME: The window titlebar color now follows the system light/dark color scheme preference, see :opt:`wayland_titlebar_color` - Wayland KDE: Fix mouse cursor hiding not working in Plasma 6 (:iss:`7265`) - Wayland IME: Fix a bug with handling synthetic keypresses generated by ZMK keyboard + fcitx (:pull:`7283`) - A new option :opt:`terminfo_type` to allow passing the terminfo database embedded into the :envvar:`TERMINFO` env var directly instead of via a file - Mouse reporting: Fix drag release event outside the window not being reported in legacy mouse reporting modes (:iss:`7244`) - macOS: Fix a regression in the previous release that broke rendering of some symbols on some systems (:iss:`7249`) - Fix handling of tab character when cursor is at end of line and wrapping is enabled (:iss:`7250`) - Splits layout: Fix :ac:`move_window_forward` not working (:iss:`7264`) - macOS: Fix an abort due to an assertion when a program tries to set an invalid window title (:iss:`7271`) - fish shell integration: Fix clicking at the prompt causing autosuggestions to be accepted, needs fish >= 3.8.0 (:iss:`7168`) - Linux: Fix for a regression in 0.32.0 that caused some CJK fonts to not render glyphs (:iss:`7263`) - Wayland: Support preferred integer scales - Wayland: A new option :opt:`wayland_enable_ime` to turn off Input Method Extensions which add latency and create bugs - Wayland: Fix :opt:`hide_window_decorations` not working on non GNOME desktops - When asking for quit confirmation because of a running program, mention the program name (:iss:`7331`) - Fix flickering of prompt during window resize (:iss:`7324`) 0.33.1 [2024-03-21] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Fix a regression in the previous release that caused requesting data from the clipboard via OSC 52 to instead return data from the primary selection (:iss:`7213`) - Splits layout: Allow resizing until one of the halves in a split is minimally sized (:iss:`7220`) - macOS: Fix text rendered with fallback fonts not respecting bold/italic styling (:disc:`7241`) - macOS: When CoreText fails to find a fallback font for a character in the first Private Use Unicode Area, preferentially use the NERD font, if available, for it (:iss:`6043`) 0.33.0 [2024-03-12] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :ref:`Cheetah speed ` with a redesigned render loop and a 2x faster escape code parser that uses SIMD CPU vector instruction to parse data in parallel (:iss:`7005`) - A new benchmark kitten (``kitten __benchmark__``) to measure terminal throughput performance - Graphics protocol: Add a new delete mode for deleting images whose ids fall within a range. Useful for bulk deletion (:iss:`7080`) - Keyboard protocol: Fix the :kbd:`Enter`, :kbd:`Tab` and :kbd:`Backspace` keys generating spurious release events even when report all keys as escape codes is not set (:iss:`7136`) - macOS: The command line args from :file:`macos-launch-services-cmdline` are now prefixed to any args from ``open --args`` rather than overwriting them (:iss:`7135`) - Allow specifying where the new tab is created for :ac:`detach_window` (:pull:`7134`) - hints kitten: The option to set the text color for hints now allows arbitrary colors (:pull:`7150`) - icat kitten: Add a command line argument to override terminal window size detection (:iss:`7165`) - A new action :ac:`toggle_tab` to easily switch to and back from a tab with a single shortcut (:iss:`7203`) - When :ac:`clearing terminal ` add a new type ``to_cursor_scroll`` which can be used to clear to prompt while moving cleared lines into the scrollback - Fix a performance bottleneck when dealing with thousands of small images (:iss:`7080`) - kitten @ ls: Return the timestamp at which the window was created (:iss:`7178`) - hints kitten: Use default editor rather than hardcoding vim to open file at specific line (:iss:`7186`) - Remote control: Fix ``--match`` argument not working for @ls, @send-key, @set-background-image (:iss:`7192`) - Keyboard protocol: Do not deliver a fake key release events on OS window focus out for engaged modifiers (:iss:`7196`) - Ignore :opt:`startup_session` when kitty is invoked with command line options specifying a command to run (:pull:`7198`) - Box drawing: Specialize rendering for the Fira Code progress bar/spinner glyphs 0.32.2 [2024-02-12] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - kitten @ load-config: Allow (re)loading kitty.conf via remote control - Remote control: Allow running mappable actions via remote control (`kitten @ action`) - kitten @ send-text: Add a new option to automatically wrap the sent text in bracketed paste escape codes if the program in the destination window has turned on bracketed paste. - Fix a single key mapping not overriding a previously defined multi-key mapping - macOS: Fix :code:`kitten @ select-window` leaving the keyboard in a partially functional state (:iss:`7074`) - Graphics protocol: Improve display of images using Unicode placeholders or row/column boxes by resizing them using linear instead of nearest neighbor interpolation on the GPU (:iss:`7070`) - When matching URLs use the definition of legal characters in URLs from the `WHATWG spec `__ rather than older standards (:iss:`7095`) - hints kitten: Respect the kitty :opt:`url_excluded_characters` option (:iss:`7075`) - macOS: Fix an abort when changing OS window chrome for a full screen window via remote control or the themes kitten (:iss:`7106`) - Special case rendering of some more box drawing characters using shades from the block of symbols for legacy computing (:iss:`7110`) - A new action :ac:`close_other_os_windows` to close non active OS windows (:disc:`7113`) 0.32.1 [2024-01-26] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - macOS: Fix a regression in the previous release that broke overriding keyboard shortcuts for actions present in the global menu bar (:iss:`7016`) - Fix a regression in the previous release that caused multi-key sequences to not abort when pressing an unknown key (:iss:`7022`) - Fix a regression in the previous release that caused `kitten @ launch --cwd=current` to fail over SSH (:iss:`7028`) - Fix a regression in the previous release that caused `kitten @ send-text` with a match tab parameter to send text twice to the active window (:iss:`7027`) - Fix a regression in the previous release that caused overriding of existing multi-key mappings to fail (:iss:`7044`, :iss:`7058`) - Wayland+NVIDIA: Do not request an sRGB output buffer as a bug in Wayland causes kitty to not start (:iss:`7021`) 0.32.0 [2024-01-19] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :ref:`conditional_mappings` - Support for :ref:`modal_mappings` such as in modal editors like vim - A new option :opt:`notify_on_cmd_finish` to show a desktop notification when a long running command finishes (:pull:`6817`) - A new action :ac:`send_key` to simplify mapping key presses to other keys without needing :ac:`send_text` - Allow focusing previously active OS windows via :ac:`nth_os_window` (:pull:`7009`) - Wayland: Fix a regression in the previous release that broke copying to clipboard under wl-roots based compositors in some circumstances (:iss:`6890`) - macOS: Fix some combining characters not being rendered (:iss:`6898`) - macOS: Fix returning from full screen via the button when the titlebar is hidden not hiding the buttons (:iss:`6883`) - macOS: Fix newly created OS windows not always appearing on the "active" monitor (:pull:`6932`) - Font fallback: Fix the font used to render a character sometimes dependent on the order in which characters appear on screen (:iss:`6865`) - panel kitten: Fix rendering with non-zero margin/padding in kitty.conf (:iss:`6923`) - kitty keyboard protocol: Specify the behavior of the modifier bits during modifier key events (:iss:`6913`) - Wayland: Enable support for the new cursor-shape protocol so that the mouse cursor is always rendered at the correct size in compositors that support this protocol (:iss:`6914`) - GNOME Wayland: Fix remembered window size smaller than actual size (:iss:`6946`) - Mouse reporting: Fix incorrect position reported for windows with padding (:iss:`6950`) - Fix :ac:`focus_visible_window` not switching to other window in stack layout when only two windows are present (:iss:`6970`) 0.31.0 [2023-11-08] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Allow :ac:`easily running arbitrarily complex remote control scripts ` without needing to turn on remote control (:iss:`6712`) - A new option :opt:`menu_map` that allows adding entries to the global menubar on macOS (:disc:`6680`) - A new :doc:`escape code ` that can be used by programs running in the terminal to change the shape of the mouse pointer (:iss:`6711`) - Graphics protocol: Support for positioning :ref:`images relative to other images ` (:iss:`6400`) - A new option :opt:`single_window_padding_width` to use a different padding when only a single window is visible (:iss:`6734`) - A new mouse action ``mouse_selection word_and_line_from_point`` to select the current word under the mouse cursor and extend to end of line (:pull:`6663`) - A new option :opt:`underline_hyperlinks` to control when hyperlinks are underlined (:iss:`6766`) - Allow using the full range of standard mouse cursor shapes when customizing the mouse cursor - macOS: When running the default shell with the login program fix :file:`~/.hushlogin` not being respected when opening windows not in the home directory (:iss:`6689`) - macOS: Fix poor performance when using ligatures with some fonts, caused by slow harfbuzz shaping (:iss:`6743`) - :option:`kitten @ set-background-opacity --toggle` - a new flag to easily switch opacity between the specified value and the default (:iss:`6691`) - Fix a regression caused by rewrite of kittens to Go that made various kittens reset colors in a terminal when the colors were changed by escape code (:iss:`6708`) - Fix trailing bracket not ignored when detecting a multi-line URL with the trailing bracket as the first character on the last line (:iss:`6710`) - Fix the :option:`kitten @ launch --copy-env` option not copying current environment variables (:iss:`6724`) - Fix a regression that broke :program:`kitten update-self` (:iss:`6729`) - Two new event types for :ref:`watchers `, :code:`on_title_change` and :code:`on_set_user_var` - When pasting, if the text contains terminal control codes ask the user for permission. See :opt:`paste_actions` for details. Thanks to David Leadbeater for discovering this. - Render Private Use Unicode symbols using two cells if the second cell contains an en-space as well as a normal space - macOS: Fix a regression in the previous release that caused kitten @ ls to not report the environment variables for the default shell (:iss:`6749`) - :doc:`Desktop notification protocol `: Allow applications sending notifications to specify that the notification should only be displayed if the window is currently unfocused (:iss:`6755`) - :doc:`unicode_input kitten `: Fix a regression that broke the "Emoticons" tab (:iss:`6760`) - Shell integration: Fix ``sudo --edit`` not working and also fix completions for sudo not working in zsh (:iss:`6754`, :iss:`6771`) - A new action :ac:`set_window_title` to interactively change the title of the active window - ssh kitten: Fix a regression that broken :kbd:`ctrl+space` mapping in zsh (:iss:`6780`) - Wayland: Fix primary selections not working with the river compositor (:iss:`6785`) 0.30.1 [2023-10-05] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Shell integration: Automatically alias sudo to make the kitty terminfo files available in the sudo environment. Can be turned off via :opt:`shell_integration` - ssh kitten: Fix a regression in 0.28.0 that caused using ``--kitten`` to override :file:`ssh.conf` not inheriting settings from :file:`ssh.conf` (:iss:`6639`) - themes kitten: Allow absolute paths for ``--config-file-name`` (:iss:`6638`) - Expand environment variables in the :opt:`shell` option (:iss:`6511`) - macOS: When running the default shell, run it via the login program so that calls to ``getlogin()`` work (:iss:`6511`) - X11: Fix a crash on startup when the ibus service returns errors and the GLFW_IM_MODULE env var is set to ibus (:iss:`6650`) 0.30.0 [2023-09-18] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new :doc:`transfer kitten ` that can be used to transfer files efficiently over the TTY device - ssh kitten: A new configuration directive :opt:`to automatically forward the kitty remote control socket ` - Allow :doc:`easily building kitty from source ` needing the installation of only C and Go compilers. All other dependencies are automatically vendored - kitten @ set-user-vars: New remote control command to set user variables on a window (:iss:`6502`) - kitten @ ls: Add user variables set on windows to the output (:iss:`6502`) - kitten @ ls: Allow limiting output to matched windows/tabs (:iss:`6520`) - kitten icat: Fix image being displayed one cell to the right when using both ``--place`` and ``--unicode-placeholder`` (:iss:`6556`) - kitten run-shell: Make kitty terminfo database available if needed before starting the shell - macOS: Fix keyboard shortcuts in the Apple global menubar not being changed when reloading the config - Fix a crash when resizing an OS Window that is displaying more than one image and the new size is smaller than the image needs (:iss:`6555`) - Remote control: Allow using a random TCP port as the remote control socket and also allow using TCP sockets in :opt:`listen_on` - unicode_input kitten: Add an option to specify the startup tab (:iss:`6552`) - X11: Print an error to :file:`STDERR` instead of refusing to start when the user sets a custom window icon larger than 128x128 (:iss:`6507`) - Remote control: Allow matching by neighbor of active window. Useful for navigation plugins like vim-kitty-navigator - Fix a regression that caused changing :opt:`text_fg_override_threshold` or :opt:`text_composition_strategy` via config reload causing incorrect rendering (:iss:`6559`) - When running a shell for ``--hold`` set the env variable ``KITTY_HOLD=1`` to allow users to customize what happens (:disc:`6587`) - When multiple confirmable close requests are made focus the existing close confirmation window instead of opening a new one for each request (:iss:`6601`) - Config file format: allow splitting lines by starting subsequent lines with a backslash (:pull:`6603`) - ssh kitten: Fix a regression causing hostname directives in :file:`ssh.conf` not matching when username is specified (:disc:`6609`) - diff kitten: Add support for files that are identical apart from mode changes (:iss:`6611`) - Wayland: Do not request idle inhibition for full screen windows (:iss:`6613`) - Adjust the workaround for non-linear blending of transparent pixels in compositors to hopefully further reduce fringing around text with certain color issues (:iss:`6534`) 0.29.2 [2023-07-27] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - macOS: Fix a performance regression on M1 machines using outdated macOS versions (:iss:`6479`) - macOS: Disable OS window shadows for transparent windows as they cause rendering artifacts due to Cocoa bugs (:iss:`6439`) - Detect .tex and Makefiles as plain text files (:iss:`6492`) - unicode_input kitten: Fix scrolling over multiple screens not working (:iss:`6497`) 0.29.1 [2023-07-17] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new value for :opt:`background_image_layout` to scale the background image while preserving its aspect ratio. Also have centered images work even for images larger than the window size (:pull:`6458`) - Fix a regression that caused using unicode placeholders to display images to break and also partially offscreen images to sometimes be slightly distorted (:iss:`6467`) - macOS: Fix a regression that caused rendering to hang when transitioning to full screen with :opt:`macos_colorspace` set to ``default`` (:iss:`6435`) - macOS: Fix a regression causing *burn-in* of text when resizing semi-transparent OS windows (:iss:`6439`) - macOS: Add a new value ``titlebar-and-corners`` for :opt:`hide_window_decorations` that emulates the behavior of ``hide_window_decorations yes`` in older versions of kitty - macOS: Fix a regression in the previous release that caused :opt:`hide_window_decorations` = ``yes`` to prevent window from being resizable (:iss:`6436`) - macOS: Fix a regression that caused the titlebar to be translucent even for non-translucent windows (:iss:`6450`) - GNOME: Fix :opt:`wayland_titlebar_color` not being applied until the color is changed at least once (:iss:`6447`) - Remote control launch: Fix ``--env`` not implemented when using ``--cwd=current`` with the SSH kitten (:iss:`6438`) - Allow using a custom OS window icon on X11 as well as macOS (:pull:`6475`) 0.29.0 [2023-07-10] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new escape code ``[22J`` that moves the current contents of the screen into the scrollback before clearing it - A new kitten :ref:`run-shell ` to allow creating sub-shells with shell integration enabled - A new option :opt:`background_blur` to blur the background for transparent windows (:pull:`6135`) - The :option:`--hold` flag now holds the window open at a shell prompt instead of asking the user to press a key - A new option :opt:`text_fg_override_threshold` to force text colors to have high contrast regardless of color scheme (:pull:`6283`) - When resizing OS Windows make the animation less jerky. Also show the window size in cells during the resize (:iss:`6341`) - unicode_input kitten: Fix a regression in 0.28.0 that caused the order of recent and favorites entries to not be respected (:iss:`6214`) - unicode_input kitten: Fix a regression in 0.28.0 that caused editing of favorites to sometimes hang - clipboard kitten: Fix a bug causing the last MIME type available on the clipboard not being recognized when pasting - clipboard kitten: Dont set clipboard when getting clipboard in filter mode (:iss:`6302`) - Fix regression in 0.28.0 causing color fringing when rendering in transparent windows on light backgrounds (:iss:`6209`) - show_key kitten: In kitty mode show the actual bytes sent by the terminal rather than a re-encoding of the parsed key event - hints kitten: Fix a regression in 0.28.0 that broke using sub-groups in regexp captures (:iss:`6228`) - hints kitten: Fix a regression in 0.28.0 that broke using lookahead/lookbehind in regexp captures (:iss:`6265`) - diff kitten: Fix a regression in 0.28.0 that broke using relative paths as arguments to the kitten (:iss:`6325`) - Fix re-using the image id of an animated image for a still image causing a crash (:iss:`6244`) - kitty +open: Ask for permission before executing script files that are not marked as executable. This prevents accidental execution of script files via MIME type association from programs that unconditionally "open" attachments/downloaded files - edit-in-kitty: Fix running edit-in-kitty with elevated privileges to edit a restricted file not working (:disc:`6245`) - ssh kitten: Fix a regression in 0.28.0 that caused interrupt during setup to not be handled gracefully (:iss:`6254`) - ssh kitten: Allow configuring the ssh kitten to skip some hosts via a new ``delegate`` config directive - Graphics: Move images up along with text when the window is shrunk vertically (:iss:`6278`) - Fix a regression in 0.28.0 that caused a buffer overflow when clearing the screen (:iss:`6306`, :pull:`6308`) - Fix a regression in 0.27.0 that broke setting of specific edge padding/margin via remote control (:iss:`6333`) - macOS: Fix window shadows not being drawn for transparent windows (:iss:`2827`, :pull:`6416`) - Do not echo invalid DECRQSS queries back, behavior inherited from xterm (CVE-2008-2383). Similarly, fix an echo bug in the file transfer protocol due to insufficient sanitization of safe strings. 0.28.1 [2023-04-21] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Fix a regression in the previous release that broke the remote file kitten (:iss:`6186`) - Fix a regression in the previous release that broke handling of some keyboard shortcuts in some kittens on some keyboard layouts (:iss:`6189`) - Fix a regression in the previous release that broke usage of custom themes (:iss:`6191`) 0.28.0 [2023-04-15] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - **Text rendering change**: Use sRGB correct linear gamma blending for nicer font rendering and better color accuracy with transparent windows. See the option :opt:`text_composition_strategy` for details. The obsolete :opt:`macos_thicken_font` will make the font too thick and needs to be removed manually if it is configured. (:pull:`5969`) - icat kitten: Support display of images inside tmux >= 3.3 (:pull:`5664`) - Graphics protocol: Add support for displaying images inside programs that do not support the protocol such as vim and tmux (:pull:`5664`) - diff kitten: Add support for selecting multi-line text with the mouse - Fix a regression in 0.27.0 that broke ``kitty @ set-font-size 0`` (:iss:`5992`) - launch: When using ``--cwd=current`` for a remote system support running non shell commands as well (:disc:`5987`) - When changing the cursor color via escape codes or remote control to a fixed color, do not reset cursor_text_color (:iss:`5994`) - Input Method Extensions: Fix incorrect rendering of IME in-progress and committed text in some situations (:pull:`6049`, :pull:`6087`) - Linux: Reduce minimum required OpenGL version from 3.3 to 3.1 + extensions (:iss:`2790`) - Fix a regression that broke drawing of images below cell backgrounds (:iss:`6061`) - macOS: Fix the window buttons not being hidden after exiting the traditional full screen (:iss:`6009`) - When reloading configuration, also reload custom MIME types from :file:`mime.types` config file (:pull:`6012`) - launch: Allow specifying the state (full screen/maximized/minimized) for newly created OS Windows (:iss:`6026`) - Sessions: Allow specifying the OS window state via the ``os_window_state`` directive (:iss:`5863`) - macOS: Display the newly created OS window in specified state to avoid or reduce the window transition animations (:pull:`6035`) - macOS: Fix the maximized window not taking up full space when the title bar is hidden or when :opt:`resize_in_steps` is configured (:iss:`6021`) - Linux: A new option :opt:`linux_bell_theme` to control which sound theme is used for the bell sound (:pull:`4858`) - ssh kitten: Change the syntax of glob patterns slightly to match common usage elsewhere. Now the syntax is the same as "extendedglob" in most shells. - hints kitten: Allow copying matches to named buffers (:disc:`6073`) - Fix overlay windows not inheriting the per-window padding and margin settings of their parents (:iss:`6063`) - Wayland KDE: Fix selecting in un-focused OS window not working correctly (:iss:`6095`) - Linux X11: Fix a crash if the X server requests clipboard data after we have relinquished the clipboard (:iss:`5650`) - Allow stopping of URL detection at newlines via :opt:`url_excluded_characters` (:iss:`6122`) - Linux Wayland: Fix animated images not being animated continuously (:iss:`6126`) - Keyboard input: Fix text not being reported as unicode codepoints for multi-byte characters in the kitty keyboard protocol (:iss:`6167`) 0.27.1 [2023-02-07] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Fix :opt:`modify_font` not working for strikethrough position (:iss:`5946`) - Fix a regression causing the ``edit-in-kitty`` command not working if :file:`kitten` is not added to PATH (:iss:`5956`) - icat kitten: Fix a regression that broke display of animated GIFs over SSH (:iss:`5958`) - Wayland GNOME: Fix for ibus not working when using XWayland (:iss:`5967`) - Fix regression in previous release that caused incorrect entries in terminfo for modifier+F3 key combinations (:pull:`5970`) - Bring back the deprecated and removed ``kitty +complete`` and delegate it to :program:`kitten` for backward compatibility (:pull:`5977`) - Bump the version of Go needed to build kitty to ``1.20`` so we can use the Go stdlib ecdh package for crypto. 0.27.0 [2023-01-31] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new statically compiled, standalone executable, ``kitten`` (written in Go) that can be used on all UNIX-like servers for remote control (``kitten @``), viewing images (``kitten icat``), manipulating the clipboard (``kitten clipboard``), etc. - :doc:`clipboard kitten `: Allow copying arbitrary data types to/from the clipboard, not just plain text - Speed up the ``kitty @`` executable by ~10x reducing the time for typical remote control commands from ~50ms to ~5ms - icat kitten: Speed up by using POSIX shared memory when possible to transfer image data to the terminal. Also support common image formats GIF/PNG/JPEG/WEBP/TIFF/BMP out of the box without needing ImageMagick. - Option :opt:`show_hyperlink_targets` to show the target of terminal hyperlinks when hovering over them with the mouse (:pull:`5830`) - Keyboard protocol: Remove ``CSI R`` from the allowed encodings of the :kbd:`F3` key as it conflicts with the *Cursor Position Report* escape code (:disc:`5813`) - Allow using the cwd of the original process for :option:`launch --cwd` (:iss:`5672`) - Session files: Expand environment variables (:disc:`5917`) - Pass key events mapped to scroll actions to the program running in the terminal when the terminal is in alternate screen mode (:iss:`5839`) - Implement :ref:`edit-in-kitty ` using the new ``kitten`` static executable (:iss:`5546`, :iss:`5630`) - Add an option :opt:`background_tint_gaps` to control background image tinting for window gaps (:iss:`5596`) - A new option :opt:`undercurl_style` to control the rendering of undercurls (:pull:`5883`) - Bash integration: Fix ``clone-in-kitty`` not working on bash >= 5.2 if environment variable values contain newlines or other special characters (:iss:`5629`) - A new :ac:`sleep` action useful in combine based mappings to make kitty sleep before executing the next action - Wayland GNOME: Workaround for latest mutter release breaking full screen for semi-transparent kitty windows (:iss:`5677`) - A new option :opt:`tab_title_max_length` to limit the length of tab (:iss:`5718`) - When drawing the tab bar have the default left and right margins drawn in a color matching the neighboring tab (:iss:`5719`) - When using the :code:`include` directive in :file:`kitty.conf` make the environment variable :envvar:`KITTY_OS` available for OS specific config - Wayland: Fix signal handling not working with some GPU drivers (:iss:`4636`) - Remote control: When matching windows allow using negative id numbers to match recently created windows (:iss:`5753`) - ZSH Integration: Bind :kbd:`alt+left` and :kbd:`alt+right` to move by word if not already bound. This mimics the default bindings in Terminal.app (:iss:`5793`) - macOS: Allow to customize :sc:`Hide `, :sc:`Hide Others `, :sc:`Minimize `, and :sc:`Quit ` global menu shortcuts. Note that :opt:`clear_all_shortcuts` will remove these shortcuts now (:iss:`948`) - When a multi-key sequence does not match any action, send all key events to the child program (:pull:`5841`) - broadcast kitten: Allow pressing a key to stop echoing of input into the broadcast window itself (:disc:`5868`) - When reporting unused activity in a window, ignore activity that occurs soon after a window resize (:iss:`5881`) - Fix using :opt:`cursor` = ``none`` not working on text that has reverse video (:iss:`5897`) - Fix ssh kitten not working on FreeBSD (:iss:`5928`) - macOS: Export kitty selected text to the system for use with services that accept it (patch by Sertaç Ö. Yıldız) 0.26.5 [2022-11-07] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Splits layout: Add a new mappable action to move the active window to the screen edge (:iss:`5643`) - ssh kitten: Allow using absolute paths for the location of transferred data (:iss:`5607`) - Fix a regression in the previous release that caused a ``resize_draw_strategy`` of ``static`` to not work (:iss:`5601`) - Wayland KDE: Fix abort when pasting into Firefox (:iss:`5603`) - Wayland GNOME: Fix ghosting when using :opt:`background_tint` (:iss:`5605`) - Fix cursor position at x=0 changing to x=1 on resize (:iss:`5635`) - Wayland GNOME: Fix incorrect window size in some circumstances when switching between windows with window decorations disabled (:iss:`4802`) - Wayland: Fix high CPU usage when using some input methods (:pull:`5369`) - Remote control: When matching window by `state:focused` and no window currently has keyboard focus, match the window belonging to the OS window that was last focused (:iss:`5602`) 0.26.4 [2022-10-17] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - macOS: Allow changing the kitty icon by placing a custom icon in the kitty config folder (:pull:`5464`) - Allow centering the :opt:`background_image` (:iss:`5525`) - X11: Fix a regression in the previous release that caused pasting from GTK based applications to have extra newlines (:iss:`5528`) - Tab bar: Improve empty space management when some tabs have short titles, allocate the saved space to the active tab (:iss:`5548`) - Fix :opt:`background_tint` not applying to window margins and padding (:iss:`3933`) - Wayland: Fix background image scaling using tiled mode on high DPI screens - Wayland: Fix an abort when changing background colors with :opt:`wayland_titlebar_color` set to ``background`` (:iss:`5562`) - Update to Unicode 15.0 (:pull:`5542`) - GNOME Wayland: Fix a memory leak in gnome-shell when using client side decorations 0.26.3 [2022-09-22] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Wayland: Mark windows in which a bell occurs as urgent on compositors that support the xdg-activation protocol - Allow passing null bytes through the system clipboard (:iss:`5483`) - ssh kitten: Fix :envvar:`KITTY_PUBLIC_KEY` not being encoded properly when transmitting (:iss:`5496`) - Sessions: Allow controlling which OS Window is active via the ``focus_os_window`` directive - Wayland: Fix for bug in NVIDIA drivers that prevents transparency working (:iss:`5479`) - Wayland: Fix for a bug that could cause kitty to become non-responsive when using multiple OS windows in a single instance on some compositors (:iss:`5495`) - Wayland: Fix for a bug preventing kitty from starting on Hyprland when using a non-unit scale (:iss:`5467`) - Wayland: Generate a XDG_ACTIVATION_TOKEN when opening URLs or running programs in the background via the launch action - Fix a regression that caused kitty not to restore SIGPIPE after python nukes it when launching children. Affects bash which does not sanitize its signal mask. (:iss:`5500`) - Fix a use-after-free when handling fake mouse clicks and the action causes windows to be removed/re-allocated (:iss:`5506`) 0.26.2 [2022-09-05] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Allow creating :code:`overlay-main` windows, which are treated as the active window unlike normal overlays (:iss:`5392`) - hints kitten: Allow using :doc:`launch` as the program to run, to open the result in a new kitty tab/window/etc. (:iss:`5462`) - hyperlinked_grep kitten: Allow control over which parts of ``rg`` output are hyperlinked (:pull:`5428`) - Fix regression in 0.26.0 that caused launching kitty without working STDIO handles to result in high CPU usage and prewarming failing (:iss:`5444`) - :doc:`/launch`: Allow setting the margin and padding for newly created windows (:iss:`5463`) - macOS: Fix regression in 0.26.0 that caused asking the user for a line of input such as for :ac:`set_tab_title` to not work (:iss:`5447`) - hints kitten: hyperlink matching: Fix hints occasionally matching text on subsequent line as part of hyperlink (:pull:`5450`) - Fix a regression in 0.26.0 that broke mapping of native keys whose key codes did not fit in 21 bits (:iss:`5452`) - Wayland: Fix remembering window size not accurate when client side decorations are present - Fix an issue where notification identifiers were not sanitized leading to code execution if the user clicked on a notification popup from a malicious source. Thanks to Carter Sande for discovering this vulnerability. 0.26.1 [2022-08-30] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - ssh kitten: Fix executable permission missing from kitty bootstrap script (:iss:`5438`) - Fix a regression in 0.26.0 that caused kitty to no longer set the ``LANG`` environment variable on macOS (:iss:`5439`) - Allow specifying a title when using the :ac:`set_tab_title` action (:iss:`5441`) 0.26.0 [2022-08-29] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new option :opt:`remote_control_password` to use fine grained permissions for what can be remote controlled (:disc:`5320`) - Reduce startup latency by ~30 milliseconds when running kittens via key bindings inside kitty (:iss:`5159`) - A new option :opt:`modify_font` to adjust various font metrics like underlines, cell sizes etc. (:pull:`5265`) - A new shortcut :sc:`show_kitty_doc` to display the kitty docs in a browser - Graphics protocol: Only delete temp files if they have the string :code:`tty-graphics-protocol` in their file paths. This prevents deletion of arbitrary files in :file:`/tmp`. - Deprecate the ``adjust_baseline``, ``adjust_line_height`` and ``adjust_column_width`` options in favor of :opt:`modify_font` - Wayland: Fix a regression in the previous release that caused mouse cursor animation and keyboard repeat to stop working when switching seats (:iss:`5188`) - Allow resizing windows created in session files (:pull:`5196`) - Fix horizontal wheel events not being reported to client programs when they grab the mouse (:iss:`2819`) - macOS: Remote control: Fix unable to launch a new OS window or background process when there is no OS window (:iss:`5210`) - macOS: Fix unable to open new tab or new window when there is no OS window (:iss:`5276`) - kitty @ set-colors: Fix changing inactive_tab_foreground not working (:iss:`5214`) - macOS: Fix a regression that caused switching keyboard input using Eisu and Kana keys not working (:iss:`5232`) - Add a mappable action to toggle the mirrored setting for the tall and fat layouts (:pull:`5344`) - Add a mappable action to switch between predefined bias values for the tall and fat layouts (:pull:`5352`) - Wayland: Reduce flicker at startup by not using render frames immediately after a resize (:iss:`5235`) - Linux: Update cursor position after all key presses not just pre-edit text changes (:iss:`5241`) - ssh kitten: Allow ssh kitten to work from inside tmux, provided the tmux session inherits the correct KITTY env vars (:iss:`5227`) - ssh kitten: A new option :code:`--symlink-strategy` to control how symlinks are copied to the remote machine (:iss:`5249`) - ssh kitten: Allow pressing :kbd:`Ctrl+C` to abort ssh before the connection is completed (:iss:`5271`) - Bash integration: Fix declare not creating global variables in .bashrc (:iss:`5254`) - Bash integration: Fix the inherit_errexit option being set by shell integration (:iss:`5349`) - :command:`kitty @ scroll-window` allow scrolling by fractions of a screen (:iss:`5294`) - remote files kitten: Fix working with files whose names have characters that need to be quoted in shell scripts (:iss:`5313`) - Expand ~ in paths configured in :opt:`editor` and :opt:`exe_search_path` (:disc:`5298`) - Allow showing the working directory of the active window in tab titles (:pull:`5314`) - ssh kitten: Allow completion of ssh options between the destination and command (:iss:`5322`) - macOS: Fix speaking selected text not working (:iss:`5357`) - Allow ignoring failure to close windows/tabs via rc commands (:disc:`5406`) - Fix hyperlinks not present when fetching text from the history buffer (:iss:`5427`) 0.25.2 [2022-06-07] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new command :command:`edit-in-kitty` to :ref:`edit_file` - Allow getting the last non-empty command output easily via an action or remote control (:pull:`4973`) - Fix a bug that caused :opt:`macos_colorspace` to always be ``default`` regardless of its actual value (:iss:`5129`) - diff kitten: A new option :opt:`kitten-diff.ignore_name` to exclude files and directories from being scanned (:pull:`5171`) - ssh kitten: Fix bash not being executed as a login shell since kitty 0.25.0 (:iss:`5130`) - macOS: When pasting text and the clipboard has a filesystem path, paste the full path instead of the text, which is sometimes just the file name (:pull:`5142`) - macOS: Allow opening executables without a file extension with kitty as well (:iss:`5160`) - Themes kitten: Add a tab to show user defined custom color themes separately (:pull:`5150`) - Iosevka: Fix incorrect rendering when there is a combining char that does not group with its neighbors (:iss:`5153`) - Weston: Fix client side decorations flickering on slow computers during window resize (:iss:`5162`) - Remote control: Fix commands with large or asynchronous payloads like :command:`kitty @ set-backround-image`, :command:`kitty @ set-window-logo` and :command:`kitty @ select-window` not working correctly when using a socket (:iss:`5165`) - hints kitten: Fix surrounding quotes/brackets and embedded carriage returns not being removed when using line number processing (:iss:`5170`) 0.25.1 [2022-05-26] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Shell integration: Add a command to :ref:`clone_shell` - Remote control: Allow using :ref:`Boolean operators ` when constructing queries to match windows or tabs - Sessions: Fix :code:`os_window_size` and :code:`os_window_class` not applying to the first OS Window (:iss:`4957`) - Allow using the cwd of the oldest as well as the newest foreground process for :option:`launch --cwd` (:disc:`4869`) - Bash integration: Fix the value of :opt:`shell_integration` not taking effect if the integration script is sourced in bashrc (:pull:`4964`) - Fix a regression in the previous release that caused mouse move events to be incorrectly reported as drag events even when a button is not pressed (:iss:`4992`) - remote file kitten: Integrate with the ssh kitten for improved performance and robustness. Re-uses the control master connection of the ssh kitten to avoid round-trip latency. - Fix tab selection when closing a new tab not correct in some scenarios (:iss:`4987`) - A new action :ac:`open_url` to open the specified URL (:pull:`5004`) - A new option :opt:`select_by_word_characters_forward` that allows changing which characters are considered part of a word to the right when double clicking to select words (:pull:`5103`) - macOS: Make the global menu shortcut to open kitty website configurable (:pull:`5004`) - macOS: Add the :opt:`macos_colorspace` option to control what color space colors are rendered in (:iss:`4686`) - Fix reloading of config not working when :file:`kitty.conf` does not exist when kitty is launched (:iss:`5071`) - Fix deleting images by row not calculating image bounds correctly (:iss:`5081`) - Increase the max number of combining chars per cell from two to three, without increasing memory usage. - Linux: Load libfontconfig at runtime to allow the binaries to work for running kittens on servers without FontConfig - GNOME: Fix for high CPU usage caused by GNOME's text input subsystem going into an infinite loop when IME cursor position is updated after a done event (:iss:`5105`) 0.25.0 [2022-04-11] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :doc:`kittens/ssh`: automatic shell integration when using SSH. Easily clone local shell and editor configuration on remote machines, and automatic re-use of existing connections to avoid connection setup latency. - When pasting URLs at shell prompts automatically quote them. Also allow filtering pasted text and confirm pastes. See :opt:`paste_actions` for details. (:iss:`4873`) - Change the default value of :opt:`confirm_os_window_close` to ask for confirmation when closing windows that are not sitting at shell prompts - A new value :code:`last_reported` for :option:`launch --cwd` to use the current working directory last reported by the program running in the terminal - macOS: When using Apple's less as the pager for viewing scrollback strip out OSC codes as it can't parse them (:iss:`4788`) - diff kitten: Fix incorrect rendering in rare circumstances when scrolling after changing the context size (:iss:`4831`) - icat kitten: Fix a regression that broke :option:`kitty +kitten icat --print-window-size` (:pull:`4818`) - Wayland: Fix :opt:`hide_window_decorations` causing docked windows to be resized on blur (:iss:`4797`) - Bash integration: Prevent shell integration code from running twice if user enables both automatic and manual integration - Bash integration: Handle existing PROMPT_COMMAND ending with a literal newline - Fix continued lines not having their continued status reset on line feed (:iss:`4837`) - macOS: Allow the New kitty Tab/Window Here services to open multiple selected folders. (:pull:`4848`) - Wayland: Fix a regression that broke IME when changing windows/tabs (:iss:`4853`) - macOS: Fix Unicode paths not decoded correctly when dropping files (:pull:`4879`) - Avoid flicker when starting kittens such as the hints kitten (:iss:`4674`) - A new action :ac:`scroll_prompt_to_top` to move the current prompt to the top (:pull:`4891`) - :ac:`select_tab`: Use stable numbers when selecting the tab (:iss:`4792`) - Only check for updates in the official binary builds. Distro packages or source builds will no longer check for updates, regardless of the value of :opt:`update_check_interval`. - Fix :opt:`inactive_text_alpha` still being applied to the cursor hidden window after focus (:iss:`4928`) - Fix resizing window that is extra tall/wide because of left-over cells not working reliably (:iss:`4913`) - A new action :ac:`close_other_tabs_in_os_window` to close other tabs in the active OS window (:pull:`4944`) 0.24.4 [2022-03-03] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Shell integration: Fix the default Bash :code:`$HISTFILE` changing to :file:`~/.sh_history` instead of :file:`~/.bash_history` (:iss:`4765`) - Linux binaries: Fix binaries not working on systems with older Wayland client libraries (:iss:`4760`) - Fix a regression in the previous release that broke kittens launched with :code:`STDIN` not connected to a terminal (:iss:`4763`) - Wayland: Fix surface configure events not being acknowledged before commit the resized buffer (:pull:`4768`) 0.24.3 [2022-02-28] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Bash integration: No longer modify :file:`~/.bashrc` to load :ref:`shell integration `. It is recommended to remove the lines used to load the shell integration from :file:`~/.bashrc` as they are no-ops. - macOS: Allow kitty to handle various URL types. Can be configured via :ref:`launch_actions` (:pull:`4618`) - macOS: Add a new service ``Open with kitty`` to open file types that are not recognized by the system (:pull:`4641`) - Splits layout: A new value for :option:`launch --location` to auto-select the split axis when splitting existing windows. Wide windows are split side-by-side and tall windows are split one-above-the-other - hints kitten: Fix a regression that broke recognition of path:linenumber:colnumber (:iss:`4675`) - Fix a regression in the previous release that broke :opt:`active_tab_foreground` (:iss:`4620`) - Fix :ac:`show_last_command_output` not working when the output is stored partially in the scrollback pager history buffer (:iss:`4435`) - When dropping URLs/files onto kitty at a shell prompt insert them appropriately quoted and space separated (:iss:`4734`) - Improve CWD detection when there are multiple foreground processes in the TTY process group - A new option :opt:`narrow_symbols` to turn off opportunistic wide rendering of private use codepoints - ssh kitten: Fix location of generated terminfo files on NetBSD (:iss:`4622`) - A new action to clear the screen up to the line containing the cursor, see :ac:`clear_terminal` - A new action :ac:`copy_ansi_to_clipboard` to copy the current selection with ANSI formatting codes (:iss:`4665`) - Linux: Do not rescale fallback fonts to match the main font cell height, instead just set the font size and let FreeType take care of it. This matches rendering on macOS (:iss:`4707`) - macOS: Fix a regression in the previous release that broke switching input sources by keyboard (:iss:`4621`) - macOS: Add the default shortcut :kbd:`cmd+k` to clear the terminal screen and scrollback up to the cursor (:iss:`4625`) - Fix a regression in the previous release that broke strikethrough (:disc:`4632`) - A new action :ac:`scroll_prompt_to_bottom` to move the current prompt to the bottom, filling in the window from the scrollback (:pull:`4634`) - Add two special arguments ``@first-line-on-screen`` and ``@last-line-on-screen`` for the :doc:`launch ` command to be used for pager positioning. (:iss:`4462`) - Linux: Fix rendering of emoji when using scalable fonts such as Segoe UI Emoji - Shell integration: bash: Dont fail if an existing PROMPT_COMMAND ends with a semi-colon (:iss:`4645`) - Shell integration: bash: Fix rendering of multiline prompts with more than two lines (:iss:`4681`) - Shell integration: fish: Check fish version 3.3.0+ and exit on outdated versions (:pull:`4745`) - Shell integration: fish: Fix pipestatus being overwritten (:pull:`4756`) - Linux: Fix fontconfig alias not being used if the aliased font is dual spaced instead of monospaced (:iss:`4649`) - macOS: Add an option :opt:`macos_menubar_title_max_length` to control the max length of the window title displayed in the global menubar (:iss:`2132`) - Fix :opt:`touch_scroll_multiplier` also taking effect in terminal programs such as vim that handle mouse events themselves (:iss:`4680`) - Fix symbol/PUA glyphs loaded via :opt:`symbol_map` instead of as fallbacks not using following spaces to render larger versions (:iss:`4670`) - macOS: Fix regression in previous release that caused Apple's global shortcuts to not work if they had never been configured on a particular machine (:iss:`4657`) - Fix a fast *click, move mouse, click* sequence causing the first click event to be discarded (:iss:`4603`) - Wayland: Fix wheel mice with line based scrolling being incorrectly handled as high precision devices (:iss:`4694`) - Wayland: Fix touchpads and high resolution wheels not scrolling at the same speed on monitors with different scales (:iss:`4703`) - Add an option :opt:`wheel_scroll_min_lines` to set the minimum number of lines for mouse wheel scrolling when using a mouse with a wheel that generates very small offsets when slow scrolling (:pull:`4710`) - macOS: Make the shortcut to toggle full screen configurable (:pull:`4714`) - macOS: Fix the mouse cursor being set to arrow after switching desktops or toggling full screen (:pull:`4716`) - Fix copying of selection after selection has been scrolled off history buffer raising an error (:iss:`4713`) 0.24.2 [2022-02-03] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - macOS: Allow opening text files, images and directories with kitty when launched using "Open with" in Finder (:iss:`4460`) - Allow including config files matching glob patterns in :file:`kitty.conf` (:iss:`4533`) - Shell integration: Fix bash integration not working when ``PROMPT_COMMAND`` is used to change the prompt variables (:iss:`4476`) - Shell integration: Fix cursor shape not being restored to default when running commands in the shell - Improve the UI of the ask kitten (:iss:`4545`) - Allow customizing the placement and formatting of the :opt:`tab_activity_symbol` and :opt:`bell_on_tab` symbols by adding them to the :opt:`tab_title_template` (:iss:`4581`, :pull:`4507`) - macOS: Persist "Secure Keyboard Entry" across restarts to match the behavior of Terminal.app (:iss:`4471`) - hints kitten: Fix common single letter extension files not being detected (:iss:`4491`) - Support dotted and dashed underline styles (:pull:`4529`) - For the vertical and horizontal layouts have the windows arranged on a ring rather than a plane. This means the first and last window are considered neighbors (:iss:`4494`) - A new action to clear the current selection (:iss:`4600`) - Shell integration: fish: Fix cursor shape not working with fish's vi mode (:iss:`4508`) - Shell integration: fish: Dont override fish's native title setting functionality. See `discussion `__. - macOS: Fix hiding via :kbd:`cmd+h` not working on macOS 10.15.7 (:iss:`4472`) - Draw the dots for braille characters more evenly spaced at all font sizes (:iss:`4499`) - icat kitten: Add options to mirror images and remove their transparency before displaying them (:iss:`4513`) - macOS: Respect the users system-wide global keyboard shortcut preferences (:iss:`4501`) - macOS: Fix a few key-presses causing beeps from Cocoa's text input system (:iss:`4489`) - macOS: Fix using shortcuts from the global menu bar as subsequent key presses in a multi key mapping not working (:iss:`4519`) - Fix getting last command output not working correctly when the screen is scrolled (:pull:`4522`) - Show number of windows per tab in the :ac:`select_tab` action (:pull:`4523`) - macOS: Fix the shift key not clearing pre-edit text in IME (:iss:`4541`) - Fix clicking in a window to focus it and typing immediately sometimes having unexpected effects if at a shell prompt (:iss:`4128`) - themes kitten: Allow writing to a different file than :file:`kitty.conf`. 0.24.1 [2022-01-06] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Shell integration: Work around conflicts with some zsh plugins (:iss:`4428`) - Have the zero width space and various other characters from the *Other, formatting* Unicode category be treated as combining characters (:iss:`4439`) - Fix using ``--shell-integration`` with :file:`setup.py` broken (:iss:`4434`) - Fix showing debug information not working if kitty's :file:`STDIN` is not a tty (:iss:`4424`) - Linux: Fix a regression that broke rendering of emoji with variation selectors (:iss:`4444`) 0.24.0 [2022-01-04] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Integrate kitty closely with common shells such as zsh, fish and bash. This allows lots of niceties such as jumping to previous prompts, opening the output of the last command in a new window, etc. See :ref:`shell_integration` for details. Packagers please read :ref:`packagers`. - A new shortcut :sc:`focus_visible_window` to visually focus a window using the keyboard. Pressing it causes numbers to appear over each visible window and you can press the number to focus the corresponding window (:iss:`4110`) - A new facility :opt:`window_logo_path` to draw an arbitrary PNG image as logo in the corner of a kitty window (:pull:`4167`) - Allow rendering the cursor with a *reverse video* effect. See :opt:`cursor` for details (:iss:`126`) - Allow rendering the mouse selection with a *reverse video* effect. See :opt:`selection_foreground` (:iss:`646`) - A new option :opt:`tab_bar_align` to draw the tab bar centered or right aligned (:iss:`3946`) - Allow the user to supply a custom Python function to draw tab bar. See :opt:`tab_bar_style` - A new remote control command to :program:`change the tab color ` (:iss:`1287`) - A new remote control command to :program:`visually select a window ` (:iss:`4165`) - Add support for reporting mouse events with pixel coordinates using the ``SGR_PIXEL_PROTOCOL`` introduced in xterm 359 - When programs ask to read from the clipboard prompt, ask the user to allow the request by default instead of denying it by default. See :opt:`clipboard_control` for details (:iss:`4022`) - A new mappable action ``swap_with_window`` to swap the current window with another window in the tab, visually - A new :program:`remote control command ` to change the enabled layouts in a tab (:iss:`4129`) - A new option :opt:`bell_path` to specify the path to a sound file to use as the bell sound - A new option :opt:`exe_search_path` to modify the locations kitty searches for executables to run (:iss:`4324`) - broadcast kitten: Show a "fake" cursor in all windows being broadcast too (:iss:`4225`) - Allow defining :opt:`aliases ` for more general actions, not just kittens (:pull:`4260`) - Fix a regression that caused :option:`kitty --title` to not work when opening new OS windows using :option:`kitty --single-instance` (:iss:`3893`) - icat kitten: Fix display of JPEG images that are rotated via EXIF data and larger than available screen size (:iss:`3949`) - macOS: Fix SIGUSR1 quitting kitty instead of reloading the config file (:iss:`3952`) - Launch command: Allow specifying the OS window title - broadcast kitten: Allow broadcasting :kbd:`ctrl+c` (:pull:`3956`) - Fix space ligatures not working with Iosevka for some characters in the Enclosed Alphanumeric Supplement (:iss:`3954`) - hints kitten: Fix a regression that caused using the default open program to trigger open actions instead of running the program (:iss:`3968`) - Allow deleting environment variables in :opt:`env` by specifying just the variable name, without a value - Fix :opt:`active_tab_foreground` not being honored when :opt:`tab_bar_style` is ``slant`` (:iss:`4053`) - When a :opt:`tab_bar_background` is specified it should extend to the edges of the OS window (:iss:`4054`) - Linux: Fix IME with fcitx5 not working after fcitx5 is restarted (:pull:`4059`) - Various improvements to IME integration (:iss:`4219`) - Remote file transfer: Fix transfer not working if custom ssh port or identity is specified on the command line (:iss:`4067`) - Unicode input kitten: Implement scrolling when more results are found than the available display space (:pull:`4068`) - Allow middle clicking on a tab to close it (:iss:`4151`) - The command line option ``--watcher`` has been deprecated in favor of the :opt:`watcher` option in :file:`kitty.conf`. It has the advantage of applying to all windows, not just the initially created ones. Note that ``--watcher`` now also applies to all windows, not just initially created ones. - **Backward incompatibility**: No longer turn on the kitty extended keyboard protocol's disambiguate mode when the client sends the XTMODKEYS escape code. Applications must use the dedicated escape code to turn on the protocol. (:iss:`4075`) - Fix soft hyphens not being preserved when round tripping text through the terminal - macOS: Fix :kbd:`ctrl+shift` with :kbd:`Esc` or :kbd:`F1` - :kbd:`F12` not working (:iss:`4109`) - macOS: Fix :opt:`resize_in_steps` not working correctly on high DPI screens (:iss:`4114`) - Fix the :program:`resize OS Windows ` setting a slightly incorrect size on high DPI screens (:iss:`4114`) - :program:`kitty @ launch` - when creating tabs with the ``--match`` option create the tab in the OS Window containing the result of the match rather than the active OS Window (:iss:`4126`) - Linux X11: Add support for 10bit colors (:iss:`4150`) - Fix various issues with changing :opt:`tab_bar_background` by remote control (:iss:`4152`) - A new option :opt:`tab_bar_margin_color` to control the color of the tab bar margins - A new option :opt:`visual_bell_color` to customize the color of the visual bell (:pull:`4181`) - Add support for OSC 777 based desktop notifications - Wayland: Fix pasting from applications that use a MIME type of "text/plain" rather than "text/plain;charset=utf-8" not working (:iss:`4183`) - A new mappable action to close windows with a confirmation (:iss:`4195`) - When remembering OS window sizes for full screen windows use the size before the window became fullscreen (:iss:`4221`) - macOS: Fix keyboard input not working after toggling fullscreen till the window is clicked in - A new mappable action ``nth_os_window`` to focus the specified nth OS window. (:pull:`4316`) - macOS: The kitty window can be scrolled by the mouse wheel when OS window not in focus. (:pull:`4371`) - macOS: Light or dark system appearance can be specified in :opt:`macos_titlebar_color` and used in kitty themes. (:pull:`4378`) 0.23.1 [2021-08-17] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - macOS: Fix themes kitten failing to download themes because of missing SSL root certificates (:iss:`3936`) - A new option :opt:`clipboard_max_size` to control the maximum size of data that kitty will transmit to the system clipboard on behalf of programs running inside it (:iss:`3937`) - When matching windows/tabs in kittens or using remote control, allow matching by recency. ``recent:0`` matches the active window/tab, ``recent:1`` matches the previous window/tab and so on - themes kitten: Fix only the first custom theme file being loaded correctly (:iss:`3938`) 0.23.0 [2021-08-16] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new :doc:`themes kitten ` to easily change kitty themes. Choose from almost two hundred themes in the `kitty themes repository `_ - A new style for the tab bar that makes tabs looks like the tabs in a physical tabbed file, see :opt:`tab_bar_style` - Make the visual bell flash more gentle, especially on dark themes (:pull:`2937`) - Fix :option:`kitty --title` not overriding the OS Window title when multiple tabs are present. Also this option is no longer used as the default title for windows, allowing individual tabs/windows to have their own titles, even when the OS Window has a fixed overall title (:iss:`3893`) - Linux: Fix some very long ligatures being rendered incorrectly at some font sizes (:iss:`3896`) - Fix shift+middle click to paste sending a mouse press event but no release event which breaks some applications that grab the mouse but can't handle mouse events (:iss:`3902`) - macOS: When the language is set to English and the country to one for which an English locale does not exist, set :envvar:`LANG` to ``en_US.UTF-8`` (:iss:`3899`) - terminfo: Fix "cnorm" the property for setting the cursor to normal using a solid block rather than a blinking block cursor (:iss:`3906`) - Add :opt:`clear_all_mouse_actions` to clear all mouse actions defined to that point (:iss:`3907`) - Fix the remote file kitten not working when using -- with ssh. The ssh kitten was recently changed to do this (:iss:`3929`) - When dragging word or line selections, ensure the initially selected item is never deselected. This matches behavior in most other programs (:iss:`3930`) - hints kitten: Make copy/paste with the :option:`kitty +kitten hints --program` option work when using the ``self`` :option:`kitty +kitten hints --linenum-action` (:iss:`3931`) 0.22.2 [2021-08-02] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - macOS: Fix a long standing bug that could cause kitty windows to stop updating, that got worse in the previous release (:iss:`3890` and :iss:`2016`) - Wayland: A better fix for compositors like sway that can toggle client side decorations on and off (:iss:`3888`) 0.22.1 [2021-07-31] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Fix a regression in the previous release that broke ``kitty --help`` (:iss:`3869`) - Graphics protocol: Fix composing onto currently displayed frame not updating the frame on the GPU (:iss:`3874`) - Fix switching to previously active tab after detaching a tab not working (:pull:`3871`) - macOS: Fix an error on Apple silicon when enumerating monitors (:pull:`3875`) - detach_window: Allow specifying the previously active tab or the tab to the left/right of the active tab (:disc:`3877`) - broadcast kitten: Fix a regression in ``0.20.0`` that broke sending of some keys, such as backspace - Linux binary: Remove any RPATH build artifacts from bundled libraries - Wayland: If the compositor turns off server side decorations after turning them on do not draw client side decorations (:iss:`3888`) 0.22.0 [2021-07-26] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add a new :ac:`toggle_layout` action to easily zoom/unzoom a window - When right clicking to extend a selection, move the nearest selection boundary rather than the end of the selection. To restore previous behavior use ``mouse_map right press ungrabbed mouse_selection move-end``. - When opening hyperlinks, allow defining open actions for directories (:pull:`3836`) - When using the OSC 52 escape code to copy to clipboard allow large copies (up to 8MB) without needing a kitty specific chunking protocol. Note that if you used the chunking protocol in the past, it will no longer work and you should switch to using the unmodified protocol which has the advantage of working with all terminal emulators. - Fix a bug in the implementation of the synchronized updates escape code that could cause incorrect parsing if either the pending buffer capacity or the pending timeout were exceeded (:iss:`3779`) - A new remote control command to :program:`resize the OS Window ` - Graphics protocol: Add support for composing rectangles from one animation frame onto another (:iss:`3809`) - diff kitten: Remove limit on max line length of 4096 characters (:iss:`3806`) - Fix turning off cursor blink via escape codes not working (:iss:`3808`) - Allow using neighboring window operations in the stack layout. The previous window is considered the left and top neighbor and the next window is considered the bottom and right neighbor (:iss:`3778`) - macOS: Render colors in the sRGB colorspace to match other macOS terminal applications (:iss:`2249`) - Add a new variable ``{num_window_groups}`` for the :opt:`tab_title_template` (:iss:`3837`) - Wayland: Fix :opt:`initial_window_width/height ` specified in cells not working on High DPI screens (:iss:`3834`) - A new theme for the kitty website with support for dark mode. - Render ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ with spaces at the edges. Matches rendering in most other programs and allows long chains of them to look better (:iss:`3844`) - hints kitten: Detect paths and hashes that appear over multiple lines. Note that this means that all line breaks in the text are no longer \n soft breaks are instead \r. If you use a custom regular expression that is meant to match over line breaks, you will need to match over both. (:iss:`3845`) - Allow leading or trailing spaces in :opt:`tab_activity_symbol` - Fix mouse actions not working when caps lock or num lock are engaged (:iss:`3859`) - macOS: Fix automatic detection of bold/italic faces for fonts that use the family name as the full face name of the regular font not working (:iss:`3861`) - clipboard kitten: fix copies to clipboard not working without the :option:`kitty +kitten clipboard --wait-for-completion` option 0.21.2 [2021-06-28] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new ``adjust_baseline`` option to adjust the vertical alignment of text inside a line (:pull:`3734`) - A new :opt:`url_excluded_characters` option to exclude additional characters when detecting URLs under the mouse (:pull:`3738`) - Fix a regression in 0.21.0 that broke rendering of private use Unicode symbols followed by spaces, when they also exist not followed by spaces (:iss:`3729`) - ssh kitten: Support systems where the login shell is a non-POSIX shell (:iss:`3405`) - ssh kitten: Add completion (:iss:`3760`) - ssh kitten: Fix "Connection closed" message being printed by ssh when running remote commands - Add support for the XTVERSION escape code - macOS: Fix a regression in 0.21.0 that broke middle-click to paste from clipboard (:iss:`3730`) - macOS: Fix shortcuts in the global menu bar responding slowly when cursor blink is disabled/timed out (:iss:`3693`) - When displaying scrollback ensure that the window does not quit if the amount of scrollback is less than a screen and the user has the ``--quit-if-one-screen`` option enabled for less (:iss:`3740`) - Linux: Fix Emoji/bitmapped fonts not use able in symbol_map - query terminal kitten: Allow querying font face and size information (:iss:`3756`) - hyperlinked grep kitten: Fix context options not generating contextual output (:iss:`3759`) - Allow using superscripts in tab titles (:iss:`3763`) - Unicode input kitten: Fix searching when a word has more than 1024 matches (:iss:`3773`) 0.21.1 [2021-06-14] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - macOS: Fix a regression in the previous release that broke rendering of strikeout (:iss:`3717`) - macOS: Fix a crash when rendering ligatures larger than 128 characters (:iss:`3724`) - Fix a regression in the previous release that could cause a crash when changing layouts and mousing (:iss:`3713`) 0.21.0 [2021-06-12] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Allow reloading the :file:`kitty.conf` config file by pressing :sc:`reload_config_file`. (:iss:`1292`) - Allow clicking URLs to open them without needing to also hold :kbd:`ctrl+shift` - Allow remapping all mouse button press/release events to perform arbitrary actions. :ref:`See details ` (:iss:`1033`) - Support infinite length ligatures (:iss:`3504`) - **Backward incompatibility**: The options to control which modifiers keys to press for various mouse actions have been removed, if you used these options, you will need to replace them with configuration using the new :ref:`mouse actions framework ` as they will be ignored. The options were: ``terminal_select_modifiers``, ``rectangle_select_modifiers`` and ``open_url_modifiers``. - Add a configurable mouse action (:kbd:`ctrl+alt+triplepress` to select from the clicked point to the end of the line. (:iss:`3585`) - Add the ability to un-scroll the screen to the ``kitty @ scroll-window`` remote control command (:iss:`3604`) - A new option, :opt:`tab_bar_margin_height` to add margins around the top and bottom edges of the tab bar (:iss:`3247`) - Unicode input kitten: Fix a regression in 0.20.0 that broke keyboard handling when the NumLock or CapsLock modifiers were engaged. (:iss:`3587`) - Fix a regression in 0.20.0 that sent incorrect bytes for the :kbd:`F1`-:kbd:`F4` keys in rmkx mode (:iss:`3586`) - macOS: When the Apple Color Emoji font lacks an emoji glyph search for it in other installed fonts (:iss:`3591`) - macOS: Fix rendering getting stuck on some machines after sleep/screensaver (:iss:`2016`) - macOS: Add a new ``Shell`` menu to the global menubar with some commonly used actions (:pull:`3653`) - macOS: Fix the baseline for text not matching other CoreText based applications for some fonts (:iss:`2022`) - Add a few more special commandline arguments for the launch command. Now all ``KITTY_PIPE_DATA`` is also available via command line argument substitution (:iss:`3593`) - Fix dynamically changing the background color in a window causing rendering artifacts in the tab bar (:iss:`3595`) - Fix passing STDIN to launched background processes causing them to not inherit environment variables (:pull:`3603`) - Fix deleting windows that are not the last window via remote control leaving no window focused (:iss:`3619`) - Add an option :option:`kitten @ get-text --add-cursor` to also get the current cursor position and state as ANSI escape codes (:iss:`3625`) - Add an option :option:`kitten @ get-text --add-wrap-markers` to add line wrap markers to the output (:pull:`3633`) - Improve rendering of curly underlines on HiDPI screens (:pull:`3637`) - ssh kitten: Mimic behavior of ssh command line client more closely by executing any command specified on the command line via the users' shell just as ssh does (:iss:`3638`) - Fix trailing parentheses in URLs not being detected (:iss:`3688`) - Tab bar: Use a lower contrast color for tab separators (:pull:`3666`) - Fix a regression that caused using the ``title`` command in session files to stop working (:iss:`3676`) - macOS: Fix a rare crash on exit (:iss:`3686`) - Fix ligatures not working with the `Iosevka `_ font (requires Iosevka >= 7.0.4) (:iss:`297`) - Remote control: Allow matching tabs by index number in currently active OS Window (:iss:`3708`) - ssh kitten: Fix non-standard properties in terminfo such as the ones used for true color not being copied (:iss:`312`) 0.20.3 [2021-05-06] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - macOS: Distribute universal binaries with both ARM and Intel architectures - A new ``show_key`` kitten to easily see the bytes generated by the terminal for key presses in the various keyboard modes (:pull:`3556`) - Linux: Fix keyboard layout change keys defined via compose rules not being ignored - macOS: Fix Spotlight search of global menu not working in non-English locales (:pull:`3567`) - Fix tab activity symbol not appearing if no other changes happen in tab bar even when there is activity in a tab (:iss:`3571`) - Fix focus changes not being sent to windows when focused window changes because of the previously focused window being closed (:iss:`3571`) 0.20.2 [2021-04-28] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new protocol extension to :ref:`unscroll ` text from the scrollback buffer onto the screen. Useful, for example, to restore the screen after showing completions below the shell prompt. - A new remote control command :ref:`at-env` to change the default environment passed to newly created windows (:iss:`3529`) - Linux: Fix binary kitty builds not able to load fonts in WOFF2 format (:iss:`3506`) - macOS: Prevent :kbd:`option` based shortcuts for being used for global menu actions (:iss:`3515`) - Fix ``kitty @ close-tab`` not working with pipe based remote control (:iss:`3510`) - Fix removal of inactive tab that is before the currently active tab causing the highlighted tab to be incorrect (:iss:`3516`) - icat kitten: Respect EXIF orientation when displaying JPEG images (:iss:`3518`) - GNOME: Fix maximize state not being remembered when focus changes and window decorations are hidden (:iss:`3507`) - GNOME: Add a new :opt:`wayland_titlebar_color` option to control the color of the kitty window title bar - Fix reading :option:`kitty --session` from ``STDIN`` not working when the :code:`kitty --detach` option is used (:iss:`3523`) - Special case rendering of the few remaining Powerline box drawing chars (:iss:`3535`) - Fix ``kitty @ set-colors`` not working for the :opt:`active_tab_foreground`. 0.20.1 [2021-04-19] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - icat: Fix some broken GIF images with no frame delays not being animated (:iss:`3498`) - hints kitten: Fix sending hyperlinks to their default handler not working (:pull:`3500`) - Wayland: Fix regression in previous release causing window decorations to be drawn even when compositor supports server side decorations (:iss:`3501`) 0.20.0 [2021-04-19] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Support display of animated images ``kitty +kitten icat animation.gif``. See :ref:`animation_protocol` for details on animation support in the kitty graphics protocol. - A new keyboard reporting protocol with various advanced features that can be used by full screen terminal programs and even games, see :doc:`keyboard-protocol` (:iss:`3248`) - **Backward incompatibility**: Session files now use the full :doc:`launch ` command with all its capabilities. However, the syntax of the command is slightly different from before. In particular watchers are now specified directly on launch and environment variables are set using ``--env``. - Allow setting colors when creating windows using the :doc:`launch ` command. - A new option :opt:`tab_powerline_style` to control the appearance of the tab bar when using the powerline tab bar style. - A new option :opt:`scrollback_fill_enlarged_window` to fill extra lines in the window when the window is expanded with lines from the scrollback (:pull:`3371`) - diff kitten: Implement recursive diff over SSH (:iss:`3268`) - ssh kitten: Allow using python instead of the shell on the server, useful if the shell used is a non-POSIX compliant one, such as fish (:iss:`3277`) - Add support for the color settings stack that XTerm copied from us without acknowledgement and decided to use incompatible escape codes for. - Add entries to the terminfo file for some user capabilities that are shared with XTerm (:pull:`3193`) - The launch command now does more sophisticated resolving of executables to run. The system-wide PATH is used first, then system specific default paths, and finally the PATH inside the shell. - Double clicking on empty tab bar area now opens a new tab (:iss:`3201`) - kitty @ ls: Show only environment variables that are different for each window, by default. - When passing a directory or a non-executable file as the program to run to kitty opens it with the shell or by parsing the shebang, instead of just failing. - Linux: Fix rendering of emoji followed by the graphics variation selector not being colored with some fonts (:iss:`3211`) - Unicode input: Fix using index in select by name mode not working for indices larger than 16. Also using an index does not filter the list of matches. (:pull:`3219`) - Wayland: Add support for the text input protocol (:iss:`3410`) - Wayland: Fix mouse handling when using client side decorations - Wayland: Fix un-maximizing a window not restoring its size to what it was before being maximized - GNOME/Wayland: Improve window decorations the titlebar now shows the window title. Allow running under Wayland on GNOME by default. (:iss:`3284`) - Panel kitten: Allow setting WM_CLASS (:iss:`3233`) - macOS: Add menu items to close the OS window and the current tab (:pull:`3240`, :iss:`3246`) - macOS: Allow opening script and command files with kitty (:iss:`3366`) - Also detect ``gemini://`` URLs when hovering with the mouse (:iss:`3370`) - When using a non-US keyboard layout and pressing :kbd:`ctrl+key` when the key matches an English key, send that to the program running in the terminal automatically (:iss:`2000`) - When matching shortcuts, also match on shifted keys, so a shortcut defined as :kbd:`ctrl+plus` will match a keyboard where you have to press :kbd:`shift+equal` to get the plus key (:iss:`2000`) - Fix extra space at bottom of OS window when using the fat layout with the tab bar at the top (:iss:`3258`) - Fix window icon not working on X11 with 64bits (:iss:`3260`) - Fix OS window sizes under 100px resulting in scaled display (:iss:`3307`) - Fix rendering of ligatures in the latest release of Cascadia code, which for some reason puts empty glyphs after the ligature glyph rather than before it (:iss:`3313`) - Improve handling of infinite length ligatures in newer versions of FiraCode and CascadiaCode. Now such ligatures are detected based on glyph naming convention. This removes the gap in the ligatures at cell boundaries (:iss:`2695`) - macOS: Disable the native operating system tabs as they are non-functional and can be confusing (:iss:`3325`) - hints kitten: When using the linenumber action with a background action, preserve the working directory (:iss:`3352`) - Graphics protocol: Fix suppression of responses not working for chunked transmission (:iss:`3375`) - Fix inactive tab closing causing active tab to change (:iss:`3398`) - Fix a crash on systems using musl as libc (:iss:`3395`) - Improve rendering of rounded corners by using a rectircle equation rather than a cubic bezier (:iss:`3409`) - Graphics protocol: Add a control to allow clients to specify that the cursor should not move when displaying an image (:iss:`3411`) - Fix marking of text not working on lines that contain zero cells (:iss:`3403`) - Fix the selection getting changed if the screen contents scroll while the selection is in progress (:iss:`3431`) - X11: Fix :opt:`resize_in_steps` being applied even when window is maximized (:iss:`3473`) 0.19.3 [2020-12-19] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Happy holidays to all kitty users! - A new :doc:`broadcast ` kitten to type in all kitty windows simultaneously (:iss:`1569`) - Add a new mappable `select_tab` action to choose a tab to switch to even when the tab bar is hidden (:iss:`3115`) - Allow specifying text formatting in :opt:`tab_title_template` (:iss:`3146`) - Linux: Read :opt:`font_features` from the FontConfig database as well, so that they can be configured in a single, central location (:pull:`3174`) - Graphics protocol: Add support for giving individual image placements their own ids and for asking the terminal emulator to assign ids for images. Also allow suppressing responses from the terminal to commands. These are backwards compatible protocol extensions. (:iss:`3133`, :iss:`3163`) - Distribute extra pixels among all eight-blocks rather than adding them all to the last block (:iss:`3097`) - Fix drawing of a few sextant characters incorrect (:pull:`3105`) - macOS: Fix minimize not working for chromeless windows (:iss:`3112`) - Preserve lines in the scrollback if a scrolling region is defined that is contiguous with the top of the screen (:iss:`3113`) - Wayland: Fix key repeat being stopped by the release of an unrelated key (:iss:`2191`) - Add an option, :opt:`detect_urls` to control whether kitty will detect URLs when the mouse moves over them (:pull:`3118`) - Graphics protocol: Dont return filename in the error message when opening file fails, since filenames can contain control characters (:iss:`3128`) - macOS: Partial fix for traditional fullscreen not working on Big Sur (:iss:`3100`) - Fix one ANSI formatting escape code not being removed from the pager history buffer when piping it as plain text (:iss:`3132`) - Match the save/restore cursor behavior of other terminals, for the sake of interoperability. This means that doing a DECRC without a prior DECSC is now undefined (:iss:`1264`) - Fix mapping ``remote_control send-text`` not working (:iss:`3147`) - Add a ``right`` option for :opt:`tab_switch_strategy` (:pull:`3155`) - Fix a regression in 0.19.0 that caused a rare crash when using the optional :opt:`scrollback_pager_history_size` (:iss:`3049`) - Full screen kittens: Fix incorrect cursor position after kitten quits (:iss:`3176`) 0.19.2 [2020-11-13] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new :doc:`kittens/query_terminal` kitten to easily query the running kitty via escape codes to detect its version, and the values of configuration options that enable or disable terminal features. - Options to control mouse pointer shape, :opt:`default_pointer_shape`, and :opt:`pointer_shape_when_dragging` (:pull:`3041`) - Font independent rendering for braille characters, which ensures they are properly aligned at all font sizes. - Fix a regression in 0.19.0 that caused borders not to be drawn when setting :opt:`window_margin_width` and keeping :opt:`draw_minimal_borders` on (:iss:`3017`) - Fix a regression in 0.19.0 that broke rendering of one-eight bar unicode characters at very small font sizes (:iss:`3025`) - Wayland: Fix a crash under GNOME when using multiple OS windows (:pull:`3066`) - Fix selections created by dragging upwards not being auto-cleared when screen contents change (:pull:`3028`) - macOS: Fix kitty not being added to PATH automatically when using pre-built binaries (:iss:`3063`) - Allow adding MIME definitions to kitty by placing a ``mime.types`` file in the kitty config directory (:iss:`3056`) - Dont ignore :option:`--title` when using a session file that defines no windows (:iss:`3055`) - Fix the send_text action not working in URL handlers (:iss:`3081`) - Fix last character of URL not being detected if it is the only character on a new line (:iss:`3088`) - Don't restrict the ICH,DCH,REP control codes to only the current scroll region (:iss:`3090`, :iss:`3096`) 0.19.1 [2020-10-06] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - hints kitten: Add an ``ip`` type for easy selection of IP addresses (:pull:`3009`) - Fix a regression that caused a segfault when using :opt:`scrollback_pager_history_size` and it needs to be expanded (:iss:`3011`) - Fix update available notifications repeating (:pull:`3006`) 0.19.0 [2020-10-04] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add support for `hyperlinks from terminal programs `_. Controlled via :opt:`allow_hyperlinks` (:iss:`68`) - Add support for easily editing or downloading files over SSH sessions without the need for any special software, see :doc:`kittens/remote_file` - A new :doc:`kittens/hyperlinked_grep` kitten to easily search files and open the results at the matched line by clicking on them. - Allow customizing the :doc:`actions kitty takes ` when clicking on URLs - Improve rendering of borders when using minimal borders. Use less space and do not display a box around active windows - Add a new extensible escape code to allow terminal programs to trigger desktop notifications. See :ref:`desktop_notifications` (:iss:`1474`) - Implement special rendering for various characters from the set of "Symbols for Legacy Computing" from the Unicode 13 standard - Unicode input kitten: Allow choosing symbols from the NERD font as well. These are mostly Private Use symbols not in any standard, however are common. (:iss:`2972`) - Allow specifying border sizes in either pts or pixels. Change the default to 0.5pt borders as this works best with the new minimal border style - Add support for displaying correct colors with non-sRGB PNG files (Adds a dependency on liblcms2) - hints kitten: Add a new :option:`kitty +kitten hints --type` of ``hyperlink`` useful for activating hyperlinks using just the keyboard - Allow tracking focus change events in watchers (:iss:`2918`) - Allow specifying watchers in session files and via a command line argument (:iss:`2933`) - Add a setting :opt:`tab_activity_symbol` to show a symbol in the tab title if one of the windows has some activity after it was last focused (:iss:`2515`) - macOS: Switch to using the User Notifications framework for notifications. The current notifications framework has been deprecated in Big Sur. The new framework only allows notifications from signed and notarized applications, so people using kitty from homebrew/source are out of luck. Complain to Apple. - When in the main screen and a program grabs the mouse, do not use the scroll wheel events to scroll the scrollback buffer, instead send them to the program (:iss:`2939`) - Fix unfocused windows in which a bell occurs not changing their border color to red until a relayout - Linux: Fix automatic detection of bold/italic faces for fonts such as IBM Plex Mono that have the regular face with a full name that is the same as the family name (:iss:`2951`) - Fix a regression that broke :opt:`kitten_alias` (:iss:`2952`) - Fix a regression that broke the ``move_window_to_top`` action (:pull:`2953`) - Fix a memory leak when changing font sizes - Fix some lines in the scrollback buffer not being properly rendered after a window resize/font size change (:iss:`2619`) 0.18.3 [2020-08-11] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - hints kitten: Allow customizing hint colors (:pull:`2894`) - Wayland: Fix a typo in the previous release that broke reading mouse cursor size (:iss:`2895`) - Fix a regression in the previous release that could cause an exception during startup in rare circumstances (:iss:`2896`) - Fix image leaving behind a black rectangle when switch away and back to alternate screen (:iss:`2901`) - Fix one pixel misalignment of rounded corners when either the cell dimensions or the thickness of the line is an odd number of pixels (:iss:`2907`) - Fix a regression that broke specifying OS window size in the session file (:iss:`2908`) 0.18.2 [2020-07-28] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - X11: Improve handling of multiple keyboards. Now pressing a modifier key in one keyboard and a normal key in another works (:iss:`2362`). Don't rebuild keymaps on new keyboard events that only change geometry (:iss:`2787`). Better handling of multiple keyboards with incompatible layouts (:iss:`2726`) - Improve anti-aliasing of triangular box drawing characters, noticeable on low-resolution screens (:iss:`2844`) - Fix ``kitty @ send-text`` not working reliably when using a socket for remote control (:iss:`2852`) - Implement support for box drawing rounded-corners characters (:iss:`2240`) - Allow setting the class for new OS windows in a session file - When a character from the Unicode Dingbat block is followed by a space, use the extra space to render a larger version of the character (:iss:`2850`) - macOS: Fix the LC_CTYPE env var being set to UTF-8 on systems in which the language and country code do not form a valid locale (:iss:`1233`) - macOS: Fix :kbd:`cmd+plus` not changing font size (:iss:`2839`) - Make neighboring window selection in grid and splits layouts more intelligent (:pull:`2840`) - Allow passing the current selection to kittens (:iss:`2796`) - Fix pre-edit text not always being cleared with ibus input (:iss:`2862`) - Allow setting the :opt:`background_opacity` of new OS windows created via :option:`kitty --single-instance` using the :option:`kitty --override` command line argument (:iss:`2806`) - Fix the CSI J (Erase in display ED) escape code not removing line continued markers (:iss:`2809`) - hints kitten: In linenumber mode expand paths that starts with ~ (:iss:`2822`) - Fix ``launch --location=last`` not working (:iss:`2841`) - Fix incorrect centering when a PUA or symbol glyph is followed by more than one space - Have the :opt:`confirm_os_window_close` option also apply when closing tabs with multiple windows (:iss:`2857`) - Add support for legacy DECSET codes 47, 1047 and 1048 (:pull:`2871`) - macOS: no longer render emoji 20% below the baseline. This caused some emoji to be cut-off and also look misaligned with very high cells (:iss:`2873`) - macOS: Make the window id of OS windows available in the ``WINDOWID`` environment variable (:pull:`2877`) - Wayland: Fix a regression in 0.18.0 that could cause crashes related to mouse cursors in some rare circumstances (:iss:`2810`) - Fix change in window size that does not change number of cells not being reported to the kernel (:iss:`2880`) 0.18.1 [2020-06-23] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - macOS: Fix for diff kitten not working with python 3.8 (:iss:`2780`) 0.18.0 [2020-06-20] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Allow multiple overlay windows per normal window - Add an option :opt:`confirm_os_window_close` to ask for confirmation when closing an OS window with multiple kitty windows. - Tall and Fat layouts: Add a ``mirrored`` option to put the full size window on the opposite edge of the screen (:iss:`2654`) - Tall and Fat layouts: Add mappable actions to increase or decrease the number of full size windows (:iss:`2688`) - Allow sending arbitrary signals to the current foreground process in a window using either a mapping in kitty.conf or via remote control (:iss:`2778`) - Allow sending the back and forward mouse buttons to terminal applications (:pull:`2742`) - **Backwards incompatibility**: The numbers used to encode mouse buttons for the ``send_mouse_event`` function that can be used in kittens have been changed (see :ref:`send_mouse_event`). - Add a new mappable ``quit`` action to quit kitty completely. - Fix marks using different colors with regexes using only a single color (:pull:`2663`) - Linux: Workaround for broken Nvidia drivers for old cards (:iss:`456`) - Wayland: Fix kitty being killed on some Wayland compositors if a hidden window has a lot of output (:iss:`2329`) - BSD: Fix controlling terminal not being established (:pull:`2686`) - Add support for the CSI REP escape code (:pull:`2702`) - Wayland: Fix mouse cursor rendering on HiDPI screens (:pull:`2709`) - X11: Recompile keymaps on XkbNewKeyboardNotify events (:iss:`2726`) - X11: Reduce startup time by ~25% by only querying GLX for framebuffer configurations once (:iss:`2754`) - macOS: Notarize the kitty application bundle (:iss:`2040`) - Fix the kitty shell launched via a mapping needlessly requiring :opt:`allow_remote_control` to be turned on. 0.17.4 [2020-05-09] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Allow showing the name of the current layout and the number of windows in tab titles (:iss:`2634`) - macOS: Fix a regression in the previous release that caused ligatures to be not be centered horizontally (:iss:`2591`) - By default, double clicking no longer considers the : as part of words, see :opt:`select_by_word_characters` (:iss:`2602`) - Fix a regression that caused clicking in the padding/margins of windows in the stack layout to switch the window to the first window (:iss:`2604`) - macOS: Fix a regression that broke drag and drop (:iss:`2605`) - Report modifier key state when sending wheel events to the terminal program - Fix kitty @ send-text not working with text larger than 1024 bytes when using :option:`kitty --listen-on` (:iss:`2607`) - Wayland: Fix OS window title not updating for hidden windows (:iss:`2629`) - Fix :opt:`background_tint` making the window semi-transparent (:iss:`2618`) 0.17.3 [2020-04-23] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Allow individually setting margins and padding for each edge (left, right, top, bottom). Margins can also be controlled per window via remote control (:iss:`2546`) - Fix reverse video not being rendered correctly when using transparency or a background image (:iss:`2419`) - Allow mapping arbitrary remote control commands to key presses in :file:`kitty.conf` - X11: Fix crash when doing drag and drop from some applications (:iss:`2505`) - Fix :option:`launch --stdin-add-formatting` not working (:iss:`2512`) - Update to Unicode 13.0 (:iss:`2513`) - Render country flags designated by a pair of unicode codepoints in two cells instead of four. - diff kitten: New option to control the background color for filler lines in the margin (:iss:`2518`) - Fix specifying options for layouts in the startup session file not working (:iss:`2520`) - macOS: Fix incorrect horizontal positioning of some full-width East Asian characters (:iss:`1457`) - macOS: Render multi-cell PUA characters centered, matching behavior on other platforms - Linux: Ignore keys if they are designated as layout/group/mode switch keys (:iss:`2519`) - Marks: Fix marks not handling wide characters and tab characters correctly (:iss:`2534`) - Add a new :opt:`listen_on` option in kitty.conf to set :option:`kitty --listen-on` globally. Also allow using environment variables in this option (:iss:`2569`). - Allow sending mouse events in kittens (:pull:`2538`) - icat kitten: Fix display of 16-bit depth images (:iss:`2542`) - Add ncurses specific terminfo definitions for strikethrough (:pull:`2567`) - Fix a regression in 0.17 that broke displaying graphics over SSH (:iss:`2568`) - Fix :option:`--title` not being applied at window creation time (:iss:`2570`) 0.17.2 [2020-03-29] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add a :option:`launch --watcher` option that allows defining callbacks that are called for various events in the window's life-cycle (:iss:`2440`) - Fix a regression in 0.17 that broke drawing of borders with non-minimal borders (:iss:`2474`) - Hints kitten: Allow copying to primary selection as well as clipboard (:pull:`2487`) - Add a new mappable action ``close_other_windows_in_tab`` to close all but the active window (:iss:`2484`) - Hints kitten: Adjust the default regex used to detect line numbers to handle line+column numbers (:iss:`2268`) - Fix blank space at the start of tab bar in the powerline style when first tab is inactive (:iss:`2478`) - Fix regression causing incorrect rendering of separators in tab bar when defining a tab bar background color (:pull:`2480`) - Fix a regression in 0.17 that broke the kitty @ launch remote command and also broke the --tab-title option when creating a new tab. (:iss:`2488`) - Linux: Fix selection of fonts with multiple width variants not preferring the normal width faces (:iss:`2491`) 0.17.1 [2020-03-24] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Fix :opt:`cursor_underline_thickness` not working (:iss:`2465`) - Fix a regression in 0.17 that caused tab bar background to be rendered after the last tab as well (:iss:`2464`) - macOS: Fix a regression in 0.17 that caused incorrect variants to be automatically selected for some fonts (:iss:`2462`) - Fix a regression in 0.17 that caused kitty @ set-colors to require setting cursor_text_color (:iss:`2470`) 0.17.0 [2020-03-24] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :ref:`splits_layout` to arrange windows in arbitrary splits (:iss:`2308`) - Add support for specifying a background image, see :opt:`background_image` (:iss:`163` and :pull:`2326`; thanks to Fredrick Brennan.) - A new :opt:`background_tint` option to darken the background under the text area when using background images and/or transparent windows. - Allow selection of single cells with the mouse. Also improve mouse selection to follow semantics common to most programs (:iss:`945`) - New options :opt:`cursor_beam_thickness` and :opt:`cursor_underline_thickness` to control the thickness of the beam and underline cursors (:iss:`2337` and :pull:`2342`) - When the application running in the terminal grabs the mouse, pass middle clicks to the application unless `terminal_select_modifiers` are pressed (:iss:`2368`) - A new ``copy_and_clear_or_interrupt`` function (:iss:`2403`) - X11: Fix arrow mouse cursor using right pointing instead of the default left pointing arrow (:iss:`2341`) - Allow passing the currently active kitty window id in the launch command (:iss:`2391`) - unicode input kitten: Allow pressing :kbd:`ctrl+tab` to change the input mode (:iss:`2343`) - Fix a bug that prevented using custom functions with the new marks feature (:iss:`2344`) - Make the set of URL prefixes that are recognized while hovering with the mouse configurable (:iss:`2416`) - Fix border/margin/padding sizes not being recalculated on DPI change (:iss:`2346`) - diff kitten: Fix directory diffing with removed binary files failing (:iss:`2378`) - macOS: Fix menubar title not updating on OS Window focus change (:iss:`2350`) - Fix rendering of combining characters with fonts that have glyphs for precomposed characters but not decomposed versions (:iss:`2365`) - Fix incorrect rendering of selection when using rectangular select and scrolling (:iss:`2351`) - Allow setting WM_CLASS and WM_NAME when creating new OS windows with the launch command (:option:`launch --os-window-class`) - macOS: When switching input method while a pending multi-key input is in progress, clear the pending input (:iss:`2358`) - Fix a regression in the previous release that broke switching to neighboring windows in the Grid layout when there are less than four windows (:iss:`2377`) - Fix colors in scrollback pager off if the window has redefined terminal colors using escape codes (:iss:`2381`) - Fix selection not updating properly while scrolling (:iss:`2442`) - Allow extending selections by dragging with right button pressed (:iss:`2445`) - Workaround for bug in less that causes colors to reset at wrapped lines (:iss:`2381`) - X11/Wayland: Allow drag and drop of text/plain in addition to text/uri-list (:iss:`2441`) - Dont strip :code:`&` and :code:`-` from the end of URLs (:iss:`2436`) - Fix ``@selection`` placeholder not working with launch command (:iss:`2417`) - Drop support for python 3.5 - Wayland: Fix a crash when drag and dropping into kitty (:iss:`2432`) - diff kitten: Fix images lingering as blank rectangles after the kitten quits (:iss:`2449`) - diff kitten: Fix images losing position when scrolling using mouse wheel/touchpad 0.16.0 [2020-01-28] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new :doc:`marks` feature that allows highlighting and scrolling to arbitrary text in the terminal window. - hints kitten: Allow pressing :sc:`goto_file_line` to quickly open the selected file at the selected line in vim or a configurable editor (:iss:`2268`) - Allow having more than one full height window in the :code:`tall` layout (:iss:`2276`) - Allow choosing OpenType features for individual fonts via the :opt:`font_features` option. (:pull:`2248`) - Wayland: Fix a freeze in rare circumstances when having multiple OS Windows (:iss:`2307` and :iss:`1722`) - Wayland: Fix window titles being set to very long strings on the order of 8KB causing a crash (:iss:`1526`) - Add an option :opt:`force_ltr` to turn off the display of text in RTL scripts in right-to-left order (:pull:`2293`) - Allow opening new tabs/windows before the current tab/window as well as after it with the :option:`launch --location` option. - Add a :opt:`resize_in_steps` option that can be used to resize the OS window in steps as large as character cells (:pull:`2131`) - When triple-click+dragging to select multiple lines, extend the selection of the first line to match the rest on the left (:pull:`2284`) - macOS: Add a :code:`titlebar-only` setting to :opt:`hide_window_decorations` to only hide the title bar (:pull:`2286`) - Fix a segfault when using ``--debug-config`` with maps (:iss:`2270`) - ``goto_tab`` now maps numbers larger than the last tab to the last tab (:iss:`2291`) - Fix URL detection not working for urls of the form scheme:///url (:iss:`2292`) - When windows are semi-transparent and all contain graphics, correctly render them. (:iss:`2310`) 0.15.1 [2019-12-21] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Fix a crash/incorrect rendering when detaching a window in some circumstances (:iss:`2173`) - hints kitten: Add an option :option:`kitty +kitten hints --ascending` to control if the hints numbers increase or decrease from top to bottom - Fix :opt:`background_opacity` incorrectly applying to selected text and reverse video text (:iss:`2177`) - Add a new option :opt:`tab_bar_background` to specify a different color for the tab bar (:iss:`2198`) - Add a new option :opt:`active_tab_title_template` to specify a different template for active tab titles (:iss:`2198`) - Fix lines at the edge of the window at certain windows sizes when drawing images on a transparent window (:iss:`2079`) - Fix window not being rendered for the first time until some input has been received from child process (:iss:`2216`) 0.15.0 [2019-11-27] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add a new action :ref:`detach_window ` that can be used to move the current window into a different tab (:iss:`1310`) - Add a new action :doc:`launch ` that unifies launching of processes in new kitty windows/tabs. - Add a new style ``powerline`` for tab bar rendering, see :opt:`tab_bar_style` (:pull:`2021`) - Allow changing colors by mapping a keyboard shortcut to read a kitty config file with color definitions. See the :doc:`FAQ ` for details (:iss:`2083`) - hints kitten: Allow completely customizing the matching and actions performed by the kitten using your own script (:iss:`2124`) - Wayland: Fix key repeat not being stopped when focus leaves window. This is expected behavior on Wayland, apparently (:iss:`2014`) - When drawing unicode symbols that are followed by spaces, use multiple cells to avoid resized or cut-off glyphs (:iss:`1452`) - diff kitten: Allow diffing remote files easily via ssh (:iss:`727`) - unicode input kitten: Add an option :option:`kitty +kitten unicode_input --emoji-variation` to control the presentation variant of selected emojis (:iss:`2139`) - Add specialised rendering for a few more box powerline and unicode symbols (:pull:`2074` and :pull:`2021`) - Add a new socket only mode for :opt:`allow_remote_control`. This makes it possible for programs running on the local machine to control kitty but not programs running over ssh. - hints kitten: Allow using named groups in the regular expression. The named groups are passed to the invoked program for further processing. - Fix a regression in 0.14.5 that caused rendering of private use glyphs with and without spaces to be identical (:iss:`2117`) - Wayland: Fix incorrect scale used when first creating an OS window (:iss:`2133`) - macOS: Disable mouse hiding by default as getting it to work robustly on Cocoa is too much effort (:iss:`2158`) 0.14.6 [2019-09-25] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - macOS: Fix a regression in the previous release that caused a crash when pressing a unprintable key, such as the POWER key (:iss:`1997`) - Fix a regression in the previous release that caused kitty to not always respond to DPI changes (:pull:`1999`) 0.14.5 [2019-09-23] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Implement a hack to (mostly) preserve tabs when cat-ting a file with them and then copying the text or passing screen contents to another program (:iss:`1829`) - When all visible windows have the same background color, use that as the color for the global padding, instead of the configured background color (:iss:`1957`) - When resetting the terminal, also reset parser state, this allows easy recovery from incomplete escape codes (:iss:`1961`) - Allow mapping keys commonly found on European keyboards (:pull:`1928`) - Fix incorrect rendering of some symbols when followed by a space while using the PowerLine font which does not have a space glyph (:iss:`1225`) - Linux: Allow using fonts with spacing=90 in addition to fonts with spacing=100 (:iss:`1968`) - Use selection foreground color for underlines as well (:iss:`1982`) 0.14.4 [2019-08-31] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - hints kitten: Add a :option:`kitty +kitten hints --alphabet` option to control what alphabets are used for hints (:iss:`1879`) - hints kitten: Allow specifying :option:`kitty +kitten hints --program` multiple times to run multiple programs (:iss:`1879`) - Add a :opt:`kitten_alias` option that can be used to alias kitten invocation for brevity and to change kitten option defaults globally (:iss:`1879`) - macOS: Add an option :opt:`macos_show_window_title_in` to control showing the window title in the menubar/titlebar (:pull:`1837`) - macOS: Allow drag and drop of text from other applications into kitty (:pull:`1921`) - When running kittens, use the colorscheme of the current window rather than the configured colorscheme (:iss:`1906`) - Don't fail to start if running the shell to read the EDITOR env var fails (:iss:`1869`) - Disable the ``liga`` and ``dlig`` OpenType features for broken fonts such as Nimbus Mono. - Fix a regression that broke setting background_opacity via remote control (:iss:`1895`) - Fix piping PNG images into the icat kitten not working (:iss:`1920`) - When the OS returns a fallback font that does not actually contain glyphs for the text, do not exhaust the list of fallback fonts (:iss:`1918`) - Fix formatting attributes not reset across line boundaries when passing buffer as ANSI (:iss:`1924`) 0.14.3 [2019-07-29] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Remote control: Add a command `kitty @ scroll-window` to scroll windows - Allow passing a ``!neighbor`` argument to the new_window mapping to open a new window next to the active window (:iss:`1746`) - Document the kitty remote control protocol (:iss:`1646`) - Add a new option :opt:`pointer_shape_when_grabbed` that allows you to control the mouse pointer shape when the terminal programs grabs the pointer (:iss:`1808`) - Add an option `terminal_select_modifiers` to control which modifiers are used to override mouse selection even when a terminal application has grabbed the mouse (:iss:`1774`) - When piping data to a child in the pipe command do it in a thread so as not to block the UI (:iss:`1708`) - unicode_input kitten: Fix a regression that broke using indices to select recently used symbols. - Fix a regression that caused closing an overlay window to focus the previously focused window rather than the underlying window (:iss:`1720`) - macOS: Reduce energy consumption when idle by shutting down Apple's display link thread after 30 second of inactivity (:iss:`1763`) - Linux: Fix incorrect scaling for fallback fonts when the font has an underscore that renders out of bounds (:iss:`1713`) - macOS: Fix finding fallback font for private use unicode symbols not working reliably (:iss:`1650`) - Fix an out of bounds read causing a crash when selecting text with the mouse in the alternate screen mode (:iss:`1578`) - Linux: Use the system "bell" sound for the terminal bell. Adds libcanberra as a new dependency to play the system sound. - macOS: Fix a rare deadlock causing kitty to hang (:iss:`1779`) - Linux: Fix a regression in 0.14.0 that caused the event loop to tick continuously, wasting CPU even when idle (:iss:`1782`) - ssh kitten: Make argument parsing more like ssh (:iss:`1787`) - When using :opt:`strip_trailing_spaces` do not remove empty lines (:iss:`1802`) - Fix a crash when displaying very large number of images (:iss:`1825`) 0.14.2 [2019-06-09] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add an option :opt:`placement_strategy` to control how the cell area is aligned inside the window when the window size is not an exact multiple of the cell size (:pull:`1670`) - hints kitten: Add a :option:`kitty +kitten hints --multiple-joiner` option to control how multiple selections are serialized when copying to clipboard or inserting into the terminal. You can have them on separate lines, separated by arbitrary characters, or even serialized as JSON (:iss:`1665`) - macOS: Fix a regression in the previous release that broke using :kbd:`ctrl+shift+tab` (:iss:`1671`) - panel kitten: Fix the contents of the panel kitten not being positioned correctly on the vertical axis - icat kitten: Fix a regression that broke passing directories to icat (:iss:`1683`) - clipboard kitten: Add a :option:`kitty +kitten clipboard --wait-for-completion` option to have the kitten wait till copying to clipboard is complete (:iss:`1693`) - Allow using the :doc:`pipe ` command to send screen and scrollback contents directly to the clipboard (:iss:`1693`) - Linux: Disable the Wayland backend on GNOME by default as GNOME has no support for server side decorations. Can be controlled by :opt:`linux_display_server`. - Add an option to control the default :opt:`update_check_interval` when building kitty packages - Wayland: Fix resizing the window on a compositor that does not provide server side window decorations, such a GNOME or Weston not working correctly (:iss:`1659`) - Wayland: Fix crash when enabling disabling monitors on sway (:iss:`1696`) 0.14.1 [2019-05-29] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add an option :opt:`command_on_bell` to run an arbitrary command when a bell occurs (:iss:`1660`) - Add a shortcut to toggle maximized window state :sc:`toggle_maximized` - Add support for the underscore key found in some keyboard layouts (:iss:`1639`) - Fix a missing newline when using the pipe command between the scrollback and screen contents (:iss:`1642`) - Fix colors not being preserved when using the pipe command with the pager history buffer (:pull:`1657`) - macOS: Fix a regression that could cause rendering of a kitty window to occasionally freeze in certain situations, such as moving it between monitors or transitioning from/to fullscreen (:iss:`1641`) - macOS: Fix a regression that caused :kbd:`cmd+v` to double up in the dvorak keyboard layout (:iss:`1652`) - When resizing and only a single window is present in the current layout, use that window's background color to fill in the blank areas. - Linux: Automatically increase cell height if the font being used is broken and draws the underscore outside the bounding box (:iss:`690`) - Wayland: Fix maximizing the window on a compositor that does not provide server side window decorations, such a GNOME or Weston not working (:iss:`1662`) 0.14.0 [2019-05-24] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - macOS: The default behavior of the Option key has changed. It now generates unicode characters rather than acting as the :kbd:`Alt` modifier. See :opt:`macos_option_as_alt`. - Support for an arbitrary number of internal clipboard buffers to copy/paste from, see (:ref:`cpbuf`) - Allow using the new private internal clipboard buffers with the :opt:`copy_on_select` option (:iss:`1390`) - macOS: Allow opening new kitty tabs/top-level windows from Finder (:pull:`1350`) - Add an option :opt:`disable_ligatures` to disable multi-character ligatures under the cursor to make editing easier or disable them completely (:iss:`461`) - Allow creating new OS windows in session files (:iss:`1514`) - Allow setting OS window size in session files - Add an option :opt:`tab_switch_strategy` to control which tab becomes active when the current tab is closed (:pull:`1524`) - Allow specifying a value of ``none`` for the :opt:`selection_foreground` which will cause kitty to not change text color in selections (:iss:`1358`) - Make live resizing of OS windows smoother and add an option ``resize_draw_strategy`` to control what is drawn while a resize is in progress. - macOS: Improve handling of IME extended input. Compose characters are now highlighted and the IME panel moves along with the text (:pull:`1586`). Also fixes handling of delete key in Chinese IME (:iss:`1461`) - When a window is closed, switch focus to the previously active window (if any) instead of picking the previous window in the layout (:iss:`1450`) - icat kitten: Add support for displaying images at http(s) URLs (:iss:`1340`) - A new option :opt:`strip_trailing_spaces` to optionally remove trailing spaces from lines when copying to clipboard. - A new option :opt:`tab_bar_min_tabs` to control how many tabs must be present before the tab-bar is shown (:iss:`1382`) - Automatically check for new releases and notify when an update is available, via the system notification facilities. Can be controlled by :opt:`update_check_interval` (:iss:`1342`) - macOS: Fix :kbd:`cmd+period` key not working (:iss:`1318`) - macOS: Add an option `macos_show_window_title_in_menubar` to not show the current window title in the menu-bar (:iss:`1066`) - macOS: Workaround for cocoa bug that could cause the mouse cursor to become hidden in other applications in rare circumstances (:iss:`1218`) - macOS: Allow assigning only the left or right :kbd:`Option` key to work as the :kbd:`Alt` key. See :opt:`macos_option_as_alt` for details (:iss:`1022`) - Fix using remote control to set cursor text color causing errors when creating new windows (:iss:`1326`) - Fix window title for minimized windows not being updated (:iss:`1332`) - macOS: Fix using multi-key sequences to input text ignoring the first few key presses if the sequence is aborted (:iss:`1311`) - macOS: Add a number of common macOS keyboard shortcuts - macOS: Reduce energy consumption by not rendering occluded windows - Fix scrollback pager history not being cleared when clearing the main scrollback buffer (:iss:`1387`) - macOS: When closing a top-level window only switch focus to the previous kitty window if it is on the same workspace (:iss:`1379`) - macOS: Fix :opt:`sync_to_monitor` not working on Mojave. - macOS: Use the system cursor blink interval by default :opt:`cursor_blink_interval`. - Wayland: Use the kitty Wayland backend by default. Can be switched back to using XWayland by setting the environment variable: ``KITTY_DISABLE_WAYLAND=1`` - Add a ``no-append`` setting to :opt:`clipboard_control` to disable the kitty copy concatenation protocol extension for OSC 52. - Update to using the Unicode 12 standard - Unicode input kitten: Allow using the arrow keys in code mode to go to next and previous unicode symbol. - macOS: Fix specifying initial window size in cells not working correctly on Retina screens (:iss:`1444`) - Fix a regression in version 0.13.0 that caused background colors of space characters after private use unicode characters to not be respected (:iss:`1455`) - Only update the selected text to clipboard when the selection is finished, not continuously as it is updated. (:iss:`1460`) - Allow setting :opt:`active_border_color` to ``none`` to not draw a border around the active window (:iss:`805`) - Use negative values for :opt:`mouse_hide_wait` to hide the mouse cursor immediately when pressing a key (:iss:`1534`) - When encountering errors in :file:`kitty.conf` report them to the user instead of failing to start. - Allow the user to control the resize debounce time via :opt:`resize_debounce_time`. - Remote control: Make the :ref:`at-set-font-size` command more capable. It can now increment font size and reset it. It also only acts on the active top-level window, by default (:iss:`1581`) - When launching child processes set the :code:`PWD` environment variable (:iss:`1595`) - X11: use the window manager's native full-screen implementation when making windows full-screen (:iss:`1605`) - Mouse selection: When extending by word, fix extending selection to non-word characters not working well (:iss:`1616`) 0.13.3 [2019-01-19] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - icat kitten: Add a ``--stdin`` option to control if image data is read from STDIN (:iss:`1308`) - hints kitten: Start hints numbering at one instead of zero by default. Added an option ``--hints-offset`` to control it. (:iss:`1289`) - Fix a regression in the previous release that broke using ``background`` for :opt:`cursor_text_color` (:iss:`1288`) - macOS: Fix dragging kitty window tabs in traditional full screen mode causing crashes (:iss:`1296`) - macOS: Ensure that when running from a bundle, the bundle kitty exe is preferred over any kitty in PATH (:iss:`1280`) - macOS: Fix a regression that broke mapping of :kbd:`ctrl+tab` (:iss:`1304`) - Add a list of user-created kittens to the docs - Fix a regression that broke changing mouse wheel scroll direction with negative :opt:`wheel_scroll_multiplier` values in full-screen applications like vim (:iss:`1299`) - Fix :opt:`background_opacity` not working with pure white backgrounds (:iss:`1285`) - macOS: Fix "New OS Window" dock action not working when kitty is not focused (:iss:`1312`) - macOS: Add aliases for close window and new tab actions that conform to common Apple shortcuts for these actions (:iss:`1313`) - macOS: Fix some kittens causing 100% CPU usage 0.13.2 [2019-01-04] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add a new option :opt:`tab_title_template` to control how tab titles are formatted. In particular the template can be used to display the tab number next to the title (:iss:`1223`) - Report the current foreground processes as well as the original child process, when using `kitty @ ls` - Use the current working directory of the foreground process for the `*_with_cwd` actions that open a new window with the current working directory. - Add a new ``copy_or_interrupt`` action that can be mapped to kbd:`ctrl+c`. It will copy if there is a selection and interrupt otherwise (:iss:`1286`) - Fix setting :opt:`background_opacity` causing window margins/padding to be slightly different shade from background (:iss:`1221`) - Handle keyboards with a "+" key (:iss:`1224`) - Fix Private use Unicode area characters followed by spaces at the end of text not being rendered correctly (:iss:`1210`) - macOS: Add an entry to the dock menu to open a new OS window (:iss:`1242`) - macOS: Fix scrolling very slowly with wheel mice not working (:iss:`1238`) - Fix changing :opt:`cursor_text_color` via remote control not working (:iss:`1229`) - Add an action to resize windows that can be mapped to shortcuts in :file:`kitty.conf` (:pull:`1245`) - Fix using the ``new_tab !neighbor`` action changing the order of the non-neighboring tabs (:iss:`1256`) - macOS: Fix momentum scrolling continuing when changing the active window/tab (:iss:`1267`) 0.13.1 [2018-12-06] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Fix passing input via the pipe action to a program without a window not working. - Linux: Fix a regression in the previous release that caused automatic selection of bold/italic fonts when using aliases such as "monospace" to not work (:iss:`1209`) - Fix resizing window smaller and then restoring causing some wrapped lines to not be properly unwrapped (:iss:`1206`) 0.13.0 [2018-12-05] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add an option :opt:`scrollback_pager_history_size` to tell kitty to store extended scrollback to use when viewing the scrollback buffer in a pager (:iss:`970`) - Modify the kittens sub-system to allow creating custom kittens without any user interface. This is useful for creating more complex actions that can be bound to key presses in :file:`kitty.conf`. See doc:`kittens/custom`. (:iss:`870`) - Add a new ``nth_window`` action that can be used to go to the nth window and also previously active windows, using negative numbers. Similarly, ``goto_tab`` now accepts negative numbers to go to previously active tabs (:iss:`1040`) - Allow hiding the tab bar completely, by setting :opt:`tab_bar_style` to ``hidden``. (:iss:`1014`) - Allow private use unicode characters to stretch over more than a single neighboring space (:pull:`1036`) - Add a new :opt:`touch_scroll_multiplier` option to modify the amount scrolled by high precision scrolling devices such as touchpads (:pull:`1129`) - icat kitten: Implement reading image data from STDIN, if STDIN is not connected to a terminal (:iss:`1130`) - hints kitten: Insert trailing spaces after matches when using the ``--multiple`` option. Also add a separate ``--add-trailing-space`` option to control this behavior (:pull:`1132`) - Fix the ``*_with_cwd`` actions using the cwd of the overlay window rather than the underlying window's cwd (:iss:`1045`) - Fix incorrect key repeat rate on wayland (:pull:`1055`) - macOS: Fix drag and drop of files not working on Mojave (:iss:`1058`) - macOS: Fix IME input for East Asian languages (:iss:`910`) - macOS: Fix rendering frames-per-second very low when processing large amounts of input in small chunks (:pull:`1082`) - macOS: Fix incorrect text sizes calculated when using an external display that is set to mirror the main display (:iss:`1056`) - macOS: Use the system default double click interval (:pull:`1090`) - macOS: Fix touch scrolling sensitivity low on retina screens (:iss:`1112`) - Linux: Fix incorrect rendering of some fonts when hinting is disabled at small sizes (:iss:`1173`) - Linux: Fix match rules used as aliases in Fontconfig configuration not being respected (:iss:`1085`) - Linux: Fix a crash when using the GNU Unifont as a fallback font (:iss:`1087`) - Wayland: Fix copying from hidden kitty windows hanging (:iss:`1051`) - Wayland: Add support for the primary selection protocol implemented by some compositors (:pull:`1095`) - Fix expansion of env vars not working in the :opt:`env` directive (:iss:`1075`) - Fix :opt:`mouse_hide_wait` only taking effect after an event such as cursor blink or key press (:iss:`1073`) - Fix the ``set_background_opacity`` action not working correctly (:pull:`1147`) - Fix second cell of emoji created using variation selectors not having the same attributes as the first cell (:iss:`1109`) - Fix focusing neighboring windows in the grid layout with less than 4 windows not working (:iss:`1115`) - Fix :kbd:`ctrl+shift+special` key not working in normal and application keyboard modes (:iss:`1114`) - Add a terminfo entry for full keyboard mode. - Fix incorrect text-antialiasing when using very low background opacity (:iss:`1005`) - When double or triple clicking ignore clicks if they are "far" from each other (:iss:`1093`) - Follow xterm's behavior for the menu key (:iss:`597`) - Fix hover detection of URLs not working when hovering over the first colon and slash characters in short URLs (:iss:`1201`) 0.12.3 [2018-09-29] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - macOS: Fix kitty window not being rendered on macOS Mojave until the window is moved or resized at least once (:iss:`887`) - Unicode input: Fix an error when searching for the string 'fir' (:iss:`1035`) 0.12.2 [2018-09-24] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new ``last_used_layout`` function that can be mapped to a shortcut to switch to the previously used window layout (:iss:`870`) - New ``neighboring_window`` and ``move_window`` functions to switch to neighboring windows in the current layout, and move them around, similar to window movement in vim (:iss:`916`) - A new ``pipe`` function that can be used to pipe the contents of the screen and scrollback buffer to any desired program running in a new window, tab or overlay window. (:iss:`933`) - Add a new :option:`kitty --start-as` command line flag to start kitty full-screen/maximized/minimized. This replaces the ``--start-in-fullscreen`` flag introduced in the previous release (:iss:`935`) - When mapping the ``new_tab`` action allow specifying that the tab should open next to the current tab instead of at the end of the tabs list (:iss:`979`) - macOS: Add a new :opt:`macos_thicken_font` to make text rendering on macs thicker, which makes it similar to the result of sub-pixel antialiasing (:pull:`950`) - macOS: Add an option :opt:`macos_traditional_fullscreen` to make full-screening of kitty windows much faster, but less pretty. (:iss:`911`) - Fix a bug causing incorrect line ordering when viewing the scrollback buffer if the scrollback buffer is full (:iss:`960`) - Fix drag-scrolling not working when the mouse leaves the window confines (:iss:`917`) - Workaround for broken editors like nano that cannot handle newlines in pasted text (:iss:`994`) - Linux: Ensure that the python embedded in the kitty binary build uses UTF-8 mode to process command-line arguments (:iss:`924`) - Linux: Handle fonts that contain monochrome bitmaps (such as the Terminus TTF font) (:pull:`934`) - Have the :option:`kitty --title` flag apply to all windows created using :option:`kitty --session` (:iss:`921`) - Revert change for backspacing of wide characters in the previous release, as it breaks backspacing in some wide character aware programs (:iss:`875`) - Fix kitty @set-colors not working for tab backgrounds when using the `fade` tabbar style (:iss:`937`) - macOS: Fix resizing semi-transparent windows causing the windows to be invisible during the resize (:iss:`941`) - Linux: Fix window icon not set on X11 for the first OS window (:iss:`961`) - macOS: Add an :opt:`macos_custom_beam_cursor` option to use a special mouse cursor image that can be seen on both light and dark backgrounds (:iss:`359`) - Remote control: Fix the ``focus_window`` command not focusing the top-level OS window of the specified kitty window (:iss:`1003`) - Fix using :opt:`focus_follows_mouse` causing text selection with the mouse to malfunction when using multiple kitty windows (:iss:`1002`) 0.12.1 [2018-09-08] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add a new ``--start-in-fullscreen`` command line flag to start kitty in full screen mode (:iss:`856`) - macOS: Fix a character that cannot be rendered in any font causing font fallback for all subsequent characters that cannot be rendered in the main font to fail (:iss:`799`) - Linux: Do not enable IME input via ibus unless the ``GLFW_IM_MODULE=ibus`` environment variable is set. IME causes key processing latency and even missed keystrokes for many people, so it is now off by default. - Fix backspacing of wide characters in wide-character unaware programs not working (:iss:`875`) - Linux: Fix number pad arrow keys not working when Numlock is off (:iss:`857`) - Wayland: Implement support for clipboard copy/paste (:iss:`855`) - Allow mapping shortcuts using the raw key code from the OS (:iss:`848`) - Allow mapping of individual key-presses without modifiers as shortcuts - Fix legacy invocation of icat as `kitty icat` not working (:iss:`850`) - Improve rendering of wavy underline at small font sizes (:iss:`853`) - Fix a regression in 0.12.0 that broke dynamic resizing of layouts (:iss:`860`) - Wayland: Allow using the :code:`kitty --class` command line flag to set the app id (:iss:`862`) - Add completion of the kitty command for the fish shell (:pull:`829`) - Linux: Fix XCompose rules with no defined symbol not working (:iss:`880`) - Linux: Fix crash with some Nvidia drivers when creating tabs in the first top level-window after creating a second top-level window. (:iss:`873`) - macOS: Diff kitten: Fix syntax highlighting not working because of a bug in the 0.12.0 macOS package 0.12.0 [2018-09-01] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Preserve the mouse selection even when the contents of the screen are scrolled or overwritten provided the new text does not intersect the selected lines. - Linux: Implement support for Input Method Extensions (multilingual input using standard keyboards) via `IBus `_ (:iss:`469`) - Implement completion for the kitty command in bash and zsh. See :ref:`shell_integration`. - Render the text under the cursor in a fixed color, configurable via the option :opt:`cursor_text_color` (:iss:`126`) - Add an option :opt:`env` to set environment variables in child processes from kitty.conf - Add an action to the ``clear_terminal`` function to scroll the screen contents into the scrollback buffer (:iss:`1113`) - Implement high precision scrolling with the trackpad on platforms such as macOS and Wayland that implement it. (:pull:`819`) - macOS: Allow scrolling window contents using mouse wheel/trackpad even when the window is not the active window (:iss:`729`) - Remote control: Allow changing the current window layout with a new :ref:`at-goto-layout` command (:iss:`845`) - Remote control: Allow matching windows by the environment variables of their child process as well - Allow running kittens via the remote control system (:iss:`738`) - Allow enabling remote control in only some kitty windows - Add a keyboard shortcut to reset the terminal (:sc:`reset_terminal`). It takes parameters so you can define your own shortcuts to clear the screen/scrollback also (:iss:`747`) - Fix one-pixel line appearing at window edges at some window sizes when displaying images with background opacity enabled (:iss:`741`) - diff kitten: Fix error when right hand side file is binary and left hand side file is text (:pull:`752`) - kitty @ new-window: Add a new option :option:`kitten @ new-window --window-type` to create top-level OS windows (:iss:`770`) - macOS: The :opt:`focus_follows_mouse` option now also works across top-level kitty OS windows (:iss:`754`) - Fix detection of URLs in HTML source code (URLs inside quotes) (:iss:`785`) - Implement support for emoji skin tone modifiers (:iss:`787`) - Round-trip the zwj unicode character. Rendering of sequences containing zwj is still not implemented, since it can cause the collapse of an unbounded number of characters into a single cell. However, kitty at least preserves the zwj by storing it as a combining character. - macOS: Disable the custom mouse cursor. Using a custom cursor fails on dual GPU machines. I give up, Apple users will just have to live with the limitations of their choice of OS. (:iss:`794`) - macOS: Fix control+tab key combination not working (:iss:`801`) - Linux: Fix slow startup on some systems caused by GLFW searching for joysticks. Since kitty does not use joysticks, disable joystick support. (:iss:`830`) 0.11.3 [2018-07-10] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Draw only the minimum borders needed for inactive windows. That is only the borders that separate the inactive window from a neighbor. Note that setting a non-zero window margin overrides this and causes all borders to be drawn. The old behavior of drawing all borders can be restored via the :opt:`draw_minimal_borders` setting in kitty.conf. (:iss:`699`) - macOS: Add an option :opt:`macos_window_resizable` to control if kitty top-level windows are resizable using the mouse or not (:iss:`698`) - macOS: Use a custom mouse cursor that shows up well on both light and dark backgrounds (:iss:`359`) - macOS: Workaround for switching from fullscreen to windowed mode with the titlebar hidden causing window resizing to not work. (:iss:`711`) - Fix triple-click to select line not working when the entire line is filled (:iss:`703`) - When dragging to select with the mouse "grab" the mouse so that if it strays into neighboring windows, the selection is still updated (:pull:`624`) - When clicking in the margin/border area of a window, map the click to the nearest cell in the window. Avoids selection with the mouse failing when starting the selection just outside the window. - When drag-scrolling stop the scroll when the mouse button is released. - Fix a regression in the previous release that caused pasting large amounts of text to be duplicated (:iss:`709`) 0.11.2 [2018-07-01] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Linux: Allow using XKB key names to bind shortcuts to keys not supported by GLFW (:pull:`665`) - kitty shell: Ignore failure to read readline history file. Happens if the user migrates their kitty cache directory between systems with incompatible readline implementations. - macOS: Fix an error in remote control when using --listen-on (:iss:`679`) - hints kitten: Add a :option:`kitty +kitten hints --multiple` option to select multiple items (:iss:`687`) - Fix pasting large amounts of text very slow (:iss:`682`) - Add an option :opt:`single_window_margin_width` to allow different margins when only a single window is visible in the layout (:iss:`688`) - Add a :option:`kitty --hold` command line option to stay open after the child process exits (:iss:`667`) - diff kitten: When triggering a search scroll to the first match automatically - :option:`kitty --debug-font-fallback` also prints out what basic fonts were matched - When closing a kitty window reset the mouse cursor to its default shape and ensure it is visible (:iss:`655`). - Remote control: Speed-up reading of command responses - Linux installer: Fix installer failing on systems with python < 3.5 - Support "-T" as an alias for "--title" (:pull:`659`) - Fix a regression in the previous release that broke using ``--debug-config`` with custom key mappings (:iss:`695`) 0.11.1 [2018-06-17] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - diff kitten: Implement searching for text in the diff (:iss:`574`) - Add an option :opt:`startup_session` to :file:`kitty.conf` to specify a default startup session (:iss:`641`) - Add a command line option :option:`kitty --wait-for-single-instance-window-close` to make :option:`kitty --single-instance` wait for the closing of the newly opened window before quitting (:iss:`630`) - diff kitten: Allow theming the selection background/foreground as well - diff kitten: Display CRLF line endings using the unicode return symbol instead of as it is less intrusive (:iss:`638`) - diff kitten: Fix default foreground/background colors not being restored when kitten quits (:iss:`637`) - Fix :option:`kitten @ set-colors --all` not working when more than one window present (:iss:`632`) - Fix a regression that broke the legacy increase/decrease_font_size actions - Clear scrollback on reset (:iss:`631`) 0.11.0 [2018-06-12] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new tab bar style "fade" in which each tab's edges fade into the background. See :opt:`tab_bar_style` and :opt:`tab_fade` for details. The old look can be restored by setting :opt:`tab_bar_style` to :code:`separator`. - :doc:`Pre-compiled binaries ` with all bundled dependencies for Linux (:iss:`595`) - A :doc:`new kitten ` to create dock panels on X11 desktops showing the output from arbitrary terminal programs. - Reduce data sent to the GPU per render by 30% (:commit:`8dea5b3`) - Implement changing the font size for individual top level (OS) windows (:iss:`408`) - When viewing the scrollback in less using :sc:`show_scrollback` and kitty is currently scrolled, position the scrollback in less to match kitty's scroll position. (:iss:`148`) - ssh kitten: Support all SSH options. It can now be aliased directly to ssh for convenience. (:pull:`591`) - icat kitten: Add :option:`kitty +kitten icat --print-window-size` to easily detect the window size in pixels from scripting languages (:iss:`581`) - hints kitten: Allow selecting hashes from the terminal with :sc:`insert_selected_hash` useful for git commits. (:pull:`604`) - Allow specifying initial window size in number of cells in addition to pixels (:iss:`436`) - Add a setting to control the margins to the left and right of the tab-bar (:iss:`584`) - When closing a tab switch to the last active tab instead of the right-most tab (:iss:`585`) - Wayland: Fix kitty not starting when using wl_roots based compositors (:iss:`157`) - Wayland: Fix mouse wheel/touchpad scrolling in opposite direction to other apps (:iss:`594`) - macOS: Fix the new OS window keyboard shortcut (:sc:`new_os_window`) not working if no kitty window currently has focus. (:iss:`524`) - macOS: Keep kitty running even when the last window is closed. This is in line with how applications are supposed to behave on macOS (:iss:`543`). There is a new option (:opt:`macos_quit_when_last_window_closed`) to control this. - macOS: Add macOS standard shortcuts for copy, paste and new OS window (⌘+C, ⌘+V, ⌘+N) - Add a config option (:opt:`editor`) to set the EDITOR kitty uses (:iss:`580`) - Add a config option (``x11_hide_window_decorations``) to hide window decorations under X11/Wayland (:iss:`607`) - Add an option to @set-window-title to make the title change non-permanent (:iss:`592`) - Add support for the CSI t escape code to query window and cell sizes (:iss:`581`) - Linux: When using layouts that map the keys to non-ascii characters, map shortcuts using the ascii equivalents, from the default layout. (:iss:`606`) - Linux: Fix fonts not being correctly read from TrueType Collection (.ttc) files (:iss:`577`) - Fix :opt:`inactive_text_alpha` also applying to the tab bar (:iss:`612`) - :doc:`hints kitten `: Fix a regression that caused some blank lines to be not be displayed. - Linux: Include a man page and the HTML docs when building the linux-package - Remote control: Fix kitty @ sometimes failing to read the response from kitty. (:iss:`614`) - Fix `kitty @ set-colors` not working with the window border colors. (:iss:`623`) - Fix a regression in 0.10 that caused incorrect rendering of the status bar in irssi when used inside screen. (:iss:`621`) 0.10.1 [2018-05-24] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add a kitten to easily ssh into servers that automatically copies the terminfo files over. ``kitty +kitten ssh myserver``. - diff kitten: Make the keyboard shortcuts configurable (:iss:`563`) - Allow controlling *background_opacity* via either keyboard shortcuts or remote control. Note that you must set *dynamic_background_opacity yes* in kitty.conf first. (:iss:`569`) - diff kitten: Add keybindings to scroll by page - diff kitten: Fix incorrect syntax highlighting for a few file formats such as yaml - macOS: Fix regression that caused the *macos_option_as_alt* setting to always be disabled for all OS windows in a kitty instance after the first window (:iss:`571`) - Fix Ctrl+Alt+Space not working in normal and application keyboard modes (:iss:`562`) 0.10.0 [2018-05-21] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A diff kitten to show side-by-side diffs with syntax highlighting and support for images. See :doc:`diff kitten `. - Make windows in the various kitty layouts manually resizable. See :ref:`layouts` for details. - Implement support for the SGR *faint* escape code to make text blend into the background (:iss:`446`). - Make the hints kitten a little smarter (:commit:`ad1109b`) so that URLs that stretch over multiple lines are detected. Also improve detection of surrounding brackets/quotes. - Make the kitty window id available as the environment variable ``KITTY_WINDOW_ID`` (:iss:`532`). - Add a "fat" layout that is similar to the "tall" layout but vertically oriented. - Expand environment variables in config file include directives - Allow programs running in kitty to read/write from the clipboard (:commit:`889ca77`). By default only writing is allowed. This feature is supported in many terminals, search for `OSC 52 clipboard` to find out more about using it. - Fix moving cursor outside a defined page area incorrectly causing the cursor to be placed inside the page area. Caused incorrect rendering in neovim, in some situations (:iss:`542`). - Render a couple more powerline symbols directly, bypassing the font (:iss:`550`). - Fix ctrl+alt+ not working in normal and application keyboard (:iss:`548`). - Partial fix for rendering Right-to-left languages like Arabic. Rendering of Arabic is never going to be perfect, but now it is at least readable. - Fix Ctrl+backspace acting as plain backspace in normal and application keyboard modes (:iss:`538`). - Have the paste_from_selection action paste from the clipboard on platforms that do not have a primary selection such as Wayland and macOS (:iss:`529`) - Fix cursor_stop_blinking_after=0 not working (:iss:`530`) 0.9.1 [2018-05-05] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Show a bell symbol on the tab if a bell occurs in one of the windows in the tab and the window is not the currently focused window - Change the window border color if a bell occurs in an unfocused window. Can be disabled by setting the bell_border_color to be the same as the inactive_border_color. - macOS: Add support for dead keys - Unicode input: When searching by name search for prefix matches as well as whole word matches - Dynamically allocate the memory used for the scrollback history buffer. Reduces startup memory consumption when using very large scrollback buffer sizes. - Add an option to not request window attention on bell. - Remote control: Allow matching windows by number (visible position). - macOS: Fix changing tab title and kitty shell not working - When triple-clicking select all wrapped lines belonging to a single logical line. - hints kitten: Detect bracketed URLs and don't include the closing bracket in the URL. - When calling pass_selection_to_program use the current directory of the child process as the cwd of the program. - Add macos_hide_from_tasks option to hide kitty from the macOS task switcher - macOS: When the macos_titlebar_color is set to background change the titlebar colors to match the current background color of the active kitty window - Add a setting to clear all shortcuts defined up to that point in the config file(s) - Add a setting (kitty_mod) to change the modifier used by all the default kitty shortcuts, globally - Fix Shift+function key not working - Support the F13 to F25 function keys - Don't fail to start if the user deletes the hintstyle key from their fontconfig configuration. - When rendering a private use unicode codepoint and a space as a two cell ligature, set the foreground colors of the space cell to match the colors of the first cell. Works around applications like powerline that use different colors for the two cells. - Fix passing @text to other programs such as when viewing the scrollback buffer not working correctly if kitty is itself scrolled up. - Fix window focus gained/lost events not being reported to child programs when switching windows/tabs using the various keyboard shortcuts. - Fix tab title not changing to reflect the window title when switching between different windows in a tab - Ignore -e if it is specified on the command line. This is for compatibility with broken software that assumes terminals should run with an -e option to execute commands instead of just passing the commands as arguments. 0.9.0 [2018-04-15] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A new kitty command shell to allow controlling kitty via commands. Press `ctrl+shift+escape` to run the shell. - The hints kitten has become much more powerful. Now in addition to URLs you can use it to select word, paths, filenames, lines, etc. from the screen. These can be inserted into the terminal, copied to clipboard or sent to external programs. - Linux: Switch to libxkbcommon for keyboard handling. It allows kitty to support XCompose and dead keys and also react to keyboard remapping/layout change without needing a restart. - Add support for multiple-key-sequence shortcuts - A new remote control command `set-colors` to change the current and/or configured colors. - When double-clicking to select a word, select words that continue onto the next/prev line as well. - Add an `include` directive for the config files to read multiple config files - Improve mouse selection for windows with padding. Moving the mouse into the padding area now acts as if the mouse is over the nearest cell. - Allow setting all 256 terminal colors in the config file - Fix using `kitty --single-instance` to open a new window in a running kitty instance, not respecting the `--directory` flag - URL hints: Exclude trailing punctuation from URLs - URL hints: Launch the browser from the kitty parent process rather than the hints kitten. Fixes launching on some systems where xdg-open doesn't like being run from a kitten. - Allow using rectangle select mode by pressing shift in addition to the rectangle select modifiers even when the terminal program has grabbed the mouse. 0.8.4 [2018-03-31] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Fix presence of XDG_CONFIG_DIRS and absence of XDG_CONFIG_HOME preventing kitty from starting - Revert change in last release to cell width calculation. Instead just clip the right edges of characters that overflow the cell by at most two pixels 0.8.3 [2018-03-29] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Fix a regression that broke the visual bell and invert screen colors escape code - Allow double-click and triple-click + drag to extend selections word at a time or line at a time - Add a keyboard shortcut to set the tab title - Fix setting window title to empty via OSC escape code not working correctly - Linux: Fix cell width calculation incorrect for some fonts (cell widths are now calculated by actually rendering bitmaps, which is slower but more accurate) - Allow specifying a system wide kitty config file, for all users - Add a --debug-config command line flag to output data about the system and kitty configuration. - Wayland: Fix auto-repeat of keys not working 0.8.2 [2018-03-17] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Allow extending existing selections by right clicking - Add a configurable keyboard shortcut and remote command to set the font size to a specific value - Add an option to have kitty close the window when the main processes running in it exits, even if there are still background processes writing to that terminal - Add configurable keyboard shortcuts to switch to a specific layout - Add a keyboard shortcut to edit the kitty config file easily - macOS: Fix restoring of window size not correct on Retina screens - macOS: Add a facility to specify command line arguments when running kitty from the GUI - Add a focus-tab remote command - Fix screen not being refreshed immediately after moving a window. - Fix a crash when getting the contents of the scrollback buffer as text 0.8.1 [2018-03-09] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Extend kitty's remote control feature to work over both UNIX and TCP sockets, so now you can control kitty from across the internet, if you want to. - Render private use unicode characters that are followed by a space as a two character ligature. This fixes rendering for applications that misuse private-use characters to display square symbols. - Fix Unicode emoji presentation variant selector causing new a fallback font instance to be created - Fix a rare error that prevented the Unicode input kitten from working sometimes - Allow using Ctrl+Alt+letter in legacy keyboard modes by outputting them as Ctrl+letter and Alt+letter. This matches other terminals' behavior. - Fix cursor position off-by-one on horizontal axis when resizing the terminal - Wayland: Fix auto-repeat of keys not working - Wayland: Add support for window decorations provided by the Wayland shell - macOS: Fix URL hints not working - macOS: Fix shell not starting in login mode on some computers - macOS: Output errors into console.app when running as a bundle 0.8.0 [2018-02-24] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A framework for kittens, that is, small terminal programs designed to run inside kitty and extend its capabilities. Examples include unicode input and selecting URLs with the keyboard. - Input arbitrary unicode characters by pressing Ctrl+Shift+u. You can choose characters by name, by hex code, by recently used, etc. There is even and editable Favorites list. - Open URLs using only the keyboard. kitty has a new "hints mode". Press Ctrl+Shift+e and all detected URLs on the screen are highlighted with a key to press to open them. The facility is customizable so you can change what is detected as a URL and which program is used to open it. - Add an option to change the titlebar color of kitty windows on macOS - Only consider Emoji characters with default Emoji presentation to be two cells wide. This matches the standard. Also add support for the Unicode Emoji variation presentation selector. - Prevent video tearing during high speed scrolling by syncing draws to the monitor's refresh rate. There is a new configuration option to control this ``sync_to_monitor``. - When displaying only a single window, use the default background color of the window (which can be changed via escape codes) as the color for the margin and padding of the window. - Add some non standard terminfo capabilities used by neovim and tmux. - Fix large drop in performance when using multiple top-level windows on macOS - Fix save/restore of window sizes not working correctly. - Remove option to use system wcwidth(). Now always use a wcwidth() based on the Unicode standard. Only sane way. - Fix a regression that caused a few ligature glyphs to not render correctly in rare circumstances. - Browsing the scrollback buffer now happens in an overlay window instead of a new window/tab. 0.7.1 [2018-01-31] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add an option to adjust the width of character cells - Fix selecting text with the mouse in the scrollback buffer selecting text from the line above the actually selected line - Fix some italic fonts having the right edge of characters cut-off, unnecessarily 0.7.0 [2018-01-24] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Allow controlling kitty from the shell prompt/scripts. You can open/close/rename windows and tabs and even send input to specific windows. See the README for details. - Add option to put tab bar at the top instead of the bottom - Add option to override the default shell - Add "Horizontal" and "Vertical" window layouts - Sessions: Allow setting titles and working directories for individual windows - Option to copy to clipboard on mouse select - Fix incorrect reporting of mouse move events when using the SGR protocol - Make alt+backspace delete the previous word - Take the mouse wheel multiplier option in to account when generating fake key scroll events - macOS: Fix closing top-level window does not transfer focus to other top-level windows. - macOS: Fix alt+arrow keys not working when disabling the macos_option_as_alt config option. - kitty icat: Workaround for bug in ImageMagick that would cause some images to fail to display at certain sizes. - Fix rendering of text with ligature fonts that do not use dummy glyphs - Fix a regression that caused copying of the selection to clipboard to only copy the visible part of the selection - Fix incorrect handling of some unicode combining marks that are not re-ordered - Fix handling on non-BMP combining characters - Drop the dependency on libunistring 0.6.1 [2017-12-28] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add an option to fade the text in inactive windows - Add new actions to open windows/tabs/etc. with the working directory set to the working directory of the current window. - Automatically adjust cell size when DPI changes, for example when kitty is moved from one monitor to another with a different DPI - Ensure underlines are rendered even for fonts with very poor metrics - Fix some emoji glyphs not colored on Linux - Internal wcwidth() implementation is now auto-generated from the unicode standard database - Allow configuring the modifiers to use for rectangular selection with the mouse. - Fix incorrect minimum wayland version in the build script - Fix a crash when detecting a URL that ends at the end of the line - Fix regression that broke drawing of hollow cursor when window loses focus 0.6.0 [2017-12-18] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Support background transparency via the background_opacity option. Provided that your OS/window manager supports transparency, you can now have kitty render pixels that have only the default background color as semi-transparent. - Support multiple top level (OS) windows. These windows all share the sprite texture cache on the GPU, further reducing overall resource usage. Use the shortcut `ctrl+shift+n` to open a new top-level window. - Add support for a *daemon* mode using the `--single-instance` command line option. With this option you can have only a single kitty instance running. All future invocations simply open new top-level windows in the existing instance. - Support colored emoji - Use CoreText instead of FreeType to render text on macOS - Support running on the "low power" GPU on dual GPU macOS machines - Add a new "grid" window layout - Drop the dependency on glfw (kitty now uses a modified, bundled copy of glfw) - Add an option to control the audio bell volume on X11 systems - Add a command line switch to set the name part of the WM_CLASS window property independently. - Add a command line switch to set the window title. - Add more options to customize the tab-bar's appearance (font styles and separator) - Allow drag and drop of files into kitty. On drop kitty will paste the file path to the running program. - Add an option to control the underline style for URL highlighting on hover - X11: Set the WINDOWID environment variable - Fix middle and right buttons swapped when sending mouse events to child processes - Allow selecting in a rectangle by holding down Ctrl+Alt while dragging with the mouse. 0.5.1 [2017-12-01] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add an option to control the thickness of lines in box drawing characters - Increase max. allowed ligature length to nine characters - Fix text not vertically centered when adjusting line height - Fix unicode block characters not being rendered properly - Fix shift+up/down not generating correct escape codes - Image display: Fix displaying images taller than two screen heights not scrolling properly 0.5.0 [2017-11-19] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Add support for ligature fonts such as Fira Code, Hasklig, etc. kitty now uses harfbuzz for text shaping which allow it to support advanced OpenType features such as contextual alternates/ligatures/combining glyphs/etc. - Make it easy to select fonts by allowing listing of monospace fonts using: kitty list-fonts - Add an option to have window focus follow mouse - Add a keyboard shortcut (ctrl+shift+f11) to toggle fullscreen mode - macOS: Fix handling of option key. It now behaves just like the alt key on Linux. There is an option to make it type unicode characters instead. - Linux: Add support for startup notification on X11 desktops. kitty will now inform the window manager when its startup is complete. - Fix shell prompt being duplicated when window is resized - Fix crash when displaying more than 64 images in the same session - Add support for colons in SGR color codes. These are generated by some applications such as neovim when they mistakenly identify kitty as a libvte based terminal. - Fix mouse interaction not working in apps using obsolete mouse interaction protocols - Linux: no longer require glew as a dependency 0.4.2 [2017-10-23] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Fix a regression in 0.4.0 that broke custom key mappings - Fix a regression in 0.4.0 that broke support for non-QWERTY keyboard layouts - Avoid using threads to reap zombie child processes. Also prevent kitty from hanging if the open program hangs when clicking on a URL. 0.4.0 [2017-10-22] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Support for drawing arbitrary raster graphics (images) in the terminal via a new graphics protocol. kitty can draw images with full 32-bit color using both ssh connections and files/shared memory (when available) for better performance. The drawing primitives support alpha blending and z-index. Images can be drawn both above and below text. See :doc:`graphics-protocol`. for details. - Refactor kitty's internals to make it even faster and more efficient. The CPU usage of kitty + X server while doing intensive tasks such as scrolling a file continuously in less has been reduced by 50%. There are now two configuration options ``repaint_delay`` and ``input_delay`` you can use to fine tune kitty's performance vs CPU usage profile. The CPU usage of kitty + X when scrolling in less is now significantly better than most (all?) other terminals. See :doc:`performance`. - Hovering over URLs with the mouse now underlines them to indicate they can be clicked. Hold down Ctrl+Shift while clicking to open the URL. - Selection using the mouse is now more intelligent. It does not add blank cells (i.e. cells that have no content) after the end of text in a line to the selection. - The block cursor in now fully opaque but renders the character under it in the background color, for enhanced visibility. - Allow combining multiple independent actions into a single shortcut - Add a new shortcut to pass the current selection to an external program - Allow creating shortcuts to open new windows running arbitrary commands. You can also pass the current selection to the command as an arguments and the contents of the screen + scrollback buffer as stdin to the command. kitty-0.41.1/docs/clipboard.rst0000664000175000017510000001566414773370543015744 0ustar nileshnileshCopying all data types to the clipboard ============================================== There already exists an escape code to allow terminal programs to read/write plain text data from the system clipboard, *OSC 52*. kitty introduces a more advanced protocol that supports: * Copy arbitrary data including images, rich text documents, etc. * Allow terminals to ask the user for permission to access the clipboard and report permission denied The escape code is *OSC 5522*, an extension of *OSC 52*. The basic format of the escape code is:: 5522;metadata;payload Here, *metadata* is a colon separated list of key-value pairs and payload is base64 encoded data. :code:`OSC` is :code:`[`. :code:`ST` is the string terminator, :code:`\\`. Reading data from the system clipboard ---------------------------------------- To read data from the system clipboard, the escape code is:: 5522;type=read; For example, to read plain text and PNG data, the payload would be:: text/plain image/png encoded as base64. To read from the primary selection instead of the clipboard, add the key ``loc=primary`` to the metadata section. To get the list of MIME types available on the clipboard the payload must be just a period (``.``), encoded as base64. The terminal emulator will reply with a sequence of escape codes of the form:: 5522;type=read:status=OK 5522;type=read:status=DATA:mime=; 5522;type=read:status=DATA:mime=; . . . 5522;type=read:status=DONE Here, the ``status=DATA`` packets deliver the data (as base64 encoded bytes) associated with each MIME type. The terminal emulator should chunk up the data for an individual type, into chunks of size **no more** than 4096 bytes (4096 is the size of a chunk *before* base64 encoding). All the chunks for a given type must be transmitted sequentially and only once they are done the chunks for the next type, if any, should be sent. The end of data is indicated by a ``status=DONE`` packet. If an error occurs, instead of the opening ``status=OK`` packet the terminal must send a ``status=ERRORCODE`` packet. The error code must be one of: ``status=ENOSYS`` Sent if the requested clipboard type is not available. For example, primary selection is not available on all systems and ``loc=primary`` was used. ``status=EPERM`` Sent if permission to read from the clipboard was denied by the system or the user. ``status=EBUSY`` Sent if there is some temporary problem, such as multiple clients in a multiplexer trying to access the clipboard simultaneously. Terminals should ask the user for permission before allowing a read request. However, if a read request only wishes to list the available data types on the clipboard, it should be allowed without a permission prompt. This is so that the user is not presented with a double permission prompt for reading the available MIME types and then reading the actual data. Writing data to the system clipboard ---------------------------------------- To write data to the system clipboard, the terminal programs sends the following sequence of packets:: 5522;type=write 5522;type=wdata:mime=; 5522;type=wdata:mime=; . . . 5522;type=wdata The final packet with no mime and no data indicates end of transmission. The data for every MIME type should be split into chunks of no more than 4096 bytes (4096 is the size of the data before base64 encoding). All the chunks for a given MIME type must be sent sequentially, before sending chunks for the next MIME type. After the transmission is complete, the terminal replies with a single packet indicating success:: 5522;type=write:status=DONE If an error occurs the terminal can, at any time, send an error packet of the form:: 5522;type=write:status=ERRORCODE Here ``ERRORCODE`` must be one of: ``status=EIO`` An I/O error occurred while processing the data ``status=EINVAL`` One of the packets was invalid, usually because of invalid base64 encoding. ``status=ENOSYS`` The client asked to write to the primary selection with (``loc=primary``) and that is not available on the system ``status=EPERM`` Sent if permission to write to the clipboard was denied by the system or the user. ``status=EBUSY`` Sent if there is some temporary problem, such as multiple clients in a multiplexer trying to access the clipboard simultaneously. Once an error occurs, the terminal must ignore all further OSC 5522 write related packets until it sees the start of a new write with a ``type=write`` packet. The client can send to the primary selection instead of the clipboard by adding ``loc=primary`` to the initial ``type=write`` packet. Finally, clients have the ability to *alias* MIME types when sending data to the clipboard. To do that, the client must send a ``type=walias`` packet of the form:: 5522;type=walias;mime=; The effect of an alias is that the system clipboard will make available all the aliased MIME types, with the same data as was transmitted for the target MIME type. This saves bandwidth, allowing the client to only transmit one copy of the data, but create multiple references to it in the system clipboard. Alias packets can be sent anytime after the initial write packet and before the end of data packet. Support for terminal multiplexers ------------------------------------ Since this protocol involves two way communication between the terminal emulator and the client program, multiplexers need a way to know which window to send responses from the terminal to. In order to make this possible, the metadata portion of this escape code includes an optional ``id`` field. If present the terminal emulator must send it back unchanged with every response. Valid ids must include only characters from the set: ``[a-zA-Z0-9-_+.]``. Any other characters must be stripped out from the id by the terminal emulator before retransmitting it. Note that when using a terminal multiplexer it is possible for two different programs to overwrite each others clipboard requests. This is fundamentally unavoidable since the system clipboard is a single global shared resource. However, there is an additional complication where responses form this protocol could get lost if, for instance, multiple write requests are received simultaneously. It is up to well designed multiplexers to ensure that only a single request is in flight at a time. The multiplexer can abort requests by sending back the ``EBUSY`` error code indicating some other window is trying to access the clipboard. kitty-0.41.1/docs/color-stack.rst0000664000175000017510000002166714773370543016226 0ustar nileshnileshColor control ==================== Saving and restoring colors ------------------------------ It is often useful for a full screen application with its own color themes to set the default foreground, background, selection and cursor colors and the ANSI color table. This allows for various performance optimizations when drawing the screen. The problem is that if the user previously used the escape codes to change these colors themselves, then running the full screen application will lose those changes even after it exits. To avoid this, kitty introduces a new pair of *OSC* escape codes to push and pop the current color values from a stack:: ]30001\ # push onto stack ]30101\ # pop from stack These escape codes save/restore the colors, default background, default foreground, selection background, selection foreground and cursor color and the 256 colors of the ANSI color table. .. note:: In July 2020, after several years, xterm copied this protocol extension, without acknowledgement, and using incompatible escape codes (XTPUSHCOLORS, XTPOPCOLORS, XTREPORTCOLORS). And they decided to save not just the dynamic colors but the entire ANSI color table. In the interests of promoting interoperability, kitty added support for xterm's escape codes as well, and changed this extension to also save/restore the entire ANSI color table. .. _color_control: Setting and querying colors ------------------------------- While there exists a legacy protocol developed by XTerm for querying and setting colors, as with most XTerm protocols it suffers from the usual design limitations of being under specified and in-sufficient. XTerm implements querying of colors using OSC 4,5,6,10-19,104,105,106,110-119. This absurd profusion of numbers is completely unnecessary, redundant and requires adding two new numbers for every new color. Also XTerm's protocol doesn't handle the case of colors that are unknown to the terminal or that are not a set value, for example, many terminals implement selection as a reverse video effect not a fixed color. The XTerm protocol has no way to query for this condition. The protocol also doesn't actually specify the format in which colors are reported, deferring to a man page for X11! Instead kitty has developed a single number based protocol that addresses all these shortcomings and is future proof by virtue of using string keys rather than numbers. The syntax of the escape code is:: 21 ; key=value ; key=value ; ... The spaces in the above definition are for reading clarity and should be ignored. Here, ```` is the two bytes ``0x1b (ESC)`` and ``0x5d (])``. ``ST`` is either ``0x7 (BEL)`` or the two bytes ``0x1b (ESC)`` and ``0x5c (\\)``. ``key`` is a number from 0-255 to query or set the color values from the terminals ANSI color table, or one of the strings in the table below for special colors: ================================= =============================================== =============================== key meaning dynamic ================================= =============================================== =============================== foreground The default foreground text color Not applicable background The default background text color Not applicable selection_background The background color of selections Reverse video selection_foreground The foreground color of selections Reverse video cursor The color of the text cursor Foreground color cursor_text The color of text under the cursor Background color visual_bell The color of a visual bell Automatic color selection based on current screen colors transparent_background_color1..8 A background color that is rendered Unset with the specified opacity in cells that have the specified background color. An opacity value less than zero means, use the :opt:`background_opacity` value. ================================= =============================================== =============================== In this table the third column shows what effect setting the color to *dynamic* has in kitty and many other terminal emulators. It is advisory only, terminal emulators may not support dynamic colors for these or they may have other effects. Setting the ANSI color table colors to dynamic is not allowed. Querying current color values ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To query colors values, the client program sends this escape code with the ``value`` field set to ``?`` (the byte ``0x3f``). The terminal then responds with the same escape code, but with the ``?`` replaced by the :ref:`encoded color value `. If the queried color is one that does not have a defined value, for example, if the terminal is using a reverse video effect or a gradient or similar, then the value must be empty, that is the response contains only the key and ``=``, no value. For example, if the client sends:: 21 ; foreground=? ; cursor=? The terminal responds:: 21 ; foreground=rgb:ff/00/00 ; cursor= This indicates that the foreground color is red and the cursor color is undefined (typically the cursor takes the color of the text under it and the text takes the color of the background). If the terminal does not know a field that a client send to it for a query it must respond back with the ``field=?``, that is, it must send back a question mark as the value. Setting color values ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To set a color value, the client program sends this escape code with the ``value`` field set to either an :ref:`encoded color value ` or the empty value. The empty value means the terminal should use a dynamic color for example reverse video for selections or similar. To reset a color to its default value (i.e. the value it would have if it was never set) the client program should send just the key name with no ``=`` and no value. For example:: 21 ; foreground=green ; cursor= ; background This sets the foreground to the color green, sets the cursor color to dynamic (usually meaning the cursor takes the color of the text under it) and resets the background color to its default value. To check if setting succeeded, the client can simply query the color, in fact the two can be combined into a single escape code, for example:: 21 ; foreground=white ; foreground=? The terminal will change the foreground color and reply with the new foreground color. .. _color_control_color_encoding: Color value encoding ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The color encoding is inherited from the scheme used by XTerm, for compatibility, but a sane, rigorously specified subset is chosen. RGB colors are encoded in one of three forms: ``rgb://`` | , , := h | hh | hhh | hhhh | h := single hexadecimal digits (case insignificant) | Note that h indicates the value scaled in 4 bits, hh the value scaled in 8 bits, hhh the value scaled in 12 bits, and hhhh the value scaled in 16 bits, respectively. ``#`` | h := single hexadecimal digits (case insignificant) | #RGB (4 bits each) | #RRGGBB (8 bits each) | #RRRGGGBBB (12 bits each) | #RRRRGGGGBBBB (16 bits each) | The R, G, and B represent single hexadecimal digits. When fewer than 16 bits each are specified, they represent the most significant bits of the value (unlike the “rgb:” syntax, in which values are scaled). For example, the string ``#3a7`` is the same as ``#3000a0007000``. ``rgbi://`` red, green, and blue are floating-point values between 0.0 and 1.0, inclusive. The input format for these values is an optional sign, a string of numbers possibly containing a decimal point, and an optional exponent field containing an E or e followed by a possibly signed integer string. Values outside the ``0 - 1`` range must be clipped to be within the range. If a color should have an alpha component, it must be suffixed to the color specification in the form :code:`@number between zero and one`. For example:: red@0.5 rgb:ff0000@0.1 #ff0000@0.3 The syntax for the floating point alpha component is the same as used for the components of ``rgbi`` defined above. When not specified, the default alpha value is ``1.0``. Values outside the range ``0 - 1`` must be clipped to be within the range, negative values may have special context dependent meaning. In addition, the following color names are accepted (case-insensitively) corresponding to the specified RGB values. .. include:: generated/color-names.rst kitty-0.41.1/docs/conf.py0000664000175000017510000007106014773370543014542 0ustar nileshnilesh#!/usr/bin/env python # vim:fileencoding=utf-8 # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # https://www.sphinx-doc.org/en/master/config import glob import os import re import subprocess import sys import time from functools import lru_cache, partial from typing import Any, Callable, Dict, Iterable, Iterator, List, Tuple from docutils import nodes from docutils.parsers.rst.roles import set_classes from pygments.lexer import RegexLexer, bygroups # type: ignore from pygments.token import Comment, Error, Keyword, Literal, Name, Number, String, Whitespace # type: ignore from sphinx import addnodes, version_info from sphinx.util.logging import getLogger kitty_src = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if kitty_src not in sys.path: sys.path.insert(0, kitty_src) from kitty.conf.types import Definition, expand_opt_references # noqa from kitty.constants import str_version, website_url # noqa from kitty.fast_data_types import Shlex, TEXT_SIZE_CODE # noqa # config {{{ # -- Project information ----------------------------------------------------- project = 'kitty' copyright = time.strftime('%Y, Kovid Goyal') author = 'Kovid Goyal' building_man_pages = 'man' in sys.argv # The short X.Y version version = str_version # The full version, including alpha/beta/rc tags release = str_version logger = getLogger(__name__) # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.7' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'sphinx.ext.githubpages', 'sphinx.ext.extlinks', 'sphinx_copybutton', 'sphinx_inline_tabs', "sphinxext.opengraph", ] # URL for OpenGraph tags ogp_site_url = website_url() # OGP needs a PNG image because of: https://github.com/wpilibsuite/sphinxext-opengraph/issues/96 ogp_social_cards = { 'image': '../logo/kitty.png' } # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language: str = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . exclude_patterns = [ '_build', 'Thumbs.db', '.DS_Store', 'basic.rst', 'generated/cli-*.rst', 'generated/conf-*.rst', 'generated/actions.rst' ] rst_prolog = ''' .. |kitty| replace:: *kitty* .. |version| replace:: VERSION .. _tarball: https://github.com/kovidgoyal/kitty/releases/download/vVERSION/kitty-VERSION.tar.xz .. role:: italic '''.replace('VERSION', str_version) smartquotes_action = 'qe' # educate quotes and ellipses but not dashes def go_version(go_mod_path: str) -> str: # {{{ with open(go_mod_path) as f: for line in f: if line.startswith('go '): return line.strip().split()[1] raise SystemExit(f'No Go version in {go_mod_path}') # }}} string_replacements = { '_kitty_install_cmd': 'curl -L https://sw.kovidgoyal.net/kitty/installer.sh | sh /dev/stdin', '_build_go_version': go_version('../go.mod'), '_text_size_code': str(TEXT_SIZE_CODE), } # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'furo' html_title = 'kitty' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. github_icon_path = 'M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z' # noqa html_theme_options: Dict[str, Any] = { 'sidebar_hide_name': True, 'navigation_with_keys': True, 'footer_icons': [ { "name": "GitHub", "url": "https://github.com/kovidgoyal/kitty", "html": f""" """, "class": "", }, ], } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] html_favicon = html_logo = '../logo/kitty.svg' html_css_files = ['custom.css', 'timestamps.css'] html_js_files = ['custom.js', 'timestamps.js'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # html_show_sourcelink = False html_show_sphinx = False manpages_url = 'https://man7.org/linux/man-pages/man{section}/{page}.{section}.html' # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('invocation', 'kitty', 'The fast, feature rich terminal emulator', [author], 1), ('conf', 'kitty.conf', 'Configuration file for kitty', [author], 5) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'kitty', 'kitty Documentation', author, 'kitty', 'Cross-platform, fast, feature-rich, GPU based terminal', 'Miscellaneous'), ] # }}} # GitHub linking inline roles {{{ extlinks = { 'iss': ('https://github.com/kovidgoyal/kitty/issues/%s', '#%s'), 'pull': ('https://github.com/kovidgoyal/kitty/pull/%s', '#%s'), 'disc': ('https://github.com/kovidgoyal/kitty/discussions/%s', '#%s'), } def commit_role( name: str, rawtext: str, text: str, lineno: int, inliner: Any, options: Any = {}, content: Any = [] ) -> Tuple[List[nodes.reference], List[nodes.problematic]]: ' Link to a github commit ' try: commit_id = subprocess.check_output( f'git rev-list --max-count=1 {text}'.split()).decode('utf-8').strip() except Exception: msg = inliner.reporter.error( f'git commit id "{text}" not recognized.', line=lineno) prb = inliner.problematic(rawtext, rawtext, msg) return [prb], [msg] url = f'https://github.com/kovidgoyal/kitty/commit/{commit_id}' set_classes(options) short_id = subprocess.check_output( f'git rev-list --max-count=1 --abbrev-commit {commit_id}'.split()).decode('utf-8').strip() node = nodes.reference(rawtext, f'commit: {short_id}', refuri=url, **options) return [node], [] # }}} # CLI docs {{{ def write_cli_docs(all_kitten_names: Iterable[str]) -> None: from kittens.ssh.main import copy_message, option_text from kitty.cli import option_spec_as_rst with open('generated/ssh-copy.rst', 'w') as f: f.write(option_spec_as_rst( appname='copy', ospec=option_text, heading_char='^', usage='file-or-dir-to-copy ...', message=copy_message )) del sys.modules['kittens.ssh.main'] from kitty.launch import options_spec as launch_options_spec with open('generated/launch.rst', 'w') as f: f.write(option_spec_as_rst( appname='launch', ospec=launch_options_spec, heading_char='_', message='''\ Launch an arbitrary program in a new kitty window/tab. Note that if you specify a program-to-run you can use the special placeholder :code:`@selection` which will be replaced by the current selection. ''' )) with open('generated/cli-kitty.rst', 'w') as f: f.write(option_spec_as_rst(appname='kitty').replace( 'kitty --to', 'kitty @ --to')) as_rst = partial(option_spec_as_rst, heading_char='_') from kitty.rc.base import all_command_names, command_for_name from kitty.remote_control import cli_msg, global_options_spec with open('generated/cli-kitten-at.rst', 'w') as f: p = partial(print, file=f) p('kitten @') p('-' * 80) p('.. program::', 'kitten @') p('\n\n' + as_rst( global_options_spec, message=cli_msg, usage='command ...', appname='kitten @')) from kitty.rc.base import cli_params_for for cmd_name in sorted(all_command_names()): func = command_for_name(cmd_name) p(f'.. _at-{func.name}:\n') p('kitten @', func.name) p('-' * 120) p('.. program::', 'kitten @', func.name) p('\n\n' + as_rst(*cli_params_for(func))) from kittens.runner import get_kitten_cli_docs for kitten in all_kitten_names: data = get_kitten_cli_docs(kitten) if data: with open(f'generated/cli-kitten-{kitten}.rst', 'w') as f: p = partial(print, file=f) p('.. program::', 'kitty +kitten', kitten) p('\nSource code for', kitten) p('-' * 72) scurl = f'https://github.com/kovidgoyal/kitty/tree/master/kittens/{kitten}' p(f'\nThe source code for this kitten is `available on GitHub <{scurl}>`_.') p('\nCommand Line Interface') p('-' * 72) appname = f'kitten {kitten}' if kitten in ('panel', 'broadcast', 'remote_file'): appname = 'kitty +' + appname p('\n\n' + option_spec_as_rst( data['options'], message=data['help_text'], usage=data['usage'], appname=appname, heading_char='^')) # }}} def write_color_names_table() -> None: # {{{ from kitty.rgb import color_names def s(c: Any) -> str: return f'{c.red:02x}/{c.green:02x}/{c.blue:02x}' with open('generated/color-names.rst', 'w') as f: p = partial(print, file=f) p('=' * 50, '=' * 20) p('Name'.ljust(50), 'RGB value') p('=' * 50, '=' * 20) for name, col in color_names.items(): p(name.ljust(50), s(col)) p('=' * 50, '=' * 20) # }}} def write_remote_control_protocol_docs() -> None: # {{{ from kitty.rc.base import RemoteCommand, all_command_names, command_for_name field_pat = re.compile(r'\s*([^:]+?)\s*:\s*(.+)') def format_cmd(p: Callable[..., None], name: str, cmd: RemoteCommand) -> None: p(name) p('-' * 80) lines = (cmd.__doc__ or '').strip().splitlines() fields = [] for line in lines: m = field_pat.match(line) if m is None: p(line) else: fields.append((m.group(1).split('/')[0], m.group(2))) if fields: p('\nFields are:\n') for (name, desc) in fields: if '+' in name: title = name.replace('+', ' (required)') else: title = name defval = cmd.get_default(name.replace('-', '_'), cmd) if defval is not cmd: title = f'{title} (default: {defval})' else: title = f'{title} (optional)' p(f':code:`{title}`') p(' ', desc) p() p() p() with open('generated/rc.rst', 'w') as f: p = partial(print, file=f) for name in sorted(all_command_names()): cmd = command_for_name(name) if not cmd.__doc__: continue name = name.replace('_', '-') format_cmd(p, name, cmd) # }}} def replace_string(app: Any, docname: str, source: List[str]) -> None: # {{{ src = source[0] for k, v in app.config.string_replacements.items(): src = src.replace(k, v) source[0] = src # }}} # config file docs {{{ class ConfLexer(RegexLexer): # type: ignore name = 'Conf' aliases = ['conf'] filenames = ['*.conf'] def map_flags(self: RegexLexer, val: str, start_pos: int) -> Iterator[Tuple[int, Any, str]]: expecting_arg = '' s = Shlex(val) from kitty.options.utils import allowed_key_map_options last_pos = 0 while (tok := s.next_word())[0] > -1: x = tok[1] if tok[0] > last_pos: yield start_pos + last_pos, Whitespace, ' ' * (tok[0] - last_pos) last_pos = tok[0] + len(x) tok_start = start_pos + tok[0] if expecting_arg: yield tok_start, String, x expecting_arg = '' elif x.startswith('--'): expecting_arg = x[2:] k, sep, v = expecting_arg.partition('=') k = k.replace('-', '_') expecting_arg = k if expecting_arg not in allowed_key_map_options: yield tok_start, Error, x elif sep == '=': expecting_arg = '' yield tok_start, Name, x else: yield tok_start, Name, x else: break def mapargs(self: RegexLexer, match: 're.Match[str]') -> Iterator[Tuple[int, Any, str]]: start_pos = match.start() val = match.group() parts = val.split(maxsplit=1) if parts[0].startswith('--'): seen = 0 for (pos, token, text) in self.map_flags(val, start_pos): yield pos, token, text seen += len(text) start_pos += seen val = val[seen:] parts = val.split(maxsplit=1) if not val: return yield start_pos, Literal, parts[0] # key spec if len(parts) == 1: return start_pos += len(parts[0]) val = val[len(parts[0]):] m = re.match(r'(\s+)(\S+)', val) if m is None: return yield start_pos, Whitespace, m.group(1) yield start_pos + m.start(2), Name.Function, m.group(2) # action function yield start_pos + m.end(2), String, val[m.end(2):] tokens = { 'root': [ (r'#.*?$', Comment.Single), (r'\s+$', Whitespace), (r'\s+', Whitespace), (r'(include)(\s+)(.+?)$', bygroups(Comment.Preproc, Whitespace, Name.Namespace)), (r'(map)(\s+)', bygroups( Keyword.Declaration, Whitespace), 'mapargs'), (r'(mouse_map)(\s+)(\S+)(\s+)(\S+)(\s+)(\S+)(\s+)', bygroups( Keyword.Declaration, Whitespace, String, Whitespace, Name.Variable, Whitespace, String, Whitespace), 'action'), (r'(symbol_map)(\s+)(\S+)(\s+)(.+?)$', bygroups( Keyword.Declaration, Whitespace, String, Whitespace, Literal)), (r'([a-zA-Z_0-9]+)(\s+)', bygroups( Name.Variable, Whitespace), 'args'), ], 'action': [ (r'[a-z_0-9]+$', Name.Function, 'root'), (r'[a-z_0-9]+', Name.Function, 'args'), ], 'mapargs': [ (r'.+$', mapargs, 'root'), ], 'args': [ (r'\s+', Whitespace, 'args'), (r'\b(yes|no)\b$', Number.Bin, 'root'), (r'\b(yes|no)\b', Number.Bin, 'args'), (r'[+-]?[0-9]+\s*$', Number.Integer, 'root'), (r'[+-]?[0-9.]+\s*$', Number.Float, 'root'), (r'[+-]?[0-9]+', Number.Integer, 'args'), (r'[+-]?[0-9.]+', Number.Float, 'args'), (r'#[a-fA-F0-9]{3,6}\s*$', String, 'root'), (r'#[a-fA-F0-9]{3,6}\s*', String, 'args'), (r'.+', String, 'root'), ], } class SessionLexer(RegexLexer): # type: ignore name = 'Session' aliases = ['session'] filenames = ['*.session'] tokens = { 'root': [ (r'#.*?$', Comment.Single), (r'[a-z][a-z0-9_]+', Name.Function, 'args'), ], 'args': [ (r'.*?$', Literal, 'root'), ] } def link_role( name: str, rawtext: str, text: str, lineno: int, inliner: Any, options: Any = {}, content: Any = [] ) -> Tuple[List[nodes.reference], List[nodes.problematic]]: text = text.replace('\n', ' ') m = re.match(r'(.+)\s+<(.+?)>', text) if m is None: msg = inliner.reporter.error(f'link "{text}" not recognized', line=lineno) prb = inliner.problematic(rawtext, rawtext, msg) return [prb], [msg] text, url = m.group(1, 2) url = url.replace(' ', '') set_classes(options) node = nodes.reference(rawtext, text, refuri=url, **options) return [node], [] opt_aliases: Dict[str, str] = {} shortcut_slugs: Dict[str, Tuple[str, str]] = {} def parse_opt_node(env: Any, sig: str, signode: Any) -> str: """Transform an option description into RST nodes.""" count = 0 firstname = '' for potential_option in sig.split(', '): optname = potential_option.strip() if count: signode += addnodes.desc_addname(', ', ', ') text = optname.split('.', 1)[-1] signode += addnodes.desc_name(text, text) if not count: firstname = optname signode['allnames'] = [optname] else: signode['allnames'].append(optname) opt_aliases[optname] = firstname count += 1 if not firstname: raise ValueError(f'{sig} is not a valid opt') return firstname def parse_shortcut_node(env: Any, sig: str, signode: Any) -> str: """Transform a shortcut description into RST nodes.""" conf_name, text = sig.split('.', 1) signode += addnodes.desc_name(text, text) return sig def parse_action_node(env: Any, sig: str, signode: Any) -> str: """Transform an action description into RST nodes.""" signode += addnodes.desc_name(sig, sig) return sig def process_opt_link(env: Any, refnode: Any, has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]: conf_name, opt = target.partition('.')[::2] if not opt: conf_name, opt = 'kitty', conf_name full_name = f'{conf_name}.{opt}' return title, opt_aliases.get(full_name, full_name) def process_action_link(env: Any, refnode: Any, has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]: return title, target def process_shortcut_link(env: Any, refnode: Any, has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]: conf_name, slug = target.partition('.')[::2] if not slug: conf_name, slug = 'kitty', conf_name full_name = f'{conf_name}.{slug}' try: target, stitle = shortcut_slugs[full_name] except KeyError: logger.warning(f'Unknown shortcut: {target}', location=refnode) else: if not has_explicit_title: title = stitle return title, target def write_conf_docs(app: Any, all_kitten_names: Iterable[str]) -> None: app.add_lexer('conf', ConfLexer() if version_info[0] < 3 else ConfLexer) app.add_object_type( 'opt', 'opt', indextemplate="pair: %s; Config Setting", parse_node=parse_opt_node, ) # Warn about opt references that could not be resolved opt_role = app.registry.domain_roles['std']['opt'] opt_role.warn_dangling = True opt_role.process_link = process_opt_link app.add_object_type( 'shortcut', 'sc', indextemplate="pair: %s; Keyboard Shortcut", parse_node=parse_shortcut_node, ) sc_role = app.registry.domain_roles['std']['sc'] sc_role.warn_dangling = True sc_role.process_link = process_shortcut_link shortcut_slugs.clear() app.add_object_type( 'action', 'ac', indextemplate="pair: %s; Action", parse_node=parse_action_node, ) ac_role = app.registry.domain_roles['std']['ac'] ac_role.warn_dangling = True ac_role.process_link = process_action_link def generate_default_config(definition: Definition, name: str) -> None: with open(f'generated/conf-{name}.rst', 'w', encoding='utf-8') as f: print('.. highlight:: conf\n', file=f) f.write('\n'.join(definition.as_rst(name, shortcut_slugs))) conf_name = re.sub(r'^kitten-', '', name) + '.conf' with open(f'generated/conf/{conf_name}', 'w', encoding='utf-8') as f: text = '\n'.join(definition.as_conf(commented=True)) print(text, file=f) from kitty.options.definition import definition generate_default_config(definition, 'kitty') from kittens.runner import get_kitten_conf_docs for kitten in all_kitten_names: defn = get_kitten_conf_docs(kitten) if defn is not None: generate_default_config(defn, f'kitten-{kitten}') from kitty.actions import as_rst with open('generated/actions.rst', 'w', encoding='utf-8') as f: f.write(as_rst()) from kitty.rc.base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION with open('generated/matching.rst', 'w') as f: print('Matching windows', file=f) print('______________________________', file=f) w = 'm' + MATCH_WINDOW_OPTION[MATCH_WINDOW_OPTION.find('Match') + 1:] print('When matching windows,', w, file=f) print('Matching tabs', file=f) print('______________________________', file=f) w = 'm' + MATCH_TAB_OPTION[MATCH_TAB_OPTION.find('Match') + 1:] print('When matching tabs,', w, file=f) # }}} def add_html_context(app: Any, pagename: str, templatename: str, context: Any, doctree: Any, *args: Any) -> None: context['analytics_id'] = app.config.analytics_id if 'toctree' in context: # this is needed with furo to use all titles from pages # in the sidebar (global) toc original_toctee_function = context['toctree'] def include_sub_headings(**kwargs: Any) -> Any: kwargs['titles_only'] = False return original_toctee_function(**kwargs) context['toctree'] = include_sub_headings @lru_cache def monkeypatch_man_writer() -> None: ''' Monkeypatch the docutils man translator to be nicer ''' from docutils.nodes import Element from docutils.writers.manpage import Table, Translator from sphinx.writers.manpage import ManualPageTranslator # Generate nicer tables https://sourceforge.net/p/docutils/bugs/475/ class PatchedTable(Table): # type: ignore _options: list[str] def __init__(self) -> None: super().__init__() self.needs_border_removal = self._options == ['center'] if self.needs_border_removal: self._options = ['box', 'center'] def as_list(self) -> list[str]: ans: list[str] = super().as_list() if self.needs_border_removal: # remove side and top borders as we use box in self._options ans[2] = ans[2][1:] a, b = ans[2].rpartition('|')[::2] ans[2] = a + b if ans[3] == '_\n': del ans[3] # top border del ans[-2] # bottom border return ans def visit_table(self: ManualPageTranslator, node: object) -> None: setattr(self, '_active_table', PatchedTable()) setattr(ManualPageTranslator, 'visit_table', visit_table) # Improve header generation def header(self: ManualPageTranslator) -> str: di = getattr(self, '_docinfo') di['ktitle'] = di['title'].replace('_', '-') th = (".TH \"%(ktitle)s\" %(manual_section)s" " \"%(date)s\" \"%(version)s\"") % di if di["manual_group"]: th += " \"%(manual_group)s\"" % di th += "\n" sh_tmpl: str = (".SH Name\n" "%(ktitle)s \\- %(subtitle)s\n") return th + sh_tmpl % di # type: ignore setattr(ManualPageTranslator, 'header', header) def visit_image(self: ManualPageTranslator, node: Element) -> None: pass def depart_image(self: ManualPageTranslator, node: Element) -> None: pass def depart_figure(self: ManualPageTranslator, node: Element) -> None: self.body.append(' (images not supported)\n') Translator.depart_figure(self, node) setattr(ManualPageTranslator, 'visit_image', visit_image) setattr(ManualPageTranslator, 'depart_image', depart_image) setattr(ManualPageTranslator, 'depart_figure', depart_figure) orig_astext = Translator.astext def astext(self: Translator) -> Any: b = [] for line in self.body: if line.startswith('.SH'): x, y = line.split(' ', 1) parts = y.splitlines(keepends=True) parts[0] = parts[0].capitalize() line = x + ' ' + '\n'.join(parts) b.append(line) self.body = b return orig_astext(self) setattr(Translator, 'astext', astext) def setup_man_pages() -> None: from kittens.runner import get_kitten_cli_docs base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) for x in glob.glob(os.path.join(base, 'docs/kittens/*.rst')): kn = os.path.basename(x).rpartition('.')[0] if kn in ('custom', 'developing-builtin-kittens'): continue cd = get_kitten_cli_docs(kn) or {} khn = kn.replace('_', '-') man_pages.append((f'kittens/{kn}', 'kitten-' + khn, cd.get('short_desc', 'kitten Documentation'), [author], 1)) monkeypatch_man_writer() def build_extra_man_pages() -> None: base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) kitten = os.environ.get('KITTEN_EXE_FOR_DOCS', os.path.join(base, 'kitty/launcher/kitten')) if not os.path.exists(kitten): kitten = os.path.join(base, 'kitty/launcher/kitty.app/Contents/MacOS/kitten') if not os.path.exists(kitten): subprocess.call(['find', os.path.join(base, 'kitty/launcher')]) raise Exception(f'The kitten binary {kitten} is not built cannot generate man pages') raw = subprocess.check_output([kitten, '-h']).decode() started = 0 names = set() for line in raw.splitlines(): if line.strip() == '@': started = len(line.rstrip()[:-1]) q = line.strip() if started and len(q.split()) == 1 and not q.startswith('-') and ':' not in q: if len(line) - len(line.lstrip()) == started: if not os.path.exists(os.path.join(base, f'docs/kittens/{q}.rst')): names.add(q) cwd = os.path.join(base, 'docs/_build/man') subprocess.check_call([kitten, '__generate_man_pages__'], cwd=cwd) subprocess.check_call([kitten, '__generate_man_pages__'] + list(names), cwd=cwd) if building_man_pages: setup_man_pages() def build_finished(*a: Any, **kw: Any) -> None: if building_man_pages: build_extra_man_pages() def setup(app: Any) -> None: os.makedirs('generated/conf', exist_ok=True) from kittens.runner import all_kitten_names kn = all_kitten_names() write_cli_docs(kn) write_remote_control_protocol_docs() write_color_names_table() write_conf_docs(app, kn) app.add_config_value('string_replacements', {}, True) app.connect('source-read', replace_string) app.add_config_value('analytics_id', '', 'env') app.connect('html-page-context', add_html_context) app.connect('build-finished', build_finished) app.add_lexer('session', SessionLexer() if version_info[0] < 3 else SessionLexer) app.add_role('link', link_role) app.add_role('commit', commit_role) kitty-0.41.1/docs/conf.rst0000664000175000017510000000752714773370543014731 0ustar nileshnileshkitty.conf ================ .. highlight:: conf .. only:: man Overview -------------- |kitty| is highly customizable, everything from keyboard shortcuts, to rendering frames-per-second. See below for an overview of all customization possibilities. You can open the config file within |kitty| by pressing :sc:`edit_config_file` (:kbd:`⌘+,` on macOS). A :file:`kitty.conf` with commented default configurations and descriptions will be created if the file does not exist. You can reload the config file within |kitty| by pressing :sc:`reload_config_file` (:kbd:`⌃+⌘+,` on macOS) or sending |kitty| the ``SIGUSR1`` signal with ``kill -SIGUSR1 $KITTY_PID``. You can also display the current configuration by pressing :sc:`debug_config` (:kbd:`⌥+⌘+,` on macOS). .. _confloc: |kitty| looks for a config file in the OS config directories (usually :file:`~/.config/kitty/kitty.conf`) but you can pass a specific path via the :option:`kitty --config` option or use the :envvar:`KITTY_CONFIG_DIRECTORY` environment variable. See :option:`kitty --config` for full details. **Comments** can be added to the config file as lines starting with the ``#`` character. This works only if the ``#`` character is the first character in the line. **Lines can be split** by starting the next line with the ``\`` character. All leading whitespace and the ``\`` character are removed. .. _include: You can **include secondary config files** via the :code:`include` directive. If you use a relative path for :code:`include`, it is resolved with respect to the location of the current config file. Note that environment variables are expanded, so :code:`${USER}.conf` becomes :file:`name.conf` if :code:`USER=name`. A special environment variable :envvar:`KITTY_OS` is available, to detect the operating system. It is ``linux``, ``macos`` or ``bsd``. Also, you can use :code:`globinclude` to include files matching a shell glob pattern and :code:`envinclude` to include configuration from environment variables. Finally, you can dynamically generate configuration by running a program using :code:`geninclude`. For example:: # Include other.conf include other.conf # Include *.conf files from all subdirs of kitty.d inside the kitty config dir globinclude kitty.d/**/*.conf # Include the *contents* of all env vars starting with KITTY_CONF_ envinclude KITTY_CONF_* # Run the script dynamic.py placed in the same directory as this config file # and include its :file:`STDOUT`. Note that Python scripts are fastest # as they use the embedded Python interpreter, but any executable script # or program is supported, in any language. Remember to mark the script # file executable. geninclude dynamic.py .. note:: Syntax highlighting for :file:`kitty.conf` in vim is available via `vim-kitty `__. .. include:: /generated/conf-kitty.rst Sample kitty.conf -------------------- .. only:: html You can download a sample :file:`kitty.conf` file with all default settings and comments describing each setting by clicking: :download:`sample kitty.conf `. .. only:: man You can edit a fully commented sample kitty.conf by pressing the :sc:`edit_config_file` shortcut in kitty. This will generate a config file with full documentation and all settings commented out. If you have a pre-existing :file:`kitty.conf`, then that will be used instead, delete it to see the sample file. A default configuration file can also be generated by running:: kitty +runpy 'from kitty.config import *; print(commented_out_default_config())' This will print the commented out default config file to :file:`STDOUT`. All mappable actions ------------------------ See the :doc:`list of all the things you can make |kitty| can do `. .. toctree:: :hidden: actions kitty-0.41.1/docs/deccara.rst0000664000175000017510000000177214773370543015362 0ustar nileshnileshSetting text styles/colors in arbitrary regions of the screen ------------------------------------------------------------------ There already exists an escape code to set *some* text attributes in arbitrary regions of the screen, `DECCARA `__. However, it is limited to only a few attributes. |kitty| extends this to work with *all* SGR attributes. So, for example, this can be used to set the background color in an arbitrary region of the screen. The motivation for this extension is the various problems with the existing solution for erasing to background color, namely the *background color erase (bce)* capability. See :iss:`this discussion <160#issuecomment-346470545>` and `this FAQ `__ for a summary of problems with *bce*. For example, to set the background color to blue in a rectangular region of the screen from (3, 4) to (10, 11), you use:: [2*x[4;3;11;10;44$r[*x kitty-0.41.1/docs/desktop-notifications.rst0000664000175000017510000006311414773370543020316 0ustar nileshnilesh.. _desktop_notifications: Desktop notifications ======================= |kitty| implements an extensible escape code (OSC 99) to show desktop notifications. It is easy to use from shell scripts and fully extensible to show title and body. Clicking on the notification can optionally focus the window it came from, and/or send an escape code back to the application running in that window. The design of the escape code is partially based on the discussion in the defunct `terminal-wg `__ The escape code has the form:: 99 ; metadata ; payload Here ```` is :code:`]` and ```` is :code:``. The ``metadata`` is a section of colon separated :code:`key=value` pairs. Every key must be a single character from the set :code:`a-zA-Z` and every value must be a word consisting of characters from the set :code:`a-zA-Z0-9-_/\+.,(){}[]*&^%$#@!`~`. The payload must be interpreted based on the metadata section. The two semi-colons *must* always be present even when no metadata is present. Before going into details, lets see how one can display a simple, single line notification from a shell script:: printf '\x1b]99;;Hello world\x1b\\' To show a message with a title and a body:: printf '\x1b]99;i=1:d=0;Hello world\x1b\\' printf '\x1b]99;i=1:p=body;This is cool\x1b\\' .. tip:: |kitty| also comes with its own :doc:`statically compiled command line tool ` to easily display notifications, with all their advanced features. For example: .. code-block:: sh kitten notify "Hello world" A good day to you The most important key in the metadata is the ``p`` key, it controls how the payload is interpreted. A value of ``title`` means the payload is setting the title for the notification. A value of ``body`` means it is setting the body, and so on, see the table below for full details. The design of the escape code is fundamentally chunked, this is because different terminal emulators have different limits on how large a single escape code can be. Chunking is accomplished by the ``i`` and ``d`` keys. The ``i`` key is the *notification id* which is an :ref:`identifier`. The ``d`` key stands for *done* and can only take the values ``0`` and ``1``. A value of ``0`` means the notification is not yet done and the terminal emulator should hold off displaying it. A non-zero value means the notification is done, and should be displayed. You can specify the title or body multiple times and the terminal emulator will concatenate them, thereby allowing arbitrarily long text (terminal emulators are free to impose a sensible limit to avoid Denial-of-Service attacks). The size of the payload must be no longer than ``2048`` bytes, *before being encoded* or ``4096`` encoded bytes. Both the ``title`` and ``body`` payloads must be either :ref:`safe_utf8` text or UTF-8 text that is :ref:`base64` encoded, in which case there must be an ``e=1`` key in the metadata to indicate the payload is :ref:`base64` encoded. No HTML or other markup in the plain text is allowed. It is strictly plain text, to be interpreted as such. Allowing users to filter notifications ------------------------------------------------------- .. versionadded:: 0.36.0 Specifying application name and notification type Well behaved applications should identify themselves to the terminal by means of two keys ``f`` which is the application name and ``t`` which is the notification type. These are free form keys, they can contain any values, their purpose is to allow users to easily filter out notifications they do not want. Both keys must have :ref:`base64` encoded UTF-8 text as their values. The ``t`` key can be specified multiple times, as notifications can have more than one type. See the `freedesktop.org spec `__ for examples of notification types. .. note:: The application name should generally be set to the filename of the applications `desktop file `__ (without the ``.desktop`` part) or the bundle identifier for a macOS application. While not strictly necessary, this allows the terminal emulator to deduce an icon for the notification when one is not specified. .. tip:: |kitty| has sophisticated notification filtering and management capabilities via :opt:`filter_notification`. Being informed when user activates the notification ------------------------------------------------------- When the user clicks the notification, a couple of things can happen, the terminal emulator can focus the window from which the notification came, and/or it can send back an escape code to the application indicating the notification was activated. This is controlled by the ``a`` key which takes a comma separated set of values, ``report`` and ``focus``. The value ``focus`` means focus the window from which the notification was issued and is the default. ``report`` means send an escape code back to the application. The format of the returned escape code is:: 99 ; i=identifier ; The value of ``identifier`` comes from the ``i`` key in the escape code sent by the application. If the application sends no identifier, then the terminal *must* use ``i=0``. (Ideally ``i`` should have been left out from the response, but for backwards compatibility ``i=0`` is used). Actions can be preceded by a negative sign to turn them off, so for example if you do not want any action, turn off the default ``focus`` action with:: a=-focus Complete specification of all the metadata keys is in the :ref:`table below `. If a terminal emulator encounters a key in the metadata it does not understand, the key *must* be ignored, to allow for future extensibility of this escape code. Similarly if values for known keys are unknown, the terminal emulator *should* either ignore the entire escape code or perform a best guess effort to display it based on what it does understand. Being informed when a notification is closed ------------------------------------------------ .. versionadded:: 0.36.0 Notifications of close events If you wish to be informed when a notification is closed, you can specify ``c=1`` when sending the notification. For example:: 99 ; i=mynotification : c=1 ; hello world Then, the terminal will send the following escape code to inform when the notification is closed:: 99 ; i=mynotification : p=close ; If no notification id was specified ``i=0`` will be used in the response If ``a=report`` is specified and the notification is activated/clicked on then both the activation report and close notification are sent. If the notification is updated then the close event is not sent unless the updated notification also requests a close notification. Note that on some platforms, such as macOS, the OS does not inform applications when notifications are closed, on such platforms, terminals reply with:: 99 ; i=mynotification : p=close ; untracked This means that the terminal has no way of knowing when the notification is closed. Instead, applications can poll the terminal to determine which notifications are still alive (not closed), with:: 99 ; i=myid : p=alive ; The terminal will reply with:: 99 ; i=myid : p=alive ; id1,id2,id3 Here, ``myid`` is present for multiplexer support. The response from the terminal contains a comma separated list of ids that are still alive. Updating or closing an existing notification ---------------------------------------------- .. versionadded:: 0.36.0 The ability to update and close a previous notification To update a previous notification simply send a new notification with the same *notification id* (``i`` key) as the one you want to update. If the original notification is still displayed it will be replaced, otherwise a new notification is displayed. This can be used, for example, to show progress of an operation. How smoothly the existing notification is replaced depends on the underlying OS, for example, on Linux the replacement is usually flicker free, on macOS it isn't, because of Apple's design choices. Note that if no ``i`` key is specified, no updating must take place, even if there is a previous notification without an identifier. The terminal must treat these as being two unique *unidentified* notifications. To close a previous notification, send:: i= : p=close ; This will close a previous notification with the specified id. If no such notification exists (perhaps because it was already closed or it was activated) then the request is ignored. If no ``i`` key is specified, this must be a no-op. Automatically expiring notifications ------------------------------------- A notification can be marked as expiring (being closed) automatically after a specified number of milliseconds using the ``w`` key. The default if unspecified is ``-1`` which means to use whatever expiry policy the OS has for notifications. A value of ``0`` means the notification should never expire. Values greater than zero specify the number of milliseconds after which the notification should be auto-closed. Note that the value of ``0`` is best effort, some platforms honor it and some do not. Positive values are robust, since they can be implemented by the terminal emulator itself, by manually closing the notification after the expiry time. The notification could still be closed before the expiry time by user interaction or OS policy, but it is guaranteed to be closed once the expiry time has passed. Adding icons to notifications -------------------------------- .. versionadded:: 0.36.0 Custom icons in notifications Applications can specify a custom icon to be displayed with a notification. This can be the application's logo or a symbol such as error or warning symbols. The simplest way to specify an icon is by *name*, using the ``n`` key. The value of this key is :ref:`base64` encoded UTF-8 text. Names can be either application names, or symbol names. The terminal emulator will try to resolve the name based on icons and applications available on the computer it is running on. The following list of well defined names must be supported by any terminal emulator implementing this spec. The ``n`` key can be specified multiple times, the terminal will go through the list in order and use the first icon that it finds available on the system. .. table:: Universally available icon names ======================== ============================================== Name Description ======================== ============================================== ``error`` An error symbol ``warn``, ``warning`` A warning symbol ``info`` A symbol denoting an informational message ``question`` A symbol denoting asking the user a question ``help`` A symbol denoting a help message ``file-manager`` A symbol denoting a generic file manager application ``system-monitor`` A symbol denoting a generic system monitoring/information application ``text-editor`` A symbol denoting a generic text editor application ======================== ============================================== If an icon name is an application name it should be an application identifier, such as the filename of the application's :file:`.desktop` file on Linux or its bundle identifier on macOS. For example if the cross-platform application FooBar has a desktop file named: :file:`foo-bar.desktop` and a bundle identifier of ``net.foo-bar-website.foobar`` then it should use the icon names ``net.foo-bar-website.foobar`` *and* ``foo-bar`` so that terminals running on both platforms can find the application icon. If no icon is specified, but the ``f`` key (application name) is specified, the terminal emulator should use the value of the ``f`` key to try to find a suitable icon. Adding icons by transmitting icon data ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This can be done by using the ``p=icon`` key. Then, the payload is the icon image in any of the ``PNG``, ``JPEG`` or ``GIF`` image formats. It is recommended to use an image size of ``256x256`` for icons. Since icons are binary data, they must be transmitted encoded, with ``e=1``. When both an icon name and an image are specified, the terminal emulator must first try to find a locally available icon matching the name and only if one is not found, fallback to the provided image. This is so that users are presented with icons from their current icon theme, where possible. Transmitted icon data can be cached using the ``g`` key. The value of the ``g`` key must be a random globally unique UUID like :ref:`identifier`. Then, the terminal emulator will cache the transmitted data using that key. The cache should exist for as long as the terminal emulator remains running. Thus, in future notifications, the application can simply send the ``g`` key to display a previously cached icon image with needing to re-transmit the actual data with ``p=icon``. The ``g`` key refers only to the icon data, multiple different notifications with different icon or application names can use the same ``g`` key to refer to the same icon. Terminal multiplexers must cache icon data themselves and refresh it in the underlying terminal implementation when detaching and then re-attaching. This means that applications once started need to transmit icon data only once until they are quit. .. note:: To avoid DoS attacks terminal implementations can impose a reasonable max size on the icon cache and evict icons in order of last used. Thus theoretically, a previously cached icon may become unavailable, but given that icons are small images, practically this is not an issue in all but the most resource constrained environments, and the failure mode is simply that the icon is not displayed. .. note:: How the icon is displayed depends on the underlying OS notifications implementation. For example, on Linux, typically a single icon is displayed. On macOS, both the terminal emulator's icon and the specified custom icon are displayed. Adding buttons to the notification --------------------------------------- Buttons can be added to the notification using the *buttons* payload, with ``p=buttons``. Buttons are a list of UTF-8 text separated by the Unicode Line Separator character (U+2028) which is the UTF-8 bytes ``0xe2 0x80 0xa8``. They can be sent either as :ref:`safe_utf8` or :ref:`base64`. When the user clicks on one of the buttons, and reporting is enabled with ``a=report``, the terminal will send an escape code of the form:: 99 ; i=identifier ; button_number Here, `button_number` is a number from 1 onwards, where 1 corresponds to the first button, two to the second and so on. If the user activates the notification as a whole, and not a specific button, the response, as described above is:: 99 ; i=identifier ; If no identifier was specified when creating the notification, ``i=0`` is used. The terminal *must not* send a response unless report is requested with ``a=report``. .. note:: The appearance of the buttons depends on the underlying OS implementation. On most Linux systems, the buttons appear as individual buttons on the notification. On macOS they appear as a drop down menu that is accessible when hovering the notification. Generally, using more than two or three buttons is not a good idea. .. _notifications_query: Playing a sound with notifications ----------------------------------------- .. versionadded:: 0.36.0 The ability to control the sound played with notifications By default, notifications may or may not have a sound associated with them depending on the policies of the OS notifications service. Sometimes it might be useful to ensure a notification is not accompanied by a sound. This can be done by using the ``s`` key which accepts :ref:`base64` encoded UTF-8 text as its value. The set of known sounds names is in the table below, any other names are implementation dependent, for instance, on Linux, terminal emulators will probably support the `standard sound names `__ .. table:: Standard sound names ======================== ============================================== Name Description ======================== ============================================== ``system`` The default system sound for a notification, which may be some kind of beep or just silence ``silent`` No sound must accompany the notification ``error`` A sound associated with error messages ``warn``, ``warning`` A sound associated with warning messages ``info`` A sound associated with information messages ``question`` A sound associated with questions ======================== ============================================== Support for sound names can be queried as described below. Querying for support ------------------------- .. versionadded:: 0.36.0 The ability to query for support An application can query the terminal emulator for support of this protocol, by sending the following escape code:: 99 ; i= : p=? ; A conforming terminal must respond with an escape code of the form:: 99 ; i= : p=? ; key=value : key=value The identifier is present to support terminal multiplexers, so that they know which window to redirect the query response too. Here, the ``key=value`` parts specify details about what the terminal implementation supports. Currently, the following keys are defined: ======= ================================================================================ Key Value ======= ================================================================================ ``a`` Comma separated list of actions from the ``a`` key that the terminal implements. If no actions are supported, the ``a`` key must be absent from the query response. ``c`` ``c=1`` if the terminal supports close events, otherwise the ``c`` must be omitted. ``o`` Comma separated list of occassions from the ``o`` key that the terminal implements. If no occasions are supported, the value ``o=always`` must be sent in the query response. ``p`` Comma separated list of supported payload types (i.e. values of the ``p`` key that the terminal implements). These must contain at least ``title``. ``s`` Comma separated list of sound names from the table of standard sound names above. Terminals will report the list of standard sound names they support. Terminals *should* support atleast ``system`` and ``silent``. ``u`` Comma separated list of urgency values that the terminal implements. If urgency is not supported, the ``u`` key must be absent from the query response. ``w`` ``w=1`` if the terminal supports auto expiring of notifications. ======= ================================================================================ In the future, if this protocol expands, more keys might be added. Clients must ignore keys they do not understand in the query response. To check if a terminal emulator supports this notifications protocol the best way is to send the above *query action* followed by a request for the `primary device attributes `_. If you get back an answer for the device attributes without getting back an answer for the *query action* the terminal emulator does not support this notifications protocol. .. _keys_in_notificatons_protocol: Specification of all keys used in the protocol -------------------------------------------------- ======= ==================== ========== ================= Key Value Default Description ======= ==================== ========== ================= ``a`` Comma separated list ``focus`` What action to perform when the of ``report``, notification is clicked ``focus``, with optional leading ``-`` ``c`` ``0`` or ``1`` ``0`` When non-zero an escape code is sent to the application when the notification is closed. ``d`` ``0`` or ``1`` ``1`` Indicates if the notification is complete or not. A non-zero value means it is complete. ``e`` ``0`` or ``1`` ``0`` If set to ``1`` means the payload is :ref:`base64` encoded UTF-8, otherwise it is plain UTF-8 text with no C0 control codes in it ``f`` :ref:`base64` ``unset`` The name of the application sending the notification. Can be used to filter out notifications. encoded UTF-8 application name ``g`` :ref:`identifier` ``unset`` Identifier for icon data. Make these globally unqiue, like an UUID. ``i`` :ref:`identifier` ``unset`` Identifier for the notification. Make these globally unqiue, like an UUID, so that terminal multiplexers can direct responses to the correct window. Note that for backwards compatibility reasons i=0 is special and should not be used. ``n`` :ref:`base64` ``unset`` Icon name. Can be specified multiple times. encoded UTF-8 application name ``o`` One of ``always``, ``always`` When to honor the notification request. ``unfocused`` means when the window ``unfocused`` or the notification is sent on does not have keyboard focus. ``invisible`` ``invisible`` means the window both is unfocused and not visible to the user, for example, because it is in an inactive tab or its OS window is not currently active. ``always`` is the default and always honors the request. ``p`` One of ``title``, ``title`` Type of the payload. If a notification has no title, the body will be used as title. ``body``, A notification with not title and no body is ignored. Terminal ``close``, emulators should ignore payloads of unknown type to allow for future ``icon``, expansion of this protocol. ``?``, ``alive``, ``buttons`` ``s`` :ref:`base64` ``system`` The sound name to play with the notification. ``silent`` means no sound. encoded sound ``system`` means to play the default sound, if any, of the platform notification service. name Other names are implementation dependent. ``t`` :ref:`base64` ``unset`` The type of the notification. Used to filter out notifications. Can be specified multiple times. encoded UTF-8 notification type ``u`` ``0, 1 or 2`` ``unset`` The *urgency* of the notification. ``0`` is low, ``1`` is normal and ``2`` is critical. If not specified normal is used. ``w`` ``>=-1`` ``-1`` The number of milliseconds to auto-close the notification after. ======= ==================== ========== ================= .. versionadded:: 0.35.0 Support for the ``u`` key to specify urgency .. versionadded:: 0.31.0 Support for the ``o`` key to prevent notifications from focused windows .. note:: |kitty| also supports the `legacy OSC 9 protocol developed by iTerm2 `__ for desktop notifications. .. _base64: Base64 --------------- The base64 encoding used in the this specification is the one defined in :rfc:`4648`. When a base64 payload is chunked, either the chunking should be done before encoding or after. When the chunking is done before encoding, no more than 2048 bytes of data should be encoded per chunk and the encoded data **must** include the base64 padding bytes, if any. When the chunking is done after encoding, each encoded chunk must be no more than 4096 bytes in size. There may or may not be padding bytes at the end of the last chunk, terminals must handle either case. .. _safe_utf8: Escape code safe UTF-8 -------------------------- This must be valid UTF-8 as per the spec in :rfc:`3629`. In addition, in order to make it safe for transmission embedded inside an escape code, it must contain none of the C0 and C1 control characters, that is, the Unicode characters: U+0000 (NUL) - U+1F (Unit separator), U+7F (DEL) and U+80 (PAD) - U+9F (APC). Note that in particular, this means that no newlines, carriage returns, tabs, etc. are allowed. .. _identifier: Identifier ---------------- Any string consisting solely of characters from the set ``[a-zA-Z0-9_-+.]``, that is, the letters ``a-z``, ``A-Z``, the underscore, the hyphen, the plus sign and the period. Applications should make these globally unique, like a UUID for maximum robustness. .. important:: Terminals **must** sanitize ids received from client programs before sending them back in responses, to mitigate input injection based attacks. That is, they must either reject ids containing characters not from the above set, or remove bad characters when reading ids sent to them. kitty-0.41.1/docs/extract-rst-targets.py0000775000175000017510000000370514773370543017550 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2022, Kovid Goyal import os import re from typing import Dict, Iterator tgt_pat = re.compile(r'^.. _(\S+?):$', re.MULTILINE) title_pat = re.compile('^(.+)\n[-=^#*]{5,}$', re.MULTILINE) def find_explicit_targets(text: str) -> Iterator[str]: for m in tgt_pat.finditer(text): yield m.group(1) def find_page_title(text: str) -> str: for m in title_pat.finditer(text): return m.group(1) return '' def main() -> Dict[str, Dict[str, str]]: refs = {} docs = {} base = os.path.dirname(os.path.abspath(__file__)) for dirpath, dirnames, filenames in os.walk(base): if 'generated' in dirnames: dirnames.remove('generated') for f in filenames: if f.endswith('.rst'): with open(os.path.join(dirpath, f)) as stream: raw = stream.read() href = os.path.relpath(stream.name, base).replace(os.sep, '/') href = href.rpartition('.')[0] + '/' docs[href.rstrip('/')] = find_page_title(raw) first_line = raw.lstrip('\n').partition('\n')[0] first_target_added = False for explicit_target in find_explicit_targets(raw): # Shorten the reference link to the top of the page. # Note that anchor links should still be used in HTML docs # to allow jumping within the same page. if not first_target_added: first_target_added = True if first_line.startswith(f'.. _{explicit_target}:'): refs[explicit_target] = href continue refs[explicit_target] = href + f'#{explicit_target.replace("_", "-")}' return {'ref': refs, 'doc': docs} if __name__ == '__main__': import json print(json.dumps(main(), indent=2)) kitty-0.41.1/docs/faq.rst0000664000175000017510000006066314773370543014553 0ustar nileshnileshFrequently Asked Questions ============================== .. highlight:: sh Some special symbols are rendered small/truncated in kitty? ----------------------------------------------------------- The number of cells a Unicode character takes up are controlled by the Unicode standard. All characters are rendered in a single cell unless the Unicode standard says they should be rendered in two cells. When a symbol does not fit, it will either be rescaled to be smaller or truncated (depending on how much extra space it needs). This is often different from other terminals which just let the character overflow into neighboring cells, which is fine if the neighboring cell is empty, but looks terrible if it is not. Some programs, like Powerline, vim with fancy gutter symbols/status-bar, etc. use Unicode characters from the private use area to represent symbols. Often these symbols are wide and should be rendered in two cells. However, since private use area symbols all have their width set to one in the Unicode standard, |kitty| renders them either smaller or truncated. The exception is if these characters are followed by a space or en-space (U+2002) in which case kitty makes use of the extra cell to render them in two cells. This behavior can be turned off for specific symbols using :opt:`narrow_symbols`. As of version 0.40 kitty has innovated a :doc:`new protocol ` that allows programs running in the terminal to control how many cells a character is rendered in thereby solving the issue of character width once and for all. Similarly, some monospaced font families are buggy and have bold or italic faces that have characters wider than the width of the normal face, these will also result in clipping. Such issues should be reported to the font developer. Monospaced font families must have all their characters rendered within a fixed width across all faces of the font, otherwise they aren't really monospaced. Using a color theme with a background color does not work well in vim? ----------------------------------------------------------------------- First, be sure to `use a color scheme in vim `__ instead of relying on the terminal theme. Otherwise, background and text selection colours may be difficult to read. Sadly, vim has very poor out-of-the-box detection for modern terminal features. Furthermore, it `recently broke detection even more `__. It kind of, but not really, supports terminfo, except it overrides it with its own hard-coded values when it feels like it. Worst of all, it has no ability to detect modern features not present in terminfo, at all, even security sensitive ones like bracketed paste. Thankfully, probably as a consequence of this lack of detection, vim allows users to configure these low level details. So, to make vim work well with any modern terminal, including kitty, add the following to your :file:`~/.vimrc`. .. code-block:: vim " Mouse support set mouse=a set ttymouse=sgr set balloonevalterm " Styled and colored underline support let &t_AU = "\e[58:5:%dm" let &t_8u = "\e[58:2:%lu:%lu:%lum" let &t_Us = "\e[4:2m" let &t_Cs = "\e[4:3m" let &t_ds = "\e[4:4m" let &t_Ds = "\e[4:5m" let &t_Ce = "\e[4:0m" " Strikethrough let &t_Ts = "\e[9m" let &t_Te = "\e[29m" " Truecolor support let &t_8f = "\e[38:2:%lu:%lu:%lum" let &t_8b = "\e[48:2:%lu:%lu:%lum" let &t_RF = "\e]10;?\e\\" let &t_RB = "\e]11;?\e\\" " Bracketed paste let &t_BE = "\e[?2004h" let &t_BD = "\e[?2004l" let &t_PS = "\e[200~" let &t_PE = "\e[201~" " Cursor control let &t_RC = "\e[?12$p" let &t_SH = "\e[%d q" let &t_RS = "\eP$q q\e\\" let &t_SI = "\e[5 q" let &t_SR = "\e[3 q" let &t_EI = "\e[1 q" let &t_VS = "\e[?12l" " Focus tracking let &t_fe = "\e[?1004h" let &t_fd = "\e[?1004l" execute "set =\[I" execute "set =\[O" " Window title let &t_ST = "\e[22;2t" let &t_RT = "\e[23;2t" " vim hardcodes background color erase even if the terminfo file does " not contain bce. This causes incorrect background rendering when " using a color theme with a background color in terminals such as " kitty that do not support background color erase. let &t_ut='' These settings must be placed **before** setting the ``colorscheme``. It is also important that the value of the vim ``term`` variable is not changed after these settings. I get errors about the terminal being unknown or opening the terminal failing or functional keys like arrow keys don't work? ------------------------------------------------------------------------------------------------------------------------------- These issues all have the same root cause: the kitty terminfo files not being available. The most common way this happens is SSHing into a computer that does not have the kitty terminfo files. The simplest fix for that is running:: kitten ssh myserver It will automatically copy over the terminfo files and also magically enable :doc:`shell integration ` on the remote machine. This :doc:`ssh kitten ` takes all the same command line arguments as :program:`ssh`, you can alias it to something small in your shell's rc files to avoid having to type it each time:: alias s="kitten ssh" If this does not work, see :ref:`manual_terminfo_copy` for alternative ways to get the kitty terminfo files onto a remote computer. The next most common reason for this is if you are running commands as root using :program:`sudo` or :program:`su`. These programs often filter the :envvar:`TERMINFO` environment variable which is what points to the kitty terminfo files. First, make sure the :envvar:`TERM` is set to ``xterm-kitty`` in the sudo environment. By default, it should be automatically copied over. If you are using a well maintained Linux distribution, it will have a ``kitty-terminfo`` package that you can simply install to make the kitty terminfo files available system-wide. Then the problem will no longer occur. Alternately, you can configure :program:`sudo` to preserve :envvar:`TERMINFO` by running ``sudo visudo`` and adding the following line:: Defaults env_keep += "TERM TERMINFO" If none of these are suitable for you, you can run sudo as :: sudo TERMINFO="$TERMINFO" This will make :envvar:`TERMINFO` available in the sudo environment. Create an alias in your shell rc files to make this convenient:: alias sudo="sudo TERMINFO=\"$TERMINFO\"" If you have double width characters in your prompt, you may also need to explicitly set a UTF-8 locale, like:: export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 I cannot use the key combination X in program Y? ------------------------------------------------------- First, run:: kitten show-key -m kitty Press the key combination X. If the kitten reports the key press that means kitty is correctly sending the key press to terminal programs. You need to report the issue to the developer of the terminal program. Most likely they have not added support for :doc:`/keyboard-protocol`. If the kitten does not report it, it means that the key is bound to some action in kitty. You can unbind it in :file:`kitty.conf` with: .. code-block:: conf map X no_op Here X is the keys you press on the keyboard. So for example :kbd:`ctrl+shift+1`. How do I change the colors in a running kitty instance? ------------------------------------------------------------ The easiest way to do it is to use the :doc:`themes kitten `, to choose a new color theme. Simply run:: kitten themes And choose your theme from the list. You can also define keyboard shortcuts to set colors, for example:: map f1 set_colors --configured /path/to/some/config/file/colors.conf Or you can enable :doc:`remote control ` for |kitty| and use :ref:`at-set-colors`. The shortcut mapping technique has the same syntax as the remote control command, for details, see :ref:`at-set-colors`. To change colors when SSHing into a remote host, use the :opt:`color_scheme ` setting for the :doc:`ssh kitten `. Additionally, You can use the `OSC terminal escape codes `__ to set colors. Examples of using OSC escape codes to set colors:: Change the default foreground color: printf '\x1b]10;#ff0000\x1b\\' Change the default background color: printf '\x1b]11;blue\x1b\\' Change the cursor color: printf '\x1b]12;blue\x1b\\' Change the selection background color: printf '\x1b]17;blue\x1b\\' Change the selection foreground color: printf '\x1b]19;blue\x1b\\' Change the nth color (0 - 255): printf '\x1b]4;n;green\x1b\\' You can use various syntaxes/names for color specifications in the above examples. See `XParseColor `__ for full details. If a ``?`` is given rather than a color specification, kitty will respond with the current value for the specified color. How do I specify command line options for kitty on macOS? --------------------------------------------------------------- Apple does not want you to use command line options with GUI applications. To workaround that limitation, |kitty| will read command line options from the file :file:`/macos-launch-services-cmdline` when it is launched from the GUI, i.e. by clicking the |kitty| application icon or using ``open -a kitty``. Note that this file is *only read* when running via the GUI. You can, of course, also run |kitty| from a terminal with command line options, using: :file:`/Applications/kitty.app/Contents/MacOS/kitty`. And within |kitty| itself, you can always run |kitty| using just ``kitty`` as it cleverly adds itself to the :envvar:`PATH`. I catted a binary file and now kitty is hung? ----------------------------------------------- **Never** output unknown binary data directly into a terminal. Terminals have a single channel for both data and control. Certain bytes are control codes. Some of these control codes are of arbitrary length, so if the binary data you output into the terminal happens to contain the starting sequence for one of these control codes, the terminal will hang waiting for the closing sequence. Press :sc:`reset_terminal` to reset the terminal. If you do want to cat unknown data, use ``cat -v``. kitty is not able to use my favorite font? --------------------------------------------- |kitty| achieves its stellar performance by caching alpha masks of each rendered character on the GPU, and rendering them all in parallel. This means it is a strictly character cell based display. As such it can use only monospace fonts, since every cell in the grid has to be the same size. Furthermore, it needs fonts to be freely resizable, so it does not support bitmapped fonts. .. note:: If you are trying to use a font patched with `Nerd Fonts `__ symbols, don't do that as patching destroys fonts. There is no need, kitty has a builtin NERD font and will use it for symbols not found in any other font on your system. If you have patched fonts on your system they might be used instead for NERD symbols, so to force kitty to use the pure NERD font for NERD symbols, add the following line to :file:`kitty.conf`:: # Nerd Fonts v3.3.0 symbol_map U+e000-U+e00a,U+ea60-U+ebeb,U+e0a0-U+e0c8,U+e0ca,U+e0cc-U+e0d7,U+e200-U+e2a9,U+e300-U+e3e3,U+e5fa-U+e6b7,U+e700-U+e8ef,U+ed00-U+efc1,U+f000-U+f2ff,U+f000-U+f2e0,U+f300-U+f381,U+f400-U+f533,U+f0001-U+f1af0 Symbols Nerd Font Mono Those Unicode symbols not in the `Unicode private use areas `__ are not included. If your font is not listed in ``kitten choose-fonts`` it means that it is not monospace or is a bitmapped font. On Linux you can list all monospace fonts with:: fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True On macOS, you can open *Font Book* and look in the :guilabel:`Fixed width` collection to see all monospaced fonts on your system. Note that **on Linux**, the spacing property is calculated by fontconfig based on actual glyph widths in the font. If for some reason fontconfig concludes your favorite monospace font does not have ``spacing=100`` you can override it by using the following :file:`~/.config/fontconfig/fonts.conf`:: Your Font Family Name 100 After creating (or modifying) this file, you may need to run the following command to rebuild your fontconfig cache:: fc-cache -r Then, the font will be available in ``kitten choose-fonts``. How can I assign a single global shortcut to bring up the kitty terminal? ----------------------------------------------------------------------------- Bringing up applications on a single key press is the job of the window manager/desktop environment. For ways to do it with kitty (or indeed any terminal) in different environments, see :iss:`here <45>`. I do not like the kitty icon! ------------------------------- The kitty icon was created as tribute to my cat of nine years who passed away, as such it is not going to change. However, if you do not like it, there are many alternate icons available, click on an icon to visit its homepage: .. image:: https://github.com/k0nserv/kitty-icon/raw/main/kitty.iconset/icon_256x256.png :target: https://github.com/k0nserv/kitty-icon :width: 256 .. image:: https://github.com/DinkDonk/kitty-icon/raw/main/kitty-dark.png :target: https://github.com/DinkDonk/kitty-icon :width: 256 .. image:: https://github.com/DinkDonk/kitty-icon/raw/main/kitty-light.png :target: https://github.com/DinkDonk/kitty-icon :width: 256 .. image:: https://github.com/hristost/kitty-alternative-icon/raw/main/kitty_icon.png :target: https://github.com/hristost/kitty-alternative-icon :width: 256 .. image:: https://github.com/igrmk/whiskers/raw/main/whiskers.svg :target: https://github.com/igrmk/whiskers :width: 256 .. image:: https://github.com/samholmes/whiskers/raw/main/whiskers.png :target: https://github.com/samholmes/whiskers :width: 256 .. image:: https://github.com/user-attachments/assets/a37d7830-4a8c-45a8-988a-3e98a41ea541 :target: https://github.com/diegobit/kitty-icon :width: 256 .. image:: https://github.com/eccentric-j/eccentric-icons/raw/main/icons/kitty-terminal/2d/kitty-preview.png :target: https://github.com/eccentric-j/eccentric-icons :width: 256 .. image:: https://github.com/eccentric-j/eccentric-icons/raw/main/icons/kitty-terminal/3d/kitty-preview.png :target: https://github.com/eccentric-j/eccentric-icons :width: 256 .. image:: https://github.com/sodapopcan/kitty-icon/raw/main/kitty.app.png :target: https://github.com/sodapopcan/kitty-icon :width: 256 .. image:: https://github.com/sfsam/some_icons/raw/main/kitty.app.iconset/icon_128x128@2x.png :target: https://github.com/sfsam/some_icons :width: 256 .. image:: https://github.com/igrmk/twiskers/raw/main/icon/twiskers.svg :target: https://github.com/igrmk/twiskers :width: 256 You can put :file:`kitty.app.icns` (macOS only) or :file:`kitty.app.png` in the :ref:`kitty configuration directory `, and this icon will be applied automatically at startup. On X11 and Wayland, this will set the icon for kitty windows. Note that not all Wayland compositors support the `protocol needed `__ for changing window icons. Unfortunately, on macOS, Apple's Dock does not change its cached icon so the custom icon will revert when kitty is quit. Run the following to force the Dock to update its cached icons: .. code-block:: sh rm /var/folders/*/*/*/com.apple.dock.iconcache; killall Dock If you prefer not to keep a custom icon in the kitty config folder, on macOS, you can also set it with the following command: .. code-block:: sh # Set kitty.icns as the icon for currently running kitty kitty +runpy 'from kitty.fast_data_types import cocoa_set_app_icon; import sys; cocoa_set_app_icon(*sys.argv[1:]); print("OK")' kitty.icns # Set the icon for app bundle specified by the path kitty +runpy 'from kitty.fast_data_types import cocoa_set_app_icon; import sys; cocoa_set_app_icon(*sys.argv[1:]); print("OK")' /path/to/icon.png /Applications/kitty.app You can also change the icon manually by following the steps: #. Find :file:`kitty.app` in the Applications folder, select it and press :kbd:`⌘+I` #. Drag :file:`kitty.icns` onto the application icon in the kitty info pane #. Delete the icon cache and restart Dock: .. code-block:: sh rm /var/folders/*/*/*/com.apple.dock.iconcache; killall Dock How do I map key presses in kitty to different keys in the terminal program? -------------------------------------------------------------------------------------- This is accomplished by using ``map`` with :ac:`send_key` in :file:`kitty.conf`. For example:: map alt+s send_key ctrl+s map ctrl+alt+2 combine : send_key ctrl+c : send_key h : send_key a This causes the program running in kitty to receive the :kbd:`ctrl+s` key when you press the :kbd:`alt+s` key and several keystrokes when you press :kbd:`ctrl+alt+2`. To see this in action, run:: kitten show-key -m kitty Which will print out what key events it receives. To send arbitrary text rather than a key press, see :sc:`send_text ` instead. How do I open a new window or tab with the same working directory as the current window? -------------------------------------------------------------------------------------------- In :file:`kitty.conf` add the following:: map f1 launch --cwd=current map f2 launch --cwd=current --type=tab Pressing :kbd:`F1` will open a new kitty window with the same working directory as the current window. The :doc:`launch command ` is very powerful, explore :doc:`its documentation `. Things behave differently when running kitty from system launcher vs. from another terminal? ----------------------------------------------------------------------------------------------- This will be because of environment variables. When you run kitty from the system launcher, it gets a default set of system environment variables. When you run kitty from another terminal, you are actually running it from a shell, and the shell's rc files will have setup a whole different set of environment variables which kitty will now inherit. You need to make sure that the environment variables you define in your shell's rc files are either also defined system wide or via the :opt:`env` directive in :file:`kitty.conf`. Common environment variables that cause issues are those related to localization, such as :envvar:`LANG`, ``LC_*`` and loading of configuration files such as ``XDG_*``, :envvar:`KITTY_CONFIG_DIRECTORY`. To see the environment variables that kitty sees, you can add the following mapping to :file:`kitty.conf`:: map f1 show_kitty_env_vars then pressing :kbd:`F1` will show you the environment variables kitty sees. This problem is most common on macOS, as Apple makes it exceedingly difficult to setup environment variables system-wide, so people end up putting them in all sorts of places where they may or may not work. I am using tmux and have a problem -------------------------------------- First, terminal multiplexers are :iss:`a bad idea <391#issuecomment-638320745>`, do not use them, if at all possible. kitty contains features that do all of what tmux does, but better, with the exception of remote persistence (:iss:`391`). If you still want to use tmux, read on. Using ancient versions of tmux such as 1.8 will cause gibberish on screen when pressing keys (:iss:`3541`). If you are using tmux with multiple terminals or you start it under one terminal and then switch to another and these terminals have different :envvar:`TERM` variables, tmux will break. You will need to restart it as tmux does not support multiple terminfo definitions. Displaying images while inside programs such as nvim or ranger may not work depending on whether those programs have adopted support for the :ref:`unicode placeholders ` workaround that kitty created for tmux refusing to support images. If you use any of the advanced features that kitty has innovated, such as :doc:`styled underlines `, :doc:`desktop notifications `, :doc:`extended keyboard support `, :doc:`file transfer `, :doc:`the ssh kitten `, :doc:`shell integration ` etc. they may or may not work, depending on the whims of tmux's maintainer, your version of tmux, etc. I opened and closed a lot of windows/tabs and top shows kitty's memory usage is very high? ------------------------------------------------------------------------------------------- :program:`top` is not a good way to measure process memory usage. That is because on modern systems, when allocating memory to a process, the C library functions will typically allocate memory in large blocks, and give the process chunks of these blocks. When the process frees a chunk, the C library will not necessarily release the underlying block back to the OS. So even though the application has released the memory, :program:`top` will still claim the process is using it. To check for memory leaks, instead use a tool like `Valgrind `__. Run:: PYTHONMALLOC=malloc valgrind --tool=massif kitty Now open lots of tabs/windows, generate lots of output using tools like find/yes etc. Then close all but one window. Do some random work for a few seconds in that window, maybe run yes or find again. Then quit kitty and run:: massif-visualizer massif.out.* You will see the allocations graph goes up when you opened the windows, then goes back down when you closed them, indicating there were no memory leaks. For those interested, you can get a similar profile out of :program:`valgrind` as you get with :program:`top` by adding ``--pages-as-heap=yes`` then you will see that memory allocated in malloc is not freed in free. This can be further refined if you use ``glibc`` as your C library by setting the environment variable ``MALLOC_MMAP_THRESHOLD_=64``. This will cause free to actually free memory allocated in sizes of more than 64 bytes. With this set, memory usage will climb high, then fall when closing windows, but not fall all the way back. The remaining used memory can be investigated using valgrind again, and it will come from arenas in the GPU drivers and the per thread arenas glibc's malloc maintains. These too allocate memory in large blocks and don't release it back to the OS immediately. Why does kitty sometimes start slowly on my Linux system? ------------------------------------------------------------------------------------------- |kitty| takes no longer (within 100ms) to start than other similar GPU terminal emulators, (and may be faster than some). If |kitty| occasionally takes a long time to start, it could be a power management issue with the graphics card. On a multi-GPU system (which many modern laptops are, having a power efficient GPU that's built into the processor and a power hungry dedicated one that's usually off), even if the answer of the GPU will only be "don't use me". For example, if you have a system with an AMD CPU and an NVIDIA GPU, and you know that you want to use the lower powered card to save battery life and because kitty does not require a powerful GPU to function, you can choose not to wake up the dedicated card, which has been reported on at least one system (:iss:`4292`) to take ≈2 seconds, by running |kitty| as:: MESA_LOADER_DRIVER_OVERRIDE=radeonsi __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json kitty The correct command will depend on your situation and hardware. ``__EGL_VENDOR_LIBRARY_FILENAMES`` instructs the GL dispatch library to use :file:`libEGL_mesa.so` and ignore :file:`libEGL_nvidia.so` also available on the system, which will wake the NVIDIA card during device enumeration. ``MESA_LOADER_DRIVER_OVERRIDE`` also assures that Mesa won't offer any NVIDIA card during enumeration, and will instead just use :file:`radeonsi_dri.so`. kitty-0.41.1/docs/file-transfer-protocol.rst0000664000175000017510000006350714773370543020404 0ustar nileshnileshFile transfer over the TTY =============================== There are sometimes situations where the TTY is the only convenient pipe between two connected systems, for example, nested SSH sessions, a serial line, etc. In such scenarios, it is useful to be able to transfer files over the TTY. This protocol provides the ability to transfer regular files, directories and links (both symbolic and hard) preserving most of their metadata. It can optionally use compression and transmit only binary diffs to speed up transfers. However, since all data is base64 encoded for transmission over the TTY, this protocol will never be competitive with more direct file transfer mechanisms. Overall design ---------------- The basic design of this protocol is around transfer "sessions". Since untrusted software should not be able to read/write to another machines filesystem, a session must be approved by the user in the terminal emulator before any actual data is transmitted, unless a :ref:`pre-shared password is provided `. There can be either send or receive sessions. In send sessions files are sent from remote client to the terminal emulator and vice versa for receive sessions. Every session basically consists of sending metadata for the files first and then sending the actual data. The session is a series of commands, every command carrying the session id (which should be a random unique-ish identifier, to avoid conflicts). The session is bi-directional with commands going both to and from the terminal emulator. Every command in a session also carries an ``action`` field that specifies what the command does. The remaining fields in the command are dependent on the nature of the command. Let's look at some simple examples of sessions to get a feel for the protocol. Sending files to the computer running the terminal emulator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The client starts by sending a start send command:: → action=send id=someid It then waits for a status message from the terminal either allowing the transfer or refusing it. Until this message is received the client is not allowed to send any more commands for the session. The terminal emulator should drop a session if it receives any commands before sending an ``OK`` response. If the user accepts the transfer, the terminal will send:: ← action=status id=someid status=OK Or if the transfer is refused:: ← action=status id=someid status=EPERM:User refused the transfer The client then sends one or more ``file`` commands with the metadata of the file it wants to transfer:: → action=file id=someid file_id=f1 name=/path/to/destination → action=file id=someid file_id=f2 name=/path/to/destination2 ftype=directory The terminal responds with either ``OK`` for directories or ``STARTED`` for files:: ← action=status id=someid file_id=f1 status=STARTED ← action=status id=someid file_id=f2 status=OK If there was an error with the file, for example, if the terminal does not have permission to write to the specified location, it will instead respond with an error, such as:: ← action=status id=someid file_id=f1 status=EPERM:No permission The client sends data for files using ``data`` commands. It does not need to wait for the ``STARTED`` from the terminal for this, the terminal must discard data for files that are not ``STARTED``. Data for a file is sent in individual chunks of no larger than ``4096`` bytes. For example:: → action=data id=someid file_id=f1 data=chunk of bytes → action=data id=someid file_id=f1 data=chunk of bytes ... → action=end_data id=someid file_id=f1 data=chunk of bytes The sequence of data transmission for a file is ended with an ``end_data`` command. After each data packet is received the terminal replies with an acknowledgement of the form:: ← action=status id=someid file_id=f1 status=PROGRESS size=bytes written After ``end_data`` the terminal replies with:: ← action=status id=someid file_id=f1 status=OK size=bytes written If an error occurs while writing the data, the terminal replies with an error code and ignores further commands about that file, for example:: ← action=status id=someid file_id=f1 status=EIO:Failed to write to file Once the client has finished sending as many files as it wants to, it ends the session with:: → action=finish id=someid At this point the terminal commits the session, applying file metadata, creating links, etc. If any errors occur it responds with an error message, such as:: ← action=status id=someid status=Some error occurred Receiving files from the computer running terminal emulator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The client starts by sending a start receive command:: → action=receive id=someid size=num_of_paths It then sends a list of ``num_of_paths`` paths it is interested in receiving:: → action=file id=someid file_id=f1 name=/some/path → action=file id=someid file_id=f2 name=/some/path2 ... The client must then wait for responses from the terminal emulator. It is an error to send anymore commands to the terminal until an ``OK`` response is received from the terminal. The terminal wait for the user to accept the request. If accepted, it sends:: ← action=status id=someid status=OK If permission is denied it sends:: ← action=status id=someid status=EPERM:User refused the transfer The terminal then sends the metadata for all requested files. If any of them are directories, it traverses the directories recursively, listing all files. Note that symlinks must not be followed, but sent as symlinks:: ← action=file id=someid file_id=f1 mtime=XXX permissions=XXX name=/absolute/path status=file_id1 size=size_in_bytes file_type=type parent=file_id of parent ← action=file id=someid file_id=f1 mtime=XXX permissions=XXX name=/absolute/path2 status=file_id2 size=size_in_bytes file_type=type parent=file_id of parent ... Here the ``file_id`` field is set to the ``file_id`` value sent from the client and the ``status`` field is set to the actual file id for each file. This is because a file query sent from the client can result in multiple actual files if it is a directory. The ``parent`` field is the actual ``file_id`` of the directory containing this file and is set for entries that are generated from client requests that match directories. This allows the client to build an unambiguous picture of the file tree. Once all the files are listed, the terminal sends an ``OK`` response that also specifies the absolute path to the home directory for the user account running the terminal:: ← action=status id=someid status=OK name=/path/to/home If an error occurs while listing any of the files asked for by the client, the terminal will send an error response like:: ← action=status id=someid file_id=f1 status=ENOENT: Does not exist Here, ``file_id`` is the same as was sent by the client in its initial query. Now, the client can send requests for file data using the paths sent by the terminal emulator:: → action=file id=someid file_id=f1 name=/some/path ... The client must not send requests for directories and absolute symlinks. The terminal emulator replies with the data for the files, as a sequence of ``data`` commands each with a chunk of data no larger than ``4096`` bytes, for each file (the terminal emulator must send the data for one file at a time):: ← action=data id=someid file_id=f1 data=chunk of bytes ... ← action=end_data id=someid file_id=f1 data=chunk of bytes If any errors occur reading file data, the terminal emulator sends an error message for the file, for example:: ← action=status id=someid file_id=f1 status=EIO:Could not read Once the client is done reading data for all the files it expects, it terminates the session with:: → action=finished id=someid Canceling a session ---------------------- A client can decide to cancel a session at any time (for example if the user presses :kbd:`ctrl+c`). To cancel a session it sends a ``cancel`` action to the terminal emulator:: → action=cancel id=someid The terminal emulator drops the session and sends a cancel acknowledgement:: ← action=status id=someid status=CANCELED The client **must** wait for the canceled response from the emulator discarding any other responses till the cancel is received. If it does not wait, after it quits the responses might end up being printed to screen. Quieting responses from the terminal ------------------------------------- The above protocol includes lots of messages from the terminal acknowledging receipt of data, granting permission etc., acknowledging cancel requests, etc. For extremely simple clients like shell scripts, it might be useful to suppress these responses, which can be done by adding the ``quiet`` key to the start session command:: → action=send id=someid quiet=1 The key can take the values ``1`` - meaning suppress acknowledgement responses or ``2`` - meaning suppress all responses including errors. Only actual data responses are sent. Note that in particular this means acknowledgement of permission for the transfer to go ahead is suppressed, so this is typically useful only with :ref:`bypass_auth`. .. _file_metadata: File metadata ----------------- File metadata includes file paths, permissions and modification times. They are somewhat tricky as different operating systems support different kinds of metadata. This specification defines a common minimum set which should work across most operating systems. File paths File paths must be valid UTF-8 encoded POSIX paths (i.e. using the forward slash ``/`` as a separator). Linux systems allow non UTF-8 file paths, these are not supported. A leading ``~/`` means a path is relative to the ``HOME`` directory. All path must be either absolute (i.e. with a leading ``/``) or relative to the HOME directory. Individual components of the path must be no longer than 255 UTF-8 bytes. Total path length must be no more than 4096 bytes. Paths from Windows systems must use the forward slash as the separator, the first path component must be the drive letter with a colon. For example: :file:`C:\\some\\file.txt` is represented as :file:`/C:/some/file.txt`. For maximum portability, the following characters *should* be omitted from paths (however implementations are free to try to support them returning errors for non-representable paths):: \ * : < > ? | / File modification times Must be represented as the number of nanoseconds since the UNIX epoch. An individual file system may not store file metadata with this level of accuracy in which case it should use the closest possible approximation. File permissions Represented as a number with the usual UNIX read, write and execute bits. In addition, the sticky, set-group-id and set-user-id bits may be present. Implementations should make a best effort to preserve as many bits as possible. On Windows, there is only a read-only bit. When reading file metadata all the ``WRITE`` bits should be set if the read only bit is clear and cleared if it is set. When writing files, the read-only bit should be set if the bit indicating write permission for the user is clear. The other UNIX bits must be ignored when writing. When reading, all the ``READ`` bits should always be set and all the ``EXECUTE`` bits should be set if the file is directly executable by the Windows Operating system. There is no attempt to map Window's ACLs to permission bits. Symbolic and hard links --------------------------- Symbolic and hard links can be preserved by this protocol. .. note:: In the following when target paths of symlinks are sent as actual paths, they must be encoded in the same way as discussed in :ref:`file_metadata`. It is up to the receiving side to translate them into appropriate paths for the local operating system. This may not always be possible, in which case either the symlink should not be created or a broken symlink should be created. Sending links to the terminal emulator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When sending files to the terminal emulator, the file command has the form:: → action=file id=someid file_id=f1 name=/path/to/link file_type=link → action=file id=someid file_id=f2 name=/path/to/symlink file_type=symlink Then, when the client is sending data for the files, for hardlinks, the data will be the ``file_id`` of the target file (assuming the target file is also being transmitted, otherwise the hard link should be transmitted as a plain file):: → action=end_data id=someid file_id=f1 data=target_file_id_encoded_as_utf8 For symbolic links, the data is a little more complex. If the symbolic link is to a destination being transmitted, the data has the form:: → action=end_data id=someid file_id=f1 data=fid:target_file_id_encoded_as_utf8 → action=end_data id=someid file_id=f1 data=fid_abs:target_file_id_encoded_as_utf8 The ``fid_abs`` form is used if the symlink uses an absolute path, ``fid`` if it uses a relative path. If the symlink is to a destination that is not being transmitted, then the prefix ``path:`` and the actual path in the symlink is transmitted. Receiving links from the terminal emulator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When receiving files from the terminal emulator, link data is transmitted in two parts. First when the emulator sends the initial file listing to the client, the ``file_type`` is set to the link type and the ``data`` field is set to file_id of the target file if the target file is included in the listing. For example:: ← action=file id=someid file_id=f1 status=file_id1 ... ← action=file id=someid file_id=f1 status=file_id2 file_type=symlink data=file_id1 ... Here the rest of the metadata has been left out for clarity. Notice that the second file is symlink whose ``data`` field is set to the file id of the first file (the value of the ``status`` field of the first file). The same technique is used for hard links. The client should not request data for hard links, instead creating them directly after transmission is complete. For symbolic links the terminal must send the actual symbolic link target as a UTF-8 encoded path in the data field. The client can use this path either as-is (when the target is not a transmitted file) or to decide whether to create the symlink with a relative or absolute path when the target is a transmitted file. Transmitting binary deltas ----------------------------- Repeated transfer of large files that have only changed a little between the receiving and sending side can be sped up significantly by transmitting binary deltas of only the changed portions. This protocol has built-in support for doing that. This support uses the `rsync algorithm `__. In this algorithm, first the receiving side sends a file signature that contains hashes of blocks in the file. Then the sending side sends only those blocks that have changed. The receiving side applies these deltas to the file to update it till it matches the file on the sending side. The modification to the basic protocol consists of setting the ``transmission_type`` key to ``rsync`` when requesting a file. This triggers transmission of signatures and deltas instead of file data. The details are different for sending and receiving. Sending to the terminal emulator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When sending the metadata of the file it wants to transfer, the client adds the ``transmission_type`` key:: → action=file id=someid file_id=f1 name=/path/to/destination transmission_type=rsync The ``STARTED`` response from the terminal will have ``transmission_type`` set to ``rsync`` if the file exists and the terminal is able to send signature data:: ← action=status id=someid file_id=f1 status=STARTED transmission_type=rsync The terminal then transmits the signature using ``data`` commands:: ← action=data id=someid file_id=f1 data=... ... ← action=end_data id=someid file_id=f1 data=... Once the client receives and processes the full signature, it transmits the file delta to the terminal as ``data`` commands:: → action=data id=someid file_id=f1 data=... → action=data id=someid file_id=f1 data=... ... → action=end_data id=someid file_id=f1 data=... The terminal then uses this delta to update the file. Receiving from the terminal emulator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When the client requests file data from the terminal emulator, it can add the ``transmission_type=rsync`` key to indicate it will be sending a signature for that file:: → action=file id=someid file_id=f1 name=/some/path transmission_type=rsync The client then sends the signature using ``data`` commands:: → action=data id=someid file_id=f1 data=... ... → action=end_data id=someid file_id=f1 data=... After receiving the signature the terminal replies with the delta as a series of ``data`` commands:: ← action=data id=someid file_id=f1 data=... ... ← action=end_data id=someid file_id=f1 data=... The client then uses this delta to update the file. The format of signatures and deltas ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In what follows, all integers must be encoded in little-endian format, regardless of the architecture of the machines involved. The XXH3 hash family refers to `the xxHash algorithm `__. A signature first has a 12 byte header of the form: .. code:: uint16 version uint16 checksum_type uint16 strong_hash_type uint16 weak_hash_type uint32 block_size These fields define the parameters to the rsync algorithm. Allowed values are currently all zero except for ``block_size``, which is usually the square root of the file size, but implementations are free to use any algorithm they like to arrive at the block size. ``checksum_type`` must be ``0`` which indicates using the XXH3-128 bit hash to verify file integrity after transmission. ``strong_hash_type`` must be ``0`` which indicates using the XXH3-64 bit hash to identify blocks. ``weak_hash_type`` must be ``0`` which indicates using the `rsync rolling checksum hash `__ to identify blocks, weakly. After the header comes the list of block signatures. The number of blocks is unknown allowing for streaming, the transfer protocol takes care of indicating end-of-stream via an ``action=end_data`` packet. Each signature in the list is of the form: .. code:: uint64 index uint32 weak_hash uint64 strong_hash Here, ``index`` is the zero-based block number. ``weak_hash`` is the weak, but easy to calculate hash of the block and strong hash is a stronger hash of the block that is very unlikely to collide. The algorithms used for these hashes are specified by the signature header above. Given the ``block_size`` from the header and ``index`` the position of a block in the file is: ``index * block_size``. Once the sending side receives the signature, it calculates a *delta* based on the actual file contents and transmits that delta to the receiving side. The delta is of the form of a list of *operations*. An operation is a single byte denoting the operation type followed by variable length data depending on the type. The types of operations are: ``Block (type=0)`` Followed by an 8 byte ``uint64`` that is the block index. It means copy the specified block from the existing file to the output, unmodified. ``Data (type=1)`` Followed by a 4 byte ``uint32`` that is the size of the payload and then the payload itself. The payload must be written to the output. ``Hash (type=2)`` Followed by a 2 byte ``uint16`` specifying the size of the hash checksum and then the checksum itself. The checksum of the output file must match this checksum. The algorithm used to calculate the checksum is specified in the signature header. ``BlockRange (type=3)`` Followed by an 8 byte ``uint64`` that is the starting block index and then a 4 byte ``uint32`` (``N``) that is the number of additional blocks. Works just like ``Block`` above, except that after copying the block an additional (``N``) more blocks must be copied. Compression -------------- Individual files can be transmitted compressed if needed. Currently, only :rfc:`1950` ZLIB based deflate compression is supported, which is specified using the ``compression=zlib`` key when requesting a file. For example when sending files to the terminal emulator, when sending the file metadata the ``compression`` key can also be specified:: → action=file id=someid file_id=f1 name=/path/to/destination compression=zlib Similarly when receiving files from the terminal emulator, the final file command that the client sends to the terminal requesting the start of the transfer of data for the file can include the ``compression`` key:: → action=file id=someid file_id=f1 name=/some/path compression=zlib .. _bypass_auth: Bypassing explicit user authorization ------------------------------------------ In order to bypass the requirement of interactive user authentication, this protocol has the ability to use a pre-shared secret (password). When initiating a transfer session the client sends a hash of the password and the session id:: → action=send id=someid bypass=sha256:hash_value For example, suppose that the session id is ``mysession`` and the shared secret is ``mypassword``. Then the value of the ``bypass`` key above is ``sha256:SHA256("mysession" + ";" + "mypassword")``, which is:: → action=send id=mysession bypass=sha256:192bd215915eeaa8c2b2a4c0f8f851826497d12b30036d8b5b1b4fc4411caf2c The value of ``bypass`` is of the form ``hash_function_name : hash_value`` (without spaces). Currently, only the SHA256 hash function is supported. .. warning:: Hashing does not effectively hide the value of the password. So this functionality should only be used in secure/trusted contexts. While there exist hash functions harder to compute than SHA256, they are unsuitable as they will introduce a lot of latency to starting a session and in any case there is no mathematical proof that **any** hash function is not brute-forceable. Terminal implementations are free to use their own more advanced hashing schemes, with prefixes other than those starting with ``sha``, which are reserved. For instance, kitty uses a scheme based on public key encryption via :envvar:`KITTY_PUBLIC_KEY`. For details of this scheme, see the ``check_bypass()`` function in the kitty source code. Encoding of transfer commands as escape codes ------------------------------------------------ Transfer commands are encoded as ``OSC`` escape codes of the form:: 5113 ; key=value ; key=value ... Here ``OSC`` is the bytes ``0x1b 0x5d`` and ``ST`` is the bytes ``0x1b 0x5c``. Keys are words containing only the characters ``[a-zA-Z0-9_]`` and ``value`` is arbitrary data, whose encoding is dependent on the value of ``key``. Unknown keys **must** be ignored when decoding a command. The number ``5113`` is a constant and is unused by any known OSC codes. It is the numeralization of the word ``file``. .. table:: The keys and value types for this protocol :align: left ================= ======== ============== ======================================================================= Key Key name Value type Notes ================= ======== ============== ======================================================================= action ac enum send, file, data, end_data, receive, cancel, status, finish compression zip enum none, zlib file_type ft enum regular, directory, symlink, link transmission_type tt enum simple, rsync id id safe_string A unique-ish value, to avoid collisions file_id fid safe_string Must be unique per file in a session bypass pw safe_string hash of the bypass password and the session id quiet q integer 0 - verbose, 1 - only errors, 2 - totally silent mtime mod integer the modification time of file in nanoseconds since the UNIX epoch permissions prm integer the UNIX file permissions bits size sz integer size in bytes name n base64_string The path to a file status st base64_string Status messages parent pr safe_string The file id of the parent directory data d base64_bytes Binary data ================= ======== ============== ======================================================================= The ``Key name`` is the actual serialized name of the key sent in the escape code. So for example, ``permissions=123`` is serialized as ``prm=123``. This is done to reduce overhead. The value types are: enum One from a permitted set of values, for example:: ac=file safe_string A string consisting only of characters from the set ``[0-9a-zA-Z_:./@-]`` Note that the semi-colon is missing from this set. integer A base-10 number composed of the characters ``[0-9]`` with a possible leading ``-`` sign. When missing the value is zero. base64_string A base64 encoded UTF-8 string using the standard base64 encoding base64_bytes Binary data encoded using the standard base64 encoding An example of serializing an escape code is shown below:: action=send id=test name=somefile size=3 data=01 02 03 becomes:: 5113 ; ac=send ; id=test ; n=c29tZWZpbGU= ; sz=3 ; d=AQID Here ``c29tZWZpbGU`` is the base64 encoded form of somefile and ``AQID`` is the base64 encoded form of the bytes ``0x01 0x02 0x03``. The spaces in the encoded form are present for clarity and should be ignored. kitty-0.41.1/docs/glossary.rst0000664000175000017510000002320314773370543015634 0ustar nileshnilesh:orphan: Glossary ========= .. glossary:: os_window kitty has two kinds of windows. Operating System windows, referred to as :term:`OS Window `, and *kitty windows*. An OS Window consists of one or more kitty :term:`tabs `. Each tab in turn consists of one or more *kitty windows* organized in a :term:`layout`. tab A *tab* refers to a group of :term:`kitty windows `, organized in a :term:`layout`. Every :term:`OS Window ` contains one or more tabs. layout A *layout* is a system of organizing :term:`kitty windows ` in groups inside a tab. The layout automatically maintains the size and position of the windows, think of a layout as a tiling window manager for the terminal. See :doc:`layouts` for details. window kitty has two kinds of windows. Operating System windows, referred to as :term:`OS Window `, and *kitty windows*. An OS Window consists of one or more kitty :term:`tabs `. Each tab in turn consists of one or more *kitty windows* organized in a :term:`layout`. overlay An *overlay window* is a :term:`kitty window ` that is placed on top of an existing kitty window, entirely covering it. Overlays are used throughout kitty, for example, to display the :ref:`the scrollback buffer `, to display :doc:`hints `, for :doc:`unicode input ` etc. Normal overlays are meant for short duration popups and so are not considered the :italic:`active window` when determining the current working directory or getting input text for kittens, launch commands, etc. To create an overlay considered as a :italic:`main window` use the :code:`overlay-main` argument to :doc:`launch`. hyperlinks Terminals can have hyperlinks, just like the internet. In kitty you can :doc:`control exactly what happens ` when clicking on a hyperlink, based on the type of link and its URL. See also `Hyperlinks in terminal emulators `__. kittens Small, independent statically compiled command line programs that are designed to run inside kitty windows and provide it with lots of powerful and flexible features such as viewing images, connecting conveniently to remote computers, transferring files, inputting unicode characters, etc. They can also be written by users in Python and used to customize and extend kitty functionality, see :doc:`kittens_intro` for details. easing function A function that controls how an animation progresses over time. kitty support the `CSS syntax for easing functions `__. Commonly used easing functions are :code:`linear` for a constant rate animation and :code:`ease-in-out` for an animation that starts slow, becomes fast in the middle and ends slowly. These are used to control various animations in kitty, such as :opt:`cursor_blink_interval` and :opt:`visual_bell_duration`. .. _env_vars: Environment variables ------------------------ Variables that influence kitty behavior ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. envvar:: KITTY_CONFIG_DIRECTORY Controls where kitty looks for :file:`kitty.conf` and other configuration files. Defaults to :file:`~/.config/kitty`. For full details of the config directory lookup mechanism see, :option:`kitty --config`. .. envvar:: KITTY_CACHE_DIRECTORY Controls where kitty stores cache files. Defaults to :file:`~/.cache/kitty` or :file:`~/Library/Caches/kitty` on macOS. .. envvar:: KITTY_RUNTIME_DIRECTORY Controls where kitty stores runtime files like sockets. Defaults to the :code:`XDG_RUNTIME_DIR` environment variable if that is defined otherwise the run directory inside the kitty cache directory is used. .. envvar:: VISUAL The terminal based text editor (such as :program:`vi` or :program:`nano`) kitty uses, when, for instance, opening :file:`kitty.conf` in response to :sc:`edit_config_file`. .. envvar:: EDITOR Same as :envvar:`VISUAL`. Used if :envvar:`VISUAL` is not set. .. envvar:: SHELL Specifies the default shell kitty will run when :opt:`shell` is set to :code:`.`. .. envvar:: GLFW_IM_MODULE Set this to ``ibus`` to enable support for IME under X11. .. envvar:: KITTY_WAYLAND_DETECT_MODIFIERS When set to a non-empty value, kitty attempts to autodiscover XKB modifiers under Wayland. This is useful if using non-standard modifiers like hyper. It is possible for the autodiscovery to fail; the default Wayland XKB mappings are used in this case. See :pull:`3943` for details. .. envvar:: SSH_ASKPASS Specify the program for SSH to ask for passwords. When this is set, :doc:`ssh kitten ` will use this environment variable by default. See :opt:`askpass ` for details. .. envvar:: KITTY_CLONE_SOURCE_CODE Set this to some shell code that will be executed in the cloned window with :code:`eval` when :ref:`clone-in-kitty ` is used. .. envvar:: KITTY_CLONE_SOURCE_PATH Set this to the path of a file that will be sourced in the cloned window when :ref:`clone-in-kitty ` is used. .. envvar:: KITTY_DEVELOP_FROM Set this to the directory path of the kitty source code and its Python code will be loaded from there. Only works with official binary builds. .. envvar:: KITTY_RC_PASSWORD Set this to a pass phrase to use the ``kitten @`` remote control command with :opt:`remote_control_password`. Variables that kitty sets when running child programs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. envvar:: LANG This is only set on macOS. If the country and language from the macOS user settings form an invalid locale, it will be set to :code:`en_US.UTF-8`. .. envvar:: PATH kitty prepends itself to the PATH of its own environment to ensure the functions calling :program:`kitty` will work properly. .. envvar:: KITTY_WINDOW_ID An integer that is the id for the kitty :term:`window` the program is running in. Can be used with the :doc:`kitty remote control facility `. .. envvar:: KITTY_PID An integer that is the process id for the kitty process in which the program is running. Allows programs to tell kitty to reload its config by sending it the SIGUSR1 signal. .. envvar:: KITTY_PUBLIC_KEY A public key that programs can use to communicate securely with kitty using the remote control protocol. The format is: :code:`protocol:key data`. .. envvar:: WINDOWID The id for the :term:`OS Window ` the program is running in. Only available on platforms that have ids for their windows, such as X11 and macOS. .. envvar:: TERM The name of the terminal, defaults to ``xterm-kitty``. See :opt:`term`. .. envvar:: TERMINFO Path to a directory containing the kitty terminfo database. Or the terminfo database itself encoded in base64. See :opt:`terminfo_type`. .. envvar:: KITTY_INSTALLATION_DIR Path to the kitty installation directory. .. envvar:: COLORTERM Set to the value ``truecolor`` to indicate that kitty supports 16 million colors. .. envvar:: KITTY_LISTEN_ON Set when the :doc:`remote control ` facility is enabled and the a socket is used for control via :option:`kitty --listen-on` or :opt:`listen_on`. Contains the path to the socket. Avoid the need to use :option:`kitten @ --to` when issuing remote control commands. Can also be a file descriptor of the form fd:num instead of a socket address, in which case, remote control communication should proceed over the specified file descriptor. .. envvar:: KITTY_PIPE_DATA Set to data describing the layout of the screen when running child programs using :option:`launch --stdin-source` with the contents of the screen/scrollback piped to them. .. envvar:: KITTY_CHILD_CMDLINE Set to the command line of the child process running in the kitty window when calling the notification callback program on terminal bell, see :opt:`command_on_bell`. .. envvar:: KITTY_COMMON_OPTS Set with the values of some common kitty options when running kittens, so kittens can use them without needing to load :file:`kitty.conf`. .. envvar:: KITTY_SHELL_INTEGRATION Set when enabling :ref:`shell_integration`. It is automatically removed by the shell integration scripts. .. envvar:: ZDOTDIR Set when enabling :ref:`shell_integration` with :program:`zsh`, allowing :program:`zsh` to automatically load the integration script. .. envvar:: XDG_DATA_DIRS Set when enabling :ref:`shell_integration` with :program:`fish`, allowing :program:`fish` to automatically load the integration script. .. envvar:: ENV Set when enabling :ref:`shell_integration` with :program:`bash`, allowing :program:`bash` to automatically load the integration script. .. envvar:: KITTY_OS Set when using the include directive in kitty.conf. Can take values: ``linux``, ``macos``, ``bsd``. .. envvar:: KITTY_HOLD Set to ``1`` when kitty is running a shell because of the ``--hold`` flag. Can be used to specialize shell behavior in the shell rc files as desired. .. envvar:: KITTY_SIMD Set it to ``128`` to use 128 bit vector registers, ``256`` to use 256 bit vector registers or any other value to prevent kitty from using SIMD CPU vector instructions. Warning, this overrides CPU capability detection so will cause kitty to crash with SIGILL if your CPU does not support the necessary SIMD extensions. kitty-0.41.1/docs/graphics-protocol.rst0000664000175000017510000015731414773370543017443 0ustar nileshnileshTerminal graphics protocol ================================= The goal of this specification is to create a flexible and performant protocol that allows the program running in the terminal, hereafter called the *client*, to render arbitrary pixel (raster) graphics to the screen of the terminal emulator. The major design goals are: * Should not require terminal emulators to understand image formats. * Should allow specifying graphics to be drawn at individual pixel positions. * The graphics should integrate with the text, in particular it should be possible to draw graphics below as well as above the text, with alpha blending. The graphics should also scroll with the text, automatically. * Should use optimizations when the client is running on the same computer as the terminal emulator. For some discussion regarding the design choices, see :iss:`33`. To see a quick demo, inside a |kitty| terminal run:: kitten icat path/to/some/image.png You can also see a screenshot with more sophisticated features such as alpha-blending and text over graphics. .. image:: https://user-images.githubusercontent.com/1308621/31647475-1188ab66-b326-11e7-8d26-24b937f1c3e8.png :alt: Demo of graphics rendering in kitty :align: center Some applications that use the kitty graphics protocol: * `awrit `_ - Chromium-based web browser rendered in Kitty with mouse and keyboard support * `broot `_ - a terminal file explorer and manager, with preview of images, SVG, PDF, etc. * `chafa `_ - a terminal image viewer * :doc:`kitty-diff ` - a side-by-side terminal diff program with support for images * `fzf `_ - A command line fuzzy finder * `mpv `_ - A video player that can play videos in the terminal * `neofetch `_ - A command line system information tool * `pixcat `_ - a third party CLI and python library that wraps the graphics protocol * `ranger `_ - a terminal file manager, with image previews * `termpdf.py `_ - a terminal PDF/DJVU/CBR viewer * `timg `_ - a terminal image and video viewer * `tpix `_ - a statically compiled binary that can be used to display images and easily installed on remote servers without root access * `twitch-tui `_ - Twitch chat in the terminal * `vat `_ - a terminal image viewer for vector graphics, including Android Vector Drawables * `viu `_ - a terminal image viewer * `Yazi `_ - Blazing fast terminal file manager written in Rust, based on async I/O Libraries: * `ctx.graphics `_ - Library for drawing graphics * `notcurses `_ - C library for terminal graphics with bindings for C++, Rust and Python * `rasterm `_ - Go library to display images in the terminal * `hologram.nvim `_ - view images inside nvim * `image.nvim `_ - Bringing images to neovim * `image_preview.nvim `_ - Image preview for neovim * `kui.nvim `_ - Build sophisticated UIs inside neovim using the kitty graphics protocol * `term-image `_ - A Python library, CLI and TUI to display and browse images in the terminal * `glkitty `_ - C library to draw OpenGL shaders in the terminal with a glgears demo Other terminals that have implemented the graphics protocol: * `Ghostty `_ * `Konsole `_ * `st (with a patch) `_ * `Warp `_ * `wayst `_ * `WezTerm `_ Getting the window size ------------------------- In order to know what size of images to display and how to position them, the client must be able to get the window size in pixels and the number of cells per row and column. The cell width is then simply the window size divided by the number of rows. This can be done by using the ``TIOCGWINSZ`` ioctl. Some code to demonstrate its use .. tab:: C .. code-block:: c #include #include int main(int argc, char **argv) { struct winsize sz; ioctl(0, TIOCGWINSZ, &sz); printf( "number of rows: %i, number of columns: %i, screen width: %i, screen height: %i\n", sz.ws_row, sz.ws_col, sz.ws_xpixel, sz.ws_ypixel); return 0; } .. tab:: Python .. code-block:: python import array, fcntl, sys, termios buf = array.array('H', [0, 0, 0, 0]) fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, buf) print(( 'number of rows: {} number of columns: {}' 'screen width: {} screen height: {}').format(*buf)) .. tab:: Go .. code-block:: go package main import ( "fmt" "os" "golang.org/x/sys/unix" ) func main() { var err error var f *os.File if f, err = os.OpenFile("/dev/tty", unix.O_NOCTTY|unix.O_CLOEXEC|unix.O_NDELAY|unix.O_RDWR, 0666); err == nil { var sz *unix.Winsize if sz, err = unix.IoctlGetWinsize(int(f.Fd()), unix.TIOCGWINSZ); err == nil { fmt.Printf("rows: %v columns: %v width: %v height %v\n", sz.Row, sz.Col, sz.Xpixel, sz.Ypixel) return } } fmt.Fprintln(os.Stderr, err) os.Exit(1) } .. tab:: Bash .. code-block:: sh #!/bin/bash # This uses the kitten standalone binary from kitty to get the pixel sizes # since we can't do IOCTLs directly. Fortunately, kitten is a static exe # pre-built for every Unix like OS under the sun. builtin read -r rows cols < <(command stty size) IFS=x builtin read -r width height < <(command kitten icat --print-window-size); builtin unset IFS builtin echo "number of rows: $rows number of columns: $cols screen width: $width screen height: $height" Note that some terminals return ``0`` for the width and height values. Such terminals should be modified to return the correct values. Examples of terminals that return correct values: ``kitty, xterm`` You can also use the *CSI t* escape code to get the screen size. Send ``[14t`` to ``STDOUT`` and kitty will reply on ``STDIN`` with ``[4;;t`` where ``height`` and ``width`` are the window size in pixels. This escape code is supported in many terminals, not just kitty. A minimal example ------------------ Some minimal code to display PNG images in kitty, using the most basic features of the graphics protocol: .. tab:: Bash .. code-block:: sh #!/bin/bash transmit_png() { data=$(base64 "$1") data="${data//[[:space:]]}" builtin local pos=0 builtin local chunk_size=4096 while [ $pos -lt ${#data} ]; do builtin printf "\e_G" [ $pos = "0" ] && printf "a=T,f=100," builtin local chunk="${data:$pos:$chunk_size}" pos=$(($pos+$chunk_size)) [ $pos -lt ${#data} ] && builtin printf "m=1" [ ${#chunk} -gt 0 ] && builtin printf ";%s" "${chunk}" builtin printf "\e\\" done } transmit_png "$1" .. tab:: Python .. code-block:: python #!/usr/bin/python import sys from base64 import standard_b64encode def serialize_gr_command(**cmd): payload = cmd.pop('payload', None) cmd = ','.join(f'{k}={v}' for k, v in cmd.items()) ans = [] w = ans.append w(b'\033_G'), w(cmd.encode('ascii')) if payload: w(b';') w(payload) w(b'\033\\') return b''.join(ans) def write_chunked(**cmd): data = standard_b64encode(cmd.pop('data')) while data: chunk, data = data[:4096], data[4096:] m = 1 if data else 0 sys.stdout.buffer.write(serialize_gr_command(payload=chunk, m=m, **cmd)) sys.stdout.flush() cmd.clear() with open(sys.argv[-1], 'rb') as f: write_chunked(a='T', f=100, data=f.read()) Save this script as :file:`send-png`, then you can use it to display any PNG file in kitty as:: chmod +x send-png ./send-png file.png The graphics escape code --------------------------- All graphics escape codes are of the form:: _G;\ This is a so-called *Application Programming Command (APC)*. Most terminal emulators ignore APC codes, making it safe to use. The control data is a comma-separated list of ``key=value`` pairs. The payload is arbitrary binary data, :rfc:`base64 <4648>` encoded to prevent interoperation problems with legacy terminals that get confused by control codes within an APC code. The meaning of the payload is interpreted based on the control data. The first step is to transmit the actual image data. .. _transferring_pixel_data: Transferring pixel data -------------------------- The first consideration when transferring data between the client and the terminal emulator is the format in which to do so. Since there is a vast and growing number of image formats in existence, it does not make sense to have every terminal emulator implement support for them. Instead, the client should send simple pixel data to the terminal emulator. The obvious downside to this is performance, especially when the client is running on a remote machine. Techniques for remedying this limitation are discussed later. The terminal emulator must understand pixel data in three formats, 24-bit RGB, 32-bit RGBA and PNG. This is specified using the ``f`` key in the control data. ``f=32`` (which is the default) indicates 32-bit RGBA data and ``f=24`` indicates 24-bit RGB data and ``f=100`` indicates PNG data. The PNG format is supported both for convenience, and as a compact way of transmitting paletted images. RGB and RGBA data ~~~~~~~~~~~~~~~~~~~ In these formats the pixel data is stored directly as 3 or 4 bytes per pixel, respectively. The colors in the data **must** be in the *sRGB color space*. When specifying images in this format, the image dimensions **must** be sent in the control data. For example:: _Gf=24,s=10,v=20;\ Here the width and height are specified using the ``s`` and ``v`` keys respectively. Since ``f=24`` there are three bytes per pixel and therefore the pixel data must be ``3 * 10 * 20 = 600`` bytes. PNG data ~~~~~~~~~~~~~~~ In this format any PNG image can be transmitted directly. For example:: _Gf=100;\ The PNG format is specified using the ``f=100`` key. The width and height of the image will be read from the PNG data itself. Note that if you use both PNG and compression, then you must provide the ``S`` key with the size of the PNG data. Compression ~~~~~~~~~~~~~ The client can send compressed image data to the terminal emulator, by specifying the ``o`` key. Currently, only :rfc:`1950` ZLIB based deflate compression is supported, which is specified using ``o=z``. For example:: _Gf=24,s=10,v=20,o=z;\ This is the same as the example from the RGB data section, except that the payload is now compressed using deflate (this occurs prior to :rfc:`base64 <4648>` encoding). The terminal emulator will decompress it before rendering. You can specify compression for any format. The terminal emulator will decompress before interpreting the pixel data. The transmission medium ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The transmission medium is specified using the ``t`` key. The ``t`` key defaults to ``d`` and can take the values: ================== ============ Value of `t` Meaning ================== ============ ``d`` Direct (the data is transmitted within the escape code itself) ``f`` A simple file (regular files only, not named pipes, device files, etc.) ``t`` A temporary file, the terminal emulator will delete the file after reading the pixel data. For security reasons the terminal emulator should only delete the file if it is in a known temporary directory, such as :file:`/tmp`, :file:`/dev/shm`, :file:`TMPDIR env var if present` and any platform specific temporary directories and the file has the string :code:`tty-graphics-protocol` in its full file path. ``s`` A *shared memory object*, which on POSIX systems is a `POSIX shared memory object `_ and on Windows is a `Named shared memory object `_. The terminal emulator must read the data from the memory object and then unlink and close it on POSIX and just close it on Windows. ================== ============ When opening files, the terminal emulator must follow symlinks. In case of symlink loops or too many symlinks, it should fail and respond with an error, similar to reporting any other kind of I/O error. Since the file paths come from potentially untrusted sources, terminal emulators **must** refuse to read any device/socket/etc. special files. Only regular files are allowed. Additionally, terminal emulators may refuse to read files in *sensitive* parts of the filesystem, such as :file:`/proc`, :file:`/sys`, :file:`/dev/`, etc. Local client ^^^^^^^^^^^^^^ First let us consider the local client techniques (files and shared memory). Some examples:: _Gf=100,t=f;\ Here we tell the terminal emulator to read PNG data from the specified file of the specified size:: _Gs=10,v=2,t=s,o=z;\ Here we tell the terminal emulator to read compressed image data from the specified shared memory object. The client can also specify a size and offset to tell the terminal emulator to only read a part of the specified file. The is done using the ``S`` and ``O`` keys respectively. For example:: _Gs=10,v=2,t=s,S=80,O=10;\ This tells the terminal emulator to read ``80`` bytes starting from the offset ``10`` inside the specified shared memory buffer. Remote client ^^^^^^^^^^^^^^^^ Remote clients, those that are unable to use the filesystem/shared memory to transmit data, must send the pixel data directly using escape codes. Since escape codes are of limited maximum length, the data will need to be chunked up for transfer. This is done using the ``m`` key. The pixel data must first be :rfc:`base64 <4648>` encoded then chunked up into chunks no larger than ``4096`` bytes. All chunks, except the last, must have a size that is a multiple of 4. The client then sends the graphics escape code as usual, with the addition of an ``m`` key that must have the value ``1`` for all but the last chunk, where it must be ``0``. For example, if the data is split into three chunks, the client would send the following sequence of escape codes to the terminal emulator:: _Gs=100,v=30,m=1;\ _Gm=1;\ _Gm=0;\ Note that only the first escape code needs to have the full set of control codes such as width, height, format, etc. Subsequent chunks **must** have only the ``m`` and optionally ``q`` keys. When sending animation frame data, subsequent chunks **must** also specify the ``a=f`` key. The client **must** finish sending all chunks for a single image before sending any other graphics related escape codes. Note that the cursor position used to display the image **must** be the position when the final chunk is received. Finally, terminals must not display anything, until the entire sequence is received and validated. Querying support and available transmission mediums ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Since a client has no a-priori knowledge of whether it shares a filesystem/shared memory with the terminal emulator, it can send an id with the control data, using the ``i`` key (which can be an arbitrary positive integer up to 4294967295, it must not be zero). If it does so, the terminal emulator will reply after trying to load the image, saying whether loading was successful or not. For example:: _Gi=31,s=10,v=2,t=s;\ to which the terminal emulator will reply (after trying to load the data):: _Gi=31;error message or OK\ Here the ``i`` value will be the same as was sent by the client in the original request. The message data will be a ASCII encoded string containing only printable characters and spaces. The string will be ``OK`` if reading the pixel data succeeded or an error message. Sometimes, using an id is not appropriate, for example, if you do not want to replace a previously sent image with the same id, or if you are sending a dummy image and do not want it stored by the terminal emulator. In that case, you can use the *query action*, set ``a=q``. Then the terminal emulator will try to load the image and respond with either OK or an error, as above, but it will not replace an existing image with the same id, nor will it store the image. We intend that any terminal emulator that wishes to support it can do so. To check if a terminal emulator supports the graphics protocol the best way is to send the above *query action* followed by a request for the `primary device attributes `_. If you get back an answer for the device attributes without getting back an answer for the *query action* the terminal emulator does not support the graphics protocol. This means that terminal emulators that support the graphics protocol, **must** reply to *query actions* immediately without processing other input. Most terminal emulators handle input in a FIFO manner, anyway. So for example, you could send:: _Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\[c If you get back a response to the graphics query, the terminal emulator supports the protocol, if you get back a response to the device attributes query without a response to the graphics query, it does not. Display images on screen ----------------------------- Every transmitted image can be displayed an arbitrary number of times on the screen, in different locations, using different parts of the source image, as needed. Each such display of an image is called a *placement*. You can either simultaneously transmit and display an image using the action ``a=T``, or first transmit the image with a id, such as ``i=10`` and then display it with ``a=p,i=10`` which will display the previously transmitted image at the current cursor position. When specifying an image id, the terminal emulator will reply to the placement request with an acknowledgement code, which will be either:: _Gi=;OK\ when the image referred to by id was found, or:: _Gi=;ENOENT:\ when the image with the specified id was not found. This is similar to the scheme described above for querying available transmission media, except that here we are querying if the image with the specified id is available or needs to be re-transmitted. Since there can be many placements per image, you can also give placements an id. To do so add the ``p`` key with a number between ``1`` and ``4294967295``. When you specify a placement id, it will be added to the acknowledgement code above. Every placement is uniquely identified by the pair of the ``image id`` and the ``placement id``. If you specify a placement id for an image that does not have an id (i.e. has id=0), it will be ignored. In particular this means there can exist multiple images with ``image id=0, placement id=0``. Not specifying a placement id or using ``p=0`` for multiple put commands (``a=p``) with the same non-zero image id results in multiple placements the image. An example response:: _Gi=,p=;OK\ If you send two placements with the same ``image id`` and ``placement id`` the second one will replace the first. This can be used to resize or move placements around the screen, without flicker. .. versionadded:: 0.19.3 Support for specifying placement ids (see :doc:`kittens/query_terminal` to query kitty version) Controlling displayed image layout ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The image is rendered at the current cursor position, from the upper left corner of the current cell. You can also specify extra ``X=3`` and ``Y=4`` pixel offsets to display from a different origin within the cell. Note that the offsets must be smaller than the size of the cell. By default, the entire image will be displayed (images wider than the available width will be truncated on the right edge). You can choose a source rectangle (in pixels) as the part of the image to display. This is done with the keys: ``x, y, w, h`` which specify the top-left corner, width and height of the source rectangle. The displayed area is the intersection of the specified rectangle with the source image rectangle. You can also ask the terminal emulator to display the image in a specified rectangle (num of columns / num of lines), using the control codes ``c,r``. ``c`` is the number of columns and `r` the number of rows. The image will be scaled (enlarged/shrunk) as needed to fit the specified area. Note that if you specify a start cell offset via the ``X,Y`` keys, it is not added to the number of rows/columns. If only one of either ``r`` or ``c`` is specified, the other one is computed based on the source image aspect ratio, so that the image is displayed without distortion. Finally, you can specify the image *z-index*, i.e. the vertical stacking order. Images placed in the same location with different z-index values will be blended if they are semi-transparent. You can specify z-index values using the ``z`` key. Negative z-index values mean that the images will be drawn under the text. This allows rendering of text on top of images. Negative z-index values below INT32_MIN/2 (-1,073,741,824) will be drawn under cells with non-default background colors. If two images with the same z-index overlap then the image with the lower id is considered to have the lower z-index. If the images have the same z-index and the same id, then the behavior is undefined. .. note:: After placing an image on the screen the cursor must be moved to the right by the number of cols in the image placement rectangle and down by the number of rows in the image placement rectangle. If either of these cause the cursor to leave either the screen or the scroll area, the exact positioning of the cursor is undefined, and up to implementations. The client can ask the terminal emulator to not move the cursor at all by specifying ``C=1`` in the command, which sets the cursor movement policy to no movement for placing the current image. .. versionadded:: 0.20.0 Support for the C=1 cursor movement policy .. _graphics_unicode_placeholders: Unicode placeholders ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 0.28.0 Support for image display via Unicode placeholders You can also use a special Unicode character ``U+10EEEE`` as a placeholder for an image. This approach is less flexible, but it allows using images inside any host application that supports Unicode, foreground colors (tmux, vim, weechat, etc.), and a way to pass escape codes through to the underlying terminal. The central idea is that we use a single *Private Use* Unicode character as a *placeholder* to indicate to the terminal that an image is supposed to be displayed at that cell. Since this character is just normal text, Unicode aware application will move it around as needed when they redraw their screens, thereby automatically moving the displayed image as well, even though they know nothing about the graphics protocol. So an image is first created using the normal graphics protocol escape codes (albeit in quiet mode (``q=2``) so that there are no responses from the terminal that could confuse the host application). Then, the actual image is displayed by getting the host application to emit normal text consisting of ``U+10EEEE`` and various diacritics (Unicode combining characters) and colors. To use it, first create an image as you would normally with the graphics protocol with (``q=2``), but do not create a placement for it, that is, do not display it. Then, create a *virtual image placement* by specifying ``U=1`` and the desired number of lines and columns:: _Ga=p,U=1,i=,c=,r=\ The creation of the placement need not be a separate escape code, it can be combined with ``a=T`` to both transmit and create the virtual placement with a single code. The image will eventually be fit to the specified rectangle, its aspect ratio preserved. Finally, the image can be actually displayed by using the placeholder character, encoding the image ID in its foreground color. The row and column values are specified with diacritics listed in :download:`rowcolumn-diacritics.txt <../gen/rowcolumn-diacritics.txt>`. For example, here is how you can print a ``2x2`` placeholder for image ID ``42``: .. code-block:: sh printf "\e[38;5;42m\U10EEEE\U0305\U0305\U10EEEE\U0305\U030D\e[39m\n" printf "\e[38;5;42m\U10EEEE\U030D\U0305\U10EEEE\U030D\U030D\e[39m\n" Here, ``U+305`` is the diacritic corresponding to the number ``0`` and ``U+30D`` corresponds to ``1``. So these two commands create the following ``2x2`` placeholder: ========== ========== (0, 0) (0, 1) (1, 0) (1, 1) ========== ========== This will cause the image with ID ``42`` to be displayed in a ``2x2`` grid. Ideally, you would print out as many cells as the number of rows and columns specified when creating the virtual placement, but in case of a mismatch only part of the image will be displayed. By using only the foreground color for image ID you are limited to either 8-bit IDs in 256 color mode or 24-bit IDs in true color mode. Since IDs are in a global namespace there can easily be collisions. If you need more bits for the image ID, you can specify the most significant byte via a third diacritic. For example, this is the placeholder for the image ID ``33554474 = 42 + (2 << 24)``: .. code-block:: sh printf "\e[38;5;42m\U10EEEE\U0305\U0305\U030E\U10EEEE\U0305\U030D\U030E\n" printf "\e[38;5;42m\U10EEEE\U030D\U0305\U030E\U10EEEE\U030D\U030D\U030E\n" Here, ``U+30E`` is the diacritic corresponding to the number ``2``. You can also specify a placement ID using the underline color (if it's omitted or zero, the terminal may choose any virtual placement of the given image). The background color is interpreted as the background color, visible if the image is transparent. Other text attributes are reserved for future use. Row, column and most significant byte diacritics may also be omitted, in which case the placeholder cell will inherit the missing values from the placeholder cell to the left, following the algorithm: - If no diacritics are present, and the previous placeholder cell has the same foreground and underline colors, then the row of the current cell will be the row of the cell to the left, the column will be the column of the cell to the left plus one, and the most significant image ID byte will be the most significant image ID byte of the cell to the left. - If only the row diacritic is present, and the previous placeholder cell has the same row and the same foreground and underline colors, then the column of the current cell will be the column of the cell to the left plus one, and the most significant image ID byte will be the most significant image ID byte of the cell to the left. - If only the row and column diacritics are present, and the previous placeholder cell has the same row, the same foreground and underline colors, and its column is one less than the current column, then the most significant image ID byte of the current cell will be the most significant image ID byte of the cell to the left. These rules are applied left-to-right, which allows specifying only row diacritics of the first column, i.e. here is a 2 rows by 3 columns placeholder: .. code-block:: sh printf "\e[38;5;42m\U10EEEE\U0305\U10EEEE\U10EEEE\n" printf "\e[38;5;42m\U10EEEE\U030D\U10EEEE\U10EEEE\n" This will not work for horizontal scrolling and overlapping images since the two given rules will fail to guess the missing information. In such cases, the terminal may apply other heuristics (but it doesn't have to). It is important to distinguish between virtual image placements and real images displayed on top of Unicode placeholders. Virtual placements are invisible and only play the role of prototypes for real images. Virtual placements can be deleted by a deletion command only when the `d` key is equal to ``i``, ``I``, ``r``, ``R``, ``n`` or ``N``. The key values ``a``, ``c``, ``p``, ``q``, ``x``, ``y``, ``z`` and their capital variants never affect virtual placements because they do not have a physical location on the screen. Real images displayed on top of Unicode placeholders are not considered placements from the protocol perspective. They cannot be manipulated using graphics commands, instead they should be moved, deleted, or modified by manipulating the underlying Unicode placeholder as normal text. .. _relative_image_placement: Relative placements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 0.31.0 Support for positioning images relative to other images You can specify that a placement is positioned relative to another placement. This is particularly useful in combination with :ref:`graphics_unicode_placeholders` above. It can be used to specify a single transparent pixel image using a Unicode placeholder, which moves around naturally with the text, the real image(s) can base their position relative to the placeholder. To specify that a placement should be relative to another, use the ``P=,Q=`` keys, when creating the relative placement. For example:: _Ga=p,i=,p=,P=,Q=\ This will create a *relative placement* that refers to the *parent placement* specified by the ``P`` and ``Q`` keys. When the parent placement moves, the relative placement moves along with it. The relative placement can be offset from the parent's location by a specified number of cells, using the ``H`` and ``V`` keys for horizontal and vertical displacement. Positive values move right and down. Negative values move left and up. The origin is the top left cell of the parent placement. The lifetime of a relative placement is tied to the lifetime of its parent. If its parent is deleted, it is deleted as well. If the image that the relative placement is a placement of, has no more placements, the image is deleted as well. Thus, a parent and its relative placements form a *group* that is managed together. A relative placement can refer to another relative placement as its parent. Thus the relative placements can form a chain. It is implementation dependent how long a chain of such placements is allowed, but implementation must allow a chain of length at least 8. If the implementation max depth is exceeded, the terminal must respond with the ``ETOODEEP`` error code. Virtual placements created for Unicode placeholder based images cannot also be relative placements. However, a relative placement can refer to a virtual placement as its parent. When a virtual placement is the parent, its position is derived from all the actual Unicode placeholder images that refer to it. The x position is the minimum of all the placeholder x positions and the y position is the minimum of all the placeholder y positions. If a client attempts to make a virtual placement relative the terminal must respond with the ``EINVAL`` error code. Terminals are required to reject the creation of a relative placement that would create a cycle, such as when A is relative to B and B is relative to C and C is relative to A. In such cases, the terminal must respond with the ``ECYCLE`` error code. If a client attempts to create a reference to a placement that does not exist the terminal must respond with the ``ENOPARENT`` error code. .. note:: Since a relative placement gets its position specified based on another placement, instead of the cursor, the cursor must not move after a relative position, regardless of the value of the ``C`` key to control cursor movement. Deleting images --------------------- Images can be deleted by using the delete action ``a=d``. If specified without any other keys, it will delete all images visible on screen. To delete specific images, use the `d` key as described in the table below. Note that each value of d has both a lowercase and an uppercase variant. The lowercase variant only deletes the images without necessarily freeing up the stored image data, so that the images can be re-displayed without needing to resend the data. The uppercase variants will delete the image data as well, provided that the image is not referenced elsewhere, such as in the scrollback buffer. The values of the ``x`` and ``y`` keys are the same as cursor positions (i.e. ``x=1, y=1`` is the top left cell). ================= ============ Value of ``d`` Meaning ================= ============ ``a`` or ``A`` Delete all placements visible on screen ``i`` or ``I`` Delete all images with the specified id, specified using the ``i`` key. If you specify a ``p`` key for the placement id as well, then only the placement with the specified image id and placement id will be deleted. ``n`` or ``N`` Delete newest image with the specified number, specified using the ``I`` key. If you specify a ``p`` key for the placement id as well, then only the placement with the specified number and placement id will be deleted. ``c`` or ``C`` Delete all placements that intersect with the current cursor position. ``f`` or ``F`` Delete animation frames. ``p`` or ``P`` Delete all placements that intersect a specific cell, the cell is specified using the ``x`` and ``y`` keys ``q`` or ``Q`` Delete all placements that intersect a specific cell having a specific z-index. The cell and z-index is specified using the ``x``, ``y`` and ``z`` keys. ``r`` or ``R`` Delete all images whose id is greater than or equal to the value of the ``x`` key and less than or equal to the value of the ``y`` (added in kitty version 0.33.0). ``x`` or ``X`` Delete all placements that intersect the specified column, specified using the ``x`` key. ``y`` or ``Y`` Delete all placements that intersect the specified row, specified using the ``y`` key. ``z`` or ``Z`` Delete all placements that have the specified z-index, specified using the ``z`` key. ================= ============ Note when all placements for an image have been deleted, the image is also deleted, if the capital letter form above is specified. Also, when the terminal is running out of quota space for new images, existing images without placements will be preferentially deleted. If an image is being loaded in chunks and the upload is not complete when any delete command is received, the partial upload must be aborted. Some examples:: _Ga=d\ # delete all visible placements _Ga=d,d=i,i=10\ # delete the image with id=10, without freeing data _Ga=d,d=i,i=10,p=7\ # delete the image with id=10 and placement id=7, without freeing data _Ga=d,d=Z,z=-1\ # delete the placements with z-index -1, also freeing up image data _Ga=d,d=p,x=3,y=4\ # delete all placements that intersect the cell at (3, 4), without freeing data Suppressing responses from the terminal ------------------------------------------- If you are using the graphics protocol from a limited client, such as a shell script, it might be useful to avoid having to process responses from the terminal. For this, you can use the ``q`` key. Set it to ``1`` to suppress ``OK`` responses and to ``2`` to suppress failure responses. .. versionadded:: 0.19.3 The ability to suppress responses (see :doc:`kittens/query_terminal` to query kitty version) Requesting image ids from the terminal ------------------------------------------- If you are writing a program that is going to share the screen with other programs and you still want to use image ids, it is not possible to know what image ids are free to use. In this case, instead of using the ``i`` key to specify an image id use the ``I`` key to specify an image number instead. These numbers are not unique. When creating a new image, even if an existing image has the same number a new one is created. And the terminal will reply with the id of the newly created image. For example, when creating an image with ``I=13``, the terminal will send the response:: _Gi=99,I=13;OK\ Here, the value of ``i`` is the id for the newly created image and the value of ``I`` is the same as was sent in the creation command. All future commands that refer to images using the image number, such as creating placements or deleting images, will act on only the newest image with that number. This allows the client program to send a bunch of commands dealing with an image by image number without waiting for a response from the terminal with the image id. Once such a response is received, the client program should use the ``i`` key with the image id for all future communication. .. note:: Specifying both ``i`` and ``I`` keys in any command is an error. The terminal must reply with an EINVAL error message, unless silenced. .. versionadded:: 0.19.3 The ability to use image numbers (see :doc:`kittens/query_terminal` to query kitty version) .. _animation_protocol: Animation ------------------------------------------- .. versionadded:: 0.20.0 Animation support (see :doc:`kittens/query_terminal` to query kitty version) When designing support for animation, the two main considerations were: #. There should be a way for both client and terminal driven animations. Since there is unknown and variable latency between client and terminal, especially over SSH, client driven animations are not sufficient. #. Animations often consist of small changes from one frame to the next, the protocol should thus allow transmitting these deltas for efficiency and performance reasons. Animation support is added to the protocol by adding two new modes for the ``a`` (action) key. A ``f`` mode for transmitting frame data and an ``a`` mode for controlling the animation of an image. Animation proceeds in two steps, first a normal image is created as described earlier. Then animation frames are added to the image to make it into an animation. Since every animation is associated with a single image, all animation escape codes must specify either the ``i`` or ``I`` keys to identify the image being operated on. Transferring animation frame data ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Transferring animation frame data is very similar to :ref:`transferring_pixel_data` above. The main difference is that the image the frame belongs to must be specified and it is possible to transmit data for only part of a frame, declaring the rest of the frame to be filled in by data from a previous frame, or left blank. To transfer frame data the ``a=f`` key must be used in all escape codes. First, to transfer a simple frame that has data for the full image area, the escape codes used are exactly the same as for transferring image data, with the addition of: ``a=f,i=`` or ``a=f,I=``. If the frame has data for only a part of the image, you can specify the rectangle for it using the ``x, y, s, v`` keys, for example:: x=10,y=5,s=100,v=200 # A 100x200 rectangle with its top left corner at (10, 5) Frames are created by composing the transmitted data onto a background canvas. This canvas can be either a single color, or the pixels from a previous frame. The composition can be of two types, either a simple replacement (``X=1``) key or a full alpha blend (the default). To use a background color for the canvas, specify the ``Y`` key as a 32-bit RGBA color. For example:: Y=4278190335 # 0xff0000ff opaque red Y=16711816 # 0x00ff0088 translucent green (alpha=0.53) The default background color when none is specified is ``0`` i.e. a black, transparent pixel. To use the data from a previous frame, specify the ``c`` key which is a 1-based frame number. Thus ``c=1`` refers to the root frame (the base image data), ``c=2`` refers to the second frame and so on. If the frame is composed of multiple rectangular blocks, these can be expressed by using the ``r`` key. When specifying the ``r`` key the data for an existing frame is edited. The same composition operation as above happens, but now the background canvas is the existing frame itself. ``r`` is a 1-based index, so ``r=1`` is the root frame (base image data), ``r=2`` is the second frame and so on. Finally, while transferring frame data, the frame *gap* can also be specified using the ``z`` key. The gap is the number of milliseconds to wait before displaying the next frame when the animation is running. A value of ``z=0`` is ignored, ``z=positive number`` sets the gap to the specified number of milliseconds and ``z=negative number`` creates a *gapless* frame. Gapless frames are not displayed to the user since they are instantly skipped over, however they can be useful as the base data for subsequent frames. For example, for an animation where the background remains the same and a small object or two move. Controlling animations ~~~~~~~~~~~~~~~~~~~~~~~~~~ Clients can control animations by using the ``a=a`` key in the escape code sent to the terminal. The simplest is client driven animations, where the client transmits the frame data and then also instructs the terminal to make a particular frame the current frame. To change the current frame, use the ``c`` key:: _Ga=a,i=3,c=7\ This will make the seventh frame in the image with id ``3`` the current frame. However, client driven animations can be sub-optimal, since the latency between the client and terminal is unknown and variable especially over the network. Also they require the client to remain running for the lifetime of the animation, which is not desirable for cat like utilities. Terminal driven animations are achieved by the client specifying *gaps* (time in milliseconds) between frames and instructing the terminal to stop or start the animation. The animation state is controlled by the ``s`` key. ``s=1`` stops the animation. ``s=2`` runs the animation, but in *loading* mode, in this mode when reaching the last frame, instead of looping, the terminal will wait for the arrival of more frames. ``s=3`` runs the animation normally, after the last frame, the terminal loops back to the first frame. The number of loops can be controlled by the ``v`` key. ``v=0`` is ignored, ``v=1`` is loop infinitely, and any other positive number is loop ``number - 1`` times. Note that stopping the animation resets the loop counter. Finally, the *gap* for frames can be set using the ``z`` key. This can be specified either when the frame is created as part of the transmit escape code or separately using the animation control escape code. The *gap* is the time in milliseconds to wait before displaying the next frame in the animation. For example:: _Ga=a,i=7,r=3,z=48\ This sets the gap for the third frame of the image with id ``7`` to ``48`` milliseconds. Note that *gapless* frames are not displayed to the user since the next frame comes immediately, however they can be useful to store base data for subsequent frames, such as in an animation with an object moving against a static background. In particular, the first frame or *root frame* is created with the base image data and has no gap, so its gap must be set using this control code. Composing animation frames ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 0.22.0 Support for frame composition Clients can *compose* animation frames, this means that they can compose pixels in rectangular regions from one frame onto another frame. This allows for fast and low band-width modification of frames. To achieve this use the ``a=c`` key. The source frame is specified with ``r=frame number`` and the destination frame as ``c=frame number``. The size of the rectangle is specified as ``w=width,h=height`` pixels. If unspecified, the full image width and height are used. The offset of the rectangle from the top-left corner for the source frame is specified by the ``x,y`` keys and the destination frame by the ``X,Y`` keys. The composition operation is specified by the ``C`` key with the default being to alpha blend the source rectangle onto the destination rectangle. With ``C=1`` it will be a simple replacement of pixels. For example:: _Ga=c,i=1,r=7,c=9,w=23,h=27,X=4,Y=8,x=1,y=3\ Will compose a ``23x27`` rectangle located at ``(4, 8)`` in the ``7th frame`` onto the rectangle located at ``(1, 3)`` in the ``9th frame``. These will be in the image with ``id=1``. If the frames or the image are not found the terminal emulator must respond with `ENOENT`. If the rectangles go out of bounds of the image the terminal must respond with `EINVAL`. If the source and destination frames are the same and the rectangles overlap, the terminal must respond with `EINVAL`. .. note:: In kitty, doing a composition will cause a frame to be *fully rendered* potentially increasing its storage requirements, when the frame was previously stored as a set of operations on other frames. If this happens and there is not enough storage space, kitty will respond with ENOSPC. Image persistence and storage quotas ----------------------------------------- In order to avoid *Denial-of-Service* attacks, terminal emulators should have a maximum storage quota for image data. It should allow at least a few full screen images. For example the quota in kitty is 320MB per buffer. When adding a new image, if the total size exceeds the quota, the terminal emulator should delete older images to make space for the new one. In kitty, for animations, the additional frame data is stored on disk and has a separate, larger quota of five times the base quota. Control data reference --------------------------- The table below shows all the control data keys as well as what values they can take, and the default value they take when missing. All integers are 32-bit. ======= ==================== ========= ================= Key Value Default Description ======= ==================== ========= ================= ``a`` Single character. ``t`` The overall action this graphics command is performing. ``(a, c, d, f, `` ``t`` - transmit data, ``T`` - transmit data and display image, ``p, q, t, T)`` ``q`` - query terminal, ``p`` - put (display) previous transmitted image, ``d`` - delete image, ``f`` - transmit data for animation frames, ``a`` - control animation, ``c`` - compose animation frames ``q`` ``0, 1, 2`` ``0`` Suppress responses from the terminal to this graphics command. **Keys for image transmission** ----------------------------------------------------------- ``f`` Positive integer. ``32`` The format in which the image data is sent. ``(24, 32, 100)``. ``t`` Single character. ``d`` The transmission medium used. ``(d, f, t, s)``. ``s`` Positive integer. ``0`` The width of the image being sent. ``v`` Positive integer. ``0`` The height of the image being sent. ``S`` Positive integer. ``0`` The size of data to read from a file. ``O`` Positive integer. ``0`` The offset from which to read data from a file. ``i`` Positive integer. ``(0 - 4294967295)`` ``0`` The image id ``I`` Positive integer. ``(0 - 4294967295)`` ``0`` The image number ``p`` Positive integer. ``(0 - 4294967295)`` ``0`` The placement id ``o`` Single character. ``null`` The type of data compression. ``only z`` ``m`` zero or one ``0`` Whether there is more chunked data available. **Keys for image display** ----------------------------------------------------------- ``x`` Positive integer ``0`` The left edge (in pixels) of the image area to display ``y`` Positive integer ``0`` The top edge (in pixels) of the image area to display ``w`` Positive integer ``0`` The width (in pixels) of the image area to display. By default, the entire width is used ``h`` Positive integer ``0`` The height (in pixels) of the image area to display. By default, the entire height is used ``X`` Positive integer ``0`` The x-offset within the first cell at which to start displaying the image ``Y`` Positive integer ``0`` The y-offset within the first cell at which to start displaying the image ``c`` Positive integer ``0`` The number of columns to display the image over ``r`` Positive integer ``0`` The number of rows to display the image over ``C`` Positive integer ``0`` Cursor movement policy. ``0`` is the default, to move the cursor to after the image. ``1`` is to not move the cursor at all when placing the image. ``U`` Positive integer ``0`` Set to ``1`` to create a virtual placement for a Unicode placeholder. ``z`` 32-bit integer ``0`` The *z-index* vertical stacking order of the image ``P`` Positive integer ``0`` The id of a parent image for relative placement ``Q`` Positive integer ``0`` The id of a placement in the parent image for relative placement ``H`` 32-bit integer ``0`` The offset in cells in the horizontal direction for relative placement ``V`` 32-bit integer ``0`` The offset in cells in the vertical direction for relative placement **Keys for animation frame loading** ----------------------------------------------------------- ``x`` Positive integer ``0`` The left edge (in pixels) of where the frame data should be updated ``y`` Positive integer ``0`` The top edge (in pixels) of where the frame data should be updated ``c`` Positive integer ``0`` The 1-based frame number of the frame whose image data serves as the base data when creating a new frame, by default the base data is black, fully transparent pixels ``r`` Positive integer ``0`` The 1-based frame number of the frame that is being edited. By default, a new frame is created ``z`` 32-bit integer ``0`` The gap (in milliseconds) of this frame from the next one. A value of zero is ignored. Negative values create a *gapless* frame. If not specified, frames have a default gap of ``40ms``. The root frame defaults to zero gap. ``X`` Positive integer ``0`` The composition mode for blending pixels when creating a new frame or editing a frame's data. The default is full alpha blending. ``1`` means a simple overwrite. ``Y`` Positive integer ``0`` The background color for pixels not specified in the frame data. Must be in 32-bit RGBA format **Keys for animation frame composition** ----------------------------------------------------------- ``c`` Positive integer ``0`` The 1-based frame number of the frame whose image data serves as the overlaid data ``r`` Positive integer ``0`` The 1-based frame number of the frame that is being edited. ``x`` Positive integer ``0`` The left edge (in pixels) of the destination rectangle ``y`` Positive integer ``0`` The top edge (in pixels) of the destination rectangle ``w`` Positive integer ``0`` The width (in pixels) of the source and destination rectangles. By default, the entire width is used ``h`` Positive integer ``0`` The height (in pixels) of the source and destination rectangles. By default, the entire height is used ``X`` Positive integer ``0`` The left edge (in pixels) of the source rectangle ``Y`` Positive integer ``0`` The top edge (in pixels) of the source rectangle ``C`` Positive integer ``0`` The composition mode for blending pixels. Default is full alpha blending. ``1`` means a simple overwrite. **Keys for animation control** ----------------------------------------------------------- ``s`` Positive integer ``0`` ``1`` - stop animation, ``2`` - run animation, but wait for new frames, ``3`` - run animation ``r`` Positive integer ``0`` The 1-based frame number of the frame that is being affected ``z`` 32-bit integer ``0`` The gap (in milliseconds) of this frame from the next one. A value of zero is ignored. Negative values create a *gapless* frame. ``c`` Positive integer ``0`` The 1-based frame number of the frame that should be made the current frame ``v`` Positive integer ``0`` The number of loops to play. ``0`` is ignored, ``1`` is play infinite and is the default and larger number means play that number ``-1`` loops **Keys for deleting images** ----------------------------------------------------------- ``d`` Single character. ``a`` What to delete. ``( a, A, c, C, n, N, i, I, p, P, q, Q, r, R, x, X, y, Y, z, Z )``. ======= ==================== ========= ================= Interaction with other terminal actions -------------------------------------------- When resetting the terminal, all images that are visible on the screen must be cleared. When switching from the main screen to the alternate screen buffer (1049 private mode) all images in the alternate screen must be cleared, just as all text is cleared. The clear screen escape code (usually ``[2J``) should also clear all images. This is so that the clear command works. The other commands to erase text must have no effect on graphics. The dedicated delete graphics commands must be used for those. When scrolling the screen (such as when using index cursor movement commands, or scrolling through the history buffer), images must be scrolled along with text. When page margins are defined and the index commands are used, only images that are entirely within the page area (between the margins) must be scrolled. When scrolling them would cause them to extend outside the page area, they must be clipped. kitty-0.41.1/docs/index.rst0000664000175000017510000000346714773370543015112 0ustar nileshnileshkitty ========================================================== *The fast, feature-rich, GPU based terminal emulator* .. toctree:: :hidden: quickstart overview faq support performance changelog integrations protocol-extensions press-mentions .. tab:: Fast * Uses GPU and SIMD vector CPU instructions for :doc:`best in class ` * Uses threaded rendering for :iss:`absolutely minimal latency <2701#issuecomment-636497270>` * Performance tradeoffs can be :ref:`tuned ` .. tab:: Capable * Graphics, with :doc:`images and animations ` * Ligatures, emoji with :opt:`per glyph font substitution ` and :doc:`variable fonts and font features ` * :term:`Hyperlinks`, with :doc:`configurable actions ` .. tab:: Scriptable * Control from :doc:`scripts or the shell ` * Extend with :ref:`kittens ` using the Python language * Use :ref:`startup sessions ` to specify working environments .. tab:: Composable * Programmable tabs, :ref:`splits ` and multiple :doc:`layouts ` to manage windows * Browse the :ref:`entire history ` or the :sc:`output from the last command ` comfortably in pagers and editors * Edit or download :doc:`remote files ` in an existing SSH session .. tab:: Cross-platform * Linux * macOS * Various BSDs .. tab:: Innovative Pioneered various extensions to move the entire terminal ecosystem forward * :doc:`graphics-protocol` * :doc:`keyboard-protocol` * Lots more in :doc:`protocol-extensions` To get started see :doc:`quickstart`. .. only:: dirhtml .. include:: intro_vid.rst kitty-0.41.1/docs/installer.sh0000664000175000017510000001073314773370543015574 0ustar nileshnilesh#!/bin/sh # Copyright (C) 2018 Kovid Goyal # # Distributed under terms of the GPLv3 license. { \unalias command; \unset -f command; } >/dev/null 2>&1 tdir='' cleanup() { [ -n "$tdir" ] && { command rm -rf "$tdir" tdir='' } } die() { cleanup printf "\033[31m%s\033[m\n\r" "$*" > /dev/stderr; exit 1; } detect_network_tool() { if command -v curl 2> /dev/null > /dev/null; then fetch() { command curl -fL "$1" } fetch_quiet() { command curl -fsSL "$1" } elif command -v wget 2> /dev/null > /dev/null; then fetch() { command wget -O- "$1" } fetch_quiet() { command wget --quiet -O- "$1" } else die "Neither curl nor wget available, cannot download kitty" fi } detect_os() { arch="" case "$(command uname)" in 'Darwin') OS="macos";; 'Linux') OS="linux" case "$(command uname -m)" in amd64|x86_64) arch="x86_64";; aarch64*) arch="arm64";; armv8*) arch="arm64";; *) die "kitty binaries not available for architecture $(command uname -m)";; esac ;; *) die "kitty binaries are not available for $(command uname)" esac } expand_tilde() { tilde_less="${1#\~/}" [ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less" printf '%s' "$tilde_less" } parse_args() { dest='~/.local' [ "$OS" = "macos" ] && dest="/Applications" launch='y' installer='' while :; do case "$1" in dest=*) dest="${1#*=}";; launch=*) launch="${1#*=}";; installer=*) installer="${1#*=}";; "") break;; *) die "Unrecognized command line option: $1";; esac shift done dest=$(expand_tilde "${dest}") [ "$launch" != "y" -a "$launch" != "n" ] && die "Unrecognized command line option: launch=$launch" dest="$dest/kitty.app" } get_file_url() { url="https://github.com/kovidgoyal/kitty/releases/download/$1/kitty-$2" if [ "$OS" = "macos" ]; then url="$url.dmg" else url="$url-$arch.txz" fi } get_release_url() { release_version=$(fetch_quiet "https://sw.kovidgoyal.net/kitty/current-version.txt") [ $? -ne 0 -o -z "$release_version" ] && die "Could not get kitty latest release version" get_file_url "v$release_version" "$release_version" } get_version_url() { get_file_url "v$1" "$1" } get_nightly_url() { get_file_url "nightly" "nightly" } get_download_url() { installer_is_file="n" case "$installer" in "nightly") get_nightly_url ;; "") get_release_url ;; version-*) get_version_url "${installer#*-}";; *) installer_is_file="y" ;; esac } download_installer() { tdir=$(command mktemp -d "/tmp/kitty-install-XXXXXXXXXXXX") [ "$installer_is_file" != "y" ] && { printf '%s\n\n' "Downloading from: $url" if [ "$OS" = "macos" ]; then installer="$tdir/kitty.dmg" else installer="$tdir/kitty.txz" fi fetch "$url" > "$installer" || die "Failed to download: $url" installer_is_file="y" } } ensure_dest() { printf "%s\n" "Installing to $dest" command rm -rf "$dest" || die "Failed to delete $dest" command mkdir -p "$dest" || die "Failed to mkdir -p $dest" command rm -rf "$dest" || die "Failed to delete $dest" } linux_install() { command mkdir "$tdir/mp" command tar -C "$tdir/mp" "-xJof" "$installer" || die "Failed to extract kitty tarball" ensure_dest command mv "$tdir/mp" "$dest" || die "Failed to move kitty.app to $dest" } macos_install() { command mkdir "$tdir/mp" command hdiutil attach "$installer" "-mountpoint" "$tdir/mp" || die "Failed to mount kitty.dmg" ensure_dest command ditto -v "$tdir/mp/kitty.app" "$dest" rc="$?" command hdiutil detach "$tdir/mp" [ "$rc" != "0" ] && die "Failed to copy kitty.app from mounted dmg" } exec_kitty() { if [ "$OS" = "macos" ]; then exec "open" "$dest" else exec "$dest/bin/kitty" "--detach" fi die "Failed to launch kitty" } main() { detect_os parse_args "$@" detect_network_tool get_download_url download_installer if [ "$OS" = "macos" ]; then macos_install else linux_install fi cleanup [ "$launch" = "y" ] && exec_kitty exit 0 } main "$@" kitty-0.41.1/docs/integrations.rst0000664000175000017510000002636214773370543016510 0ustar nileshnilesh:tocdepth: 2 Integrations with other tools ================================ kitty provides extremely powerful interfaces such as :doc:`remote-control` and :doc:`kittens/custom` and :doc:`kittens/icat` that allow it to be integrated with other tools seamlessly. Image and document viewers ---------------------------- Powered by kitty's :doc:`graphics-protocol` there exist many tools for viewing images and other types of documents directly in your terminal, even over SSH. .. _tool_termpdf: `termpdf.py `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A terminal PDF/DJVU/CBR viewer .. _tool_tdf: `tdf `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A terminal PDF viewer .. _tool_fancy_cat: `fancy-cat `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A terminal PDF viewer .. _tool_meowpdf: `meowpdf `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A terminal PDF viewer with GUI-like usage and Vim-like keybindings written in Rust .. _tool_mdcat: `mdcat `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Display markdown files nicely formatted with images in the terminal .. _tool_ranger: `ranger `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A terminal file manager, with previews of file contents powered by kitty's graphics protocol. .. _tool_nnn: `nnn `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Another terminal file manager, with previews of file contents powered by kitty's graphics protocol. .. _tool_yazi: `Yazi `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Blazing fast terminal file manager, with built-in kitty graphics protocol support (implemented both Classic protocol and Unicode placeholders). .. _tool_clifm: `clifm `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The shell-like, command line terminal file manager, uses the kitty graphics and keyboard protocols. .. _tool_hunter: `hunter `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Another terminal file manager, with previews of file contents powered by kitty's graphics protocol. .. _tool_presentterm: `presenterm `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Show markdown based slides with images in your terminal, powered by the kitty graphics protocol. .. _tool_term_image: `term-image `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Tool to browse images in a terminal using kitty's graphics protocol. .. _tool_koneko: `koneko `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Browse images from the pixiv artist community directly in kitty. .. _tool_viu: `viu `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ View images in the terminal, similar to kitty's icat. .. _tool_nb: `nb `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Command line and local web note-taking, bookmarking, archiving, and knowledge base application that uses kitty's graphics protocol for images. .. _tool_w3m: `w3m `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A text mode WWW browser that supports kitty's graphics protocol to display images. .. _tool_awrit: `awrit `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A full Chromium based web browser running in the terminal using kitty's graphics protocol. .. _tool_chawan: `chawan `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A text mode WWW browser that supports kitty's graphics protocol to display images. .. _tool_mpv: `mpv `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A video player that can play videos in the terminal. .. _tool_timg: `timg `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A terminal image and video viewer, that displays static and animated images or plays videos. Fast multi-threaded loading, JPEG exif rotation, grid view and connecting to the webcam make it a versatile terminal utility. System and data visualisation tools --------------------------------------- .. _tool_neofetch: `neofetch `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A command line system information tool that shows images using kitty's graphics protocol .. _tool_matplotlib: matplotlib ^^^^^^^^^^^^^^ There exist multiple backends for matplotlib to draw images directly in kitty. * `matplotlib-backend-kitty `__ * `kitcat `__ .. _tool_KittyTerminalImage: `KittyTerminalImages.jl `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Show images from Julia directly in kitty .. _tool_euporie: `euporie `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A text-based user interface for running and editing Jupyter notebooks, powered by kitty's graphics protocol for displaying plots .. _tool_gnuplot: `gnuplot `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A graphing and data visualization tool that can be made to display its output in kitty with the following bash snippet: .. code-block:: sh function iplot { cat <`_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A Terminal Operating Test hardware equipment .. tool_onefetch: `onefetch `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A tool to fetch information about your git repositories .. tool_patat: `patat `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Terminal based presentations using pandoc and kitty's image protocol for images .. tool_wttr: `wttr.in `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A tool to display weather information in your terminal with curl .. tool_wl_clipboard: `wl-clipboard-manager `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ View and manage the system clipboard under Wayland in your kitty terminal .. tool_nemu: `NEMU `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TUI for QEMU used to manage virtual machines, can display the Virtual Machine in the terminal using the kitty graphics protocol. Editor integration ----------------------- |kitty| can be integrated into many different terminal based text editors to add features such a split windows, previews, REPLs etc. .. tool_kakoune: `kakoune `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Integrates with kitty to use native kitty windows for its windows/panels and REPLs. .. tool_vim_slime: `vim-slime `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Uses kitty remote control for a Lisp REPL. .. tool_vim_kitty_navigator: `vim-kitty-navigator `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Allows you to navigate seamlessly between vim and kitty splits using a consistent set of hotkeys. .. tool_vim_test: `vim-test `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Allows easily running tests in a terminal window .. tool_nvim_image_viewers: Various image viewing plugins for editors ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * `snacks.nvim `__ - Enables seamless inline images in various file formats within nvim * `image.nvim `_ - Bringing images to neovim * `image_preview.nvim `_ - Image preview for neovim * `hologram.nvim `_ - view images inside nvim Scrollback manipulation ------------------------- .. tool_kitty_scrollback_nvim: `kitty-scrollback.nvim `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Browse the scrollback buffer with Neovim, with simple key actions for efficient copy/paste and even execution of commands. .. tool_kitty_search: `kitty-search `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Live incremental search of the scrollback buffer. .. tool_kitty_grab: `kitty-grab `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Keyboard based text selection for the kitty scrollback buffer. Miscellaneous ------------------ .. tool_gattino: `gattino `__ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Integrate kitty with an LLM to convert plain language prompts into shell commands. .. tool_kitty_smart_tab: `kitty-smart-tab `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use keys to either control tabs or pass them onto running applications if no tabs are present .. tool_kitty_smart_scroll: `kitty-smart-scroll `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use keys to either scroll or pass them onto running applications if no scrollback buffer is present .. tool_kitti3: `kitti3 `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Allow using kitty as a drop-down terminal under the i3 window manager .. tool_weechat_hints: `weechat-hints `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ URL hints kitten for WeeChat that works without having to use WeeChat's raw-mode. .. tool_glkitty: `glkitty `_ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C library to draw OpenGL shaders in the terminal with a glgears demo kitty-0.41.1/docs/intro_vid.rst0000664000175000017510000000370114773370543015767 0ustar nileshnilesh.. raw:: html
.. rst-class:: caption caption-text Watch kitty in action! Timestamps for the above video: 00:00 Intro 00:39 Pager: View command output in same window: :kbd:`Ctrl+Shift+g` 01:43 Pager: View command output in a separate window 02:14 Pager: Uses shell integration in kitty 02:27 Tab text: The output of cwd and last cmd 03:03 Open files from ls output with mouse: :kbd:`Ctrl+Shift+Right-click` 04:04 Open files from ls output with keyboard: :kbd:`Ctrl+Shift+P>y` 04:26 Open files on click: ``ls --hyperlink=auto`` 05:03 Open files on click: Filetype settings in open-actions.conf 05:45 hyperlinked-grep kitten: Open grep output in editor 07:18 Remote-file kitten: View remote files locally 08:31 Remote-file kitten: Edit remote files locally 10:01 icat kitten: View images directly 10:36 icat kitten: Download & display image/gif from internet 11:03 Kitty Graphics Protocol: Live image preview in ranger 11:25 icat kitten: Display image from remote server 12:04 unicode-input kitten: Emojis in terminal 12:54 Windows: Intro 13:36 Windows: Switch focus: :kbd:`Ctrl+Shift+win_nr` 13:48 Windows: Visual selection: :kbd:`Ctrl+Shift+F7` 13:58 Windows: Simultaneous input 14:15 Interactive Kitty Shell: :kbd:`Ctrl+Shift+Esc` 14:36 Broadcast text: ``launch --allow-remote-control kitten broadcast`` 15:18 Kitty Remote Control Protocol 15:52 Interactive Kitty Shell: Help 16:34 Choose theme interactively: ``kitten themes -h`` 17:23 Choose theme by name: ``kitten themes [options] [theme_name]`` .. raw:: html
kitty-0.41.1/docs/invocation.rst0000664000175000017510000000031314773370543016137 0ustar nileshnilesh:orphan: The kitty command line interface ==================================== .. program:: kitty .. include:: generated/cli-kitty.rst .. include:: basic.rst See also ----------- See kitty.conf(5) kitty-0.41.1/docs/keyboard-protocol.rst0000664000175000017510000010456714773370543017445 0ustar nileshnileshComprehensive keyboard handling in terminals ============================================== There are various problems with the current state of keyboard handling in terminals. They include: * No way to use modifiers other than ``ctrl`` and ``alt`` * No way to reliably use multiple modifier keys, other than, ``shift+alt`` and ``ctrl+alt``. * Many of the existing escape codes used to encode these events are ambiguous with different key presses mapping to the same escape code. * No way to handle different types of keyboard events, such as press, release or repeat * No reliable way to distinguish single ``Esc`` key presses from the start of a escape sequence. Currently, client programs use fragile timing related hacks for this, leading to bugs, for example: `neovim #2035 `_. To solve these issues and others, kitty has created a new keyboard protocol, that is backward compatible but allows applications to opt-in to support more advanced usages. The protocol is based on initial work in `fixterms `_, however, it corrects various issues in that proposal, listed at the :ref:`bottom of this document `. For public discussion of this spec, see :iss:`3248`. You can see this protocol with all enhancements in action by running:: kitten show-key -m kitty inside the kitty terminal to report key events. In addition to kitty, this protocol is also implemented in: * The `alacritty terminal `__ * The `ghostty terminal `__ * The `foot terminal `__ * The `iTerm2 terminal `__ * The `rio terminal `__ * The `WezTerm terminal `__ Libraries implementing this protocol: * The `notcurses library `__ * The `crossterm library `__ * The `textual library `__ * The vaxis library `go `__ and `zig `__ Programs implementing this protocol: * The `Vim text editor `__ * The `Emacs text editor via the kkp package `__ * The `Neovim text editor `__ * The `kakoune text editor `__ * The `dte text editor `__ * The `Helix text editor `__ * The `far2l file manager `__ * The `Yazi file manager `__ * The `awrit web browser `__ * The `Turbo Vision `__/`Free Vision `__ IDEs * The `aerc email client `__ Shells implementing this protocol: * The `nushell shell `__ * The `fish shell `__ .. versionadded:: 0.20.0 Quickstart --------------- If you are an application or library developer just interested in using this protocol to make keyboard handling simpler and more robust in your application, without too many changes, do the following: #. Emit the escape code ``CSI > 1 u`` at application startup if using the main screen or when entering alternate screen mode, if using the alternate screen. #. All key events will now be sent in only a few forms to your application, that are easy to parse unambiguously. #. Emit the escape sequence ``CSI < u`` at application exit if using the main screen or just before leaving alternate screen mode if using the alternate screen, to restore whatever the keyboard mode was before step 1. Key events will all be delivered to your application either as plain UTF-8 text, or using the following escape codes, for those keys that do not produce text (``CSI`` is the bytes ``0x1b 0x5b``):: CSI number ; modifiers [u~] CSI 1; modifiers [ABCDEFHPQS] 0x0d - for the Enter key 0x7f or 0x08 - for Backspace 0x09 - for Tab The ``number`` in the first form above will be either the Unicode codepoint for a key, such as ``97`` for the :kbd:`a` key, or one of the numbers from the :ref:`functional` table below. The ``modifiers`` optional parameter encodes any modifiers active for the key event. The encoding is described in the :ref:`modifiers` section. The second form is used for a few functional keys, such as the :kbd:`Home`, :kbd:`End`, :kbd:`Arrow` keys and :kbd:`F1` ... :kbd:`F4`, they are enumerated in the :ref:`functional` table below. Note that if no modifiers are present the parameters are omitted entirely giving an escape code of the form ``CSI [ABCDEFHPQS]``. If you want support for more advanced features such as repeat and release events, alternate keys for shortcut matching et cetera, these can be turned on using :ref:`progressive_enhancement` as documented in the rest of this specification. An overview ------------------ Key events are divided into two types, those that produce text and those that do not. When a key event produces text, the text is sent directly as UTF-8 encoded bytes. This is safe as UTF-8 contains no C0 control codes. When the key event does not have text, the key event is encoded as an escape code. In legacy compatibility mode (the default) this uses legacy escape codes, so old terminal applications continue to work. For more advanced features, such as release/repeat reporting etc., applications can tell the terminal they want this information by sending an escape code to :ref:`progressively enhance ` the data reported for key events. The central escape code used to encode key events is:: CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u Spaces in the above definition are present for clarity and should be ignored. ``CSI`` is the bytes ``0x1b 0x5b``. All parameters are decimal numbers. Fields are separated by the semi-colon and sub-fields by the colon. Only the ``unicode-key-code`` field is mandatory, everything else is optional. The escape code is terminated by the ``u`` character (the byte ``0x75``). .. _key_codes: Key codes ~~~~~~~~~~~~~~ The ``unicode-key-code`` above is the Unicode codepoint representing the key, as a decimal number. For example, the :kbd:`A` key is represented as ``97`` which is the unicode code for lowercase ``a``. Note that the codepoint used is *always* the lower-case (or more technically, un-shifted) version of the key. If the user presses, for example, :kbd:`ctrl+shift+a` the escape code would be ``CSI 97;modifiers u``. It *must not* be ``CSI 65; modifiers u``. If *alternate key reporting* is requested by the program running in the terminal, the terminal can send two additional Unicode codepoints, the *shifted key* and *base layout key*, separated by colons. The shifted key is simply the upper-case version of ``unicode-codepoint``, or more technically, the shifted version. So `a` becomes `A` and so on, based on the current keyboard layout. This is needed to be able to match against a shortcut such as :kbd:`ctrl+plus` which depending on the type of keyboard could be either :kbd:`ctrl+shift+equal` or :kbd:`ctrl+plus`. Note that the shifted key must be present only if shift is also present in the modifiers. The *base layout key* is the key corresponding to the physical key in the standard PC-101 key layout. So for example, if the user is using a Cyrillic keyboard with a Cyrillic keyboard layout pressing the :kbd:`ctrl+С` key will be :kbd:`ctrl+c` in the standard layout. So the terminal should send the *base layout key* as ``99`` corresponding to the ``c`` key. If only one alternate key is present, it is the *shifted key*. If the terminal wants to send only a base layout key but no shifted key, it must use an empty sub-field for the shifted key, like this:: CSI unicode-key-code::base-layout-key .. _modifiers: Modifiers ~~~~~~~~~~~~~~ This protocol supports six modifier keys, :kbd:`shift`, :kbd:`alt`, :kbd:`ctrl`, :kbd:`super`, :kbd:`hyper`, :kbd:`meta`, :kbd:`num_lock` and :kbd:`caps_lock`. Here :kbd:`super` is either the *Windows/Linux* key or the :kbd:`command` key on mac keyboards. The :kbd:`alt` key is the :kbd:`option` key on mac keyboards. :kbd:`hyper` and :kbd:`meta` are typically present only on X11/Wayland based systems with special XKB rules. Modifiers are encoded as a bit field with:: shift 0b1 (1) alt 0b10 (2) ctrl 0b100 (4) super 0b1000 (8) hyper 0b10000 (16) meta 0b100000 (32) caps_lock 0b1000000 (64) num_lock 0b10000000 (128) In the escape code, the modifier value is encoded as a decimal number which is ``1 + actual modifiers``. So to represent :kbd:`shift` only, the value would be ``1 + 1 = 2``, to represent :kbd:`ctrl+shift` the value would be ``1 + 0b101 = 6`` and so on. If the modifier field is not present in the escape code, its default value is ``1`` which means no modifiers. If a modifier is *active* when the key event occurs, i.e. if the key is pressed or the lock (for caps lock/num lock) is enabled, the key event must have the bit for that modifier set. When the key event is related to an actual modifier key, the corresponding modifier's bit must be set to the modifier state including the effect for the current event. For example, when pressing the :kbd:`LEFT_CONTROL` key, the ``ctrl`` bit must be set and when releasing it, it must be reset. When both left and right control keys are pressed and one is released, the release event must have the ``ctrl`` bit set. See :iss:`6913` for discussion of this design. .. _event_types: Event types ~~~~~~~~~~~~~~~~ There are three key event types: ``press, repeat and release``. They are reported (if requested ``0b10``) as a sub-field of the modifiers field (separated by a colon). If no modifiers are present, the modifiers field must have the value ``1`` and the event type sub-field the type of event. The ``press`` event type has value ``1`` and is the default if no event type sub field is present. The ``repeat`` type is ``2`` and the ``release`` type is ``3``. So for example:: CSI key-code # this is a press event CSI key-code;modifier # this is a press event CSI key-code;modifier:1 # this is a press event CSI key-code;modifier:2 # this is a repeat event CSI key-code;modifier:3 # this is a release event .. note:: Key events that result in text are reported as plain UTF-8 text, so events are not supported for them, unless the application requests *key report mode*, see below. .. _text_as_codepoints: Text as code points ~~~~~~~~~~~~~~~~~~~~~ The terminal can optionally send the text associated with key events as a sequence of Unicode code points. This behavior is opt-in by the :ref:`progressive enhancement ` mechanism described below. Some examples:: shift+a -> CSI 97 ; 2 ; 65 u # The text 'A' is reported as 65 option+a -> CSI 97 ; ; 229 u # The text 'å' is reported as 229 If multiple code points are present, they must be separated by colons. If no known key is associated with the text the key number ``0`` must be used. The associated text must not contain control codes (control codes are code points below U+0020 and codepoints in the C0 and C1 blocks). Non-Unicode keys ~~~~~~~~~~~~~~~~~~~~~~~ There are many keys that don't correspond to letters from human languages, and thus aren't represented in Unicode. Think of functional keys, such as :kbd:`Escape`, :kbd:`Play`, :kbd:`Pause`, :kbd:`F1`, :kbd:`Home`, etc. These are encoded using Unicode code points from the Private Use Area (``57344 - 63743``). The mapping of key names to code points for these keys is in the :ref:`Functional key definition table below `. .. _progressive_enhancement: Progressive enhancement -------------------------- While, in theory, every key event could be completely represented by this protocol and all would be hunk-dory, in reality there is a vast universe of existing terminal programs that expect legacy control codes for key events and that are not likely to ever be updated. To support these, in default mode, the terminal will emit legacy escape codes for compatibility. If a terminal program wants more robust key handling, it can request it from the terminal, via the mechanism described here. Each enhancement is described in detail below. The escape code for requesting enhancements is:: CSI = flags ; mode u Here ``flags`` is a decimal encoded integer to specify a set of bit-flags. The meanings of the flags are given below. The second, ``mode`` parameter is optional (defaulting to ``1``) and specifies how the flags are applied. The value ``1`` means all set bits are set and all unset bits are reset. The value ``2`` means all set bits are set, unset bits are left unchanged. The value ``3`` means all set bits are reset, unset bits are left unchanged. .. csv-table:: The progressive enhancement flags :header: "Bit", "Meaning" "0b1 (1)", ":ref:`disambiguate`" "0b10 (2)", ":ref:`report_events`" "0b100 (4)", ":ref:`report_alternates`" "0b1000 (8)", ":ref:`report_all_keys`" "0b10000 (16)", ":ref:`report_text`" The program running in the terminal can query the terminal for the current values of the flags by sending:: CSI ? u The terminal will reply with:: CSI ? flags u The program can also push/pop the current flags onto a stack in the terminal with:: CSI > flags u # for push, if flags omitted default to zero CSI < number u # to pop number entries, defaulting to 1 if unspecified Terminals should limit the size of the stack as appropriate, to prevent Denial-of-Service attacks. Terminals must maintain separate stacks for the main and alternate screens. If a pop request is received that empties the stack, all flags are reset. If a push request is received and the stack is full, the oldest entry from the stack must be evicted. .. note:: The main and alternate screens in the terminal emulator must maintain their own, independent, keyboard mode stacks. This is so that a program that uses the alternate screen such as an editor, can change the keyboard mode in the alternate screen only, without affecting the mode in the main screen or even knowing what that mode is. Without this, and if no stack is implemented for keyboard modes (such as in some legacy terminal emulators) the editor would have to somehow know what the keyboard mode of the main screen is and restore to that mode on exit. .. _disambiguate: Disambiguate escape codes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This type of progressive enhancement (``0b1``) fixes the problem of some legacy key press encodings overlapping with other control codes. For instance, pressing the :kbd:`Esc` key generates the byte ``0x1b`` which also is used to indicate the start of an escape code. Similarly pressing the key :kbd:`alt+[` will generate the bytes used for CSI control codes. Turning on this flag will cause the terminal to report the :kbd:`Esc`, :kbd:`alt+key`, :kbd:`ctrl+key`, :kbd:`ctrl+alt+key`, :kbd:`shift+alt+key` keys using ``CSI u`` sequences instead of legacy ones. Here key is any ASCII key as described in :ref:`legacy_text`. Additionally, all non text keypad keys will be reported as separate keys with ``CSI u`` encoding, using dedicated numbers from the :ref:`table below `. With this flag turned on, all key events that do not generate text are represented in one of the following two forms:: CSI number; modifier u CSI 1; modifier [~ABCDEFHPQS] This makes it very easy to parse key events in an application. In particular, :kbd:`ctrl+c` will no longer generate the ``SIGINT`` signal, but instead be delivered as a ``CSI u`` escape code. This has the nice side effect of making it much easier to integrate into the application event loop. The only exceptions are the :kbd:`Enter`, :kbd:`Tab` and :kbd:`Backspace` keys which still generate the same bytes as in legacy mode this is to allow the user to type and execute commands in the shell such as ``reset`` after a program that sets this mode crashes without clearing it. Note that the Lock modifiers are not reported for text producing keys, to keep them useable in legacy programs. To get lock modifiers for all keys use the :ref:`report_all_keys` enhancement. .. _report_events: Report event types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This progressive enhancement (``0b10``) causes the terminal to report key repeat and key release events. Normally only key press events are reported and key repeat events are treated as key press events. See :ref:`event_types` for details on how these are reported. .. note:: The :kbd:`Enter`, :kbd:`Tab` and :kbd:`Backspace` keys will not have release events unless :ref:`report_all_keys` is also set, so that the user can still type reset at a shell prompt when a program that sets this mode ends without resetting it. .. _report_alternates: Report alternate keys ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This progressive enhancement (``0b100``) causes the terminal to report alternate key values *in addition* to the main value, to aid in shortcut matching. See :ref:`key_codes` for details on how these are reported. Note that this flag is a pure enhancement to the form of the escape code used to represent key events, only key events represented as escape codes due to the other enhancements in effect will be affected by this enhancement. In other words, only if a key event was already going to be represented as an escape code due to one of the other enhancements will this enhancement affect it. .. _report_all_keys: Report all keys as escape codes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Key events that generate text, such as plain key presses without modifiers, result in just the text being sent, in the legacy protocol. There is no way to be notified of key repeat/release events. These types of events are needed for some applications, such as games (think of movement using the ``WASD`` keys). This progressive enhancement (``0b1000``) turns on key reporting even for key events that generate text. When it is enabled, text will not be sent, instead only key events are sent. If the text is needed as well, combine with the Report associated text enhancement below. Additionally, with this mode, events for pressing modifier keys are reported. Note that *all* keys are reported as escape codes, including :kbd:`Enter`, :kbd:`Tab`, :kbd:`Backspace` etc. Note that this enhancement implies all keys are automatically disambiguated as well, since they are represented in their canonical escape code form. .. _report_text: Report associated text ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This progressive enhancement (``0b10000``) *additionally* causes key events that generate text to be reported as ``CSI u`` escape codes with the text embedded in the escape code. See :ref:`text_as_codepoints` above for details on the mechanism. Note that this flag is an enhancement to :ref:`report_all_keys` and is undefined if used without it. .. _detection: Detection of support for this protocol ------------------------------------------ An application can query the terminal for support of this protocol by sending the escape code querying for the :ref:`current progressive enhancement ` status followed by request for the `primary device attributes `__. If an answer for the device attributes is received without getting back an answer for the progressive enhancement the terminal does not support this protocol. .. note:: Terminal implementations of this protocol are **strongly** encouraged to implement all progressive enhancements. It does not make sense to implement only a subset. Nonetheless, there are likely to be some terminal implementations that do not do so, applications can detect such implementations by first setting the desired progressive enhancements and then querying for the :ref:`current progressive enhancement ` Legacy key event encoding -------------------------------- In the default mode, the terminal uses a legacy encoding for key events. In this encoding, only key press and repeat events are sent and there is no way to distinguish between them. Text is sent directly as UTF-8 bytes. Any key events not described in this section are sent using the standard ``CSI u`` encoding. This includes keys that are not encodable in the legacy encoding, thereby increasing the space of usable key combinations even without progressive enhancement. Legacy functional keys ~~~~~~~~~~~~~~~~~~~~~~~~ These keys are encoded using three schemes:: CSI number ; modifier ~ CSI 1 ; modifier {ABCDEFHPQS} SS3 {ABCDEFHPQRS} In the above, if there are no modifiers, the modifier parameter is omitted. The modifier value is encoded as described in the :ref:`modifiers` section, above. When the second form is used, the number is always ``1`` and must be omitted if the modifiers field is also absent. The third form becomes the second form when modifiers are present (``SS3 is the bytes 0x1b 0x4f``). These sequences must match entries in the terminfo database for maximum compatibility. The table below lists the key, its terminfo entry name and the escape code used for it by kitty. A different terminal would use whatever escape code is present in its terminfo database for the key. Some keys have an alternate representation when the terminal is in *cursor key mode* (the ``smkx/rmkx`` terminfo capabilities). This form is used only in *cursor key mode* and only when no modifiers are present. .. csv-table:: Legacy functional encoding :header: "Name", "Terminfo name", "Escape code" "INSERT", "kich1", "CSI 2 ~" "DELETE", "kdch1", "CSI 3 ~" "PAGE_UP", "kpp", "CSI 5 ~" "PAGE_DOWN", "knp", "CSI 6 ~" "UP", "cuu1,kcuu1", "CSI A, SS3 A" "DOWN", "cud1,kcud1", "CSI B, SS3 B" "RIGHT", "cuf1,kcuf1", "CSI C, SS3 C" "LEFT", "cub1,kcub1", "CSI D, SS3 D" "HOME", "home,khome", "CSI H, SS3 H" "END", "-,kend", "CSI F, SS3 F" "F1", "kf1", "SS3 P" "F2", "kf2", "SS3 Q" "F3", "kf3", "SS3 R" "F4", "kf4", "SS3 S" "F5", "kf5", "CSI 15 ~" "F6", "kf6", "CSI 17 ~" "F7", "kf7", "CSI 18 ~" "F8", "kf8", "CSI 19 ~" "F9", "kf9", "CSI 20 ~" "F10", "kf10", "CSI 21 ~" "F11", "kf11", "CSI 23 ~" "F12", "kf12", "CSI 24 ~" "MENU", "kf16", "CSI 29 ~" There are a few more functional keys that have special cased legacy encodings. These are present because they are commonly used and for the sake of legacy terminal applications that get confused when seeing CSI u escape codes: .. csv-table:: C0 controls :header: "Key", "No mods", "Ctrl", "Alt", "Shift", "Ctrl + Shift", "Alt + Shift", "Ctrl + Alt" "Enter", "0xd", "0xd", "0x1b 0xd", "0xd", "0xd", "0x1b 0xd", "0x1b 0xd" "Escape", "0x1b", "0x1b", "0x1b 0x1b", "0x1b", "0x1b", "0x1b 0x1b", "0x1b 0x1b" "Backspace", "0x7f", "0x8", "0x1b 0x7f", "0x7f", "0x8", "0x1b 0x7f", "0x1b 0x8" "Tab", "0x9", "0x9", "0x1b 0x9", "CSI Z", "CSI Z", "0x1b CSI Z", "0x1b 0x9" "Space", "0x20", "0x0", "0x1b 0x20", "0x20", "0x0", "0x1b 0x20", "0x1b 0x0" Note that :kbd:`Backspace` and :kbd:`ctrl+Backspace` are swapped in some terminals, this can be detected using the ``kbs`` terminfo property that must correspond to the :kbd:`Backspace` key. All keypad keys are reported as their equivalent non-keypad keys. To distinguish these, use the :ref:`disambiguate ` flag. Terminals may choose what they want to do about functional keys that have no legacy encoding. kitty chooses to encode these using ``CSI u`` encoding even in legacy mode, so that they become usable even in programs that do not understand the full kitty keyboard protocol. However, terminals may instead choose to ignore such keys in legacy mode instead, or have an option to control this behavior. .. _legacy_text: Legacy text keys ~~~~~~~~~~~~~~~~~~~ For legacy compatibility, the keys :kbd:`a`-:kbd:`z` :kbd:`0`-:kbd:`9` :kbd:`\`` :kbd:`-` :kbd:`=` :kbd:`[` :kbd:`]` :kbd:`\\` :kbd:`;` :kbd:`'` :kbd:`,` :kbd:`.` :kbd:`/` with the modifiers :kbd:`shift`, :kbd:`alt`, :kbd:`ctrl`, :kbd:`shift+alt`, :kbd:`ctrl+alt` are output using the following algorithm: #. If the :kbd:`alt` key is pressed output the byte for ``ESC (0x1b)`` #. If the :kbd:`ctrl` modifier is pressed map the key using the table in :ref:`ctrl_mapping`. #. Otherwise, if the :kbd:`shift` modifier is pressed, output the shifted key, for example, ``A`` for ``a`` and ``$`` for ``4``. #. Otherwise, output the key unmodified Additionally, :kbd:`ctrl+space` is output as the NULL byte ``(0x0)``. Any other combination of modifiers with these keys is output as the appropriate ``CSI u`` escape code. .. csv-table:: Example encodings :header: "Key", "Plain", "shift", "alt", "ctrl", "shift+alt", "alt+ctrl", "ctrl+shift" "i", "i (105)", "I (73)", "ESC i", ") (41)", "ESC I", "ESC )", "CSI 105; 6 u" "3", "3 (51)", "# (35)", "ESC 3", "3 (51)", "ESC #", "ESC 3", "CSI 51; 6 u" ";", "; (59)", ": (58)", "ESC ;", "; (59)", "ESC :", "ESC ;", "CSI 59; 6 u" .. note:: Many of the legacy escape codes are ambiguous with multiple different key presses yielding the same escape code(s), for example, :kbd:`ctrl+i` is the same as :kbd:`tab`, :kbd:`ctrl+m` is the same as :kbd:`Enter`, :kbd:`ctrl+r` is the same :kbd:`ctrl+shift+r`, etc. To resolve these use the :ref:`disambiguate progressive enhancement `. .. _functional: Functional key definitions ---------------------------- All numbers are in the Unicode Private Use Area (``57344 - 63743``) except for a handful of keys that use numbers under 32 and 127 (C0 control codes) for legacy compatibility reasons. .. {{{ .. start functional key table (auto generated by gen-key-constants.py do not edit) .. csv-table:: Functional key codes :header: "Name", "CSI", "Name", "CSI" "ESCAPE", "``27 u``", "ENTER", "``13 u``" "TAB", "``9 u``", "BACKSPACE", "``127 u``" "INSERT", "``2 ~``", "DELETE", "``3 ~``" "LEFT", "``1 D``", "RIGHT", "``1 C``" "UP", "``1 A``", "DOWN", "``1 B``" "PAGE_UP", "``5 ~``", "PAGE_DOWN", "``6 ~``" "HOME", "``1 H or 7 ~``", "END", "``1 F or 8 ~``" "CAPS_LOCK", "``57358 u``", "SCROLL_LOCK", "``57359 u``" "NUM_LOCK", "``57360 u``", "PRINT_SCREEN", "``57361 u``" "PAUSE", "``57362 u``", "MENU", "``57363 u``" "F1", "``1 P or 11 ~``", "F2", "``1 Q or 12 ~``" "F3", "``13 ~``", "F4", "``1 S or 14 ~``" "F5", "``15 ~``", "F6", "``17 ~``" "F7", "``18 ~``", "F8", "``19 ~``" "F9", "``20 ~``", "F10", "``21 ~``" "F11", "``23 ~``", "F12", "``24 ~``" "F13", "``57376 u``", "F14", "``57377 u``" "F15", "``57378 u``", "F16", "``57379 u``" "F17", "``57380 u``", "F18", "``57381 u``" "F19", "``57382 u``", "F20", "``57383 u``" "F21", "``57384 u``", "F22", "``57385 u``" "F23", "``57386 u``", "F24", "``57387 u``" "F25", "``57388 u``", "F26", "``57389 u``" "F27", "``57390 u``", "F28", "``57391 u``" "F29", "``57392 u``", "F30", "``57393 u``" "F31", "``57394 u``", "F32", "``57395 u``" "F33", "``57396 u``", "F34", "``57397 u``" "F35", "``57398 u``", "KP_0", "``57399 u``" "KP_1", "``57400 u``", "KP_2", "``57401 u``" "KP_3", "``57402 u``", "KP_4", "``57403 u``" "KP_5", "``57404 u``", "KP_6", "``57405 u``" "KP_7", "``57406 u``", "KP_8", "``57407 u``" "KP_9", "``57408 u``", "KP_DECIMAL", "``57409 u``" "KP_DIVIDE", "``57410 u``", "KP_MULTIPLY", "``57411 u``" "KP_SUBTRACT", "``57412 u``", "KP_ADD", "``57413 u``" "KP_ENTER", "``57414 u``", "KP_EQUAL", "``57415 u``" "KP_SEPARATOR", "``57416 u``", "KP_LEFT", "``57417 u``" "KP_RIGHT", "``57418 u``", "KP_UP", "``57419 u``" "KP_DOWN", "``57420 u``", "KP_PAGE_UP", "``57421 u``" "KP_PAGE_DOWN", "``57422 u``", "KP_HOME", "``57423 u``" "KP_END", "``57424 u``", "KP_INSERT", "``57425 u``" "KP_DELETE", "``57426 u``", "KP_BEGIN", "``1 E or 57427 ~``" "MEDIA_PLAY", "``57428 u``", "MEDIA_PAUSE", "``57429 u``" "MEDIA_PLAY_PAUSE", "``57430 u``", "MEDIA_REVERSE", "``57431 u``" "MEDIA_STOP", "``57432 u``", "MEDIA_FAST_FORWARD", "``57433 u``" "MEDIA_REWIND", "``57434 u``", "MEDIA_TRACK_NEXT", "``57435 u``" "MEDIA_TRACK_PREVIOUS", "``57436 u``", "MEDIA_RECORD", "``57437 u``" "LOWER_VOLUME", "``57438 u``", "RAISE_VOLUME", "``57439 u``" "MUTE_VOLUME", "``57440 u``", "LEFT_SHIFT", "``57441 u``" "LEFT_CONTROL", "``57442 u``", "LEFT_ALT", "``57443 u``" "LEFT_SUPER", "``57444 u``", "LEFT_HYPER", "``57445 u``" "LEFT_META", "``57446 u``", "RIGHT_SHIFT", "``57447 u``" "RIGHT_CONTROL", "``57448 u``", "RIGHT_ALT", "``57449 u``" "RIGHT_SUPER", "``57450 u``", "RIGHT_HYPER", "``57451 u``" "RIGHT_META", "``57452 u``", "ISO_LEVEL3_SHIFT", "``57453 u``" "ISO_LEVEL5_SHIFT", "``57454 u``" .. end functional key table .. }}} .. note:: The escape codes above of the form ``CSI 1 letter`` will omit the ``1`` if there are no modifiers, since ``1`` is the default value. .. note:: The original version of this specification allowed F3 to be encoded as both CSI R and CSI ~. However, CSI R conflicts with the Cursor Position Report, so it was removed. .. _ctrl_mapping: Legacy :kbd:`ctrl` mapping of ASCII keys ------------------------------------------ When the :kbd:`ctrl` key and another key are pressed on the keyboard, terminals map the result *for some keys* to a *C0 control code* i.e. an value from ``0 - 31``. This mapping was historically dependent on the layout of hardware terminal keyboards and is not specified anywhere, completely. The best known reference is `Table 3-5 in the VT-100 docs `_. The table below provides a mapping that is a commonly used superset of the table above. Any ASCII keys not in the table must be left untouched by :kbd:`ctrl`. .. {{{ .. start ctrl mapping (auto generated by gen-key-constants.py do not edit) .. csv-table:: Emitted bytes when :kbd:`ctrl` is held down and a key is pressed :header: "Key", "Byte", "Key", "Byte", "Key", "Byte" "SPC ", "0", "/", "31", "0", "48" "1", "49", "2", "0", "3", "27" "4", "28", "5", "29", "6", "30" "7", "31", "8", "127", "9", "57" "?", "127", "@", "0", "[", "27" "\\", "28", "]", "29", "^", "30" "_", "31", "a", "1", "b", "2" "c", "3", "d", "4", "e", "5" "f", "6", "g", "7", "h", "8" "i", "9", "j", "10", "k", "11" "l", "12", "m", "13", "n", "14" "o", "15", "p", "16", "q", "17" "r", "18", "s", "19", "t", "20" "u", "21", "v", "22", "w", "23" "x", "24", "y", "25", "z", "26" "~", "30" .. end ctrl mapping .. }}} .. _fixterms_bugs: Bugs in fixterms ------------------- The following is a list of errata in the `original fixterms proposal `_, corrected in this specification. * No way to disambiguate :kbd:`Esc` key presses, other than using 8-bit controls which are undesirable for other reasons * Incorrectly claims special keys are sometimes encoded using ``CSI letter`` encodings when it is actually ``SS3 letter`` in all terminals newer than a VT-52, which is pretty much everything. * :kbd:`ctrl+shift+tab` should be ``CSI 9 ; 6 u`` not ``CSI 1 ; 5 Z`` (shift+tab is not a separate key from tab) * No support for the :kbd:`super` modifier. * Makes no mention of cursor key mode and how it changes encodings * Incorrectly encoding shifted keys when shift modifier is used, for instance, for :kbd:`ctrl+shift+i` is encoded as :kbd:`ctrl+I`. * No way to have non-conflicting escape codes for :kbd:`alt+letter`, :kbd:`ctrl+letter`, :kbd:`ctrl+alt+letter` key presses * No way to specify both shifted and unshifted keys for robust shortcut matching (think matching :kbd:`ctrl+shift+equal` and :kbd:`ctrl+plus`) * No way to specify alternate layout key. This is useful for keyboard layouts such as Cyrillic where you want the shortcut :kbd:`ctrl+c` to work when pressing the :kbd:`ctrl+С` on the keyboard. * No way to report repeat and release key events, only key press events * No way to report key events for presses that generate text, useful for gaming. Think of using the :kbd:`WASD` keys to control movement. * Only a small subset of all possible functional keys are assigned numbers. * Claims the ``CSI u`` escape code has no fixed meaning, but has been used for decades as ``SCORC`` for instance by xterm and ansi.sys and `DECSMBV `_ by the VT-510 hardware terminal. This doesn't really matter since these uses are for communication to the terminal not from the terminal. * Handwaves that :kbd:`ctrl` *tends to* mask with ``0x1f``. In actual fact it does this only for some keys. The action of :kbd:`ctrl` is not specified and varies between terminals, historically because of different keyboard layouts. Why xterm's modifyOtherKeys should not be used --------------------------------------------------- * Does not support release events * Does not fix the issue of :kbd:`Esc` key presses not being distinguishable from escape codes. * Does not fix the issue of some keypresses generating identical bytes and thus being indistinguishable * There is no robust way to query it or manage its state from a program running in the terminal. * No support for shifted keys. * No support for alternate keyboard layouts. * No support for modifiers beyond the basic four. * No support for lock keys like Num lock and Caps lock. * Is completely unspecified. The most discussion of it available anywhere is `here `__ And it contains no specification of what numbers to assign to what function keys beyond running a Perl script on an X11 system!! kitty-0.41.1/docs/kittens/0000775000175000017510000000000014773370543014720 5ustar nileshnileshkitty-0.41.1/docs/kittens/broadcast.rst0000664000175000017510000000154114773370543017415 0ustar nileshnileshbroadcast ================================================== .. only:: man Overview -------------- *Type text in all kitty windows simultaneously* The ``broadcast`` kitten can be used to type text simultaneously in all :term:`kitty windows ` (or a subset as desired). To use it, simply create a mapping in :file:`kitty.conf` such as:: map f1 launch --allow-remote-control kitty +kitten broadcast Then press the :kbd:`F1` key and whatever you type in the newly created window will be sent to all kitty windows. You can use the options described below to control which windows are selected. For example, only broadcast to other windows in the current tab:: map f1 launch --allow-remote-control kitty +kitten broadcast --match-tab state:focused .. program:: kitty +kitten broadcast .. include:: /generated/cli-kitten-broadcast.rst kitty-0.41.1/docs/kittens/choose-fonts.rst0000664000175000017510000001356714773370543020075 0ustar nileshnileshChanging kitty fonts ======================== .. only:: man Overview -------------- Terminal aficionados spend all day staring at text, as such, getting text rendering just right is very important. kitty has extremely powerful facilities for fine-tuning text rendering. It supports `OpenType features `__ to select alternate glyph shapes, and `Variable fonts `__ to control the weight or spacing of a font precisely. You can also :opt:`select which font is used to render particular unicode codepoints ` and you can :opt:`modify font metrics ` and even :opt:`adjust the gamma curves ` used for rendering text onto the background color. The first step is to select the font faces kitty will use for rendering regular, bold and italic text. kitty comes with a convenient UI for choosing fonts, in the form of the *choose-fonts* kitten. Simply run:: kitten choose-fonts and follow the on screen prompts. First, choose the family you want, the list of families can be easily filtered by typing a few letters from the family name you are looking for. The family selection screen shows you a preview of how the family looks. .. image:: ../screenshots/family-selection.png :alt: Choosing a family with the choose fonts kitten :width: 600 Once you select a family by pressing the :kbd:`Enter` key, you are shown previews of what the regular, bold and italic faces look like for that family. You can choose to fine tune any of the faces. Start with fine-tuning the regular face by pressing the :kbd:`R` key. The other styles will be automatically adjusted based on what you select for the regular face. .. image:: ../screenshots/font-fine-tune.png :alt: Fine tune a font by choosing a precise weight and features :width: 600 You can choose a specific style or font feature by clicking on it. A precise value for any variable axes can be selected using the slider, in the screenshot above, the font supports precise weight adjustment. If you are lucky the font designer has included descriptive names for font features, which will be displayed, if not, consult the documentation of the font to see what each feature does. .. _font_spec_syntax: The font specification syntax -------------------------------- If you don't like the choose fonts kitten or simply want to understand and write font selection options into :file:`kitty.conf` yourself, read on. There are four font face selection keys: `font_family`, `bold_font`, `italic_font` and `bold_italic_font`. Each of these supports the syntax described below. Their values can be of three types, either a font family name, the keyword ``auto`` or an extended ``key=value`` syntax for specifying font selection precisely. If a font family name is specified kitty will use Operating System APIs to search for a matching font. The keyword ``auto`` means kitty will choose a font completely automatically, typically this is used for automatically selecting bold/italic variants once the :opt:`font_family` is set. The bold and italic variants will then automatically use the same set of features as the main face. To specify font face selection more precisely, a ``key=value`` syntax is used. First, let's look at a few examples:: # Select by family only, actual face selection is automatic font_family family="Fira Code" # Select an exact face by Postscript name font_family postscript_name=FiraCode # Select an exact face by family with features and variable weight font_family family=SourceCodeVF variable_name=SourceCodeUpright features="+zero cv01=2" wght=380 The following are the known keys, any other keys are names of *variable axes*, that is, they are used to set the variable value for some font characteristic. ``family`` A font family name. A family typically has multiple actual font faces, such as bold and italic variants. One or more of the faces can even be variable, allowing fine tuning of font characteristics. ``style`` A style name to choose a particular font from a given family. Useful only with the ``family`` key, when no more precise methods for face selection are specified. Can also be used to specify a named variable style for variable fonts. ``postscript_name`` The actual postscript name for a font face. This allows selecting a particular variant within a font family. But note that postscript names are usually insufficient for selecting variable fonts. ``full_name`` This can be used to select a particular font face in a family. However, it is less precise than ``postscript_name`` and should not generally be used. ``variable_name`` Some families with variable fonts actually contain multiple font files. For example, a family could have variable weights with one font file containing upright variable weight faces and another containing italic variable weight faces. Well designed fonts use a *variable name* to distinguish between such files. Should be used in conjunction with ``family`` to select a particular variable font file. ``features`` A space separated list of OpenType font features to enable/disable or select a value of, for this font. Consult the documentation for the font family to see what features it supports and their effects. The exact syntax for specifying features is `documented by HarfBuzz `__ ``system`` This can be used to pass an arbitrary string, usuall a family or full name to the OS font selection APIs. Should not be used in conjunction with any other keys. Is the same as specifying just the font name without any keys. In addition to these keys, any four letter key is treated as the name of a variable characteristic of the font. It's value is used to set the value for the name. kitty-0.41.1/docs/kittens/clipboard.rst0000664000175000017510000000354514773370543017420 0ustar nileshnileshclipboard ================================================== .. only:: man Overview -------------- *Copy/paste to the system clipboard from shell scripts* .. highlight:: sh The ``clipboard`` kitten can be used to read or write to the system clipboard from the shell. It even works over SSH. Using it is as simple as:: echo hooray | kitten clipboard All text received on :file:`STDIN` is copied to the clipboard. To get text from the clipboard:: kitten clipboard --get-clipboard The text will be written to :file:`STDOUT`. Note that by default kitty asks for permission when a program attempts to read the clipboard. This can be controlled via :opt:`clipboard_control`. .. versionadded:: 0.27.0 Support for copying arbitrary data types The clipboard kitten can be used to send/receive more than just plain text from the system clipboard. You can transfer arbitrary data types. Best illustrated with some examples:: # Copy an image to the clipboard: kitten clipboard picture.png # Copy an image and some text to the clipboard: kitten clipboard picture.jpg text.txt # Copy text from STDIN and an image to the clipboard: echo hello | kitten clipboard picture.png /dev/stdin # Copy any raster image available on the clipboard to a PNG file: kitten clipboard -g picture.png # Copy an image to a file and text to STDOUT: kitten clipboard -g picture.png /dev/stdout # List the formats available on the system clipboard kitten clipboard -g -m . /dev/stdout Normally, the kitten guesses MIME types based on the file names. To control the MIME types precisely, use the :option:`--mime ` option. This kitten uses a new protocol developed by kitty to function, for details, see :doc:`/clipboard`. .. program:: kitty +kitten clipboard .. include:: /generated/cli-kitten-clipboard.rst kitty-0.41.1/docs/kittens/custom.rst0000664000175000017510000004040614773370543016770 0ustar nileshnileshCustom kittens ================= You can easily create your own kittens to extend kitty. They are just terminal programs written in Python. When launching a kitten, kitty will open an overlay window over the current window and optionally pass the contents of the current window/scrollback to the kitten over its :file:`STDIN`. The kitten can then perform whatever actions it likes, just as a normal terminal program. After execution of the kitten is complete, it has access to the running kitty instance so it can perform arbitrary actions such as closing windows, pasting text, etc. Let's see a simple example of creating a kitten. It will ask the user for some input and paste it into the terminal window. Create a file in the kitty config directory, :file:`~/.config/kitty/mykitten.py` (you might need to adjust the path to wherever the :ref:`kitty config directory ` is on your machine). .. code-block:: python from kitty.boss import Boss def main(args: list[str]) -> str: # this is the main entry point of the kitten, it will be executed in # the overlay window when the kitten is launched answer = input('Enter some text: ') # whatever this function returns will be available in the # handle_result() function return answer def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) -> None: # get the kitty window into which to paste answer w = boss.window_id_map.get(target_window_id) if w is not None: w.paste_text(answer) Now in :file:`kitty.conf` add the lines:: map ctrl+k kitten mykitten.py Start kitty and press :kbd:`Ctrl+K` and you should see the kitten running. The best way to develop your own kittens is to modify one of the built-in kittens. Look in the `kittens sub-directory `__ of the kitty source code for those. Or see below for a list of :ref:`third-party kittens `, that other kitty users have created. kitty API to use with kittens ------------------------------- Kittens have full access to internal kitty APIs. However these are neither entirely stable nor documented. You can instead use the kitty :doc:`Remote control API
`. Simply call :code:`boss.call_remote_control()`, with the same arguments you would pass to ``kitten @``. For example: .. code-block:: python def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) -> None: # get the kitty window to which to send text w = boss.window_id_map.get(target_window_id) if w is not None: boss.call_remote_control(w, ('send-text', f'--match=id:{w.id}', 'hello world')) .. note:: Inside handle_result() the active window is still the window in which the kitten was run, therefore when using call_remote_control() be sure to pass the appropriate option to select the target window, usually ``--match`` as shown above or ``--self``. Run, ``kitten @ --help`` in a kitty terminal, to see all the remote control commands available to you. Passing arguments to kittens ------------------------------ You can pass arguments to kittens by defining them in the map directive in :file:`kitty.conf`. For example:: map ctrl+k kitten mykitten.py arg1 arg2 These will be available as the ``args`` parameter in the ``main()`` and ``handle_result()`` functions. Note also that the current working directory of the kitten is set to the working directory of whatever program is running in the active kitty window. The special argument ``@selection`` is replaced by the currently selected text in the active kitty window. Passing the contents of the screen to the kitten --------------------------------------------------- If you would like your kitten to have access to the contents of the screen and/or the scrollback buffer, you just need to add an annotation to the ``handle_result()`` function, telling kitty what kind of input your kitten would like. For example: .. code-block:: py from kitty.boss import Boss # in main, STDIN is for the kitten process and will contain # the contents of the screen def main(args: list[str]) -> str: return sys.stdin.read() # in handle_result, STDIN is for the kitty process itself, rather # than the kitten process and should not be read from. from kittens.tui.handler import result_handler @result_handler(type_of_input='text') def handle_result(args: list[str], stdin_data: str, target_window_id: int, boss: Boss) -> None: pass This will send the plain text of the active window to the kitten's :file:`STDIN`. There are many other types of input you can ask for, described in the table below: .. table:: Types of input to kittens :align: left =========================== ======================================================================================================= Keyword Type of :file:`STDIN` input =========================== ======================================================================================================= ``text`` Plain text of active window ``ansi`` Formatted text of active window ``screen`` Plain text of active window with line wrap markers ``screen-ansi`` Formatted text of active window with line wrap markers ``history`` Plain text of active window and its scrollback ``ansi-history`` Formatted text of active window and its scrollback ``screen-history`` Plain text of active window and its scrollback with line wrap markers ``screen-ansi-history`` Formatted text of active window and its scrollback with line wrap markers ``output`` Plain text of the output from the last run command ``output-screen`` Plain text of the output from the last run command with wrap markers ``output-ansi`` Formatted text of the output from the last run command ``output-screen-ansi`` Formatted text of the output from the last run command with wrap markers ``selection`` The text currently selected with the mouse =========================== ======================================================================================================= In addition to ``output``, that gets the output of the last run command, ``last_visited_output`` gives the output of the command last jumped to and ``first_output`` gives the output of the first command currently on screen. These can also be combined with ``screen`` and ``ansi`` for formatting. .. note:: For the types based on the output of a command, :ref:`shell_integration` is required. Using kittens to script kitty, without any terminal UI ----------------------------------------------------------- If you would like your kitten to script kitty, without bothering to write a terminal program, you can tell the kittens system to run the ``handle_result()`` function without first running the ``main()`` function. For example, here is a kitten that "zooms in/zooms out" the current terminal window by switching to the stack layout or back to the previous layout. This is equivalent to the builtin :ac:`toggle_layout` action. Create a Python file in the :ref:`kitty config directory `, :file:`~/.config/kitty/zoom_toggle.py` .. code-block:: py from kitty.boss import Boss def main(args: list[str]) -> str: pass from kittens.tui.handler import result_handler @result_handler(no_ui=True) def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) -> None: tab = boss.active_tab if tab is not None: if tab.current_layout.name == 'stack': tab.last_used_layout() else: tab.goto_layout('stack') Now in :file:`kitty.conf` add:: map f11 kitten zoom_toggle.py Pressing :kbd:`F11` will now act as a zoom toggle function. You can get even more fancy, switching the kitty OS window to fullscreen as well as changing the layout, by simply adding the line:: boss.toggle_fullscreen() to the ``handle_result()`` function, above. .. _send_mouse_event: Sending mouse events -------------------- If the program running in a window is receiving mouse events, you can simulate those using:: from kitty.fast_data_types import send_mouse_event send_mouse_event(screen, x, y, button, action, mods) ``screen`` is the ``screen`` attribute of the window you want to send the event to. ``x`` and ``y`` are the 0-indexed coordinates. ``button`` is a number using the same numbering as X11 (left: ``1``, middle: ``2``, right: ``3``, scroll up: ``4``, scroll down: ``5``, scroll left: ``6``, scroll right: ``7``, back: ``8``, forward: ``9``). ``action`` is one of ``PRESS``, ``RELEASE``, ``DRAG`` or ``MOVE``. ``mods`` is a bitmask of ``GLFW_MOD_{mod}`` where ``{mod}`` is one of ``SHIFT``, ``CONTROL`` or ``ALT``. All the mentioned constants are imported from ``kitty.fast_data_types``. For example, to send a left click at position x: 2, y: 3 to the active window:: from kitty.fast_data_types import send_mouse_event, PRESS send_mouse_event(boss.active_window.screen, 2, 3, 1, PRESS, 0) The function will only send the event if the program is receiving events of that type, and will return ``True`` if it sent the event, and ``False`` if not. .. _kitten_main_rc: Using remote control inside the main() kitten function ------------------------------------------------------------ You can use kitty's remote control features inside the main() function of a kitten, even without enabling remote control. This is useful if you want to probe kitty for more information before presenting some UI to the user or if you want the user to be able to control kitty from within your kitten's UI rather than after it has finished running. To enable it, simply tell kitty your kitten requires remote control, as shown in the example below:: import json import sys from pprint import pprint from kittens.tui.handler import kitten_ui @kitten_ui(allow_remote_control=True) def main(args: list[str]) -> str: # get the result of running kitten @ ls cp = main.remote_control(['ls'], capture_output=True) if cp.returncode != 0: sys.stderr.buffer.write(cp.stderr) raise SystemExit(cp.returncode) output = json.loads(cp.stdout) pprint(output) # open a new tab with a title specified by the user title = input('Enter the name of tab: ') window_id = main.remote_control(['launch', '--type=tab', '--tab-title', title], check=True, capture_output=True).stdout.decode() return window_id :code:`allow_remote_control=True` tells kitty to run this kitten with remote control enabled, regardless of whether it is enabled globally or not. To run a remote control command use the :code:`main.remote_control()` function which is a thin wrapper around Python's :code:`subprocess.run` function. Note that by default, for security, child processes launched by your kitten cannot use remote control, thus it is necessary to use :code:`main.remote_control()`. If you wish to enable child processes to use remote control, call :code:`main.allow_indiscriminate_remote_control()`. Remote control access can be further secured by using :code:`kitten_ui(allow_remote_control=True, remote_control_password='ls set-colors')`. This will use a secure generated password to restrict remote control. You can specify a space separated list of remote control commands to allow, see :opt:`remote_control_password` for details. The password value is accessible as :code:`main.password` and is used by :code:`main.remote_control()` automatically. Debugging kittens -------------------- The part of the kitten that runs in ``main()`` is just a normal program and the output of print statements will be visible in the kitten window. Or alternately, you can use:: from kittens.tui.loop import debug debug('whatever') The ``debug()`` function is just like ``print()`` except that the output will appear in the ``STDOUT`` of the kitty process inside which the kitten is running. The ``handle_result()`` part of the kitten runs inside the kitty process. The output of print statements will go to the ``STDOUT`` of the kitty process. So if you run kitty from another kitty instance, the output will be visible in the first kitty instance. Adding options to kittens ---------------------------- If you would like to use kitty's config framework to make your kittens configurable, you will need some boilerplate. Put the following files in the directory of your kitten. :file:`kitten_options_definition.py` .. code-block:: python from kitty.conf.types import Action, Definition definition = Definition( '!kitten_options_utils', Action( 'map', 'parse_map', {'key_definitions': 'kitty.conf.utils.KittensKeyMap'}, ['kitty.types.ParsedShortcut', 'kitty.conf.utils.KeyAction'] ), ) agr = definition.add_group egr = definition.end_group opt = definition.add_option map = definition.add_map # main options {{{ agr('main', 'Main') opt('some_option', '33', option_type='some_option_parser', long_text=''' Help text for this option ''' ) egr() # }}} # shortcuts {{{ agr('shortcuts', 'Keyboard shortcuts') map('Quit', 'quit q quit') egr() # }}} :file:`kitten_options_utils.py` .. code-block:: python from kitty.conf.utils import KittensKeyDefinition, key_func, parse_kittens_key func_with_args, args_funcs = key_func() FuncArgsType = Tuple[str, Sequence[Any]] def some_option_parser(val: str) -> int: return int(val) + 3000 def parse_map(val: str) -> Iterable[KittensKeyDefinition]: x = parse_kittens_key(val, args_funcs) if x is not None: yield x Then run:: kitty +runpy 'from kitty.conf.generate import main; main()' /path/to/kitten_options_definition.py You can parse and read the options in your kitten using the following code: .. code-block:: python from .kitten_options_types import Options, defaults from kitty.conf.utils import load_config as _load_config, parse_config_base from typing import Optional, Iterable, Dict, Any def load_config(*paths: str, overrides: Optional[Iterable[str]] = None) -> Options: from .kitten_options_parse import ( create_result_dict, merge_result_dicts, parse_conf_item ) def parse_config(lines: Iterable[str]) -> Dict[str, Any]: ans: Dict[str, Any] = create_result_dict() parse_config_base( lines, parse_conf_item, ans, ) return ans overrides = tuple(overrides) if overrides is not None else () opts_dict, found_paths = _load_config(defaults, parse_config, merge_result_dicts, *paths, overrides=overrides) opts = Options(opts_dict) opts.config_paths = found_paths opts.all_config_paths = paths opts.config_overrides = overrides return opts See `the code `__ for the builtin :doc:`diff kitten ` for examples of creating more options and keyboard shortcuts. Developing builtin kittens for inclusion with kitty ---------------------------------------------------------- There is documentation for :doc:`developing-builtin-kittens` which are written in the Go language. .. _external_kittens: Kittens created by kitty users --------------------------------------------- `vim-kitty-navigator `_ Allows you to navigate seamlessly between vim and kitty splits using a consistent set of hotkeys. `smart-scroll `_ Makes the kitty scroll bindings work in full screen applications `gattino `__ Integrate kitty with an LLM to convert plain language prompts into shell commands. :iss:`insert password <1222>` Insert a password from a CLI password manager, taking care to only do it at a password prompt. `weechat-hints `_ URL hints kitten for WeeChat that works without having to use WeeChat's raw-mode. kitty-0.41.1/docs/kittens/developing-builtin-kittens.rst0000664000175000017510000000654614773370543022744 0ustar nileshnileshDeveloping builtin kittens ============================= Builtin kittens in kitty are written in the Go language, with small Python wrapper scripts to define command line options and handle UI integration. Getting started ----------------------- To get started with creating a builtin kitten, one that will become part of kitty and be available as ``kitten my-kitten``, create a directory named :file:`my_kitten` in the :file:`kittens` directory. Then, in this directory add three, files: :file:`__init__.py` (an empty file), :file:`__main__.py` and :file:`main.go`. Template for `main.py` ^^^^^^^^^^^^^^^^^^^^^^ The file :file:`main.py` contains the command line option definitions for your kitten. Change the actual options and help text below as needed. .. code-block:: python #!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import sys # See the file kitty/cli.py in the kitty sourcecode for more examples of # the syntax for defining options OPTIONS = r''' --some-string-option -s default=my_default_value Help text for a simple option taking a string value. --some-boolean-option -b type=bool-set Help text for a boolean option defaulting to false. --some-inverted-boolean-option type=bool-unset Help text for a boolean option defaulting to true. --an-integer-option type=int default=13 bla bla --an-enum-option choices=a,b,c,d default=a This option can only take the values a, b, c, or d '''.format help_text = '''\ The introductory help text for your kitten. Can contain multiple paragraphs with :bold:`bold` :green:`colored`, :code:`code`, :link:`links ` etc. formatting. Option help strings can also use this formatting. ''' # The usage string for your kitten usage = 'TITLE [BODY ...]' short_description = 'some short description of your kitten it will show up when running kitten without arguments to list all kittens` if __name__ == '__main__': raise SystemExit('This should be run as kitten my-kitten') elif __name__ == '__doc__': cd = sys.cli_docs # type: ignore cd['usage'] = usage cd['options'] = OPTIONS cd['help_text'] = help_text cd['short_desc'] = short_description Template for `main.go` ^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: go package my_kitten import ( "fmt" "kitty/tools/cli" ) var _ = fmt.Print func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) { // Here rc is the exit code for the kitten which should be 1 or higher if err is not nil fmt.Println("Hello world!") fmt.Println(args) fmt.Println(fmt.Sprintf("%#v", opts)) return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } Edit :file:`tools/cmd/tool/main.go` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add the entry point of the kitten into :file:`tools/cmd/tool/main.go`. First, import the kitten into this file. To do this, add :code:`"kitty/kittens/my_kitten"` into the :code:`import ( ... )` section at the top. Then, add ``my_kitten.EntryPoint(root)`` into ``func KittyToolEntryPoints(root *cli.Command)`` and you are done. After running make you should be able to test your kitten by running:: kitten my-kitten kitty-0.41.1/docs/kittens/diff.rst0000664000175000017510000001020214773370543016355 0ustar nileshnileshkitty-diff ================================================================================ *A fast side-by-side diff tool with syntax highlighting and images* .. highlight:: sh Major Features ----------------- .. container:: major-features * Displays diffs side-by-side in the kitty terminal * Does syntax highlighting of the displayed diffs, asynchronously, for maximum speed * Displays images as well as text diffs, even over SSH * Does recursive directory diffing .. figure:: ../screenshots/diff.png :alt: Screenshot, showing a sample diff :align: center :width: 100% Screenshot, showing a sample diff Installation --------------- Simply :ref:`install kitty `. Usage -------- In the kitty terminal, run:: kitten diff file1 file2 to see the diff between :file:`file1` and :file:`file2`. Create an alias in your shell's startup file to shorten the command, for example: .. code-block:: sh alias d="kitten diff" Now all you need to do to diff two files is:: d file1 file2 You can also pass directories instead of files to see the recursive diff of the directory contents. Keyboard controls ---------------------- =========================== =========================== Action Shortcut =========================== =========================== Quit :kbd:`Q`, :kbd:`Esc` Scroll line up :kbd:`K`, :kbd:`Up` Scroll line down :kbd:`J`, :kbd:`Down` Scroll page up :kbd:`PgUp` Scroll page down :kbd:`PgDn` Scroll to top :kbd:`Home` Scroll to bottom :kbd:`End` Scroll to next page :kbd:`Space`, :kbd:`PgDn` Scroll to previous page :kbd:`PgUp` Scroll to next change :kbd:`N` Scroll to previous change :kbd:`P` Increase lines of context :kbd:`+` Decrease lines of context :kbd:`-` All lines of context :kbd:`A` Restore default context :kbd:`=` Search forwards :kbd:`/` Search backwards :kbd:`?` Clear search :kbd:`Esc` Scroll to next match :kbd:`>`, :kbd:`.` Scroll to previous match :kbd:`<`, :kbd:`,` Copy selection to clipboard :kbd:`y` Copy selection or exit :kbd:`Ctrl+C` =========================== =========================== Integrating with git ----------------------- Add the following to :file:`~/.gitconfig`: .. code-block:: ini [diff] tool = kitty guitool = kitty.gui [difftool] prompt = false trustExitCode = true [difftool "kitty"] cmd = kitten diff $LOCAL $REMOTE [difftool "kitty.gui"] cmd = kitten diff $LOCAL $REMOTE Now to use kitty-diff to view git diffs, you can simply do:: git difftool --no-symlinks --dir-diff Once again, creating an alias for this command is useful. Why does this work only in kitty? ---------------------------------------- The diff kitten makes use of various features that are :doc:`kitty only `, such as the :doc:`kitty graphics protocol `, the :doc:`extended keyboard protocol `, etc. It also leverages terminal program infrastructure I created for all of kitty's other kittens to reduce the amount of code needed (the entire implementation is under 3000 lines of code). And fundamentally, it's kitty only because I wrote it for myself, and I am highly unlikely to use any other terminals :) Configuration ------------------------ You can configure the colors used, keyboard shortcuts, the diff implementation, the default lines of context, etc. by creating a :file:`diff.conf` file in your :ref:`kitty config folder `. See below for the supported configuration directives. .. include:: /generated/conf-kitten-diff.rst .. include:: /generated/cli-kitten-diff.rst Sample diff.conf ----------------- You can download a sample :file:`diff.conf` file with all default settings and comments describing each setting by clicking: :download:`sample diff.conf `. kitty-0.41.1/docs/kittens/hints.rst0000664000175000017510000001222014773370543016574 0ustar nileshnileshHints ========== .. only:: man Overview -------------- |kitty| has a *hints mode* to select and act on arbitrary text snippets currently visible on the screen. For example, you can press :sc:`open_url` to choose any URL visible on the screen and then open it using your default web browser. .. figure:: ../screenshots/hints_mode.png :alt: URL hints mode :align: center :width: 100% URL hints mode Similarly, you can press :sc:`insert_selected_path` to select anything that looks like a path or filename and then insert it into the terminal, very useful for picking files from the output of a :program:`git` or :program:`ls` command and adding them to the command line for the next command. You can also press :sc:`goto_file_line` to select anything that looks like a path or filename followed by a colon and a line number and open the file in your default editor at the specified line number (opening at line number will work only if your editor supports the +linenum command line syntax or is a "known" editor). The patterns and editor to be used can be modified using options passed to the kitten. For example:: map ctrl+g kitten hints --type=linenum --linenum-action=tab nvim +{line} {path} will open the selected file in a new tab inside `Neovim `__ when you press :kbd:`Ctrl+G`. Pressing :sc:`open_selected_hyperlink` will open :term:`hyperlinks`, i.e. a URL that has been marked as such by the program running in the terminal, for example, by ``ls --hyperlink=auto``. If :program:`ls` comes with your OS does not support hyperlink, you may need to install `GNU Coreutils `__. You can also :doc:`customize what actions are taken for different types of URLs <../open_actions>`. .. note:: If there are more hints than letters, hints will use multiple letters. In this case, when you press the first letter, only hints starting with that letter are displayed. Pressing the second letter will select that hint or press :kbd:`Enter` or :kbd:`Space` to select the empty hint. For mouse lovers, the hints kitten also allows you to click on any matched text to select it instead of typing the hint character. The hints kitten is very powerful to see more detailed help on its various options and modes of operation, see below. You can use these options to create mappings in :file:`kitty.conf` to select various different text snippets. See :sc:`insert_selected_path ` for examples. Completely customizing the matching and actions of the kitten --------------------------------------------------------------- The hints kitten supports writing simple Python scripts that can be used to completely customize how it finds matches and what happens when a match is selected. This allows the hints kitten to provide the user interface, while you can provide the logic for finding matches and performing actions on them. This is best illustrated with an example. Create the file :file:`custom-hints.py` in the :ref:`kitty config directory ` with the following contents: .. code-block:: python import re def mark(text, args, Mark, extra_cli_args, *a): # This function is responsible for finding all # matching text. extra_cli_args are any extra arguments # passed on the command line when invoking the kitten. # We mark all individual word for potential selection for idx, m in enumerate(re.finditer(r'\w+', text)): start, end = m.span() mark_text = text[start:end].replace('\n', '').replace('\0', '') # The empty dictionary below will be available as groupdicts # in handle_result() and can contain string keys and arbitrary JSON # serializable values. yield Mark(idx, start, end, mark_text, {}) def handle_result(args, data, target_window_id, boss, extra_cli_args, *a): # This function is responsible for performing some # action on the selected text. # matches is a list of the selected entries and groupdicts contains # the arbitrary data associated with each entry in mark() above matches, groupdicts = [], [] for m, g in zip(data['match'], data['groupdicts']): if m: matches.append(m), groupdicts.append(g) for word, match_data in zip(matches, groupdicts): # Lookup the word in a dictionary, the open_url function # will open the provided url in the system browser boss.open_url(f'https://www.google.com/search?q=define:{word}') Now run kitty with:: kitty -o 'map f1 kitten hints --customize-processing custom-hints.py' When you press the :kbd:`F1` key you will be able to select a word to look it up in the Google dictionary. .. include:: ../generated/cli-kitten-hints.rst .. note:: To avoid having to specify the same command line options on every invocation, you can use the :opt:`action_alias` option in :file:`kitty.conf`, creating aliases that have common sets of options. For example:: action_alias myhints kitten hints --alphabet qfjdkslaureitywovmcxzpq1234567890 map f1 myhints --customize-processing custom-hints.py kitty-0.41.1/docs/kittens/hyperlinked_grep.rst0000664000175000017510000000652314773370543021013 0ustar nileshnileshHyperlinked grep ================= .. only:: man Overview -------------- .. note:: As of ripgrep versions newer that 13.0 it supports hyperlinks natively so you can just add the following alias in your shell rc file: ``alias rg="rg --hyperlink-format=kitty"`` no need to use this kitten. But, see below for instructions on how to customize kitty to have it open the hyperlinks from ripgrep in your favorite editor. This kitten allows you to search your files using `ripgrep `__ and open the results directly in your favorite editor in the terminal, at the line containing the search result, simply by clicking on the result you want. .. versionadded:: 0.19.0 To set it up, first create :file:`~/.config/kitty/open-actions.conf` with the following contents: .. code:: conf # Open any file with a fragment in vim, fragments are generated # by the hyperlink-grep kitten and nothing else so far. protocol file fragment_matches [0-9]+ action launch --type=overlay --cwd=current vim +${FRAGMENT} -- ${FILE_PATH} # Open text files without fragments in the editor protocol file mime text/* action launch --type=overlay --cwd=current -- ${EDITOR} -- ${FILE_PATH} Now, run a search with:: kitten hyperlinked-grep something Hold down the :kbd:`Ctrl+Shift` keys and click on any of the result lines, to open the file in :program:`vim` at the matching line. If you use some editor other than :program:`vim`, you should adjust the :file:`open-actions.conf` file accordingly. TO open links with the keyboard instead, use :sc:`open_selected_hyperlink`. Finally, add an alias to your shell's rc files to invoke the kitten as :command:`hg`:: alias hg="kitten hyperlinked-grep" You can now run searches with:: hg some-search-term To learn more about kitty's powerful framework for customizing URL click actions, see :doc:`here
`. By default, this kitten adds hyperlinks for several parts of ripgrep output: the per-file header, match context lines, and match lines. You can control which items are linked with a :code:`--kitten hyperlink` flag. For example, :code:`--kitten hyperlink=matching_lines` will only add hyperlinks to the match lines. :code:`--kitten hyperlink=file_headers,context_lines` will link file headers and context lines but not match lines. :code:`--kitten hyperlink=none` will cause the command line to be passed to directly to :command:`rg` so no hyperlinking will be performed. :code:`--kitten hyperlink` may be specified multiple times. Hopefully, someday this functionality will make it into some `upstream grep `__ program directly removing the need for this kitten. .. note:: While you can pass any of ripgrep's command line options to the kitten and they will be forwarded to :program:`rg`, do not use options that change the output formatting as the kitten works by parsing the output from ripgrep. The unsupported options are: :code:`--context-separator`, :code:`--field-context-separator`, :code:`--field-match-separator`, :code:`--json`, :code:`-I --no-filename`, :code:`-0 --null`, :code:`--null-data`, :code:`--path-separator`. If you specify options via configuration file, then any changes to the default output format will not be supported, not just the ones listed. kitty-0.41.1/docs/kittens/icat.rst0000664000175000017510000000530414773370543016374 0ustar nileshnileshicat ======================================== .. only:: man Overview -------------- *Display images in the terminal* The ``icat`` kitten can be used to display arbitrary images in the |kitty| terminal. Using it is as simple as:: kitten icat image.jpeg It supports all image types supported by `ImageMagick `__. It even works over SSH. For details, see the :doc:`kitty graphics protocol `. You might want to create an alias in your shell's configuration files:: alias icat="kitten icat" Then you can simply use ``icat image.png`` to view images. .. note:: `ImageMagick `__ must be installed for the full range of image types. Without it only PNG/JPG/GIF/BMP/TIFF/WEBP are supported. .. note:: kitty's image display protocol may not work when used within a terminal multiplexer such as :program:`screen` or :program:`tmux`, depending on whether the multiplexer has added support for it or not. .. program:: kitty +kitten icat The ``icat`` kitten has various command line arguments to allow it to be used from inside other programs to display images. In particular, :option:`--place`, :option:`--detect-support` and :option:`--print-window-size`. If you are trying to integrate icat into a complex program like a file manager or editor, there are a few things to keep in mind. icat normally works by communicating over the TTY device, it both writes to and reads from the TTY. So it is imperative that while it is running the host program does not do any TTY I/O. Any key presses or other input from the user on the TTY device will be discarded. If you would instead like to use it just as a backend to generate the escape codes for image display, you need to pass it options to tell it the window dimensions, where to place the image in the window and the transfer mode to use. If you do that, it will not try to communicate with the TTY device at all. The requisite options are: :option:`--use-window-size`, :option:`--place` and :option:`--transfer-mode`, :option:`--stdin=no`. For example, to demonstrate usage without access to the TTY: .. code:: sh zsh -c 'setsid kitten icat --stdin=no --use-window-size $COLUMNS,$LINES,3000,2000 --transfer-mode=file myimage.png' Here, ``setsid`` ensures icat has no access to the TTY device. The values, 3000, 2000 are made up. They are the window width and height in pixels, to obtain which access to the TTY is needed. To be really robust you should consider writing proper support for the :doc:`kitty graphics protocol ` in the program instead. Nowadays there are many libraries that have support for it. .. include:: /generated/cli-kitten-icat.rst kitty-0.41.1/docs/kittens/notify.rst0000664000175000017510000000242014773370543016760 0ustar nileshnileshnotify ================================================== .. only:: man Overview -------------- Show pop-up system notifications. .. highlight:: sh .. versionadded:: 0.36.0 The notify kitten The ``notify`` kitten can be used to show pop-up system notifications from the shell. It even works over SSH. Using it is as simple as:: kitten notify "Good morning" Hello world, it is a nice day! To add an icon, use:: kitten notify --icon-path /path/to/some/image.png "Good morning" Hello world, it is a nice day! kitten notify --icon firefox "Good morning" Hello world, it is a nice day! To be informed when the notification is activated:: kitten notify --wait-for-completion "Good morning" Hello world, it is a nice day! Then, the kitten will wait till the notification is either closed or activated. If activated, a ``0`` is printed to :file:`STDOUT`. You can press the :kbd:`Esc` or :kbd:`Ctrl+c` keys to abort, closing the notification. To add buttons to the notification:: kitten notify --wait-for-completion --button One --button Two "Good morning" Hello world, it is a nice day! .. program:: kitty +kitten notify .. tip:: Learn about the underlying :doc:`/desktop-notifications` escape code protocol. .. include:: /generated/cli-kitten-notify.rst kitty-0.41.1/docs/kittens/panel.rst0000664000175000017510000000365314773370543016560 0ustar nileshnileshDraw a GPU accelerated dock panel on your desktop ==================================================================================================== .. highlight:: sh .. only:: man Overview -------------- You can use this kitten to draw a GPU accelerated panel on the edge of your screen or as the desktop wallpaper, that shows the output from an arbitrary terminal program. It is useful for showing status information or notifications on your desktop using terminal programs instead of GUI toolkits. .. figure:: ../screenshots/panel.png :alt: Screenshot, showing a sample panel :align: center :width: 100% Screenshot, showing a sample panel The screenshot above shows a sample panel that displays the current desktop and window title as well as miscellaneous system information such as network activity, CPU load, date/time, etc. .. versionadded:: 0.34.0 Support for Wayland .. note:: This kitten currently only works on X11 desktops and Wayland compositors that support the `wlr layer shell protocol `__ (which is almost all of them except the, as usual, crippled GNOME). Using this kitten is simple, for example:: kitty +kitten panel sh -c 'printf "\n\n\nHello, world."; sleep 5s' This will show ``Hello, world.`` at the top edge of your screen for five seconds. Here, the terminal program we are running is :program:`sh` with a script to print out ``Hello, world!``. You can make the terminal program as complex as you like, as demonstrated in the screenshot above. If you are on Wayland, you can, for instance run:: kitty +kitten panel --edge=background htop to display htop as your desktop background. Remember this works in everything but GNOME and also, in sway, you have to disable the background wallpaper as sway renders that over the panel kitten surface. .. include:: ../generated/cli-kitten-panel.rst kitty-0.41.1/docs/kittens/query_terminal.rst0000664000175000017510000000162314773370543020514 0ustar nileshnileshQuery terminal ================= .. only:: man Overview -------------- This kitten is used to query |kitty| from terminal programs about version, values of various runtime options controlling its features, etc. The querying is done using the (*semi*) standard XTGETTCAP escape sequence pioneered by xterm, so it works over SSH as well. The downside is that it is slow, since it requires a roundtrip to the terminal emulator and back. If you want to do some of the same querying in your terminal program without depending on the kitten, you can do so, by processing the same escape codes. Search `this page `__ for *XTGETTCAP* to see the syntax for the escape code. The kitty specific keys are all documented below, when sent via escape code they must be prefixed with ``kitty-query-``. .. include:: ../generated/cli-kitten-query_terminal.rst kitty-0.41.1/docs/kittens/remote_file.rst0000664000175000017510000000330314773370543017743 0ustar nileshnileshRemote files ============== .. only:: man Overview -------------- |kitty| has the ability to easily *Edit*, *Open* or *Download* files from a computer into which you are SSHed. In your SSH session run:: ls --hyperlink=auto Then hold down :kbd:`Ctrl+Shift` and click the name of the file. .. figure:: ../screenshots/remote_file.png :alt: Remote file actions :align: center :width: 100% Remote file actions |kitty| will ask you what you want to do with the remote file. You can choose to *Edit* it in which case kitty will download it and open it locally in your :envvar:`EDITOR`. As you make changes to the file, they are automatically transferred to the remote computer. Note that this happens without needing to install *any* special software on the server, beyond :program:`ls` that supports hyperlinks. .. seealso:: See the :ref:`edit-in-kitty ` command .. seealso:: See the :doc:`transfer` kitten .. versionadded:: 0.19.0 .. note:: For best results, use this kitten with the :doc:`ssh kitten <./ssh>`. Otherwise, nested SSH sessions are not supported. The kitten will always try to copy remote files from the first SSH host. This is because, without the ssh kitten, there is no way for |kitty| to detect and follow a nested SSH session robustly. Use the :doc:`transfer` kitten for such situations. .. note:: If you have not setup automatic password-less SSH access, and are not using the ssh kitten, then, when editing starts you will be asked to enter your password just once, thereafter the SSH connection will be re-used. Similarly, you can choose to save the file to the local computer or download and open it in its default file handler. kitty-0.41.1/docs/kittens/ssh.rst0000664000175000017510000002012214773370543016244 0ustar nileshnileshTruly convenient SSH ========================================= .. only:: man Overview ---------------- * Automatic :ref:`shell_integration` on remote hosts * Easily :ref:`clone local shell/editor config ` on remote hosts * Automatic :opt:`re-use of existing connections ` to avoid connection setup latency * Make the kitten binary available in the remote host :opt:`on demand ` * Easily :opt:`change terminal colors ` when connecting to remote hosts * Automatically :opt:`forward the kitty remote control socket ` to configured hosts .. versionadded:: 0.25.0 Automatic shell integration, file transfer and reuse of connections .. versionadded:: 0.30.0 Automatic forwarding of remote control sockets The ssh kitten allows you to login easily to remote hosts, and automatically setup the environment there to be as comfortable as your local shell. You can specify environment variables to set on the remote host and files to copy there, making your remote experience just like your local shell. Additionally, it automatically sets up :ref:`shell_integration` on the remote host and copies the kitty terminfo database there. The ssh kitten is a thin wrapper around the traditional `ssh `__ command line program and supports all the same options and arguments and configuration. In interactive usage scenarios it is a drop in replacement for :program:`ssh`. To try it out, simply run: .. code-block:: sh kitten ssh some-hostname-to-connect-to You should end up at a shell prompt on the remote host, with shell integration enabled. If you like it you can add an alias to it in your shell's rc files: .. code-block:: sh alias s="kitten ssh" So now you can just type ``s hostname`` to connect. If you define a mapping in :file:`kitty.conf` such as:: map f1 new_window_with_cwd Then, pressing :kbd:`F1` will open a new window automatically logged into the same host using the ssh kitten, at the same directory. The ssh kitten can be configured using the :file:`~/.config/kitty/ssh.conf` file where you can specify environment variables to set on the remote host and files to copy from the local to the remote host. Let's see a quick example: .. code-block:: conf # Copy the files and directories needed to setup some common tools copy .zshrc .vimrc .vim # Setup some environment variables env SOME_VAR=x # COPIED_VAR will have the same value on the remote host as it does locally env COPIED_VAR=_kitty_copy_env_var_ # Create some per hostname settings hostname someserver-* copy env-files env SOMETHING=else hostname someuser@somehost copy --dest=foo/bar some-file copy --glob some/files.* See below for full details on the syntax and options of :file:`ssh.conf`. Additionally, you can pass config options on the command line: .. code-block:: sh kitten ssh --kitten interpreter=python servername The :code:`--kitten` argument can be specified multiple times, with directives from :file:`ssh.conf`. These override the final options used for the matched host, as if they had been appended to the end of the matching section for that host in :file:`ssh.conf`. They apply only to the host being SSHed to by this invocation, so any :opt:`hostname ` directives are ignored. .. warning:: Due to limitations in the design of SSH, any typing you do before the shell prompt appears may be lost. So ideally don't start typing till you see the shell prompt. 😇 .. _real_world_ssh_kitten_config: A real world example ---------------------- Suppose you often SSH into a production server, and you would like to setup your shell and editor there using your custom settings. However, other people could SSH in as well and you don't want to clobber their settings. Here is how this could be achieved using the ssh kitten with :program:`zsh` and :program:`vim` as the shell and editor, respectively: .. code-block:: conf # Have these settings apply to servers in my organization hostname myserver-* # Setup zsh to read its files from my-conf/zsh env ZDOTDIR=$HOME/my-conf/zsh copy --dest my-conf/zsh/.zshrc .zshrc copy --dest my-conf/zsh/.zshenv .zshenv # If you use other zsh init files add them in a similar manner # Setup vim to read its config from my-conf/vim env VIMINIT=$HOME/my-conf/vim/vimrc env VIMRUNTIME=$HOME/my-conf/vim copy --dest my-conf/vim .vim copy --dest my-conf/vim/vimrc .vimrc How it works ---------------- The ssh kitten works by having SSH transmit and execute a POSIX sh (or :opt:`optionally ` Python) bootstrap script on the remote host using an :opt:`interpreter `. This script reads setup data over the TTY device, which kitty sends as a Base64 encoded compressed tarball. The script extracts it and places the :opt:`files ` and sets the :opt:`environment variables ` before finally launching the :opt:`login shell ` with :opt:`shell integration ` enabled. The data is requested by the kitten over the TTY with a random one time password. kitty reads the request and if the password matches a password pre-stored in shared memory on the localhost by the kitten, the transmission is allowed. If your local `OpenSSH `__ version is >= 8.4 then the data is transmitted instantly without any roundtrip delay. .. note:: When connecting to BSD hosts, it is possible the bootstrap script will fail or run slowly, because the default shells are crippled in various ways. Your best bet is to install Python on the remote, make sure the login shell is something POSIX sh compliant, and use :code:`python` as the :opt:`interpreter ` in :file:`ssh.conf`. .. note:: This may or may not work when using terminal multiplexers, depending on whether they passthrough the escape codes and if the values of the environment variables :envvar:`KITTY_PID` and :envvar:`KITTY_WINDOW_ID` are correct in the current session (they can be wrong when connecting to a tmux session running in a different window) and the ssh kitten is run in the currently active multiplexer window. .. include:: /generated/conf-kitten-ssh.rst .. _ssh_copy_command: The copy command -------------------- .. include:: /generated/ssh-copy.rst .. _manual_terminfo_copy: Copying terminfo files manually ------------------------------------- Sometimes, the ssh kitten can fail, or maybe you dont like to use it. In such cases, the terminfo files can be copied over manually to a server with the following one liner:: infocmp -a xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin If you are behind a proxy (like Balabit) that prevents this, or you are SSHing into macOS where the :program:`tic` does not support reading from :file:`STDIN`, you must redirect the first command to a file, copy that to the server and run :program:`tic` manually. If you connect to a server, embedded, or Android system that doesn't have :program:`tic`, copy over your local file terminfo to the other system as :file:`~/.terminfo/x/xterm-kitty`. If the server is running a relatively modern Linux distribution and you have root access to it, you could simply install the ``kitty-terminfo`` package on the server to make the terminfo files available. Really, the correct solution for this is to convince the OpenSSH maintainers to have :program:`ssh` do this automatically, if possible, when connecting to a server, so that all terminals work transparently. If the server is running FreeBSD, or another system that relies on termcap rather than terminfo, you will need to convert the terminfo file on your local machine by running (on local machine with |kitty|):: infocmp -CrT0 xterm-kitty The output of this command is the termcap description, which should be appended to :file:`/usr/share/misc/termcap` on the remote server. Then run the following command to apply your change (on the server):: cap_mkdb /usr/share/misc/termcap kitty-0.41.1/docs/kittens/themes.rst0000664000175000017510000001246014773370543016742 0ustar nileshnileshChanging kitty colors ======================== .. only:: man Overview -------------- The themes kitten allows you to easily change color themes, from a collection of over three hundred pre-built themes available at `kitty-themes `_. To use it, simply run:: kitten themes .. image:: ../screenshots/themes.png :alt: The themes kitten in action :width: 600 The kitten allows you to pick a theme, with live previews of the colors. You can choose between light and dark themes and search by theme name by just typing a few characters from the name. The kitten maintains a list of recently used themes to allow quick switching. If you want to restore the colors to default, you can do so by choosing the ``Default`` theme. .. versionadded:: 0.23.0 The themes kitten How it works ---------------- A theme in kitty is just a :file:`.conf` file containing kitty settings. When you select a theme, the kitten simply copies the :file:`.conf` file to :file:`~/.config/kitty/current-theme.conf` and adds an include for :file:`current-theme.conf` to :file:`kitty.conf`. It also comments out any existing color settings in :file:`kitty.conf` so they do not interfere. Once that's done, the kitten sends kitty a signal to make it reload its config. .. note:: If you want to have some color settings in your :file:`kitty.conf` that the theme kitten does not override, move them into a separate conf file and ``include`` it into kitty.conf. The include should be placed after the inclusion of :file:`current-theme.conf` so that the settings in it override conflicting settings from :file:`current-theme.conf`. .. _auto_color_scheme: Change color themes automatically when the OS switches between light and dark -------------------------------------------------------------------------------- .. versionadded:: 0.38.0 You can have kitty automatically change its color theme when the OS switches between dark, light and no-preference modes. In order to do this, run the theme kitten as normal and at the final screen select the option to save your chosen theme as either light, dark, or no-preference. Repeat until you have chosen a theme for each of the three modes. Then, once you restart kitty, it will automatically use your chosen themes depending on the OS color scheme. This works by creating three files: :file:`dark-theme.auto.conf`, :file:`light-theme.auto.conf` and :file:`no-preference-theme.auto.conf` in the kitty config directory. When these files exist, kitty queries the OS for its color scheme and uses the appropriate file. Note that the colors in these files override all other colors, even those specified using the :option:`kitty --override` command line flag. kitty will also automatically change colors when the OS color scheme changes, for example, during night/day transitions. When using these colors, you can still dynamically change colors, but the next time the OS changes its color mode, any dynamic changes will be overridden. .. note:: On the GNOME desktop, the desktop reports the color preference as no-preference when the "Dark style" is not enabled. So use :file:`no-preference-theme.auto.conf` to select colors for light mode on GNOME. You can manually enable light style with ``gsettings set org.gnome.desktop.interface color-scheme prefer-light`` in which case GNOME will report the color scheme as light and kitty will use :file:`light-theme.auto.conf`. Using your own themes ----------------------- You can also create your own themes as :file:`.conf` files. Put them in the :file:`themes` sub-directory of the :ref:`kitty config directory `, usually, :file:`~/.config/kitty/themes`. The kitten will automatically add them to the list of themes. You can use this to modify the builtin themes, by giving the conf file the name :file:`Some theme name.conf` to override the builtin theme of that name. Here, ``Some theme name`` is the actual builtin theme name, not its file name. Note that after doing so you have to run the kitten and choose that theme once for your changes to be applied. Contributing new themes ------------------------- If you wish to contribute a new theme to the kitty theme repository, start by going to the `kitty-themes `__ repository. `Fork it `__, and use the file :download:`template.conf ` as a template when creating your theme. Once you are satisfied with how it looks, `submit a pull request `__ to have your theme merged into the `kitty-themes `__ repository, which will make it available in this kitten automatically. Changing the theme non-interactively --------------------------------------- You can specify the theme name as an argument when invoking the kitten to have it change to that theme instantly. For example:: kitten themes --reload-in=all Dimmed Monokai Will change the theme to ``Dimmed Monokai`` in all running kitty instances. See below for more details on non-interactive operation. .. include:: ../generated/cli-kitten-themes.rst kitty-0.41.1/docs/kittens/transfer.rst0000664000175000017510000000645614773370543017311 0ustar nileshnileshTransfer files ================ .. only:: man Overview -------------- .. versionadded:: 0.30.0 .. _rsync: https://en.wikipedia.org/wiki/Rsync Transfer files to and from remote computers over the ``TTY`` device itself. This means that file transfer works over nested SSH sessions, serial links, etc. Anywhere you have a terminal device, you can transfer files. .. image:: ../screenshots/transfer.png :alt: The transfer kitten at work This kitten supports transferring entire directory trees, preserving soft and hard links, file permissions, times, etc. It even supports the rsync_ protocol to transfer only changes to large files. .. seealso:: See the :doc:`remote_file` kitten Basic usage --------------- Simply ssh into a remote computer using the :doc:`ssh kitten ` and run the this kitten (which the ssh kitten makes available for you on the remote computer automatically). Some illustrative examples are below. To copy a file from a remote computer:: $ kitten ssh my-remote-computer $ kitten transfer some-file /path/on/local/computer This, will copy :file:`some-file` from the computer into which you have SSHed to your local computer at :file:`/path/on/local/computer`. kitty will ask you for confirmation before allowing the transfer, so that the file transfer protocol cannot be abused to read/write files on your computer. To copy a file from your local computer to the remote computer:: $ kitten ssh my-remote-computer $ kitten transfer --direction=upload /path/on/local/computer remote-file For more detailed usage examples, see the command line interface section below. .. note:: If you dont want to use the ssh kitten, you can install the kitten binary on the remote machine yourself, it is a standalone, statically compiled binary available from the `kitty releases page `__. Or you can write your own script/program to use the underlying :doc:`file transfer protocol `. Avoiding the confirmation prompt ------------------------------------ Normally, when you start a file transfer kitty will prompt you for confirmation. This is to ensure that hostile programs running on a remote machine cannot read/write files on your computer without your permission. If the remote machine is trusted, then you can disable the confirmation prompt by: #. Setting the :opt:`file_transfer_confirmation_bypass` option to some password. #. When invoking the kitten use the :option:`--permissions-bypass ` to supply the password you set in step one. .. warning:: Using a password to bypass confirmation means any software running on the remote machine could potentially learn that password and use it to gain full access to your computer. Delta transfers ----------------------------------- This kitten has the ability to use the rsync_ protocol to only transfer the differences between files. To turn it on use the :option:`--transmit-deltas ` option. Note that this will actually be slower when transferring small files or on a very fast network, because of round trip overhead, so use with care. .. include:: ../generated/cli-kitten-transfer.rst kitty-0.41.1/docs/kittens/unicode_input.rst0000664000175000017510000000267714773370543020333 0ustar nileshnileshUnicode input ================ .. only:: man Overview -------------- You can input Unicode characters by name, hex code, recently used and even an editable favorites list. Press :sc:`input_unicode_character` to start the unicode input kitten, shown below. .. figure:: ../screenshots/unicode.png :alt: A screenshot of the unicode input kitten :align: center :width: 100% A screenshot of the unicode input kitten In :guilabel:`Code` mode, you enter a Unicode character by typing in the hex code for the character and pressing :kbd:`Enter`. For example, type in ``2716`` and press :kbd:`Enter` to get ``✖``. You can also choose a character from the list of recently used characters by typing a leading period ``.`` and then the two character index and pressing :kbd:`Enter`. The :kbd:`Up` and :kbd:`Down` arrow keys can be used to choose the previous and next Unicode symbol respectively. In :guilabel:`Name` mode you instead type words from the character name and use the :kbd:`ArrowKeys` / :kbd:`Tab` to select the character from the displayed matches. You can also type a space followed by a period and the index for the match if you don't like to use arrow keys. You can switch between modes using either the keys :kbd:`F1` ... :kbd:`F4` or :kbd:`Ctrl+1` ... :kbd:`Ctrl+4` or by pressing :kbd:`Ctrl+[` and :kbd:`Ctrl+]` or by pressing :kbd:`Ctrl+Tab` and :kbd:`Ctrl+Shift+Tab`. .. include:: ../generated/cli-kitten-unicode_input.rst kitty-0.41.1/docs/kittens_intro.rst0000664000175000017510000000503714773370543016672 0ustar nileshnilesh.. _kittens: Extend with kittens ----------------------- .. toctree:: :hidden: :glob: kittens/icat kittens/diff kittens/unicode_input kittens/themes kittens/choose-fonts kittens/hints kittens/remote_file kittens/hyperlinked_grep kittens/transfer kittens/ssh kittens/custom kittens/* |kitty| has a framework for easily creating terminal programs that make use of its advanced features. These programs are called kittens. They are used both to add features to |kitty| itself and to create useful standalone programs. Some prominent kittens: :doc:`icat ` Display images in the terminal. :doc:`diff ` A fast, side-by-side diff for the terminal with syntax highlighting and images. :doc:`Unicode input ` Easily input arbitrary Unicode characters in |kitty| by name or hex code. :doc:`Themes ` Preview and quick switch between over three hundred color themes. :doc:`Fonts ` Preview, fine-tune and quick switch the fonts used by kitty. :doc:`Hints ` Select and open/paste/insert arbitrary text snippets such as URLs, filenames, words, lines, etc. from the terminal screen. :doc:`Remote file ` Edit, open, or download remote files over SSH easily, by simply clicking on the filename. :doc:`Transfer files ` Transfer files and directories seamlessly and easily from remote machines over your existing SSH sessions with a simple command. :doc:`Hyperlinked grep ` Search your files using `ripgrep `__ and open the results directly in your favorite editor in the terminal, at the line containing the search result, simply by clicking on the result you want. :doc:`Broadcast ` Type in one :term:`kitty window ` and have it broadcast to all (or a subset) of other :term:`kitty windows `. :doc:`SSH ` SSH with automatic :ref:`shell integration `, connection re-use for low latency and easy cloning of local shell and editor configuration to the remote host. :doc:`Panel ` Draw a GPU accelerated dock panel on your desktop showing the output from an arbitrary terminal program. :doc:`Clipboard ` Copy/paste to the clipboard from shell scripts, even over SSH. You can also :doc:`Learn to create your own kittens `. kitty-0.41.1/docs/launch.rst0000664000175000017510000001705714773370543015255 0ustar nileshnileshThe :command:`launch` command -------------------------------- .. program:: launch |kitty| has a :code:`launch` action that can be used to run arbitrary programs in new windows/tabs. It can be mapped to user defined shortcuts in :file:`kitty.conf`. It is very powerful and allows sending the contents of the current window to the launched program, as well as many other options. In the simplest form, you can use it to open a new kitty window running the shell, as shown below:: map f1 launch To run a different program simply pass the command line as arguments to launch:: map f1 launch vim path/to/some/file To open a new window with the same working directory as the currently active window:: map f1 launch --cwd=current To open the new window in a new tab:: map f1 launch --type=tab To run multiple commands in a shell, use:: map f1 launch sh -c "ls && exec zsh" To pass the contents of the current screen and scrollback to the started process:: map f1 launch --stdin-source=@screen_scrollback less There are many more powerful options, refer to the complete list below. .. note:: To avoid duplicating launch actions with frequently used parameters, you can use :opt:`action_alias` to define launch action aliases. For example:: action_alias launch_tab launch --cwd=current --type=tab map f1 launch_tab vim map f2 launch_tab emacs The :kbd:`F1` key will now open :program:`vim` in a new tab with the current windows working directory. The piping environment -------------------------- When using :option:`launch --stdin-source`, the program to which the data is piped has a special environment variable declared, :envvar:`KITTY_PIPE_DATA` whose contents are:: KITTY_PIPE_DATA={scrolled_by}:{cursor_x},{cursor_y}:{lines},{columns} where ``scrolled_by`` is the number of lines kitty is currently scrolled by, ``cursor_(x|y)`` is the position of the cursor on the screen with ``(1,1)`` being the top left corner and ``{lines},{columns}`` being the number of rows and columns of the screen. Special arguments ------------------- There are a few special placeholder arguments that can be specified as part of the command line: ``@selection`` Replaced by the currently selected text. ``@active-kitty-window-id`` Replaced by the id of the currently active kitty window. ``@line-count`` Replaced by the number of lines in STDIN. Only present when passing some data to STDIN. ``@input-line-number`` Replaced by the number of lines a pager should scroll to match the current scroll position in kitty. See :opt:`scrollback_pager` for details. ``@scrolled-by`` Replaced by the number of lines kitty is currently scrolled by. ``@cursor-x`` Replaced by the current cursor x position with 1 being the leftmost cell. ``@cursor-y`` Replaced by the current cursor y position with 1 being the topmost cell. ``@first-line-on-screen`` Replaced by the first line on screen. Can be used for pager positioning. ``@last-line-on-screen`` Replaced by the last line on screen. Can be used for pager positioning. For example:: map f1 launch my-program @active-kitty-window-id .. _watchers: Watching launched windows --------------------------- The :option:`launch --watcher` option allows you to specify Python functions that will be called at specific events, such as when the window is resized or closed. Note that you can also specify watchers that are loaded for all windows, via :opt:`watcher`. To create a watcher, specify the path to a Python module that specifies callback functions for the events you are interested in, for create :file:`~/.config/kitty/mywatcher.py` and use :option:`launch --watcher` = :file:`mywatcher.py`: .. code-block:: python # ~/.config/kitty/mywatcher.py from typing import Any from kitty.boss import Boss from kitty.window import Window def on_load(boss: Boss, data: dict[str, Any]) -> None: # This is a special function that is called just once when this watcher # module is first loaded, can be used to perform any initializztion/one # time setup. Any exceptions in this function are printed to kitty's # STDERR but otherwise ignored. ... def on_resize(boss: Boss, window: Window, data: dict[str, Any]) -> None: # Here data will contain old_geometry and new_geometry # Note that resize is also called the first time a window is created # which can be detected as old_geometry will have all zero values, in # particular, old_geometry.xnum and old_geometry.ynum will be zero. ... def on_focus_change(boss: Boss, window: Window, data: dict[str, Any])-> None: # Here data will contain focused ... def on_close(boss: Boss, window: Window, data: dict[str, Any])-> None: # called when window is closed, typically when the program running in # it exits ... def on_set_user_var(boss: Boss, window: Window, data: dict[str, Any]) -> None: # called when a "user variable" is set or deleted on a window. Here # data will contain key and value ... def on_title_change(boss: Boss, window: Window, data: dict[str, Any]) -> None: # called when the window title is changed on a window. Here # data will contain title and from_child. from_child will be True # when a title change was requested via escape code from the program # running in the terminal ... def on_cmd_startstop(boss: Boss, window: Window, data: dict[str, Any]) -> None: # called when the shell starts/stops executing a command. Here # data will contain is_start, cmdline and time. ... def on_color_scheme_preference_change(boss: Boss, window: Window, data: dict[str, Any]) -> None: # called when the color scheme preference of this window changes from # light to dark or vice versa. data contains is_dark and via_escape_code # the latter will be true if the color scheme was changed via escape # code received from the program running in the window ... Every callback is passed a reference to the global ``Boss`` object as well as the ``Window`` object the action is occurring on. The ``data`` object is a dict that contains event dependent data. You have full access to kitty internals in the watcher scripts, however kitty internals are not documented/stable so for most things you are better off using the kitty :doc:`Remote control API
`. Simply call :code:`boss.call_remote_control()`, with the same arguments you would pass to ``kitten @``. For example: .. code-block:: python def on_resize(boss: Boss, window: Window, data: dict[str, Any]) -> None: # send some text to the resized window boss.call_remote_control(window, ('send-text', f'--match=id:{window.id}', 'hello world')) Run, ``kitten @ --help`` in a kitty terminal, to see all the remote control commands available to you. Finding executables ----------------------- When you specify a command to run as just a name rather than an absolute path, it is searched for in the system-wide :envvar:`PATH` environment variable. Note that this **may not** be the value of :envvar:`PATH` inside a shell, as shell startup scripts often change the value of this variable. If it is not found there, then a system specific list of default paths is searched. If it is still not found, then your shell is run and the value of :envvar:`PATH` inside the shell is used. See :opt:`exe_search_path` for details and how to control this. Syntax reference ------------------ .. include:: /generated/launch.rst kitty-0.41.1/docs/layouts.rst0000664000175000017510000003060214773370543015472 0ustar nileshnileshArrange windows ------------------- kitty has the ability to define its own windows that can be tiled next to each other in arbitrary arrangements, based on *Layouts*, see below for examples: .. figure:: screenshots/screenshot.png :alt: Screenshot, showing three programs in the 'Tall' layout :align: center :width: 100% Screenshot, showing :program:`vim`, :program:`tig` and :program:`git` running in |kitty| with the *Tall* layout .. figure:: screenshots/splits.png :alt: Screenshot, showing windows in the 'Splits' layout :align: center :width: 100% Screenshot, showing windows with arbitrary arrangement in the *Splits* layout There are many different layouts available. They are all enabled by default, you can switch layouts using :ac:`next_layout` (:sc:`next_layout` by default). To control which layouts are available use :opt:`enabled_layouts`, the first listed layout becomes the default. Individual layouts and how to use them are described below. The Stack Layout ------------------ This is the simplest layout. It displays a single window using all available space, other windows are hidden behind it. This layout has no options:: enabled_layouts stack The Tall Layout ------------------ Displays one (or optionally more) full-height windows on the left half of the screen. Remaining windows are tiled vertically on the right half of the screen. There are options to control how the screen is split horizontally ``bias`` (an integer between ``10`` and ``90``) and options to control how many full-height windows there are ``full_size`` (a positive integer). The ``mirrored`` option when set to ``true`` will cause the full-height windows to be on the right side of the screen instead of the left. The syntax for the options is:: enabled_layouts tall:bias=50;full_size=1;mirrored=false ┌──────────────┬───────────────┐ │ │ │ │ │ │ │ │ │ │ ├───────────────┤ │ │ │ │ │ │ │ │ │ │ ├───────────────┤ │ │ │ │ │ │ │ │ │ └──────────────┴───────────────┘ In addition, you can map keys to increase or decrease the number of full-height windows, or toggle the mirrored setting, for example:: map ctrl+[ layout_action decrease_num_full_size_windows map ctrl+] layout_action increase_num_full_size_windows map ctrl+/ layout_action mirror toggle map ctrl+y layout_action mirror true map ctrl+n layout_action mirror false You can also map a key to change the bias by providing a list of percentages and it will rotate through the list as you press the key. If you only provide one number it'll toggle between that percentage and 50, for example:: map ctrl+. layout_action bias 50 62 70 map ctrl+, layout_action bias 62 The Fat Layout ---------------- Displays one (or optionally more) full-width windows on the top half of the screen. Remaining windows are tiled horizontally on the bottom half of the screen. There are options to control how the screen is split vertically ``bias`` (an integer between ``10`` and ``90``) and options to control how many full-width windows there are ``full_size`` (a positive integer). The ``mirrored`` option when set to ``true`` will cause the full-width windows to be on the bottom of the screen instead of the top. The syntax for the options is:: enabled_layouts fat:bias=50;full_size=1;mirrored=false ┌──────────────────────────────┐ │ │ │ │ │ │ │ │ ├─────────┬──────────┬─────────┤ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────────┴──────────┴─────────┘ This layout also supports the same layout actions as the *Tall* layout, shown above. The Grid Layout -------------------- Display windows in a balanced grid with all windows the same size except the last column if there are not enough windows to fill the grid. This layout has no options:: enabled_layouts grid ┌─────────┬──────────┬─────────┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─────────┼──────────┼─────────┤ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────────┴──────────┴─────────┘ .. _splits_layout: The Splits Layout -------------------- This is the most flexible layout. You can create any arrangement of windows by splitting existing windows repeatedly. To best use this layout you should define a few extra key bindings in :file:`kitty.conf`:: # Create a new window splitting the space used by the existing one so that # the two windows are placed one above the other map f5 launch --location=hsplit # Create a new window splitting the space used by the existing one so that # the two windows are placed side by side map f6 launch --location=vsplit # Create a new window splitting the space used by the existing one so that # the two windows are placed side by side if the existing window is wide or # one above the other if the existing window is tall. map f4 launch --location=split # Rotate the current split, changing its split axis from vertical to # horizontal or vice versa map f7 layout_action rotate # Move the active window in the indicated direction map shift+up move_window up map shift+left move_window left map shift+right move_window right map shift+down move_window down # Move the active window to the indicated screen edge map ctrl+shift+up layout_action move_to_screen_edge top map ctrl+shift+left layout_action move_to_screen_edge left map ctrl+shift+right layout_action move_to_screen_edge right map ctrl+shift+down layout_action move_to_screen_edge bottom # Switch focus to the neighboring window in the indicated direction map ctrl+left neighboring_window left map ctrl+right neighboring_window right map ctrl+up neighboring_window up map ctrl+down neighboring_window down # Set the bias of the split containing the currently focused window. The # currently focused window will take up the specified percent of its parent # window's size. map ctrl+. layout_action bias 80 Windows can be resized using :ref:`window_resizing`. You can swap the windows in a split using the ``rotate`` action with an argument of ``180`` and rotate and swap with an argument of ``270``. This layout takes one option, ``split_axis`` that controls whether new windows are placed into vertical or horizontal splits when a :option:`--location ` is not specified. A value of ``horizontal`` (same as ``--location=vsplit``) means when a new split is created the two windows will be placed side by side and a value of ``vertical`` (same as ``--location=hsplit``) means the two windows will be placed one on top of the other. A value of ``auto`` means the axis of the split is chosen automatically (same as ``--location=split``). By default:: enabled_layouts splits:split_axis=horizontal ┌──────────────┬───────────────┐ │ │ │ │ │ │ │ │ │ │ ├───────┬───────┤ │ │ │ │ │ │ │ │ │ │ │ │ │ ├───────┴───────┤ │ │ │ │ │ │ │ │ │ └──────────────┴───────────────┘ .. versionadded:: 0.17.0 The Splits layout The Horizontal Layout ------------------------ All windows are shown side by side. This layout has no options:: enabled_layouts horizontal ┌─────────┬──────────┬─────────┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────────┴──────────┴─────────┘ The Vertical Layout ----------------------- All windows are shown one below the other. This layout has no options:: enabled_layouts vertical ┌──────────────────────────────┐ │ │ │ │ │ │ ├──────────────────────────────┤ │ │ │ │ │ │ ├──────────────────────────────┤ │ │ │ │ │ │ └──────────────────────────────┘ .. _window_resizing: Resizing windows ------------------ You can resize windows inside layouts. Press :sc:`start_resizing_window` (also :kbd:`⌘+r` on macOS) to enter resizing mode and follow the on-screen instructions. In a given window layout only some operations may be possible for a particular window. For example, in the *Tall* layout you can make the first window wider/narrower, but not taller/shorter. Note that what you are resizing is actually not a window, but a row/column in the layout, all windows in that row/column will be resized. You can also define shortcuts in :file:`kitty.conf` to make the active window wider, narrower, taller, or shorter by mapping to the :ac:`resize_window` action, for example:: map ctrl+left resize_window narrower map ctrl+right resize_window wider map ctrl+up resize_window taller map ctrl+down resize_window shorter 3 # reset all windows in the tab to default sizes map ctrl+home resize_window reset The :ac:`resize_window` action has a second optional argument to control the resizing increment (a positive integer that defaults to 1). Some layouts take options to control their behavior. For example, the *Fat* and *Tall* layouts accept the ``bias`` and ``full_size`` options to control how the available space is split up. To specify the option, in :opt:`kitty.conf ` use:: enabled_layouts tall:bias=70;full_size=2 This will have ``2`` instead of a single tall window, that occupy ``70%`` instead of ``50%`` of available width. ``bias`` can be any number between ``10`` and ``90``. Writing a new layout only requires about two hundred lines of code, so if there is some layout you want, take a look at one of the existing layouts in the `layout `__ package and submit a pull request! kitty-0.41.1/docs/mapping.rst0000664000175000017510000003223714773370543015433 0ustar nileshnilesh:orphan: Making your keyboard dance ============================== .. highlight:: conf kitty has extremely powerful facilities for mapping keyboard actions. Things like combining actions, multi-key mappings, modal mappings, mappings that send arbitrary text, and mappings dependent on the program currently running in kitty. Let's start with the basics. You can map a key press to an action in kitty using the following syntax:: map ctrl+a new_window_with_cwd This will map the key press :kbd:`Ctrl+a` to open a new :term:`window` with the working directory set to the working directory of the current window. This is the basic operation of the map directive, the tip of the iceberg, for more read the sections below. Combining multiple actions on a single keypress ----------------------------------------------------- Multiple actions can be combined on a single keypress, like a macro. To do this map the key press to the :ac:`combine` action:: map key combine action1 action2 action3 ... For example:: map kitty_mod+e combine : new_window : next_layout This will create a new window and switch to the next available layout. You can also run arbitrarily powerful scripts on a key press. There are two major techniques for doing this, using remote control scripts or using kittens. Remote control scripts ^^^^^^^^^^^^^^^^^^^^^^^^^ These can be written in any language and use the "kitten" binary to control kitty via its extensive :doc:`Remote control ` API. First, if you just want to run a single remote control command on a key press, you can just do:: map f1 remote_control set-spacing margin=30 This will run the ``set-spacing`` command, changing window margins to 30 pixels. For more complex scripts, write a script file in any language you like and save it somewhere, preferably in the kitty configuration directory. Do not forget to make it executable. In the script file you run remote control commands by running the "kitten" binary, for example: .. code-block:: sh #!/bin/sh kitten @ set-spacing margin=30 kitten @ new_window ... The script can perform arbitrarily complex logic and actions, limited only by the remote control API, that you can browse by running ``kitten @ --help``. To run the script you created on a key press, use:: map f1 remote_control_script /path/to/myscript Kittens ^^^^^^^^^^^^^ Here, kittens refer to Python scripts. The scripts have two parts, one that runs as a regular command line program inside a kitty window to, for example, ask the user for some input and a second part that runs inside the kitty process itself and can perform any operation on the kitty UI, which is itself implemented in Python. However, the kitty internal API is not documented and can (very rarely) change, so kittens are harder to get started with than remote control scripts. To run a kitten on a key press:: map f1 kitten mykitten.py Many of kitty's features are themselves implemented as kittens, for example, :doc:`/kittens/unicode_input`, :doc:`/kittens/hints` and :doc:`/kittens/themes`. To learn about writing your own kittens, see :doc:`/kittens/custom`. Syntax for specifying keys ----------------------------- A mapping maps a key press to some action. In their most basic form, keypresses are :code:`modifier+key`. Keys are identified simply by their lowercase Unicode characters. For example: :code:`a` for the :kbd:`A` key, :code:`[` for the left square bracket key, etc. For functional keys, such as :kbd:`Enter` or :kbd:`Escape`, the names are present at :ref:`Functional key definitions `. For modifier keys, the names are :kbd:`ctrl` (:kbd:`control`, :kbd:`⌃`), :kbd:`shift` (:kbd:`⇧`), :kbd:`alt` (:kbd:`opt`, :kbd:`option`, :kbd:`⌥`), :kbd:`super` (:kbd:`cmd`, :kbd:`command`, :kbd:`⌘`). Additionally, you can use the name :opt:`kitty_mod` as a modifier, the default value of which is :kbd:`ctrl+shift`. The default kitty shortcuts are defined using this value, so by changing it in :file:`kitty.conf` you can change all the modifiers used by all the default shortcuts. On Linux, you can also use XKB names for functional keys that don't have kitty names. See :link:`XKB keys ` for a list of key names. The name to use is the part after the :code:`XKB_KEY_` prefix. Note that you can only use an XKB key name for keys that are not known as kitty keys. Finally, you can use raw system key codes to map keys, again only for keys that are not known as kitty keys. To see the system key code for a key, start kitty with the :option:`kitty --debug-input` option, kitty will output some debug text for every key event. In that text look for :code:`native_code`, the value of that becomes the key name in the shortcut. For example: .. code-block:: none on_key_input: glfw key: 0x61 native_code: 0x61 action: PRESS mods: none text: 'a' Here, the key name for the :kbd:`A` key is :code:`0x61` and you can use it with:: map ctrl+0x61 something This maps :kbd:`Ctrl+A` to something. Multi-key mappings -------------------- A mapping in kitty can involve pressing multiple keys in sequence, with the syntax shown below:: map key1>key2>key3 action For example:: map ctrl+f>2 set_font_size 20 The default mappings to run the :doc:`hints kitten ` to select text on the screen are examples of multi-key mappings. Unmapping default shortcuts ----------------------------- kitty comes with dozens of default keyboard mappings for common operations. See :doc:`actions` for the full list of actions and the default shortcuts that map to them. You can unmap an individual shortcut, so that it is passed on to the program running inside kitty, by mapping it to nothing, for example:: map kitty_mod+enter This unmaps the default shortcut :sc:`new_window` to open a new window. Almost all default shortcuts are of the form ``modifier + key`` where the modifier defaults to :kbd:`Ctrl+Shift` and can be changed using the :opt:`kitty_mod` setting in :file:`kitty.conf`. If you want to clear all default shortcuts, you can use :opt:`clear_all_shortcuts` in :file:`kitty.conf`. If you would like kitty to completely ignore a key event, not even sending it to the program running in the terminal, map it to :ac:`discard_event`:: map kitty_mod+f1 discard_event .. _conditional_mappings: Conditional mappings depending on the state of the focused window ---------------------------------------------------------------------- Sometimes, you may want different mappings to be active when running a particular program in kitty, perhaps because it has some native functionality that duplicates kitty functions or there is a conflict, etc. kitty has the ability to create mappings that work only when the currently focused window matches some criteria, such as when it has a particular title or user variable. Let's see some examples:: map --when-focus-on title:keyboard.protocol kitty_mod+t This will cause :kbd:`kitty_mod+t` (the default shortcut for opening a new tab) to be unmapped only when the focused window has :code:`keyboard protocol` in its title. Run the show-key kitten as:: kitten show-key -m kitty Press :kbd:`ctrl+shift+t` and instead of a new tab opening, you will see the key press being reported by the kitten. :code:`--when-focus-on` can test the focused window using very powerful criteria, see :ref:`search_syntax` for details. A more practical example unmaps the key when the focused window is running an editor:: map --when-focus-on var:in_editor kitty_mod+c In order to make this work, you need to configure your editor as show below: .. tab:: vim In :file:`~/.vimrc` add: .. code-block:: vim let &t_ti = &t_ti . "\033]1337;SetUserVar=in_editor=MQo\007" let &t_te = &t_te . "\033]1337;SetUserVar=in_editor\007" .. tab:: neovim In :file:`~/.config/nvim/init.lua` add: .. code-block:: lua vim.api.nvim_create_autocmd({ "VimEnter", "VimResume" }, { group = vim.api.nvim_create_augroup("KittySetVarVimEnter", { clear = true }), callback = function() io.stdout:write("\x1b]1337;SetUserVar=in_editor=MQo\007") end, }) vim.api.nvim_create_autocmd({ "VimLeave", "VimSuspend" }, { group = vim.api.nvim_create_augroup("KittyUnsetVarVimLeave", { clear = true }), callback = function() io.stdout:write("\x1b]1337;SetUserVar=in_editor\007") end, }) These cause the editor to set the :code:`in_editor` variable in kitty and unset it when exiting. As a result, the :kbd:`ctrl+shift+c` key will be passed to the editor instead of copying to clipboard. In the editor, you can map it to copy to the clipboard, thereby allowing use of a common shortcut both inside and outside the editor for copying to clipboard. Sending arbitrary text or keys to the program running in kitty -------------------------------------------------------------------------------- This is accomplished by using ``map`` with :sc:`send_text ` in :file:`kitty.conf`. For example:: map f1 send_text normal,application Hello, world! Now, pressing :kbd:`f1` will cause ``Hello, world!`` to show up at your shell prompt. To have the shell execute a command sent via ``send_text`` you need to also simulate pressing the enter key which is ``\r``. For example:: map f1 send_text normal,application echo Hello, world!\r Now, if you press :kbd:`f1` when at shell prompt it will run the ``echo Hello, world!`` command. To have one key press send another key press, use :ac:`send_key`:: map alt+s send_key ctrl+s This causes the program running in kitty to receive the :kbd:`ctrl+s` key when you press the :kbd:`alt+s` key. To see this in action, run:: kitten show-key -m kitty Which will print out what key events it receives. .. _modal_mappings: Modal mappings -------------------------- kitty has the ability, like vim, to use *modal* key maps. Except that unlike vim it allows you to define your own arbitrary number of modes. To create a new mode, use ``map --new-mode ``. For example, lets create a mode to manage windows: switching focus, moving the window, etc.:: # Create a new "manage windows" mode (mw) map --new-mode mw kitty_mod+f7 # Switch focus to the neighboring window in the indicated direction using arrow keys map --mode mw left neighboring_window left map --mode mw right neighboring_window right map --mode mw up neighboring_window up map --mode mw down neighboring_window down # Move the active window in the indicated direction map --mode mw shift+up move_window up map --mode mw shift+left move_window left map --mode mw shift+right move_window right map --mode mw shift+down move_window down # Resize the active window map --mode mw n resize_window narrower map --mode mw w resize_window wider map --mode mw t resize_window taller map --mode mw s resize_window shorter # Exit the manage window mode map --mode mw esc pop_keyboard_mode Now, if you run kitty as: .. code-block:: sh kitty -o enabled_layouts=vertical --session <(echo "launch\nlaunch\nlaunch") Press :kbd:`Ctrl+Shift+F7` to enter the mode and then press the up and down arrow keys to focus the next/previous window. Press :kbd:`Shift+Up` or :kbd:`Shift+Down` to move the active window up and down. Press :kbd:`t` to make the active window taller and :kbd:`s` to make it shorter. To exit the mode press :kbd:`Esc`. Pressing an unknown key while in a custom keyboard mode by default beeps. This can be controlled by the ``map --on-unknown`` option as shown below:: # Beep on unknown keys map --new-mode XXX --on-unknown beep ... # Ingore unknown keys silently map --new-mode XXX --on-unknown ignore ... # Beep and exit the keyboard mode on unknown key map --new-mode XXX --on-unknown end ... # Pass unknown keys to the program running in the active window map --new-mode XXX --on-unknown passthrough ... When a key matches an action in a custom keyboard mode, the action is performed and the custom keyboard mode remains in effect. If you would rather have the keyboard mode end after the action you can use ``map --on-action`` as shown below:: # Have this keyboard mode automatically exit after performing any action map --new-mode XXX --on-action end ... All mappable actions ------------------------ There is a list of :doc:`all mappable actions `. Debugging mapping issues ------------------------------ To debug mapping issues, kitty has several facilities. First, when you run kitty with the ``--debug-input`` command line flag it outputs details about all key events it receives form the system and how they are handled. To see what key events are sent to applications, run kitty like this:: kitty kitten show-key Press the keys you want to debug and the kitten will print out the bytes it receives. Note that this uses the legacy terminal keyboard protocol that does not support all keys and key events. To debug the :doc:`full kitty keyboard protocol that ` that is nowadays being adopted by more and more programs, use:: kitty kitten show-key -m kitty kitty-0.41.1/docs/marks.rst0000664000175000017510000000770214773370543015114 0ustar nileshnileshMark text on screen --------------------- kitty has the ability to mark text on the screen based on regular expressions. This can be useful to highlight words or phrases when browsing output from long running programs or similar. Lets start with a few examples: Examples ---------- Suppose we want to be able to highlight the word :code:`ERROR` in the current window. Add the following to :file:`kitty.conf`:: map f1 toggle_marker text 1 ERROR Now when you press :kbd:`F1`, all instances of the word :code:`ERROR` will be highlighted. To turn off the highlighting, press :kbd:`F1` again. If you want to make it case-insensitive, use:: map f1 toggle_marker itext 1 ERROR To make it match only complete words, use:: map f1 toggle_marker regex 1 \\bERROR\\b Suppose you want to highlight both :code:`ERROR` and :code:`WARNING`, case insensitively:: map f1 toggle_marker iregex 1 \\bERROR\\b 2 \\bWARNING\\b kitty supports up to 3 mark groups (the numbers in the commands above). You can control the colors used for these groups in :file:`kitty.conf` with:: mark1_foreground red mark1_background gray mark2_foreground green ... .. note:: For performance reasons, matching is done per line only, and only when that line is altered in any way. So you cannot match text that stretches across multiple lines. Creating markers dynamically --------------------------------- If you want to create markers dynamically rather than pre-defining them in :file:`kitty.conf`, you can do so as follows:: map f1 create_marker map f2 remove_marker Then pressing :kbd:`F1` will allow you to enter the marker definition and set it and pressing :kbd:`F2` will remove the marker. :ac:`create_marker` accepts the same syntax as :ac:`toggle_marker` above. Note that while creating markers, the prompt has history so you can easily re-use previous marker expressions. You can also use the facilities for :doc:`remote-control` to dynamically add or remove markers. Scrolling to marks -------------------- kitty has a :ac:`scroll_to_mark` action to scroll to the next line that contains a mark. You can use it by mapping it to some shortcut in :file:`kitty.conf`:: map ctrl+p scroll_to_mark prev map ctrl+n scroll_to_mark next Then pressing :kbd:`Ctrl+P` will scroll to the first line in the scrollback buffer above the current top line that contains a mark. Pressing :kbd:`Ctrl+N` will scroll to show the first line below the current last line that contains a mark. If you wish to jump to a mark of a specific type, you can add that to the mapping:: map ctrl+1 scroll_to_mark prev 1 Which will scroll only to marks of type 1. The full syntax for creating marks ------------------------------------- The syntax of the :ac:`toggle_marker` action is:: toggle_marker Here :code:`marker-type` is one of: * :code:`text` - Simple substring matching * :code:`itext` - Case-insensitive substring matching * :code:`regex` - A Python regular expression * :code:`iregex` - A case-insensitive Python regular expression * :code:`function` - An arbitrary function defined in a Python file, see :ref:`marker_funcs`. .. _marker_funcs: Arbitrary marker functions ----------------------------- You can create your own marker functions. Create a Python file named :file:`mymarker.py` and in it create a :code:`marker` function. This function receives the text of the line as input and must yield three numbers, the starting character position, the ending character position and the mark group (1-3). For example: .. code-block:: def marker(text): # Function to highlight the letter X for i, ch in enumerate(text): if ch.lower() == 'x': yield i, i, 3 Save this file somewhere and in :file:`kitty.conf`, use:: map f1 toggle_marker function /path/to/mymarker.py If you save the file in the :ref:`kitty config directory `, you can use:: map f1 toggle_marker function mymarker.py kitty-0.41.1/docs/misc-protocol.rst0000664000175000017510000000321114773370543016560 0ustar nileshnileshMiscellaneous protocol extensions ============================================== These are a few small protocol extensions kitty implements, primarily for use by its own kitten, they are documented here for completeness. Simple save/restore of all terminal modes -------------------------------------------- XTerm has the XTSAVE/XTRESTORE escape codes to save and restore terminal private modes. However, they require specifying an explicit list of modes to save/restore. kitty extends this protocol to specify that when no modes are specified, all side-effect free modes should be saved/restored. By side-effects we mean things that can affect other terminal state such as cursor position or screen contents. Examples of modes that have side effects are: `DECOM `__ and `DECCOLM `__. This allows TUI applications to easily save and restore emulator state without needing to maintain lists of modes. Independent control of bold and faint SGR properties ------------------------------------------------------- In common terminal usage, bold is set via SGR 1 and faint by SGR 2. However, there is only one number to reset these attributes, SGR 22, which resets both. There is no way to reset one and not the other. kitty uses 221 and 222 to reset bold and faint independently. kitty specific private escape codes --------------------------------------- These are a family of escape codes used by kitty for various things including remote control. They are all DCS (Device Control String) escape codes starting with ``\x1b P @ kitty-`` (ignoring spaces present for clarity). kitty-0.41.1/docs/notifications.py0000664000175000017510000000365314773370543016471 0ustar nileshnilesh#!/usr/bin/env python # A sample script to process notifications. Save it as # ~/.config/kitty/notifications.py import subprocess from kitty.notifications import NotificationCommand, Urgency def log_notification(nc: NotificationCommand) -> None: # Log notifications to /tmp/notifications-log.txt with open('/tmp/notifications-log.txt', 'a') as log: print(f'title: {nc.title}', file=log) print(f'body: {nc.body}', file=log) print(f'app: {nc.application_name}', file=log) print(f'types: {nc.notification_types}', file=log) print('\n', file=log) def on_notification_activated(nc: NotificationCommand, which: int) -> None: # do something when this notification is activated (clicked on) # remember to assign this to the on_activation field in main() pass def main(nc: NotificationCommand) -> bool: ''' This function should return True to filter out the notification ''' log_notification(nc) # filter out notifications with 'unwanted' in their titles if 'unwanted' in nc.title.lower(): return True # force the notification to be silent nc.sound_name = 'silent' # filter out notifications from the application badapp if nc.application_name == 'badapp': return True # filter out low urgency notifications if nc.urgency is Urgency.Low: return True # replace some bad text in the notification body nc.body = nc.body.replace('bad text', 'good text') # run a script if this notification is from myapp and has # type foo, passing in the title and body as command line args # to the script. if nc.application_name == 'myapp' and 'foo' in nc.notification_types: subprocess.Popen(['/path/to/my/script', nc.title, nc.body]) # do some arbitrary actions when this notification is activated nc.on_activation = on_notification_activated # dont filter out this notification return False kitty-0.41.1/docs/open_actions.rst0000664000175000017510000001371314773370543016457 0ustar nileshnileshScripting the mouse click ====================================================== |kitty| has support for :term:`terminal hyperlinks `. These are generated by many terminal programs, such as ``ls``, ``gcc``, ``systemd``, :ref:`tool_mdcat`, etc. You can customize exactly what happens when clicking on these hyperlinks in |kitty|. You can tell kitty to take arbitrarily many, complex actions when a link is clicked. Let us illustrate with some examples, first. Create the file :file:`~/.config/kitty/open-actions.conf` with the following: .. code:: conf # Open any image in the full kitty window by clicking on it protocol file mime image/* action launch --type=overlay kitten icat --hold -- ${FILE_PATH} Now, run ``ls --hyperlink=auto`` in kitty and click on the filename of an image, holding down :kbd:`ctrl+shift`. It will be opened over the current window. Press any key to close it. .. note:: The :program:`ls` comes with macOS does not support hyperlink, you need to install `GNU Coreutils `__. If you install it via `Homebrew `__, it will be :program:`gls`. Each entry in :file:`open-actions.conf` consists of one or more :ref:`matching_criteria`, such as ``protocol`` and ``mime`` and one or more ``action`` entries. In the example above kitty uses the :doc:`launch ` action which can be used to run external programs. Entries are separated by blank lines. Actions are very powerful, anything that you can map to a key combination in :file:`kitty.conf` can be used as an action. You can specify more than one action per entry if you like, for example: .. code:: conf # Tail a log file (*.log) in a new OS Window and reduce its font size protocol file ext log action launch --title ${FILE} --type=os-window tail -f -- ${FILE_PATH} action change_font_size current -2 In the action specification you can expand environment variables, as shown in the examples above. In addition to regular environment variables, there are some special variables, documented below: ``URL`` The full URL being opened ``FILE_PATH`` The path portion of the URL (unquoted) ``FILE`` The file portion of the path of the URL (unquoted) ``FRAGMENT`` The fragment (unquoted), if any of the URL or the empty string. ``NETLOC`` The net location aka hostname (unquoted), if any of the URL or the empty string. ``URL_PATH`` The path, query and fragment portions of the URL, without any unquoting. ``EDITOR`` The terminal based text editor. The configured :opt:`editor` in :file:`kitty.conf` is preferred. ``SHELL`` The path to the shell. The configured :opt:`shell` in :file:`kitty.conf` is preferred, without arguments. .. note:: You can use the :opt:`action_alias` option just as in :file:`kitty.conf` to define aliases for frequently used actions. .. _matching_criteria: Matching criteria ------------------ An entry in :file:`open-actions.conf` must have one or more matching criteria. URLs that match all criteria for an entry will trigger that entry's actions. Processing stops at the first matching entry, so put more specific matching criteria at the start of the list. Entries in the file are separated by blank lines. The various available criteria are: ``protocol`` A comma separated list of protocols, for example: ``http, https``. If absent, there is no constraint on protocol. ``url`` A regular expression that must match against the entire (unquoted) URL ``fragment_matches`` A regular expression that must match against the fragment (part after #) in the URL ``mime`` A comma separated list of MIME types, for example: ``text/*, image/*, application/pdf``. You can add MIME types to kitty by creating a file named :file:`mime.types` in the :ref:`kitty configuration directory `. Useful if your system MIME database does not have definitions you need. This file is in the standard format of one definition per line, like: ``text/plain rst md``. Note that the MIME type for directories is ``inode/directory``. MIME types are detected based on file extension, not file contents. ``ext`` A comma separated list of file extensions, for example: ``jpeg, tar.gz`` ``file`` A shell glob pattern that must match the filename, for example: ``image-??.png`` .. _launch_actions: Scripting the opening of files with kitty on macOS ------------------------------------------------------- On macOS you can use :guilabel:`Open With` in Finder or drag and drop files and URLs onto the kitty dock icon to open them with kitty. The default actions are: * Open text files in your editor and images using the icat kitten. * Run shell scripts in a shell * Open SSH urls using the ssh command These actions can also be executed from the command line by running:: kitty +open file_or_url another_url ... # macOS only open -a kitty.app file_or_url another_url ... Since macOS lacks an official interface to set default URL scheme handlers, kitty has a command you can use for it. The first argument for is the URL scheme, and the second optional argument is the bundle id of the app, which defaults to kitty, if not specified. For example: .. code-block:: sh # Set kitty as the handler for ssh:// URLs kitty +runpy 'from kitty.fast_data_types import cocoa_set_url_handler; import sys; cocoa_set_url_handler(*sys.argv[1:]); print("OK")' ssh # Set someapp as the handler for xyz:// URLs kitty +runpy 'from kitty.fast_data_types import cocoa_set_url_handler; import sys; cocoa_set_url_handler(*sys.argv[1:]); print("OK")' xyz someapp.bundle.identifier You can customize these actions by creating a :file:`launch-actions.conf` file in the :ref:`kitty config directory `, just like the :file:`open-actions.conf` file above. For example: .. literalinclude:: ../kitty/open_actions.py :language: conf :start-at: # Open script files :end-before: '''.splitlines())) kitty-0.41.1/docs/overview.rst0000664000175000017510000003121014773370543015634 0ustar nileshnileshOverview ============== Design philosophy ------------------- |kitty| is designed for power keyboard users. To that end all its controls work with the keyboard (although it fully supports mouse interactions as well). Its configuration is a simple, human editable, single file for easy reproducibility (I like to store configuration in source control). The code in |kitty| is designed to be simple, modular and hackable. It is written in a mix of C (for performance sensitive parts), Python (for easy extensibility and flexibility of the UI) and Go (for the command line :term:`kittens`). It does not depend on any large and complex UI toolkit, using only OpenGL for rendering everything. Finally, |kitty| is designed from the ground up to support all modern terminal features, such as Unicode, true color, bold/italic fonts, text formatting, etc. It even extends existing text formatting escape codes, to add support for features not available elsewhere, such as colored and styled (curly) underlines. One of the design goals of |kitty| is to be easily extensible so that new features can be added in the future with relatively little effort. .. include:: basic.rst Configuring kitty ------------------- |kitty| is highly configurable, everything from keyboard shortcuts to painting frames-per-second. Press :sc:`edit_config_file` in kitty to open its fully commented sample config file in your text editor. For details see the :doc:`configuration docs `. .. toctree:: :hidden: conf .. _layouts: Layouts ---------- A :term:`layout` is an arrangement of multiple :term:`kitty windows ` inside a top-level :term:`OS window `. The layout manages all its windows automatically, resizing and moving them as needed. You can create a new :term:`window` using the :sc:`new_window` key combination. Currently, there are seven layouts available: * **Fat** -- One (or optionally more) windows are shown full width on the top, the rest of the windows are shown side-by-side on the bottom * **Grid** -- All windows are shown in a grid * **Horizontal** -- All windows are shown side-by-side * **Splits** -- Windows arranged in arbitrary patterns created using horizontal and vertical splits * **Stack** -- Only a single maximized window is shown at a time * **Tall** -- One (or optionally more) windows are shown full height on the left, the rest of the windows are shown one below the other on the right * **Vertical** -- All windows are shown one below the other By default, all layouts are enabled and you can switch between layouts using the :sc:`next_layout` key combination. You can also create shortcuts to select particular layouts, and choose which layouts you want to enable, see :ref:`conf-kitty-shortcuts.layout` for examples. The first layout listed in :opt:`enabled_layouts` becomes the default layout. For more details on the layouts and how to use them see :doc:`the documentation `. .. toctree:: :hidden: layouts Extending kitty ------------------ kitty has a powerful framework for scripting. You can create small terminal programs called :doc:`kittens `. These can be used to add features to kitty, for example, :doc:`editing remote files ` or :doc:`inputting Unicode characters `. They can also be used to create programs that leverage kitty's powerful features, for example, :doc:`viewing images ` or :doc:`diffing files with image support `. You can :doc:`create your own kittens to scratch your own itches `. For a list of all the builtin kittens, :ref:`see here `. Additionally, you can use the :ref:`watchers ` framework to create Python scripts that run in response to various events such as windows being resized, closing, having their titles changed, etc. .. toctree:: :hidden: kittens_intro Remote control ------------------ |kitty| has a very powerful system that allows you to control it from the :doc:`shell prompt, even over SSH `. You can change colors, fonts, open new :term:`windows `, :term:`tabs `, set their titles, change window layout, get text from one window and send text to another, etc. The possibilities are endless. See the :doc:`tutorial ` to get started. .. toctree:: :hidden: remote-control .. _sessions: Startup Sessions ------------------ You can control the :term:`tabs `, :term:`kitty window ` layout, working directory, startup programs, etc. by creating a *session* file and using the :option:`kitty --session` command line flag or the :opt:`startup_session` option in :file:`kitty.conf`. An example, showing all available commands: .. code-block:: session # Set the layout for the current tab layout tall # Set the working directory for windows in the current tab cd ~ # Create a window and run the specified command in it launch zsh # Create a window with some environment variables set and run vim in it launch --env FOO=BAR vim # Set the title for the next window launch --title "Chat with x" irssi --profile x # Run a short lived command and see its output launch --hold message-of-the-day # Create a new tab # The part after new_tab is the optional tab title which will be displayed in # the tab bar, if omitted, the title of the active window will be used instead. new_tab my tab cd ~/somewhere # Set the layouts allowed in this tab enabled_layouts tall,stack # Set the current layout layout stack launch zsh # Create a new OS window # Any definitions specified before the first new_os_window will apply to first OS window. new_os_window # Set new window size to 80x24 cells os_window_size 80c 24c # Set the --class for the new OS window os_window_class mywindow # Set the --name for the new OS window os_window_name myname # Change the OS window state to normal, fullscreen, maximized or minimized os_window_state normal launch sh # Resize the current window (see the resize_window action for details) resize_window wider 2 # Make the current window the active (focused) window in its tab focus # Make the current OS Window the globally active window (not supported on Wayland) focus_os_window launch emacs # Create a complex layout using multiple splits. Creates two columns of # windows with two windows in each column. The windows in the first column are # split 50:50. In the second column the windows are not evenly split. new_tab complex tab layout splits # First window, set a user variable on it so we can focus it later launch --var window=first # Create the second column by splitting the first window vertically launch --location=vsplit # Create the third window in the second column by splitting the second window horizontally # Make it take 40% of the height instead of 50% launch --location=hsplit --bias=40 # Go back to focusing the first window, so that we can split it focus_matching_window var:window=first # Create the final window in the first column launch --location=hsplit .. note:: The :doc:`launch ` command when used in a session file cannot create new OS windows, or tabs. .. note:: Environment variables of the form :code:`${NAME}` or :code:`$NAME` are expanded in the session file, except in the *arguments* (not options) to the launch command. Creating tabs/windows ------------------------------- kitty can be told to run arbitrary programs in new :term:`tabs `, :term:`windows ` or :term:`overlays ` at a keypress. To learn how to do this, see :doc:`here `. .. toctree:: :hidden: launch Mouse features ------------------- * You can click on a URL to open it in a browser. * You can double click to select a word and then drag to select more words. * You can triple click to select a line and then drag to select more lines. * You can triple click while holding :kbd:`Ctrl+Alt` to select from clicked point to end of line. * You can right click to extend a previous selection. * You can hold down :kbd:`Ctrl+Alt` and drag with the mouse to select in columns. * Selecting text automatically copies it to the primary clipboard (on platforms with a primary clipboard). * You can middle click to paste from the primary clipboard (on platforms with a primary clipboard). * You can right click while holding :kbd:`Ctrl+Shift` to open the output of the clicked on command in a pager (requires :ref:`shell_integration`) * You can select text with kitty even when a terminal program has grabbed the mouse by holding down the :kbd:`Shift` key All these actions can be customized in :file:`kitty.conf` as described :ref:`here `. You can also customize what happens when clicking on :term:`hyperlinks` in kitty, having it open files in your editor, download remote files, open things in your browser, etc. For details, see :doc:`here `. .. toctree:: :hidden: open_actions Font control ----------------- |kitty| has extremely flexible and powerful font selection features. You can specify individual families for the regular, bold, italic and bold+italic fonts. You can even specify specific font families for specific ranges of Unicode characters. This allows precise control over text rendering. It can come in handy for applications like powerline, without the need to use patched fonts. See the various font related configuration directives in :ref:`conf-kitty-fonts`. .. _scrollback: The scrollback buffer ----------------------- |kitty| supports scrolling back to view history, just like most terminals. You can use either keyboard shortcuts or the mouse scroll wheel to do so. While you are browsing the scrollback a :opt:`small indicator ` is displayed along the right edge of the window to show how far back you are. However, |kitty| has an extra, neat feature. Sometimes you need to explore the scrollback buffer in more detail, maybe search for some text or refer to it side-by-side while typing in a follow-up command. |kitty| allows you to do this by pressing the :sc:`show_scrollback` shortcut, which will open the scrollback buffer in your favorite pager program (which is :program:`less` by default). Colors and text formatting are preserved. You can explore the scrollback buffer comfortably within the pager. Additionally, you can pipe the contents of the scrollback buffer to an arbitrary, command running in a new :term:`window`, :term:`tab` or :term:`overlay`. For example:: map f1 launch --stdin-source=@screen_scrollback --stdin-add-formatting less +G -R Would open the scrollback buffer in a new :term:`window` when you press the :kbd:`F1` key. See :sc:`show_scrollback ` for details. If you want to use it with an editor such as :program:`vim` to get more powerful features, see for example, `kitty-scrollback.nvim `__ or `kitty-grab `__ or see more tips for using various editor programs, in :iss:`this thread <719>`. If you wish to store very large amounts of scrollback to view using the piping or :sc:`show_scrollback ` features, you can use the :opt:`scrollback_pager_history_size` option. Integration with shells --------------------------------- kitty has the ability to integrate closely within common shells, such as `zsh `__, `fish `__ and `bash `__ to enable features such as jumping to previous prompts in the scrollback, viewing the output of the last command in :program:`less`, using the mouse to move the cursor while editing prompts, etc. See :doc:`shell-integration` for details. .. toctree:: :hidden: shell-integration .. _cpbuf: Multiple copy/paste buffers ----------------------------- In addition to being able to copy/paste from the system clipboard, in |kitty| you can also setup an arbitrary number of copy paste buffers. To do so, simply add something like the following to your :file:`kitty.conf`:: map f1 copy_to_buffer a map f2 paste_from_buffer a This will allow you to press :kbd:`F1` to copy the current selection to an internal buffer named ``a`` and :kbd:`F2` to paste from that buffer. The buffer names are arbitrary strings, so you can define as many such buffers as you need. Marks ------------- kitty has the ability to mark text on the screen based on regular expressions. This can be useful to highlight words or phrases when browsing output from long running programs or similar. To learn how this feature works, see :doc:`marks`. .. toctree:: :hidden: marks kitty-0.41.1/docs/performance.rst0000664000175000017510000001515414773370543016300 0ustar nileshnileshPerformance =================== The main goals for |kitty| performance are user perceived latency while typing and "smoothness" while scrolling as well as CPU usage. |kitty| tries hard to find an optimum balance for these. To that end it keeps a cache of each rendered glyph in video RAM so that font rendering is not a bottleneck. Interaction with child programs takes place in a separate thread from rendering, to improve smoothness. Parsing of the byte stream is done using `vector CPU instructions `__ for maximum performance. Updates to the screen typically require sending just a few bytes to the GPU. There are two config options you can tune to adjust the performance, :opt:`repaint_delay` and :opt:`input_delay`. These control the artificial delays introduced into the render loop to reduce CPU usage. See :ref:`conf-kitty-performance` for details. See also the :opt:`sync_to_monitor` option to further decrease latency at the cost of some `screen tearing `__ while scrolling. Benchmarks ------------- Measuring terminal emulator performance is fairly subtle, there are three main axes on which performance is measured: Energy usage for typical tasks, Keyboard to screen latency, and throughput (processing large amounts of data). Keyboard to screen latency ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is measured either with dedicated hardware, or software such as `Typometer `__. Third party measurements comparing kitty with other terminal emulators on various systems show kitty has best in class keyboard to screen latency. Note that to minimize latency at the expense of more energy usage, use the following settings in kitty.conf:: input_delay 0 repaint_delay 2 sync_to_monitor no wayland_enable_ime no `Hardware based measurement on macOS `__ show that kitty and Apple's Terminal.app share the crown for best latency. These measurements were done with :opt:`input_delay` at its default value of ``3 ms`` which means kitty's actual numbers would be even lower. `Typometer based measurements on Linux `__ show that kitty has far and away the best latency of the terminals tested. .. _throughput: Throughput ^^^^^^^^^^^^^^^^ kitty has a builtin kitten to measure throughput, it works by dumping large amounts of data of different types into the tty device and measuring how fast the terminal parses and responds to it. The measurements below were taken with the same font, font size and window size for all terminals, and default settings, on the same computer. They clearly show kitty has the fastest throughput. To run the tests yourself, run ``kitten __benchmark__`` in the terminal emulator you want to test, where the kitten binary is part of the kitty install. The numbers are megabytes per second of data that the terminal processes. Measurements were taken under Linux/X11 with an ``AMD Ryzen 7 PRO 5850U``. Entries are in order of decreasing performance. kitty is twice as fast as the next best. ================ ====== ======= ===== ====== ======= Terminal ASCII Unicode CSI Images Average ================ ====== ======= ===== ====== ======= kitty 0.33 121.8 105.0 59.8 251.6 134.55 gnometerm 3.50.1 33.4 55.0 16.1 142.8 61.83 alacritty 0.13.1 43.1 46.5 32.5 94.1 54.05 wezterm 20230712 16.4 26.0 11.1 140.5 48.5 xterm 389 47.7 18.3 0.6 56.3 30.72 konsole 23.08.04 25.2 37.7 23.6 23.4 27.48 alacritty+tmux 30.3 7.8 14.7 46.1 24.73 ================ ====== ======= ===== ====== ======= In this table, each column represents different types of data. The CSI column is for data consisting of a mix of typical formatting escape codes and some ASCII only text. .. note:: By default, the benchmark kitten suppresses actual rendering, to better focus on parser speed, you can pass it the ``--render`` flag to not suppress rendering. However, modern terminals typically render asynchronously, therefore the numbers are not really useful for comparison, as it is just a game about how much input to *batch* before rendering the next frame. However, even with rendering enabled kitty is still faster than all the rest. For brevity those numbers are not included. .. note:: foot, iterm2 and Terminal.app are left out as they do not run under X11. Alacritty+tmux is included just to show the effect of putting a terminal multiplexer into the mix (halving throughput) and because alacritty isnt remotely comparable to any of the other terminals feature wise without tmux. .. note:: konsole, gnome-terminal and xterm do not support the `Synchronized update `__ escape code used to suppress rendering, if and when they gain support for it their numbers are likely to improve by ``20 - 50%``, depending on how well they implement it. Energy usage ^^^^^^^^^^^^^^^^^ Sadly, I do not have the infrastructure to measure actual energy usage so CPU usage will have to stand in for it. Here are some CPU usage numbers for the task of scrolling a file continuously in :program:`less`. The CPU usage is for the terminal process and X together and is measured using :program:`htop`. The measurements are taken at the same font and window size for all terminals on a ``Intel(R) Core(TM) i7-4820K CPU @ 3.70GHz`` CPU with a ``Advanced Micro Devices, Inc. [AMD/ATI] Cape Verde XT [Radeon HD 7770/8760 / R7 250X]`` GPU. ============== ========================= Terminal CPU usage (X + terminal) ============== ========================= |kitty| 6 - 8% xterm 5 - 7% (but scrolling was extremely janky) termite 10 - 13% urxvt 12 - 14% gnome-terminal 15 - 17% konsole 29 - 31% ============== ========================= As you can see, |kitty| uses much less CPU than all terminals, except xterm, but its scrolling "smoothness" is much better than that of xterm (at least to my, admittedly biased, eyes). Instrumenting kitty ----------------------- You can generate detailed per-function performance data using `gperftools `__. Build |kitty| with ``make profile``. Run kitty and perform the task you want to analyse, for example, scrolling a large file with :program:`less`. After you quit, function call statistics will be displayed in *KCachegrind*. Hence, profiling is best done on Linux which has these tools easily available. kitty-0.41.1/docs/pipe.rst0000664000175000017510000000547214773370543014736 0ustar nileshnilesh:orphan: Working with the screen and history buffer contents ====================================================== .. warning:: The pipe action has been deprecated in favor of the :doc:`launch ` action which is more powerful. You can pipe the contents of the current screen and history buffer as :file:`STDIN` to an arbitrary program using the ``pipe`` function. The program can be displayed in a kitty window or overlay. For example, the following in :file:`kitty.conf` will open the scrollback buffer in less in an overlay window, when you press :kbd:`F1`:: map f1 pipe @ansi overlay less +G -R The syntax of the ``pipe`` function is:: pipe The piping environment -------------------------- The program to which the data is piped has a special environment variable declared, ``KITTY_PIPE_DATA`` whose contents are:: KITTY_PIPE_DATA={scrolled_by}:{cursor_x},{cursor_y}:{lines},{columns} where ``scrolled_by`` is the number of lines kitty is currently scrolled by, ``cursor_(x|y)`` is the position of the cursor on the screen with ``(1,1)`` being the top left corner and ``{lines},{columns}`` being the number of rows and columns of the screen. You can choose where to run the pipe program: ``overlay`` An overlay window over the current kitty window ``window`` A new kitty window ``os_window`` A new top-level window ``tab`` A new window in a new tab ``clipboard, primary`` Copy the text directly to the clipboard. In this case the specified program is not run, so use some dummy program name for it. ``none`` Run it in the background Input placeholders -------------------- There are various different kinds of placeholders ``@selection`` Plain text, currently selected text ``@text`` Plain text, current screen + scrollback buffer ``@ansi`` Text with formatting, current screen + scrollback buffer ``@screen`` Plain text, only current screen ``@ansi_screen`` Text with formatting, only current screen ``@alternate`` Plain text, secondary screen. The secondary screen is the screen not currently displayed. For example if you run a fullscreen terminal application, the secondary screen will be the screen you return to when quitting the application. ``@ansi_alternate`` Text with formatting, secondary screen. ``@alternate_scrollback`` Plain text, secondary screen + scrollback, if any. ``@ansi_alternate_scrollback`` Text with formatting, secondary screen + scrollback, if any. ``none`` No input You can also add the suffix ``_wrap`` to the placeholder, in which case kitty will insert the carriage return at every line wrap location (where long lines are wrapped at screen edges). This is useful if you want to pipe to program that wants to duplicate the screen layout of the screen. kitty-0.41.1/docs/pointer-shapes.rst0000664000175000017510000001370414773370543016737 0ustar nileshnileshMouse pointer shapes ======================= .. versionadded:: 0.31.0 This is a simple escape code that can be used by terminal programs to change the shape of the mouse pointer. This is useful for buttons/links, dragging to resize panes, etc. It is based on the original escape code proposal from xterm however, it properly specifies names for the different shapes in a system independent manner, adds a stack for easy push/pop of shapes, allows programs to query support and specifies interaction with other terminal state. The escape code is of the form:: 22 ; \ Here, ```` is the bytes ``]`` and ```` is the byte ``0x1b``. Spaces in the above are present for clarity only and should not be actually used. First some examples:: # Set the pointer to a pointing hand 22 ; pointer \ # Reset the pointer to default 22 ; \ # Push a shape onto the stack making it the current shape 22 ; >wait \ # Pop a shape off the stack restoring to the previous shape 22 ; < \ # Query the terminal for what the currently set shape is 22 ; ?__current__ \ To demo the various shapes, simply run the following command inside kitty:: kitten mouse-demo For more details see below. Setting the pointer shape ------------------------------- For set operations, the optional first char can be either ``=`` or omitted. Follow the first char with the name of the shape. See the :ref:`pointer_shape_names` table. Pushing and popping shapes onto the stack --------------------------------------------- The terminal emulator maintains a stack of shapes. To add shapes to the stack, the optional first char must be ``>`` followed by a comma separated list of shape names. See the :ref:`pointer_shape_names` table. All the specified names are added to the stack, with the last name being the top of the stack and the current shape. If the stack is full, the entry at the bottom of the stack is evicted. Terminal implementations are free to choose an appropriate maximum stack size, with a minimum stack size of 16. To pop shapes of the top of the stack the optional first char must be ``<``. The comma separated list of names is ignored. Once the stack is empty further pops have no effect. An empty stack means the terminal is free to use whatever pointer shape it likes. Querying support ------------------- Terminal programs can ask the terminal about this feature by setting the optional first char to ``?``. The comma separated list of names is then considered the query to which the terminal must respond with an OSC 22 code. For example:: 22 ; ?__current__ \ results in 22 ; shape_name \ Here, ``shape_name`` will be a name from the table of shape names below or ``0`` if the stack is empty, i.e., no shape is currently set. To check if the terminal supports some shapes, pass the shape names and the terminal will reply with a comma separated list of zeros and ones where 1 means the shape name is supported and zero means it is not. For example:: 22 ; ?pointer,crosshair,no-such-name,wait \ results in 22 ; 1,1,0,1 \ In addition to ``__current__`` there are a couple of other special names:: __default__ - The terminal responds with the shape name of the shape used by default __grabbed__ - The terminal responds with the shape name of the shape used when the mouse is "grabbed" Interaction with other terminal features --------------------------------------------- The terminal must maintain separate shape stacks for the *main* and *alternate* screens. This allows full screen programs, which are likely to be the main consumers of this feature, to easily temporarily switch back from the alternate screen, without needing to worry about pointer shape state. Think of suspending a terminal editor to get back to the shell, for example. Resetting the terminal must empty both the shape stacks. When dragging to select text, the terminal is free to ignore any mouse pointer shape specified using this escape code in favor of one appropriate for dragging. Similarly, when hovering over a URL or OSC 8 based hyperlink, the terminal may choose to change the mouse pointer regardless of the value set by this escape code. This feature is independent of mouse reporting. The changed pointer shapes apply regardless of whether the terminal program has enabled mouse reporting or not. .. _pointer_shape_names: Pointer shape names ---------------------------------- There is a well defined set of shape names that all conforming terminal emulators must support. The list is based on the names used by the `cursor property in the CSS standard `__, click the link to see representative images for the names. Valid names must consist of only the characters from the set ``a-z0-9_-``. .. start list of shape css names (auto generated by gen-key-constants.py do not edit) #. alias #. cell #. copy #. crosshair #. default #. e-resize #. ew-resize #. grab #. grabbing #. help #. move #. n-resize #. ne-resize #. nesw-resize #. no-drop #. not-allowed #. ns-resize #. nw-resize #. nwse-resize #. pointer #. progress #. s-resize #. se-resize #. sw-resize #. text #. vertical-text #. w-resize #. wait #. zoom-in #. zoom-out .. end list of shape css names To demo the various shapes, simply run the following command inside kitty:: kitten mouse-demo Legacy xterm compatibility ---------------------------- The original xterm proposal for this escape code used shape names from the :file:`X11/cursorfont.h` header on X11 based systems. Terminal implementations wishing to maintain compatibility with xterm can also implement these names as aliases for the CSS based names defined in the :ref:`pointer_shape_names` table. The simplest mode of operation of this escape code, which is no leading optional char and a single shape name is compatible with xterm. kitty-0.41.1/docs/press-mentions.rst0000664000175000017510000000100414773370543016752 0ustar nileshnileshPress mentions of kitty ======================== `Python Bytes 272 `__ (Feb 2022) A podcast demoing some of kitty's coolness `Console #88 `__ (Jan 2022) An interview with Kovid about kitty Video reviews -------------- `Review (Jan 2021) `__ A kitty review by distrotube `Review (Dec 2020) `__ A kitty review/intro by TechHut kitty-0.41.1/docs/protocol-extensions.rst0000664000175000017510000000261114773370543020027 0ustar nileshnileshTerminal protocol extensions =================================== |kitty| has extensions to the legacy terminal protocol, to enable advanced features. These are typically in the form of new or re-purposed escape codes. While these extensions are currently |kitty| specific, it would be nice to get some of them adopted more broadly, to push the state of terminal emulators forward. The goal of these extensions is to be as small and unobtrusive as possible, while filling in some gaps in the existing xterm protocol. In particular, one of the goals of this specification is explicitly not to "re-imagine" the TTY. The TTY should remain what it is -- a device for efficiently processing text received as a simple byte stream. Another objective is to only move the minimum possible amount of extra functionality into the terminal program itself. This is to make it as easy to implement these protocol extensions as possible, thereby hopefully encouraging their widespread adoption. If you wish to discuss these extensions, propose additions or changes to them, please do so by opening issues in the `GitHub bug tracker `__. .. toctree:: :maxdepth: 1 underlines graphics-protocol keyboard-protocol text-sizing-protocol file-transfer-protocol desktop-notifications pointer-shapes unscroll color-stack deccara clipboard misc-protocol kitty-0.41.1/docs/quickstart.rst0000664000175000017510000000161714773370543016170 0ustar nileshnilesh.. _quickstart: Quickstart =========== .. toctree:: :hidden: binary build Pre-built binaries of |kitty| are available for both macOS and Linux. See the :doc:`binary install instructions `. You can also :doc:`build from source `. Additionally, you can use your favorite package manager to install the |kitty| package, but note that some Linux distribution packages are woefully outdated. |kitty| is available in a vast number of package repositories for macOS and Linux. .. image:: https://repology.org/badge/tiny-repos/kitty-terminal.svg :target: https://repology.org/project/kitty-terminal/versions :alt: Number of repositories kitty is available in See :doc:`Configuring kitty ` for help on configuring |kitty| and :doc:`Invocation ` for the command line arguments |kitty| supports. For a tour of kitty's design and features, see the :doc:`overview`. kitty-0.41.1/docs/rc_protocol.rst0000664000175000017510000001141314773370543016316 0ustar nileshnileshThe kitty remote control protocol ================================== The kitty remote control protocol is a simple protocol that involves sending data to kitty in the form of JSON. Any individual command of kitty has the form:: P@kitty-cmd\ Where ```` is the byte ``0x1b``. The JSON object has the form: .. code-block:: json { "cmd": "command name", "version": "", "no_response": "", "kitty_window_id": "", "payload": "" } The ``version`` above is an array of the form :code:`[0, 14, 2]`. If you are developing a standalone client, use the kitty version that you are developing against. Using a version greater than the version of the kitty instance you are talking to, will cause a failure. Set ``no_response`` to ``true`` if you don't want a response from kitty. The optional payload is a JSON object that is specific to the actual command being sent. The fields in the object for every command are documented below. As a quick example showing how easy to use this protocol is, we will implement the ``@ ls`` command from the shell using only shell tools. First, run kitty as:: kitty -o allow_remote_control=socket-only --listen-on unix:/tmp/test Now, in a different terminal, you can get the pretty printed ``@ ls`` output with the following command line:: echo -en '\eP@kitty-cmd{"cmd":"ls","version":[0,14,2]}\e\\' | socat - unix:/tmp/test | awk '{ print substr($0, 13, length($0) - 14) }' | jq -c '.data | fromjson' | jq . There is also the statically compiled stand-alone executable ``kitten`` that can be used for this, available from the `kitty releases `__ page:: kitten @ --help .. _rc_crypto: Encrypted communication -------------------------- .. versionadded:: 0.26.0 When using the :opt:`remote_control_password` option communication to the terminal is encrypted to keep the password secure. A public key is used from the :envvar:`KITTY_PUBLIC_KEY` environment variable. Currently, only one encryption protocol is supported. The protocol number is present in :envvar:`KITTY_PUBLIC_KEY` as ``1``. The key data in this environment variable is :rfc:`Base-85 <1924>` encoded. The algorithm used is `Elliptic Curve Diffie Helman `__ with the `X25519 curve `__. A time based nonce is used to minimise replay attacks. The original JSON command has the fields: ``password`` and ``timestamp`` added. The timestamp is the number of nanoseconds since the epoch, excluding leap seconds. Commands with a timestamp more than 5 minutes from the current time are rejected. The command is then encrypted using AES-256-GCM in authenticated encryption mode, with a symmetric key that is derived from the ECDH key-pair by running the shared secret through SHA-256 hashing, once. An IV of at least 96 bits of CSPRNG data is used. The tag for authenticated encryption **must** be at least 128 bits long. The tag **must** authenticate only the value of the ``encrypted`` field. A new command is created and transmitted that contains the fields: .. code-block:: json { "version": "", "iv": "base85 encoded IV", "tag": "base85 encoded AEAD tag", "pubkey": "base85 encoded ECDH public key of sender", "encrypted": "The original command encrypted and base85 encoded" } Async and streaming requests --------------------------------- Some remote control commands require asynchronous communication, that is, the response from the terminal can happen after an arbitrary amount of time. For example, the :code:`select-window` command requires the user to select a window before a response can be sent. Such command must set the field :code:`async` in the JSON block above to a random string that serves as a unique id. The client can cancel an async request in flight by adding the :code:`cancel_async` field to the JSON block. A async response remains in flight until the terminal sends a response to the request. Note that cancellation requests dont need to be encrypted as users must not be prompted for these and the worst a malicious cancellation request can do is prevent another sync request from getting a response. Similar to async requests are *streaming* requests. In these the client has to send a large amount of data to the terminal and so the request is split into chunks. In every chunk the JSON block must contain the field ``stream`` set to ``true`` and ``stream_id`` set to a random long string, that should be the same for all chunks in a request. End of data is indicated by sending a chunk with no data. .. include:: generated/rc.rst kitty-0.41.1/docs/remote-control.rst0000664000175000017510000003315514773370543016751 0ustar nileshnileshControl kitty from scripts ---------------------------- .. highlight:: sh |kitty| can be controlled from scripts or the shell prompt. You can open new windows, send arbitrary text input to any window, change the title of windows and tabs, etc. Let's walk through a few examples of controlling |kitty|. Tutorial ------------ Start by running |kitty| as:: kitty -o allow_remote_control=yes -o enabled_layouts=tall In order for control to work, :opt:`allow_remote_control` or :opt:`remote_control_password` must be enabled in :file:`kitty.conf`. Here we turn it on explicitly at the command line. Now, in the new |kitty| window, enter the command:: kitten @ launch --title Output --keep-focus cat This will open a new window, running the :program:`cat` program that will appear next to the current window. Let's send some text to this new window:: kitten @ send-text --match cmdline:cat Hello, World This will make ``Hello, World`` show up in the window running the :program:`cat` program. The :option:`kitten @ send-text --match` option is very powerful, it allows selecting windows by their titles, the command line of the program running in the window, the working directory of the program running in the window, etc. See :ref:`kitten @ send-text --help ` for details. More usefully, you can pipe the output of a command running in one window to another window, for example:: ls | kitten @ send-text --match 'title:^Output' --stdin This will show the output of :program:`ls` in the output window instead of the current window. You can use this technique to, for example, show the output of running :program:`make` in your editor in a different window. The possibilities are endless. You can even have things you type show up in a different window. Run:: kitten @ send-text --match 'title:^Output' --stdin And type some text, it will show up in the output window, instead of the current window. Type :kbd:`Ctrl+D` when you are ready to stop. Now, let's open a new tab:: kitten @ launch --type=tab --tab-title "My Tab" --keep-focus bash This will open a new tab running the bash shell with the title "My Tab". We can change the title of the tab to "New Title" with:: kitten @ set-tab-title --match 'title:^My' New Title Let's change the title of the current tab:: kitten @ set-tab-title Master Tab Now lets switch to the newly opened tab:: kitten @ focus-tab --match 'title:^New' Similarly, to focus the previously opened output window (which will also switch back to the old tab, automatically):: kitten @ focus-window --match 'title:^Output' You can get a listing of available tabs and windows, by running:: kitten @ ls This outputs a tree of data in JSON format. The top level of the tree is all :term:`OS windows `. Each OS window has an id and a list of :term:`tabs `. Each tab has its own id, a title and a list of :term:`kitty windows `. Each window has an id, title, current working directory, process id (PID) and command-line of the process running in the window. You can use this information with :option:`kitten @ focus-window --match` to control individual windows. As you can see, it is very easy to control |kitty| using the ``kitten @`` messaging system. This tutorial touches only the surface of what is possible. See ``kitten @ --help`` for more details. In the example's above, ``kitten @`` messaging works only when run inside a |kitty| window, not anywhere. But, within a |kitty| window it even works over SSH. If you want to control |kitty| from programs/scripts not running inside a |kitty| window, see the section on :ref:`using a socket for remote control ` below. Note that if all you want to do is run a single |kitty| "daemon" and have subsequent |kitty| invocations appear as new top-level windows, you can use the simpler :option:`kitty --single-instance` option, see ``kitty --help`` for that. .. _rc_via_socket: Remote control via a socket -------------------------------- To control kitty from outside kitty, it is necessary to setup a socket to communicate with kitty. First, start |kitty| as:: kitty -o allow_remote_control=yes --listen-on unix:/tmp/mykitty The :option:`kitty --listen-on` option tells |kitty| to listen for control messages at the specified UNIX-domain socket. See ``kitty --help`` for details. Now you can control this instance of |kitty| using the :option:`kitten @ --to` command line argument to ``kitten @``. For example:: kitten @ --to unix:/tmp/mykitty ls The builtin kitty shell -------------------------- You can explore the |kitty| command language more easily using the builtin |kitty| shell. Run ``kitten @`` with no arguments and you will be dropped into the |kitty| shell with completion for |kitty| command names and options. You can even open the |kitty| shell inside a running |kitty| using a simple keyboard shortcut (:sc:`kitty_shell` by default). .. note:: Using the keyboard shortcut has the added advantage that you don't need to use :opt:`allow_remote_control` to make it work. Allowing only some windows to control kitty ---------------------------------------------- If you do not want to allow all programs running in |kitty| to control it, you can selectively enable remote control for only some |kitty| windows. Simply create a shortcut such as:: map ctrl+k launch --allow-remote-control some_program Then programs running in windows created with that shortcut can use ``kitten @`` to control kitty. Note that any program with the right level of permissions can still write to the pipes of any other program on the same computer and therefore can control |kitty|. It can, however, be useful to block programs running on other computers (for example, over SSH) or as other users. .. note:: You don't need :opt:`allow_remote_control` to make this work as it is limited to only programs running in that specific window. Be careful with what programs you run in such windows, since they can effectively control kitty, as if you were running with :opt:`allow_remote_control` turned on. You can further restrict what is allowed in these windows by using :option:`kitten @ launch --remote-control-password`. Fine grained permissions for remote control ---------------------------------------------- .. versionadded:: 0.26.0 The :opt:`allow_remote_control` option discussed so far is a blunt instrument, granting the ability to any program running on your computer or even on remote computers via SSH the ability to use remote control. You can instead define remote control passwords that can be used to grant different levels of control to different places. You can even write your own script to decide which remote control requests are allowed. This is done using the :opt:`remote_control_password` option in :file:`kitty.conf`. Set :opt:`allow_remote_control` to :code:`password` to use this feature. Let's see some examples: .. code-block:: conf remote_control_password "control colors" get-colors set-colors Now, using this password, you can, in scripts run the command:: kitten @ --password="control colors" set-colors background=red Any script with access to the password can now change colors in kitty using remote control, but only that and nothing else. You can even supply the password via the :envvar:`KITTY_RC_PASSWORD` environment variable, or the file :file:`~/.config/kitty/rc-password` to avoid having to type it repeatedly. See :option:`kitten @ --password-file` and :option:`kitten @ --password-env`. The :opt:`remote_control_password` can be specified multiple times to create different passwords with different capabilities. Run the following to get a list of all action names:: kitten @ --help You can even use glob patterns to match action names, for example: .. code-block:: conf remote_control_password "control colors" *-colors If no action names are specified, all actions are allowed. If ``kitten @`` is run with a password that is not present in :file:`kitty.conf`, then kitty will interactively prompt the user to allow or disallow the remote control request. The user can choose to allow or disallow either just that request or all requests using that password. The user's decision is remembered for the duration of that kitty instance. .. note:: For password based authentication to work over SSH, you must pass the :envvar:`KITTY_PUBLIC_KEY` environment variable to the remote host. The :doc:`ssh kitten ` does this for you automatically. When using a password, :ref:`rc_crypto` is used to ensure the password is kept secure. This does mean that using password based authentication is slower as the entire command is encrypted before transmission. This can be noticeable when using a command like ``kitten @ set-background-image`` which transmits large amounts of image data. Also, the clock on the remote system must match (within a few minutes) the clock on the local system. kitty uses a time based nonce to minimise the potential for replay attacks. .. _rc_custom_auth: Customizing authorization with your own program ____________________________________________________________ If the ability to control access by action names is not fine grained enough, you can define your own Python script to examine every remote control command and allow/disallow it. To do so create a file in the kitty configuration directory, :file:`~/.config/kitty/my_rc_auth.py` and add the following to :file:`kitty.conf`: .. code-block:: conf remote_control_password "testing custom auth" my_rc_auth.py :file:`my_rc_auth.py` should define a :code:`is_cmd_allowed` function as shown below: .. code-block:: py def is_cmd_allowed(pcmd, window, from_socket, extra_data): cmd_name = pcmd['cmd'] # the name of the command cmd_payload = pcmd['payload'] # the arguments to the command # examine the cmd_name and cmd_payload and return True to allow # the command or False to disallow it. Return None to have no # effect on the command. # The command payload will vary from command to command, see # the rc protocol docs for details. Below is an example of # restricting the launch command to allow only running the # default shell. if cmd_name != 'launch': return None if cmd_payload.get('args') or cmd_payload.get('env') or cmd_payload.get('copy_cmdline') or cmd_payload.get('copy_env'): return False # prints in this function go to the parent kitty process STDOUT print('Allowing launch command:', cmd_payload) return True .. _rc_mapping: Mapping key presses to remote control commands -------------------------------------------------- If you wish to trigger a remote control command easily with just a keypress, you can map it in :file:`kitty.conf`. For example:: map f1 remote_control set-spacing margin=30 Then pressing the :kbd:`F1` key will set the active window margins to :code:`30`. The syntax for what follows :ac:`remote_control` is exactly the same as the syntax for what follows :code:`kitten @` above. If you wish to ignore errors from the command, prefix the command with an ``!``. For example, the following will not return an error when no windows are matched:: map f1 remote_control !focus-window --match XXXXXX If you wish to run a more complex script, you can use:: map f1 remote_control_script /path/to/myscript In this script you can use ``kitten @`` to run as many remote control commands as you like and process their output. :ac:`remote_control_script` is similar to the :ac:`launch` command with ``--type=background --allow-remote-control``. For more advanced usage, including fine grained permissions, setting env vars, command line interpolation, passing data to STDIN, etc. the :doc:`launch ` command should be used. .. note:: You do not need :opt:`allow_remote_control` to use these mappings, as they are not actual remote programs, but are simply a way to reuse the remote control infrastructure via keybings. Broadcasting what you type to all kitty windows -------------------------------------------------- As a simple illustration of the power of remote control, lets have what we type sent to all open kitty windows. To do that define the following mapping in :file:`kitty.conf`:: map f1 launch --allow-remote-control kitty +kitten broadcast Now press :kbd:`F1` and start typing, what you type will be sent to all windows, live, as you type it. The remote control protocol ----------------------------------------------- If you wish to develop your own client to talk to |kitty|, you can use the :doc:`remote control protocol specification `. Note that there is a statically compiled, standalone executable, ``kitten`` available that can be used as a remote control client on any UNIX like computer. This can be downloaded and used directly from the `kitty releases `__ page:: kitten @ --help .. _search_syntax: Matching windows and tabs ---------------------------- Many remote control operations operate on windows or tabs. To select these, the :code:`--match` option is often used. This allows matching using various sophisticated criteria such as title, ids, command lines, etc. These criteria are expressions of the form :code:`field:query`. Where :italic:`field` is the field against which to match and :italic:`query` is the expression to match. They can be further combined using Boolean operators, best illustrated with some examples:: title:"My special window" or id:43 title:bash and env:USER=kovid not id:1 (id:2 or id:3) and title:something .. include:: generated/matching.rst .. toctree:: :hidden: rc_protocol .. include:: generated/cli-kitten-at.rst kitty-0.41.1/docs/requirements.txt0000664000175000017510000000012614773370543016522 0ustar nileshnileshsphinx furo sphinx-copybutton sphinxext-opengraph sphinx-inline-tabs sphinx-autobuild kitty-0.41.1/docs/screenshots/0000775000175000017510000000000014773370543015577 5ustar nileshnileshkitty-0.41.1/docs/screenshots/diff.png0000664000175000017510000032217614773370543017230 0ustar nileshnileshPNG  IHDRh γgAMA a cHRMz&u0`:pQ< pHYs+bKGDIDATx;0;\U\q38b$)<.%4@4 @sW hi^pc? 1}߿n@'mp=@sgMӼg9@_D(EPL6ivΜ~MF Θ383706@d6$[I6l'[6a9M/mV}DdMlp3gyb?R=wm;\GM6l!Dd#V$I6Eƹ.o7ӗ9;˺g7?S,mޯ{u`@s\|Lu= 4e֌4?7y vuz+06@Tl| N۶kifu0'>"`ld &m$I~3s>1|V:=k=Z-f@ˋ&@\..9:@SͳdF2 45\].m?g:jDQ`.U+Hh ]`!n&@E MEti7-|_r 0̌ˏnpirIJx@<_(J~LV[\@t:Z&x\ץio"?1~Z~IR4 rt:'I{]Y\V9oVft ss|wzx<ĸ^H$4o6bz=Ͳ٬]cߐD"&ɺn $t|JPе3 dLF~Y^{OnWFy*JV+ ,hfW.u<5b ͪժZv[|>~[,K ,h<Ţ\NpYR)f4hTnY,Lw5?8d($FL&) V2,DRd5YL $_E|nuG|^3:ӫ4FK\]/~&U6չp8b<4r(hr,r9z-~_yHR{.AN}Џs B L&su:{Ft:ߦt@AF˥Ņf͝l&&&v]QZbV2zLەϊŢ>;ÂfوI[ ǵjb112N٬%M׻ˍZZi4|>IR\.ﲥRIs`QAv D"H$d0p8t@A75M-L^|b!///zSgZPP|v+DL2dzw(as8p H%"*]6EdbBo(&"MuFE"g n&!||f׶ 7 4Q 4v;jZt:vp 4/h 4 `@~1 4G @My?@@SW+ӶbqI'K'5Mo0hF>Д$I$I$IʵhӃE1Sg 3H$I$IIhV6$I$I$I$ 4~ߠj2o0h74bEh@`1h 4@@sϫI$IcZ72n%I$g$I$$I$F$I@#I$h 4I$II$I$I$F$I2h 4$I@#I$@@s;E:6TӕJK9}57||ʪ{zӳ$M@st'۱ۍ~:6TSӕX(sk2ENê>?#~~޾iWoO=;$8L^߾¡#;t[k~ZpZ/#/?&-Wf ()5f@nP/_@H𿅝<ϝ{r#Ow3:@;G(4!~ g&%ED,}<\U=:˜~Qӣ'sRGwwr N \\7/_>>2򿌔)]4IwW&&=2".ИF 7(q06j`Ÿ)!`"#B\m?>vg?~;{;yoϷ~|A;_wrO7M3[VXA~H ʏn Q7lIŔMw3 M@ ^CY"иRyQ1N)tff4:pK\؃(<:4ˬ;hş%筛)H:ʳ ?sގ3\\%>0|'-}ze|T$ʰ_׽|cOZU'k&ДUdeGqi _5~ 1čLlg9;%V*=[96@Z^EY"lvsBi~137FI+ !69'ZZEt!k49)Rsdh)\7mk~X{FހܜZQrlPP'3e\1 IucDt^$‚93<ur?gJ[m ih " ' 4=c* "hdWIH5$pq߂:hPBF2E3Q6ܒzH!eemũ{e"¼*;zсMIPQ+mMe#ꔏMDh }z K)vChN9:'C~%m߼DPSF=ltu&R ~X{FހcZkXYkŠdO3gM@c+#Lj@6HsfhLޫ)7t]WFн%C8xH4+M@@7X+A F43_&I/cgu׵u&$?FHAJ"^qC=h[r}J&B:; AJ?h #YHuruDOp:5e\o^Ov鱢v<"Ԁcox~ʆDa|wsfu;Ԑr yEccLP|u {P_}"< @.ɏ=J("6~-9C jG#H}B0{/@etzޕe f, 2FdRÜLޜu6a$UHu:#ewU`Z3=%) 6 om~ AUZuk.}y4aM"Qvhaz 4^+iIp⒟C3D')CpR a YjAx5[hV@~OzNv-Dc; w_>,{T;_VZJ P2--hlՓg/9"1m} fӈq*>=pк!)3?)J= ˍzsR`3&h\.yWN qb`kÆ l&\ 4y{F.А a}kqDM[W*1ՃiIp⒟f')CpR a YjAx5rV@~UF= g 8V~ZQOEK}Sm5MK jolՓg/I"Ĵ:8`&FSy|LyNIW홖?ǨGy8'6s^oeʁBU{j֩TR IUת{u-kB969d3zshWK?_c C hUHOB E6?8!OH5Hq#O#uƦqdU?w ƹ95k`ǒ5mx! 'C-\<~A!}vKדowz~Qk;Y1z- #_wݺK,#N67'/4?.FH7@CB5iܘJj3BhCz4 4F!Н`OnzN\-2y'f[>xD뎍{0]bt`9 4>a)o/!ݤ 1XFWR x.чjO0G5a^v7@3b(3yw:ܜ'i=<<@CLOMxi$@nW±$&"u^;G ?'?T;vu7jZ'k'*.h攣_wu4sc9d3zshd^gϪ%m=bõ/Nb 4NxQOVSFtAȋ^Wr(@B6KOxi$@n QVRH~򎠿. iB_m޿],Аb͞ NX-U|BZW~9,u³L1\OU^;x:tu9նct`9 4e2y/3g6Im&{U󢜃f٦ʈ9h+^B쫞h/&Mxd$А('aSwY؞kמPH4Hc=d@F UFкE M;c)6Q=n܋!H+GNKF=.{8n`N:LM)_3In3_EU!sN 4}yhWn=Ozxxx/ԓ@D~y#@Cnʟlr"{ 9 xUr8p bV46ϵ#!,Ҙq2 B=\Fֺ&I6QXi@1k/xdE 1=ҾQ L|S0'l&Xo/ 3ifz=:,t0701 ō 4:ON @{~]Ozxxx 4hr~SX[p[GRާkeL=2sf&hWE+be2Hقkk6M}Z9ghP#o% X$h@=[o _'9_X#aaA00P/u$FzT}1A^4#Ў=+-v<SxMpNY  Uɟ3/5t("hRէ[wbh)<9HOb W#o$'~wժ.=1!Ql1o,WN@ʕ*1"_y$WݼY3]GuǹG.;!ϑڌux"W9p"|f/"i0N嬩9w%lG=F@Ӷe+^] D/Ozxxx 4u^z#2z89,8W,#aaA00P]^$AzT}3׃y9Fء;֩{\$x dBn gxl$t'Wn'/N0h!F6骭t,“d[8=<r AϱD}4XKx=-+hZm; "]@IZ/x&r7HN< W y dJH*}{f> a~KȂT7޽!2c3r}`'|vͱbmЖ#EƂ PAƒ% ^G6 X1hԠ66D<$x /K[e_ g#dtNގnw1DGdrb THľB16͌}()أB[zψc}|D cSJ 4d$$vG9h4 ȿA8uBaug;[ϩI=@eZivyt(pϓ ̃.ô.Lg3,cTqϓ ^Vץl&͗  |^hFh@F@@hhhhFFFhhh@FFFhnb'vVb}~2zc)b"f;Ь@h 6Vcca:c=F8r452/ZUY4@#4ܝfl-pwhsmtD_Q 0oFLY[&0[Ʌ8(Qn'"sbh.j53-8+?m{_Zs\[{zo_r ~khfLNKGTtxڔkhH-/W+arawhZ9c@4lї/p4Az_0ylھ.a*>l% {'WRE皷Z4@#Fy۝oYv,WoX߽U |%5L&„iE/ԧ[4@#ДqiagV|Sa2&T0g)&9r @4L9 zr9.i(94I|2aǥͷwo-V@hf*ЌEШMwz47"f!\)l1>XhF@3M׵U=< *?,_20/bJV 1EM;mrE7/[4@#TyUӁ揮[%\0kfJɅưhgQt hf3[7eK{8dɖ,b^Llb#òE,F]Z!sun\ZUEեTF/Zx-ԵeNiTcGoyN/>oysrޜ7\QaC ^Uz"c@@!hN+jZEFiQaPJwC*{2X h4M'$ 00*(0d\Ƕ_g.$+vm&+ vFyF|Zz}!kuʯl)<0J.hh4hRj'"%GGEv#r>ux|  @4͝G ,$q#=9Saiw hG%3sl %jB/M"zɪ}%>tl_3GIL)~x~HJɫ$)&Pߓ_ճ~u FIi-yEY]wZ`o6GM#ir9g{EegBxw&|d9[pg 4HJehƙt jɰ*Wo^\PxR*yeE|x= !h4/ȖͲʹS\hn3\ и L,ķnw-CCr.6ĥ5&@u{#HؠN2׷ҠU^Ct4"ײrJkY{9ArGϡ9qr$O Ʋ^ @ĨiͲW"h܁nKC6߯ƊsOueON[DM]UWӐ5G6-`\Vu$iipE 96e]hhBF67Qzh+B~?,)Ced? XLkkK&g57ƳqT1w24s!bn:X#"hL@J G9jRV$vʚeO܈^s\I'W/ʩg?]Zeh4O'^Yo/-Awy$5,۷cc<ʀ@czǓL"bI_g^Y%ٕHBtԿou=~'%zFS^14sw&7o,k03cq v'?%e qy)=̀@cz3ͼn^E dkL_c A=[sҳ|9'q4S*ʽh4)8_|+ϐu_ܦ{mRRh[K׽G)IhFC!  -_/"( =O@@S_P 0h)߬Ȑ&f@cguOo1hDi{  hFv}eC2R.}ZĻU%Y1ʤo;VѸ,@3> =9nz `h ĕ5e5^' h 44)pNMi-ңv=C[\Wi׵Sx/l6/Ii_f@s0h⎳i7aQqlS|?0 Ɯ@S[*& 4+T᷆/3rwRsMf@Ӧq&@QMPi&Y!~w9 Cb֊wY"Bbo-,ى'B=~o}ͅgFNs?=&;Tn2&g12e5T."ݷ/5IM…\h!"""b,\2Tbۻ>R!"""ba^Үpwcca\6v0/K0iRL^LbS;`L[sZW $1q5f;,>1!c1kı3ƌee+1^B|K->[\B{qMӯoBu]q6v3^\D=""""NX)R?0DDDD 4 4 4fmwcy{QxD@3D^S@y7cF',޼N;3Vڋ{`Cr9Oa .n8z '=}pn`x_ŐP\.~ oH| ~pʼn 8rΝǾ|;ǰalwapؾ KݦmXa+߼%wREV[h>Keh XuG\""""-wa{܏?l"_dAE88s/> DǙSg\Iq'D9_'Τg>3>8~ϺcY88GOE3G~!qޟ~m{qf3]9#мqh%st5-V]k18o[E!"""ba/f܁p/b܁m`q~Uytk͖W--gl_k&ך9f8Flj3^k&1s-u>S|tuLZZ3qm+X/y xř=GUW.1|vR~{hhK) ^#MR:h[>gn[n-qmҵ.'ŕAEQeK&Wd|o7k=܍""""֍x1V. M4ZHf5Nni*<8cfö́4 4ۡNZ47p|t[|s BDDD@@Y9i"<>@k"""" (WNr1hh4W+O͟I^Z4SBDDD@bǑ3{q?O7a7‘Ʈ!*9A0{Ɵ41.uiKc_\47=7DDD@@eWL!t@ay!"""""D߉Ŏa=gkB_LoMZ(zU-@EI X@s'l\h* 26m]&U]_+TGY?ײyē"L&aBEN\:\tu_ ^]/HOˈ'\TҘc?0! ODDD@@/'tz0|hDr"\h4D #7I7gX?'">pZ5{eؘ'wȽDqI r'~1_{UƤx|+3iKcMDDD@@fP ~ 4z1m\h% J!6lg;LE"Έ}s4y/q1QX9sC6qÖ}d/10@f~+COؠ_ہ&$.1 !g1MkPxܰ·X h|ߺ[mzAӂ}aӽFmߖ<0,?F舁]c #zlToL4Kgaj"Qe2,3QŢ.,1jd턒A" QQs)u@ϳjK6T@nujTV9Œ*iLKc[XƼ49 ͅy!iHsF;nܼT̩[Q\"""baRh* HYw2[ nS}0tC<7axӏc.܍ح@s2.1dHzSΘ3cXqłJDW_0m@lbF)#QW95(Zd2JxBVTa?biR1J|~θA87-8 i̊2u@i,san9֯î:hh@sl 2?ËXKĝ#Ñpf^]x]\+G mR !GGI$oM 49GW]h-BDDD XaA]Bt!U_T/ϫy8n\ךh\ jПᳱr&@,_&}?z̅"""/,Фsmǰ^-3:ж r\ib׾ hS %q^T]/Lc*u>N*nJ}v@hLq1d8wK-M=@_J&1fr^kUÜssXV8pb4gEDD@@_ 4dYcI57)IV2qn5K1~Qx9߷D$ѶwRBDDDb? Uϖ~b~%aƔa9D&R"(|d&$Ty|/PI^b)tQb,;T^6,cܘ=m(1s/N<)ӣy<'9+_#y.l10|fzr5kx;FracOL"e݃{"мʅ"""xI+U,6]_EVWO; XϤID:VleƤ}p1"jN{E*ŲݓfQYB@XR?eQ?rO\ T9xCύ?%;+e,@ݨlaQBXo?=1)ɣIsF̝$ͩLlI#w\s9lt4EDD@@^Ud+,*~3< 5ǪqUUF@#$\@$у /DDDD@-$g1NDzS'2/YUP:D) |f5|j#9YFGhUsc✳ 4W")h n=6-ę?Yž,lj05$1}hTi.9+H3iJsUçHsXe#q)ʇ\DDD 4 4c߿<cTI܏7mVB:^8}+,h 4|j=܏>.O(b^3]|gapLȗ\T"""">A%7CpR%#qU^ ۾ϼ~pXiT JwFe aF*J ?%D|e6d1 3:,X@se{>G*h3Rg2_e$ ~Thw0ӻϸzqrY_\l""""Y ,1UEt )ȈwF}MШbatc6 Y@veˣ'»~H8H^[,8#W\X""""|Al/ͮan6XIمCH8ZWFʙ_ն귘AuN"FvBXٵN >*gd2RڥN3}-*V?:ڳkb\&`@eǙ擇&iIc KDDD@@Zݵ" am+\.,^kͯ2zU/@CO&oI^ŘC߹[,޸iHM'p~ޯp-X|=>1S +0~XWVV=j"I2 d1zNz%)~d.&BszqV9wF,>†(h"C_}?.: MLg ;vLoR1dh '3$%Wp{ noas4("Š'V/r݆nv`cn QC..:ɞ)ð_: F@v#i[to.qtubb\& `\ aRfEܡ{\ W(D\?eԈK8yb`۫u pUB"&f+$:ʲ;vQ4#4"(eXNEClSgӷ}1'g4WEDDD 4 40y a5/'~V7>U[T&@D^侏^Q',>ص}zfj;ҠT޲iߢƏS҅Ĝ[8 \"""ba%x ~V> E-WhB6k= Y/%6l "3hw ‹"궰0t3WڷUDDD 4 4_@\͓D Sq5j"L<2ߕrR(Дi!60]/=#^lt&"""b{0b!YC IFѬFEdAɂo\^8cExД1X52.W(/-`LQzlorhrrUfO!5IO :4qFnjQ_Z"͊1q5:k&3B&E<9f(37o_{to8Əz!Mc> ͅ+""" 4\ArjwQMA@S.tcJOkLDDD@`HR@<]3{sO"^Ѵs:=n Ipͽ?CYw^ٺ>7- A邘Ҳ`ɠfpFA# K}ZMz8K\t$MnYIwgڴ&^4kpv-UpL؟Mctz"ԨD͆iq|2lb#~':`AOG&5j|7EWD@&Kʸwf6POi0@`nk;B\UIsPbN:8Xrh)K;W'erٹ'Z, "`AQ<!~аhZF@s;IfD'Poqy{cPPzBlu[ 8LsVzlǻG\d""""OvUՍbʥbsֻE{ti o}9ƴC7ͿyX|3cg 31flY֙13~ٗѦD$ي"iʒE,%S2~~ҏ/t}ǥ:wg>y?衃hoC,"""hF4@*Xt:7B~go׶'h&M){mRqٶBFj0b/ α1鞜4Įfc bP] (U-s[qB@#ʼhEsh@LZ~ss`D_Yt&&"""bͿocue433T[anJsXCDDD 4 4qo(c"|q18m\<iEį=V{gzwe{ X^qh"}=,y=v (8l""""'0HK?*+8""""LQH4C 9Hݨ_;Q#޿OZSFh)ݏ( Ħ]㇅n$y1A@Sx$r+Ľ|)22ݨV4K#bkρ1Д Uf^zuFժQ?>ۼ9""""v $ꗻjhaLݰW=Gz4p#~i ۢ#~G@,7[#May(gQB@T"'摎?uS+|c1ևa2>viR DžU_3|S%F:Ώ/wMKh":3qU1sq#ie4/kOH#H<_jz Vb욈ysa 8E8M\Ϳa{Uz?fL)'M*6gȕǙF  ;Ώӄ1u'C(#DOugHo8a7WG@s5V@sN"HEy૮j{k3 le /hlZxf QDDD@@Sڔuscڠ&&~&E?7߯e- 4\W\D4hnƻhnk9Z e[( sBRUL1 hh>km"12#O qDDDD 4E3_+PW]I]&zwPi#ѣx 10Ns21Nli|FS8rW-_=ФE}bMP ` |zhn he }$XFI.b?[}٤ U<,{p!'("""baayѾhնf/Z 7mW8hRE$9b=ERqYmy ɮ1Eʺ&bF"idwj>[7tm: D3(? glg.Ss#5sXuޯ|mݘ,4%ws5"\?#sbИ6Ak]7v(tG,Ә mRC'"""bLq? oѵMA#mm| "M|Fjk+?3e&F#G`GhODDD@@fz:ZIFMGS*LZHs3wq\F1W@RXK|)ؚ7 EfӭŖiR^V>ODDD@#Fh>,4!^9ֻf`*q-[Bn]ԭU 5>ոuYe,l'npWޣ^}59ۏ\=|Q1s 4!9sǟ CHD"O& y,۸Y@s-zNRX"!bly>c7LJ 4_vE0 L2$kJL-h*`HDtqXj}('l>{1fT Z|?"xzTp8 4+=e 4Ye4٧׫ֳ!X@#_>A7hXL]?6G5H(h4@ L[^Qݺ UxfhϞeO+78<"""" 4;N롣7B2}[=?߿U- Ѡ@c6oN@M@ZϾ"MP `L|L@Q{^xFn0+γbM#9x"""" q VxSض{E-#)Ӷf 98"""" 4;N$~[^ߔE{cx>-jbA]lcpVm5&/\Q~AMSzΦHhfO0wm4fzh{c Lv9/~bvGhh*&tYz5Qp,IN~"`VXhgċH"P5"dHDxmO7exQtjΧ0{Cx2D5esDDDD 4E#4&?܃쒻cځFY%wEp1i=zٹ?HyqXDDDD 4 4hƷ$Tz5!:z#lWú;3T$l/@V$r{zXOIC+$ 4gtE_X\{[3mXSRZODDD@2 q<)!w:GÕǙ/^@@p5=gb."[ lOt$(wiQC3hgd!6ē4{#". VzVhQOhʈWi]3 M1L<Mul0nQL|^RPBODDD@nb >DDD@@[e٭0K-u})ϯ2P]uh\1g27~鵥^>X&X"@L**鑦rY8=T@|E<{hXpL]| G<#Ś6EsDDDD 4 4@CDDD@@SfrZ(C6{xvBÿQs[ 6fG!pl=aƢ%a Iz:V&#I@ DJt&Dy~8GAej?uoVa,α1Sp[ODDD@@C 4DDDD 4 4h1gw_՝1sMىsȸ$^JG+…tMtVT>N@ZONc2$y@n/:,W+?NYI.<'źgsEDDD 4 4@CDDD@@#yFX go_f殫O ġ'3Y3)fTa΅@MqzJ6X)(|O|M^p ĥ )tRPDDD@@C 4DDDD 4 4 4hψmzl]Ldk 4.h.EZϤ 42݆EmzXC13f/Ҭ"hhhީ@#ΙX 7qFwL@!mLoԍmDn%wsUkT@## uh8(L_}&[#Ś|"9""""b!"""baywQZ!&OŀV5ѥ{h]_KW?e\ǥүP_8%exoi94"et*P@Tӭa 5XVicm1=ƾy$kZ[10  ͻh.{q?64* /tD+;̰_≚Ĵt8iDQGi5|,Um+l4lV ,bʊ "V>PDDD@@ `Ĉ3f ;TVAFK#k]=?VM[₽QVf2r&bj=%yaK!Uae\494hIbwb]iB M)fBjj*^ F""""61rN|qF!Qp+_r[ mhp=>ԚKAlMfsE~&%LbMa8""""ɓ' *h֬  f [Ԥ@# Xkĩ )hn=܌w/qO% 4c\vi16~ &)hh5j 11Qcihhhޥ@%;[i\\|u '%" 7x/jfƛ[h&=\-B*&~7s;jkh~cqQ2#ŚQpEDDD 4 4B.]/0010мk?!h㥮=znf#v.n*FСpxaz&-|v5ex,] *0rKFjl1& SSa'Kj]s9""""* #   ;hL1ЯF>3Vlwf.gh nDnRٽibHf 1>mVp9~5 ƔI$ 4) 8""""ԯ_iii4 4DDDD 4 4b鋾-hD <&ΜkI  4ŷ35"<\j]ICDX1-:hjk@߱+`2(C>R)LQDDD@@#* #   f5(@ xR@}r ~6׬VLӼ@#<@j ͣѥrTF5آw^q &,9Y31/ekUA10||}}ihhh 4?!Цt#4G@)WxK)͝sN]HCpڰ 4*W.=K]jM % 4Q;дf }㘱La(͓=N{D ooo"ɓ'ѲeKF""""vgG{KSḭ綛4{hyoׯ 4'%@t,.^`{OF&o5e],UQFWFm 4:_w#摮<"HG9F/{weqq)\!r)kjf*ulڦlr*K\*DCSTT1WT\PTDpwr_TT@qsv< h4%"Mhh(@@!иaCդujp窱WUX{kZ@c`QyƹKIՂUqھ 517-Ь 4;Yhm*195`bd&jM]JPi9f4w @@!и?м`Ui7~f2 Kh9gC&w=Ȕ 7߰"&ZhvΞ+1E.+=씚=m虷i6}z„w"&@C!h44ﶬ*%>uӽp 68(3g|}y߼uP@6Gh 2Hb+Jk^5Y3jRH!ƫ^j[泇_C7닉GL(@!i4M,Ie-|TkÈfnu<[Xh f_JY bN?uǰQ:z2S~c'j [cJ+7+gޞ*aG_+2"ש5#|]r @@!и7|P5) }?wkwߘouXhA!J=jܬu4ԙ4?m +t{%#L0=`,=jj՟\ywkY!yh @@!и7t]u]X]\ чݢ>4'+f&^WA%eQZqedf)@.^tFJ߼OA3M"^7@szR @3XW i( 0z1(z;ꅹwĪ1-ǭfB&@C!h4>F}|Z-uxm@#`+-. @1$ly9cJq-t=1d5};P_MɔwhW;Wm&|Gϼ5Nw 7Ԭ3Aow@33 )@!i4;4}w N{v 4Y[hn^j\-sI=V']+h>z8t>fГ<6IO6Zw5C^gǣv@ )@!i4M 4OsΠ́e}K9rm1ODzQ!&V'Je: XO8 NfEvgÝxcFyYLJ HC h*VP/66 4y7ZhLΜ-1+9@sE~<ߛRsg>y638sWcR_Ǩ,䐻WR @@!T@=a-Vsп'M$ZQfz]y-!KfuLNk?Rn֧_<i}6)C==Ь^Ȥ h~HuV" h4!߿zIkQkXPzW@S|:{bg1`AoW1ƵzgHslsYxhf%1)4" @CwyC~ROyZIk~ZV uv_hCq-4.5M BLt= .>aLM|qq4oSd~; _V̤ h4 ~C6pçoSa&a[h91gzPuf@~Ԟyu4mVĘ^3r+bƬq)ulf"{g21@CH3yd" @Cq\BC6Qݥ#3Kq@c 8USk8nCޖq&WQNGmۜU1s.9W85?+%31@C!h4KzQ%hw՘խ_/6cȤ:p8MϞJ?~R[ҴQgq;r Mv&{l͑ۘWݵYU1]s(db  @C!h4ni3Ǔ*ۆʋj)~\siiZ3nߪՁƥW`,X,Ӆ_P`֖]{jfeCmh\ayI̱-_L1̵f'11@C!h4h\[iVU>V?P͵_}­zlZI-mqVi0gsY]3#*Z ڦcY:wVBIYΔlS`B] HC hnZ1[OJٮUR-ە-7VSo[ln~(И^~ۼU݆ W'_['M$)v$ bv.Sh4^~QILL!@4O 4}zCYO/Ŷ(סM~%ޣ־(dM1ezxpg#~ԤHA\bVXhӷ[QNGzvI9 HӫW/B h4h^K&5V5/鈦Zڻ?SݑiM4IE 4F ?[~RYjHOTRZz)k<МM)slW{tIY DS@dB h4=Ќxh&Ӭ¥i}\'W "ˠaZzTιs:}rr;5 4J\q ??g82k3hS{",4'-ߣuٹLP@!h,3)))jݺ5!@4 h\lZIߩ>R}ƩMڥoZU-j]ס?7sڟrXGǰQސISJ?dTMe9w(@s(Gc|:WH Ɗ8c|D h<#ДOhÐ}7U[ͪtOV$ fฉ >K'ORqE9P}9dMm&Xtv ^QсfB& h47]v8tڕCO[WUdʋl"8Qx)*:XI z}hˮ=Z*N bVk]:z2SY:e~/]h@Sy҉2ȣELP@!h3 hnW֤jxxci=&t]rh##zC}9ǦъXuQY5S#;nڙ:3ͅeoB4@C!g4+t6qȿݭ>-o"0oBC~~WГw9b/kM˪zMafh ޵U1R{[h.sli=:^Wx*I*4 q@@!И@D5-][|w"5ǝzU5ת9WS믤#J8cZnL0Ȧ@MΙeҩ\Fʣ#LR@!h3 KۢYSY3J}\vW@~1'zfDE%˴$.^Ժm;+0PMֱ/hE'6nLR@!h3 Ɗ@Sz3k_uu-[Z^R,Д~o?}7nb7nVY4.<Ф]Wс&b]Th4 @C16C5ީ+QF[dP) +d\k,4I;u =:-/f  @C!h4&ΧhغRt#UU~+h$hw`gTQcmh.e"ծ:Y-h|+Uh4 @Cqֱ)uǞfފXg1<8.^ {v<䥮/w#{tٰ*4 q@@!8lCͬq?i<ƜIc@c&<*w|syt*4 q@@!8ns zCmgС߶ 4}7Tዢ4oeB{ϖ覑̝/qgG+@C!g46-զ^j^E-k9uqz;5?rv 4u8Bac_ᓚс7Rt:*4 q@@!wrgזyu%KӻܦWUVXQG*ڒzﱪz=Ɋ\gLP~c'>*Dٿ@s8+Ε+=-*4 @CoUe)N 'j)sn ~5ʜ> 4]觽}AmwNiB>&ZhvΞ+w#=:lUd} q@@!7U['zi?nU1emThTIlh@gXmݳOO\5ct2\c~[Ϳv4{L$ڕr8;ȣ ELV@!7g4}MЛե%wj[5bcsϭ"jT={NZg Д+0ضjq[ccŽB4cV\e  ƾ8C hh~x{w|_S:ߪ׵4gOPH鋗?0NHZ@_RR=:.e  ƞ8C hh;Ր{酦]gĸӘFc}@gxq];5Za?~ߕ\EwwAQqǗ2ˎ12Ú̌j01F&'pY w ioɴ]h\vH)*(Sdދ !2&$ 4wx9]kw饭4-\"""baQw} v8(p6uuuh@CDDD 4 44~z +FbXhj HO|;h-ɶ[F6`53':f4<+f,VTr,ၦ(TuIο+"""9zĩL^$t|Puu5<<<hF݁f`-c5 ww02n??. ؔ/VRDͱα5U0 zVDDD 4 4* 483= 10Ш;Ќ~Ukv[j01st"RƻⓗhM[&,iySY-ndߏI$q?Ou8k$dٙf.Z10Ш4 2ę q@3n8"""baQu5QnhodzFOC;)i1 0&v|Hק#fT!5;sۗ.("\o;Ϙ۪4 JMϞ=)Έ4>>> 4DDD@@@,( u]4_|@nNq쁡{FwyRhDQXismAF,-1} fW`uh+;Ǎŭ4.Z10Ш3ЈTg3{8zՕhhh_:7 =vȅ~}#XO4k2'19l1FD!{OvWî [X3>$B 4d؝cQMUc-.Z10Ш8xyya޽L2E(͘1cP__tqȑ#h[hFFR XD<=Рs P35X3I;Ѳ }]dYbX+a݃;ؘ.m7Y_4_ew5\Su pEDD@@@#v튙3gbj"))  JBdBmmLUUMc; 10t@#wZ=bjmF={ >zQ/i0}4 4DDD@@ө"Df=juXb-?vA-R(zGaQT16aaYg,aIMnqb֦"Δ4Wrx/\s[Ձ .\100  ÙZ CHBMycn\io^ 9 |V̍ITti>O"cv繡UՁ& WDDD 4 4 4@CDDD@@z[p CZZlѲsFqFvŬ0g`;sI,,I0*:sN"νd?M-m A4vAKh98&e&TC7JPA4 l1༜3|߁g~<',}| 4u+h@h6xW^T_f-,\/ gkcgBck[ںz±4g/uYf hsKu)m a9kq@# 4{jFH:D< ʢdEtwm[{&vB|Iܑ 7=LmPr!Mlt`fg>M!#f-@hFI^f6ӪM9i_Mu{=oy@# 4@DxC9,:д.Y^@#44@#Jzu+@3|9Sh3+W 4  @#4hu&hNзT;!,g-@hFh "̍đ$i?S9=94FhhB4m-a(}C^ʼn]Mu3o@# 4hn_ ;DiGءp*)={i*8ʕ3L J[+UB酶iiHi ziK\LtaָJM4ΙtϊgL2̻IC x<&NLZ]\ܘi xqxգxy:saڶ L n<4NLX[}f$ah+>ς~ c7h~~k]9koMF,"""b!"""b f8wG4j/a| J/G Z o ,œXN)RD3;oa#/aTEn!MeJNc48+iw *&vj 2R @#|>@rRPo??hhh6Qܻ32Fe%? W( Od]oBBiDۃ0SBrM+"-=N<>zr9gtbL)K,(2 8dE]5꘠ ^kf`↓[f8 .,.8FtxM;|97^(<3?1u$CRdg#GEߌwO4M^.N~ZĬ6͊52+vL_\pL\Vi<'sKmh&n ͠^=駼lJ{d(4)JO*z2|L6Uf 2_Dϓ>Z50fsI2_hΌggƻԚƻͪ f4f}l|ar8.e,@87/agnq@C@c@9r]@oρ'(++, uhdi֩[;b@!h@ =Μ&͞)SqzhnQ&'aYW쬴u?X! ͶU ^6E4["̉ 0E֌h/s{|.ߕI[oyYS2mh%Wq@C@c@}zƪy_SFtdNg2%iai޾Jw9kΙR[Eq@C@c@(A^DNb9dmRi]:0cޑN"CA |W9.gqD ;- h4 h4[qs2kI_Ov.mdf50P:jZɰ6bY4'p@C@C!xBI-Ȍ-e؇oHO#:Gs:k]^ltպ 44 *8@C!Ѐ@@!hhaZoh4͍ Q1G>Nb~znj}Z7Cֶ9^Ƙi YRR3%LG1Vh4O+-<̂Ջ@C!и9h7qq+Ƭ1CEWZci֤#OtolJ'RQ4{o.<z3@!h4uhF}Bzi $ttQ:i!`h}$q"[ݿsl_*Q@Sϖ0lΧ74$[ 45J-I Q&H@CoE/f-Xol9T![1wq+h,0W2l]pd_[ͥGmhȾ ,X0#b4C;o mTf &.\<Q+!fBȼmdxSR@O` 9T>iR4o* 7BZT&[m 6ԯ-Lhv^:]rpqX'R1߆QDN_>zSƅyd1nWy|3sZO&yT Khh47{ep|?hJi)qh ʡL 2!9K 14N).,vB%&ݧ8hӓLY@펆U+.\j-o>7[:дѡh\[Sa!ԯ}[7őz`Wt<}ˁ(.94'BﯡC\Ω@h@h*h=jVDٽyu]m+[ߛhj>[qכUBT4l(nسec46ĕc#qL<p'rԒnv?!݈[H!}00s\8su ,`b@@mB!oooIl[KJKY&b 7vŶVꗗ17rЃiMJnǪZ!dzG'#{1ZO{SXſgQADaD*8Ý[}jz+lD60?~747[O\\CB!B!BΚZ#cpy)Ư;v:96guVH2&QyIͿ% W("ޡ玝@ `xVXL5s{?M+>ߏƲ' !B!B! 4vAګ{uzܱ ͗lxDĸׄ/,mr~_ab>kcP8bLeL02[iSm~'[O1edWoDFSO/$Gl||/G7Z++܋RnPRsFY_ޘ_kyYds᚟> ~v$B!B!T 4ZQ j`܉뽲Q !B!B@4K\HT̗&rI8*WTmKdHe|JXY4}ؒO=>IO J@s8$dCMX:2G4Ŗecz=JoXVЬo:J"!уf5.Dt#recG/CB!B! 4S/H & k@[":Fi,} D? Aq-d&xL#;\wG&DfFii.=\X\ұdUٯg~4L4[YAE{TC系w] 5B!B!Pv39]ękmW=fV鬎%Φ>Id ŕ@sy1m:qύxh{KJ2~^}eYA퍤E |`B!B!Bf*wp2OV"Jz?osA HlKeh~IM(6tƍ@#1֬k|cp,S>vi/чovz)w'B|zwfc v$B!B! 4( 3ʵl%,%.N@ jcz^~fffgMZm,L|ؓK 4?_<aݻo2_=}Hk{O |GE86ƅN7xRhQ*boˇ&!B!B!hvxŮʳH9s\U$7u\5m)>V0(\xn.%D&:[ݾ~01{RDS&g?bJ$:\4RK"!e 2YVP92eWd>zOr}"h"eʰ&-5Let{Hؓlɺkc$e " Gf}3-.&kI!B!BhJ:#q@ {81:J)ݔ@BS/.Iabfg<2P DV2FьA ״aV TNt-QQ19M{9W[eõx /w+zq3>qd1Y?X(eE\cP ;f3U|\o$I$Ih^?+kiUv OdARm?y|;$I$I hff@7qB%8ͷ:oS\91)qP4c?j6kkٕ2D$I$I2M(NJ~)I󟗸QI^C~LЌiz:=4߼I$I$I42q 迮=Z`@Om|86$I$I$ h:D8 `@ h h@ `@8~ع&8Û Tx=x)+1eCh[cXZCcf#?3\sKYV~=óÛx  9²\ùmlH%K*KKv\fE `A4>OK^/kvvh.=@.⩸ǽwkCA ߟ9w /XД9H$^v P\."/7O̫hxk~nNk/&MMIR($%*dFݞLsP/ M әo3*hzshҒ yE}A_)qA ϩf~v 3A0pI}Vko7nVvV[qulW͍k3|Щ5{ӭG=Yf <^jOy?gߓ>5mڦ:629mmUí-Ec``` """/P(T*Y^5p9` PfM#4s,fg{ ]᧰,'_X~Njss7z ud]빵7W՚J%+o_oKpFQ`lQkk[  d}] 67՜xyUٙd]!L2̀ LҗV O՗b1ʱ\!&͠iZn>@̑@Ad7E""";h////A \.')u]HYJD0Ѣ(0>@(*ȋ2"0K]W3?PhY"2V7.ʙqSst>C/s̫3h xy߫qNEvTǍC VMrSmŅ/dItvwbJu /]d6 Ƙ_*4/s\Q#N9_Bq.չlku WDD|~>%#C։Hٿ_#WظQ֊`%%IT=}_LD@JI"Y7s~\4,<xUi/\-;rxpp?*]]",> !~A B(h^yQWϷbqB Tf~/4]]Ep xܹ*s8F{N$ŋVv6L In;xP2D3{^vVsGDϜCB!<_/ B!Ќ#mիxzwu @IYRx0sm}|t$Yic)|zSCu[,PNȎNDUe#7+KٲC֖p6oF?m\VPs%%*#EB!~A_/!Bx@}h쐶&@vJ>-yڰ*"Uƫ_ "I檵ݾ ݚ@ QhIq(ݣ9% Rv(.V/"#1(==lZGYu4qq*۴ &D~@"·"!Bx@C_/B!<Z(iO0QO=5uX~/]+[ wtfdrL^MQ0CdnYW7` )tI[Go5'OFˮ\aP9٨36I;ӳڰ0ȭ#Ш=e+KgLa +=~\kε)!JN+|.Dܾn(gδ^Zaޤ"ʄc\\s{{σ6,͇aF #Z77$3hjQI dFL6;;'&j{J%9n\z 4leE::<^@w/ `@#^zN gf47#}ۭ,FCj6u./<@w/ `@Àz+lyYJ$[OOyRwzɉ͆2 `@C_} czڽ/33G,-Iww\/  *F&#ٳy2wjuUMk}A_0a@ }0a@C@ h h( h0a@C@7;wVaKr1 ]ɡSm 6J;E)EPB !SK1KZDD,  *,h ,h(,haA }rZ˙yޜU*5?/&/@_4BATA:>>a'U2KÀ:n󝢯PhCѨR*(x}A_͌ ˶3`*LPfJ&zS.2>=9c 9aH>`l}43.hRp OuiEZ/)懾/43.h^\[^߾ j:ux9;#ȯkF_LyyxPW~UU厹s+1#;γwZxƑɜdlS+wWjv<[/궴ުn/J8vL\-YN8M\NC3`#R<.y/UR0(O%( 8+v ܊V8Dcߡ)j)H iS(rAc. i((X8I]SȄxBdvY7 LOːyʗ(c`` """". Tdο{בk`*77dX3lA1o[P|W>MnN;MmMҬ嚺Y^5gvX:]\_<\:(Tc7v;97B!v= 9R#!^\\&>,67o`F1jq> """UDDDD?^Ѵ|l84^a@vM(ŤxoJ]RC{Ӡ";鯜*i6<+jGkbBL8#y(AQ逄]EMuㅗBf;a-v[L6Sgr6]`Ymle`:i ;fx/6h#0}s[t٨5;ܤ8U $HHh G """TDDDDM>y=:*fahhzߠ2Э||RO2QS_6"x\O5YE_ jj@3:T]B8[ڑ#3ꉉol<"c_^e|pE* """"&  jLiS]uIzʹe?z|5(\3dSO.,IHf]]E,!@MNݻ!z{{JfI>-͘ ADDD///4|@ V|y߿e_K Pzow'`"!ܵ68tA&}e5=/u AǍs@Up.p>X@_DDDDx@4dwפŋ[&@MzذTJg}4;B,9\{Xdٝ;R?x!Tؒkoǧ˽?s7BLaԔQ /@?oO """ &g0V ,OI2jzvZ1c PUU%?baq܏P=e{0Ò"u"pܺNP''=-RS)%nnaCI [611( fÂh;``` """"Ьyzrvtt[bTxP/6!7s)ժLV:@5ṁUqv\ݡ:gdY >tx_m~=-ףEyv +q\HO܍8l$%A8 =hhV]-prsu03cw +@7hSR{ """TDDDDuLf$v;BFOO,+{zv{\J@BZ~^ETJs}s ;/KJ\l $J0gVt7jNw#L%pNRSU@HYs9**%ǁRY +  """TDw-iqǟR GO@i0w66V 'h\a2/ 0l>lUYmG`؀} 4 0hP`q_ 4 0hoh/0и/@cq@}h 4 4 @ 4J_1˭U0Ь3Ќ' u{1buQ+&I_;q_{4|'ڝu #<3 @sρxg XY3R) 0uI2h$I$h$II@#I$@c$F 4$I$$h 4/v27I'8(aa ;ř,`1 u8ǜR qTN=q(o92€|m%M[X4<ϯ}>>yO(!B!BACAC!4B(h(h(h!aq|r,뗬M=X!44l1Y~cAAC8EV׀X 1?2q n,5vJo9{Blǡ̏2256>Id7/7 BAn}z.€i8hׯ}V]I=j6;4m*ȉs g`~$ր[?sLdQaKͰ\BeqVgWȘC6{e vk,&l0o0ab {2ߌNA0Q83S-hw{c5(:8FmzgPmSa:=rbl#4E4Ԣ^_RxG& uZFR-I4$E 9'KSSRc:X4_EoYq3x- >>Qoxؔy_AV;w'h{ŃA '~F҄AYN,pBAPA#B{,U*hAPU4'"r>DAM;j*~Q!;fW.O5?O F0f#=5;0#9r %!WhFi3@@-n4Jo槃4fボ,/Թ"%M-0Y448{k XqsU{.k14j˻gn5?WbStYNOEDfr'4J4 ˟Jxz>ms> {b%ձ#PaU\ʮKXyF4GG<7/'cByP᷿)hLvdho3S {b%Uֱlĩ4 &9VTX-8SyF4$rLL-ʉ@(h4CcXY'(^_jl__CUoo}SVTomn?28|Apu*䤦WQROZ( {ApgDŽsޠ0o^cClj%;vY8;o>>Ȏ'6E>R]?7}{7l4|q_ 6'OԗW`yq(v]^9ohk͞󟇛~376a~#7|ՔO/,$?*$~?|ۆ%F//9%%C?|1/]sgD3 wGiƙQ!s5A>-1j}]^ݽQρ' TţF;hN?Gv̀ EtjY_EC/Kw{SPVojm? 2|9ßqu*G$WPQ>Z$ {A+`DŽ0/ܼ cO,J秤%w+X?8oޠ{Ȏp7v[ /8S 2rp")/p3VSP-߹<;cQ(wL[6?7}fdn Fn))_PX`T6}Z7E __MXe.X'S Uwbڤ&`=LАfޕFgQdq EEѰ0* ]Y"*`!a3N BV !!#:80sT}:~3P?t[ow?Oսv=:(POyG|Oݺی'l|ywTP 46jz_t;Utƒg-}2{[E+(fAsFEQRlZ^et8C~Vh@>:a"8&rvr+g`M fA>KO=~' ;%6nDwy'~sgyZysl$1#HH6HI&"[X>o>m쿁&n6&!_~cbuڼn=ͥcGvGǢx[/gX#W3fA#TA1ݝV.^e-ΧY gQVJy $?L|A=~Y((b6yoߞ9upg,ZL>gzG;`J^D_8藎 C@$48奕QFl!Ĝh sϧ훳x5O˩otܟ,_b1ϧdl`QkeEIk q*()'b!?M 4 } ?rv+a8FZⷂf.FOz>!6YȄgsgѲޘsl7oa~h r k,M};SrEFvk@|2VD^)iA*c(|q:E{ 5O{>@g}ZImzǯx^Oak@#n^qM|oL!5!~2XWPH*<5Q&~_ť'`@osAc ˝;`nfzI[ $:MyXn a-†2J8b S3r\?ݴF '5 A%AWkqnoKizL<^/z 굕ZG`UT|Rg1'ƞqO:up6{_`,Ư59⋡@33h>΅x!23@1:WS{-{w/ L#|\p%a߰ }U2WP*K)"<DGFB,AYX0ضp@ 5导k/361y 9ڤ9{Ex"I?cB|ˆM?.\µ[ȌKC2<0fh s=k} mvMFm3_~mh4qm뮻0`J^D_8hB39. /;-_DY02XWPP]:J}Aёm>r`=V0ҹ3xHGc=t 4kq3={#,N._hO:uL^(c}%܌/N6]?^CR0  aCm&_93`@v#<cWA9i sP_}i9ھ\[3UMU: <#yX] \[C7 n7GC{RhJJQVDn M޿X,{F5~++_1~oiL,NQcy==<"]x2 O#el`@K1_k*;| ׆ L(}?rf 8rFhckvr$Y=(>'ED3ޣ ?3+/ i2L9%etuH4HA8VG6-f@͵YX;l@SPWz0;&$:6HTn6ybVhLo7 yu \_.i)Fg},[Z⼘VR*lgU?7R_SKy=BV JҬCZMՊj./9e@Ǵt\QB KYm٘YUHR#DDbŐg3gFBzxu!tX{q~Vg)G8,$^b^Z<(d@XQx-Xhh6C͔1>}-}$?S^ƯWz%~%@>gađB΅dl`@V#BSSKY&`631^:GsH$VlaoIfbD{±bH}`Wx AH(])$3ydU$`i &T󽴺@Ytx@:.$1l66j)b|-}/C@tqm's^(c}%܌Ml|(r|k ,h@!Me?lЍmX[eE%S{[Y(r’Y$a5%Ttkvu>D <&dJ -rkf'+6A>HSQv 4@=6oAN 6j)>5ʖ@c(_ G~޲cuޔZ*-im$[E+(X$Фw.LE(G3UTzxS?s3Mmb5(˫->AeYȉ)Gx! tȯ),*-jv|D cydJ -(rf')6Aج)vʑ|.؅*cxpfʿ|`K1}_->?5~-͂H3daK 42>xHGc=t 4O7NޔS"-$ϩn"@(͵Tw I۴pv+eU&>:sbC wj  {$ufA#$}#įϢ׃Y "HƎ6`糑w+`fD۽|R0s` YMl<94o_ lzׇ旚? ~ M8\RMa$+߶jNDd_NIFE8[6`*2AFW_~?5׀s&lf򯳓Bhw?"_ G~*.h#*ÆR7/͸Pxxzi.lECSu{e0q?vf]cUiu4U=wMcF~ cHuhѾ)Ӵ}!v+%&*>2QMtzy 4eRj ̰ۓP!o#$?w"Pؑ V̘#X6$~w/Ä~&f}MŲ}o73ٻXm¡rkJm$\ND>|>`\gJ#ׅl_ES&-ėcG Ss 8l6m4j)>섰;]ƯɆPJ]߄߽l(֗XR$X*.5P2Ɛ{w/(Pd_{ 4eMۢƺ՛eO\SYAV۹'VL|lɹӭ t=w{cFHتf Jq$a0;II]-SA٩MQ^u H>lG1 |X p;9;3Ä:t%a p B=Ms@.ϻ#a4)65u*HLnKRL.?>+VKZhPQh#@RG@n~3y~NzD1W$ X#o>9)8IO?XzA0VfF6?No h6SB}ϖ@c}Q)!F_ ƯJJ#xv|CDmA-$.`@tv~^ɺk\{B6dďXԷju9U淮pu9toB*R]Ae}[-"MDTŤq賂anL>9 p;vrfߋ u.K³l@v\!¦9 u @#Y3,=m 2qܾ/ -1ab]~; m 2 +y@6A_70~YԣWOCzEoX=2בO) rW7s0^0AiΏv|sg>][]^@>1j@[8& [ʶKx{9-%L 1VC6EB"irŠ7 զݲՉk|օd,=羚\]SVVIFzXͅUľhHq$^yJCF`+5~ AG`+6[?h qr^RPXJVFw-QD?x %DDϐ"之%SR=EH1eU&|bYI77Ӽ3.:C)X^-lK2FApKT`>4}h%EVݼxdl 3:<-Qy?RUju(hlz4Ҝש<-~ MyAs8_usscshɛSݯ=_M26ey߆-#UXFAp%xC1w9n_ׇԯߝjݔ6 IƦi^>Q/f&jc{" F%cdh(su}~n[7,/$kD4 H7F;g_8svl} {å$c͝(hZ}?NUp46PP(h/P_FAcE+*aqqD`Ҵl( Iƚ4P_3_46Pcyy90==#d2~X, i"|~~.SSSo?QY]]͆rXxɄnR)ΐBc-P_p^da 0T88h]4$W7b`b% &ڄ4O@K Px+'q(zx.>''G_/6hZIj+1?+UDc18NXV8F)P*qrr"M4UPưo3RLOOc||>D"8;;y`̼h<|ёR:p Z^_Son>?I>\^½KKom!^Q|l@rA!d__/ ~AaMtOH3l{+H( ^{L] @BVamm z~P(`ffFEQZx<0 ݅db@@R) Ѩf4dG$:%k*bp#WcK&$r\/2  ~A_BؠAcXDv}L~f_~@߅"'Jڇ(0ZQTeI뽽=yN:BZ/ JHFHSMS4*D$vVVV{D  Q~`^/ B4}6h%|/:mAp}^d|CDs ,\cTtuueLMMassّ%jZyQvbaaccc\lii]i4<ƋyM3cABb1Eb3aD2:2%=g_"mb%i$UB/^(MiiD*̛7tv_ |F_;ڵ +ppf͚U8lXvlRSSUBB:}4pE$uQ㾐J#m%/sqЦ)^pAm޼٩ ޽ݠ}AII e`Νjɓ'n`NR'W_}څ]7oT.]sʤ/lٛ bL c@aÆfɫ,/<"V F2ٮJ vLTvL wR!|fW^ϟ?ZzD&--lO 9cz^~#  |db *7}p|/_7F.&{y}^WLN8p|9uꔾ6gMAցXz5wf䌞2e ls5Enj_^x۷.BfZ@-{3-[# ;ۧ87"t"Ve]|y$QPW~v TYyqKDo]CUxz^'=ܺuk0s$$ 4JDYY*'7i ;a%ϥ*W  JDV825< I#&ݻw[}@)C(s˺:t%cūF}i&D B•J*%nܹ{C@*U踑H? H$BI ʠ/lڛ{(~o_ܯ~T+ԃQֻׯBMKRR8EE*4iD3k9P_P_P_P_P_P_P_P_P_B O,p!mnRRRM0i9Yݼ|}#kkEr o2g7۷W0UHu I[8eH~*)B.q:8K++߿ș"!P B :uoݺ̟?KmӼys=?`#S ;ʤ/ڛ{ (M$I=TRu$7xwߩx߮Ήpv>MU#!!/hw&%]QVO .4aޒP&r.VZUbyjV7嚐$g_d,6[p҈SAo}7+WwL8PWllWbÆ 3g'MFÇ*c|?~<"lӷo_lx({ 76Xz.(mok Ɇyɠm·u:$,˗c٫DR r iUzug2&.]n߱c:=ᆥݻww6"Z#}]Uh#͚5ҧODmɲn8yA8φDdC6#D[s>{,o>:6*]SrƎ=Z͝;ש?4oFs9j(]O0h_h%Ɂ ;ρg{ODmٳU۶mL&7СnOS‘2Eva Š_^D90 7! G?+8%ĵѣ)MG(9Gs2k$!'+9e8|컷o(;23;y <<;r`HH@}A}A}A}A}A}A}A}A}A 4y؀@K`5ٸ)p.L^+X,=&Oq5vr1:r7ԨQ#8$gXn+r1g8A)!...GtIFS|޳gg3ʐ\˲!!Dgՠ}5NBM8ӦMC%q>QϡC\ro8x~٘vah`y>ʓhfoFW¦A2t./f l>_p6r"my#Ɏ9Qix3൏Dd;~D!2.N:!Ql/k5!P_P_P_P_P_P_P_P_P_BMy:>v\/1eD>_:)h7::ZYva{Ye}d_D ]:y?(["3A\I~  _%--YAGXElS8x(sPl>9l0DoI (zqJ9æD 8"$wx0r /h/G !/h"Y@\REEE9&6H:$,HIMDη庲 6af~\rff-ӏ@CWPo/IyQ*z94uB/ P `2@A#_  /PQP/Pи`QPh4boo@|X,F^7  @A=VNt)}EP8==<\>2I[ۮfcc#}%I1227xk41zVpjHMuy9*y<;noocll,&''V :U@/ M _R)rT*~u?~|dIwppGGqv~)TՊVBV hR\\\D B^·lc\=kQ!k $!W .Uppj@ֈԊhhMfUm]mWZz|>缏*mfۯnMG`}}Hx|^D'uHDX./Q=>KnNNj~o;<ďr#olFJwNB/ ~A_B7hްAL&T*~ܢzzכoIM3LB)]!JHd$Kp8h40Kt: xYկc6(P7 ԄSױ!73!ᵵ5롿>>B2ڐf2zUڧS'}voژf$JHJuPBǵ+5~w6Q]}Dž.tF7(b!.ZT kh\A AQH҅j.b#jTDkb Ä>v&3=csN&gs>}/ ]>X8VKCCW㧨=} -_ܜ9s.EPN2 b;ŸFgoyˑ#G\ g8s5X׾dno혣wLѣO.A AЈ~mqT`lE/b̃O?uܼymg*!HBMH7h" .YVv ;|pE#݆S.OGM@8p`qRlzM q-D!M羥ۧs$ իW C<p ѕA)AkY@3qtDo (vp6AwNXX~Vɓ'O2!MWΝ;ԩSy}aGqyFK@=,.67-2pҵ1ͻhlkBH_H_H_H_H_H_H_H_!nЄp`\rTVU߸ǰ'n?wws&$׀XSS,(4m۶ipLRϸ$ PO'W+VE(r,t<ŀi˓M@Armkg’%KLcǎH$*--W:I:X#ǚЈTЃHkFc׭3 ꭩI(-^B7f}(4$yUVBH_H_H_H_H_H_H_H_!n8ߠ9ߺUa^|'OOqlUuuxT ( !/++rDpx Ο Ъ*,6 -c9Ax~NVBk;8]ۂ !b8/\$w@1S;dRD.''DR.zyg̷gzeYYt} ߱F*!HB =Cq1Lc&x>?xKkkT^^EJss8 (f\.\`Ax}}8/^Ϟ='SՃ}N)XhӶ-k׮p.4> <MYF@ ~s˵4gCs'"-8/$Ś0HBb&,P`AqPϛx_Ey|]lCBH_H_H_H_H_H_H_H_!n8ޠ\|kHx}C_oW6Ι2eb!g \_F<*h=|l˖-IV8?~XP`rStX\f`N110xM2E>1 XN1xUpikc_/M8qFpG~&XTN71q,Yfs.ohoe >1vbu n?767>/_`Ş={*D۫BH_H_H_H_H_H_H_H_!n8ܠ|1~lnsiS[[GU] ҹ6>͟ 'lvArYXc/PhqCyܺkoo7Sq~.ǩ4xvõ1w̻tۤ Fcy"\˭e$EpȥKc1k.'tTxoݺվOX> ̙3h=k84C--"5}uu t8džI'9zF}_B/\vr#fÇ<K/e̘1l޼YUB$B $ x[/B= .]ӢS_j###6kE(,bz烅|uGg܄ٲc^ZtZZٸqcR;w빐`FF#t#޽eQCg$f{|Mo>xbtیsΝK… YR\ÇY;wNH\:~E2,((9“Q.,kOah+" \Ƥ_t2/{P'4 /q~ ưup[GO%%0=-?ltYύrp'uGg\oʲePTU#I }!}!}!}!}!}!}!}!Aq61`f 6;[~/zMA*j?9ot0oڴNG,Ī$I"}!ToB44,1ՙBiiF q]7W:6>붱a:[ӄH$ζ38B77 !  LPi*U7O D[gg:vl\v#B\t{wTqK.r\# NĐt`hҦIT:Hcl0UmZ (Ub$ <9KzOͿ[[q%G;'lK /;2Ь&.//'gCmOo\!JRr6tq1^$gCwoIv:XXXBAT}B_hn@ 4 @hh 4 40h 4jZ Alnnz'lp/M]R7@/{fP(DTJ8;^4r9(ù$p~v>q|GkO }/0Ќ1Мw{Q=jww#E&l6o^q}4[Q,#tl<۸tmڎUn }/0\g V|>ׇPP3g /@sAb? ^trO|G o;Q`\-TTX,,Vr B-ڈ6$"  ' Ik6",9ɀΜt"cH$ٌjdP(NFPKRTv-ǰlt!@UU x<l6c6aZ!bv#c^cxl6  ߏ,P?Ćggh^wnۣLV\[>ȷ&}%]*c1 1cN%ILK#_c1v_p~1c'h8A#ICyz]o PYU};OQ߽MtY&t}gJGDDD||||ADDD@63$'':O᧤ёXVp#&&FjjjDoqqhtKQQM<X|o-ŋ#>moȻq 7gPTϴaEWG7U}Gkx#""" b``` """{XАLLNq(CX!4׋Vee%S]cT]]-z.+4h|Ȑ\t˅N6 s*++È4Bc*xgPa./XKKU҃gP=U_U}TmS |lT}ͪ_G3y#""" b``` """9h|;2<<, P1ۋ B-"nō>Ul>KB(*)A!==]$ @™RL4|ng.md\_ ]Y_^,16T}.EPu;\YҳY|{Ǎ:+U߃+Qhα@Tx1yz"4@ %& (,,kkkU+̺>-𚦦&&귑bM5uJN277'^A ۧz[n!ؼ774›T>kk{ޓ{hθ@ܒAvEz gUt]EEEMoyy d||\Nckk k8@544`l6,Zv=qϓ v [0n gF7Ps Pn6νwU]UDDDD\9sY\ZǢ6{~yr6F,=??P,e@0YEF D VTT`tkk+ق0tfff(Uw&dChJvv6F 1KFAF2Lg|sdTǟ4WW;+.WWgGB>[d+ާ1Sݘ?mzS Wm2 8{$Ie0_/$I4_Đj;`v& vUjZZt:Mf3&m~ךRF1ج^ASܳhRb\ܟ%P(P\miy3Z[][a)fN~e֯=YF1|/4^{'|I$2_/ I$ /hM$I.b I$I44$I,h$IJ$I1_H$I$IdA&I$Y͛rp‚Ƃ,h,h,h  ƂƂ cr=z;tpI|5=y<ONzK> ΗC+soJ*By?TTPEFޒ3:E!07N&wf=gw !РA#RH$M%ŵ-fd,;.q{,,,ͤʄ8/圄aiz@4;9oR:Pwav{%D4e6X ѽ#W7?`ֹAI2J9^s2%Y9_ݫ=|3?}Kh+}۽z@E9o7!B! |aK@KCLטX"F!vobBX9NlYZD!ēv~ߺ}Rrn}2΋ً04MLgde5?,\_-󩘆Ie_ƵBT N$wѹ+uمXbgX(/kTO @2h6>`/u>XPn}z9Za{o^~z1JUb+I`8 4p$b$XM-E<)󱬒5+b Ǘ-m}#}a|4{N !B!4hf)/_%v0BbH-+T1nݱi0ҠYέI*4=ZGjKwx`Hvb"yXW58Z ֺwѠy_oQſ/?R++(*ZKAM/ ް ]0͒gfs@h>xz譨DϯgϽӳAzIɪhcp8p@s(}vq: C-8ȉCs" JձY}SR!h͌=Y_;BIѮ$b_ Yi]һ.9E 0@׀~ .PY{K&lQ=GT" %#-Hǜc<<]k߼^IL8 {Od󂪄P":_(C.@79LooBdMbZ#_;aal.W^(Qdoy{v$jCB"^)Y C*JKe6[b${L[UT1p8p8\9aN0 ׻H ׆+˦P 9"Խ#'4VX;搳\5Oι5<+D"z&5PwQh?K̀ώnR[F 4#/7uxV?~D)^/9i @ !@-'"lHBnٓb-#RKpZ兀>!2 DY7~;8(7`qpʆD0XBD)T>uKVdΌg3Rޙ}z3U{V:n})/ )=\_qDb1GG%O ]v9XHyEɤ[r. RAsPL6AYd޹Z/Д6 4"{Jj=JPu"TJZETl ,CyX{ )C%Ԑ1UdNm샩һ-Dγe4qd٦.ɡT=0GaaaX2]vDBnSODԱD dŧhRJm}Z=rlī#ZE7SV% 埿|E> 3@CFYHg˯!N;pAu?MUV@ĆJis 4_)mh2"Jw@Gdܣ@:_YE `>I&u6E e? -3I>nEe 4lM7[SWj V2X=hثS/D sZaaahv@!JrD.U: ̓:Y:6X@a:@tٱ9D\h DglBvUqSwDwߧ,2\Md-h6PMeě!Z8zl<|RE `q>_RM )}Ϟ^k! (˕0y &.Hw3 D{GI~dl{aaaf1R(DJBOr$nV7Ą/DkN(s 4DB.jfhվDpC4a z6[J<|LB劸nT1ꍥ;|İyhk1@(6-_hx=P(oKl9>e2}ҼdtqB;Yy_"L'zy&9;WvRhaЃ~k=9hdA6.v!נ^4~h4 0 0 Ͷ 48[2*7%@^y?0vN]wt/!$z0;vls?.cp%^Q5eIX1 1Z(O4UиS)n}K)kSd6Z_@la~~qܕD5iГB4Ȥ"[@#c >HUUI"BW٠蔬ݛl#>w=b" W@9z4OF^=p`ld-o}w*)\*"k" (y)r,ִcão>asb>7tF-糐~*WO0kM#QTpu?i-!r *Wɽ&\;M<:{yFzwNC}g f} =k{A2kօfCaaaXn>_"A5!9B'V)G$m¾-b###qu"D@~KApCĩ{Kd7:ܓ^GeP-©2t%RY'֌Ƚ4_ ;<l`ld-%@KNYGe B@Wd?$$zH$DB'(tO 8;gl}IA#@\#bSFgMa&aaaXrDӔhGWz&#=}RC޻F5:^(c4tJly6QS *,mЈ(c6"*0)naahr3%sY*UD hfM?}&4ܼcYKܚHQϳ+~Y 25G62+z;Hi+<8t c"n@pNKl 4JՔ1mzh@&m>K;9y^[.bkq󈈈h b&xxRb͋qq/q ;KLwS.UGkzX9JzfQ|^=F%j6]|.h?fP~5 %6[qLȱ'"""""4ՠ&.1}Ln>狎st[6ߗ'r܃^455*7=hЀB} 4_/k~FlOn"S@$M:;;Kb1^GEF\OOOS;ixx8ٴJRP`zW@---/n%v|gbkj8s4*Ją"D $ B$$jh%.4bPDPchG:8Nj:sOV^{|_F۷{9,yJEeƹ;De*##o-==_@rX2JKKeذahiiipoo5kݻ Cirɑ@qZʚ>\  EEE!@ ~A!p %*e?~ǚ-7/n%%(O(^ҫW/IOOرctQķZnzS@FFqܐeȋBQ~A_/!Nx8ASW /_&ʓ'O;c pGkDQV^mk_7oޔ`k-a:vDEE?11QLbbbG瑩)SL렠 ymV|Xoߎ̮qq=,V}f, @ݺu TV3 6blxiӦQA->Yʊ bJ8!_/ B!4&hkHݿ_;cѬ51(Pmz!-::ںPbue?{LL^j_+3c xt xVTc8dddsdcY@VӱGfeڵD39pj\(ӧOA!/~A_B!7ASBy葑9J૶cke}dfQx;#Gɟ d)qݻ'g>(di̫޽{y7(HC.]޾(1ȍĉȪCƗS=nݺƍAѣƺ{M(-ֲaB! B_/B'h|HC}٧O{2h/v@NNɃ-Pׯ_w vh:ܹ#(k?JZ&O=W5$cƌqgmҚx$xOPY?A-+sҲ//N3I͐rWs6ݻwѧ?#G"hw':u;cm … Q[؛U]]-z}ۅGv6$ԩSD@ g }Fr;HJJjhV7oB/ ! M|^b":@kh T;dddXpU ($bA=T)P2o<,(eb-P&|[ b1Ta[| +, Ǿx Tvv6A!/~A_B!xSPPh^PEX5PvBqq1C=Z]p.,9(OR2Bb xl_n-P߿) Ԋ+&r9|֕+W|.Pyyyݯ_?SZȘrqǷy GF~o0!_/ B!4OTVUKjj$%%kf.iʋc͖#@>r8b ˍh޽+5 F}ŋ#s)89}X̮@Hܹsw?ʄ  ۴icQI`claaȬqm6"5UT[W^5m,*jF"\l6lGevM-}(`?RqȚ4)++C`׮]e֭c[oq(bHNN6ˈ5a/KϞ=eٲeiB! / ! 'hT|~Hll<р%1ѡ7Y^k8`D@yP6c q&!!A` _ H(Q`K苎@I`yرݻs-VqXX!N8MlE\3U D}a&!k.׮]}b7x`,;JhQ˹5GnY|K?]ӝ3␹-[ `x*5TdҤIXT94!~A Bgo(BL4Uhq^5Q\ކ(N"T7Itt8ZiMR壔R!9<|K8ǀ?r|q&|@gMlo`1uɲ,G=GoPmZ-=j)`A_ }/@c8esgZEQ4R~&86g0l6)>F#7mzBTN}/nGZȱ^Ot +FYn@v(1 0Ѐ 4@/ 4@/@cP`F@@cP`F@@s1zɳ~I]xm:O?|4nnh4& `|I?>qdY_ M]|e1LY@dnV 0DQ-h.b *4)4DM2PAhV灃;@ 8ۼ /.it;O;㩷^3tq^/͑j*5w?~Ơwg~0; RA4G4P} z#^yPLg퉸O U/́bCMX &nT*<^_zNOka>_xRB44,5\.WWe(dٸ. RB44lV;jVMB. 㣝{sM/TZΒ~L&ßgͦG< E @AxZ8sX /{w0aOEH1Nj7[CݴUi;_! 9=ܒ@ @@vh~?^OyӳUJfc@@ @<Ф-c2,)2=g4;w^~}44/cz4jY,tfy ȲjU*U 2@4s4O׏rZvAws6ߏ|>.<99ۻ͖ _ S?S>L>bggg| _ o,hT(Zh6]Y,"E # w*maǝ{=@hZN{\KOc@ QAx$<|C&!/hn>ФO\.s?8L佝n7jzxF50 >Фm6oh5JRZe\/7h4X,FRͭ(`@C6eYc155}W jZzpig0 >e(!`1h 4(0и/0X\~\uz$IfC$In#F$IF$I@#I$@#I$@c1H$I2H$I2h$I$h$I$h 4=:K ??E5w!I2H$I2h.)Z8^/cfaAn|pޜƫg1? .9{h1Ǝ&tKT1=k[,D7ƺGco`ÆƨRDtDihl% H{33̌ ;q7;{k֤̄$ԯO];u=.P1'aϦf_7%[lޜ.ŞX "hAAAAc)A6JNr*Sȿp'Bq8)2 <=(/< i‘TuQUBd?>ټjZkv- u\FPjLlXl\jтl7`s>69g`O;&>M<<8ݽA QX?$hpzѦ חx "hAAAAVД_Τ˧c*M^Su.&8:F)= EqϴYײ]ۊօ,Lywng'ʊhC̑́?Ljݲ%H?orn?ӒsDИ >$ݶ"?~<޷_3AQ4 #.l[HR$΂ AAADTF3L %_O/MIS,ى)86F٥+QAƜ y)hMg3jU,pTvmJ>TDИJb-^bf„ 9udooO)ֻuB%wJA4   1C@DR1*L1= 憹3•X >xRDY&%=uܑj;֢* @ltў/s_3/c21l%:(~WLmm˩A} PSC}z%z@ hMrޥ*#{ 9>~(؊"h@zZ6/QCK^qsϑBC*ʧ#k2UïbjPC#PCz}չu}~}H.4Zr6 nẠo3rH թAژ9/YplYg~dp.Ƅ JN ۗ3'dat/^|1#FP i!CoO%Wo08n<9ӧgOʯc,gV:9Q?߼&G(ڵƎ~?AdHկW^k MO*&~C 4&S6h@mZ]hh_:Acwk^8slJ "hAAAAV 現47Xrd2kФۻ_ #Aj_S]HT %b-Q-d ҥTcI {itEet1nܾ<5ےOժˬ)Eֱ/(uECzi!͙^DGSːcXCd@! GG}Y`aȳ\}HW=N=M}Xj K롯ӼBjb,Ѿ IibR t* |k\,,rwژ"[pevιxߏ6lcEL(ϻjZ[|;,xEhŷ07Ȃp7=tVV)Si2=0BqɢL:o`M&Qm\ ٳkW15914~ܰ~[R Nle ֔sޚ̢ڵjTRyV/u9gcoSժUT@JAcw} tral?/KdRA   mAS}(dtp Ji ~?L=iS1_UDT/ǽa[꺶)ڙ. Tj1sf8cX/a}ql&خLչɧErdŧƥ@ B~ߚ|F\K2Z3Ec!KFJ.>bYܸf}OիWGv w%)Š[ ?M׊omV.WpTƺoX6=pyh=uAFɰ%(e۪3*jPnk+2{pK .'B 넯@̞VdN̸bYIΙׄn `isķJN=Aɰ_DmVmz 4&+-l̰&;+,7^ޚρ8oDMDZ~]QM1;nբ}ЫlL O   "h4 c c9p J=NDBd%V7JwG~\F\h zNB:0ݽ\ȏM 4,G 7'"~f+H%ג hS 1ȌyAJrjaAӵSbi"&Cl)5&׬ ?pIeד/ ~5dhmsvoJ6^8c&yobk2 t\ kS.fzp c_BNJN;Tq*L=sU!hsƤ  D   A[#(+||:F 9ƿ|MIc3׏ ONS#h\M`|l^g1cEYRИ(aw# 4rI4ݷELEƍ4~% QhxO$h"%:2q(Q^98T@ ̘%FMj4{.ׅMBYƍQ㏽U %!cXx3gY:fJFzk" I's* (4@0{ GzoVxDDB<s1ÚG}W,lo`3 ch :}zu26m1e2FJw1c>^xyфg9dl^ AAAAH䥤qF 7d9EΧ3^ЀԨӐ=G)?n+=iT p&5 :>3-Oq*}+6μg zմ^٭Y0?ڂ iqt_lMHMQQR[7RK)sY1~V\: e N[r.,* 1v>=K1G3hVN= 3šE܏7:>}̉م}BIo"+Ș;Y ?OMimM!^>7Ka'N.v֖mq6?l@Y.|?O, =Tܔ>*_5m,}zĜnzLqp`yq{#g?d߇i޴)JߣicI;(s9{[4gY(Y6=G6/AD   y$hzglhrΆ!-HΆAVPDAA#  49ܙ)/pI#!" 1غv/9U~(eZ  FA{tp|_D$)!ҿ)dt "v R ZbMtQ2&'%Q,qܞ}|Zj䊩cZRzϙW  4Llj=wFFM ;7nZ@hhF@hhhFh@F7^:/RHD0rHR&ƭ M7ץj_,-vDvy)?ދw=h7qjdHj҃ph6ftz#>VQ|,Fώ4@4#|Ԣ2;ӷc:vy#-@2bנ^Bd{>\ȝOCqh3Q(3 Jx)(8qqרh-.NI4111.&]Zj.Ⱦ/DAElSeݪ;xT+~pKBX)T 䂻' h4gUi~ ɚ41 <WUxq ?@@QyIip9;;snm*hZK#‰&;Nqps׬9y&O[rG7ن r3U9fTMz/ f>>_HcGmQ^rj(3:u@Ç ;ju)4Pq[,Rr[EI;Sޙ ~U3 ez9z1f_W7Χ93gҰA5k I]jd=*EZ=cь%Ooؿ?hޜmۮI?mj٢-]/z{G//ϛ6y 5x5kڔ~-3kՊ,uM%c; "hB/ТGԪeKK&4fHDASWAS.,UtJ^,pT+\Y;ha"(`Ѣ_\Fk`Mwn*y"CR=e,fT{M&ƎC%beCft>Ѕ<._YXV&sCȟnoTiKɗ? yy s՞ܿ2r0,rc|Ǩ)}f)k7K"")J*y&&&’/>xmGjcarG'cccr|u} 5hЀJ~151fֳi+, iղ4ol԰aChݚB|$h%cf7ѵA2` %hJExSaer_ Wp 4UۛC<"N|?w8Ŧ]kYW5NxIeހmt-џog|·|cĐ/x\gBk47-Ѓb<$>UO\LJ*ؾt'%Us7% KUigY{A%Bk[|+!,wxFڽG/?ة.zxU'O\٥S' U4bPV4AKД$`bXHELETO yf‘q2*wǹZF2]WHdcez4߉jUm#)"~޸IgR-M8]@qU[*A|ї*A2,~Vʨ*`7kTr;*N5ǟ,4Fƍx{vu'd r|eכYS*sA'hFd]9,`ޕ`:9\ yҒ[3 wTێm;޳|W*;zpY}$e{&^N'4 7.ҔQӧwo珃(@ h -hb5VDja|24=wyni .yGeuPA;DZlpWԛ]_*s3^MJ)aj96o.ڏ/ k%h&;[qK3M4g9Y 8?Ku̠oirڋ8)Ȭ4ZbCk.RQTsQ@@HuLh:EnṮ|Iv]Y}?z=bUZۙ=02zbS/&~SXAs/5]Ϛ+udFr+1'kצ-͚>6Fhٶ~jnM",2 H?ߕkB|$i5 ۙ|gW܎:?` AT:,n^k<;]48 h:A8)(..\ #2$!ErtYYr:e^P8I>ʘq^A~>m(2eQϜ{g63?13oj6\WhkmVwgޟvq31/7\:\ĢQk_:5[QMbkgqc?R ۴םGT*4t( S0]{j]W2 ՜s+4}*<3{kIֹ'l;Ł(9Xx,6m ct 鳌k Nh lJ#y  h~4hhh !@@ vٹ8Á$Z}۴lS]`;EV&gƷєF|0 5 ,Ĺd("9s/3wc 4 4@4@#4 @# 4@4@s7Sͧ…Guv޾P/ 4'< }nZjj^ g>@S=k:=hfO= a> 4ʼn]cRx[cuOox֖e3hF{¹g[kkqJ?tZJ W]q}}=h`8ۮCU!t|vMZ'gWbX*'rI 4iG,Фe'' x,ho_r$ϹjjB&kY&GbG9b7MI/Om|^]аZ7.3%/^UF`^'wg1f_A ֝ByIPP\t i@ hY hldN]ziHg%'zbj'A ,^[[$g\aˢX[@Cm= ELsji滃Fbo;X}Tu8hD]*=z6ݗ싷k1@]&ɬ:ܸ2WO)b-ja|bi@@qp 0=&?/TzAw\"S6 4S}]XdU>@P/zVBNiU|\Zyǥb2B)#&d~1B9lf@l2}Q`Cz1bkd]&ShdD"M?CBh&. OJ*=ZhZeƮ]һfn?u4UT,whhh2:TYe|S/0zT /gW]Jn3bqiO5ޮɑa 4: "רLxr$=^{̮ݟYqkv-81s/%GIHrM[&}{kQ16=6C{;%sgҦnM d|_#z0; 44 4zTx<Yn0׻Xl#'Ԇƀ5tؿ@jnYL^ sۓs;c3ԇͽȅ2ԁFQɓ<9ny4u&!Gh!'/h%LS3o1=C~y"*2Rbxơf9q@/{G`6&"vҤND,쬴P+rgmEQAu]D<( 'N!,:hS:Wnh<@\r~h䆇㦸WIN6_M )+ 48{XFaa1&Frq]zu\[!4ͳX)Isf)!役ACݴ5'a&ږsM0:w-YWgߏGݞEcJu;xbB۪Q)DYz4JEA!8AUd( >f/:2yN7UMܚ,]Z6:kSvc{8SX2>| _ss44WhΧ3 do[?Fsh%kmhYcYKAtw.6hr;cӁ>svserO"okfͅ:[Z_ǛTǦ`?9ϚWpHكK>@@s.xd7`h%`.RoFc͜9:Owݦk#tۿu X fh475I8z䰆D>K.qhVs+v?soy^oz|ob9wƘqsCI#lLK;Xε4;7qeZ9fj͉r,>kVN %РkXMA͖s7i7ߔ>9F! s{e4?㑎րY?} 4(uw4̯'_.M}7ٲȌ-磝a64 h#2ؽO= Yp `bIKA g冏7bbYPwʜ W6l{HgfW6իr{Ϥf}ׇ޵O<8yq\@M@s:-F=r29bJ\*'o{_E[ʼڹiܱ ɥ$v.ՙU35XrSͪWrM4yF5h@@CʱFdw[),bp/@@Cʓ?lV,޳@@!Sius\fY g TЇKVAUK8)tPp 8#6F4q` ]z;mlo-pʃ\}& 4@4@H@hhF@hX;wZwO@hH>y-KǦPt+if+f2V4@3g|~Wߗ"f2$z SCXs =x6DGÖr'%&ymn-$ǫssd>ieuȺzMs?qn<;u6do2hϾԐitbϰo}=!u(/9CӬ p\N%j^,B !B!f AWU喹4$)'^'2!{@ښVRD 4#f\N(~c_d厈8F!v6._LWsKwūOc6Pw {9x#{l2ʅoުK>Kr2""V;ۘ*0 r3L0Q9dYfkK?[ eoak /(RJf@.@D^8(*H4ikkgsA·v\s9{<]xlx&8ۮO7lYĻo/AAA::31k9 ~/]1ߛԸ&~|on^\s"! B!BM{'dJQavrdW#3bPM?^^\,**!DCQ3IɆ0Ԙ|ɿfj|7W2HwdN>弰Dnbogk!!!"'3K_⎦Tcg,GƑ~o*.Տ˪ _9&sd=B!4B! @4C]& zhY.t3iw{ &KAC( 4#tQA5&39֮~ c\9sDXX*y@t]lzwlȖD˙dˣơ{B(h!B!4"h\J:'*KJ cn!]ec"]t4Av|l yi/*DeWT %oדQQ&j.7[^]~?왱d =;X},WCld J4Iޔ,lB!B!PQ^l`ШmRܠ|tR2FG/kM-GoU*2k#H/bɢEĈl=-fL6/FʱeI՗&Co2b1Sb)Où藣G!B!BAȂ"h[0;ob)hIݱS dA̙5GS{/V{^fb +#:%m6Jiru@0+N5ݏ1s$o@ Óx(ӄh:*c gS I޲! B!BM d7YEHX~(N*/ {֔ϘE, 14LˀXmzTb2q+6a=mn3}v$n݇}%qP6GsU u^9s1Ȧ}\'DYl\~ \oO܈SM~=dhG!B!BAn%RU-+gKDA~nA.Ę`V%e1tFBaܼ#'OFWGɑĕVC|a'Z& 'L1q"\y(h 2gdB\޺M,^een"'?%8$BBKIDO3F55 Pb54DRSm!E AOykx->w=Û|?RXXGN{:^k{'Yvjl,"`)L!hl|D^elE>jA[ov}4u_69~PIc2wkνdcfKTaGe&V] -+1B7D. ,JQ(1)Z]KHb4etf:@G<9Bx?89^3 ,-ι-y_i&<\'}.M.#+0] <ȗ羼\-vY2-WL56-5M!36ӂ&Pp_ <={\ 4gk0ޛYhJMV#d}B!B!PD(h QNdiw;j* 3F՗JhO-kVt틽54dCy] XD[xlq"0 5W[ h;%H@/.M_UT}ejْo/؆kH{Q*otm'#p<[G4:jD`8d-gS%p8IhJIZf:~ϟ_\*`s-}g!8,r;,305%hou' d^RRD{P]I1.%q~:1+X,8o?B(h!58㌗hKoqNuc) h[@ bkEmVk;P,טpJJҐHPs!~BHHBI6MC ?;8?9}d?s! kE\yCP7&k4~gC%-uuT~M^עZꥠY{Vk^Dc-ם-12]?o͒see }5kĞ9uDӯ~>ҹ&BAC!B!4A3eʔWTmMМAl$FqiY;h<Pirq``6ڦF_tn)ߥ/,=]>Z{ d 3)0|Vw?Cummƻy$h43VAcf\Ǝ=8b64)GwOj]c8ҹ&BAC!B!WQ\1o>"Yi, Κ ݞ^ T.,Rb!/왹R1FJw׋b[:;0nLswNɪ}':lP7%vK:bsM3+33#isq\,շYum 0 ǎDi>yѩ:q-^ \kwّs:= w)}Qluu32Nj;\I.~B!B!P|x*h76KMQI~f$&$@! 5u[MR/y3U4(Cr!1 7A&}YKۺb&o :i4V*1W!l6G@L (_4YBxt&e캱YLbkw /~<7nX斯P=;̸ҸM&8o.ƍa FiD:S%+"1!~JC;9?{OPۄ}9\fNG΀Qbǹ4U +!]vؠ盻v[j/vm~7|N2AkegȘ1cdѼ BAC!B.Nnc45$9*Yu u)ڻO 54紶ʆUu|ح/}KnIEjӉ!nNjAtpYrU"YgAnuS?Ď9cqrO]d~\~2NM3o=cKIT:6u}Ѝ_3o_KU8o33Á$$I$HS}tMmbn5Ys]ƙtk5$I2H$Iғ4㌁@矁F$@#I$@c 4$II$IF2H$$I$M@3 4@s@`0h@c 4 PF_8DD|-jg1wqY\cn`e3(8)7kwJM.%7;ɉǵCr9=}T|^}B ǘ+@C4Y_x})y5\I~~_[UL|9\Vr)jj8m!h(Jl?x-ܬsU˷=yqVUB*q?ݴ^O_qY=_Kk f>IMrMLLL@Įf6/\k_ QNN~$w=$Ό榞?AZښYXQdk_y/_ 4ts힚FYiol ;\74_,ɿ>n->Ζd(Dp'ŗEVvJ_TX; 4"icͦ+urjƥ߼p._*˭nښ ۳g16C0=c T*hn=~&6e2n^Xy)$̯YXöiR0Ic#t:m6s֖^gG}JR!@1u)UO}\XN/Z{aƂŠِ;аeY3rY4v٠MMM;hlfvv6L5@spg~ v@ͫvBW߱{QAn1+ǑmwwJRfiBA(cX6-Qh#&\\\T*"vwh:GbuKC4j43ު/W,+ [^q鷳c%&nKؘb~^8{Kv._ XJCd.eR8삳:>DO;~6ыxNNh+ϡ Z6T*vSF7 H?W!"wb:pqnϼ;,?g:I)l<:nW }Y+l?Ip˜?ľEj]t:E4Mcl6+Kh4&Yzˀƞ*F,zdrl7jxt~b/Y[ϻVC7~ϢB6}^O,pXo*ݴfmI|N%X,J.j*XVicS%f3)Je2W@Jց}Gھ.y) i^ө4vפU]62hA``0ale ;h;L&sw jch=<%?p޽8UaN8HB8Ѡxp5\_>L;M:0%O-YgV%I$i۷o2(HyM$H66Д#9r`My#$I$I$i}ӻw>}_ƒe)@f:9][PGy={纘zG$I$I$I֭l BdF$IwPh굫s<=}D$I$I$i-ldfmr4~"I$I$Ine鶐 FN3+44VgZi:qZ~wʃ[U5Ӻ^}߼3D>oCKd}"E=~U=϶Qud(womVd+Y3+4i}d2؍4z磼z['!z*I$I$IlοUwlm?g68l>6̹Yr&`4uW܋-c?,Lz^k˛OmhD+]dvn\)ogO̎4|wr._ͺ4MMt Xl?ov1&;F=|̊'gVoH_pq$skrɚy3Mf>]YdȖ5qt[l@prfOB;NsTwg"o$j:ys)ElM6l<3J 4_$͋/Fkj:ys)E0LdsNCmE677|{mȶцl89IzPY䍴`~lm1w9FF\l.5mlmiL6eO $MXg-2mY̠ND`0DFY|&څ!51a5E&o `:f "ySCbZ2f 4@ I$I$I$OG@$ "iIH'D?! 4O~B$ "iIH'D?! 4O~B$ "iIH?VoH@$ 0 4pH`"i4(D"iQ DIF!.IB$ \ H@$ 0 4pH`"i4(DқRZ/^H.\mf~Vވ?y](g7ĨN$ ]--rg|;`?"iYUuTY^}&xHzsIF4HIB$9xcbfHD$ F!DUmԝukaXN<s/&1姴dg}N*)Mܷ0ǎ?}\~H>B$ fu|׃esKڏ;PD[ZDT!I>Gf̩.h>Ri=g$}iHJAdF%GoDܴ(;65r^WރUsFV{M*˻s?vJdF^M}R%E[+*->6>5$ ;N$ !MgGܛ9urh܌ڊN]6+Q~KܢN$HZ^߼;L$ !ehaa=ٽ=r~pzA5:<;HI(2\]NdWu#"i0 FbIIlŕE"i"iZ[Ҩm󒨨^(*=v"iHw/;/'#TqedUՌOg7q;DA;>|ŽKmawGg뀞?Qk_Z8qYcy 际Yo8N(ҙ`v^ΑGcǏI~+Jkpdh|zbni^j}yTn~^u@TqĜljk{MvA Ptzp{W/J#{1ʚCV_3F!!!IjIꉤ"iÁҍ鎺˲۽mG4Bk Édtj lΌ*ADݧ!+aWb;=k\D>v֢1aipn3[;:#$yx˱,Dޔ [7k&0c$a?FN╮ڑt{\SkS$=&MOo߾3J}%Hmܶp=_\|@SqDp;WbUkLeoTЛHrK-PCrv?w=H(D`f[;?⼹yJKm^aIv${=P8}KwtyU:\ܸuӛIXn,jܨ\P/\OT޲(@QY m̽>azk͙sgǥO-(4}fanquF'l\rAn;6J?sWY'f&G4әAI;U_ڨcrOc'#C#j85 R.L5^uIG%Qƾ (o/77cy ܨT 1Í#iC0՚aO7]Q1749{IJG!o߽]PTP^UQX>824=?}>[?X,0Iu]g˕VOĹ<\xA\쇌-S㾮!%sHM[۰HebfRԖŅm-r}"H)xqk=aeǗ oIяaOo?EiQIwI.?h\aL"J5aj<@wBY}pO^UFcw`vn"?~Ujgjs3ϲNe'!?2** vp6JJ?Ii2}g:!!!yyrZz} eV%) r:\Pg<.^/7܎=2'֬|cB}(Z;>=o)}x-V P{k?FU1ؾIN^ǎ਽ehĪTKIj#j7ə)[!D"F7qcz{ku:#C,Vڔr&~hW_Zp6"D$ ~ ͫ?tlŧpy;MO]޽=.1RzFza;*t>tX)6:#6MVă{>qaawogPʑ Z֣#7 ,^!?RF7Je8W LV]~U=;E:Yw%po#?3II1X,\oCJ"iYjzkubfR|FGO; b'| 2ܒ^'m8 LI*$Ny_lv_Hj?!h$Ol0; sA-꼳ny_{/(8[-Yܹ;;/G?(I66}H?Do\WN)Pn6GD_vٚ D4?v8={!"*Pv< ĚO;zIt-˱fjî=]C6Fǂȓv{)aʩ\r~k)~lDո"i-߮.qE_7D`fm m..Y, []\=bY 06Fڣ*3t6#iʭNO>*gSa[A9\MHdg[3 3YbVN[s7|_¯H]TcFEK؈}Y$s^fNVWo҃^G٬r8VO4$޶8+OBtn'15\z=yߩY菤I_yg`w0P".}zSUeA5T c? 77{N]e#UGҙ.7-lqOأq$IwvHcʣZG=_88m:EҥR^XaI[naaaiÃHsJ'O։ʦÁ~t}Yؤ1Ϝ;3 ׉b[gae䩓F77?Oyytn~߼r#|GI*Xץo6l^Hz"bٽ#1m_ ۏĽݪqlt6R?VY:>{-Hu:;G:~=xVܟZu9.D<1c?ӝEFt)=fS5$urŧpghI\2pȔtdpni^:`7l+\~- Gz@{?#i9_kio =(7ykM ?kj7]q]y(16)id͙ ):_r~q(#x_Wwq=/P些zkޭHztrLcSHZ|*_Yv_Rn6E .W]m{ un?tJƽޫ Q@Py$#;}EIkģ0jGa5(ޘn?kX_5j;U^~nm虞[=EU%|rSdUGi T"*j&̫v'0c$3s@ʷF;Y٫қHZl3|_b'yFo߽5U$MXG탼MxQblZLD)>e[wn|S9:}gڲ2{MZ;.O>KW[̹[t`Ʀ%?BI KmdڏrW:#iq)_wPH:(0n#Dv x,*jA-NGfVFiˣPՖUat~wGO>yiG'Wv>QQJe][PA#%GcAlj-wrƽr@ŗȈاx1Ukؐ_*sw"i0 3Fҽ}?oxٙ$$5VokG5nDҁqO Kz{ǧ'y-7kk+ W^sgρzegJT5†8v> oo{WGgO(~ШU|֢fffWU܋Qm-**Jw&ČϟC.׷kj[.\9w㧏:E&9fA$-9r}H7/I?"i0};dE8 O>ϽwgS%#i_*\݉ H] }vkIاڠ7q鋍j(\T_{Y+YG-'0c$[t2`l#gd~VN4"鰰<'nHf#H H,%F)G'U-wÉojo4ycK܋Q^?4r}y+WlQg$0S~.7"OԻ?gfa=3(6~D`*W7b_ǕV U[qvY<%&3­_nDҪY՞GWbV2VMHX~DUr".?(.w&(X=Jݧ!o36a`Iǽ%ό~h~.Yqq<#{1ӓ=-m-Mm͝=]ÃKvN~rWHGp(zfaVl˿ĕW原_246"g}x1Ȑɢػzۻ;Z;D5476VVWU}-ߧnx]yCЈf;ģGK]h0>?1xKr~QƃH:}4hu7***4M]y T΍]|̏WȮu97Gү7FҙU[GO#ie%-箫{ɌHCؓ佱>L |ϝ'!7y~YK]!{C`bS重[#Ƙξ(7Ez醷~kfEѫ HLŒS6F?bWu5ڑt̽elgSɛz]twZM氧˼ϧys?N:Tǎ*[3Xr~I/5ֹ\íQ}ʭHSZM~.7"iyP6+C "i0C*jU{چ]ܾUSŝHZc_"Iu$=9Ηvӳ=rJ'{a%SN lS~^*5x>auR-.$0e$- *?6uZS_I+9u[WI'խIX$$&8[ͧ15EY],~CŲdnq ajl%sa֛+wݝ_Z%=mZq7^݊%o>Ү1 c?Ś%G-UZQ> /7jj O7#ݭcD)I )?[+ݍPn6DҧNZۊ!t&fև9<7H)]*_;w}CuF+ˮW$D*tp U֙[:>pDF$7b*fb̪ S xk!{wVI\kg()Tݯ?hC$Mcg@auJG$HLŒsǧvʫk׈ۻֿ ?LyUr6`c(^?^Z[Vzj$+~e7?GHz$GmUWʧT1=>TV^{]oGcq X,k]L{_׳}yDG6V$-k/X_t]~A6}矄?I9-87bPawmLikF'/V :l?(901푝GjCRVuj .3JgTo1ر ڍYjgzn.ipSA_u|D`fϕխojЈco]rHEPYϑwxLk}Tny룯5;)-uS<.-sqeQO"<6əgx?>X/A}HKfNl_T%qO6V$Tõl<[;u=:9|Mn4MPՀ  W#M-6S:Sԁcb㮾orIG#7pXT-<'wk8tD,8bJ*<*Hg˲CX,:Z5 ?7jq==$I3}*i|zB, Ey9{>eG]aҤlqͽ>ꏤSq'g\n6ErZy?II;xI b#G^硤 @υ-{ovnUC'7%tNի.r/eّp>ͩ~bhFWGW7X#ލ}z:|/l=Jw1ćjg^n6ڥ7k<I92ѳ]GKop|-W-e%z}z~FڭaUI~Z~ٰ0^n$&%گ8#xpd>5qP@o׋=#΍Ue"ŹʚĤ+䭉/+7#^W/q& Ws/F9W?5=}tsz"iQW䡢 r,V]:->y#r>N^]rk㡜ꨬ輆CG&};cg(xf{}˩UwO;ʨ9â6ؠ;h7qmeњ *ЧbW=S}ɎlHD>xG.+B{D8.:.Z7>};cq}߅Žޛ_eVFlǧMU(4#鑉Q=7jEc~_ILJ|^VT)(ZU[}x 6>=ZVY)-5}y̌WVWUVUTW66 )}-BHH|}\ezg+#.- Rv`tbLXO>#Er.W٩fIղrR3N&czY{-ː42&/IHLHHklko(D%Q^,^Hz:'O?e%^> RR^lk>-7#om?7?嫤I9yW"N]2T~)Kq|^KZ=QG70:[߅i'!nl6ؑRxK-:o;TSD\"3MH rt<,$us"i0 3Fңcz֗ǜB ~ihn/s K5k9[?u\ƕ[x屘W>8_ڨJv![sVߌ¡Çaǝ--Y{U^@[r-T 9v,[/ޠQ:aɸ,ϖ]V QP޼&}I?鏤K:둈 v}2itCV랽ewgݧz/XƓskoh[3|ĭ!#8;Dv3e١~o: D~eR"0c$=69gH:߾i?z=vTa[g}NHLh$88|?#Ź!4Rng70܋T;I)HNg[{fbfR;ӞN2<>21tVߌ6Z+/ֱGA;ŃQP<3^"3٦!+~X勥2y=7H.IF'F+hwp(;xn=gQDێ}Sr |;^}wǂs7AMT!g{C 0c$=>+niouI gϟmY[687<>"~2,]lSZ^vKv揱2fWW^7g9 {wڊ8'm_ =rkjmŅ3 ]y3xg|)JmLLO//OjqeѭM>{&+7[utX|" ݇=+.+34/^8>=ܘ=WV4"iQP_|r8rTTT^WQQĎݳZ K32?KAEu峄nMHg$돥G̪Ib+)}_zA#y lEgry=֮)\q?H տ;tO~241mH|m]~NuulxjQ7tFxZq:-4"in :~CGFFZ,Xx\у/?y/#%"io#<2Y_w;=;M{%t?Cwcǎ|b4 J# peVCMNW6ޢ<I E\o_yY_8.IbHsK?_ye[kpIgd)M1Ȑ+W[SԻHU xISz^".oD$ 0#鞁^Oڻ~D+VBBB?jXcۊJHU;]>_afaVwoI@ymާ|P^9pH`3Fҽ}|δ$!E[;lъ)ڻ::{zzC֑鉩٩啿W{17-!ϸhϟtү3<}Ty(%KD1^|I 32?~[U[INn-PvO,H:MIKb(/D$ 0#i9'::].^quƚ~ X^>rfN9zIbHzxlDWIbQuMxI?Kx.?/,fY,vOm?GчVRrE( ND2ѳ~cKv$-UOb]LLzN)9sƚ~6-Q2U܋QJx'~ND1ճ~SHZ((*X*T@S[syUX-+'+5##Gڳ?X,C҈bY>9!1!-#Mlyۼ<>@?"iQINYYO$-o>vܿp#bu_8v.#I+/rt}~gozaذˣo%;zp 4(f&֢3yfbfR;u6=I_xA\Fү޼ҙ*#ʽUulQ#*=]D1II+\!cQiqs[ʢWL*owE.ste%ṥk + ͍ TwgF1Q$ ̉H`"i4(D"iQ DIF!.IB$ \ H@$ 0 4pH`"i4(D"iQ DIF!.IB$ \ H@$ 0 4pH`"i4(D"iQ DIF!.I#19%rh#6J٧o$J^ܴy,_ޕ*%oD]?}'ߊ/0V.\mf~ON<)-ijn⥋|(j~o1R.%I@QjQE7>|~"M"5O#sG9!>mUl_x621#5vgQ\,rh#6JK^U|N?I I㷯oDiW?Gj5vW굫T H(]arQNbbfH}}#O8RC!Vc$}M9>L%LJ)L`XN<s/&1姴dg}N*)("iY>.޷C~ZXXxɱO=#-]C$M ѾmJ}#* 0ӮGj52Q$=57cS+|}=]WQ``A64)UTZl=+5$"{f TENU7Pn߻ }}@$1?OE{H7~oA$Mx5CJ@H `[1i$-NcǏk=Uu{IVؖJ.>-7"i\> ɋxV~G2ETEotD Y/ɖS [} D"y]SjFbIIlŕE"ߕ/!6A~@)-݇9Y:>5.bxf-T?\ZreڞiI{S"ij?IӮ8RC!VI769[aC-HH&fAQ|=KĔ*)e.lL8ω._2V!.zMrDHOD"-q &6ARƉC[[D1a$)G\w3iI%N5Uc߫]q=z_˗rvLNwY60>=1`I~|?\-mIWULLO: ju@㟈v}l.b#i4? ܋LI/헣 C/nxI}w/Rj3h|_=8[}zY`Dë&;X'6Ó?܉hZ}>O:d鏤kgW.SCΜ;;>5mZK{I߾{[+}^3 s⤳?G'lQ<;؈H|5^˫* &45H=nj/՞^πu֛@;rы.I:Z$TZ~}e%sDAuElmCYqh?%C\JKmn09on$om[%ݱ>9TzJYMihn}%LR]W#OHȆ_< `E*nU& =fD<~P%; Bm[P{炃ZDҁ;lSzk+m!5\O$_m~0H:1eK{x!ԟS#woԺ:RnYJK=u᱑{I?y/a)iܥ"T|ʚ#yy />&@zzUle$&m@E213)ZY Z~D&^P8x Jś?i?mɜO?ˁ9y9ޑ45w! Iww>h.6hm>7f.%rYxov_rytbL^36_T /<<ߐ0嶭"#GIXljk^GΑ6Ç{agOGfU#c7n7WzYJ[Xg׷+CEܲ+聝WN>biY$]ap,OU(H5ƴHz+2 $=ۼAdv,˝ T/xVyrxg[?3sUiEjkIv V+VOrr8\mayHzK Ei<"J,}>5#Mc嬜,9#>{2N~,[Ri` ĨvZWS_>[կQ<&Vy^x;=H }WM^eb6j"3FҶY/i Zm#{@HZNڊRV^Xo³ e%[#[3,,,xw #x[\1}72 ? C{": ELI}0 t>.o;b=?v?_sEy |-T#DSn{IvFLUxWtGLV(.,.Tݰ^|遣>6bkQ;)+?WO:͛jGҗ._RHbu/9]{e߼veX핕H?G FQ"izaj"3F}r%b-YVYHZiNFըla+a#a}6^lK/"i7+ÜobY(T`81zIcϜMmtV҆\u0DžTgHQEta+k7œ5JGZŃHƐ&vDGҢTk>4|mx8~HJgf~jy9n8-tppz ~K_* !C0z1Zoj~&0g#T.GQ"izaj"SFҿ[L=|_x-VKs393mioup&owb&т.cob @碹YԠSܖ-y#iQ^3v'@aH 9\YɊ% ԘFK̥]>1(ثs=s^P+x^EI6pבZgGָrާQ<Ј588`W7q=o-TY}Og?{./~ζh?bZyAE#[}qE2ӮGv !lEfEvޢYk̲^++I+b|~/Em`!"i'QbXG_>65& #G;Y’"x~fHFOoOel赮cAۯ~ '}솾ƽ%a.Te5kM7D5_lۿ?}sj?tm"ʍ/i/il$K$"i .+(rKkʜc޿H:88}m̙{Ԑ(+؆4tI.7єUS96d f& *U6xYc+9v}\?paH `+2c$;gɗLOhZ_kG'F3ʫ=ŅCj/ra NhE{LWv2%2= /_%6[3~{&U^g<aήtVE^5oC7,,խE5 DbKV;%=@$!t```ܓ’if8QQQ(ޗIMTOc!I*=ocw+Kښ]/Ile.^fS^^LzGQC!؊I+At'Uy"3~sٜ>ōVmgO!74_3`7(mcƜSsފ4ƌFҪ#|3lm p7۹~<%84/Ir&sxmRCR3e([W=豣ޜ}gijߺ\sK+I|{rM3.CGijp뵆_LksGCjPC!؊IwzFcnh]\gKqEb5H?<8.hb +|gIF4oDyayU^6ߏ9#ik2己?'PChi,bkޯhr뵆_LksGkPC!؊I[G+'7 fW԰Y\!!!sK:K_=9*VS5׺"S0p=\2^/#c81gulXy?v~qgoFiG‡eў>}ߠݱ KcS9Yv|N׿Þշ6C]N)+߸us}yAE옲'qn+iv ]hx 1yJ `+2c$嫤 IOGҧϞ.Y5EgOԔ{1=M[qu VڊK&wsV掟YU}.|_<(o8>syPE|IE`٧ўxNoj/;!p~YD8`T^%Kc'O8/?ٚfbMSYUI?D_Ůg~e.涖ͺTXCӻrD?): =tMMշrZ<!n8P]LksGkSj5[)#w/:~9w_"Qcc^Cfnq.!1AleimY71tHLJ_GZ*OV.P][`|zĞV\ ~ 9#!!lCV!/[;`D莉}!?Z- k۔#|z5xjb `ě:#W8tOq4N{9R(1;TCKL>hg<7_ oQgۼ|cef9zrI?[Y5D ZU܋QVzVTx_]>*c-n(z/H}5w5l)5Ȍ8ԺivYɟ%C` XuuKpw_On~%C|\jTO#nioljf51 ,7/GT}SwWw|ὪzbE*xĵSZjdgHցo_?y8qB Q DQP,%\z";Lclێs7,{#sZD{&BJcFxާ󃖿.o+f}t?,"|p$zp8C38't:[;413)MB4g>/j-=zGe=zcTS3v+DMEiȝDI>5:x{L8\M )? \D10#G$:OS tQaUh3;(E$}'#]v;9.;Uɸå-_̇r:3D^O^'9lJzͭB={(?֝ۢ󐝵CtFBjzjx_=ޖ!ʓO2Qm4E}>l 3%y0MnwXn޾tO"iz^>Q&qwն{1j7Ie ʍ:7H+VDy"U)Ti=꾄2_GhQOd/#FyP "vzw8#i[}юKS3\4L8깟zʳjoYEuFftr6cGu9Ԕ!#逍߃˻;y'N5D!ZT]:6(F|Q~\">gRA2i3& r'^LksGo(M~RC! 6&{78|IO퇓J_g`m^n J~apqN+y9J?Qͪnw™ 4EuE}js;)aPfϞ?WP;0<80'5<>"~8-vSZ[N;}LVnhXGŖjg"鰰щ1ۢM]h ٷ,V!*W^hQu +%o?G?Wupdh~iA=Ungd~fEu峄n}e$6y^ ^\]r^Ӯ~SzSj5&G$m}>6 ?اɠ|H CCیh?oP3s=)5oIo{1-M}c#3 '1_\]BY6gMt1g \yJ `K 4 4w\zl:"i3(zFпvRPn?ۮ<})s=)5-Hz\v%iz~$ IX4FT9A(,,ljnZ\v&g>L{ SzSj5[4pH9ݸu(Y5`+qw痢E>/?}`g^Usfyy@#9>(.J@I7ܝa~CDآ2f7"b;FҋK}굫Tm X,7>E$S=wg xⓧ?}+ZZQVR^Z\VRTZ,.=B)9gӱO=#k=Dҿqy1s$Yg4&榕Ը1@n0{p_*qm44)%| A)jl^9!1AYyh{vӮ(,WVniouN{wNtxrv*44Tc'OWU(?gK̽w A4^KKGV^`]c-n5T<09#iߵUcZMm.o(3Sť/!f1aN'z\>pT_̴`+N&?!a$-<;/'#wgdǭqVsσIo[I[GŶka~IyiSk"3DgiIsy#i8aCU[HH]I*Dp,wg(`o#Cufegox~K}oF#邢z}Ic#iQ?)gQn{̖nˁBrW}74bkPnIoB6qʹg|ļ5 Uݵ)cGa^:{grxч'goCǠ }3I{h~H:g@-Ioݖ[H:!kzGYZ[ IoE$= #捤r%kw$&vצD--RofɼRt "i? oE$= #fWlQRdNYeJjd^gN>521:=?#޴Jy{Օc#s+C'rSt).Ƨ'u5=r"icˍHZRYanq.P;Q=x KpX=sǛ2|ߌeH$]^U11=)kq虖q.9҂jppI܋癅ߕ#iCZ>k_X^q_Ѿ);:IևGĥxۊγػpm^S\5 ka)-+]_'%|pdgGFF (o+͝h茤U Z7yӶ4"8TQ8 +&jpxH͊Jir'[ETTl__4?5[džۻڷVuͳk5}$mXDm,vfimY4i~7殻&1Ru@TfQh<י7o= =g#iq6/֧3O:;mV'4dP~FkGfiS "icˍHZ/\ʢi>aWj+DsYQ#ofΐH rtUXRX|_yZA΅I8yB䑴!-kׯ 8ѯ,~t5WHǭػ|Y^_ ?TӿۊbQ)Ϝ8֝3 sewlkWˀX,f78NNEp;$44~p&C0gcI?(eqMMOMLJ?7eޝd7o0E<+@Vnv;ケ;{8χVZQ P:;eR ˝su"菤kcҶ4"8}vAQAyUE]cȐh9L?.\ 6e{L ~֪hy_{p j1VҭǘR\E%T<RlgRvbe${LIww>9FWU–7;ȸ5t؈O[[E{B+G'N=]XT E<|_¯~dbT/%L.OCC,ܳP1$U(r [I+^^[^Tzk_YseII,9zDc&'I;\2dxч'YV][lhcށ>/w r$-\qAyo{8{g~iA߮od$-YXo{&j9(jt1Jsg ϞQޢ5ރu$-D㹜\巢y#⥋$9Z'HZYyD+N?;꛱mKk/3vcpd=znHͨk#ibGdn7S'o{ 3F}?,ٰFƜ"iֻ!ŗ vmvcӿWIKZ<i^ZF҂3߾v?ɏ݇vqN{kz}=??}$?N1q;TIk, +Mm`䑴!-+W蜜fF9Ծ7<ˮf8~ᬜ,巉I>@O$]XԸ[ߌm[XC ;t$q}8eǣǎWuoo^^C|Ijuaq\Ƥ'jŧgY(LIrni?E޶µ~}o2/(rjvJʏ ɈH~-wEojT x}_fʸ\F'ǴHZx6Ժ[bGҢ^ ?k40>:mE*/_%[3#iCZrHqtt'~뀸%LOOLN _1bMjojkvy y_|Iwv)+?}L۴4}'Ik,V\a`}3mi` 168$W2wsPEޭן?DҢ9ZN5f=u"wg?Y(I+;.] Rvp3?Dڷ^HZn 6DO$-OM?eCեYKu5~8/. JdHZO)!G҆8zc,?޻;=#iz>idc^88L-0ޔ𶥁5L&ڙr\{ILzgro)Ko^^CIߍlJ4i!ׇM{1K|wfŒ|ǵ::]B[$=k>DFFjIܿgvuY9$6"iէ|yC!44TG,_ unU9/.>-c#?#_mQV/*<60>=>lĘulxpdHlg[\[;ښښkjj˫*JK K 7>YA%**wgۋ/TMI}lR 6'Gën޾)mKkMLOʥ-W4ҚZDutS Lgc}Nt !\YS%*U[kb,޳P1?X66McΖiW(~9oڑsg.5[.mE9Z_7FGg6pdd40Q)-UR{7_]$-qs$-ׁ֦}& i9 X^tћr}yp0yswv+p>$o?8l[Mo9\𶥁5&^2|VnF>E\朞Q?zo^^CI.BUWĤK|wf”ԿC2׿aV5~XS; \\]4&1g$.}Cmx|DNg$zWܻdqu~ /ogbt/(D[_nͭ I?Kx?#i#Z0G:;T:#wO"N,3+IVz[R}'H:I\4:X o[XC ΄`|SOimSgݞ|7k?!~˫*Uc](/I=Eg?Y(I=d6Xs+y8?D7Դjӎ6q9D{W֍墘[^9OCN|ϑ raeѨ܍[7]kQ|I9wv׌I&9z. E9.Ir `䑴!-ⲒK}G7;v)y;"Xe$=0&xԌ4If23𶥁5,`UUΞ<ηuyw~3HZ{jvJg2Ξ?3˟,fUwcrWXHzLڍ'Xe$9ܿYs-I#=vtn$-rKӐFg]ѿdgM^Wzp' X$TC rU[ƳLIrxPܾw6]vϔpjܝ#GG},HٽoFҏ炳Oflblp2L3r4W<£Gf`5ב75KKWxRZQ CP_Y~g0c$mi͊$2 [prxڑ r O,SYSlə $c?VmG,R>zyEu*'V߾Ku 7I˳tW!/j͖Fi`܊­.l䑴Q-{+>{}u9l䘲[7pw^J5֔s|8I+c}IBl։'V̤Y o-!gֱaU.P:Y?'qVMƶߌ:V; X^Ack9Yv3ggY(Ix*i|zB,?&˝2IK /VHuU3ut~LM쨮 zMMI;ҤIMCCCv䉞VF݇k|Sƺ>*^XFҷ^|yY5:Gp]\]9 ,H:E{rvJ<sLIr^ i Bޔ0J%Wϐ1IɣNeܞ]$sKʌvm-u"茤)Z=GҧNTAڋh,F<o"|oGϟ٧ S:i r"j<8;b}R<8|gS}ʑE<soIrP=ˑsہ\$G|qwGu4CP)ZN~>V/㤡 !7.'<Uߌm[RC~j)gqjhnO{%oUmLR{}~~ 1j)}A*lUQ{t,YlHzlRP¢`)?3xOOhD|I/.`E=.7RYS_׆mb$-媾ZAzٷn[:Z]>:}(:7?od|T- }۬HZzi1bՎe J4pq؉^$mEX 6`q꺚IM{%AVlftF^; 5,.S%Θ2`m6'FҪo\i6I$-4RPf`Ґ"zʳzTVQ]qxQ(_S$ק7kïrcgulᄢ'(.Ud>ވ/,YlHz\v-'G8``$}a/;wxIorpG9'€uȭQtH:00ph**U]c}J'`Tߺm֙Y<8d[knk;3` oYA<3y߂BQ]y,W]agx&G! V옳Xxc~H,A$mHڐ+WJw?ӑ!RQ],Nv;𾭳}tbDZ.Cױ+$~㺸.7Ջ߾v6tVNEۜ;72>ZP+ fTҐMp7ԯc$ v`xpfaNVI)N"t*!NOiA$- 27IOk5Wkׯ󽣻S1԰ʍn=ēutOOR,>: g"iؓqs[Q``h=tG o'X)Oa_<4^7~{挤}(krfmImKgރg}e04(捤r%kI+**豣 @gJU:s6IbHzqu?_3W Z^[WI:}*7?:0=?xdS[z^;21*^(^UYS<᱑9Mq=eObEͱᡖⲒԔ;1w/ΧNohnY%'k|xmw_8o}LMZ<1=YS_gW}}cǏOkpdh|zbni^|48N`v^ΑG6,UN.\ ~"OQ.PTo?m_?Ps츓DU^V^}(ZYKvGmƭ6{#1}#G46٬X4z*\Eu5MwuX,"^L`h^~^B⋧ϟed~U{8 {*X`qYTn~^Cs*qv.(*(kkl0?eY,ምo/-/+j[VV76kD#}MUmԔĤ_2ڻ:TZZQ# ,._\]@cS⸔,-74&&%6ԉM}қW~>/rx•,x|zB|Ęg@ĄҊ2\&9ysK)i082$X!~S=G\y?G֪rۊ('O?On޾ÃN_AI_C(G* Y: E=8'>Tz{k;S%@NMH4Y/-w4(f;;--Ё1!{D;Q8{grg)Rh(.V~&xT̤J({1|ryeg]q]t׊?ƒRIWwLo'%%1pVsڬږ٦֦)Cy{qOWԖ}lPY-}4(f;{?m#..WU/-TY$/|W821r׺ } ElKYerei8Ԍ4gk)so_ySޣ_ h844G*ddɽeϞ?~LMY%?iRYS7?$U޷P_̭RX1>/,/8yBcMۯ6A$ 0#nϼ+V#<ӵ䩓rLcP(,hoT)v(88X޼iO/]X3|_zϟH###~+HQ&ǵ =8g۶;.6Dد{v@$ 0)#G-BsWo^VvZA<F?fA5Ըw\79298LwKQW22>l#V{"}!v cgO[wnk/#uC9@wTLiC =56+RƗ ef[]7SqI?yt="iQI ~b끻m%$$Da~vtIچq}ʇ# Wu[/.q-9@zdyX[Gz7s 0HqB&f&oџl8gLewKU~(v)k>\{SSsH;XC#VekeF+X8ڊ3*/\XYtF]ZS "iQLIK/SSGܦfW5"i+҂wQ^%~Ç562 Vvz~S[:'Ol?-+Y[қWVnX[snH!h .uu{oV%hbpdH9]mwc+,'HھǽS2"iQI )?A#/*X`y!P[gd9:rԥ=˨:62 ;~W([sV&r^1Eppm%Gzჵ)U{ScgLrZ5_PkW]nYY?I["#>zղrĩI< #Ō؈󗯒Ƨ'ĢDiY$}(ZUO{d.*]/>9\M/KQ׀uPL'N\'!/?ϛwlfaj&TX,EWőS󉥺&$$ijy\ޔi62?UV[䎨bvr$7ذؽ켜Ay}#O0+直9M %Ei_xBݕW4VPT ٩fQmjY9Yi'N鉐%CهچZ=/x} OOAYFqO]Ħ=OȈF1c$=:1g}y<\pay8cg?\,JYkꉺ t2mhn&n߽gk#mlir$69?GGut9un-=m{O%ʋ~_Sֶܼ}+@|sKb;K3|s_5 bH`3Fcz֗Sg?FeZcÏb|;9pƚ:J9FT-cY;ź󖿭8XO$=`?G2<>ⰏhL(#ִ̤< |<8 WC-ӞlF1c$=>+ӈm\R\Vbu\XYhhnLHLй{#]tQcMQjjg^. <*OiΆK~*[_^-,.9**J:VgĮbQGgrۻ:+lP-R>) aqeѧg;ǧ奣cH?;t+"%EYn@48&፠ǎIw#~+B X,TM!$4"iQ#iM~84j@H`"iI!!!9y 蕿W 䈤F!4/[;X*KF!4\=<620k?s$.LM4(DP#K 󭻯+ HjDp굫꺚W.SDoH`"i5 XJ?X,/[wtצ>{~WDIF!.IB$ \ H@$ 0 4pH`"i4(D"iQ DIF!.IB$ \ H@$ 0 4pH`"i4(D"S7%/OnZ(rHIDlD"C;\Nͻ~٣(E>헏>z.S^'QBxH@$\!nݫ;)h✲Gq;P^".Io?Iw[@jbf꒲׮RBxH頻JyΤ@olH:zz6q>!/ eX¢?~왋.^~PoCS TE?y/J;kiEYIyiqYIQi$=B)mV^2Q$=574ƦƵW2ރ|ww(;sarv*44  eH䣮~Y޲"i!G"w=w=j)O 16'o[jL6^:[j~{V[g^ppRTښ]֮əS\{&E+wuI7?xڊr<}BVA$鑴TI*D쏠=UII#,,L48O`~{ɑtv^ Fka>F`K0o$-lB"iDG_^PVKLzIm{`0g$}L`?Tj@_獫|HML*5N̝Eg.#]pim9,!wl捤r%kI+**豣 eIH*hж=U3v;UElĐH:uXnko o[ϷA;<-I[v ҐR):j/gHzqu?_ȃl)錤O>oY{U\Awx'H3693F]+VgDk@g$,,LȣGqrs['rЦZD;L{4ڔ:Ƀb3+X!!!ζ ڑuF'{ljޕ5UAAAr.^Û^׮_p,~LMI dM_T-QM^碽n;lhoW*__i}Hڄ϶C$=Pnl$g('NGEa>g%gg,?< !65h6#/nMrhlm.> wcڿ:/E>u52Motkg14oZIyTXR'>s츓K^V^}(ZYKvGmƭ6{=q: 0bDSkG~ J 88WQ]}M{S]şG*?lp*vR#mT|?IWV+ VMjfanquپF'l\rYTlI؈yOو`p)m}Bַ|Ivg#X>wSi#}> {B~óH{O[\ބzy9^/a/nGFMH$+e͎N{33lͶ8{#i,KY~:hrH4#yy />gW3DHH0$P}e%bSy ͍޾{NG~:l~HqlX,;7_T_Z^WնnjmֈGT >ꏩ)I/ӿdwuvoGj$ShMNz@cS⸔,-74&&%6ԉM}қW94"(;7ʰ[VybyI~QːPxы.ߋ 65hN#鴌4eMy|0_{KcڏV͗̒ a/?Wl,mHRmF hrfY[_k8dߴ|Ęg@ĄҊ2շEe0'/w}dOih--{7%rS|(oϑ;ְ* 7QOƻ޼}{5wq"9(*EQX EzxwaNMIf^>|`2;;={̏+\v 'Ru, W4%UGD(eC7fiQS_"kna^׎E718tŀ1߮7~%9[Ϳz"#) 'D{e!r"ω'ʊ-I:!6o,Jx1g~'^Y{+3Ud;|3.Q+= vIZzT6ٻ!Ļ)s-Izf~ǒ=˛99QK``LI0;7[%i~5͙\=7y&C {%yOOHZF:?qaq!OV;.؄)Tlat;I| {|Sҽ"NExh JbL_;9YB/zJź90]mƔ.CmIeIZlh2b罦osm'RBlO28wIڛk(IK{?K i&GIZK/`ck^oʄSU`'@VN73^!dgYidd xo6^T]~źKN#}e=wiΖCBBX#h!4qCȧ`Gihj [JBҕ"8Ï$i3.٠"Ioމ}0}ҤXI?-WWIk(IO|iNm(8!>U'~ܪ(I/->;)mUbШW;jϝ?^l[BCwl޼JoW$)OhŨwNi:Աvh΢jhS oθS>Oʒ/p/F{H\57ohڣ_{ry4DIZ'I9?hO7D'1Ibw$]ϳ;K>tҹ!I -tO/{#k/b}_q;!:pK;L+gy?'CJҳGong&4JN޾I^, \xLÃ'OMxOV̻w;6Xq|j9$ŠXY򻖮`QtNүy?JcB$$=?hY믞=kΞ9nÿ$isK'٠q%I>sʵzk)?'v޻.}EFKOe:pK\;*>kBzsr1$=e>Y{BBBN;>mJ~#l5e+uSJꙜD ?N8͘')*Iw_ o-loT5ڕPD֭MKb>{6=`r5b}Zc$XbYq{fyc$$N`ק?CsqRc߇wYOgsK!Jg);{;46s}6Vľuke$M28y!^!=99Q>)(>1P6<6"}Rt+=zf];]mpﯹr뙜4ajvaVk٥>Hy>á!r2x14-=M90Ө(0e{w%NOMФpl_2/.Q7-m&Eo W` &m'Y$m-o_c~,*<޿|$i%?~jiY1D) Ga@IZeIZle=C7f<ըj.Jܱ"}z,!srO1$~op<;}洴]&IqXgt_$?.iHz-٠qi>o;سԋe"pޛľ?`|jB5b%i>6NtԳ?ň4H޼͗ՏSIH zGӔ_4H夔)k&aStAigZcG* *)ώS=ǟݩkNa^7Jmiuټ08Om74; wuSZ;ښ[jk5V!Opj$݈s7)Uh* ޳Xm*ۛ_!HH&;ư?j~{Ol pVIט&"[0$A$iAKu5yin={S=tk:?^Y "7W%jN#FMf$=05U4b4 @Zưu$i*m՛-B|Z NSѾIDl{7$$g{Ӑ?H[~&I7}4\ǪwD٠qu!c4 ޫׯ{; 4?o{֫k5'CJŅw`x}J+nZЃjM9Mc4$1\󧫐90$]\ߎU0$훹$iA*Igl|{ vm,iUCNv>/IӨW%pNFQ*,J 帝t[#C,ݔ=2IXXR0Gofq7 S/Uv8i惜; >z[4f8OY&L9~ʥ˗G>x9I-U9ɼ%J'K}c*~nf_pp0;{gש-y_uzͯp2fxT}vʢsq/ۛ lTт|1aܻ{&"h^h[ iO1$]wYJ)_çIK囹A$iA>sI_{CBBx41szd':Ț9cY3G{gujz,sr0$MS/V6VmT$wiNP~C-f'#JҋKfi*4]9ol-]~vnvQIMMĔ$giRS>utvmwuҠ4ݝNx꺚O ޿|՛Wlԭcl7{537E"IyyW[_KSaȚ%ij֫GNZJI;WP"yCK[]C#>µyP0^Aw(IɝS{ke&'+=±uecuhl"G|,*酆C=Ɣ>@U/UIX>%Γ1%tDO\ =j86zc?9Tzȿ;W{ BCCwx) cmtq X>ͫ¤8vI:}3-.-qq挧3?S0ٽSFjU}l[=K$$,ͩ$M;^qz$LW޴ENoײf`5NcK~%6)C"4JWw_]lN'-kLRn/ZJ|2QQ?[XY{ͳeJ{n%#F-4QY]%/˻0~f|^q6q?m!C$$7u CeVmq;]Jm:Z"yՠRn"Pv$Ե>yjYBf\z3ʺ׬`ye9"a$MLfWi]Fu{yV/Afd|tiuw)1T>cW&I;K%Ķ^=7sr_0$ !(((,,,***b4MDMi^EBdddhha"$`h CM7 ƔW)7y[xE hfYGyz "i  H@$Bқ2M6SP4Kn]<?~H@$BwO D{/$ 8>q{{%-BBB*轿c$i !Pνe3{hoI I# ?(qI'*mť%(Ȁ$ @" -O_u7*DŽ Ca )S&g&-M#%gC)H@uqAgY8@?K,JCl9!<93x+@r Wdbt]c38@ַ6h[;۷ۖΞ$ ̦Ɩ'OQ g|jbucm|zho)ǁ~{;n4 }ضh~]PXpU I*@ܻ[Ԑ<E+$i$  I$iI( I@HDI*@ HP4Q@ 4T$ @$i$  I$iI( I@HDI*@ HP4Q@ 4T$ @$i$  I$ ]^_Q\]W/}>~Gj|Q|]rhtHDaPIcbOLBG$=eVt' 4@o IA%i > IUd4]]t4@o Iq%W!SSe$ippF]NI7¸4UzH(Ie I4QWfM!f IKTTTgN@$MW[g7!I4QQߕ((s d;i/_\U[=k6ml[ާW-k#hwqunڻ:)u/,/nlQ)tK{kL_Fֽsymena~ht~} 3F\ŕ%u+~Vwrf2Bi6H(,_\}=nuvkI9C6>=18MU='==NmmٶsՕ//*Ae~21Hsj HDaDIztbl?Fe>dFI:,,E&bZzB \fGO&]EB!”G]K@ޫ<;e>Btams] |st(9eߌohsew-o'ݞ7н> $(CWeMckqݒԀ= ˆȬdYHW_[q|*l<{, 993UZQvz>ݹwUo߿SM]s O24`ĩA-ҥ [;[m(G5:1˜2RLͨ[k) *jk}xI9XwשkԘ= ˜G};sI:Fƶ[\YyӔiZ7sU4$$u˴Ɩ&o_MNl了֎ѹyJS>|_Ae١66WHs`xPA^XYCaAnދҒщ1YRZ|S ʗ+|g߶hZfų{`(,.˥ϻz(*^A_%ijZ7Y 7+Ksj؞ HDaHIzr,Iwu] &$$DY][]v%I/%J"K^Z9Mv:V`W]^'ósZd>(9PUB+X킢4[8P9F8źM{79=շLGP!<}y1&F{J&ߓeZ|c>+> $MM>k^ZtIZ`Ng4QQ əCfӍ-Mʒ4s3gm++clekrVW-kW6VY)Tlw4*Xҝ$>})}{%D:4Әؿvv߿ջ/ I|yOeʒ*Iͩ_,g@ #JSə)笠>޵Tڕ$~2]\[\]RV9zU7#(I\HWK{ }[+]%EBSefn=Ty/jM}-/ S{{-9PX`8?* iꐝ§+$]ݭ3 >ŘUIZ`N@x$i0$=1=I{5/%ws[$]U[;-TK/9`^l{ʱ5sZyO7e ),o:5k3^!dgYiddU fۡ&kڊrȫׯX7 $Ͷ!an+n'f/D_Ph,@ #Jғ3_$)667J"NEHVyUUA71Z:۷*wR6+UbN"jG[˶E!yi=t:kwNz索Rco%Z;T[\9/Iq1WINYӣѳI( )I~M5|R mx8{{lb{Tz4b}U{ JЍz[4f,}SUNxoGPf+795x$i0$=NHtו$MʢOppuۑq+NznL_ gc,s֎6HMUŬ#uhԮ!*l&sYHJrʟiI #00HMOeQ 8ݒ[[VcMlNܳI((Inh07]Tm{YϞ;roON™ U̼Ҋ2/ yb\ /I+y^Z[:Qc;XAN,W]5H%iZfUV`NܳI((I/,/xJlKt+I칳T=e7FNJCPP` g&Ɠc/NͰյޔ00uU0ӂy uNS;X:{BBB<+Y|T Ky- ^nlwll(I ̩{@? I!%E-$?‡ Iyž̧vu5 gˑݤwҷ|(Gmdddu] EU`^~_ħ{wy1tlrUTz4!1af[U/xP2-2Uwsg=7at%i95l$i0$%$8֎wٹE%E#h܃ gNOic gsm:{rrn'{JBvtw:}sgj>+xWo^Q>|PU[1k6Z隙 v~^>yyW[_[Q]jZ]gsr~9HymJ-wS1E"x+Q!:>G|k󦁑Aj6(R}B^/TZQ95l$i0$%Ƞ$M;wg:fJ w#N!ᬾ^5Ut $il&h TByIZqA2O[טʚ*>[ʣz(:$颒"풴gt4QQ^^[WN]IDĩΞ.~Ϧ2//>nPQ8kl{U֖r(XhӨ2UA=]ӂuusϲiY9QPXt/)CCMeղ~B%N AAArBg4QQ^Y$I)H [XvV6Vs5&oqu=.fBHٵ}ݦ9uRH<|"+cQ+wY9Y25b۶YW-kS7o󣢢4ԩ$m;ضPR=&,*NrjyU/PMVj/HE18fٶPyR!/ It)X>Y@ I¢/F;ޛx(o߿Ǣ !222444004Q@r] HDIȁ$ @$i$ @ 4Q@r II( I9Ȁ$ @Hd@ H@NOljliz4004Q@ 4T$ @$i$  I$iI( I@HDI*@ HP4Q@ 4T$ @$i$  I$iI( I@HDI7o}l[P5Dp`x`}k#fNOYZ[fUPX5K/}(,2M/l۬U_rQAmsFtݏ~|(i :yBʫ+RꯝG )zAvT~FiIzqueA?Izms=%Uqh?etv1loGʔ&I:{PNKHoGď,6W򅇨wGFUaņ[ H$ @6wS2ҏ|y3W`?vy´ګnJJEw+ I{I,66 -7^@$i ˞rrz]-Bҕcގ mL=zP VH$5"0Z+`zI#3LU44674547Ue>ϼ}$ @9`O9wn6dVan }*NqY]GwfæCcz'j5P V""?s6ИRHEEE]r*I'g<`rC~$ ֋WWGKpTާ@ךe1FasI#%i~US_+tpp0{GͤeS$}|!@&6etO S*$#Ed[nh1$MbLBəIHEjꌙ H (I>$%]mG͌OO0<;e/$ގA/]q+ $iǭzbPIB@G^6/-ukmjm\X +c$i FԣK>&ŶLfdqzIڰ@(7JBV++?ɘ_4+k+$i FϞ;Kb;;*Dzh!I݇w$kgIg IH`\I*d}c=$ipQO|u51m`{` 3T. Ipd ye97g7BvVo޾9y$ 3 $i[r_0$M̈́NCN@vѧϜ {E&NE$i a=bY9Gӹ$$i-3 $i[r_0$+Ql~d;i/_\U[=k6ml[ާW-k4^ZҍtW{W<ö͝-0enioK9ˆS޺Cq.-74O}a݈+T7ؿdnҳx@YPژcck{Սn-I;<cb6[;Ƨ'W6Vvjǐ$iP-yBC4T 4?4BRJ-ZXY.IY9Y߶Ch[bf|jISo2] crT1ioo *Sj6wƆ_Z8ILL#_ BեU=l*{PW|2?ٵя V^|$>$}r kXJLw*}K pu'.'I?|!eacCZG}Z{EJ}^[5%C$} *guSv)3,褏F)I{]~% I&ė yղ&dAn~ 8ΒtkG7c dOinC}+cD( $7/WeBI{+2iv˛' S(**V?jOmo^vmv}JŖIyzXK]^Js*oWc%?w%ircZj\݋󺮌 (I{?[M sZz?>^RGYEt)+GX[T&!1*ȉ޽G/{uu $=:1(<'m"I݈ض`+Kյٹ9ϲ}2MfH.tkղU^UQ~pd߷ 7l?gI!s3B/Fߌ=rK"hBreLuuʾ1Y=RPzQmo) no Yx[ Jv/߽UTraS>Y 9WIM> 3(tly $-6$-]Us$G?JVQcr0=ڗ4>u‡Sfti%:)IM1hy̧,R -uZ}"d[iIYEYSkWnI:#8zgN߈]\8:+7^Izr,I7teFFﲵ+IffeQIPؚeʁ;aG3!1aiuvU%~2<;7E惂j_bv >*PF+TnQ=N{Bw|_}[LG gcbn=6._Oƪ$iWlm{mGU~ $c"I082D`cg@[7~ĭ{.Ge_ŔħWrS@]jS_~ykyUeZ[en1Z9x4/m,\$-6$Meq3(תt5w^ $MbSgY:VȘYiyy,w-[چ:^uw#ceК9á!gjvK?9/7^)#J4%>3?vO74)K6j]=7y&Ӵ+z`'?qaq! `eȬҕ0ś0Nhr,N>䔾 0K-!T`bbaOA$m^2.]Z}ahuSm۬_SP~,S1cP7r_‘xȎ.d8UnFn+S1Fǧ&迒_]OqkG+ďC Vʶ[检ɵtz\ m%EB1ӥݕ@ǒtr#7Ľ\4H wke$i#ڊ(22ӲUKS={keL Xm;bb7i5+TeO zuUW ^c(IOLOj6Nv}GI}[Xї._|sꃂ3BV9fN+LX!\;%88"+ǫ3:f5.ɖLҴc{IfMƵ^ik-Mt-4- ̊$JZjʵeQ$iW I C<[6]n]8ڒ/#_R|V?5>-@oq9;X]%] *$=%6B%5NT}(oQqƕS I+=.t4LIڜ>qz,I96z'ҳ9[x%ə/4;*Q q*Bʫ*- 켉9af޾5SyrT4T5;eΝ?^l[BCwl 'ҪBo$)U8XxN++:NDUkGjx w*"I1`"]R wt]B I C4ڭti7׀A;M>6`_1Drg@+׭erI12-ȭWouu <ƐC݉SI)7rj2ٹ^>K{--*9Nvܤc$ic@[oم#/Ixd9o`{hkǯ;OoFyV[!IS$[gz3I:?vɭ\;uȿxTX$-|$olnd&?j;}4[|v@g FFKQV};37֚ݿ$i=lBrbr3rb:cDIzznF?*WM3<6"}RtRҟe=ۀy'6`{?q%M@@@ӌ镍U+wRR𿨳]CC NOMKOSL)g)4**0-yȰOM07k6/W(y4Fo۬;6~syIZ}S}K`-P UwWO~ 11P7r˿q ޮF1]f㧟~v*>J;)um,٥Q,H*eD}%is$MDLwUv %IIrU5>nOmg.J+6/5+P$i#9|M{-,."*/tv)FKQVftT+IaeLL|89x%ḯ3m%eW2It?4 -&9<[҄UV1+_Z4U [< kqvR"rSp($Iw-,-kLcm{4[bhoBjISmWo--N@:x) \n]8&a[&Tiٷq8Uc- 83DJ%>qڱFS$+_$]Vc/9c 荈,66#[^dMfS]cUޫշd4Z2O;%i=liXr3rb:cDIeK D:p*IfwNtEFFzuuc֯lNN-#C#dL sۻ^XAAA%ekkw[t~~v.pGw9M{_4{ޤ_ԥkiuټ08Om,wcc#6ZkuuSZ;ښ[jkOxIF%?e|O!snTʿ %_/)@1{{[oم#Ie_˾ekրQLVԳؚһ'I&{ s[sj|}hcIZ`Nݒ"X8$i>ekJUݭHv[قW7ſͽkyWr2.gk§/!!!;ꪄĄoop71hth)ʤX$-|$Ǿf%޵J3SRzV*IaZE@]1x1$*(yv%I?qtI[KKtWs{]Yf7-\V\7l?͑{U1g91;Sd3v( ʵ {ݭAUx+ODI̞NMץ)1DD聭7r˿±xew}~!H?UNQD3%8Q56(I ϩ[t,Lwk3|^>Hyp`H7G[]1x1$D{)_'q%Io'V}dؤZu^T+>H9a)*n-(;B򇥾x-U>)+7AU&\z2X 4uOVd4rex I Ctݲ\n]%i/#b3귞O?,PC4Ãʁs^UQw $i9uKf;+6jϦg4ɖꌾY$H~ ܶ}jR𭃳z̻@+ÿz<IzK>6]6$-꭮NQk+dhJ&KZ80;&/o+imvYjsex_ G| /ٹ,$90U&5=EEF1QhɀjZCnSO<!Ima[~d՗YA8_;e3"Jr nkDSoMQSu%?[Fĉʸj|~͍3IJD1錾Y$H!pS6:fQ]y6Z 2KWOMZƿ$i=Zf4lIZ`[]1x1$m^Z?gW7~p;Ч I6YϞ;r _LPʯYvW WOׯ}c*+sAt"|;jϮٷ>KSf)a5=T$!.-$0T{6= ؍) 1Hg|rfJ 0>=!dͭߴtq2!!!C$Zb;=؉P*xa4lLI9x%i/V6V&xJq@9nWܻT+;X4KsjoJOź*^4y)wlOWgOYe yv5’"ov^4*g|[ ٹ9yQWS9ud~ $-p U"h #(+rhs:6飼x*]Rt"# 闞kIn{ST=T$q9{3$Q\LCYA>|{=P:>p~=Hje޼͗;>5 aşi񻹳% $12p(7^])CJ+6JGIď-EOΞρB{^~͛ʻM{ǩWS_KY^DFFVX\%Nfwݝ5XqWQӄ7a-㪕iioYt]y/gjyF魝-F.xɆze=]H{f~ $-p I"tyGvARFK֝ݝ':GKVIy~t}D̗XԶ?rYLW7߳[nNk$i9%[g9?s&rǒ4G-9qauF߬ I Y0W_J[aؠjYh)ȊşxḠ4o^eq$iW?uu <ƈ5ݰ$8y)֎wٹE%EdbUS>kӕp;6uVގNO<{, >+xWo^QhPU[1k6?B537*$M+%:ds4qzX'ZN@>Sٹ99/rqNۧ8 u|6gM#l(XyeyaIQXtt6J767] RJߩx*.W O %IF^n]xaecuhlLd?Rd4Iڏ{"]k^kJb7ut 1Qwr^W=l&y?jf!so$ߴ $-<$MWEJ]}4Jλ[ż]t74JvsFL2+_);h /] EZD?ELS$i{;0;[z$$&.~{`s @WO M Qu=ǧ9r5G۝Fx{N5IM]obNó"Lͣfr(I+\/o *u,^VնQx\c:/š2yvg~DrmVc>8=Q6a f`x7NVJ[۔U_eJ~4_XYtu7͑zNї4+$iȝ=]ME㠖#;|D.;kimY: b4pd|Թqpu>Zv"Is_S4Z.i¡B^96$MuO>o7dMJscs#w  I{6ø0^ n,g]6q<6r8;̎{՘J,{PˎwP9$Cʏ4Wzrz-̮OR*c{"^K^F~eJrl|ҳwlG(.WkuUo'GZ}Cdot-7jި+FSp%uM4$-x+̒yiAѺCoV_2]6-Y[͝ED Еdz#ŶmR2MpmJҶm %уm¢r+׮WUNQR̎ ?Qky_𡡹bnqfZm 'ގo[41mY\Y"zEx-P"޵RԹKkI'zͼggi9˜I٤7rKu[*їξm}J3MK|m7|u`+Sj}yoUV?jŸ4>W1?~㰠B$v ;In~9v1P>]ڝ):'_<UC]xy5Mǒ{U)Տj6uԥ {,"` Q ծzPVΟgk^׋@9^#)840ZD_&%jUհEBdddhh3^8_4l=hopT=d<@H@KIy(Iȁ$ @$iC$$$]{_'n0,Hd@1'>.)4<>jKWqi @r II@oR LL.,/ZOr}(,@YH@$i2 IMvnͮ?OL%JDAHd@Л[o97-=] ("4c2[d> D$ 7{DWowAakWQ2$iI( I@HDI*@ HP4Q@ 4T$ @$i$  I$iI( I@HDI*@ HP4Q@ 4T$ @$i$  I:CC| ;^>~@)^@ yD= $CgղQ$ip %COO```XXXTTŘ+׮ߌt{rv h 芁$ 61]^_Q\]W/}>~Gc669Nb=iB􅝽>$b$mDB;mI0$}11 'g&!I#Ium5nve$ic&ޑ4 4]E] BGUֶN>FIژwd$6p0$=0<*djzPH(eg(`!Qz=$mDB;2@F8VW YXImx4fXX`ϲ`;{;Qt=$mDB;2@F8VWfM!f IKTTTgN@ έ۷g-iE  =/$6p0$+QlFd;i/_\U[=k6ml[ާW-k#hwqunڻ:)u/,/nlQ)tK{kL_Fֽsymena~ht~} 3F\ŕ%u7% NLQF(mӦck{Սn-I;<cb6[;Ƨ'W6Vvjǐ$iP-yBji>݈! 𱨐X^_ AN#SÏ$i- ,Z.OMoo۬t)';ˢMHLr@QPP?[o+#nt Y&=щQ]FG'>}9ylhtX$"[bZzD>ᗎfy/"Ջz4y<(Fz.-hŢ qfimW+N#瀖dAAASFxZ:g7-{n'ݞ7нezIl1[AZor4FeN#S/$i/-`Q!Y%h@K#NEؾN~Z\YҘe!-hVF`{#&']294afe2t-7][/S(I̚,v56K -V\(ESmgϝe!'gJ+=N/ZX޹wUo߿Ӳ[Oi5;0ϳ3Q@cTx_0r?0 .I ߊo3۴o ?5f{QϰP7<[[ jiuY @v)//Zn^shcsoyNz Q$w#IT][,yI٧)ӴloShHH`Ɩ&o_2ru um==4#[t:@Yv(13ͭu5Eu$ͻ'fGMvtw~(,{Q\Z2:1&K+OzT"((J.% L s |xvlCŅyyw_EW}>|u,H.İfsC F%iQ bVTWil&şUs1>5z#^zl1si,mӇehZY_ѵtj1$=9$C֐ŀ ekW4l JWaͲj:F? K|+׮j/ٹm-Ribv >*P`nq]1i-M~wL;鉯e !Ze'TOe^ў \v 'recIWӇ=v qNO,I <^7[W5kʢx#beoHŜьstw_OM}SibMx%qKsədӍ-Mʒ4s3gp*+cҢQOHZF:?baq!;iղ$-rzɷv鿳fߗ_%'N%/ ~wq'Y{Hy(\xht(VƊc)~]slo!Z>OMml_2/.Q7imۘSoJ<9p1$-Zd߾xǾXTn䞉zFmYᒴtNtҀ&%ivḧ́3 P%e2I?4bΛGWhhEHXXXUm5B5#@*,}Z^C?|\% iҝ$V2KkxqGdd${SxoV|SQ}_)?HB, (0&/:rHOg-@[_VF$=1mc֯2z Fyi|Fί~KO Au,Sל&ݽ/lo^5//-Sۣ0F'džF(m}}ݔ֎ZTo}467kEvڧ/s*/E?H$(]{y@@} DzFmYVo>(7 (IvIz`xo?( ˕$;85kڬ`b_FEEyIyb;c W٤-UEJ[GXȬ,?5U4b4 /iZؼq677n$-@rb#]ֽs/ȩ`~ `$(>}t)Y^r3QP/+#\ik lh`HIsXX\ȯ$W]ݮ$i~Uin).-q[;ޛEHpp0ƶˊKfw‚m^txh~Q΋yν4 ^EYPU&g&=~{SKKՍ/2)%i,VIDt'Iw)zFmY46<8rz<0R,-,J O㹝t[#C,\Q.Bx %E Oqfw#ny}EEq̂xƶ9}54٫i̠$@::1+,R,)%i, /:} -+Yl]'jL+gyg-([_VFl{#/)L|Sn[/8Q_u tו$M Hi4 ˛E/&v [ɲsYHJr/بa(vLjz*^9V'P\ߌW 7T`_0r?$S"vwåPކW3QP#+#ɦsii[n[/8Q6/-Ы?d:koڬgϝUxtwS"D %J/r}c*~ >/@־ vĜzS5j,W]5TB`ɛ[w T`_0r?$WU𲙉gow H/,zǢ-@[GVFx{df<8rzk/V6VmӐa떫`ۗT {i0=rCWgOWHHg»ŐyK Ϝ޵?qnl'>ȩ`~ wIZyؕ0HĔVm%r'.:ⱨgd (ot=|z&#!%imۅ$}]tQ0)Ss9Pt]~͛Erdg704e/Jȳ775 K ܻ;k60c㮢# 6+b,}iio壒Θjj{J k˴2wxos*/xWvض^;;nyW73%፨gX (ϲn o1$%$8“3msJh*NuW݃/BOnQ]ϵ}˥۴l`AiNO<{,?+xWo^Qhr_U[1k6[]蚙 v~^yyW[_[Q]IW:Em-7_urVΗ--ه#NJHW]0>Zީ"Is_SS])CCME&8^Z[L_O)|6_ל F Jb-a9ĹHg7G]/_Ԩ.z?27ɵKCrӻĈʺ&I?^OAHd^ZݦuogecoǻG/dr-zw_iabݢTmlѢ>yDV2ǢBW.rdxmfi(9u*I6-T*F6IʩS\Z^U1:1FK1;Z79m- >447RQ-ӊȲmBѵ,=L {fn%7=T`~ 8a`IZT[e_F˅},2:nY렮F]z3b}`ʸ D$.Sk ͍ <$i$  I$iI( I@HDI*@ HP4Q@ 4T$ @$i$  I$iI( I@HDI*@ HP4QGIzim >J.D_xWwoec5i"#I)b? omߌG"٩Ih | 1VsX\|CaAOizymef>:u T9:y% 4ʰ4<|@~kYq?>ks (bDŽV-ijh;NF >|^y8ki釒HHKh| $ibƗnfu GIz֎ϝ?(i$ KSVY^PX_[_[]WS\Zq*GH& &gzjcXI#8D:;X*(3hg2[Q @6r|h`b74473gZ }*Nq٣S4000,,,**bL̕kWN=9;Ia IG|Q|]rhtXSٍ~>M=`}kr9QBwʿXE՞ ٭c/Ŷv)$])SP JHKhgګrg_&v Ia 9>@F_8beS̊ӑQHG JSiIwk[u Ia\I. Mn{&IHKt}t2 `goNV`_K{k]c=38y/ݢaHH#Uѡq $)j ` @>b@6r|$bbb[M3(@>zZt8V"$Lp25=U֖ I V][O3\E80O^hxsFMXݟq1ȟ+_N8ej ׻BݒiEZTVkY>xzǧ&7iFek8KBb!F+s1&REc ZJYZh)܄ׂX h`d{*P1(iAOݭVʋuo?12 CÍ-MTd2#\Az&IfJ%Uʚ*+KDuoD>FܻLPթIҩ/.i *ybY`V2IҏR=$潠2_JңcqUN%FI:,,EflZzĈ>2E3'*[->m+ـc+Y2 @>ibzHKSs>dVN͉Mfo'ݞ74 {;ZgC).iOE,zC<'w fTו1Z-7 F FjrrJW"$×Nu$mp *IWVSa]{Y`~SF_!!!)eΒim-2#769N]]Ө375TVWn*$͔y/?SgENXIZ9ZRH&gf~wF[hL㾏%щFoSL9%k-t܍m _Yյٹ9ϲ}Ո+ԺxCΚ,EEMo_f'g+*m+,T2t?NN1 Iֈ8}l^\K+EdWQeg}?4PX\HK&Ǽ{R!b1m=*%^aѲfyznc,N)I/ K]~{Ӈ0Nyyr"5\m>Jv)'Snߦ3 ayo҃?Q= Rٓ?%^ 82K\޻#>UȂziZfKI0C<'gѸJj]\%iaRlM4] {VcYMqi ոru um==#s/}}_j\T$mp *IK H5/-_| %{M-l(O ;yҺCuT ݥn=/%iH4`~*·RW#_ T&5hf5(Jf e]ӼuY:NV`W]ueSp _:EΛ\9a@uDS`]pjv5ޑ$Tl+W+q٘^Ȟ ݈u^Ɣ ~0=+g1ju,QB7L S"G8`xT|L![:S*:urϖ!o3t/Z$im`# jKU''41sr`GL YŘC-ŖZkllOh55qԂsżlxyru ? 1Px{]NMOU]ӕ0 c_ܘH%39Oٹ9ltcK$oms\tŠJe0?!i鼝pPLlQ}6//8 3jۑZ+,. q8_VY NGX kcРAfXgcz]вɤYxE$b$ 07-"t}<=V(|I"NE`Dy s;B.>|eNk1srZkvmRh>-ŖZh n kO˗~1qԂHj\ ]cw=x3 UV6G#'\ 2Iz|KcyR 7%ۻߝJO[O Ⱦ:C敖%B\(C >ǖ 1,3N !IkNz{mR> sbcK3}6_ n Cy\q|-I+72޼_z6QW6?$i&:NJJd嫡Ԗm[g+IQcHƻZpźq|slz7PPPP܍8ʽIj^Z˃Co C$i!#o^Vhxg޻$[5&ـY:@I\Ђg> m gkp eonw*I͝--zFAa6:V2b99{|ղ&@4Z-7jA4]0=I4--BBBOӿlиS$6bPIZvPe7O7ۻVI|8a~6|!խa̕}=+nI,ʁ<[cwht(V[fOJ+ʤOssN|5<6"}RU~KgclparNH6 IЄ͉e_xVڧRY@ݍ6kZOM5̴#{Nڶͺoc/Wj9)^u67Z: &"YT¯%i~\k=|Zo?b{hx1sLgd}@i?$?SCFG~D% "<ǢBC >'_6-yRlZ [@cSKj<Ђ5V2M S=4.I/l1Ġt㏒cvm:X>0WX\¿NHwukU[4[eʆ~+L>p:}(;݅2Io?44i`ΛG+FZ*9>N 9ׅjY]?Z{E;"YZ[VMKe^PKh]T%q$ fKSn/-;:kKN/uSɫ:ޘؿx`c_WZb9yҝ$scQrӻƷ ƴʗFx9%,,ܼ>иSm1Ĩw ju{I> >.|#Ig>dۻ:dߺ%IN9/r4XPPPI'*yжsؘ-K o6u~T.~V]Zzw˨cu 22c#G|&Y-cZN9-COSᏒ#;ʐEf..~`bzrtrlxld`dVG}ݝmͭM 5bAl>S/(IqpIһYOmXX:qr-|%|1l>l?CKh/cHDv7 $I-M_FW^^ xW}vwf1srJ?q#5Z-7Ԃv1g5hAaO-$ͯtKnuq|gG2IdKÃ7O°:{\IҼoY- y/-==V-v屑#m @W˽t~\Y_O|_HG$ZL#g~Ѭq=bz#/}Y!R+"N$WxxsmnWgm}!HOe:ݑA/ɣ/Fgʗrq-koiOj55@ 0v ǣ *h\>S%i۬cA%iG.D@S$|6'$|Fnhco'ݖ}KҪ?~׆VKҜ4WY2I|{תd-ť%*ϝ?ﱑ#m @_w >Hyֽ3 S@u>,[o%]J6n3riIҼ /CS]NxJr9P>\ֻ;IV@yf 1JF%h)[@cSZj<Ђx$mQ>۶{e\-Z'u$mpu 'I:V";#-#g-,)]#uoyIbTj`d>0qe݈[涬@fKWtHHd52_oKG#m~$)p]\C񔤇=J}ֽ-M~G/·bЕ5U>:u 4($!&.-vIJ?ZBuX#Rye9W?ԋ "ù;Nη&h[nU -1@ƿf5hA n"sb[xه/XLiρSו$M?[dNNyUj͏ V1|K$=4j_֦uojz*o$@Zgz~kG.N#P"xHI]S+u7aOw#Ѿ5X+IYgg*lҵbdgm 둡yDq[nU Z,1@ƿf5jAr5; U"Zu*D6:Tv5J$i_[ɏ@dM-e=SWi Şoo`yi톖d/Jҵ ulVq8d3xIZ%U#-S!m1Ġt[g`f%7oM_Sދ¹M9WyqX[>h!L?2oec.D(Ib'(iE~l4ڇOG"FZxHrJةY{vkxJlX?uY|p]XR.6[gzGŕ{CYBJjG$˾ʚ;mtG1i$Pn$w`fŇ2"%IbJgy?|N~cx ,*r;Zh n kOEi55qK rsGWZ>OͰյ>и|P$iڬcIy#WI'!ݕ^:tC}O!dT_wVV|U^YYMEFFV?075%im?^R$}_'eaRs I,-^楑6?xsr{{m\cffcOIiS%$W&-;[]y/ȘMX|Xޑm ?wKh_֩$I߈)IvGo!jτXv:4]L O:7K/$ضYzy1}헗cDBbU:Buƒ;Z-7ׂvToҘ^H+;Iw8 e)z4.ԩ(I6aPIKɧ8!|{wT^*Ðqֽ4RW8:17)M3fȺb&̳=w+xWo^рEclnn688-M2j +N%y74kh]TRDC%"/I+HM!j4颫w>hW-ka&zs,'c$;`?,:j~5N&~`dw Fs’K>ΆŮnecuhlf;d]yZT [>4}tݽd1/I%X.M&(H<]HًݻKxjOTcX_y({y^?kIXݟ,G~ IuƽoV_aD엗cߤG?nyx+rаvT|Go$T>:uBbuPa!;;}q$mXI1duT%^ԥܱ* >Ea- F582F69ToZ^V5IҜP4qy%BN0%U$F#-0mFᶪ܌IڛVe~yjp7j̏R+CXgzЌR*k|RJ/+/I+oa@&ڋ A;+'5f3OQ3GɾB5JDaq{áUo΋0o#'S X>-Ŗtƴ ƴjvvd/+dorAV%N^9 :3߶Y͂$}t8N9^ZXu]pK;.2Y9ZdѨ:9;*mKn Y ii}ޣ,4^^^tzȐ$-MyXii%ip/zEȒeCkg646|X6ߵ_%IClx/:ֻ";7pl͗:5$}r$xI$eij~so,R9"VΦK zH'~|;.}zhn(+b93,, -ŖtF ƴjnݾYN+mlw|]Kk˒}1Z pKlt֫ZX;6 aKҊu5sfr[IՕc4uضȾnl-,/(4ó9 7K˶]ꐣc>c\$IZ\%;K0YiP¶mVڧ(߷&Ikah%o%64v)ֽ-o}ڌ,I4 Z4ojHaA$ IyA4/88248Oڲm!;v;L'tΆK;Z8})yhUF)?yس:5$ =tJY^{ߧ"#tK݌|/%9%L5mGF#?Owϝ3'!KҔBU[tXX-hYA(Ο}!#ɝr ZR:iiP~[n h`L{*P1cIZy}ݦ9u뛈H܀omP,*tt,\[K3GfAǔ Ҩ}1Z~2Px +I:Mk6A8#&/iS`$iI+BCCrP&`2$iNϻ]^_r6;ú$  IsniE$ܜ^jz* lP4T$ @bnq~x_AZ$  IX泬g(Y@#$iK}]c3Q2`v I@H$Ƨ&V7Ƨ'l HP4Q@ 4T$ @$i$  I$iI( I@HDI*@ HP4Q@ 4T$ @$i$  I$iI( Ip|YZ[fQAGj-4Q@Wy(Zp$i$ esgGi(Zp$i$ Nc/Ŷvm۬|8^)SЗٽ~ ],;ՕZXYnP-tX ^I(+IO'I$7M {+ˠҕw%]m^P-(4޵*b9d F#B@ Jt%?HvvmH2eU鹙щ1*ѡq $)jpmc֢mtey?$ @F]NIZ?Y9FA-ꉬ:;5޵4В\v8!1Nd>dEYik&b^2o!II( -IʁixH:12>rW][}{HTTTgNx7Ԃz>&i,2=7y$ |$i0$m'߈$}Yic۲em`du{#NEh9_NvYc޽Ip؁ote<26*:#emymena꫱~}RZ\]x|ڻ:)u/,/nlѳַ6L-1ڸqşW,Mٹ+P@E޺}GLYYS59;N`2ut˿}A:OOhIvXXelr\m@t칳xz{ ւIl|O=no IA%FweMckq׵Kaaam-2yVbHy$}ڄ͞^PXel)*@ޫ<-Q]r882$}269*ܼI!LlϬ+$$jV?I:rRZ? 63?%1==OLOJ<2+'kd|t `2t#شOlo IA%n G W_(ISȕUe}-!I Ip ы+K^o߿RVs OT=w*(UyνFT|R`-TTW.P?ٷs J.(S5Ev9eE`|?%[źE%(..Ka(N~IZ^ԵĎHbӦS?ހ7@ JU72,0OAm᥽ܜgYK>M3=Ε0Ξ.vWEuek+*m©qtppDZ%?Hkkh[\;ռ 564QQ_S qRʊnmnm.s`xgZp,Ŗ_Dܕz0BiIYEYSkW=w0#]:ЕU plt5洴*d7oNV;ke}EZcD6=4QTsqIKt``I25J"K^YkQm?LLyOHH",Λ^&'ósZַ6d4ZF~*MUw#N&!1aiuvUWVVW[E3}P gtmCk u^xw vӀe$Jg1Y|olQuB35;SS_ pD6T`{I(*Ix7Oey'$s3gv++U_ڄ3jaNaqϢhwmA5G?- ھ+YHH/f)>njLs4MN S=Q`p{; ͍L>%D5㼄Mn /peׂIlS x $i0$]S_+}837+}2>=,I }WTN+NR5mx@Y:5n=J~l  UdSĤ.k3ѴlK !]O2]5?aonWO(GOOŶ71 48"mot}AYTK/񛑓QkrYǣ(1ݝ">=btrU0 vask3: {2`յ<gg[[k) %eϲ}nqzUGOXHDaPIeMKkIΟWgMLӢϲ?iNQIU`+O e/l۬N:Rk!CX7:Ղcw}۾_%§z܂]2#}ȼCH lղWs?ݻw~K/K$-N71 t}c=%e2PTT$*F;mzpށ>~Ct zBavخy޾mW7{vpdF U=jq}s] O%B;x=t|zH(}Xd<ĨLllibcbR㐲 kAG֩<4QUnj`n|w^>}洂(1vbzR;Swt qt-,^q!!!'_e^NrbԨwyF$5d>E;3ހ=Vl-+8RM]Zͪ" Ӂ:JK;+o`)@LYW_UGOUz71 tCs#UeM5$$>8 d~ɾgY{#6T^{`}uSM S+G+I؏t)k%_Oh3*CClxos" ScJ|mfU7o]`[[Nn?z:գ$ @O[mү } dqI<=a evޤH 7euLʷUVomҠeWn 1({pK>u3~Ye9/1ao_CGӟdSH봮{)!Wk_@l|wV><6)ֆ⚘tt =GOUz71¨w Oyud BFaI.ӴoF;mʟ?T)TkȻ:5;M7824028>5aZ{iTkBw%iO|jkkwۀ'U NqWpLy*S ?:9* S;S1}RS_+]!엾pAKqO'IS=HDaPIYŘc^}: /9fMۊs^[z{{#6f^2; 0vDŽ WGvt޼uZbqTTB<]yTTWkW+8^~P)˾q)ߴ/iᛠ?5i9x޻,$-Nhoc IA%̦ !V6H*SQ]i_YZH,aNW7-\V,NGΟWKIZCa6y_ւ _\3}X?N{3g]O37-( )OHy`iu-tAQ5x%iAuG{I(IN(Ite+!!!{n>}pdw]90/UTMKܻw]XR[{W; aV6VYeB$i"F2 `>Z W ryM(怂D"APAr%nl)6gzpzzj+>koo+-7wtZJӛnv$}]qԆ>1hgzĩ7$mGD|728J>#0VB P)隘ꇏ"Ok>Gp'$:xbpԨ$y5WC.^0I)I{Y̭>-X7OLN2ISb,E*,yέ͡ޠ=ys:^߀@^*I*ѱ*FBF/5gNta{UT4sޗ1」vԣy1ƆE**L"Y岚Q>.uqP&V6e-xdqu))%Ib޶$io?}=}G@xf/.+Y;'/W[:ZE9ݗϛ\4RI趁AI"=]ׅ̏Yˬ>~3kI=⥋}["u!؀` _LLoI]WBMtPI-+LpMp|j}YّQOb};N/ j{СC^9SSdq\}I9Noe Ip'I17B%hȨHa\W1fqk֣yi_0-95Y~7޽O{Ye9nЫa&YuD *(&ҕU-FMaUn޺gP[3465伷yݝ侉e.o2Uj?듳Sm]>E/hܤ[蓾iQo2OB ޼uJ&59h!S ;?7&ȥ.H2R3K+ ޿~Ջ_ҷrJK?7|t}uowwo {Rtr/tiPA[톐.-[[,Nw-8#97gߩ $iNᥒ3I:m$M9{36Y7r^lBeu}<Ⲓ?7w[ͯoo\(2I56 @Ԗen*#uE<|Rz m-ǎۍorY{*<WU Sn닫KSs-_+7{dJHw645lڴǩ}ݤF'TEۻQqݏxo9Noe Ip / dEq_q-ɩZ'zOex0m]푏$k|zBW|>X| I'RWpbb-{Mq-|.\S.?07wcnD}V{"5z@Y o9Noe Ip o~1̖qGG󺅕٩fj}֎3+үnd2cysx8???i]08:4ctr>O{.n:cS"c2RWRJJ\//LM :#ԩS-)8 gΞFFfӬ,y#ݞ^nf{ =ys:[߀@^$I/GL> G9uT3g-84- j Ip HzX8AԷ$iNI$(.-GV^y 8$iNI.GG?y[Xݡ ̨`Է]$iNI`Ȩ涖ocs EXrQVmoIS@&95Y ko}nBAԷ=$iNI`wn늃s_ܼuEP8$imWfFGz46df_ EԷ $iNI@`$iNI@`$iNI@`$iNI@`$iNI@`$iNI@`$iNI@`$iNI]Ƨ' <;/`I]?9x'χ?s WI]ψx4$i`w&MqYrEF@xE{@vJ$}.W%i___???k),.;ط@vaWÄWm;I:qTyeyWo՟k| z5n<3g'o$ "bb$mx$1_&|}qg[ge/]v~y瀀ݵֽCO `?IEx-$(ކJ}&7߽wW^Z{\U$[y>tP/U[__~4[믿 =_ o{%i"E|I}dW^Emnkil;I-_=x;7\ABZnh5[2tK{mZK{Krjr@@{I<8WK+^rY曷nZJ ➅'1O[?O In䯿pӧQWՒ4]F߸n)Iwtw{JK[?O I~wT.-q T_^6G7_'g=qO[vnLwn[&UVYN4?cVeoC ˋk+Ccϒσj>LLO.|_z5OJKL06>V\iϵЂ+:6&mދ/O:)[_zAy_޹{+{޾{K7Jt 4>3MQi1]C)S|/.<) JU5V諽9UKIҼ';7۝U;%Iild} #U_Z G⥋")$8Z[+ m']F;Yg$ jBhg^8yū\YV76I-4,%)%IOL<} πĄ¢Bm++'KWݎ|%x![.__y᯿[tl4}I63#F\~RUkqWn޾{/KK~k3?f}}}ՓaĨglr.筨xbfrci~quy˒tݗ:qn:SsBW]>yII3 ..+4AD4<"YZ>{jn'1/^/Mٳz&'o\7'<)W[1._wPg\{<)PX^6hRIlffQo$}m>L%I~TDwvӸ[[_RF+KKf'uY֍H$nXg]<򷡁ЋnjmVԽ}hU@QdQwoD{K{&5foJWFVe#=W<|r!֝/^o掌T}ԩS/^^1WT@o߽HyRPXޅOJ#*Eo2`?॒CߑQ͗._80:9WÃF]ftb̖$]UA?=6~ǺOBQ\|~-s,{ImI߆%iB\y.zY8{n޺9>5o ރWIҶT\pFffI{BxzEs5"Kѱ1FǙ?\N^̳07 >r^{ϓ-/믢M2B-}r!$9u֝۔\OxH{‹pI< ̏޾{G|h].\pJe>mOThT}~ZX^xe17?JUm,Ȥ["?}R{Ж${߫#nDX,Vsc;,ITF4=$o昸X{j>ՈԾ67BHXX… ~D~s!c s24NS9<^DJC yKH|>ѐm5n1I#iT1_e~WT:/'&c .\$])IcSʇ-MC#%iyK *vn~ԴTg%iԖ$7/nN9 "yj읶AHS SVͰF:t y9ߺvAo9/ZqP)戇F܌H>sZq$r*ǃϹ-<>uTP)>Έ>t)/#Ɩ&pOZ:ZͷM+I{Nܟ/S;OjK Ou M Ix26w,{S\sB5Z(Lmtce_D§䚬rGSl;|ԴښFdGM *IWWf?|V>\P")%7LM3tq<8 Z36rmˎ}CrYD5d9K%骚O4>)/-8p>9vewKk\[,|7aW{'O qIڅ'%Iw&|b;.m\$iZ IgΞJeR+a!&&,@<&_ʩ{߸+%~ʹid}IѣHs~¥(d}Ҝߑ3V^G?Q>#6$D9K%% ˋʟ*/_޸yCeD7o74{YOm=-I[l4g1+1IH<ĝ{wRĕ+;whox"MqoÃ=7-%)%cǎ9~z㨤7Y""߆D[>19Iƨ9;7GAjDBN/pǑO*Í[773Ut:Jқ:5?MN X|I U"ycWgf?yNR[tgO9qOPPIjk$ C' Q+7zt0_uYӭpop^UG s'h*vuG>_&s`^qawJ8^*IWQ|Hp|ˠəIە65ҕڒۻ;B5lOM{N2 IH<ī׿E ?d2X\x0}%Ub+}E .5a׮݇}e}owfVY-\v$ܽw>uFl].@ gh RIƖ&寃#49(_r%[ߵ+Igfm iIZI$=57mUk{f$3gϚIH<_Ut9};eG,_l|瑏#]HeG>ҕ.lzCt[[BGWB`_$[o|y}ڂF6Ǯ$&Sܯu$m\[nZKGVFȓSs`k$ 9rD(/^09yW8p yꁃf7o[mp5J;v;mǴ%8vIïs*wUoJOto{/?}ҽbX}f"1[ߵ+I,W9II?&nX{U;46oҽg x$5H@ ™upL\ɝ '1Ѯ\l|ܖ K7c%#'w<ʬ(pGґQ|{ rs_eRJr-~cc'&-~K%Oku+^GK??aZEUۖ3o߽$-?=ϒ\z}mnܲսKҗ\6I 4x-x!Ju 7툳r]#Vx9H}žV<ÆBtefq-M|'z K~,nxO"IT3m`dAI⥋}[,O9m+4DR(#CF"ޒA4O8VcǏ2Mȸ!B\̬7,=&GFE_e,qVq }5I#Dw,^\Y[?pСo%$&|Ҥߎ;6n7K:uyaQ94؇x$]ɝ|R ]MI`߫*]{quɵ`v%W[k˒O4NJCN^nqiISkBV%kWM~4x-8΁ m^󔇑ï_tVϒJ:#+3Bȱ.^8Izf֕GL\,aaQarj2[)o#G=ffenʯ~N)tۢ3gϸ\/_me}BbBÈǑqO_zZYԓ>*?EL+^;`_⥒3I:m$ci^xZfeU݋sM?tA!I'UhL- _oeunRS[5S{H@8WïqiuG@IM~E/%3!sɼu疹 Q\1wK0 #F%ȓb8]_.{RI|[n)_//m]푏,*^S"mtQ!%iTc}Fϥ=۞>K~ï($iZ Ip*,*LH|/i ۸G_2f2ym_/~s>|w&K~fDѩ'xB$ {M7e '*Ib~siE|ϵ#}ݕU4ZF)I n _ |R|s|jbymequijv+3+ wݝ>:37І$ ^ $iY]VnW/ݿg*]~%#+319Z5ˀ~NUw OLt[yADǎ&e`x`lj7ojpD___oH66>N#99x`L\az] $i7I]<ˇ{UG+,*U|X\]u_k-)/٫ՠ_:Ǔ]{Uw{H- It$gZI:00pptRKݞOΟ' IxoݾUfd|dn;MV'g&[;ڒSS9x40ՋOkimh`bfr~y~Q怀7?}UZQVSS Oj>Њ.!1\9ԧl5c@Iz൒tWo7B4񛚛ֵ1O٠ՠS'Y7$ {__߰kaOb\|FF߸yLNޏx%ih@~kyAѱ1w=y#iܼuJgyM-MUVYnTn4ֽ(媚ɩ)SK%gNd:21JZWBk> I6VKGznC͇kxv?r.^$I $=wJ_3<>~ȇU5U{POwGxKҴUJLN$ v Us Bi|y&g~nY:<6( Rۻ;zѕ&t:h'M)K-\4b67R"pimYΖvȅoʕ.Ŧ& w6'rQO[(T%LYK{볤DW|C2s4']?{&$^N>][_jo~;#ǤgIc\QI#;21ڕZ:Z% V)kxlޅlmdaV*V-9>BO$^.|y+cN]^ޚ:}zǞ$|޼}I}}CJ^\Y@kjU}%ޢGG~PISMK{Se}:>T~ I" It$gBI+?WE8jV /Ьrlj[jZ7$ I-}Z)^gI0"aSKyݤ\ZJ\@ {Ξ.U([ [UK.&tl|Ih$ 'זf~.->_XY}$6'bR i^ v\f> .*Rn0/*+ǧ&DAttw9rDU|~p2sh *ez=՟j^qFPPy"\.*-m*w.zq*߭wuid|T,N#}kEUxtGH @3x$,K=6j,4 cÐ󾤈k3 oݾu#h\YYR^j9ڒشDQi>7ԧL~or>}R&OM'7=7/;;r"G96Ok5ѝj뒒4e-8(sUB㤦.e L265NoȇO-,F߆tjik̘f~v-$>-.^ RVQƵ9˙IϷmC9y97nu gc낽5*/q-QY YϖOm.IOL4sTUFmm;%Icz{nS+:}洪?64&;RVT6wNwSOv^^̸$iH] IPe7W gq.B$ W.g=G64M#-9+IssW*t3pA놅S.,Lj#7ou[]?/T].@IJS|yԴw nikBw*mBKMH|fV.U]3sgΞ. *BpqAsC74aai:1=*-'34 &f&y{5H>t8AGQAbj'\e|24|>.ƄS^ ݱ3IZ4@H{/.^$i.A^NgoזvCԱO0iqi4NQX8ߙ'݁a?Ko)0Al"H;pXX7VwNuJs v}I^&ZЉB/[wRkZ38i-~D!!y -ɩ)#˗vޏx 4_2wm~lEhp()/p i^(WY?hK&[y$->:6ڲZX^d({N9-,j 11dO^Veal/D~vb&z8߽lǎgp^z!2*swY\Y6:?؋'\]2:СCe%*\I=7"I歛*zƦie87?G\ZDKI`t:h6070}f`dfD>|0M[)4rQY#Ͷ mE'hA?4:<;?GI4ْ颼QLVn|(o߹MsxZ}D9;/OiL2_??ܷy=S ]U_z&k4۪ߣȖ()r˜k<.#JօY]-w$iω36?USOՀr)Qܨ=SR~6ô:SUz<:.HknmEirm}|:FCJE5S8NflF?%'n߲|v:u[gywYEL|3? ӷ]ӄNU r&!B޻$ɳĴW/2PK_&Z88Ê$R%_zE{qմw*=.saD:S>qe:a~jk&<7܏x`+1ۗiU{%ew$i[ M!JH _ymW鉒4u[r &jB^bC&VPohTxhFVںfq$f4QV~}h*FѿMir3m$退ygQΘoKkqI惛/\_{'1z醦ʇ4+enq>RIc&i"krsSk3+ [73dD-7~yiq[>@-#Re"Esy7Ix񪚪SǿCcwݵ%I+"k!E_Z_.gZ`!- Ft&Kk_F67Wc*>l,ZJ'OPOhVQO99Nvm΂UK{W!<E5\ Rtvgq>+GMw@NMKߥpei*a´W];Q!qcS&uIۂYӎ꛹Mmn#oF+>Tk u>V+6 U*Bv/ee([=OR0 J!Mkѕ6\~OOd.ƥL7fEEkKKeR|Nm丹g 1ԴYM*%#9Ԅp,)쵒45[oݔ)+otqYj+dCy~ʢE%E&}VCL~WQA_x Qhw;HG+QOv- 7؜nR3pK 4WNuKA?-#y ~#Ilw5}c3mGbNn+8p&T j4{[XPR^ڬҩ{* S/)/ȯPѶ-ȓz(o}%I쟺ekKI)ItVNE)܋hbٺ%8G=w$MF/-Lki}Yƺ豣܄\|xET8릵Wo_Э$ܺC9E;l?4,T YT=IIS}M#ʘW4K(*R4(Ddf7 /&0w(9?77{^g~#>tGO$$-]KM#4wv$mGxq<0='Id4 F4ptuX fI\̰^=N;p_Ҧ&BW.koҪ7㚦GyWnj`LlkV:7)}󥱡厞N꒶2"}2F&F[[|{xG\X6U&4w?}kP g*hʨz@Hsk3哦<)/Rf{ŕ3;ܷy4-,~OQA05~եFet+ ӋF#J]\S'jeXgo //UechyC ;ze$iiZ*X!n{Pi~ou@ѪaTTK)5'*#߮n4~ ߲n$/uIDCU O?7139])#?1q(3m]e{$$QWPY]I+&~coķ^l͛]6vF宁d ۊ1l¤1}u꡽VʌN<+NMֳ\ ?LN|+[h|ʥ1Wb,ljJ܄S~Ŝ ȍȐNSiX5[ JJ<۵-P#4PO+.:6FT&S^i'nDn,ndnllv5s,pJfqײ2%iaNWOt\4Fj2Nfmz|CZʌFGYnpUW\2V+?W%u(,.\MNM\&گL΃3+Jxڲ#wDΰ⃍PܶZ]ۻ:D|B,or*hآVoK&f.9pƮOQo䑃 DcD.م9х5ubbsxlX՝.Gi I,x/jW|z5hN&/IyJ`Uk#Yq~QVBq<j(%i>2uC J"X$hkyHsC)ƛr}}Rg؇gV{ ?ML"Z57nwCcf1<-}b&FYfKw_w4^s45s.-m9=!Ig0pnYa$-v1RpO,T!$1l>GRi.H;՟bJSuF/hnF-Dx4gyͭTGz{0lMٹ9iҔ^u' YdTU: p=J{󉯭A&^ 'ϒU [J4;;{5Ejܷ/-#; I9r )~FzR vU^tq'%i;LL[x c #2/X9]WMWB dnSy0͕Y 0-!Lc9lx($4*ʧeq]?fr'$iu~ꖊd#&h. 6Y7l$2M<lV NI+?WU1?NK}=,_izP1סڤ}PǥqxJBerH F/‹^ȓG^^_n=0TFbT^ׅ:7yXb:.3%ђhCWݳRM=ؠ;K>:㵷ITbAo<zu_>74utM"K/,r2kw\y$ݾs{÷^7oпPBnoR_*YBD*RZ;Z}6bA,gq,G歛P$)%YhiRݸk- .LL&@Dŵ%P Mw3r3GA'Nn%;l ,LFZ鹘3yzί$3m˭0G>rNr:};\ciKJҪ4:Pʢ*ɯ<6ikڭjdd?kH.i/™J0U6迒!ש1igO]ubDw.DȧOvGR/ȷ4Cٛ4w=&CKsI+IS9qĕͣ?˶Eģ-s)pwI>7ّᆦtS.,~/mЙÈN{eK'o2_p$K.Yug>y@'Uv|G202'[*|KF-_Hfj5NԺ/I󥗉 8sU^eks"Igde zb8+V>y꤉3 D>6鉎C$Mʮ 98u_QO$e *ꝟ"R/$WUv4߶kʲRsBh67e&u;6~ZZYXVE]<ܩr/)v鞖.0Go2oƔF\ O +>j /PQ5Nu_SlioW~A~m} C赃C&>s'M0fP]dLuK* 4.('G8ݢQc{L9sC=[u?-wpmYIᠣS${pX$]!ۍG*w.,.z_Swr.WϚ<ɼyHdd|TC$ͧP)}|>m˼6(D Z2KVb IkG Gi4Mt厲& ̗>ɨEĖ\:uQIJej[I:+'V+r[3Lv!/h:#IZ9\VS֣cK.:s%i___䩩yphTJ2tn3,)Q4u1>, ,wJ[;{SvQx]7ŽJy;Ͼ%\\'UZ#ϗdv@Ruɓ[ڮ~`x`xlxbzR84vX.UQ*$LfG[..ISwG}?4if* @QAI;o|6|'yUAB$mg}Ƕ/_룙xcys-yF!\Pʐ"yJ: p*h! mrfqI}A%\طeQf*jJw;.u25>} ]]+eMU}o6p!Md>.*~BT+ig?J[]ᣯj`Kʏ$M윍2\nS9޷t?"IӀ}/&7/ҚZyT7L(ZwJEc"5IZz{λ8Q#zGb7 BbGJՅz)Pܳ-+/h$^ͨ/KRfM^Ƨ&%iˣ*4qO=.IXҒ~aΜ=꯽l{v]IƛYFjeɫm Il([;&Is|n#`.K O_4Z3[ؒ}u`: %iZ{#@F8$>5A%d*d1{{SZMUyCSMhȯTSY]UVYx4P h\mBcKUN:7i֪Kk4nPN.sYI:!ݮ!:6沈-Q$-3uVtB!WpHҶfqZ˂ξyCKoV;qy$˾~at{OO4!Il߁nq@J!"J)@!)DyqEue+Dε>,cF}wcN Iח2s W/ A@@UF6?4:K.M)Ц :jЪjle{*Ϸ8W 8rҷs̊-<v-LoĎLpsamؕ}~eݚLį="IP2_fZ}N^.,/)IXޝ,=*yќOW ]t{WI;(Im߸1\~5^C:7fI:'?/WhiOIWJ܎LFYGtI 2jD+/^oIm Cȼ{iuO+m2(!IDv/'\wVI: ^~*xم<<*IHJIvhlBCWw$kC zGJ3)zEINݲ^Wpz;4*7=3]{zo\U殓iUBɉlnF-p$9W=.|bon^0cG͏p7*wj<[ٝޣ\4W,-:#m1dV{:"%Z4193%G E>el _aǸ/ur%3_^G3e\YJ];9\_QL0;5Vo6:*IS m T\ ly&㈷I҄8D +0ɟQnP@l%= o';R#JFVN6[ qS ٤\EH׳D>H'NS^~7aLFh'Vse7ּ;ŏr{Ɩ$z5lqM;s,7;?g˷Jg.|>NsIeskz|R^+IE$龁~KS)Q ;1XR+q$wEʌHL^ͩӧ%$ U'1OJ%Ek9?4CmX5hqyKZo?|>伥_oK2-z$iuOFuTyvQp[ڷK O }富LX7 BW,yC$TZNݎ#9dznFh}Ȝ^UK%IҔ[a¯41"Eم] /P+HcԳ?;q֗-祑d@ZxKxc׎–7nK溄`jxq4.::3%p~tcIۼS{$Q1fZZhw'#I'_5x%36. IkܛwNɵ=j ]~<7Q# zS2U eR:'u6oᵒ–WdB{H ^:4n2(-D$א2jѕ+bNYagqDNLNThlYA:*ΞƖ|IZ5+U|-@rmnX`뻻Knjia9dҤG=L֦&c:}~nuyO]/$umKT"Tw"ɶ}vV͍!*'~7<5W]2IY'EEJxq!6YJ/OmB3$ij" ߼usgJūh ebID+\].b͖$CsHK|QO MehZ+rkrI|we>IoY qq}#_k2J*v߇%܁ СCz'%ih5\4yqWE5jvsM_:.u2B3;NLeJ;Q_xe[ԓ(RyR`V2:߂­✕,)Q< _ZMkka#7hWx ps八^yv;4ܝZ\]rs#˗m\Lz/Cn]Iږ^6{-7n.lw:RP%-U <=HYܪQQ3wAOEC$5#Gޙ&C2bƛL>xӿ6=v ٓ>KpviN}r;& K܉ @ŝ9{@p*,v$ܕ&(hnsk]j5Iȡ:1sP:qK8Wɏkoge<޸̽o o'/ISUjOJ^x+ǎ/~uh58x _ܹBּ"ȇI)"d{nRF|𠾊C ʮts_s陚v TCcÒGn}}Ӗ$Mg;J˼֎6dd,DA:pxlX>+tFXnZ*)`x'*eϨٕ%%ࠝ^$Iު^U;ꦕP$ZFa7_+/)IߘV@ϕI[:Hq%退~V {Iې\Fw vԥ=V}Xe#_oR,$3:7'NIw>m2>hMK*&/Io, 9?^ڲ"qc,K=z]MKh*)/5@iZ)N@˨E#\*Ʀ =C3ݶp_kń$mkeH gj&:I:>!G0:K=aWL+Zqhu2>NKJV͓Ѱ%ugVGem|m쒕;Żtaa h ab!ЭњTskZBR?/:"׶H= oN]g&T[tl ?z$F fbbt8|qײ!־<`K;(I%3STbȧϵ4_[lv}ru/G廃-SA)ۼ!Ӹ0:9fKSez4w &;ʠE/F;%Qmm|G/ӾNy@7xi[Y]5lz_R$zƲG(}7˫*R3%m< MR]*$M%LYKשgɿ7BՆv[W;MgXЭl!s"@B/~6⥋tصnJҏpD9TKEZ˴>dVI;`HDO*> 4lG(A򒴲q-\e6C"!'/GfV#orT3-hꅚTwm>|KBՒ7`9:MSlhZt:}\Q~^{Òj9םcHH>۽R3C kBUɴ4ZiZX^rIDUMo\s ys܅?kBn>swvi,uieS4=jGȈ*6$߯'O<>h\~K!RުE"7@lI)ϷVbޣd3~\lju+!Mc,L>󚺴M35f[3ʵYR`skKRJ"Oޠ0u2H|V`4l݋a{":MhhME+AQ![%hn~ln*Jo\]|CНrpI* ܨOvA6T;ʔI8raFPltR}6 2s![^{2|ENZs,)Q&Gd,4r?$VVWʬ4{ﳙ_FsAqҍ.scmI{Glx;<'vA$uCôIYe$aEyœj;fru_>˯lU_WF+ZS)}pqu)e@MfSsӔOP ӑjQص0I0-GFə)ngtrJJTlQHk*%.o sv˻/<&|XY^U񹡞zxrJB2JPe$GCzpG+i,<]i\VF<8`7Q|nϜ.)/垿t/#$iCeŧ!۝U>\edSK <ȶyW2'< FUKKI:z/'쪕tZ OѼ$+Rj>hDhnmQ΁噌$MK__ tD2cbu u M{ֽhx-lEYYkGRJ }4Vb#KH.HS|=SFګEs\`J>d-/Ez sKP\긌phye'302x0`xç O)"ZL6Xf$}޹{G~hW绅gϫڒʪׇn,~h0 U.Tu\kae1~pD/'3ќ~p8:|.8êqP&YMP^ԿWa#J!sLdw 7Gt_tt}/H o\Pad|T¢ GY[W{4C);`s6*~N>w:;M ʹVH:%v@R@vDVf&ܩtB=;jk*>~`[ߋW/$˃DSKQuMh% Byn:-hia㢾nNV:PV~KcÆ=6MeϷ^&}UU5U&ָaZ±l.t˒t`` 졕 si0IQtoSYd"N-kzF%h\`*0^ g6#㣺w$-Z 5qǺOnzvSYʉIE 0Bitzsfѝ G;5&⮽y846:YJ;I2NFͦLlru{sIʙ'ߗɟqС_)v{/CꪖƖr7 †m] ܏ˈ)#+S eV_ZALsA28|pVN[;ZiQ^Ua[%1q*46P 9ieÈܷy%%۝NV~ߣ:2`w׵T{[;;[Zu<f~uTK^4?}|_};F'-:"u}/AkzfzYe9=8O[gub  O(CR]SS2˗Qڻ:e%v+,| hj$а}炃iUZQtSE8yx79YeeIW.ǎoRJrUeI8:$/uL@$ݕh93ͯqJhI I[ZIPu_pGvn±\+tŇ?~3UC< $iXIx-wG\fBjpO8Wq͈'$ __م9do$iXIx-՟aߝ>#Zz-3{HQR^"eMGx`$i40M\Rܹ{Gk%:{:H'.-|.@RY][ȅ[Y[0ӄ?ēffA٫@@KfVꖏ x `$i\ ( 6\ppUMW(v…Ŗq\E]3?fHzI67df׍LI{WH4f>7jз9y9I)ɑ^/)&ÊrvJ!~pth5K}vǺO'O޺}p^/ U\vldཀiquI١\6𽵣(B IH`tDN^N/}#3S#Cߚ[ܺ}k>%%T|(*)~v- U>x?`$iNI@`$iNI@`$iNI@`$iNI@`$iNI@`$iNI@`$iNI@`$iehlxqu4v[ЀwՒ۷j>Ռ-|_{uaeqrf-95/oY E(؟@Z&g_DC{ܧϵ27<21<;?w  0gbzBTEo#"/0ѣky}%19 ƣ@Kk 1[/$ibWKǃ%:ΙgP`p]O__SOu- 4,qcryX[{U\_U7Zv$ cW/ۡ|rj %.4˯4fwݥzI3|:I:z8ף/)*,*oWIՋ+#xHҒpI[H_gn[\[RfWK˳s-ѱ1)~lji^u_>WU͋s͇Xƛ ݔ-_L+w-ْRRKʢR?hn68:[V*W)] RR]{|ܽcөغLJqcee2pƖF/Ҍ=Mxf;䅟l+mxq~ph9_F2aZSEJ+y]}i^H_*|C略U{W豣 ) JZJ:#](51RVJH|v-AE8])XTÒo͋zcka?||uDhJW4VA%$b D"G?zMhŇ ++z̯^#w/%>!nʯwޱ8mhuwtw9rD26#:$*Ip I"IDdaΟzeΏyZKk#o%LT8LYuI\Kvlj\&1q|{--Q$*n[.*ԫOk]{|^xeLnGRC_Ejgoֹ͇ذeS5bm\|_! ͺj-NםV9+*jbrLRY^Tmmܶc'aZM73,9;wD<ѕ-Srr 4aFETgle.U. @7L[E湖Jqֱt`Y[R[_gT#8ZݑTdJ 7v&uRq<'K jv-lquI4m} ixՔAK9h*= 7=$*I]!IfznFr)KzsKIzK1>bIҾH>%-u)u=*I\5W+RӞAF|'C%bZJҴ& XK%I 12 |VVkY&hK\ Y%xm5Rw$]X~ʢ|; Bzb5%2ڦJwV\Fdh0Q}6ib+ #QTڒփtϷ^o Br\KD8k[ذfmIgORaJGD5nTi b7]TŇJG[K*qbֿWd3{{֎_.ocT=uYx]e9K{+6@/v wwH>tXR7b|:~Ҟ$*IA$ ~_~DkMoC--hS`pIV/g袮>e0(~>U&)%d$m+o&K0$M@+%%ijV[4Aj'g:Uמop55M4偒4*K˺w懣)9!ϪvcLXUxUn)KTuΞ.ЦUZ[ OJޣ%<*@LTqyB>y?[Hl.˗x JEܾsw}"izf:oFYwwH泏ىWq#ӻKX*˲ީr/4AinaOuԕT 2eeF4MȪzLƯ|1+~i#GCFUڬƧ&DLRVު(]uo{A c 16 `~׋$7Y~9y|@ {xyBS%e k+6IIڝrM[!qTm}-(Mz8j'Ħ{)w$b5IЦh9d)IŪZR^2YRE.$킕$]Y]TC۟&NͧNENv{NbѥsDNLi(c/iIz{B4]eV֟5 [27$M2JҊ2B7i-eUhj.& M]W4$>T,DzrZTJ3)STז.)Ur!\=<`d-IGZ~wY$ <$Ie'krmRSjq&92tԓ(nKoH ͥ׍KR4nc<452Se_-IZJ.*kԄˢ¤iڿo)qREI.pG-"ހZ4hw-o{Nv_y׸jȘw-F]v{vBo{IP^QE382Dz5W]53ҍZ+͕rO\QnFMIYũ8Xē\3%{玭Ŵ}x|4?Oi5ލ!elwH43ŕE}[Z{"!@XQ[_HufM&JƛL Nf%1cquY{tN8y릮_щ1,CÖdxB6i: lbZxyBL*ܴVF($H>syWT-nt/j߁w $>_񨸬D[F^4q.8xrf =RCV~_qI65%I [ʽR;rϽ%rIKWBXym8r)~~ȝK1sAUWAosYE-ty9$ШKI䅇40PSqnx]BN T!WI(4U|-Kׁ.6Fk/|q.\ <)"o5w׸3^3q-)idlj=At`99ZƖ=%IG=y,۷!79͏,ٞ( Y٩iiJM=J6w'rs[Kn~. 9u|Qj08qDpacLl?=3M$QDEUŧϵ_vtw4ԕ7fhD9|ZW1$k׸-JKSS%%ý)9ٖqV^k|fBFSj |<~_#U>觙 !&&Sj*v?d9 i~?<6"Ris3G5$ޮ8ߪ=pb7TZ^gm[ߴȼ M3 ~ەНk%ifim3$kKcD܉3>53^]I]y$=7?WR ɩ)bV8/O5e₹h)Is3p.x=ו\C.P[ɅjMLROTxSШ]8hO6hUh]\6ዀ.M0)%Y rCxqUͺ@+WD=2!p aO=;?'sxẔ杤@dx܄D Z|B*jϩ|eL6Љ )o^N]k? zB FRu IoN9^2Vl49Nq$A "IskRk묋+((Hu `x]nl$ ;}-Ef*.aAp)PLy] 7??ܴ}IWUݖͺ%}ytWo7M5Veq l#r ]IrGzM4rw NVM-;+IrbtWH|wim)}$iqX9Qky^-/zFVqmiж6=y"/IS%,4oU5dO1L7 FnnhϪyVN I@yF]$];3xRoܼ].豣bLuFm.1O%׷\7O.rlپbd[)c-ɆEϭS&"'/G<_]®o\|VʤY\VBr) MBć%z`'&FĀԭ*Yُ1rpDcP? &tqdXsk}+f,l듷˴ձC8Gv{$͇$ˎW[h5<{*pgދˇ O1^2~u "\ME7%b7i>d׼eM!I+t!VuT 1}Y;^nk+)ŵC1OSw❒k6AIw&jȮ93#g.IT[Z$sOuҭXꅼ$DӪtk.IsTtoy\|U9C.p Y X$H훈tU/鹙-0Yfe~/))ӥ5#-W2פ&-l5\W~䩓Κ_5ϯ bEI j]EȻ?!o:6)>sTK r>%}[F2u/#thBx[;ZUǎWFnD 6uy&m!%nX%f;=CW_#iy1$iOBIχrd a$sԳ$zb+q01H·*Rv4R7:P$]R^*nKܨ3&X>G~RJi%iMHVSax$ͭ?Tk.48$ͧm'MJISptwFӧE>{8l>V,seNƅ.7M >B>sXpS#ֱ,ؖΔ ba3ͣ8\$x/U_qm;NމwJULIחKF62/I?T\$}e.Q܂<'I9ϣpBhFJyIZ8jie.Is̮K~Pk.$Lh3@PYjW~eu9م9^EW~9ly앒trC&engUN𸰵jWILOƙ/;޽$6]3 :qB@7'%eT7n RRJ1IGY k4-+ *[2A} $Pn] wš9'1۔aI1q&#y Uj{4#WϷ^9I?g+HLNtr?B͜9}\qrQqSPzbf^tЧ!I{SO[$i\vJ-Y9ԪvFێ]ޕe.z'\9ݑ7mN'g&em[,ْ102swt+I{~l4mo2Mk;x')I:SvA~#iu%A핟k;ch my屍ҽ*I>|xjnZ%Bfz9IZf| kgkR^ yDͥ哧Ne.IsSʎNצp.MJǃ/0 ϪEݬļYJNyq`8%DN+x$MpQq~hxMt_P |F|x=!;{u|1iD0*[wNGսA982AiZ7~~~n]'3\ {9R֞YWE)Ajʇ/_uMyB#,uẄ^'|%NGrѲ !4P!S$GPɚ,ij>l $P}Y e{ͽZ &<%-sM&Z[ WIZcI&i@xT9;QօE|$69&f&KKm+|;ObI줌_.4 ۩R:N@Ók㏢";{:|,7 =( i/aG?{]»`Ukj05t%in*ӊU?yyLlyRbJ2f}=~UM'gfW7jQL)I___zSPT{z#I6%=:iEC$-wbI"q$r㛙<]Iz)Hѿ&ҴTJ{&obN~e\HoSk/Qʘ}oH|hB,W~ g7ӧEL֢cuavWq GF U8$J͘orEKGuFH9Qi\o'yɌ=.VqZ^3}WjsJm Ã;SnJ<NB'u=QnV:teIz) _sL5IZ$[w$ͣ8^GH*Izy}{/avWŲ\kT-ImI>7nzZ\2+R U{/*ոqbSMCsSٜ\/ {w!N"jXK5G+Cj](J(*ΞO5&{-4'G=2$&'jFy]:H :)߼|n;Ay`H;Osku0rnXT@ DLl3:$(w?>5A^ZPijNO@_.SMeuUYey@gOCWcom/m_$CKzl;&\9.IsUpd|TBSzf=Qn< EPP˒S$i ͎:tMRtGOǖ^^e4:s3`-/ZOaI_bët7w@.*)G/^pTcw$铧Ny>PtEn gE KҒڒɜgծݔ)%-uˡ:- ]fI Q8b@ 7njnZ#LgdܸyCq­]CJuC o|zUZ_b au hzϡE/y1602hBz-Gy:̔I@YF^bRDEﺉQVeΪ.*8$gwKť%|#$$ڻ;X$ԲrMc#׍Q _B0GFE.}IQBn~:G%i[D3'59r,2ȞTG㥲B$rqN:$x$͵$gO&I 2AKF{wP{廏& ݔ% I3qOQ5y$A"Qk$5W T+I9| ]^hìvU]œ7WGsK QeEF|UyQV% $,IiV")bvK',:ݑLq"L=Pח-&H?A:x'/8Jxw30 !U^8ftC5NFNB21:BMoˀ?ʨ:*_^\p2;:KFVxYY9Y0'"Is#e 965WKy,c#\"fIk˸.ܠQ#[@h3g{tݑy9&&IzܜeKfm'p"|/j%iO?sIRSw❒45Io0 OiM~Eq=H.HSs*Ǡ}v 5v@^glM)ݔ‡h\t#Q-s"{K򫹵*T[Jf5[Uyxя+p[s+WZ;ZOCݒ4H**lHwJH|k9Y>D*XԨWҳGӅoa*;CBE!c>%(Eꦮ,׭\|2r;&O(IƏ'1OjX))/&Ni^#3kCV* *hRmfU҅ mN#sI U"U5$$iOW$xoO<VȨ>kC ;;YZ_&2WhܡJDq&IK;NމwJ|; $iZK0ƩUv ?I!I I/,jow\uzeg$7Gœvo…&{@S'ymתi YUj HP,$ݘcva@&)ѱѶRpY$AN-J st_e\峐`9fTd^oU|j}-_SuNma{ZĊsP}jgLcl]K:U`>ZKylKbǃ+ڂT$yPlM_>B--)Dv)[*D zAJX\`$iOpqf[$io~LzK K{$ds|CGY\wb#<+go7\φWn= @L 'f&=]n"$i[Iͧ*Ic3wUnZ{$%i.5HW/IU ]e4%%iqOtokji⚤t3Ӳ=ܱ4tq}1ҕ?*s5f$ͳJ ByQ8!fd)I_xuH>TݎeIZL[H_~عI|.DQ T_CyB،QKa{[ZK{k1ޠ7fxߗ Z&x${l?Xw++G߫ahjT\u/9jIs =mu{dG@.B٤Bt 269f&!I{`z!H~ˎ&gfٳɟ^im# @5~@g).ʼnL_os#>[\bQϘs,If54JҎ]XJҞ~;%rE%EXIJC̼箐{F K||4.IOLÝ}ԶMcܗU:>ѕ}AS ]hTUYwMڏ[׷\8 J146l.Kǃs_ІkMqVSxQX HىOW "Fo#Vv-';+^ ۄ]V|_wnoyg2 C+M'¯Piy[*w-F"("tbrP(i:}SWr!>OLg =JF~.xXRuP[*:2f#=4c)H>tZ9rD͎`4S*Qj۵6)+l}oa4\tQ۰{*/Lq#2>sZ]4?uuFݮ͈;,b::B>F5.AHvn$w NSGd>ؕo޺=o IG>$CW*n1I;[}NIb A-H9/OHK0JKou{pO -d|v%N}Q& \|w +s-_VY *9iR $ |%hUDE Iw皼N9˖$-牉ZIrҶCkXa}k%iZͱuj]I{eɂmWHҾ$*IKx'|K4yҡvc IgRĊWi$I w/P)6Mqu=!IߥS'-/˵ըIl8hXz筤 j,ʎ]$44n z?d:Bli=7o)RgS4樚q ܝˆVKҼsiZ; (4Luva[1K7A®[bPׁWtnqo&"J/қFdO3+Tmt2QܰjDV[*צ$\R^»\/&'%%TiHVJ?$iwu: [Y].LB|iDABG B_![:Zi}wEolْǖW1iJJ3;w臘=u1?fBƛL 3'/7=3u=o5}%959m^Gw'Ͽ#,%-,Z-$igˍK1e$iSwⵒJ L=)I[j|1+$iiܷ G%iZxӜxqhl閗V{I#I&'Mkznَ'$iUVe&=WRVIE&(Ý=]Obl`;NrGe#]8G:Įd{~iLZX?Mx#Z<eU곛tDCy|50W/|ྷeOLQ5D蛷n$E=PBF&F#rŵ%4".̉He쳤DqG UACp. /ڊejaM P>QT5lL[1Kݚ<OHa„_epѷxO/iq3b$|g.I8rhtQoh9prI鋼WO!xY ce՟k*ɖ{%ۙ7Z<+ȕUBYtͦ2Qy$'I@ξ/WGsI~A>PM|U9li:PSa4EZKX9{ܷytG$;;IYXզI:)>'<n7W2 BC8 %RA`y AD{$?F-gU 1SI2)s?FS]n:`W͒vV < i(#hHҩiiho߹m8%4'cTV&luHz''%s0G.H [0_?1I*ohimHTu$74=іO<>2K>cӢȄwP\Lу%d?$ Ռ䣆i`cÉlcޘxví(77Z~-1>7fx*Ò4wO9qGSpKw ?4?ilr\L豣bE{Q+6*xtUHv% ŇŦr3YqIo_j$!m:ؕf#+Ջ'|~YQZX(aI*ͨqC49e\%I_|lji6R.TW)KSsӵu ϔ1ZU)AuхnJQO5ajY9Y7nޠhOm瓺/qtVԐWq[mmrҕ-IwM KI.f'9r^W5;?nZZZ_.3-,~$B7>A eFU 2~Oʫu_!%aNs?xlo}s7~KwaӨuC"HG2d%\J xZ'BKkF"6%6ڸIv//}~6:fꞓf2A/qGw-VT Dd1I|sm]+jSI5 j)I+BAV C7sϯȪR·zLUţrlI>#SӍ,m_F;#D?Kz͖ܮk{ӘV w%wjEM8r^luP!U8N,O[^J;@芿]zWw!٘ئڶFe27?GeleKcmͭ͢z WhRe:#{?k[x<眫W^ēq.j~n&f Z|{\ A/I+Ui{2fi6dI>$ZvyRލ/[i/?]Uv!)I -=I%]*:6Z_3F-E^JJVyVO2lv%iCR+,i'$>sLܗ}~nBhS* ]Ba___2'./d*9φ00./ξ$iO6x V$c?] r!$>!{ɰYfׄ]:uD[}^Dnܼ2oPXo NqОA{Y_NAKc-ZL4,-tih2;cYV{F4}Vss#>ןBIZx|jBPڻ:nݾ%"I!-ڵZOKfǾ$˥2E#cU{edbT {ڝ~V@HҊ E-( U EoerROseEr{ Imm3ob&Z|_{C3uV^OQT1%UJJIv*=dzyaݲ0duD՟n*[S}ū3SNW=1#rp3khtgBoy`douPu|ffs}Ԩ]Kp#U^@4,EEe%_:ۚ[[h6ȼHr>|~A~/KK\pMM{^񡢱kUMwcHCl|\YeƶZ\Zlqg(޻Y=ydfVfyeƆ j ͽ>Ok)ݝM-}%$I& /TnhP]$$c'{PsҖRޕ&ͭ4vӴҤ :6)d7U&4fbfYӾbxl{tq^a*LB_X"ѕ^sIFIvJ!KKQ&gg$}\ϺYs>737ٍ(XvTE_LA`hh#L^$ir5CpMfVpCkZs$Eh%^7H4A'NZf I{y^Kk^ja{IGos7%_G?|Ox a@ *ҴZclrdbtqmxqK$0g]iG#՟k/^Dal~.T=Om˒447s[i:5J %$dfN5`I/b[~M˯=! Ywb$^X{UO73]ྷe4,$ ;LaQᏥy]I+_@04j >: x:GG1<̤f4,$ ;ۼζѩiF>D>t3gXp~yaqm)y . ӵӋ/ (WN~4,$ '!Y``WïݻFxR,UF'޻2@3_(H4`#F IH4,$ ) IH4,$ ) IH4,$ ) IH4,$ ) I?P.rP s244MN=C$ ) I5mm\V^Y9Y`4.o_}X{؇@/ Ţw,IL`IS@jw@[\] ޾s{c>7K;S{؇@%I^tI̓W WVWU|,,/(6&.cxgȑ#N:r%,wsV[&U|64}^Y_+ޥe QOpVhE457?Ox4H8B@@+(Oci^tGkKn9$ J}9RZ^g+=Ë4dhios%95{1!I$ 4?2) %[[n2ZOԽNz⇢cM_B/ײ$^]Z[~:/]&I+\ 69;m@Գ$!1Aⓘ'{I!I$ <>ww L}ؐ2;s4ᩤ99r`x@XVnLVYsG?V.ubq6xcG]w$eqZCWt8v$Mܾs{9Ż(lQiy~RHҜSN>qġC^N͛>cMMzmy?kY& ߎdJ|X$gU5U^[o\CdćF-BgO'œ:`+UXc'zSo|F/aJD}C,t`dpv~n%ZtJmŘXC4ݔy3gϊ46|%6>k׉ɅEPQis_lli4Zj?}t~AƑQqU˖ٹ}<{lj^;>|U[_7:1F*Tyhr[ ;dzf//PI~[[7e唱KT,'/_}\^[~|TU}y}J;jBTVttѯח֖f46<|jʵvxZ5j>Ԩ&XAU I 62>*3Cߝz]sC}Ň g\NMH +J:3XSYG$s+??1NBBgDn&8l4&ͦ2/ /ЌEJ:}]],I=#G~TG1;ŝ}2rufPg~w򶸲H'}ΤN"rpdPո&ꕱ#΅>RVRӞKfͷ@áV7sHMġq/ +˽=x157҂cǦumzI5vxUAuȼ28p ؓ@Ppd!#QFe{ӌNv+4hji|lu픏#4"$s~JZ\O;\˗fk?OUJߩz(!w8_N.4#g|W&g&-UQPar!yP|DPRh[Ƨ&{n޺OG|5zE33?f,SE<$gNo |{WTd^n+ܗxnbdbklreiUR JN^Q}RENG/c'qIZF~;u ?W_2F I)""NNIN?YL+>Tcn{N^j2&.vrfڿɩ)GII D |XM/YS[;ʷ u-cq7jj?$]Z7שvhqUY]U\Z25;͛T G93Xjs0Uک'j/u, P6.tJ Q(#e38E7?b+NI>ܴ_4 ¶=>!^^ޠΜ=M?0q>Lu$M<'I M->^eڿ:gxlDe{^ ̑#GVeˆ/-G<J1-5/_a|<;ewCŢR$ M>F'J>lo@ym¯yJXQN4rlN x4Px ZUs?}ET6NHE9Ӻ]\]XY]E=yՄ՟kTwzRidk'@a޻;1=almsIWmjy%Ȥ3(Bvȅm4 RnKL϶tŇJZ.D̑LO=džw$,],I >'nPY]r(nYTZQy7"AӦB65B.^X jz!\ZdK{˛,*Hbby}%c˥ xvv-\q}ayQ:7?nqw1^I=,Ȥ[bWC7^EtD-ZlvˣVFFEbH;IŇ q^Q? v>-|'x~y;ǃQ2Ci1NLh> +%SO "--L$'L,)%ɇƒ|0Y$iq8S XxXan>x4PD37_loSIqf_=; :qqwodQr%5/U1,j毴qx$-\k_!sz "qj6<`d#l0a%kWU-(ۊ>,{F4:EUxCnEZ[֊ζSk?뽄gv$4i[gpSKS^77oSŘ̷HKl8zPlu~HlS"t $M}CewpJ.&\W>?]p0YIҋkKӞ^.GI뽡iax/] 0޿g[/**?//%B+g% D#8%IOMk? ձĶ6O{0fb`'٭4E{eto[\{u!tlIZ ^{]$I_ &| kv~;U S6L/&̽6 jj?DȵK^Jy2nio=|#,tl(R[gο_ҍ-MܲȎVm ~}.aC+!:A޽'I{ig$i9٣7i 3Z~=o"Iw"$Q.֩uGfkx|D;JDp ůXJ~ǃ V.U(:;=$M44 X7#8"Iww9aW#|Kas)IZ5kCS 472>r6O,<< agOj>F

UrDx_\Y>:ڻ:Lr+fw  pljR>3渋˪c‘Q;e%,aR[PQ&SnU^HMG. )m'=s[3о~SG&ǵP[g v$M\W~=U犀cO5vU_Z\\i=tKH,eELͨ'Q_>O[[R'qIP/I TTMLNEzD%mя=$I\ |io,_tӸ/a^-I^++=6IdT꯯3ҵ^}zqQ8XPa|ze&=(VIVk[/,%iO-7nPK[ xA TŢ=hn Ɖrefe?$(So۩Gk/~a{)"f"IMH\p* ܴy̥YPo[lIN~Уi{T["SOgɨ%A߈>Qr^v;#I'$&|h;݁O촪4ȇ`o$i8l1=7_$ٻrd?h|q8:qzowbCqY3W%iy}%)%I[bMV>y~ >nJFXz"aThz#f(!JQiZcv!clD:MM{l X=WLͧilqFL)̉_U~MgSyؕP߱CK JL_?hkxlD w]$%ܖx);-%iO9}P䦩/~<>Kе j׳r8$ꄸtjR J81EhXpos5!i( 6}g J|`BpqSnio|5(̂‚޼uSl,[xm󚜙kLjKԎVm+;u8PTۻȲ"Tˊ EA@&I」f$9ikL3,X߼US)6mZuDSZͣAmLzyX&ɼfΧ(aQ#cHZ5;52*JHWAz-%gʓXBsecUgJz=@MiDܽש^!;7GwuRGcSRoZǫ_ɱɰt JGDh8imkhʮ0F_Z>JFDҁӂVSns3=vJUD6}z..PaKL *J54:b%)~5[{>7#頠 G ,cll׮v]}kk1 _F+k޽wW˺lkQ,ﯳO!چ~^{MKEwǾ|5[~=(h2Sܽj׮_UM{WnQ~'IM7g/0jhj@9;]U~2ǎ\{SohxH$>65.J9Iy.bO}+,-229ynuBy(K!?}(/wkJJ~l-,otf$/PAs``;KfgMB[r𑙺4}sIxH$,I*P?TXIQh6KLMt DOElNs"OX=wֻB9ÑQ %`:PAVM䰚D~SsyD)*+f?~Si V "l/ -FҊsUը? /A_Y߈P:'>+kӹH5 5›卖ȠqJ?KߟuR ƵesB/]j$``V,0|.1P?Q"!]T=w)BGx)"iLol: ɳ'/5Ǧ*;_XsGLC=tYmCD/9V?so߽U.zO 026"FO?U~o,S9"\V~Ԯ<{~I-#"#Te q7%K+/[Kyே^F_IV:@nAj+M _JS"iU I*u1z9gCIYֺݺ}ˮj55@s"i?rG&v{u|jB#"Wɯ蝟Ã9yn޺ɕF~+3dݠjM3Zt3Ͻ5} :Ԩyg)%]XX3!|JʥHzfaV;#l:҉M[SCNO ]4HZ|ml8"ޖ+J˗#imJ/YoRf\gOٚi}ê*_ω[XYRT]xӴMKけv6FBWow`_Gwgs7u+_QnӚ};3:ci4 X}Vzy(,>IB{WGmCr~j('ĉn{ʽ{#"#ʫ*}{(2BYeזc)Ɨ|`` ?W]yb3glymӱ3k޹{G,!V|/?uNϝ?guܯ-͢'DaOz{`Dr)W,kHZZ=|9VDFEMNY%"+'=]VɳO?yl:ܴH]ͣfiɑԴTn:ZGw4\@PvΉ=rk602=FzZ}JZF5Nkl†שܻWCTلr |uSW/6U"R޶Zuu>3M QZ!48[X1.τ޻=_0-SvgOҺzMov*-rC$}q]D҉Oז% E,.DҀ769&ޢ.ٲf HEt/f#w {Ŏ.{ ׇk[*_^,3 gծPug-,/!V%Iq't⣔w"iBiAQ!x"iC{psI![{qrE fDZbYS_V7\}Dg[gu+WΏۜYCK+ĩHz>߇R2?r]$-}Uv#Ig|zls7=yQ$}9oR2d|5y)QS.[DҧN%HZjS}257-qXEҢ&.DҀY^_5M=W^%%(VdDϸ$RF~6:9+?=0=?.$d{yL_ẽuO$}q'G#N,v:>-SEbԖV.DҀQ97UէϜU+Vܸy": ;{:o޺ ~#EH:%x5}.)rr4"if=6)λ"饵e\> 30<8002XS_Kh"iW[^_qI8,$$DUNtL+HO5urvɡNK [q' VIݽwwe"~6DAwSu: ?VM3nnf{W_sI58_ (>{-<4HZM,ޕ+EҋmquOfdehÛn645nn w3VSnՙ<'|专m/JKŷ;{?HZ&W{wF<4HZHI}'A OӅEݸcgecuvqnqui}{CԴTwg$Z[BgH{ 86~m߇?zRߟ;aFW_4>5!ToCI+Ro/r>+ / ycݭζ3gYDTZ=w))+K~FgVGK$-]‹č=r32:D[XYp" VI;vKs:~ CŹ5咭m-+ԁ1Ց~IlIЀN[Gұbgl372*uim4up22ݍݿ۾*00o sI+~H:5#}cG+~C11y4Yߗ榉I t{Ŗ N=}CONFҗ\[)[ே$3I+~H}z8i .ZI+(ܱ48}*7!"i`4+p5"i VII4H\?8I+I+I+¬xϜ}DY~HzaeQgNMK&@4@1^X'&pH EAFo߹G DY 򸲺h`rvjy}eM!2*ʇ)xn$}Ư_ֶoqM[x {& HH Fu5?v#ZcK~lͭߌKs)IdH:((hxlDnؙ_^7?4|KOsoG$ "Ξ.vngO盔7w#x;"i,I'Nf׷7>9R Yt@x7[^Y9R YҼx72љrrslY3VTvlusmqu[[_VS_;0:0hcgS?۾>%aDY<+<8yxt$00PAAA1gcn.Iỉ()/ܤIǧ&jJԉKH욊Yv}|CSZF$ ų"頠i{щ1K65;mW$]S_+/$/(ۢ"kׯ7oݜUw{*"i,I+nĭomV+qFU5U$C_۹OF$ "iţGK'F?| e|{# $Cw߳INHGҢ(Φhc%H 'F~.7}Xmni=Fҥe7nYKԮHzd<1lQ'zkw[z1ZkG/'UDY<46+[\ĵkiҜHXOIMQ?|$Ijk[bUC h}ݼWIdHIm+88ر1퍤?S߫I;svE/^_[{x9"鑴OƧ'4Yt쵫fo$@OHW?$1vH튤G,-(,ظ2?IH wDFş7wX_9c8FyyFV!鑴GjW$=02(:J~mڲc0tF#4@o7nL"w{#\?+;K"iԮHh@tN~l_3H~DY,VM~.)k{#霼Ovn!EҶ]t`[}VE0ш7A$ "iEXXx;93i퍤ssD7F6]t@Rif//>:IoH WF~g.ol\{#鬜\Cm9Ru$=?Zw_蜞lH:2*Jg4"iMIdHރ}IH:c[~ #UG+uv/1lyGH"iMIdH}{wjnڮIg| !zBE;:wumϬTȹ uF#4@ x[USe펤2D\Ix;?wEWɯtlh۟k:thD }GGkYlmDDF5_I~σ#C9369nXمHr3C$ "i,^Ivmmw;?wߤw(#ൠP+"iS{O~ζ.,WvRmfaVr53C$ >H gE]}}ݝͭߔծ|exld~yaǎ?)ΦIgΏÑ#M|y2TM}Um>UfGSGұ׮lH~DY<+^\Dq:Cutwn15gecU .IKqH I/,̩i#;F+=nIdH? `0(;=;}HH DOWVW LN-Ur5#DFEWU\"F0 .^S^n~a~AQ璢wS[%<< mxn$}Ư_ֶoqM[x {& H}e)'ٝ;|$]S_D;8Ց2IdH1m]#7~3/M>|HڴOMܽ{IdH:((hxlDmؙ_^7?4|KOsrr?~UTZmnqN}I=4@;{ڝ=oRީ}T.u$]USUQ]iK 9ΝI7456yqeQt[^[  DY<+NzS_=ϑJϑJG$Q^U!zvpp"i,I-͋w/*'7ǖ÷ܶ:TEuckKƯe]xم핍Ձ+Wc]}=,(969&._YZ[lU|=/MSqmQNHCS |H~_ZPE9I/w3nwms]S:T-mH:q91375@$ ų"5n6'7Ǚ*+eEZ[{po`0$Lxே 6ףc]z.^C-wUEOr%ѡK'pcg3%;o)ʞqj ]\]ߧtPn74@ϊ'gĻgj+)/][\]Z\p$I0;هԾHzJ$!: }\RnͶ{&U# O]sK{i*~H75@$ ų"ʃSGGe s6&F]t͜/M{|jl~iA8Hͮ%u`*hmC]AQaYey{W&vM"tPjqe1Tnߋnl`0Pin^reͱZoxV$457~O;:1fæf튤J‹ M"Ubw5nH튤OG QG歛3nc&IGDn:{(+/[FvQQ=i6020WI޿g)VWvJEQ썝M&m,.!HZѪT@~UW}LEO ʅ0&B$ #i>jw~64[*k;{#2znYKԮHzdliA?Q'zkw[z1ZkG{ u$}ĉk 2_{lj_z쵫e5~qms]9϶HZ9deO>vcLHW#⡑Q^A&[^[N̰FťŢJj!#iԮHzxlDtVV[ZIhbnK2?fiMlo4oT~\ReM} Oo=&7i]؋H GG~N"nlW/'V vlL{#şEԃٟHڙ#+V~l*؂:k뱥60QWDҊچ:,uZ{Ik_Wdl܅|G"DY<=6z&vՁ썤?" #iԮHzpdR{!HZ9ouQ$m0&gL>8%z*TRRjwV7{EUDY#6*,uJ{DZ7+32I8R"遑AUk}֖E3ٟmln җ7ܽ#vKlL5\Ik{YJW MIc#bo$+gegrQ$mבI oY\]}'IHZQA}ηvܽ8nOom걼鈤xY$Wlggo$IQ?H#+߾{kʢ3E%UMv`+}ݢseMHG$ "iEXXx;93i퍤ssD7F6]t@Rif//>:X$kDؔ#Oy;{:@:"i,^Iis퍤r>r:HՑh}=sz}#Ȩ(|)V;Ч4ݿ,-i#oEщ1H F{M`}FҙD܂GL$}jrcnEDGFE \Z[֟0K+%"E瞁^H F njnڮIg| !zBE;)m{W~]Of̪Jp^g4KLOMKU+=vD u≍_8KvE%NnIdH`0OOUvfw$!|.P?H#+J~3`kG\}ԑ˗tFH|sovu¢)ۢDU|ʁ+V$&3x_$}ёZ[v`o$!DҶȐcƌM[:ava?{Ex}$ே{ۢ쉦:Gɐ%u-o2gOь_t ʢ?HG$ k"Ԕ֎J&卽CIE\I;p=4;lrQ.؊K͎60+\kHZ93b4崛yR9􄍙ڲ44_~}%V\}B4@"i,Iwv utw6~SZ[W᱑u&:VH:=c?x-,>p~iDSjk?喖wu%>ʫ*̎c]/C(,_PmmZ[S*{KKqOWGccE%Ei^JzMۂm+ExV$I,%:;mqv~*JH?K%Q@աz ?j53IO呴Uk8IեS.f3 /mU gXmefJp"i,ImGo;[=O[ˁRy1ԁHH#iGjz*ththKk)n/_%Y-]٨EҚX?,,L{OMHExv#l7)o&tvL>Hz{o[9UyDY<+^?Xx@u]M‹D`PNFңcbiE"iG1wof~vkw{cgs~io?+;+((+mm2? 1;mowӜΞۣ-ۭVr>*?}b*\R=>5-(>E'V\#ɶ1'IdH? (((dHXXHuϟ&M~*I^xxV$ <4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"i`4@"iݮ]/(*]^[l DYw[Z7F6q6x"i,DҀI:DY/0 px"i,^I?y\Y]?409;sWX"!~>>:8^XQ;푴>񡨴ZyWU.xz*[1DY<7pB/"sq9J}cӛգ#̏YJ-.5}kz!7'xh$]]Wc6[4mo[owu-=ϿC$-q׹QoG$ "頠Mcg~yad{`w_OGwgw_wЀEK߿QSEv=Hzec]U[[ [ScF$ "Ξ.vngO盔7\*EDk[#ui7n ̝HzxlĴÉ'+W7ԟ*%L4@ϊ^'wO? TDd:>>1'; +#ikׯ-ͫS཈xV$;'͖Wsy`G[Y޵I$}Gn~.w/ैxV$S)/]TVY>:9cgcgs~y+=-OҊqcc(?}bڡR!mS}}sclE $nl>B‹֎م --MFD&ky}XCL6~VvUSaHs푴y2Ĩ_ ү)"YlNnaO8Qaǎkjnys.\ :-onTI?yfȐ=PL~;Ч2&l<q_,.YZ w.-""#6wDyb;D#88lecUgE#iO vE҉/! 9"i,IONws3AAAΏyj~2h7u&NIOX{zW7vv[7fr旅kڹ VY];Vl1!j=_^:^"@ 4@ϊ++ohGG\kKk꘻MWɯ FԛkZÑO9}=ɅE募KL<91?vgkqeqvqnjvzlj\99:Ceq6w:;ssSRS*CsCV7X;P"g|ݿv ͭߊJ jz֭FҞy스;b'5+^ ү)?"i<IMMӎN9\,`0LLO,´Oj{-/8q´]Q#i#QD{ecK&zS8<:ZQ]ji@Mܶ|嫤~?| rK/vPGVωz38?;ys溥z[DҞy쏤,o[*;Z~MIdH "rssJ^eMnIӊKKMIGٲ3my}cNiiЀzBtmC]PΛ2܅ʥ[{C4bs=](QDDIәĽn5ثgg$yrvկ@$ "iţG}탄 Y_T7=?߷Ց֎ bkW>ٹ9bO:=}=z]}ctU8]:Y,I/uF%ثgg$ItukA5 IdHZafgU'wxz4스Gƾ6j)ʱ{6K9׷7q) s=\-P؂N&{E HZ'ثgg$=69&wο^SjDY<46+I_-}H\][RlubI+VtNxaic)1gc=Qf[bƯ__V#ꂼsMRYSex'_?{"rUFկ4@..&  1eunqΖ Rkl;$#+CUP+xxw]?Hzymhӎ kA4@aaassJOG'mN҇zu\R<ʫ*J.?:X7_Fμ^SjDY26 }oUMiʚZx 1*hmgn~-GX2ݿ MΜم: ˋcxΥ Z\ dž{ [-d<0+:{p뮂K#ig^ _"i,I_xaxtdjֆI-nĉɒKf@/[4Z٘qѳݝ)/^:pZԋ)[PPcgdP-sf\BG&&V,KHڙׂW=H Dҁ))m[{7)o,=E]Wiݝ!'C4}Njt"ΞNu ZPT`0*7s[qi-G?ےnm7R\Vh Mꡔ)%]XX/5yb]ȽCgwىŝ[SoTg=*4v xV$7Mim]WF ΦaBU[֘6~&M^A^@Gyeh/5;XM}Ur[츭s7Ɏ'|z~&0?%ݛo߽MIM1[XROFV8:1ۥL[qiqnAޅy|mi6{k[EPYSUQ]\R7!O=UwV2xYeiM22t/g^FN^S4@ϊeqV:{KluvV՘357cGԱ]ͣv{ேS6Ҋ2ў<{}|o};DfduSNi~^w$FҮx-ȽxV$)=a6v{BO6~L_U٩ mjht8Kk)n/_%4^f{ܜihZF-Hzsees368;D1'+[{ڵ⋙*H]Z{MIHZSwb@u]M‹DwuVmC2RԖd~蛙ٜ_Z ޽wwqeC:~pʆ˱WJ}Szec4eڵ~ɮP|ymymkk=Te&f&6M'& /PN'Pʀ3]~ءpjnKs7. WFҶ$^SDY<+ 9 Ğp>2*J9^A9@RG A$ ų"iI+I+I+I+I+I+I+I+I+I+I+I+I+I+I+I+I+I+I+I+IV=|Q3N|xswC$ Hkx`h vIVI4@"i "i t#OS3JO7)kV7BJ-8]Vp%+*:sНWA9UagJZ"*;#&IdH:ء㥭4дO.<8-h<9HɛcS()2JNRqR˫;ͽ H HpPNUWwG=~s]< ̯?Y=gIG]4hNEj͕H?VhIp4@ tZQh`L]ӷC+lӎsʼnSskNrY"> V쒏+:(mH/}'=yĩCC=eSeâ53vÑt~jZmYk֞N>qgblTVZvUEk'ܬV_|bfruwike4K:sGɽ'REMjKTzmD_2h.zIp4@FҊPK}×bz3y2SMm3쎤azzgM)Gc<㯮}Qy`~J lT3aY)UQxƙI5Un[# G6. uy3=n3i74`XM$ IDY\I+(P?2Jv*¿D^8bu~hk$Tsi$}6֩}lin ha?y{N;/'ږ6{M53Mw^D"iMq究,kCIi$ŝ:-NDY\InawY+m8z(%dkr׎j6误t+:"5l=Gұw4hh?ƯaT|05=$1{Mӏ[]^ [t]-f@agr+h*+7ϛU+OGIp4@Gҗn9WMo uI`egiv^[WZSᳵK188K:(ԮM+4[\x$}+>ЙhI-c͂u}њk;TɁHsf)W7@mG^:stbq4@F^ɖG^~ԙQфkUQЬXberncmFE_1ωf]M/ })/3'?,%q^{'qPrѳsGkK`kbS]ؾɽ_NMgW7!ijK$ IDY\IxBZuyHMoy܇U>jM+,Wjwj"i iC-UuQI/1B;vn/iΓ@g.VJIdž{M5nM."Mxu iI$ IDY\I硏aZG8$k5$SC%WSm#Hܜ&֙)Qb=6 K~?Oa|~P O:s=tJ*|:^Sʁu1*~MT78 #`"i,#GRI'4ջfI}MBu/44tE5–3K7숤,Lw F;>=᚞(j~ĵr`sܚӮ>o¡~Q9U>y?4'IdI PhW]ھu1C8qphbQMaھ (Ϛ_0%Χ"XUjFifSu[ФvcykzQ.pskO "osCL֍ ?ge=L"iN""3~*NZegkYxHSlm_?:)mn9o>I)yjvz$îvo@ٟF;Rק-]y-EGҟOGX R]wMif+#5k!*f4e&>4'IdIkJ} ?}D^<ٟ'Lbھh$jb8TiD<}{T}$L'Uy0Lxlv݄/eBkj:'JJ:OMKuQ7G47Ӕ3ͤ)"iN""!3AvUX1w;#%99[Dy{dm_٤8 :KSNH?8'J[.,i󀙭n (=[{3Fx?&uA~XcDE~9jJ쐷BL0_[vMv)Yv*s2ӷtvCqD55BoY[kG]zM^1=hI'ӎgRtsEMn;oFSʛE$ IDY#)v {k-.8j5~կ}Pm0՝eMYc:Tnh%eZY%ˠ;4[rO۵{5upfH-,MNBf+7XЉöN8H t*,돎}]=*?u&zak:`@̳wVwOg{'@EQf"GǁQzJ/"l͖^VptE1h'(M%圸֍p[^\DD$ H:acpfWlm䝴%'84wԖ$YgHmo~CXm,0}C'*:oMNю=z"jluQhV \z_x쪰0w|̠[77\D?"iN#"+3]Hz&} Ϯ K-8XXÃ돴ϡ-QƑ埌`8玤̯?P]i\O^YkDAe𿒂.Lq,`|+ͧe1[iJ:gSNfW)ץ#矓iLK%ٵ{W UaZCۮ{ Ϟ(窬-"-u~2xni|{]E:o6~D$ IDY$D !ab phĹ#B{< >tCsId!VId!VId!VId!VId!VId!VId!VId!VId!VId!Sg$W##|4<43鈤A$ OG$ "ix:"igII>HHDtD #|4<43鈤A$Y}HG$ "ig-,45-guu&)skD$ "iglyb#43)wn"iI>HY;?wETe É'Ϟ;w9J܍;w I"|Hz_N_``8ݿwH43ؾ4799YD DfI>Cr$7WQ]iKKMKkIE$ ɑtAQ!aDfI>HڃIE$ ϊCOnYXZ[]m+/ǩ[_\[\]2~%-#īƯ*^Xr5t@el)}mg߁Hz`xpqeqmk]9olPЀ[7=%|gEO+=b|޶`mN‹=_^<3#Ư_S:;myJЭmSf<%|gEҙlI~ǧ&^$I [fjhj XR^:8sC"qXO+R][(43<+~aUMU/}Kkf_s,_4sJK47iF)-/_ڟ.mᠠ1q7f~a$._o [6Gݞ'"isPi2dCq7,EG4vU^X5 fm7||zk5wovI>Cr$mK3"%2*jזM;ͥHZs,UA))/}E.E$ s3bhͣ?8Fߋ -V<{ܣDH:(((d_?4 {ghZF!_֖E|jC@@؈ݣDϐIWV߾s[]tіa_&V -͋8մ%H<7 O<~W#iEM}xzͣާG?z=J$ O\ĉe勫K6NNI}!7?W<~ȇ#s΋ۦvtw 9E(43<.? ;4?<:50<865n4"'~~ȇ#iY9SΤwݣDH:qq͵ӥ [~y4;7G<;E/_%F'ד&L*pDH:00PXZ[tN-͖E+r>Zۑbva8{w5~]{HIqr ;7}kIo3?fY#b1ȷvnݣDHo_s=~Ư_D<:c|>=id}{#88M1샿z=J$ 'g8YINkD%EG32ws!-4ܴ7ޣDHzlr\SR^ #ErMu$}ݿmomrVu(OllW7{HIvq6v6޿g' R0چ:M,I+ԑm؉vl4ƅyI>Ã"g v~[ܼu;)}]\]2>"z~mi I8$NqM]St{o۝(43<(VTV揦'jIH߯4-#cwl>+RRv6, nɜ_^Pwo.3 MNKI>ó"iŗ&MΫn3 on1gc{4.~X$eKVn-ͫG{ޱql?5W(t[43<.V^265swecujnZʋnK}=rMK$Vy@$=:9HĨsI U\en8pۋH"ix!'XA$탂FD[USՇC$ "i_w#nlrLs^}DD \R4}h箈n%蹔}Iє("r2rb;,+ٔ'"r™]u1}Ǎx{%RVhöKmop}VʈdMQ8Su7(̐_g$uQ*E37柼%8S2bcJ7:JW_J%"Lc{kɻt3ʚǓt䠬=` GϥNIΦ\6g2"/\ yk}fH]ڻS=d[3TުD"lٻFSneRt b ]s ZHˊi1[;g-{bŏC-|Z34OCDmj'<0׺?"rBrG0kwc,}i,kw4 -,lޕ vA}\O9t}Te+y˭ߣ[6gԵg5y' y`zV>퀓2aP:xJ;sdنk0B+ۙyϪ2u\ȹؚY]Zy6YԁIhy# 17HЙzq="ow#$vJU3|_S3c5eW#5)DRj`V>96X+R2bIn.S+kb?L޼+hmʥ̖y}VZ"4@U%^l̦;ybE[dp!6e~dҋ6z\vAlީšBO=JpF8+XTâ߃VVn5Ѝ7zgD#y,hotiNn1<{-伒PHDuεsMbt={e"_ZVnv8/;#" d c/*lsn#Rj"a={g]{@M3q*.f m:FI7n婇01Q:O{ꝕ^[ylqMc#Gp<]X H ~j{)uS>1SnbNX+Y:&"<04[9)ǂbӎ^C_l4E(\TB-)%)t߿cYZ 2QONk)㚢c-72nߝo ~y8T54H]H5農¿[q{z+9|6QL_Ndc7W3N:T|K2ȃ/5cͰw%\4͜qf .btVufHcȦN٭e0p6Ŀ”Xn6?/#" d6Hp2r[q/xlqZ F߉؉i^BTO~IF{//7$y0 V^>UT֤ mGRmykn;{X1""u$ﲎaMbk=f62x$ˍ% gr7G#r;ouiuW:6)8:?T@pkVB˭< 5SvYade:u5V$/~厬N[gŷ/dvX㗂Jf%S=5rڝz'=zyAVhmM-55"뱂ߗmk 濲i\>"kbbiWzOm< &"̫]ٍ9 (Խk6ZJqZ܈\b쏈Pmvt+(߳&mHW/{pöx ܟo.JJ)(;v>E3,b+#76-+&53&%#&؞He`fŠWI("E;00J>֕k~ܑyp!9=FDZ+5+3ׄ_-S#򲝞#r5xD~ԊE50{F20STQ#Z3HC_Evm/J70[bk)˥r#rf?"rB)~Mdg?Lc̼Xu(VXM(,\-@i0窺֭!sN_KY[3fj .}*kȉZqn;҆dEnD9BGGJi-#fZ@쏈+xכĿ{ i"0WD|V\}FI)ڈ5rsI.hB.66v]S/0kjCN鹦 A="G={zk[KS[n=/#" co\#cwOxavP @&\=ҼjWv_})u.K)#jnvArwWh""r&ʵIfjYԙvI+Fc0"׌r+ςc^c 6="4TfG~m G|tA/h.w7ܺx8W%thf0HiHkqߵ)Uzݶ&qhg^14&6oP=qjT]HռSjW00}w_qvm׺yxE@;xqJICg~#AӲb4Ńݧ[V 1(M%Ԗij)FtDBL3OaA'"()-föx8N}S[.w:z܈|Fowܑevki6k"r#f DKtLTeMrdNkWģ}hmfɚݏ@fo|?cW|H~S Rsexn~X~xAσ¾"+cG|tbϱ,-+rShF䪻$j~Q}GEb܏σ Ǯ1ifdhmD>\D"1"߸=մk=m#y3r)9ffSn=/@DdĈlA᭯tw䁓Io4Hi\JhC8sڊ:0޸ߡT۬}_sa01}zv0Gь/*v务yLW:-;4??țh\$F=Q`&lR"r)f?"rkay[|qir LZ()z}4[HQA Ngּ`s`({*SMn#"@z$8 8lG`DZ^I\}m|3Yɛ4CqSö. Z&7jC/Ds06-x5LCiuΧI9t:yu;;+Y5~m#$.)U;(& }zuZi܌rS4 )}t`؈aQσ® 4TwT3ЊĪzjy-K0Jf񂺛YD>||(I͋pAf 2"Xnv>/#" MiG;-۹"rT~ojh^j)|/d;Hzm24-NN0pOwv47>; S]&ڕmv)N^IX4UϤwElDӥ'[3N  Oh""r-u6筍U=BHGΥiAf &"[nv>/#" hB1JTp!=Qnc\lվ^LHD9G}Rex㥱X~iJּP'3bD[u*C>ռ s`u"QnEu;:mڑ @!weh60S!Dr[$#+g502Hق喛 l޵.]G@E@hNHu{}h]vrR3[0/ZvCZY6YnP f0YVgSuةɣs,(7fGѼ5 #Rݧ;{xa"Ѷ8e_qcDnjXDm{5?vv˷4Uf9"rl-@Q4gL5w퉩[5=;Jֆ#U,Gi-_70]!Ҩ/0V:^|_y4gSLx`WnO<}FO%2LJȣFV(񝢦H.E%?>:mfF^bki#r3`sDQFNmq5&V;Pod.K۰-^s{ܺͧ*9;kg{"A%YLoΧx\FnHO*Ԥ1dьmR.NS+kVݙ o( F{ָc>MNioD~@H"e%8liY18x&@ ZKSIt#r-;>T,P18y%M 8lnD78Oh^QKlDo[od6ZkZK3r -"rBfxn,ʻha4g<4 8`KV/Ggњ cާg:cK/7DxGoCG[qc3cnܞp%}4oHCީ "a-Ey+Ru]6\/hkZK)Iū[b0S|3+Hx.#R|x/t``ρI>w!{;Ut74#}=x#18GUSD.i/^n*S-CIۢVpm\kR[eTAmvLDJ;etL/nFk-wlr`V][nat^6DD@Gkg kPPx#[8eԗEGk1Q[\wr7~Mki]㾿L}ϥ9Lڕ36xff^+ic>طf:v嵞ͻ}rUىQ&Gיdl~E;9VU˲czXggY-R0y.m~qâKHV>RVA 8"6>r "rB柈|wP{}o라k=ٵO%g6\{dqIZrH޹ ڦ\N_1<+DRzE`OAi_q{™tzb}<^Yb|i1o>r.zƵl_ z~+;p¿67J8ې.6>vGb긗#K{7omXxc(Gz͝rX+Ycޚ{ ?Z߆G}#8:{Ş;b]y j'.K)\YGծ# uX.h'C=>aMk#M;[-L &ꔞ[RXW_%>$NAFNR2bSDGǰqtjRK#bVy 98]7kFDiɋx`5"rX]QwI'.յg8EâJ #"`KmFƪ.[+ "r8S3/ظ=@0pozFz6 boU{2>0StVމ!GDw%M7 ^:ӐәM>Jח6\obkû?k8xPwoO=}NpGD[傖`gӓ9tr !+Kv=8i-9BۖEeGdKZK2D Dp~mm7e;vɖXecccKחVXwwhձ̬Lf15I@ 3zG9Kl@@}ňpAp8iiiyyykKJ6mv>~$Cڋܚz_ a뗲 98Zb <"|/Eb죠L>2b\I"r.&A}C8""YW[:(:2ӑl,48<t U9NeiGlllHʇi-ʘ+ӹg[*PGDΩ$D䀂 Q>wl`c2L3S*P98Zb^Z^cm$wJ97PsA8ZOllWc>K?&&"܈0[KJr"'sOgHKK\Eǟ?(t;SGu/V$9! uD>חkdʈ8 VwST\3M ٧/]ܺmkvv6BD΀Zb21"w)R>~sIۖ:"sDs%ȹ ለP#h *} ?,|T^ü8—W/nvPj[ 0fϚ7!\ZdEw:N\P}-' wsԧgS!}p L8ߒ6;Y(V~AP)iY>hВm{=<st(]٦̬L mظO_D]‡.]-+%2Hz]kKJ6߹5ы7/?_bLٓD={y~ۯ?}{og1]SCDXQCFn~F>]`qpI:o}_:fpQ pm!C[{=F*%hOٺmk{WOđ%_|*JL#cw!sF`2-djD^oM͚)P͚qW5%u\NJ*xme?֖_[nM39mox#ET;Чl8l\fLTQ#;BrdWUW%wvw8x Χv ^=.g@ˮ>$[#ǂjWR]|(~St}eX'[{USVvTݩHSt՜Wܵ9o_txyԞ+())-"WSמkީ"޼hi0WgW}׍j]_s3zrMݛ4b2--m֐k_Ͽ9z쨕m83l8LLM^ *qZ!:R҂o>|ڷމ{ؑw=._DW@a[j)|p/ꎣvG&Z{u@m|kD_Jy0KtCuj"#@y۵gCz1kf;j$ ,cjĴd?q#a$Kqǥm{å[)r"o+/3Vn]D7yJJMʻ7R|jQok@)qI<Zv!4%8"{,ȪwӀp(WVcǫߧnyķ>3n[U;Nu"vS..d}m=Ц{:e6.LL=0drfÈ<%#FYNC_NeMJT~?]qi{%JPeަKޖp3koH|Sm#yy JD!/v{e3.&7m.jb+oRH:迊CO_<٧1xӄIiߔ@|qim>/mь7ٰ+Ie~qbj*:@{x*Sj8 gܐb{{-4Y{\nvާ7[g*a^\R2-mgN?_k*3 /KcfXDj䶽aZڰ9y`Q#cwfg~\hϽuSȃo#r#bC,{PJfQok@)$-4%Dǂ{m~_?(*$}oXG>>9XS[#}hmG:+/VXlg/ߛ78<Orfψ<~M:v9>\u!b[fܼ/bb$F+SmXΧWrun}.e8S}SEMykfbJ:>1/&K n{SԴ4Uqy-LٓO5TlqVU[w`_O_[cc|K{=I'$$[\4#?}̽~! Dzj^ϿѬ3:"SS{yьfo YVYCū\]<{\@`\gzǥ={t>y̿uVRj:"WM\Pnvާ q0;掠4l;""}ն8H[6"wDE/Ugly]AN\4/p:g\WҢ^ⲅM 7-P7"W#ki>RDKJĤr5xmq Sf0">yB_oyG%7%{,Ȫ\sy5xVT DK;uF{aD/FƏ^Y]SKz=wg^G6l>q*:#2/̳Qrfψ:Yykcqm#yٲ }?8)u%e%n.uA ٣Fl1#5xDAc_/0Kk=ĹWJ#(NQ}VѲ ]zF3 <;)z#-iD"E¢į48Խ99nf@S= $.CPWʶ^{a5DEE̯޽puRk;]lltҏ%+DKJL .Ͷ [[T3A -+"R|ogƪLJs,աk/t3s$ >.&o6ۉS'?WlCnljT~@J|h 3+3o4ܫ9#^ٵ{=:"k2z>U'2ôk޽%vꬤ_9X{ O~GCӏfyL=ٷNnDY5J`y7ggڪ^۶v#YY}"rHD~LIa7G2Кz ZY|,=rA'UNy/ʢz2s`_缱1o0VZh}f}w њYr&ĈaK9ߖy`XG% zO-#-e;@6o޿[a]X}cu=ڲ|ĥmߞx̣ITbvxa {@ob}Ҕ8UG!uYnvާs:KS_?q~V}Fn?ĶHrCR7˴dKo>#+u0 ׀ :˂z ZY,=]nBڵg\=jyxSY+7*pQ+/{fetw|&isl-|qө~ܥI$F9|L:Rc Ҕ/#;yQG)SF/0ˉJF̪4>f շ$oeggǒ؜`ToO?5Y_?b<}쮛k\'/ppxP8n*EoHJJRv8KZS rkHv:sZh@iF"r;j$2,dR:uD?UjSY%E*ٲ1eKW#}$mG%nni6[l˭om[7W6ܣ=|qeGG-yBr,|eO[DU.;Umwdr)^[&m߱][>_P޾VmDo4tz& t/wS;ȈpïֻT#|hʛӳmTr/a<$r{&PPX<%}o98x_$.Mz>]K~4mma!_U _2Qarp%{, =QgDi#pbY&5ަçnTsݖIX\6׫W#}$mf^fh\;N%`=DᄥaMz׶=|LqI2#ޚ<`!9|^155?|/܈<=#]}8|fZ ewQo`]Eކ⭪zK 4$FK8toUs)C=87 .ϟJ"$7,C:o/ rxH\M/[5tl[CmΕSf;2ۣ=#zWOoi I'"7Xnܧ}1>M{),8ӷ4܈3/Wu&wVG CNظ(Zݽmπv#I }e_5[l=t"r%/߼r:a}>m_:z\R̾쨷%X֢UJ"rSɏʷz% .\M$xj>͡~ƥuG\?SE5KHbD5|>jiD.Bxfk^V72xc2N-9>R -= bzFPRޞV( ĥIoԥq!)(WDo YY%#ݣO.'>qnUN>7H`<"7Xn6ܧE&xe[AnD.\MNexSϠu[Qn^ܶg@;zCw3Њ#cDt:EmF&kqI2#ޚ<`V koSK u ,"^0uPSzZ'M}hg<Ws/߾Q/߿.<`-Pzl-zKE:;UKmqDjW7n嵍︗5^3Y7U`}Ho9"+Y%y*5ɿsϭRq{8տi&-x^^2AܥIoߦ(3=vTE.2]7{i2o=W5_7ZSPrko} >#pɂL0}K3H|]PB⇢l*frt1Xo 6D*Ho{m{y)B"r:;WqD.RGTꁧ]I8VԌ~={&ueG`G5-y;כ+R|Dn?a=z<.p'àM}`( >%wi-G hDm#~) R߻|yz*SuXZF>"G#_Yiiiݝ?;i&XR{7,ʡm "eA8kjkoXj}&Nx'#3פ<"oWE6կ7a ~uA2tTM9#cct2:nz{ ]eA>CbeG*4CeϞ֟Yqۻ:,٭D|U^^K'<#vMHn{[{dGܧ6)y#"^2-`jD>JW[XJbVX6Ho{m{yɚ7/~)"rGu]SF{ܡfo&{m÷L=.Y]}v[ cAVU_ˈmL%uOŬyPHQտ!k^#¥KfDY;5FyCXỎ$J/ë]F^g^ |j#|ɴ8NѤO_ߊsgtX].;UXPX̺]mrf]gϝ՟Y}n޽we]7Ր#կKwڲk.?ޛ<)je&FImOE-\~/KJ2QG^}2-`MDr=%rWRW忥lE6zJj䶽v>ڼ$1-[!&^mKxȅFշ|RSv[,,>;E ߸5>{T6ȧfol<|ү ,)Rgi~TEzj0ةi~}tf)߰ͦ[5aعkϏ=K6e~q}庢SzϢT-.:ǂ3eM>ЫWcL0}K3XFr/mR;w7dfj䶽6?ڹ$19>}MX[^NDt:yit5{ӌͼ~z %:ˤ{k-<$^u6YT`lON/7GGQEzUn7%f̈rD+3"WgghE%boڑ`] ߹_}k /T,fo6-[gۮ|H83&}S7-9W ټ<^*dfe. ӑ.yG{,HOoo:u,L GQ+?we*,mrDEG*m{m~sIbcs-7[ ~gO-\nF'"خ~=(}3k~H{eG`G}I<$޺zI"rS4"x@YxUurqc&kJSj^-wigȌȏ֦( `F׭c,?Pf}9"߼Fy[MFzߏw,?~lUݴ6#u>[pu˃iTlCY=K۾3#DYyBYl^C#TP8Yy7+1">MJJR?6+nv+sWG&g Y KXstMTI{^ì^ܶg@;$>69m=rr-\n}ӏȅѧϴڶSSᇊ=.Yf\}|ԇkh k+J ,"^0M#r~ԙKMzc`뢭fĈ\CgF+$׭bz' J_,|S>.8[+oF4?%$;ܫsR1:;ͩ~F뇀Po'+F,\=v[Pl^CԝC vR߾|Noo6JmO/]ӼiV?/-jY;e[Jg}D~%9]ok yN}hK{5r^mG2;mzozJH%.ٱccY6,7"rgDU>G5~ݮ@KuW!$XX{"r;S4"`\Y'Xa͕}vMlk+ӥw&KIyNȅGg5eRg)' kӷt&[#-{eAK)Rn_/4>9ޕ?xމ&asמ>_Wok[m۶oSwk]M_wٽwWs<3g-ov!=bڴL<Xm +1">4&S:o/e[eeDۛ|(C&%͝i96{{jm{Zڶdv~n jgqiqdѪc*7#rHDW[2^[]Z),WV{"XX{zC#r;S4"7\]ף1\݂|˹56_/]_q9r/\ 8;;yjN#^ -y>{EV9?-uW꼽pTmSH|L͙ʼnPyS9vD!:55,E4Qb9,Cʍ{sFXgϝ_}v˖&}SG⳯޽E-*s_e o ٹG:]*SqB:YWbDٽܔWUUWw٫ԳIyozniW~EAfZvCrƥ+)b\Ew*X+LHUSb_֏E's2[R'KLML=jm{âgWS~3bmN5-#~^j\b}3G=_Uba^]bjKu zS[r)yǂ{nD}v/'PO 毮iϾ>|HzOdq!3]ӽm#y x '.y ӱ ٰ-@\Ë=[BSO)5̩'bҩk䰺`Pac}k>qKҾ#riX mk#[6TxAb"-)WbD.T}=}ĞIїu0ScȆde[8S<= N_֩,cCHp-`pjlEki>RZϖwzL[cvHjjk8&A&&xkņZ FBcSft0:Z5f_gva^k)+9"z,Ȫ..T.5"^X#{!>y{1Ѫc>W?NyRlKGkruVlבD#)߽9},_3S2b f҂lߌDs^3(A͞5D=y |\dNCWnDv#Q8ث1}PE~<&ZW83p|-rRP^=pi-G:|mo>}Yɹ^HGw[~r7mP/+r> a)׀A%:+[ ER)wED#r"rcw~ ~Z`/޼—/߿~cRj]fħD>t{庼< wivg=ލ͉]68*kRڳs;M L {vzǾI'm_aQTǏyYv!?\]΢6n?4k׭_,n<*+/-'kIĉz_ëm,qsьjd_]ZLj\|D\bXOA7Q;Q\Ot٪p5h+(,TJ/Ы1OgVF+0@JMbfV,)_,.{_:Yp59=?w<[7_xgh+̪9Qki>Rϳ ՗o+FĞ8zD|JA!. #Lo~Eiiio߿]qp9dkYuՇe0^y-$՜V{yDIHHg5~-|&wiB,pvvvJJJw4#3ҕ!DC2"X(+´W[i11%qETS|]iC|QNLljtl*# _ON' Xe=LBDܞO*2YU^ua/߾@*DbkzHwxK*l.))?|cp"9D."rؽwO_GpVG*[oL>}ňM-͔B"yȽxۯoՋu(u qݛ_!_kZDCn{u瓧OR8aL=LgONA  rܛo5ӞޜJ&عc8m۷QD "r\ccsv8f!"D("r@""D("r@""D("r@""D("r@""D("r@""D("r@""D("r@ <"/*.RO﫪(JTo? `Jח6\obk`#'sOڪIe|T `޼l9 $VgD9VKJ>Zull9 VgD~9Vߔ cUED7[HKK[[RlS֊v>~ܸ;wjJϿ~){pp#XX} {pv8-[q:?bccW>%"9<#޲ձOȩo!GDψاf#"9<#brDx}j6"r@3⭼99)))}6 dFZõG3?}Ƿ_p聃Xcǫ}}ǟ?,̽zq(MoظO_Ċ-~.h1eMB#Z222>f5ʷ7hfb l}ZXowa}D;f>z߾|^Z6LjPao^hjn2Dα @ icGԁ&IHH0䝻v>9-OYt>M >"OKK5 O7G;w(s>~7VlP>rmSCM,7ԞYl&%%YOz%FS ߹q񓷥y_/z&5U"rporɉȍ$Of^zE'?Rn75Ȉ|>WL[AշϽ~aKܯ|ԪPwVf̬̉ w?.~Ze &4i*:3Ǿ[K?/?vcAňc!D;w5ԞkpvZ ߹LY#~}hlj>zlwuR{_LD^ӗovvw9{PU]q9J(`{Vb`#Med7;ӼL^ ,{;{\So#OWlxJW FfSQJUq~Qwo^Bܟ;ϧ/2գqMnnV}ٳGNDRgOP**Sڳo?rCgP0_pDp8^yDbK穽x^}X1QyW;_,JU+kFړXnfhPwۗ  >}}t:58rſ">ybmI#rQC{:EYOYfggU^Ilc0{xD>8<96~Vru vo""ZOj}vT79e u#I<{\msG׶(RQ` 1DDId%DB!s9gqkZ͆IݽramooOwOtnX+ZQIy_EʶMyzs!}sã'YA+W9 %^MM ,xGy*q]c}Чo Y& G5Y` yzz:AtlrW>_m^Z]WvqwUT{yzsׯBego?%r@M"oY$rH<3=P[;ۂ>mjmV>{&ObkWM؂D~EN3_clӒsm_{eMl[Skgؑ7xB.iچZcvꒃҼD8Yg$+׮]AZ;ڻ:$񑱩ə%]yz  ʾɣAo޾"7n2;A멏 *Fs[K q=Îxǹsc#z ESkӱYdc~ y= ~oD~.A(ᑉ j-r}SdP_ 'S8(QG=v-URSS保+>j8t%˓N_ ׳ I7nP+? 46pOttwv5445WU}Q,<;7'ֈ\־>˔CW;zhࣆHƎxyƍs߼gOM_":O<7oH"O+t`xФRf -HB2﫯}za‚o}Я)Dù 6hbiE=ÎxyHhN}d"Og$OU'O}BO#nՂD/[7󕼂|s>MMM]/B$oM:֝3}=Îxyϋ9U$iG¦M]ܰa:Tny6+9$} IH" tMjsK 맃uo :55uni^]X! \]W'V=C߿nԛ>rxtrLjljum]л\M{Ӧ%Km969jzE"Og ;2æX[;1=,$kqwiM۵ JgOW A{wGu]cx|DOPVQHpێ;<o_zeY-]xӦ%Km*\tQENƋDς./*\k_q; ٹ96רaM"߹kZk?~W?'g6n6YmCm M`ܴTOp@rquqEE_fum]ЕHɼiS6 ٦gRU9==ZƋDςO[6ז`Hdcǎjaye50[^m۶ٿUkyoje2gLNzH}()R8uϠ4S_c|[<>=.#9^1 >חoڗ,p@uwy{0"'DwO 3Wojmۢ֎օ%i@͛AV&f&%ƊO%Jvnߪe<55u@&O=sNNn䬴Q rֳ {d]І75WV-BgYvYe=?},MS>}o ַM,mi bg=چ:ys^yνw߽yca#<7j\{+6qL"7jjG pbOɜ3.!a+.`ni>Rv=8:<+=.|qGQiqؗ,gΝ1ǤlM [Mor_ہYuM>{hdF-$D}⥿)'!`(;58:tm źvzU |]5>eb[HJЛ|r[۱c/M613; '<7gя͌t-}YNn&ޅmh,`wMYYCcQHŗDB|7AŢ \SsXqu\8qD^A~[g?3 C*^j|ǎ{x`|jbznzni~iuy!Ay@򔔗 .//M5߹wMPΓST~t;?quޛ04093}ioAn߽R*i鉮ʊOر6)Yy֦_]^kGQ_iF[;Z2fu$rs<R}9$ v%vNLOwO7nr#?T~RUnq놺克/ЉAH^Ntد۷_MKo^'A!~=$r߀D ]\?}~iɳ'ibю;woڴi@"344!M CHOtG'Tu|"DxמO@"D^TZ̯\g7ooOB6F~箝/_jhu5\vftbL+s}y{1_`457X{ƅ*Fgf%)WVV :1~a9|Tu]c؈tLb-Vpޜ-+;׏8noa2k813)ԸEUg6GաMc )Dy~yABzl@SksIy4ٌy_T(u;8'M'z{XZ]yV۶m+.+Z,vw9(7HmOL.v?ٮ~豣i[W_u6syre;'Fj@鉆GOLm_uO=V|(ҁ= Hζi(3ejO,۱M_5\QkD.-%%M*\9IϚhrAKvqSSSŲve4>5!=|KٷY.H?zj xxx.Ibme I";K2?BٙxX\jy&*Hkϰ `W.[.VBĽ)p)̞2q"=9bޖTN['N苔 ƦN%nli Eu3\Yy?Rn3e0U͍#]bf7gsق-jnkڦ4u[ЦM#P`&RdJMM)5*!.·:I7qWLj$ҷ|xlN"0U,'&6o\<~jll#!~x3{lg}$g4M6tH Glz{JDH&)^$r"IkʖA흲 ?4T"t>z,:pD"?"9+: ֫s+999$OfdbTW삾MVY.ܞ:;ehjmNxU ȿDȪv )bJ[gVB@ǎq,谫<}V9OJ&xFܖ,:gܞ<h[i/3lnyb7ZpSMRgΛ7fZ!p9/zF瞳tZ3Ax8l&2\Z]QyeAA55K%WVRTR遃,\ZdptH؈{ۡ>}d`9KտѾ]pGrMuΜ #Y`QW׿rrsH"?q6փIZݳ ?4H"7=55cܑA}8 xxxnԛgf?VUF2[Im3q]"1U)@"7mJMiԓIoߏLMOLř ߯UEvW_f˼'QO3=8Cz2ez{KKe*(6>h 50`mgUJOfo {zkj* >\rUU ͍Q%rGZzsi7:Rz}_s{+ֺ_b;l:PƽM.׹r_AWEtmM?磦F|IY'e˃vfL5J ލOM1WrCw_W/#qO54N<7]zmqR*_ƑD.Զ+l9e\}N iЃؾ[q!Y\~Kܩ'A8 xxxnԛp8P]xml=uv&KT< ϞYmnBɔ 7T )G*dtx_Thfy?"l[)hp|bmx?zŪ8|zǓV}@ArAQ+SEYGn$]| \&Pg90̊8ݹk{?%r=S>֟_^0]v.XS}萺'+z35&S7K}ܛ}Ыqܨ7%=KX }U^9xu\GU$0kG*5z*KW._<#r۰a·6ňŊ4}5iW6,χ~.M ;Y9 #;߮^f&}}/Z;,IT4u_f+/Ƨ'†PyE7n?yr #=1mg[w\ z'O$l׶vppGw5P/Mi–?4 Fz=^G<[ D^S_w|ypt(ڔh7;m:1)"<¦Djf~jl<<<7q\?B豣:y>.6ŝ=uv&KoHlU àGo;ۦT5d*e>P#6+?:)R{w7ݾ~9mܸQ&h37g[!y$rg[iĿA 7ݑ5/(듥B>~%-/iW2ɤ!$ȫ\iR92:g\ƹУzzcZ(Zp*ު Gsk|oW?i/킳>meR=ڍ:kOSSSu-R铸Sn~ʣn㇮h7;5o 4fqun6=O>>R\>iY??saeiӦMeOי$<%r=k\ɔ?mrXܵfimčND0%S&% OτMfVm^(--Mym[Vp&;nݹfM"/*) 3ܽ'#y\-4HCHm*=5&ϻ{M1@_Ds/#I;q]u,㨻M&'7GvbMY@Nَe]ɑu Hζw2꒽]7쩮su9I0c`7smrE}}f56m<ݰ 4&[[m,W=U7WVsssHqA"݆Ng]=u|&n^"OU  ϲ,DLTOnR,}]$nF޲L^4 |mbep0z~s oomO6o H_5ge=A~z-_%DG F|55&gٹkgmCu-9 969fÂjj:wvLYࠏx~ )c*6m7Hˋ>r2?D~ Mqv/ `Nc*zWm}ܛ}ʫy+999 wu4Ϟ1P?ʌ]=u|&n^"OU zZwbF|ҷ#QƢAuWaYK#w0z,E @v+Wo~ȝo⺭Lu0IR[~"MMM-t׹kmm)$ƍUFiu'/^'khl8R .F}LjL,mVvA6ݴiZi]y:>|R\q&y h|bOݘCƢDmRPX`13?AZ{Amaѱowu@;'c +r3w-/,)k}>#4)mQк 68"QjWSkӱ֥ܫ7IN75W Z1S ^ZTZ-]w^;= )srsΈd)ĎD.},L_9>-޾c1]+8& y~WY;vũY}$gT5okE %{ZS_k"SY"-QD^TR$e3.mL\rYw L3Ψm-h;K&*6UO>>|ܐ[;_lW1S7fH>娪D?&l_PTQ]M?v˞DVwЕP^XuӜ㭐<SMgcDn!͛7Su/~P/vtx$?+o;uqEaah^, YaɿvA6vsmuԬ&adOeYAqO<ӊ-$5ߗPucڑbU= xю4&[|.㜅l}ܘ}Ыy+999"kţS7fHyu]`c2ko޿k&wܝ.bHˈ)涖ƺOU>U/* L| #%׶mےj0ڸqLwǧ&lqN[!y$r[ƅ1 |$CVWwvKU74IwG*V?)hq|_| 0%h6lP5f@ dr[".vM.ԗtu۹m۶r 8zz)wRS_GyO{wGu.8#9ۦkiY黵Ž3S}u+븓%CG"o^Md LGlfjm<<<7-ڋ( ux jAv$bOݘ#C`Q"?x9ݔF5W Ƌú2٬2;S5w8ښ]w'`$&J?D&Aٻ ՛$rۆ S .^h0F"U 7 h?nUȀn<5k66?.]sv?;8֫xzHk[oͽV&Y:^}/ufKk'KrBf2YKIrCQkQei&}륃;Gc],38}cqniiir2ϭ4Mٕ]pGrM QJjOկPZQ-D.2ƾ5ٝ{wU ?xf<Լ-Kn,W,8 ٫y+999b>O$vD[iK7K}'y3q$8U ~.ŵxrMOrݼ} eM}f2_P_~fMmkbzC:oNu0z.O[7G}z_rܵӃVpk4,, & syG׶G)u"&S^"OX$4ȩer}'X]4i F[M{A!.KP9WVز}$gT5DhS}r`N M&u K)ߟyܨ^D ]cSX_^,,8 ܫs+999qن Tʌa*A4u25?{yRXSѦwM}Z(.Rd }_Ye6s>ٹ9`غ:Ӥײ9=?M+WoE&n_fY\|Q\jN,zmC~9Ď`Y"@ݿP"?yꤲ_[/}{f6]6K a'1^h;v;5i-<,meޓeA{5~n<<<.!wiPQ "1g",EDC{L=k539h<Tm߱cvl{M8W ޾^\]2gLȟgOwO1W{yܩRyʿNj룒g`dP}|xiW.nkqY_9d4Q[&Ct6_Ɓ=D#d9Š d9 +Io3.Cdg8Ky4ܻ{]yXFt.8#9ۦ.]iGŘ0S5QrɞwuFGUƱN"OZlfmv/ڎ]pNu[,Ԅi%KeaYo޼cғDn Ɵ#ljmXU)]Ξ.'KGyaQԳL2 x {LUW B,G.AzHYa  g_@&{Ч3Ï8ditCI5>3`$H 5]gΝ1YWR`MnIsܮ7˹I=Asn,7m ?^{dH]*ꛏS&ude'SK2WBUS_?29Gk1=L:,,#=M/L֥/ zEi4؟Y32ècv6ƊjVrɞ֎ҵrWHA#ϩ~w*\ Ƨg\TmIzj| xxx F U]Wcl|nO*J$rܵ]a؈q5ǭ]857}/$~ z4WC6t' xkQʿ5iB/,+Q}rK{^6[z~$kljHe3p.>VU.Zӏ&F'i&{&Xg|j|{I7[/K7ǦfbReX Z_2k|ɬ|hCΒHT]#9ۦ_;1Ui%e%O?r0AeL<<#-]p9uqNS_mX=,m##ޠ?NjjjO#|:,3y`0)ݖJy̜>{XA|aj2#WXSm^$r)$sž:5OU i&1xߴ8:|X͛7a^$A߁Ӓ:c6[26lMfR$cdHŊ+7? |D\L p]"y#T88У~(@@"Ϊ54:LM@DL$r#6WG0?~׃ٍNź 93qHr/la˅Ks߼jno :\lrtt-}Yt 9@L$DL ֑M =2 {ҋ, ?<ڻVY}1Ǿ?xuIjșDƝ{w .:v6 ` ޼}șDÚ䩹骚GG%E$șDFZZҗe1Ã#C]k~9+5"k|jDM}M`_IySSS$H @DI 9$)H @DI 9$)H @DI 9$)H @nhnifX<";{tblni]aS"\H16mzftbL#uU|H$r'y|8"Dk#.U|o},jcKu].@D?佡%7n|8>51LPCs㳬甍Q6?mÆ O=MLO.,JffKK?񤸬DOJ75zy՝vhL`J+Ki߾%Eb d00;46\d[^YX[vҗŹ)1MMe˟gسwwm]9H-zibKnor57+6;7GU{q&.)el-cS͛7|(׹6lH$<)NkG+u>hvAb iiir*h $Wo֦~8;k. ǻ@`=k[=m˲%zE2ƝR6FAJlp ڽ{}%^ܺs;<$VJ;|KzzzgoWT08:-DWoz..},OMMWѦ~8;k. ǻ@`=T'm6k>g[g9}𗃷.(,Z9un|ܸSF([6ʄ^;JƆOq4޷g%w$ǀDПG--M.,VUWeege>/xd:c9--z%S7?4:<>=d`C{%oܦ>8;. ǻ@pEQOUJYݷgs۹kԕKo{ݮ/[];sLh-?linkQda8~Q.]\g$4yö́ c@"O` >ץS'OUo?HTS_w氶#WU]>4:<0i?qmvyTۻC#tt7mSebRȓA 8s 1<65~aYqyxP'kzߝ{w"%ÖI-]Q6WO"e@%Zߕ+>NH}Tz e~y!sSs)JU޾N)elU(Fv?X - &` $rٱcޟnM6ɮz^]eWhhpRQ/_bp/-DՎ<y .#1bb6g"KbW|口513)~չO=ٳwO_ =w2m.0qǭ?jI{WLGa8۴g ×S;lx_T~L srsTϵ2ʨpu]vftbl~iAi}2Y50|zW%Ҭ쬳g-Xgψeo7> ];_zYS_;8:du >rX .LuwlmֱǬ5rr]aA`ԇz*ܣ_\UƍO/]2̍ppB ee^rK2nrY{0=d76g"K£. p6JQIA7yo AW9.a/˧N6'ϟ:(8xg+׮ԮzTtnܺrnli\]t箝)el-E[D`&۱^=$rX@@%3%jYIQ$مPyd|4豣z5qtVǭ ͍&-砯E=}֮cM"K6uۡɱ¯r=Qnԝ13{Ǫ5wߢ6>5aA93xrbgY&c1rF̤v2#0}%她s)(w~2D! ,مxo_s{]aA]c}ni9|jjf ͑ -RALW[gMKKkjo?/20-56G\m!3ԯǑFwUN9 o.džU'TuMz5q7t.$Del̤ SYgYæY>OC,{u]" OcKSrёQ=/~z#6+ *65XZ]yl"u2o޼Y :65~ɠ42P'ǬwN)elACޟ4m2uƵXJ_] ( Z*ν;e˃v,ڼ`$+cF"v,~zjF3l+ӗ9gS<dǴky&f& y,-55u`xPއgS_11=Roi߾X[xiNԻذ14ffE-T~bgyer7iXiso8ۦz55t.ZPe=Wν ^V d>RVJ+҂]"78U\?w{Qs[Ke721VFZbogs eÆ j?TUϸu綾kqu)KP +Kv"/[SF(S8+篨6֩^H ;ԌzekSSz}4ɅJk+䯿O0ihnxL[AIreϰܵ\/za!vA$\[ӗyb2B#bK<4ۆaqMjh]?%щ10m۶mSrXf f7ߢEE!4ȭo?&68uOB&'NOnayRdS[T۷tH^l_~U] ;l-˦G%!8cGGs'v$r]3W~ gΝYދ< jX5g[aƍC.^(#kݓeR]Wj8BO-j1o>V[>bofGbbfRׯ2H)ը~A8>XU6;Z$,(41zmꇹ6g(Ԃ8x 0'l@ϭ;է%?D{fb}S"?s̼嫗'<’;x̸NZg5Ї%lFls%sYr6E6 iu}.aG"WdG'W&T 3v \8f+^J}=Q xNg[!--MPr.]Y?x,;M2N,Y,,۶m[W,vrӷgr̷(OPЧeAf 157]sw2NjD-3߂Z 0=6\ƃY@`Q"N)XZ]7*"ԁ[[ 5 iK/ S"WBCa5G Mn]Uݹ7k5J^)el\w^XٌeA_IJDo1ji|?;!ev޽JLltH03n!=~zxDoP W%ASҭmdo4ڕVĦCz+W?6HzL\|Q$kj WSA MsƖ&Bh;wTc"|ğ=6\ƃY@`Q" *mVq[Cs^u∠omz#1t豣v<|>Kvk|YECC_E^H^A>rXʁb '**2y!^2n /sف2,/ m-9H+.+Q6ʦ#%ɭBǧgGJKՂ\i~yaӦM2Xm]U`bˤ&g&V_>q^*sn*赇~ۻ;T⛷o__xbmZ\9}Z)t ~(YfĔ;lGʬϵ5XG&F-5ywNpKm Dzg] cU]joNi[XHqCwy~hS;-G;ɔnq>X@tNҳS٪/u* /^m᱑;FWKs\,&7z!2kbwjՆZ5`|LZ;W"g. u~>9z妫H%:7æ9A33ebQ6=j^*{e\_62>:8:?<;'vtwknkihiY]WDzE3Ƨ&t)V~[\cK!wnqMy|0&c߽L]ōqظqcWzWSсsSԭtئ[vnNQS5f6\AESꁤCW=ysKbjٻGp+d,yV 9ճ:@ !G8ަz51Hl,J䇏V]aA7yrהo"L֕f]ZQj3>m;\n=]뒛xj?[=|V[|a~;l뷯<9&|`xФ2Ie%}(Nc\DޢiQ Ӯz% ߇eux &564$\_ZS_ji-"M|xptHH_vS]gtwj?͛7Gb$)ZSyN?X.=:۽CmSg$0%TTVV<!#'f&mp6lЏbxR#O"oih(==]5x[mcgˠ nq2U♅YQ6SUṥ]w5,@d&rVMM堡E[;!ɳ'n[@]"*g-z72H亾9=? 6rӗ-ΛJ^A׹*tKl†\d4 Ja[-)T_?.3vl:$ ( jj:555CS _%gmqbKzD".Y;F}Tnݝ&>v@j^~|WRwJ(e3ɭ;U)ϳךZKKP[baF\d`qbqO 8 &9|(u,c_"t,G]?}Zo'Nub\vrr qÆ *V<懬H-|;䱨 /%riYE}}0mqMjfXGGyޝ8Jjsavˌ(<%r}Uc Bmsh-RS_2BbQ6fٺƴ(,K䗮\R_ k%ͣo2^cVp[;5'[XԺ&#^r99 ѣʵ;!ĦD~}5 6Gւ},7N6M#G669f慨{q{ۍܹwWZg Z/ ٚ{вumƺh`]EqM3Y9 @c]" 꺚?=Z(cǎЬ +, _"\Hy#Cq[()Z[psӏG_VDBzYSS;wJ(e3f۶mJCXȅщ aѵQ5A]~m"p} vm9l3 pbbY[^ǎDkoXUi mkQV^.\F{3ͣqNܨ:8x :asFEeGHRr}mPWo ICnSeeXkjqꁓgϨ#X\]~{W쬌/_)Lt q=lnI09o_/~ڷOfVjgc=MQ(4uޛچ:3_Q]۷gĥ֨K)elͨ/ AM}mLfÎD~milȔÞtE9+m-Ԅ{ګHM QoZ<2=?(Xȥn)6vکWǁ'V-!U굌~\Ҡvϕyik39b]y+D"=z,J:@ &^Iu÷CcԢS ;V+җ\g/sO"TU]ua4瞽{._"꺚!}^Ti]r9r,:pηm۶֋zz#-}V^,,/YyaN)elay>:p ŽD4{WO{W?OٸqBT CMMM}WX"odf1^v+| >ח'syI4޿#d''# ~hS-oj}͟q>C+>?9z쨾k38< d+V}}IrͳڋRh?q<(8e/~շA8R;{{m\Ʒs@ĺD.nwFd~_{|:w~ ^~SUgAy~S[_yd?yK vWoȐ$k~i!gdiY_}Ƚh}qA&]R+N>VU~Mޛ7o߾˓%$;n(*)**-]x:RSSyܹw''7>gɸp>'JPP'u )wݽ{ 2ǎ3mu]Oy5Y~KtGOKa*_,ûv㺌iDZ^qOyJGe0yR=ĥ6ui.9 ؒ/_[߰'we447Fv2]XL^ $[WgBB!آQl5  bAĎ{ &w?׬ݳð~]{ܝݝsΜ2"`ɘFB}[Cfq_}sCe-?;b¤WcZ' . 6WMl|6>!11Q]ط MܹkLM5^e>|v:چ;Oꗪ>O%aWC~A-p[]]W*!;q1bL4MF!vn ; r״^S![əkī?m+vnǂMĨLc4qDKw2%ڰy57A`|\݁ȶ V]tzf0wբ53-\VTۉTS>f{^>wlnJOI{kZBaqW%2A=7nhљ0cUFkg[JK~1<ԙ;kkDY ʃ=fnwr &"/ve&|$%N;~ҖKNnNPHtCdוbbf2؇TtqrDCmĨLc4q$D cщ7SRV7l0F`;A u}=ogx+ShxnDvUT/|pYınߖwvMCl뺾̢5F"Wd,] nv~nljSFl|/pF~K&{G k97<~3269&=tfʫʻǦ&WꗿV^n F^qq`xwy;#_Iq)}{e I7p݀"Ky;R8weȫjjQl3&"pe&g} (榇 ()n9=Cw_b}" iiiѴwwH;&!wNyZ` ۲m2]UCbT1˸gȝ^?)'XbEZZںu6nڸ~vzo;ثWNMMdUPCr"GL-L@/SRR>4n>%_ܹ7"O_~; Ƣ J.>T]W7?65ogoPl("?}zѶk. "{v LVܵh䢈<99yܛɱށ涖ܜnuSD""x9ED("rG<QD""x9ED("rG<QD""x9ED("rG<QD""x9ED("rG<QD""x9ED("rG<QD""x9ED("rG<QD""x9ED("rG<QD""x9ED("rG<QD""x9ED("rGnȗ%'NL)D<wHWvu3."f'-_9寍~;$o7 3.PC\Cew:sZ+=O_<{|gO[jumڼ[dٻN+nh{=η mSks`ԸZ"زm+}#jɗ{*?tŬKro߹_p;/]ΒBv)e2p@-()oEgp7}FJ3k]g "o"hu]<}eطT'FRC!PP}Ykhn$"wcoY?#J>z=_$`%m7PWF{:|(nrR=Nu͗54:TU[Rn(Ӹ#;"߼SS(twսTj]ףUƣY󟁈<Dn455xUZT_B ;'ymZjD.SM7[<5"rT [G\߈5.2NU}={GRD"vDY)yoz%y_yswG b(5 jD;WZQig/65M <5s-5"GNޭ`LLLKIIQ_Cc#-Mu5563O>N#"^S/|_pV^ ܓzLM>{2xL$kg޹%e"'SȣF䏟<~T\d瑾2I@!ԐXP#r?̞^u^$8c5L\{L&9"l0k k֝x>99=cjz¢BvMDwیS*2rL$t*5}ecaE/:bu?3u>׷~+> ;_{WI+l8Yo#ck~W_%R|ǍS_,ʪ%"ޮĐ/9y?|;93eZ|YHrpwڏ?KOirH{U7ĩq:|xf@/e2u)M7KMt~ZWi0Dqd˅E4wyEynEAjȗ6.:侤c_wmȐw|eR["">1tt;yïbSE_KL'"׎wgOBvclsGo oݿ#w%@S<)]뗹)L!/8mGื$]tOZ-yiz_kW6}-m*{~D:_63;|XG.5֫~NwUD.[n5>*#dF##L:MQZQ@ik֮7c/st(/_Bwl0 Dx#'"v+Mo㦍?2f 7-PG]O_<˿_p9 ?s$O4VpaQaUm)ml Րȩ!qYCGo%e-y37hfgνuQ.-ijE 8 |O ,G-O? ;fD}U&F=;?-mSYDj<zajrZyUZb}=>~3˫,Z|zu[g{rrrی9P QjDn\uk2cA=Gbwl75W(,*<™sg7l9UqSct/8U]܅swc+V<<دJ7-U4ԙj wmDnZɤD8 }[b/"߶1 z>/?1Eh{"#H,aOwxtWRCבE%y7OڗS9(Ϸ.h̏??c}vU~S ƿ3m~w[=o\ۍN(W4Z?Gcog,F9>=a(&<*.WLWxIrepYZ: JtxZT2sv90<>[!މȩ!q~D;7~~#Iŏ?tՐbN#rB,wEMRn) T ?q|&g15;qA۫XPwԸz(yi|)ؾc<ͤ: OgOW4koNBd}oNMA?eCRMHx"k|/!#-hXaGӒ;p/8u om-A4cdO/_>k2ғ֎Ex8gګ/;ܢaroDny,w/#f;D|9"/rթ1աﱄ}EEu:P[yŭ=RgWŃf scᤡi[vF]W_0"[{_\utw'OM?Lu_pV]/{9Vҭ3Yϱn-,#`=RThK00ڗ6jd6ySD>TUDΥp7("/~Zl_ta*@vE(!7(Q*IeD#s]$Vw¼hߪs֦e=xϬ7} Օ@!kv"riMٵ{WКqZoٶ՘kMǺ wu\_X}RQ/#]W"3#r[4묷f+DƱȭV'ijLG7}|iyu7'K!SAM_G,]|KSJOFꆡEx>ou3ɹ6t"#CM"V3ڰr1Zc;N뇝Ï8~|:SP7n}~cM-jD=/NExfm955ND^Q]ieڽ|Ɣg[CŠ@'K2/8g9ψ[;Ziӏ\ƪ p ʺ#rW]C?nȭDYcκP^~,b49vꕐXߜ,XGViyfrh5B]Lν1m;ZF1.|<99Y}EeMeD_?Bb>nʈQpr6&ZT.C|f_D|iUTF_\ᆈUb uP0UTY^UogWBgq9;я%jliו(5b-XDbŊ5kuKcKͷTX]=y箝1!ałV;s?-B,W1"_/h]\ג2\cUА+ɳpuDn},}=;ݹwH^uÃ9Y DҬ-ڹ(TsFΜ;R5v-GTKohSW.#[NYMz'͵,z$"7-Yso̓TuQMgegW "}俽뒂CDk_I>]+.zRm묥=,*4f!.-TďtiQC!#K{+M1.zS#a]W>LKh!ał]nQN4 ,2vq/;1w^vo!rXk'O22pcS q=FYj<roz˂@N>Xߜ,"YHrϡÇttŮ%w_Fq"8rXJſPW.#ݷ7d5";K"\wQOv 4/]>s}nvӪ*7Sܹ|2Wd\^rbO?cOl~ZmA3rSD\EҗHbju?wo5}n͈H4[ ])[@cB F䅏%%%FRz`Ŧ3<0<;#3XJ!75UVW./}ϋ=yT\2=5$Xeak.]3Yd̓ՔށײM'ΗJ\c;,[e#XY[.mc!'B_7#gPo+z Rp "?}䒲?*Hm9G ~ =:*k,"runu%KʄyׯkO꩎3szl@PClF`"O-p8fW2"sqLϮ7]G 6XYSf4I-{x_YOoy*3 9#Rwpg213^h|¹OakJFB߬f5KxRC!6`)O 6YH'GFں%̹3ofi1Ţ_g4~3l뭽!4on{5 ZW'%un0#ን֡_kW lXu̓GWWɟQxIu 4y$~ɛnݩW"o{pDwO>\ҕ-OҪ~(dwrKXY5FRY#s{WzqBiyi4ĩlӯ'dM뾀,'ȿųe&,1^kH+}Cy"rj;k͈}ҳLLOL4Yo|zb֥իW4֐ иqXOc/\{"p[€ګHox£,p2O8:ԶԴᬫSA \X}7-^9S "˿_ºu[HCjSڿ3:t>H)`d-G{;q թb)+Svک^};9n6yjjzِeDLc?(LSVWɟ^֙6V(́}IE_6gG*3Jڸ}xm_9r[;t^m~ws. ? GQ҅kTT{Gi;?{yz/#cN8f^2:}=]bU[/؉c2*(it2)*){m %^ڙv,N8O-fS_l|$}RZв5]GSC!GϞ>~X9S#㣝=]/_{pOW5Dfzm[W >6NHWFXOb/\{}"~p\c=r쨺|kKzrV֕,rnPdmc셶ν1 gZ654J*G<*k/FY L)}+ I{|gOdqWNKKZRRRzU]$]^To]m%NQcASz \zԙ'O< ++*iH[EeM4Mywn_tA.v"o03?,ػC+>_d"߼SSRPdNJg 3]9w?y1feϰe8ikeUNBǃGꗯ>0A?b;{I2WNj10<违T̨}6=y,ㆉIye>)>v23nZ%E|M p?em5FjHRO޴yŬR`2Q~_-5$a u2/2S;21XCSYƔؿ Y-"rW*RFCHYVslcj9dk,Y6i/[<*k*F#N [rGˏ5N }7WT\DzL#BdFA0v&غ}[yUyW_ԄtӍ|e r+/bqP֣{_6l0%?;FVCEwDN H +"]CXȹi"k.yLMMNMG}-b9L߿0қ\yݸY}{d+k*\r=_GOgY.FD>4j+"VD[#y̔d@o' HAsO]p`4]/)cf)'3|pRE}۳w/MRe"33?|_3OeFx;H hͰKǺ%rԻXSeP$1io<22u)nRdy~XyZZtK+ʮ^˖+'È8ߋW]=p!깈I.c45B ˷%&&X"%%%}ez7XXaîݻ?z¹SgN9p^1IRISdHS9H "j߭/%V1Y*3v:xZV@oykj)kj5Bo(Sܖ>ugYG>{1WmOTg=սvr]RRR>4.[mʙE]A PC Xe v$:Y+;L?rh~mn͏/+ 7 PC!p,2eܖʂT;<+KGKCu5}cSo>vfnN}+V'߷PC!^F Xe  "p]kk~Sv_vh+wM,I 80asèƙ1?u?XZn9o7l,.m쫂eZ L֭͒[i柶nw=mz'ln83~c1Fr?2G \+zONN6^5׻D ,0F6j#!S |G yjjjiE񪴄ҊL0)B&l.83~c1FB D`4LG\WGpx!'l,0F7{0XLi[yd]bs!'lD`?%A-"/\0=a#"c$%)  r,p"r&`I2a#"c$%)`[nM׬]g srs,b{n;[efrlLIk~UZ7?>=1щ7s(y'NfVTW MN˦&Ǻz>vN^ƪւ] G[ߺG(p';W}C|N>ܴ<}=uO_<C֎6-_o46ξ~3RVYngz4ʚ`U߬Օ}-1l!M7}U2i{ OL[,#R$Ɩ&]j=xvZFG'gu+m{2rZ[Oo/ǵ^ifdi'_>y4 )?|~۾cTq٠͵AꛮcAćE]RI(-2R~u5 H-Dz"GEz Kj|Q'.{ 雹9ޡÇ4NKWܴڑRRWX0^G;~!GgOBB>oH ˤ,Ѡc-c!--MLo^.6#1%%e%d~!&f&ξs>7Yh:/P;׀k-_qݢ5EE^kK)-ӿ_~}$;̐[3"W%쀦$g4憱 iuÃܵS=7M bb׆M{wGșLȓ]xd";怵WkLַrO*'lzG5ڿ\J8Yr/sg]~c$-zJ=7:ߚe4rZ[Oc ?/?O=+jUU567NQ]}XزmPԇ|`޿v~ n#r)))7mzᑼVdaGws>,*4Sc63?[]W{;bE{yU7 ҍ)J+ʌ #δFGtig8o<)nEu{Lo* ^%3/f]}7ɴqnMo)o5kylڑQ_c(м]gQ"VX`p)Qzd&/;/#K){KT{Wyaȷnnk1_h,SEfD~iuFwk`T7~kw <{o|QDn{Y2FZ–\zN5kZ8>'tAokjOcxaڴG3Ve|/-:ߤ3i:z:+k*.|6vI"r]7-cA@)DL}`ekƶu Qq#lېה7 \/ :szk, NQRi7rj)hc~ epδT{x7MDR#x#72Oeٱ5,2ga1FZ\]ʹw/^0-F#yTc}y%k3>tr?CIy^[B'vƟϞVϚ_96~=熱Ai !# I:Y?֭뒲Rj="o8x`V\77?N>3N7 ^wkΗfVTWbMo"=~[o[2ݠ♅E3ggNxY߉z; 2;ah!#18,o]}35;-??X5 -q[mxtgrJ܌bMz{ܴzzv'ۊ+̸*[{o +Ӎh: VW^vnw~ 9r~\aup.kʜ_>WFu?UѴޚ anrlj|yM<Šj )XOiY۔[;m7KO9L$`s+|-Sm m'g N&Y'E2 RO:{snft3>=VN2g0E4zToxwTZGE=,ݒߤ>Ox֖{Dd䆖dGVgŎ}'i\FlkZoŦ¥ \C[Dnahrv8Ғۯk ;ݻº˘30+"W;`vR ~~rvZozO[[R mܴqiTc">0pB\~dٟ&$VU`W^vXX ""پzʚJƣ@ vyK#%?zBeiuΌbS]F'O_y9o})Y4CcuEv,tt YNڝ{w-t֌+fo3#R,Lp@|F:L?vȲ@9ƢKpVDr2I)zO->Z'nb~NF+\;`c$IWo%s!o?xj2IqQYSegyUSsʊ1n#<יb!;/Qo)z# s1n*357MćǦƍWY?S=-Zh?)35Nv#}O=M),/[AOtߤ$5&?l4nRwYm^0Ϧ 2 6oRoGVXPBh2_$3Rb c!HywZ e7>MF]Ƨ'CҎߤ13.\ok%c; F_eŝ#XD~S^S#|D=kx 3lv=5m2 :S"8;ubO?5t5;&vQ?-#3#Q pzf4m-tv 0}L&gΪwv2"ono ylJaɧh%&&f,~]>ؔlo'e}c[i)DZ D<&Ϧ Yn~eGVX"vnrD3<.gz+/יFCo /jS5"TQu)U^%t{*ic]֒1{H20ޱ'Z+0PXCǂoTK/ɞ{}kK:oUPXk.Gֵי&6o zܙ_{߽̿_`\_i7-XSE^o`D䮺]qe<R9 Ly `w&|,[ZZâBX?.f] +DžpzR[Q)o&"u}6m}{Q#+IL,L9M;jU8W;t-S7Gڿ|D7{-Mn֒1HƏ?U-=:;b7"Mcџje<5[z=KdhTWV?o y='d,j%ǫײzf֕,Sfg|ɆG#ի@~-={{=^GFMM 5 W./}ϋ=yT\XR|D.̔’Oܿd2fDDgnlSP'M]}ݓ3Ssdr\l'bQ Is[WXilC仨+,GYo]~d5)m<ͩt2q[uSYs3cSj6/XvБaz^9#r%c$W-եokS!摃ވ\7Q}/7Q=عYM>F~:|'OיgjJ@9ݹ@>ܹkj?͊~ uE3ͱs2"on0lJ)m&gJI8{x?؍zJ+B'`W6nO~aL7j1lې6O~@YaM;/|`SgN%"[1ۨ7'T,(Z8Z;LGz4viF*uޓ1d~<13~Ȭ//Ѡ3-cA=%|rfK>0^rn_RچP|ϸϠ2.snz5_!O_< xbR'OFcSOi]ܸF)*?77u<'3?\"2ޚRоLss|6m#K9''V4)IQw!|=ce ҘI7Xz ""ONNVk|zb-[{WXzm-#gQ^9<}A9=Q2l:n2v7l082%owl7f vvhЙOSغ}a t&g߯T_+{|OvU=e'>#r݅@;qL_\[jp_*C UV(JAM=eo;ڐd]׸0>A!Hfŏ8McbfFL7 [$"uk;J71vX"55U nLG5zED.vޥK>~sz/;#r%c$WYa }L_&f9 ڿi({O ݝ1g]RW2ojp~SGk}\8tfEv,,, ''''O}%؏MZPgrPh,(t@X?"T\#c\ ERny9wՕ 'Υ4韛[…!GnK5262n>v& U4f^ y*si?6ȱƵ6׬e\"ٟyklb߿_]WNYCSeq¦}TZGFMu2r[JJzy4o,!O_wX|DѷFo%ZcA~a ڿiL({}0X,Ѡ-cㅵVfmr5(j=7~G}?"y]@,#q+u ng}9ߕ#7?/gE3XrmA]R:`O۸iqf_JB)h,?7 Y߻wnMkq+e*"lDq+8E piڦmjޯ'}4m۷]|s87j*-Z0(炻m^CVO)&O:cbb;|EPtVʈzG<8}Xj?/,TƆn#rU jxS|B5UZP՚ܼNcq/{w{G2۽/;!?m _O}|pX1,̪ĤDes7 ,̅`wm!s}ҕ[{qV(WNq¸ř<=?#p5?//M(-M3"Wue,=zT&455n^ĤD:y(_~Y0-e5?Cj[BZ|пgHAIT~\NB{G+_>~~sZsKsM}_W*7oj}]zk˵ښ WsY9wFrqxUK qtdoԂ`9'h'wȈ[YeG]݆bMn.Ĩ|͓݉J3 ?n*Je ֩?!y ٪@sKmCԢb+P}z b4PldBBG_|In?"+cԴ{'"_टa1Ƶ')1[]cqSqq#jGȍ:n#޲Y;~'=&1󝃱m=2VxeJOqjp2gꪋ zсȍ:{zy"""Ƥm VRTRT[_+J.4#\U1mժk呈l9;$@D{/N ".mNי2͂X;xϋ8œ2 ["TH湷TQBwcC |?5]:w Cqn:9%nO`>{-x^]cCn揶 UѳZp;©ӧ8Y:7/W^Ez?Ҷ YC" Ӽx8~'?*hOţقs)Q8.éiEcA=8<3%ebs F-xW ~z3q{)H^=mvM7P{d|TW-Z0oSX~TT|1Ҵ-KjN.ԩrk$74݁c^*r2t4(]UrsܸG $yRV/4睃?އ~j컌Ei\[ۻܼ\jG7yFW%k¯w7?Qb;{)!*D>~tL*Aex|$7ЊTU_{e䘸j/.,./}2lm&>~eQ4 q F<Um_|7juهEOj jZqڽYœ82FabjRD5xNCm;3pqQcKG-rܼxiOY;o\׷b;3GWKsC]omy5/8i?//~wգT)qYs;VxW^?]gGyBB'x|b ?)Ĉ$Ǎ{k跦sҨO}~1j&3 F[kމ| 9nŅ{*^%{M䢁A_ۂ&l_[X45^)e^ ;z7Ny7'`111-7Ǘ~gR?$"r?"rOi4fPi'98-I'O75 6j[9>e8cҗ_+.s|`~y{ NYu߹Ts+#"# " 70',̞/KVw D`GkVثQn#kcyXGD|rޣ7,+L9LśK؆h/yiwTXpEm^V}7k,ɸj9z/ʍm{8P {&G׹}ɇKˢyFԘx1W/]c{j:&gp@py/Q*]g"f9?ߛ봿3~JIywdךkVǼLZztyIܗYÝUDJ; }9.v=ʆ@pEglnqK˱{ s@yz"Gt ,%eZ}]u./Ld<.rw6ĺz1*818IOL4"?y0b>Hv=-qzq:ũ@)(-$d`&"~n>is>g#\y݆S]I.zv$#"&У q{n=x4r?@;E>n[rG's@PxF{#KTcdg9]џg cWktlw?YnXOM0>o }./?t{v N]ybzצpՀN,3iK.#mTyyyU&/,߽(YڮD[Q FiFiFiFiFiFiFiҼǪfT~%`gy(MUaJ,:v3i,QG3?p6|KWDjO\:y{GT+T|0k lu#֬R0~e_#/:yUD>ħU̥NpwD~-IV\Z܈m@,s((-`0dHD.uS FiFiFiFiFiFiFiҼțrdߪ\Ji6~Ku'9S]vxiM&*Ug{Do32v?I%"AOPd nb9tQQ"]QQQQQQQ4#ʳr*iF,oÑ1eZŒo_gj_DrijjMUO3f&<<|qym34JfT>.9QQQQQQQ4"9k(V[|y[ǿ͢i`Ƿȳb gK*}ۙ4Օ4,y'˔ԯiI}$]5Xn%/"hE]4˗((-0>1*"߱Z4J4J4J4J4J4J4JSD7-k޻{=۝<{X/Z}͎Pe-ҌEK{Ћ3"rx7l߱(S;#ⓩJ4J4J4J4J4J4J4J XiG䪱57~[Al W:\{soZ4J4J4J4J4J4J4J@}D.=nƮ\=3r7OإWE[vo%IiFi+mkQ!޺QQQQQQQ4"__i֌)~q2k۸bPu.J^0J.5l_]/"rm{y3^oD_~>}\[_KiFi,mm]cG J4J4J4J4J4J4J4J piG}r{P?Nh_.~kJ4J4J4J4J4J4J4J |i^Fϓ4VI_>224X:{9\O]x=,">4h^H:5סW'x=Ht,ʯC{bgz讷<~&/4Nm8UhXg85~0"/#PYs!J{S$i /Rv&w:ǢbW':YhdUM)~T~, ׈0^O|4Бz8Mz)]+mͻkl_Ãs@y;McOF$Ʈv>mg2cWOu,0եN>Ny~#KnE%m8Yj#/s:zP=57J!&eD^xl l~|N5GOՙejwRyFo3Nί0URty٦w&Ϸ' d:bU=is[xUM#Nӎz:cDxq}r< ێ^GU wctrD>i6eDqi۱5JWuč=HMy(6ȉ-v:4/:d\9tG#]8ӝJg93rDqn6eDY?G])p"W_<9z?QbE,)k8<`vD$Ʈ>kMYƒuC'zۖ?Fsv@ "e#A82 "r]6ܑ "r]:*c4Gһ[fq R[嘌5>m8#`QD""X9ED("rE,`QD""X9ED("rE,`QD""X9ED("rE,`QD""X9ED("rE,`QD""X9ED("rE,`QD""X9ED("rE,`QD""X9ED("rE,`QD""X9ED("rE,`QD""X9ED("rE,`QD䀹~g굫%\[=IMM VM=W\Zrr!HE|B&ODGG`223o ǐCDH۷kV\,|V&MP@UTVH?yBfU˞}{UG&5Q!03pE_NM] H+"r2Vd\:a"""2226oSν:|hpx@Q#Mꠠ f^$Df]y@sJ!9s8AA-+g8IxGwbTWR,"r>"H09"G%/NU@D`œG-ӭ2gSK " ]D"PDD"r ZW$9SK_~\,)ɜ " D "G0pE/">tP^"u]>n|o@F; 9D? j\`׬YY\ZLDGD "G0pE'$g]_ O4WJDoiL/)+=sVQc~۲m+'#"H0"ܼs .]Z9]C\Oʛ n51!zȍj{f[3WG吸DDà f`TD{^D%uj<壓c&99sffm[55~ݜ!N0pEW ##;W̾fG:_zR4ٵkKm8`b_xĞ*]>ڻM="ڎʘٞPPIv:f~뿽IgB khN0pEo߱:Rf8 /|YIo ZW$QybR"93cD~|O]Dq"b51#5IXg}rۍinLq{КJC%$/eax\ajtb{wk b %6̋A6N= AڲqF>~j^:>St)״TaTD.<޿+ZY&q]||ܼ\D^ۼe8o?z .eqMq2yATs,͐R;˂^ž{= *QmR@\\\QQQo\olnmuzԙSmڴ)0ɅEm۱]ˋ/x" Pt\|qؘ#GWUUW96$jl9o>s7֋+//Vfc^ 6nƋ嗮T]^SrlݢwV[P_wW*jjirrgNϣO>)dsKڋO l`+R܊+;כjjJ״<2*ԙӢ}{)Y"r@D~ h˶x=)oiFk弪:`=$ xo[lv 'txkԡ=m7[Qn~F)i_t]* | %n:|hxlU!>]xAWU}P,xKm^O^ ܼ/95N\ ¶۔5ka)"Sv>JE*_ĭc9J,&j f"x f9.SN kvjxDʽ=%?zqnBxxq@edaSdddTx:'[e'ϟ:вyfh=7p633U!כin˗\Jyͬ]Ybo\WVpL~Rvic` ( ĕj+" mgQXXXAyՖ\q}ێ6nT7eihjp`-?Q-U׮nݾ-`g۩+鏈f]џA煮GIyי=; eѷ<{ 鉫Z ‰/رzJUstbL\H?b5(ei檄/g>}Ytwu/t[IMg*/o7]-_ @el-xiE;UtK|X\^r{P[0Ovʚ8vE5it'7bvJDnNyG8^TS~㔞Uyu uZp@SQi3s/x$=wo'؁zm@Rme4JYy(XZXtqb7sK˝]V$Xx6cXfc[ Fقsr9ݒfҾ3[[0OSvE5it'7bvJD"D Iy&݄TDZ>qއYl:{S~JsۖG.:fڈ|yo\Lwr(16"֬ү^t//n?ivU(iS\_[|Yv/)+{!/ڝ'xN{GG<otrL<eV鍂'J![[\sTk[ w={ˢ_tii{Ż[V^R*؛75hk}\y NC x0ORGwSENG'nhi~z;j f"=eֶ֢)74ZgΝ kvj{Zg\\?[pVUW*O:y3*F^ moZh(V\Zo$+ HIE%EwS}cZ~T>۹+KcMqIQ˗^ uJ^.Ql87}sMc)YQw:7C6[UW/ nT<6e FwU+S;v#k<7?O펟"ֻ`yj rEQdTdcs2hvʯLTk۩ 9ȈVBչ(B~! hĚU ~ew{~tzԔh1"}D^֗ WYKu5pnI%l/9]RSߓw &ѕQ ff>:Hft۔׼{tNM^ >2<_QVv [Zo(PT(jᄑ"r9v*W$hkU NX3"7ϝCjjeHf#O/4+Q>:::u!^#w/s Kq̏!JNG&ȫ}O}M09%Y@Wbb=lji^S(BхשkGւGy!yowf}̙*cj rEQnJq7{bRܑ|#(..uEQwU$}nxqD~!G؛!"X8y}ky==3=ݼ摣G>%Sׂ "riVm=v<(E&o rE2GxǏr<`cGz#(G]]}Z'ڽ(<殛@FCwrCs$ByZxL:V@.?ɩʣMH6I9c!垰(zs4F]%#NM^ >2< )+-8mcsʠ ӯy-111LSDiS&o6֌cpxH9 $L/ˮ 'bvc4""BV#;_0:16q0Q_{:gCzmeR>9WrCA Q75(iy>@|d`-x_\. W巪)MB{2Y.Ov65y;!"GP?"߳E]Τgs=)^,~LM#V徿Ά)N{zpWN}z^2RS/޺18<$`[&&WAdToXXXYEo51-)+Q^aFy^̬+F4ŐۿLjܗ:5y-Z0"jBNHJ߽ɿ."7y[+/lݢlE}38tP{`^Snlv ף$Ʈw-4E$yݚ3{NPɽ[vx_-ƨ.)e?N3Έ竡S-UG9"rgk#co'߿;q§/%7/mrG`U = M/_#kBuZ򯼋MB  +/5׳픈<222V cSO)e9k%x‹W/^!(6e:JLJmVnի~k5lrG9"rgk٥+.WT^ZuZںFq `[6o|#pjZW+Tf`Dnl|d`-x_,$6S#oU_&."7y[+/ %Sye^S"r Pq:җpYo[?C2"elOs' =Sxpka?kDǞEiS\w'OHH*kX{))+ w:wq3tn|䵿?"G|d`-x˽ oSV^="7y[+<ܼ\픈<gVkU>Y?16NG)jfac_`011)1DOZW jm?&#j:8##wxkڵDSׂՂG: G&o rEE4xQIl޲YyIӈܸ픈 'u쿕9>AM1mZ.F7D_;uLdYZӇ"]#!=ďUTݖ NxX+Jk*!vnD֜uVG'AG'DO{yP3)bM6ܼ'/,OM~eo˞ |0D.wzɱx?zoՋW/Ş>yQܽ֝SnsNӠ\HǬ<|㺫?"r|ᄑƾW*4vw=`Tt{-x2!ms5wwB\|qQhex|DK^O?kvjCDD6oRQc>nBٓ#˿~U>?)q3OƽX^R;6ol{boj8{we .(P+ ?rʩg^WXbbb|Z&+kDj c:|܉E&o rEŞ}{.WdKǍ45y;!"GP?"OMXxpҞ_(vOk -/Ջ9y0"Xk%;G#B EGG+=cWc" 1|3]S/xqC |Z&_+jAFHWZm+"r3"B>yAK(/kkvjCDD_~YVE><2v=]ϺgӞg_p|o]lLLO m͚5II:Q}2CD.ψQS nڼӗ}fcb}Z&X rD^SWw^yه_y-r]Hq%Gr5y;!"GP?"T1ǥ ՚O2&~X+EG g]_pmzmڨ9kR %ǭV{!6 <TqBkݥWRrj#[ޓ˗~Em}+֩ӧ<}xW^eV_-֩kGBrJ#>y@*ԑwB]|4ve0܌I;"45y;!".ҩSYڐboؠ|z4ߩ^TR֩hw M!5řs+A'aH:ڈ-[mU^1ȘkmZ|Z&X QQ+Ci߸=paqF/EG{-GWm8}k֬iln\]sg۩ 9(J5-J~7׹LjbtV2dEf{8i#MN)߳%\5+=)bu&lK}%Iw'[l slK@ԂPxlmύ51~[D-t6ľ//t7Ż$&$w_țo V d 5=?#]wm4'KJSSS}9X]z򝻲|]fX |T\77Z?ؖf*ܴm!H>{yowbzܼ\e/,k|`kvj$w6_\gfa;+{=#چgOMd-yT<4#}z  ;tINLN`/;_SN=lK}c{h_daq.-שkȈ<אQNr! mc4Q"rӶк"H[,℉Rwؼesm'54>r05s;rDn;"GBlGnYL|cP~.]9_zKG˶< @-U1PٝׯKAz;)?D-~]zSGC>|4Wzz@JP z!}ZmMEew>~~sZsKsM}ί$?^yrPTK8t.>̵``Dc-wA {[z+ůbo547-Gc҈=\4R[_+)okvjlD.5qHqSgN>"7ɝ"7?oC>1gt6ԉf[TR\\Z,Iޜz%T\tRS~Ͼ8(f>>5T-bv+s}| 5=lݶU NHܽg"Ѻzes=Q>KvwovnIsqy|L3#TT:.SO.Up]jC"·*jcoD=Ua{Z-)hX<;/cQJEYEq[{id#/wZ; j/N'&=G8~3q|xr>hwoXf#rCkAxwc*REʈۆFl uE TEl ?{MNȇF^;30yGNeW?Fo-쨊V<uEQc*Cߪ6nIĚU!Y6ZxvcMx7F4ĆCJV/ 7ETΜ;|㺫 @Y0b@j^ﲆkL"6FqٻrvBA4`4zrMvVQujZoDnD-DEG+m}㧟>{F#"1:"7a[+Vt8roqRU]Uǻgi۩I"gy+V_~e]wK؃ޔ~I{<;{%99r?q7O}ۙzy䕬yyLwr_[|{xyG#]( YHc4t['k磴^okɿ 5.^+}ݴK~{#$:;ɶyZ;ӹjcE&t68MYwu=Y˲x'g ʫ{q`^RoƆ=cg?~]Sq&O_\X\^䢏/5a[eͽoRfV;4}ۗ/Ů Z p88zObGt-G߾wgy K?y^TRܳׄ$yôyq\m(VQW`* AtcDZĉvsqr}}5],^p؝9̙gh -sHcbj2Y2lb]xl*)`${PSCN~'Ԉ\]'_Vq~:Ki}zyB0=i8]^'c ֖Թם o۝J IIfjKPƵcw=P  EQ2Z9FȂ1JyKHTEBqw8,TDhSעq_'GB<|tl4cWU$"D\P^ux|<,(LS".,.#. +--- BNNw-{s PP2@"H䕊zTDh\d5b6  9$rPH =r?U e^C:m;mH~s Lۼnɸ@"T >_asskk{pȷwwD ෺\Lst|,%{y(7xirK6EQR5y\o؜vV#SX H@8jIm ٩{ cp4,u~its#eÚmR+`fQ 7ҷ|~ĥ麺+V" OQnGOoTH,rjphPi'<ՉyؑS~5*MfKKK-SsGG9ˋJXxyEu<,v15pXT~soo/?4c0i39G|W³Y¡2U>?7޼ςg)U;{tonPUV̫&^M5XNX6tYEƫq%t.k^%2$[:8:BFb[u^H%OI QX{ a6൐J $YJn|&N>~LlVjsr6mzv'7Ǩ4J,R1SU[FӖ)3nDlӔL-/78Nh8v˼?yNwO7?>" aWK Ӧ?pIMf8;(.%Av>]SI;؞3eۭgf^0>UVŸ(<  \?SH³Y@OMd'AM!WWEضU,s\'-&*+yHMammmKɋo{!p"Ur'wt|t>褼7gQX,4O-Ju~\NίONC&uAZNkK"ko"v-{R94J /=>OWW6:zEIA-19_Oνc1=ɼEѡaWzㅒ8\vq㲑&VmyuzcuEJMMMt Oh < *T-:~?x< %UNʂp,ŠH̓2յ՜vwkY|AzOZ|UU"Op耢 ĕvÖ"@{ߪ*B"/97㶒%uۧ G&+ #q>\2.o|6+-J&f'&)Tڔ[bǽk٥K&':LD淚wl6jnln.zQ SZyCjɋN{y)\H乩Os~BDk[7ch8-%?)F$#gy,x6ŏ5‹~v2< U>+7G' mkb fܗs'~-Y|$r!O*$*S12=qxd!W oo[^HEָjDrjPj"obBA![oM#AwO7#٩jNwkc~AjŪ:/1g|TV/#ɣ ho(J"]vv X"Ier-slےq)3p¡!5j?yjf: Td"`[[+O*ȩNK+< g;PWJjςg)t.:(_\`3݃şC_)G #{kZ2'/- Hxp8JE^Ue;[&| QV~&0UOe@"%r"9cyu%vK /&~_n6wbrp![5*"IFF* Ar빦&٤LTIE#@"ho(J"0mt=(&ԖzL֟ձWOarM""2=8;sn~.9F)PONfpy2*wg4,^J a,xV{?m*z l碛^GAIdiB_.ҳBV*U"{K(̩ۙbatEڛV%R$REr8J97X]Jfk3^YagLS>sr>S.mBy\b #%K@{@ySSSŻro"/U<^}}=߉VE--̙lA( RC}/1]kV&hXm3*&rT]]'V'Y,m<+mςg$kne})߹OW>z+aUXϗ%$rJco e;,U2H{ߪDBDR$O߰u*&٤|*~q__9l~ 5WWz:N`NίRovXyٽTvvv%V&31H TKZjEB*iZr`^<"-zL%9\MOOO^{;(\`dlɭ5@g,~피'gYK766?e wondZV(LSvM)ӥ+ijBV*R"{K+s] oU_`@"%r"y[dBM uB2kY^3Y YRkK? ӳ3*O"^ԼcV>WD.@5PD~>>Nϸ)0Xޥ ڄ:A>Pnw pRA)lfU0&w=ݪiN< ?94 DςgiY[ʶ9eXY*5:D~Z%+V͡cQD>;?Ǯyz~*8RVx'=K/dR[ZَUj[U75[58DL..S(XJp]~{ƬCq22G'YN`NN!״=$+L"WNV&4:ho(\"gS\-/& mR)(ia~q`| E"fiX^z.s=cҳ&^z@^]/C# FDN>S',x6ů+uۉQ|x|ςgrlJVR[v:vL8N02].y=9KaM%䭴b$rܬ؛ Hek%/ikֺNr,&aV'X4.GKÞ#&r+L"WNEicc#$ s9Yho(P"onnf㑞prvd0°atCd;===EB]n]7J;ÿwrvBQx/|Աz _"(Wė j %;:󜗝C³Yy?`goWd~}ςgeQ AAJzߥ*6N\^YV~YQ%ʐvc }ߪD^־Y$! ܊ iGַ;:o*\zbbj2Jh_tDd)0'_Y]a]X\Tau*Ol 6ys TJ}\XWh w!;^ Son1j3.VɬHˏB+i[ςgiY9Ǯp決 ςg¯6WJ"t2c/ikkcZ榝6o=K/Qy[Znph0/M{S)U|}MHn0LfS0Th3W.&nufj|ehxH[IFT(Y ɗ9|Ѹ$r5TR%G9GK-.@5PDǬ{p-gzfZQS`T^խ]rKp"VLOj3q?JM?s328ԓ}ԍHM>8*visڭv~tײmΫZ%rޖrAςgiY\|` eCY*,~|eHISS9wyy?D:^W9aN{YSWD.6VL?`׉"H>l`]b>'РBxo@P}Y1YS; \.=WBc9]ڳXYZ¯!1ӂggQw?IIۻ;n.uKZHzz Q0u^Tɳ k{K+YRVjoŷj@ϪgCd~۴">E|z3-/Ȝߑ2m ըSQ~ʿ#rIR o:{H4c>%ɇP4,SK\r*L %vu_fobmpaG-7Zۈ\".YRyLҴekxbϱH&Mqzy,ёT`ZԶ7[eg5 HzCD^g?t=?8"9 {}#GQWn(6, DvktIjԩ@?:ho(\";E{b#5M)^pǶc/ +8:~],~W|:J'5BAo_ZJTn|ln -jY{zK"<  O:14jt?)[³YZfHt6rvn0@cx<:=zG>3[7 YrEѳ?^zE"ԣcE^)LVQɳ k{Փf4Tg>S=7[eg5IS"[ kEJx?2mߨY/I|OlM Cſ0zK|Pd)*'kM5RTl xYn֡tY~:rNV^@Qym~-JUʖf阝Z!hۻ dY^Y2Y6ַgQ'?prj ~hmcmѸD 9[j^  %bѳfl>L@8mNBQr#Umi9ONp|u }?G~O ςgiYInߌO& óYUY$uKʽͺs{IVģ 4;mɼ|8p]'a'{Փn(zR[rxI^^&ToTDvA ᳲC 8=ݽ/^`#H7V/Nkkkիc9_X\ZY]ɗ$d)RTN~K"PK": _ҭRGffnvvn5s򊽠9?0yMUoEuZ9[_G@ԍ (R\L275>mAd- *^knX\Jkhll,W> @5o³Y,%Ci)П(c(,xVuzUwvAeD<9O,zaNS9"HQ򘞙V ,@ux,]zQW=\޾r\VYMծEv;zK;x /weGVM(0j[깭r_]NH-wuu/Y nI*IYj7m(d(~#JA)%$#FNY~>b0'&_)?df:u'ΜsOVyeߞoR̞T8LJi]̣)G9gn;i$[C[Åςgi]&nGje7x< %,)Ţ|}oO>S/GϢ5MT~M~`8[b=K/f WzP D.jj{}W N4o{V"CSXh#}IommYri[꼭ԗJZ9oIHBrr^"_WS":U\XZ b/KNJ,'0mN]^f$Q.AM +q,.%tE"md%梱`Cd׉HI[&:\t֓Zvo.ҋcKݪmbd(ɲȜ_m}SET:,Qy~ct{URzɆZ'&{doeRJioب}@.JјL\ (Wun,P.Y yyAEQo}}}^Eb FQ,XTL:j%jPNQ@o+ %{< Q*Wg566En;D Y4@)(H F"WXhs(vYBD^I/TD Y4@)(H F"%OPpR=}c<*jdtVPBJȸE)@bD $rϒ"G@ b%,*X"ub~a&P/oms֪]ut缓`$TD Y4@)(H @"L4?PX"=m$^"Wrl64+B- #B Pu 9(Ԗ?}܀< +ճ../q{`<W"feR=h:SP<?7?3zgU 'g'VLȰwQQC"GƅN$r_(gvNN HHHHHHHHHHHHHH%2 z/@,m``l| @YGvv|>ߊģ&I'km}r"! "!"!"ͨLt0z,-Q @8Ȍdvn` jlb @9Fu^gagģ?u)4)n(p4̌dtl`D@=_X4.j#H!"AsHHHUEeJPơ555uuu{UIOoz"9|h%zPGRëGWXP'ϮA  @DBDBD**S"n`hnnnmk|}VqoW ͮu>謤~}{D@ _%ݶ::>|YoϮAJ}}sf\x6h<^x+FsHHHH%t8!ܟAyԣ]'춶nJ޽{2'OL鋈eUNɭ +4PFF/Ulu}UΥAJs;qk9D$D$D$DÆG3O8KYi~3>sTR@ƳOٜ珎Y+aBi 0"9"yDr&0ymm-哮SW0$s%K G9#pEwtvPD׉^f,d[t޾ QWgֳ4ԈجavnЗNQ=eٳX^Y b$ٙn qȬC)9l !c&c:=<>b{Qw:#P?0E2[mEb*[JVv?X᠔geι ^ۛC"J*bjz @s EFKkٷԎᮁ=,buuu2'n=b5tS;{tonTɈkocH&\\^H5UG2R ̢;:;eO\'2A"ҭgBzm~BlVh2oGTE5BBDBDBDBDb$MwmYKJyG ŒcAٍUD#Pǯ D)b"P g޾%M2R>vhK+LLSȩɝoeD'P4|#leE<OU5Z Av>]SSs;؜vy2 ~zj߬";O\### 71Dx$%r%Immڑ\x|O}GꠚG$WQ,zX_QI fy\nSnJtZ""""$r9(; V^xײe2oxptHT&Pgw-#1բ}d\8 LJ"M4h ۯg U{{i# ]g':ӮF}ݝ-%ǞVK!c+6:)QĐji %1KDkg{u= EB?Kx }.Hh)z|HW.GJh\6ڌ%VmyuDAW[ENʫUiR"9"9"R b8pjfvfumyJ3fr ",/^ewm}d6Qn3Dr5VlO 7Fvw[n ĨwI!b|Q%tAFn{ \9`yƕeyB˩)W} T}|g jY;{;P4Jqૣ@do_7ʝ{A͖>-ߢ<>2>1֪"yIHIlzҗ}FIֶYʌՎY`}e{w;gf}[K4~z|ΎxWc7dHH^\C|SE1dʌ#GݪɅgѷ ˑr"OD$A/ѮreWSiT7㕶=edNmvcײ8 \!EIMCC N, Q2$r%<~ /SᗰN;R۝MLM1V(`rOeHD d;{w{$rOJ"Q&^h|6mɸ*Z`>wQK/U|6~Ңږl {{ps+ۛ1Hwke9drQHݿf9g],(_l/Qm\`gҳ %ZZZؤ"*"@V^lߜ&&]QA     y^.S 3֪@o B37K< %NM-n[ #nWXIvk\ʆit=(:jKaYGF=mea]~'=8;s.cI$e0˯Lm]ּx(n'_d,d'NJh"@{S5ɕwkyk%y-*Ϣٜ ۗڲoQƪ-YzĖ[yϩ~G;$is *A    J(\"Щ) nwk;p7j<߬)sS 0·:guMN2j^-ka}Uy:RKSx+D~MV6QǛ}E-qɣea˧P-̙li?r7z 5|$ ji=yr3Fɵ7UcH\y`04/*̢X".A׮H.6sгgYXnlAxYY[yK&k}6RHHHH IQ x>~¼ڷ{>7*v`f*EBRHUS~L +Ģq)5 Vy^̥ȹԄ_Yg kmm-5YHjaGiWD'MBJymqL) |pccb{K"B$G$G$Ԏ!EF򼺵r9wzzfѬ3L'_pc#*sйgV[n)2ʭ|;5i^csKu*H!"!"!"!"UEIcc6NP~K;vʹQp*Aa7 -mLNUt28mW*Ve De0yI8qe ga=m e X.U$ȉ7780wGyttdfkR NVap++k؛1HWm[cJh8iUGs=E$/Q=Krs{"}mEcoU ~ŻËI==Ɩg'FRHHHH IQ91puDrl;lhh05kkk]g46jȓ A./ ^9::]]OQUB(VV; E^*OTj@%ٌ%[ﭰ=5"bҫzD@^1YpwS4m*VLܒ e"9"yyٛ1HWvvnհ|P49VU"H.$sйgVXnLLy0'HA=8:q*\KEQ w K{4cRhDEQNMMΙ2r3;ҎTHwyOlpo1urPnNrrV{PH^)D줒ϋ-13ܩ8iojǐ"#y^.zEela$*Ef:, Rˍ8Vvy R#`{ߔߟ""""H#F"ORWWv d$Q] rnnsON<>\>D@"u Ը~JXy~{>,waݿvOSMg+,V;-[//gH<*3]"'s%Cu{ݔ RJp9vb)(ۻ;[f]\Lܞ4 %LNMP$A$G$G$Ԏ!EF򼺵)}v@E8IG5ɋtYO xAj)j; df bݾA    ")sKffl*.Y,*$rKlaSNF)MXV`(x-r fmJVS5Za~J?YsrEnGQ |ݥ)aO' "9"nMRd$ϫ[cWH/zfN k "y=Kr7',4bGR\ ԦiSf ]Cqy2 xTY_?qR?::;d.ˋij`\n<`!NK+,]A*Ga&u~bK9w~٨w;284(ޡGK-6z>\cAm%7\{S;Z,^UWx͏ o5ɋtY?ڀ+ \z)7]\f/]-()^RHHHH@ %+s.٥TyuEձpܴeZOutQUv*Vx܅~ād/DO9)`iQ.BXhnp/wv԰Z%jk%yrz~DXEeHH^\CyukpPH.<˯*DH^Lsҭ %::;إ≸̚uIT)HQ LOLN;RZڵYBDBDBDBDԕ^qXj4 ?&\=ƱkI&˱l'P?ʅkY + 'zυAmOjGW-n7؝#cGzK?ZtX Py,ёTj`p@f+vvv +οNv:!"9"yUٛ1H[]˞ڑ\xkmmm2WL"G$/&sйgV[nb9A KbVr^Y \!KD{|9&v^^L%Zz>UWW׫5|pax^jkVWW+555+! O_;Բ-{yql;V>. ?|l|L2-U^~D$%% b$3p䅕nK+Ԏ!EFrZFhl+<Rae[4ɋYĖQ})Huts;K9+ko K|,alnjNZļqz c_?{b g\鬍:!4+P2}R.}KPإSOONkmm3͢$-Gjxٌh7xP__ǒ9%W+bahxrx|g&ZtDN,:iojǐ"#nֶ95W Ϣΰ|\KAϞgAJ`򞷃2ӪNYD<@ ֣ nRK H!"!"!"!"!"iySS"q{w'N!euNuĮbZv7[~88:,rI&JߓF<?}CC. T';SSj)֮AXy?!_a%HvӝSt̉LSI R&)z6LJ.凨-U^rIVxMSy۷9G334~هLJlu#QZ*dG$G$a$H{S;n\ G֔8 \ ϢoI"#9ֳ,H- γVײhRTipV \.jS.,.洷kGRiy)D$D$D$D$D$)\"oLqzt%*QLn2^O=N3#?0b+ԭ%'gxS8=ǘPN} xWC^Ld0Jb vy9lTtPcw)B~ P`V6 6 KX!嫆{t56+eW+kd0T tH6>?nnvEOA,_brj*g-ն#y7lgN1KVk t{ݔ7X,tچicyu]kjjy9 ~\H^v )2ڛ=BjYvvBhjE]#WU$W#sЧg\Xn#iG:37;;7K*?C"-;Ajd&Ŵu>=ϩ= cDKq$F"PR/B~/meױKe3/E.M# 'DJB9oՔڏ?'BFaJE̜kӹ[z DV Ig5:!\Xs{W|Wr FWxaw:VV8./tk-ն޴I"u叼fUw'Ǟ_\hnOxzyB0]- ./蓴I(pS 6s;G]}y e6$ Dʄ?a $m zQ +`;t +97}ֆiNXYY[ J"Oggiw4Jƛ휞E-7gI%jEV}\Qp $يeXJRMȧՔ ==KƥR"!*R[&oŃHHe$`{CG棷y uBv*Y,:M"_*",zX_(XJ{qvVCg2o#ȍ" Ljgm}r?95roOgdq۷d.m0>yǬ]i9 "!"!"!"!"D~?~;P(3SǥS2t]C 7JX4.Vsd㊑@z~L2ȶ6 /dR}DK!\XA$ZFrɑ9PL D HQD0{rb6o/ۜv&NTVɁڑ"9* ".xW9\Nȉcʃy}_I#<7Upahao "( ?^?}R,,VWm"BxX0DEH7P IK`gISDپ2c.RWWvW3.Qj+ -#9 DrT@D$vR%i—>«k?o^qTA5#VXA$ZFr$H%' aQ @ֶVE4NDb>y\WWE C0d ^( -#9 DrT@D $?a Vx"9YD$@DD^bVV_/Q@ww&&'P @DD;T-T)T)T)T)T)T)T)T)T)T)T)T)zg?&/=Jƕez`ox= iiiY4.۬< w?fU%eO]JK dl<`OXZ6Tϙ͏MótNٙqj z)ӎw-{PCHY5:6ZO!^PU^555.\P$tEG(v,O(2he@+S$SZGWt?NLNd m$rxVattv,-Jx",Cˁ©f.?[SߣJD9ѫKzz{* `pp2x)Foo/J ZFSS)F.ݦP555uuueY.iϋRf.v_%34<<1597?g\6~B"@V|?\O=O7|o~]r?<'}_?~O>z>j?/,Qrlaoo d$uDnZ2]oe> 5K2o]'>/ BGr|(m$rxr6LxLł ѵDtS]]]V{9PW^}!q]Y+39^קD/ӁxERe߲oI[aG"{C+ZIҀz7Gtnvbr`ģP9>9v߿O>{~]]]]}?sl@(afnŊ@( Qh @!lx0̺lccc8fKj6,mT}}}U+$r=00Ͽ) @3 H@%:t~7IYO{1 |:$S39W2ѧ҄ ';{;,VPO>9[VbZVf ii;eeã#ZD]gphaÖ lx x_1ڵɸ7=˩Dļ폩X+ޛ[[[ݻ؈m[ ^j^*Bۻ;RUΉUD{C+> H6:>N;+ 0[\K$σ9)΄"y珟~wڀ}K9W~/~O?w?~ѧYwuuW?~Ϟ'%\|վԚ.|~L_x FaE'P"۷Ƿ葩 ub`p@c|I7@~>wW_UwU S߾Y`>2|w˯mӴdx'R'D/_bss;_keѫȴej;P4nʹ2lڪn§L:!EQ9==9ak| DbPGc˶CO Ŋ<=? t3yzuztPinnEc{tnn:\].UwUm:0o9}c29GJ\ 4,3mRtr~EwL.L}a\^os'{RjZWffųamon3<:Up2heyM\Tn"RjpaqA7Vn="+h\ k]];Q+1/N{VD^[[tʸ|+c=i=Kx]' Y} [t޾M3czIm$0DDp92W%N;yx_,~}CC(FnW_oɪ#gwOY]qHJ4^Vdޒ?{ަR]kn~׿}'Y%s^5;7^W+dɯ|4#[eIٓO[ze\"(DOVWGbɽ R+lmE%[l%QvJ(a!vD9+'Ǯ2Ro* /z{{?i%FmGR>*S l۬Nc|a8J7lWCA+#O_usZ%N2he tʟL=zny9:Q8YE֩pJȩH G2+г::;)_J~:=3[,Z;tw2j坟='O ߰?/z7μ&ğ>{ƛ=n_u%rjM37$<~/4-%%VAմo$2ms1\vmôJV䷼B~wJ:53j_d_|?ue'Y50nr:uj,^,.-vq"[RbTM*)!{r]Spˋg(&ʼ}(d51e睨rՋڧ*wB+V&'W -yA+V&+vvC䓳rR{y!J,N{/+yD+,p7֣TWOo_o::;v- o$2 tK%Hn Uۿ?~/%W_՛?|fss?^[DgJ}+e/ ~2矦>ߤ@p݄?]\`Q9 oMAzweىfy˿|׿}?Eѧ?/CtM s9cWzom0 dR/Q"1'}'e}wOnPbO̚%gLJ^? *wYoc>zOWWH"Sz~qc]':z+eSҨ P˼֥NbPD~t~MVU#9Ĭ>K1kߟm(<ӏJ_~)1wxi69%l?(sT,//Jhp67^O[vϯ<%݁io^rJ%\׿}}W_.~3UVϞ&?yTYgTA" VdM"9!Wk+aSn'{3,ȼ&j|?I]G~u3$Oe%!7LUQ۔xqx VӔ?.ֶY:t7}mbSuƣlUקge3GUjhePv뇬# ~}[B+VFmgȻkI/sA}ش7n[jӈF"=d&sVfd7]?_5@:p92_vtiZ,W #+(B?LI˿|x|zdnɺL-𽰸 +x??`쿾,/z,eWϾ)/J~=5Bz|>?`U@K/>?氳yݴ `2/3Lϼ J_SSK\SfhxH72襤[o'^$^TSS'i2|ٴ>,yu{>8{T?\Ze2J EgiMַ["ˀe񬵍uc9}1(ꫩ[_.,zհԦ&=KDNi)]u|Â'T /~vD`'1,ڵkμPEXaa0Uhї ]̅^O]]ʰDGOο^KZDKFzx]R^*n]5<{131,^;G5P6ĘxAvU%W.{2r]W]B.^7gK/63m%G23^`a1 ~4C4BOUqδɞQ"'>i2`,ቒ~.e2xg)8CUmu=K\RZZ[6{; {XmSK"1V \oqm9@U"[\f%+gs3,`1w7^< ' \ KtFœ8?x$>K4?~g_>/O'? ;|+r"z2<,=jjK6QO\ǟ2:>*Nxݷ^dqy !S?Is^u vDNݏ1_ !hjnoU61CC=/.R1!^$5wo/.^%ȍ }`'ڥ$29YT((('353Md/L=.]׋rxwu|JIyzr*@լCkY%+5 ,Ix gDYj\{!*+mjҳtImX |fl ~N|͙MDnܸ=(y L4}J] 7i^<v#9+/>Wn{*D6I_~U&YJeeçNDC!q&,›~Ė2_*l}N8/<_WF.zחWf x?/E%TJ s 3&^vz \.;fO(IkJ:H"u\ѸhRepקONSw[W]p.YHe22\ٸX2h9X,cg hWI;I365Y$&cEx|VcDΟx|\8X$v&r?p`W" % K\V]ʹ]'KoӘD߈o}W/>O>};ֻoW^{{gSfd;?|xMj{?}LqD?⑬R㩳+ǿݒTv]"Ś~:X,3,S}Z4,aDVK`mYŎb $mvY(++c2;AmjҳtI|K].f̳ !&+vJ|#\UB+$8vB?WC^װ"/ z%nP4gְD滉]1)GRnW{F.}2&|MCwcS-0dk74Ⱥ/?O[eC&3=O~O S0RaD޻7ǿKdX4>k̯[IWlYTU]3oӘD>=;-~uNz_2u:) xDVL@f=c+ⷞzi5> SI^ ڮn*|} bV//VV5.4J[ZhmkR(!u)Sjn(̧h4Y-ղGKlSc᝹-7> stdrz2{=KD]X,4_S;:;o?%r X6[16Y65Y%r\3sV{|?SjD^ZZ*RqtBadqq>vӕ/N_<[1vуlj037 2 _]vnsfƷQ&%7q)Cwx}*V;QrDKTzF7`LBII /A1Ww7p1Q SIJR,+aϒ/o)lȻzʧt셮Ve](B)<ދ"qHDNP C#?DMy:)^~au}LpYhOv3Oѧ* */Ox{~xo~Hc?M$oS(|/v/umԙ /K"UH/ Ow?~albjO󙚙\`ΈE񿾡HDx뛺w6,^fY&v%l=d˲>zF>qkg:g4/zEݩg)Ɠvj.i25 Jq=^|nc1-_^Y8y0>m?:vrgq<ܛłƆ٤6X,ppihe2y2nhu^8;)NL(..h\ʏMZ,.McDNa~A"6CiCAo`p`u}텗_C_yIOZ.r}=9!xs|͗k<,"tʴQk~^/D>K~ۂ^ OP+'I(\B 0zIdTӋ!ދ}"F?F)zhRFFPE#PZEO|<~cċ?,y&&)ħP4ak}An/YJ H k+s stZUݣ/&F%uuub {QOaཚD44Id;%2C#t]]ttgZL7S҈'+^1)߾b<Q.ݛE^ˋ<5ҁ$ALM;,vN3 TE X&Y& IBCoAY+kkU/ t,,hL¸Z6Ȧ&=K5Cӳ3:!pi x|Kjn9J"Nza-Gս,ji%|u>?=u=?%lU-ȕW^{U7_ Cz3xhikosbj̥觟 iMt5(0K~< 5ޝETF7F}J765j% \-R)Ei qsx1 Ũ|BX\^R{z{&fҫTP>V)m}cWYU(ϗ+ 446Vpg^ĬLuC#{;z{ <"mgkSyJKKK X,,#pI~btwc޵QF;եe22}}]be'dg mm*ݳ$rcɻ\ϒ/0\9) +)uKɂΏ߀KgfU_ޙM{7 [t|7%򋁲'>]\^T}h[bz/7?||5?^z!=?x@Cc#uOMr/T%i_w_'}WUNL'ZrwK¢~W59|xZ2ϫGI={iŋQ Jtݞq?(=K^#2a`U1-L.U+**uקsޢfM(d/~FΙ^{I2ws<+;q_g8ˀee4BLBԻF,7hniNJ&JқZiu}؛ 36Ykފwed,$+g5m pqܳh,/,')Mǿ)۰Dgkγ+5/>oxY;^yzP?sT\;/'3֤J ?#zV緟~&Ӿߋa]!{0`zv :۷jn-./҃ڏFDJ/I#a OuUs}⨮sovStL>@)XF?/\. SMQ~ RbpgBb6ŇVDUfONO<0+`L*߸nx/X,o,ٙY4E(X n.al*ݳv<16('r_3,ݙkcgD<$8JKK{ݓY##C7%+g{ R@OSsS}CC>#tYZZMjjjZ0nf#(lC?|w-)**EJEEy,v?`XjvrSY(P!7)++Q: >*e22*~|2z 㷂o.}aȥ 0Sy9IG XeV@9X,him[)v`@P : pbś5<±kT^l=9X,())pB Z2 $ru@"{o;}czP8H`Lu22:B쐴0XT +\p\O~)>v@l gHL6H`LyyŻُF2bg#2 HD~|ӏ4 gbxd8nmO e2YήδVmm-XT \ QYYݏǷoh/EB7y%r X&P[WV{&2َ)>t5CX %W,W3 v;`gsγ8]"At L]Q$v%U׃smr{/,$\ oU+ ! ;`g3 v;%ci߸OrWTVTWW'm ӳ>Y^ m/^׃smr{/,$\ {{`g3 v;Vyqqqb89.((Q`mc !v|Ŏbã9z<&2Y[]ݓ8|xDk9YH`g3 v;9hԳs px od_̓摄2HuذH4284TUUe&!Gv; `g3 vv6ZQYY)o@@y$IXڵѱ}'[c=$ v; v;$r8<G@躓Sx yX~}$`g3 `g3 h 8= Rx=h4$ U~^xOb-H`g3`g3 X+B[QqڵRl.$][]6I~y$`@ p-(**f(xƍ̵%rEeEKkK_wKEv;`g v;Lfxd~4rk`h@۹ںZ6LLMCC#ڶ=҈ ttv^VV6=;=<>D#[շ^ss{+ D͍P$L_+84(CUW_`c[I8 v;`g39qywO7MڗgIזE dBbEgPGihl8$b Է[Zw3,!!(**ezzDi~u}Mܔ_4++k+on(tju8eM{=ţ ͠`λ%;M* A7KǷRJ"j{\ Kl嚝Mߴeo.(tNI Obͼe\/%FJG}}S$ v;`g3 vjUF,ˊ1nh/ GbW)WFiCP, s85#5X\/,,9Яi]G?EN|L ѱQ F!պzM"bS^Oή=4jd)93ͺ)+RмĶUߪRH kI%~4Bnjsvp iݝe\/5X+ٸ\.$`g3 v;`gsH䅅M.K4LLM''ӳ3 CݠD(av~6i8 sD9di|`jfz|½M}4AqG`aqa`p{xtx}s= ;jh@py;8Mͣ@ns& ;`g3 v;9;;Q"p*%Cj(#g4Assr ܪEnM1ή ,u'N/1&%3f񭵽M\N3O!5#1/$}q˷EԤ=/2rjrNNm)G8/ z\H`g3 v;`g`J" '_74[>{G:;̭p#Sꠙ~IZcQ*++y߆~])=C2YS"w/!)0"qn^*%rmjݮ܃C UHy+mwi{&ƍ؁xD-mS3WŔe:(OsFn.tfƻ}0 5gVsspB^.:8?𠤤$ӳ3H`g3 v;`g`\"/**cG|YfUSOԹW~ 7+--=4~Wia5Ѫ*3lY q 㯂O\mu4lA!Aoϝݕp;-,.W4ĶQ $S &.:-dVs+ggq_Cv;`g3 vgv%bz W=tt^hL]D-0^g/X շ;=|qGZ(:og*+-y4^?W%rlj)_9}c7NӼ&q|VVf3m{2A#WYŧiEIv_H-r`{jrZ~%^W8nBv;`g3 v;SVĤC P˜{L?2,onoO\7RijfRઞs*5NO"):GgR%j~ZڦVŢHKjy&w|[ZEr D)eWU};Lµ…'&kTTTez9ˁI8v }G`'PЀ$ v;`g3 v$>-0Z`NJ'7,o0hhlTYȍ9x}PT]X lt$Bje-4D [iKS4o Q☆!m%%%”wf[]i]Iv_$O(PgVs38cf̴Q(gp3 v;`gs~0%`WDGoWz/1,\{X2f7kIf官g+BшwzC"Ӧ?y˷eȍ ? B0a4v;o,q;QӱD4_#Vи/=mRJsfi ҖVLʝJy_wհYqw]6mѻ^gG"콎86ېȝ3,c[YO…?d#6765x.%`g3 v;`|c 4Pr6,x͔ K L""" u/巌9nh7KZic 9Չy3G$rGŸMd_fytQfN%Ҽ#Ң]2Z ܶ *uUpegn/66$ۜd+jrx>sLY~*3J$`g3 v;`d笑(**;wrq װK C<=DŽD{*.?zaqR 'Nh7(mS][Y^Y^\^-NLOLMZ]'Ab^9'2(++Dyr1a |[dz x]^͍4si^ zoi~%m%u٭m9- 67,'UiyL;]hc%`g3 v;`a,ȹR2U7,2oRIAjѰ';HWuDnM _L*Ƞmma~- _Y\β ,NLZ*!XVsÓ)٤?^~$`g3 v;`Z  K}Wwr_74XlYV֪Ug#.3߸.=C\My}qq ! '/,.dڛ;H7HLUv$mbZ0_΢$j_5֚2p՜$Y 737g$ v;`g3l, 555=!)AB"IȻS^$m%%%|:%Vͭ,J­DZZ[2p՜$!d4{i$`g3 v;`dg`D~6oSBG%r(J"[[Z}%bdl:4I9}!>:+%fz>ѣ -%C@ʝJy/:)6%m8av~Vjb'Оս7|Sk{XtI|\Bv;`g3 vOvKAu}uK䬴HDfl(f_qmh?\]]퐎URR3O[ƝNwG$򢢢:GUcw/ĀQy>Ԙw)*%^&pi^ƷP-W{,mrz20'G92lu._$2ҹjrr;>`fo$`g3 v;`geհ\"܏F~]/%ܷX"Tc0Ȇ_OEC'-n108Jga7 *?7~f/Xʣ$򼲩1[㊻TUUmU T.K:zR-PRn(;(2HLR GU9- ^Ĕ,#,$4#aN`emefnv=`A|)}dtd3626bfp3 v;`g3jX(MLM'S3VS:#H4JP $|o}sÞ$$n#,tt .{H QDG'q2cR|!M@sy^Ԙ/yD515lT.v/POK;-^g_)FG[{ Inے^J&]W]IPDEIp}aye91%\S%񡮙ғp3 v;`g3 Pz9~dlAd6I/mdI+Wp7YLNMǩ ~";V4.'ǡHyV1PU}Gcx%hηվL.(r.ŋ n?Nʦw 8b/ G]=wjmy1j w*%o--G'}vtݽ===n>q@eAn yxO[ZX?g`p龠R er՜$|xtD{վYEI8 v;`g3vvDԨ  Wˮf}z(/BJ{N\ ʗ%pԙ&|io0P>[do\QH=e٦ҽ~jfJ[iXN-y>(o,rF./89V.':{;z[EGCcXF.K,''WIWRFqqyOyȵ- ;`g3 v;NTã55b^tc/V"Q"'j25x!-FGYX1MBxqGc/˜.kY3}!M@sT@S[WkUsslS+~nab̏n|O ǵu:8=]x:.AmNn( u9,UͿ^'YF.K,''U7Ĝ#E-7`g3 v;agIԍal۞W^I5I |^QJ4\~'evO]kkk 080~cp3 v$ru@"7fpwg?ɴʀ!s\O~)N ;V `Sp}$SMjUwj喝$|o8 ;`gy@"W$rHqrU[[k"q>ӏs)>[p-ǘ{̆Vww7@v;DHQ[W%+++ x9\cI6j/g]v;D%_?37sB<(..><>$76<ˋC$>xO4 M*Lۊ$ v; J ?ܳ%l $r OS@"<$r OS@"<$r OS@"<$r Or7nx] ؀A7 ߾sۢ_ ac1b$f854ONMn67acD h҃2ZU[WI m]<#^ҿP$,~= +k= ).7_p2b$ȖFL$`cD RN6;ZHE+mmxI|MAsã,r@ K$rv$r\MG1+uxI {0:>) Foȥ9Bތ+vq$vۙ[+H6Y5_~:{ ].Wyy93dMߖycFr JV׌HƐloȥyDML=<z"@"n@N ^>9.((cׂ5Hɑ/^׷6^9rx  @"Gț>* V:_iig?59Fr &_-z|>rD<C K$rXY[W=C==f'b@"` 1#_a|mc}zvKv'!b$c\ #佀1q)_ПTGpqyC#+ H @@"H|!W%}ye%rOoOIILB5I!g! HR!oWˮ QL4ϒG\8.,,4ȭ$r@  c$Gg!2d0\'')CѳO >H @"Hy,2V^^e怉_ZEEŵkJKK~4l;E  9|(r~8R]" FsƐ˲$r)0(>4nnh7z=zxDa/w߸qCKޥL]/owk Y}=$jX(Fwmb˲HGMLM_':"hхxr݆F{ck+ yfKkKsR%B$H0v|,/657|1nnoh`oR\\3bRx q;w촩tκ}Mvtr|; z<}}hZ:;YO0>اhku}ufnf`hk9|pob!j95JKh鮵uR鐵 Rk#шm*ѦiaL"88<z/=yς{sKiݡΠʵ|'uUV[{'!m. 1b$Hf8+b$AD~F' cB$]$:_ D6ӖgnRήNʔx) X.kv~6VрBֶVqx[{OUceGTĦJSS%r=WnJw )E"{nx/m(.,qqGVFa8 r@l*WT{i#-lVXޞ|$ I(6}.515iCG>`όW6(KCoem% cWMl<㨾U͝=,,.r A 1RH&9+b$KAD^ZZ*t1,A",+p8&&3]auc"Q,{휍;w6pB A:wna^\V-SeJ"ϻ2)tIZ܍ R65lSpJn{fuشAЍdGD4._לǦ9kt|H;ǏYtvfVޠ\+*U QАRc%f԰ٔ4%6~uvP,,rm*Q"88<-^.>rmd+dD_{wmSO@. 1bpVH7Yv|a1 HH%r,ffg阞cɉI1wbȳh{SŎWC]==O"vZQ?$ rh&O$`$.M12:Uv%ڜ/Z!3DNA@R7 GJOifnvb)24(_NmkUwXݧ|{xdxmc=ipHņ;M 4 |l::LQiMggܦt.0Q+3һ?ȉT} } 6YD}A?ã#R'G˫scS# YԼ5N[h`rZ!u%ϔe ܓnpbsMߖ\RRRQQqm ͤEEEdL=5BY1e: *צ%rc^]GYm|+8OM"M[nܡB֯@. 1bdqVHV7F{ dq v ˯g:ANhnVL=!LKC%D#4XImMLMu-굁8h9E3Vy7Obz|ι<'ih1VES"p kimETDf¦mg ;Dilmd\ Bo.Aml*&'y&i}/ŠqIv;:j|Xlk IܩUWWꊔNʓ"r(taJ!{/̴:9^]_MMM^)6uΧy%vf@"G 1RHqVnHD+hi4 A޳z}զQ#AEsUΖ"錋8u/Y޸qCIY<+‰3-͆ON'V yGgGK8=Kz;M ۔o驰*.v˰ЍN~x0ȵ\j.2j¢?쬊؅,7P,RxGTqz<];ZvΫ˦iq(@٩B%=׼Mhi^I H져XA"G 1RHqVnHD+hzYѱm9EՉ5 'ʕ{_Oh_{91]Iy,-2-,E!oSs=iʴF>EEE"Q|mv~ g(6m*I]:nhmF465 ezVVT Wi%Y1l@hiZ8D"wlK)TIL5oSY|?ysK3/ M^@lXQQk i? b$H-Fr;FZAU"c;J)NgX%WVV6S3*]|= շE72HKЭZ*YB{@3iE\QosXY\\W%GʋeܵK.vNaS6VR&pK l*7tpYh$ӖD%%%Iۦ[dSEtzVeY ;k?+JsřK%2IiV*\XM!c^E\L3׼MfiHļl'FkQk{w2\1b$H#YY#or,߾sO%yQQynmmƭS+*GjYK ̇={ӆ555+ko*&&)%;<+A>[fT&gj]"?֕xanno&%"Ck%Ѧr9kqyQ㤡﮶ ;kׅ&5r=K{GYeq[Ge ki]D丗pp>˒fzy: Sy?7zD@n/Sv!U6A 1RHqVnHr\+(H|]rH+vr U#6+JKokPRR"jhy_/эoK,Wm׮]m,+g.l|j#r-`KD9g*'NaS6`AvCcV;¦r?f2眾bE)0$KºS0Av;ݽ^(x0;W4'fz!CK{uv$2`SD(9kvAhjnJʝ;a啳]OSw4=:{P[i%nlm*0oYzaZ۔/Zgufwqf , GkA 9m6NOfjf^8$J=2Er=˱_y9+;OT5,ܱqoaf9SacWMkDV5]A㡱D9b$H:FY#Y!˲ȏO/>ޮ'fDnV"9BM$vDH:,`3u~Q&_eD߫4S99VR29:>Kbnyū\KʃL{;ih;S԰My?U 6UVV,uC*b QWN,,ΌSoSwg9\.{>_zttYM+pgz,:V"wlDI@iikMlgili3 #FB)c$1E+\шu{B"Oի.ޛ3Mp(!ˆvANx&k޵"Ԥj+gZ;8?,Q22;ΙiK"ϒGPRg96_q`M6U/K"wlܛQ^^nZgS[| @"G 1RHR8+c$M2m/295 Vшޝau̪+Җ v+q, y^Z{|av79/@!gR?J|/LK"[KNaS6te.!74ӿ+KeXueSE͎%mnikq02W6;pShg96#&v:9~sSM3˗.)kMn)$r+1b$H9#I܎$7V$y"_|qOHOG*CbzvاtEKJJZDÕܪE!gR[0zh$OL !U9u?Qwm%A~ӞK"IBw )R"° =MT(G"RFMs֖?1Pqj EE\&YM6VTNHi*Y"7086c ʵ/U{6uȧȭ@nK䈑#!FʫI gv$ɵD^\\v$YA56+>|NPnB|Pj)r/A 1RHR8+c$M%rƒ %mh˷m-8. zk']]ߥ'vEPi|SeS|E=7dMǿ?tw6 7x ;WTT~q=MD>L?nzM;MٔWDRJ*W(.Yc\6m*_t'斶VJ=KzU՚ag,Ϲ+ <ߕs{(tǔnӴ"C&yjԦt>uN`5hhr/A 1RHR8+c$MT%+l+7 K nGy{-KbyBwOwYY+/,k™iU+"BOQ^\.2|o?C#bJ,ћfJjF<c݌:753eԦc$E'%zpOlci%t%qs䎵565fS>xzB}C/77`Txjjj\N-tJ`ӸAvK.wSϡvbj2vѳfflӿ;wx=c-t\ظD~˔DnMl||j8&7S9 FB)?c$Y1.+hKKKyG]ݶ s\"8Ena~dltt1>p阜 NN155Ct}SC q(|޵9:m=6826B~s~/=12{ҽ@pZ+..tI˷==;=84Hny]9 rF꺜DŔ;Mt1,y&&C)"?5O&؃T Q=7?GCݜGtA/' 2%~ur&[Z]PH(q}ʴ9\+8dȒihhlPhyɱbOݸ)LG|׀Ԩ/x]U*K3+k|cL$rBup~^ReT UoY>!VQ캻mjg5'')ޢ4vT+@RFoyjfJ[]K>`589b3J`$z̆F2ƻv+ٴ'CSuQ;ƽ;L"W E많=|'m/ M\1b$Hy#Y9#IorQ"' %U2~ySs޻KvJ#mz>E^#5(vL\+qK y{yŕ UM/2+.vÇGkk+PgmeT`={TYӠRM w  qYV4-~–Dc%)Ñ/ʵeqa.+ؙËQ)?[EE+'n$^yic/ԁ k_0Y6IC^oc`DNMl#!FBʽIzkJ!_D^YY}LNӸ PBa4v|="XKmL`79{E=QG_^Y֨0fQrF>%= m5VʼnY?1R->iѐ2UEo۬ EK%Ս77)>Q"Hpwg3k5Ew i .Hӳ3&MѱѕU &ihЋ'N @1VqKM/߸gηjn-./҃ڏFv/ISBN`RrkipuuSh$u1._0 )65,o|58$%ԱVOG"s'ie#!FBW1K1& $rEuϒ~)$rQȢWnXIC+K"eh;MUYp]u*'*ݦl&5{=+ۇJJKKO@H/ Nnj2#!Fʷ) XHHt 94|)Ǽ?yxD7 ỲK48#A 1 D7ʧ+Wri9ownB{=ۂgoQRR2>Dړ@ Z FB j@"Gța[){tl{Ajڴatlts{otM]W]--##kQ^>oxt|$Vw׵#rH#$\ y'&PQYaCQm@XrS< `LrѦ@0u6mni^^RDSwȪH@|!EEEb2 rH#WDk!c=fC(P݇_DHTD,*Hw,, bFbטøev+z>摇agN/9{YR&)u}`u`UM>svmgTO_oRRGmWv,=H#+Dx;|_XGrӦMOu> MWMjݦC#CN~rꫯV6MJNr{޶54mOΑW[W˽2U/"r5LN;:sH_|m*#;b~{Qq*u otWOWdd&nI9*&(sxN۝{Gw؄MVkȮRI{zݤn"|su2uh}c, 궨ã#VUf~p#]iXZoqq:(&MUUJXaMƲ`[%\mAБCJ8ӗ_{NS`~ ՟ɸT=9<:c2T'bD䶹iGxX&Tmi}jޭka#Xtaspoޒ.<n*k1ٻgwҲRyw-Annmѿ9 ז(Ӳo}^ꫯn[QUXV^)c9e:;e .Ƴܭ0Rc7WSW+ykr߶}H yTTl>OQ̞laexh2 ;2A\o!~."sf@. RBQIܽw9F>+mf,dҏ~9{|UTV|1~cG|}Ç>x3fS6<B%Wjt^7gʺꐿ<ӫwoT9u> );//jS=~D8k뇇O-GnU ޗelhcfvY$q ` <""bpZ_m}~KwNu,#ǮU] b`hPjAeuk i1Wm3mi+*]ݗ::f>Ӯ[O܎oLm&lu u-6TVWo/EVxΞ):>攒ܭY:3پusļ=i~DvEyttQ/*+'A\o!~}Iݑa29qk'g?)p}A"/w?|Ϟ$${T%7r__S٫|S,.v?#[F䑑6] l0l j7no0$PINIlmocxF;[_㵼22U^&i`EWO*-\DYĶz~[G[Aak燢bgoF VYٞJ/~g#rYWD5u֭ip٠h2xi&nGSM*MN;< | yr woJЭӇ6'αiЗT>'[B&lnQ5e)K d y(KHʋ;j<݇)ܽ'G)_affnݻĠͨin̬L}ErױE <3;KEENG3<UH<߽dTgյNʹ7X1ͭ&ͮ<"2뫠/U6nP n5%%Z>-jʐ:6B8 &%%-V6UFnd_%8E"|]^ |=u"{>xFvTs&o޹߹kOrM gM|.9o*VC+sv[m֜{~ٮ}QSKSKNDVn>UHO_q;;T`ma.\Nݱˎk㘘_A_|zN2]+ڬmqج^<"6޸ȍ7.VpFTut-GDޣ to&RAYo!x}I,6SXFm ?2֤r {o V*"ZI? NN=bx}jRof}jkчODS7XBNf׮]o0n:l˺.TtLV1o%V}+޻v>sj6$-=mViOĨGD.e:z>]+ϛk{2  D 9C z 4K*4ݯީp׳S{R3a]O&uy7/:ɹ$/._Tיhj{r|ԄW_53|yB\Dl=Q58ww_b/. L0̬L5ro\7l ;gb3Ym6Qgwע{3oݶ5m!kFɈuTY:4!_^M2`cs)[A_R1d&{{,C#֑Qul6>9aw<{X<5 ^O$J &MHTinmQq?ZZL}ʥ׮\yƝ߹_=yg/޼|_ߩeZ՟i~qkנͭjY.JDD6mڤ&:Twb 7[쵣]i<źv,# ]=P9"5V. u؛wI&d#d]tjRP;NãA; nZ#k.f_޾o^x[+OV\/7D15sf&Pܸ}f9& #"" %\ޝFTTRlܶ OU:f#rY?4F;:+dնa>4!zV  ڱD-Ӡ/ja˰e`hG̎N9Z[[efjkk*ܾսp[7\OCJ}}>bypֽ7n߼vW/^t҅ g̓Op~9r#6t.._ʗK[fՔںZ$o*vΞՖ]㭱¢B5jCCMMYw[Gv?"r$fPv+mjVf/8+Φ&Ey"`mMFjph<Ӡ/頮M|=˰~%{4\rIE÷6~;jZ3mz?aʅ>ŗ;z-N-6nn\>6wt-VMKOi^FG_&d#ry+4r-z 4KZSWܜzdae_7\OCDo>hok}-oXܥA.e1?^8 PD'"_L,m]:UzbҾvZ} fԹћ,j:Fqtg9FK<=3çծuVe+=A kB;"׿"8u_nq%MHW#LN;vVE6mZ7\OCAiy٫woT(,ը.u99wq1i;oG{6'iZHvrWk]dpxͭ-JJ:ڴ[FL IVEv TT-29~Q;eՖcut9᰻mYF;Pq`|!m7uI22}Zŗ^3AK2q+ <-=mܫ9,z 4KoFft̚V]ȍ0""iJjgOT.㇪Od|^8 ZyR5}jrU<8!"K_xx¹PX"k3^ɓɅ>$M|]1UKքvD:i csI[A_99 *}>p7Nۉz*~V=]AhikUEQ5Xg3fU M>M+g ہٗOU!XnVD<{CdzzԚilnںuk CXmpL;6nhfZ}VPX:BJjjb"mPa6;b9?JeͯFS}ߞf}9֢ޞ:NTTTeu~d]ZsG=v,z 4KZPh [CSܽqqq>m"-D\OxG}襤 wE٫O |UfskJ̹ل NTTVȶFxA<|YyONI'Fx!R* 9䝙ttu7ԗW(*.ڟL}y 4ulX-M-MUU2u 2yEFF[$Y=9%ŝ]]jU땔LL=CY=}=&c5Es7t*=#]Yi{rnO.[moކF‹1]=wO3w+xDk7tfjhtDVc={dx fR9NbIW:ju7hE% nG䮽k [?;I9ϝgp:tᣇ|}ȱZ8O |ɍ7g/eɩIٟ323S(9111u-3j^|QQd]v{=^@Tx]QYNJ҆={phnm1{ 78LyjmU5Uqcfu'T'iGttk !H?#E)SmHݑy VYiuZۖa+1vfV6hy;d\j-ë9NhIeW79v7]Dn\< x569x}&rr{o}>ՆRJIM/pعWHO'&k~-=9&]N\5mYOh&z) YI_i [҃-T!ꫦۈol<)Zꏝ]˰Nf#Y-,.6(ǂߖ!"tI4+;K|?ȷs=L ve1=}l\3=咸%QfI_[inm]-7SjV!+^RrRYyY{gǀepf;kS;|9RO(n7Ԥu|l2PSW멲EKJ>UXGZ[86lܠv?"rJy[ninm56aeM,#CXڬ]EEVp '&&W[&lS./W|: y jjdGF4>>9!mPU/ݼ ^O2Q~Aex7_pֽۏ~yo^yWn'#"cw}Ż_}GϞih@ "~xtX߯j?bŪ''|j"VC6O22EX[!6mڴaㆸkFFFrz YDAxpvUk׮SWO[Zzڄþu ݈^ U|=2"r?}DUW3mߦo|:bGM/7z9PY119ON;۾r"]8]}) Hy_VZV^ףs.#U͗ŮMKO+-+з\RVnPU)lՍ*+vz)~j{vsb>66W#S9fQ`GovXT;ήj~QAq z 3"73W/RYϒ2<fWpsvW`Go9{rvޤ/}2v}5bYGM6=yT3gOPttӴͬށŠ93;KQVvvO_τP)m@TXGS&Ѻu8,"r ˰EKH&l-m-*ڬ]=]ҷcaP=yx!59 zò>3;WZ^;gwZzZ~aAwg+*+&OJJRvv{ ]^OKJ6ahl>߶pIz1<:leS-XE>=c000000000aMU!KWoj]${wgOɩI5K5uJo7^ߒR+8:+m8յ5Mw;7}Xh Lpo$-#Ӵz23qJWO"%]a̗Ikwq20000000,CD|{QI똍͍j2Uiim1uFHv۴j~6?SsewNN eIeB|oskdΝD D aٻG]#֑m=i~42׮]kXLLW_*wvi&t`ddȪk7IJG93Sd\P׆ȃjJU iMؖbe```````Y!t#JEÖ71S+,)-q999V_.=ռ54 4ijNfL[ hbrf>~jVO%lNsa̎.MlٺE}owFuU=5 wI{Ysu:o2DDv/MQƪM~ Ź-dl|FKUv0Ʌ#:=3C*Z}]6D%9ؓ6TTkT/Hkk8 Urz[ ٻȅE#tvwi z3ҊJKLLܕ+7o_EUek{exH09OM=<:*GEE9}SON;$isԱ [B ?8_\ƙ|հT`)`͇3Slqht飌L%CZ'j wtuVT.ׅY_Y-A-ׯd"nݶURT\$/,lqUqcˌ+pJNNVkiyӧݽݟ싈TƍUSFDFFOJN |azkw ?J!.i,ZuٸE2RΌP gX2#" ۴iJT{GnlWucc5_c =ڵB`-obbUuۭ6k^yp4w_%"* Q~rrrfv|~ ``jOcUƿk_㜕i<6dVMFF-vXZu+*}%-.)V͗GFFʿuⴶi#WSWcˏ+L24ZllB=a?Yk{{yyRw![njEklD2 [+SyVvqI++-.-QiJ'"׏,"r0qmHܒv.+uCu^[ܮP#l d-5bwQZV7{wա INNu .i}cjjCi#h O8 X~DXa1ռ6'.V/?u ;w4y n;;/nnjHHkӚߓנز2UfQI{j)*taƍ Zr,钶7LJNnwq `E`%߰^ŲsMsجr33 [[ۙQ_|`h0-=-qKbFfFUMu`k+u9;mmU2o%e%Օ2C8xjySc13r.dSKSYyYEUE}cCwo趄`-igw~ށ>"##8^\ZY !"JړW 1iik1lw_cr&bw`~aqC%Q5Ȍ\yal|S!AY^]X/CZF?zw@Lz{SKuAƝ;w:\xVؐ9uk5;U|v󍤏Y?|j2!!!劏0vM}I,MttG_]f++m;>914:]\Rl˵ MN;[[ZAeRrR]C}@Ĥ>59nkj32###}}'2H!/s=dLQU润 pIFT8)nijiZlee_.V 9VuiGtt4+r""Y~! +2#"ٳwX!9VLum Y!9VM6B,3"rW``e_~ra7ԳB,?"r@"")"r@"")"r@"")"r@"")"r@"")"r@"")"r@"")"r@""3ҿ CouOE;rHh^Zzڱᄍ}/^xۧ/=xK9bcc SM]*>5)PQUi#"*ϷoKY yw2cw;f* W~z١PV#X3 M,á#8- |//^Bҗo_ݾg7{j=}Q{gj }wL_$$$xK?{oD_ܼ}GEE6hΝ%%[naU %%'8q/bnSRS掬m_ƑP]SǞH!Q ?,/ЌȧNko~[SXTĠ6wϞ+S xK{IHH(-/kmo/_Ά|aÆⶎ]iU۵tQq;09qjn{v;kjs̤d)S]@",ʋW/ W.Q'_|u8N853%GD'Ǵ[޽Vqz%|pVݽ=rruƲwg-pzqYl˨(^8w/:ٺmBfϟ-.[J۟_O \+'?_s^{i O͸Lյof>&9{2 /nGk^GWKD}޾ ;w~Oȃ݇fӉwnC` TV˳ܥ|Mz.~vS!!?'NʍЃ'mWtκuwdO5}hZ֡ON%ŧfOɞ,O}ƓOyˀͣ|=79Ϸnó%)9u7 7X{LTN|OÛo޸Q&Kn[JֽΧ|˝w3S[9YP|-3su6ͻޛߦMZrcp[[USkC&jf 4|0ٓw!@oͺ\jf۶>.nGӿ_1fEjyr̩?6S^]}u|Lبrorn?7ږ4rszw{͛ݖ3V܄zٞ]+΃>퟿z*zZՄ?Hhʩkr#}ܬn\7Y(jǝ>>ś\ei M-́O"7Hڶī?Er. \ zv]==z8XU)0xxgm n.U]\SI_!yn޽e|S.K.oNS"|uuxۊQOe؄d))z]<4kTgE}ܹקUͨSBMJ[~A9Hijx_:,=ʗA+?9uKW/C^YσOE\fUm_+˦"r7Yj,3%n\\oY'o,Op99aSQ9" B%3/B-WO-)-Q;~?>jTup#]i[y_\vR37)"0'OdffG\ԟd<{an_]?z ~yT.r88]d]GEssGf T̛~+P|xNO}V[9BV#}}=_??ϿAџ%ʧ!+M\<}Կf&|jr\MF1?Ҋ vY3g}_Yxclu9:cqIZDH Zi-m-?CڈEpL;Trʊ4YOUnWnmNܬ<_DR+?_9|Kfo}Ol7_Fo#W!_pu燋W/S<޺w[{7b)7oZ)#c=}=Ӈfݸ==~|?ǿ3?oAU],*.2STqͱQYdg3Yܥjܾw{rQSW_/gCӲ! !mӘ &n39@sɹL^Pj`ĩnUTiʎ>e[GQQQ˙[hLҧ"rɬTDܒ7lؠjj::\G[z9x7Ȳ("~<6y\\ܿ7]yX.)6ҮߩK>5pN?s=3@. "^υӝ PI T^rxn㣙G`u#䖒:`erZz+#NeZ£ ]\Pwf-|:VNkji4mL*wfmhjty.v3ˇ'3l욐m?"sD.ZD $"r#uvwic>w[nv ]sT;~?h3 O򸘔v4îbd?tB=UtS+mG:?rjR|ȵ) ԓgO<=?=|~}]lHjJyTzC~FZ޿YSXrwn!Uu|,vLs@.ef/Wã&S[n{oU}.z{\;R?z +֠#"5D\,L;KKif֡|vت_by:ҵW;zb&"?x{2T}La6-u.v.|}~9uuF䋱W_Bq%ŝKڍgFյ/Vnki%\0Ou19Fbx|3ewot|o_=\}{;?\:"e/vM EDp:>FD' OɠF=*UT}}B|#hQq%Ѭ,ۑ5`m 퇼~&Jĝ]ZTqwnkS9<:7t>b fvcyWyq)ܫҮöbeҶ<-u6Uf[ DLLVAٹJ[տ|\-&k@à3C.LF˭1\E'rnkI&>>`"5#r_ii&AMEfK[x\ [c#]B0VvE*Rcxץ #׾k=9jn\T#^pҵf?i]2k'e1}B0"&vpbع@ks-@NnGtxJ匭9\bp0.!!A,[-Ժ6wSuNڧ&//ȵYzUH("5D;9Bܾ:1cbbIiyj-Zn^%ݾР<"?$^&SkꤻUĠdcڳ//So"rmr35SѾ7!"͹>jh 5#WVkOwhf+}[MɎ;Wf^PrgTO5;k1~sTX "# =E䲍TeXYO"5"ɅM&AE Fgn*"?|;W~zf?qLD.gŚZ/|Alflݢ{qL; 5"o 'YJj uӦMFS31)4#rOfD]59>^&-ՊKwoSSC^ƥ*n{Uz s_iO30`TεWr.]GVa6=ֆ`DsvGDDkBȖ&ƦFU'Z2wDx2U yc͉5u5֧|~ui8@\"{hwwfUhmԘSsiU|vcQQQk>u*3}}ħr|ՊgE:h111`-Oqs+_PDB)PAADDDۭ]~3aHUZI~wpiiڍj3J? d^vs.+!:::(;Р?e-o'^tT=Lɘ˹t ׮ֽܹr5n/%5*uu [woKᗮ] cwiǹ{t$_!:_[̭/_׀vF#"ٛsȡ~>,W)9={F[f~Dnd%o~ϓMc06#╋dVe;9vޠpOd%KrZxcf22Q >m%*v_ylJJK|@"rU[r w󳲽gW<,,N6>p>00LWr{b feɧ&%':qqӉKki>dlf GpVyO7kɝNxu!+srOݱscfe8x׃ӉKkN2{0L"PˁߚK ZQ, _F i(hS?Omk{lfA.s|ר_A[ǣǛ_߲WTUN䔥F^d~ʍkV|D.V…+Pr14[iAe+=UZ[m+ymR+V]_Y}%ȵ]etZԾFO6^<..NU5y+"K[ɞ?azӨZ~+Wب0yD!D?Ѻ.k @{hPzȧfc_NJrr`pwZ7>`P֗͡j}6>Տ Su |K\gF1rۧ:]4gwc{oCIRUø{j́}wuȎup`˙A!;3&涳K-쵛 YEz{䑧=ٸ A9̞< r!gk>"+Wll<_ڎ7-[hGL_CYciSֹk)>4 otT,ڞK3f:ftr:=#][ =1V=:,ܦk-VB4 ErAVͻd=M_\=Ul$UyӏoY{3Û#O rM2pCɦN; ~O57wykRkCغ܎jԧ>ErPe3bDfuA$_JDѠHȍ#r,9f c-$-4aZf0o/ijNT''>z73~DdU8Z^z|0ԍ+/Z'>Aݷ7}ўZ>$ {o;y9{jiM)]7WLX1ɒBvg˞*fG@T{yyܐsXO*`zlvS\~keR3k@z5u5ZM&ӫչvKx,U9H)m]`E f"r V;ׅ+]<'847ȵ@NrۤV'R;n[ID.XZ'Y]=]ŁGrKtB\ʌkoO;>fhw޲h}X?\?{"'ӋTskt N\^*4]> U˩#TV>7^ꗉکINYr魻eᯏĩ*&\rNܱt}~cp5ZqOլ3NZ%f-kA #EPNDA~X-_gˁ#!C#C=P[ן,[DKr{-;ի쉾֭v2݌'9{sT+ܢm&9emٺ$Si;ɦfOL 9e;ԚA˨oߏU"ru8rjtjpәKe~ADn9J5se?a"rZxt=T'z%xAgkdNjr7|v#r*Us+σ~έƠȵtO Tr\][52spi<-zާ~} 5u5u.ƧZnk\4ypiٷxa0~9"Awf,fpXPW]Zp;<"W=Eh(!:!-t &#Ω'>>4>mN<ᰫr^0 /^TV9{FY\_a h!*-=MOrpѮjJyVqY`\Q]_{N>Y)e] 7 f0535 >~nV/{lO_$ qWzkw`4{)ȧY׋rJQe&X)TSJj9ZT>.w]nmG O`K zޝz@wnj=O'%)E{1/J4SdS;^|ԑQ]x3jq |ڬAӠ$/Cz#g)ahz~-k.eΝ{rlji1e㾾TjګV9RjjGMB5Cp">M.rly[FCQ%rm&ޞEPWDNXJ!XCPx'51VH@qdCâVY%ݻS**rZ2-dݠQ_筎pkLTr$_&|z>;w {}b-hUc(DI;Ap=&9ʦD.!n*SgKj=R|t9G9nR`)LIT+zD5Ω|KuZ&Մ<b8†D.飤F jkEaU"Ys(I×z!/ųD.Cweb28%gSY\BWgHF\|9u/$4 {&:+ db /B}T<c҂K4{72 %r)'{Z_4R$?qh u*ˋ.!FMp.9w9 FQ3DFt iN5ř]?,bﮖmU$rZs H4 E,b,.EGHg2@|mUIrHL| \&dܫ\YQ'\vE),y1uyE2>@TgGA͛6kԌ|ZE"ɬTQh⻙5NQS_=Mq=^˻b&rNl$r :È7h!.{1;#S P<5;][_IئP%%dw:ۛ[+E=z a{ Tj s:Z+EIq]NU'Za]'BqhGSы-T"n|%Ŵ((; m6镕%JE]/-!̪uum,;amD.,xM}5:IQ" .C(KIi(KL*{*ЭOmhkI" Ӝ_p[@>.Y+KijDN__;~Q(t`xjvvAEELdÄ&*S) #-Ib -P]KC>п'h/M*E&D}"S?0/,D"AJ15I!b߈*NWhE6fg^d'-*rc"ǎ 2$-N֓j],6耦D^\R桾޽4}s_m\*9plo4ٱ&gQW460Z}]L}MUV%@`IFl)tƠg6(?8Oͼ{w6WhKuCsYڂ!*آKN9wV@lJ(}Z@[hC;\<ˢ5t2H:sDN֛E\wt'ݵ_CcѾ}@+*Ak'/.Hmnbsd $I?֌1t|ɍ3l*9Aɧ'#}r+=Z4'iU_:>NH6V0 w(نJ2g3mHk"B}3+-yŘ!?%j|Hk9vi!Uq-sxʠO镤ƤŞ62/Lva$I[EvU_& a&'ISkfޔW"ĒYG1{(eaeaaTkGgng4-K8]i[$rXՋv_߽;&1pݒmm)ߺ}}/.)_j(qs%PV:&+'khx&lFMaIf2n므H]}ԌBra*&z~YY:Ļ(%rd1Ox.Q"DVHqyι]* =M{Ƨ&W[3/Dʇ#%I>(z4p$1Bh$y5/$r1UZ( F78[H"W\{$ռpbZŚbn|:kw;+Y69ى),=7V&d~'JgϟcGf68ڊã]Jz@בBNf >H'߁fck\^m{'Iu%rsfgik)g z0xI`];U2O:_p[k&FA/<},=c5e;Kcx((`tH'zu@/Y(){ +'[]OzoR3: ~dQ0eIG-;՚ʤ@g["UAimn@zTb%%r4ȩ2*F!d"G/]gBف$D*6=AV9*1Ma˶D. $h M$rbOGHƆidu Z&Mkk+J!Ãr=ۇvqf3oK䒇F,foGU$x^C UT$ ]K~We53uFQ-!Ub- k&šcG( ~_p,.]dO=t0#.z-PcJ~6dy*D6RV"$n?$に=l1{,6\nE?$M-*^~X-Ŭ<33EapF0a,*EX?直H'N'vtw84&8nQIgF4:l{TT"F76%-nQǠJ P:THh*&RYx&CqdfI~4:- ;z(ʊW_wfտࡃd6OLjrL"Y}sFǾ>*=}99J!==릘C1$"9F!AY#e#DN bw b4]gIRe0oX6$ryv{7*Y;Q-!˩5O1ЅjkW4:|hjG1RS+ktx$<=)=t-ﺻڍ=. [G- dgg Q/"IXur.GIAQV_K䁕Նc{U9-% 7;:jHQTRj$kT^ \ Oe ۻKK]9%$rz>f̠I`Y-nUDk/~R$Rūv&g^[6 3dž&ۃ:|soOιxbD~հ ;ך$z9(GUA/$rV%FnZOj}>D=Ѩ._{lzܺbG֩NQhN#@Hon/ܺFq Êfڈ:ۍ28ĶD>0o⣽%uʭWC41,!򂇍jHIHhi@(1aH 0,D^".ygT鍠A[]b骩UumMb;+;ևvӃoN=!%$Xd sb$Ew._g02;YENlD [wdulj\W9Z{ֆ|G:D4J݅SO9ؔel)vw|4r ; u `Xm\"߅d~_JEZ<)B"Bf+BD" vC \#f#[9SQYcڟ̬'Vii>-){aĞ4w1ffg'g&*$ /uu*͝Ϻؓi֐~ DN[RZ5VD"Ι 3QBV!69P3 ŜHvӘfvP✰slH/,TY3Es9^cT[tvhJa8xFĿD^ EMЫY"nݢti9i+.ebzňT1޻7]ZuKNocQ"ŦD.,N"n4c43imV{ $7$$M1x!8@"ODN&idGC(`D"&iGw^ \ O]r R3g༶=D3[B"?{wE#ݖm5zLQYNA8hIFNwv%] ;RuNȮ!N],t-<( .i׸koDNG&馌vD`dIbgzg7/҇?,wǭ CQ8^-n鶲ߣYNvL6d#'5#Y< e}Ĥ@ܧkmzQXȓ4 t^5?d{(F}KH8eܒDN3Ce82)b#˦>%r3Fo~[^[1]>)6Im%r~[ d2ɡD.xTΫڦ4!l*^ `|AgsI ?QIaHz+/¼ÚF[8䯻wo!S(:ް)KOKg罓eӯQu/rZ@td_W;6Ub\ DUjjN*!kn2dn1d4U9dy[.Y?ln|:^H-u=x~|j YMr%$r1٩CFv3ΆbpG߾o [fddp3jcs +&()eO,ySzygs8QZ9>THQ~']'1J^Ǵd , $m#u{2i d mEqTT豣摋=Խ;{Yt3[ '[/ǎ$>E;!PFyNu]EeP}^UDl?-bO" Fވ[E"X6ȇ,K9F JZJs\Ɂxj"YzAkV :abJը=1=inoIE.Cu*9eM5m5.Zj^voX@}GwthlP1vXVw; %Vr 5:祅LD\yb =}遭M\:tK4gLZb27wD!tW$r ]^[0KK<)(A"ך2|N9WrWRA \7b ͏{1py7F>O%0I8KdbvQ"תD.UkĐEx13,{AQk=N欑VKJ@_;﹅yQ7FnE7wm\"/G_!Oc{FF{/,)AFsܕȩ> sy4()>#gM|M&FEXɇZ(y=KplzgdX*塸Q3]ېȓBlF=Vw:vbO"Mh0*D7Ij%D>R *Ԣ%׃̓> 1ΗwZKIw@SHlu7w w%rmmZtHT1ёPD"J[ru(JNK[&]K׆_.dd K8l ie9ϯP/Q&g_xX.JҀ=]I )Kt=ڍ= {Q$8}t0ǝg14uP"_l!&u^ ~T2 kP@Χɒ2A$r HgXxwlӉD.<(z d,rΪ-$''>kiܳ*GJwic2;a,尰KX&'r;~|O,63O!v;S+*+R',urGȐ[o9-!DP9lD/,,9ɬnVοӲ_׫VXY~mE6OEխX+12FE(:aQYۓȃQK6slHk_#dHY仄UP"'jZE˹Ur"K'Nw[Z,8if$K䭾VD(I\2 [C=\6~y1pirKZF Y,YmHҽ P߂qǧ&t-E^~ *WOaX Ro.)_ۡ333k)!-A ۔͏los86F%}c)eO0ls5 Fz FP]]Œ(uhͺw=l!7~{x.V% ~}˶D.1U:qU[}{H䴦Pd!M8A1sfx_;v1D%>ֻLU%7jO6=Hg.%W-C˒rYEPOz M !D>r߅,F$E7yzx@FQG3"\=%6%r%.jd anߋL3=Gش@ RRHs|`xP8"ȵfwTNي"^} i$joſD9T.ζQW rCpD铏vw|:"S۲gI"w4XzyL*'"XA`8;2ߧ  řzw䖤}4 TUW] dc|j"2Q~A>S +1OoN+ɹy"+9(Xup ?le$~t;ȹDN=sn>1uՖ]IdLvtEQ𘚝֝%Ҋ26&֒{eɶ $ryik.P]U]Ky&;}{?m&Bl_3<mP$V]]=3^H~\V6 9ܺ}* &B(%WByujXòk>,r,g 90`(5s;ʵ+:#dd!fE=#SJc.pJ3ڊؗiU#5%ZS;RgCQ"BQ:/!ݓQ|Fc_ÇzL#? d&ɬ}Ts\XӔ`O7յ$}#[DΖwݏ[T_wqim|ECGd䶁6yJdI]=¥ b=zh$ާa{m@^_}F7/]F ByR(Y4 SzXVoYh4yZu p1 hr }AP Ck<%+pj#OF]233< ".t#Qޝzg9KK%+Iz[s?B+.I92ő0èQ:dTU~INҽmDث 4{SRfpN\_k|\LNNzM_kdbh6 QٕU];S𞇍LrX\Q"I^gi>U ](Vׁ&uL6BhJ1D:`p%EE$R$C㼬+DN+4('&{jvZe̪DN)sIdIIWlH{~#4͡"IR}K'Tրy)7f|2PјdDn5scQ.]$ k)QP쩤 O IO0Dew'9+3 doiy]yI֙w1 >bND։Ѡӥikz,'1kXPi< a\_%VbCt"AV4-p%ӝ{w͑,$#~끃*- $=,꺚0wO;hAJvrlt.#7-;j~Yٓ2zLw N<UՂDnC"{ɹ4aHf}ws "ȓ6孡'Nқ+RQ&Km5$ \ߗM.XodaFd%rziVꮅutKd%eQJ˳E5#iJOOQiI֩,[.KFv`mHdU9)t5wK# E8 HT+Mdz9T+~:x-AbCDW%xKd]ҨH@X d 2R)){LOh`inycRRU;v?* +^R7nl)-ޒDN7L!;'[fK奢be掍ٓ2XM8&_36܊LUUkHW>'V^Y!ZzdXݝZ 2;B%(k' Ͷdh˻\I&/,804h**+.]D xS76ovzүiJGQ^eS$/K_͇r1b%@e&gB4zMH"#oz$v6UL^ C~l9Q5Ԥן,+i^Q?lC"+M,DnHq"$cc" 葑aLM?sԄ,DV[?VI Ee={yן'M$ӉP.4gVi-LR2MȞ*Y-698yĔ,*Z{tb$ry҆#A3|1dVX7DλerJU*aoJe;NƓ,YmEK)2̖ߦ"'{6DRݨVdScjہlV_Ϣ9EԠO6-Ī6X<0cbچD`&S3H@y $6P9U\.\ Μ;kd*h֒-꓋n7Eh;dD#-E֠7B%"֜l''4I_Ҝ4$Eswn)9Z!FpZw_b>i QxN&W{gZ;`5c,x_+ @0`ao3J503>0y͊e)#mZc j!;ݠBMJg6]$&t">t{'>T4ZѼydH"?pVeV2*먕 ->yF|HKFxSOGl~.;VV`5_k]L,?`!4}:|^r8vbD[L"O8e4Lv p%r>vA6YHfC+,4jn7Ч8ٻZ ibGڌF"p3Fm24Iף=7՟p? D=o$SLA_dx|e3:ۣFryƱTz(zs?=\<= ΋oW\4D<ܙ*qؘIQj6:1fϡDUiI'Kr#69 NQUi$Zqil˛6 zfjiɕuW6+~W-BSWפH֕:1+z'D.#iqJ1]ziI;ߜ^omaZ4nF6/-A-p][ 4:I6/d;:tqA&gΝ-L)-/}UfhzۜHy'EuczX4e>n4vtk{O/JP]<,,4B, a606ȰImi;Dh_> [AU>{ێn j%MXs_W;z^4>eܶG`9}tm}- /4Sg{Z]eYk;~N}e Ȅ,r: 3ӆj>,tEǧVXiX+(C]9͵ſh@ Dq]V߼{˯\j~lou겊gv`tum $rhЫͻw-zmC,rZ}mh`ίw?,)]]_]^[ugdY9pJM} VF޼{[QYqA~Mڻ:> )`];w<?/~84i˜ d{#ҤP D\ jĘnsR׆F:--ϋ_L[!H|֎~ >_ 89p'x8i`ʷU嵕/K_榻j߷[7_5z굫 ]; =nskr-:U3 $''_)~qͼL -$r $r $r $r $r $r $r $r $r $r $r $r $r $rl%jVkkW~c ] ؛z~|r@546qVhme+[ (~h~3U..,.[}THII9p_~:BSxkW|kU9'ȁMoYzX^[W>*-A끨=)g℉I˹9&7 88e'NUچ::}33Ȼ٭/sKeU%rmj,,72-- //8ˤ=vt]Ը| ˗ U;JNu;+`(gvS/^uv N y6f^VxrfäwwmݪXqIyUOuq/R:Ͻq\t{^ S3󳓳S*ϳtqߺ} 8M<99굫T"A?ι줮%G̣jx!}#C^XYo>?>=wDk㏘cx1"}ev+(ë8t{~GklK?<+UXHtK}3u˘|˜[۷1\qjLLMzdgOm?r[{wu] pe}՞dqS"?z(-ՖˑXu}wRW'{w7d .C*-/)-,^|ɕG"oy^jQ`o{K v]^inVpZc灿@v}m~ 47qY)))4ܸugϟs4-<=EP<&s㼿UհJ>57햊ͰL3I"&{iMVW#~52>bW]|זqkǎ*9]ϫ襵鉙ϳK*#G'>_ w|eZ?qdɗM3񌍽3c.$gGdyf h<ے![}j-<$ܒȩWA(~4WDƀǚqvN6oܭ:{>: )MY3Z -oW]VÒb;?~ǚ×?#9=J'BZZ9dNjWaywwoYyK֩>,丷egF|Nk8 ]o~s@esAeuD"/-/uciljܕ%n[K9?U(~a0=-u鼿' hpkGM@hmʳ=PWͯeqeC<[%\]}#1dL+y¹WsB+VzoJB^7np^HU!#$6G`[D>>=a޲R|mtٞQz׍&iBsʸKY2'u3zX׳*ru;fY4i#W鲓l3Qu Ky۱:1[ ;'[6eeU7ފR?<>bܻß;?jO;/NMMe/uHDNtLnк+yov}^]Sx޿ν ON8LW 9pg08?+ފCВ̹Q/.{R~!_uv҄Nvo{BsO؈,$ݝ;WGe^`005"=.v,Z5r.ylce~Y\׃<4~1bIΕ\,ٗ=r*mQ/G/s&;Z-m+K8o ].\"Q3-kρ mGݽ9b۸)Yd<ڊW_ox|g,yЪ.xRUCy;>5atyk^QKTzvw8~+D9;F}Vaonޖ:55̬@B\;92Z}mqϞFS_r)'''Km)Իy``&t>EŹD^'Nvaq?&9/^4s9 ƑD˯8x -E)+Ky&?1`N9ͷrs՟?( _~·U|]%vDqZ~ZdIqtS 4ElL<=e=[޸xOH1@'ݾS`&G\<-SP#>gw|]^(wǻڷF=brMy^H8PK?xa8gg#dt;{j:z(?U ʯ-f#s(Z];Y\^\]m7E~97灿T7ˁXeݾzZ@v+n57Qhu<p%Qj;~̭{My [pv|j"l0i<#i8:|[Wn=<И՞z7 J9Ң)33Un n4C -[J9Wi9m,-[m-esbģDJ P+)p`_G"VC|YzAO.PKJK$rzuҹs00<n8I9?/_X[#1Ve!k$U_`'-4D8-Y{> cÂJSdKz=,ToXj#㱸)91싾)p(sdz3y qNJJ/$r>-i-H4I䙙 ز'R/A~ZuVF'9ledd=B;HKK[XY_:/NϜ8yҧH64S:E%g~Gko{؉rh?̻GOyaE _izB"հr18<&ҋj{굫KgN?! B[82RyOVW9Wr~֡Ço;*ʸ5^w"Ӄ@@N?]٩8ʫ OZcr~d{4Mĵt}=H{)WFMKѣr=ڴ!y9;U)_W; "kҪ~}6* S$+.]4i`jnKE`o%;;z}dtbL6TBC <8s_|tv#$O.dO"F]ynwtkA]wkB,rj&)ܶa+׮̘N7\ٷWeW:|mSSS_6ظ\Km6Ki-&|^{5וF|I{g3?OO le RZQݧ=]? vE7*See)))3Jl_̇/btLjE\2Fg q1ܽw|w#WU} --fkvIïvϟF?ljDN9`u-_thU:LU{^bL9^H ]k={=(,.a?/~mk9k(ޗ{~ÑR2--iugJ!}aR&e/^zsmz}t}{#V:*_s x)-oSnܺ>n+ܩ.(!jk>Om1|ܙsg_x7)IxtaexQnϢGu/xP@vO{H8ȓBi$./x'j["gdK3gȑ'U-TS_C <- ݱ9RFԭ۷ditrLUU~l\6l'ܑg$\emW{97GքUUn5>20<(=˽}C└~BUDy%՛%[xȟRX\VKb~aIoXna-`|z肬SY1~:&Zb_[Zl noݻ_xvtt'*9U5ϸb45ԥ]WGztDHq{pd(斖\L#ǡ~t7sȉ+׮=0*z 0;ڴlkiMrw$ky׍R2uЪ߶X/K_%=s}+{RVS~bCr]ȽR- E24OM=vt~y6SMQC!Qp7|왰_xcLM+<5 xI;wq-tM?kpRizb굫}GyFT">O(&FYd m #ܚZ71@x}dI^]SuH~"EвM~V} YRpv.'N`ebfRT-Yv %}-gdd[}AɁAݏVЌ.m\l>äR).)vrZ}8+ G.qgVNAcU]SF:8g@"wrA;{!s;33D`GcǏyX'RaFo+a +&Z|pwɧ-ɗGEX*yo߷peJ!MoC!;iMW_ySoMLztƦ0gK=?&D:N{'8n-vs^D-ծMYР-W_;>܅"wK^zg%s97#'./ΐݟ7 Lx7@"D$W._z755uz~A?b{GwDt0"dddpPvpB;Ij׾8c01NqZ}miii^|ʉ'Fhhs_@JG&7\݅QQ<q N/_s9VH`\*:\_ǎ+[PP757-׼}btR$z -wK?IOOx'5jȈ4E֩,"9@RWV[ⶶ-jxÈn}޼{Uݬ}ݘQ`]oܺG"¨kc.F;چF&f&{>Uo/]DߎkKmuh mRVs ѵ C/# pA"缔C|A6ZZ]VUnssV9]Ç.KII1 1:1 \`'''j~Ж%rYEYm{ݽs=]^'6q7 yz]&>/Ʀ7K"1L򮇵;=v78rHK5"b9wQE(G[x!s+6.Hn]_ ~ $[n>reӫk SSSMӵmʵ+URZZU3_7gQɻvgv\hśs%W*JR?߅ZJwyeŦGj~}ٙQݖ4mιsn|֖5mэHff&GOMyUyeڔ-2a= PRrHiii|ijn[^+A{ @o+xp`$Zƙq%/ٮtHt}bZ?FUtFFF@߿W?ܴT ==צUciBjj*/ڗ.IϽ_sx|$߫Exݗ;UZi&Dv[ym?a&>t6DgϸrCHrNAx۸ t;Z\]_:}u7_5tVWͽN@djc2gΝDD%oԿƬp//ڣoW荠FX/Iu/8x^+m\'DfffWu>- )Ҟ +zTκϵ+\_\!$rwܒ-ȟVsm ÿDN?qū_g{zZNe5Ե:{޼{¥ r)]ؓ<3Xu5 xZ}K㶒 u{_W;u]'G˛L}q^-K}Wͯ;ܽ#Щ{Z՘II!Q+}TTV /$Pm6?AkyYe?;._JZ/t+?4̶o޽/{-RYZ,֝4u[w%?!ȝNJ'?s.wmY^rwt{xЀI"}&?lo+eǦw=z(SSSޙ%isVRD? .WiaeܵstrkilC׎m\@M-]4Үm6 $Ǿ?fyi4J{{UMZZZW_w džW~Z]ߗKm#3g./~o/o{n2g^.-Uc̹&5ԩTls%v>./uXK㧏QGzRU9>=V7-%觭H@^g*wtwōt2֦摉Qx^`+_"m[x1"5T*L.;qJ.]Yn)(*.*){ؖȥP nGh- C.f_m?8$wGwVD9 >D9) D AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AD AёQPPPPPPPPPPPPPPPPPPPPP@"GAAAAAAAAAAAAAAAAAAAADHSYSIENDB`kitty-0.41.1/docs/screenshots/hints_mode.png0000664000175000017510000013000214773370543020432 0ustar nileshnileshPNG  IHDRDoitgAMA a cHRMz&u0`:pQ< pHYs+bKGDIDATxػ"QRD0@0LLA@DD ן*iF;X 9џ$I$I$9$I$II$I$Is|9 s$I0'9 9' ÈHr9rn74M#b8&New:vznk^lv{>/$IZ,q\~6E~/ɛ ڋ6y>Bs椏V8N._Vh4Jd0D>gu/j,~>~_,0'aNfL&x<J%yRt^#b\&v<#b:&?{g~ժVٻ0 B"!x.Q!Ay(JCx>r2޿_܅a`VI3j<4 NIqC !qMVEDq8I˲("ΕP @'en PdaHDCI ضMDA=Nr/ː$/Au8FBnn[P@d2!rR/$0l}gq{xITT<~w]W7Äa8MӜN%js~ϝ[IBp U`08lק8Bn"W~1]l՘#?X. $tG탽yb APTPЈ "]Bp%~pxE3e_U2[d{~u礪nP:^vz˗/sc/|R߻wOH@ Ǐ;Ri$XѸ:1Lo"5+s#q5|+ i0sx]y'O t/ ݻeIOOOiHr4>]|+z3rֿrT|QE|Xҗap@?n 33N բ=BODcܒ;^Z1c9 ;UerׯsJc'!͛QHƐr-~WbEʟ(8ϟh^x[3_k W#Nrʼn,mw]G (8<DZ0_;H^e`&N@1r2>]!7n`=z.8::ır4z~L58EVN n-_^Oj[.y!U=ϟ>}ʣpo15oWc1cNRǧOƌI8!4+߾}[^$ك(bsr%lUoiIJ'Bx㳖/26_NON9"fgٯ -I!CƧ)<8>]}y;#;`j7I oa'V&}-Iz]K̵+yc1sXvȀ?H!Lwv*.0\,B!vZ+졜)F@]DZjЄ҆1Xubv ")ОvD-*>g$/I=+XZ )+)jC>Ч԰8ppn༌DM,(i`'5ܿ_^tPLu;M\31c1a'!;H@LL/IG:2clgRJfXVK_~\CdYI݁$E7 %h*^$ ==$ $+Ȃ`j4S> H94N;)F&Gu4R-:1Y[N%؇wq[X#ULw vR[[KZZmN—GZ%_c1J)nĞ,$M*VIH7L}vy ڳSF(+=)G Z]GtFW[^<{LQʉiB tΗ$W DX"bF7PɌ;g|/[`$(Npd AQLr*ܹG-;$Jy["-,|Rc1s(vh#"_PM2MH@6h'D튶ȳ|8]d ͑\J%%.+"2 Q dE9M@iKQC{:+$=c T3D^:D'H{)."j"ġcr2o/O;0 'c`1c9,;)ʗ}@UNۏoWrhpdEˤ"k[+(Btit3*7grarufT`M`X4TTνDNkXa'ݽ{W}Fq0/Xc1cNH HqmvjeyFAh'RbISo naO(b|A il'`9\\VE2;0>=4 c仍+Es`ʝ8 +i_j뿼>) jxL^3Ԉdk1cI7oD9oQTA $`;jʮsc1s(v1?YG QpI5f'QFugkLAIhT߼3@"wqx6S(#&MiR ihO|%;)fRv6uIn8)CBP ӃDՕgeIDL+gqUq($FvNNN.@2n.qL#F 7/_^߿caOvgYn10&c1s(vPćHUW6f')g&tbT8> \&!UxԛI`:AWNt9ю3p!TV ~AD\*P0> %`"2& IUrk,B;'Hzm_ 뿰>c8_7>oqNC9 hRN#+?鈿Z1c9;pmj@%H#!(z|ttțDB(MoT' HJVc_eU0_u5'/qp4n!Wx{>v^gxO1y'Yt:bŇJx0>x?mCwx,EU U;_.+W(׷p:O7`-tj4Im???٬aӯn߸<Žݝtj4;@;C"N"NB b19N#Z=;;3]}~Eb0s{}?~շe'm*hIIIxlZ>??wF~/i\& GGGDLJ~o,;8i\.'T*I"y?dEf)ɜP޷'';sW `!((XXx ZE&F1F ʚ</B{L֙o10 [.p8x^7v"^"߄@td2!\C^vv|Na_VQUfx[=bd295$l6ÈHDzC qº ,[nFz=b1j(hUjFf3D?ڠk:FW^"lEDRIv>$ , àX,,[0!P9tR(Dd$'w{GUUg t/6Nh4 6_glaPÉ}~>a5Q<k8r)2؁$o ɐu({e®Zrsc"^<+*G@ /t),>tAtUHGaLB?Ow} W_ߺi1ncL땲dAvbFq>Uiʇ$zA =j*ǰpߥp"X s(uwХj]$M F{n{1Pl$1$ aA>đh0!qB]|\ApH Ľ aw$- [՝T.j7n~,n 3[/ ..oIy߭'fzR@a~U)GP8!F]YQoOP"Y./Dd 'Peo@||s =1O܂S5S[>Ow}Q%D FwJo!_ܘU~s pLb`'ۊX&!Iө9> RhJ #B_ΟNl,m A?|;br"!7W1 xMvdG?V A#{Zcqcυ;񡗗2 6g_wBܬ7y@]^__I4ΧY> p|+MXx`e#$[1d/ 跩΋k%jsw7N1kﵓ}s㝸kC.`tDqyyiN9_$`myk28 BPQV *ڗ>%g9NOw"d/=Z$@ `l "%+AuUjPyy$o$yfAqYQb81Xy;)'{0_xGGA % xVFUѪSo,vuxUfi[ NI/ԽT̅+'@t|헽#3Ud3PA_0ktwֻrmȃ@x^n{I]F ge+ 8}zvD!h.BBƩDHt,#ABqqqW1guyp'iPqTH̃\y{Sk+XK(cI +梲t#r'QaZ0wX~AΔH'\jj)M[~p,];h|9Wf,FfXlo<@:ݤ`eGE/تlڨ;6 $gU1+eڏ&/mx<?I?_8 X,d0ͦPk6Q@ҝ ^zt]t?}[%/H(P9NRROII}J5~6 R$\.ht@&y.5 A^G8 Ir|p8pҭVtFRs@a~$Wo)x?q$>܋7t:qPVP(|:IFQ0:??qٵa|" z#."@L EQ?lO2ʂ4υȺp8Nb5U7 ΀4'K,^]]p;SvJ­RLӾ" )D6Plū 1 ,BY(AS8׀ (VfA&34ny~H-=ԝtې ״' ~rrJ :~` ej0ݰ p+! 8I(%~fl/lKdHq*0󀴉Alkn=}m&b(&/i'd?1ql?$]~W~G; @[q&% P_$'D^R6#Yz}K[Co?%D9I71Ou]u*p8vvvY:|LNRdOH"ZÚ--|FjG?`Z#VG'&X#"K`f[R_J5 2eֱFb3"kG(]z > $$iB'WIfmfaWalG${<==EUpdJQXz v5Y".ēϰY&7JN"gI͠5?+SH,39??gu$ёWWW熓E7P?a[jٯip8z8 ff^1KN\Y-Rjf ޅeԗ4gEwƼq09[%Ȕ)Ygt; C WL Ese' ƂLN5t; ٬' ,l@-1#ɆYW;N-C!O%\B<Lwc}WV"9vG͐c~2Y3F:I!Q̗ҔI'ͿKUɻP(  rKC}P[y@ f`h'MBs V KL1 U-4Es`}KZ~;CaBvK'1>gϹm{J ߃<$x$@)?5N4 TW)3aA C$߽băOX[NԁN}7[_x@5gMXMﻵ=@%Ă/nZ+no BP(nN0fҎ(PP$hoIXTrysM1ϫ'!lP9B@B`;J^On$ Hޒ1c1qdRrc4-m HSN퉁$Xo ̄*`Lwo}^~pG-~ѸkZ9nG: BQII#oԅBP(4|]b袤>0,:i PV8a?/?$ 6U x~~֮)9wbE['+4N+g7㓳Zj'Ք%\d<<[l}Y5h`ͯ?̒"XLv#lD2i>~Tˠ. BA$msMq؜jKоA=« x1܋`R6!?>HoIAOZJFJ."/:rA"aGmy# 6@|&藷B=x\M7G_ҾcI'eaQ7}-=A]( BI#$*$2 okJñ"\^X ?q2HCw`"G"dtRna[kBq7ȿXJIh"R53"Mʔm! iVb$x_fDkys_/o {F:IB}-=5]( ­IqaIsF*%]H$E9=>NPz6EeS%u;-1a7(,A4~4:4 ##A} g=ߡ7"?'AJ' IIy.[;@ "\T4@8uǭ.Ni\H_fkys_E4o \Y`-*?:]ୌ}-=VhRP( N'<)"F k3A>Eg/c?d[<kX"Mj1$ 6}-=)]( ­IlPl l\\6R fgI///CIlO}_\80`ےTd@r~/q:FI922=f><EWKQ[a4dtHbiU7N'ʦ:kW}oů9Ud B'_)_GOyd^קVRjֹC;?^7N"+ [ uYDf~2LaL:鐻M5;yC O o~`lλGL(Ɂ2^׏>_{g#GC 8u|pPhFSDA l\EO'N'SCļNp?1Nт$>N`TN?zR_"I{QC<=!?"R |*8 X-v։+eZD>Bز넓aO\>28)<"}-jhy#q!ҳx/#'ٺ$ D ~?_PXW/~{jA~?Qu#Eǹ]>cV+5 ֎ʠy ێ9.5#v|?$`Gi͇DԿj>0뗈oz?~x/OO:SCw '<_]K{G{ %E(O^--fwfLi(6 ?7Ip{:ȎXp6渡/MNe x/#'9tywď)m|!b¿yxeS? WǏQ6+U; 0gppO`,6,u e Hvjw'8I (쿙K~&AG׏hVcg fBpR]~rʲ }ՐSE'Wo8czप98 {iBpח'Ty{KIQ (gC]zR>bpRچY` "t$e;߇1BNڜ/Bx'/'yO46$)g&agBWcӰ^F6jẎpY[p~8irI3kS^ /_DA_~]$_ ƿE1[blqyw$P'=ug N8_9^IzO4N̞{_ÏO]&߸LBnw6ȨxK$D V8)XI*oGkoχgMڃXׯ'am!(3pbָA~9 c>܏KR'շ[.=NeϾ9z}s ǬcWuUw1Tny̋Um Bd߆nۑz߭|lxݙ/;9 '53+_oi܉{4M)Z J~|z4F=eBS ֋zg|DItp?[YD| ֣{yRzk{z|8SZ:Hkϟ?? !ֿ;}ᤣ۷o z6Y"y 0 f&,H储iQ ^O%GW#S(HeOdžW;Al'/~KjA~?&ڀn `YǯGolIhq9*rz>"}mӚz{smpRNoNR?Z˿|><뗈oz~x+" j %ɇ/:Y= 3!3dK4;d gG"< ]fzpRO:xu 2NRMHz𑳾L: 8m5k|;+(jo`B'jf{z`<>ـ,ZVʿy2cyb9ODz* yx%_R? OClZncӰ^Lfkٺ/5$Ve}OTx3%tQ4X%8xN ֣)kwI(!t.W3p,Nr2X{  bu)1)!x~ILT I%@닆H;2s̎b(e PKUʰ-8=#ayUPƊ26;zzBX3**JYqm|$3Xߦ KC֜4+_aiאϾ~?R>v=|T D\$,^\h:=n/'E3I`e[oˊ/nv?j߿v_yO&+@NΜN 'rrrwVED<#"',2^v]DutdZo׫IGp8DDUUIGuYyfpC$#EQ$OSDe`C$G|?9~9INb]0@m<R,D-BDPocd>CdUB&fF|>/nFH\>x ~t: jtt:ȑ0ZoB눿X,`>; E>:ZC/f(,bvR2r HL'a5b d2v1rDJf1υ?_O~ΟG|zX,zE#?D8|? jcz tGtJ˕p_iVTHbRTf3Q?X BP(N  )t뇡KRD7ɈRD4xQi~{$T%b肯G; bo<n7aV`>;) RwO?Kb1!D"d׷$H84BN4zL+B2Gς?_O~NGbdfO4O@ :o: s *1K+z¢( >dHaAUL6۰M}}W[1 wo3XcQG0<B!4: {˗/"4g|zzNab:ÖrpԼڏԏ\ǁC'''|[^d/믱UD.T C-?b?r[q]"= : Ey"1ݻթ۷&Hs;Ԩ:e'[o}﷧___]S%t~4~FD'rSzn '÷ot;BQ%iL1£˓ K!B8I_^^ `6NҟvC6Bg~+X _61o3Q$:X-eĊ @tqq?6C7QRG~4w C{l8Vڟ:&չ_wFHv|I~BW*o޼WƷԜg իd%dB!5ÇJ; !NE!H8IMaI /Tb'9/_8Jd`k^_] c^ŗf竦fXݳ&7k8ȕÏHV` lYZ}qo﷧___ zZTRB㧏?*Gl nG|B? ڰw?$B!+wJ; U{r5 nMvD,kd$< ;ɪxc'2$xw`CO-i%֞`Vˊ(MWxsׯ9eNX*\3~aж{_{iwַ.$;i?>/^C >0 ^R4P %b Z  Ooy27 !BGf'51RZJ(+ VIX=9f$!* d@c+a'Y/_ᄴo'.CE`#~! N~CldIMWI<ނDfBc#YtM=/n;}ɓi IOQ%W knٳg*ꟲV~i$B!j]$5dQVIde.ލ'-&XErX-vU[/C/x;IJ?[Ժčؽ_Ƞ/`<;M3܈~BlI 'sSb X֤1$?[~|=zavv?~?>D#rYn$SG1&}-=ۂF$6< (O'!B8$6a|hBPJ@Q!0!՚?~dw8\k'om~IZw&ՇţE$}a!vV%ݠ7NX[q{6``'}_{wחp U;i?CjMXm{i2Cvy>4dzB!NB߫k`mίk($:ֆ%a'׺AF6򏷓>È؍3 (Iǀ@×&Ģ<=A"Zad}qo﷧___% bvv)fi?+]o̚899YR߯_nbɞn!B>"!rXԼUKIі0dW#*쉣 Z㰖{7w//_؎vC4i-$(aDp[fXTTowm>N2wӯѯo]_yDL^RnCN9~zK5ߠa Vy'R1ɿ3,ˌ&B!NR)M7̘Lߥ[v~jwS*"( aFG6^eZHbO3I}7w//_@paK46;XGŋb1`;IAC d @Os3$(r R.x Q$`'z_{w׷k (0~L!'0ΈFs)9VӨs\_d?\yTфB!CIR)Aiԡ5MLOɪ[tW5!)q|-wBi'DDlEc5԰ɷ(Gtf׵}~}QRp  ΝdIu)*[Up IF~{>ZL^G=ud.ßj&?}kA-ѣ&ŀ\D,ϟ?ˏ[0.t&[0O8фB!CI * ED.UYe*, aL >&?7a (E N@5='_&+DV"W"x;ϙe@8 >?oxJmCؠ4Iv%N$N@8 $@8Wv@2TUUUvRUUUU;TUUUNTUUUNj'UUUUIUUUUIUUUUvRUUUU;TUUUNTUUUNj'UUUUIUUUUIUUUUvRUUUU;TUع{0 AD D,VlҤ*.,.XG溊@ 9 $$$$$$ y%yxL&'N&|>l_9 ("t:%O2.'UG>j6~8ijf1ybQjJӴjIQ?nnݨp<ϓWn#, h4rREza9aNz\.M9|/FQt:YEq^`0r$'IsRNeٯ}uRzGNBN^%'_:ͫQƏ,,BI|#I 1-N3C]j23>;yVan#f2OqnNgٜNtZh4V/7asNej>0N)l6KǃxL!B{,JBVZ(aXT*-ejMRXtݢL8&+OpݔBBMOu<%#n)A~=z^4J`0>rܭC\}4)ROh4Fh``ڂ '$2,ߓ&\DP\.PH\.}s'I8I0^ìbJ91' w+$0Xhl6('*aY 0?\#3`&)|>M>qeR:S׹d2a !↗|&H0Qݮ?E xa ;H a`PABTPq .|ݸPW.]9HFgv!+43͟7-Ωl`;z|$Ԕ_g>0C!Bm'a4a?E”pqqAN ߫} ^בٌ%'50А IwC@⭺GۣDB y c9v@ wmmI͈1D^A6xXy#20UƋǻg?kG`We'lllpo>xu$OÑG oV+`Ⰻd"ވB!BN!߿ $a;tj`U&yLNB} vR|~{@RJv"Yqz >N$C!Bߡw8ld^LMYԌv(g*;N0BF 0 sd; !.hܭ $ ]z >^]tzbw7xgc!B;Ij K4)=wr씢?z@ (^y; !:pvvV(5)E&OBN_ 6vOc|Len'-#$EnvR;^N?>jlDMY=I ]n~B!Ÿm'Qoƴ_CQG}Qq  j /ҙi,% ,b'ql+t]H >~<.k3_JD˱xRJC25Y(rv Wj'ceE,*0zgc!Ba!;h9޷Pe(7۔S˻Zз<ҋPA+J u: u~ Q%I4^@}pc=qEՃfvOo|IaIvV#DH=`Ͱ;XSQH%?_NJpVx19Pf4ƫ[N' uڱ1Q$I+jYeԥX{E+KHIi{*8b}`?h8e0'fβ9E]*kNOG?쌉'gij=,$Lcr o("θ

Mxn } qٻ^-'ekLZiǓIv>129IN@N@N@N@N@N229IN@Nc<h3|:UO'VUz-egϷjXDv\ԘO;8-# zo0l6z"f]狀~jm|^<t?|{lrIݛNQp8|Qm2vstz<y^׷z-/,4$ ç['&NW$.`B"cAGDyD}P|1 ( I$3 : . :.(2 wE}M k&TĜ19u_V[Ӿ"'oa'E/9IKAIY=CHv])' W7)'GE#,ڷ:p=XqF~{ T1cP>SN2ox P9_y AcjCr̙3aޗ/_zlo^=>%=.9_Zď"'G r  mBNJ|J)@ 9){222={V\hm_(&`ANj9)r?>x?%>>["'  I9C0rvZ%'=zHvZ2ط߷o߃>|0xnݺmذ޽;rHii)<-1oV/ܡƸx"\ӧ촴 ;w>ܳgO޽{oݺ͛3f#F(;vhe,]n䉹pC[N^E=N_WVV^z}'L@ ON:0+jjj0a֯_S%99yҤI;v{ϟp%|{Ǘ;Azk`M8ĉXwOO>}MΜ9sΝׯ_Z1^~녳bԩSߏ\[[ SƏ92{-a:<7FX"' /9)ʢR*'S' )) ;^J4˗*;w"'&sѡCj6b/[g]]܀\Pi&O|uǏ{M֬Y4Υ=RԖ-[T`鯑N>dWEk. 2m4X73ONGvf5j6*ʋGo2z2dkDE{ X755Uy7n^r4pZt?KJJN<e(џt)Řpᐘ:tS9)ĹYzC[3;U祶9s( C<۷ ;VZZ*={XJ|rJiΝKDx '*5p}"A>NhT(3VM (ALB[֖FAZ0]^'4|Oy{|NF ĜiNtw{Xt^/*yQsfc.CG`F2^gI"DYqCJ0Ga*ZMuu<}<,⁑ӖhLSyjci6ns=>܌v<~I@xbo#lYfje,Bhr| VoGy!ۅ p~L((JrRyDb+为+=}V~rۅ/cp\^A!\IOZet&l]y٨";k9iM[nwPP8s8pnK$.:r9 Z*Ls>;c8 Lj4vV~ ()D́0`j+;I>J}͟nṾ7n3}7˼y~fH‘[%7 oï%F$a>kYz Y`tݻ*I1XIrR}Ø,gȮ*K@Έۿy-u9qq$ڦX47Q~ONQVASy3GGGz`0$7 >^.)JBC>wbz,Qƶ:9:-=??  oAj9;;%p.ozi Ѻ ͭH Gh*$|?vg_ ݮzMd;ojPYHGqL q7o~E?.'98Ӏ(ko}[v+ո7>9i2"FK[`46QZ0$7bpvFzASGHd fgp4i/XuOQg666@ L_jIڳiXj-#'ԺN^oh]ГRGz @|Jcw&PR&Xsr$_\\lGS탫Uax_a3oIr88 Iyg]eKr7HG $j/INNN|5 _n?O#FfA80AW#'^:==M [rvhiNI HzkYN?>޲AO?vH R5ǺQ2hh4-َڇ}A0cJ䌄sr_.'98Ӏ7$)W_֟ jf ٬I@ijn%J?OS5$4 og]M f!HrP: 2?V U=!e7FmL?KfG')6IaiFm ,鄔`kc: P7?v6wg߻z|P!H(HF2PȣT̻7_絛DgRq72NGg%FLjQ*pa֣\=F#czz:3[t?}Iv7@ &M?,O{i1Q-bYhU lUB$ I6).ʚfh R3:61@;{d } !f;[oNFWc޿TAN ־?H믮"A Nj獵5q?_t!)o bzŅKI]:>>Z)"FF|?] ݃FE(&gUI5ߔr)uyyƗ U SSS?n _}%IBVlX5z\=lC-C8jlRѫ[V֋a=z/i:ֈEg 7/dcZ+6ȁ@ K'g$3J%>*u9 ,pŗ ZJ 60P@;{T3 ;MM*aU5#X\\lAjmN*-}%?\Pg*m%snB!|C\R8LW]뙂?~4+1ebZ wlljܺZo.PU7@[˷^L=߸[{K'X r 5Jr ]D;nl$9bK@V`UHgO:E>l}ЮsrȑP!>mcQd*` Fsi999)e~ar' 'IU8?CcFWIlI1>>N]D{{{_#W(3Onll|0ҭf|#vB>Rߙ_cQ(L16F\  n `ck9! F 98_WZߩ!T E'^Z_eYv8;ت ]O}^'].֝z]v4ϣv=tx-^,6z=j0<<'z=cx?>d'uRoԏX,_0BHy{>u'v;N7}!oIȲ:)fD2..W'ҫ&I4~]o@̻|Rg_NJN|>e\.HyOs II,HyOs I)b>fze#P'P'N@: u$N@: u$I";&A V/Sh`O ' ' ' ' ' '9 9 9 9 9 9IIIIII.@N@N@N@N@N@N4IrrrrP5(  YȒ1cא@2$C m?.Ģ8($ $((+&!/.\9? NqW@DtzzZ*h:Օ:<<~{}>0~CRj:q?I{{{F#<냒 IRADdD7x=?_Y BGGG|lߝ3h4~"gVݧW\Ņ-^''EM ܈bծ_ޯYku~)%`^kuדMld21bIDD8wj|1;ф!NrZ 8d2x<^*yuE>{xxnˍ̦t:-jh4`\Bi8FHwb@u))UJ2()a" HQJ002 1AQ^IB^QN;>d9xsZ{=S)66V흘[YY܌%N/gh4Ftҏ>.^R onnO"DGGǰSu!(}x$tvvnmm!;??+))! R 񩯯(򀎎6!EEEl066F&xA4\W _?r*++=<dWBY:d" O̫+uyIiu^^ tۗ<JhY\\tP´399)_|}_N'/g~$eQT 0@#SA#M6/ f"w1^GS cY"QǶz ZqYuogebSd2HAvCǏF )C 4ezE%z u=xpHo:@e?_}1 )0n___V'"6ĶSX& 2N8lr4^E[tL"c%HYua|CA/&bDŽS>rif= DehA$tTW[q'^dl8KkN۳~??QA?NKVdM%JfnK@ˀx'ab$2}I^ ,L]t$ЙA? Âtx|>-C(yƅ _$?=,`^a8/3ަc OY((+yvY~j 1CP*l6 D/>K F<zzzz=7I}+m~ h$""DOl?WA?!K_g~T± Id[r_>>ܧKhOyh4*tT+m} yxwGiqW_k>~tY?T?yM$D"q$>.計dMaG*9watcR2\TkLqIoA !졭.IBsd'qRY?047\cdG?Wr<̅Q!넔21pY9:)%khJ^W u"| 5wIz9%KBPŸi/r,|' ~h<;4Tׯ&ݝwұ_g@HD "f][Z,li1`o(b@N0g'ﳚggΜ~~^]]usG7f;c:ERÝDJ m>z U9\ou\]]#@.?:myAil^߼ysk2y V166fP7 A,(MFDǫ!HKɧ$TXYvn߿/WҠ3ߝܿ*v>ImMu}@B @ v$PWTAVk Mm Fe7n]M~!wˆ z_@% NI$\7Щȫ 1R(B>u:|s`r,pp.W,N" ,{gO4tAd٘|r9l\0L&5 S^BAf j<D̃8O{+G>J_4~>; p[s{wR[_g]v@ ;0XJCݵy:`rADi766ȤsyXMC8d$HŐggg6Lr'Y6ǖK?99Jeu@I5_3biLp13;&j( s_|Ńd9::J0U7E/CyX#at|˻FҠ4T\ 筙7pFKr{ ]>J_d~>;1ו9r{-wR[_g{?@ GwRw~͏E·|G-PLT&TQcaW޲Re덟}||+)@"wF 83MC]YY)X9yZROOONJՁǤ́f6ozsGSnˏk 3]20?bcŶÙ 3Gzdg~yrqqQ)ļ!@ 8S1at||~tzb'g'%hWҡa^WJlE:|ϙ Fe+W}L@[4>vj7K~T'@ N&"PG[J[D4&>)k@?af @OOOhU8 gj'̃)#0*իWՉH~BvuIcsnfbBt]~tSSFs?sFCCCCD]x6 zZ?{JӲ!Xo]tqCD4޺=at|/_ -kZ%M~sƇkWҡs}} ]y$ _Z?}ۉ<#I U7Nw(kk[hRCKjs]4z&*I7:Ch``&n%$L\ 9B@pȧ7Z ?R)WAe~Ay`CĽ;+j]+ nds^x_ #ֻw?n!F#Xғ'On]ƫ7jP6?f(X/;ph\Hat|Aq %E鋔zJr.0$84%I>J_*t~ $oi,&U=Ҷϋ4|H?s=qJ@ VB=Up'=BXU3~iloǶ CQF$zz6`J` $kP0˽Gpc馎)R 9J R>߾ UIV0 zJϻ_rRZ3kx<}^~( '}l^1Mv-=~Iiqr{ϋ 'e^v+=r2  ' ' ' ' '^9 9 9 9 9 IIIII&@N@N@N@N@N 0rrrrr H/wя$9 9 9 9 9 9drNS4áY.}GDUU>|yw]ybck8Һ,HCH1ďH(!Ohyyj(1hAQ5הAz+;=mݼr{{ss~8^ `*JAͨb/:~Iu'5jԈ _z 3ƚԛ3_SN2e*Ր!C?ӧ[6nؠ7|/0+IɭT#qRi̙̟?_Am߿ȠO$~8R*EؿdPJ)W9_~6l0YQbv ;2Z(d-AAd7j۶mˆgϞIcK6mw) Ȼw$5kDT͛K\"e/_+?~Po){VNڵ;v7o̞=zB5TZl=uI{f%77e˖zmuuQzc_|*44RJQQQiii999M6Ho߾-k;{q`=x?⮾(,GmR/n޼ƍCU\e޽\ a656޾}+Vz0ʹsA\XIHH0^:Oqrʈ#fVZftSdd;w>4lؐKah==_ym͚5̜_z޽;+j8Z=zJDDz[v*R֭i(%%8Ir+^)U|aeذaf1m$̴a m/-qRd ç$bE"!I+ۥ3E B:vȅ&L6La ;uֵ:_,XrI&cE6fAXTE_l37# +.]ҥ )bES#mZ[ݻפIRLvƱ+Is:N lD->d3+-"iBcƌBCB2l&3fƌdAٶݙ|LLL +'Nng5j(2w X̙3fɺB qT 0X#lr|\qo:d7ʒ%KHc$&LJs/^#mMF^I=_~eӧ׻m$e+qZYzjV$-{<kJ||A=*[8xѹQZMΙY2~ oT_"PjutV6*Jp;X Jr++WISl>feر4h>gqҺuXvǏ͛7s" ^6V͛W ? CYrInz W>nI iTC$Q$9НUVQQ[la%..SNllI?~TfZje2K[GbΜ9C'q#=#\>tϠތAng7k̹2ѓe߿z~D?jr!D<]8)999-:|0+˗//{cm>҃ʰmȑ#,{ز2[wL:V&N8iȑz`ttt;v`Eԛ,Ԡkqbڷo.]ժUs\fO/(qR?(2Y{B+I2>0?yS4m vŅw* :uޗylbE6I"{ܬ]cqԹsg 'M׺ukz3zSTT8IrGq']*HNrN`s!9ɋ0OJJrC*"" 9(/;!a6XN>W^%e %IzzIrڝlXc--𳒙iP$]ca62ɛ`Ecd'$$"S_>//A}BB>{^Y*((D:GrrrX9piN}ao߾]92=J_zIbJ9RaJ&iAYӇ;"##YڻOMh-R ų.N OZ`c jO$f4Vç޽z= INgr$[Ο?/)IbbDQW\$=M6N.]ئW^T2Xf<0r+z |=.\x&77^_Cg,pY[ehpI^HkGf ȝTVbbbd9u@ǔ>?`"l6qLJ arfx>k!,jŤ^N3FQZY)rOڙ?6IBO__lhENj"JO|y0Nq?`۷ٖΛֳe@tzV0_TJV'ea'+z0_UJea|Cgb N?Zp-hw?/q'BK!0D`k`@DxA *|]mtD49<|sxXUD4M;$Vsi '\Q*aX"( ?I3 'DD(9 0'jb:ۜ1S/1N=|MGGGrRiwΟiD ýI.z@NJZ}+ ;rt:{\x|ww'')iG_T"^'˴Z(:o?'-7rRl6;??2')JI8 rFٔ\ZANXAr҂S1|===%syys|>nɂJ2'Ix~~>;;KF3;<<,vaszz:#fEc/ŀ8/ IEA\H9IC^^:~DDDDDDD<'I80M@$'IR6˩g^~Rf=)D}dY%Hҹ*Jy uCJӑ$9Fδk}uNʕ/Ӹ^g70z=fr[d2q0Nc),a) Ry(#$ E㻰ȋ@4ڏŢݛ\{jY+q wR׸^_IR*|~w/-4Q f *"n/DT. / q;ןiH~G1 ctsN}}u]]]Y($J JO 0zb,5ӻ)I QG]ojU 瘙?Ts_GUK>RHBSL w#ѐAMU^8FO_)%NY]&HqZ!:,f}gt8I}~=dt777Y]ϧ7I*j6O㚻]^̌C$?M)%)n1$I4g!6Yʻ:`ʋz d'loE]^jZwww nB(t3>>9ʤTۑͺ*zĩ:+#?=")KZ^^.awƶ|#I%ށ?'Embܢqf '1 IGGG"nQIɆtnzl6.IӸc8ICuZ$ܟjdd$?a]#/?'鴵nNjZlE/v}};550xq0w&?}`/[˙_bX,bX,߄RC%tEXtdate:create2021-11-25T05:38:33+00:00/)|e%tEXtdate:modify2021-11-25T05:38:33+00:00^tIENDB`kitty-0.41.1/docs/screenshots/panel.png0000664000175000017510000002112414773370543017404 0ustar nileshnileshPNG  IHDRh=gAMA a cHRMz&u0`:pQ< pHYs+bKGD!VIDATx;0;'qʸqe|}ŀD_#gnNwOhh@m#@ whC̺O5\nڏ3!fg Ml4%JNtgbѮ^S@U-gDLe,[j{)ElXF.g"D#~ Ήnw6j/D#y܉hsiڸ+Iޝ6a\bvQkԪոf8$1hVG΁q`Ifޅ gCй9izуL߿X,Z+/˲e4n Una1ڸ}줩^H>ֶhtm5r?/2]#<.;ԶsFȃy@iyp/$F iMlx|۶nnnn12uѪi3aB(C-잎;-wd`b}" 7 7 dOU"Mm;gBjc]6N:~{/MUok{bw="|ppn:7r󃪑6s0ǟ80/Vo[{O&to`7\EfMG\S?>{ۅh=o>@F)~Y$=/F2gZG]:^5-_&?@/v#bpp072nM!$ޔR4\XNv+w8&n 컍T4'9r<$ɥpx%M _8t:i<e @m$PgֆVoEm0I,yr(XE)+8t^@GvgnoqH(™c27?ċe"XFbYe&XBlϗ4ɧ6dH_lם֜i h9I t/7pVrpNX<8hlTg{It=^z5-EL&<,q6rOlx rSUJahik@Sl$Hr9J@E Yw2CuYVniΎkZT9*},IUHSeZh4: rY2^l6 qiΜx4Ne6=yFC/S#}* r> T*iZRVXb?Y)󶟴_'D 쥛»IPh۪DAt gE䡥S7QF=\ռ ·=0vs/f |y~f~:o33Gp+%_1'ggHov .]~"%ή\F.!FvM] %?( !g1V@c2`00qRP( Nll6 )*/YQ,ũ4$F$I\ ־`0@F>Wڱv>K'@C/E"X,M|>E Ce~.b 8\>h8㣠 ?C NhH(QD^"DH I$5y /`A«IB `Z`mt%doywgowvooK.v1cFTh%|2h 늠1l~98i&ry_\@0P9>x/xSږ,Y"H>,Z7Q]۹sg^d+qcU{\ټy|7"=zcü {G@{8&F ԦB"(6-[i{Znm,0OLLռ}rr2넠 ړ%eN)((}w"4~R-PFL-%T2OrV[P&k>^&Wg?>V"/r]MnA1MA̤KKgN;砉D0 U Q#TӠ>sb<4KfӦMBqI$R^".8}Z~'UV_(Ο?O9j͚5rqժUxb^ekӵkW0aB "=zcü {G@:d 3ghxC(lH~I`da5Ah~59qB*A0`5F՞ 60TC!ywk" @XL4Inܸ! #Cj@>,]Vu 1&{m/5m%hh/$ $F>|\ܚ$h"e1ʤl7 v_̚5K"_N_Q,< Nh,,,,,,,,,,,CshԳ77W<'2zh!+A@a޿ESb=zcü {"l:t(XԺ[7F JB'qg _ ,@`!:吁޽91uL*1O&Z 7 $:F9\ B(E#F趁 qjtfd˕us]nnj ٠N]ᏟP dOgs+ozM7s V6M1;8?tga$.zUBޤIdЖyXKa96I1˅:̀9ǃ%⇏`LL@4zjS^&Su X \3m.G4sqFM*)$ȏxO==7Lk52ZvwKXXXXXXXXXXX%9ARƾɰ]a릜V"a JL0%Dl{Ɔy49:Cnoj)j&wX,n9ױ|fflOؤՐ!ЮZeA9Tg(*o;.\1X$ӕ+W4E ܹSnʃ% n5g|>vW{Pq2 hLʻE,B?"鵍Ai܏ Pj̏͊#SK-\x_(wC%hJ)HK) HӦMQF??$ *WAcNΠyG QH0,Pׯ_O@'O~ƣ @+h0);V)h#y4>(Gcqu _>uhF9Q!hbc"ƓE}8- 9L=iN_XBlՁ>XdYr¢4hZEcf] s!ж7V(rDZepǐnNh߻XA1! i͝;W=h"h4kv{ꊤA(\ =41Bɓx4IB@.CkLNY1@_WuqFsWCC` 6,`- ܅.L2UeHYy99F~ 1wB?d*̬-䏏-͇2-bC\a.a-u ޓ{8MT DV4 C (g`wB`G"`VX0$>L2СCt(m`+-:0|,Y2bRYꕔxCq5 %&^HF5ʉAl:3čg}مK 6U [$gP C)z&&hTpt`2lu.L ~oJ=NK|EC86w/'T$hdC! 'Ǵi*a@틌<4G:G<,bbGX ܀As琑Uo۶ +DAnݺG*k7l.&SejvLyGJN_|)0~7+)TfPv/Cߓշ o^ MŧAӽ21MӬpIqڶmF`.Nn%9n3iv4, >Ip_m@:4/_΢ ?AUహ+\MWS5'h86vܝZ_-qEvv6qmT [ "(BXbɴH4V.M)!bۣ16 ?)oH|V|ݟA˹p f$HB|euLXOhPUǀXJ6`!#,q܈o'B#hݼOwcb[K$c·2l\>P OgsŔե957dh#hbԸqc/HNn  \HT͙#hnm0wC8X t~om4ɪLa8SA@,:-$ -r)THEXܔX|P]u&hдx8pd nt&P.>,- #&Z}1C?bE0KL>a.یEx?ݜZXXXXXXXXXܚ`?`CONWxGtMuOаR rxX?ZA\@h?nSب~oݾlKq3GS%Gֽ#Md~:J4ol$`F(—w/1=z*&x71"POd-ȭ𴂛,FޢӞÇ JiiiXP|\eeeG+[XcMn K凐"9+s#ZP@E2'RK3qɁ29Xz ?օw&,CwBFSEo#!K\3W| sKdݕ蟺UV|UB7}ªf؝"!߼DžcgfI7ob>i޼9󈉋'BD0^E`CNbک4*`eD9j\'&&F8NNN/~x%'NF3ezH!!M%C=ׂ%MڬSmOg A>|XFn$!Ѿ}6X0 >4(j3 Çm*˞{{{OB+[gd^W  ǁ*eeeo߾Z xN 7[$ʵylY?!yu93Hi^!DT- ^z%~ ~{\CPAƍ@5:|C>\ Xa1f@qꫯ;>C1j) y"DOS?<EC: & +U6K'%.PDkJ$_.񣏔 1 dP;c(0 ;4*8¥P㬄]k0jabb%A Qh!CHp?c= {\L<-;;3;p6C0y]rWZ' /Nnw߳ϰ37&1M2IE44 Z Ip 9fvvVؘO䃸%|Pӡ#WzUZB!0NYX$B=7N!t_t^ʓl2Q,v )I v cuu~ȯwjOA!?Jnv?u੸@3oh"Fd=fq F.f!5|mZkJhyԝ?\zB!*>5et9G*&w !B!0Nfj =$7;7\X;`j<{K55: OO/m.~Hs|;/ 7nsw-[aT2mXB|4şi9^I93%cDar 2Oz;1_QjEB!B`)z={nWlǨni8DRF", 6Fظt qB ]ҍ]k #׃< iÇ;)B/j\Zyorט]jg<_|~ǟw\|ߥy$=x䁦Y^p4_}k9;xLKiڣESڣΪTJV:VzW7Q]\ןO,ct06ni4-0246v}vJZHF.~f}Yvi,{59f67k=[9H'"!gl{5v7>V 14f443:gYਝ4u\x켕h;wHN}Ht4H;e>qfe3ϣ#MyqMjn_K]JbědtXHHHHHH/̙ vt34vpz}!&7?و,(jѯD/XGU4ii1K+94q{:7[ 7ld!QzEgmquyTy6<3IOfŚT#B}hQ&<3L?BMBcss)U:OLGgFvCMgDZh=#m4%qfir"XGi-ʴ0q3i&eZ9ΙwҴP3kup;iZbQ5:83M/L0YG0#C43sa@hF8 c1c1c1s~D%g[%tEXtdate:create2021-11-25T05:38:33+00:00/)|e%tEXtdate:modify2021-11-25T05:38:33+00:00^tIENDB`kitty-0.41.1/docs/screenshots/remote_file.png0000664000175000017510000011560514773370543020607 0ustar nileshnileshPNG  IHDR/ũgAMA a cHRMz&u0`:pQ< pHYs+bKGDIDATx1jba`I E U֐B A,Dlp"nRXY`p21h&p޽'8k6L7l6u~@jjJ4"bXH>O^"v=n+JW m-dܻN~D|>rL rt:?++h_KVbzGۍpjgB!=>>>===??t:-?1Lk˵w>Ow^(Ft:vN +urn\.YRIEzrVQq6\-7bl:0>P.J+)цBBHR1TZmxQ"h ԢA`K(h`"j/}r'YعA &~ݽs̜3ϜY;޼yCDuuu)))_`T`0h&==};ϟ'r5-bFEݢ5`vѽ{ \y˨\`?zYW\!vr5FhHr]CΞ=+kP*BB!Naa0xaÍ7EHH644 cݻwʰ;E~MGGdzϧYh5!!,v-lw!TDFFݻ}FGG_xqӧOE!~$"HGSXMJ]&''B G%"066 UU%KOذaåK^|F_ +=k(!0Z~j)4+Yu4b ]XXݍWFGGGDDy["#G蠯ky˖-h6x[XX׶mn޼ \uBz4hɌ«_Dĵ=JnW Gw' &>3jYda *ٌKW4IY҄||HG|s|WWBd/1J`/\.Y;vLxr8˭[}c V +N[*PQq8t萰DtQ~] T1kl/U6P=uW^Kb*mvE輨J <(=+̀5m-]_?@C \hK)-4̝;wKWW9UTSSC~@@r6ϫ/߳gϤ1ir:EEEŋ;vD8yJ<Ҙ {@Rk!`ӂ_ >v)k?4]䜻$ %/<3Hΐ ؝[H)=l\AdqAenn.fC0BL''']oNYYY`Qׯ_&ׅF<*~ą ?Ҝig3޿sqwRIcoÇ e8q~;_d(q?~,6 b0p"?[B,qR9C*eJ^Qb?Dta=|$Xu6Cm޼Y6 ۡMqSD_@_Gŋ"? ;p6Կ l΅?*غu< b+Wļ]vMî^_kWqwyR,ҖK2J؅.%8b%k~0r vk8 Y.hvh@-ܖqc(v& vBH[))rC h|.cYn^_{H*-s45$ -+6,uޮj~$I\>=Q*nKd֣?P72y{k?z-u#]v{9%?r3$n Qut~+M3En}8y_^Bqx&+N+ ^?Wڰ#Y.XSN5aCCC&Ic{kij\:/0|"fHڵk+Yo+Yn|k{>U[u0^[gwvJ9wW뉻ܾ'z4QcƷY?bt{XrщU_ o HBBxT|/ "#w||Z?[س뎬WW[m ujĽF=޻$ 6k? -:1N# CrD1dA RZN?X?z9ɜ F+>b¿TgYf*cE֯__s%M  wS=2\rrf zٴsm)ڰY.Tdtĭ}cG\Zq`D1&''?ĕuO'q1aWrkaL{78=u%y_Ŷ|<ˍo /sub& r'?j>RI*s}/1RPG?63]#MEi[U]K9^2G)=-VKJ" "B&q+!08bVks>@F[xξ5fB֒)24J _6vnZt|:p@V˗/[|`kJ:reۀ6۷1CXZǼ_m˭3o45D*Yo+Yn|k{Y P[Wuew"tK{#ߵn~0d}%}%HBBa~صkPƾ%7 D6nރvLKyMM rgRb`+firvA YG֛m!\]fJEpQO)$ ~Y|ORG hÎe9sli(nfpyw VZY.rj>S}T!7}Wrc_ϕXB5kY9{uK!{b<ɉEYCU,\ݯG0 -HHXP *uj߾}k*¿$l!R2}:x KیD-N@v8G\49q(ApJ/;6UWoUЭd1ؖgeNlZЙY.Y=y oܽ^s<^%q_jHJ_TkSD&ޑp`/be'PGZ/Jj BLoyYdI2?"(%CEUW\1H:H9/2ܹ3W q/9B e7l9C:Z@N0[+9ǀ6p &Eb0xrrJ/;6PÀb 4+!CD+Yc̻+Yn|k{ٮNj O~4i,\0O8[HEK1Й[rP=R XHM६tA{|oeq`LG}r{I%!NTl֥k-t߼իWo۶ }_y5ĉİmٲnݺ&H4%cW5],C*p%6I&69l@ꋵ8 U[ $Sۛ ̏ %rm,&|.h,߿?46o)YecWÀ1rj5St%USCS\C篒:ƼZXn@k8zn<_<l pj=;Hw)Å.)Ĺ?jf-)&e=?D |2i͗3J / p kyD{Wd3ssL52,W q3ҥ`wJG/{6F.Gt?E/3xkh,wӦM,1*Z[O "Z6h̅am KVRelĄKK ۞\,h)*cmIH1 G2ػwo]g bpFg-@Fҹ1FIv!<|pFcJP}'9'Q7[;{V,<ɚK^[ppQM93evv6&2/~8ƼkD9+ְ3EqMGW *Urc_1-/j!_!5r?q+ɨzgbkΌ?;7eyKh}%ONtZTmPOz~XjRjƍ6[I%!!!ăkrܱ-J  1:]oMqJA\Tԥ ED70N^6umGu.ڎR/ Xo8JѺzZ  ІVV}Ns'GH˲ִx:% 44442e``T*i@QV]ڵ_ر#?l !w^}թۺ5=)5RQj;_HQ}j%y2j%7o8E9GyQJl劭_ IfNjr.J^<[{ 8jE*UeK#9|(ټ9KoX"g۶ܙR)۷gݺ,]є q||+Mjxʝj%w,=S9Ϩ;ǏwNWes6ܸz8ciiISk<Oe~+qc:./egR- ׮V>毙H5'{[lLl,h8āf8GguϹ9?po6uO75̄αT[Pw\2:W=ѼG,WAln#VF˕hg4KgchZQ,oJJx6ltlXnn[˥2Ƒr>22ؖˎo\),D_/?7:tP,WAlpOպ]r%Y˅[TX=C{-ׂfvgTq1#(npe] &+jj4  jU,;wf0xm{e9c¸qh3ie(Mb]V 6mT(o٢ԛ3*+Uh92\sPOe]-^=p  xeح[.Nee<;dp~dȿ}>J2SԉcpreKy]syJ$aـ~s؞zxxxxxdÇ$y7uyvlIۂB4ZSDmxǠ%0hz5X iD$k:vJg: \:x-y .Fb 2d2IdH 1F(7(PkY=_UBަϛr@bjeHE3+X^_ݻӐl@?{lr=<<<<< tr%y܍IsgX>a|*=U2z~bq]KXjԚ2S'n eɘxq9pATҋ`8&^nN%^.NDE9)9QiB0@\-`ܲ/b9쿻Ug͔P)Ga2ĉr|)j4X}C^NU+ZNE)Lm+=3oue[y+eieha>+?UrZc5?+S{GcF'r+aQPY8_ WqC1Ez!Tcd^,έwoNx{vTYەĽ?c\21mKr1V-Y]S7h$E}4 d/F$ۜg{ XXҸiJ"֔9@oWt2A(jt=lQ-'.ll'C;wа)̃ r+VD3-M Y9b:M~3>^[\N-r:q<3 Pz{loxxxxxBЊ?VB "d!,<9 , b-},O@jG0F㑟oOUQNNDΰ,]j)5-7ങ2dxμpCLAK;#3eXV+p%NB.nS &Yp>/,\~OMO#UKrDPzvYGyk懱ŋx6// Tak^8ݺD,'k%<0ȧ<]̂SSӌUHg2F$^xRaC2` 赜LMkbL~;ˋ2=-qpӹŚ@k}{MdXk/őOgΰ\e\_@} DhuqM/rߜa[ ҹN1tGh()*i؜ԯxtǎ4+ ,dH g01D e§~΀ghQ9rxR+uSL2dj to143VD[5VO9USeV?>2,ML͚)|3 eM OC=@!8?ln\L=Vb@3}@Q/zښ+2˵7`WaЩ;jSrUT =`zU!NⲓɋKՕfga8F*{B! c750k0txON͈H$i!/;DPkٳD/CU;ZNE(%f/]4[=LLQzKTs-DŽ?VjZna?*`h]%e3ER;|/s*dfLEJ,W1E뱽p 4iLkX;ߥ\ؿ`c>X.7Ҭ'.sl${XRIKPOS% bѨ} |x~)*>ӧpv:}2J,쏸Yײ9)*[1`1!/7iث(M4 >hl."zcanqV-CnHS[͠PUW16U0l30kdeKH*ܼ҄)NN{n_#,Dt&gX 0V.C!$X.7Sq_pÓ 鋪L\~XKf¤caA? N?RN^;Xn+S׊x|ҳ޽)LYJ_΁g7w=}NH b9?l!7iG_-fM`T%r1/VCX.rVIV<vu޷J \G=EhM(*F z<>;vbh[HH! ={ƌ{gΘcY-%22#~T.y XdHÄTz(MDr-JrK$L/Z\HwϧHu*r)șmքh3 F9E]_(o{:f0Vu,W뤜ק2ѫWP]wDtB1Ez@XJrE i81Ĭƨ8pӡH=sWql/T2h ZnNBf X㭯4^ۦ /C$9rC⢡>dx`Spצ:%n=,>-'ߢ,XX.AK]\(h"}+Mh3 =8wz !qۑ`Dn"͌b(B5:Kl+c,n׮Ns1O&QF9Kj4`nzZ;BY[JuPai᾽X`R %p~/Dط/^q%EK3"h%0i3,\z~')p^skҒ G馓xhN6i? >7CMq+u*@snJc!ԁZܵ`',[PA,9(\k[?HGkى];9eHAasEmXz1"c,Xc650n ?:u eکtnUE fR\w)EZՌ@5EZ4`.%@cl|"WidUU!nr 6z<1~s*CqǏQ-|KQ " .(B)"XXQ$Kλl6.l* *9p[$-wA BŲ,qnT*T.|eF*~N&O l˨X,r[T.˲i]0D>x _r'|>lӴr[53,`e\ÑJDQvi\ v?E~/I(1p8LrJ)jƼlVi+aklJ$rx5'Umۋł^N'1q }CUUR*^Pn۫QP1/k}eYv:.^x.k4R${yyzƢ8]&d2ile\~!N'r,2UUbFEJxy>E"J'{JqY-R 0c0s(91HJQrɈ(:)$K/׮rjޯh܈}^OOAerîg~pٗ;::j>Auooo7*++ED/妥 www"LZhjj2,/~5حCMǟn^7=4Ɛr0}ʽì51YW{5aRqRRR4`PyrwwrHjhhHfP֊v~FAA8***!_ 6,\:嶛/h\RG|| 255%"[[[Kjj8!_ 6,&''xf] 媎ϔ#"+++4i&[hHW )777WŅLOOrrE~_Cci)fgg'''ѐrï}>n )ó\ u#s`?S{xxqTD?);k٧\A-tRnp5ؒ>;z==={O#Yq477;k٧rQ0j)׻JHO І/_s1BMM;H~Sf9m=^ʔ] .'nQ:JGRvuu!qlllD<5fq*###uuu%%%yyy999&lk٧\WbbVO~f}PP׊~5ح"ri^ۓ7ED'Psss^^^D1Too񒐐򆞸 kY\S[[{rr"^p{}w{{+PWkkcHܽDOkVIqt̲鋦zK9cDD;5$<---}zX`iZ__7,ֲK?qhhH:::җc Y1Տ\__G? CFFOT eY(OSXŏ(c%PDK`,4(6iBvgɆun<]9a @pqqj]]]zhyVm0DoVlƯd wi$Ý(\__g %`і%rzEAuhY_X^nDYɪ Èx{{˪4OY+\+]˷вXwwww<ϓ>4 p6w,d2l;%rk՟r lZ$|vu][^( y$>}+iMVnݞO#.< .//[uK;hʵrI> 꺷\]M|Nfʅ:n~?yvEGS8<G1>t^} fp8Zh)lYzhhQQ:Gn/1*xM4x^ ${Ces# kRs|R+}Ro-x%Ie}' ;X{sV*xa7`) | .FiXOzn|>Ԯjc\fa /+C"O؍ Fڳ~}Vr-bLRM|>/EݛeV ?m3ɟdNZOὃ(lTUeRH@>/Ɛm{lRDLұ޼o`eن@P4k\d/Jr@B {gEn]_j(DDbG-dgI*B{Qc٣EN4ƔRZQ|>Az rސ!Dr2P`nZ)ASk9) .!XjxvK - ^OM#.Q.5,i(5rwQG7#_p"w.+&0ЎvdHnRe┭i^q!<^M3ey^i#,K!-tiʖװ!K3Kmg_ U5^QBuЬqK˺мmbar+D c[(!D,GhX0 t]CeRpW0fc;fIbOX1)%iP1vPJY|>?& UtlQR=Xn Ynl?y€Ha0b' %F%~h  P2rs>a"Qʂu6&/Z"iBe m\`81 AAb,~wzIAӪėeC.f#~^kxޣW6XJ6A2kp''3y J+6.y%Vu; o!7< pdk5b˾CtWiv,KCUɤ0JB]a[|oeOkbU\S^p~tl) xK; fq|~aӫ'.Sb\^J[MÌ?R 'ix]kaÌeiYyb^D]B@pQ,WhoyWe}6v___&نWgF$6b%<#ϜO&LXam!̬J+ ܵ3\ZUbX8%bJB5/"d$+˝ϟ9զ'c4%旻̦EɅ}LmHO)Yav^{AּÆˊӆ6H#jD]Jr=v~Sm@ֳ;5nA"£摒qѕM^@Ne0*a_Kwztz ۿWqKXR_QɋFނy+˝F>L)lCbƱ5 r${EkܞA,DbҚ\'h-H6w0cYqpVG."@pQ,W#W2o~ﱓ 9NQ*y<gوy8*\${SbcJJrrψ{p ji~UYnRC+N,T혫fKM Ơ0~;,wJ>C|}Xn^)QxSoDuxx^wv“N2ybHּÆˊӆ6ʡAǴ{yD\ `^ɰYnFU]|qTJc5鍈 59ǽ>{Q4-,`dUf'b~UJ` hE ~UY.ȒW"M,xqWC:e$FߕNibln+Gb $c}CĿPVqa|x"8O8'ǀM\,p#¹QR|;mۆ8ryx'i*5A&شL/'JI#ļr`[Pr}2~I6aF,+}㨉KѢX?!Ceto{Am'+Y.I~r1#Y4|9CUJɯpvY.qY0}$g< ]r1In\r[,Jp\nV6$r4b-+ o RvZe8oQ,7l>ˆYnWVbyآD.@^BjvTo,w+]FCtw,CR&2F,ZV2eO}^Jix9#)6sN3ejLKާi7wq/z'P*Mjbۨokog-L-!3[ނ_ε"FyAF}΅Ul-2H6`T9˥=aWV0R<95ak0wCxj q\NyN„s}U"DaWV0~gQ{Xe %@QEM뒎Afk-,w+9fl U& F4q`xVCMnWH'kΑ_,.Y9?NIxmv Wr93,rk1;uHRLE݈x&2w =K6a,7Wɍ1}}}iM$F4b-O+ \pZzh@cX 0m΃p m^WQл V*'kSs^r>DW0)e^I遉!3SwW嶑{ i' G!e8r (E!>u }c;% NWaؽ4˒JdQSH(#a%D x"'X)'/.cݡw15Z=`m0|^?(aeP¼tgl#|VT'ۆ)1NfgtB h-^r[T?ˍ0\_:YG{egƽk/z(B ~GytA! ܏YL >&!I]r-^)(w=!T_tam^kVsƍyqW e8rMfP>1\yB_Dħϩ]|b()_Ј4\(Zcȿ/g.bמn, }dT+]lo~ȵϋ|^l'0fw$wH>P ridZWn bZdJ4`ʓ?Mx%cp46-b!Pai47ZB(QfK_,ubX,. V IY\dp4jn2X,bYbX\ܺyrz ,ݶFkxabX,ebqݳDa|$M Q+ Xۨ`c#j'*ᅢ).a/SlΞ̋H?e>jLNؤ6^mR.o3 F<؀$5?ox8nj !ğeoooRxյ}||n뙒PŹqQ,\.=4___n Mﰆ(v !,tJj`Kʕr\)kHt:r=<<<==UZz:*)ƚf8V+p%^ܘrG}R,Ru8ٯu:tJ /Rr\)W5ܫkHSr,y_5/"JRLN&wXC r21>???>>JR`W9n2vj5GQ߯q3[vAl63녓Z6x-JrS\f;X( <AyB\DQĀ1bX( 0..~ǚ.X0tuﹱg KM۶.//ۦMElFGGMLOOGF1G!w8<<4@Wqggo5wvvlllObyqTІ9&&&8{{{쬏lT3\c:::Ly@*Tquu,4ؾy-L}}}T#: \a)/. vrM*hPW]qyX OUFFFzV&*;'`um96ַ|T5A.EF|1$+"Raa FrQáQ F%}/w;DMŹ9/{+kJyA "^! [WTioFOpTQI Q6kgub |)]"\EQds@"dn;\༾oOi_)9Q.>`!Uoq4:+u}HBZޝZFc5(O r9_YUg%a>gc`H, (_|(vdgmm(`ӧf9{"L8zJ;x\6Xyc5^86hB5![Q#}Rq=g]HׄQ֪rw3cTBF8WJgKtpMtz{{TR*zyysNpDX E$SV{9B('@0|.I9VyFJY+%3%$UgsQNjP/KP!"fSo ܔZYA$pG#QDuQ.nclTrDahw+j,!5,ïq6=bЩSk '*cb>OHI2KRnJ%aQI0G1yc6UzX<Rr8\^G!u6Wb}BrsϫDKQqCxJ3*e85GN7e FVVTwVr6ʗ(_zrq|5k퀍JY^%=~VX0v!Ϧ'".dE(ER 1 8awj"b2~$YPunG X0@ שՈN 5w(:WJgL}@2ܴ׸fmF&+Oq"` k:]|XGS, ~=<. l_9Z<-vkCM~Bcqr鹸\~LB4*%Sj1L.8vk14P*IJ'}h+(DBH9{TCà~ yE={2_2#e_~qP5nlU3V qPn%K?}Z6*Nx' 'e_7V#`2(iF76UUiM xFnubW_g/h3ֽhPr/O=@jZ6ʅX`++Ev^Pmh쿦8AKf^*@Vs9_ z8*.`&Wsl좈=4.QyKT^j+I9p)`Oh&B,.{ |W r}ͦMG) +qُb]dioG)࿭`4)Qۨy=HrN gDktO6aϢq;hˬ&޿n:IKs<%Gs~W։5?BGv,K,v̥bjr۲U2p |@*=qI(-"84 %4 > 4$8ݠ (I =Xt:yp-wW{\N7^P(4`0ϓɤgR0 9p  PK8v[LFNJ~/*ZV\aO^ʅOt:YVn6njo6uZV.Ѩ^Rv[~IudXl6[Xrܴ˧|'QTUn(vJV4F!'T.usͺgG=m6JpiL^$rRC\Ԭ&[̏^C TTaDRȟrRW/D.=8^!t/ FjxfsTzrXt:T*%H$x%E]7.x'\)̪%(l83 (mz $b1l6oT.]a0ey:Pw!p|>On력5?jW~?L:[*v0xhet:i*/K`|>a:L$Qnw:L.#RD"x<qڕWuVx4WT"%AhǏ\iE!B%y\rq;c6)˪wv3\ B0LL&9cXVZ-rSAP>z^_+ QGk5Q8:(ňJ FDQ+AA\t5aDq"*)T xcCJxy~b]<>'3O$MD3 x[2e|A;v5FPS%0tJ -''_n2ЮLVLQ _|OOc̸2>>N@ ̡PH 3ϬȺ)0+dHLJk>]5ܚrc ioɔ54Wӓ]5ܚrc /NG $r?~oJyMOrssc/iYڼcq%immmyyyaaa~~~ffYfE ZTí)׮8׎hۿ ]vv6+dHOO7&&&~.+\j7{9)׿EϤu> EOäD1!+ V[__W)W&J7Ntޟ?x~ϐeedd",9;iZZZXSQ\YHtuFr-kVG,}r U#Oο{Hܱ1dddLOO6FeKV677e8<#鈕D\ ]_\\$ ZT)׬.H h?)Cx<V$ȥmmmrdRkd.?:?? U755֖VH$j%lOOOCCCUU>a% }IPo1BPHt ]H#Zj@|u;I}Qޟ2Rd\ 0KKK2T, rn0 գ#+Ur[L2\}l6!nBF;z=ʠM#%3,J!1 :!dslO'&o}nctrNhVpFzjoϾ~S*;!YerUuI*?r28IsH\jmZMZVtt !,>>>>??s\>FϾ~OUV*RN'$}=*[gawX,m4߷+W*wϐfA*7mHRo *\U)X'QixCEQA+%|L`hO<,s363쬬w2ISܒG,7!!dڲ8Z?68Qܔ',7rjWrS[^㨔&$$,wff}ϻ0AnQ׫Ȉ726 mdխ#Tt[m)pqq!XY.yVVVhd!JxPѰ8s%;8Vr`|nkj)Z|P篯/"3>ޞ&,/pC~B/.XΐA`bbBU݆aTS?5? YM%Q,,,'9CFҨF Ү ,aJb PCik52///ԍ,'E%[M=\po=`z,n|v@M$,--@i3jkB8%󺢬A`3x||`fl.B﬇M"N<K>߆@Yj;ō9W)z#X^,5^.]:7[Ur-MdQraE(@f~ * d2UlSFq6B3|JI ͗v.ly?4ۃփ\Cmo&01A6n2UJi#d+]e33ʯ\‹"=G;Ԃ KXLk1Ӎh 7VL G5ʛEb%g-w$Y`9_^^ ggg<8 W^48W'fع274ahh>y/F?V^W5-܍!n1#XYZh&/ d/ewHR$1u0 cp`<N"~v4dľ욕 3%O}R7hWc(#ҊU g !S}Cm|~^ksDSiCoR!i^7({rp{N(jFLPH` p/q@3YB0)7 |πp>I6mBQw>";PL>NNPڳO5Ir(}U܍,jc;l,C|^1Gd?ߪPRFZ pVZ7oYqpV1/޽k\_y:Gy-!h,ZqG5˅W<j>ϫHCԲ5`z~G*zKzNkeȭC&S%r˅-_m/IT.otw~^]޿O-z2SuКwa~ˊӆyyߨ499QE;1̃JK1k60Qm,G K^: ,WЯn/9{w,7gApeN2i)9V"ܼVKqz`_+q6*R"ipOZtX\Na}xѹ@?j9=y!i*3A&lIchy淬8m8+ UkڣN(Ӯlr/; kY$)< Wڷo]Я~{g,?9]*:$@aFGA}:,Zp-"c4S"k[$7<L,Hz \epk[يІyy ,XFW9(V:Gy-aP[w.r=Kԑ$)<&y,WPn,0,X.ΔY_Y'r, @!Hf:,i|w}j`(غPLŵdOVOϜZV"c^7h}缢pѣWXY:QRF8PbN s-TKkS,['a}Ej~ r&I:,$uZdއ^1ݨ\ARZ5@X. BUSKHj(k/:u/ܖx WdvDQIW}C,ϋ8(Jx5<`^םobc^7dqO @`5Z(`O!聪N@WAm6`<+Xc<<'AK)K/цסϺdKHndA~J=!څZ*g- cfrv-4|CZو׆yy` Utx)(3>{8sfF8c/6Q'\3/(q$s_ /DIЭn,\zXݱPD[)Lf'=_K5kJ[J׆&VIj mo-چn 3Xr1v $ >$szܯpUp#!ӳs m}}QYϋ+@B110foH+pV0/=+d!G @pGF49T/_C@/l(G{9id.G ,hC~*pw{XWB0z"v~厌" mpJ+F##¨=g7>#######i<&mr(dddX} G?~䔵L5PLIm'E ˽ϟΙՒ4EHk$-F6:WKz 47| |ױq7 !C`P "9:8xw "ťMhSG'w/[ps/๨j4cSV'rnm.mr9ݧlz,AܶSJ&nk4*/էx%~wߢ^~匿n$+w8tu8,٬m)^|P(Ri:\_{:>Q9ܳG*j].jj0\.]-xxrݾDqrU3kmkYt)2%1)%QDk5jǰv2{b#,K ԠًPxhlh~|Wpr^|F)Rrz|= WLVhUPfPcӼ&rNq+ 7*\G]\P5۹uΝ{R0<}޽{\.whh(##QWW>|HMM%3---Ϟ=~|ovv6}><ǏOPŋ}}}rΝ;Mh֬Y2̼k.a3\ $Ԅ*wvYw}6,ihhHII!ŋ");g|>2d ;v}̶_lG k˖-C߈}jHF9xH*li*w~,% n%%%7nܽ{wSSݻw ,c.]jA@O֫Vzuu,"ԨrrrX)--=v+r k}6Um.tS{Yp8j-t523(;w"'!!Ξ=ׇ̝YٺukdVn}}=+s̱7h\볁&.յPܜ,zʕOGunwە^/ܾ}_|y@ y+9}ݷo_rm+*l )sR⟣Bb39H)W+k*|˗/_zUݻw2sft'%33b2W2|ď,ݸqCBK󭭭Nfʵ7h\볁l!5yvYTFFӇkײn:R<*Yko\QPs_܄MGj+odzYVȌb)[٧r~̜-_ߺ:d*//O>7mߵ1ĉ7o^zuVV|ʵ1h\볁X50 7DHӀ)f;x ).N`@pބCupTRE(" s^p8?~ o>l6Kl6޸d|~uuJ$Lf4/iZoK&F#>0NUW:~ޚNFE.//Îz[qVh_R [B!rvR3a`R8| Kx9$>㮯|Je2RZwfOOOOw.z ,ؿ[^|5k{ߢ=OZaE~mp١ġC0/⟄fe|grr)ѫ,wɒ%O< u֢=|w^xQǢEi5,p,Y[vvk\p!t <{ڵߡk$th`ٲeE{?xĸq&L0iҤ)Szz5F[r/ ϭ,/?k͡Ċ+|_r)mܾ}{\vq#E v>{7o,:޽{{kr{MXY\_r)rohkGӦM])\vhh?[K,]Yn6z,Y\X].\gXĉ'B Z4͹ԬҥKg;u,L8q^;̑|Y{K,w˖-3e^zuiHXr%?~Onܸ|^&8zhQs΅Vxa bEp/ܹs@,<|߉nmݺu{H>|A~ Q8OqNB|dd͛7ϟ yǐe۶ma6VJ}F b'O_>{1QoұcǎP٣Taoe=z,/a, 2K^]:ßADSE;v 1ɓkΝ-MӧO >1csY\4|a+wt8]ˬ㐁OҮhxC:FQ7L^h4E3[1ٱr+:+m@G)ٳg|ʕ+ڰc&2?nb0 ^Z.Z@suC B¬@.W2/^9sf}C\:TB( t[yΜ91[^ڌ-e^riޫkC_bZ`m[X.S}!@y}|G_ǒlݻwswp$M* NɬZ,E|ʾ_W,RW]̙3TlaʥeL]P1zԩ`@v,7EANիI9(VJ g %]F:X3@g5t8ekɾ_5lz3feY// :C ;P~.[ݧlW^ɜGuKk$EX5!x$;޼/XI.VBP*#OE6[Bkvt+ru^qFYo2`%(`he7S#,W˚ &mȂP0\dCf.Y28 {)c\6xC v B9Prd6N9˝5kV(A(JO(hP1`DgGpuߠ*d+rU Imdk!<ˀ K-B )@,W˂ ҼW׆Ty1UE!9˅vG-M^v{P3;`;MRJZEW>[pu(AhHe.DH^(A_ XYCfh^ Y,Wм^#(. %[XdmY6{ȥ{ 7$.}!Ǐ+gy 4u˅ e}eY1vY.`i%l!)I&qNcL] Wkdx͊aJ̛7YnJs^OD!;vo={3de]se[ d{Lּ³ Xy $X9;Ǎe|-5" MhX6{ȥ{ .Mmd:g,vAҝ@XY,y%bcbZ!BI QJ$ Ziyx:wĿ1bF4LUXrA^t".)Z9= B ꥝u{ߠFr$lMSqD('5,+h^b/ &Vp@`v΀JcY\Jڨ1SĴ!f|9:5>// қrrgrV }r[=8_GAQ|O>bL:˥.rqc{r)c D yHZvywjgȅ \=XQy7g\A{I0r&MxMo,@.{%m-7PKUIշuƗm.eg敀i7jYrڑc -f)6Ynu$EW Dʊ}.;bQokg\y-/f$[ϧ޽5Y.mƊ %(`ejVfQ{<{ȥxz'iV Bac4Ɨibt>:p8r%Y &*`I,IMuJ,/fr.W4cU[a?o7*9wvIU;˕k𨢬扬y7gtKP%ջ⾱`/yDz`/4յ#oAxlgr%"M{v/>// M̽5>dA\)k,JPutel_jUgvg 핪vMsb.(3_D b2M캤M<Ukg\ HP5 ![at6ɫ K; 9(K^,@.{umZ)%\B+fB.n ō.>//苈7RtCKx'yk!ݸp>fVH9ȦX#z ;6X.YUTrE^ܝ@!7" <@D6Hd@9:GZep"1ٶLo>rqn-)"=!W<Ư˚}C*t7T4/A++'˪H!ojfly, 2K^I< 8\\V@7rV?Ƭ%"*$h>/苩#]P).u'->Pi>yfs;nj 8h4@whP2sJ-8@G =KHZi#r(˚mhCԡr3.C{^BC/r*_/K%oeh 2:d+rQEl;n] |Cr@3: qRKS096 TXe 潺6ts^רF~´L~'ڮ> /.9i罂;p8zQTp qN~Lp"OUjda}'\iՠ6R78^` ]jچn s3"5,xMYC{ZKr3?N!UYJv^@|!E?-) `9Ju3fn7|'3ٮlbp3X|Op/?vF@ C b8{w{s!zL$MlrF2z-qh:\JjF)5OT.G%kcѹơ Zg~_}jUb("x$Il2|w?q]?,J $,!UJl$( 6d'N3EsJ$R/c;;iJWgyݷp8jJF(~2~D"t:=$Y,6ɄB_\.yF'JkdYDj I6p8l4bP(b1~6v\tzݒrk>a6+-DAxzzb~?I^x/Kpxۻ2d2ID9 1 (⚗y":NF ?ڻ8(3QSA&6 I& ĕH` VnAAέԅ" ԅeq~Ό3o.aV*Ày89 UwBsB IJuN$QAɉozyyt[^^"@2+f yDĎHijXujjj$:!GoSSS"ɼzJ.R.D rh51j]]$Hr)**Uxaa![S Xؖr4۳Ф\Hy?N+G*[ ܜۋ Uޖxv5J ?vtt͊|U>kCݥ%׵iCvˤ\H~vyyYU?~i᝝WVVOv>esgqa@yMy?l52)+ &&&TզTF)JԱ]mttToߒrңeBzKٲZ$366)x%%%تyU$T{NUǞLw c۸W7E2kw,`Xzzzio%eEu\֦%g#yqձaۿrQY^Y}'e]᪪zdUJLg[ZZ>+ZA][sklZ;g|I[0Z]]]QQQXXH\܌* +Vcyy(..Vwʍγ3MH ‚qW8t}}}&|aKίoRnHʲ\ :w666,y&v'6''KRnϟ744fdbaoYnwwޞږM%^`F|YEuHOOOKK<྿$W!*%tEXtdate:create2021-11-25T05:38:33+00:00/)|e%tEXtdate:modify2021-11-25T05:38:33+00:00^tIENDB`kitty-0.41.1/docs/screenshots/screenshot.png0000664000175000017510000331323314773370543020472 0ustar nileshnileshPNG  IHDR(x g ;gAMA a cHRMz&u0`:pQ< pHYs+bKGD IDATx[ 0Aj&#z )MF.Z Ƞ e2`P AEDl@ί̈Z̸<Ŝ;"Z y\S =r)儋{G 0J۰b \Jr[D&`3)N90w?󗀹@,NC e=I[NׂmB)&Z!Rnk99䌑Y >7cכr -)䌑q)9L\n-h$Rnc99J3Fax]-R<5o?ػ(( HD*hD1^6t/8T%HBN`iwϿogiw'yNrߴiܤ3hSt<ϯEUl67_ݝM;c{5nR>}srr%Cd ZSd\U.oߩ888Ͳ=n4iyNqsZ߱]["Rܜ<Ha_Ux[vmWգbSV7M˛7mo፶L3` ޶dOjϟ=^U UklwsF4iyi4i}f.%^6Rs99Y$KzKUc٧jX7tV7M;6{m'D~sr ,2~T~߇aUG<߯jaaE~V/"FpaqJ')>][q%ސz0"B#`C2d{.6-n͍u߇=Z>m}.)ǿٜ㣣#`"6'8i:Vڶ}Tmfs@V'PN8Mp4ikCC|9M,Rp:%thc=w:rmgآ@<'G:Z pswwn}(L&o3h4XU)?ui NF8piآxo`@ @ e{hTtz}(%NS|Jrˡ9|bL&hN8-pZZ\7ʝZ뺧{{{m@o?Te(0$/+}4,xD16YI|_W/q9WPn^(tBBFP%H r9S,GBb YdÒN3z44䲳wFsZ& L~+t>ؾ;_ėj%Y^q]s8}"FXF{Ky@OFN#;Z7pkgO7\Y`%;ӝ kkkGz+߭t$zM@T`kFӀVb.v<ȵrMMͼ\'Jdy}s&ٹmlXTjO[ ~R5eUm,,5U2r' .vncrϿ>VTLI]&\ÖRRVV~F= ?L&O>ٟ+ "T͐7~'*Zy+ -<~Hz(o'GzguǬ'>yZF}֯8LG4}/wܮ}_k;ϽHSxVC0|r2yB^~Gjd@9hěu-Ӵ\9R'[ñz{5&%ǀs4u)un:W[*ƹyܼ[scYhS~m1u 下[}d:i=Î%^[7JΎ((zus]xiƲKst,Oꏌx]UwKdz~7e.']}#ʚ6yLP֝ḀO>.s(|m~.ʏ/yD^n\%CiݢJӊ~cJ6t0+̟-,˗=֭׭,ڻNgNwUkWr~ |?TI&GXk[*)X6Gpԩ#g|Fb/H-9=EfgY}c|.ϱ/,3LeWkW<|Otiapf (۫ySgᅲ_"˩4I`KRgx|ӰݎѦ?tB-s[&bus!#=G=|7Dn1҆/C?.d|e3g[iO?X@&̀XMc}]R[Z+Z_W'"jD:HXtuoFZ}NE12bo/w-TJz9(ORܣ:8a %jDlz/c[qB=˲OP@e3\_e3Bn*hq(S-[4 ܺmD~~f;q/6K (?:O?9_f̘޾򟥲`%(Prh'kۓ\R_n5zi8"''r6A{Qp2a]i؞Թ88[~6O0C_l;]T-GdE qM GCsdo9?u(p)++C k l [8dHc^:&Ջy#c^ǘ:gv>W弍eی>+j}m /o}`Mcl}Qvo8+,$e\3l۳4lY?a^ 8Π_7k . Jb:GtQ(AuWDdeDJTŸəOğWq a<y}缻z>ssqX,Y1[qŘ'/=8^~R ʶRcǦQRRbo^Q{J{ee޼6/QH՛ۭ* 5UݬP`zo<8E:Vt~_*]9adz0gW4'G>}=>gH^׊ymnRx ilmA2ŸջzE~h͓tuU ܿ>?Vh?OϘq(OAAAAAeB؊/$JE$.D#F9 Z5;}VKTaTTH23yp-f$ս4dlz_W"?2ؑLIb%MJĆ9};J"]˚իȢC QH H8(p * g{{z]սuypL|=tߑꫪު>OllT_>-^5V+B*ԹwdҙxBu _ξy>| ?%>UbZ;; &#/aV{2 E;<Ĺڎ F뿘q^|B+mñMjx'}d{dj_PI~Lqrglsdɺ[KT-u2L3o+?~^?S|9DQ\SGasu Ġ3Bp%@;<:2OvޯLdz{t#l1=7.c˳؃2>I%g|&|nů|b=hƺ3>'\|yhWe+޸kmĘi y7̿OAAryrNa0WeAq*uZ-?CuM99/bAձC{_oM('~N%}~ }O|e˳؃2>I%g|&|nۙ!|F+joDI3%t@2̘Y y7̽OE,% Ν;ХKh_ ;\u]kԈ}?ԾBm\sg'}Wntyɩ(#'^o 7o>?~% +Dֻ͛իWϴ>n،g 6dZ [+%p? ,Ybmd=_>RF+/1m|2"۫h}G/#u!gT׮j~:+lۺ%YLܢ&NksխPgM3>~/{]G}zazOW(C[5ivlۦ4q ?G[dͅ(3ˮge~}L7wlS#G M' و۷o# AAAAA2@pD 2#%0ijDbv.|d!AX`عy5SpS{4,2"1cSI'1A'5n}togJ  de )P>s!&1yW$uuLM&ӹ]K]dԬ(mF Q99 {lu-7?m6^!C2߿D ;O)oʄdD%>RFڵXSZ7D]y8u{3GVtzơ; {"\y5ݺuQn;sTk@_#~uPaO}$w     @dU }ApEkCYu+BP?VSxV%ϕnޏ'7^-'SAD>jxx{zVeM+T ^hD 㹃Șnke[J7!]gUR5eqߩ58~{g'׈I{lB[#MY䙆gVeסoҌ<|_7"/c̦J'Zm_;8e;L@V?,})5UxVx[y/Py Rl]x;ٲ'S>}?cqV, C)}=ZRu}Y}a)t ~B(Lue-2a}-Ɖ'+ˋؐ-QoڿtI}*Pnᯣ6ʞmd{ǽ5-Ig9;|i˜$݊3ww,hpֺ(,ʗBy@9Ɏ >IYR  x.P>e>sE-jsΩPRs-Z4V\|MZK.V75 fLJ@YMVn6-ݻ"cg 'd@yaȑì2d:ųLZ FDYm+-P\(8]ؓԁU|G٢g=tqa.?)m=2y"P׈G2?JdYԑYʂ     !Pv9 C DDW["g.D\J7C6^`Ƌ-FNFX\ֈIyLy(^ [y++ swb$P&*{"]P9ھYN%%32lߖj2p.`eMxֿC۝Eͳ5OH'Xt.@Y- ~zgVP8%Pf ã'n7?~xAo ܿ%{B`v2a2 }{;WlƄ4d)+:fv&K$Ӗ9 IogdG6> џCYU= oUMW9j~=*X}d@ /yOIv̄oIb9(z?Nڪ mFw[u~07#/ ,?K'}Pf S,P >hCxPyagzQ%>$ lUʰM me&|GD@:uf@f̘g/Y~2$){= 3z[o=%ꝲ]g/+Pvx3uGQT؊~:57JYA|)d23u>3>I}@l }s;!sn;bpl(mdB23Oa.x>ٿ[?q˦7pEٵCCW(C_NgׯOM(CPnA(O%]}ܑ(qe~}TpS]w+s{ި      @ htoUC m籣*dĿVsdP:=e$f`uma9 ;$}kdQqdx~>x0ds e]] )z<|*{"m52Ymg?/P>TM 俼B:VE%ӪE{ Fg!$Ν$PF0g2֥_mSӈ26Py1<kXlmhes7my3__yu0{31_'L@όO2߭h;"{YK3Ue'$;f7$F7E$% k@Ec 8v^xZ?Cmתh֭\ӠA f![o]7k}2ƎY#PDfl(7lPw_^F |PiG3Iz] eȫ}cQFiN>Xs25nT@_#~zם@yℱ"PAAAAA~@xBbU@rٔ>9Q˔]>cKaUMA]kL4 Nvl[d;Ӷj1%ڍqv=vR擏WXٮ!Zl -'Y@>F8;ECMsx-вl*}<e{&Pf|nڙ2ۉ5U .1 G,1m YߠPz/D+'$;f7$F7E,% k@ُ!2g3j׭SK/N@y;AЎGvMQl|1˛h敓sK=vdnmBe 2e:Ȅڧ܋6$}mk]\62w @Yb?(o| ߧ/BLۊ_#~!reuC      k\le`QQKPm)ݔ2)M,A#N?o <^6@_#2"4r2Bs@~\^lb5}dk.l*1~#ʱ3h=;h}-M5!j}YphaԵ2Y'M,]eV5M |u8{GMW#/: y~ jNԳ1984sGܣEBƶ)kEv~|X3(ܰV+;#hYpS__yp0{31_'Iigh tű7d" ?h3Ov_1>I'1S'5ju,b)AAD\&BnsSͿW67e@7oL=~˿ANWytJxmߖ0o߶Bv?kպu+͖p2 0BI5rYM}kLΨ_"{'Pq{(;ضg@GWzhxO(׈Gם:vHZm:ȶ.eAAAAAW TN,x ,)}(ˆXxyK 7wCV0phCUV&<JobeCK<̳l2xlb(>7u5L0r>ǘ Xzva-# d[X $5B3jK AtSV/ݭs1AH&2k&PNAA~wtoe36kV_Xsm̰; JÞu|wB+ٿ5tUZgL K~P[vޯEϖȡ?¯<8H=ڙ/D[O$݊3ϓUCg1a>ޟW4yOIb̔o0Ifof'y;XJA($r7@dVw"U6fMV_ta*(pu;WysgoMVnhށgҶqF_궏]sX{( 9=xAunfY;F__u=u sfOʋއz:thG9i^lϿ2n@F>ء=ɺkluf<     kaNEfO@P,1"@Q^BȟTna;խC#47E?ضッq}ȲEk AP0Jᚕ9x.5JON 2{(%%YcZ^]!z ^heq|4GMWcJ*ʄO~mll^պ_GC%s>2bNOxy/v\ ,꤅2 [B0!:T p֙zFg{]|Zk>^'{;W B%Ё~J-]g+: f2vf$6SʌO߭8;?H\SGzYeo+̱RxH PP'q^y}3}X#9I"AA)P>'R=W ^ ת^: iqέV^rCUVTءoԹ*f2@6]#2յK{vE] :aCԂs'Vڮ~F(ͩ~.yɍPcA#hs5 'jwphn2x-q mA]8+ƟY[e@Ylwpqf@’t3o+[Xg/'sߚa'/B>^ Q c+UuDaNt(hgY;3%|he'VirW9 DžPf /P/[oˉ{Usy,9I>I >Iw͜$ogK  (_qo!&vd_=r+_?Sr)Eek%GY]o uΩWm5k/Lg7o`ڽwcN:KM;}i볨|իWB^ٹ2u@ @ Nm1c& 7kN}2k+~}[+m715A+eAAAAA(#xYȀ]m02Xm)}s_1UO@6KK桞 0O!~flBP+9т5VP!p$3ǡn≎Y=8s)Pfle'Sn]F?v|eޟe+d?38~9)ۿ.P*_'lEy2QiYgWud̗Imf $[qvfYW>iMñ@n\=>xQnyOIb̜oIbo'y;XJA(W@7rnjfݥ̛-gOQGߡڷn;JߖV=pjTn)~ͤPBG׭T~w ~DǶ`z`󗟾mbtEQ3?dFų, N|Zefl4a= ?W?|%2jO?T?ZhS2x2ءzf NZ| `+ #G M4}DK ̘L@o =k:|p/2k?|u6MسGwV~MT>ܩ?V%'0dYGAAAAANlDԤȋ':ؒx1H36QOW}*ˑ dm#oՃ%sѝ*mq"Tq)@9^-Gbt,tZ<. V}282FO:47xLU*/DKM@?thu/Z%;/8} ރGcԆ#&^u .Q񰩃9ܐ$ $qa(>}C|@&QDw5%i/P9jLVv dV$:ߔ De7ʉy|V佝+158JmlM(^w`I >Iw͌$ogJ  eJH@/Exǎ{      {Oz!Yz" WSt+ly+>Qbeo 8Y?쏚tjpDA>)IAAA"Pr6Ӻu+/Zy_4L!NzuN2ԉ'K]ļ#s{|Nu=B@ (ttJk'g]tgӾ`tP8Z"u$IP&L@9K!B!(<3=A}ZjLݙ$(ǚ (P$SHNАr>K}`bTL<^g3/G2PnJuJWg[x8 ʙL'P(@i,P(ef'P~) @Y Po\Te`6sIT??y3/I|Z]?cP,G\.R$QVT*,F l6#˙s>|>Zm9}pfet:v#MS3`b}Vka /{+qvvFSi8X>_qzg+q8:y-^m,߲``0p8vmL4MEߏz~޽Eup߽FXD0<V PWPcP+#14DMLLӱj%2c}>A⣢BVq|O__2/Kz :v7;s={:saخEΟ?cVYY)SSXL'OX =GwzOT?^1~jԨQعs'ׇ^>}(,,TSy7==}BLX(2))/iS'Kn3d⚭[W~[su^hWM]$lyQWuRsY3 Bk e@^M? r~eշh`xA) b:5AD!U~_]zU,XII ,Oطomii"$$DQWW狡1(}r似<:tbq|lժU ]أy^G>V__/oݻ'vwڸq#UϱU'Oebebwd|Μ9.7??>kl%`2PP1~v (&۷QVVd$&&"''K.q~g$ Ư$ysng?=P׀o۪\r A닰sشaP^YLP&""""""""""""" (g'O/$3R>ZSF.V}몧`R =" eu iX;ʺCuvWs*]m \LÑs DuQMMυ~{$IvqΝ;9s"8i&<~e]V5ǪoW()+--e}gΝm6M~H+Ûox^tMIIQّQScCjj8q"Izs;vW (?-{ f#1!QHIEJx3֝+V>-aPAH K[!4$e"""""""""""""b@9 e#nFM'(a߰0F[>14i G~\OUzRX*; qRN.17?eP!(KD/qBKX8z+"vxE^4U^^>Ls2PvIܳ*[weD; YYYXf DlnVUiθZ0#9sF^=vXdddȡÇY]+A* Jݎ8G#yBPf@8P9֑P9<<\M]gR@$Γ}&tIS~RVod@. @K:oԇ1yZ_qf.X&b@kEv]$,pQQQv]=ݵk&I]k:Ν?kkknG֬߿F#:::K6 0̀2'Pf@Y#e@ٳgkll1c^Fզ8zwz=qIBuʹm|Goblڒ:44U0/?kUК^X@Zn qȯXcޥ>|1?~7oޔGqqqnݻw=QY-T!D.haJ{ a116J~ -%;u9Ńܹ_szjZFѨ|D Y7j2&Cp߯鴪T*z=jlRU8#~Nj!B6cU*mAVưZ<aS1vq:2].Vp8|k^O A @ve^M&ةk>o,3{lvGp k3SE".gÎG|BjB`f&}c6/2`cnW MD¼'@vjQ\vcnx6B8$cΖ'I/%J|>WfSE 9"ʅqF:gxD@9@ԇ8>b`X.M?Fcy/f,σmJ|mq[ gX6p  {`k+lY{][/䞁g{`(cAKkޛ\ӼyngPDX{ʡPH=4,@|NZz|AAAAAAAA.& .$|Xٱ@zW_%(ߘ/0,ԩ1(e"̴@BB.hm Z1E R0US߻6̼c,{ϼsys>)뻤mjx@WúM w?m&K@?BPHHqEFC]w ચ|} mrů}Ψ7.hx+Aⷕ󀲩Mm.i[FXYGw!RvaP:O@D:3_dw?Ek \v@Tnqn"xggA$PB % p\.ƘCk,jyuuP(<)u0_<d2f~~νTXS"wc __~uGGCs18i:fmXpPb[/eQR>+;m6^Urs9$̘σ>b\s k^hv:nNg >317YQ_.Jsgn;3րr___8^N6hLŏ,vt{BI̒k;TyOV8To˻<`,ҋ 7 çJ 9ms,469J׮P=YC] lbϯzeJ7i}M͌k+"H$D"H$D"H$D"Qxzk=OS4i'*\ſ3uTXۈn_]vQK]+5$}썲Ԛ#Ȭ&wGi?ym*8}Z5MZ64^Ġh'Ѓ$'"!'كw5-LIHHahvg1|K=*ީ M[FYկ^}namk=v8i [{&3]LW =k'>Jyw `lo,m靻g+}Mр^c%J/րfboiÎ{9{|paFܶ.6G~-w |xZ=]_+|b1 S"CXRr$XI0J³"ҰeHѨ' D]Kx$Dy311lH$У}}R8p8\ I -BRɤ-/'Jb'VɃu+6 k 2XNػdI RITDP.2~H4::.^@}>>V߯~Qb*o|DZwbH=$l:;nwkk˶f]]th/{#@ҁ@@4>V3f9XcGHʷϫc>aj4&)M]u_l[J<+p.AOcF"{fff+邍4>G &0O~؍opW*Ѕ=ʙL9˞%g@FZ}w7f"]|H3NϝgAXV(N*-S cHsz7]E3__wu꘽zugЬPn"ꣻfddS13[rMY;}f$֞9APL??<\/zٖ# Wk7?mlLclZ0L?l*!QS"A颂bQ[ ( jnG~(.\(ZTܺp?0ʱLÐ.ə;̽Ny{6/͹;._(rWB!B!B!BT@K8\t1=a*˥ʥIwSq:cbhMx^#eO]2V_Nj-wL8[ѵwPQmPcsDA">9VGێ>313ʛUNέYŌP\4N9Ȑ>~<02@%G#NN"P=us9&y}zSA*C!B!B!B*(h/Ti?N&R`r͵[+P65tb_Tp~eT鵪M秷l ѿipTUӝ^&Y; [؆EBQ#LX 0Ea׈A!(, 0, U3U2_j|mםˋÿH}ywyΏ 8e'yǖk"{Fh7רql1x}.xGO[~>F8G?8,]0$ %܀2*kܤ%ousJ= ?!TXX&nɏ7K(Ҁz"x0C<ݻУG`A= ɶ (zPk<Ф_Ǐy:A8 PpUQzL 'x m6cҁl⭷ޒYYY `^z^(I0j0 a#ﱔ6ׯz@9''xlgV!9gC9rJt: T|8"#C܌c Ao޼y9RWLXA5m6lsﰺesuF3akPwU3fXw8F1(e%zX^pq=?qFPW!gM>W.P&(;{@x4si31|m}-3 3X,bX,bX,bX,};rͶBO"ڧCԻHNl'ψ+iޛhۮe=jt},ĩs܇ (G^^wAa v?Pe%lc?gy@.+\Ue^Jm^Y|-h:)׵산.i.; y۽y?Lw@;i`؝H8{0H@v)SMh^cPFʶ/S2Kbw_aeX,bX,bX,bX (Y3=˰M ۚ`p{PeyPN9/v&b# '#\pDzgl@=W6~zdwU2O7׶ =Ӧ-cYDΒCq%qw7+!y8쀲mXg."DW߉g#DdW݅OcoB[Ice@`6_F^`A+###,}[z,ƍfG4>z*G>|XHp{=8Pt3D)Dll6Z^^N P߿f\ݻwrnJըQ8 Xz a5]rE 6,rn7Ga>}:Ki3eQׯyZZZ<6m"<~ϡ>妦&A_~*?N>-{wt{}цjժ|y/qTaU(S>}PY?ܧ;.g6(L#TmD]Ey:c (%%h<6=wCi)|6UTc^ʗni?>/(@dQ1okw~F,bX,bX,bX,(UEJk0ݟB\mHdNSd)T#Ues{'!)/wP6 g'^e9&IDDZ,Z|]@, رNk?e8dHr! ]8G_W (C!EQN<)ÿ{0 $0|GC&Cb4>}zg)$ṢdumN)99pP'FU}U@a~ʔOxTf#F:kY(~:ʄ6戰U}QqWQymVpU{gP;0UQA+i>j`o#e w%%P>vO~Ӕ2/ɰsGn6`X,bX,bX,bP=@@ M>ٙޏF@&prtEֹ3arZ`+ƐzZLVGy!ioW%c}\'eoeO'zs2is4@{ޤmIuAQsý^sD5z#xڛ#BK.Wo߾T@966VܼySyB˗/|Ǐzz'(јYLqFa#6S/PM\qqq NP }~.`ϲB|#Cg-p~*ʵOcI"̯2@I9"٘}ZÆ,Cp'$0G5Hݟҧg^^g6 (w@y@fR>޼Z$'%?i2M⹺/?}'=jL?nXe4y-m`St O?L/{dfX,bX,bX,bm"J&>@ PzPީ `̓a]PL$bx-& `NYcƛ"1ʃ ("mUq[7ZK #+0 z-vzsU]Ӆ+Ln"ҝ-kEpQ0=eT7ƷFc1fPe0B#-[ _煒fVV|"{ 7炶ͻvJ5=՟e @Ng͚%h/'p }ϡ\AAA -iO#q2w5HiKҜwit&:Iwz!b]>˦Oi'E:o)"2LkG1n.8Cp?>[~Q:䘡 "(k#:/`(Y#Xܗv gY6|&YFۈU*%{^76[k35A2L"6Gow>/QEQϔԈh.\UhCΨ(O#J 3)f-a6ˆ"%aAmBZD2sL'g;?|xC|̉#;d~YYwBk킏ϲd9xۘGu_^;L B!B!B!$Xe+uOe&c(ZY|K"P1>v_l]Qjre[_ g[P(Cxc7'v }MEhGoծ*5:GGxB &PU[F=ukbUݳfjmGb-+xS!ǁ~՜scC D"FAv=?BbD0hH?99ke{m@9˧״3kE PV}49~.aƘ͕e=kH36YE^us{]s_g rEEnZ.mr*zP 6;){K災Ǖŵ}z19.kWH-@y |lH&g{T=vs4zH *1C/*2^=}:A yiDR/KxZgUrLL??qz)ȅx/^&yB!B!B! \ZvE"|^8>Ώ&1&EVS̪`6CnZEn (ԅv-61- Eh4)ABV9ϝx0_q4y;s=op_[`'ɇO2P[G&̼WJ>P2%y-{쮽jZ6%+/ ~̅Ye {x2zr0H^@7}\{ns̷6;5doq%3̮w)\8y|z2,5/uΟ;ƞYPf 90'`\Zs?Z5#+gsBW^M!@y 00A]ZX-b'͕h0t]X*N5)h$CkT}f1^Xg4$uqd bڒt: c)j0vFGlͻ;;;0,h6 I$ pnȃAOǥlRJVX,Y/nTrRץ&*u +rlrCj5G5ؒ<1Irk:(1  Ԟ z{^} 6ap2/J Gc}H$2~d2bX6lf$Y;c:+w|g;ldB,d"&Sء} ?cC )|X&oƆ7:8Z\aĒ~6q5}BFFRF~bOoZ{Y _>OU 'PV}P#x>OaV$3^g|;fi+bbkKQT Tqp[ZEC` ,1ص"vKF~qn=o.֤<o)w}<5jc vH7UnPog~sc r9j@rof|wG;j3/ց}=ɗvƁo-L>?ڊcBD@IJKFo#`'`JVF R? I1F]kpOp!IND1!(& (4 kg kKC`;ZokVۚ?lʬVON> _x:z?܆[OO5b$ϕ+34+A?E%TiI*%ymOs=#֜'PƦD E-V<1W ev~PW? ]6碡`!wQԬ:vddO| l|||]sc筹>/eMG(x^AHb_a >S%P>xlհLW :Z1 "P޲L @ryF΅/,sG{LL> u3[D=%3k (e"""""""""""(OWDF\Val ټ' >ω'#%1IOZ~Gf/Wv«{'ꜳ6|63H$mbLs({ޓuQ(X|QxQAi%Yٶ-!G4ͻ/LʾX,eU ^1P&1PfOr.4@ӴiJxTdmUl\yEr޳KxI;~_?==oz<`p#N;gG `t7vL^D~}ߑoH|grq{i* ,zqAWN* t@2bKAՌ4J+b6ZH j( 'Ŧ7Ň@2_StL.=3 ~wH\1[Gt9gg~8xn`*=뿃0 0 0 0 0 0,PVa#܄Y&Ჶzϵ.ip%n܂dUl+Wiu#UhloQ}%F14HѠvah 0D [h.-u|މ$ wO@#6M8g]~_idų*J&$Ze0I*Bz|Hrp8Eq, Ng1,2n7:;;Kazz@,ōb0:l6~75`tt7{gՕq diTphhpB FQ@A 8$fҤSF1c418 ښVw~_䬾. YoI3{ιusss1GgΜio)Խr劊M21&N:볟`%o߮Ν;Z{"''7do= =F[_Ν;EYYx1?BFw7nw]dhժ梲kzm/0oիH0W4g/Gׯ_G:*QPP@'ϳUseKp^{>ߡ,zN|g~T>}g,$k<.Yg3rk=z>,k`} =ɒ0 0 ðU nt[4gt8)(KQU'b7Asbϡ|l$y~yh\4@5?֊cGCPpk|쇹$_U'-WPnC!rϏJP^iA]S |0 0 0 0 0 0 ʍ״aJ6zݝ)XIRm-:[cscղ^kx!,ۣMq(Nߗ[G}tտ]j-+[fPo-Bsw ʡy{|}G{LGU'(%ӻĜ6MvCZw! `~W !bq#j r  >: 41?b\>}Z'@ O7tPo/P޵kW.Zg~AL4Im丐\רe˖0C q0vXYOEPٳ'\/ހk.zazAp`}@'bq߿aBAACJJJyjL&^Rٚ$=n@f:5I7QY' ^"$)V" $!3*y38}6foa֝AA9@X?ao(l!- O ږ]BG'Q2ګc_(s=@WdaoA#Rc!:{MAFQenG\< }ͳR][GR59^B%(fg-bBf[MЉ9ڵɚRA߾} RGTk~,CF4/cV'3"Yw8)˸4hHNN~\dtlkb 7đb|EEEx\~~ҵѮ_5AAA7!D,˗:q~!h1,iRŦMqOݺu/ɬmF#roě-[xG{02ɬk߿?G^EBw0Gm۶"66?ǸZfE܇IIz9(M̯vbhg&gye~+ئWK0W|6g;վ@e@ʑ)`?`~nKp֙!ͯ/aY`aaRiyu\L.=Ev RTb使F Q\Rڥ4XГ,;Qsψ?]o-/jsGi<,ɣl\vfQmBA٘g}J̲ids6`n uS;AzgΝcLSgإO#6w,q^(ӀL=I/]g_>baaaaae{,3p3ԻT+zLelѨR hDk(U`וKd3(ײ!ʞ #P~!F_ ONsoWׇbюh?Y_ Yd [65:q,ȶ(,#<ʃ-CYsrqJ/rP߯=P%C6ڣګ5 ~n_RZ,ZnmFm!0nq|/ϕ+pYfz2Ë %(d)]Y\\Pm6)zdr 7oތq'NƕD=zVTT4mڨŋEeeoT\ÿB#FoOBCCKfm= ]wźbSTXPuQO+NVbrXXeUcL[/(>x-\3loL1W+/ ##o?|KKUؙ[%q\#CKIPJ ?0 0 0 0 0 0,(7BPnio)C -vtvu@Ai!jQRFP[(!K@ˊk_=Jo|JSQGƬKpBʾLLNS9eh5&9j #|~2iuN"68)K l%אi %5hMqh6ͣѶ?G'eSTT9M +Ubk1_SeBshTocO#⽵ʲ`WI4qFA}>Jf,eLL# ӂT-T_ٺӧ# bAV6bY߿ɓ'#FΝ;P2-֭[h׻wo%5ݼyCz7d B'lhb %*3<@PPϦ) ) {$9ϟǸs̱JP8se=I1K}^]_깲{<ߡd<`Ytedff3K+(Ӟ9 ׈ aaf#(,&c?SsD`}{ˋg##?ec#'})6I9Rŵ*޷_"hBRPTmr=rqo/?ZdFZsuԇhwkT5}9ASP^v1v} ,}/1 0 0 0 0 0 <"A97|di8^Nxqeb5rg/|mcBq;#푺nl%q}W_~v\e1ʝGGB*2 p2m~X#e~SDhvvn/DhN6ںlk ?P/-@]|̻<.Me uVg {$ZoF[&SB]V/T+{̤ǠZ_Lp͖պu ᳖RӞ={OJJq!vkP>{~_G>mС29^(++g`Ѯ]۝8qm22pˌN>푍oРA ~t{ZP67ۇÇ9;v@ǏzRSS"<<\/J򘶏Ph"e3^P6ޓ4l\џ畹+OJvv]e]rLS?w~&l|Z (9 kD 0 0 Ârsx;f3"Oٙ$(_VΈ*RUw6 ޤȆA-(?;i5S CӇۿ6Fl~{=qk!CnL0595W\=Zk~yaaaaaa vKIYs _g4y 9p\^,ZmN{ nǂ<^ F"# Ukևżȅ2s2mޤk,j$y_cո*?9@I9dϐ1z(Aު~F{zz>J>[P4 {n) v"~:y@۱}_Wl'5vO ΞZ,NGm[T/3]'?\Us%e-88Y*++6m%$Ξ=MfСC=s _z%|&FMM:$F)> lݺUwb~UjsŲ.T`o4~ ɓ'eYśw}g]>%|MF`-)zAxO̳%seKv^/\Y=\}Pk^lkkku{p޽]`?`~&_q*(Ӟ92K 0 0 Â2 ?2p챆ܡb]y)vNPy uV6%([7 V7~rrai4rǎE͝3eM &=SyJ~wY'̕Z'6 2֨G!(cogx旇aaaaaa/(#rmۯ >21_W/(WIAOg3d+3m`\ uqxQ}[2*yOJP޺5u_ݻw_F~7$=MbFP+F=.u4AYl0.L?0 0 0 0 0 0,(ߌIw~]'#f&V㖴ί_G1YQBǃ~!.&բѯf)86P(r b w o7}ϫF!{-(%gnFa:aZ]}oiF|pJYrv=П2pODDyio%GA2dPt^俻v"fΜ)Ȁ1;:ݟL{/RZ ? e\NM )++C=J ѣ/͂52_qe 6\P_#(!=sn*g^8˦f9gRfr^$knҋ6זJlP6(Šț^"XؼQ""9?Fԍssv.'[AYoo0 0 0,( AY'mU:k,@F+H:V~{NQaeUPKb`WpS/Q!U;҃M78wLBM\}o$sŧ$'l~m$ͻWѽ:8{??~%D˜Zzf64(z[mtIЗp3#8Z״UfNb3/t+]I"sl:caN/.;[\@NAP<[IBB7$O䇏aaaaaaA9(YmT[GR͛NÞ ֐Y: Y[e5W4G2+u.yBg^ _Pە[/y~X,6>㿒I֦-Aqy&]IwDŷa<"~^srCT ![,e&V~[*go5(l d,mFրˑoqP(x+FePdA1OOǑ"RT|Zfb>a"):IEEIq΢fu+FZ8(ڈ5},M^?Ypι!'&纮zot,S~KPcznۺ .(Pk9 }ƤυҊ+TX#dHa^ϕǯz_brnpk[3`$(G'DI%208E9AF^̷IԆ,#0o_+'XYa>}ڕA^Vomd#HEyyy-wERTTB:u&IC^5k$%%Ijj*V#l\rP .r3wybb\ْ%\f$6%%%nB*~F}3nP\\,;w RlRUl˖-M6IUUHHa*<@y4e=p:rg>7BΩ8m)}]ӧOg̞=[Dn͵`2$*37AYk_+#y^)Z|}w+**PÇje󬀯y$UWY7yu]_[F6caS!Ϣm VVk_utV.}l#+<~eŋ#i:¹byVx.(kk3v$+UX+ރO⽯UMp={hg|=:~ :9&-:R; AYg}2!B!G#|_֭*Rcp.l޸V^\#Ǎ󺎩rݕ 2y]6.%v8\.^kF3!^ME?ju#6AH߯Jy|]ҋ=M9_!Bk 5*4R24H3굲@uȌk9'$5k~T' 6"5m{p<~e ~#׎=U#B!B!B!PPR+Bφ_K(N=tO-3>3Ѥ5]2>W|b'DZ<3YN4X{ɕkr<I]=Қ&_L|Ce k )#[n閴Vf QJX=+aW[ -x F,؈gleYHKqC_gP؈g$PrsEɷ/U:4{3#Mz1}~1x'l19n+r|cƚ˘T;NP7wMvK??̷SeL > u䙫F'r{=JR>Q(޸_+` *DIvʂKQܻwǙ3gl"RKJMMBHyD7nX :"H3'N%c(cAb;ɑf=.e'|H3k&)L@@JKKk獽鱟!nJ}/OKKÞ K3G+V'OB2ggi׮]h>L)R+XWW~0"gϞ8 _eu*<9=$ P1^e)g|?#B:o9ի FF"iݻZU# !B ʣ H+׃B!B!B!oGPғQAܼut4no'gxdƌNJ[|9ׄBF!6l{A!B!2e  !B!B!B! cc~^液""%;]=QB(K 'u*Jn |bC[^|xxӛ(Vۑ3o# RVFiɆrl&%y;N,-- e@Y lRkx%J1 k1|u!RH]\ԺgOs?뱸1==QlOn4f0*JLNND9n' P( @. rҍv{p*?sf]p?N(NjbttԼ?@7R3 H@ z׾E(<cHy8Vcd(ם(>G"sAKg rV{{{qxx'''Dj+++xEٌ8>>؈B`6 PA;$&\2Hjqp(#, RJct'nNb]Fgh 7w8@ @Y P@,P(ee2 P@,P@u}7kSgkX@y#l5vWs3fcgni-jd6KF` e(eg'8M ` F۟qIi0(o5s#WFL&?z=~5E59WTp8Fa&(uI`8|ƻ'{i*MrQu)QP@P̈\d@d֪",ׁrŬq$fq18^F3Av]EQg=%G0>y=}iPF@P._,+v= 4U9kgg+WKMIHoktOps 2Qmur\ZUbN>U{yƺ¾ ^}ty g:ywStT؃w|MG%KM(p)L͖|t'PIqjy6dG1kMڸ|;K 1R1 YΛsek$}ཟ+Oؗ=TPV:ˊr:IjÏydC^rTqzRPb֏Qk\Bݤ (h-~  Tf]NNF{9͛7OrJ"///QKK իz}=^O4K.M8Ohh(ӣG͛74::JT]]MbǏyΩϟ~[nq˪G>|zvv6` .ߝ;w'x%YtRRrt%oWUSaqkU^9qj?/. _BB`w,{_7o-/{8)^ }'<MNNͮyO$8 7P.ܾuҵN}Fc (lp&('%%Bʛ=2h$s6.gA.~-ToMr{9,c)~(ì("?֞qJ?lZ2 jnݕ+W8k~MKKro}}=`q/_Җ-[>Y@YTRMM ^Þ_W+٢VTTĵ00`'s= _Lĸ5,(RQan2.j9#¨ 2Qjb,-<.&w (,)+Vk<,Ψ(]236ӑ}BѪoժUfc '(;ʼVstᔘb: (lp& (3*CBVb (Os.ҳumMH0m;#_Yc<1\䖮 ߿*ȧ}n훿?{FFF(tDh4Lӑ`O 59i@LJOV4ovm*(( VKڽ~:ݸqâGV[ h4Zt+k;!Čs\kmmE@P3dk:n6J:q@9vM)J13Pnonr`DngNqCS).}{wqU進@wiΧOpgy11h}ꋊQ_oϚ}2-ǚK*x*C@ّφPlw%l!=( }u"C1:/~6u!s~;pNjZWPc]msӷkPfJf!au؀]iC9/ﺁ0N)=־z L& +0D A7or}ttvS*))Iyd\W+~Oa:5.^2 l7i((W TXe1}͡[곪+7y (w6S-1V&dfL]b3$$foiPq>Ex6skycMT<ʱkI ǭ!BWG |'gB?6 7H/L@x7'\{WheroaH$/ekY8QsCrhmiҹy޳_n-=I7I%^ؚ@ysi/~B>,nz~ !D"TTh41g\[ 8njz7!l4Fh4rL],]x6QߧL&Cc'se"^J+$NY>$,9m g1/jvfqҢyrpwv-7V܆Be'z< UJ%*?"bs[`+L&,/xbL+?V1^غf\NC||l6Nc-z.j'9£Md@ @dj 4O?Ǭ|@ f^8Nޔ bm H6 \Me)M۠Y8+IMhREtxW8_Ƒ9۞qx~y?  [ZPSns d4gAwvPC`rvެcRlAYȊL4Ţ!!w_!Y9&(o\XQJZ{}|Lznl9OLy`дw;[ P (9"bl~~ FAAAAAM݋d~|TAy}< AYJMڦnY="™>5q}\F|XwAzVقl_TYp}9rA&6kk"m$/1;]-_sW7".|W]qwKV r gl3g% Y,틍NBPaR 52Ţ^-$\.'lePUfĪKZb\CM>GR5Ynp=!fj5Ly?E.߻gFPV9 9pQkc3v-J[ů \s!Oꜫ Wz>[sø! IB>`^w o:n=AAAAr ʝl.|.殦LX.ˮ$,>vup4A'kՓ^JΥS^rdvsw+:k"yلsV/( O_c^8?n9MlVc3&x#cP n,E!)-X]hEIA]M7z]Jèt8Nt(t;_=2 ){ F3Pv0%%%%%%%%%%%%%%%%%%%%%W@|INmC3s14cUc{B˖X(uik[ޣw-q).~x0Pf|>M|&yEEQgl#bdҙ֋8qs%&\r3HoaJ:ο޴}٘sh+l$nxtV'/5ⶸ7u}[>ɤmvҝq>5->5 h~ >2z HWN|Um5dߦ";ׂPDnɷ^jS*(Z/,W2X6◈mUQXUI˻*6'*C,"Tc[ܯ?|!|t{?W$#F$!A3.(J%k[cI-;~cS4ژMFDS, AU1˲*a}8X$*"WVZVUP( i9`aaImV휙LF2%(BFF 'dav1hORȃ}Ȕ&\ooob"ia4vd21ؐ~!wsgydYw;<\0-(~޿C !B! *'gwRMrq.mVlࠖ_,y7T@`yEu``M `CDQ )A[ .vnLL1Z1~K$~㈯hEȕfPeo$(CDҰbU6Rimql玫ӊ=Ơ8~qKH>$kR}ꋔV?7db:[<%0GAsjAqoFnkZH!f9A|MՂ;C)2 Kɻ@",(HA ά7oY*urTق$}:ףYo_H^1xqϗ}U HOs|!#]?y!*o&"_GA Hk.//W< 쯬Dۋ\PΔꋔebXoAyʂ20 0 0 0,(P1 91 M5"(0Du *w7}*(n%<s1:kOt y 9e_7J7d:͕m5u&AyLXGgKdMԙeeEmiVV;gع}?vx㭠P'(wlh>&'0 0 0 0 0 0~#eOĐ<+—OH1ڹ弰|Z?=d$+ދ,&8\om6Iˮeҧ,G s.'~$Ec=w.<^CP&L"01$,#98 Тlǻ]y*(C&Rµ7([rnHAv, vO^gS%mA()׾oB1Tzeu}еLDAw|rA)Y~CA=7v%ޜR4z5}%%%d2e%?~,V^- d8ٳr˗]1 ##ó?3.'|"ܹ"%;I\v{yU~mVQQQ%(C둠 .""BDG AYž*說*ֹq{!הիrٳg5Ri^JUsfuϳ$] ^Pϟ9^\]]MuBs'(w( 0 0 0 ð9HIDL#/((PwSd iiiH݂hC\ K-ǎg8tpR%l!R ʵ5Kiqu:៮ցGP۬ݞr4ƭh{丽76u;E!Ǭ[Ӭ@P#I̒bq٣9Iaaaaaa!(lRlgx푠J]OU,(<a#S}xڳlshnt`e)֩4"|Q򫠬9HdUK]\3t3 ~C%E_J.?@Xv'e{ nESaI8hTZKJVIwgsp}ءwK ʖǽ?w>@&gS1Ci}8}jjj4}#"e${a駟BF*HU[o\ߋї_QfI]Xn^,Rc5ҟbtܺ:W1C 5A9%%q%RM{BggO?UzϜ9P Y+ڐFK޽+233ɓ,{~;ɰߧIU<{5{P!(+7|#ۏ=ګ&R;eaaaaXP~Vˤd|S{vK"ms'eWLP=k:x#GfMs3qBC܍s@JT&BMhCj˵F,c,LA rnWQ=A ϓ BDJ:AB̝ (""," 0X,6a&_1EuОݝ{ͤ`k+EB3tJUPF[XE}x< @>_ HkjjR.(`1o>A)D5$cݎB"u'GB&C?$>j9s&ڤDW+$g 99Y[[[92UfUz9eee搁R͛75I.۱c|}-$TPGl6{$^yV @PVQ/{SPO)΅M70 0 0 0 ʢ%R2n|©}˃.;wJAߺgrF#Y(׻ϝ6(PL[C6w ʔ|pVZ"DP[;ɶ]Msΐ)ݷأ-<$D\?!((r:gCs}bذE1aaaaaEAv,(/ m4G屡yt4pj/S{|P\Ib—kW>9Ncw>x{ UBvU&dgw=gR. N7B/ FuSI2^WކtD21N7M}]/n[u{Ty]4-0>g2hn0F4WZPi)dH#@b3j[[R`)2..}ٳgчHwt ΡH6P7;TưaÜjq 9aתTP܊1, YYY4BAYʮ믿ȉ.X={\ܹsHe?oٲE$$$H)… xFeK g}q>7^^}Xg$'Syo ˗/ǚ۷ ('0 0 0 0 Js)WUuj.)4NwXcO唔aؑ]=I(U0׷Լibn2 fR.S iR8 攖]Î@K 5k!R\W.-Δ`,$ZLd4۩99@2AY{AE|]^_caaaaa,4X`bAym7)9*x0+ ʢЏ5<>;q˹cr"jeJP-iQekZrkeͶdH vߐgSKYuOQ"h)8Kx}>ʮ78}. zyQ]#sZ&H9;SAShՙoJe׊ij@%//6)UHQwmυNS/7b{-[VJnf\@hڿUi_!斖qr#JfI7w3##,(sm{,,)vuuV#(뫩ny{QbS?KdeebLR릍mO)Kq"?ci.jIo ;x*֡5L\ #F8d"--@2CvC/ XP9ߊ~ c 0 0 0 0 0 | ʔ|C9CGLH72\把%1Y\FBF_\* ~H)u~^Gb|XHuWۧڻbӄwEʓ"Œ}9WFgCRfȕ藼(ΐBk je-X4!;L_AsEV3JZ1G!+H.>'SE`\L!BԊd+jI\(Q)0c9f ,ck87 .=a AE{{&fH1ҊS) Lwm֣}"h89tj"دuЦ(;Ck~o)Hv0(@ؖ*]+ؒ앙v~+mݺtϟ霹ې1U!7GEE8hhY4R=6?qkF&IETR*>yDJC /[ ECqpwgniiI!N|x!*xB}nh4hOy*k֬QZgeJQA=|(--=߼3^,S!(w( 0 0 0 ð슘(HnYrHJsґ]\$LVu4ۥ`W]ڍ0I !++Kɝ5s: ))ܮ}1v!ƣBg4gWҧE^ׯiq'TMR. Ǐs=:K#WeK)1 n9ylX0] ųiekZV_caaaaa[P&Ffoa$KKOvB—9R+8ŭze䎨Nw ⪽&N8K($5!];2y;QHDhUP#e׋߰9)؛ovK$/ҿG%"r6 ¦ޠs_"P"(V !)nڵ c!wi=L`O$3g&b~WWJ RP'ugɁk0Xܶm}ѽ R]1x`ՂR c e;&~mʞyw4qTsNJLOJJr;z(%voVϳ}_ -2lWQgJ`MH$BX@_\X O <~0 0 0 0 ʮ # KֆJѹIl"6ՋKY"_奯FҲ~eX0{>r/OcJLP:3AZZiaaaaaaoA3W+S⻤^R潚XQAWybw~)LrO7 BR2QpI4iyE6Il,>IZ_O-ƽ'}} :V&]|ޏ}&]Z V$ N.Bgl¾o/M?ʴ`%BO8F&_h k_DU5H_֥"sj[V-ɷVDj~m4 α8#j%k 1Q*dhq(Lq8$ W~zkg1Y2w{LOd&.w b>uugKH:FrnS !)Rx񢨫sJcǎHwQZ iw܁T*WsAs]&6l؀thUuRzDOÇoܸ!.\{=ܞc$gRr1Hc/U*F?HlH%u>ڐK{HP&{1#><#Rtt|W cz!RݝO>:::TcbMz'px@`U<{/n8#,hmmׯ_v)g:ӞH`OHH@}XVQYYdb|f^ݻhCb7^DxאvZtqF%oeAaaaa冺*)Imh$z ? 0 0 0 0 kuq4kbApԶk7ke:"R&uZluS+J3T+ RAءQڋBPo*xh2lux]~ ~ {eҳ%-?j{/ߥ8\wݥ(ddLjwOUTM@6T//=m7zRjapB\|:qGGCCzh@zEk$%:#hh=ebny|4 ƮH#;#s^9Tm~~>cxx8z{{3cvv乹9( [J+*_& @ (sl^)Ӄh;}aWħѐL&c}}!_RdY( Ͻz12:5QVQZ7HeBh=y1-'.DӽSѰ~GGRmS>Ud Rw>7%SPt:˱ 166er5( @@Y e@@ P( ?Ԁ\ j=g.]-(oOd/=?Md?/nOumvmkd#y[$nrv(v1@r3g+SC~r܌y&z:|mvV\qp.o_o^щk=_x.8sי|יػ;2f2 c@(0Ê0Ak[fePnKkIG}2VnDa36 27VӸd?,M }ϛ_3y{+{rpK^~AGN;:&}}yB2ץa|/R=eE1ڪAJ'OO cw|ѱ ^|˟%oWͽ̢lv|>qws~k֬|H{{q_͒qu{a>= ((((;&7X6rװ '{d /tlZ]!-&O@9[eeg}}R?寻s8%8} 'hA~8ue̫Fʚwo[4r)k[`kkF0܆n} ^kG e?Uceebo?& y=oIĘc g,V[DW@IH\Ԝ {G9c]HФ옿*Y\_q("/*bP$ZXV-yn7oʍ7'IAAAбgϞCK.kzmټ^5>ƎHmm]VRSStJ]]@\.=>1z_6{@ P@2h\|;p|~N&rљ u^t.@YWZ"RG;tNz&PL? ?V)Ӥӵ!,_%T"X9b|/(3(9r)F: %-IE?C5(GLq_#PƼ#rIM~(W$E?ί",9sFтe˖IY{l/Ĥ JlN,ĜxXS$zzU~@!͉$O~x>p`;HV$'&[S.W$,˜=bYrTߊ 9}0G|7{6qe@ P^IGiMovj<<((((@9\e]ZM}%Pf##M@ٸ[mUgt}C720C%-mO xȾ>|)DQ 1~C@٪ȲW'O%_zB8S˜^->V'$`%Pf} (OG3OOyeʛJ\20|I<_|9 U_ :w+mޠ333+GD {דtPR+uVjTO% ljkV\JPX+-RF.[iZJE]qg<\~w^'q YB¿oPVupbL-5N.ɸDxypCVO/,= .sLAycao_߰ĵq9.TcY8\Y*t7\K0|ݷ}”gY1|;#uNHX|3@Jc. шLfͪasҦ۝sfPf0 `0 `02hP62Ssy ʶAXsg<<lA@@&;C EPHG@`/X1E{̪5"Ƃ t5d7){˽www?;Y}f| h·Oxsr9pʻgn@c+2!#T.59/Ά=v156ϢdY9;I'kt7\{=1\Kƒs>rk7(Ci{8zs'νnRPvEA]{ 8eipB3(zQsTacfs.pi N7c'ju|_! K4y76gZ3s-WA]uZ@+}6YFT,**w}N>=z8~8466r{|37cGbbb(&۶mWƒݻźn{MMME֭[֝h"ݣ aڵpQ޽{(_pVZ` [[[}9Y>ٹ<w^):ҥK;j$(AAAA ʿXAwP,4:07LLAhNJ>F{_X*reB.?JAe___\~LXFYX:`k,/|t'(bwCK OwA*(WꅴH_ԕA/6$Eõ*YBd#j4 Ծ\2*nl?&,,,ⶱMFNPաlJ@u ]% Ay.w^eԿA db7 AAAA1p2QZ4QB:SW3Q5v kp>σݞݙ]rq5sJ5c\2+?sNWL %堀k-Q9vohTi(+?3y^-Rk#B %䧨31cpQYPvmS}os. aB! ?|IsN& K9\~~~<ƄRX%mJc",, .^h A6""Z uqqC偯3bǏ3e     A#(;:8F{Ì W.h}ΟYV ʗz?72g5Vmۀ}Xj!MHbۅ[i^hnZd쌋` 252ҡT|)HWoAqo~%eV;+֙uVxny9yvYUcFH}S5ߟO9~~݁˪f|Hy1qPYS Qfݧq܈7sslǫ,:*]]PWb!%" E7rZ1A :(Ů8b|\oD9ue o,G1wYd7J  'II8Wz8+:Df+pMrFC8[2y鐯qIl_{'?F19Y]jݚFCGm)g],he&b'du[$V٘Y [Z1?XsZPzhV=sgo:Vm7\<֍XRJ|_uհ9l. AAAA1-[1  m"r/dswaGe tH!N 'ѵbh<)buc;) ;%g~yWG߀q+GCC/\P8'3CMǴ=#@EV]ix}1|@4 j7:Us[P,NciZuQ0xۂÄWמ{ @K=S *6ּ4똹Vydm rU(,\:x/W*l |) ؅833#8˖-TPnj&Mƕ+Waڴi|Lш7oHcV>nܸʑWP𼬬,~aÆ1>F$rL+>J[2(GθaHOO dpƺ7nX+--7oryӘ8ݴߟuf6NVWAF|uֱҸ+?w\Peݝ%;uF1o̘1;߷8{,eY HP&    oAс4ז(&Zg,jJClT(.(|c 2\5ƅp6y~_wazA.I@} PlKZo}YD^=pbhZxPpu:Uz̮䟩`MkʫZ!1_{y=x̼k"qL@j ?<ɝ-"yy[4s֓+٠ 1͍ ?/Bj,RK !wRx0Lomb\_N,'^Pj5p|s_gT6B(ME~2̘i<k|zcL ;N̆ `{-}X7 A?ɹ|Ӎ~Z ó4-΂Ek_WP^QL9Zmckp_ s:W7yo/$A     |A>W`v(p-BAuDUeA,15E-.Uwsc"\޽Yz.<-lT(jsXB03u?.V h4GhE\ޅK|ԩS8VPP +݆q1QpfYrLklVΚ5kp۷3ݻ,R*Iw"wDGG`]}b^?&6⮈drAy`W¾Zg;v,?WNN AAAAAs.(;8닛 dKhp7WVuZ_ X #F((#MxꅐOk% m_/{ubsiUXbWYĻ &D\<eN..!^bonZŻF ւ\j0wUV vcWxeSHZ_噓!14ش4>5daAb9,x0q7,(/L{q8K{ +'Ƞϻ2J&sXP t5VBAv gyݕdA|.$w    b|{CNXL ʕCf5a{L/8*F N` ."푿_+)˥BƱcP.B̂qlƌ|lΝ8VSScX^QŎϸst:AL/datuuuؘCÄY.?XǏc ;j}gSP:[ŋ1e     A9NN0gdOr"UU*偹4c9U`ggk-AY2??nl4WY2.E0ِtAH>{is*kKٲr3R#g>3߷oߨA#(uK̕#Yl% ʥE<ӳpG#"om?o@alm_L7>ɾ{ H =\~"v*dꌋS$ ʬK4=]SbtN8fT`7xAM3 ĉ(>6g.euϺZ ެspKeyw}u kA2AAAAA JA@X8b)&| Ɩ qN ,;S3Mp`ѵ].e#pB+zE/>9Wwyc&$cp*q ˬsj{8M nn>fB}L8nuIp]:ypis %}f(qu1qyW:)ץ<8$4`cyB} >s.씷b\&uR`uA~J{LAy:*CK.^.Y>?s عU J]9+V01y_pnnnn߾?ntNJJ _?$$Ģz*[ZZ wڅGߟKAyquuٳgp}y&v޾sΟ?e     Ayh_(7܁X{Lt^; UjEs`-{^]N+]P9=xr9S( [(D\3<(唴w=[X,(eLς<1<(*" lŘ)6L4*~/Q[2VJb}e}!x#2 8L&Jx?_eO&j (pw[,:w9PPTo7ؾΦ_`0nk) AAAA1e!1\-5vZ4Agݒ _ GN(uZɘA|,M1gXry71K2+?cTiQduNo>|%pupHl<.Ϣ̅-8ؤhR`& jSZ}< \07-rm_ouA>F6yPy|;-_pV &LD_~egl 1Ǡ޽{8v1+pB.2asnjtN@@}sa|-[<י-$(AAAA ʿ8A,@ɸ0b-+L2sZ0k v3S𖵼0Ɉ} ]gp+K$YO~;ЂFwϱ5y6OCBuJ;Ԃ :vA9-2(ZPYdk5j\kBPFp~$&k#q\\ ,lǛ/(|_sD p_9FJPx9=ǂ2/\ cA1c X/1vY맵HPgSP^pCAfLAAAAϞ|ywFg20#NIf9B5zv6 80(v ;P+k';d,-W(q[od,=W~PE2o kBPV .W(ՠtYL ʑGUอߓI&Ώ{d6ugXءB\FTPF6ORyXp\uɓP8+(_+DFF(kό `-\\\p+WLAAAA$(y(Ϯ*8h+,nc;|_nű9K_!DFELȶ;[0[P>ұN&:Qsq6+7CB=>TU{/(gdk`͟VP../y % :GE**_JC@P6Eșhk!68PjU8Z5=_Wn苲mDÕEl;)&bjt蜙I}sBd9F/(@MHxuhX|h5jZ-ʟ^$;&@e]# (Kz_Yj+?&A     QAyۛ(gH}{:sz(Qr?_3]!`.[+w8cْ\qZǤJ]&θfNYGEsQ®i$De܅ݍxCl}ep)NQuoTT7i~6g\2☤qpi ;a}H_de鵒իW@矛#h" ɒ夤$GMٔ)¼75|j\oÇ[}v~aB5CHHqV/I,}jՠs`` _3;;[,S<}IP&    OAyNM JEO}x̺rXj! 8LN?([jdȒ8&ƻo7oul ~~~,nB``ܔT ~V)i.(oҾ `{`+*.DIL֯s0w4YsaYdHG`_3!.$bA˖ZJ%27HmEq9@,z~}A w鈿 eu){u>jk6gcC XYe *t,rTL:KK%lu>|ݕ}IP&    gPP[-_`5k~ G8b ld,=jH w酑*w$c.I}L@Y.cQ$F/Ԃ:sy8N^b ,[ŵ^' 并N67R%똶A9Fi+bCZD6~SP^+a! /_69]zRAYTA='''뷶⼮."(zaރ͍²5 9sE ޢs֭[j[[[p_e˥qSjY`Pl?$    A_(r_K1wbbl5e_=֦U th^~ˀl!.Agrn|8-&%lV+eI32M*(Mv[wof~dWev}܆q_VcbLJTPFYRs<3:76I|VZ[g 5WPfR21}FIwkAYoNI10Q4՘7xA?]dO'F8]$NWaӊYkOĉK ,}3&gX[PFa+^KP\wن 8Za'urP|g!(3y]~K0}%RWz HLw,-#A     =cn8[;"A9!KG,7kXG9Mcb]͔zڎb1 T6*S\v}WauC'--'*5O///|4~agffҼ`X#1\y-"(}P^Gww7966͉JBSS[CCCӓIT[[[k4~t}6N5H5^ѣh4r~=CzuYyrJJA;(~)@ @ ACPVXSHL4npwI^D|,PFg,N3P;j_Tb89:XLPoaB&t%/ŅOpb=իP 됼dqsLo@OpXg,NŦUx4_Y7n| =k R3[dZ>Zbj?!ys;~^e01QR^@R)S&Γ.Vn\Da{źR{h>Kf&7#yL3/CwWe%KY|(.SPf,%gE!qf(G3:FۧϧU4?*x:Ia~7[/)(+3ؐ`M$](qYQ5[i$Źl`rX*1"6?KJP^>;Q3 3X3Iw+&L".cbϠÛwĤu.$bpbM;zA ..}5 Ak*-(u[Xs1Z>w0IL;>y Wyuq{ګBP@ @ Af^J:ra/%ktлq=L%U3JRP~c>`ZRSplJ4W] `r@Hp6) Zg+ͻu N?#.pӸaTlᒱjR=q6~ @C,y֚ vv'΂zTG:-su&U2V1Jx>̇}4ca=% F,Rñ 6 xG@r2&,s^&qYRM?%K}Rُ55$b8%1y5^!p_q)LqĽgCsJ.Q $(Bs *{Q81Yo+8MP^Y[ G!LJIx444eѮ$˱AƺժU*XW"[{|K ʟxJdx: 23H$yerL8<[JZq=|xTro܏E3O=,W3}P&.FYz2e)(m< R8ZBFK `Iee+1w)UX+T g GjMA$_PK0Mp:f D=HN?ӒDMW~GC/(iQ`B芒8h]'[Lsqu[!βTi>W[Jc)AR鮤ϫDPVvLǢ.!( @ @ ϧLnǸNcՁP;-͗%dpMVh4ůPV1w#Em:nȭ%c3Rru\2V^AUfƜ5YW0%.(3隞GUˊƒMs_}ρs{uAD2傲 Zs5}js( *{ @ @ BP~>ekk MEjbʋp5ؼy9K dHZҼ,JJmZYtD͚ G{kAAj$ϯmMY)x>LW1y4+?oG[ض{_?~NB<'#'Uɺ$<&@1UWkX#&$Sz d):t \s>.vBO5k9F͒WWP^Et5f_Yv{'Usy"ys]oj|*(.3fvE!Dv*%!G`+h+e $dWD\6ͭBL)|&U2PsGq.'B0Kh] %"WϏ=ʴҘhaL\F/IFL_Zt^7YҚVen%OgoϦ=Otҕ1KaIZ@يhe4W/đB4܀$`\$5vN~{e|w5%v6f!aq%e"rn0%)&ZVZw[$G u  hv+BzӐű`Achw\Iͯ!,rbAY*'0饻[h7*6ݱ@ @ !UV*̱Mpң ǔ{ %(HHkNxmt-ƞEP˄Spj8%kWQyfp;Y1 (w (q}_&>$Vi克T{}\/ ~8={|a`W| '8Sl3#*['k)4gRSq+eIu*j`;jWwA9'Y3% 1,2~Kf\ɷs\"U$3WEϳ^oX#ױ3.0ljl ٝp쥳 L %oztUgh/*WebQ6+MΡe{ nRc$&+Ғ{{{Y0󶵵QbYa]e:9%hA=# \B!('@r A1'urjuy qսr>=}{$ttt2.,,D@yuu5)9J1\,sD۸罽=!OmG~^&~9q{g4'B_vzz:mnnF({vvR9mOyv>Z+J;ojkwvv"n\u>A?>>O"k;O(w$P%ݪ(/GḱIe;w+g#нK=yyʞ377w(  P(#P2To)'=Rp&(wXTScSSSe@e P2tB 78]5v+m3 T=.o}N V0 ߺͲk(,"؆̏ _e`4 FaA4-/@'-8_>yNzϟ@,P&r4mFʙ=xd4o:?xi@ z6i\*erw^e=;Y|];}{ee2*WK/#9zɵgwc1zZ$OQ8GN#}[14:n6(wIVfV+FTH P(A( P( Cz@Y P( eeT+K_u63cl,. e{PT{䱰\P mcE4DCb2 U+*8&me"IъTD icHkNәK8eWfv={s?%]ըDj0  zuĽADDDDDDD (3PMSkJ/iݿ,=?w5v/٩0ۢ^Oc 9a>چ(={]Iz\7\}ߗ{{(3P&G|2 @#?ox̄0]džKE{~ #(Mc>ǁʱSpG".ѯ9v}8'E }2{dnCDDDDDDDD@(C!R`+uZe23ܹsxn~o(_>۹'Tsi 4fc[dipqvzޒ#DE0P]QǙhe+s|N-e!:u: 2E\Ty >3Vf_9stV6cg,\k3('-&̞㗭{'bmmz}^h]@(;HxU}ƀfq\MG#$.ѿ ,x5WK roǞccggZPvJRazz#>9F9u[*w1uf:P1P! (cW~7E$F$xn0R z 0 hkkkV י{kE JKK1huu5ߗvI""""""b,=7R拐XI4xh<߸Pڜ@ ,in"oK^2['g$PŊ0pu?U E2lf$a!Í9N5_ ejMʮLq>t8Z@2eI"F@׈ߗ2L "lȲ4NXM'^@9>_Pr`x7Hػ*|ϛxkPa)ĉf .3BU.ߔ__Iyo@!sRHq8J(lpƬ`f5p?3K)*~?=5ZzDl֍9$#=X҃V$ԢjL4!: zxʋ/t2ހD??Ø\>ϻ(#>1h;tyM,8nN)tR@Y $H A0l-<IyuSPև Vy.FfʺG[F5E`̯97(yRŏk7DՆgf>Ȓli#XҼ˪96Tp${h𳭆e wCdQOl<;{kh>UeC<Ȝ}՜h=pw?OSȏo*ŽMPBۂRl[xT_4|R8tVh {&PnOV8_߿#E\I V\Hj6x: rP. [ZZKʊ\.X,e/+/Kf;44DDq.]PniiA B6ΐUey?~~yj;ruHtؙ -ҵA2\E'JOD`׊ҁW?_'ۥ#ܯfō3EcX)i$>uvfx0P2 yx! /(Cܖ|fᬗG'(<Uj[4A&ֶ"DPV2uo? q>^}^cGYPfaaaav<0 v7;YimnC{1 ꃈNu}4Z گCҁ> s4&ִ"nk[_461MX/.o JAz)&ˁ[+ȩߜ[⵬Lh퐀wU[\ lZ/,!}P] :c{r BǂgAE\5ZYnCT$fbxxX]L&Z$#*4AYڝN'{dL\. uZѱ"H-\.H$B_J(sEjB-XHﺝS+y| R}<"NX)@Xv,(3 0 0/0DIB1;J {ov3H$RPVcc\۝BMuݴwN|-7a_ HUN)r5qBoJˏ>7u"[ۏE_QAU&TFрzMh?W]\H,2h-󊵠|Z)ԓ͘Gf}6BROr6u԰Ef̖Ӛ\kcx wfeHH*> W/BbETȷZQ _ig]H6gG{_^ER#ݑʠ~5{Lu Xzq3Tς^}%D"H$D"H$llx0K EIwy}aJݰMA}<qIo4.+[R̂ QeAQAADQ@gbD98D)*Θh5јtN~vyZISEJ^?מ>u/D^[E\[ކD~FewsshXQw+ "T]!::'>j< "晞+s%wǎ"_!!!b͊ yXGȳ N]]-*|zxxĉ!??_ulpp0V{۰adff=Pb5Sr\QR+*T57»uyT{ɒ%Vxgt^}n݊;#,, π9 ʖ#sBBj%$  0 0 0,(BB2bue 6k (qJ>>R Sg̉@Vadpl#`u r$E#; %T鿾}y)k8?,< ?xlQgF>Cxn)X FYXmݵ_z2>}Ƚ+q]lR!H ʤLElTѿ ϬyS"cl^V^~oSsjgVT!FZ+6)" 2<MUriqA_D^cw|L-d27 a(EΫ7C_zdܒ ؆ճ9o}K?eaaaa\`喍m/^]P}=NяW0vS܌q\ gk;vuJQt #)bK77#1O۔SKS{GpUEcTmJ(vUE)Zfݩ(,el5^y}X ΫRM[6ek<⼚yw䒱U5Zog|, roZt9eooo&EヒrAw7=zHG OJVVhfR(DP"(=\i?W?~< <@>}Z̻vZǢXc9EYXrٌ,eߖCrv~[PM!nTHѽe۾3XPfaaaa^kA~ B& /{/pso{6 {~qdw>Jr$XYcͪ1X֮-VpEPvVcvQ#3RF~硇cOZqA?ZynaV4y_z6ʶB mt%>wwgesa+ri&9PP2uTEŠ 1XܸqC XZ7fj %WVq/^EEE<@߿_>*iPZgĊ& Ki,W_9cbbdeGH۶m6K:yXwlK6d%b_ AmsSA1YJ,(3 0 0 ð̂quun*)czBbZ2H<ބ( ͖px!UYºDqԥzfLqC-S?c.:n\b`[_\UTg,׸SjF[EPF~Y1ze3WkM ٔL_jXGP.LN~As,OJ'RJ!^.+sm闀^N˔H=9R2?[KP^/); X%Z^(-Ԍ4 IڛpGr8P ne+M}Oܜsc ʚ:p5y!۶,욠MώWTU^aaaaZPFz;baxEY)r|']'l$zY=±ZVzs㸫k)%m|ŲYbbrl{]p±RX>soM+R> 5n<@![H7;Z^_ f}ՍA!+O =Yt=בm v?ڎy ׽ ρc2kOS(EӾD[uy{9rN ƪtm[9gmc'|-q Hۧʟy[((22\#3a6͛7&޽{E~1];6m=znݺgXQ[s ȃDUU%LpGg#G)>|fϞ ݻw79X 2!WιsĜ999]tɠnrr2]g?<[KR yF}}h?yluAy߾}"ٳ-EDD0 0 0 Â2 CwYM91.Jї98Yϯ,'3PV9ٴc5aXl(Gid ՝QDQŸ3(-Ǒ9z5W/aYOM^yKMƽ!(+EGb?B/-& ->l)~,I.M2m`^2>΍IbÅ)(uN,Ю(#B4ƶH򎣥R X=emAYW,T}bLJy%Ak5J߽+UY KXPfaaaa^{Aj:-S~ XQXȲG}ONsrgxnPy(7rFEfX9jB:Z Vsmg^e$dcc*QeaA9ij[~Ru nObX},Ww1kWu{ o Ϝ2KxV))]M+.*AI!0M1y&m5fmQ}ؼ(v NQmw|4j0|F;1PΏ?jٲeB~5_0G# c̛7BPvBB%Ꭼ/*Qse5Aի2W,(3 0 0 ð̂)ʋ h* 0CPu'u["(OObdܛ)9!ܑm ʍ/w-(|ɭ?-cu)7bci̗l룩h|XPk7"-i0pl׏eAaaaayex{@S/8;E_qlU|f^kturGgp|x{"cbffJ8v;v6vpcê">hn:7r F₲/yAc[k6 #tf >",u}#9KO=oTPy,=YZQLx[ cϋ_Yw7P %e:_sUrlSk۪M䪴EQjBICCP1Ippkgg"q1f,kZ,ΞTXih%jAr%Y'gQQ]"=s(?ؿE/2D/H*jk 2WW5CP&/)W/(&'b olcR]9Y!("92es*Ÿg* !<c|poWtnRxJ9Yd/pR:c?V6{O=׋|UU;=g U{eLj|&",.΢f.p TB,rK$-DeqΚ(+!ysiqdܽyWTNti,﨨X5i7{ޭ&(#.m=d0oXbbE_p!b mVٲBM1V$՘`iyVy 򌕘qϾ$~>2G]s[̿htѹVoZ&e;JL7%(S+:yU*.De%2J]>lHw MGy%fem{{T92A/B}1B ֨=_+1\u>Y5ܑMEi uSW/9qs㻳&('Ṯ`V{i\1ϢI2~-e*ſNa ?.;aAaaaa p# h[ '/}o>9ph;cjz-؏U_ ;]v;VVe=5YcO N SڂrH`e˭?M`,q֡=幆63صE aLp/5'yÇ˱L1g;J۪c],!g9}оVe t9Rb~?l[nI ?\(T<7o4[YYX<q,^7AHK҂!hwDxHy2%VΕի<{TEFF3N2RKa KwV8租~ ˖-?GGGǏ6l5yYImm-mw,;H#y3F7==݊2ݎȻvvZaL,0 0 0 Âo/($ڤR}禪 i$ڶ{î.Ja3t"_w1/?U)wf<'KDͫ,鿾>(,=/ysѹjVʪ},)(kщ 1WesEZVP~3'S9G Mr R5%mCI]v<73!wV,J2vza4F{ jR2طo\QwW&-یwϘh)wdrH x!ʞ @Q6gT?$Uubb9+F@A1? 2aѐO?, _wɆK8g\]'༢rXx8۹\_jA/^أdNdhF8ۤ%SOvM\j]F` XEM9OyUj5?'͒?'R c GeaݶOfd]h ƏQcGvG!Vx8rGd >vTzEjRُ+JESCvn& e(eg cD+FKyRAd앻gp`UOiSTT,qOӊwF_z_ (b o,2eZ>BÚR|vOoBx(dVj JǓAuZ DClho1./>E\)ӖF$?8~0\HOsi '+S𳐋`gb#Ea췳*]^YדRƞsզF;Bi]P?QyD|L4hwD(|$5#NuE&ǖJrغ(ZD'cM1oU>UPd9}h_ż(d(20 0 0 0 Q(hn>$eK]CsƩRڡ\~w&(dBtˆEu:M8Nt4qӹafEAb]EMh0vziⰨ5gbu;a]=;֊39.Pq9}+6:ka{] z9O,.(9k vت akOԧP1st9w1x{{0p§ByU1r<ع{gے j࿘ 5בo WĚ~(`11^sP3~=GĺW~*sU%(ڵOoσa{yWpton,"(Bx|)u>}ZYYY|reAQ__sa\jrJT}􁼼<`P43/Vq_|񅐱z ;vooo Fq<ɱcǢ$((( P)j \Lϕez+: 8p@~gŽP'4l0 O>Bn 8V뒒f͚E_J(cmF={3e;>(><{2ĉ -9s( w|Gy(KeHoz޵kWEeeTwwwʞ_\џgzٲeޱ 祮+oVq>.m+>7o#X:&Y `aaaAٲ=D -/es+ajX4s*L ccmVP7]K_¦}jk|xg&!o6ʒXWJZ%3D?59tN_ޜ/wNxi6c'IXX]~;&(bewB/$%L#-ҙͶ2/e:4ƖY)%[˴6&""|9{xD.=~sg/%=t4?~T*|5 Xe,9aB]gN|0ó8EsR0Ogf6!YJ ڨcNhvzKǡoϔ,@Y1h _<y;;fFyW9Y:U╂o`{NKP2?>=ϕOA׏SWA tYK2>Q+]-:r̟RJ38{k5-ʋ]5䁔 ,/ &lcSrԻ!;O4e:M/= [fekWe0VYBr-OU$S}ݮQsϱr9)I6TQO;fog!JV Pf'lG1F5x؂W ɞOIM@GFL0،W{KwtB@Y^$NRXhun$7`St.?<{7- V-S (+=#]|#_X~8UbQT(+߯r R3~g}]/J(xS}Ƿч` kbv #c {bx5; 1 8(dř@= ;mZ- T[ mPR2Ji˜]EhP4Uk>DZiN9ᛳ]706ucM1W `vRA0I (FviaH:%!80RP 5h=A<<9>.y~ ~!k惹˲bz=mvKhzQ"VjiI[5&clg u^ F|{ ]y.sl{ Vk![̥CM=H'Z\ ͼ͹kYF_-<0~/(O:o[M6ѦGP6wvixuN>rv/OPwXkϺMhO;0Zxj{{ۂ4:B8bQ鴆ǚv]^^R$رƆvn %IYZZ.1OYpfwwr98eS DkZkBK@ʥ۹rC:PvsjN`fe!jjrAN[s6y[Jakӿi] _Zu?W|>(sEr?un- J&wvmwg9ڜH$* X9Y]]gI=Kb$DYxgu(go\n(S h25L"􌁋w,k{8@ TJfvv(P& 2eP(2e j]!f^.:xzdAC:pĉLzM5iKdżtԍ A4țbŰ-eP! b ju& m,*..ɘ쌂ߟ( =A@ P( ee2e@rLx=o2tW{4c?ز mV[c@L^"o{7*++>jeX Cq[\΁4M^纻u;>.lzipӽ㷓}KI<?rr>VL_;}Yy͏oϾ_t"s/_ogݻw3/> ›@|ًqy'? ˞#{vy(/DKQ'{wEp|&RJf $%%DT|§0|| MQS3qTԱIQ2u,FƦ~hjO\rx~v=Z|#99YN<)G?KCCǡÇ6yn Η\@LL"6%OgW? }N}칺eet".d$*Pn["MMjظX-=s\/".50M?F's}5.NM{EXXuO}`Ƚfx}w@@nSQQFGaNY5~65"B|+@x+M&I!c^xN,ee3db)4FKpPPk((0J~&%'ust'%7+P}RO_>JLN>0D U\&?n7M}ǬşVN҂ _HYsjy umjkun F3^=@@y` 9s٧gB'dVDl!ݎKe.:Nu[m~d1q۵cMf%d[zHsΉcFb;YLAe"odKiz|HPu[Et٧HE(CYX;|롐/hv7nԷfv@fIXXWLH_LƆ6ڒ8_ ((1DV,լJޚhsE/ѭ(wpoʯ@yp^^<$+'dؗ I 视!Pjl52@m,Gm:ޚ/ -;>ǨznbS{(P_n,{Q(ʶ7~4V\8Hl}&E*b(nTUUɱc$##ueoeeeb2F@2jfhK_#|+@l\+M̾ihL7%Rb{DFaZ3@ʁ({#P&Pr wjouo{RdYG셻{XSCfHH }qWes"]7Y|`>>"(l1*U7:#""76n+~eNF<'+;OlR5[yZdxnqk(@@@@ʁ(\'r\.q'iݱ(̵rVL4? m(O /тٕ]FH?\bbV̆ͳԣ]6x~K{>=(yaWJ%+|OLLYJdϡ?W?/˥Ngdv0'M__ϕnÛҫw]ORaok`T\8@k~$@_e:`qErY320WĖ${怳̛s9;=B!'f#) Z/DbQ>1frũ1:C6`>Ji^Fηx21h-~oAu>#8>ZY[n10(6%ƫE7j86Dd?j03/W σwH^E'yFr0uRU%Fꍈ㫫̼]6DΡO[ A#Fm [iņۆ^;VZ]9㌨Q6ƛ&|qJu𩕜p:EOo^Z3L` Lދt`aE8Sz #%JyD7L͊dϔǎ݂Rae$'+LZ J=B%&ű&3.lP8J CVL/#|O鉻N~ש=1EWSS&ݭ"M1n-8QQƉAؠr:Xq%bdʎvtݓ~a':,`4(?tm?f׮x+gstbVg.$[e~7 ҽI!x{ŞJ54ksm!̋&:YZ P/ᒍ`^q A+(w[q|1J|їBTz^hy )^ư%gsnq}ۘl3f`\+;(b}78b oJ/9}U Jأ>N~Nc+G G(y?O8}O_!h}) \vB!K2iYN677)T*arrm墘x<3NNcoo333uu:@ L&CZ |P0׳3WX,T/zBp@vd2v#͢R `cccccRy?v;|>rMX,Flhnx8rx\q.gk5W{ YH$ӉvE2DXdu.{+~WVVF48浉 ڐ/m ՃԐT!A įk!DPƣH-҃b{!H=SЋ$7}}ns̻ٙ;Km6u!轀.kZwX,*kp`BAu&Y   M geqOL Kh6Zv,oϱ0]xz^ɀwK$fB}a4vC{{ s4APʬk7gAv=Kkd*77,W 3˷(Qux@?%!mByԧׯiβfnEOf|ZZuw'E%c ~\e/(XF0hX2 ҧH@[cYߠV8Eh6|)0 Y,$"FI    XP^q6=Qgʙϐ~;죓+жy)vvU9XOPƏ͎O_Q$b>AپX-fA((n2 N)gŸԖީcsM2pڠ֫m ! 'rkyAvϞkG߃LAA4#2QCỵDQDQP'bTc1N$jH8vQ45qJkI[Msν뾗d/kWUun?$eeggֿoo59 >ޘ\[‘TΞ ƴMOU]’9HAظpP]R]*\Wfcp^Vƌd3Rph/ʁx}sGp͞yV7 ~tr/?i@뷬fχ,:6J]q!~nCk0ď~ju^`P b\ҡEh}}}{wV.ls9xͲ_@C]Gܡ5&Kd kzr7fhFS0߄L2j8gd1r*3gs^xY Pn2krD8ƥ g`=ɢ Oeq1jP +: NVUMqsWNy#y~lH08//NH\M])+R34o- Wx6A,0a2@6)6FFjA|^9Ѧj^p,sL,j nS"óH@Y@k @=|e"ebqPTkyhStrL2Go@yױR{RB@Ym7k\Ew}6G#`Qgԙ$@D"H$D"H$ ;+Yh육8}n@~w/װnɏ;!Y,%ΪG{}"m -KvvwT9\,!׃9\36|p5vmuHB8gt3wֽClqoowvWݧ<| @ RyVz!>M5\9lAn0f@>GgfUA1HBx׫n]cs;`Rր[VgPٗ\C[yhV}#d$Q~QB| }9+rJK`#]GAΚ5kd;wAоdhkkY||< `L9 OU.ws h(ր744'}Qhy0#iegZ!m۶T sWUUg}6]u|-[|WRw={TVWW8ےK./_XXȲG2g\bj=W!EGzC+]eD=!O PNJJ~Ν;]MNNf.\< PnwQ+zy\O>]./. ʝ6mRSSa}AAA܅9S1;hy H$D"H(6,b*g^=asm (nw.4ϿTf M΄6l>o!x=nW9L) Qwc *Lt ~bO+C8Yɬby.BBC]+ (sgC mUL.j{4lDܛ5WS&pGhxfxYP^5fzc ,sxY,0g75"WSb bX;$4qqz倫ZZP um`#Sg7d,r *˂B,(r@g V\>%q6&> ²7(I~ӳXl6zd XͶmPpl|Yp?DB J{xXy^L !ļ"HW%*evm 0@wPnwA/r>lȑ#m˗/_ĺ߿^ ?!Çlm\TיD"H$DxͲ d\OcNN\ pY᳖>1-RGJ8pU2hٰR%#@N ǂiU@=UnNDٓ'(biWƸ>2V:2lXVrj=QNc[k3shsn*@1 ڕ K]s](΅aYC\:Vmh|(1'ҩ0/qoL>/{yMFJdx3ns)@w3nC@S\KF4d,$,@8=2LL"H$D"H$巺me{zgz䮾'ޟN~O@>W8 %]š ssc!a| wy=gUHͿ NvNnօgK_xpֽTsn 5/Jϡ`eWqbUkVnŶ~Ήs[E@Y ^>s;i@)z{0Ywhm@YK~pYFee%í.2(O>xAA"pBnѼ+.v (댯!TPMM \\r=E 3MsO^sk_^yesX|5_YP=gף"s<*Bsu=JH5KNoJ]ٗ9h]ƶGzݲ$= \V'@Y ̝;WѾo>h?q|dݻwCG}d4799YK./3V!!!}Ϟ=v)944TW'A;w '@S+̹1/j/( vqK߱Rl֬YСCڵkv;JuѢE:.xϜ9m&~ϟW9({+r-]vmuv H$D"H((J;8?,!ƇlP'ٌ5Z_g ?ջ#g6dE 鈔$c {-E]|~wۢ??sGNZmG g1-mk_CoWAa UQpҦFSx])+9OlM@Y{0۸;{dr2L #J$VTKq@`ً9,(.WXŊ_#u& P&H$D"H$DjRݒ%X멄+:̓\3g,f{grs6gYj}(kC8Ns-tY2ҳגsBaH2i>=f]`Yv@K2MHգb wMV~EtmW  ΂: /]ŋ*@jǎsȂvs5pv(댯!W^yEϻwellpW͛7k;z(=z4v(#j8W!ŬٳжuV ߱w3{RCG:.v{We^Y}B{SS .YDg6_g|.3$D"H$eefgd\3?,T`NlzKMIwJeh6=O3 ,Iuh  S(k(φiy0U{Ǜ.A;wM8.!aڹk2qrf(\U7ة|njYq3(\m! )lPeHk\ dx6sأ6(_}Z mPӕR~xM6ZʛރȨ y*:V}-?$eu:(H$D"H$D"?@tag *;U=jYͰۓ4hD.PFenfYP4g{qa{ώɝC~TPOi!Bz~10]szZ:ƴ@ݎ=ll?˽ϱŀ2L3hT$;v/JN> uuus2o>eD! s)93ܹ,n#?~<J!4 ʈZ!By}ew#vvv@&C:.ϟm555ի]RR"σ(}{!72. ϻtRALD"H$D"@yY255kC@xح߲6&6ZgŚE>aFF@m γnmH:8Wcś'!w36}f|а[JU$d4@ZgtU{4:#UϪ,1 uCʚRөv(NJ8hlTdK##LB˧e-n\~a9=_bBVS`3 -Al;jE\Nnr/ ~`i-}BMW{ n=je@f{xOzw [֓(9X1FT''j 7/VBPNe}& P&H$D"H$Djrm׍٠h,f;9>75A¾4_Ey>욱^+39}Yrms6x I ʕ+ wzy3YCW;}6v} =`rQ2~޶(~Ay\T5?_-v8`\Pv <μέ sIY-?qbM6mP[|/ڼaaaгΈ\e:0A"H$D"PP(cHLX*ǿΔ IO` v?wI1k8Pyb {6l~͠~O36{USK lY7s1wQk`Y%?!I'稘8lX7 e ڿG+旵~g_4PF (H5xtasPA6rP3"R@@Zs@'d`s:}\7č 0W@زo}N*2(T0 b (d҄}wegI΅g~>5Gr@`p-@CʢbCY°11[9gR Wg #XlB8KVlRң (0}{o S\NܔDŽZkZ/` ĸk;sgssl({ʖ>Zkgcj^_D1geD"H$D"HPsg}쌗ʔk;Bo w9>W M>\\}@Y\h$ڧ-d6."gg[{r-XsTZQL%Ee9.bo~sWFlCʿαqqI~¥Avwd0u}XD|q6Zk7 ʀOy5jj\,YFA \q,GuԓcL 椥: hӄdHdg] ʟUЧ6v6GRIB".LXnz4dJEq)^6\ 5j;|}8CBv'b?>%Έğg8F!cw| #;v>#.y,*ՙFȠfqHѐ%heA7lӯ9F{c2R̮X:Ez' AkVBo&2/`yQw28 _~hGOa_lW=sc,s1< Ʋ%uZcs xwW3V4f4iAy?AwzM-b+WLq8@ZEY 5\#y%& y!҄hYF2ؕ^~b-_B\vwe-ї͹s K*#ߝGȿ] ʚz*A]fWfD0݂2u }Dn_~oCA9""B311˩ש̈ PUU%2LnݯbcONNjܔ@6 {es 杚BDJIIҫer$*ٌ5Qaa!,& b ʵh˅GT”~7,+y Kpe' L8Ų}5X.)bշqccc2Z-~2'-((?#gP|aaql'3LGAYfg$Ÿ+++];*h[^^Ɲ_ܜ8#Jx\wzz;};c-[%(ٿ ϋ*ʫR09&%%QFFRwwVY&VY  BP( B ʛ-(DEq\LPc""bj1`!!dSj!(bC)Ae."rY5v .mfFVm/n}3wk:ww;хM<WcGWK{:iiY pf"qؑDE Wl(c] v w^%.Rr) V&Pk}]|ᩱ7+ Z!2fUAJv`JllCEkZ#(3l g2ڵ3qL!N-߼Zf/jz Ϛ;ZZljQrMZPH"^ZaǑ=r"Dj=)ϓWA&Qe0<@p z+qR}6m}=RAnig\wrf8_~WTJPFj1~^_ƜYgReAAAA=((Ԫ_!Rlr%ZqZ oPZpݛ:&zʌkn/>G̑,g]&JW_J!MPj EjrCߓYHH6 ʐS( HF-c6鿍W^TR>exiPgo֨,>'/(L?# 92:j 2[D"W[]]EmiwF;>_邮yz#_ܡA33Vn6V \AVB٘'{b~}%dr9S=Zg3*(U{ztt"H{@8/syw?+9pg := UKP ܟ A|{{ԏ[9sz9 g"L   r庺5TjL=NG:S ފ .a^ 8Nv?.lR29Qҥ`#4 ,ʄ1ZkM7a\n?xzg瀻x/6syo) = W\ϷKu( . ZdRj20PWN̉x2BOdl U[_%o[9ΩF&F0 s ':_0o(k*g r!kwPSNE4n48/?L:k>_C50NPV ?/0$#!X4CJ1UGPgj{pu9YAA= |viks6Zڐ4{ry R:e FeAAAA0ɓW!:o| o08ne :4 ZTPfeY%v3,0:Ais"-:EQ)t,WBzhN:NϞ7:{9ٍ V2PNb#3f+vttLeFڿY7Ι@Y@e˅@Hݝͅ@Ƈ, `(C+o-d[~ggfz|X Cbd<|Nq ( (@@ u.ՙԦ-:, t33fip:Fk[6wG?nW'gNs?2PB333122\.zzzb`` *ۓYnI"}}*PF,P_M䶛GOߺ"P(CU2gIH)t_{s9-y(J\NX( RSPןۯR|&Ʈ; E@YJKڻ#rǝznվ~ gǗz{e2T%3>mӯh˛Wy2< 1h=cY3^i)'~ ]ZML^GRhV=; P( OSϲ0^}>V6ٟ3{x@iukiw3ߝ!8|{e&Ƹ S 8ff ].rqwT&/X쒍lqSbR 2HXrwr2 &J/>s89s>_5nX,2;#ɯJe#B]zE? 2ei<u+6o=\Dt ~ÿԡSSϿ-*A\0ZO5eʣZh=&.|ևr8c`hhqqqmdjj P*3a!rss>Bss3j5t:&&&Յ&$&&r177'Ŝp_{"""b@eу;aS᧙ X\4# ヘiN Uuk}qU5)a@yŝXeo@R"\|0AmQѡr.d-|vҪue""""""""b@ehj  I;$ Q^9FD}?'9"s{2z ǃPT בv4KqK"Z[u]:#%!e^}Yda2Mm_LE@9=<z\DI?JU@<^apmFU/: &nO7~] ) #,&x&j*"w}f87#vKwx@)D Eěf2LDDDDDDDDP Ç{om#cTgMNf%C]X;(S\;гhJ8各\Ġ~tG~5n8߂._=lW!de]9Ήl0i^v`Vuy(;cDokWNutJ:*!c/JMMBo[o---yȰ/ʴ׍-`mm GGe FjFff& \E-No޺Ab~~u|\&++k8,~$_'kW?3г9&DDĀ%3Υ{ezU\΅aUT*E꭭*=2Ĥx-~A\.HVo4T@L5+6/bJ͵1RhN i T [˾p*~gur/̓x|ys;}JTt̡B,[ecM W"k1:o+Sj{&eB!B!bHAy+VN,8ivxK:bߓ+(qH b[XqCL !09beCʡ H]k.gRPW<})(3<'s-%{1ta9<<|LlFss'@eEA~4 yyy깧 JsՇg _ !PPKrF5Jr%#&: 'ҭmc,Ik`IeQ&l.|iMQ b>M,G>BTKdXDt:ڗaaa<_9gB zj5Op\\KcFZeNnȽ^/222PRRvttt+//ZZZx]PP&ƴZ =>Ok{B92eB!熠"Ƕl>o2J7}*Z)(SPLec IInn)IBr@r9NHYp7,՜GURʳ[r^-vLL ʄB!B!poI'v}LGSJ$øߜiV>\^bYB aPW1.mK/uGkʓܿJ7fp") jLA Ҏ|8(&~!awl6y((s΄P`ap(77Wۛz̒*U's;bvMVG+>(?{9*+xa[q=%A'7&IE,\-oUx^/b<.e1^ ͞DL-F]R]t<8)$sN'xV-BYT&M 8/GF;ki mvFE!nOFxY!t5PqI$yC}y ( F%o׷E,~guiiKD]6q* \j/`|@Ae8u:.NmU:~4FׯGBw4$?S3\]w|-ksET_,NdY~/]Wk_ @ @:D$GQvkMDfkD5GPv?H%ak|z|wxɞ4q.!9;=θA((dDIэorӡHi KY;oZp:MQvttpĢ?LHʍ>kj;x'ۮkzaPl,-7" sb)kmӹveBToɭVNxO,BR1qDEj6uvuqqi===399&JIsz@[[FGG1??OXZZ"W~{T*100elӴʐr cqq[[[tD2d144tCSSM3dOZeS;Ivffwvvv S'33---PTdFj4C4TVCP@"p<-lkO8eJJ 󏽧2OLLr[mSSSٹkkkCUU|}}Eynnn|P_ R7D;(0v0R;XmٵDȊ(~肁))tJdC Ŏcyreysfy%wywwfYx?LSڞL&vb6'g\.ܕXYٟd1'jugA0|^9H$ 8RD4e5^oGyrzzڙHMߠsz^BaA>IIz?_K_3 B^ք1H:a d:`{Js%(K!8Mp ҋH]X,]V|U^lk?5u So!r&(撯chDa(?{.jTPuS" ސu!5RPH~'=@ ~ q̽"}@εW #m.mM!5qe\P0Cf fbi1J>&PU%tUAAy_t^僰 Yؼ q[pg传N@]1*uAS+vjlñж6ԊaԷ"^Py 升1U 0 0 0 0̶}>%X 򙢨*+wUc<(CLFgwQF\i^=v>FPV҃zȺk]WvǬhUBsWr^qsV6Aܢ -ތ[('fbrLJkDފrk{utOcS6]` BBF=<Μ :+ꀢ6R~:bWCe| GQR)D9 I!94@6q KQ/ {%/%s9]st9 -TR ˆVTQd7!vQ 骋=ఱ7x.v~s^5 c )pf6Nť\~}q>OLLdsK𗙨ݍ _W|進|p0AP7ԛe`pIA4yFaa!`.Q^zxggg]@Y瘸gyy-}}(k<b:d s\߄8/',__+?35;AiYeYfe((}{vQWp0ݽ3z>7G{{e(&CS(fwd?N_"W+QSEmAMMѹ񡌱MFk$,FCjnu( D:oe'jC~V rVF?pг۔/~MP,PWo帇m߬Pt4ڼoX~³OUͪ}˹,ɠkJrcYAUtYO_.Fjiq#/f.&Ty^b^sxny"a/򤧁F)a ERف:Cm^˪gk]v< 뫫|m|+Т)$Oy)/c R9fvU(O#V8V1yna.gUdń5 (3 \8Ve rTeiOo"&vY/Gswu"|28w}IEqԋ]tYvlYh!` z(("=dEdP*Рz jos.zu<|;~'aTg(V^=:2y᜾PІj5lr[ ( #NxJ{λ`DP*_-+ _v_(*"-g6hujN;ϰJI' :.[%(s"!=3?LS7 <_!a$=똣Wl.v|u j̜?Er".%BXaN]~'S%Xg9b c]>e ﬓ]Bi٣e}nlcd jgNҘ箫~ALrWh<:ɽ?yj}qV֐7o~$e{rr|BTU-|`T*^g\@9 CVQ6eCX CTPk߾ÿ.ǐp2dw ÔH$\ԓ\d2JLi,ˌn'T0$WT`ǐ>/^S* Z-rPBIEUO(jV ؁f7*TxߑQ( @B Q/˻X,s;ދ j<4sô3zxwQUyqW%+**((.!*.b[D&qoQ1$j'N3̙33׼owy:m=rkU[8C\8 P;?1XR̤4Gp}eR;ZM׏ }  %911 nn+!V$Ĥ<7GuZ]81.'Fg7oh}8<=!]Psd/\Opa`p,ywo$1{pKUmBgW/_ЯvƤ{_|gcſ^&UNxZZZZZPn|}}T^ς۷u:ꪺ6L@ &O @V_kDn?!tI݋R~rzV+}۾Ƃ*ʛWUAm5ôT.*:ʤ~Ņx6 [W _}X6Xrׁ5߹U46@<ƶ_m/>vܟ:Y\ >#flbO<[(~;vyyWN5 lp!`mVwt7˂9:)&s)PEKuwf݀i Qv&1r^HA 3/fib7w5R(_7.,5-%&pv4;t<6>|Y=g쥣x |fgޒrdpT~,95ED嗏 h8 ؠa=yO5 'ܯB_gI9o>4 F\8;ҎިĚh@YKKKKKKKKKKKKKKKKe@ܖUx (/nA(GgkN,'if(ye Uݶ, ]NȽؙx+wM󏅜0h@<}|U"Vz*]rϦ9yZr_@m=5qsr (9}p]s@e4lN8Pev e n6Arw"[[;P,[[wU|`Fv#h^K(afcg8R߀~'#\a'rzѣ$=K+Bp*?MNiq^;١CJl駟5UUU2:gO"oݺׯ_\Q8-07"k %ĕH<Ƥ}C|d F:}> ˙IJ/yZVV/v㙧1]'OUV ĤyoeJe˖6kjjd⽋Y5=GO?uM_]&I|7*\9jӦ rW^^nknD~U]5&x<+W7gP_4deYoQfYN0ѪesVUYMGSe.K~ɔf>&@+ApE-[}rWRφ@M8Jݶe fr\|,3% }~W W?}+]H(PƜ?Oy%F7wޯF:ߣcZ߷.c'%V]ӵ\ <ehGhӼRrm)뾝1*xaϻY:M$ZwizKX7G AɩX">&{d|FRܳMo8]#rva 04<ޝ (5+ 2{ }5c9xiV-@H30{2 OKGi<;SPpJ+1ʀVUrSpC&h+u'N&zJSPn6{&S7<UNJPVkpW4qc"| fQk怲{k8(Ù{~V v >*]jen5a;UWskJSH`2򛬉|@\8*4$ ppD@CtOp)B+еkxeeeVy{졹#@:7i>ی߷6mڄ4R\\\t7sC(G7 G_PP yÇ%WRY<& 5"w?琖Ki{E䭯zqD{xHH2D< Ĥy7]P \Ն &6N< {Z@Y#e}g̘APV.$>f IfAp5uMIIK~U]5&x<+W7gP_4heYe;C\:%y6piݪ[P6)+g^Nŕ71ͧzms n+iJtAMN@cK] z(/{4- (+ߞmN/k8Ӹ}ɽxe.SccL)PW p P~L2ɾ}X# 2\Q̴|26vF6ϔHOޙrVA1.?^i}w8A@z\{(3ܥL^f/e-----------------7}ApZ?c~ͶXڻ+Aв-@9Y>h PNlTԱz!カyoߖ-g`w;j:.̗;ޠ;\a}Ri'fn^MC?#[3!wR/JӚf`֗sk)PXJ/յ5n[$r\F`h'[{/1l/>)Qj@vҒR# tfe̦pмQ\yuש/'`&RptS (7k֌OGa7n,Cp,$fT]t޽{lܹ_r ݻ<֬Yҡ̌ \\;H?| Dciժ#WTT(,hQQQq%B1)P_Ccǎe@W=x{n^ԩSzbR)+NJ~3ؿ5UM( Wo: iiiiii@Y^{ܔz6XMla4^vY@SL_$]uN:B9ZFkT)/Nv k<jx:rj/<׏}ZV(*_e-cwx}k~ߴ8^\6< .T#<˾(|3L7koE8LJuITY. 5(FF\5R(o=2YP6g9}6~tn˚*(Rnp~nE3)-\9]a^Pr @PЧc-ʋ[-'B0;@@f9TAVKy? :F%/3xs,37qg'纛ws䒳uͿ3+39׹5?`y5i%5ʤ9_3=}߀T3H3l\_%\ˌ#x"].\>bZ%w%mH ,0۷o~glX7W\iжmۨgbc1)wܡk cFW_uK.^{53@8Y(&kW=xG;: {ҸZ|9oի(\'C_pU(F o3WȁΠV;[^TT$?Ǥ>)+NJ~3ȿ5UM(Wo: iiiiii@YJ 8t<1{iV-\)'6`2@HրU二kU~$OՐi2W8 @gekx;o~k4?]vaߧ=daak$~)RONk48{0MEwB#Ӈ7  7bv6TF]͖o`b^]hKfiSd>A| nh6iPnɟ+% e@zgM[SE2j (d@-"w POyHHb͛7((KBۑB/^(3go_vp:t~b=B HӧyG𷫈~+xI孯z۷7 T}Au+Kx4iE}Њe{R\!4#G:eH2Ǭ{C>)F kC;Sڵ߯v'Y|}JE!ޯoB ϠPiOKKKKK$AsLqެ[ZhPd Pf < Ĥ^2ߚ<NvOG?ѱۆo?d,Q5 PZ2 MmtLo.띜lA<7gHR\]spRnȥ.cp sڀr~rA#{&PxM0lW1Ql(yޓ9YbTWuubr.oJDe/m pP>Vt8)7&Mk1Gwe2,:"R ϙTw᩻'01bC3+22;&o[hpKG9PNv~"+>χ/Scc㻲y>WԁPz2`X@{0tA]Z? >aHoVvpo PNKr>%nw)ٹneuvdZf /C2X@V376"ԃ[Gp:Xy֬=|ۼӛN0ƞߘMuW:گx]# gݘ3=#;0>3X@ N+-yhAPWP[epe8'S<`;J9 }Xll,rnOXxx8O?y1޽oܸQ`ܮ ([QʼnǕx<ˌI}C|LP("55o`w Q1)eeTѣJ*>>d '>{b@Y#/xO ?e@l(}IF ߱C ߵeϑ ֭[JwǤ{_|g+oh ?M!<----- (C5-ǓrF=/,f[@r@yJD84Ƶ҆ dcGXؐ1:˪B,e&U|n fN..x.rlCRDZ6*۲{{ w"fx#HR\XR^״nr_Nڂ2_)0WϾB@yQZj``Wz>FS]S; Q Q߽ kwbJeŀ{`2C `t˸<] P&c]'112ݡCYfͤ111tSL>w @ʞ>}떖C.\/_; an:Yc#pLWXAZ~~~q%RcR|_l(I7оD@MڡCe֬YCQ OOO:~n(+oܸA%чb@Y#`}:xx:Je2Ǭ{Cw,y rm@YsD7kx;K,3g_[Rݮ+.1)'Y|}JE!گoBϠxiOKKKKKʢ {1Wߤ!A+HS(/[ 'cݘ`I3Z֦k ~AcƏB0]7pqU/%螉=, /wx?| ^'6.3kq*lQ,<"f;;>+61oE&KϒRbQIl;YZz/2ZFm\z\Na~b=w՚lT]EE{js(F*~IɱlĘl)ݯ0,g}To GH5}_"4xD/ (kiiiiiiiiiiiiiiii=w@ǀnh PqehK͘Gp? R@j>n<1ϵ9Pv L@}CoZ\P"罶mxhтݾ}Yjp 0,ӧO!Nlcƌqp77Smm-xb`a'O{\kᐆ0f:.g8NƍxGl9X}v q%2cR|_#9n8_`` ٖ.]1,s}IN(YrHHgp2!{\9rXz7N/\ W տť>&x<\3x5UM(Wo: iiiiii@Lnp8$-k߶5{ldb,W.b>VƦ,irI3'#T_^;N^:́CYbR/vJfMEy+?#ߟ}fOC]Kp?|.[-h\:nLc##YiE@Prꀾ['CLViYwޯ*T@9,,=.s={La*ERxk$~u4^65LgfB6[_Gu&f/ 2ܙe̕/P >ld6wqV@i<|&FueC-M3NLeeX6->%t`ڒ)um$#6\~_X*{hx󹚝֟2'pqzpqlVGu?.Ok?̙tomgdZG%cW6Ukh,xx+HIcՃP4ekP#=fU#-3\: ~JgAeiAـ2i^W|s >`eggsym*xsS&MbMMڠUVX0,,..BV;B%PA -Hd=&&w,̜3{ iiiiiiiiiiiii9 =f{ l~Pf<]_s6pȱOƞ D1u:vNRӽӼ C<ac./Sil?A#Xc?|&h/ ~z/FNF/Oӱ [{rt$6t^ V?ЗsZbx pu+%:hUU959i`3tg>hBK[/%k"8YpP+Cg՝ŠOu=.ouR=,6=Pɹ}~3w@/Vr ܤ]K(!ީ^*Y}8%oqNt%v9OPA++S:nfrSH$8W(R\%*P4"AJ&J] 4UU ҖZ^*nJ5JC. qX3`>p85LTBޖb5p͘sT¥5|$Q, Z:@3;@uP-.ÜBA]uZϝd yk :*q&9l r9g+ !pc"E"33#{{;ߘ8AkXcY_'H1FC PV@ɱ Y&2)nofShOh$3ZʹW=cg{vÕ]sMOO? XOL[ {;Vz ڏkqffqxơ<%jjj^G~c4ZKʭ9Ik=' Jn ׅwB5Q[t322222J0vTue1[]`HÕ[ @G쩾ʘ_c9+O(+pL~#]ղʀeEYXP/SXq;W (Cp"։,h;prvb@Wrvpr}\UG0FО>B}<6>:v3;7[[Iފi[ZeY,\uX٨'W Pk1ʩiaWn9h%6ѪֆUI (C9pkm z Ʊޱ vp^foxml3n_6{ %ˏN9}ek׬Ey"I@B9zv2qgm?4bҵY}/m^FX5 < -(vÁ^݂٩^,_j,<Pe(8Saڬװ6\WPA_ɀQvUʐ,g} &ïvzEr`ܤ=tRz}_TсQ1w*]  ^|EFF6@Y% 8N03znP΢u~kP;e VPPnd ໾BBBXggg)yM-֕ P^g"ǽ 'DX2w$6AYL%uT}},;;;M,b)쿺rkNZI5zg5u坐` j7 Һu'XNV;\zqXq,%19|4tPNΓ8yU:HvO?kf~Յqp#pJ1wN"<_`Cϲ ^zbmK;M_A}W{pE9GmέMul1pN߯F~RRXrj"E[lNEK}ps͚r|glv\9cDg 4փX=!۱s2nɈCE 2 e]ٻ |nV ,?; pl6P5L|.xo~Be>M&e%%%%%%%%%%%%%%%%(ĥ+k㽄Soʊ*Sb;ÉkJcpm-0 A!By\!j#eHQc\}ImOKƳzʘDOw VYp_S} EH]@9jH(s]9g=2ޝ%yb4Z/iRv9tc6pox@z#KC&ӹp8Y5.MV~{d}*NY<džT"@\yK (SIwMa]x :8.iRnZN7X_/aYK"W4l6xӡ҅v렓il0~.:*7a;dIJרP(@ 16+5YS6 ^ntUb3lk׬17{ӢTq,fYJ+%@%rB"ڵ6qAJ0tu{zOtp{:]|9s<[. i!`^°^(/ ` %ͭg9v !jyUu_:c:pZ@Y!YuzxS*u ZW9Śt8oxϑX,s) i[,>MUueuV2'mNֆ|^:uވM&u"vpl,ߕJ%~֙:쩻jUD9Ez=l6Zcnш q_ (,cP۩:_Daw]uTH^#t-<; >fu-sGrsף-:)VZײ+ERIVkq^kJ&ߴXTI0rgاg ?y_'(WuW)KN#PtN=L=H(S eB@HUV8Y̦&AA4j PW@zx (P?=j L@ L@pp^duVw`jȄ:iԵP(Pث7qu: (%1s3{4/  &IhZ{j ( P(_pg'JbKk s'9k~Ϸ(^Z2A@yJ}lq-3]unƓ7clyy:3 P&PeeexԌt,^.y 1=:U,\*se1 xĤ8I\@>ees6@j|KF)iii-/2P=[zE|vi$|%Y[^t̢'-r6)*-q{o7no~ܝ~or6wU:9 [eܔA9?}Wߩy^%ʑo;s]U^zs4ߨ;ץұrc\r\9eW^*v,)1ZZx`W{ |QFT,k rlɜpg-{R^^{֭[ke-wL]oΞ=;d`ҤIr!9wܸqCۥA<(\'6 P&P&PSZ|Ǵ}T`WUh9K܁<(-I oTȭm{߱:ټkDG(R@ٚؾzV{ OMҥ++̘4Pbl%>fX:_vn)wF(]!Ycʦ+J%@TO.;TIcsdNVX*)NǽtT g/u4q W/Ą(;32 M+qaBe@9Y]j\W|=UR{e=Z)]nʹ^۬y_A6\Qs[zNΪhl$3 [~Si\.-(%e$sin<9)-.xWoCڧWE ,W";f6Zz:Ao4(&;'dCpK죾'~;$p;2bkUH`ћ;B|a@LIĕ;I.^uۧM'ikxNd)$V ?8+??!p.ȑ#~\<) '%PvL#(&ISWW7eE萢"'jbŝy< PE*(vfSF<沕j fk] H&CY>;n5j@cesfpJB="}}7uг?nz*;w -r}'/W=aڳDR :=Ar-*DWY @YtOmF}հ_{r3,lP˂ y#NE *eĶ6:Q pA1Y';Mծk oщHJCC?^ӹ1yd-婵J=8.geΗ^e@كcKw(@YI6E=.O]"IS4Hڙ ~}-{K-ۙe+gwg-(圀\y7J$7E&~ʁvj_ƞ-}ݢ??Esimdn(>#>a-R/m({lI;Bw}t7}"u⟧$!!!wNr}]C@2 ~ըQ@<eb[xoFۓbz-J.~*@Sc:.22@Mc~(;m8]LekN;➾g_3_)m]υGǷ:lS!-'w,-^PE5cNhH{uc~a~f\ٶTo7h"jzgQ*8IFV ʣn2 9q ǒ 4Řc~G(ţG7( ţejţxQ?+O^oZ;ljr\aTM73?[řouW1]0._ > >wQD7nH_F p6߮o,|X`\8>PW.PEm/ʥ :o-AXea@82$0`$5;Je@e3Kk+Myq^|Ӧy9ss~|?K (c[G?)^+گ=$;{)'8a3S:ڦqi6Fqw=e$Hޙw}9e~EX'?j}ZƾӍ2nez)ǺecKBaqܹ5 ,}7~a}rg?evCoW>mqr]V}On&w. wlu (4`";);T#yȉ4/HϿ %e!!!!!!!!!!!!!!!!({3~)ة$k#Eٰ̗Aͽ~#Ln |mۃQoes?6 Ai~.Gj;%_:blU˛nJ]A7-z+6=ЖQ/CxtrpDoL8Eh&j7* ncBr[jCWa&g3*/tb;ߎ jͺ0( <3BOq{/ؓd(|+BuUm=LSzʢGhanS~חϛ';Dg`srEk=8`BPXj z"?'y,]Ԑ_8&`aa3<s9 ⏗CgwʅRr{ 55UIIIYGgh{{;033DGrr2ﳛs +q_~j0T^ՠFYIVߠ\x>eJ1^̀`'()fd3Ef%<߇(e~@YN~Twg9>%ړtΟg@I uhU_Wg}ge}9ԢCoj1jW,'긘|~q%؇2ےsZn< =LගsóFP>F ;ϗЦlJ{Ve|Bk-x,+ av37OYniJPx( R!z`4" 7RkwefeF~7~7ct;SSEz5/@D_9/AYdV?#Gq#E2Z\.[DU{V(zq Uf3'1+50+~0Zwt4doxAjerZx8V>DPNRx^}_do%(d$,AAr23acZhwh|R'lXW(A5V!8vt\ }Uu8ۯ0O@ j*Y[ ahxZ1_xk1}2`(j?+7^爳¿7ZvC3ϠԺP7[ c31?~-oeo%/sVl: -;5k\mpxaJSP޹6Day4ijrŐMvѨWI45bӢii66FDQ]t VtajV Tпs"^07tY|s73fO_܅y]_߆wU,,9qx\8πY֟PR4lNŤ_UK|O`^WRh7LwYR-HIt$lS'#uCp;WUx HQo Ks{p_F=&dVL8fGn)9ht똊gPF(|eK9EXQ/WV3VhTP=peLF_}[+as7Ts21az2CǺ0I\\\\\\\\\\\\\\\\\P]끛g)Uxo4)}Ae(i1Ow%iXc0j+ۿ@΁j6qՕf'hjbi%NkT5B0AkAD-i֗[nfAdՖ>zO"bk Vk5gu1dcIԛEj .;N,X[p<қlL p]5j.8z( o4*+V1S29DMdRDAAAQTpQ8+HT$yāHhCp6*I)ӝ~|^]rSUe9gasd rY,bOe'E\Y=S%z@K*--m΋.'O0fݺuTNp˗S9<5!!%*xiU P _\:t(o߾,88\jWVVqnn.֭@wlbjLpylxx8g4/9+ڵ uYRRB{޽{͍g\y? Kqz\;rr˟+5ڽ{7bpTW^^Ns}x-?8>ZoUU\Sy8q~?eqlzgB< 096 E8:oKqecݳwVüeB2@21ʁe&s% (-GuA!hLue[Reݦ7P9%2 W:xb˽n$$=x܃jlz%/ݓo@^cy6>Ow6SVn6G<òTۺOs}`\n|.܋[PNסot2e`d Xf2ldCɽ15P_18&wpPg&쐲gV=kXSD.&wepVP&y,8 ]nm~}`2[Z@٬u^ \򋊊U)p!5@СC[rC2@׆eREEsYL;̟+ksgeeXx19988Pܹsy> 8r?W זeGRP>|8!ӃT.PZvQ e~8˱U;j}_goVeǏ&ݻwKW@KBBBBBBBP~e=˓MLErtfmeenMN#g1(+;tXQO7Qn~sW80F.YӔIz+KWcZ*P>u ˯?3;3o ix\qJy@93*!C2wk,eYZż,Iƿ9{&}.c̗#S5/Ksej~=92׈Phl[k~9 qmϐ@#FBy],VVt_Q"u𑃚 (G 9̟灡N.ө91lvrQ\Z(( u$@̂V| `Wz݂6MSS{ڴӧ hm܆?v&||rǮ%hΦeUZXyvK?&r_q +4fgǖb?1~ jqˬFeac!7GRƀC&TPokrd7\vƶ*\C9!Yu`s %XnBIc~ U }ʇ迟 9_Ϸr5qߊ}e1...ƍ4̙3i WWv(Ӌ]$"5ޟsB@P TcÄwUmvbWf7yK66>?|2n9fUrup\lee0$"Ç"{y8Fcc#@rt'8q?u ۷\TMP~7%sX/G/̟+kT__˖-kսްX3|ǎmP3Ν;{$WsV+W<gޭ}Psė+eի1Ar=z(_^Ie~8qU=j}_gori1+%!!!!!!!(ƀ2)f*iP&%&D)qMHk (e( (7V @YHHHHHHHHHHHHHHHH$pEpv[]TfYuc }Xp)Uy|F=|_V6>MӝƉqml~hfpɆ6ϙ S93 6(?W4-zyYwpTJ}@4&˶5h4p_5ϊ>899Ᾰuڀ W8A57\|Ƃ-^. @YʝXFxJ@TQzq㶵zH.$mV[ u] m2Awշڸss(68 `gɉy`ã"_u׾4ϊԅ*,{2łrSNt&U3"*84LJB`&@YVQn8%y,<(7EtzV{%?2 A_mG7 Pܔ4] Cl3;ڰ`??ҸG2pN(WΔQ1v?;{o #Lʣ[Ь)f`yc֏fJ\ТdE(s]_,CgS_zYBBBBBBBBBBBBBBBBB 2K4|==%u;K\94i{b-ʃ#UyXi)eO &c?< *93U_g ~iJvLi]rG%) 7X8PFg!g<[m>"3؏ӂ+LԃyeGP6c@i? (ew  5bC\kʴI)lVLݜb{{w/СCE5g~`]vmn< 333Uի+))ܾ,rnrtt4\8ˑ? 47NU{j31gZmیb39˯^e<=y%☳j]/T9 ,{NH`K) @e<{!XT>?{bܳg4gHZqN6 (F(r=}5ŏa)k7%MBjv (Wn-؇?nq3s(6"2]enϓ$aů>;ßg_>LP65ҴU ~~~<ʻ%cz6$:P*NOb;JN~I+** z>3vU> pEaA~Vm(s\_vWͭ( 2pcq̛6o0n&Y.Xw5fWN} JݖSӍaNVBϫX Ip(ou@!q-g.LI31¯O1W09DHvt,OxG reYn,X2.KϘWG̱`~ 1(MXbIT\Mk;1zף]i@6k 蝴m P&^n8&<<ߤ$tS7n`NZgϞ/ (ϱ߂I;|dƍ nڵki]<3Yk=<<86 %AKggCF1@z7.fŵLqV&aaCa &pQ,(co~Ǝ|Bn{;*Ǜ=o끟 EK{*eRŖ5T:xT7I<.7Բys؄qlDn!KeG~ĦgA}dpgV W2W9@YtK& cGL ud>(aQB  `  sLgge: aaL?;פ0{iG[mlMilPkcѼ\,֍Q^hQb]"xy"z>ՠWk籎OU~njeL%fguuv$(nw1\gUz7X;5|^'#}}ۈ17s3N;BiEY,.SN\@)zo  {?nY9؂20.ʱ3ŵۧj~zzWsT&at| p8p8^Pt2p5  ~4 OK4i U18mR5s`:U.DEkPl!5i6nV1p'j4֏/'ՙIa,呪 م3kA ;[eILF2z6Hջ\06KcRįXO(7Črd,£0x(62!` KUSW /=fx~LfR+Isڝ )+;L@[ڢHa|[UPFc`^JqAYlҁ5WxA.OQnBs@{RFIh)(x~tgs5g |d|O 6d2,t*D"A /\G|>OgvC(X\|l6 33?;ۘ*0|Ydis*M?AgCD9RPJP`)[6Wk&-e !J6 *҇Zߴ{A?vz偝79JLL(UZ LSrrs0땕8N|``k2,%M0k_gZWģm(##ke?,$~z^v˗x(//}RSS(}]g~p_9.u 655 WHigj/Cm9h:38[07]4ȑ#"o||\><g144$1֮]#BDDuww[(sl~=3Zvܷ[fRR2jxYKKKKKKK PĦ:YK+W۵:o7~ĵWCKZZZZZZZP~吐G(jHGSC:u0fH79 (o}tU ط>xIw;ݤk=$d:1| q?Fhd:v|IϞw[^޳^N \+-[]p+\~O{aj?@^i#/ܧEW΢]kYϚ t\ŭ3w8꿩zᚌ9-wf*=JVr=da"'&V2jXʟWNJ?kl7v'SW~z `Vyt>3m_YEz ],\ */W-8>wH@, (Ф#2^: WYTS_lLE}E[Ec=n/4q|Mm?)M$2%ARH/̢mҴRIQ jT+t⦋]MFA!!^3\|v<;M|fS=ykkV10|Rj23ZyAْ*ܶ]7$'+{4KU;B/z8tup}Ӂ PYk9e4?~;8RKBbwTC;ܧ~ 7'plr LQv\wmG|;E"(G:߈>T²ÙSnNcsN6@ @ `U1X9C)XdzL7nC}(]I'9ؑĵQ4$4Q25;\_nj\n}4'!!*1޺x3I, k˲1'%2=2=Ja5&q3q}(+JAڭRnQj2U6!nO S0ϳ0khuAW;JcVnoxAlK-&EGg 04I,-IL栜4Q F<r4GꕅY'o\E?/wcd@F$H ^iRPfRTP\kL\*>V!iX]5%_+Smݫu,(sjGߔ~K9bcnO >'*D<\)7K$fS$ID@ ׋ݽQq0e]$D1hH)p@,GVF+^NxNpx^ϙ:rGhT::n\tR);˥kvl6YϪz>p (9+ >x5S.%(_]@],83.PR7À2G@PKzGE>dN pPTD*5p3s ( P&L@ePpST8Y <=S7A3uVQ L@2A@ ^yD>Mysټ)lEQ+^4MSTLP0l֔J%tF,M&1Dv^ۆ!X%0 pu,+h¥%U1$rqD[/tRr0Jzg>,e2 P(m=0e3te/NњJb!4p%A$j0PRl6k^*JmH9˙@YW׷' ( &PkcuetyO-roњØ+F x w1{HRt:ہrZ-ft:뽠/G* bF#vT( d@,Pv2 P(e2@Y,P( P(xgNc8?3 aU cK(x "V@F٦n֫jVAQ;Pezvi=}<Iy0 ^|2 !P/hϔ(""""""""bucP=|+~F8;aHtf,x=үADvwEVVgTXX`6`r ׯ_>8xM1Pf ơeq=bLDDDDDDDD L˸gFՌ3P&"bLvV$OxV U~m@@&(6Pj4 | ƕZYa^h8;w1~#|QJrQa Wg").:ʽK^QIE棢zHqܧ'3vq.cаP;8fyfL2&zU\WMNwۗ,şߓ- _t|I! d[Jܦ[S;Apbϥb~O;$nZw1'K8? MgxU$aՅ (-X1P^sN[c&ussS_[2P&x/.k-e),"dPl*T[:. {I$9~-Zo^4+WO|.we \m(gDle1,|eYh)"5?LTlϟ[C@\%͈zfR.oR)O=X~c(ޔPDF"yv r g!zFJkO-*DDDDDDDD4ty݌ՏuӸ]Vy#J,}\Ǫ?f4Z8s]*ȭˇ?`< e_5w,*v] *S(@((3P ±cP__I&ukljjxϗ2{2I""""""ʶ Â({q#1#a|q MCE)cGO59hx|ӓg.NcK23`wBTm1P (w'PVž;KzmPPe e'ݒ3m?qu)-ӈT"bE{MHB%SW45V0aJzG%)un$)Gʮsm0Py?ձ{xyP˴'& Hs^^^JH~~2GGG^c@Z%D.JDDDDDD ?PHe,%Eƛge̓ٹӲ)1r7ʇ2]/e"}( +eA kzP-Ծ}6ϩq?@Y@RܥLR8 uV1IlmT?Vzǻ&!pDk,Q JTU9̈́XeꈔK8G m( wY㝧\A}ІZ G212P&"~%""""""=59#2ZYFi=yJ cZTJR,POC:رb)v8k*]zmyW`7ɽ26؊kw.ᓺ(ٰV^WIrە_Wu+FJ}y3eصm4@|m|kp*tUC2ǥ\wҺ7\y.._qKOEC TJ˭-H"@P)\-HAJH%Ӈ*.]\Jd˥HP h,Rd2'f#Yf>m~s̜s7?HL_'Mח|Ϣ/jTbb6^b`V*ylQ|qn\q>dLfa/bcL\Q ਯEc1 sV7mێ3!%_6(b02]I:hx[lYJXϧwm.Q%mwˏꨈdyK7;2b{fefT_ 4o N8~瘯%2;짶aXHL5yFj{ل09M>@e@ @ $P&Q,fUڕZM6nloN/v7ig'y֭~Gۛ-dVqHOQ>tOU![ۇR:YOƷ&|W-.6'SzdELMW׶ʹKOpCV<7RUro O1>d%pճS^v(wv;w-6>ߎ\Q}S&#ppr{D!̎P({zxx %%---ְE "3tM:SSSXYY///(::]]]*FFFPSSwwLYբXZZb99#((z>>dk |ɦqr9_ ץQlmxx8NOOE__{~؜^MMM4r9+={Ո{T*s7f3I{[Slzߟ% !ͯ윤R){X}!@ Be RMڎ}XzeWde43%PfЄ$̘`rwsqXxxϢ/@Vm̑(_IgxښF'wQQ^w %)"Bވ'M4&քKņFM&zLι'ܻϹ{??wgr;̐͛![콟={H?C[iQ5NOSJG'ū ^^^xVbHXNkʃrYqJH T5GZm2.xܪnQUpiQ!kHؙan\d,/(Ӹ 2Z(nd`Ab3prJFfЖWAp_{ E؈!(- ,͗DnuAY){A.<^JmO_%Uqp<aaaaߥO <΂Aݚ]Ek2ǖCyZj<e9kO?L{9b<4 Jr3ͼ{`#Zu4GZW姠><Q%g6\3 ~SLalb jD;d(EoN)U;IPF2k,9(͛E HhE#q?99*z-t,\P5テbq={sYAec%Wlȟ/(Yv.&&E9btlR_[,s;V{%s|KwB"ݷo5߃,?رܹH qqq%m:,K0 0 0 Âe'GG6dd\GL`E T8Z%DBei }9cIHc,~xr>+e고$R +'cìƸ7=,x|YAy&Qޱݐ[1S^Y\Pn.΁̰P $Qw]z2f Uk28*#)kQԮl8=T􋲪"n4'1wEJ<bˋbaNDl+2GP=l )-f.1Adt8/M3SEI!$?i2hq[eFx{*>GXĜY{Kٚ3C\E1cVd3R*f(~tuq(Ċ,U,SP9H%b,prckAyJ=Ѿ2fF Zmzb0볩g09䟯>'AIeeaaaaw)(-C0f]YsTԷwlq2!u&ls(28HK觇'8%5#vSK]ͶuSm1Q9fRoN?)Cݠ@p) Tbd#X˂Ω]+Ni!t|:^֟ ͇GBח@sa̞sK@s"E)u-As^O .Y=toAmACQ)nH!j.Iw'Qd%kر;Æ ʆGRmQQQ!!!ɓ'X҈$yP7߄ppO? (s&i̡CB^$Μ? ((0{ #0,, K J:Vl`@wss3f?lט1ӠIٰܖS{IBVV@f\!IûCb k,KaFRCp~-=z 66TkӦMz;]"0f65|t}"Xĝc۽ceΑ3JF#=sFugyA,?l1suҥKm?,K0 0 0 Â-(;99LlD]E tg}fj,_3:H͞YHeDY77W-e8o_Ȋ\9zDڿLPkMibmc_,(~m^~+1NԄq|Ӷ&8Eٛ;7`;Wf261Y!(v+:MfH῿1%(:Q=lSsoiW_啓2]XP~{.j&[cg.f^~'?R_r`v\uaVcl_A92_&(bw޿(S9{{Yܛp?˩\4^/dI># ʞ#ԽFjZV!5x(:>ZYAyYи16Sݼ~W yC>P5ei.K`ISEAyKK1o}0Ө`F{TI=_}V‚20 0 0 0 WYM ٶzWL&V]Pk!*s~LdN6KA~_eFk(fPyajS"9T<֚ kdי%?#cG=ܰWVt:Ayٿd(( żPu 4HD6l0=O>&z.քE_sBɄW>VlHqQduտO>Dƒ@b\ɳy^ti[,ArrqK^P@pvȊxl?-XVkʴ/Z1רkx Mf6)(ڰ79>T,Bȧlr{%\P&vfPw jD]kbW%)EiTwfȰ|:3 ۘY1''( +2k)(FyG1|P<#iA15Ad@7fq|FM2^j #ϻ2Jcl,(X%xyAbnÊ20 0 0 0 ;{{!HfYLo+q?G08'b6={ڠCm?~LM=W^qkkk,X 2Z|gcP c"ۻwDD$b%WlH'(zڋ!ChϚSu_},(d-[`JVϹGbdĂr6sY10fԶW e3fW?~<gR{aaaXP@P S& afF>tcA9Y{^ !GHR ٿ߇׶h++c 16,e}8յ3=fcy|IE$GcAY 64*?q_n9%ʬkkϨN({"Wh/(EE`=:FQ?ǛʋA]9ya +JD߯e*? ( {8Zekn/T{eWHd|F2fgʋT۔M' %l/(R`$ N+(YMI0 0 0 0 ð݊Q%aޒ;^-CP?'=΂Ω=cf[6fhq[ 2s<[ Cٻ[x(5{uΥ`/u})L9gIe.y%?L{I*e VsVQa".؎Q5X9:G7pnX;Zqcq,8~Ȑ h}M>;k%+fnc^ϙ-@{U/On(@o}}(eϞ="SZ8'[n0k,8t|(L,wQ!-\(fT~8}_X|gwGzF{n-@'aذaj}IJ 1ِ8ϝOP3g?!!A}l;}41k߹sD;o,jӜ޽eĂr6smnncǎY=?@PQu։s&u?[r0 0 0,(KʚaB0 4L5TFnn6AyRp `Mlք0S,gAY~;|>! W~{_{)fAڃϨɓO+/ܫ/(zT@̲13UUZbi=GcZ]Fc,ZP69\L!g$)(mfRhgd{Ayۡr!~V̞̂8KKM}-ED]ZYPfaaaa~;I]wz0+0Y:2/{"ph1hS캾m%(Y[93 QxQйb,241q.U3zeV%c`=jIRu>kJ]=06ϴ!BrD^vʝ'ϡ̮cQ|B_c4.%@{smYP6#\x-Z$$AهܹsjQF=Aȶss$Y1R۾}5rtjFaRܺ}Q#G@JJ '@b\ɳ!q;ӟ(~ʕ|5]ogc^>0 Y(2&&(MК e ւ-hޤ4^,__Q}#Rr7M VAY~tjUr"ɽr GP8# ܬ5!BEPPٱQ\Ŷ[QXyUOKr((<ܪc&X/(Ӹeb\kr\̂sgԱk3 $_}B_w~zˌ(|!c14)y=$(pQ'(3 0 0 0 0 ʭ-C(eR?#ksNJ5kl65^i$'=q_F]+NRF_kWGnɻ2(f#FA~#ecex}_c;_=؏J١_{3{hh:WOM .Eer t^… Ho:ֵ5 \4߈L#Lҡ .@s)sǎVI?6N/fFqzlb\%+6ĸ(b6Off&>؟G@b\!q;.]{5 jNy͛7\f ve@-// >?~ z^bRwĸ6%k;G~Ǐ3XB(KMMB%g}%aaaAY)$ϚoT?ˉݻ@sJ믙jBԄHXZdI3j|B#rLHoQPG%eY̪2ހ յ3iTzmfA܍!!9ʯ~qY^P>~/9FAYb&eseHU"26D[no,eDd~A =W + Um }IMi|lZ>c&es٤ų:dEVK9{g^P^Hxeg'.!I F}')~jDl->>^1yӾiF.21]A9QWZP̂20 0 0 0 Âr+SrH1.8H쐢k߁ͿZleY2 :ւAYU3?k_, {VmGb;I.TRQskK:V)(Sgqτ;RxwpnuS?n/έ[H>I&2fNƂ24Pvʡ.W:XpXPV+̤l.{xvFu)Զ {>1;v؆}}}-d(ĈqMѣUgCo2Y #Es=%K-kLLLwuu2N~+V`fDyMϠw]ZZ~ ׆UG8qB<9_PޗXq1s2_rE[AYۉe'>yl۶M~gaaaa;<$ r7WXCF+z=,[Aiњ 3J1{+ T O _nTA"&ՅIAiJ/de YIQYXoDd{O gΘFuo~->w*{h> ʸwTӿqaeب Lƾ6![v$? rҩ`eR\ 9ɉN +RQ5~ӓ> ke}ս=-2ArmHV]79>i_U|FK⌄׭π#ϑ oF},iT)Ι2ٯIZ!aF,[|,(3 0 0 0 0;H}N|/^ԟ>Bqkn2 s9u- [s=`Pfb_gǘE#\0ʝh,h̲li,XuAn 2!7@W_}F`?8}R,A?Cv}9ւ2Z({x}Ub~k-(s%΀@m_6$wwBV\嚚oРA&۬_ې ҵkWY)w%Fm(XzұgCSme9r$<{ qrβgC\__y[6eG1X#?npp3 jxwauPP>|$3fPƍeγĝ e}Α7//ˉxeowů^mXEr>  0 0 ðlkAsȒ_@p$LfP;2VpoLMU15Aᛆ*%lvW^sH+UAÒ07?o/keQN=YfeߝVͿ..ҹ@Y;/Fsgd{AgO# 1c`lD iّ0wE:xy{gJ> c!b| $Gr >5LBPV7`jD ؘ !i*h엲({N_$z-eǯ>_bO"ar8hl],|YPfaaaaIAYEW$QG5I11Z>i_s.C_6,_"(,;{ !wml~t69<Rl_ȼN Eogn,XuA!%, 4kIJuMe b]ngέMk9v8i>俦Lznp 3$2PoN@w_a۷S۷oK=<گu8'l}b_O|e,qtAY{_b6sEakΦwN|cc#A^Pre5k ϟY`aaeeڊ"H ^/_xyPHWA2!;99*{ խYkfC0Q^m5= FAXʧW‰BAKH1`QT͙,T{vìr5ԥ@^Q6,]96lY! "­8;? ke}V9鮐W-܅a&=U?Kk^E BWdIﳼL`bK-v&LNO)x^HBV 'b?< K0>X2fg`T?Z (Rp26'ƠLu$& Rʩjj?)h4Idh\UP>=>^q!(.=,E\#&UjuTDoph\YiO} ʥlj %1%dX?AIxFyXNgFJ ڳhx]PZ,<%JGМ3BaVQ^zF z[~zOLJ5Gݲ)cHq`qQmq66)YŢ5YZĦ}+%(?_yAyۅ8_Ab|mT[͂20 0 0 0 i#tei,&Ăeu ps]Azmק]cun7[1q? ~nw v6,PL(ڮ㺔4 y!DVb+소QڋzQbF{dAQ@,(:G7p-5;%Y]rwԷH{έf&UH "F\+Nk_2݃4ԟ4%(v֫ n]3v1EEn'N1M.Z"(ˁ'?S]E@?<XN2έBbbb-qQ%P$2n۶;I3e~:CF={9kT]`Xc -- iɊ++6jkkŸ()8i$:cƌtLͰo><==w޸O(%{F#ʫ«'9OO(\>jvU@TG80ht[l򂲒Gw3 >+AW%ܮ@< LwEVс2ܞ22j28K5O2'ZPVY*2ZVxRP&Y5㎔a[X5Q ʳx{يb1e3Xm!#bVrM YUVcRhgd{A)%i5 &|ԽfcWm)DP7!.?]:n)!bVilg_?oeZLJ}R,Zϟ>eaaaa( avHWi2!*_PX z-Q;K oNml/(Yخ]u_>XZwOEցcl5;g}ұeS TZv3ֈmf͍&( -ꆖVA@CAYsݪ:.-WAAv*Kr2ض#I!5۫љe6oެNʞܡ=qiMMMV?_XHyq5"ݻQj@b\y~7LkwQޢvcǽtY$//OfmNq׭]An*2YzmvȏK]Dfi'B6m~V ʚϫVmݻFgR{ aaaXPZPFzAW.UKkaYC%, ad$22%/WEYK!vB88 5H_ZʨK#0 ޳ ~qD _Oo,YS_aQE+R'?P_W \W/xf5}F@Ӧp+X۸f1,JfTG0/f98J8V{3fhg7%So> Zǡ_`dk7;Cl|j̪2Y>B2)&tB+˟_彆,ʆGs+QJ+Lf#asdRUz1YKϖî ȉSXV\U _W{TM]ꦦ.IYPBfRpH/lE`j5cI,sCK)$(@HH,-5b%,v8r\}abfNq^pI\%)ZWCګ '>8rpH&e''"%X :J&/1P;RK+MFYfhWΛӚ_jHHZ9GaOyak5[S 3ϷfgFܼp+p Wa*0ԁᏗXj-ܯ(7zk125C3-~}K|ngO5 aQbtI"t Sc x0hYGrﲈL|zR0g.xoP!( @ @ nMfQkF*5JÓށAN;d/`2kӁ66!-&-{>ˏ8fšbV6y=r5Gu"(kvKk̢mxu ~$Ag}$, INՏotPsx%_-on)MHX5!1e99ĤdJHg,^}۩AzT'A9ݕfaAy%G 2TP$x i&.ggUxBj *JrH|X:|x !Pyaa!^];('}'@yڗJY_0g PߘGہyO IT{҈89Dm&f1kf&e@,PF eeD;pfث*̌ P( @2( ?MN+g+y=OWimqr6ͬ{{{ikk+VFZZZJ;;;'gf@Y,PeJG#eug2e`j#"|{S PUWI(8){7ވ Dʄjh%hinh&i  =3×3VVt:m/?>~0@,P@Ņ]?lѕB( Pkl"7/c)j1s1s,hb'F*Rj5z4hZh4RDX$ @Ye@Y P@,P( P(( V_#wG7(!67gAkp(PJڻ={ГBr9rܟL&ޝEypwX`p"HuEEAؠxp\q#IR5ҔI5cjb5_gw caYw}ދuk"t:C^^^Oxe GRLԼ}t_ĝopA;;7!-k۸>6 %*_(NZOo5ߌHIM q,+#ruiYsBtOX5\ǒs\mެecrsa>1όZOV \Q/ʐ3}]coU_ָp' ū23؆ ޸g("""""""" sB5o;ٍ0\|F111<&dsv؁>;;;<~sنp=3(3P~_ y]o``,(-<>(DW[ {г'u~⃫Ez9@yGsl}DDDDDDDD@2k555rwybؗ2e(J^BDDDDDDD@>I>zY4wnAe./FNz2E=a?#f * BnfER1P^pUDDG#:6 YҊ=kMrj'@2%/ԣ b{얌}k|Gp"j.3EBW&PN\sncS2ŵqlGZ&|)W=V v2P&""""""" 1.qx=qצypm{dMN3PUwNeZ{=d{C9펈xeկ815gW =npM9E(eq&U{$8-I+cI|[%L(MqD6sY\Guu:^dsT*42l@َgTtww{?S(nP(,v!гfSnxNHO`qlи1P& qr_dM.uʉ+QKشye"+K^$q( 拈fzLՎU\q(BpX^Ǚk)?v|GPo-""b^(V鑖(B1t~e>)Aw/0P&"""""""1(W7ԭRng>~D#|am QB Źp&[svA6(@Y?XMZ@~Y*&ZgϞESS"##y+++c9[;PybL!"""""""blŻDLk IlٸZM67~{#M19a(2gla,sE0P?P`(1P~(ȉ⩷Zme9sgrv_!5LlWm(^`LDDDDDDDDc.PNpI~gMO;SNX0a!y$Oi90(7$LX* G  =A,WWW^_@/eʃ y(U, FpfmݴFDrrr2",>N3V4Czҟ_ (G, 02Pd^.HZ;Pֻj Z8oL5(^`LDDDDDDDDc.PVH ܲDt{ej8u%.11lwez{ j蜴+8}euG "F( (^(a|Q΋ )-o(;88"b93PfLDDDDDDDD b`92ȲyKJu+F,PBs{S_>vPl"v}gqGG^^xC~ֿ^ýĝo_wKrCBbD0`Uۭ뿺ay '1Btz3sElkiA-IIxv!\F AB XEB2,fesXah*`F KagbNB,mqsr~w~oŻXc?xRюl+PVoC̕im^h14Vx kZ'ek~b`YMoV;_];Xco3_#6B!BgͿ=72+f^٣hdg`46jYY<)$ғ z_hӾC0V③O,sE^:#lRlkZŧ^Mc'e95)LCENZo4ɢ"q-G_-u>;ǵe0VBi.IF\h3g#.I7|mܼZۚr4>`4syOLL@oo/TUU|%F _}.gaaaeBB޸a"AO j旗Sh (+N^4̍HWUJJǹ W{W![l99 AY ; ;z?"tض# ؖxyA95'Ԑ^8_{%Wr<(ꊫ7BQ]3FV3Vxa_j-e;|<NJENI6BWo\!((Ӹ˴#adae5s.pԙ+sF+NPvy(" [ZQ+V8בe]S]0thȭ!(E",(3 0 0 0 0+ZPN5턡oQ=6RPK1{nZq[:ۏ 剕o~ew#&>3 0 0 0 R&CbY6x,oo_bc!Swɵ'Nw>g.'en>mMN~iʿoPTrzΞJ#lFYhjDњ/|4߮WڱIq w0iJ/wQ[EՕYz-;218ŞB}afeg~Ut*1~lk:w}PP|׋Uq_Cr|!r]^Uw廟)e{%Wᄆf>D7=Xb3Hݡ_倪=T5ؾ# S-$b_UCΕ1Nb4nBcg*U^>VyOc[\]ƛACNn.ɂ|P@mnܯȀ4vW|sFp><˶+z?-Uk.Lц|Uq.g{ATV@l]w}eXEw_g!4v*[ZUsEN _4闰reX>c/Jaq9.FfA uZZ K<(˂%%D/Jb:x :!^=21M30>̳,ȇc8| @YHHHHHHHHHHHHHHHHPgٽ>u誧Hgb8eݗr.7 zHv/z?VhUבXOOY_pm[m#VQҵJ~\iz u_o1㴹昔r_Xkښ pm̅v<5z7dk̝XR#?c1ls{9,J[.98M>SY&9ް4?As'/{ɋ >?#'83NܺKϱ۾5r@S^{wc=7s%Esr.?ʾboR>24jdbz+/oZ:S.P(D>TUbH%@r$YP(w]CR5h7 ਋:=.$N3K2)bdpr04g"&ɰ=0R)w=Xo]dѬ=i (+BpQ5s0s֭p8Rz!LL{uƾ<:#qN& Z8#??F5,{oB\kwTpbī9*HGʝN2'zCߏ3ռVQZՍ~wQQ][y'IfDEEgDQ@pA85qH1FLyӯW{tSrí s/E[VsnUVV}ȶgi&$((~ Y_ga8ӏ   eVo c k%(˿ "+qE2`MΘeʜyאАV' w|gz?/xGGk&(ٸBJ]lҋ+p<yLo8&ޝs c7@;x[?\~U@lu dH+tc%+(Jkyo&=add͙?ʚhƸ}%YA66>4ԸSyX. ʃCC0wdD8ݓidj\CH `~oaˌIgdyAϗ]xD"ZG(E,{H\v2ȧU2ªb~O.[cZg \  k)y;-Ҫao.K3~|` }<+.(G a7[2aPN2AAAAѡ ,Ɛp)|Dab c$:Puޮ]FϭLo;Yw_e$]uefAX D b͛j ",z)kˡtY\ĬtY] ?cVC@_%AʜjyӘ9Y重^eF߹7dPuAjYpHZ&dkMeRÿՆzr_mv#c=buX?jf2drpIl{~@:am/"dU[PX7xj]xedd`YAN~a ޽{~ͪ퍱ذao^|뫫8͊׺ux'O/ԃTgIAYTPƵףU cpG~ UVVB;Wirrrp|ՋӦMyAX|Fp_2OkO1fAXl]L˧zGy]dlYk .f}*Rr 晃yNe[Ȏxm,e\9˞e}eeGȘ !2d&Vܳm٨?l9u=H퇕1̮a AqϊYj|]̬LJ &{UzYmIm ٱcQii)+̠ܼyDKAdN(I8eΝ e 1BV]A؂6XyAZó0!dCUKPǻ'3gXن-(( J{ٳg!&(655kjj 6733S<έϋrׯy}nnjZx1f,ȯ"},~)B6vߏ{4#k?[J e wrϯrGAAAAvrwd6K rɶEEV6#WmY jʭb~x[M/cOG{h\Ӧ*,'E%,0˻ϙΚJ[R̅+8 -?ǂً7b&nnŲvqy8~ YmmQA5uȈH( ,vm&Pz/O.þ!sse:YXP X~ LAAAAtxA95%Y.P& goy,G PNgj_xϓI[nu{79PDIg@y{7>u^Ad͚gu[oig`:['Ǎqh Il甽2lj2̽%7x:"}ee ̥8֔]mM8̲ 2}B14Ggbfs. c!(Y F27 C|ͺ]4i|Ha~Zp(c5yʺǀ?R% 4Օ͛7ֲ/4 #%ʗK,iA2?*1/;q񓒒0v̻o>^VWWצyCCCUz=f\rd<(qYHP?#;]rB9==Cd˗[dxMHHhh<|/X@4̋LXm?ElѢEo߾zzzj'( >qAAA Vsxa jȖ>:Ujy $MceOQe'Hx&dteg5lX[M~vd1ip97ykLں8/%`voڈKP~ ^y:9n,pbaP*K g_@"&k7rt.b;,sΙҞ2[;#tH DC63<"L,/(יִ§A`$(+ p_}l#$(^uǮeXj޻=bɼGzByc۷pJ+fmύSLEy +3f"6f]Qw.E%@@cRAxĬ{Gc= ]_dLG\ !%R<;g1A.܊9$Yw_tNKeZ5;3jfc 2dB^T>}\ ; E&%.]e@]Uݽ{}AkPY|9(Gk֬,|Fw=ǬZk.+\/7G4~: قq6kކ}~-s-((o}}=/߶m[{ w}ILO<Ǝk;ނv4e_L?,'   pBql.Nlme)/~PDGd!%Y\SRGwUcߋ|(~r`DҼ9 3 ʍ/bly}_o~Īּ 2WZϬy~mkV}'_ɧQN 8$Wu<]T.Cƞ .w$Ql ȶH_ ʓ^߀ږHпruq?5QvUփAg EYA)LnQAߗXY uihtN$(㽂Xm3 ʘ-ѣ2:2f^^^֖pΝ;JPp/@kU$(~󥪠cCK(qBee3(>y^ i >ڵk[nAg?(qY,sx ^ؼkjj̐_kl9AY|!>qDfaIFr,ue˖a6`Mea8ӏ   ߆++2|,<ȡ|5̽8 p$H,w qqr`TBP>sJ٤q c%u?/_\YfAyva-4 E%jٸ8~_.9FI7W E/ލufJ(V_PICtܠ(,٪t{fLBX{ożˊ"$W^/ae#OgA9dRV&g=Pͬa؆ȵpd Q; kwHdτ]Xp1C| GEKƘSbV9~3&o9$r"W8#A    ] 9\}+w2UysPcޙ},os3i BZX&՞q^5 Ȫ(:>tgM ,`=ȥLڝKNY3盼iI_I5̘STP tCPx ec._q\_g:ٺ{J)rm`r2dN`iApf({`,<`tX؅b_^2+Ȥ=ºǀ$(09xgϞ{~54hԫJPPuܸqrm@ZPFDEQx߾}>ļMCCCSNIr]46ge9RTڃL%4Mb8x  d={R'|yEDR8[XP5]6C~دEﳔѣGvvvN|!s2߸qÌ3Ҏ^zr,ݻw={6fLܹ6ݰ+g9AAAAl>pxy\֥xݻva,cPĊ09]{[3|.űv{ +q1೯sx^W`/+.'E!(#Ɇ~\jzIiߙqwdg1` x)a8`eM6\ 0{/+.Dv~S^Ȃ:# ʹIsYqN.1ޫh7A9vpdeS4}̪|e8֡Q1!q*OW8Jr9y$(AAAAALsͅ25ۺ풭_繁_uh>L9a>{fwt-fUqL]gd˯0KҦW(s%exC;R 5fVZBR+$(sa&Ff7ţKC0Q=ٷ_^лm0z%2ަ (> #C?ycǎofN5 C½we)pqq1AZrs^]e_ae/_dW"ikx T:uA ڱ׮]cV#""0C7Hf6m ElyAY Y AG")off&399҂2^z™Hpi"( K="qAAA ˫7K{|@6fM떡/[n)Ug'K(]N: ʃāDpMںXpHp IG~˖wXA900Wΐ}{bR`{PlFލ/*ؽ&=E6 (͠`0sei[NNkQ,r@ ac=ZbSySڂ,( r2˞(ΘЦ&%B 1{3'2_Ř ʳAȵ .1 eCG)8OΕxD6dD$γ.8%ƨ)(0|/+qla\\cwŦ  {Xߓ˦Hqy/ű\Z 8Ho*YڤxVW<09sEi     :+}pd3(?Irtvuwv1[Vm0p SUúGCiQ]xKچM1ר钾:n(:bVz3,( :;e~|?Tܯ}b6a_.EgQrc:.Lgʬ{0@$o/A鼹O/$E2ޭ(+ ɕPe0c׮gvwwg}dاOܨUI&eP xFR%Ⱐ'{!566J$B-lސXIeiZT-엏cIٜ h(g hϞ=f%U`ʿwĻ )/>/]=SL󹹹q񾪪 ֠A|/_.)t… "W]Pz~EL?0'   cT.g0{۵kgk:>>^,~P+@ٹ 'xv ʷ>A;&q)v+5HrRF,6.l. [dQ~Kv^6t5ԍe9S۫ 5ZXPF@L>' lT.Pla߆u,ovOSǥ@ZT5|ً=D p|eWe{vJ,2.MEyRC{y)YYAܸmpkXQl6~ kWxAlDzqآ#@:.B}\:׿ZTolHK1\2Ǥ<;3$%4".ʴÇ5͢ػ\RDz06.E;İ,6}Pn>$^c,!Wc^\-3mnuMwVu9 Q>6h7K)ΩңJV^Ȗt^Ɔ;dv0/O{9{]aEtͪ8n# SaU)ʳvqXsp}/zAbں{ cc@Y]ٙfv'o碯 st.]!s4ZQ^uz\p7c[P_'+(ֽ"KW}eE+^ZfQ`]v[1M@_+<]MAa<g!6p':nRP۷/\!{fLL b/nѾmhh`ӧX 24CQɓ'\߿?Ƨ {o.uu3P4ӧOl!!!,AkQ~د3wA.`˖-NQ|aϟC p RbMKK-(72ˏ7 e,??UTT|> K="qAAA f"1J @6}F%hLI3.FsAG$"ȫW??A;E`L?̖\|foM(8<"\0 :Z&6a}tt^5sM "=(K/虏1+rXxh{>|jv 3W^JWUP6 Lhb1kIPPʲYRjyVܪ_4Ţ̽T!yq슇LS*MU7=2!F}|諸f}}eS⫼92KDᶈއf1-i8pYPnAqL6̵Pvbۡ^meƮ- C&(#[nZ|$&&b6OĶg6׮]uAtt4fhVʘ-<|MMM,22R,C2Tr^+W:JY爐ޣGx}}}=dU-6֭g[Rm6|"" 2>G% U`R@&v /faFk0&(hw^N>b{D>E_8   eXSY8V-?eggXDvA 3wv/MakE-[ 4dnáml1,WD#L^‚ TclfkZ.]D=Ź-\|9p=/|ַbo o,m&x pr))(0us _A|#^]Z <0ZM| z_oD1ykس笈$}KTJ.\k8S3Te5?ȑʍ, R͈ܝ^0`;QwToeU%&f#JT)F=bN\X"dt6hGGWbz%K ()wϠ{d7 lx^Gj"^%`҈وgZѮ |{ӺxeBB.4JP-'v|LA&tۑ?ogGdwS=PP <3u_O;kdK/4j;VýW٭IdZk sE5쮝ttN3&fc-͌?̪R݂KH}F<#VmK]Z7 xF[~"wadZS55줼~׍弼^zܪN,\~Rŵ\0˦E!9,HuWfMd. Y3<҉vT (oyɀ,Ydɒ%K_kRank.Š!zDE0h5lQ]UR! uD#20я% *k*MW^=dh}&:% Oݣl6_ҋ6q&m s6!rO? ,^z{y &7^eau i+i \Aʵ= _%픢5囤"}&Kd$7rgbVfk]o% c*(3>fZ9HN|3q[:B)$__k29>E}aUdX,)82 uy!Q5{^HE'3m+   )d2Z-Z)H˸p2 jy\?ۍ)($donH4X,G =ٛ i:Oh^gq Jh`xX.~_yNBeYz7AƸc|>x<Vd{\70<ӉDJB澨jf(WiV4D"=l6qޫzc^ǻ]Y'^gAAAA}*?=0_PN I=)(K=TAAݳ6qIk_JEDT TQ1 c YKS ]!8d ~[rPHM*5\s9ϒ!P'=UBc^i$<۳Op///@,P( ʀ@2p܆o(ּ&@T*Ib Br4yd@@,P(e@?ی1xi1Rs:?]hݞArMîzhsN{ 2e]*&r7c?řs;;;I xH)OMM8rt:jZrtS.crr^(8{(wtj~ij?)>( P '1%&^Wb}1@zN@KTjz=bcc#2m޽4n6?]v " Z(xO!bcggca "(X*XY~X 5O3730˼d2\.bJT*QV\.GPl6o?(( e@ @Y,PejVwse#~ Ss㱱c{۟t{y;3Ϫ>» P<( Ԧ㸹?GkV_<+/8g_G;^uLgcL{U}qfsF$R7I%E6i%X YDBRK(NP:%)f"VЙ>ݕfL^L~᜜<*={x<ӭt/;?E*- }[w^ V7 @rvN)sq>@LLkGQO_Y|@@@SUmu2=һ[7~¬⨳@~-&:߫F|ʉVd1([0S#vg){7l4+-{w]q] P P@<i9jVGjmPnkU[*-TFzZYۇ*j^5=ҹQ~V, ʛ!PFBwY~VNLL \fzo[ą ;]W/o4Oc637As?iչq)(7,ŵl0јs]Bs[b]5uz- pLOk:e2A+:5㌪k?uz7%l;1#jq*Þ9ǜX[Vץu1Ae9E!|a@9n-2R:<ў-Ώ^Ŗ;,[Y;nsȒRR!(O@fv&y#v9'Z}wc]=Y PF5}(((4W/2a y&"ǥ0{ =OmsU0w%~t{4O ~jFmѤ95gnKk"p}#{@ NiDm 4-i Bjf6PnZcl-CqhMʛcN] ojKDr.WgkKͣZp sӤ"xsU?7OXp>m&mm]kvvr(@ؗ@@q GOl~f>&UM'MtrJ q73?7k_6_ PNxlX{Ӭs0r`n-i, K_8@yFEY@a]L&@iWJr'6vl`փ5 u2U ALKL 8a|*.xq5tw679{@ > q#ŸOxDP2Gύ+P{=O^6&Dh|9㟗V^{,-î/Z&R2"[r:2reϕwRR~E,Ylk@<fƞɹyl 7Ya',P=>Kk`u? {׼ {E{{Ηc[HGa}?o:wzRGh0siԬuXg9 6]KSaor^yBEszEryKMelC@`PHŖmE2eRȥcVtQ}Ŷpo9˷elx6Wz}Q<765U!m(ňt'6U2 bS ;[7R ՒnjqmSqsW pQ(}|C^X'-(W&MIM, srioץY47tbcM'U-+?(Cg&Zz%gMgp?&]L_D@ ?ҽŎQ 2]'Ηcnbq]4eL_Ls=|M`{W]xPBu^./ e͕exҀ:}I:xFE >K39Hg|9u*,<{a>f^*Gi)"X1ѼHܼE[£Uch%di1K(f"pkUs=B|:Ihk8&O& '% po.h]^'jhבֿʙ_ E[p,Z OLsF8i)0(1>@ vy<ﶫ}8X5AYiw_WuRGTӃEP͹E,RU|քMx@Ox7gv%ң4:A3۽MKci,;3䬁|&8EYo͈Κj^067!sm{i}m9>NI2ߣT"W{XP\,ʜD%j SmIW7澼K+#ۼ>%=rJ~pm ֶB+~s0 Ɋ?>r Yv% 1CRR0?;d ,A-VT+$U>h0eXx|COGq]mS.9]\?~/ҍw /Tk~-+` IKZܯXPɵ?ʱ_qG0l?\w'ܯkE_9WͻܓWaQBP[sEG{d!dȺ=Ym'j2<@,Tgl\h_d]P}jkcae ,n|*9ŝ\Z`Y$nQh PktvۻsBP6 n9MMABBMjB"cD_-(qW2zX+O"G ȵ6n%49DRv< m*(hw^"b0ړK:3.O 0 0 0 0 0 ðܟ,!\\bύeM9V2:vVa\"^MLhIKEVSsGtt]Z[PAӦgP,*./D֒B k7P.{}kXzWwo1nA\$IR;A4I%Gw"܅Z_yJ>[tk?;+YZPAAZ/@G[KsUQQY>MNMB]L$)Aj;Fc\EE7]om,uܕe[ĤNxtQϺ]#"IN/'MN4A!jwEWb)W wF:ƙ[ѝ,y=uYWP>VO%ujTDWfM;#m{=ń[/My [tk;Ԗb\ꊌk}^PTtJ@}fYݵo+ew5򘂺3B+jSu7ːec5$hsFvCgK(cz˂20 0 0 0 @у"h6y@isAy}ȭ"'"nR q}W9-^_կ95b\h2z7%`λc>8{MOZmN0!;j:M(sٸ8: `,~Cr+:@?CZmA)`d8Ҳ0w=r.#g: " 1xmD 0.^5 ܽZp뒳Am?<qN3\]떏<`^9DdwkBR70 0 0 0 0 0,(GAyܘ~:$SssƏM7#}A9,3Y]gl&넹9<'2sb#qcp *iɽCY:-ŝj W{| Ay/vA릠|Hc\|}͐ϷΪOsO0 0 0 0 0 0,(A{ zrq[C9 p̟%MU4h'ƌR{C9bk5јQ>EPR/KkRmcZy͍r:~.(zx c}xHuiE7\HÝcQrdH?$SbNIFnf~-cQ83;]KϟZW~ 8˺mwI⻞kp% sT}Z[g稊h3˽R[ W͆ qdno b]H !$52\={A5BV{2 ,k.{rT&A-"vZ3 ,(3 0 0 0 14Z,wNF+[.:+_uq-Zm+'_"U>+[o]Ɓ#1pw I&tV)( :>'oH ;Io*HyVZ8'5K'0:>"ew~Ek.5gsq#լma8kI?baaaaa{ rr{7dhf?3f".w^n-ApVA``5CLةkVNRP޽Qt]go?D-{+KqInF7WX*?RLܤNǐ/m_ ʾȗBMHs&S&}绿YP8gXW~ ̚Dw<UuECo?}3/ ?ۯ~AY GO K@8,q Rd a燮Ӣ論Z A W\=i,imϭJ(\VCA)!-2֭P%(']qm]D=WWc)!q?(js WރIriT`ϩ:둠|NP,(3 0 0 0 K @ qzKPG}< kӃ_h@5#&uM>#HFww G]l8|Fr2c^{}H=Zy&݈uqZe弝ȃ`;cPݩs aֻ8WNaQwVrHLޗ2X!)ӲHoֽT n'(YmeAaaaaaaegg'j/LRݻe59BjQ e$&'Ц7O3ll\YʵKD Q}((s,β"MA̕c:)ktslErgLj6;Š/޵=#K/hsѵ,(*Y:ձ_Su^TZ\mO9(>1r3_\ ]~S%($q0sR4s-iRZ$+ѓō{@zkUEF}yemfZӕqtR&-,̟2r%y:M=eAy%fkbJogIg{OPN͘h6GMkVJ&kc+; m,)G_YPfaaaNgUAyno \cV[7)PK{wG,˪Bdu &wُg)K0`8dyoy1u;kU gf2z.{$ Xx Ce1{P lzZP.'vdpT:!\{KP:)kt'aaaaaaRP.̝xe{ խIXP Ӳ-sE'Fz}Ypͦf"5KJPV!({-ֽul9;@) o#g-A% -Ijb]-:b) bq-{jf]o8gXW~ k@'sӳN}]%IY8[/(+ⷮO7g!$؆[FEKeij_i*I]yV+oiBz,(Zι3{"({&t?o7@t7 k!}WE={A~|<'o9޿krlB6ε8+3eW,s&rtL<+6_ ʐЏ]k~BK`Aaaaa)qvǰݽ)(ͻO<@VxDq.uAUv!6^'t&: 9-ga{ӄbdl.7]5tYb-Z]2nOߔvC&ǜX'chux>e^qjhGΙ$# 2+ =GXq|Q΁ngt-Ƞ=zt_x1 0 0 0 0 0 [P=G+Zh093ZWj3A ˄x/ڥ_dܽ__=rBRH!lj{oW[IEg]om}/ bGwz HuIc:(7w4XUP5gPzCϾgk~8g``\!ÿI_pKhkjR]/(+]葠[gsAy݌,o\8(+MS&TPHzkUY!}{=E[wtm!!%Kr2boє}v[`LYb+MgOW-8I6&ŅQRj͘@mfy3cٔ4kVܒO91! ~+(t2iҺ0kqVQ~yWe{`"곉9!St ܥ1|.A\p F 2Kj3kV[PV_~GEXזR~S:lA9xzOeˣ2bͷ1d&܇yُ%E{QPّsCx. >P!-'O7˫jaaaaa+(@", gU:z.m ?rss%G1ܛΖc !&f򭇗daOP j\y 2o$edM(6.X%WST-+:~+E}FbTX26o_e ˖bO;H4svd@4[hD~ۋ"WI!/Y4;&5B9;5.Uےt?W^Ӧ{󑕓A􉖠,80W~֝駿?c&ٗuuW-UG]~6SҒwQ"eqY߯~Alٱ^\GSCs5M5M~-BP[g=AYd-8!fNEiiD !˖XԀpH%\*(X]LWQtoң"):8ub!Ju>5Io*7*CD5ˈ}z8ghы%_':;Meי[ NJ!nuʎ y)cIJ+bc&(j|Sr$1C( 8 "(dڂ208{{qSw,vv+Cy;!# 2x ;GCjx@`$jq8iX8O$(Qrqz]P6a줽((ه8ǎ\nؑ{e3)g$;D><8d|0ۂsɒ ''jaaaaa+(hD˥<0<~,Qdt2 6O]:Yڃ . iS̩"x@||dULJ=~A:WZ9w.L|]kPpF>B<)uV _hRYR*ȥ'UU@g_H׿_دwA[P ;\Eg}Gv1ܕZ٘a>$,Dv"ɑtJu[Vδ8×RpHet-ƽR߯AYW{i2 xVJ EA,O2Yr˅Xd;0; !f56tՉ촃{xaۻ;Ӄ`}Ǟh?\60 0 0 0 3嵋AբoB}$s1aR 1͞l7mK-@}zAsUU/Owlr*.R;{T_ %rg7(9cu4]gl<D)Ы\ݽetYKͽ28LGM+DmB H@֘7R+R5R!(/A8HǓ,] 2ԣQr2dta`^0 0 0 0 0 ð<k*(tx5XAVnm鹪 %IA)!=K=ut> =RDotF#{<##@ٝO$HG 3&AYE,jz m*4[aɵ9xfo H"9n!*"筕 )"r]d$ q.H`řu5C4wH$SL=469("l X׿_دB2vkW|$0-puW[Hż{ k**ާKP #M6H)+HBּWB,+!Y&WK=8]J' wW^8VRF4#y"UHъW$>/qJ&DN7Rr-4eh/:-_@տ;84gnn*LV.BEB-3 "# L9؂2 .v7Xm]X^<_z (גAٳ(ȍcn 1ee{SK/ ;P%=pOɕObaMe2@Y,P(Ctt'[8xroG PnMN @Y,P( e3(c$-L PXGq[Q^#ٺ*VGӳJ'7 @,P(y byޮ6&ttƟ?Sc;#ӿfAȶww>#P((<˚@Y\mC@Y,Pl\V_wWZ⳵mؘ vl)w l7CE _ï>+ޝoۏFÚmk@Ye@,P( e@Y P@,P( P(CƎGca{4hXmm6VO튵ݶp/k,PN%Siz:lH{YkMCF}=J"FGG=njmm^@,P(S]'f_c/cbT+~ƫ<9}{}L{GrľX?@sjz,&/*kM7طןeQh1ٸm@@mnP"P&NA`J0ΨĠX$# KbE̘'tY5+ߋOsN\xsN6al4Ά'|zI\<~XRu7ň .}kw)H }Y1jeMb6~'51[X h:*ACarr. Fs"sX^^^e||\6s~8N,..bnnv}}}0'2Zyi[,%q>YY """""" (Vnj,gpEqkm2d3u Xa}{ojZ U)ӆ2$'3PlƢl} ? 7<o}~od>,SmDDDDDDDD (efpp:N eYYYYp;ill,WT">>^1PfIij@DDDDDD (GFBYmpՌ As{Vyu`LfYp72P&"""""""@Y)(aTf03>MSF/b @9H\ P+C(z; ZsiN!jGP 7̎812CY 9" zZ@uh|=z}:}(s* Zf)P#}}PUU$$$$ //բyW^ }j5׈2L"""""""b,@YPto?5,nZ eVk`T6rm]V$bX(@YR\'E=}uv݉@-6W\btNA{]ch}y@qi>Nه$DDDDDDDD P~?z5ǁZٻ#CW @YPRԧ^yx z߫P"bxFhhUYb\ߡh=׭)zJB@Yݎ yi28lϳt{pN~fYiyBZaf4,ysHPăxQV.]K@' ak}x<=~}ߛoccC%@ٶmgRL&|@(`/G.G' Nߜt}NM}XuWevHfaY%(@NܛS9s5@ eWQyD"Fܼ2<2(s3dZN/۟~|/(4A<$"%>/'[/*ePz]OWPrkkUo\+=FDnORv 3o=И*R_{?m(7\>.k5{ Ֆ(ՋxFW\^ٌ܎Ȗog9@ @YؿNmZ#e8;)RČ*|̪0f@$/%'7+ 3+R`8 Ж&0ϽABيx<=i%M_&L8rq?κL<Eas8\X vƭJN@TU۶1n}Rh4q3F0n6'O:<9j%!T>vƕvQ_ʓu! r Ank 1l?HWM}z?bG Hz)F0^Qa0p]+Ǟkf~⪢8,vBPǦ& M6T[h!2(MP0"@D!,bp촔[pKY;$s0ə@z9g/k=VGPNvV~`K9GFo͚_J0Sol8RP5J1 (γxݯћqWy aaaav5{B6oٸcl<80)O80DjNijCq> zo[w;0n?5KA9;0kC_C=7E?FEpNhxi`%&+it %IdDeilA>҉_Tо[)Ayf >q** ^Nxiݱ ]6_۟{#uԼfYo wF---s;00"f3B7+U^ %%֦h7n8|wCZWrk 66VqjYA<0 0 0 ÂN#_g[KM rngǓ2 "fy8awU KXue~&UG[P?hsra\INeV/۰0'zWz}LvkK w<2l?7HLw%(BɜJkC`v+vq=*]86+S#@H N) R\])ƥO\}bR ο4/hLYr鼞cTw\+Ij ndHX&Y2۲9)5~AbR.Ts\-Nrn+'w|&.}"-/(GYPfAaaaaeH2&ieĒL1N}gҜ ƫg%AY뭵:o4,+VRK}G]PEAQ@wqKGq[M\o itw=3=N[O/w%9! llu]?9r8U}isd gWiz;<1̰>`VAYXWL>\U%?Q[e=68g1f3ZpGe+r,\C8N$pejYYlfT8w dwq8~V퐷kZ*s0wo.?ߏ;//Ձ'B]R5TB;9q1ߚ>+pi\4K̒u* (H󦱬0)%w_76`?(/Wc̹X kݚ3ǰksfA\a,2@k@a$Ƴ^z~Z*[(ohFՋUcn@y2x9W(c5@r +ɕ볡=J/$D"H$D"H$j{l¶8`;8!:#wvpbAc<~ox xbtu8g!֡Tm:F(fZcjGlƱӃy ;[yrP&4,&3GDIf9lUTX˯ cMmvDUu5.8K4Po~?CŐ fsܝX*s.0u?7PEa|0l3U_>Mp6b|D|#td*3ϵg`L y&em1țYz&:PluujZY 3aw)~@Zz"ߟxvu\(]༈@"8&''3 _Fj.ajxx8PRܳg~"kkkq߿'PWHw}}= 炻q>}@uU*ރTlΐ>l(/[ NMMMPmw|Y .pƻN䂫,sG]/B2YJKK3 ǟ,33$2kZ*Q}W1xJXφ< -;o/XT^7;M] di˝kfy cm˕ ,q!ZndR|Wc (G:20߉A{` 8/Okp9*?y2_?]kxќL{d^@Ý]xDߨbE,.!Gr"̖C_TP>ra.[=-XKg[䳺=P8*u_+fz 5(C{iϢs!XnZ[AnArMrC[PvlYW<frG1Fꮾ(; `2GB5)而278 #3K%]y(k$Ǽ_g˘Z,ehe+"@eD"H$D"H$|wC <w+Sk;/л' 2l^ajK񀲼  (F7Qt2a544H`BQ\KprT Awu~ZC:( r6mVƙu%H{&]g3f=#hѢEjkk>Ykk+/ϗ-^eXiGg8Қ P DcGvy%n ardd$IX2p6;||f$D"H$D"@u89U%24.F yuuʕ[0?CuXn.+5aܵzlEsfR'K.2)*'x-m7t^(dWαlkS (*Ij9݇}=m,Q{|xN񜯚/`,^[S @o/^ՖR%X>'ޖ/o՝@q*m퍲|~]^(EaS` u/)m#vHט !_[ rblKVNV,+ W_ /HΝN:n߾͞?`w9~1Ò%KWTTssvvV{awy:~@@@ʱؾq!^ɓ]\=SW {p?#?w _bEÇ~7([ (畀~u6j׮]+)+//cfE xD"H$D"ܛeglYu)Tv xd2@g@!Ɲ/ٜbpt5bʷZgɶ?i :C4,)  ୷7kG?@PUMr|;Fǻc&ŀṙg :݇}O?*Ymװ~=UQ@PHW3=#E}P0;:v7W* yMI@=U-"#2P(`(p!Q (˃lǯgمqO~Ue7P&@D"H$D"H$Ni̾RH)wYoX)Pwn#FKCW*mȷ1T v8@s೬bpNݴpr=(\|L2ޭwFXύ+I^XNr2jf1|LW挐}UI [zăitHr"/6K:V>.dvJ!.P'P}M<ߣ ( x^ WVZs%{rq߸qe{3y}D"H$D"H(&@ɱɵ݀vne5[e_[`2Ďʍ-O/|%$C9P:O W?_iLuwwgwބAӨ~NtnKLt6nݵ?nyy܏"?yx٣ك(@9it0¥:Me15Q3]'Gseԟ71Uk;y4 U[EzP8?[ıULP~ ߮1"e\alDPպbT@Aը\~Z1rfw܃б< eyzy(?2:ʢיeD"H$D"H$. 8spC(yb@YGs(5ZfV*+UkƋ]r \X(7bL,tH2fR38,ɦC?6 +kWZZsؾIj0( kT"rv~~zw}V'ݡ!xUv?5A2'/dP;q ()< \m^[n. j PF7'N$NRصkנp@Pbjjj(/_]^p۷K Wh4:u@HJр2=͘` 2m|Eïl2|7nzNw(䌌 1. <lamp  ( #w $D"H$D"@7VSEE:8Y!h&'N{Ұ,?!zZ%8@d?`;OotP-!Yg3ʂFaߊѝSljF #an]k7|{Yu̸V7EyNɡ!Fۦb lLѺ;N$ŕ< +08Uy@/m&(̈́88)w&{1ehufǶ 26_iP~(}'^}v˞'QZ(Z^ l\ޞ1ݣE`MI8l+PƄL"H$D"H$D2 }=Z/ :-]mKt\1> ~:A@9o>sT;BLE{`F :CdJj}0~zXyR;Cv31[-R# #c_0x@4^g`i\ (ti}{JIsc vZos$\?ŀw5sxS07Ju8nRl" `k˙/yx ]y;k,1@9<*PM κ˜3pUx9u<ʡavVn(_eSp0 I$D"H$D"PF]J<~}hVY-\ʑe_͇z,q-۔Mz@&S;I~}YOX6xR֌q䷥j춱eE*9m\,=&vo=߃v``BJe+t̵|+Μ2J^Pc _݇okSx cpڲ@pRSSr}}=+`Zx1@900=[D!Ce,-- ]֭)_fkk+7pO:įYW yՕH9 /W ,3"L; 1,# (y%_gC^xY]]63,G?3.ЗI$D"H$D9nsy5[mT*v\ƄnZ⋿~k(/zVIA*YiPa΀ksh =xb7D+%V2 NPV~6^?Ⱦ-^Y\q=R>f:}c8 e+d!fAyX2@ ̕jMJ"r-W l{`FzrwwX+Y@d~/{geN9¾ӻ48 flӽ5GfљJZMF!,uz[:yzylg2Ig !,"&%EU۲XBJH@?6n~MI~Wo]!,jˁ|Ure?/>b[ǯ7 xC>_ sH+PVehwRB2D"H$D"H$үPPnCb;!/[6c#`yw~:(ONU(nvYcYwn7oڐxgc Wvud4L/ +=.K\zAιOMtT}]I\+r2@g!c1$P1(k4ʾS0/Ŭ2vSYeE꿣~ff`>3b*jW @IX ^(geeK]ݻwy͛1Փ'Ox|&sP @_tBCC'\yP {2Xk1BUUUqrrr{Nr6I_6iGepu1a18p~Sss3]j9e+ 8eF߿ۨƽIHH`@_'H$D"H$sp(XZ#ۆBQ@VPe!.K g8['1YxDG@DoxERՕMf36@&,`A:Âl7jъk.@e)SYFTVY3WSO] @aW cܡM~<8;?)m2p؏ac (8a_; !\ƲL{$fޯq*aS&z#,8C,YWaU,|$}^êt{B}}x^ZX(Ӗ+UCE!C͉GӰn,p%k a<%(Db|x1 d\X?$? napȸ_jƮMm5Ic;:@/«&'>挰1lqE#,z?{dq=oөmsα,js9'ٶІ "] Eb j7 *օ%7y\$x{sy~Wޛ\eS$5Gf/7v;'Lz}>.±+Kt (^>ok렋_@cWw!l9 ®=A_mxC-6'@1â+{7'2 .(uѢf{k]5״;0kef`p2Mu0Pp4 &O\k'? EJ6VéL ,z<<4'_"8Nɦk]Ϛ[F=Mm,}bFZqz8 =c2hVyvgՅںqoƮr J\L9ض>کLâ6s\'Z@L5S^:w@ yjwtQ}K9`QtrP&h?kAqyq6L຀? (nS,j D"\|>grn*\pX2Wm4L&affaipxZ (r$GEtP| NC6b/4B fjwX]](ŖJ%f xB,^A萉nTύr4ጻ72:dA|6hEwey|R`^$m:8-HQ$1QJA#R.i ADD ԸV(lCh |_#~ݻ֜ѨHReuڭzqA6bq#9x<.<P(j*:N/ p˺J0o.LZ'Áwy+M&ۿ "AykZ&A\e=j~ xaaan ON@,6r*6 蘒xfIV=RՁ1=.&?7$GUekU+0VR/ }L@u6FF=|rzג{D2ge}E/(8 4+& ʐe 9caALSY]|/&jeN XѭX1^CV~?׹> 6i%p`/(CQB=2&.!XDZ,)(gUqFL K (+ ctK#(r4oytfAaaaa?7AV˳Yȿ{՛Z~y-sqkAwx)e!v(ŕ2Q#e^#۷YYN^lA!FιgMkӵن<Da K!Ad]],֥9o浉 Ds?Ƚ.miƒbb0TE"RQ/*zPԸ~Ħ6ԨW?:t.BPΛww'!/jQ_5@|P6Ζ mם$PоФS}'F5 (CauYLplz, |L\ .%Pj5mqX6u,4Ms H^WXOAһ74.kp8,a_$95ް PS,w}}'T@|@s+߼r#WJ r(4Ɉkg`P'' (G:w1'b`Ue8L.=6pLQ}}MZ\Ա_Pps ֏5Z T|vH." @Yh(gcf2Gƍk7`wR뭗3(psYF4T tո\QJN}8<@Rm|dw1_^w_+3 U-,P3z9z^ޛW.,c%W7$ʨv?k=ɜj?ܦ?}#/scw^oގEiilP2{'k|QϥpE,YAvuWo:z^{qf.J'X<:a/D.PwD?Ћ9ʘh_7=fu4_LGC12Xj;ϩOێAO30GRTPfvFLD䛛凁hAyr߯Xpn?hB֍RPH$D"H$D"Yr:{/S01 F]aJ9{d:֜) /% J 1kJq)*& ~9g|8u kI<>DlhggK4iȖ + a3 /g5ƥFS26NZlfJo K"('o:rk(5E#ĉ9 ,̦fFNi=XPf%.EO%HhGZ[&E(Kڣ$&SrrrW^!jߧ{^Y6r}._uVu ȧ}$StSPfogTPkɫL\arիBZ %_+I ݳqVH(ECG()"ѩS]BW- /qpQU8O?! }Rmyh \ܟh:_=v^^:PJX[wmrBA?nS׿.mTvyϱXTU3NURԣ[AdTfwYV=EhG!˙Ncukp88P(`-Yi4j5}Qu~m6C!\^?6m^n4f>kFzm}o3emtp&|!mzڠs5.pZ-yn}g/y|]ʾt:muk]D"qpz߭f2ric|>' _9x݇>eіt?K|_ e>B}8fr;e@yn~8Yf3ԎGpX,ωx< P&P&Pe(( Po\8YD$F}9f @ ee@ }nn[x4W7;c]qƬeK&&h4j2<~Ft]222𓽻e(8Xz Ă +!]*1f.(":7V w`}J+Q9or~9 PFreG+adɱQ3:YY9wG{e2h1Dعa2MP(T "d2i> P(r|uM}uŧo,;{e2h]3|$1C1Xns2M.GTrb1r\$ @ee2e@( P( ᫦'cs{5f[y6Vc~qsp7..O#I;i02ޝEypveA]V.EQoxr(Pm3Nk6äRMJ̢I@Q*m&Mڙ꫾53cvcñ^|F<ϳo;L&x1aO@DDDDDDD HS9D㐿?fnsdQ6+""""""""b@ȱ[ߺڛ={*\Ɵ5I4Rb56,r`uKϝCϿ۵t<8>_y;q +z\{T%νm+W{_6d>ڏ ӹ2//XOUNq\ +^("""""""Q= AtM_|G6`j+ .|}#..n݂bʕ+|>}j=x 0WsQXX8>kXW\e ]5>/e4LX'H@U1Vd~tԠFD;DEJj7db&JQ /ItSB`}wD+!?^ܯ={&⮇~~~ӧhmZz^seY}]+s6krxBTTى'OѣG|2`0l޽[AX۹RJUKT_G)̓gc FaV0P %}}> ˓Nx};cPƾ (8L#POZLD+!G GmRN@9:&F>{)ݿrKpf$/t"+*(;-0P&""""""I(/h4ib6IEMΜҧGW@Y=mpG EMbۡt% ҺړAm &&F(KDXׯ_ǒ%KF]] UFFx_'k1()&A~Z]]ϟ;e"""""""b<e_=kxOf̋nj3 ;37ʑ')>kK؊=H]aFLo/DA(sM1PQ[劧FEE0P؁ [qf$̏Ѿ/ŎƹNZ`LDDDDDDD.PVQphڀs"=|Zֺ,; =\$z$aƄŞKq"M9=pBudryTo pCb?S$Pvπ쮈n4EB^Oɑז_G ~C]$ [Z@k`}7``?ᏆSѯ0۠PhYZ1֯(uJV"Ir4z1~Itb,V̠'*նiL, ^m89;zHXR7bn~^ܹ!" tIҨf8խqBK }&O͔Qc)'_bN;yqYBp8f_  '_!u7?kFĠ/O\.UTFQA 4VϰxXޱvWG3~jix| lO[;x^NHΐqSS8V<4}^ ƈq9]eE䋼ac!0+=1N FoSb&ѐ/ e4͞$Lm<\i}u<ڰ:"ɜ:!,\X%`py |n{h:qp^1o{JoHTS+B%Bq?{{QLS7فD-g vq⡿<\.uFs1tn\^O"R+-3PrmiP._j+rѦJǃ(q'0osI>ƒHKMTyt TwHc+kASe ُmѿp [c!>ϳz#@W>kge6bG ;ce30 3ս&tq5;xQMEEn]6Ѭv`PUYP1 'KL/sp~ޏ!B"Dqʣ 8D5-5 fg KI LG__sliFKs1`$ fY|T .oYv-rIqV^Ye[Za|? *n[ue-GGB~^A*v 7 `ۖ,QC,m nU `_Dy7=U sk~v̭e(~j)ewJ^]k@C[:uO=M$h4]}r9mA*{CJ&p:]TٰlzcPcR쭭-,n~~`k3EbX__qK1vZRx. *LuҤjE ٨ݙhX,wQ 6773fSIP~r9eaahhHy:_%(Xͣ7(.Z8 fPJMQ䘩j)ڄA etAF1C ~@CLaM_Px0Λ8Έg1yyyz^9?rZPf\QQU8V*#JT"32L&їH$Bvqo{,.   c5\VZm߄.|΃ 4E L:j)^'W{|la|HbN',,.WV9=iXmh$dsmZH7Oi`O <[#(+/ _zy.OȠ/_K!S?)ǜ/-(`ۏtl"3jmxu&?k'&'FUd~%޾p2SƮgf422Buǣv1uRAX,/(Cܦ2`F:ԍiT%-N3]!(7dBewDP6e{^"ʂ    GfWQ`u4QUBT`cUƑb=RkwT~l53O!+SP>p J*yiAt(HC&ݶ˜%w1ky\`a^+j_i<ž;#Ǩ5!;;2l0vTP.gPkw^Q ;ܱo:;d\ ʡPHynƞ |A] k zAε[?2ˠ0hI:0q+n7;;3}ښ3dj{TB,ڴa~r ڼn]'Ei/0^pS6]ǿyZ10`ow Pڲ\tv *"NOtwJ,b@-iPhݞevXQ[vk;lq)v"c,֨2T8Wty{BE\&ڔ_Zxc@{D]rAp&x䇭5DGD ,q tr$]\tpuᒬ9W111X`J=SpؾԜ }3ݢE (+L/~קO]ٵkW7%` i.eҥK3`;bX,bPʯv5=~W'퉘P0H۳}M9] lQ<W (Νgi9Vy6wlj lɎ:&EUo4J{sJ]g?-o\ h181CBZp]*}suc9`ajۺk#O~@`%-(-4K1:u="ԺUP+r0KND#1}EJᜬL*ZP^r| ΤgJ_z0.Tߙ>ڲs!R;vvyyıFԍ kָ6׷_xiqz>WcZ*Q7W$ݝa~9&&,p8;]b9̐&)#ʁW4gŅ"[8L'F L&gWPz(۔.斍+Lj EP8<;[G@8ʹ*o(o|?[ Gyr`})z}z@>:gR[iܪPfX,bX,+۬I/ ꕏktw &x&O'[W\_<,%{( ْycͼ (D xUyQxzN'KmOI7 's_/ecj)(#_;5lz9ÚI+rn 1/UХz[P-n`FZGc:V]5aUz"䄄U(;Js83"IzgNѣG9Q[ii)O>|8oIJ(EG-,- &~2x\M lp WiVTmka;42P?9d||< k1L*~ J#/,$݆<9vNDD*JR0V-KIhjd X(N XYw,da#%rY0r@&l}. ȱrF`- Z1]wʀx 8P Ѽk"(HVr 1~0eW P&>whM<`(,bX,b^J@n7|%ى> uN30Ɛ[ˮ2t̗ע@ٜ܃_ Irّ2v܋ʻr9.[FnlsOmsNzրgcέ_c2 gz9zAr۶mhLe8~R Us7NoWD~]ifM3jw\;){e-(NF};,/=Mm0UY+܉zӤjC8WuB|C~s% x;*/~ϤZ (+8PΊE\*E;'"$X»_MğJ`2?r5&θ1}ɽxc=@Pcx-p{aF6({)s#G eDټ,} G9{PGl= BoP>yg!%,{= un!›E%{nrW(0.8NTUϷUr|MovesElƢpn).wtw9P50m!arT|"'[3!cs~j_jk>vlx<ԼZ@yF]x& kXr,F25}V{?6KxsSeC6gث ֓[u |V1gjj5c}m+*eMP ƍ8@|7y@'l7̙32gjj*J;|ุF(Cݓ9s(Unn.Η}||ĬYÇō7< h"KkEZIIB?~\噴zÆ޿_,X@tUѼߣ{ʶC?tP׺u& (۷OԩS Zoxx@Rt6oew:+}'NHyt颉_93bX,bX,_f@mVbIq>ޞfDGg!2hZc |v N©p8e:)|虃fIβ5^QpA/SpF,b i4sj |(z7U*kPN8 M"xL /͙.3LZ^l^CRuߖ#V6M1 >H'K60YTWgN0k=eztM@@46X{E(#;=zessDxC)Vqz]PfX,bX,R{:ly xZ!91u_tĘz5e9+/uaq-[ΓcWYg'|Y OTV)2q[\<_{i[ g%s̮}  e]JgϞ=MP&qΝvA>hβ`Ν;˗/kVZd rŋ-ŋuǞ={Vo߾]噴z ̻}cĈ#nL={MPڵl=zz γJٰ^+W\Ҙew:+} d:dD.ٞb (X,bX,Pnݲ d(:2e_/>EF(C!!bμ4);בrS?{SgՉ~Ea%͘=U}cP/?d4T%ڸm|ﮃ[oi5!g.E``O/HWi( `UJ=0la9b] ^kxTgu{Fp>@tJe3{!I\Sd !Uyxt8LB /Vuɍڱtmfn (n)yfP@k ( P({ႌ D9P mi@ I7jM 4D 4 t P"deu6Czivf1bX,bX, _͇P1rmm2@dG:~pM~Mx9{ `qy̙ c-Ir5 kN' =H,^{t յ4W 7brnbAy;՟30n]܄[rwhvkm{,'`r탄/teϬhLP@YEzeՎ$Lv\V >=³A;4jԨ&(>}Zݶm]a˜ڵkҥ6//!{$ .(.]JΜM&\x.rO0gUUWQm{h^zI?vXKB{Dw#i (z{ Rs6[{yy5V@Yݷ*a__ Am)))4۫3,bX,b@e5s%&^P+| ֍W:w3d@__dB@Ya [WYf9P@,@쩓O@vs&4ü:ɹ)>lPMCxfMHQcSb徝 kW>[O.ZPZ]s0UY+rfNG&S<ʩv LWy&-]@L6}@9+.qQ}9:3ʆG?,,͟)bz*k>:I\c1 ElA'ܤi/F~]ϴAGa83#;4g\|UyN[H9X d:!Amwj~$Ln#X<^2+8)M{nrrr2@u *Uhʲ$(&+}?S 2Dddj*ѱcG S9rD'OڤVIII}lӑS֥UV-FϞ={Q (9\TI5TΝżyǏCߺu+U+%cՋ \p ;lGQQQFPVpY;h =ӎ;߯3,bX,b@e3 y3DV-23ʉ}Ə9y6hg O%xz2M,uL 9 P֗>5q@}7ʽu`/8>xq][+eL= XڭVpb{-q: {w֛kݖߣoPQl AZi>4gX~h#`W՟sHSf:U(8;&?WAAШH2):OkU#l:vj5|D"F: P$_?@kן㞉cGf\3mbtRG j`O9v}|1#49oS@99K)m(G )0A=l0^O4ebX,bX/{}.Fwrv/<},xr_VY@Q{|aa!! @K.Vs^dѣ6mlߴiZh刈'j8]c7@9##ޛL_1w#p\˲sdzi:ugÆ #RWWW}_ Eܸqz<7)@+:W^/__l!ccc_=sf@bX,bX,몳pEdlAnhpws:yH P^R;*_7j?8GdڄэP? !ٻ?f.7Lr~wfwCy|R/~~b KK0kכdQ!9Pݣcg?D`PVi#in5(\4hG):W9L*~V(MMu}&~8;msY (Hk~\±Z 5E<6”ԅ:]~_E^'3IR4boqRR*T>e,4s,Lȗ)DCKDDeҬ3gTQnL# .]=zp5JiSxvPv%D%`4;suϸ*}1ي3jtPK4L(ϣ/ϬB=:"5&oҰ [>TP+CwS(C{U!9Eܧ#=M$Oۨ0ʊ>1\U.I|#P(DxS)Jzf4t:Ml[(P P_+ mnnR8eJ$6UV{{WwNJf ;V*J@vl6d2)}]\\5r|oZx]XXj`r@ON"'/%Pk }}<"mmm mJX7+9<n/R޳X,j; g7Ye/~djrL&,Rূ `_|g (kiiiiiiiiiiiii@2ʯ_??hSQm)*X Hߴj( )E:4 A uZ\D+VA3ԡ8_9$w~=}~ ;pRgӏtdmϽ 8G]Z^l#٧IMD*H#5W.<[{]+Rciϴ<~C=ɣCZJw^EN(y'&eRGǂR^[3DTNAG4 p.ht Q2Uw~mioC w㟷!Y>y_At_PqFFt2Bz2dٝcZAMmYc}_>$($Lr99v1"]? Ζߣ cGUe8 `i; gmUy؛z8Ay|2>l'.&/pHF#+ jhY2RqVݟoeHFʂ    :A93ES'4yT]CgAV%$(sXq/m"(SrcL[_U$B߱FPy,(UykED_o,Zބl Ȧyu2z/>H%jJ 7AxQ:ѳGzq2eBNZnAMRY\.gK%p/$L&HӔ<$$?zJRY*Lg~HqN~AmVܷGaxGO,O2͘{~hޱ/:|ڜlX:Kdsk+< {X U(+ؓa|?+ Osl.%,_LBq_ + LX.Ąm+śBn2Y8/W Ek𪚱ggh ¡DX1ݰ\C@LD:5(s)Ix)|a4__4]XǓ]sy+ZLk~QO mF\sT#(GQ3{f-4STj-x0R b  s* f$8>x>cS<[͘ai|?P֤K:(Wj弎z9o<& Iw_F#("(_kQ}SnZWb `$* \tQwAd8]A9A%j5ߛ+= lq{.{yseX,:m_z/5;ʡ9{+k7wd@j:yxV9 lAz6UxlGNGW4_yZt]Țݚn}_` 9H+3wj@Ter\wݞwpM;{ݹ <> x[mҗ7f}e~DL\zFy~d&ùY9q1N4<  GʐlC׶K)bk5 c.vjtD";T*XH~/WWeYt:X`3d0}v:o^7ǩX,`0@C8rbIvqn rΩ[T*XAS:nah4pgp0toB!NppE$vq&M|b@'j) ܢ/>^ <5MF^8 dhb Hja?!f܁.pYE;JA_J&T*PW5x?m@y^p- Z/VBi ξ<+}(>qakf@bX,bX (@@ ?7$xzmqlΒ5EF)[(\х돵ڃ.  A> WS* S {h>Ac]D~;R (c}n Q⁲=82P)Uc״R(e`{_>{^m~_^OڇDL&#LbZ@Y@@Y e@@ @Y P@,PHGøWq#C\||rl`W|u6w>^y/.jġ-y̏v_:v/DI?'@|t*.,#cGd8r DX!`@T E ãDjh,JZd4ZTN@@VN۴NxG p|~xen]r?]gp_T^\܅7#$~>&'Ǭ^8dˏ("""""""bL4鶵¾sX-yAfohhظqK,XA}}=9"!88X{#rN0wrI&]g˗/ZU(3P&R+gpS"E I'P+xU|߻k!/OODש(#,^;Xo/N?)ǶDxz2P6!1~@9l|Oކ.6;e/l{Кg 2e"4i4xxxɎNudHSS hmm|0P&BDDDDDD@y\f;!qu$ 7bVUm 3lmF ,'e25ȁ;+ I'˻(ٻ#7$&y]Y". RrQLԸǦL\ DF:#:\r&5kuJRt/]OR#.pZ7qs45cxZz뙘yVNglݖ& ]AfjY:`pWan #!t`,}UW*0 u*8՛%''Noo/,--(Gir$4 `~8VcǎF`ɬeoooCCCy2P&3VVV@xft@EVg%!su =5Q6`Nv+6H0P&bH:†M@9tI5+blF(7z3PځB?*`[Z7W4TW1+(Ѵ *-^Ogv̎7MҥHO0ckyY;fifiAbwpۣw@9qXыE;Speݘu뛐yV'vjI&86)?`l@Yœbk3wۀ)l^OL q*8cy͛PXX(#G0P)(\Rޯؚ'd#00iiiE]]3㲲qǏGxx8ēP]]-fCTҾ><=N7%毠@:vOeL ]seD|\YV+KK묊X*I[7~(1$!;u9P uE =Py{{J "oiR.JϯyVvDDDDDDD4xD\VW8v8> ϡZ:\ӷ o[ spX`+gԂeIE+h6(6PPC>\WA;xx8V^Omg^󖓓#" SW(e5K~/fs܍s=sYl;L_蘋Ku[ _ۿg:WRW/om{P%}V4 Y??Otn# ~U]!pM'?V _BkoUiKLr- kh^s_1屩e1!6wE2HȈsJl\5ZECĀ(=|%~X#wBgYl |xiZT# F.ZH(.,W9 Ht`梢$ A(XEAk?7#+~i Z9~$(*Jy*Le HdMUAqcߣhQ' +]1{r3GudֱV90~  d<GZwY{, js3QQR;b/,,ncGW4wՃk\r䉺5I KUbյ=F{p&j%系g3yZxf;5lZ(Ľ:<{M⮷峧~vҚu,|lT!Q n8V8#%DOOOaip ~eCR/T8rX"or\&M)1p^^y#/-Xת4lŰ 7玓}+all WiF*lx2$&~T_%!eo y6",ޓX|2HnyaRtsWd|F '_[ h|I!/~wFYrЊxq6d^f>,Aq5qg3=>r:֬e%%%%%%%%%%%%%%%( Ml[p}v9{ xY=]]fPq׫ J1O`I^),ĭ7s&E{ou^{p\6Mw]ױG0pɒRlcNM5d Vb6Kp wpuo3@iMf7?OP9u,<os?=P6yKاs+ _0#tϡ.kܣǷo4\w:svf&@'& Wy+{v3Q;Nj=U瀶6:M\{';ݷ抄y;| 6lm N^^^zyp} xɨ(8QtZJ ;;, eM4={0)1/櫯g=B޽{{̅ypMNNF9@wB3p[7}eqqq8/ E^/!8)̃f| ˩K!N:ٝvZ nfgg[@>xwPJ~ʹ;9'N?k SW^-_9[N ]]gl@Rt}$ ^+e]tayyy(w ~ 3{}D53&&F|^Z9;GaeԨQ;$ _-()))))))))))@Y-{27W?TZ͞>%'3w*C[%Ho\;rr 1mjYU*n}} m ̕mCRHa1?ߋ8`c#aR2[}}k{7jb)ID;1[1 ]~s7[ol`X²m͢k46 (.Z^'Am |Sg$%/V9rqz}5}nY{e#=qfښu,Z.>c|ߴPiBSg _ ~k=XgMСGٻ eOYuNr|cA²mX|5[Yoէ{$%PxP#j)8'³ӏ0ls\2_ܪu@rk2|֭ (6oy`Fg۴Ǩ㵺Kw@=XI-:.|40 9?XFV:P6rcw:*W^2yf#N7PתGC},`~<ݾ}m۶9ݮp;U `SSӯׯG쫯/ m4L_I(377Wq?mAa@IO>Y@̲VU}O gU`U@Nly}%V簪|V_]ּ4OH-[κ" xպXt|./S;@;ላ<.9#Ѷ:vh[vlh߿gi)\]GDDhb8؍yEq=Zt~9 X+#\ou3wJtEhF;/2ۿ/]@۲ktЍׅtI K3e~)oժ[LWwl\Z\#]v[/kg#w}./@V l(zqy}Weg h||t*n@yM 0}~^"ψ (󚌖p6L;#ʇkfPޚ2 ^c"5s8UukePX0Vb#;ne~/,::Xx h?ͯ]#(Cp@} 9ǭ߅~O) 1fwҴzg(2@Y@70c4b|8"e-`dhs"<^,E>3>bεh#. d|p&gIקCV3,ÞR*p(K IH Wџiz$4;o5,;8u.s܁ w8tbƍy/tȳF!u! 'M2n:Waz|khpp^S6r-@ىkb>@RKΜ9 !PvkZUVVʺ]QIv߱-ޚXjjuJJ&ƝPe:>WP~BvagX _Lާ},3 ]vMW JJJJJJJJJJJ PV2ĺtfKkg0(f-u1XHp6apvyX2oswwsLVQ4 ؖkٰ /^. @y%jKchrrib D-jbo6wv/rhkg3wsN؅%SPB4n~>ΗP+'ʄR`_*;nrviϷؿrp"G[w{EP)"e7##~/MuX\Q"ޒmW,`_^ߝau@ϥRYm(y?*3~ȀpF Ic&FG; 5kƈ?򙻋ѯ*75qo8=h/lßez>ٲN\4^U ur >_?<|vzm%u+ߔ*K0h]|Y,f\E͗CƘ2~r׮N2y pf`\=Ŗq#g&KwܞKmy;4Mc!ri~N3^EYĩޑ#G,bƍCL&MwAlF҅IIZ۷e)9~llCrرch;u>1dbΜ9ݼySҀO<ɾd%= _)()))))))))))@Yf,^P]|;>X/q@YB鮀>*jfU/gD?87(_om~cKgh kbd 62*rŘ E1jj8{XV3d,a dύs[9;_pZ9Pk5*k{mČ%a{3_}=1kښm>\P{b:`ڰPُK׵2qxJ[rކ|y$&hψ(Fjx9P~a@9 *ܓl ˺}^ˆ=|jRe(+))))))))))))))) @9߯@0yy9`i\]⳽re9pqO`tpa#;f= E,#"{ve"@kj;/0g*= 6-҉ ]:ݷ3@B阞g}^D̶=]Ԍ! aQpn3tڟV^WόZ-D^րv({ Ay>nif>c=LNTǒc Zsa:p3gZjtt.رw^;`b.]s_ĉ@矗9"W@Fhۿa-4Zѵzj2+tAѣPvk,___J̧PJ> -V :#""= 6Gpw}(P&nD/Pv}&|uvƀ2~[-siҥT@W JJJJJJJJJJJ P~Ν$\O*x%9 P"##ؼ*8m ( ]+&ν"lVEàl;mXR`yǀ m)k\&ڼU̻MH3::~o9frW%WNeHJ8_B(SK} o?C?i)xb {Ap*E sdiNEUy,f(C?wPm>>7s^]JNj}-1o%riP+\6=!Q mO1 ڏ+n{k41™g88V8O^z,<".@$s2 PVRRRRRRRRRRRRRRRjentDObfn n۔KPJwugQٲ sb|a &͆sx.ܱ]ܙ2Kr3}sܱe+jknnnÇ-jjj4ΝCΝ;Q; f.]b=<Ay鵒ς)0 c|8QE+߭κʤ >@u˴07}&I<'Y;88ϔ/{-? ZPRRRRRRRRRRR({zzU%Քs8!Řsf"LW:6X~%99%IgūKD 'ʀg$( (tLy֊:srYħ豣4wgRu,1/E8KMO]گ}yAk.mE@Y곻gI Ke~/Vr̩7> xG{& NlsbMf (Tz|,8q,yq /fɱ\jU2mSNVܤYqnCEDѝHdBgƀ75k'뙶~Q3cʟ\< p8&o?\p*0c_*L?_zt /s>өf|O~'|^mXGxD1i]@د|:t~jeM0#" a=cȥPrǂK N?4gcNbPK^[hݍga8s2r!X1z}@g}l@P8VWpR-5(!󦤤 A{ASiG:9Y:!mrYke`%%%%%%%%%%%(?\lE}%ڥƭ*Ǹ3ZPYU 07]:j>2F{@{k6rr&B,q&1=ymRu>Ga -232>br^2 g6pbƼ9sp;Ν#]9L*Zkۭ(k(%5pZ9PkGeh]@~ضߘ:?s6&<\\0(kyV͝ݕ2Z}?M+c7 vO`, M3xf*}F8y_wGU,Zg|@jxLj8'n1>x6LiUp6TU7X^*rbRf囦a_WJJJJJJJJJJJJJJJJk%uď}(lK:, fجN >g?'}Ӳ=;]{h[W>w9jL8~yyX]XpǠ"{BMv esre|6y7;!72OVy]10ط_*2a!||Y3|PƽwſnWЇ|]n9ζmۘRSScʲm& e?tևg(p@y͚5&ޔ\:K7wӦMt` jojEϛ7ob3g4@ϯScz}>Ay鵒r:QC"<~җ@s3}eeK eAIwƍv)#zM&ڡ̶6ovʄgW JJJJJJJJJJJ P~ C/sX faI(~K[Z4ң426kPv预'M崌d8hlJg=oowްJZvpPfsb|~z\XH/:P=V6@Rj@_2KAvau{Ex:P^xbYCI>j/ % sZ=Z{&DE{`ZE<,42LE!_[LDxKe@NF`$n0[B@r7P5'Ȩpvۗk.1 NcXΔ4`D;1c#XJ6fb [e*Nr4 `ş}M>X>ʭ8Gbզ%lj01.\cb#O,c85e~\ټG˱lTʼ_K9_(+)))))))))))))))=]2t;|y5n xe9@Y>nw)66rP3.@К=soPr7;p ָO|s.3̛8][gÞV.z1,й㴷D;K-*9la8(GPƾ|/"[{L`&]cEPl3qP-Ig?Cxe {e.wXΟ?xԨNi iFe \1+,b{EΝ;˓ʵ{>D|߾}\:oݺ@ ICb Pv~*++sJd2c\\,ڬC[Ǐ~eׇD^ y7nh |nw> Y+_{=Cws3}E2 w ~ UXX(ٳg>^g=wn?qUQ C˴E耭%m Ж02k#S(Pjƨ!& DJ%*jBBP#>{!+a}pf?|ڷu>'brmdhϞ=8}}}[ (----------- (?̀r]Evf+eOڲ*ϦcӁG)EGǒ; ]{ًqzZk%8ǺEԽ1sP=22pD#m>.ʿ"ptu4ɾ"V@5Jz7Yz=ݚIu 5TRV(?~.eWf fuTX⦲ T ^|?5p{JjL9Vpvܻ&υ-.q_o xwJ: ASIB(WUȗ%ͮgs-ŵo+gd\TQw 0TOMr:S|3-ڛʟ7gvqmrS)-:$0m3K_k Xف?Uҕz1cKoŅT8BWԏW5?]i0vWSufuk$uW¯R}V&\S,h (2n&g}1:^)ٴo˕eO 4/X#s7KFʬwnn_Wj@YKKKKKKKKKKKKKKKff38+``ngُ#1ex94y "DQr9\c_R.7Ʒ,Ď^_Nmf^WZMBcͶ>0 as?ҋԿ-QJ;Gi:+D Ȓqg}C/jl6 ݊Wg›m<$ދͶxPBcq?em38EOm{uQ85$ t:,!Y?Β1WempOv&k!EEE1,r逹}ha0d||))) @AQkxxX&g)22R:#@w\#D?áe8s:YX0#ח]pA+(h_XX@ n2'竭$@9R;22래 8ɓ'QuxyP:MP mVe OW굒P$ggg9N+R6l$tg ;FG~z^䮮2bAUWPʽ"J~opܕt?GuuGxJ7l[ZZB}(""o( &ILU,t2*q-͏j)"RPR)2h B@ :-.B+D V0֡8H'x} snwӈ|ó|9瞓(Jv ʭ,.0 0 0 Â,(߾9؊[F]mw mOj&.]|<S' 𷟫Ӏ9'.B@\ V!^.]P<TE'rFYi8 ArWY8fY?bݑ=xUР3- >Awi̯D%2yɲ/su&|FL\cmU@`f2dd²oa2j*PSyVW?+L]Q[,h 996d:Z1)c{UV1 2t8҇$kS}4Ayx4,ޭHCE4j-[ZR?/E/ 0 0 0 0D`Ȳr=rFM2r}Гܐ抈oE׫gi[m{YO8g2n|_2>ۉdJ`O¹>s&S+ 6XFnȽւ2xڕ(b; 5pS'EFf],=5R5R!(Ol <1v۷Y+]P8_ɐZ&rLb0֜Bꥃ5gI,@j_/(d]ϜfJED"ԶIl> ʝ?+:^W eO4xGMR.w/搒} ug;7Lw⻽Ib< NKQ/ |H3ę.'6diLEn Rײm|i:r*_{qa \|{sg_q?q,_B2!!x7d=+1,˟Gy.˜_,B, &ky&Y P%%%%%%%%%%%(πr_g˦m=0 pJh&;CS9A;wldz[(ÍM̀ Ąf+ et y\7~,-ⱳ7V@iF[9_< adNUU,&~fT8̲n] rll-r֕s  jԌk?DZړh`f=pen]Bc]?'zwVyd}%r%U# @Y.^yWea}w.Z2k]]`{ԚJb/|szXN/t}ty,&͡c\6Wѝ3t:+w׆OkK5/k.pq2-U㳯a} X!bĻfkF8"ێb :X{S^=3Wu_o9OuF<ߧ;wmN69ϝ򋹂M}󊘂[ZɘFyޙ w)vhkDم()kt( %3T`+odgej9D 3uǧPM#=S7MmH'7lSPsR<؁yvs@orTTE y`X'Bm42fꆟMkQ'dLB~X6MV]DF\5`Z\(JQ`DD@FєĂ-$ƅ뙗2"Ӗ]<2̽Myxc]#29}^~ylyBac6R/5w#vQBP( BP( axa(6k >ØΤYeGp>,QVn3`Ҹi{>Cձn8GSgT4Z W홹|L,wOe$1"Oa\,HM[eUeJW?cj2E߹b{Qøes?dob+eGhY}Δ~%u9ٙ7$+k癆-R5n,q_x}QxzOʻEA e Bk<Ɩ5LN_L\nJk^>gOHRk/wȳ\nrD"Za<4f' ŔFBd2H$v-(,BT:"hsZFD$ǹ?I룤jPיJFt4ke^~f29:b1r9[.⼕J+I|-^xӒclBLͩۥn2< [_i䤜EƾƙwN)wݘk>>>.+++</IԠjm~UP9*F#ªq{e>R`ssyT_:>66 XSꝯytt4֗i{^*~zzg2>FmW8P~G$K}(w>GePVHI@ve_z9l5-//׼V-BӉ^zp<'GGG]xp9{ ,, 2 yGɀ^LV>uெcJmy5A@~"&GceeeeefϛYky]^5Ћd]yXV (ý>JjBj6e||dngd_}PVfggK*efflmmŵ ,, # (`eo[oOWoػF0û;ݬ%[*@;FlS*i  &M $@o`_! Fgxg+7-!?fb8b#2pfiefBR}qGK P~L l6X,e8ʗWoKe43qop}Z{e@ ?VL֢݌ݓ?ɝVZ9ܿ @^]JZjZznRDP0ϐӹ6t:c4EM(ˑ P( ee2e2@@Y L_fB 8CdHnm͖S3IZ1lMVtY؊:uu QaCFTaVN]+6{z><D +&,`[͒y-Z_y%K렖Z[ Q{:42S: ~}NÉh:ң/y춿ӝ'Q58鯗o_gԭ{4r(wΣ'wO@e|uVHiu_Яk=z\BA*w{Qg _{}Wa3Yցna Ujٖʨ| 8edjji>93Pa!˙}VT!֛}p6q:&O"ةOdfkiXj۶Y=q(j72aFż]{u<2 P222+?P^z͊v+1tB#g+r$Xn)z?ǩ+9^@elv>P~N(d~^8%}܂Э\,~:v K0M]3đ"t(]:$ĬD( MK-^ʘ)kWo&PS6n,xJnlD}zN߅^DDDDDDDd@MqZ6Y[k5ik{)K]p\9gj5 VgXtfaE%:Ư/垉HI5,4J 7kiZy랾oF"T=HaG|x-|矾Dhh Ԯg(zsI ;M„W}ygfV,xT$R{fQki*8~;MŦt+j”+""!iAaDAXBiQVDAeEAWAIN~gx"m0{A9|9Mηunq|Mʫ"P6wjҜή{Ex.ٯɿnr {d9'N%%(OY_Edz"E (((;ع꩷X@e{WM˷k[AN_,[ϳs@@Tݐc6> kh/mi57{5c+|sv;wFh;5+n:( w-ِOEk3ORVRtM/|WUlD{R%Uޭbl#V=;Rvŧi@rWO5bڟ= el4x_i0͵k-PV:; mιvf?ج>n:8^ӽCn\%P  DB9!Gh,\@ݶ?"Q7u.ٞ%P(0PO8wK>gϹX5],*h!Y5MFб^$"?VdzK}]ɵfϨ 6M L \P"uQ^PDIG4Q4j̨Ѩ F!.XC3A޶ddy͜s!q˼i}fQnBw^,Ò,to߼C0ɡ_ʹkLqnԿIsfnfQI6Z%"6::McXy\3"+I⑄h*p "Q|wME}g)<\Tq<_^O Z52H N4B*<]CƉ"lNo7G1wP96Ӱle0qvZu׵c_m&U8+(q!yRƞwiM:k#C#PWs>XPfaaa\z)ɱ@m d7~S$bRq4ٸ; N<[zIu愚Kkgh/&׿Heʕ/U3OЗ'} Ѣ֯_PVc8PVR(2CNjX7(LsZwD[-0F, ԩu0AcXLvA٢̐yz<:+߼o='*q:*++с444`Ŋܘl۶ Boo/QWWlYYYAggȧߦ&ZFcaaaaaG^P$(c.7ӍbR`R,~`hLwFC CyR\:3h9*UzdrH% q{چ)StW[/3O_rwQR9-Ih>PlV^%A]!AO=wC2¢ZjKWyWk3(_+oI!9Wz3?.5Kds w\y6hJHU WcK @6e (L߼dLVFG2 $n( ԣX ̴e5E]JO;I6ԥ޹U?YZoϘm*(Kv o!7.^k*!H=vlVm=G:SwpqȞ$ q8j{IuCnpkO>9ؾ}G` {긠{k=[+{sR1b{ .){.U:RoP@"@$A:F\梱33bh4~K4MT*T*AUϛYZE6šKTa-|,\I"GGIL%pcFA'F LP1[$TTdCR+Z'7Á;si;CY8w/qV&$$sD fQaaaaaaXPə[Ut:h[Ślɀp (tÉ xDP4%0jHPmn؛EF'"B]E(RcrЫ6N=Q6V P`T.w ܎Xqɓ8_E^%X2/wF'|`$W( Sl`zW{FP+w8 Emr(3#xAޕk9,YmMg]UXM(Ń;u̅(|دS]^kIJNVv&!wpjMuC+$(uAZHl]G{ ims4Uouc[qVR HuF1ϣWr؏FZ NS3tlAdT*X,/1 0 0 0 0 0 CPnl+ (GTuup}zj "kjj\xZ$A:0A"ŚgM# ujbL 2Jײt&L\6;I蒔M?AdWad۝$OX)9 }yKz(|J/\ډ@ i2AY0+e__sE%~O{+SP&v'ǰ_(e.^:$ۧwrP²3Woh_K㣃pN)XA9lݭ kW]{gՕ5pzeugEndQąE!"lPhH1Q&:qW Q#\0d"jB2LN!{Mi`s߽?ޑBAykl쀌ݳo_P^@x=Xʿ}*(+rhQ,8X, \X:oO咠L2AAAAĀy`5>'~@V3l qV=̓.Ȧyˇ("/_u EB'Ins݋4^L3/KtKb>eM.{cAك h ]b*]Xw|L"5e'E,1l:Kbs-) Bd3=.vp_i ^~m΁(*e g\ 5y]Ϫxq:aZAxc.A7jz>~ zܯrtmzV. 8A93.˻_:_)Ղ,gA(oe4}{nY&L|9DVcq/rX?H ]!J~lLΌ5>Duzb$ ʗ6Bd ˆ[y7tz1VAYN\.|s&A    b "7D?:r3v"nof.*]vAv+ƢSL> .t 6/ rܖ5oIs>sžXww7>۰a{aAAAAAAeϑ#ثe\,^_͆x&όqf Q|6kw/<$+..86c_>%[n*viX071rtA #o1aZ O1'Y%)܎%(/vJ/MY ('NyT\R㝌YYxCZ Vq6(0*8q:s#e\>{Q;R.( Vw|DM+tO&AY^Kƞi^˖$<э2e    ,: 4kUW1g0mx¸9swxV Q Z@ M Ds<;voa?eR>r߃zwGq;(Y t:V+b13J͂ ktܡz ̣J{uN(qԷMPOpb.INKwsU/肰>|1Ÿeՙ*]{³ \ ouulLHH +ɵIP;vT.c݃y͛7i4AAAAAAPNɲJ2b4ڊ"AtMN-߅]|Ü{UrdΚL<0w۫8VX)0߃\+s͂<&_{{f LJg;YPP7PP:[+(+_egxW~3^ CP3i" x ~%y0]Xk:ccP<7*}SV '(#_:3UEj'ֆʹYk (s'ΰNPusp]kq] (%( c2[܆mcVAԔ0< y"?)d  IP&   \2rgy @YK6#5Xox~kX5 Z?S阻ڝ m{PNEAy"(CX{ՄPe& [;ꣲzW3=]w ݈AW\qEM.$ם^Av?P&sUϠһ3w0(LRPv< ߗi=y'!$݉$({gC妦&.9r*%6 ANPFt:1␖f4bAAAAAAr zV^E %&9ٽל7*K,&$UK詂os\؜_2ʳ $g孰((_h1j4 i W.su?g1GPn k:z6?#>\E{ks[b :guAY*?+)g }w?L_:(̸hJOyQJwJD%Lwv\lZxvb ^FQ-2/m$$(]IR7iɓ$cVĄ R1\ܑ3k\BSigO/O)QfH 1wL{"W 뷦up eEks&A    b ʻ'ue|x[SSB/tC9p%6 ;q^***Xgg'`>>>c1      Aoe rxsE!5b=jb 8WlsA4Ʊa֜eUsi RPܴ ʰt7#F2ClX2s;o +D3\:B^Gv/ءEiu[;b]{q~GcZ)(+_g̜eP;M*F~3^b b'#(^6 rR{,S?UߤܼXe,g-Jj>wp|[e+ A{_WtK{EU$^]mpk\iZܧl g߾N])A9jjΦ%EK2AAAA Hi.򮖜0-=}l^ !Ok ^`Й˯/0JYܖpIY?i%wIPBơ2tYj=bA\tBhgڠOw^0AxFɾǠ k"(`v\B ʈʞj'h<"(Ý;'2gpI˺%%%\ѣ%c ZVIM۱{ d]]] H?#     /(bqؠ| ƽJnqhFp/(Nq%SKBj}.sx7Y0h}hǬ]7qnrx-oUZUJ v̞2me;jrʲ~r$B}[*F~3{mo8I o%KvhZ cΌUP*Ҕʲ5e6qE 1łG-Y e-sLjZP)dYТ[tU_2Ču-`\l fW;_qT ׂ<#qΦDz[Y!HNuClzb$>%G{Y)S)(0|mXMlѪx..5坥lpw8YlIv]C |oWb3kDPy{K~m{Rư*/ AAAAar>. j( ǹN:yIZamTvD/l@PlVXL"H$D"H$D"H$)(W^PH:FvbJAy+Hw0 yHȼ)M t$IB }gG_6EbǷh<G;B'hB\T 2gzE) Xj4@fs1a Tr.WHQ ( љQy(::q9a1O_{.}*& iZULP1u}ʂZ+t,ͦ5Iho2/?AїX2wdh&iŇψ$5veOj(}Gu=, kw̍S]+{ `:Ӥ5 `"\PF Z(G7LƗl^F&Af%"BL&w=T*\.(WUse\C!P(@.x<~N'QgbAAAAAA,^P+"qKiqZ'ޡ(/ǯMk יֈr| y|p4nAqW_>=+NE=in| CCz|4[7ttJXǸ/;yXݙ^a^ 8[+ TS"X"",LJ 0D&DF Jp# Q7$%Z6F/춋_|{<}>-^==O"RJAi<#e[Y#w [u' R Ξh5X^P,;{>Y=Ȕ@5{nJ떯+g)ɡeYTN~i)Z ʂc[>l){T{] "(31麽#*YJ#gq/bܕ~ʕ/ 0 0 0 0LPn1V;W JEq?M)4[S*(_lertwmم|hM9)m΍i]d 2d[\?|)^m(V,rRl M g2xZ+a4":BqdCٺ2AY:T79@EP}Dj rse Yvz f AY ڙ|S$l60 0 0 0 0 ð^Pһm9*|9ZJOu҃ǃ(Dc}twr 9Yg.]Ӥ7;~M=$,Ʒ#+dQl$\ CoS`!vh?Ԧ_ qF*7/V:zSrVE a^(NMЬ (M,m"%D(S4(5eH!v2RHм"(Rdkr1s=l5\{}YA%&E<l MgI/@-θ$Ϩk!g8AtV8Aa:^gO]9kUskg"OL@>'_3ڔ/VBE|b(K?~/:r-Y¥.y<;C2N᳘L.yf!J( V+:>nDX1i/܍l?q9, (3¸u/O.;τ>1F=ʽ Ͳ?XZtk2gHv$s^`p=VfΘ.̜o$f0#Oe*|{ vX.m g 8FI5!eaȽi4sŽ_Soe,Ɲ,$^@2ƄK'O_KrߪzuQrj4>P쏬{&[`o3}e*Hȁk~WI`M ew3eܯ|V FGŢtKZ,|v IgVc켚 (gm]01ڲ EFp_pu)ay+Wrg6"w̚Oסg{yt$fY.n#/oBTbmH@mw]}PnZ]%kv,XO_%0 ()T$Q2"$FmZ#Cf'h Z!h3$C|2%6 9V2…}s$:n8WteOm v@9i=qG;<)n9z6#4*Eߧ09mnɍV(M,m^`ֻ~FBבwNLR@5=˿('/c.u"\t?ظ\Eߧ\|3(]I?֓#~y6|>_I5cccEūQTbzz:z{{wZ6EZMQru~~>~y]>(˱+++irwSfq2d/|q2+XO88vHuNmtK,6uVBBB֜i5m.BMu~@- <$!ft_XZ9}wUڳ*ghO\ѽz1mP^$Rz^LgMY"|xZϧƾ\d>˸tDz? .z`(@9gM)ungfCrLSvo'eeeto^ QԜUZPl޵sɱ._"؉oCJ P^iM5 k;Pݷ[Wʜv9vIuz? ׏:w22e޺eZ :p(Nו-:nMI*\;֬F2=X#ڮVe[qaMlaŅnWyAʩvt y}\x;bz33R*@`/;8ەz60'Pػ8pp8mz @aC bf SD~ HᤢպL@ I'"ZZs.ivs11|>9|ϾwDu.Ӑ蚛ESJBJAG~ ո/q*@Yl4YZqv#qoSNܪ8c}[>D4gaaҙdOt; akmSV!o o`b ^}UX al ѣ &;51P@Y4l+G>Q[^X̡wW6'@KmW0@x-l)OeQ=Wb?fN$kKexL:x9~(&tΟR734/Gvn:sQ}#6KLk2zM헀8T'=h=> ̙@yU Ŏ 2 R c;zR{)7m0P@ٰA!fRan+<)ho"""""""""""bWrxmW*Gz(8׮~4>ʼnU؜5 qΊle6@cGg(@Yd=֎r4?gVzZ`LDDDDDDDD2e c^}oisɄ{40gq9vX`*\O]v학W>p VR 5u㎔;#}+M*BHg0T# uk6X펔K:yeuV/d ~qQ#'&C`,QWt\e EFZOy\Y&qW{ s5/X*'1P&D D'eNSERs7{V/y^~-0P&""""""""Z^1yE~P\O8zKӜ٨h܌#Ud,d} +Cw1@YSct>.MxZ3)Aa7m0P^@Y񡼎QA3r\e? tZ+2ֈG͔hdߐ 30:9Q~NtC/ySSSt ._w&?׿_nHZ};&8Ct AxQQަiVjRdNrQB6uNgV.E4,W$atl4ĖN ۞s1{܆9~o;\~9{5y|ZD(*93z.`_VNn=f3 B;/|0ffoF1ÁldnO-F0s/Ygw4;6LJ1XW <wcQ) */-jJ4!w+>wJ0 DMAnX"5TU`6%ѦXV&'Ï{f|ꅘ iDz `j [Jrs#vm56!y}28e"B^F:١l3~~{F~up)0y  GY.8uӳ] {hi"jeUǽ L+`_ @?,`q*q$kKBlb3Rѡ`p\w+p dV WRiQjUis:p9v#^M眀P2BP( BP( A;7#EDm.2k#lѷH"l[7%gyI0[eCr7z)iTTg**T40<ɬ2"2ȌL΢&qqDMF!hbMZWN:]v}ϻέSU=,޽;Y| 6R=vi~sA8yyf0E5_JٞpDkB]ྋ׾}:=`5\[,5vQn v ,s:yM\Cy@fn_DZ"ʓ֪й$UBdyX6\k>]kseF_oes̚n=kg4Q5W˿UmCiޚZX.CDPF\|rx^Q]IA9ג#PaGAHm,Y11n7U[fe~Ef$B.̵.eqݷKͭ2 (nI`IT ʙq("[A^QArEqaCBPX/UgL2AAAAA<Ay݈(ܞB-%8bN=ʕ1r~lcX>butF{?ٜ u Eww̷=c IlN83z$IZ1I+CEAY\gA_je֙ 3t񔖢Sn rP 0P8= !pmOq1ttMy'qOPu"V~xOz;/"b*]D1"+w{Pv74]⒲PAAAAAA<qޞ' ܳdh+Q$].>S.g1#pio!0r2;IlqG ? bq1L{g___Q<(8~o[eZC'o>dr3{uf>w*|)Ӧ0NfyöMb:J1I9`K~$&bœUX(S;_Bfyy_lksW+Yԅ rCˌ9QY]%RvA9!$k?|~V&68| a/[1~`n hpe__8sH^=r 95 bCQhLG4Ǜ6˳((?3l-59[B.lS^+rD?&@brBP?ye68Ǫyo9L1 kv[#9lp^mT<=˽. AAAA;_EӞAe2b݊1g'  lxb*7Yɢ\`{WpXdl8qIQhL+qQW&W:{OPc,zHjVS(Mc\;c-Pk*(|xGxW'yd)M 6acYe/V-!#+Q"6~v(jQ`ܑ=:{={ㅻc@AAAAAAPszK9yfųgF`5CҔDn.퇗#Ŭ]q|ew/[&= u$ ^ ϝk"#sr-h2\B=#[vxG݅&e|\3Lb1vwX6L&+B-K3ٚ( :XP^ 31s.g$&+w@ol yA*E$:R>w[ (|A9($@֞'yWV庶<:+HP&A     HP: N(;Y^Y>r<שL L f9s-~DPV:&AYQ[sUnC}mLquq͔ʎCP挾m*/뵮n~;T}לdޑNiO?"     "( ˚kP2n+ڡϵ:zy.,7+{JfAyTqɿ}ird/,f5Ȥ"(#_tA޸dw8ev2~H>~.klaYdw&] A94,^]7OOolW.^oݵ\VUlҭZ^KXɊ4wזZ&V/֕fԕ$2ٗw/ޜ aR{@n/՗c4)&'gFG!(.ј{lNx>gTD v IS#p|+ vQ\87vz>AYz_>j-r3 AAAAA DPfR1a`BV7 uu,݋\]I }ϑE^0~u|~&n bAY Msygx]&ck%cs}GL:f6_.kj W6~ Ay{?<+QP>ha\ Y=v;yMa9jDAAAAAA$(Am0XRbq[c8<ϰy谡"(#Uuewhʹt45Uc`odyEdž|O`|k/Ư}vɼXњq~ POmxWT^ޡ߂<5<(*" hØ%2+~e8Pyơ9%=VQh;{T (}$%zǎ\j&Ae     AQoHm~D|{OS)ac*8b؍9H,+t*f1DHνr4Be_>](6ascXhMP%;O7qql`?>Kb;\Vx⸡@q`cjE2g}PQR54v`t"y21 笞U,+n˃&(@;F2~uk]̧8{v,?bmH_~ׂO?"     p9]$'B(g( H@?,lo`b2NE݁3L76CMP护t{A|m &;a]׬_/.˿w{>.(Y>뚌{LHg3f×5`AyZZ OeMoBPN gq n5Uq5|}΂`iSr93"1Gځs( SP}5NwA׭EPHCP.ȸ7+{"3|Aڷkp"oWsny^{cn W)=8뚌ݢG pP8^?? ,uA:~:.%V9Y`SځuWU+kP6ɻz ݈QGZq]OgCW9,~sW}߸F-Wh܃BH?0ԟKqSP%WRv?iaҵҞn;P8\PWxW[IP&     ߂ѨeMUd2ܼLs~u$ Ww#Yn)O]8O5X{15; ?wp6c:_M]PɅPrfvϟR+s %E|߄\ȬIQwaUT-(^i觠苲m~ '8vRM?ё9KH{4__PzHxML<􊤧=A9%-Z4GmkFK 596<2r=_Y. AAAAA|Cw -caslNvN%q$D&Y[Ӂ^Xm<:x>S=S_vۊG:_dž MLDaU9{D@ӖiJɎo9eELL\P束.c˅g{~ϧtM|0y .=Fs)! C* !(cdk>lAN؁V[KYP^a'e ݳPH?"     rgHF smޅ8oeqMy 7wԵVbٹ("kev6:c~ɺ)c?}?gc{&.bUS=0ݟKr ʯ`A G!(sRl!־83ݦՂ:3sf{ 균Ekv͙A` ,_3KzK_rݯ ?w|F/(7L ;ߙ>>v>jrlBhT~ϰrח+\-,cSe+=[_e     AYΑDv5.{ٜ }aEn*76w](Y~V`]Wx]"_uYYPf{ڝ#%N86TWAy\sbgb_eY1PRVG6E3eZ2J ]u v#&w*᝔EP^!%B꠼pڔ \&½-c5Yi ߴ7@sL{rM>>xWJ[__e&%39coͳ(.['[4|,޹ hp倉~pJ&Z&.O B )%$@gzKΆ9qjKf-(0||t@~gZѱ!rxWCAE2(>lpB[(_vuy3%̼xX;`AYk^e     J;%\\5{ؙX*gä_Q0vƳ t (mͤf׻ [1r}]]]~EAAAAA$(]A$? %↪Bj5fhԢ:@s])$9FƏivBm%(_{p<&٫'"Ȉi *:bbXw` ӰtQ0?{a~K-bPT:V6X7؂2göey%5+f̂:sh#"Ùc\__{\]xmK76盘Eӓ 8#+ UAh/eݺk|uL$^#;)Y\ &ELe1U#MZj1dq#槄Bg͵fjJ,ojD8DNĺ(&:!%IZ1W+Y.r%_ki7=..B}6:=84'(ovHx9nxwcU?$l>0PuUӞe8k$Ohu Eú c>'ɞnMτ`M(LTB6JyGiyP(hDP۳`?.4SZܳl8镟BD_SyG <.LbFjL(80J|҂2xiCǠt6=onb/ޱB7 w9Vޭ?M{V(A_wƸ>NXV N)AQyE& J';uyN8i{1mک3v/[PF"* v7`(+ #1tvv2I5X#/      Ay L F+Duní,o10VE2(W \4+Ae܇߱TDW aaeeO,9E;Y k'Njbzc x^1_>5 sY R3sDBIes8;ߌͻAJPFY&u+0WZ)XɳCҒ1P0%x,ɹ1(ZEjz.8؜ ?i]PF ʓ5)M>($_jvݎ6'Õǫ̮۲j,v\+:m%(c`|V+X#/    o( H3!VVf6R5e\250!lE$Zk-76+Jsꢋ?{ݠ{Ź0qs9cW+([ YY; t`W.rQV)Jk$9YnArF<\"IM%n-|$oV6L,DaQNN"O~%1ǒ( \00+ AIKV-,R!m[J*lC,YFH,:=Dm]+~&ORZK,Ypb5#; :mb%7K`Z~D?9N UO\vV}b{^d"dMfwR~A0G5VMz&%cQqQÍ5^ $ztZ;S:eD|eFMSR)9b1qhQ^cJ|7Qa YITـDGH;NRK뗍1ӪMᢂLWY_+B/ySZĻhݭЙ )];ьf1Ka8F2h;DbUý܋e_3ERkyUi% \E3ڴb.fox*O[PQR>׷OZo֓+W*ki ޵~u&҃;:4#[?(L(ຽ %V捰a*_>A9G}p8p8󗽻{i*8~{9ng3ߝE+FH" 0C"B$"ń ˆVPXl6ss~٥~y: V# y=rfw"|sZn<\)r긧oDCrCSn#YhNN;+y̆g^ TOZw=I;^ҍǓm7W75Ow@C|Ou>ozsk-u^{(#MeUs{ӷKb% ^B[?"g9ϖ(wʨ6ү٣u&iܙٿ8g'm5n@Y7E"[Ŋ]ȬμY_Ԃɞx1NX{?mγj0km+{׹N.=CrYj{L!]"2Uظ1$dH+Cogi~1xw47ZFN%J"[ P^@h/UbՁrr @yTce<?PJjhP0((( P f&v`f@LL (((3@ P&Pomݪ4q׽67b\&8ldA[2E F +0kXaAgSv>yDfj!Rӑ(G܈K[e( V'Vsrv(P2+t:O {w1@Y@ ~@,P@lz1H|[ɗ{ @ Jf4FQغ(=F9o^GvC鬝e7(e2@Y,P(e2e2@@|-vv7biy*|wAQ.maQpQ$pH DArIF"CL-*EQʪImtBS$$(& F .ĄKCCti;N9d$\dex>f}Ͼg9*&6 #9ĵODDDDDDDD sTVTCAD4M?CDDÎ o:)(??"""""""" ({4^Ph~CkC䠺6=-u|x4]s+MtEW99/NAݟw8̇OIhߋ_`C} qFك\~3g6z5"¸d+i-H8j¨BG'8ͩEv%h, ]-,1烦" ikkЌ+%%%1+W`2|rsppP4ᱣxo4?gS"""""""" i)9nu0P&`$ƈש(,OJ~`aDcCBpvޫEٞl3Piq9>l}q¸q߾,ƎdX7/dg0P&"""""""b<D.5 7Xp@2M[jAAA+eR+ .0PNvkN(^`ؽ vmE٦B,m"c(]nmTw.2P&d&`xWBT\:W+O},b_7n[mzǺW#G3&PI0y+[wc$bnp}Q?]~_fDDDDDDDD3(PvK86 Cl3k<_R"<4F֡ѿIIqO1šbv?b_,w5<$ ]0ڟJ Mp*ܳ-r#Ჴ*WO`%k:Mr0ks;%uH9)BS\%遳m]\\奨eLf'Z2)MKK#3^gk~} 3)eT*V>{}Uol/D2PIcO+L5 &٦$͐@YH_/JӷJ=?DgK&mVd,1rDd(>Zٮ*ܱo*STE\|4D#}u6na=Mk}3P&"""""""bl5P֪xV"|vuێEGz(bѮ18;?qSO+x)+Z㱩* (<#!$*kѽJe;T.kD/^<2̔ Lxx8q%$&&:R{D?u5h~\DDDDDDDD C6/EtD"DZ.?[ O`,e7D m(žyH<6<<EPex;"va^Qq(Ok(1P(tA%-]OCD"ܳ^S"eCej ~moiڭ> V'&C$=Sϻf^닡!twwB^GeL (Oʎ(s  CX9"2>\Sg牌eL ((+BCW#<"T C5fVl?{gt&%5_25PY,BEDffsj.MjQYM˅+mD좫֟\^`|sCfڥX(縧_ٳ4«gDq_"s峽ɞQs?HkopW爟uJ;ZhaяAl8,`e؅N V|6QRgKeE\25u㫝j_2>= 8onVjeL'齪Q3BN->'u&܃r j3 {ҿ feU1Yh]%mθˊUk'Րe;TNcôc0#L/'mTxחàg0(|<6`fy[{qyk/'g i5UR;UC0lGh";>̓`āz.rkgM*wrΖWNQqFb>6w{zɉ-tqAIV0ſOx1viO;#[?QC}Gl_-Lh)3ƗG6/"9ܚ iLwg!7 ,@ f\^83 S1-!f\DF#fIa w-fUIH" %- _ŗ=wwNj/^xH@]O!>ؚ;_ K6O;ߔ|b@Y: ñItNxLfs%U|#3vά}7ҡݿǃ.?`:pcd̩g{%us dϿ"T=[;PHyHζJaQm~\d,i>`YQtmƏ< l@rmV_  H}ˇG?WG?i`"+~kBt}ҕfRd]Mb3LhW(hжVHX,r6z=V`kkk? aYas6*5O [d:bHVsc4B.fgZ-̑ngAtw}}7ȳ777xz@J%}\kH$H1w\P(`0t:.[*tr9Z-a|>IanN_V%o2qBk)ub1zBóJ ~_(/w~/MFag NQ`lL A?d iH*.w)IDzэ`u3EMn&~]vH}732x.>= APnooWLFcz<Ɯ;CEQ}꿝fY|AAAAMP=]'e׋=C@XHn^#>ssjL?Y;?I~+ /^X Ӷ,fk恚tӣ"KؤT?ː EV.E8 Zڄl%dXU၀)_v\PgTjniAë"(|u%7ef>xv A3gλ    `DPmlw"J嶲WhRE=rInZnneP\.`)ϸWYS(r {gqߏ,s!tQM_: Z7LkA?gZPs޹6DQ{tФ6 Ibۼ1i&4E@hEDTP -(T 0B \ؕ ~kEP wq6303wJ~EԤ纾A3|gH$D"H$D(o7@9w2`<6 Pb2>~uMq2)v L6;45ɲ^ZaڟU3_%tȏys/z{x]˺߱ϸD~x*mf:;_Zl'y/5ъ~5PUC! ;l䶴05P2_3[ºp+֎RX z4rZsncQ#{ѿ"ɷ&"7\8>'~>#ϮsusQTR`}w~:{y (D"_Υ( LgIjnMh&f,n,)xi(Q""&`AЈ(.*( f? }O2L:g|I U( BP( E([)ښ͚&"s}1faK Fe?^/gO>^F]_ c"EObO弤b0CKV ѿUy0A\.sn;Ϩm{]E=1(E,4E ""X56b/(j70jhL^n{ߗw߸y3ag6;Y?8{5Wk??^S_+EGPKKK8"ds(p׺2cN]]O>h Q~UK3:vg>>>^⼸8cwwwHX8р2wڲqWXXrr2Ɯ1W KοJ -~zp[{y{z46:X.; .ƶܧy?ٵeuC/) (>v}vCc0wOEJ{$P^Lj1~2pV=Jc˕ ,q!PP/sn?OOWcZ Gxcw֦X`o/yT"/ҎM;1YPvqqfJ Ⱥ+%,8EF0_}E<둌l\E(slV)ͭ-ObkY.({5}" vk)YMQ7|Ǎ9_ yt+I= c,o&CckW%o[ 1r2ח׈jF:'faݸ102.8(úZ$Y_,uڟͺϽj5m }9o:=:ʝPmڣDϏr0/eT4h-ZN< `*++ĘPV}͚5ӧzshu4={,FjȑիWCL@7..Bi׮]zgcܿo޼YzI3^_y333q^bEIII .XjjUꀾxbYիWt UZ,{Ĉ#s+*W־ apNfQ @ضj*CL"H$D"H$(;*gq8sJ\2 \k0ҙZUa. ({x5o+u#]p & N9Dt'聮G؂2{0",@>rcc,jClTx\.ƻ{dl@;5]v۫JyXӹn tXV\:k[kEB 0:P^8`e\Ϗ  ݳopq|FrHOf2g] pBN^>jf1WWZ)sC΀$nzH捊`!uVޯ5P&H$D"H$D"0nkoD$״/X~ڦAW!G4 ޶]l @"J::r W (wȼ6 :'ۗ?]Z'e sٕ܂s0iL]VF_c)1> Xn|8³\}@=pLgpPg6l=yd=n]Au{ )-#];PF+Ztظ5ʃ:cR0{t9w!֕ek |8&3g΀5z8,6$D"H$D"PW8b%39`\R,,̅+3' A}6n]",Y>A]PF=kuA^r)w(_sعp_#cӋ`kjk Y@Я.S%op P6_w#@Y|o|_SB}%2Y[8o:Y3$D"H$D"HNA໊'B=&x^OÌ(3i`^gZ@CM\GS:E+5epaj!9b&ks)5cfa:ynMi\4ֽJe ̮nLc|?A]-ÊhE 0ۂ/:ꋰe}M/ ƌ (م;mP>Ccg1k=9jx#^Vt@>|Xm„ i4QP ]tIM3,,L۳ٳgzvM.\gbb˖Iݪ7 `ǏrDD[X+P޻w/{qC}tttT]OwRZZZx9sWy^tA/++3 (_MԕXA pxZ*rw7xk$q^!^oþkAoic8o}NaG _Qgd|@Y +'lPKd8N]VwPrgU*_$GL2D"H$D"H$e%W^Nld pN`:M^0YM{ /By:a]~=|% hcg!kɮjf  xQT^^^y0kΝtא!CxÇ GW[ҥ^kr1~_zu0$:u `n؀ap->aRޯp@зT'ۯW^ހ2 gS@⚻}^h.J5㻀%_𚅼MMM, P>vYMeuP έ$GL2D"H$D"H$eE9:C2k3^sDC?ۿSsfu}9Z;h= k2CudeCSe~_Rf}oo\۬Lcc$@jQe333ٳvs y555֭[ #XVVV0{c.89-'`;p@fnn옇{nP; \! &|s%''+ 66&J~ʍx8%URTuFW\: دy?ںuk{Nlxbt>5ӧj LA L"H$D"H$ ([ZZl/evr oY~g$tBhn(TG>S=gQL{j=?,4,3,:n!0@fMc?.(y`oU332 s(}9l3VܐqpRnM"ap>y؇)=5EEDY02,2ueC1#$cŪʟaXU+wL@.VO@9 :;;~rhbꬼ_k&@D"H$D"H$Y=g#p;l(Yc?K?>= Aϛe-~zXuz*X6[L+軝*ƾm2uN;"P_Ӟ"&=[zwcrlfWzcu+Φ5w{93v: Nz0~2(cxI-l< "wv;{+e톺y߃2???yŊl=qsuusν Nθ'ʍkP2?sg˖-Aex~9Yyy9g~ (GDD`MJ~cbbp ۷Or.$N kuP {7Ut]׹y̙"ܻwoU8_qkV~֋pxBB5}97n @D"H$D"H$PNKYO^r (k,ƒZEŌc%:W_*iKLGHr( फjNpSG`}?'MImw اy~{}aCesm>_P+x@yI-`* .(\zW$th -[5! ٶ8Cj@iD52#=Їn3:omy!sW32>]8^Fn;qN.1ܟg4(3xdkS:Pv{|8֡: S~@Y 3]f (+Wy(H$D"H$D"5b]F9Z 9wYA}xM>)[ qR5( .Js ȕYc }6!k E(P({'aه (l39`oQe83&dX,>ke&esTVy Uw;:^v-_\ւ+jyyy==Ӯ͵݀2@'C۾m¾SڵJ fdfewWv"%p-ˣg#C|X2ͭ\\d}jd+Yb' P&÷\c\}YɦLrvުMlDpZ_ͦD¸Eۍ8֑sD{M~[&jܯ@Y b$s]UL U0ܿ:߯5L"H$D"H$DҀrU<&j/ pnWQ^dkI! my.t}Ptqcܹ5Xk9g_8MZ+w浊ž ȕA:ۤ8ߏ ?\%(k9c=Tʰ/(̽&2mg PF҃Y'-y6x@ġ?Է[EU Յ 8c {! }/ζnnp+?~,q477#hAyɿ`---_ii)̧G36Acҥ(:?{ڵK@D 0uTČuUGwR(`P;}?p@MyZ) WKgF>00P!G~W욕zA\ۃG5L"H$D"H$ (gNqan*4(KK [O57 7?ggZ/:v 1,8N᜙zy+v364rmXZV [|>۰uUgʨpO'ƲYqY-bTH༑cGc+ϱi,%=kˍjDù57,aRg,(`<=~I<_8,V#12' dcL mT>.c}9b`%ZX6*zzIA"L[8&\`T7C?TAdfNuӦ+1ܨ1?{gTfn4W6ֶV"$Ưk)JCM (6M-7\(.T.DR"uQf#?Ic`,]ᒙ3sfdp3r/R.Ćr}o_1.wF42Rj {Ț'.sAZ缐'(L3_h~"hˊhewcMKpڹ2_)/X%6rjqyjWI!}X@lSdT׹~f-(k4Fh4Fh 6hm4YܻIgt>;H'X{npwf57<C$#$:{ NKy翱, 1kTq [ ĶsFc!1F0JF ^P)(3.ɝͅ V7W{D_Yc .tgrHKo}\M)Cv[tb]A}\|#v]P6 j{]ox Q8Ik#ߴrA~\3;WӨ 2V/444clTB:6D" J$!{#X,"!Z_':yʹJ"P(u3j4rORf.qj_^.!?KhQm &Ѫ4P]XXIBb)(LA$v hE\ЂM@Q+f #'Y|srs'/|>LT]d2AgTbaRp/ Ep_ !ЃzQk"PHqdxM {I"~7ޑv=T*zoZT*%0$BBB'IﺎH$2AtiH/ \.[]@?kyZ *XPfaaaN]dlU\P^^]h+ YܯHU?DWY"8) H~9B~Tc\RPF㥇ʞO?މiý<+^PVqB:uL Vc!~*-E-MԴ@ׂA|z^ D/m]NM%Hm/gέ֔$gD (p1ʕ v@<[~o-RH y/M)YJΖIq0l_<+Ο/9-2(,^;k$3~eaaaacJH- f=i4aX27k[URsC#ޗ}$|lӞ.5ٚM3.n7Ó\wW-(=My| - ٻ%=U 2כKod-R5RߚEx׶-7q2v BNN5R)ni 8e$:F^ QÈacqA!?G$(3v &nVƭf) -2ad/4mir09/!{9WoXXVL8֧GqLJJS,|jZ5XUL8UzݧC-@A@(  G z!vgNn5诀3( L KB:UyׅyVع@ 7sg?ǭV+܋,7/Ï@BRRRRRRRRRRRRRRRPG@>X\rs˜\z1;yt朑&oY9hi% H ӨMǝV=ZY"w7+1!4_8h6{irG_E7 >s$.1/5Qv QO ,%ܔ?jy@>Kng3sL'iOs+~ @2F>G0%W#W,oJ'] d<Nȁg1C4^0 VUJg PAfk&ca \ *h^3Wp|-C`, >h G7sC@@ɦ/pZe XSu,+k*xϥy]E1dkV𜷫pak= @JePmmAr Yo7rԽvmPkti7w=v}.Ot18{ݠ̬엸ͻ-|{m|SEzLb`yRG?k&7Wѱ\"ve(2- J'/SzQ աɗ\?8銳 %gPcc,VM O+ (C1dLNϮ9Mۢ1.P. 6QdK]t5Oe!ȩ/*9E1H0hthwµcpMƘb$0`iʿ]}nآ!ژU 7fAsP3c mi >sWS}`2J[Y>:aVj*Gy1ڐ_DF$#DrԊOehYC['@Gn;A W!bZ|03/ggcQc-,,D,Kݳi ,qCVT]] H/:44PØnW]+11ZZZ?z9xpledd JmE-~z, :* H  (X;xi28N3PHca at<zKī=(x3[0>}w|k1 9qo^Of3Ttrr1hVɭ;;;"o83o{H1NSF1FR:<< Nq_iq/,,2<.͡\.͸gx^\3o1^X]]3s`l}n㹱73 P@<::_FQ'P^[([~ P w@WǕCքbccΆ縺GP>q5@( 2e`+P=[njj*sښ@coZʚe@e@@ P7i|}t9TS|&^mw'OV DZMsssizzHy~~^`s2sppJjRŴיo@Y C(=_מY# ݷo3?>(QUK"BML|]_g7uՈm+_c-^b)X%4z1nG^|>?&P`n4f3:NFv'Өj$ޑ@2e@@ P( eeXzŢVRTfY j'i&{Z!%W#=7{yq.e((`.rPo$# 5xjt6fBcզj@ ɂDX4DǚdL>wiͰGPv| ʞ˞=/gM9D.hذaHKK8P, ǃx_qsD(//Gvv6ǃ(3P&[9US|Į q>\sn9r|n|M>`y.'%Qݺ>n~wv]X]x8|ܶQǽl݊/C*딵f"W1? "ՆWy[YzmƼzP$Wбye""""""""r@Y+i#!?DShlA}M% à5B'2q.)6Ǻ kT óN=d]]DraDzDaO /7-<]x9 %Я8k6 ʪ+Iaw& i4K+ae a BIMt1^999!>]rEvUUU MyݙxM3z=9RPm:z+//ep.w^EU""n1VmmmXr%1zh"//OȾ\GDDDD@yeFySqpK"*k{yA(.xA@dVuPur爁2YDXBT[Dj'yxSz"KIOLl1.5O '+7d.FJjR&% sAּ%Bd_@*P>> *< #t#Ķv車4#ԅ(gGST*rX< 9{N#*+0m0Pv@+YBR^Bfy;%h( f)S(;8l1 I{CU8\ 9G+W@d2󛛛;}P}-([ F)i](JjHKWm(eDη}@y„(-[-FG]O}T([ʙ,|${OXO#&LšrH{ ֡^_>~QP =F!#K K')۾_4Ʃrw2nFނ56>4u w~ ]xhCc?$G>A!B?~-r`!$d( B~.!}rPP1(Cs72eq 2Pr-ty~/1Pv@YHEq^\U/O^''Qq%-aL 9GD (&(2Ph2l5UpL>y*̯83k2S,`hq[H[ ("DA^͐#TN E \TFF;=P+_^ákق\CI=`0g>N_?Yqw=MQ"[ٌ B!Mᢄ8ڀ @ 1B`@)IC Xl(:܇}؇k;Sڌv3uι9~D'$XR'sr5a<iY?v7JڡÓ?RXͿA]K>z!=mdK892Ň?W`~4Ξ6'G[|**'TGߝ#we8"9+/ZY^o3(ʗD7 ';ʮ GIzf /bi#~xlnZej/hQ(3b&ٺt\!?y>iT+Zu5 bdC6pe~kb߯Clqmʷ *7n12s:(-+i'y]?!(VL̞rskH|[gbso83a|B$jCX5=oA.(UX7om׾`<ߦzRXXUFv'6( H9xˠȑAo $ٌZvӚIzTj͘2`CE Izz⿆vfߊ9oZ{mݤK.g*u.\~WAVjqqqڜ ~8Y9c ;Ϝ*'YfᙩJJJb|~^e̙urH<  "(O?A9m|M84C?#1^U_!ɔketR %tc+|Z㡿\X4j߷e_O*W(-=-ѯFM%h 2$p^!rZ dr>w. 5#AP潃| Z?77Pa4jcio{wV9Өw[4͵\M\7faJ XWf9^2 dux! O q4{58AW憈l)^=ȞP "s!(/u?D/Dn#Aٖ'}"(,    !_fߟ{4&#v1]Lq}8(gFC5ϫtjL[A~fyk{e޸/(k 8e5ԋ4y;gbyrTl4X`t1WClȉp~ü !#ΒXg 3mKXgݸ2C}̞;d)!(CbkooWmmmzU0\V1OLa)(挌PM7??g&pb!A ,k +p(82Vsx?眞z Β[r3c/zmcKKK 02_[j S82gwAX9cCr3缚2cO.Prr2wѰW QxeƜYgʂ  b7ņMPF{ٰ+SC{i72\QrE;w7!PDEU]9DS}yW\@յU "(Y6VRAUxU_@E5hpȫ;{/qzaлRkZ;FWG<< ςDjjjD 3~<5lRڴn)ѮCej黆ׅO;>řOަukJe&)ce1 gk*<;V9J}t *]0fQU]Ue:/dU]Wb3j̱| UyC4ȵɏhlYgROEU+ǵ ƨ[5)B- tgSASv5j2?hO 0.m&uCc-VUk.ɢ-5hGUd̅F!qwsP^ӐXe-h$BhE-_ C|D'.4ʏՐ͞3,7(USY_AAAA!DZ0*?0MY{T{i 5&DȰ!C0 7 %7Bv 7r@v=whZ z0{}~h-k4WoPzu߽m бj> <{~#]%*t<P>g:/Tɭ("Ya)<|&aib%ͻF`WҖ.Ll|ճ*AcOJc]w-OP-]$w ubճe:'`'CWw~O}RΒbsr#b0m)xk:gity/)yR09GLP@=Яf_b v"@3ב\10n[U̲ӗ+T5$`'{R~2FV* VNGT*;?/3zh&A(jyрJ7B~ 2 8P(wE1N>\.7̾tAj7y={P+I*֊lQ{Ƽ&BH 1 #YP&ѧ_H鲽’|?Z/jMUǿݾSk@>[n 5"#c*7o}##/u'lj#/o=Z[QA6WgxyqW$r މ"&>Bʵc'%1;mO2W΢dQ:]L+*/&VVee3)\,;Jа`.ER܊4EΆ}yІƫQ+C]_aaaaFCAGcw BM%f1 &Q_rjPԥ}o[9z1w)b} ֊˫No{2#y ^+9GrjxnBL} 2DVslPAٷ-g Cr]l :qWM\0X(A/-0o<[ed̘1wrqTUU3DX6.8a OۇҪ"%D-F cƂ2̟@).\>ApHRkw@.c<YPF>"[fI^FV3=s"o>{|Ud162 :TPF,(UQ4uI2Z8l z]u&<%Q􊰼N'A c~@Le~kDS`d.xCgi/(gQ'ݑ2Hk/((:R Tʪ?-"B 1aw. ,(3 0 0 0 02FN Akd`Or|mS;)~ኌ@ʹpӹ6+N+_t^!KpfL.(^P>72HMW[weh_Fi:=D5,iim, @Zx&U_ !217(2hō1+E 5N¹ 2{-4ǜb2F$wһ8@P6 67B=)/O>UEg̙Y.ѡAtHujuA2$oǎ8? =:^Pƨ"Qeٳg2?;;q\tI-**5R'D1 #bysL]#%~ܽ{DF4 fd5Pdʯϔ |3H%[oc jp_iXeAaaaAB~bv)g 77$(B#ޯ\*z [4%=5<\vR;Wzl۹y⸢uU-%BA^:A9"2B,^Y&\;*? ѳ loejM1y Cﮘge;ͯQ~LrV|,)*9"$Xʻ~s&}yJ" {^A{Y(6wnivɭԷȁG6"wWó]pS)\gMP&eD 0DEa:9('Qsa91;d=0uwмw=]frU»𼱭MBj}\ވ( cBz3{?10(^e_.xDPnsO y(((W֞};FPEA0/ٵklQmm&onq ͛+|,0ܹ#f̘!x ղ#G0&(W%%%GLٳv}E@sw`;eaae -7s`JIoZm䫘f/-yvyh22_GDi9YjEj2[.+Z,4˂2_eK2gO*ݢFc:&CHh`}v2{Y3( D:U /kl3Jޓ(I%?Dv[% N[{"DScLPvt2N s tv||@l>Cj\ ]c~Z\A":8_phi&{E_ V 28oܸ(w!1h ӧe;DP>̟={:pbKkDfΜ9DuuLFK0p_ԝA\;7of2}_mٲ\Ft~)3߱|: R3zeG/bcc_&Hw, 0 0 Ks('*JPFBB҉ &+ӛ>S ʛ*0rco=*^{FU4(F>uF~倀qy(Q+Vp$$%A 5+'@FIxHS['w$(LN!>8eI]\ )/ A:DMbma!2rrtWoNPF~ BY亡]1+.˓rpv2'$ok<QPV QݬFKP^VAEK W\uoy )3t Ai/Uvh< ̂20 0 0 0 Ðeuv#eu6Ⱦ (_zssr(*;EamfTB\PVF1(FCe+]'KػB2ʠY[PGP"|r >~gW)?#G[ !jW|з ʞY!drMe_b$VGz]ںγeW0W_9AY׋{B]%zɓJ{EwAWW1|p=E$֐gB#sΩ %:yOݮ];9vooo) ]v~e;w.F6LFK0ӧO,[L>_|YFó!EGLWF7oIׯa!C4_KsXcgW/{cΝ޽{H5hp4cYPfaaXP~^Dl)(>^e:q%⧿?(1=e7KQ/|lLȇ+.?ֽ󿟊ì֝01_i͂ԁd_hG1jq\1QNDR-u E,+*(ר Y(]QVkR`FA*Z14Y0P{2} ,u7F{aX{eD_)&KP>;!hҸuU-3>Lxpb{F捰F ,(/ K!~I'nϬX7g@7Ey%?2+D 1y\ L+A7%ʤ3e}YPfaaaaXPBciwW+ҳ|e׾~z?Ee۷^+ںU( y;|d(|M{eb_PnJV1kpRQ8 5f O)%{&-6r>AS. Yߴ=ȿ(<=o;7Yx_xO tޭ0s`b]C1 9Y>\P< i7-w: i]MJP}-EƵp(A"4cOYÇbŊgD!^پ}lXwݺuFCƎڴiSo ŋTPܹ5IrRRZFt&L5kfLFA'55Uk.)?/^XmVDPPL?~8KWFѦ嘸Es+*>3|\lk){))[9 (֗2ϔeAaaaaaAپ׷ޤHmO6Sj[f͓y7 r+ֶüG}ej#(4*Xʯ fp9zݺftj!ʲվuexyEB.%\=|xP&X=Q>]Æa]{k)(#^uy%*NZ#xv ޷jPFNzF&(h 2ubbbPf "VWW+WEy*(bcu!r*DKUQ O>z猌 \_`ԅІ|޿3Ag%?-t"<BL_z5KW{}uW\XEmO\//MP\;XllV%{iӦX|"v Lciaa_AC;~^(Ay(W>+kэQ47ܒmUVRиr #F m2Ȧ(\C ;Ž]ģobt)2}G&KmS21sX#(M܂rHϷek~WZ>W,8yMkNtX?_n]ELl1ld*ĩI?Qz퉌;841o =edٚ8^ 3Eʐb!$WUyyo/ΊYacF 좼: 1XٴYSMP! HQ3GårHP_e%ޙ>Uua\. \GqDKF A\ "D˨2FGqq88E$ DR|~+Թ\MMbxvݧίa`$`[ mM@ڸ}-kY.c<1 8N5$ ߿cDDjAYRDʛg)c^*f/\G}epKNa~!l\ `ZqPu\XȽۿ]CswX o/XK̤%LĆАAy!qL=Bjr2'58O)SEiIp%ʏcʳFDK7'Z Y/'~$9w32lc/1xbG{h],:.GD9OЂ/Ke筿mjSc-V]oovAelcξ;"bmi)6+(DWJJJJJJJJJJJJJJJJJ- 7JhQ<2wq8:0඾&o-.u-1VGi xSw*-Xb&_9{݃gMl"'4gsXP|@مІ}qpv?''Saa&~G>I8WXy/ͦpToG4f\ beH/GvN%l8w2wjVk8/YitpF"%EkW<;-.˛fܘ pPW% ϵ9p%€' )q\8.s01E .pwrT*bu93Q_梹A_x|nn dk/Lswׅpw;f1\[&5[~( dPmND:P2ﬠOC7rtZZc'Ц}ŭB~x*VQIm>mܛBq#(CGp݋ykyJk%F➨ɧGkP4+'}PDT0m?AVҍg9|*VmMaʲk$G^[@ŝ;o輙NQJnЍ^bcJN'/mnIɴ~Q1Xe=d>{[uś崻>!z냶PaNУ_aCŔqiWP|ner3 5#hfuQ @ߒ[>"76H$fo)M( `/ ( _Ȣ%.V"q~ypl _5~vx:_G3=2es|?gz'~ϧѻt}+gѻp"70_9A(JQVVEшVz=*JA{^W@Y P@,P(ee2 P@,P(Tq|"Q1Xo5$Ѫ[J,tPӢ⣥*QtQATFDO! Mo|w:''~LA((SP շW]q<0P8@/w(أ%#jw|>ٶ-4LA2{]%`)0:E7U[ Xezx<|'F (VҢ-hmFױf<҄U(ؒ9Q7m53Frڽ CĻ5@]2Ly䀈|iU^sm^_=B{"fGƒ!(eN[ۼ3_(jij Y~}՟sDDDDDDDDKɷ\#6ގ+H cu%EܗOџ4clL&ΨJ,5.QU']n\!T)uj$$%;>Ggq=UרnUoNCr̥gamQ2Ku/LY  -!a\i:{1扄LsOH#9*!~X[0Wp< ۽L9.^({p_#&(S Ei I ލYQpe""""""""bꁲNCQ^N?Zm -J>|2fsVߴGr(e"""""""" @*؊QJDP2_O9n%ECt6Edepc~&A1~+퇐dp!ZoGyب{aN7qSE D59o'v~6LSZMm.|=VnY>Nt}VȺoaʨE+z fü[#JNG (UMqYBVc2   +짃oe.vUkn0+a~0PfLDDDDDDDD@y&֮NaұݻDd|&q.Sfˁ;@2e"RS2PV T!v7W|=YA( ("=8s͹&QKc]5ӎDž8Vz|K[tjl{^O1,v\'g{Q㳥Ή 2m0PllsI;%%(ϝo/{gՕqX&#b" 6"" (QLub\b% *( a3" &qjfj23)Uu^wGej=vWtͻ'([XXbXPfXPfAaaaaaXPV(r-ͱu}kgLP? U%$~y6 7Aߚ]G%._@VjnLFaY<?qoQPr3P\O)~[wI->>Ѽ?oThCpzh\hj -k1 1'g'q< hÕ|Kh?Y\ h5 Gq)A!yJrĩpT^;)&DP_2]S:rԎvJRp}^u˫k^_, m[q0H|g|YP^+X/ pypFM̷JWieaaaamkWCORT%a$;[Pe\6qT6(UEA6!F摀,}=$c =Amw4n +a ?^7%]]F7} z1ZN\@ϴ`/M,戾ٯK`V7H1Q'NyaA٬"fvc,U*:)`>mV_!bms/&ٜP~Q+3X:la~agmrAy`VxCXOr믅z`=n&Պ7T{XHio{};V2fSQQa9o߾QSSZ۷q1,^X67 yyyhllD[[ʰyf 44'ODmm-_~Ç҂{aϞ=R{8p%%%ىf#%%vvv tRfjj*QUU%Q14iTՄigΜQ_KDFF"''EEEV ѣ{FEE tJI x"Z=ȑ#u{o4 w J75fZaaaaf<Μ^( e!XG< [_\qpC}זfC燠m}$;4,Itb4 M2w tW&>P{esh$>+\}xL\."s ͞!禞XҬ\yGzyƍ+!+yr#adIܖJ>$"#^SOe\o32 DnyU ,(+ j%JUiIk/ 0 0 0 0 ]vNNԥǡZ"{G?uۋKyG~sڴ1/߄kp|3̬qcx~?9d!!AYs~3I4(3Oҭ$;XFr fV6FWU&-0(9TGLr%,B(~$!SܢM*(%cY'1*-9-7xL输 .(--+WJg>00=zww߳+ zzJA'G!ͭ[G1 2B,pvv~e*LgKh/ 3y1^)sI5F,(_+ZM{Rk茄T0 0 0 0 ðY& IX/G෣zQ̮-k)媦R3!%8x!:oߝفtdꐛ}=֦'Iϡ$ArW&`k<:.4|8Q"0ܻOj[7n\ܬ-bmV^=zzyP~YeQMr %>hq\xy'C&֮/]" j]E5>ӏ}bR? ~jq3ˇPMeFaNLwH3>\P>4=1͕Dݽas4׺>NK`diSPBr*uuۉ5<ϡzqv˛faص[IvG}ٖ2삲ÔgDW{;lrp4!.;VAI/M<9W8T@|ҥfgT0hss3KKKBdpp0 ? Utq6mbccy^AAy&J2T;h4,11,ވI۶ma˫޸qpF ,>> 2/))>}ՉףkBWG$W *bÚYtt43J̰>̓fmmkR|7){%{~¸`_QQ{֦䷯ ^azϐ{@2AAAAA4؋5*$ң=+#-*Cx3gniEaQ# .ʜ'Gqqm33tBuM͞qyAڣma} cA%Q#@mSosp _lN1}v|/x$fN-!qP9`TVX8%0(Pcxb~62N=jk&@Ξ!K~Bm,TU+v &9W\P={50VNá#ݸ+04j!PA U]2|ek_]{.X'5]{reB!PXuƌ8wz:?PA2kN:ǿ>>|y(_^=>MMM2uW Ĕ?~Dz$E{f)g$C8CwzM ,><\Ȭ81R~ +S^I?#{が ^.@oIP&    HP67]]X}l.M39Wρ~/h, "(PŸq)=6c~.^wCU1 /zۗ,/h[2 {b?9!ƕX 8}N/~܎glؼU`Eb Y>Aٙ=?YTUvOeG؊ CPO;3 2T0FSѕ 9T`nrY+';u e.'41 ,8V_y`Sl>-13!'Tbk1&!WTPV *&3TPĽ~WSZhʎ4\[Օ´2qՂYwofDIWN}:XBm%+W&AU`#̽ <⚠Z PK(@bXqA$RݻwXYY1"ݻ>˂2rE>^uu5|F?텅ʝ;wQQQX=d͛7C g^Pvqqrrr+U*= AAAA }!(HKZ%(g+KK6+/5,d(- (K"of GP^_ט BvUAnڸR( '/;vqY+z0>'Ya9prxD8{s]=;q}}ygkk>^FϨ8WҵAGؚ!r7ݮ)c?,B{䌞`\,ȾXxXBD;Hϕ.B^('IJ4=QOјQ8ftp3R^P^y@=> /|s3 NjD*(}_T=}Vntؽ?T@L,~$(AAAA$(wIΥlנݬ$}O\E+\ۯpQ 5 Jo`cTvBiFm8 X+g}n ]6/㘧ϳ8r BC>P٤\g Bay~f3P5ekaiy53TxUjsg>Y7ny)+-HW<~ax&A     AYnA٭+[ZSƅ3޾Yªb}‚PPvr(-(#OգjnocYo|{-Aҭ7I F zW IgZ2{*ܫum99+CX{W'! LJbei})(ff%<#2hEK8#wXz2 Y.phߏOcE O%(rZTw#0_wBC%A     Piu1eyՅrFe+.o_xȃv򳧕 C%gh!6 aie/Wc5tJ6Qf~D;QV̱ b edMf9d1kFIձ` ,g2s}ZmGW %e7agMPyQqI ъ˾V~e___vYϫ&׿ōIxDKpĔq>Eυ:,RNq\q8Q쫚r}2&q"Wc ~rsX' f ({Uf@Pyp^c噣!(PpOtF ʗ7@EQx9rU0DDŽBi/Qv?$uYXOPϊ _W¾[[K RΗe    w-(,T Bd^ʓYFa6 *(#<? "@3rhns C%fA+GFV_o4plrw'؏Z; V1*fØvYS~W.^lX!w5: k@_)n}~ ~Kkf~f>?vOus^v0V^>.*(;8 vK[0x&+S9 w'(jo>EPL43˖-ߺuܶ6c>)AYRA_bŬ#ߧeek9s ҥKP ?yݿ5e { }9qIP&    HPSPaX٘jǡA~ПI>c kuV.VI9BՋy\GNLJwIiJ>qoNz#65gҊ"sZfSS&3O-(3'\#Q*ݽWQ.0lxξ_T.dRsfqW4EPF@mxŹJʽUƳ0|h} (3|F ʍ ؍EbѾǥ QVfR^B䍻.0a,k,(K?_,nH߫㷗`B5qYeL2AAAAb 6(i^XzyjAm*ۏyl#Ds\**!ijg;9˲ Sm^Qt[^ɏ}:*AX5ۘ]6f0 .%;~n-X 4IEA+?]AE* ^6nsq{QȗbDe qMX.ۂ{ڄu' *)V=#HyAY\ϤZw=vZǏYLL R &嶴@.T5tXX>A%G$ Wἑf>#5Z 8qXǠh4)wRU\cٳEDD𿵿c 1YPm6Aր~noʄB~u^#CE_tu,]:g\X$++aY{}&(ƅ X4MNqUu#Bԯ.AP|UsKCs6Hml0vre}&A    x!Q(~܂2VSwty'PdqㇽΛɀ>PiY6Ay& X'w;]mLLג#vr[l3,J#J ʲ s\wY%>~ xY ;O: XA:t2ZhۜkEX1<-(;vcpBm (_rX!dKVΜǵTUUy{{YnVVVe!UVۛ󌐺:<Aa?2 r'8_[}9rJAƅ{f){%$߾߂yIP&    HPCPJ5)wV^[\]Umq#p!ޞ +Zb_dA9_6^(e'Q22}ҳ.(5T>7Q6ԉGq?<1@]\%HWXAzAYIS&ܻ Fg5io/ҵ(̆hycMQ5$35Sj^2Rp U@}}AƾT Ž?)j-$hAdeƔ^eߝٓz$fwWNu%r`?;}u"r-cc1#qQ,sZ|ggc,ZFb#GGI#PQn~qz6`,ӵ.ofc"aֽWȦLqŭ%8'"<~>k&Lpe 竜,}u`k?.=ng3 Re    Ⅹ+[RW^n:UBik`yl9b1 /:Ɓ,B.\M c9-W})2ؖ:!w5,k>M'r97@rrG.Y KseDg!c!kHJ&,rce\cIYT_b!J3lg(D׷oX0"w^ 'Pߛn\gNP+hw~صkxNSSA{T)JP26: Uyoolljܯ;v?{gd+iWƪ. 077mKmk6mn*-!G)!AP]ط7I$EAQJL]$9).N;q@Icy8=RvtkZ驱e.y&Jeee2 ur^_*3%Q+Ԫ$kR\.kyyeaaaaeӻE871.VhiCߙ+/ߛ)V_Ceeź()Oeĉ-p;BaW,:<S.G1ӏ04:,MAAyv~xIMP&sedqZ57P5!6TWpX_Dꀐ|+Oz'xPU'4(L7dsY8n['?ъSEf#$(4}>|/b8'G|#]x矌LZ=h1fsLu/cfj34A)eXP&P%1F6Q{\q+%T펍RꖈV-b$(L2LK,(*6ޜ/TYPfaaaaWADυj[& q@q4qA.MŨɢ.B"3\veD  dxT,`… =}^Jvh'z^?3qw !>=*|z:P~voVk-jp(AlHX37W>Mz:E~t /}ػ @Ӧ\]w1|z^;z6WCE2Gz vrXJ怄U k@_@e`aN7 շi?3/+ckguby%|SO (k8AbI3 r|1xCЗzyWBMVVki_= ո2ݛn_e /?I>1Sfk] >4iC K({o7Ns sR[ndUMӠX,BV3o6tl5J%V.=Ey2F|B{t,eVM?BVEϘd>((#il_Be"^`*)׺\R1QXBұшL&JޤR)Bp8q/DU(^[9]K9>|aaeA$Qْ\ƕȈht*(\zj:!jE)C`۪U?dUY=E L~?A I?I#;3|V^k&/~}rbV:JNdh;A܃!<;*(@HY'ej0RP,u"jb ju{)Pc8Tidi\ttUKPF.~ue󭽠?+Q2R|2oܤ|ΔΙeaaaafS  Y 9ݲj9 J";dץLуk8X ABEsB3h@c5O=ECݏzGq.q}M0o_ˊ۔buáqn{iA@ ;/{~ E3"bllHU<|[]cՔkI1 P~p?j*Z B.pH2W^*:lB',RUJѪV&>AT)ylȶ2^'2NS3:fSdsm(+ UާoIy3QU)d/m2='h}otm]%>j9c0l3J"՚m1P L##XvBjЬOKO…kF }w*rVHN .@4nJ?-G- )>oSތ}$hJ6^z>C>\tQBkNje6Ϣ>\-򕁤u4_u/SY >^FpV%=QxuE1-vӒN+mU~h~35)⳦1&c̼^>pDż^U?ui.,(ot RtIO;K>+L}f[NvV=wH^0kή%խEA92]3Zf6$'M }G Hvޣ%3zт277Tk*Mjzm.ɴ Ðd2Jժep~LNNJ. O@i .AR!Q/BV7Ϩ%HY*bp8 )YlccC :X,ֱD"!|ŸZweeɯX~o[[f!Ƴ䌜[~{D" :j !B!Bۿ",(/K]Pf?,(3ݬ2!B!B٭2?'I> 9@fO,DQ 8{B!B~w.Q`˞rc@ @(T N&?feDRT${cYb1jkk @ye߾|@YKڿ;SzbE#{d<*C͘;sϞXj("67DՙWD͵{EySwlHJP(DLOOퟥ0@Lyv8iiT*m) DZoIlई 'dPAQW'ןD0gw<;iFRb/e2@@Y ( @@Y e@^TCK)uF[WW USs-fY2<_u8޼k5rfH<l6 ݿy7e2;ڊy˥_u5nc|j:}5GS,lǗd^NTk5,4Rwܯ'g#֫v8ާ]kgswhMD,C1 \63nAA,zG"X DTHbI4{5**vh=;s}}}>o'5aF/fue\YM_OMFkF 2N߭5D|J8ݧ    >f@Uhpw\uE]`M4џzX JAi6m|wp_5Bu*a|)?6? ]zo~`ڵpEhjj@MM TWWC||a$(wPڒ0Du555pe AeCנΟo}>=?g -((K* Q,UP ΎBHʘ7e<(HP&A7Ae AYDd4zA-(#œ@ iSK x{7~AY@2AAAAѡe _hɱ 8zxY( n/ UpibP6 {`7WrI*he J'py_`8 =&` X'@c,Y z /^RRR֭[\Rvqq!A a ^G2A>|8wڕ7zsx{e  HP=A~^,1SuQD^o䳺!AeIP&uÍ%AY7w7@< ((?Tʖw}ϳtAY@2AAAAѡMݷ4{%Sڱ4dlYcd9Lz{߃>fDdoe/7*\~ay,.m'&μ[$(8ajx>(yH/OWN yAI.?<8gJh(ʾlE1ԴI41Vu98qkSu`xxs/E4N]1:prp(6ټahN:T5+gB~l ,fz{ݠlD9 :P.(GQ|8j΃Y b1T,M)Ԫ@(HM_;bh},㻥pal?"EP9=tT:>P ΄OAyܫg`c_?O2,*d!l?ծy5BVg  AAAADGY`. gFIԫ+;4|w#֛*?nބQk{^E]d뷿{ Skt=6-.X \~LJY4mNR;]y|B_ n%M]eyue#N;8GAaiߺ XN=`7j^mI Xc-#&}'ՠlͯU:̕=슮{`WzlskzFl[٩NUU \in_6`6 ׷6 ˡe"{_Caco&NpI}6477@VWW˗/H2($V~hh(/"\=vw܁'OŋqaeѣGrJ^ž={˗aQ͡ DZvZmZ[[c[$q={e{_9/5 CǎǏb ٳZyT;xl oܸ۷ot{%$IRfn`zj8uܿ}93g@vjQQ8qŋڵkO Y9JYY=z%vͲ>q6nAAA$z \3"u%ܟ:233a;o9kA=9i$lʕ+gy59KI:A`y1 t J>o=zJ}{O#Ƶe{=~ߑfΜ ߓ$xA ologn@P౮P,S5HccLqne& )cQȀ`|U4;zL"<|yC[}w*SRRmSSSYٳ_P y>G} Boݺ޽ ph{&_YYW^ɓKPWjB zm{@faE y1cFz:uB^a?K>g9AiGS/_D٫φP872ILHع(Ig<^Ck_z3L%A7}C z?^'}OJE~[Ϗe  HP (ˌrdX0/gan.X&~d$//AEU#gNtMlܱKσAdJf ֍1gV)̆X*z}dfOԋEn5caxBYFuss{.\q6FU_R, ch% ƍo1Og\f=vLpF KrxL1G Iq\EOv^>[e};}~pOQZ1pm-< -c,_sIoJ5(|p:Q]"c^sy֟0jo,=$f~e|dødbQ1o~/F^?yquR_ Z~M(^E  ^Ŕ/}Ie r\RfWo7`::by9ln1Z󘰁p1o2KgQ\$1 LHbYj.7/`+˼_VVoQh:"\{x^x<JYj53拎WZ=YhbLAAAA+(zj٩ v8@<04~"*c6ˣ2'&.mf|Za-XS9_Y2􋑙-HM?Ge.!k@gX6aL{u@OtPv".QVK"mc'F56v7Bi@‚ʁ yg@*,E;`ʮº3(9XO~" fS/U@afTv7 V+Yxަ^`oAfl˱{BKY#CJʴwC(]2[o&u7//O[Y&bR1pdd$xyyL>aYF8CVlMƏDaePekk o1& :w cƌinnnjUQx'|aaaLzahn\pqqQ>j*̿zy, AMEc"t-l$''GFKC 11l,|_wX<_nc?>>><&_= CL4h }es%Kz~nn.ҥ ۃ8oSLc ,^էOfwK{"SËJxVV/^,VWX#vOh,> $╹Iϧ , 59KI:נH}{`{Z~!e+ z0 z~^'}OJG~nڴ Tu޻w`{R/  A)(ۛ ,34b WUKPf(#W(b"(rߛ`\ZHye6VyscP{&B 6,hi?wTL;vn/;<0璘9bә@})& Oa>޺ [#y"$'B2'1oͭ%1WA*~\ &b4  _ם! ]{yyGJY- 15xxDj,0 ʃ<~.0ޑAL!:7a~L.x\'FF#(j%0(ń@XdʵS c`t EwK֥%)7*Voς2BP`r./v&D O/ws)Y Nj k>˂AXC9jy =WMBpWHyIK2AAAAAK ']qe;F$di-OSS2By e:` x,_aFsQ]P>Z\ }'٧_1پUW\ԕ &=O( a3LVQ cU2LXWu9¹vGNf>v1܇EPfѪDr*zP})(;6oTWh3^ӮQ*SU`~b9ďEDNmm-[r222xievXѣGo.xQD[PNMMw=66;wxZϞ=Gf={jyNNN\d₸x3C-ٙK,seccw?&!!֟Hy}c⅑G}mnn_ByM|).P^~?gӯ_?>&Ϡφxt|~/ױ}ů$dx6xyѣLi .d qr]NkP|,]SSVVV2@+}zߠ c{ߠ~{g_u{R+}}xǻ|rI3\AA2 ʿW]frgop3SSpt`j+Փ`%+AHl%8*I]Wt} Ybp|d.cj9J4lKKWE<ʮ|Uu'Ƿrj:*#, RW9]Q|~Reae/уw&&c*qd$=[QESɗ7'A%^:}~"<<JTPΛXDcީ$(LAAAA: +V( <b=#]k1-z(OmGnjNp},nQj'\`n@m*t3 p6u6Ѱyu$ktG6Npic8\ah}FN/&(3:8~ٰ+P貭ddĕf0Y PT L fe0r-IUPVvyELfAYa=j؍XvQͭSxZв6] !(s\i/>യ}"t:~4HLLM~ӃV2Ԇ XJ2̳4W9NP'uJ%| &xIP zhבl}W"+V&v-LA F.(sz:Ò9XVee1e"Eݰ ,EA!"(#߼\J]p/RcSs'q  X&0(p]y$(|t),a,0nV'eM (˻Fkg-b7e}SG0WTPΌ gH~Dyj?,ͅTz)/65}cp}y5 tω,}JVg^AY<ҥjuuux2F%&(ܹGK~tt4_tдj*s石<tK.. !bԩSy, CA.O_OX,qQϽ1ٽ&$߸2̕Q #X> jyuYicݘDb7vyQuws$֕lc_yiFFruد  F#(o>Kʢ'YEr/CZ0IJy%$(LAAAA: ʋT{, d%\'Spʱx,FKy|zz} (\"'c[aY^P#Fc;"Ƭ_*-ekzZh1nWxQ$P#Xj2g}03PqjWxA & Gci}`_+:V˨u<0['2lhV&r"5uZf>;oq6Y*Cݝ5h;w8XnʕmPʕ+?w\?v^hilldx1}"(3Ӷmۦ}oooLx#jj-,,ѣGWg}ݷBvaca7nNEL5j81C}_u4QIlcl٢,מ<3?ݻXyJ>g {oPPxWUU=^}7~בl}Iّޯ AAl86ba)1(Șe L񉱭-<겱 ʍO ʯ7^_ %W1Fg돩ӺOWf϶W7ay\ebsM8.iktĎ ʼ3{ts2e+|NyEX:b`?rwǨ !4O`#2qQ? WW: Yim<f~uD!(~Yd<7pHX#\iXPFbͿ,u1iz5"O| ɶێ烇{CWZAYUc ƺ̽>rP{8J<-({]9AA&s/KGܐ2I Pvkn (lOP}seɓ'r0<Uiii>|e///VѨ~ agΜ[$Xz3,<-11Խ{w/㼈i0aH2xϟ#^K|~FbnncƌA9ϥ%z^glpaHP>Yt=KWWW[:lwÆ ʲٳgcڊ+,"سիW܌egiHs6,}OJz*<^}7~בlY範:{RN+l]HP& &(m,_X  ((W oxP2>dy(l ~+//'>cTv_aǁO=qSEdsA?@raIsa=&oDD]ub1Zmmq!Jѕ5my]%Z8Q2̕q ʙ,=( noeQ"`UJ|WQ.0?JI)m}Q&AtDk4p`C[{z/sFWlT/#I{&ܫhXO<^ +q.}T <ұ'l`9xؕvf^]7ٓK^Bep 2FNFq:甡ej*.nPkR:e0|-L]eƍ5(YPDZ'|𸩩 @#mU8iw0BhZ|[嫾jn޼YM󶵵[0~@Z˗X?33S&aH:zrv (Θ'~60#&fG)S{啎w~񾮃,۞LNNƴ={@`` V} =zkȑ#)8L"@c?wd̻toa[tN/| bƺC"~~p+򽧧 s%z TPASCd߭W myuA^P*V/7nnmo؍r;MPzg݄FUx>okoL!~j,\=A,}}IPb     ʭ٧ MLb:̯<4V,imx,ލJ5&CF/(rއlhlzAY_刹lwl1 ӭWPCtq,%RW +H`>R|t}WU.kQ6ٷ_bYmRy]ZPFl3Z+M{ky?YPC2VqKYMf[YY?$2m^(ݸq-da.899a.xd={ [z5Z/F ge>gqxx@_F׷]8"`a c>}da ?yt0됲2V\G kA=#x =%IRuVP>޷QPߓ`.z9ezߠ}.H|}-[ƯkkkI3\AA2 bnXX{;[ KW_n%MM|s#at{.{b2p"{Q ?_ $FǍSVYy'y^2v|}/-v. kQr}G¶+(,X"y; H_3PwJS_PV:̅z-UBё3Le2_^ob֤IX9v.اbܻwcc#1*AՕQ6k%MUV+9yb#o,b Μ96߲e mhh@!.ïE<ǎkAYa٭[r ۷c"ׂ,{ܜ?ty,(PP>޷QPߓIߓR_%%%si* FC\!--R[ҦHE^r[FӘh F#h" Ձ?s{]~Iro6WӅ\.ST*}6`.C 2d&dx}_]4ѦbeNft?*} HǴq ,|̕`[@X<26*?(wk^?T&Xz/[NeVq& !TMKpy<&r)խٴ#61}rU7Hr\jz"]tgىQy|W=<غr,v]@N=磐6E^_ߓSuW ! BV$ǵsfA: @fwfw(# Kn)'[lP"L1U;Sw!IK3A&TWa A|'-`3Æ .ô%VIg9]e}U Pwq|qDVtqfYk_1瓸NNmgdZNa;`q3BhEXx*J9CێP4fT+@E@m/u5{d&úJ>(Pf=x>۾ g;vbh@53{^2wgM+ xĘk@$6KJ$ E - %$48 :dRPvPKTK:yޞ r=V8s|YޟOWZZZZZZZZZZZZZZZZZZC"-˅/"{l^Osuo0>^ (O/ 9pk9a}ʖiLZt/O&q8csB-'_r6\1^ |<[^yD]2g{9Iǧ4ufX`* 1~6v%/ \17bF-{4j? Y\'2<;M[o :ϛ }Xsp1W0iɸ$mros.,ʐkY'-пͩ 4}D<נOMZ($r@}xsbur-Fp7 K߰敾#QÅ(Arjs٠ 'n`TT#p2D3\x\gE2 rY:Iq@ ɱs5|v1"񠈝ǢqR)D"H$hooP`4"9&T*)ȫ7hcc17Ns̵Z2 s6z֐#3@ igg!!gh1ڶMfS!. j;\MxG8[iww(r У-F~#ᾇ*8(MN*S 4qEA׾|bR}dwtϹkQl ^Gb~f 5OoL̐zuBe{꼑lZA'k{@m@=Pz2dٯYϾɁ>:A>jY ݘ&A x)BQNNL%H֯gg]Qtc kT}ADz*A ޫw3HBN;\/8Tiy2>=DڬJ H-ZWP.}EPAAAA* :|)N[UY)Ot+n5VcUP~y##Ȝu,t~a||Ag%p0WB|fXj6lv7B}LfA@< e_GH 刾Բγ%dӸ:AY.JT-R5RKrfeo#kAiMz^&8xGuuu`_x@YսL^f q|g(Q5`(F=oͯg_u [ʄ $/gMl^L2eʔ (r̍짾v7jc6V-vzc誡/`t -r-Ok ppLV:w~;1(|Rȋ4)VWo U gWrGgtY\E&Y\ڢpT.xanpc-b^ ͸ݗlںZ\ Ga)'EU1\sӘ%X"Ljz8h$\Amֿ=~bP2{}rF;+/8",P/tc vdu== f<>pPWG l$€15ΎqGdfA;ً u/WOѓY[l{k9-GO s^U[nh=:Sױg?}bcÅk?lEsT~@ZhF7)2IKG7g8V/߫YiΥ( É>1"hjՅ5Rh7A(Qk"DLDdbUF.!=A$Y7 <:7&-%%L8]([uee۽d;s-Lp 6E Jiw֭gη^pGrnRoA6;zhL@dCةg;=rmk%9<t^@em׵cv`ZcPndc&vYmzfxO@Y{ phc2 ?CbN;c}:KW Lsp9O\І͹J`Vy.9j_gK\'{z!@ιr/+lk9ֲqe:z_kGW0ery˵ڭQD"(+ YҮ Yp}ib_si]b߭F@ -t\RD"PȄhbHe8ƺfL&C{f`W+?_XhTad7>\&81 M&&&X,r k-Gȡ2Ȱk'Jϳ҉}$.k_[[Kk#űi.YϺt:|>OϦ6}499R)VZ(2Bw7@9~ZE `[e}ή笟YeZen7<77~ӟ)NFš|y@WG2+Ne\qڅx͹ GOz-(į4a'.H0IRWEt*Q^X46R2LIӤxTBzF Y$&ԊxLsht|_swúu<0!ur96%ez3֤3,9o֝OMc).W}i./k8xW/͖2βuZzZZZvS $Ўqlmט,n+( p0p\Q8 ssssx<̃ $ eds'߯yhddD===r:d;e Q=\)t%~\^d(u18#ee[A>N5+39)]1^S9N_ ]eqq2(( < ;%%EiiiW52gYg·#^''s|̿Ef,Gd78P(d.Q-(nW}%=ߤC GNv/٣4֨D{즠lΊ5IFA˚pi`]<.LAH@是ڮ@*'cz(n9%尩(('gO_5:2ULnib#|24d=6e$|`kZvip{MOSWo/:Z-X RAc,dJDesBplY`=,NE6D"t!.ܖ`C˵~_|9瞞sǠ;4:%Ι7>_J7Jݝ]K8d-qՋw[#eW=QhMšmd6ي+Aq=1A`{`k'<ˡJaH1ar0fKȜŴ>}C [_kI@C²<ߕ`oMTbdd$5zccc:,&Y,|L&vJggg I|8CI:'wkuq=G!2qۛ{]#$_1o333A^^rssQ\\p8,`l| /8h{MSdjkƺՅytZv/%[o /LA8 ʟ^?5Bt_Ǚ_My]% Ĭ)E;TFYB2!"]QE{w$w2!B!Bѕ,&g rGGW܆ǘWAy+hQ/rf)leMZ|i˂\nbfe]ʦ 2fѤ{ jx&$L@Gii31r1*-$>e!dc] r]`>ld|&D6rDu+G|>yblLAY 2nZo.y {v(εG(Xs8V;="Sy5Z}'ߩg3 'AY?/\UN %('|Mgc_}ɝ.PP&B!B!3Y5{Jc/?(4P 8+\hh0Q5Ϸr2`\#EJjNz C2 e@^ E_RգߕѽmU*q?wYpшxX*/]x!0k0m #Ż+RvrZy6((/,$,i5]0v^^=1٘#\.uE\t:iZ'A&SP}BupK(r5J1)kݏY"($ bYOg䂷c*EPJ,jY0"| &mPP^\A(!;(on>PLAKL)"WdeeQ0SVKh53lu!(J,",$"R4$lH{PaHܻ)Cؽ̙3sp 08g7ࡱf@h4ǡ60|ֱ@Y>0 rS=e(o_'DĥB=2 ߲ >=uk_(HDp_ldƪpi"B24n+4IlCw=Zgߪ{ .$U=&z1-}Vt:gyW+y؞,ƥՉ鬒|@Xc5}BZF(gMvMݐ=yΌC}[?V:l:3OI"|B"]8WkTJUJlD>;JhlrRkrmKQ$[H'~53=V홣?VY6qӯBxK7+VJe5#|ة욑Q(GLicPX"6XJy\?՗8>>Z@YF{,ֻF$Vl"d:'oH{Hueaaaa?(&/E=NkhvlsЩO\.~$-w[egD~{q~DzGmxXizCykӎ.GaF,{dsGh /4H"/H/Jqvc2ڠEx]NH2nlX;8xdx>އ֔/7Vg7BX(>[u( kk ^[!)Ocze` Wf(_qB}źq{ ;4lN.{!}_ !־?/Oyxַ(Dc 5j)ia b  D6<\ӱ߿6 u.XA8B!w,lud;HǙc ؛s{0d@Yh?*ODBvݝQO%ׁ7*X¢|wE׹Su$$@Nf5(kpo&ل{e?[ L*rWwT|~KGaScEU=>ݯOGqwGcdLs4,'Y?38;(2SS (/e-;sռľ~;gPfX,bX,bPwᦪft|9M8McQ[sхy#w`ݖj슐qY{m HV̘8Oķ.#-ԛDձ1֌Ppjqx`لJ_󼻢:: TBɒK Վ _9xG+0xenoo~kkK8H$ЖfE</P9>.8!L ǎ`hWHOJ\NLMMyE" P'2` sk| Q2'Qdt{0PV E###%Xk}]W`My=5kl;(.29lee7 X,p9B,A4Zg??{^F=Cs b0ΦezǙc (ˀF5إڂZRDK(NJܢO:yp3x36 $I3AHB $@e& "<'F!L 2)WPTp"CU|ǽo?^}rܦ~.5svU:'Ē?<|AGBg MPTK'p pL(]k,wgkx_Žr r圜, کzhǽKG5X%艥 }/X 6؀2M9! iۧ(# 2,Sig-3?gU_~hT_k ꑩY0QH;:N="{ݮob줖-a_Xʻ'“(/H=;]9ot1K~MCBY]ƴfw>zA:ofBwt:Bc]刎.}5XY{3y9Sr$ْ ʑiXB3 j3]IQΔl1 ]|遡cEQ璳cOr:#>w&ENV)[)9w!=9b0P9nnGOͣѰSvgJXgb@;șǦ衵> /`?HHYwE[@(Ъ ,?_L-6mڤ79~E=s# ^hD<}EL2Du27е8;ybڵݣ A|W>/Ք#Ly^sҤ*iӧO ;u!A2@4P mD=W-Z;'%%984mڴgڵ+us9j(=4Xk /Y н{w*((,֤EFwEo~~>M8vRuґegǎO9qㆨC6\>DL籱䎛l1SNeb/_d tdd .%9sF}>}x|=(ؿL'mK) zE[!9" g!Cq{-@_M{A]cu3ZO3pUt}WuOڹo PIwX>+߻O5=iern]V;i,짺ѶuP^ֿPս64z.ŔM}r~am;ݯovv]eլ8PI X{8T[,B_F[.ԥ6EQY]R`c1ϔk{xJYvzv~O>ޞKFp32227]a}ii  } V~")u;x&?@.+il'|[},>W/nD( `Z@&gg OБqeݕ݊rH??vO^XN~dͶ)NKo>P/,S>k'Yc̯b;TVs'Lde################PL-|.  W(w O,rRez>8sK٢NKWL; *a΄4ݬeRPS7n7 e̾l[-9]閳CWAYUɡQlL@9]L)aGu&p6~LCed ñ1*y~D6u znryW`Fݻ7PPU(~饗4k?KMMn;vXӠFխ[PkW_n߾}Lz!YwkCA_kbY6פ:S ֺN}@ի։`ZW;>D3S&~ŋ(9sLĽ{@>3(-˶l"ߖٳgEݿ|'EƓ?bbbfgT Ç-3c3_f]}Wѵ_ sAۙ($;,|˗Ʒ6 Pѕ>;7?`[x@:x <DQe?/+ۮzrOaȊ(@Nˇ gLl1߷ OĹlN٫pW3w\ Эw=5[1oxϻ!-}k-83 L2J@&U? ^̞#2,S(c_Z (۟d@YudG{/oبHoe################Dd̾;ۋ7+Pr@ړ=V?Qrxr65 DsC瓩z+sr;ހ2KW^rD V[}M(s8])울eeYTI( 8 meր3Ș,>#|-״hb]tLG#]]@Y0 "S׆Q , u&ˌa01UCWmmL"6H+彥j#$7AmRwF!{?zs[d\cMZ) KӺ'Og-T~"6}v?~@&ev!n4p ;kmud ˥2W`D/ aUUUɲ{O!k񊋋eF)7:$>t~*++v(?N>ci3?sjmpIqAmt/A4Π~=u/;]E7u }_=ȷ3PIwXsw|UcG[h4{z5[&f1<=r8``~Su`'AC6CU>PDvZY+8!'ukGy({)x9Z\?ߧߋeˬ>Zw_CFy`[_hɢ,2L"+,Y;>fw&Z>WoFXskRϹo }5f#G6gigY҇TϘ_ gN@Yg?LВJ lddddddddddddddddrrfDW=`3ih[q3~'28|y'ergeu&RAtmJ*SP# *('8.PRǗQ,9b8`£AgqXYeF]ueJekA=Uc >5TpuPPvMzV\&5(9"@D2ˮW 1~,}B,:ΧwZb礤$y#{wp]|rO4 %aU6}v?~@&O>/ q6gdX?葉)HIg5Yw}iU{=ԯZJog_]L=,=gE⯍9my/A4Π~=u%;]E7u3{{og>|;6X(ZvVͧ (s:EkUnUY-`,y>v͜7.&qR3p$rR]>Ŀ._Kdj@?lUu2(4Lj#[sj>u[_w_yȞ#+z\RVS/=فt:Y B~_ {\c9Iρz]k{1Geʾ,~IZ^d?yaO&~,@(Rei_Q)}ݝO? ڴT"[ 0U>}gϒuQAIPu 0 UFR6k2q%LLGyܲ? e-2+(KB]KZ܀T}= Q"9tvIqK.>kվOPxYUF1¨M|n7qf>g&2[Us 22)j (&&Ҋ==~Ud}gjMPSO=7._7sճgπ>i„ k%%Wۙet>46(IeV;C׳YWwo}g}_炾=ȷn@og}2\0f keUrxzh=3ï>**jF}u/,m | f[A]FJ^p$rR]>̿.__rC$,)o\_V-Þ7yN!>mVmP.*m(#s>ߗ¾m(O `M}NApϝ#]2, @ O2vKWuvvՌ>{5P62222222222222222 )@9#?O;j89)#@Ow mvnfbV0NoⳊM)S5 sHf1FXEc3f|]|}% FDz`Pd#^sVNY*fP~g`^]3G;;a-*=gk݄] qXrPܒvOr:e2 )S/"3lO߻ahe+j(;N裏=6(_pm;֢ >w #B`42N=+Ch^^ W/2 X6==uq jN~_ϫP/RdpU['>r}}=۽{7>_vMdC5?>|3Zog\W+,Sc \rE^g/ؼyD޳w9֕@k:B=q=ȿ稨(iɓ'kav((mzX(KEmAu˂(\CTz~y9@\BlP&,X{zӹ (R ./zatmlʅs~--;@i{__Td+U78M<8K 6O}sr( ~fM|/"C9UIe0媱659YYOӭiu]gf@P1 Ym+{~|@?#<CP62222222222222222 @9K {6kJn =7odޣGLeYz9y??! ]73k>UXUĚ;IiXOnֻL~p9z(PsDVLH)gGruRf\ F (#s2_a;(7k4P+dRn&{νtZկ~wzGhAKcǎkĉ-`$tAAA1RZ1G ><칻tBk׮kLfLMMU6~޽ias ~_ϫPֿ&EfN_zU _PFX?,ywlI&a;%駔shƳLYCC/???ֵpBQ}ޡCBo0=zgеug࿫wu.ڃ*,~ٳg3e~Y'?e۟m@Y5<|/g٦0?WA`+7UHZX^}_N*6sζWX!_dddlo> [GDD*m׮]r|˅2lҶ*W}32zʌE_c ;D@i`eؙJekR>ѣpuzw'> z eVud\?^,c,Xk(?fݬg5!kP޳mt/`){Πk=?Wѵ_ !sAY|Oa ?˳~@.Ʒ6 Pwo)̙1 m W PS'^;|Lwmie################{GKxVdeaNM8qYPTyvRxXw0/aҾ'S?@݅o.iIr)}W@ԍIrF`, ?ǧ#>E¼1cQf~e!*$<EAy'rxeb5̟L؎䈎>=b@(C^ 3 b^.sPv&t,f[Pew֩}۷g޽\opz<7ݮ}zz:}5 {nˠիWʂ!رcA{檪*ZdT0#2jrMI+ݽ{coݺ5(YFMf F_ϫPֿ&LUq۶m=08vu:a/K?.h###=!33ϊLeֳ'q.]B˗/STTj3Cj׆ .Hؤmq/`){Πk=kXW!(3U4}W;C?tAv>zW wXsP6=~ieb OBQ>}~)m\ ze7"#"(-5fM \Z@yZPoEfE}=3D&AIF#Ɣ@"׭2VK'`z]@Xn^v@x9mJʿno`@9''N%})w*GypOy:L,e9~_ev.T195x|Ds Pe|;l},ϯ(W筮ힵbe################_I~]$lXg,RȞ3@3N&;Q7yEKe۳_ӜjyQ0$ma:H+ס}NT.=4 t3.*/4U.L OW\2Q"VLCVQDrr3)VJ(j{$E!4sLڼys>"K[og,**BpZ|Z ( Ym=}yݱZbD2(_Am=GGA9)S P2z%N)eggSRR1"R[QQr--Ӟ={ ˣk׮U6x}v<:@Yĸ>D}3/иN}@:|>Ŀ6l6YRRRX;1f?n |^oGe|?i5vX$@w!kY54yd-((AQMM !7g u3Z!4ʌwM{|.]{P=9RNnPg7sE2;,:eom@9ظ qs J;64w r:e h6BH߬ǘ1wڌ5mȤnyՋ4Ȕ鳖'Frf<ف:@Be9vIjme_m9RsX}y޲јܷ_o:F_^'Y̑F@P4/偳}ve*^7!Ȯ,@(eݏhH#=#eFHxV3^@#h^پv}l߮,@jk]zRKWX-W`>[zS{JŌJF=r&Թ*z~!GTl@:|%6w]!q+~@}O nz Ufg@}P5u_sKuP5Pz\2z(~]%Ac&da_ɀѶЁA!~> BW :{,\r%XA@=3x7,T\\\ 8 탨~y0YD` Gs6ѣRzwTڵDf+W`ۙekRfdOc"8( K:8k~郶 |,y5 ={38##ç2T\\.Ƴ'΅zUVV ^0nݺeWN^8SxAzuPfhwu.ڃl6PY'=fnʊe 4qP[:K/-kSMճTKuNNG,(]ӟkཋn.e$67F/s-[?7.K6\{YhR-OZyy9Av(d~-fPBp$d$ ?K_`y2s{<>z`sugGxx/CCϒXcw>`yO#ΩJ+PR$5.%j56颶{ IÀr<=9JsO_`nu)R[d ( vƾJ I{gN\^s{]Q ?v]P.Pg2Nʼ9.;s\v@[uexwKK^_bj2vXkW(ǀy6u[z zW(+EcJ5׌U䴞 zј˜ϔcl恫>FHU]raF@΀~7 &ùY%A3"b `:|{Bȯu*5qds,yX˿(CC s򖩜rV]2Pݼ6 d9kmjNُP_7-hȀipaF>G χYɤ @Ŏ~]!H$s*0pdwHaGgYǟ }+vAL'{wV`: ER:Tšs b)\,Qݺ7q:uq(_NnP$7>óbnyIr^=ܣͿq*z:::JQrmjT8TE:88N\ޣؼaݾmΞ>Id޲G㚣zͯs߄[aOv4>>"ύ UUǺ|QnyVWqcoeBN0bwwXc{ed (3 }eoc1g*;n)\~_3dU*}b0cy!(G ^Z;///cR*E` _*9Bn@wMoo_΄D.SU;e5=&! ݈pr`P/4Ճnjhzz:AiaaAMJ@Y@YpV@Yea՗??t`5Aj(Ncccj`|PPPUgPɯu/^( idCzt> =yO"ɥ7 !T򝵳VWWSL333h/mmm@@Y@Y@PF@]-oƍMWWW(54fQ0qtgFH Р؈KP,,llB W*擈)1QOq623107/yOm׆@y$e2vj6f P(@z 0@,PfxA/#V}bF @^1yG1wٌX}9d 2(4Mm4jD׋vz=j$ @,Pe2e2@@@ @Y,Pe) q|Z=>89Dywr9J p/|)P^F1-r-Yۏt.;;_v oE EF 슊< IC5tww e=Xb{8ɹp +/p9(Ay/!)(SP&!&&2gNS7%Lt}QPc{iE2eFDG#=1Au*AJ\[SqSbCMOWKl׭:Tmh &# iaECL(Z$WbDgi V ?;gPCAB!B! Lijj%68* QQQ/7YFPf"III,G2=;\ϖgƄ㤣 ֘)(?||o|cN<-KA *PPveA+^x]XhxJA )YOwrҫTw{~*d?t6֭a|G>VĤ2okDֽ|2e&N7ƺJI.s*A&'/.=H8}h_kY?[* }U_+JgsA9wqPl鋖mC n_eW-L!B!}RPG'7pilz_B_6BntFÞP !ɨcK8w*xFgw,kŨ՟!`e'^ iK mXJTÒ ۨcz<~T@xSB>t7 hllkiiqU.3 o7q#hd RCܘ-Uoo/0a7"G2=;\ϖgƄ㤣 ֘)(?||o|cN<-KA *PPvUAy6UD2y$/_,N%(fe^W8Z_v99 `)(SP&nBy Q,tAY&gAHMb&66Xw;vqd H*;7m8w8rFJoj^~_Vm~)x#ge?Ed ( ʄB!BqkA9;S.M+ "GǴ Tj6{W11quCmD(*_ PnڠWyMt/@J,<((z477iiiefq1vKA ʊDLLܞ Տ\elg{bBIGMAr}EUiN|wg΃$_BPT0Lk+dx8AYR!(P$;(+VSPe.*-<55qtAY  e_)z((;,΍$sx1ki Upmz\8bM cј3BvȢmfӾ}jhE((SPJb۳r{@p=;L1Nr"\SP{ jN|K!$Qb,%/W-c1Ycˆ*QP2  t/e3DDClSnTlک-  !B!▂r HpP *K}EyXQ K3]Ή"xA3|dyYsvC9;%!7`e'7mPP) a#̧!^R'(j!BA)(SdLApxE((sNx5K!d%xMe1&ЂV3 WHydLOYMPJGgw$5pH/e5k2}\\,Up>|~ U$1 UH;$^k:,KoSЮnDJ9;r,R?⇼l|uۘu^{6bG'}Z;M͗KVMe$Z9kb;3kKA>4 B\N#Oʠ2Gp"PI 2̳g}8{^}7[ J9~Xioy+PV##%9g.;Uwr}E_w!r՞x5agdzi(AZ/!cD+k7=n'[Q{ z\i!ʾKͻ׺@Y*) n; ppYkR Q}c]u)LS)XsGpo0CMbsK6J^(g.!0] Z{vw>k-VA`7jEљpAE(hV6DEENMMaϞ=fmFX qqqJHݭcGGGƢ:}CBB ^ d&WWWHRTUUw~~ErrFQSScP____ '(JN^DWW a( /A+qggr˖-HKKZfe;{F9!P~!@ X,f.,,!477S<<xT+k\&DL&AVv `y>J?FuΡlf+`bb;u RV~0n^!ۺ9VA,4 zΎEQ2R;BTUL}% 0#*7n2@ާ^9C*e:擈Ae<} b㋟#p}b"agHu攠f6RtL{ _x$w@^|bCx@ʝ  (Mjr4fPv"Kf `7u|9@2KR(uk+^s[?=c Pfbbbbbbbbbbbbbb*2U+TRy%p]ZQٗ-Ils]kTþ^ſAYz:wO8%afgin5AJV?J*M5>Oz)I矑eu"]&jGЧR奱dэz"q:%N.uuG>|exp8C|6%]MOSAݱbA|@KKW@M0A ?hĨ 57n0htF%1icq?:s2IZ2i̙wܙyɜzǃd2ICz<vj(/ETlF2yq UfyZRP|}ʙ3.&}HU]`>zhxz>pA0|>AY=-O(lqHaiG Ub_~$ԙ9C}H@}/]&A?2 v7AԽTWk9i hs BWU3pCe_mx˗nww-kmpav=kj+U;ۨQO^a-W1k b|&66'bci10kxg*JcyZs@(gw~IFaO|umҲ,(0&Bp& ț`P ]u-9XH Hx:ˑwjoj v.x΁F;"c/h ܔ &n]nbyY>okPfVc1:Z~OlAyi9߷_1/ ʜX t<_缭b5q.ND ޯ<ܘcj4Ӝ%>+ RRRRRRRRRRRRRRR;ɠ gx1#a\aMW_@p>Ǭ#L a`]]9 )'0\hø4&{ӹѿ~gIq"#2Aw.0tFiM{$]7Z{z>UUޤX}' _@sS93d _#d;ɴۅ=XC)Lt{h"󀇭9@Afgte쉑~a1ڒطeu/ O6(\1bOϤg5 qsU}eUE(I jb hg'@{.3pY<Eb 71Rճp+xbTH$Pxx/XP;1*82(JU1,7"& > '[XN{.J؈e)IѦ\!݂|\rp'Lr$rcȃ6@Ej^/9Nr\ǩR pvşw\UWǽNSbA"EEQ(bbIbFlMX&cd2y|ޛywY=swm}.em1R"9h|? ʭ1*; ֚LB(~D7(@YWr=[.fptlݛG`~I83,+++Bdgg1c46mQ ;wrRNJ >) ۯD~_yvga/L!I߯7hF0)vŊ_k׮<y-L9S(?̯-RJL#g(uԳG) _7Pƍ $,46%lϞ (h=Wҥ/?CFo eȽ8.ػɢzRR+of.OpHnH\B#{`  {]=46kà떙m?`+(b![5iHChuJ~6]Y00._t[mņFy,_%/ks\ֈmVʄ9#7/P!QsX(vv!jY֦ƄxND5f8~vW=؆ݱ p9?5˂:O2H<_.PU YnA"WڨPkg$)&ۣOg?qeq#! 222CᗛgvGQ&N}=-#>~6v /C]VN M^5kϤvԉO`cK|ݸq7l@+mZׄuB^~I֒z_{5F]h}Z\l$r+[x6F 4(ZV 4S䋏jK beԮ!zսgu^NJ._#b$[m8̣c~qZe800ί @Bڼ%Ev-+_H/~ons9F E_-9$TsCTuM@Y(vNHo`]sUaؖ$Mk1²ښosG$g,[P^8d(#2\6<# clm8H<;Pn|uֻexulA!g:1k.}V鳎ٹCm؉PB:0s28Ny4d|vMZB9ٳ PХJfpj^ƣ "CG5]4q'q"kb^"BVP~GLyeLT]lGUъE=t}ҝ7FWv(:h۶{C)|L@Lƈm(\ҥEe¢"`l8ˡϞ=,UrǒG+I׮Et `@yDtG> D"Mm۶ŶY\˗.S1%\A:#'A)BP6(Jԃp@<ټy3OOgjϞ=NVL?SP<07~D8 c%`P6oG6}A/ga/L!I߯bbb S ?[`~Qȱ(S06NH+Q3ey!C~=z߭$,ӷ7ح\4L4?\/w,7PXbB}##Z<)76bPаPRvIwý< R`~k=y ={2yΈ>o4|\\[c:<@P.+_D/~dm&q1($PΌFuƾr(}/eaR8ы*s-]'ӳ0e|Ӿп? ((P&/d1XgD9(9C P>uk>3WYuyD"=m)l])q@>Οޜy;OAiqPrb\cU`LWcõݯ@~KUy鞙hmLtEڲ0v^sبlO^bkћt9_a2xqpGE`Gmc%=c1 9X\e4W"1u6or}?gpmO]S*sm}]3>xj4F0slD0n))r 9aqj?gK;&MNj/9ו}/ YxiӦbUznn.Ǣ6޽̘*3d?$$D8rjNufeԝ;w {'9s&;|0z*/hx|Ͷ& 'O» ʴVUUA'e/P֕\ϩ3g@)))3ܹsFQxccc 8@~l!޽ 0ի,kCW %?K@ټJQ@؟3&myB#._\.]"%Pe c%j&̑3Ժ2Gl VRRPgdL(k;شOf{PiPP]UސA70ߔ6l[E+: '7S4|`s>eHTͯ@ e& K@[3/}M:| P;!T9<}]< Mi&ڇܴIb9-]g3gkI@{8{9DCy35w(aCSXyd (g>xfgg 9(XyU 1uɣL9/k-boJ@YJJJJJJJJJJJJJJJ uCv@LNgM%e9Uza9 b՗NԄ2!ZKɮz;2~`%`gN a*1*ב0q}93vmBnjsϊ8 if "mՀ2j>f!UOgUupHoDڽ:^WB5Ds]G(;Q@6ϝqݴmۅ 0)e~҉r˪QLCG?k[P7 եʇ2jkk!…Et*daEtݹs2<4K2l넱 @ŋi qnJk/o߾ezkjj }˖- Pe]|6ݺu ?Ia2,CO߼ySUرclذak7P?"_g (_ # 0uפWK.۷owH///oVL#;!uD970:t+V߭$,e=y" (+00.簭 Kh enHfuH_(`d{ucE}? @:0=R,5gj@ eY7'Tu~K_O|BIoF8ΊL@5PΎ#(6:!Q<+PFe/G[ZTʘb<}yi/U "Y(\2nۀ2} gnA" PճhX$/:Bq0=c we/S+iv0MGZ=,Vwl3<'_(%уNR>4Duj^(waPF :Rl#(ݪ~06̱p_DVgP=Z0v^ 'wW4e}<`r2FH mm`{ 3 j PƨL6ETϟ?yq6VWW;̺ܽ (cETȩ#`N+@'}m epk׮ёyxx^v mU͛mP&"E7-_h@ u%sjܹPgEE|r Dg0߲ǏN(@~kx" 6E#K+yy+l_> 9Shk+[azS# \f0}ȑ6(06NH+sP> k… ,n%%%e (ox0ؼh; LWٴ12lTtw@ޟ){+"Uʓrp5fKIEyZw"H+MM6þ'UA''_DolOJ02B$gMhN@j2~NP ^+ETsD_d/PȾ ^e]*{Qʗ`d ܋mdr{1?@/I,OtD4i},3e@csgG9(WAco>}g~#z偉}Tu'[d?6s 0X_1yG*pkN(P^2}~㼯fڼ2M-J7z({:X8LqӴkyaUzgykή\KģUMo;.]xP%Nc@R軍w ]/dN%|ެ*[JC~el׶xvHrSNK_z?޽;+))<SNr#pJ) ޯD-~_pv _ĝ)5I?W<陙6(06NH+ş}rٲe`Ow+)) (K@fr i-(O% IO w+,U奤GXtV(o7.$ c̜Fp3_^.(dz|e/]l2Vme 0 (՜|a+^ضr } kEvo4#&c"iz (7~Ql5!aD@Y۷G$Yv.l~3j(CLHŀ o븑ʼ~&G2jls[z0G!*u)zՕ`ܘbrTPU+YufUXk(GD)]3(>kW۰^FRRRRRRRRRRRRRRRP_ u=n1 (B~Uj)m'zfA(ϵDltno=n*_r!L# ":$s~ynRvzk"$P#tq,㜐@鯵WL#ھAʺQ{!ڊ+TpѣG9mH_~=g.___S^?<ұZr%-?"( x"ȶ+Q~d݂.!g }MH~3@""8)`~=DW(c2k;]@YJ'o}EEE#}@G[F`wMU䱚:2an^@<%Y3&@VكX (#H½;6jkRe( nx@Se9ewXiʇCCmAJp2$sBY/a留fM9qʁ=4[6n`8K׏Y6iֳtUWfqYt\K͖Gem`XG7l.Ul'*9Yw5x^/DQ :pv5e/?[ߖBc؈11 KK|r# #e))))))))))))))) (B!z/!wQN!YjpUpnytaU>9t$l;~lu5W0+@9Nc=;&:Es#&ʳ#¼.ʚӁ2ul 2nit}0-[=(?:{0P~y8 yWcHfp=}`ld]J!= ס~vڱ7oMee%K>T]f%fG_T]]#""nc׮]2:鋥pQ=GYWҾkϳ-DP3feuk.罄yԬVB77|7n< .ӗAȗ+P&mߠe]rttऻw甆)Q yo߆yyy旺g )mX-]XjE;vPpJ, ߯D-~_pv _)5Iַ_999;w lDE痿n߾be*PNH+ş}YSS64VRRP~egg&ڵD@94(ӝMwF7^iQ@ʽ9>k2i,R,[,*:M!֓?{f?1'̢5V6i??ڞqG\Gj[i쇿=¨ߑ}K_mp(Fg^ 8ܖ Q zr c"U.*" P/vXQ0 8= .]/\|{9y}eϦȒ[ȩv8ni1Fjϫrw9 -'w n0-&K3]2L;TUcMtzdsWFЕfu7 c!B8?] aT=32Igz¸,邧7Bh",?P|Q E#l2"wC?*ȻG@T2"k 8{/toـ2kU9Ѻ>k (?ZՇnyǕdW`pN^)BuCi=S\+fW> A_y[6 +l/«tN{龚-IW <oSu"  x9 Ke9}Mck` MANPk@: X$bXX놺7Ʈ嘥ΧG!f<$Vt. j2K:iq;Nˋ`|/XQ[qcysVT(x=zѻzD|=LԶ#B〲,l潘]\2Gb غMZ#Q3eΏax"z}g \'_q}49Cih~w/uVqEeFB%;u.Į!*c-lZ2d9ЋHֺ ~Y6]J::l?_N1>\ϹÛs>۽y>Q*zWaa!{>ZL nSfcvv(((555!>gWWϜ$G#1Ne*3Fޘ(++CB9֜"#)CI5$A!-,, t\bjji@94]=$"t# pw6{]^^sIǜ P#ι u3C0xH_ 'CFFHNNZF{%%%X!pKsPXcq_NT _番}T*!%w gMrh[W;VVV=)xȟ_k~~>bm~666ʦD-**X MmIYP#wBNbPVurI󊋋Z {,Ҁut2,|=.Ha ̐&15O&TkN{apLZC$ +X`TVC/IFmS~7_HyyQ~5PƊKU]u{zׅ8^rk6Fʌ1@Y*oJ`Cy5X"K@:zՕ(˭+ʄ<|vfJޮPD&8J\  U}#CRrvH=L:~r-뻳]⻹ƨu?8)rfi'Kg*rtBd37 Pk1f @1N{uӀ (KW`W\Z:i];}/R+r#9>M3](40k8>.9߳9`c`#'P7jw$յIO&>8}Q82΅rve2& 5\m %De.> ?sP_ɀQV1,J1r ɑVIf`0GJzAІM{{ˣjerbD(8xҸ (g3! KnR9(c]܉$\0YDn um1g9snpg]Pkk+ߩuvvYbb"g83.þӃo,~'*&ew( xfXmT-- (oooY;'qo) Gѫ@kV|H|~ML1tLOH,ޞL@u{og;}CܸsML| `Ο̻g:G/&`JWpR}Sc1u} nƨkBsa 'Vz~Np54G%uo ~Z5}GN8IVkscys޼yqen8{A*;lߺ wc }ֆL].q팘yM9.s5uu2ag͞_u~MbFpFyO}z~*86^{y79_0㙺vK J0[$VjN] &RB*M .pI:Wnl^uePȷ@vv<;7>{p-d襳2\궙l1ʳ6l4kz,lq^nJP qV)l|-  "y9~Ȏԃ̽7LiJK=Lx 1oz#^Y+i\\˻?;omͯkH/ T8R{DX c?ЪCǾMEh4r~_#qPR:4Ol6 (w~~:^vZ~cKV\V/]ۏaRсt„$6664Djz~ݮjvוW(ԕSjNuT'([__ׁlz_F~-aZ y|6ފ}[s>i_|e9|5Mjk) jnk_~ 9ud Ppe7: QP{w5}? ~91Րkn8>~^Z-7:`ÝX*Pe:o -Jd@9jk2+(',|I;|{@݇:3k:J{@9PeL! 20cEljA!A}j3 b Q8::SѠl2eA@2@@2^Pw`jBtXVWW 5OP&L`2(P(Pp2/>.dy;vQ28^gwŅRp[DAKCCKE 6 A&sпnK;3|f_R:qd|8=y'`vVQ.X,FRfyz#R2+glRB!pz6z=@YX eʛ 5WmFFOw޿(e`vRqG!_h׸iVM*||X|`@vϼ_$r.3 (oasݿSw6.K=>o 2ynٗ=WbɗH=+9VLf1b\d2YZ-i#gt:1NcXh4Fllg+@e2e2@@Y }i* hQ+?c nR&HTh#DT(DH_ZT*MA+TР(7ѲV#-~999 ( ,, ( ,, /fND* ʰƁVѨ@Q+--szP%%%Q[[,e$ P k@@Y@Y@bjl;oct/qHtOQ ү+y・)'bk6NuPD&{`jHsmFsy5O)œٳ{=^#"3=o +87ڻ{?_zg lWxΨ[F2fJ?\,,,DGG7 "-t:QI.1Wd2V/8cQ2SEEy4fm'Zϙ|vU~w?Q?;N ;/z(*1`dY34B~!-ʴiijmΘIbBLPOmKxl8owrny>~{@9`k_E0@CZ͆̇kPW_[K{lϵf-l=i<)7(q^cbG ZRպ@(1P&򣖖%8tI`3er771aq-p 2PZjZܣ Xי| ""b읰0#^}9ZV^8AYX2RVL%M8=h޷@{r/_7vdfe(>t(ZѪs>АUj0=%9ƢB>?1Upm_gnl}IB:{D,yOm<*Px_ ^aLDDDDDDD6Pa.Et3.JZ<jZijJ8_2 fY5Ɲh%T}hh, AwiyدP](mcz[!L\Ձ OZŸoacs` F0^g~>"wׄB]l'{('ǢzL1(a5sDTTrQCyjyVP_ ťE:g9{? YSDX8deIrDC 3=>Y%Wk(8dԴd|wvr}'ůx|K[X)dOJGt=xDDDDDDD2rqD)N[/o.fQň}='WQʾW6Ei=vÈ:v0MPDnETAc6(@ِ%!ٿ=]t/!rK]4 ůFt; v;ى\CP_gnn0bc3eZBSS06BII_(T͍`|DgDDD N"';o^-biXNPSG~4)E%2ra*]Da%+ w=PZ$}`D9,%=zSg _P<ʜ_/e+"bz,=>c2?e"""ʃ9PJ9J\1kWr"P#gFs> (ߌ22e$'+elI6Se)@sY9 ?]ge""""""" @yn:ȡ LI(k$r:y채K#aߨJ}[(~ޒRL1ܶ>f+&ױFprk~_`|we{}Q 8S~[u@O~hdL>1bc\yED (3PdG yM({ϑc fpl4J7clj{ |tmǕ8pyy,م\l9.ˌHa˔N"}Z&bApU^ bŒ 1kK9 x%q8USu=QH;&OF`]z%w菇m9+ZUu稦o~}‚g|YT y M;-\ !2kZ˼7`Pf~BN-/h(x.f']F >-~fGKnJaS\,V[֝5i>D{yobj(\ JR(k+YEk_jL:.Ik.bIr5eA_.+38Ƚ]\,-I {"ຜhk“1Veܼa6%J9N!59)j|f\0bCdOjR4T:_Gx5a;/ܖW{wbX m532ŨWK0` N؃:?wrJriyv$wWNRrۥR*!۴K}xD^3k3 hatZpg~|F%:AY /{Tǧ۝^fLKM¢zZsXΚ`Ć{ؐ G7u)ms |"|OÑ A $eyzdػDe ʍ1}JG,k[PoS}p *rmCq_ևAbc{'|GB^?ߍ #q&̀^Q^JCFZ72NmM׉(8O}u;F\ 2jK4g~Z!STO|мzL_5;zź5#/d6Z]Gأо7{my7ʏ;Y?3 2Ҋք2ՙ~? [S- +OJ"t\FXD2lk4^c}XÜ:X]].2 W*yD"ζf/MOO.hGu*jSSSp\BSTx+++0 cWp8ųVF5o2fxxeR+16įInPVH?FVAAf1hX^t: ո,̻67N'z}39KBR~ǃQ8(.jϦKSQѡ!A 5Cb% dnw@E R# !z؛-%zPAh=Uip9wn{>ls~{|[=0g{]CC~-A30ztxQ(sk{kkK$ 􆶶6\zE2֠mrOvtt R)VZXzaA-#i[__㴳snzOjz0ollh4z[ZZ2...KgX,b@ )ζ7P7=16ʀ#*yŇϛ jWE[?XO˷e~@2ޯ]P(+b* Pi}\DH<KwoJ>յ$a|HA_LK\v*xH3EsU꧅1|G߼5%~w/f,իԫx^6ų*o1dLwe,ee9E(sTqPY *o.@^NG PV!6 (X,bX,:r-R'qx-)'͢~.3@\jqZMq#On A\|su>7p]+m3Խ3Z'{ՀrM}5&j7V#siy,'nWSq { ( 5)g q;<:r# P p({<lWxc?%@}Θ?G@nG푦i:x<p]áqFl0oiX2⊗u@3_񿡸K%\C9 cpKP:#eYX1rc :?swMʍ'dq|ifi2Ћ7< 뢀桫a1 䉢~}J&Q~=}Pփܝr(V@kz hr:ַUZv)6NH:/:q@(Uir??OO?4OC5KhkTUEyꔀ**+`ɰb ]q jZm|_CPź71 /[]^jCEuC]l=~~W ]DPa>I tBAγE=Web,5bc2_K+2ϗW@pu9NW9G(1c~ʏ (h,@{=R߶yJlt5@?&BW:>0wݎ~kZk.;E-hP 0;ڜ׭JU&r֜#`s֫#_9G\Հwb RO=G} Cm`?P~,庘 'k fP<@PEX{d<,T(fAmNmlߐ+L˪,t | >Y-(q<[Wݵݧ+j!j-EhCC%KCyS7=BAp,[_3_뫝Dzsf~fW (<_2w{DncGF󈴕+q>9̧wș؞M#{X 0w9cqy]iրBE|=/[\orm+={顀zPN?É PuJBz8܏LnR D3db0t: L&/sL5ȚLLqfv]{3~վ1e |{~ֈpֈiL}֭[w,smWVV޾}՟(Ccdmp 1c0>^Y@iBBBBBBPy(iؠhUe!_B֖.yۊi͓`>`2{P%俳wCP-8W:'4,T|L@ 0 +C8{x;yn}+-&~P,$y}EP.McBE%07 mּB eJO+x~|̩*qp sdq,*fe8vNFmgl#/3F.`M`mllXe~xdCTclq@9.,qx@ٶ Ź PP~ xCx*Aܱ5P/YuXaȡpk'MObE6cUڕwgY7{Y&[#lT},p.WOd@L0C_+kgG-4̓:Taw\j2Pze`dSL. (+ŨGY1 "c{~- <]\ud/IWS!uHX 0%VZhpy=kKz|n#c:V (C}]``&r򘸿䥘zBe/V-܆/ZM@ٻEx9[lS<޾8r؋͆K0緛m@g`䩇nݺLm!(d.v ߰aKxYMzt/ ,y1 i, 0K1.\HT>'Bk׮Vj"tWt(s ,'1" :;̣o{bdpX\_O@ڜEvAH[r>a^AX_Ki5~|@cȟdg5eLan)FٵW_Mە)9wt2FXVV)|~SXBxhxZ2aaTth8d(<\#2\:# cly8H{@9pn Pwexl4C‚ql +E@xH D4Ƽ>2},$$$$$$$$$$$$$$\^n 2F0̼,ꋞ_U1(. "~u~ h_=1]j*o5tO[tƖYvR?D4]V#if? 8 eȵF/@d"(ܛ>v35~(VLSO5)d\0GG-eύ6̥m ;o94,y&dHF !-??_-@.󲠒ΣiӦaUQ22/4P݃"##:)%ȦM jUxɓ'M5w\,g2b$[???Y[~u@E+mO޽הHzr皚>)(Qbڞ={ GЦWBBBBBBPP1c9|.̔ɚ0)r۴%(,Z6*@yba--c$?<"~}u L حRsE2ʞ2S'bQ_qj@:zxq,=s}rP^4G%v {_@W9\4clM`3p?[>4XT^x-Ĉp>m:^i(cK l8? ֈ((P~j2bΨ}k=bS 04ҟU@䭹P^XkV|9vP?Geq3ZͳK@=:@ /)ʊַcam#؂suFn=\{3<.;ˮ pnٕC=jI 0%ocA@s;DMƨͅGGnHs}a ЧwmC/ tR@=}-/65 >b FٵmK!Jqd>8T:/mfZY;7a/|P~f]5S/^G L/}y0OeDi/_A @rAPGsN 2Ç;v5<;r;w$t||pt.** {ǏcwڅxY0\i}Bnw_lu@j̙`Tڀk(hJro߆|>://6c v!gY"O#p6o<]NNիqAݿxR֗W)exI?C رc>C "9QeZAg2i} (Si (kL}ϡ&O*$@Y:^( @Y;@9:*˹Ȗ9i (Kܲv 7N,00Pm?]IրD,'eaCTBy;5txSW~6~fi#$L ]RL+Μ]sYB@9(|[Z?&&X{xC+0m`@uez?o|kIY6MP ۩! k=T@9j,e,.-ٙ, O/c^݅gPϳ+@}%@:@YXxs&ee`\`}锹R(Q'an޼iUȑ#lԨQTQ~ٲe*W}l6Kgv/]̽?+=(Z#I6믿boP3|vJ@0^zH߼yڀ2̡,j:L?4I^pF@rPHK@Yg#y~饗uI L弽LeFAʪrelfe M[?e)#r*ɣN|pђQ^b`aPLf~AC\ѡkwB=U݉e{I i(|`.ak(ʡ3GPN~sp$祓!GM6Op` DUɡٯˋm((ڰ6#uuza(ľMGOPv ]{I @Y.DA}S a*@jH{Fe}Q W׶{_wM 0(D#@VA.T[iq[O*kѼ^?CGMy{@tFf8|FP&PFHu9a򶦼F/h@ jS(gdd@:ƥ pBvhO>  `Qkxn^MM |z*Do㗩Ç^o5… gM]}1\i }e0ؘ1cYEE V O-]X;į}#NgSQ`===a\~]xvJ u3G&pj Bl(2sN@)gex>rgh('qP( +[fh@_`2]V[q9?c蘁Vey앨ȧ PXa4L3ʧ술!yRR} W2{0g(x!m3?陣l?9W@*[]BO R!( t@zJGQQuPVT<_R~tڗmhr.{d2 @_m:(+́tIhҸi_-S4˰0YiqS^#myVg|;Nݞ`=zZ0^A$76pA3T/P,e!!!!!!!!!!!!!!P㖪nOPnv9G~ 1l ƱFɕuPvO_P%V1*9\|C۰L dLv'@i(mPvgW{q38%Tt3#kN-c9)'Anyڭ{0Ajk C[Z2DRn&z6O5e8/jc )S,eСC}޵knܸӡ{Q1___Ç H?y$}ѣG9gx;,,L"'޽;dd1ɤY^5"95Yޟ鿏V|ׯR@Ar9[DUg @Y@ӄcdOS' (LIo{53SZ>kw,$,<Rm}W*mT\_Qe> !AA,U3dUm'spwɨ$g8a!TCޖ8{9c,e5ȖdbBe/%/k5_2 A p#*nn5@9:.ܪƫ IW*rPzּJ< @YHHHHHHHHHHHHHHy3=b#7w%-BWX mcql *@4@c͘FD&8k;t>|v <])_n)[C G\( mmesf 8GroXbyʆLu}9QK@ey̻B^߆u=1Uѯ޾eܒB~(0ŋ+**nxx8^-*r_KKKG(qO?Q Jeˬ.C_4t|~ R[Y5kX@\}tǏCoHayy3 Ϥ}$ }ORʕ+P}K,G u3rv@`P{?))IM@zDxiP#u5>@|||<{A{@F3F Hg#y^|9ݝI),PWٟ&$$$$$$eUePIaWVČU^يEe?lPj7"_)E8Eϖ kӎMEݳ}5 Keo 齐&AltE@>WRq0P1TqD_2{帄 D v6 ؿ 4)YT,6>F"}^`$+:|>(LOxdnC,Hi xJ]xqKh$Ws -I+BU22'Ue_" dLlܰ=x; c k {&̀[2AyjoLs[%8 ўVpd." >" 7&ކhjmn;ZvS"(5OՒuy>6 +܎EX]?xGV]'~|ɢa*޲ӄʪ o]{zq .Ad<^jp JLװ<끅oӴ} [UF_qO@5{~}k!cLۥIWn+uAYTAYФt>tWQ6U+ ܙh[-?$½DB Or&)w7Pe6l+Q,-DZd]@5\u:lk9A'b8`, rX nĕ9_s]a6AsEY":f RQ^Vɑ2CBA>u9 cDc1w2e+ؤe.3գo*ق23<{*J~gAAAAAYGPN[cPw>iQw5ѐ4!bZB$PTE"]veC>iKK >w.e>H/4,Vw^)c\+O&4s}[>@Y _zJ7H_XM?+laa{'xƪes5j3߹c '@V(ôi+M{&X57sFky|PP+o&˳{^H[غ/$2̹D(^?nPgx~pU?q4栙8ԤY(Ð?·+^GΥr=<[a@ mTbfT* UQ ^TV)J"mߧ|>OŢ G18ED(R:F3 64KЄ}șBjhgg$m8V=k>+=YQ`/aP(D^yY@A?{ttdr08_ ]KeLY15z r0u:q/ę* {Xy_qo$Q H7Hwv@o({iʪw)|>fC$@k0 wyu]{XciL*plcUHF;&0k8XpJU -mA"55fbB]2L0f\t[$C .] "8/%{bK_yrͽ?×#`,{҃\g ! #K0I,gEQEn<Βhq/=ԋtd=5 ʫ믻K!oQ-> 8o I[rCpO3$IȂHJ\,NQ2g(X ɸaAک97jd__-AYsC2<)W/_ <+WrAY?*(@}h7')5QP,m9XLVˁȳn D^$G;mɩs$r@v'yڷy~ti)>)fW'&Ǭˮ }/\[<{߇9?YJΓzKPFj1}%d|M/Aq(܋JQ9$Ǒv_NPЍ4ަZ9@@baeSst!kd9!>Q%(cYPJ~wF ^0vJ`HsF'zIj^un4M\$N˾)H"1u%IJj^B: S+;@{d|+-{RB$R6;tã4*'Uez7?JҀ=Z{ n\E͊'k2[ @?r3 ޯ.†2yxonMMK36#?#pC{ 0M+'/k,ׁWܓP||$ kIΎV\LpZ 'ktª,Lle=>4W2!ۉ( 3u6!HBU9Ҁ1S}˒G-yʪ^)̆JCd9WjRUFK[f!6ʹEe3G`Te!~c0-05[G Řܑ'p-Tc±҉* !{l])Nc4h4`!u:A|4 8%axΚ\.ǂڸv;wm$J%8Na=H^S.x<>.y+ Qcus0PIW"|>Of= @= ccPA>*ni8=n¸lٺ"ΗpcCrOz= q0߱X @5S3r?xIRxq o<Ɔ̻rPvjm3R8y"E].g-gCg{Ъ@ 6l'6l F]wOZt|#sZ P&gi/Xqqc!ޟG(T/ FlGq;=eųӔeɕ ٻ 8u并:Cqؘ5%2 aMJQֈnb莠~$_ bDXuT+т~}1R 5oǏ}^w>U XЇ:l(6WX偲=`ue@ lhZ+10&Ի$\| DP C-/ P(oHe@(e5?s݁m@+I;NNN pAx@,P( eV hF^#tvywĘ@(^*[}Dȫ{qc[Pt,,,$Ԫfff" pAx@,P@9 |׃@@,P(=_sFUj- t 2p54M{M7?1_2ml~|4=t.2w 錝lv)٘(155###x u P(2@@,P(( ee2Ԡή~P(j@[ jծn u_뭩\tfb{.˥rѕwdcdvFvhرPEݝ7P|E\XFۤ_?NhnH~:} uWsάh5tw?z:>|W{y?>p*ϿcWw?x2tz|\w>|*;>;Vk9KÃw?QqLJ]rD׫"iRXEDل}fvξoF7#7u<ʺC>A-冁 WyiE ̻&>KQSS477KvvK+b$%%keee|V=U4V1v[dB8+2zpEŪ(7oΕM+2 |er`zx1Qj3C#-财BG\PLX\7jV11I-*ԋVr^Zj]|#e~J_ ).Uq;*#V(LzܦNP g`V~7D CefIr˃KM%95# ^!&sL/؏G4=nJTƙIS<'P2%Ugӊ2 xELkk$`Q^.^&n P2ރ5X'N<,wUM|U+d@&41$h&'c^*iO~~>/=:P>q77Jccn0@=r`YPE  ҤFn)sa&NZb 0qzHMHۃqAfT]ecˎ} JV=t vΒQ1#iӓdٺt{\u_ee]w?]*}}=>PN5MP<4[VO2 e}5hky?(O/$hc]hC =%PZRRR"ᐑa(>]( IJ&l^cFū9WO_n` P(GoVOrZ#w9TvϖΒѝڶqOz_ee]irfY ʖx2)#̇><:PK-\ n pZn$&o/o #ӤWv$f1tCdv)e e&hy\om݇&~[&5 =A9$$DBp;ʮ cz#JΚ٩@y$v ge@uCDJKEQJ5܍cY?svҿ~ uyi^J[6S: {{K٠JusLs2@yEpZs|پ=#PVyD5ܴA@[>-_q`m>d&A緩ד(FCeƛrdբxܙ@yzvfz[剩z &_*shTw?ߐ5o/JqDzvGGG-iZ9SzJ6n]=&jNHmC|Muw~}GvʰaNOL)6oIuyizP?oOQT5_X8@ ˷yRZzlkK6u 95b MK 袋qa͋g00uri1| *H4{<":L噰 bta-eH l{ q{ܶSyGM4jp>M; /9'ZT _vr!Q*h"kv0K։3olrX1H_8.?YS?B7mL[as#Yh[x^Б"?ZУGr\E×UhNoN{hdPgV(ge9{s&O@u4 .)|LH vCxev.x#39CcJ[r]\is ت}5UwO6p٭eoP }WFNkA8V5ERC⻯-m#sBlNk܀Xvxp'3Dӿ-nC0+߯lAuo<zj<: cVKI%$gPtH&( aZTXWj2fT*`2{a2)* h48AiJ%er9a5?K* .왮KRpݘjkoD"hZx<<z}hžL&bh4 \^紡lAzhHk1::/t9D :w\ B#w2FbA"1DZg:7_?j|{dqi$کۆ"!bCҨZJEZXLQ&m12T4j@lZ~wɥr3w.YNb/E2T__q~wˣgPُF_|AAA (:}Y---(O+}!WW6~7$s1Y,'V͸ #}>ː!ns3vX )~Ay44Y(U1__*)ם8m A{_5^AYQX_Oo7wXp3 AAAADS ʷ]Qȱ:]܌e.l1}~ 7Ƃΐp|7*}W_@V[P<>mR:vhf ӯ Le枑fݯ;QJ c9>2cyn }'RG`4YZ@BF?!6ߴrkdIA^?{|Ǽ5~ TrXD!f].fB2څal,f21\.'3$X]ks q,1+d*MfUJ[Tb~_Pkt:Y*t ΂?J,_#u譯%(c5:_A0ߗE j R S00uAJqj]9e[pM[̓DIQ1b켖&>`lZF 2pX\y &@JZ^+sI cV8oXx$Quġ3,u` 'v///9vU$(oh]ɟ| p}o%eXb&2wئԙ%M#ǏǦvsw eXXu")QvIQ uݤ=Ϊk]k\P8rؾ4h{F{u +s/vh5X䃥_VT}),^3@cﬓy +@$­M ,S0O=.(u}v%k7՝ \ ;dwڋ\Rko aaaPSS>>VQŶ`?d+++ߒ;@II # >/"buG\^VX8 ׉U*ٞ=XWC~޽{|uAzz:{~ E(W\:_uoܸ"##q|;KE;kv}޼yئghX5""Q,(s%Nss(]Pnsj~+Evv6#(b-\P|}xNRR l 6HOAAAkXNˬ<"ÂҲWW-r?}eJc|r6vWV7Nvȏ/(^P6FI(+(/[l3#PN8Ⱔ?wλvI_xDVWf}+6,1X_[WɮmMcQgw|S%lji{%ie||F\b%dcwrz}(ev~wxWD*3E[*Up3v1ov7X)gIbvXDjl(]cwL4ejnsr98w'=G"d]eAL-IISG2Ǯk-aFX7sIQ ˷ Aw7 |u >^2}A6_9[AybHb=_~OX竒S'zʋ}}|} AAAA$(b"/c:K%a> OH84z}nѐOTxn=ߎ+0+{!`nW25Ԥonfv ~zU_KsQW($]n& &(vLիP!ۄ 8 Uq^9v7m1oS籂2 UQQAٱAפֿV5(0XSGǘG.W7:S{BPr2JMz}<^'\2T^m]]ussGm۶vvvpuֿ~zg+>xFs%|]fP=~\R1i$ѣG;vg++p7Y3BLC2 >Wzų`.) |ԼjZJ ~3[꟯wP/Zq)WK_'   HP5 P]M5%t^=qfՕbܲ|čϲw ^Ȑ险`ealS[Px{?vT (/^1WŪ2r)V&qw^_aI>%V%(W܌9J^}AizKJMxm^ؤWAٞ]˫+Y2DYV[+, (V ]~[2S .(@^Jg,+4=1F089쌄-2VŖ3RWPX6J"tniO]|ʂY;Ooo\U3F __F3Õ|/ )t2 AAAArc9bUIXJ6r"1zKV5Nk/'(k6l3gMb}]gł.YecN"ws^|Y/ΩWu6]_MҥmJX5_Y`֦buY=6sk ۬F`>5$ 8+gv X1?ceL,gۼl}ٻjjc_ycOQUR9rdcWuejf`>X:;ݻ9yǘb2Hn2 4O>ez{VԩS1I_`p Vu>Xْ+*Q[s0ɓ'ah w\&Ī,[ٻ"rgh={{{,]Pnq~ϕ3[ꞯwPjСCoСCE엾OAAAh5cG l9`ii!XP~v K&wH`̱`,ABp'e-DP>q9 c*'熍.9:CT_ұ0A66ʝ\յ rPpz!vP8j{oݖҝ`/ EQȔgFw?J~N-V/TdߍH}y59$V%9)^Z{KP3 (ch{h>g0gڦIa3.1ɛqWKѫM~7VzQ*(]3AYtoأWfH4W2_     7\0)vlx.p0o".,w?V+łrm,nu!)Z[[^8q I{ކ˝s95Jbk1_RA`5aV~_^9=zvַϽ ?!(s46(c\.,c_3s+qRl' ?ɼjsQ>nfBoر;Kil ʶkR\[Aci׽xb2ul-V0+[EWAl/ŭꥒKϪm] & eI7{,fͶy:ߓƟH~݇⎣(,)"Rcc^_||<ܹf͂_|JvINNk8PLa2>s_ݻyeISاe|_^קdD=PbS|7Y3ڷo9qoS]P.{CU\ş P|zT/AAA$(mAY9~>yF!(J~$PU[^^^c6/O I) |( QILNćceueB+otTID rrZ":Ȳ~ w2-#c^~ rbH#(Js _5Va,r3:1LW+5oMA?pIJa3 (cMfTH3RIPVE)',#΢}c9W $(]N3wQDӒ    wk2/X|a\F0.WXP#mGIJ/tz NM}sBAYDdmsu~]uAY*sFs*@ؼcJzgg.k{vJf"XX@TP̼ Ϗ0y(:Ԝ́W2V1>28L=^K%ì6kjU$gΣn<+5epqRAV@P"(ĉs 2;:#+O[7-(|i3؛?-6 AXxV|aۏMo/EK\PƟu<+o`0ł$(AAAA* \(vJg2XU2#q#yLGX5 Rc Z{3sAـl2Ӝf 5}GPV1*8MBپkV%ZX3ءD޵A1 ݢu+^VXIY4? 2kL^}hɟ7$fl³=;.)d ʣRGHlLW?h,7aoDEfA9+HPy_c_7sE2AAAAѽ0J˝Wgr^ަXP~0+?c3 t |uW:5_=n9A6{5J( 'S ;d夹Ƥd]u~㹓9AQ\aʳrAW~/-|Rp`3 >e8} ay6wRo12VNft ʈH]͎vf4 tvڦVR}oDP2dhjjnnnz>}zMݽ{7ٸq#~5***s[0`@1JRy @PW)*b"ie̡^ž7'eW`2'E7ߏsYYi=[>23RPƎyW,(XDP=VA.a⯏a9 * >aS5sA)(W'KxW]krdLd+8~UswA9,"@2HW@E/WC$*e    [AܱI\k5WڪXP3`>3Bcۣmc* e9s$J+FouK$7os>%(ۗ쬠Y؜XɯSNui#fBnTBⲙ;)\PǮJM+˺8QR4bJeˀkJ2b_h ^k:%wsxfhc0Lk[9[[[kTpZdII}6~2EB/~#l0~\3rdd$³Y5嶶6իЯ_?5l!(ʥڷlgP`ՃZ~##dE9*+ x{^S{Y߃o,^ %c`ΔTP>FUyBe+>8 VPOTPF$qcd=ǥG aLl{n|(DŽcV( % 8/v\>;{DPFy>\ Yy+@U~ ,|$"<126R"* +VHP&    A]Ws71c fLHQ\$.u,W$(Xrawl3XA9~L_Rbɬo&̳ |WRqs[v3n``I^ˠ"  Wϕ˼6IeEgoa;ۏ`R*߯u̞엥HP}9Ԝ8W hu?0֣fHޫ22`}gf`a`?]tm('s^N@IYmvA SƸu]h^_kk+qt2.Uh\4 Q'W;ַn:Yl,'''1{ʕ+y;ʃQnȳg$};TEP}Wuqq.u7:_~? ȕ;qFeW;(u={\t uw   ,(X[pߏ A>R/ 7pw7w/XX:2zLP!ʍun.ANKMO!2*re穮Ћ*ʿg95Tb/&KfM~Ī%L10{a#ʴ۴Q2B4O@P<2ǤC1PXig8.+%$OJ rIwn^7^'NB`O|eB{)((BU~s WBjՏ~3OGQ=Ɯ:кRo^cKvغ3SOeqspu[ϫoGDAY\ c`VZ"ʴDմ!zBJdV ?*$I~;0\ÜIVsjαcKa Ŷ \skn. n]t!" <97ŏ>㙟)(Wj-큾Nw=ԇ".rX%?i@0((_a_ր~I˴ib.(#?袼Wn ~ud2JVL0bgrwm*-[lb?P5H(jL$hV!R!RPps"EH:V ]"$/^3|syY>JxvSQ +qOv1~~~^\VFM!Q,v}}]ԭj)ugff(LBL}Rn3wvv(H{earT(+y94h`c#(J%! [eY{(ܟwL>+&u=y@I&- }У~ W;ݷ~\.Qeaaa (o?]Lr’380@ۛba&FG7A"8BȔK!OT̥p-ʀ)^kA%@,X] 5:/߼@VSޞcS]ǶXʫNbs*-=#85+ g m[M^Τm)jAY &F/&㶼_$b׭+bj YZ׼eɵKYU19Ide5U2$Brt'~R*4Xek@pZƴ)ʈwjXPfaaaaAXD<+ʸprqׄ2|<.e_S`*_ 1we=_6{[]PtwOO~jɻ^keF]P3g!{gqAKK@"ňZ -"@\B˵"EJ AikK)UJi`aemI!i1~?83do4O99g o'5o-$X]Xkw~F@u@xyr+@ 7{J+ 3 8 [kP6'0h|0P5%4k ecжqȧ$PdcxGfyyjN@_ WA+[8mBZ]]7((HxAZʋ~ثVBVqzBCCpbqC> o&EZhZgC@#CYJLp/lاz>ΫPC߃_RE@ف)(9! SrSTǠ䒼czt)<1wKeNG0y.VciyO ~4нӀ%" *cK6CCWޞxi2sӍ`w 9%>U7n1O4lЛ q2_b]zVYe#XsA6@pe5`jn9C?EnԠ&c KX$b(wRG40;QWTUF6;:R"&.C3d׿I5zku/c__a=~-e7T*u:f3L& i:XHfjh4R&`}7-\.i%SVxYm9=̓Ƕ8ىV22.(*2<WI(8)3."BT  Fۚ+hl #6~A {g>w:on{;?Ίs2@Ȳ|TAc ( 3(q(@Y_ZqxXa@@ dYa6f6NOQx[Ϟcn.^L;nPe@Y P( ee2e20Zf=`U6ȪF-ͺ-| ,N/E؎gF&YFs=r#MO588P;TAx m 2~TsӊSPѡtTcԳvpDkյSO|>r PE D"!>y@@@6w%YáAf so?X"Xt4,_ኺ۝~Wy6"vt|&?鯗* 7lrɭqtiGNjZG'1x}FBuG%5ʋd?GZLn;*xwYbf*:.Y[E{xG(/)ye5!l*գ3b@L&#e& ( JX,J{mmp8߳UkE~gAr3( I(((Lj7O̙@0A.Nb,"oO)r:xmk[ܝ]: v;ټ;{=(nY/Ec =.5.8d\_歝u(~Sv  A XB24"t:$6M\٬{6Άx<^[P( y yO@@r<|Pg뺟kqthZF 6Eh:k!٥C]:H &%]_ѩ~!K}?Ϯ?:Z/ 0Pl*gbΜ(D,-(ߎFy\dGx*BF? WDh9nk"e#m婳{951_@y"RMYn3P&"""""""@9&ϴ"`|ސm 3x} kpZw(./~C :%*Q  ZuŊ M?u58 Oe %> uC1!v$x%[ a; {Jp$ؔ.E4RIw|>>\.okrVh6h4(HPEw]8izNhv۲fʭ{e2^ss6(ϙ#Fʝߓ+1P@y.gEXm|xd3)ߦNGQRڬl31g Ll[$\{>#|2!xdaAaXP_e R`'׀eK }V/( ,(3 0 0 0 l!B.iWZ2DdMrr~ZrQ*('%r^ [v ݬog')m 2t\ WPn܈Z|vj}IXwE hfg9{F=\ Qw,(gժUeYPfAaaaaaXP {EC$MATcAd2a?mu%(D{H뷬D[u;kӥ+_￸J'KpB;wyytAz?~{>+Oޚ8E`G?.Ы(櫭+{ź%j5?2ϝJJWA9gZ9ZP8[7?)%A_Rѩ=T%600@ݟn3(3OE]RV[O.(&ų|gi-2b|Ν&Oh@\)a|+Ǝ'S3Onne)}};j~@@-^>JX”U{ y3gMĭt%tett`uեVe'%r:u h_4|1mԽTT8 *(R?RɳE$JI3{Gzm~Fc<_5^P,(3 0 0 0 eFvk'fPPFe-VkET_>2kB,'2osQ5\^ܼPWP~J*lk{V\Cs$}Nׁaa@P-UKSc*5[;ɉV[~ѧ|цVk&;ȤJCp6xE"}YޝyJwm rUu.r=TL. 앉1?}[)rc5m_rz[G:.k߾+<߂8N ڼA Ǐy+رŘ7"ʕ+J'N`ܒrpp0<#8pZTZf }t}z|>|jٵ.(o^εo߾Vc ȑ#TZZJ?|o޼I7o.](>6Rg VPNKK(MӧO8ڻUh޽z]x,X@-ZP϶.߽b"֭[W;hHPFy?<*+Tq6Ƥ$W| w^Jwkjaaaamݕ|;Ʉk[徱J8u((C(X&|K׈pb8sna T}D" BZ?ݥzHk, S/-Y%r^+#?2q3g6+%!Ƅ]]l^A8m`UI'Ha!tIkrm|*wz ZWhBZB&YR)n7 *4R>/X} q߽#azA^/s)|F/(IlM@r R%ܘذ!(GƄɻs/Dn}AY)ŝ20 0 0 0 HPdڝwAV6*(OkZK*1Wi2"=AY;h[q[ط. q2NT#diisbc0):-jb~e׌=7z1!pV+3.ުm6RƪMn#5 ,0 1z܎Xոf{4Բ qVp ϨUA#m?!ׄ:{ (;99ٳgE<*:::ǣիWHoQ1?mݨ].y+m9W**%@UtQE3)LIsjgU- NBz&e۟RFP>4f$ $auW'td l1Ll ];wB\vt{TQjM#D?%Cp-?&8OF\sNHr6eUs]h؞~ g1Q4("r׳&KIY[Yo5p1<^*p,sL{jâ1hת"c. Q} BJE%Wg]|PH@eܝ_A[e&6o!".X (8:{6ʢb5$h}FVCgWb`b0 0 0 0 4AM_T *o6#rx>BQi8̩ jO~*̖y.&Kc*?s-nG[ i{^a=~Ÿ,+/_nwCMZ%Tƺ{1vC?sy8Rswd*8@|marr'v]%i-A̿AmCo/EFF*8C6k&)!OToFE;y*MΞRv=MvM|,K}ƞy#ȮdriF&/(rX;U};ƯE]v=ɭ&"G s* RpoHO-(g9{՞ ^f{e>B!y,wƒ%KD_#g]*zKZnjq[j D*תU${&7*G=zs>"*⟩-(>|clٲX[K:tf͚ 0.\1sVϿ+Ay۶m@2|G ؛JjܣSN8_Zu`)z{{gW+cOl]wP_Pw~5T3V=l@䮰W"k QMeaaaaz/KClhPXPB_KTEW!HGV}OhĨeRdcMF{߷*(X=cGя墩VBk zR>;"" w7Z/ѿqjk q>VOx&!H++UtBmX+>~&,ʗsrv%_ 3+9āUH!(0}Bܩd啗?=xr+T!`=$~4T5Kr`[Xo.a2wgZL-6kD EYȯz1LB Q ~~t<3y4.o PȵcQTbNUAy驴t(4} Sh,Z5Aʁ+O>GAˌI`UjhHldb>Q 5۝keAaaaai2* >qVV6 (]?c ۖ~TC9J:yNDiz2*1WoM]R%(/n^>gp!zF[[l zݭz)muɣJW")*Br|;6f]e[Y"ejdWwV1^+j1Y9cƍ_놀] ժ-yKkSPgjJ7Y˅XSu8Gk׼W Ԗ̑UvUރFΝ;5*,*bٲe6V3yR6mr˖-QXbرE[sV _P6"J222tŬm۶]^xUVٰJ\~̽!#GV-ZHᯍU?֭[SqqQ9YlH?aYPfaaaaXP/A9[(ƥe*KWE={'.|"EB,4PY/(7|DJe.9>QY_D}gCߵsU2ƎB*O]؟zR9Jl\?_$$Y+"<.*A98$~蟿dYߺ+E;`+Q)vnk/g ww軑7^VXAA>]G=Yۉ➐e:I} YB<<#%҄2g ިի6D5r ʐx_P 4c_o ʹ3˾@QXy8YPfaaaal)#fpmTP9^V1kQ2 ⺢}4.ǭ[ɑZ{p YGPtlrV"(l1?u/>2O1zmw=۳^dTNu %qJO3J6V]EChls6VA4YoذXAy""lիkƌ,(׎,-򬬬}EG.b>}J((G>pYYYyӦMhT^o!*&ĉ,Ux6diTY֪ҳ0 0 0 0 ðܐeWgZ:g \O^ZԧlȱiR|n c@XX89ܐ1F˹%&V"(?w9׍3F7[W f>HU$R)*?sŜARs~Sf`\ÝTNY[[Uɒ"_{YUWJP/(gƈ~`Ȼgw,?aBn{]JB#ZȾzZm/ӆ\+n/Nu W&g^=ݺEhdd.njI3?Ayt3gp^w"(YgfUFTP֝מ3yAY>0 0 0 0 (5n ?HPs-n\W\mfg.tr{ %V;*m1?אz\ܒz- ʣ=2e{sh/LΞBFra;b~B5&˪>q(2}œ>ԗ\Kgri \PvIZKy%RzYeWNy`\Wcxm[!CWN=帎IX_^\ׇ^_h}Y6 *2|S{rPo1޻FP0ղq?g!1O:߿OGyTӧO#?!!r؆'(럛]vԩSc0@aHHH gΜX[TM>bccWoy/q!;vL\0 0 0 0 ðܐAc3"i #xGNRnxTVa*hk@r>rBV ( 9F7Y~0T_hwr5$P ʒɾC7놤\1 7*҈S]7}RBPa.g%qu ʎa4Qy5>/dv&e1~r||V1g׬CJ~O9x!F|-6F՗ ?~LhWMaa!qʕ?.*'#?==䜭1!(o۶ A߂\RRbi;v荍5Y} ۷okp6owܑ ֫JP|U6Ѿyfaaaa,di(2 &C XꠄUdu܆&(#)eCίza0JccxtAM6m9EDkH`͞YQSs 6+eݴc>˾NOKW%Y/釿?!??:>,eK౏ jr|.h©lո7Zd=B)UG\+2@!j[؃{#\eNGFPP<ܦ@3((F=U 2V 8'jQ ʥw,(Cjd_A[E'T}4 qtHPD U3 0 0 0 0AY ȆUX!#hۡLWa}sQ{ 9.ʓT}֋91?Ʀh"8kn8*ƨhLQ=c:-µ[)Tu")6vCʻ-AQxjk"׼-+3 ]9tJZrv? 1i!ܣ&s09&/aQPv'!ק-%ejuL}Yu,ϽۈLu'(\ae;;;:x rYE^`W^NP:t+[je5d2ת&Cb!!:Cq u (_x>|!aW]^ ʳg}V\RT.7qΜqF>}U%(ÇgC Q?ǏG&0 0 0 0 ðy$('Vּ_e(>]#4c}^0S zb'%FJVg9% 1^}cJ SfLĸ'Kh|޶{#u9=w_؆}V.(gTrAYUW XPΌ`hdl^t{3ec94V'!)Yaq/O[CA@l߯FqE;*)[&-b@bc&F  c3G(CgTmfklp9uo.Id }v3#`PGd,&07WQ3&׊W|U3 0 0 0 0AZ;6 ڊ-Z%Lsmj,(G;wsY/㼫 J?Y{Gu5E[C%i*!! \Jmٹ|2y:ck&cT̅(wKQeg&m+k^ӇzL%}e2C{0|~SHmڴ|axv ֫PP3Uo(11QҳYPfaaaaXPGAd到 V[cE%Th?AR|gbH`;}FY8( AY9ZYTaިB[:ȠZEf\0&֭P?2:gCleC[E sP}[wmP ([=W*?")#woEۍjAYt2 R^P62?imEqy@x AY};1Vsk!qgrXP }[RDPkby&b TΨb-}5:箋gTrf׷|vmĭu&(G5cZg}-3TU>hk_Z#Yf:AeAaaaairu )ëA߅vW6M.~ZTPl臀J -dUQʢ|+7=2ѻ );v@%Ԩ 56pZqmr$Y y]j߶hG9*};vǘGMe>-(  vT|es3`bs2]&(Kќh!##OQ-СCvyxx NXץhzC+gu*(Ϛ5KOM򜜜jl5YYPyyyRm۶5k@ƍmgߴ>.]B|ҥ 9Iy„ h_~*qݤI|DjߓFZZhqqq,(3 0 0 0 0,(f@/~RZڝkͅ__VvNʏG&׹e~^czDK[6 Nz֏;__2_0/OVf5GȫR2" ܿJѩ={ 2 |go˗O-'d|Y>+ޏsee<_u{e;G"iŊ梟FR{TPHkD$,M(Lq; [LK닽?܂ ʐ59}҇[}wJ14}f!1q?LFV\3_A9?]xhr~])2&z 4sI2YggƘ0/bzѠZn$%D! _n"17}EݢCTbث5;h>b\TQ S#>"0'? )7[!wmT X!4 rN@o2@Y=W rueDg״D;c6ru)Uשک9Ѩ,>gCP2 ٷ &s ס WO6,gtyf}Arqew@b Z6iӦtmZJ fJz i2,A;9Xw] ʴ:k4OM믿^ Ç7 (+>\i}|| +˗/ѿrJY=Ӌ/f~333~YzA8;;SQQhz"""|O8~ߧ'K90 0 0 0 ðIɠi)(vG)(dҴh㋫Gdɧjn]):& T5Q3y:D*UOE?*8!MZJLsh!( Ւ3Ʀa +5}|*je h q3kB% DF\Veț{&0%.:Ii:zҤuWVBWBv}/E;UK{N̐%w?{tqS[Yi:6bmm+glJʢmmͥBC %j(:ayތ~I<sxey~|/| Wv^ṱʱKw%.'^X46%.(>XkEbnL2:K_j#_P,%}4qMˣ#iDQF]`r,~i((oy'FE&ې7uCey KʭKn 懛ǿODw%FbFrE!{\ ~.HF_K9O_ȸY!c/|Ԭwi.dG %M#W!EC^pGOPz+@mE ;DRD ^皭 9V8{=A~x:%[Pky^W BP( BP(,(̱,)q4EC?YȡDqZ9񸮼l dn녬J[sY:C4FlAizﺌD熂Qgb2kl rb˽4y=[/;A)[/w/:SH/P!ҝy}}=D*[3y gHOOG%(wU׎xbq{נĉ ytʔ)˃)44ڔaD ʀ?S.** ) 3g-4445[n-`W]gD~ )w!!!ޓZ ƍnʂ    ʿ ƊRPi:,!P6:L=Nx_5MUAQӇ ߁2:YF>AѐI_TBkھbؐq戫y!>TUJrNM3|FZC jx<G;3J3kNVTQEPg竻W9e# ξm -{)V,(C)2<0Y}$7Z4XJv-SܦC6g3AB_6")j3z2XDPJwo2ײ@Fj0 |:{g <h!,m_ɐQ;e`\ pN?U.K.ɺ:ԴubPUU[q/<< Qg_2Ҽ?%o*!xƪ4kc GjX>?y2ζ԰G===Z }ž={\+//W ~~~Z_Pqn#g'5>*U߽>1''P4ꬬ,ZAAAAA'QPNNIK=,L^nÏKWvh{?ܤw!$R##i H̽y qkte? m߽EI_^8T2iN2Sg9愔B2nAY1oA $Λ}b[/=.$кIUJaT\. n֚L>Sacmާ?tLkX7'@lm5jJϳh>m>J5޻>kǩp.[PV~9I_3x{ݹ?B-L],➾.o?ٞttv.MU? ʊ(q&5_+{;!,Z@_^=]<5GeAAAAa ʊFM-[zb==A|? ; ' )t;UЛ92Ak_>YDb ꀷZ2q$%?9fgJ]65dPC<:8 Y3U/Ӌς]d%K,v:D[$zXt`jfT2tא 7\~I)"˛5kkoU={-Ҙ5:hLNLYnrү%b2'7+9Y7x=xHBUۆ)/34rL0@ , U&\`!cK; >9z kϩɜ!ͮEG,({yyR1PYCJhnnJ#TR]qq\3III?w,m1)hRPDBa\RB,EC]: K :C@g8̠] J~-b& !%q0\888bƫibx+Jj/I"i<>ʅB!F#$]_off&looǫ6Y&Ic@9/MQ4mrrY }ttui5M?7 !E<޶}>|o>@9Ơ>Ő=77@VGoUgl23POz|cl@T"ۣ@ ,.6@,P(e( e( }oF'l}rqqCĝ{!҂Vl@Y,PprPoltXW(@FW>OWBɳ{<s/jv[A,--eWJM&ea R),..}[ P( 2[ sFvxF S7~,P(wY.nr7jad쑭?b|>oAU^%0xP DT $zl ZVǢU]gpE3|?}yO-D@@ LX޻ӧXsl޾u/VO"=Nz^#Z-{L}۸SLd2l6Dߏ`( v4H_e2e@( P( 2e@( e2̬Jqpo[[6VR.J-fTj@ s$)#S=xg΃8?.ˡ.*[]V$ "J%S56DD9A9h]CcM4Z&ƣŨh<ƈɂBWsj6ms~ZY\7~x 3<[@@: ZJp8y ,_R2.FIMNNeLfAtLl˯ˑ (;) _U/@ʕi<;oaztkcx}fanڑ/7vs}|V,ju@y̤mڴx\.fz!ց5_(Q߄c8. \[Jg{NZë8<8ڂΡ% ЫJsE:6~pbvW(+OqT.~x/` n!rs`+rgc 1{yJ ) /<寧@_y;*@h$;G~]6{Y$0QB@M $KȽ&""""""""""".(Ei"Nv$P-Sx>|(^{se@Hc)"s@YȘ("RM9Dk>Q 9_o{5eza\*z@Pr#D|1# L!/ek3 e""""""""\؂!|Ծb]7"U5: !as_9lvg>с'G݀=O*8yecgsy瑳ADxJ?`<(e F8J{ѽ y *w71Pv@YV#). ?*ardX*Cjr}?ɔ#w4@21P~He(v⩷K(;PbǣrXj'챋u*c3 e""""""""trۡxpG*,X[,bmwӱǸy>87uưNo뇯1gԌ© 8uLE̾Ō2 :+uP\s. UUq*H,_!y1gk^'aH 3YkHYY?`< aQq cb`&A. ML ${MDDDDDDDDDDD ]2PrrP7;@Y%(txUU 2eb*,4t'e,a߁rjfϿ~e{2 @yˈ'zm|7BYuaaklU<%"]cowC塸fG l]"P )p*9U^:O%(sZG 6PHUM)%Y}EBY&3*7RlIԑw_އ@<ϓU]3":lsyF=qÆÄ gqe qU&_7Tָ9)=.ψێ݈o4HŴ{NkØ{ʗ'Uefe  ^8J&6?cċH swIyej/'fy47b*rԺh ڈ7|dxE0^#eߋ@2Gu#KN@}Sv5#|}3j )Eʃ_`v+-Ж_#uW(|Y}.:T#/h tUL$:~spQw ј*b~ze&Pf0 `0K3*{[ 0 QTP4Pa:HS@*EQ {7K% ћ~ssM{o{?{Κ30 ~KO& 7Ar/EnŖ2{bݣ1yVR LE\6M|smWWq*ܫXq+1wQnjZ9pNC.s5#bk݇q_G::J՚[ZiAYs7T2IΑU/>nqچ_].b }~0\B[{tl+8\oۼO/.oέ?۸DY8btg Xûڄ?|"     APvsՃK6KJ ʬž젌r-'`2PԱJXx(~ZV'f??|f/y3 #DVjPXFi(Z;,\(VRb<ˊ7]e) ʹRf2?qBjDѺQR4+VBAͤ.D}_S#.qCCaeae̱ eroe^DYJ@m{BXؐBPk m /₲43槓1*u8Ty=*%YHP&    OaR?f0~~o`݁!G٘پh<-=x?|AYuPTN|9.efYĴq1&ʼO)d UVRPV9px@).0Wk\ї8'0INC $p-QA٭cP QJG>Z.}9      e^> cjN='ek@UbbطDNv SLg&3ohmJzߟc_'gCZf2gBqY!l|}b =󝀱f%l s )evIYԽ{#{ށ<\0Kx> (y?s7%n>>/g]'k{"&(=R^P>>m FFu7e1eSDk(6zGãY(b_NV۞Q]s켼(u̞ 3\ $IoIg|WXHeq쾔_`GJa)RCQIu>n]6?&82Ǭbn[snT\\Wd܋odARoWR;_.%ViG^k1 j [ZFd5ρlmm| (A[g|^am:,FəQ}Ǫlٲ9"*Ȣ; AAAAoFPWR-gdvo!҆ieǬv1Py!K=>{lb7KEͯ=A*-Ocq?u~LvVd]Qi]A=tY[be} Vl~`_&;hl`|ly[?CfX0ǚڸLrqg2 7KA=eP^`܃p3]}_ǑͭTNCY>j­K~ܷf?8&=>_V?rcvC +p;k &)sMEAAAAA ʽZP0jJk KLᅦ0&s̉c:E%uLP^u5Ѽ uOmwlgMёcH|!0x@`|~N&(ee | g =BXkg,HyAy5P1 [ kgHe;/+j.;u!FZntP|X.c=+r cBXB25C/IB7sk1~pjodAh˟.ȗódHL `7f$*3I .τm؜Pݼ*~.eQY ~FýYK6NRPN)LL5;O:=p^ځr= θT5ď;̋ G,_koa Hxd 6{+^0=X&e     B)+9Tcp,Ӑ<4nj7>.=`txwюOFHԍx` P *x $W-JfP~ l~@GQ%6y/wBY\*E_׺1dJ 8 #ssiGdc #2/V-"#.գlݓ{oJf|h}^9      {ڥszN,Ms vn^b`j7$L-q`\SYyAyX?8X/&3qϯW +7,A84VJ?Bkz~@u분,{dAS?/g]vmsv]VE|EWRȯf:wILՙ)( Nvd(G1燅Fɬ2v϶.xcgera8xtrD3k=W w%!(4X`?ј<\["'; HRF8iY{Ǝ,ƝW H2AAAAe1 G|uJi{y&A;7ʪcd:x ?`g6+`1K [c* AAAAD7xlA! r]mab2⬻iC8qx9/,y1.XsU7͐#Ǻw1n&6.㼊 U;\\&,4Ie'_ &gX~stoTux6JBAY\o皼TN]&eX<_y9;>0\ce6nF_|Clނu<.6FA/qs]@g%{r&9sAAAAAA$(~AY` ^bd81[Ų"7iqH:&zJSVuViEshaؘ^WyPfRshb&mezLV3up(v:VV (3} 6%UL-@I8Hc¨ W<6nP"/?MLPvqDy|`</[]fh=ǟس_9       Uf]PuٺeqJl] 2 {"㍞KA8ևeT|q/+Ymk:ǐU*yKLs|Od,ܻe2 sC_}  $(AAAA}^PWB-Yie|vDvT9☭rkj0Go&'(%'#9yL$S^٘_WyXcМ|`%i~6\Ž«`׆y`A1 ]DrNϘ8Ө v:Gs~?7isཪ=q*gk0?23qL!):k %mOP6      A,(OKyfV)((+Ol| l~k :G"9.XY|q;)KtfrVNռ7vo!K*;sȨ-(uo^SrI#Ɔ.); ~XPuΛQυʩ8ﭺn (ۦ|Nݤ[dFT%t刮{$_# .ɂq9v>$AB ;&~46ga+(+߳ϫ,Õ]ͭ= -ޔe     I&z:zc㧽.1[ k{8uusp.9H"SݯdWA*QXUjN'vwh̲ErR]P{j^wε(Jc~[R ʬ0^e³_rSmLpmκ*WA3ďINb9xA;'8=g-(#. Xy,k:juI ;)KtfoEEAAAAA }QP]]\UWc2IJ?k:l^.ed0vٚ8fGg2o7y.>k? yqӛk0ɟ[ˑQ(<Fe'AYxϦHق巧dco`+*.O QBPf$qh6֮LʄXT4vA0c;'Oꎠ̟$}E|~FќNe ݿ_5h(FK& ǪPnbs\x8nrTL`%'#=C%lWg)em"WPV}g;?ełn~$(AAAA}ZPm#[Vd)@:hڰ_=OcE<`8ܖ9Qbjglxl\y ׵w(5'v&Fշ}_fR,FeVjCqG}H_2=g{K4%e%2:'''Cuu5x{{?Z/      A)(_fě{LP^a p}:KɰW?\nawAq jd 1q&^ź'wKJ{lk{ :dQ8(v}ve=zו!(=RFP^*<{BBweimRVHsF2;Mu:Z./aܽRʚ^[2JɜS$e=96ab"IKlw sG~# |KQ$ע?> ƚ n\(dMggϙZ BP0Ņ@j oIJ ( J6ͯAӸuWl+ %YxW”D~^̉x+-bsm2w_qޯL! 켲eW=WJuh} əPR3?+u+(8HP&    C[17(q+cH6l%A7ՖWZG;x.[srYuhg7;|AYuu}E)\5Nf!5XХ)xYyݼn4Am0{i2SlRi "363c9_ 5m6ܫM7*ZorYQ{P4\WBbEev~āسųŹ0vsخ?%y|E%R&+/(3Ht^?|~JR~P~*(UP} V6TB.G0PP&6~,Z/GE8ݦʿ\\QMr2seۍT*ŠpFSxK @ @ !(߂rZdBōx?24~(R+v`[uTUf,=3MsDf\ϗI jƫ<{?@Z3[:5ТCLx'3"+ *>_ɲ 0ݺ3kϬL0?{ j%Qv2''xl;< 8b SSz3@VbZ8_9 ;z؆ H&XA~{aIwY;}$0g  KՒXijg2-#Q)CMԟ {iHߙUP~9iӉwamnLe9#)g{㸌߷𱏎 tV3&ϕMPk߹;ŽfL|>v ;bc?> }z=ktfA;b:_zLp3:MwT8A wk˗:9_P҂2qxuIUBb|8NoJ;1.B-1MI,cYXE$3fm*#j,,)lX7ab^,˶=OI{[}^|B|w@V_rWDQ:Rwvs#7jLAAAA<2“(Ǟz,؞׏|Atp6}=(gP=05Ų8,^dc\WA!2+X9v!AJ;#(q. CPt, 悔BZ=\fbڋ.?k|*,5}V"+$f<>-V P23I&HY?G=Δ&GʧUN֋uo)i\;kK)9/3>*f A9HAY gwHau`U2ʲ;As[q/YEyy0ڨVSCPFp}dQrt~dG) ZH g )sQ wi5֭ xF-M8}8*`Rvjf|԰ l8eWn{wqJ lKB 8C"YW§ۃ(sK$(AAAA8M0 d|~b2QŌ?(.kyjf4kJ 7AY@]1HI˂ʧ&eЧQ6v3~kƗͯ]]l|@: Zgg j &07 ~p(q>ӆ5 |/;~{YP=9/(7<(A1nuREAQ\WABJQNF]Hoo(l"yAAAAA ʏkA.8kjn8֛ҸKq23,J%kP=~ikuI/evwkǘn諂{ƳswÅs`Wj3g%(sZ\r~&$㙧n^cЅ8ܟoܿ7Ћ}^'g|&fGXc; %62_`5Y]ac%HyC~ 3;o= LyQKa3L;H*((]4H%YԵtlZe2sCB*Cu~dpv"AA!Ws;^97{]3z "0Kt܁΢h*;w=]X-Rd7 qRt\^U6&>- B"{Xne"I!̻jxc`"~~x̱,ioV/{Em&ȁ7Ӛ_|{&K?,gvfĚ+)Йf/(}d~Q%Y9ڏNJuM@Kat&D~{ڇɰhlV[Pі3Iy- 3FWQ`*"xȁn1uv18^Las+PX~֎89~UYW{:ꓓaLkxИjϹp)8v2_yhk*,H$D"H$D"(2)ϩ`o(㩝32y9opqϘ/ [;QD!(5H@ 2nxor}U!3YE +iTLͮ3X$r[FDj2E_tjz"81Ali!Gcaje@{uqy wjIV"N0c6Y&Y5(ĢM)`C?)ADdâ(dԐ~H?_qk Ul?<~c?3'nwkdFnh;xs2 6Ֆ#=WvX`uxI߿>Oút+ﳢW591PN.E"T˗Su)w~THE:[edOO:Jb1bff&J=^ P^@>9G!{,@1?P&29POz`Q+aMe2 P( 2e2[7ק# @Y,Pe2 P@,PӑJ4wD )ۣwOn9e+@Y ʫ0P|F@q-mu=( P+H"FEVero\] .@yu \;lQ>w@2P]X=AEz)_m/_g}jh@Y@Y P( e@@ @Y P@,Påxgbxd=%Aڴ76o鳅|(ב|&[ME{FѸa={B!x-|J@Y,P^&Oo\+_W?%uI֠|hA}R|h]o ;&Mqc6{#ws]Q~|[v1)eQL IxBl>Ou\$l(K8sat{AMJB+ާIaMjٯIeMަI\V]mm*K||# 5E6>e *V> PI'(#b?#c5@רHoJՆU*"ϝm;A+M?_=̕vpǿ p*RDvv9>@zAx=*9옎n?ԏL<@@9@yA`%(KY}* pTc{NZnEm aZ6oWw tu&oc+s^e)@Y1(%n rm}Rk>kW䩗EA  j5݈lG-Ă``-BsٷЭhK=N%pR9Qlf|:?c'Ӿ]6ΝkyQ@ճ ^.ҏ1S٨}U\"]oAfEhtQTjk&0;WkW~o|ܧ/s0~cP u *L78a*'7GW\06ῺI'Kb=:W0]LW~es|ފ%cR1T< so1%g詸챤5dxX|}-󺜀zo w|O8%d*WOE=wJw111:77y;t`&MO>eϞ=1 .[kKC#G؍7ؓ'Op;wM6HE[PPV\N< {}5l2ͮ=z˗YΝZ% g_~%kXTTc'wkKv}ad:;99:fdd#e%q<yDGG;B?~577CzիV    MPvӻbdJm&(ttwSJPPNV6bs%9{dM{6\ߙ. t 5r@ RxZJi6խ/E= K^u5ٟA9פ'Ced/!zxѼ vj iVn#Qf$??=?Ծ\2)ʕeAܖԡܑ #@D6)6 q-΍K Ay@\;4yQ䖖n2{$ kDa/ϐ~U/%<Y H" =8$D{yn$(AAAAʓ:V uV ʟ{]BHk* _yvx6=s$JK s1An֪+ab5v^s:C-:o[ ʼk׶8 5A4/3G,, cƙ9Fr-UZS95(r{M<2h$d\6HQA٭MLo"ofC Qk 2ඦW^ סxV!2{;v IŸ$a^z+l4WѰK傤 "ܹs%s^~ƍЬ,kLw瀴ol3fǏN>;`/^4G_l%    HP{AY ʹ)FMe )!(h^ GVLqI6X mOpmƲLI}-1kԑЕ mlҔ2Ff`ElKXqY̗ݝB.tFY5 %î#!/V;+_{Ѕ IPqV1}"&#+ ;[˿>16s?[I|ף>ʇs׏{ 5eA'hCn8 ϑTDP>8.6Hã"P]qcB b~}q^xvoʩdUQLG1wd7ڐƦKVJs}J)O2ݍtMH>ONJ`)"^F}Ź,/.B9ym% /k}pqNX<"-vk۟],q g{G$( cJ^]~$Ve>K!&b@(EsLl1*̞r[X TaJ 볱;JɈ~Zq9S%e|^,x]65E?0Ѣ}kgLAAAA\P;_qyi &KP֫,)Zy!8)B4c_qǦ{d;aaǹ̆Â|?Gv]o}y9:kX6:dzSY@۱蜮8r16:ODc+>KYS47S Bs *'=d.kZL=f뫽q3҂8tRov?C{s؝XU\֕aꎾƥ![֮˴%LݹStb*7o<629PoCaӃt^w0M8gN PnWxnM ! ꤸl3"vkN\R A_ vuvv6Razj|ƌΞ=+ڔ$>#֩S'͆.]{BGV n {C'_e?cVRr3,866jrÇ#ȃ@233lt[YYs<]qC-ѣ\:c7%K@0AWwAeYqpG0ן3կg5Y86kI6[X–o) A9(8\>i,!)C7]<ˌl1f5_i0g7c+.3#~ZM9B^ȉg#YA/ojp }? E[yleC1v>rx_.$طɰ&/pwgTLAAAAJPn_@9v.b,(`Cݎkt~s,Yc,t Љ Dc\>[)ActN~4^'!'DBuk.H̢= 5-gž Nm!(/}AYX.8sl"(ק=[Z.Cb惓@ĔQNP1Uj]!aAW*#-ݖפ h , jk1% "Wv!"ADy~K:} ʆڝ|ݭ'? 1o 2 AAAA(nF!H1@Gb։/Zs׃]x)c=;|s<^n`wz> $ELy?h0J˺j|3w"ȝQ q>3(,([} ҥKJVϸc`]V$#c/ \1ٹ҂ڳtLAYbcK&w+tOw\Z{kjc&B#%;;mI~>N'ĺUV̙ûBFsu.WpD06m4hwu1ykj w"9,0:32{lŚ:,sŽ=>hݼV    et`fLLuTHPOQi?_x5| ^Z;,]ɥecr[1{j:C@0t657XMm1 Q98 ]s 4ZIຎ&ycwcCsc_IKSt2W,(da+-ki`*GWx]hR6fw0]YArYitt߈qע=<ւrmo~>ge kgۜ%gc'Dlzȑ#qT*؞={pĉHRrJF[6sEtpH YJl    (. _Ļ$@uK0~d~.{ix|GMk[L_q: !c%c{Qށiy"ҹ̧8gٵ`KI߃?5HWcc#s'NCY۷`ٳohhL2kpkSAʕ+۷o\;88MJ|_v,(+9@1l2kwgw6G?O_j%    HPG==:r9V 'ۥ鵕 D~`#SIJoJX~vtf03{,n&{A駫8n ke/_YRG&rjH@8q?w&@&X;0,cW(cuӆ% s9w k sl+SƷ%((9٦ l}-KBPBd89}"RPbYPFE[B8gDDP/X 'A+\?xAY~ʼ]skcR(e    2-ftȳ]aކ.[ʎUnĒKavb1.U|d{42ʵe9žw`NsuŭMLv2VkTPС{EjUU ̙3߸q%փ]/^BW\ח9::2^r۷ߋ|eZm۶")+bx_.ܑ:wDZGe?GAAAY0ĜN"g &C>PK}     AMPvrҲ%8vxA܎e1 q/ 8#S\ddc/gV^P>q @w`Sa=RrU2%c̵cAY~eQH.0֔+krqB#D(mlP*+yN潘5ņa|NN^,}GAAeԡ$(_ql4ΙfNX`Z}ʮ;j{Ayb8$sNP_#)?8Q$ZJPPvLRDPN+(+x,V(,ͭIAy`b$ȼ9ϟ5 AAAAM=+)(C'asPOvMktf,:n'b?C1p=0gUkUtk5}̺ mu .Aafk:[[YkwԣGV[[ ϊk577]җ[    4[W[:wX +KADic2IN~P\N0qdҹSϬ _'Ýu q՗KqޏoK͑k7Ze1҂2O=d}xf9AyY8W``T\a AlXy1ӌTEs'E#A59oeVC|X; YcnjzA;0w9(S}Ņ=}V%BP..67;sf-_YJ ʒ]sgk=?MA9:&Ty GA9C#xHh`|ƱA0EH7u=I2AAAA9xl}G9xwd|Bxa_ mđ"f6\gvV&.'}EmWʛl&v&Fu 4t 1]()kayħB 5f"ġ2tY6\2V9\:W} a8d\=EPv >Nz߂2+8>Hځyn6{@0etuJ֭c(񼓮ϭq,ZObb" [Pת{FsW^NmN1w^Ç,*:/YDP+[Ν;0}?Kߑ,޼y>G8ŋˋ-_\Bow^|?]˭AAAA (r_>7ksAy\}u 悧iµ66?lEќ,+gV^P8([}Tݸoɵf/yK'k7ZY/( 8ȑ:? 2GxjN|<}X Z$}im$)~dQARsAs E~~P+IAPI=1-$0RAdqll_=eDS`4 3Yske:{+r- m cD1Y͢,otG+kY: EYq<ckA^׸B;.4þagٱVKX^i ]Ag;h_kJ(_>_2rY=^ق54ASSوQ ޿ ÝBP0-UuukR2bXq0X?LAAAA|rEB8vөuƺ$#N1:)R4f{u;,%X8vu\bK%]AN9_t®e[["܃.cp?K-F We^zZIa>W Z<rIyf)Ao2. 8q9b*]grrcnilRPF:kbGZ=xUWW&vpG\?w]]}     AC@LF<:q8<$ǵtW9>HSTFhE 2.Qf7PYEEvŕuF1.C5uTp\0.:6;GVQf?|{޽~e*w&(u7\hm$aH#怿x >9+j1^{GO" cQ$%m^ ;|%0x{.((}ӗ@ ~Vl)'Cן K?s}!1y1'-<ؾRJ` {%]P&}{ilغҖ'XyY(EAed)-998baPiqDy~>zBnWƟΥ!>^$yC0P'*(_ 3B ˆuDN fc\<_)(!bXvF8qi.^At^r*<A=+ Jl]GA -yaVD)eIg42ABo'7 E1FksJ r, kS>AY!T lH&?kK%D_6fW+g(7Č7qt'/0uDdieBbIVyWAib^} Q$bpPe9} =V"ٟLv{ eZ'PL(̩r "fVLuVgooϟ?'1fWYYzYY^'N: ,_dd$ 'V:Jb==='O[[[Jrvuu!$(xj߾}}ZՅ`ٙ 5III(WSXXmiiN!!!VbccQEyy) LB?ˡB;88PVwP^}%wdAB`` Jޣ#_ٜa? >>>vvv?Aњ)::p8p87Ay< $F$ njN}8Ko9E$Q3Av@Yo IgeD{n2JCijAYB ,a{0aE 8,OPٕ"bע K E_P&YEyѺ%8Wj8O ʳ{ŬT5eZOJs!.PcQ z);yZo/g426ӇʕW{¹k?ڳf`bEcg1UHs>]ޕLtV+EPr-(5\%(^n )\Pp8p8 A t%\[% qO'ǝJ[Z?shrbp!#yI85J٨zkz.|<L Rjz͆+dT>iG7/+a(Jq}E誷cAafٯXZ2AUe8 }D၈q8W>dC ,"vX\gs(USj0 K(Aq:G\1bqIh7*_QFk=PQJepnUUEuALec0OJlh45Lkww7ʔboB@%h1iҤ!rFb`Z-8W>c@?H~0ȺG666}i&6#8#/ Q[[3r+p8p8 ?AykEѠ ʡsgõ zvkb~uhi"i=/QX&PÚ %$~>|Ix{pe|EP&^8&)5~0,X!y%[-Ps`;h.$v=e`mbJ=# ^IŒ͇W3UieM%I#((FÃlQ#{ EВ X,ɥuْ8Hꉯxmo| |1Rt(UIPs[n%day_={.HMkτeyxƘL[Rh( ʄrlhrɜml/:JIj (ZQ|y ڿ 7^o++M|(IR:Z I(֣8-T "hyCz{ ?0LӕIG;>>﭅|jTlO`/)ld$Y-ƞpeF0gǯRzw:zNB2p8p8gHf Wd6pù .oCuX)Ťd\RǝyV;}%\6([lFt DǷٟ51 FL'؀ 0 &w8&ѺֱmWDSef`摕``UtMNohKf~)sesp& Â蟃ܵ`vޯAik ʌqH& ]e~^8;7jjxѨ?!iC!v*F PP EEyM|+jy~/~_O>:kwsN2p=OsǴ$+?~_Kb_uܕf Yr1&s+#h;.ׇc{8_dc:_O W4lcONNX53D"jڅIZD"!@@_;9m[h4̼l,JL&6;;;%l9::h4j֪u*NXt5uq@PH t:lZiw]yQHãpXms9ˉ@5~uj4vRhݳ3}fJg4}-뼩TJ|>~G|1~_Χ5d6 trYp2(,^>4 d~ (S (S(ŸƲ߰`65z5N@@2e (P (8sрLM`lllH^׀5(P& 2e@@2e6q%{psOn}mKݠ|c~U 8sYVVVN, ^ f0XQ,:^mŠ U;e O>MzrnV)PբZFRF^/ݮ}@,P(er 3DVK6ln|+ώe`RdtD?¢R$A$R.@?ʯoOO P('Pkm{y9Ogu5(I5;siG6Q:}]䶯"ҌLN)nc0p8~V+ m@,P2 P(e2@Y,P( P(w.:QɎ.";C)O#5+-` 0WVqtQ UPL<j&uUoYkl|?@@2?h]dT'lњ1l6z0YL/nSHh"Bؿ:{e ,By.0|}}p8z]8_ L@aWKϺv"lSz]N5igr´nۿ޾'7ԜEۇ۸\jsĚHş_RvRy#TP:v?5d׹^챝떄z"E߶!]頪 Qe 7*8$p^%aADVS>e2upī:>%ĺl/}fY1+1(5_e/gVRR  (T(YS_{<e,ٴ2jْ _ފʬcMYD@7q2ee(>5G/\zXf-Ro/8zfS[vdǹf^Qק~f-e~]`,2fZfjL'Ks=vfx6.kF]5Nswivtf̱ ߢ-frLEskVE-"?orɵh©߫hUVVyejhhPiin*OOO+|`Vvj PK@w v׼nCUV&n#L@VLfMVX5̯Z F^Ů[6mB{}}͛3{i* 8~6k‘Zκ51"v#.JJ (e 袠tMT`EHT.n/|crw<9g;վe;1 ^Fk[XD1O8o/JѾs^vqQ=o }/0P&"""" N||+ޔŸ羗kESk[1x5ZµK噑3kL&E\.11P^G gOX32.21P\~ ( ē Cmv'ډpJ7";j O1P_׭N<1a_B)/e"""""@9R ]2[xB=hm 7nڋގ8 y >W b|\h0د"k4tzU}{!DfjߠN_'T@Yjx5箁؎bF(^란Nҭl5ݽ7C慎a;jl6(@Y~%zoP.(̇s_8v\({u6jB>{O@ ^oO4:?++p&~Kxǟvw6Ԉ kˁP%o]n9woxGfܽ{F+8w$rr1PGDa͵q  =GǨ |_|6lD|"tܹ']%Ś]#Ʉz8==}uXkPhWF|#vsGCѹΑ+nلvie9}h +Zb/{TM]Sqc\lKofXAWX[++WY +1k8\T!%I[ҧLq x>< }y_[ -.,S`Ս"GzKaQ5ڱ䣱ɗ S_6Ar$+NZM΢ZZ|L.`E`w!.ƽZeFV^MIxmm0:W/R^nXQоÎ}]1R/ɓ wbS%8_yrATTv sAL~W(tKdf48﷠/D|ƇH S!Sq'¬N֛$$Gb|0{_5W? reE{/&Zt7p\]Ymڰ3<8mD :H^~ߦHR탓7ɈS_pߎC9iO'כd[j}|~t8/&0#p w8Qnw%%3?hZE:Y?MH³H#2IF0KIlֱY'gpfxls&Pf```````&q|gdX]l \TtݬhƘ45b.vJA$.NR0t3PAEaRtx/>}<}x症`&2 |$]3lNtK{8mZ<|GVcab}[M%{@ 1ULÃ_is=e.^(z{ ޑcm}K-G#(|Wߞؿ42tw_AG~7U 6ے@@~p%][t?Y ]e[{b݉9 7V Y?@~&9ջh~ݦ)пe'aso)wae=vُ䇁A`=rzE]O;!\'@`0[V<p\HRT*d2x<0L - ~?42(6771:: ۍB|>]Hܩ)dYŰX<"t:5s"Vm+ f`6...booP[*?H`gg6fp  "ˡZ5v{ͷ}fC8Vg֬~Vm]N|ڹ3$Ix^>Vl`0 `0 `0@Y "al9k[@Y!윙3]+&~if^ 8JN #%覰,S#1Cd `*,DiVJVB!z#-oD/lU]t%3٫\={=^lkI*  ʎs2aVez /hB岿 >sY@_Fźjih5f{+UγJ=s}^V\W:Zρnq\yj&*̵4Ԛ =75֡JAN{ ,CV/(C2a++ԽG!O.mBd5L"* ;kƚ-<ޥZ7^ȍsaH{ap ʮb. <}kc\&ī۽Ϝl5wp 1>.M\FCfPVN&֤URI(W@sYTE['!O@%؜,TW"oUN>< GÜlIXPqϰM;>|n*.I4ldz߈ ZjUEtϒVo@ qo*gr^Q{DQկeϕ,PUEgy4CwB'2fAL 9@vzi>'B 48g(W'*ZI_猳"(J9c'y,g/+Zeݝ\ ~0sAɢH\cSD)r,%^ ʧב]H }S\}pw^w]gmaaaA{ *vڹC۳~&?񌥺vwmF!,mw>n5~'hWGoD9mozD@+@ PJJVStc4ƴ,1mm\Q]/ⳳp#n1䝳 VV_#׮Q7u"p WwG*8Cֵ*#)n'*Ժv&]Xmm Wk((xoy=Zu.*E{ /%SWŻ5w;aM5!P;h9ˎ=RmMgjYvdwRRTfpe|jtaю={*(O:shh/֭[SAAl kUeelƍ$Ly])uvEe[g^WF?WFPFeT Eu*BV8ŋE }&MЦMq'8cgJ9o޼YQbb"1ϝ吐Q[mݺuė󨦦?ӈY~zKaaaaaXP BjÈp))Fqd}@ !y]]pNj!C#o-q!4o/>W"6kAM>ŒSf(G6Ega^IV*$gC +sb );oYE"Dcٶq?y WjVIBaEG)V/̕+*7\=bϺ*B6NБQjlpJޑ߷12w(w"BMRDPi!K@|w0wԼrPP }M'C s\Ṳ(!A,`2٨MioWZf,1ȧuKi; i/gNsz*RHhG`-мU#J{kv! Չwq~՟(y؆,*[arᬺƮu{u20 0 0,(k ;雍 &OG 6ʡg 5Hoo1{T:{<-djJJ"k9o:IL[vH"ԲN) QX׊~):,..&~!5dZ =g5l 1{ ^"ۚS ժkOgW*I]i;6kUee3Q;ك(-*¶4>tT+hVr>?;R"(/ΐQ=XFgwԬqt'RLibq#Cusm?Za8X+1D"BEO{kϸXC5SW̫f{E19*ѱaܕuE={Xl[meS2dە@p)v~3WC.+E'Z#dYuSPp3e~eaaaAvGC]|nɢ0T'V[K>&_qnnEdm>cn۸!dg]d`+kس|K=wB9޵=yxPGN\V9ٿNPҥUt.6.k!#{gD{0mJVƇ@AZ5"HP6yITLg2*ڳ;cYywIžcՕ}4,tC xަ=C ۷$u!&ˊ-LnQE7 i…R\|EnEEAjUgHZT'(h+..mwmknUjq!rJYMV@"/-- 1 TUYP#^ZZt]v!vvv?r.;mg\, [aCnjh')9a/nAYsu3h<}">kUe[|nQqtsVzA$5YA ܯDZsrٖIBՋhY=_QM$(y/V?}z&ĩEh==İOY9f|XOw|uXZXN=TE_uuھ!ɦigݟVF=kcs=Aٰ},֕/V[Eh)66M 1 is13p3)(u/ʻeaaaV5įYeIKS+RXD\{zЩsaVXmǎǍ'#t;yn~{KaaaaaXP~e)1~[B(qʫƣ [V~6E4 ʗ?cgPYY,(;IPE׿R׮IhwU툠|sҝd5?ݖs-(B\BNڊkźlC3*t}ϖL(5"W&a%#RP[h\?>VwG e bQ [M&D,d~-4*,K&i2V?N>Q5#-DqOSUVP02&3)c;>a'Nt:~ױ̂20 0 0,(m:RҶHAYOwEn/{'X"*/zxZ3b?zV>=`ՆEYZXz]vu>Jgn}d)Uz);wD Y V]7@ t W'(Kf% 31$4UO1_i{L{f=[h| tY!続cOqwk)O8!HBP7oQ.JsW_}U,ݶmz)[Uc-]ꫯ^^^hϿ|̙r^)޼ySw.,ruuusu&Ν1߿hm9/aaaaaAAhhMke5 Kw*YX:+ I&$A/8k~c5Hy}AYsfh۶g#>z.-^>W֓Џ>@| ʃbDh©lԸ^5Zd##(4(UW6eS􅸪ΌAF*qQ2)62-:"(MS!(yEdxNQ ʗ_߱9$WeweL܁$UcyYжI{.Hi6=w^PrBAUu}3^>{On΢.~p+hxI_َ5+bAy ]]ǂ20 0 0CjfJ49ʮ{;}ur'l@r=][r{ܷ!(_i>~Vm\ۈ+AV1FXEck|-?$W7O~tYE57bj"W_Ay{ly\9>Hh~L:ԲKWqʪSGC&oNAKdjm=׷Ĝ{4 ge]Mv[__>}$H{CB]$&"viٖ-ӡCUm$^rss ϟy%TPP1Y+9{,*֮t{ AxdJkl/Ƹ_Tm BܿAqEmTw14!j }Dۯwo H%Q +$g$M'HЍ1&!NU F 4yN1n =ctg똠 B|l|K6*\13 _x׍ HOYhibӂwO  ,$vd&mϤVkUu%hk 08UE)RmMwN*!=dY}\;dsſ#dy|4gtd.5zAM W n! Բ yB~W1/R\;H)=4jRPFd?inAxndw+2h${PS B%eճ]vTA_V9sܝ;w"qFG)(ϟ?Brzz AY΋ h^W+22"}IƃunٲE~cg ʊ5[nT[[K<;ve#GTP8q"|)nڑ< ;gB%0 0 0 0 0,(ʱ*]=#Deu u|H`Ц]zAY9ڝXwD!6o-C^3 W]a)r ' DaTVǟD{JTFo/;;79 $eomUqΊW-#IlÃcUʒAx3 񚸻(kYsyC9D7XPG6DP{ D!#k+)L޻! yʁ: l q_c1ҞayF8U& A=yr=:EڋO F*ܯs>\]Gepj\lS._HR}H ?׽?2&NoYPY rqPtR>>D8SPww 0 0 0%(;^5U@w;l_n ]n= xm׵yég?E.M ʛUĐ_'#sfH|vBRvjkQe\jr-e|vjIrmr lU-kT&150GCe|h5VM-I!>+<DDAhK-UUU߽{)(11ڵkWyuuuDX77˳2Lt%YhuY`ʸ~:^t3eE'00K{mɰϷ1֦kt?#a#Юf\zAwZ2$R){ڐdsYz"Z*@ݼ5Uɪ׽&('Nw޾)Xq^kCPV\ <'z|rYf/9h9*YJ<'jg#NJ f}*(CuDykx\=3VRlPB–}ʷ⬌僥#`GeHɚa6emz dD[YcT/d!ڼRBR LgUFM㢲&boޛ#4gH{*/}~.SiZ,Ps~7Gyb ƎM$rE\ӻox~u5R>>U+EZ#n߾܅ *:g}v6z)yY~ ϙX0 0 0 0 0 S Cd\ OR1u<ڍ8up5,#s'QRr"jңrD\Q/_>伟߮'м!x>&M Y ʪ+B]qYFSq WJe!ȢZhMJOL rUOBɬ/& oRDejMd((:q4_Lkm ŇD^^b}tEpQtt4$L?{~= >|(Ju}W 9rW qfdd@RU (+w#FPPPi#D\9СCٳeѣG)''"""| 2b (I̱qF~###)%%ƍG3gDF@?` U"!/>0KQ蘨)׬RPn8wvGJ|Y1Ha-:q,JϘÀ~<+;6ۘS *nSe}ޗ#Us#WJe# N14%%ȪX ʐexnQ$G#Y[#CBL9eJϐ XVxv+C6߾ƚ,,O/:J5 F"=1k\UP76;EKC_<6Z檐Pi},isJ,~t)@ @ *3plxS t灝_|v~?)Cd諄j.^5"80NP"!ﻧdBx q4hcͼeC+9*X9oRHe(jGV1_sqI1)>4ey.gq#>Ncoc xol%Po؃;rtk,%c}g/(?d{{7c$%cIȹW0V Z^e,R>N'iWS{>P|Ҍ^=U&E* 9Պ ¹,$bwM HD6rX{[:_̟=K;t`}QV^YA޶&iGKK=o5jofB2c>kީ ړ=mԍ3f)8_۱HrF/(&u5I~36'ѫ^3v讎^L zN. lyK{2eHO^F]~!qA&= w~/Mqrn9f`A ^ ( +Y \?Q @\-RBF*.1@Kc& v1ꮮ<ȉhdg}>pv.·Hu0mC?Xa _G9J n]#,ͺɚײJϐizuY[r&eNYS})"@.^y)SAe-&'o_O\^+{X;eE^Z%) 티F (S.KKQ4]K9JAR[V#Pg:F,/NS3%M6ӘRٻ886dm}c6PDSZhL1(m%`==+9S!"VM41(d顗bsy(Ű[ns Lf7 ;_&k133鸋bϧON닩MJFsss)LMqrӱ+;G<ikkʽ2xZKǞU[Z˹\vs2g]5lm=|=kdVd#EylN<BٛWE'?=(P74l}f] @,P(`cDz;Gcp/{ë/?)P740R|b)6巚 @Y,P( (PʾTl^(e`Ci9{ލDp߶m뉖gn=u3( e2-BaFƽ}b_gb'scCe1>*P@m5b˱S|+WM-w@Y\ojC@YLy\Qwgg7k ::kT!P@m56ǝ=Gͯc{?Ŗc;E GӣcqGɜeoPe@Y P( ee2e2P 3W];;bN92+s;cx\@&i7[ Y1{ajz@;@YL6k/ϋYͨV~_c+PUKG߈>?SOw]>KWVthb.yw_Wp8t:η?{[C}qo)e庈cm]aAR5Uj "D15&EpEkLjlj&i:ڤI51tbjbpVʹtz%gs=sy~HeIgre4ݬSnZy2q)׃55g9SsҥV~O=4sa#wdŞY4ru|aVeee(qx@{nj$;>ӗjreMEIn&$W̍:P(6`{`z~ N?UܷgL{l|5;z}TT֜a@ll.<=@L e@Ɋ˓h̷ʂG eSuԯ_!U%#,{]&vyF} 5``{a(ni8䳵N$&T\ =)}B=MYXڿDY~i돭ۂA:g9:ۜ=_R>Q*kk9A Ue0ym!Rd}xCg d^c/wkRļ)e%?d-GB ! >+֕. ]_'/S/l(K&1Ӽ܇ew9I{kY15>I M"_djMb@ѣ"r|'4PJOmV &P= O鱯>qy 'sY:m|$PVfNRAԆvݼnubUn~ĂI ΅u{jQO,pUgKƤJ)]@GÚck@\ P? \C:Pm3r)T^x1Q}~?BUѭ7aKMA<$m#kEmU~]vib ze f82R 4P-[dDwd\Lf;K(@ޣ-VO};T9AjHOgKZzr6nԏ~#UZs2q4Q>;/Kr6 Kߌ#.YL>=1sJhK̵>*&KQI3m'aJ`B Ms%I=R~g|i@yes&QڷEGW({5 ~ɖxMB&r_SRb%/|ǁ9ju+K%8(ö)t}E(@ u Ă]=)AI±qgnFc'S8kߣ@|I\|r-oyEIJN.6y>\O4}<Ňj غcvcl0jjjM\_{gd.+RwQP ,2ʐr"r^Zn-E^y!+W,tɘB0Җt/|;]<0Ї Ňy~9A@{+U!73CtxU2D:d B dh(G$VnG=%aӶHN&c1'{S*Ifp57bt+DtJxؼjį8xٺdu#{OMUܖX&-Z9nEC(ȧo&„{0F(_' -g -)JJɇHl7Hwf̭XrO_Ҹ4\∋Jp([OHr_m?O؀;.9'E,xB;%x^#Z,]AxFqO1bx\Cfeg U ^ !m#u{TPn{{/ 6V8[VN&pwT'nL-ޚpk57R٤45~G{] sNa $r=NL_tp f<~_g@` EmB p8 5g{[ o\#eK wV"KTlKujg7m]NؚN~x,ai/32[s'4BBzB !BB'" J R*M ^eG[̽S~:y$fxon,tJ~ϛ䡺acrʡyLe4Zi헍c:6VT:ռ/6R^`yG4 Jպ~Jse`p,;e[I:wvrp(:Nsm:V 9;hQ ,`Ҁg"ϩ8/N ,goDZT]Ww~7p޽ XY.qJ?|"      (;;9~h5|6WP68Wgɘ_XSZؙ-_jrP =cMqÓ&gdƒonz{*׌+ɰQ}B 3ݭ6iNڦf3Ǎ31`{qrll5g-͡o ʝ–7*$ $(AAA AQc%ʿ3211vϨ}(+@P6Ij\W5TE\Ƶ{Ͱs$oS^A١ΧqL&pv/3{%wt]},ry\yAYck}U_\6Ƙ!w*0~ <=J'M`s]gcL3qN|dpAAAAAA$( (+lAW^nimP='Əu@Y|>VTVCP}hX= M-BJ-t&190<2 2saiK#_T+)נ %= ׺u5*xYU]̽ve՜LO )X]9.aZZyj/e s 3l0im%(;{ s!)5s3 +{(aj^%Uwt;.֘lg|~ʝ![@x+k~>3 GgB\!#Y }k gdp( ê'zc\ɔVTd.yr҅~sWA,?+//I8rD$VDoLBUʘ(H< s#_Jg@nx($Ec EyzChiwU89+jYapj6ȸw2 bf2؉ EyW.2<58ՋD"J` ׿Z}ANuwEVF2!9cXV;JH SrAP7O0k.Wo= S 46fԬ!sI?$RZP-49ѩy<5&GBxBVWoZCߜ͑scDk $(AAA A9>Ba8pպppboqa0kz"|u ve tc'}&Xptc-6vͩ=Sۍ'P}l.ǢPC=q J}y~)ǪgiqA9ϗs2r?^tA5(VxG/( z8bu|(K&mE56 MG4՚ -[`aK-Z  ;,!r6 //Qz}NPfV D] zM¢aӝCbQ,x/_zHVPkڨt=͡o A!p»K2AAAĀoDe`|툨2V~on|06o%sS c Ϲ Ė\s](+R 9#d.U_KsQR0uq Pm1B;D(.oAmN5c]lS+-(x#NR8>l79| h l]XZB$G/GZMA٥k+#>stqs4uڡY}dOXE?"     ((g&aL]y!>{yʰS? ꪂ`EB(|+oqR"Vey5SAԥ\u>1ANcg\aߨCb9Q>5}0V\6sXa8jĒ2.ZAc`ѹئbuedf?6&|b%`Q f1XJ ڡOadYeb=goXғZq_qOUv۩ :+; Avov?=Cm&%u&&䃴V#(      HPrr\V:eAKs-LҲ "si l޶bQM\ 5[4kV.(|snɘҪ|m)$(+euj]:n6}Gw<_ʳK i((D}AyVT8[S%}fDowm"1+:6f3{FQ?7V]2粠P돲sV_PN٥2"]Dx[MLϫb 5e7g}s'Kg7C`G2AAANXwpn%ä+co4{Cʝ@3X늕O[?cqA9>Kþʕm3]ɫEeg֌s>9Nxع8̪raY;Hgc9)v[F3Qxƪɼjs<Ρp?Y=^1)i)|<%}&&0_35shoΫ 2mTV0UE> -c~Ilv\81 ><οG^`}O)(]VfϷu;9bzgJM?"     &(gaLS]ɘ@?o>c_6Ab\w`0Hl}(?Y|s!1i ’|wb;׋ڙ<;=_q1?ʱBH .>9dk?3'UQ5~ 4 1o-:(sFp&b+.<۝j/(6sJsł2gǠlQRe{$C ӱw48U_+ yqlOkM &Qt%21>>m5;5UƋa5GPAAAAAA)aTk2&*4c.:ʈ'7Hl{$&t}w kV.(zp sN;h\R1e/sAY~W(wGbJeao/~s*--(:\?ɓ\ʄSޮgܭ+vV5H?_2r2f?i7U˹Nbsc\ܬ-5y=q|mSq>9hx^(>%(~ Jy7YŹxnۏk6] (vS ɂo] eA]8EG0-$(Nscf ʹj ͡oHթ;/LAA1e pWcjrlprk58jΏe l֛)rqFOUk6GN`K#6YS^ZjLV=Ǵ_ώUgMPqjeShA1 ݠW+]A9gCš`lu`ϕ+GkG,|6I j]p G&z0y03We|_:%;D:qVGn 8.(+<-$(AAAAAA/UP68Wgɘ_XSʉbRWwW Ŭ\arPz{ ʧ/VIY"4I3SLƽky®\(K}~MMAћ~s7X`qAyvi2<+*K3Bx{JP`흺2Q!#5aWj!w\nY!{fNeE_mc{$(*-vVIY4ɒ1S;c<=b$=<+GQ_xuHĊ Y㜽)yrഄ` ze$=y>(f*n/(d<=0@,&z}>-Q2&JGyVHcA o=~k| KAӠ)/\_4D~2(k$GA8ojc{o"Ec,k,|ϰ©H=tq*`PhsȂWs  dKa{o؊rhɘob j ͡o ʞ^8?#Kbz AAA@PB-kl.rS6>T],˞;}A|\$g1 %)!$BHX*<< ~Z4F3Ac!<:2a\O ,ޭG1}rfOE0ͻjKYDbc{dzڸrbES_O,cy^ YE߬+=ԛ-| dAo~|Jp`csG+ >%߇y l\;EB9GOMdMEA9 yAe(m o` yLAA1*(931/ڢ4lm}'[5Vysyu _&rc묍B*3"+yfr5#kQKa@!u //,R4se/Kƻ}fqhlA ӚP$W_P3Ŏ6Tb?.MMuqC9 2] lSjx~]]]{+~EAAAAA$(mAV\,6]ƎvuU _Qۋ1V-AɐS= Mq;p1h`[BROPIdZ>NM}?^7{gd셤C" eSlJM9+/TZzQDyS-*!I:~Mmg \|>=yx><=xr<ek6ms48ڇz$;XLHUܵp! Q)?X&y6jJsB6ҫ6JE6~j1rhl@#ApW ЅrW gNvB Z^v7+iTc1寿>iwu&᙭A,"⽡YLKV8O~k dZQ՚["䆼-XQoB-ؗC>QP~yX=ud2Ce9f.R&^'h5ETڇ|*1,$(b=x\la3$Yv~"U-~nYy22k&dl_3벘;y\EN<%Ő!/ roba.wo3mZ7ǏF QM B3p$ U# _ j+#u"hgtLNq^.yprx #9+㝣^P֮qKAY"H$D"HV*_@}UmkvtLɤkT ʃäK㓄,N iW알!L:6/6u`gMKJm?UGn8o 8VSh{ɩh?KTQǝ73:Rjh8Ĭ, ̐A3V&Y(TF"m]F"FrX E-Z/> ojkDⳙ{Μ;μ|%#XAƮr(죔uv sːL%\6zE\r)1cPh{e%^=jAxr2i:0@R=7I'3 (6!ʴ)}1(ohו21{faO&(oIF?`], %q~{rAdi҂B`.(q~<]Ŭ!rWgx2'DqStvvf0 0 0 0 0 0,(onA֍NJXKw, } ʳDØCZd8WQ3Hת%*h)4{%(cf*}bgqW)'ߦӫyzv*Y/n}2!=gԨh?Ѧ03rm}i<7t * ^R!݇dj YHʾ }k5wڇ<`MkUUˍcX_sH8P87jת6g@Z(ߜJ3Ckya \e$ C¥ h QPоWa nǁZ6[G^""""""""""""""""""""""Poedݚ0%0PVZiέu9r1#'= .Nf|@桺j~Lo>%hչQZdQ}٨3a5M=b"5z;e s49P3xO$^خPa &02vub>iԪpF n0k٦,צ(+mC E_` cl|ڬp'2;igXakWXPrhmi]KrR=:q#B$1yfw"]݂BɂDݘ2R߆s Iϡ_dA29wc~CuoVy:P6$94؜ PRo1vwLe2J,P( A P($N{wQ0@Y e2e2ԺId6"3ξHwEk4NO@Y,Perk~g~32P%N?}6eRhݾy /ͬ ?`J :G(;99d \^ P)ѕ/Gel]Gm_En4.`Ne@@,P(vEp"C_`tm]*I!BA!,dHLįPD!*ԈDtkOEwayg2@Y,P( P(( $+ qa?{:)2*o|!c4L6R^A(5wġMپhty P( xcϋlwKk붺 4WњM?{"~_wctM=;;Nr̖sml_cw('Cfa",O;rv<=jodG pX[<|6Ucve:n;{pou> q4L}}/e@2eht*|.ٻן8㧥B,R-RJqU(0dfSp :6̘N6^MEԹKؼ9dyii'$;yogAw_?_蘦8Yon\OE7}6C<6l,՟1@@ G+F*n,eӾ _#P@@| fK k2ߥo.5O%j%PFD|y>),*нG1KL ,jTns *PUG<6(8dC;ϬCU0Y'"e_K&PO {~g{>g@0/k Lӵnw2ʳ(ėJOR@θՌe&$ S]d|'W '6'̪l<ʱz͛5lXV#ǜo绕yGF7siulwi2S- YʲCbhSl(?4LMnO|M8b}8irD MRib{nk襘Fjiʡ (G!|i = NŽf1c1c19<)rtbyGcߑUrΖP1c4LT< 1&>aJz >)d6.]#i&ø׉CUnu9RKP_L~6Bmxc|bq 2 3+ (S:]ŰI>z?Wmj;[eR5`p |/ JLDy~= of&ߡsRsj~oyt\AH$44@eZǩW1P^諭YA*]Hk]#ĵv {KAQ Wn]ĆM ~_ nfUR1X (JВI2Mh 8"7?  +/JrQm}]j֜+kSc![KҨ?\!oww{9ss6n, _50nr 8#SQ6fhk =tvN#o(F.2˓0 E!XkL^,]Hr^1jQ#+^R"kr}a]]sJ9pUeȨf!tɿ!=Ya*3cR+xŻz~4%_d~,ސ!LJ=ƝH.`e((sp(-j>wb_P&|[GW=!bA.KxssΎ|w֤}莘pg‰b7^򳈩A9:\ȴN1y?Rz)3gFaX̬e5Y^ ['dQͷ`(JKj5kN0lF/͵ւ ">8ZǗ^AS(㨝 ދӎUeP4]܌`)}Q3?3(30000000000f<4nB`р+H(P%s&ژ`L49(lxN{Φ3z=]KtjsP~nխ0s((p]@uZ﷑<",:ĥa }XakDWx)5m,h9;{ڼn佣oo jv /z\!Z{t)!Es]ip͹ \{ Ϭt|&]T;L19ڂN`T!<:Zq& =n?L.=kxFk=#K dsoYcó2[Pp:NH>T} bmc羺 ʝ[wIy`? ;. id0 0 0 0 0 0 ϲ<49 V9S%(CBA^DDуoa5jQ?ۉ{5@,|NѷPNDIz媢Sh}tֵd_Hz'4kAӟUxD]=;^O'AGMi5{=\-V@WnVՕ[URE{ UvI*X3Rӎ| ؉2hk׃ Y2mk=I, ԖߗSTLV!(-3/DnuAYY/l»lE-Hm^Pn!A۸iqSj\S[PBL[FG*zY,ƥkk s\<{ηzZ& 0 0 0 Â2҄!4ƧX1&8y U}7>fnh9z}dwsEr7f̍=gQ&({#ȧk7/o0o)6^=j,-+!ɲyӑ,- =gi OL$g+-z<.KH .(̣+ 878B^cJFtE{%g:X;5 83Tu,$~kn$)]s6uF񞉘^t4*SpyaLJoF*2mk1Mقч"=hr/E~<|ȵO$y#wpo(D`W7[!JՑ4,ޢ?Zi=v/aEV;֫-(<}l9KNե!3k7#22v} ^/cw=j"ux%HL$&Sqžݕj>s3{#:9-({g;C׻P̕A1;ʵoAvi"$eaaaaaaAYBP R0Kf5%(˂gBƍO 15?4"bB{'24|Ÿ,4a|۞'AT/DwoO"Rۤ]MOg=,n=Gw8Ş%X-!سQBv~{5],y1Q/Iޢn+%*AEykk$zk]Z8Ы , a!l(Fʳt**;,2B,[BQ".4D1HY(B5V}c <짳@H e VXݏD6QӢW]Xw>w7+/}5ת_~{S@Mc(w++}_pw򰆠,'[|&DR,!\zس$ EB 7^ ТvLk+_\KoF,eaaayAn%!~bT&W1*Qw-@ͨ IXMPz[uRoў149'zz;v,\[XuzNnc,E[p㐳;Z*Q9 IXoeU\@.EO 5{e ϪHNV{AºE1 ҾV"s![)(6JgZ7hB`gu{h|]^}xB(aaaaaaXP~e@'U@@^8oGupuNi@TWY9: VΡ$|j('Oɳ{_P^n!z~(19^m, q:((y΃[0Rܯ<|cqԥ_$#(/^5uIEZLdbԜ+E"tXx-{v|z@_Y؍l>аPoO0>{a;JoA(g6][lj5zWfZ,؝*L4JՖs2kBV$,(/u!G'Q1Y8#ʋE2ҳ Hqv_P.Hﻎ2E͞vC‚-RtrmM1,'M*YPV}V{7/Ny'4[ɑъ? 9lgs\53R qcHRU%UCP.H5IFbR <9=i ,(3 0 0 0 *(V`?W?fAy҉Eɝ:goI*2Dbt/#=q)YuXx1fm:;}tgsr=)x:&'wl~`.(CYMwo^g-y}$f02W*Ljr`R} Ks\Cꂲ+HLFs$kݳWV5U~H+noLg_{L#ݥ=eA{.džr'NhM~`MRdHb0 0 0 0 0 0 Ϙ AfCBbZxeW^,d'9߲RmyKf=`P͒3g哗Ω/ +K䍰k;'1`)[ȵT\uk-gQx3Ѩ9Gu{uENg)5Y{5e~s >;riR<U7<8{𨱚22lqq+ҋI1M{uDp{ ׽QWa}(9,()ј{꜊ b͘8oF Hr\@ZyO K?G: *\ó (m{Vc5[,'T#IYJFƜt;?]'PUPO ر[M93iH(FDń(\u z'X<$JѿtS Dm*o>Byw?weaaayAMݶAc/>1+U/_Bmw!|GPakATWt]:gJit=?_s[y蕌ymHFFF|(#Ba@q0x0Rs[e:ﺦqP"Ċר̈́=.qX#W:iJXkv2y-!^2GR1/cMst [zBHD{~|nA;t?/b|@cWNxlioʵg#(aaaaaaXP~e܌TPA$iiI( Yd)1~Y(\wj۫yH]EM'߳Gs#ҝ!>T;{1{7ZMKl> qFϊ<42RdT}1cZlmUV1Rxȴb㽎s8X:FoAYGʋD)qAB4ȐpΨee+V)=e2nZ]BEO%(h.βƅ)ƗmQYt k*xl>nW͂r YOъ5g,e[bj22~Ge)j7wゑ+>7-{ 0 0 0 <1V^q=Cm[P.X"t7x^d1ViH:Fbaitiy qŘ{~kiIJ:$f̙ J5rH"vAZoYNQdH(j6c{؟`o Iէ; Ggh^оWSyԽrִ"..>SKnV9nb}x"<7C% aaaaaauɛVo0(GPFMi ,M53;RݾZ$YAGs~DҊϽ&WR%뫨Kɐrqasʠ/rWM~E ~1 :'v۱g߫u[f|~H^bրB?o|w3#1.TwPR!&Rp@RGD A`a)W{ڙHcա~nWD";eS◍2[!k\ AY\WNAsΨ[?,B9!?a_^)Q5sNXR4|x-#(CUπsYED "(#W\A-fMYP菚a!R)9F;QPFʲbΦUbgPHcZ"IzDZZ]"8ߧ.0 0 0 0/,D~ 嶅[ջo݂rO\\VN{3KGzD/u㛻5v^CQs=Z\WD1Rb5=㳩 }cH< 4QȻViMߑ^ۤo?cc[Սgjm3H~dEzxnA9+Y0NuIQ>cPV[:Zۚ0ڧH4xk6`=)z㕻V*!:kMV-rB&CK%W1?{sW)(#eYstӍ}{G$iu|&_'z#HHnjsp*2!NOe"N~\fq^!IY#=[kGP 0 0 0 0 0 ð< =u'CP.r$&Ч#-c?A4\vb,7R#lj:}t&꩟zK:HC[i=s_P޾ox&!!TCfOUevȾa$;w#>!ֶ=;^Ot&~w?,}GP{d~U oaI TGE8CPI<{ zd[H j[7B</`lۘ=&qw(y !*gv^Wu4@Q eu'@wc45Nݛl5 v1z JiU)*&"(*k 8_F)Ƌ+wՈ閙ko:PiuT(]HEZ' UeBM\xvKPs`R)6a\JjV'vEDViʖ iYvu߁ώ!Ru+zc*5AyD21 'cl; c%{ )GC3sRBe5uDPg/$0Gפ:أdWa/z;ނ20CNS7P3{|}{X,ρb쀚Suu5ӧzaaaaaKP~~4rcUP^j4dX-XkuJiߟ>ytQȏy{_PNLD*dO z-k͘׈y n-իŪi?ۗ?zv?jj S#(C,o9 p4oݽ⭚'`(aQw؅*88cw_>WNϜ?Mߣ+f (kYP^xLӑ" KWPHk(it{,N]sE6WܝxVeJ[HP2dIN؞|8,U<5cg2T/=2\ qyh1Դ0TqVLI"MEO RZveb:z| uJ0gYYFX%vb.NCP<~$)HE~8_=m&RZ itjȯ VJcYJcƧ (c-"UXyWԬU3׿_@W%SzN, 3Ŋ9= 502Gi ʵ#,zĔH|!ߛ'6f%U_|'1O׏ޘ&YPfaaa.AkYąRruqE ١,'n[k 8/K1ַC_NT~)=Xmo~0hօS#sj:գ)^Ct]KQǝ2'fatUt06ss:,/ޭ"#‹. 1]DvQn _" b{ȹ0v~yxØ2w}lo 5AMa]3[ "g9Ul2o4]^+/(3 :oƱK|ӌ2GPƾ#HA[Hmͤ1'!+/(gyn-4C&1rym=d.Ы,SP(Dtwx]]]jş@ @ @ +jT[C;Sg&&DꑧZ~d?Nr<^\/!sZmHߦ#cT%>gYYAUuEVa:y&IC~lͅ ʀ|]n{bN;՟8DQEWk_IjFC4 Uܱn寯Wˑ-&S$Bua#A Db˗}!Eb~yG !5wY"yN\eWJ LEd>#9L+ YBTH&IK{qҪ%}n q:6wZ+b8AS!{u%Y%,f>~dX2sXY^rї{\IΠe3|K1w)ȨEqt9f,- m^:isьk)fcF,ʈ_y.<+fՖ^uِq~wr&SrhfVd2pDӴjTNgO/E.\9Rh+(Qw.*/U3 [KZiNP(sק߽ڢ<s0=wB2ˇFkH‚?y1 0 0 0 0 0 АMXGvmxDZPI Z!MD{ٷDB^Ү4oȌb}Vvm' i8*A?sZܤ.NT|6Τ7|7" h[۴]8Vi մ2))xV;AY󍼠5Tkg]S<Ss*-IpEEq Mg-5o>c&nWubVrfaaa,YCF0[ f,YHMIo;d2%TI3#?jRtܳu $#cCQlX76[rYN_ md%- +BWȝUDأ~w[lꘂjdήZ,+S_ qFI (½0 eFZ U5%(́?db($ =G% 92:F(;]Y'/aaaaaaA7SްZKTgϣ9g3e͎[iif'd~a'}FrҮj k<TYzh5Mҥ.j>2^8Y3*= qg3L'N;0_tMWIlJ#J%jh5&Z3C( A %#m[+9m*QFYJP~/A;oދ/s}s윍Zii?gt˰(|m4ڣ8ud|Ʀ1dIj42|(³Oge @:[# b n/ՋI#[z#fFݞN$ IJ? ӨF<𢰸 s`/غ?spM;*#>s%}qQs%Ugaeb9k\S'ʐk}zd)Hq^h\e(ZW5U3@IV`aqVHDv9={/HfϓzǢ-F=-EXg7o5?08{jƞSYH׎/=fIʐaO'mXlw?3Z\ rbMzJa'uбy[(C $)z u&E]NЛLA܉'ۄ?8 OX^ HB(`JP 2Utи_ ՃQqƹ7L]]o=ۭMGix.jegW:D RhS({_YE#{N'GTEfWicطitkA3ثaVS~aVBlJKGl~1_ ӡ~Gyi=>rT)e))))))))))))))ّB-;.MKTQifbBd a{1S0+ ˘E A* RH*"+ \}[CuCX_@s{'yYZn31IXrnmN &Aoߕ3R\~Г>꼾Rh$P*=ytY}ivjx6-Î5ncU&k_v^ G#EzZsVFT $&4D&:C[=7T}Ha^ZDjc5XȶX/YD[G%\@Y~v'Dљ 7h}:T`e'{-#ĬzW f#z>duV?Xih@nU*W>['' .߮q{t~L255%S5 PNjN:yc?#5L묵h;}ُc~,{~ })մ.aMZ2}Mo6:Yf=,/VΓ+d&PFR|>Fd܃Ye}Yr~YR*P,9v N77kVQ[R;X۪.Gʉ_o@yڌN6]/k7̨׶ k ri[ 2跁A#Uc$=<dkO ۧIL&G5qoĵHkxTr#0=Ɔ xLV)))#FHbbdffJiiҋY*H}n2TWWqf5 jȬE~1 co%P'r'6 2UD|J0ⵙ]eWĽ쵚2mm(ʁSˍcmT^Wa%#3U2&JYi*Dڂ\srepCwћLe5)PЛ氢Ttk&VtP~A=}[svWYtk_1 v|>Ç%''aJJJ2eqqqof"E*#2># P&P*D~R#C"#*ޑt~ڶjp &P2~(Gl(@Y7~T,yAI;[_U`~ʸYep[6&ɶ*Cq&cDCC}!GVcG!Iݿ!5lc+ْ2NFHvDY^gsuԚ&HbP*ָbq[bݿƈC W [([ܚDjG5|Ȥ+/kY劉@j p8z3K\)@k(( c;SV2Sm~_npNyZxUuߍޝ*־ Gˏ'=n[~8&ת;?jT,ZV.{׿s'dWYYA Yee떃rR3?3 ;Q]MQ 0e^Hu:c Rx- ]22PERPr)còlg1 =PÓ}}oOR9UcaFW|<_&2w/qwxad?ffYHݤ&rUhFOxq<87BM V{X!t]g57Z5f]_s+^`uO8D VkZlevQ7AEVJd? _ qR3ë~հ BX[qv5$6z`p `'j`zvfo=%guL=os/J~p/WW_ë́hVKS娨(EN;44UV#9*JTVVCGG,j(#$H,:~~~(--^y#q8;H'"⪜8PPPgggcqlzm|=\IR4:Supp\.ǥKP]]n6ׇZDDD#9FXXͨFLJ|Z-b^ 8 ϖx(%R1l!A"j겦b`dIY)A]TfU8Id&#.* Eyߍ' >zGp)k Q*..6a~-}Ob433& Fs/;|466곶Ob1 e@P66l6`VWW,..R(^RRb~d5)].>??ONT0 0 0 ðlvEI 5UݚOP\ qv+Bݴe""龍3?ep!Jr[:g 2!nAl~[m|t%"̮Z\S+Tz@e~^ۃ {8se>j=tTn]^FIFRz:7eZk^i)=V $l ʸxY ,Cξ q[!I:A[Y&.Z͖S3֝/ںc4() <Rː2JOz`~)XOE!Qw~QtN* c|Y@pؗSU[KHi,/?y-4m{=+&WYӯ Z@R͝FԅOU˰js90oϞz ˗vx5+٥!;Z˩Ã4n>A=c(N>[-\Q!e^Ezyyn[σ*d9X|X"KnTd<(3`7KUXY 2uMnw6MDQc&ɤ S$J 62!TPEtA$(( vk\AATEw7-W 8fʤc }ȝww_ __~ZOpA[w%(t- km^S9z(sݵ.Պ6YLZ7 Ptzxxq,e!!!!!!!!!!!!!!#( PT!K&iPQ;gBLp35^cpIWBW9{P} D?GI}zM>K! r<TS*R֣|KJ'`!XWSo#nEܧ2\ mH~vSD@!x&fĂSPWWHzd( Jwu OJ N,9 (3% Ŝ 05N= _&wE0UNT&"@*'[_~ 9>to JmVr{9}e\1Wf둧n:&F֛ͦ'VP.!@<],v/&X"+tOɉa ςɄ4abi83t)bK4GT ^{{W j(Xb7*&j,'GcIr]떵9~~.{+ofwƇ῜Og^5?5`!I d$k=cda&z9 NT$9fmw#,TTW; duyy_Hj- ֬p!Rۘ |4!\eʱ!ͬw@0ݓa3`jCD\˳߬7'ES0hl(/+`IJ+Dn !Bd̙$N8E?2y@`tQq]:ssd;5 @9H .|EP?e4h(Uvl1pH$l?1Y>4XBtp6 {G+F*`OZ(|>>ń? ,Eu;E'p2;"vS̻ YIqZS@k{{5 9'SGkM^F1el&8(üHr\=֚ʞ5V׾n&ofl|#cGklӭfh(C!GJñ&cǎ (eԭ[7Q!70$թS'ӆ })̯]R?sVWVcz* ]v׬YǏٳgP3W۷7ų{.7m$Ɔ3f'Q+qFʻӹ̝GN:P'Ŗ,Y_UVVgy}mk?.zN| `_M0eݞGoI>tUUbX,bX,Yݻ)JLb zh(tB7>bt Vm "g!<_ؾLിsI{nk7-S߹ 6,86lke}P&3+ ]k<]6$W;aYM򵦚\)Iohpxj2 0972 8`es&cIʥ)ݳ-,yFGk(0R/`⢫6mng\ph8Sn\ ȏk<)*\{ ߐ9Ϟ= .%zbX,b1ܔXzIX6*JjQg ]Pָ ˿^" V %2Orť2b4C=<>7֛:|I>4,TXD\S!@vM_a5U7?JY V\<+,QK@Y~%= yT} GxӚxT'_l)Q{CEBX|%j%u2%b(ӸקZ)sRpF2Dc۪,m&>#{H[* ľoʞo+npSUЅ-,{6*sJep'epF6SS)b*Ĭ}SB7gx ]9rs(3,bX,b2A!Q9B3Z;nzR NʙYԾZnǺZl5omlp :ߡ%P:OcK&YJca w;S Sq.hIM-+#߭ x-9_Ϸg/!&e{5|shK{ 28tR\PIzpqqgAPi6J}V^awܦiްMP=SH"Sgz׻tRUWW+c0j*9˟q8iJEquHw@yΝt/~[w_>^]vHsR,wb~OC|[p!wj5;LeQUUm۶i (?ߐ4gҷ~+,bX,b@)ʭZzwBkN&y''GQMP&H_, ļ2ݹeʷ]Ě'Ljr$Z/#= Pnlh<а}JO,| (FM0=hY~>ܫ'Aݨ!Ɔ__tU.[I~ƀxW-nM߃{dnNž˳(UXxEpopJ@^IeCz@&.JO/23,bX,bp Nv8 `,&ChpnF7/.td no>ƶ ʅ[VeާG5pqU߆hkA1E.K1I{0钴߻GG_lA3w[- nȪGA5khw=Rh_mlO2{ dW7&O-@`r]e/Q)g성Zj;ah82+ܫ$@o?NsPs ,Σ0"ԱcG(skW\ip𮲲ܵkUۀ; wԻހʃ*z 29˟qXR(K9spիW7oj^^G>>KW26|ȑ&8 ilmE2ślݨ8@#7$СC@}=bX,bPn*rA/*ʳ&2<۔̝!,~bzJQ}m;sߗWDxzY{@RԀr}j;שBs3iS)>4m 7(s2ƿ|yV@ؙ~wK{ޒ"9s!A PVU\hX7|n^ֽ3MD6N^cLBaD3'ܤ,R{2fR<!jqf#wg?,zϤ X5PoEyE_ׁUu.>k. @zegn:Vlɤx/(A!b<U=Znj/w\AG&RNk%E)*sgY,bX,b5{@)n}CU; tv1h(gydco~%Z[AZkY^SZN'C[uxQ1䚶JV@O}wAeahPrսvJc5V'_K29?gA{vαӅ{m}5apoKyYٷ'(1`6S@ހ2%&v6uNMkBV&5L sϦфej={&֭[_z%"##!ߠ 6\|6r PCXm}l٢{FqiZJv\e3PNKK>,+DJ>ysF{%;ܹ(..{rm߾ɓeuowRU%j%w1~u9@YwR9gpVqY,bX,ŀrSE"|.,ϒA~~faRI5`&֮HMִvb|(lBd3UQ>涏f kqcCq_Hh?JyEqRޝhrTlEkGi (jꪬATPg(]9RO])~YHyl<0Ao1c.~@/qe%Λr7-1zB\sEDž) p@PgY,bX,b5{@95noKvrSmX=rLqYɱݥ^bN5rN9(oЙI9qRv5.W(!> vg$WkPOANwo߇~` v!(ưPv AIn('<Du1x5Lѳ6.ˠ vF&(h 5QQQ#CxZVVoݺ%<<<5³(XԩS'm֯_O𭃃C/_^/^^^^Bk]yFW]5ĸ:g7 +^JwgϞ;u‹&Mƍ!˽W{/_¸>E.44 O7;IH>FFFQRRH ;)1-[8ݷg:bX,bX ( (|zIlӰMє,"H/_9 kIںg"֣զq ؾbB{n E%j5WoZfx!fAy֜i?>yEdTo3݃fa(:}Ƿ ?$WC0.=(8HPgk?74W 6xj^m<{ :(o+L 4Z*MDkx`-@vOE@ј{Z3 ?keN܁¾njjԁ woHSKr@?qbp-{ѷH+f ~f}zy5pcp7L -7Z$iH@[->>zxg|QNH>꽖{,c,4ՌoD:K%2س۠G(I$/ky zY̌Ŧ_sAWZ#rtɱ g+z΍ɩY^킲.(ʈ'%`ʡӡ MVYKUPfQ~tk]'$wֻg,eD"H$D"H$bd3!ƾػ8z;7B,DLg?xdcW'᾿ xU3"% Rb]vzTk4,j5{)qP|>@ dk?v>>ޓu6M&db1BY}*i Pfh ?9vq-mhM(yHzB;&0ZeZڂViR"R U4^`9D#"^ڋ"HPx0B ѣċŸO"c;i+~}$x6B-2濘N}^rKV[^=2n!Z U2e"uze y]ʾZS-$L+OsR7#U묳2 K$ubOӘeȲ_fgΠV5=gU#ų D;m'CNVDj3 8wS[I&#eɩsdڪZ/甽7ODP?Ab-9nx,RQW!18_+A+j [{Ai.u6qսy`3^H~</T)ʨ ؠx^gaaa/ei=y ԂuB6\Pc{DrozY¡!6P54^f[(Ǒ\MVҽjH҃.wUmyr45Q߾R>zuQ׸Rn:Br}6҂2x]-3H 7"buxeo{4G:@?&(ÿjڂu[D\_P;A2Hd5\VɄfNPb1rBAx^K2 PQ{DZH\!i" K!q:3c9/(GQ[tkDB׺\.Ε ɫT X |^Տ{sL$I٫?2UsEjq@@iԐv{?u ׳,zaaae'hoG&wvAQVaDI-Iīt?R0f5*RpC"Aji 5`&>kPUX(@wlVwq<aG Zsug)=1N8B[h࠴@ٮf.>"צ2 7&@2`hp#@_&JHdo}|ix}E i9?b9-s:6\Y C;K>xs. UkjYQL5%1`Y[^k <5uhG_7?vP@/΁{x ݁sWϿ'㤴VM7 <ڊ3pd6kZ)am6 HoG]_v4A 4*>7l (q" @Lճ!OQ{l8 `樓t#t, AmJ=K3[E\*Z ϝqᨁb~\ "zǓ3lDNڃ9X1xuu/;] ٶuƱ#i>c#~7zakn yMQa<3]7>~kŒk2A|7K>Hתci('.Y;ʤڞ$Zٮχˆd2EOGө3r Cѝ(#&ew:  f5:p$[ّH3P^*Ey][2@cE vUƲVW" {~͢: ֗glT>24VqErkc4s)JK%Z: \cJ"}Q+mЎs׾i7BVj㈎6!:K@YFFFFFFFFFFFFFFwGa}+kVVnlեtaE ]ӓUԼzP^EV$ӚuN)uG^=^oxZpEanl++?eQ0N?v!Uzaα1nɰ>+pLVel=x& $礝Τ,^5ZyVUU)_}U>m60yxx`OGo=sFuHzػ0jPJQ$AQ*DCLT,4D>4heBJf"PZNKpB.]6Ox`P̦kZ\a杇3g3@'~Y?gYos[.|xp{=N w\^ ۩yffG{ҕ;9udNT tݴZT*8rf:Nz^a&IF#I( ( @@Y P( ee޽Eu`?, rT0@ Ex5XRE1`QuhS:i5vj2oM*& (ꢠȭ1Mt_Š+,79w߳g}EDaI֛='" XDDr-x/erGdsMhLpVa8 u,<NjOX8t:e i({|nlhusX45k=sVD\ME͖ yq8y3gG4igrϱ9OǡAg^II)([qE7f`'pu n??lyFnЉA691^80y(;PW ϝ,n >m#*<[c+.969pVʥ:] Յ\er-WPPpC#))ߣ]  \xecƍ={ҥKhkkCss3{nq(3P&O wVD 0 3"P {6''*$X]P N "'(ڒʃ-)%Fr\$P!Q1_~L5f~/}c}2`6lnEHۛ>g(3PfL@feJ2р8|ѭd2PfJD{{ƽweiЌ]ۊgHֈӵǔ@feeMGϦBT\c6N?:_ ~׌s/ f=וr9Rn>xe6y~ cZ],ùۥ_bDDZ^h/Y@^$P5YD}]Gc jhUZ>)Οl@YbR>׮0gq #PSA % wಾˏA3%v\eQI+@un& >CF(at\%-\P ZZZ@y@pٙ2р8trԈmFUQQQD&elH_\Vdggc;v,␓#weDݦKECK-eVD:q m:P6 ;EDF 3ey.%=W`!:|ּY9=pZydBą!&6 4"s_ !ՎHk@( \m'?d6,{7|@i>ejعC "PDJыõ^k_$ilkD !^SRR(t}1P5Pƕ+W ٺr#c1 TL~1P@^kFaݪ,'[ -׊UʆQGW_ 1Pځ=y"J+w*"_˕W}^hw%ʹ_A Ȭ4J\;~eWR%޵`,"[~~g\s_1}qS/0k4V(R1|ihbs`&i.%RvLɇ6(z0)ǀW.ֺ5 hjjBqq8@!e~2P!#))I===CDDDDDDD@HlG8yGYFx([e :]@ A-Ir_15}'E8ׇg$'SgF"3;l+r֦r/0P&""""""""ʳS@sc>WQ-('8M3KT/~]Ws65_6聲`ɀӂ="uY6(n؎u}z?l2$U uqTee%wvvB2Pf@(_ΙjQyƊm g_>WnLXxv u s Kuzˍ AB=On:_[q˛mQl(+lsX4GOR'?Ƣz?{m3v;aarlLcJ6ÇZgxMIJ5UciFm;7P/Fmbݖ-U}._,m{rm׬}SE<<<,o%<_L ?mΖ@zE & YAk'#96.&y8)ޛIq,5" JCYubq.(pxm8dy/6糪hK]ǿLsZ"Iݬ4$m/MW>ZuI;~E-Zaf U\*0'^OZ{ (y.&rJfԶSezRO[_8x |+E:rعڶQh=%%żͥ`mss,d,V ZE&гhhh 8{9@$jjjuz{{S,h``@p]\67Gף !!!:TM{ppsssX[[cPnmme"-t:yH$yT({V#JHH@oo/(Ɖ TVV?U455,Ҝ* CCCXYYaU933C/,,4k쌴4lK-ϣbA69suߑs%^آ"1yX}<<<`yyisFGG->W'o..............(؀rZb UJ8:8Pf31Anc AAwdެ}w4Vl3BvW *Gpjf=%AޙGGUl Μ!0 c}Z 'sgMp+c &_#=S`Ͼ\sS *ǧbܣƙܽQB֋x$wܽAog8)61[k:9.dp 2E&f3@m( ~~pa R bVQkX_׋EPY%K<|f̘TR<@9U&$M8QAoAɓ.KRAhg/:sF \W y^PF͓ϕwțaaaaXP }`&ñ+ej#)Ν?uys׫8̶ Iɉ6"'s qQؼ9{;0MȅP^5־ *%%{"7.TvRD8TƌϠy_۶VޑPV> HכBbgГn?<WB~A.uQ`Ml'źﶽSкM-ʷgT ֹ.V%F5"2BeF Aـ\ɟ\IP cH]7qVAJTi0mD*U Nq#|S/ʪ- ܕsh$k|ZtU^^?±b\yٙo(/bU9V2ET ʛJ DPI $]X'$ewsy` a!+1T$5v{QF (b%_G;("(/q7~Ym.oIh'|qoyԂswpQ3;{^X8lr'*;_VjZ3eaaaaBP6u3ɓ'dٞflC$0;R``Zaw6)i>2Vgc[VCgxBo1ؑ,U7 &tҼF8y}4go!9sKe,|rY죊!X<2( 7MW20VP!wREiv4,޲(~RZ8۩:9r V_P6yo 0`_hgGR%{w/0u=xė{$*q{z I ` ʼ#waVEZ[֫mn9.}&cT&\Y'"ݯἐU][[bVm؆>J8cH՗#""*#X:VLi322oRRIkw>0j24Xu{F O _~6q8 eHaae-j'*YNĴaEN~J>t~q/Fvv6驧^zAPP;Ν;bEV|sbmo(%%% f_+Wtڊ)'s̡?y;VUU&M熕b*tNcD* w||8} :bbbN]\#oaaaaA+(O.%9eL0]-(){f/oB&6߁eئ{NKOtH؇2MTtuveϛ12fZI,w~]oՓnl5}7aFC^_\WM-޷¹>EYXm޾៺z Aـ\ɟ\IUvDɩQ@me .3*.~gGfi%R٦ 1 zcc/KypTjKhw<8cwbaNsr9?XY! Q9kK{OR|F !!p|#4BvNfĒ\[Ugfw?PPsbZ϶NEaJXV?ra!F{i|TPްƎN1[?mӇstHTSte᥇b/= !,(3 0 0 0 0 c|rc<6*2*A^1BVP .?/S̱qmu K_|F5U_} QW(Š=>v(,ڲ8SP!. LfM_5n5 4_jQ9>3͛h{Ԙ{=q_XڞHNx]f/e.{4YlZkQ, 46踡y{y=_s\]]z}Q877מ=AUD9URұe*؇,e7p@oÆ ~C{7Nt}Eƿ;voڴɰ;O|`%d<766jPF}x&yH\\\,ڬR}h'FLqV%6Ũ ,8ES҆;<ꭲ}iJiƠ*簰0hSҕ:7Iʎ.VV'( %bsC=??%(uֿ#G'$'J4lNbΤgdҖ6klbc7ZpTCsc񒂲v_{O6<|bl/Au}0 0 0 0 7"ɾ;Ai̾~mUAyoXN=A4_RP&ҽ3P}1y@q avxb>3UMUk=6$Eylԝ3#|&^ bme ph)\&좌рc)3Ͻ |kOt6W➙ lz+'ڌ{CA}C\;i\ w5{=y_?d{e,_ӦM#aS_{{;L&}QѣG3AJ"HAY+m577۴޽ڏ?77V1DLLwߥ~K(Wa޼ypAr ܽ{3wњK,t4c% X] ecoݺEСCԈjgew|$b>Ww0 0 0 0,(̭*%AyjqD!'  چXnkoqXUn])sc<ۧ)unj *4$)럾MobW>x|2'[ɸ9Du߻mJ|sXʣcA!զolBKs-9Ւ]{gkgL,I*b}5qbX`UsF҂2 8eF*<#U`,(닳$pwTޙPV-%(Ek;Ȃrypajrǂ20 0 0 0 D OSaC(}Lc6iA4ӾJ'u]}5E"= ۔9ӅT0MټEE%b5B6@~gm>#(b`c؟`M!dc&S^=L`  >eG*(}I29 ̽VtĶwq2+ի nnn߻$544Ğ8qڷld,k|y!;g M8(U>{,@ GzUI)~ e+yEEE,(iII J/׌_lO}6JPV.HսXٻfg$/(Jnaaaߟ<T[4fO:-(gxg5bXwD=VcH ө]J]X%:#ʺ6ςsB1'&(c)]*{|ebA9)%fKee>+_-ñ eaaaaӽ3xk<s)A9(֘n7+5;AиA5}2^˨fr6C$: Ibgn=˩+E[ 5ecel-5ZA>zB0k N}:]HFgebG-(z4ۉ7w{ALsvx彀cHFfWi&X,vIOOw.bD;D)% =8nq VXکۯ_?g޼y>UKVJAtK+3R+(kq]Pnnn1&Kpp0ܿnXYfib (5\/_V+.se{^.(KYݐΑ?#yAY>Ww0 0 0 0,(~e/#Aytf ,"_qVWȒzڲcM;ǟ˷rRDlW=HBcNLCyUH(n>!rxx=_GD_f\Oʗ:yI\=<5YgRs| /ۭx`e٭`LVgei]ޘ\(VKkAxABDXS5!+]AR޺PWeQN'9}JbޝSK 4arxD(9O_8fqmzYAY#|@+JRn-@BױjM^)@"s@&ٞ{yr0[`ߨ}ǎ(3;/(kꫯ:֬Y7@8(--u_ļ֭\^AyҤIJx<_+y޶m]~ĽҜ"(^Ee>}VR4ߊ+4;qℐ[Dy8}?_A仠^Pϕ{e,gwC;Ge\ 70 0 0 ðPN&Bb|isv]N]< .|M- 7 $%'BjZ VqERS`ݪ܃yMs07VLW- [^M/~AQUEPRZ ͵(yQ~x|/WM]lGAno~~gÄh9jPU+(k!\'$ƣ+JQJJW/( KՒGB$X7eZ#Q5/1^#jCQ$FGBu986r!)2&$qDݨLEZ.W8Am'm3J*1ٜQb.=A*3][I..HN*(!bDpO걝LկL:!c/sPΚsaPZ,b|TfўKRa(E zY2SF&۝iyغEU krqvL~2sk&x^9AY|2ͼx7qzjcAaaaayet8Ct},} # 8PP3Lw/-6D##ؽއS|NZ\&Lbw*7o"555hѢ/1>KZZ>/ X!YAĻ@PV+#{e*wC;Ge\ 70 0 0 ð# ޽ ~FR,֯4IAOAsTDiΟ;GiT"a=J$?Əyv܆) uZ|tꌈiR3Fcȱq1w3*WjW>WrzAYghRE`]2ɲ\4PT&V Ay08S;CwM}AJ2( 9CqӪ(Rۋ;ʆs<ǨXgdLVfcajMCp8qsؗLA9}d<|qoM+TXU9U2U-^e繺>ymsɂ20 0 0 0 8 Dg\tb/4>[w~/MFaO/S+6vbIh( )4Js1D,! euFPByed3I+trU72"v]x.y=g߿W8`'!N455wst/QDD{2em:kZ{= s2;p N7πKl6 P~D,j3I%)Qs t;X T8Yp8rQ453)2#Vuie_YF HS4:: x2\zCY>p`WAPثL#*>+>s<#s@Y} Go-------------- (o@C;#ʦU56d*pM*5ҝ~z2o>x}e*[BntqW;~E 49{rauIiG72fbj_Se܄{&fyVyKM.S ( Es'յSn#Dq*cX;ާUQq(~M!SR|-W(?`=ln]퀒#@ 4Ӝ ^pin˒W\n:Nfl (Zjr7pN'€޵/ůz#r*` ( ziy6X'bE9M^4Fq|Mǖe8M#XXTbH 3 M21sbaTX4&iiƻ%A1(?s/ys>C_>lȌ9yQEB2ϳVs~c8][iW1310vSj6w2)Ry~H_wݥ!وK"ЄۡV yu %۵UY擓ϝos;*IlG%)D܁FL}i>45PBP5UwlAσcxʏNF땃ȳoJP~$x)(K$D"H$D"H7ARp&n!Nqzd,Pn)Śfhxz^,Gq`;}γ?VձZ| \ok˟ϜYM9*W\7?sQy\>!Nk!(W_p(0@Le2 PF,P2e2\wWizm`)U=L; 4ͼh89Ԗ?@Y,PJh<~dF0F_q?EQqj#S lr0Fz ZѠc0bd!h3}˦~_4p̵pl{{ǽx 3p~υ UiX M^wYmuaAHCrq'Xڄ6bpѴM ^%^zbI@Uz4iHzfQl ޡn@tFqEmz֡6pAg~ԈPF@eDll g>|0.yč# ؘJۚFT@[_Ez?6X1}G+U7M11(k-c_k ('~f%nY1<+ukׇp@l=E@0(ǴI ^ ;Y;cV?3/;E:,q ԥ]mh?θ \/:/h̏@@9_}gRCx>.e ?)$KЭ~_@yf@@ ĊRP% uq"*\GFl_UŸKЮJU=o\3 )KFM@9 Yvo3svڐacUKICՖ# eSۏ<~|~第~[T?o76c=P>x4l0dihd'S$^^G}F7i Y4kgﯵ-B{'x2-4a'2P;VAk*MIm(s9(I^q:G4eyծ _7וjB8hྛE6KkCj9pSVF~نT[3<]"-6q־6V2D3Aw\4:kcnϵ|_ r?#)V lPJS]y=^2P@@yee {7Ek$$ _?9t$wXwvsv31뮋W7hȎg/ -ae- d]X>ˏQ;/HmBPaeZ׆(+כrJ\7mH_%(x(:QmJVإKAE:D]D:%HMBAĄ~RRn۪58~}9|Xwf{3ۙ|"VQX77GD]Dq_ζ }eȟ9+k`;B(HJ@u?E⁘y绩'%x2(45UyWH|LB)>៙9{ν3rtl/(L;wZ LƄYGҫmd̗(zcan׿6z} C>h.saQTs&ʏY|XS]W; ayc}.L{3<ߟe\FJ)a|fs#x}8o )%ѭ_z!4lxP5AVKKZ&Պ䄀\UM >m uČ]nF먋?:@ݤesğdO@Y$>I73=G~bbY+ MKEg~&%(/z}%r Shg "տ%' /K a*: VR|)E3ܸhomhYd46R0ux92I"]3a|, u湦Ċ 86d"9r&] ,NMr7'}:1nq~͙C!I*µ#وᆹO%(9v9#$_N ) ./R:K=V;А{qWltSlI(f e0ôH_/7`$Gk Q(0~qCPS'ŘM F#]v$qnW@ڿY@?esyGw[ޣx6˲ξ g$  * {gUE%묮̪YUY5$" *ʠ()(<lN 󜕕wYq5ĵ|KsvD}N\{[AADPv΂rb^oTwfos*xVGF&h3&uۮ4Ev*N]mu mw@u,Qu{ؚJe0=)kh;״8= U Ԅ|iZyBD,Vxc-cc6x䰊fj?p׵F;1/b] P9ZLGzi^|^ߟUh"  9YC@^rs#fzzyωeAeuc~/qˉAAAk*A!(Ltwk/TPk,en9]q&/Se#+4:sHӸ.~I,?֜?bF|//#)R%ɘ/(q?Imv<A i̷ęWNP]y w U] J}Bses-ϗDn]P$hю7*eaAAAAnQ&qY{ԈUVq|Yc:J^6 \  -iS=C nhm*-DC^?/n` Y/Ɵ_Z{o'+όe#uf̃&ikqωl2u c~m9H{BNxޟğ6dM^v* cX3I-Q3UؑyZPꭚh9cl7e5+.8 /DŏU g9NcEU_*FvV#UtlJvx%떪 ic)2'?Va;Tc1 g"B]lps\Sqj|J8*6C]8 ?w͠;oئ͎{)3YGqa,tܻԟ dm?*P6ooomLwy&Ksd3.rig 붕PSTR(Eaa}!W G&&:$+Ce`l: v~i3PNJ 6vsG9C<П3x  SU|4STd*Aj/kb(*14XE\7;$eݱ8.1MpAc j !Օa;TEsϑ2HPR@vwv!HѷE@MEc,ٸbw̴<Ҫз#2T ,n5^X->;!.>ͧ},EPAAAA^IA!*|X,Q8l%v Εַ f dū>+q@˩"ރjy֐g\>nJ!7Kri{CuG5=kJշP[Rβ3XY8hǠZ뷭(>(I ҳاLK~L~ܹh 263!D]daڶa*+-enIԧ6ޙ j##(x7^AytHP}[e"ztW|;͉v˻%XkYB Y߾Wpfebsd.hx5 }4[PNL =z@AAAAWLPxFhۊsXqVxPT πHIY#(NU怀6 Ε&tqw.s眪J>nV8O[wQ?5{8?h\ UYߊ-$}Y!{+ \[ Rj?GAAa=U5_{8_Tؚhw )i-AIOk>ј=?mʢԟ6lZ,vqkgzf }Z_բK0XxYyZq0Gum dن3k~99~I5ץjon*5ȸIJuoqK{UTԏYPKUOA=|j7lQ)(zr؎zNs վ ūoZ5M_bnxTP&v$c{TeUNe5NmO Vcd8[3TG c,L_\E9k|#\Iz Usd<6=B?7P^ݓ2H1 c<||Lre!(ONm1}| ]etş+AYAAAAxep Um%܇Li^ crZji,,@QP/S @Nֵh-*Mtl *F^YjRe@JZ{!]dKAZ%uPPu4cES\t_sf Cnem鸷~0G|ADcKS&IЗԵ c7ׄu= \&%K_'aPN-(o`OUBmvAAAAW[P]Uٶ2"lHfogc{JoPDpML(oc0.e~AU ŵ!-׋˵>Կ\R΂rkѐKWKep^yzJFg-˞yp~]kYʙy:(P8s 9se~W " |CTT[2$+όX]u~-K*l@Dob-Ma9#϶ }N˿GbCf y&񼥕@^7_Fy/z`sbEP%(3X*AYAAAS̿,%p.5VUQתOשEPv !AIP_N r',(گH\{pNM> :YAVƱ_9 \fАpC`GdO_[k->nk|R'}崍m$c8M ~<( ‚˸AAdSʃzvvTm5Xc*V'WNDˋcNc0e tI9 DC_ٝ1G [O!!BdY.tPiJJ |Lj;r-W6~e>DPAAAA^qAYL>و4}X>lM%|7q?l3lb $I=nX *wZgkCyuf`!ci> a.Cc+b2k-u"=m#?!؏׊%{kc]s扤<LoUXVNeA9縌53c=3ֆ}>c]qkr=/1^V.jc7˛Qliluɿg>'ϜetFAg֒OjB:=?#Ve ͯ]2s‰AAA<|9"׆u"($(93o/+ilWdFkTLQPVB}|۟QKE5DRǽ-s$Tph7Z-il<.Uq;l+Z?W ϟeIڵ5/Rx24O~QAb?KrZN:a)p"VS`Ϣ|hCWPO_9mNq&#Y4rmǴ z@Js3pW#b[W}Bq]wo?ŚIk{r4w]Pcr{ib9PhGF A=IP$q-;dXpfxͬTWe }Q6S`;TRn4ň޽ƌ)'gb0>1k= E+im(D"Eͳ~Pe*vdX.g Bed9):q=# OHK/Uz4I-e:Xlz,pWɺد//Ul%(Ɔ 4vϑ-{___n&aǩ<$SPWr0<;!. l7n8o7+gMŶcF c3{\Aפ2wr 7[7;Kuy79G )YC4uslǙs^ү%c<*zy{U h_.`e"CPίõ-    )e#t>jŹzS [TA?x&;JOB9?w~T\l< cUucͨLs. yy$NJpu=9KÛO$8XJ=knh}(X|>P^|NnIEgy PaI <,;`ߥ\]r o|[\\kj ?6Y@e?Op|UcB^Zc /o9T̯=\OgD&=ni{\觭 T^K+TޑZ- j l |a9-Jc'5D8g03榾Ub{iO/5︜53c=3ֆM>]NjhA^JTV*XxyZAURMxU}#v }F9z u *C_<"(uȎGkẆw5\#PPfͯM2qOȈAAASxOK YP"KeP))cԻxG[wU~$ܾxNz㍿|2HDIݔnTS3[d`Ի J L~r2 PEOV XEЩ]j)hVc'Q ~Vm,s^v fd32J%$TgM=M7|P8(rQI@~FP붕/(0䍶 2.%`P.X fefA*g,37&r2 XǨO@5CU$4XX=ڳ&iG OYġ,"BKǼ_7ykGg: Z)aeA7y9c%S8]<33cmt3Z5jXxyZŊ=?<۱68yf/53U>}ߥk|MPL;Xc9Ij~d_e=㞐+    Xy{PUezއ{_Y Ê&:2NY:o{B? '/DCPͥUo_j Oy(>a{ ~RnG/ s(>Ϧv#Bs6<t3)Q\?.~]6~1Waj<|zUEރUr.eC;bRw:VA ghBzS`SHPe/[ƝH }f@-wUS'Z$cDܑXIvFCNJ"x VGoMѝ7G DbJ8TPӧG:G2{DP;ٸ3t8 UC"mW_O Pb A1<˞c fOʂ    k&(k=5y픂2?W|`ާu$UqaTذ5Tm5\COћh-Ǹռu]‡Tr9TU)jzzQlWZRomϙXuY1rKy3 J(R 㸌53c=3ֆM>]ǻtntI3$4F,o<-v {~._(`ǯdߗ+(s8Xy^AAA`<K}Xb7:bPYeo&&ƪxa?s~Oݣ!nEClR(e:u%kT@Zʾ$-êu_>T﫳׎eI@z,/̸czΑÇd+TWQ+>. VyI6ʵd<9+U]}*BkwA˟_;ӥ KJ-UO0 ).UW`na]-wZ}}<446#+q !`ӻA^AyNj2AJ9~9$F[YiPbAEtDslo(AE7]tgQZ jJ"~B)%Q2iLD"Zi5s[A~.CE [9M7^G _3 gA? zâ/+~Ǩ)qoVtc2(+ٯgςCP15]h2g zCpߗ,蚓o>vFŀ3L}M?i^\zp(eQ}ݱN@7}'coh=\twHH X!p7|#mﭼFJMH>u:{laxkޚ{ʙkAzjŁH3(Tql<`F @EcvE,P( e( P(e2e(Sl߿ŋv"|e@,P(e advb`oλ@(rƿIbq|@Y@?(`CKֶ;cb-zٹ'P( @Yle@< e2@@wQq~vۦ羸j}̒J k:fl[V ¾E( Ff2#*\%p"ֶ{}=9e@ @Y,P.uwV઩[ZڛKfptг~g@,fժT2+br(׿ sxj~;،.=5 ~'|Ù}h_szo 8N ]} _lk />a$c"|p|j(>**KxV߅#[7K75( P( EX,4VE%(JBu21xyYxLyx#bb֯_.>}FWVպS%<3ܻe2R eGgUY(2DHYuxkP$64L\XU/@yikwj N_;*mAş/Օ B_zurz43}|mrζnۓ6-䬊zv K+PMUE,Ͻt62^Z2I7Da #-t0sɻ 現fضvɊxg;3JO~= jBڪlB|Ϸ.~~M&BqgX]b@Y@]rNd~6 K[YeK?Pؐ lʢLgǢW beH3eEn ܙ3"*{YpTWzC$I&TeZjQ @c061l`, f3d<_Nu{i? ,{Eoe@@PI)n ('${Dpf -CE_8xU{!90L̶9Ǜhw7'&\/>FBJ(P4qx-x+wO;ߠ4󗬘O"Û}A'h뎷Ȕo1wT@9$4[t]o驉,-tJi168%F+Jl/݊ʍ]qPvAN:ok=bmWS?-yi+%{}ٔHwfvЭ6:@;jKi 7!͉ܦ3S[-jO kubjx[$׵gg]u!>EHq[e!"TW!:3OGoͣcϢn|Y^fh4RKo6VO;Nɻ #7ю3h;u䒀r~yށNxxҴFڼ>:N[ L/5**mn?%3L Md*ծ=g';5ܯ9+sP@@P(2D@9)6e#rPʝ h.c}e#6op=Ugʴ3sҭTw:0iU}q {u5Xoӂ P8߯cgv[>V# StA)Q\Kӛ(;,ϛνA) {{щZCs>*63NPSuE&jCe^|kq`®(sp?MORg.e2pf`2s 1D@9O#{W5[;Zfd!Y! 2(#lM]TP3sS(kljv~r'.꧔$ !Ct?MzZ,MJ^E5Q=2s(˘AeUŢ2lyu1Wg{d;5uԑєE97_>kqKrUܢ]L2<ퟮsvnR?'/W\ZQ~Rӓ.r\@+3#|+*Z_Y[J+BpiSWD+Hs(أp2W_so[+ )/"REPL ,.w7S1*UC|Ÿ(:.©\êy+/sRź?733#9^Wu#5&̔%rUJ TFy$J^< ,WeHy?x]>͌CYw^vVTk΋ +"^eruZM7l2/qqFSY<ޮksw}dwf=i+Vs9sc﫝k6>[HU?g eP@@eK[8, h| yyW9l(׾='C<aj'GۿDE&ns=eJ]2b8*zof.m9oʬ11~ YBBS se8W2并Ʃ3P^vfoEol]I&:ܴ1w1CqM;V++,ۣ>W>>S)6vh<'䰰T4CF.9lq.y>yG4Ybxsg;r@&,jk= (s0x}Z<=Y9m){ms ʎ(# 2/~A~W4%# tr_G-=s. (;x]w^)s̞?\g 3Ag[E{>Bє%ۏ~1*Z\5Y;usM5B6 W5zΓ"\QS"l7jh=1}y}e,2|b;m#ڃs]c *pS@MڀrRP3sp }}6-˶Ycztr\uZ[ f@Y;\Ud:{hK P# 7Ȱ'Xu:m9lc' (o}[OC@B@ePF@Ye"\Ydts@YKO\尢Yߌ4muYWa] 1a#㣢]o;/V.m6.~;_,%oK!f~ʪ<)iUŷkO9tqMߑv#э߉W8>i!ADT3j&`myyxʙEg̀2\҉Z c;RC"(\Yl@)֒D@׭:PU(k*ȼ7{;Qg+| n=tct<9!n3SZp?<4}Dz[yB@ePF@Y%y"Z[€hZߋBk!l̜}jHhz߳z@϶r;WY0)KL)y5_8ώ~9oܯz@Y^]\羫b{2fKsyT#{K11$~~ޯ5GUk.nL:su=Ϋ/]i:㳟6ǁu;ˢi(,⿏? /͟_S0P$I$I$I$((;TM|;'''O~9PvʐT*&$?i|~]S|e767/'[`O\m"2PM=t\9vdt_Ⲋ^c kod,=w d?!UY^;7$*J 3{t }[<ܷ8݁s6P[7%ܬ(NNGNnp|=''Wn{};RN(ήu/ZmHri|mw>6šrayzV/3P@6(/vt(gSݑXaoÊǓ@p߸dXɈafC/_o\;(Ƈ_o{'[o wz󜨨,1ufU,YU 3M̪7=zqy 0P@9}Wlok v @9Qo>|m~~rv壊sn2'J Ӻ6e O^L۟G,(` r&G3ZQreyIlZ@ee0PrTO~rR_Z^303SjV%J@Ȟl&JB ;A`EdQEv@aBHXčy9[v=ݝtlï=wyνOcSF NQ<.@PxO?Maaa|G (;#ؼt#a".Z6ۺBDWΠO[Lyll u`],}vWʝSc8e顿gRYN5p.{%+54zŘ;VIgKGs{h>UA&:rn/?URiղޅǺ}et^n f2:z;C~*0hH&f̭н]Np.qer:tGo?Jo|Cz2W>1Ci}^?:'S&ЭXY!M#_w@-%RU1ݮO_6T5%it&+8:\OdmSԏXGUDs󾑖eQtj@_WХb`#iYˆ4'M% !nN*{tr-Lcynw\uSB>ERܚEi!(; GE]_B'M5ߥ4h7a԰f ?*K[hϙItLU1ٴdZqE w1jHuxxQմZ>9~3GB4^*{yl ۏHHtZr WUHO˷O.jg}qӃN_;,'YQYO'gඞ! JҦ(e؊eQ\#?oχm5ު$s S{a9צzoLQxәvHc|Tc6f-BeZ( ʲycaeaG,n[霑 ʉi,"PߟKA^=%rn s A9$܇u_%re۔ONR?6APH~_?<;fL,?zSP檱fciXn;kqT5o FA>02RhʬT5ظ$b;hݶwc)1% rhFUՕEco%Eӗh1{[seݎI \\\ԚUEǰhmfL&t8JIKsrׄeg$wUt*nݻN/9s(>)FfҜצ=~jy*k7/W>V׍gܦxտqj)cgryt?u_KM6B<-N W88Vʕ~YV5w)/bY7po/MTr6h-/y%Qlƿb*pqT$S Ep"(oAY reJR7y6>σ0#p4qnZszX0,\qFN#(ʕ|P֚;6%VewW@$3V38)vvߖSU2Wf Z{kk6>[:0ϳw([oϤWH푹!(>#(GDDІ Ft9i+))_@">S!<jjj'۟~HNWpURRRϚw'3oy*#"z[PfSɍ%Xnʱ(?ہT{)#;B{Yajcm'3WXVcxoo{]>[C_5=TZ98B|}, %q~~UYF7X6X![9Y0JC59 WV_ ʍ{Nkޡ~ñ-lGV^^Jj5lVkk>^U<,Ɩr@oǵ7O]cejo[ b\gɍ֔Jܑg Xo&n>\Ec*b)*&"}E-(qVc6ڏ˭?7f-ʦY71ʧBPvp3gﯤȨ2IR5-.RP~}u> O1˶x^3  -蟯~q\_1 2 2ˡ$9QkAPyQ%p[v8 (RSSΝ;&( ku[Pˣ۷osu_u֭[w=_8mV0`}ec;Fou+3Ҹ}EPUvvwRg7 (<&7M>,HYР!&{mQ2$˰*R }Ǽ"}*ƪ ]?HуJAXfNrɲT)-W,1wi ʳbUdmC*grΘfEb;RPfZS^>fkUJnnre[P.MP}޾\X~̏ZP0EqM<eWeyϏʸ2" xpElalũ*\5/?Oz>.(KeR>wYu~Ne___7{|?j_z5ꣿ./*|T;&,߽䐐ҥKպ/8v…*f"=+)Km۸urFwR/x`ٙc9qI͛;i7@P~xѬ4z5=̯{KP& G Do,GQP5N1BHRxAߗ9cޘ}-( ^ âYMWZd"̕վϵ}jhAYq˳ҿ`qY{ӑm~>lfi?~iձcJF%J ʣ"ù_H 4D=P<.W"*Wsd|쫪HQ~?V]åeBwr@52U%v(1#ԜAv s p{q\/3;NL4Jk]sXPkV:gRJ@,iA׿5={%T~&(s^uu[P^~ۿb5飿nZZUڵ<˞cyطb Yի>YR('۬2G\韑MIIN/9W̗wywRw7 (>ё" gg'eW̄gb,W8fǕb5v؞*(JVJߛ|U-tٶwZzU/<&o.%n|iyձ ɪj8f_P f}ɢ$bR؃%EuuqQqci-/56MAY z%i ,Dslk9b[=Y`uqul]Pp4[&Pva$(o=Z6Q K7S{/m62諂0`,1SN#?#:~8rVXz*bƍ6+Hۼ{yZr%p*֗p͸Çsy6V&-**ΕU8,X@{x~VE7oWau׏6l`:PPVrѣG)11QږADBwŋب:j]#_~H7V{o~cI2UUjC'Wg'm#]YYp5cRy?w\C_I=ron,n XeҬl|RQP^@U0Xב{vBͭ;zJG ®./+s~||$ݼf͛_N*UJu AY!,r̃%F>ǐv ʲOayBnwAq]{ 'g\۳/ "ޞn5s߾mf3AP7W|8,%7]#JPeŐ@ ks K,dr%DFrr1w gUVq|xAxlDU>|FwilBPֿCYvԳ<)lٲ}Q < EGG),Xh":h]#ި{_s{f諪Rכֿ7%f߾}:wA'Wg'm[o'Oc=455z#%Kد'u"ʽOcCDH JvMUI 51D _{*Y~Į,nq_*ٴL~?آB||WP.*9lIErO؄~ϳ>Yڏkr%e[U٥%jQ/ RA]J*e[Dzk tBuL*0oOϞ2Ѳ;eAD_m%( s%e[դY$[ Q1c2YQy8^e&{FFpH[%6<46lxgbTL^nJuX}^}AY|{o.b g)Lmy#YFq,J5w+wN sT9#s^yX"}N-m~[ڿM l98صsH&I7WY fQ\!(y0W d8;d LayMMPy[PDeGPgPRm}+#JeW[\2Tn3r A9lΦqb<;NLTs93封fsL]PAE3cU喫S\W:DP 6g, AY|R;YTm.a/RNܝAuAEKaXBnK/*RSS%$$D:[ 0%QwU̇gΜ~jk.GڜRn:y\%m*f2z"<3,Asutt8wЗ*]v圂U3j˻ܖXx׵ K\IZ{_㈈YA}*hJ'(+rȑ#8,A)(T%vJƍo\KWn.)Љ-JCFj+T萰. YhP S;nS.gU_^Oyϳ΂ؿSmA8V-}ՆȕxooI?m6'DWPf[")c2+)V=C2 p]eJ ʪ5?KՖɭ2K,'K#mʾeukã,1yW礓͜뜑Snt4=Z p44R2RTr3ZfsԽNP ?O odQLRlYRUsiN.x|zBg\-\OYQt+=-wqRc)^?@P}^P~9JmZ[[e\UUU ʥ˂+ L6%/v9_||< /1 gEE}K.*d乮NU~'-yٶZUbgyEF6',Y1,bgNQ8ڸuw_W\iڳJ;8cO?K{1zx:ȕ9JPfq\}Gr=իWs%|~P"~л{|c*#[n5vuW$㏟qRY?Wo ,J企a4"$& cDT 7sr7ۓ_**(7ўT5dhl9rl٤D}AKe9G` G:BPyueϳ0{qNy (jCPII*qn۶N|&~CIKK4rqFտ,<?s8MJJ2qgJU?qQNڻgAG_˲t92媘"\^,xoog#v4T~?r*k,1oSgm]}aW?g*f*N{RPsukC Ʊw:֟/SZ5rl g[FV1 "ޭr܎k 1xZJF\(+2`%G[cR9vYEj+slwyiCyC|uYe!+?w)8ǝ]9v\h V,W.pra.SN9-Zڜ]P^`j-&##CͥwkK\uX:D< :C =ت+Ւ̙CGeV$Ќ33,[Lw/:# ۷oq\aYb߱򝥓+{*|Ɲ3~ (8aãg獤/hͿA=%dEMI5̬7>/s|׫t4k%:1vL=[Ý,nd+ݲ+U~g͟kIdRR,˯fEVʶWV@ͭ;"/Wu[NfqvpugiݲʳJ5i^5 !P^y=8!zP2>G@U@9Nk35՘O՚ʼq =lCNp7ʏI6?== d<+tqlu﫠ZʗS s$~?(1uּ5}\5Fëk=ju -k0~(?>>lmmi쟩jޱî\r פe 2e(`mՕEY49::XWoN3Ԙ: kJrSĤN]V_K}~<<]9?}VwLͭfSc[@Ywvvo@k.uWL@9Уn:ʹB湼]ܝۊgN,ssōv|> ^׆@yww}\.oO@6vޘn PT}Fm㛛=͡^?<<4WWW>t:n] 5WQAPw (U^]>`zLSc|5!P&? P&Mʁ^ηj MнG@ݿyQݨd0ؕ菠 2`R&V) Gduӷ|)}z{|ޮs_~:r(إkJR 7ZC %պ]}(j^toڨ }mۇ|'Ϛjx@9Lڮ~:߭g^ gcUou\.mgaoQxC]l|6^ԇz##׽ 6jRصb (oIt:R.kB L@>= ]W/ܹcbBHuuU0 ĞN'Zl6SH^ff<PS~`}֊$ h4Y+Voׯܜg۩Qc9u9Q7L z>s`ݮv޸慥өW7x<F], ۚꙀ[:yU{TT::~U|#tP(dri njz>f2Bk |^phJ׹{mפhtøO3>{fle]je<< (g=P&L@(o7-QEa?͝Lņ0M JDM$"hѲZЪB$z% ZjBhBt.ŏge2@@SW_wF{gO5m-R,IjU-l^ }( ,wߞobX޵#~cW_ϊ3}c#R3CytxQ}Sgm\?ZS?SN̗Ӌn[3P[ώsuwĝٱ|q,=q~?n?Gsq9=#QQYsR]}`K($ܐ`7&'<~yW + $ј?K*P~?2 "WvwהeB$(5 3(֯S k\M?qbo%5dSpN m (?ٻ&8 u*(7u=+jsO"UeIڪm-ֲa\OQB0 !!' {xD 9uy@lb>*7.IwWP&K2Hgxκdge%&jiҴ N5V3^mhǦ k9|/[Ȝ:6XK.oqzIPDT,imVSxd(sٽ-|7;sVv<&_KvڰbY2 4wN* q@o~}~w.?"8sF[| m-̦etsvifRHK$oW/'P,\E:Nw#}ZM;fPF0e&pM1wşW^@M Z̕}{0&f9 ^CV&TѾZD>^nzDXX7>ߛz0~ut5VЪT tw8>WזO99RUK@z=r~w_;۱صWPtS'Cu&ZͣsS_̧}fI]%529nzjM荹tB~;ñjV42GkȜQ_g7J0::uSXUV{9~#P@ ueZ,`E*?:áozFo:랍)PSmWI]s۬dF l}S9rBq]mmhc9nw Pv:3o/>|@YϪ9`wZsj:hm=Mmq+m(:y;%92>Lц<؉2l`ca efbPۿc) Пa!dNvj871%Oxi?Se])%&PJZU 4;N#7w7V\)S֏KÄ`nr_N;ffq4Z8*(Υ$S[b8'vgt395H5MfNԌd.F"v]tyWk"(ieg3۹u\G933[t鏆ʫ^]*oDyYb.7^pZmsV.eR/f$Z p8Vfn<425r*vXuB|&s;/%hӃw^NAM3x*+UV/7ȱbWpJGJ"By_4ۊ)LI~^+#esyoAW^C|%WrΡB[sfH _=K"h:PQ*k+@}Db^ "^?X$MPӡo9h96Caodu$P>sw V[g>o}*]-}'}_yLؓЮ-'~&2 P~Z{V -E_>5 E).sTsɓ;eT/is14>\>i=<=ڝs|ͷ7b'"EDyfk!UkG x/>|O+q^2k֙yyNƖvH@Yo&ȹw`T5XCD_woxn㝗7̴u-U؅XjcC Ko4^Rī;!($ܛڒx*P}5^{Nm@Կ23\3-XLKN@M/\9~F # yxY{hMs־U8'*"(UghGؼh2eoj1l:M2 P~Je9yY(/kax%=.[2#1'N||_#rhl v|MY#蜵{~L]?ftB`cp8ơu^f8ŵ-"LA``T~>Pf|d]n⹋Ӓ&{l&;P.wXVᄹ*©OgŲ ˝ P^8:"L1_t7rJoJ; 7lM@9,FYI:w-exUN@Uq5׫@-Yv;s/o-)ʊ]r( r.ޏpy}~e@ @Y/W#冊/?c Key+bbDһ`Â@ݡy7XKsV_*N|lI+x8i S(_?W]ⴵ}`#"wo-e,Ca~v{哣G;MNsȮZM@9[D8CV9kVfTګ ޤgqLquu|mE9S(.S>O(s')o4d)Vz^c5Q^iCc-t<2'瞓mrL] 2 POqj8P.ϘN2su5PS{ED6ɔ01P~uuI O؇?sjKbJ]셱cʎqW57l}=i/}tk6Plo,}IʥE }yAN~ 50/Oruq]裖j@Y3D*Jg)ߗh0n'ʈC<;e~Nlu K"(]lѴ OcOq![_Ηs?X$)TLAbi^/K톽57(5"e(k΃g:ئ9iϻ)?#"P@i R8P/9e0Z"׫rcb܁.qK?>6*kKe0m;}JH'.VU]}9@Y坔4.]9E^rQQgAxJLC"P%ۓ^H/:xxGxk0~ jK &(y'e+IooqLEa^V9>P^HG9t;I.PIT\5ɮEQ*amzwՖ2E+gh(G6PVr]6 22 PO{TUārLrEm .w=uL| 1+-8f-cS4_>1,xbݖw|O7vjZKW-qWo}} 2 UGZub AW@y}nDzp9CwxDMGѼxgf$u;PϭI'P 晙b  }?m%wUi6P.SĞ"ΞSvLY歸Ɯ9:?g's䵶n$PT\oYlLrxpo~pW|@(?́8E4 0G/Xj>zreѩ!lff+ߵxz󎵤 m? ]] V__7Os}~gcnk)w }""0s{o;@YΕzqj JW@yqZoouy],4#@YBӜ8Wߔ a\Ɗ[ݘopER wm5aO6PQYVc yI=K\4BD3I| ]^{*Z.i^Gi9a6/ٰFqEGJ0o vvq/kQ(34oex9#klc1D.~CW嵶5i(s<~o?fӢ&ڪ,P>9zDčRMc9W{i.j鬧O!`5uj* (:8mD-U<7͜BE9Ի`6-_=EGc.ʘcN3edۺiիK'Y|N"$hqE!ғ(37Z9@UP؎g@YgI<+b\y.WƓEFʱg.933VYד:z 5WRjF2g#de,ɹl’|1ǎuYwg`T~E ˻%PzPN18ǡj JK9zsNI_iR UҾjx?_ pwy"XmL*+h,̖cwZk%pM7Ŧ@3G*sɹoo${]o1v{BYk|44h]WsS8ϜHEs7@q+Ob-^/ڎgeq3Ir-K٪-6Yuf/ټ[bj@(? [RI/yV#_cY5g_\^s|^m/ݎ9rU/Gw_q7>š(Ba}Λ Usǻ]}5n}%;o(ܭ{6Q2(~V꿿Zj8WѲBdej9oOi>u\KZe0P,{ʌw"v`.2`6w%!58p0^rs OMyrya[QhYC^tR<ۭ&rh/zߖgyWikö#a*P][eǿ,w6[r."PgL^8|#j VQk))t55P%SR.(^4 B\׸DpC h@rGƃ;E ;G%(HIMAEMc'(U2!άxyg~x 118"u[)9jTB} |FW_kyYuh<EK^ʷn.u뜥Bub~q<mu<KQX5ڦJkC1<ч5mۊjE ӇaeN3oA{rO EJׁӿڮȌ:fj۞rl:ղXmɲc ]͗0qqnsVs*36⒜ty/,^bڹ;I erͿ^UA4׮fo#Pku] lD{Gn+ eypą.-e= kN N?n35-&}^kTl=i5EӞǯn5h&]*pԚLA}SuZ#ga@xP{^ +wͨIwϻ@&inĦ+Vx4Vn+ e5=f9(BO] 3DWW,3#˦̱}_6MUkU~K ]Ti} |kd0jHc<wng@T kBLӵr9z>VVG"K([-{('-PžsWo33m(yNCuUNn֬ƎN DT}`?b,(@@Bnyh1Ei08=K/-t2 n-E8rء0QЃj;uSK@e":&asnjvu:~#yH`G IM Œ YPfYl^AH\\.T) WU΁y"럽݀5/MD3a(-75FOV+ s׫zLsbG0f&DDDDĂ2 XL ʂcݴu*bc}{7Q=r*Gk 61o#r"4 q >|}whgFm(7zgʂ@{0t^@uE5♁{\u>'EVd8pdceYFh;b~Y'ad@( ΗEgyF16S1,փ~\P|\W1G/&b|<ǚՎ]s?e hv.DMJ#*5MҌw\U׶_INzΉ9'Q*P@DA@,XP!c]^bC{{?9s2'w~ko+cÂ?~d9f_36ӻc .? o7WRq++Ji.llg|P=[e yp{4Ս? L5tLb*Ikg!aCz2\mcGEp$v6[ݯtwwqVpp~.ze(}Bt/F}ʇ`n]7/iH{٬7?/ )0 2%w0N~;N}W&Xtte&j/#$jTEc63¢ٰh; >JO<;ώAhW_IݔGJ ٸwr qnp8zk"q*ջ )±]@RJ8roσTg#5=F}C׼ h}SSgVZKF%H$D"H$D"H6+W@!v]]?wnzСCѮ<&Ǭ_SSe]y}oD$9 ¹p-,I1|1T>̞=:׮]K.={`ƌh~Wg#Wnnn=,{H$ ,y9^|Q2(4?365_b,4;a~Vmңvmm:ԞmώPNn6oQi.EW(NA|rqqjˆ8._XB8.zmd۴?iq#W)jTM? HWewmZX&XPFpۦO*݃a($ l@=t X1aېpKʝC`K >l_cbP̂HM,D"H$D"H$@YZf#ڂ_y#A\B 􍏦(:a$&vΚ篘gц"7O I ٻ'FvPtQݛmڹZ/'LDSrhSy;f?.F%0H9(#FAxq#jsWsPi ]+H/ip $B/?u?v}T>ytҵÂ)j'wʗ5'8#"n~R;)& hc=a^F[ 4ln}oVBʋQs"!4*K7Ib_)!A'쓿ԍz)i4R+F8V6з918bv(#|P>!V*߹&oh5=[P^-:1b5Bжag102-*grpqq@Y$D"H$D"H$rӉ'ra%%%ɅeQЊ+ԼݿeYG:t_|Q-B?(UWWی ׯӧOi`ϝ;Grssᥗ^[OBB}nժUʡcUUU޼y#HQ2xXSw׮]QkgpG=S7( T .#~?W6_z222?{ ++ [led=s_EE"G'~%{H$@ (7'ˇTp~ WůIόKEo i@>kQQ}|$TPr-iP=ɠ AME}79e3𮡠3] g-'ј6|DȞV_mW+7.+6I@%~ŶPf;K dYk[yC<ؖQ|(yy*?oO_e<0jK @oGcWK(fPcd@ AO EADd{#\Kk MvgP^&#'S<YPvswA8W_8G3xxR['ʳVd@dlMj Oda_^@ya +{T @Y$D"H$D"H$"D0'Z^u4a„6|e(B5Z}7(@rw-[_x=T\E|'4i4Cs611Q8qMH8([Zt.ܶǨ+rc1jsJJo1Y 5Geqѯ%_.P@NNKUe%bC%e4WgM?&u ѳE0YW֯eҲ ~HE5sȶ*OCRک*² 1+ѿc#+ڷ7։T80 dƈP" cTl;7Fq60FSt׀ 7e(#k+%e/7M]˷(玌VinX[7DfE! SeH$D"H$D"mP?O3Fr]>kX}Mu[rʶYkOͻa P|Dm-@Y+v0n/6(D"H$D"H$P[nm۶ACC]>~8TWW[ol0o<8}4ܾ}Ο?K,???gHةlH˗/ȤO?&B᯽$''̙3a׮]pEΝ;pee@,**۷1)pq٥'xzz2u {,C _\vuuUix9UMŘ9˹3瘚)=1 k2zMMv3übwUmP6r>80}σ c6?sa-0|uY:ԡC|Nh cİL,׵b-ۢ ~6g {]G ʸD(ūg8Ux04}$iL^i]Wv~F[PVkG (8lӢrAq6K^S 1?tj'DE_7+1bH"+k!N9@= k=EU'| ʊhk^;rg5BЗ`= P>d >HI诛gX7\]ֻ>=vrt at@Y$D"H$D"H$P!66W!~[̃M6a$ѓ0ReiGO>TONRF|NΝ {wj^V|m1z P^x1BK}"cdb|͵ElM}σƈmxϰ>:,/f…4 m65F kl_CM}]-gS ~׵y`0YOO$rK*5!䉣ЦLSd?nX9mDMZ\iŕܢ򌹕8PFO!A-(2/ުEҿ(]5[1Jk5TvզE}|YaSq#E\(+E6d;oob"?㣕B<PRKi = }Pوr`"!Fd_Wnu1*:qgYP論=lNN^T;όn1@98[SgEU>xu*k<5TLa񀲋8=ZuJpr"H$D"H$D"~ԔtLCM믿0 &QΜ9C| gT) p_x$ڼk.uQz- x16OTgII kb$cZ` Q[@a~4i m#5|Ԣ|_׶kmv{sg }{`0~ l@7p2ZPx+Җnxʚ|9 ?%zZZ^Ԓ2֧"Hwt%Yۺ=Jp w|}J+(ܘT5քXƈWVA`saUq}t#+tؒ1FPѳۣ%1j596J|sw!lwyjBtg''M@ysF }6W2B'SA¾$jIB̪d cԺ 6aO \r9 t09)\\uY5OSFٴDB@8;"ceO<8rw#p9Is@`8}'_\)X.FQԷ}ƪ6@`{@F!$ɪ e3l[F&w/wz>0M;ok@Y$D"H$D"H$$iTMZ^^d#%y0Oߴ"f k֬QF )&^T0."4i3*e.]8`T PuD3fDϚa i*b'oQZ8ݹsb^sQ/'IT;7o>{a/*iFqQbH՘0ehk@944Tلh|}}UF[2P07ZP.++SQ{93dX ,0F#=T__ v(㞧࿌ 102{σVjs[ݟ{ϰgUy/f1_ghkA-zu:뷙oyQ ~78:+ 󪩟D(Ӓb N2yiz7@Q)oP#@ jrGd7(:۰a (,AƴHMO c`队]Np =GKf8 PUn<0FT3$,Ou^((Ά/1r X(ϥO~㪴A|ej8}){j -3(~@bX,2-9-,u1{ Lim^*ɡ;c (w_o|=;Asl`AP+"|!; v]m/|JMKRy7 N䫑UY.*6/'86П8oSg1¼FsYQ  FF'MuU/c"!K91( {tE(e1jM@D@!٬GQRѯY1܀2i޺!E8'܀eM/K'R]SO`E"H$D"H$D( х (}ĈV7oU6…0ې!CTYE uYҥKtkndg#~Fեj;CI$PώhIc r d,AeN@/Rۋ/Dz ?Ӗ9HUαcǚxheUuź<==u{,CM_Kʏ?kb.>3R07ZP^v-ݱc񟃭~mڴ۾};7F{ne8y$x"]z9CKKK2F>ZmqϽ>e555rANm#5ZW&:|_^~v g }a`kb^HR\Մؠֵ :wt֏I5om#p{zyJ'. !W["T (k~ [rn9b ?@xPM6X"{x1Jknr6w5W&//lKڀ6(,YYVWGc^p0wz!cF"6ct{t$5Aj={87RۨY 1/-uf#@A)}Y(tHQXnvAC±m[28Q  N1j12ʦƗK6'CzNO/H$D"H$D"He5BM^fmܸqT^CC6EgjuV'xBFyٲe6uwwg783N#KKrt^~>nak /un0}`'wMњKC 맾I6;ٰgeE8wF|4Ff#K$wW((e[`qp)pipdqz4AA6=zq:>_GnT#ja$rJa|){ީ DG1 !0m ~$ݝ~0|ep: ^gN%`yaL5/ ^%hǣ&á?yb^E"H$D"H$D( DLK޽^|u/Sіԯ_Hzꩧ 11/`9J777̣K@c|cljZP=Ta$|3`;54 3=an{`=ϟ 񟃭}aS {{1\'Q|M\SaV{@92{σVks[ܟҹϰ>e@eݿ^{5.ȉ cİ<}][nc` ~ab`kb^Hvt gfUS 0NB I?`!(V66 "`O˜b suE_Y/3  2Ai(P&B!B!(S|Qx<_[PЂKۋPPO<#J'n[l6N{ТH$C*RBGEjϰq2Gcd<.JZKTn)| v0" |>6R׬N2^3d_aJ@cX,9f@rv~2rY-9}7 낌V_e80Fs8GXcαhAA{06_~u l8\ 6|nk>@g:zhW~ze !e )P&PL!B!B(P5rT o6(O4B|>\l6=<Jz8sů','|~~lv@`3{=^K?ɤV)rg:Ǒ7k)Β<1ht"P~yyv=F&ؕ{en:j[@E>_?爢tl6{n}#ןmĂ;,|-{||TNXD 6|nk>Þ@g:zhW~ze !e )P&PL!B!B(P1iI1sXV-K$FAj%řUhRz|nV;ѽ^*Bq6ػb )E$*rD Q\((ByB.khHB ?Ž'+{/7/zgoۧoԕ"3c\Vk*jC ;ۍA'zR^>A '~^1?c]h,|)c͏˟/G;f 5 /ݻN'}}4uיs;S_yxh/CQ6"N+^/şd `v~ŷ#A8`_dΰo}>X#.k~/qHP(dLc޻g|gy_sαV |;[7߻|Y~AY@ e!( 1 |Al$?|vo_,@ @  Yv;jT&QhT~WR m(z1ޕrPtn*:t:ͦvl6K:V+Om(|R$\.SV3\#Zd2gWTF"sA.>tzN, =t:}Ad07AC(uoHv(s9ɯkmX, b]0^&$ Hn.K R"v+Xj%Xh4ZX(^*E h#,ֻ|KpW }]*6cqm]]]eQ(\T|Yܥ|<b]{{{ݻw1bc9?l)?TY绻@L\bP㟍 ?/(Gg---7Mqm<>ɎanEG,OOOk|g" tZo_H~u7@}Gk1>p} ?MLLy}.ɟG9|s{l;C:oTJwc]|?+](#P+a PF o {lAEM@(׾@Yb:Z.x[Tk,֥US!F TSf~~^\7>?99Y9uvY\}FGG5~(~W_y~jjucC $@dV~>(T|P1[bH8@% ߿-,,{Vy1b?^Ax˗/楢oOw٩v*>u>b:?s7~lWpqI:Dagsω4qc\?eȾ?~Ah-*"#?vc:@^{!^}?ƺ^y#εk3np7@ٟG94|s{l;C:oTJwc]|?+](#P@\W(TJСQwUתBU֩ʎWONNԥ2X an,QUuPaWu΢iO>e###X%5G8MMMTbٽl`` {QĂx"?[ï>\===];;;e[1 wnvvv&W{"M;!jovQS%IG7̳J:h4XrZiW5Eb? |RSW?di ߙuߪvu#[^^V,ieeE\7qu? gC?lyA1E?,NW[\47o'?e!竳gtr.//u6{䜫v~y>Ɔ:|YI[^%>|cc]M/P}طo߾O]]@M277e_~ B>osP;gsr29-/P errR|>I(4¾-[ჼ|R(ksIXSIM=vTuY)kc^VTpXIiI۽z-5Ruԝ<~ZWdff_^V";Z_ǩ}[֚~N;bhBok}Ek}m֚+MRFms,ҽ|.ٹӧ$8u츌 H钤n92{Õ56--) _kKs w?%(}NY@9ߟg~ mcP'III(_t =[uGo~ i.WԷsdBX@j{Iv=~lB;N$l;ꎱZj56|@J{ |zJυ@yUwׂ#ԩɆ](V\I9״#ۻ/fkV(Wʷn?YKZ P@,P^@5V$ͣ}cDɾ5VkίM+զ7ޕًؑg47h(ǟ'BޖvXCGvN-8P>sxڶ@P?@9osw7JQ@9ݖr.=C@ @Y>ų Yl%(#ܟ(XўV=s"ޛS~_x@߾ٓ鬹jǃ6nǥrس{g'V1P}ĶƿZVm3<^ r;sz [ P@,P~rrիcDɾK+P2Er1о~dvvڴ47)wz~ɛj c܈uc~_8?c%r7Wc{t쳣C+Y0s"><=osڅs=*=lۺK[ P@qsϿS9x'sgiD}"m$˲@9fdi|ߕ+Wd]zW63}++?yt'r\Yk=ίg.c@x=FNvM/=Ⱥ:Rܷp̺b߈Z'ni޸.dz/z\Msyxayw[|O-7.r\45}/ ?{4c?GUFM0;~/n@(O]6Eɍ8cБ+Qmr};(],Pn(V_*~<ņRN^r1#@-T[(ǜIQwuǾϻhݺ>Yݕ=LQ8~5ύ9~~1 ]sT}nTGrg:q؋[ P@,P~GrZK4p[|ݼ~1_Xe7V6ǎٟ- X*Kgkᡣ ?͏l673]l AkXzxt=WH۔Lֻ۷d.=峙lDvt}_ k2q~.e7mm*ύ!~A@g c\:uQQIZ9Akk@ @y奯(ʃGSiTq_/I~憹e3P~<7#CiTq_/Iez~}OZuiTq_/Iez<|h?-[,*% @ @CgoFX^|\r~L( A0ppn_`n`0` 1nN) Qlj1'<-( P( @@,P(e2@@@ @Y P@@yQUk1bt|=@z=M@ygg6KyAS啕/Bc>vŧt :qg(i:RUM@/ϗ٬xT\浵eDq4,ְ+ = !Q1Pݸ4hda7beE, bUb^HMP1&9,JmO2C-:7sy<{P~?iW{|[v*5'zĸhhzŸ9|I4Ft.ʤE*[vѧEg~.̹kâHhiwWCo!/:q]4mUh__EM$קO'6\\B{D=t3K {{h]挽s@ݙ}~?59k#{!~̱3y@@yKWWhC6uއ!]Yp (b>=y+Fll(e l5!~+qJZ78wr.5|E~f?Q~p@ƵS@ eݺ)l[lh_gGlc[hhϟ=yy.e e ,͞)(7}zAhMaS)\_z 517K$dLQr rSr*iN&ػhuJ(aOIylr|wj7Nvs|5+}/a53c|ɉֹ޼n- $G1]*Vv0iO-!?L1ڇB:W.^BJN=/k< hG+ Pb쯇ˉ֡B?+Ug\Gc׬)zkcbA]֋c5i:Hu9>wb (+Wuu#}!#(gj3?Ng"w_oVoffrΝzUiʑ{JJFP(gf{Bk#Gԫ'c)+ ),ʮ^26))T9ϝ3[9}~a~(83\g<)/s׭{Nz}į秧Gf*H su=),9Nnsz)_Nm 6+ձR&a~_7<¯PW%J 9IcdR#J̛+: m+~¾|VYsz3PTS;FO!TL% OPI <jK/ۥɩ:BNMPsrzz+Bi-ῼAs~͍֩F] Q%%9K[r:ި5kgL!r`r}ni7/oO+e^;,||*',fe.`1άWqck޺4һݵŋ=|ȅgei]'+%hӷ=FcmtPW:Ԙ mV+;~}mF֨ T'岖SM6v˦NSw?+U{en e{gdh9bSz{^ c^Ν?nߔ>6#^ݏ؏Κ?׻0of͒b_u[kÇ? ۗ)oذHYFP' (9z<[2?D;=FWP~mtQ(XuXMQ7%r  HH-Cܔ'wj|w2RnMղ>u7?ޞ#Vj\xNܻrmw-551HHGՇszUY 6h a)fy 򼃟TS m+?۹utONJO¯PW]spYZ+cZs22\%Փ=T!\:E/CeJ~/$~1U^mG ,z {>rݺoA09rOqͷEE}Pslɒ̠ex(djL}۝f' Hج~CƇ?k&kv˵ u6e9\?}Z{&kRϑ) %AN_إ'UJ}\7=755ƨ};~%MI]W]X8%g2 efia>>ManxYD.H}4pf_)_=Y?32\!5mRke=eܷfPX|jG BG~Ya:f~ ʿ#GؼֶEazC vC@8FeO;(5 ( "!%YNB_1O˖yk?~Wed;R.woǮ? GNd_? 읉kڝ,TѯNJmvnW^m:u=sJ*ȼKO7Ac41K(#s?Zu#١CLaqcqCmv \Bʣ~/~0~mǩ P (̐Ʒm\Rx{ v!n]c%,,܁eqg<ʯʓ'^F_uӳG{ϓr'yZ0߅ZZ6׮Y-gJ1ją3/ߐo\}ϤD8mVH>Qfͫѣ=}ʛ7joU@*{š (| G]H}_Ha>+9~Y}̏S@@2eP (gvsr%WF5!W=܉wqX8dQ7v@w[Z++{^~njj4m'ϫ6lo;}wcu95\^Kio۶m3W? zʉm?r^f8=c퀲[+B? v@Yn/XsZjvo7&(Wu#}!#xgo3?Ne@-\NqA,rRY.1IG'-<~;3%E'8 u;qAGlxc,`5nznDC͚c앋nn4o;{3 $`=JB$ܘ?]U>+3}{rw}ޑP. \VN9" 9m c7(MEr C%XڹSp|o*~I.%YgnYdpmcL6]hR_ Ά}PޱKW%W2eJ4mOzr]o_W%^Ͻ[ 9 ݽG0v`͑}t0̟0FN@YaR) yg5>뼯q*(P& eW%˽2ב`)5g7búm/U}u:=m8{s]y]NR]TNҍ}pe2Nx*S&~SjK1||7w'VVrrB5*;qF vW$ħW];tٿ^?K3GYs͑=tmΟ?FJ;^)؏؏š2׿0/mAg2vc=SPNPt=l9얜Xr&I&ɼ{^zK+/` yfyƮ\al;#,+;smBcn(Yǂ:A]Y//HW $uk3Uɧ>Ӛ6+B?+g!o(|N+9 }̏R@@2eP (@@2e  (P=sz?Z;v0nJ里K\<|;e7$ݺ)ὲ$~Fzu}NǎMg@Efv;;2x^jg̎ܞ C'βa~*vM#?M]g1'A@UqOw7ƀƬX` 7J;0&́.dfjYdneefEuK+hNzIm̹7jTc:e䉥I LZ!>=$5!᏾7%д1zH&go\30^xj '5;ryM6i}_gV:- T;z.ܿHOR<9{_1 )&S)|:80U2,\.?M9/kJozaG} ,&u}eJ޷7#r}O.wB^sV (/ʒuez_5ʱ$lS||XYݿHOR=oI]k-+eĐB6>d?Ɏf+sTIh-ɍ:Vf;rXY0_#>ImP$Tg&յ}?I4+l]]f<[#TI薀.^ɚPv5I@2(X喁hJhi  BӜk NƲ΀$F@YW@YgMP6uŎ:P>{{:l(P&hH@=ws:qXӃ (xY (/Lx8h[222zP($RUUz 3{2eã~F\KW;lC;_Nuȵn9Ksck v{H~FxGy}Jlo.@վrIKj__|ll9dll+'eΕmΦ[sm%J(o}ȲHD"Bџp9.^3~8P +k':imq&TC~#Lv`)?!i'I *d2 zv ^#c_!qz|!zU w]=1^^-I%T&gff`6``V`p`DdUED; (HE f1M響vo1}9y~ 8Dz_wb{!c}UqD5z5y)5_6Z~;gTE亠PgYaN7 3~ut_N[ORVS3݄=f쾮Ʈg+[ 8m7SWȸ$c_A*h;_բgw]S_;..=G̰ {6߯ptèOFFbSnRmmm=(uQuim9]}gԨQb;ltA bg9 ɫ"H$D"H$D"H$? q<PծP@\#!xf``Ucr{P/5,`Vh/LTzi0Ǔkl.~ @PV,77T:t.x ?Lo`q7W 1qd"fi<[ t_57xdgm5(rK[Z@{W @6 <@ 1F\aAR1P=Hmmzf9p+woYXKxz '#~'ץ iz햗6(+*ԯB#A3yq3V6ٌF|[G{~(=Uw/F;~a+}>h2w}PCwz'"ڵk b;tZJ,,9G]9D"H$D"H$D"P6A`ҲR٥TTHjLgCT|x3+ga)LFOS1< nP-6gepr;PPas`+ї%b:ՒJq^4m.檲-*''q뇪D4]Xs=~iӋFFz G:8|;Czo@R _U ;gMMn?=1r7|*r:O9U+f2ZTY+OMM|#NH0)l+V~Ǒ9 [4*Tm+c4ڨ:괘3օ93cض2Ԇ:VW@.dm>w}b qúvF`SR6Tže3qΨ n=ZySa.v|?'߭>i= %T=@U h Bua͛*33ӯfP ,Pǎ;{ڳgzg_}{S<i̙(ñ}aӦMt޻wjiiQ({y9|"D"H$D"H$P@w/Ћӱu?5!/ =RvL4 3TDЫC]Nj.ԏ6T bwaBn_7|U Џ3r"M4P A0Qc2_dK1֫9< pgUq 6tgv ͕N~`=ogScN cU<_(+pYajdp _Thh֗|"+Cy _3?9>\oIlbcZîk3~ch> s4Vwqv$gζ__~Z u!0+T6D|b=sl>=le쿬g.l#F,0c݊|@٭P99ghre`k(e%'䍧soFD8Mq]o0legs`T2c_cS?# em8>X:7>g\r{dTxZ|;Փ1:~mr] ^?}%?~uTvQC}ImC,i+@#^Ɖ#F~fي v>׈Wcu9<ؙ8v'9sko@ׯ͕+Wyyy8SZ(('%%/BϘ1vޭ>ׯ6mqR/^$kXwV^֗mۦz!fް9GeH$D"H$D"H$wP>S%kb~61j>\Qw/X3GT>Cu.o{*|LVTk?Qg*UsJ7*m~]XT g\jn+wgU4}0+~u~P(ǿvA %bWp1U#8ql[1bg3m5ѿlmOmW; (370G}Tê~0aiV\)= (cO:6y=,8gUQQSO ctR PXx+c=|AKc…bgP@Y$D"H$D"H$ ,xAt U2HSq=:T%LrBs;vD4J ėþZ{^/@.#Y߫6= U>lͰ! zs ̋A#55G,AhL3>07PG[G3:W bW̏#׍C鮦ZĒն=v4zjgmʚTIsŐ^mq|ű >Ѐ2c} >U$ WHl3s9++dgl~Ĉv[(:c0PUhTt@%;}*U@Sç.U dH|5q݇jƞ/43_PEkW;mP%(0ݎm[bNx0~y?P<ΙW C뇸c P: ݴ-I~ZpCFb&AԀǭޯ̦Oɭ(-`F9..>݂vҡzm8*ޱ(c* kOjqTA *u9 cm:ש͍n$bW̏#~EU\-ݱ@; C=3g,UhËuwS>8v'Pf/C}:跮ʒAU]lxfzΧ6X}~fȖGX>A!jڶϸUPowP&e?I?kە#('E|5s 1 Je_u7tT%N@y+f K̩w S8cP:T=88$L}8:8&ֺ^KgT_]& 18Ǵ-3'm+F,lY#aj6u#_XW; (370}ix&99QUWgPqPIIW`VPP{ 6 l٢n V_?U?q3U~~Z~:{>N~ӟ +ܹS9sF}jϞ=jĈo~ }. ] #v.^sIb !pR<ȷ:^u*C}ݧR9eii)4>DZcڵk7QG{룟=ΝFh1{ij*G}| )E"H$D"H$D"(Pv8uy GU0QB4Tw6z#h#J!@(B`|9Vcxm',)"3֯e~ )kꌱdw>x >|-peu<I392??{b[ 4꼂#{(VOjHƒqMcg|2e2k}d/UNl 7sc׍ E{`ÎGX`>PEtgޑs/Xǡ(sFm}y-2F> !Mz Y X8N|̧)&9s`JJN@(}{[ 78K?0`(r"gXUjcUT| f90B:'S?g!eXXes Ex{k__~_(ݱCth~ހ8bg`˳gڳ7c!a&|ű >Ѐ2c}P&}ݧutt@5 aaanŗ_~X@^89?(O4566R_+mzj~)w.ϝK灟aAP h|[6lv >\{… :s{7n(#(~miiQǏG<g3۶mm'NP/ͱqfdd;?٬K|9t׭[X yx۱2^__o)/**K"H$D"H$D"HoL Ieh}}F ELm4zp/{ l9gPnN6 qNj9e9˒<_FtZP^nuwP5p|cP}8cyvPfĠMFhsiEy%Ƈџ|:YQZx_3?9tL+mew,й4xhgmEšڋk{w0/i9kO}} Vτ)ΈA6ź㸧3 ~ @9oxۗF/k}0`{@jJAqodF)66e5迨eTb`[( ü.7XG|Pqimk}TsXsT?-߮3|;e~3_ä^{|ޢ7EPbDl*ޚUK/įPc3'm+bl_#HXS,y~=?O|yް'پaX_à  1]i?22P98 )F6mR***JEFFVu9$]oٲe*%%03#TC&) cPU1TW&͵:5 p7Ç3fe ps/X@>}7*xҵQ@1޽{}(744aO}Lh ++KCO>O~}Q r{'RUTpG?ھ}>Ga̷=PǑq]:KeB7sLƽ)aꫯbިN=>Jy]x ;oܸ?(z-[p[ߡw 녹b\)D"H$D"H$P6 MhP6 F$7.ƫ6k*_j5 yc}( FFX_ZN #XARá8~TEEӟӒ 9|&œf3:(}V>Z r0qd9aIv|[ 9P03"ṁ 'Fת+pQ"/vVg;M* ߰sg/#V+C8|@ ( g| @sNO o#fwj&~8n@ vXW%'0>2N13V6ٌF|[ ;>GQjԼuZ86$72UbxdlTaa!$ #!h"Wʡkcǎc^ 9b]xP̆{'(e~'Ń،AbVʀ n,'͛Ο?3KN>=SO=E*//7@ޑPT\b7{1~}%^~Ív5>7K5&4TW]^UB=R;Z=\jUqa:5Φs䏥9--pY k Նv5಴'kԘFb8s,|F'"ߊ(ꓱ&U>9,9tAWeJi(H}s`3P ǯXGy90`IV6ǂq6`/3#kdBV-Wlb d]>c}/0\q⦉>âbuƙi/c#?leC,͘kdCF?:n +> }?ʇ3=8FB5H@1< 2*ujjjnoTLpHUFUCeRAA*WS%Q$3^ խV̤[e|%Ksڵk;-SSSoolGm^رcz4|USS:zh/FYT44oP?ر? *\QU޹6Ea|F>bi $iM,mַEB>\ хōOt#V c8N+g G`ޙ;{o>,vUg ‘w:o8(3dl6qnj+|p 9,؛?$󧛳eYXpe`ҁP=Oe@7).pњSrα p{![J&E1 ȡ! |f~ hK8o-|گ,ƣi2T`Վ? B2nS^PW XpbBT K)sV:[mf nɕXu0 h/*X8E3ޓ1zv}^^WrC>Y(s.ܪ$cCdNTBݷX.ͭRNN3ƍ˶ y"/~XqWW,Gy+~#>y_?p˿UXn8$~}]o3^+(7Bᐋw4VLJbíu_2\'Yd\ඣF>; ra7۰wȅw1,YW@Yς Vʀ:O 81D(tOMy'9Z}}q?t/LSaAuCf~{]q=nMVOSԚLo6mնwt+τYAmʜ89|`Q[٫.cx/X\@Z;ѵ_W %I\pP}#S^ј&,Iy],%''S d =Ƴ(J.o9*㥸K7@ 0   !ʪC+apB Aق&\yQ;TMXS5tx:ç$9O7\UjIP~ř_QNSN~sCEYq t9޿O2 I; 5|Ƹ@wYx`-"xJ87uЬ#UւL?FX )﮲eWV*u z_i7B5'5s#T}6~AF!ڵ ?0d{ w l۶m Jteϸuy$lb^b۱c-{4Ayڵ"`:|[^rdD&{eee8t~gΜi7|Y\bϝ;Q)))"ʂsa"{}C[w=;\ bɒ%@ ʌ"nrTPߺu=^ZZ*O? Y!C:UVV0~tI!B!B%̂Hz K<$,( SD|/b @rpl_OJ)}hz/X@fB9㫭+} @b1DY,d ;kPr"Eu w{nn职v%wSۘt>C-:"6FP?+ב},&3s2*b }ޜ:#cL>a:.dPIs_3N\Я_: o(qyUb(A/(kMUPVYtk_w N krFaoꪋ{BBVMƊ賞\37Dt-(:V}HvW c^~pNfNjBPekɱ?d,!sR_W\+4G:\="9j8ER˶IY? M~sC~2;y_|gO%~QfnL>#އ $rg!t+֑vV*$kAn#c>Oگ4b|yfH.J}h۶駟$dSO:P̅6mr?첉)ҹCfmB8=#8 Ҟʂe)(C59MgO dL[jc>n-[V2(((Ќqcݏ( s=cnǑW# ʌ}ȑ#2]@'⏆ۛ]B!B!B((77ATeg@g0?]t$d~hU+_5uY(BF0{? mCNHz EFLں S͡ kL[wu=.JmMGwFm:NeCHAe;/dB,rcEp|tu5q֊ޯAd)Yʪ142ڷi p*='#!|Q_+nvΝp^+g:rONz#N䡊Xr-졥ǘwX891>VX`W 2<>//Ι*qJ9!zkW _Ge91ٵl 3av}F} srnoׯA{eEkAU 戉&<>̇!B!:k8dryg{ج{lMX\dbΈLdsט!lfQ9Kцcݎ"ı;;:MHJ33째wH-}LXw(EblҒ2{6 F2m= n~ig n}=T#J7 ?qi cv09%9Y A ? %&k~?af#e}d|_ѭ*crۺ8+ER:d^eڡn熞LҌ⌹us6}esB߯~> N79[a\v1 Av3bY?q]ddլ#UւL?FX 2&l~}՜̍P=xz\zՕx3k,ԹWsÇoL2ĝѡC7 Ьe۷ɓ'}2>z+s8OA1,(}O*(O:^v#({爈?Q .69я;~jѮ];6mڠMd(u3(w&//<7 ֊!S2\ 58o^z5~F6eƹqF7 ;R';vlPA׬YAB!B!BA#rs2]!PYMdyD!shrRuŸo9>mb0{op|.JHcB+(3Ԁ,~3UX1!^HM%[oYk@:jRc=óvu.Ƚ]b|32q.--eAIPh۶m8]R2P%3vEE2<<6lps*+.//- ҵ{u?C iѝ~(hOп2#G@II<B!B!BAr+&3?*\< ?CqmdAE[߈=ro /E LH3w)fB*A<Ƅ(Ap7kr z"pTX2Fރk# y(5+PoSEOLV'r@|p2Q^hHY^^z饀u?caeE_ 2222Yױ8u[f…_1r{EyCU7h2ٳ" gW>ˆ#l7og}|G>bȉ}B%(砀C,..F@27N:O>ٿYNqݻwㅅo$ 2e>✚*vnrp ŋ"lCsŻʕ+YB!B!B((SP={&1F 16_6ovjRbֱ#%crCϘޱDCK؟Z3t`twӷkivN~/E LH3bش̢?ޔ̉/d\H{>IjrˌO֙G|4d*5UXuԤHO7Lמ/br<Ͻ]b|_Z%==?2% .Ƞ#p )(CYm͚5~̏?ׂ1.O=Teyf{֭[O> `PҥKퟏ?0'=CY> EP&MX#<$({gd٬rrrLUUϚuʄ ! 8T:t`/_nnܸ!={7 ";HNZZI[Ɛ\ZYDۂ2\8KLĹWѣGڶmkw}}. g %e|п`._gΜǕB!B! aaOsgYLt~dBA^@4BCX.g =0y_;Xc:w v ߆wQB{#{$-LfЄٌ iq2=y_LXE!<ܬkV JM=陳+v FE v j|.{AyuG,Ys"ДX1*22 !؈\ U)qر,)))lpLa<}L=a6, A"r/ձ Tb{h߾CV!66Γ/cR 6DEE0'}KKK={5k!ӧuAvv,Rb]Y/_l̫DDDJWaxx}#S-"L0@̹߳s0!7òaxK] 'NqUϨQyH2|{{uw@rߨ3T0giv/ׯ}ϩ$%%AyY#"8 GB!B! íz0c9V,lSƙLDqwx kmm>s}\G)I~@1fMUvY32tؑHppr@ztCg4 wxW2W6>#$K 뿘,8p݌DDv5>3<ǂ*(:裏:+P9dt-WNmuoOe#>k׮ Ԗd݌Fl&$r2VҥKŋe@1_?'nرñ]3sdbz/v,#p %(^JM?r۶/dV3;w4W\AdnܸgŞs"3͓'OFY. EБ~_y/s {CJB!B!Be ʄB!B!B!{AYcd"ESSSQ@0wFiʐQW"͊8|6q!eA(BFZH]h+H4hdRH\V ;qℙ7o2K| 8y!~91T2()))M6XC.d} gnn9y$Hf6m̍'>>!C41Owd5n#s2;iڵkׯGL2=3|1GҺʷkm`@O׶)w^Oի1 5/%2x8?P4Adn]كly0\PM{ raQDOē 'Q ѿ03?|^0 g0}T*UgW\B㕕s='fOOOtuuw9|!%{{{iOO9]WkI@,Pxe?Ûhff& 677̓H(| J%e2@BGGGYh866f&X:wkk+t"p.1[6??_ }@@ ԮX,Foootvvf'fښP899YTߖl͍G.3,--\`H,,,ge>VWWSX)6loo7ꢯ/vvvںV:ۑe>4.//c?*J uquuDZftC1;; ϫMT4667( _@xxןE;e@Gz@r>V}+Pz޽x^yM~?'_?N'@( Pr郁kv,P~{9nl(ex bxx8Q??y0P=E&P_0xX?&դ U]iH:HH%$h2t4ZHAG-T:p="s P~/E (;Pt .'2e0z#7( P6߿iqrO@#mfnp8b^+v8=?նXns8N5p8|wljѨ컂K\.wLFB!iZ\.ed2J"6L&it:Zst:F!@@M&\x}6X,fьF#)p8t?'z]㱶. CVxs\2 sq~Z0޹zT,f)\ǣ:+J>sw{]+krgeo=PVq8IEGDBՋ ԶKEdyU^OS HRr>u٬Nͯ oVUʼn7;8PZܲ/C b^V" 7 H(E)HAUdi*;BXH_$ϴaf33sԤge'NV(31}<ݳgO1?޵n:jK.u=Jz^}!F6m®@۷w%%%ڧ6U{!> .0cw8PN-͚}0ׇL6(?Dn׮קOʈ=3H^5C gUFU56&UD eU&uWP!ʽ{khSy}UEWo PbŻ(ٳGVVV1c*Z_ߵkz|So߷o*CG5>*رWѣ^~`Up]}PָѵXlLiii!(]׮]ݴi Œ@@9u4o︴zP30~Ӹqc״݇ZjE@sm?#FwISe ( \(7o]vMO!Twep4i63k,}> y߶dɒ㪢gsss#؛7o㿾 "+l*C tǏO۷oQ+QUhSTTP>}qbB @T<~-5u?'/xokҸ ( aUTUbSn]^+_O^u/++WXUu߳ggc\2_ 98qELJ֭j¸ql\:u nϝ;x UPV*Bϝ;WmR5c U$ ӳ]?fÌ߫Yƿ,p2e(ҥKmW S+0Rߟ(+ܗtǏ¨ںuڪO::lذY' Yt}NmW[}Σ*PV>Ynk Z4j4iRZ5ߍ7cw}}c_)..c=+++u%-[t6DP//_viiijѣGu>U Wz3/Ozz ]Npο]ƍN@倀*~ ^x״iSתU+,Dto̘1'61xμ|0Usp@Y*Z5:w@yƍ:”YW͑*16UN8LۺU[XXߪfm|'̴9`ٳg]cVriii.++Kc 'U$sB۲eKT*>}R$~ V9پBD9s?1"!PyS&Ot'F@BeTs߾} )o~%KDP޾}wd},$͚5S?ի̜9S?ڵKTUPYsN?[[*ï\ro- U=״s5P޴i?#GBZS~rrrlm .]޹sǯ[nYUx/Ebzr+Wȗ.]r 4xgAN:}&=!V171}U- w9m}ɓ?VdW_}ۼfggyJKK]˖-\W߯T7)((gV /x,q0cEB \V}42DPV%G I:uӧծ*:\^=׸qc_([IUyT XkYf;w>|6ͳŠVXZ̍fm_ULـrn=W?ke VۢE"9 tU}C@@2<eU,//}>S]t (v֭[-М9s|߲2W~}ªhաk4W˗/WpB_R^jߢYZ1333eSN֤IBx~ 0;mڴ'?sƍ~+f͛Ϟ=ƍ*5xZ]v_.]{T9O5:ıb (Y}vim)P5W+)))!K@@2<t j=x@g|ͧ (kvfDPP?sTrssܚ zĉ/mM6Ǐk*WF6Ϫck߭[j (2r:u* Zj Z)}eUU9se߾}+ *9W["N5uV+c_W_}U_:~U6R޽3kqXu՗r:Hr /Tmذav޼0c%B L@;w|5ϖ-[Ν;I&Rmrrr* r]to}{wUW޽xb@(ӎcU5F[$8q[lΟ?j>/+\W2T{1Ì5JB| ݻW^pu<СCn̘1gϞO>nȑ 0GPNKKsW^UOk֯l-'ZO (BkxW֌>B7oۯ\Ft9޼y޿:*1BAc_IxzKT56leeev^=s «p2׈֕Qet;~۶m_vKu0CS)~ }Œ%K^z;dȐ~1!P& H@9w*إ0`AǨN(SdߡBR Z$N :PW[6*8l/w+ Ϙ1# nu5ky@_~V9 6(뙀fӧO'0-sj EuXUS+Ș__Pc*p"Upp]] ۷oO֯CP=W pQ_0cB L@R=wporssjӦ[|**nrΝsWHՂNA~U*1T_X-Xk׮nʕ]paWbW^^nkU>VkժUT5[D0 Jm<' $ښ|= 6LumutN=o|eyf͚И_Ej.\p;vps-N*|@پAz-eBzz;v[~zƖQU:̘yy(P(z L@2P (ewYqY7QWQAHM/6#*s#喩Q3[I$ 2,-L3+^Kc:m쏛ijJ꜋֠x}@,P( P(_fWF[Kc9ɓAk1-q$r ;wߊslV@ @Y,PCu;mdlP*ZN&٣wݚ c4(+ bmƌc9n~,[TOV͈{!C.Pnس=b^07(<ە/_]T?veb}4^\\<()3uZoe,9r4(G+&% {U;ov\sUy(4ig{o}~3Y0zd좚5sz4};cؠSZ:.j^k~ߔͽv jvevO( P@|9^f?jk&P6鏹]5gs#FOes۶nbc_k@ @9tOin~E\1mJy8M~>QνzEz5*v*lj_S| >~O-Gɹ{U2ftoHS7ܛ\Ko&U\.FÞv8]qXsut\vM}Yry)+oXGmom͟~ʧdQ7[aòUx3_4* g~o8w3=nܗ>k^@ @A7{eq|9Dt6s[fFj:Ѝš좒dF%V̘d!H!-"h]SP(" ~<]|wy9xA !߸2AP؃{\[U~-ƻK/onJOu=d\uX.Dp-"u墣C7{ZΝy6e;wdB|fJPP^Go6[b غՕ cwDClӁW\KǤ=jˁrתdd!2GwNSzZ^~>ƘӰ}6ŵAZ" kF=Ԍ5oc//]9P# RpC츿=jÁr*|t¾wV5Li|@r*sm K|:D`Ϙ;%SwӵmQ7\o>|{z27z憎;]*askWc~irm9PNu1= QK:L̯Չ>mR&'Km^PTA"2sE1Pi"P޸^Nzsn^k2Bp(XBr,NRNOcm9PN-ng4=J{z.]ۤh]:%ƯUzC5U=L P@:&82ʣi@l¸pc\Pcc.o8}[ONƌ!YZ|c?zY9~dOJ/(w߷N:53.'77I]S)l(gp2pHmrspyzoBNGIܜ|&}|:wE̯U ٻ{K<# {@ @9s_#C^ ׿vn8ܯ. \3̙9)Ƈvn@yҘӑHtؒX]Kt-+++|:T*AtNa@QA޾(9_gτxP+rz{]{o]s[g +SL1v6=ݳ5]tI#k YU"{d.֭L&qc mڰWO- # ~{Jk.M>/N޺yM.=zd_@9UXx|ahZ'n.Zo8ѺphuXYHd8@ @MB>0v̨4 ( P( ֓1Nۻ= ( P( / ~:"_7,<'b =eerVU!r}wl =eer/]] 6=zd{Xe@?e2@@,P(( MK\Wpm>@P IjHbZ4PaBAI!mT(M 7B BwE bۥ"] ~Ɣ^6L~repw@@eeٳڢYPPIKKK,--]*w>Yk,20FΫ|xkuVj02Fkkk፯zO^r%=z +++1==###ٙ r>h-7S(!gRP!Ch/}ͻ<+A \S |Y@:P6UuU{Lenn.K[5vL( wIT?w|zzzbjj*c{{;VWWӷ] Ǎ78${O>]<11Qn>'N4* 520FΫWSsüz~Poe|ի|j]޷jNmף/^/_sNoOek=Ω@o偟R@?]Wcll,7;;;Q,8]]]O+kրt_\\u ;_5'O Y@:P6UuUyߦ=۷UKe|gpxU (7uĻ{|ʍPx+ovřb}>,{A .or 3 Q cuH@?sT3?7PP.;z۝Bϝ[(7C=[?ERs|r$___66eff&Bϔ2ʘW‘(c b?3o؛(Wѱcď'O k1|ȑ*}_~Wr1r^Ea XYډ"6b#" x E $9q)!L[|DNfv e&\?pO6^aptTre d2yv4Nɶm:ψe2PZP(zjN!ʶ,# 0r9jZ4itbp,|?6hTaJF3(ubFsމE{rUD]W0rh4(ܵUBl6/. {Z,4 VqCF ]l6z}v#X=W!b1~;)Hȸ ϳ fqkk{8r8yxNףx<4Bi:x,,yW5˽7CU-+F#(w:jۿ"[v4m*L&u}JqWgŶ7~ܐʹACCCCCCCCCCC _e}wƮiEQ'mnj)dA!5P EMd(A !\\\|"tc;_8|p5r=#;=}#>Ū w>ZC>!B!B!P<ǙYZ@|ZO1_5 ArK2/Q\?%$ RD!@-⬱+++r{{;,})}tF47!8 -펯+=!}RGOn?xj+Pv!i\nߗ|>5ٴN9 R(jm6@A X!W!w[5aX'5___֖b^X,|3aW y^LrXo۝6(P&B!PL(P34 ~ nH$"JmwѰEPL:B!B!Bx|"_9yJwaJ\8Ry¹NO6^lj*TJ&K:Q\ەX,6J Ǭt:-dR7uˉ666Fn<1{{{2mԳKs|)W i6zP(L>??S\kh4?]WWpNWDu=TA֕sbՕk]輂iVK)n^pП@ו}ݰ Idhh؊j6Nu(kess nssSd2t4VzqAZ,t'hkl%{:\Mb_\so^o$  P!bQDq.j|Gjn?kʶZ-p8T"c_#}b PΎ@FDw_nwRg/T ( Z "U +AEEA]^R+Qʒ@@ P@yIIňr)ʘz=sgw>u9=ݧ9O>~|Bʷ/X 91aƼQcHk6(/D<1O@^z%ScDR3+ "ovo"DS(gpD8 }-@ HC i;?Q>-Pno!|NW5Y=O? dv2|嗩)SDM%u⥹bŊDy +a*-+ؤG|T VqӦM#-@Ljп=v\۹7r۰@c1cMNgĚYgCb#Q,lAqQ N@,>?łOI.Fx =M>={;oݺ5ݻ+B[er(Džr4V@t'HGO ,P9v9ݦslpu%P}x (oܸ1hx0>n ހ}2BM}z?ĉk l1l]oߞW^п$P۸mXl1c@$O?MnZo~6@*BݶmIi-;-Ӧt[6| 5kQFu PwZ0)O ^.81Lӧ|oXvmSūZq'ن~F^葾GпZ__>G?l5}>K_|ܳi .xK-ذ>e _|kDn+@~iӦO=2;v=#ؤ*UҿB[ePlA9vX'F-س@>{W(Wu"wC}7ti%93пJ)J´1c1c,Pa wO(Xn&Ƙc2W\AzDE!z}7|[Bg=F Ce2 F,0ir1 AIr@ Ҟ_Χ@_1Vr\wuW14iRq%P%aј,4pF{>l0"rM\cv\n]>V4v 6Qnl@P뙐v,MPKܿBdeZyC/ec1c@*lU9 &Tvle~Y26Z."yG5"9e}?kڜhG8f{sЯW D;l\c(~WyJL0rB[59O[RWh,`י58A8i%xtѣcٳpBʢQJ*}: ~R>zBG '5 wIsZ ;wD }l&w݀}2JJ}z?$/Mm2q2i _ ,\or \6ec1cmmW!4hĚ%Q1c 6oLf6w"s1tUæM`H5"utj/rc3(z6/^w'ܹs=۷Sbԩykz]̙U!A<tr5#H8㌪,/VZŚļ!/p9+9_ zuۨ޼ Oz>k rKu:7AEt a*ʤ6XE]&lrHQo+ ~bLȐ&vV|⯔U*8خ>u.b#je"8r{Ve*媾N@9@Y_ ~ac1c1( Gi'@:tPW/#i@ H'eռk֬m@9+#iUq1뫟'C.]J^ڮ˖-#/+r[)8]\2eAp }Ǯt_ov&nPMog΂9߽_ ueDBP{ A>Y^paJoǖnl%B[er~RI3!?z>!~ov7@9^ng3 1c1()y-|>s +KUR"-뮻8*1P:+ J-Ee< i-B\5;vlPDPIKWbc${+'?!оȐyyېWk >žB4Tŭ\¥^")iyY9!*&ۤι*пR[P+VHǎ9R#>Gp)س@>{W*WuQZ}?o엢1c1c=Z]ca3DcLoܸcӰ@yɒ%U#17|Yy'1bZ&e?&'D1^YRe2H^vƌDTmq@Lϟ??7rHlLS P_2ٕ7EtO B~ un`J@Y+X@4uc ԙ}ly~MWʸU[)W_Z'dα{gevCѾa 3o% p7ru(?Y`1c@Ma(|뭷6 m=%f̘Qq?9Ő|g4"Bߦf"i_>Ui[=z45jTx$Zz5ixs :3Xh,6m葨 P3,P(~)~e\e*&vV|쯄*8؎>uf̓c7i8FdfٳpRʂ+PH~ް@c1c1([ܞeD[ohLbΝ^إyn'M`s=d_ODKYDeO=9FЀ@3RGv$E ^^o@y2B_`"ώ<$|0aBycǎ%O\̝;W e:26uT*9[>/MFdO<^.7v'Avp }Ϯt_/8#BFv_M#hKig9}6vf[|a7g jvRNIbp{]aϞ=żyɓ'sXW¸/U[)W_Il2nBpo3\ÞX` _ ,\or \6g31c1(˗/O̙g"%QSl{%xgw[ 9X믧-[DZ 8H!`SHSB~?#ŠAcVWBJmkl{_C:vi~1 ?Y = +A,<e& 1c1cm)PݼSS5(w/rx)׮][5_̦L"D ‮G,Uq_+GRkxyȕ BM NC!(yGs W|v z/E@ؚUWg۵]0GdMv&-`8XOԻ%B[erU(?.qh9rdxgIܿ7Y  rm(f=c1c,Pn}s}IC[n-&MD:P&A@z*C:p¦CUo۶mձj5kD}+Yq!?|p|-d3o6mc޽U]q9W^y%wCBlThA\%ؤ*пR[P̣sA lҭس@v{W*7@Y9yʚM(c1c1e S Lp7ђ$" vL/O> Lh/z#`(ݯ*ȓq:DEꌠɟ~iv xKQ>;I3N\ӎ;XLM+o/y>L~"BATyd:WHK.-8@KD7A6l/c\Dp@~h! BZNr*}ݮt_/rW^]߿(~ٲeDdu*u/P#\e`iv̥EBlo]͛7W19z|$ ;sΌs Vn\>' 6C;l}q]D< "7CfW B;KכKyC.W c1c=sX' aGZgy8| 1g^Mtܺ :T)ݻtPN:~ag~{3gB[8['bcnA'ar>RW/['!\>"ywClRML^u(= s92Y#-PV} lRhmJ_rmyc^g8D%c,՞(P}ΙgI^`V 5ԟ7,P6c1cϩCn%_1=D$+̚5m:cz >`cv0c1cʧl$|C=T<3Ack׮MbeӄwlJ /Q :M|oqtȐ!X=QLH 7@g]9spn>үW/ɤdz}fH#F Mj뉫6ҿR}Nι*пZ[WЖyP%b&س@{֮W)Wu:au]@U}>oXl1c1 -P@ecsdk&(c9`l7 cw61c1cʧ_=AD2DP!\'SaÆ ϟO:@9XdIkIǶnݚfϞWZU_M/{W_S~z3f =1q P;$SO=u\Dwg&~L*EqށV&PV9W[򱂿Wk`ڒ:z霯 \Պ,UU}sgUܪ>R7,P6c1c([l9a+V3gdQl ( X~ec1m;Ywttƍ+f͚Ed4l۶c1c1 OpƜ޽{G}4. S)#!x'O?uc "w\o6}QzG;wܚepAV~Y'uիWk׮bѣGEu!iӦ=$[x1upYKl%QTN*N#$PV&PV9W[򱂿Wk\`;ڒ:y[`6Y =k׫+:Y㬳Rʭ#yec1c1Xl(S!CDwWs>n/ 1ƴ9N/u1c1cʧB*懈GzeCT k;/رckaРA ;ri |Ꝙ>}zhѢ꫈"EN2[tԟ}qVR|N.ι*UҿJ[WЖOo ߊpY,ŞUʕ}sz%o^t7]o|&PmRyް@c1c1([lTcE&f].gX:tec1'7n,=XiW򫮺mc1c1 O!z>#"\ٳgs 4S EYL9]r饗VkdJWXq<<-PeXYY666 iee`TL(- ٟ ylnnPPPݻuP!K K@`PIi(64%C$ 8tEPqADAAϼzAy}=@Y ܊d2ѣ$HK @ ƃҿ&&&e26 P(@+79/_:yg=5tzz|קBw.??翗yF(*//֘8::I#oƝ;w D^&w5( _ٳӹX__/~V7vC\Nz 7'P/ P( *Sv|Է_WkǏ:ghhHio\f鬪󝚚4+++Ϫn I9`^ @ P(_]M7(_200 P.@sss2 ,.X^^͸ƊU(^s>u)93@@,P.[l*}[,/ uapyF(Wiǯ;N,( 3]@@VOi{C cnn.8bgg'իWMUO=͛7c^~LIlnnPSSL&---(|Eiqppۭ<:"j#O9I@܃tNO8;y*f4!J| C uˑbaV93Z\u:d2-_^OqXC!uF by2(r93],,We2B!B!B!B!B!B _ ^E  r:h) @"<Ϸ?V"]o }n:ߤ@9XA|49zQ_ >I$q9 ^rd2[/;ִ0MQv %$KI )t)i89(" "(aN%j* ?ssu$bʿEYϼ{^^^?vF!vk2w+qԍcſwysy:H\!B!@Y!B!B!B!C-(|>@&\__H\vrrnooh4b,(_+biZ:@NPR*.LsD'HlK G7gƭT*r,,s9F΀OOO!Z{'xM9R)Zg2+c.o-Peq2Nrq_뿏6?q!B!Be!B!B!B!(Fw&L%CSxxkeb`<zcz~W9$f3_:g6ظ_9|6sݝ`bCMdZцb`2toZb} X=<X֧JzTEMlL!?򙪱%BBv6VV蚩TʪI+#Z-IM*v|>,(~U[EjGNSR[լoqvŤ:UNL_ ʞ zuٙUjk4JtNFdLJLz}}mr j5lbQ}Ďmjd24WjKӉb0QFbJ%ޝVq>L. SLMP0&Uஸ%G4ZKkAMF *?~NGƛ{s{+/ƜN'no޼oohhȩR}Jt}^hQ4טZ|Y۷1eg>۷5ۿ( l6PI/%#L@q~V8US#ܣ[4р$-۪KիI&}s/^X(eU{6B `0摱Nc`< (筭tw }qe\ն~@ٟZhPd\:;;oCs`(ZNjPP.S#ijjp~VP9k[ni*T鬢Bq}ӦM}+TUU!TIѨ6zMJKK~E׭MѣQj^R---Yo߾aVVdžo0zռ%ITMTU;1`0摱Nc`>(祭jjj³ӡٳl>Jt}^hQnӧO7ךZ|ewYV+++ =&L cU 6ϟnϣ:^2rW}~Prvʻw֋/[ϡṫɓY;wQz<̙3:F6U)e(ʛ c3&}s0V2w\=?BTF#F0ƳP(رca/u澱奏r<ߟ,9r͇~Wbc<}q((w~_rҼ7n*.wWs觸8zU|\?1 (BYBl ;rJ#6lX_s Є{_v`m.}B޼yӽ*n۶-۵tal(HvVFhdžo+B["!C~nz]~]fkgLm Df}BaqСC^@ٟG\0X'15jTN>4?˜ƳPw׮]=UuU~uQߟ,S566?3k,c[<}q((}Fͯ?єjRt+k]QyEs̉C:&P1Uӱq5վѣ(ڟSG{WaZ38Q`Ѵiׯ5zζxpnSSSdɒ83uԨJނwC% 6([n]o߾s޽E8qbj'CN+VH;ѣpmsE9uR&J}sTqqϞ=N[9B>¼Rw̙ʕ+`K 1js㪄'U~LkgLm{N3θrG:IL>ŋu[Z͛7Ο?~#}`;(w/~uQߟ,S5~s?gۿ}}9k/@@@@ʿ4(5 !g[iOa >=VB r :_>|8 N?>)2cƌLr4Tp$ӹ `9~nܸ7¹>U} TK.}ÇZ.\tN[9B>4~ 13PP;74.slgLm*j} StMgCi˖-N:ʙ k9;&=s0V 2g=]Ç1mmmxv¯~BfuCPb3ݵ.>se 󴿵UZo',sxYk/@@@@pPJSˣ?UƕkwvBɫGe}/ul 3(sU݋vc(LR)K\1"Ν;Wfi!( By (1cΪӹ ݁>Ν;q%®tm%ѣQKKKKm_xBJIԦ7oRb˵TպFa06T/ ^ Vvɓ' i+ݻUm;/kUT%Q~йq_ch쟙zD>CBKOw\s#cƤ~&P۪(|ݏI犨ʨ3GпΝS5VBTXǹe9uQߟy-]TE}35GZNjPP.oӦMKMMMlP(HK M|$e((P·AzYl/[(GڥZhYcU QD'E1J0K+P(hT袨Si%d ys:vːgx3}~;e&jV$Iv d2ɢpuz^Zh4Qt&:N:2nZf( 7a0˚4RT*Z[%rLԕqe\(u P@,Pބ~?|(eu>E߯5 2eW83 ?ޛ$^<Ɲ۷@@A('r('sUźFBNe0@ A̠ȠD%T"u8e0@ K}oSTb/>eV;/ʀWT|{~>S۶WT8߭ s\/|J99_^N@eʗs:Ç1Nt/:¬3?>gj\A.ο;?[竷8tGQYگח(g:ʙ(sP6q9 V ~U禍=w% [44-AB+(h0e~''^r׮vugCJ[rGyIhG=?z uΟu~ϝ|aҸݻ9"wn9۶:?狊M4Mצ՛<\3b/t~&{y+(9)y]:ziѠOOlmʹ\ˡL:' d}5s'tΗ̫Aϳ`eG2OvW ~>2H89#P@@ֳ-]2iP_߼+ u ֠`} j0n}mƍ([<'P(ٽ1;;[.-|;:YS/v(C\Kr9eSBO_hxdÜ'ukϘ.mD6m}=;-C;GG@fb;)_O\]- ج(XBظ0^jO;A[nrtv7޲gޫn:YNR?~/^e:' h}"幯'y9xKIw`0ѵo0Wk0n^ yy|z~ ~>2{@N/A%oTP@@R@YoǎR<P+=ų{Թ'˕ic5>$;s*[>6Vs֯8l`wkH [UCHSBSh UR =ד5P6}2zW5y N?g01kPQ7^}?o9vV>2emPEK弼m&%2.pB5vH8rB$I8J }$g-L?ErpráCL'oJSOMU;kd'o>4_1.k)0贈w~Bs~ {4'< oN9#yt8iz\ϭAƒBaMu&}ŝ])}Rw> },7=o?$bmhm z]ROAtA f`߈x ɘ[_ 뽊ɯwZ 5hP1l5hZuٯ=y{r-S (h/ѦubC*. Z񁴽Lr=m&5NsfKhMB:9aqkWa.O{=))Jfr8/o!?4۴)Ǎ3rۡCr͛R߫&_bG ?=dlwwm`;GK`ݹd.mm5 ϳ>;W[P{v 5hP1l5huYA[Zj_*(PVQz)ܥS6WPp308zwtW/ 4ګpjbpHEE7u xFNL ~1F y{m:?qֽI_ Q7h|@Y37Q}pMգG8=qoUװEgc9!%r[{B-r~vvOo, UnV.[{=s.,t?]%}~W԰|NRw\ʺsR"B}}n$>~mR !䩞[k{G j0FlOϕ?95~}O%%5]lq5huAUZA{\gS*(PJm [_ (?63&UsPpڠz.;JE_I Kʏ, (:\8o&s{hzݞ7msܓ/'#=yVչ4} }Ӧ=ʴIAᜡWcp8u!6vAWmʀrqJ}B[zqV֠3oD<&d\~/T {!٢Ep֞קN1Aڏg55`@ & ~>?Je*gM[_-Z\vw#PpC{J8ROsf'tU2pIz/µggOO39ט@XF (0+W径Z+W[9Bǁ}rStn8G '_%<ߠO<=q4mMϟ., 5c'Wȸ:u5MJde )C]0}h(^yžA_wIoNС>̪c=lP7"g}2~l __[w6t}y6X4X_q,ՠjVy:;S@@r#~S<;wjuߪzAF/(hE@ P8ɵFb]W9#ﹻq!O5 ?7Ӡ5P.*%ӽ[D ,j}#GQe[˜|^oPWΐ<{,Eo2pZ|PX~}ø3g`d2G>۫s?ۅw9B{gNי?OFoʲׄv ?tߐg7F0l}y' {aAU8Lm|G6Ԡ j?n רf.58hiڏǩ L@qtd )'XU_ڵS};nܐ@sq?KosWOi#F |5.,_rb7Q@L O޿7ǏӾ[QOO*jNect])vN狋ܾ[ӅsۄiS]Z8n;"푐Ow}A f`߈x[ʹ+_k.'߻A ~<M Ư͸{]6kpV^TP&Gi?s^3/^0?#-E ( FUJiQ (W{EƯoZ_riireV{")mTż ֯[<ePX!b]S;w'7k}JJo֭uWi8`}0Dm׾}Ry?}];9V l}#y'f};vԓaO>w65_lq5hP u٫bctZTP&l23Bh ηm+IB"ҷzKzFJq)!c KxФE@ͮȀW_ߴG%%}/ w_V7%?ϛ6X'gi\պm܉ԽzFB4X& (Mn3wN_w (W`dǎ5}#y6'f}'=;Iz*E ׾<o\ Ԃ{]jpSo2{ϏS@@2e0 (G43˵FBw|b8N,[*m$Ԕ2N!c#>:onkqOXzF5 (/~g#iSkߏ}y=ҟ[,>(?qΝZNCHѭ4"Y(Kp3gOm[rqFO6 1ϳ>?W6mƎv-j0 `}j`} ƍVw_fq*(P& ffC5)/RR"J|YݻIJ\\@yx=XҭT P]߈>}4tpMIH> |&({O40VRv O|M cNP~b\D82z}>7}ϭZ]~sg*ZO{9^Q Q`f+IRSIm֮6RO5_lq5hu٫nr)bTP&L@T;):r$0sӟN>=pA}s{)5tFNd@Kg} '#IwGIOJIl.@@9z}m>ŽWyzk1 W&+I-69z}ݚϻje8rԃq/I{+nװoz4긿PlFƕ?m4Xo Lvή9߾=˜?OÑ6 us7v^oʠoʉ?|'A>oEE\^׶i@YKc}]>l4&7 }`lWd#PCV͜aP7kPqk3n^L &ϏS@@2e0 (=,!JN{]AzS5+m3PV}%hOB.okWa A-Mjv %j`s%W.-Cy34UZ5ۿgr̵#-W;{k\G^Wj9ۀ5KN|_nK]7g}`,w\{m5hP1l5huv;'@G}TfU? z卅h7"^CFy!8KEх4(/B$jRr5L$-)嶧1t`lmO|iz.=ƆL;vg+Yr?~/zPKK9:v֕v?ݾ;ȿwNͯS86St]]]n|v|wt(ϥwس'^tieq,δrSQ\wyk[:|ptYR淬5X}eWz9<޺uoc~}_J@,P((=@Y,P(e2e2I:t(îe( T . |fggqmR 188?#+p.T*tLLLDOOOtvvӧWXe, (WfѦxŨzƗ*̾}f(DP"겎u֭P*ahlltU=ˬ_2 P@\];.Ǟڵ? /?=<#N`׋oߎ񘛛|Ɏ8v/^ԩSK+8UUU(er2uww/UW*NMME[[[Gmmm?~<Ο?Yf&>eʕ(s;+-}(w@"Lh\: \ b@^*c#AQ*H"$8*( ~`uuS]S9T}kSgZ߽_9Q P "Pvg[e &+PvA?aâ3g~J_>|ҡ? @O !B!D3(l2ر#J,۷sM%K8X@Y7B=B!B!BH>p_rX3])(;wf}-Zo#1`ڵhƌM.ٹsg4bĈ_~hŊd܋.\UVV}ɘ5f̘h,B'-ݻwQ"ڌ5*6E-I\ RF{`a=e`}0֭؞-??gc|h֬YĉqEUUUol)SD'O䜘lnmv6lodʸN~EUkKѣG_*Gd:!T7rȑ#Msd eNğd]Q8 e+7ҥ >P$Q7l`+|a.a1 =mٲ%>}z|M UWWYǏeЗ99ĠOst/ 3?;)bA!B!~2w;bEE 6ѪU+ yǰy_SV*|gvǎk6bnݺEk֬ac;xw5{ۢm˖-@c~aϞ= ܫә3gF~,r979G'B!B!B @ys^b'F(rG,N~{GN*L7SdbqvSw ed?©G@ֶO>#͛7S ۯ^oڴ`ҥdbBa%"P~L\8m4+9sxGtG0l"rIeh3 18|̦qV~ ?7\lp@t]w(}9Ge!B!B!Qܪ՝Q9@/=F(ArLW\#=ӣ2oT IN`AZ7n@>і`P|mYU8s Ů(PfҐ2dV_e555.~j*˰?d#&𣈐J`w|^JV.~ ?7lUe[ed_eA߾},@?Y9^2 S|2Cx_@g`NS|2B!:ڵC&PY_Q2(sϝ;G e /\|ٻ.2dH1SN&6l "V\iV̘1#j߾}Ժuvm~*;K,B!B!B WܧX\[3'U:Ѽ~C_ʋ#)k4r*Zvmt%&[ٯZ|8:{,._իo:E\oˎ;ߙhϞ=qC{s [[[JV.~ (d엾o8تd ߗ(Ǡ[-\ιgϞ2 'If}!/ 3?)}B!BqW_}5~G>m^զM KLfTd\GX`^lͼ-By2Jh(rsh1%ȭ!M-.(K,B!B!B(Ϝb,P9(Q/Pk?FԟnJQ٥@lʽ{nR?O=Hܥ-)/,m sig9r]-˵3L-ƚ5knF_ڵ+˾M&P_7 m^}B~JIo+lĠwYF3VBs$P6lj;_' q}!9CӗI B!!P>zh^իW/w1cw2R3lذ,,)Oa }̉]buЁ㊒v]p!6;_݃>;̡l{n>|ŋ{iӊ~?}C7@ \mUQQa;䳒BFKIĆ_Ɯ s2X ƲA>>l*@Y9%!V'vG>gΜ>kBC;w$P{m׶mۛ:lR4~-ȶ3j(9>|8~r'OoNr/}`L6&=ĉъ+ !)'>'|'Q=眮]j!B!B!BպUrX'XeĈ6 y:)vBw 0 E?޽A'O\2exꩧaA &<0rL򹶫=װO>9t=4hPt9 o̯m۲k8& f(۪c}V٣Mg[e9@MS( >ogrt  ϙL!B!&P6!+-[fϞMBR(z6*(^\~} qR9Ϛ5+.@TzʦNJj3֎.gߟc? z5('ym|$sεduFG}ɭi6eį3gΌN:E{iYuO~9hgc|},f7xCvfgl;vhH[7rujKq]O?4|Ȑ!\-B!B!BHy=(ƍA~)Y y\`w΄ vzuiw^EHbk Ӆ,*ޅʕ+69.M4ȣ|7b'gxﳊ֭[,(Q[U5 Ă>c2=]b1Xʠ/ PvvQ}J2$,B!B(3Vʻ',4xwx @`Hd,$6ڳgO4tP6dwe{dcd;=z޽{#fF`D6dv=CEhSYYƘdE<ȮlXvje@{ q+ g;dɒ,"62xr<0N8ua,[ 6pj74w'[эr?bjǎ{7j׮BN6^M%,P&Ȝn#Ȋ&_ l H9oʹǒ11Be˖ &0~/?w֍~lh@?و>cx_@gէ$cA!B!([GiyDLI1@6Bǥ I&Q(3:t`6JwۄX;M^u+muAe&J#r<`yYfBreƓLxOZ*xb[φҩS';#F@LܗLH _%iGǎ3qb.b!I9)7˂An͑Z-c)}c|5udNڊ ԯ[98Yps0^d-B!B!BH0tXܯW 5PhСCk.s wl'|ٯEݻw{eMd|9QPb b-8,b Ik׺gPYfݐHq=/w/H; "pmK&G\ۥe P#_[(;-zy~o*?;o+lg3ꀛǠG>ₑ#GRB˒L>=GSNMb'@`t/>OI B!QLC޿ykݺ5ed"P&S'uP]]] !d5(U26@:2?[ݺ`&몪*ȼDl,{rOzğycXuZ:vVg<'OΝ;dmڴf̘8qO:M|x' 822نlV?rApvN'OG93?;)cA!B!(P޲em*i۶-wu|)\QV1£w: Ac`I0UpgpIr P(Im_Ҳemag.LJ^HZj+*Y#hLSVi$ LW hrM*{ImL+ ǒnIw+=á[)3%j-)~J,;?`fcs9g[7$Pv+v ?)P6G*R!B!B!7Q,Nb#e@2%Oad FCL';& o wV 3+cO`%'m rV+) cޟ,81&gZ86Mڷ힧)NQH=blkM.TB vfde#ѿlyE8bpw H9cg3ؙkt:g %izێFsE/P_Gζ /Pn6q`k( PvXG޶ʞo+gvKuN눏AoZ`0#1VcoYhn=נt(;|y_ !B!DrZ=,>m/;G7(؈9/\??y#~OV|9!ĻC$m'>wRr12 /{ZSq<$iKbZ~!B6dt/)ljVIm'23\?V 󭉅KcOwqwas=||v{^vwљA5wYÉG4_On᮸Xwrv]T}}8qߌ ֦j#U|\w}z{PiYU2̸aL ;j@gk@Y.*( k˗S7UwXΣc[pԨQ>iۯ*/;nzoU॰=Ǐk=0[@vΡg5]}9{7'Hd9NM[[/1tPn@@2eZUB׉[nM@ .^ ֫ )[XɓA-%&MgHUm_&O{/\`-UW@Y;wʲcJu[)8Ḵc\IIuuuZ^l6nleׯ-0HY2?tǏwgΜe rjWZNmױP+ڪuUݲecƌq߷WSM/s֭[~zSSsUA߀^ʴs\SScvʕa˸74_ u*S:tHn?~ϫR37Y L@2eF.]n~]@ ƀǏ![z RrfO% m2 \> XXQ̘1oi(޽Yeرڵk~߿ҥK#o+12>}R#>O6N0!s~m־)`K@YmfmC (GΪ܁3Pׯ_ߟZN3ggϺ߿>iPހqCCU'{.Bؚ +jiAfiYmh6n7zh`#چ2n AabkGfuBZX~޽On@@2e9|6|捞tO @qYi}.SܩS,_\ӽd2z~Ws窺6AaPUq gy6mRFiQ^mJ?~T5|UeWaV^/ Y@Nym% TF0e{j}?ihh\^lk׺yiU5OF^Ν;۷oUVsp(=y;˾}RU6= U}K$nڴi:^~=sq^ 45kV~Rxebm߳g{B U~葍A:o4PυZM׶ZٰaCB2㘣˸oh tvv|ٲe:;n:^:ǂ7}67U L@2eg.-ZHOMa 4g(YF#FwsiYx2穪`pU6 d{;Ұ>.]ʺ>U\p[@>QH)ހQʖӧ=jjj 7ad܍7mYs=^N8>˖&8hhոQlԞ +75ɓ'n[Bf@*KEEy勪 իN;>樯I} U۷oײAC(A s;u(sFJ (Pб*.ZSSe2;ծ]QJU1U=\*.zBY "B]ZGTa#SYY0B\>3wUUqÇ YP6 Va.Uy!g]]{N_Yr׮]72ZZZT!ZowWW]Uׯ~ۯ\jʌEɎ*-_ZZuhPOm_/ݻw8dɒ9r7Uct޽(~Ɗ+4_,s!UXd91G,++s%%%+ jƍC#fP(ѡ1]**>tsqe0@ FD$큜UUUoyy9m?u Dn {gi$ Ȣh#((heaiiF;+366X ! !5ns%S< @MLLD>OEL&#:vソOz;99i[ueee{133SSSi\.J777kQ,L888H[[[Yu"cyy9bnn.{r~~n@AYA߰ggg+>Z\\d2^XYY e/%ƶT6=1;;i>aֺ088z=3166f@AYA)~1:^\]]Kj\.Z[*J㎏oooiޖsv_PHydYy`PP2JRzGGG1::*zjhh(677.ߣlFPۘQcoo/jm*ommŷOkqqTѱC 0̛dF-:xQ>A"C,m O߷kkʋoss3rUJ=w08`c/XHvgko8Z;Ǚ#a] P@XDf{+>^^?qO*,|M{T*Lfn/[<@<hD>>rfs{BN3 #P_WHz92&eʋ(Y]'oߏ[[9Z-]NS0j*P. 9 <$-b4;(߯+${ú@ @ywۇ7Q߉˗6qhz\z l٬4p8_g@ՕxD_o_/vn @!P8 e~w?Q_C?ԢDh14VۦHI$iI$E\Ջ93k<3(>W:@29 \lPdn>O}*˵g ʙLMSvt\hxK bEWv[4u:ZrÁVme92ϑ6I4WIZPph%~],r|ަ8XP>~K )ǖX7M4MQ7~Y>8gb}ɹT*tzܖe B_nt:)LrH+GZ.v9@Kp}BРL"BKPD@dPdyQA$ KTtB:=Y kW^]K!B!B!B!ʅ(+)1r39yw0+eHzڵ 6oWKƀfkڮ鍑@#@s:7oD$0FɄ:uBtRгgO;Gnǎ{ffVQE7i$f2-[x Ү=ӲI>>F~$+.ur_|B!B!$PNuZ_}8 B!B!B!BymP[c?J[UN 7jۃ=\]ode ]z5(++*DCЦ<󠺟Ek֨|rAYpȐ!A>}6MѣGHI)T8L~n /@&Bʐ5.:nܸ~߾}_~9Z8|pvRǨm۶#Wʕ+9 mzM4k֬`ʔ)g#GÆ zM1f̘\384FBR2ڞ zGeϊ'_oOʕ+7ӧO3g ׯ^:u½]v׮] מ=k|V~ N:g! oܸqֺwuW0vȑ#/a3f 4;u_{6mܭnoWQ5j=z9W?8eT֎AYU{o!B!B!@XⷧB7 :"o99<ޣ[gY2ϭY&ww"X6lP<1wz*Zf@ 8S{;_W#GeDusuz1$ˮŮWmw}N+.ur_wiB!B!j@b`?hKea+"le8@9b(9yf[@p+D'ƍ {<{9s/%jx2gm/b~7>Y ڬY3]Eiٲ>BWqM"PB!B!B!rayU0VbgYWʤ*PkrDu;朋~!0 l uKy_ cBVZ,PQX՘ԬS(Q3I9},Yb_;tP[튶l5)CVK. Mo$ 91'ֺa| :FxY!B!J̾I-BtTTT^/ 0@H`,,(Y@5 j 1CMܹs?`c`4C]_O6{lĤCӹsly^fڷmFX|t|\L .je|MlGM&eNfh~f=s k2rvr?S6@^JJJ2}5~xʐŜ1]%gB!B!B K,7#x)dNgN =x#Fp0"D۽{E+3f̘h9\Źhǻwαt鏑@Ǹp"^kZn#G!B;9#{ٲeuz}v ~{tpB~ 8q!P ڕE?|p=%}c'OL&}7ۆֺa|Rx'iB!B!j@!~G-bM6@/Ƶk~ӧOr-@ E-P~2&Pޱcemذ! |Ŝ]^z9,Ķ?i${4hPX˖-&eB2vy@0"DCA>, h9|}aSy(?Fe^\Bرca޶m@cBy1WCro6VUU8sڕWI!r~׷ݳgr zZ6m8QFtJfU1Sh="`[|<+"l/& k^nݺΝ;s5_e&}W29/X@F 'Mk8u3 fyLM8 u#z]/%pe[F_=C6cly~h/Z(z&sg}D(B!B!(OޞoKJ O@6 *D;s up2&g!9o˗/|J[ѓO>I] p'OFEY}T6m<iٳg:u \t)*rzw^ĨqAv' &j"(#ZjW^ጦi׵]uV_;؆?ֺa|]x'@Y!B!(&P6Q… #|F?C^Y\i7DlܸEcEy~&)G^ !yVē,(#xB̊+8 hР>|x[/oi޼9njiӦY&+W)3A,ͺnvM4 m2(Z*Š$s×9h|ӧ8f߳>"B!B!B ӧ{׎rƫeʞ~*,3ɩ G_|E{nۘϒ %Bl2D& ;w\sTMc)=o ?F@=E\Uoܸa]9g i N̍}FRnc]zM;AeΊ ZMG2[De>jW^mٲ%Ү=~xZ6m8>cNXoAOf7l`2~@Γ4˵ZT ?{G?ʙ_4~:8kٞgNlelƹ#G-KwݺuD+e[ c~#>7 ؐ!CXD4 s=| xl!9A|ㄬEs.k6)SXe{'OĎ=Ϯv/P615~֬ 9G |nv=CB!B!B!@9UL|dG3oT23&΋@y3!;w83'|UPvڸؠ9n6m,mwi?/̘12V۷Oʸq3I[|ǎر#{ʕcǎIUp;9q:qAgu HpM# ް'kX2* `v>ܷbB9(@<_vkN7la}#\Z]9ӻYr(ϛ7rY!B!E+PnѢeLKG@R7I,*,"@| 'N Zl"²7{ZWZl~={enРAƺ Pe2Ɩ"Fwe ߬ cͱd6S j{_2e/q"sQ{& x(/)) 7o=XrDlYjmb3Ͼv/PF8nkJ9VX_mڴLdv`ypY !B!B!R^I0VE(@3mBV;}wм# 3O{ MŦ:3gbpz8 a"\8Bd*DӧOG<2u`풥9'lق-lA%K8?8u:: /^|; >Xw5=~x0zhH|XAQ޽{Ψɮꫯq5i"_~=\+VM3WvxaVΝq>Ztd"s<XȎ< ?Xt f*W^^M/ykb[ŋsIuH|ui'N#Yk9o8؆?ֺa|͒Ю@Ӕ3ƏB!BQeDYʈgg`}6(Mݻwǖ z!Jӧ&Lo2!3 $oDe}v;Yds`nӸqcDSS(Y&ÇPmdΙ3 b<~2lb֟N:s6N:{fY,ZcP7~+2Y:~\; ~wo[eq+c揺pcWYe!B!B!@Di`[!BeA:u!P |D4XA͛cAK/Q6 㮁ͻ]Ćd/l,d't !&Qw(~ 2]BҶm&ڵ o' &%f!2UUp9 ;(C÷]~P!B!B!$P. 5 3*ƄboO ͜1(ұ}P 7nlݺ@$m6Q[f#=~Э[qZaٻc "H4tp::::JZr;a*BZixhŗP3$s5BJf_.zIĻsM0Ƌ x4 [u`2{;llDUUX&@]~÷6MCViIJ۶EB 'CFE :%FĬз^du~[G02_n"򢊬՗)ꀹ6t@9˜]5yج%v XrU[,K[8t&W168Fϧ*AD"H$D"H${` b*U-/~Z!SE"H#@_t:]g{~FwCcAzWi%0\(Vނ ^Xhi!6ډ` $ҥMTi^M !d1 -a%3Q̷D-g>(M(Gq'sbqsKl6νjYKf'5T|cyP|vsPN |'̫ %I$I$ɀ+Cp3C97ivq-ׯoH$I$Ii@ΌΝ:NNN aaqzG!҇ 1(* :N -ic[jsP><<0gPfʱhqSBWqQ%u+bQKB؜cN ]Za~Bf>EyP ֨jl|/q}~~f̫i$I$Id@ـ!H:8roooi>>>yUֽ~e@Y$I$Itz{#s& (sS<Qv*iYzrc{F _xMgonnbmllrz勋FٸF ҠP EjitQ;~;=jS988{9F#ߒMyPŮkZfP y51$I$I$P6vs Vw\pժ"C3___q=VU_P$I$IĀJ>g& (s|Q8읿Qc0 2$3FEHLL`W"d0|s{so!7oz99|3<>~8BjB[4M97p85D"ݶۭvt:@lЇʕꘘc' ~LPFedP~[8'Rڟ3@\ܧ r&ue<$CTD͍drFQ9>w 4~=rA>srj>52/([Vٔ8A|I$Xy5OB!B!BA2>Xh4B>?<GJ_BAB!B!B<].|Zn>M\~(rGTV NW p821TM5x* wc\~ǻ-…B!U YKvZaP([d%@6]eq|2vXRᰊb*C`6.(l6lX 9BN Wg $TRe2_䌴Oӻq׫?ϪX,BGnh>a8a!>Nv]nL&us9/_wBkh z^w`PWlNRx.b9?70J@etxg^ˆe:Cbl;fM$8^)!EB҆!!X  Vڊhb#V*]xuao8<_㎓wWpW v\:U9s!P&׺)]չUuvw`0+8~A@Cm6wePBɡc=`] گPR6*8B_,Y'w\>5{@u}SVF[fjc<uVQ疓k\UC>_ [(3]Ϝ]X6 y+al _]Id]I ʾJU:ꡮb͙euLt:6PlsXؿ%Ƞޚ\.Π @j2DUHN4nWU |ï`)ƹf2Cp84uj4|WT zM SV|n{n~޻;HacWӍFC5櫞 (վ=U3NKm:y]MivǼWft:5V$I?9___/ST:˥z0~Fk@=Aګ+r6ǠuyN.k;R)sss㡮b͙2e82vBP`XÕXA III,G2d0OQ&Wgx0( eA٠ `PlP0(`P6(0( AEQD5G_VhdP "=A٠R3U<]mP  ʍX.fy>E͝ػ(0 ai D4$lJqhjJCf|usm\;wr82 P@\сr 8u%@r%7{r}Оk0WO@r%J7S{2 P@\Ɂ ʵ55G<N{q=/P+9PnZ{vyso?{_@r%ʙL&6fS}c!wEC.'!ݝi:;92 P@\rr9v7S/k3e@ @$hl,Ưx0Rz99^O2eٺ/R<4+PLqb!N6@@hgJ ߼|R&Pʇu\n'[G>+PUQ[S C0;] z>Zϝ- 2erULeŵ05 2 P@,PN\1EMv2 P(%Pe@Y P( ee?!P=dA&at-hp"Vr^f^/ r( P(( @@Y ( e@Y P@,P,ID4Qy񈪪"ۼs1MSt]gVkuYe0u]?,γ.xʲm^{m[3q]1y.u6*MӘ9uطզ0P,([!D]?EEMZ(EPt-J 6,1dp!&%/;7_)beeY0kl& P@,P>9P}ay(9!#kjj*r\;zppJ  |4:S.{/Y?֠@@@<(x_>ǵW|b<wUkKqlHO,8<g>yt]acG8Y찟#6n;7V,}7ɂ0DZ75P^c,]_3.(^OR)=( P&PL(2}5ʾ\g2@@@y=eűdӶbhwn0nn!zx2j*~&8~ѐ{EyϠN%J;os=4IJ)5c\.'@ȹQVK9J_p^C}mkD:W\x<&k :FtFLٴ9~~||⸪{BguWjkˇh4t{}? B!B2 :VQBBz 8{{{xB!B!BAy ʛ^;[yww޼ޜEƿ\2($QÐW_B#ok ۻ$,igaxΈ-L1sùY5ƪ_]]Y2cÃ$ ='''#Ƈ!Qhve}n) P>A9.t*p؍ZAy?==jվq~~.v{YVΑS2^Z0kcn6+PhveMke}FQ9j=VuƚL6'Uݻ5:+PsPJe8g~ !B!eiP&РLf^ש !B!B!w>/JEQ_l,"h1´h1 3J$TRJK,Z#rHq'ܵSp#n󾋃#qzBg{w}6 .Pf'8w˼|;.G!k(3Nc)|H;/r6Gk^ .r(!ZTB~Nl`x^Ev YAd˾enJb!fR\:6Tjׇ(Z1%me@_$I> !6|吘;s>X WHjCeņ`eO6D/@s9rm%F>o`ft x^/ m h+I+wvc$'kp񽫁u+yt:H+˿YkfH@Yc?6QEQEQEQEQEQT? #8>_ݿ$]ƱK/]>3w?ߧ_݇H?ة+-@@!el̒ |:$\CDA10FY,IHސscb:v7[BmNq/x\Z@nu44^PD@A@ "UH& `Pc2~u{W݋_֪V].Upr!Cm6l1hg&!?7&$yŋ#/{Ncy{u?npl6G^cc;cڏ X_i2+V|G:jjj2;r<{}g? o4@<(oL0L_f='p4Pbb'96 !B!(ᷳ[?p$-Hmp N &Pn׮]a֚=[n\xv 7tib` ŕB=Xxk!B!B!(_OIS;E]Wxnٹ{tUoܹ޺r70y,e?*mt<2H&u{8y cɽښ@y2j (oѺu똎(''uirA9+,5ec>҆|!B!@Y={z*2@Ye 15k,!xgZ(wݢt'ܹs-w`Jvw566]vM~/͞=;rϋ{u:@gt-^e!B!B!BH,,P;[+EK=zx@-KE64o ;W3(F\̛7oi3f0<@h2ٵk?~TF|F/vjyn̙<-O>'NL MX  E\hL+'{.jժw^?v3~iCB!BH,rfˣ}ƣJ,Tlk8N a#(OZT`]GbߌBZ>OѣG m_KۼyB!B!B!@YqcgE/SU9>[.y ȱ??6ڎ yeA4`<ӐUWWW4"cWxϼ_uNV^P.֯_UUU@mr(ɢ({?/Xb !B!B!$Pn2y.*uy(c4ld 0Z`29qn߾=)SyxF'èGC%&x]<<C 88{„|x9]E:E?r7`M(e(Q(SWٶ,E?|_tqkW9#={#?Hw+Ǽ\(gӏ{&sA-[d(} B!B!ʄP\iߞvc6BUr[r GUk)OJ]GCxtkDa\ 60^%OG)D5V(;Eo[oe59s mt2ktǨ ډ=kK_}UϋΝ;|h'ory?ph4EY zqm[zh6>z+**Vn!PT[`?#7oʈ&αc^(@s瞵Q!B!B!r[(w9jX2F<24NԒ3S&۴i4}0 sZi@__vks<%Cc/󪪪L #Y :*`ejI&ysr)n6SNizPl@9:O(C%PB!B!BH|-;aQdn&,Z1w?+Z~9r팿E[zEGVe6,P^l?-SZTFacǏ˗3O^˜o! 0XǏ3=)*Y۸X~D{jkkiE[)1l 2`󬫸#GDSL駟ON!D Yz)F]9spsyse@ ǸhHh4XT{ P8|GL3(4;Bxʍ`^kO0:3Z̙3y1c~QCCGG qƱ`ԡÇ'ۡy}Q/n2CF'O,]=u =}?@=/({Q169_x.KX+O뙻Ϳ9 >:z(%l[clGڌ/B!(K.HZNvroBZ͢E~xݖ-[xÇKJ_֭޽kLq;MuR7R~ڵ+S]ѣe(Sxw-$1t@(&]q9Z/?{]TcQikjjX˵Zg{^.i4nv=766yZ_~ >r9Te!B!B!ׂ@ݺZӘ6鹢V2j,FNƬQ;y2&4hPj>y>-}!_ ,Z 6o\, /{g8i-i4o­hxl|d]ve(?Ky^cWFh$lkR`P#֕| lCA X/m!H9bj hWw=C@f^󛚚腝iu}?@;/({Q16# I!֕M݁_w}/;6 !B!(ظq#7Ϙ1#Fa$ֈQ6SEPϟ/\g۷kZilZI=-D]i*˨I_ǎͩĕ&P0`;lذR] ӧO]/6mG(X9D|<<_bﳾb .X)6ѭ=g/vX cǎڵk[U gf'P?J,B!B!@:(~}{3R,տ9'ZiaQ "f4A Xń!]K{{?R cΝDfؼ &DW b4'ʥ Q^| ueM#ءCx- 0ʨ+WF]/rf˶O4F`\c=%e룳gZ_@ľԾv@a3g #nz1mٲe !߯G+… %$?~s,aGGm۶6p ĉOҌh'ioߨQi#B!B ctZ6o26qQTŲm\Hz7lؐI\۾HcD?1"sv !U 1!scǎ5G={,QWLH(n;ӧO]sl^XҥKYrϋfĉvb`:D];x$c7#x57:uTp }W$`=yg{{]_y[U ۧE!~=±fyP =kPv*t+6wmiP8 ]qTQtUE'GO+r20t3",ҵ0-e>[}#Δf.JϐΟ't:m\PZ^*z_v(NP ʄB!B! )(x<"+q!BL!B!BA@[nj~fh4#{!!x@TV]Xtr")}h4^b>nHo&IW6CJlY\Pf=! @1<2~6+`0pVE!xbW/==b*i3OVeTUPTֽaGc) ([~ 27qT5 d z%֒_As;Ք^wO*3?mff&Rs۪ٿYFn½C39֨v\sc=*Giwҟ%I$I?KkgqPf_ R ZpBhWJ&Z:"HQqD%p^%!$;y{e@@ oWWWWy3@,P(wtt e2@tJ%JRrf>=ymmͬ( ^@y@Y 5nnnvwwݼ( _Hj5(Zþ8>>8??jZ#ĄG@dXXX0'dNeYd2899؈Bmmmf@Y򶷷Ӏ!@uЂ>yCsz}CCCɫye2(fɼ_7]m@ykk+ hIl7;E.=cccQ*>* ik@鉾@2(U>Kqk6P~VQV4|̘ de@d(@%Ji`Fƍ}N4+i8o&@(#P@ @ğ3iX~#  g( fa>Lqkl&NMM%=r{{[\.Iи{{{qqqZ;;;1??5wfvv6*J޹Eq#@D+Ȅ2&R (DaQ2Hy%u[g9ngp:wסfC,v L&J&Z|ݎnN'ZVP(pBP֨[,Sm6DByrr# &NRUU[z2Z)eٗizU85GUfIpbvژZM76ʺ9l^`0' R`}9V,mZDu`y|Λ@ɄrvVPsC6ޣH8Nijk\,T,yor]83^1|k&@;~zO^l&csϴbCѿdPfsnƸ>vY&ǧq5H ʥRPL&󷾢 /p[Am VxH#qfٴ+ɧ~ 0n47~_(:gUS(M !RT!AAPlD X`aa̙ qůp3ݗq1G(r>s.? L@( rVS[xOX tPUP($Lf㼓;u (}η+jjSmAӵt${wvovѪZ+T34 (bw{{+G (1Ӝߢ (ck])e8,'j:vfj]!Vڧө3G8ﱏqVW%xp8~Wu[CU8q%A ј{W*Ys+gmk>;ș#R*>??xL}1SgZ[wUsZ59͉h5*8kˏq(Pr*ntsD4_|^_ ],[+$/KoT&P+u:*ijZԮ{,\.z{{WϽ^X~~~2dhK+*ƀ֫1G8gUyZAJ,z?퇻(Up.ocm8 gqqv#g>*Zb%X6uw) Mb^5yk~q>R@w\~p /奊0_S$EIЅhEF(BXQE- ZA+ /tw0|}:5jQ]lZp5Ugcm V(9[3G9#CcNwN~r9gc]P;˗;n u.$?۝ɱ2e L@VʲݳR{duu5vK2Ȫ *| wNioox8ѣPשse\s,? L@(PeBYo޼nZ8쾝ORѠR[[ƃk׮PVM;v,.Rb^unj>hx}Pcc@9ljjQL3>^JJu T`:[QCr]XXPo߾ss3ֆܰjelusd_WـrFƻ\p!P׭sE?~,Q{gLE*&$oa@BlTD-K+;;+@ @+}ɞ).dlN଻ٱ\~VU ueө8#>:2 YpOl3\,vqv8mł ĊL̙ZGL9un&%(e|ݮ gK>ۙeޫ/ !B!BHP,B!B!B!r$G{ ދ ʕJfժ鴫jBr y`d9pDB3;o^9cZ(|Wx|QQ =N߫8xv<9T*l6x828t4`ͻx<2x.`3 ǿH^~g~yt=vM& W.}]r9l6!rڇD. &m,JA=7X1U9S3g"SEr5 L&phL痍5V+W,V^ػcE( oc&>Fn,ѢF ""i"*/;@uS8ǀtݷ\(Zeܯe,? L@(߀߿*~珢}=S{C]D0|>WGcQPx><0l6E9}J!)zv xL&̑ΖB:ٱzz]ic("8Gϯtֵa7쵲u6gsd#5[n;Eh,d@9Z{ֻͲ_Y2P&  (J%Z )"ut:QPS*^v;Q糂$ =&QFG}H l^w *o$z[':P@ԕV߅ԝrXhTgwv;f7ʒ\]:+V7HW`7^,ly}\CJAرZ sTk\fdgGQ4Lwt:UGkðoje_lfsd#5էn;EtTtoZS: gWCCͯAR)W FnN L@(H@ wy-AUVy-ŅAa/{w0`8 b!(XX x -E/`%@$yLH*YH+6f'( fI[h49YC[a#aj|,!&m زnWt$q{zsN%P!mؽa?#tzPO%AsciNá%P!mؽa?#rpw>~(x\Fۭ}pgnX=* l \f( P(( ]p"n܁,R2&%%G b3<{o?e@ @Y,Pe2@Y,P( P((\b=㲏vok1ba$N@(;(W+W\<}ər$1w~}ߎyTJE2 PjQӁj۴("R,4$5Yt.ڟXEL݉ō R,x-HD xKLi!Z&! ɜ!ߙd^2YBk>V/^De,֞t_^͹j8~,΍+ 2x\F-j>w/=e@LU*)Z߶"7>J13ϱ4+>=:ʋ!hgcd@y'bG&@y@K}OS\Pu#ş)-ܯ}c5iVML"z'/)6>x&8oɾzj` PCAΆǻ(׫3:Q2 PP_֦e)&'[@פY٣9L[:ٹ0?х6+KKi']]v Z (l"E-"0[vYٌ ItAI_zd|AEgÜg7yy/YER}d~O-}VI" &ɪDuc\ 45f}C8ǎ&_fKA N@y#--x}a#Ǐd߿fJ×eIxnߔ yכ|G6:"! dJN?Nv_♬S6G6E,۵Kv]\3D^v,9sw!=* ߙ-:'w W^9x PN!c= (ob{IفՌ  (u@9} ۶Z>U u珲֤Fnث~M$zt^dQ=!!<":@~l!Ԍ4UsegB{kJO??5cΎ^oޝFDq1!2 Y1䠗dzQ4%D&[L;AqEMEb!E%.Փw(C5KuUwM=|;$TvWϞ( svwVѰ|ClLE,9s.T5DK%ʱk󭆺] zq^AaщâOs&:4$Z&APs*oٶ[utzhn!|gCq__qbJê-ʾ> j$餀g\c㺾ݿ^Mrw'NR9F@e zϞk?MGP>51Y}PWdD^; ךy #oJeX㺮ݿ^Mr=IE޼ls22H}.NeϬne'vؚXX3@Nii$+$'X!kFEj;Rw(~~y8Nc&NO*]XB!B! )PNACm(B!B!B!B(P֟DJ%FK店Յ]"~#G<myyS[eXTY[/-z$5Pz!G .-1BI>7nGϋde'+;:,;n1zVjIJLlx geBuH5#"W;=7I+UA і$&Ma:ci1f"1!6]P FD7ݩ[{~S*xWCsssxa6?? L@9r^^pI/[PRP@@SZ; 4=G ®], YR(֯_΂ 5Rn69d}p֨lZ[_?;w߷g]g^=EAx_!̘9tǏX6 p1Y1xQ:W.YXž< ҿS<,*+u\ kR+mB.W;T~ymf;9<=]q*(PNP2ak,2*# r>tЃ2}XYQx}IIkʿS', m-] (Qy4mtXV)-̗- :#/3]k;׭\ͩ{w,|yIU҉߾&=xC]6ԉP6 l>N_kXsw߷cV6~n]}QHΝ`d&s'!X^nsVǬΕ cֆB#"+u\ kR+mB.W;T~ynB϶=l칿v7ǩ L@9E@׊\͒2 .T +fYYZsEEAFjܸolD/j͘}>ʻwIgOq[  tf6}٦`خ,lf ͥ pvB?1 :=UP`ϓ3?>NccƠ~lm\N^7Z#a Vf'ɞjr; 5I!Pe\ {0fam{,4=R)J^'͕&,+~%C]WY34LvMm ?N*,//l̴lʓqeOʛ6ik7mUVL2kJ@ P6Ճ ,=]׌߽[!ӅA@9\O$|BhS3 % WXmkx}w+?r˲qz/wza jkc2g ߴAN^[6\Ǿyza"; ) 28=Ͼzڝ˜չvaYh{S~N+eM s%YWۅJx*{ޯ>_kٟTT@@ ([B9e|OY}>G@ P^k\T_|]<2|8ݨ>?N=VZG 2g'\F'nZԿpSkmK]p֧j66vlO:==dW_S&{]pܚUV;kOdx.gm4MW9C{5zϞQ;v?AXXhN:W.YXž#< vwүIsIa1 J{_ Peo]/;UP&%aueHP- vCNÆP ޙ{$vRi*|bk(O(4 ,jW *mkr˦4;JA=յޟk& PN^WZٝ?{;qc//.KI>1ԉ끇Og '+cVJۅ1 kCsgNQ:i5)̕4f_io+*r&x'}m[bW^CzJn;#p_N^%]Ϝ[>}+y\&k~.rr0fm]6=Gx L_aJY\ cveRޡϳ)@@2<Հrp5w{}غvqf2Vf]ei ֮S=Ķ⯱{՛w39xOA%m|Mm-x'}e_{kõn[;[u,t)^z)o\3͛kdט~j>ZVzoq˜vaYh{$2~N+eM s%YWەJy*ϯޯ>Ͼkk0/ (S (7j=T^niE'k~]s(8cg-x?ѱʲh3UBl={zmͪl}55 w5 emPfS+fk#9yR6o^}{~+kkr^s~~ WϤ0GYGxr/Ƭϕ cֆB#%u\)kR+eBޮW;T{~~ynU}g~  (S({NT|7F tiSOYZAϷYG 5ݺajg1ܡv2rwMFQd)v)% 1X7Q!:REP "X1X-n<á YG!}s9dɓ;bb+ҍvjR2!g!qyx4L@`MnHobLoFd?VGGa$U#+{ [!qh(đw3*ZQ>83"Y3ժ38\`lŒM{_R=`|=|5:q<Ļs/ =7^fev~=i*)}-٫^:L|NL1sГLj]f1bz3.3ۙy|_oiVxm9/ӏ Gd^B!B!2(ˠ,B!B!B!BAȦBl#v 0{<{'cGecouUN<0l5zYĖ&Z!xϧۦ_.B1Jwx-zZz6ݵo_LLtZEܺ~)!$ z.u0vzPڵf犘DDo38 =a~%ef+'\113Wg({~u ި:˩B!B!BeePBmޥ(A )^U ij KCEYh/KeCiIQAWԕ56(fc-;>U;}Xgm\-յØ筭ɭ \v<1;.ʰu[kŭn5gNmW+\Ww{Ś+jΩxU2+歸*&+jV5Uwh[?o\b~}ZyJϩ( ( (D}@( (o100###144d}}}1<<z{{{crr2Fݽu(0Ƽl.:R8Pɨ[h2%[청ƅծBƛ/DAB + S|CD^(~y(mJb)I^|(9r|zpX~? 22(((@ѡJX3#Ll%222;r~UoxxX/s/y%bƆK.x3Fimnp8,LF=I\ PPXdO >3 P(Ws޻T\V|.T?WR&z@ҭ-(SYu혖VwK:8808|aD"a=W͚Y3 @aeeE"ĹƮwD\7Eب{ ̀GMӵ' +> BZP@@\r}}_ȿA(okߗ|ч|cN[#rkhJN 7 :PkYz&Ѯmf333}GGd2)SSS*`/kY32eI,ޯa P~bttTummM\.2s (_m|*q+(PفrRbq%sޮfݹ/cwʯ7='Oɷɿ]}}f6 n>/u\=/}Y3@̚ , U(Zs&Pf3CeG555瓶읿jAƱ"g@HHm("\mch"D+lD"h!E"o0a80D$H2ŏ澙m49h (a ]!σ=v,k      cPܺH|9Xg{ j\_<>$b];xz}G2BFLst}yLac\ҡt:ihT |"Jl[ʥC.ae5D"dʞ^a8a_s:Š\.C׃l\Jvb^/-&pB,t]S/F# !;{΅BZc0 4L&PV!ΧϿ_]~?掵A}ij sܧI{tZVoX͡ du:8Vu;N4ߴmt:+>/R_q?V#xٗa:1 ZoP }u\V-y|Gɢw?`)ٌX9q @VkW}z]vG}{Ӛ5kzjзo_װo/Wcǎ /XSRRv+ψgK|"y{̜9c`37o̳¼xWø( m/sZ8lAɓ*$3`Tsdii>z( gx94k׮}bNuoia'cy Bz=@ebyU[z;̘1:K/^̝! $N8m4)LWiiip„ \xueP\7SaAGmqoMU:7ȡu˒9mgzth]7>4y|٪϶%|g/# 'NZc2byⲲ2levt钝}ɀ@9izvlH\"B!D r4iIq~ #<'3`/4K- F,سg`Æ |rHвeKYl7s$PB!B!BHܐxN^|>a-& 5m.P V#HR:3<ʧo|_r"IN`Eʈt{yvŻK_ݼZ1T@X ^~[ +qmqq sk :l /!4yz~zQFGG%QqΝ;9@իՕ{4/3Az}OUuV˕ Jqu?>Z*yza7Ybic~{ի%KX ^q*pʅ.]-[*b<Gz-P o$C=hw]HLz,PNqxee%ګ :f;%y{7-z%PB!|+>?~EZ@xF 5;v(ٚyƵI㬄(g.0.9X10rk7C,;K,B!B!B Oc>?] F8z`kOu6ܿTyd>ޯ_?~U7=vX~G.C$,U8}:toDy&|:tຂÃ]vB[W_ ڴi7ϡl<7k±SGN/;)m2ߍ&.4h4ibkmܸfpѾ #$!1c6&.̝?+o }c=$v3"Pk+c:թ;`:xw~! ["}݇m֭[uaǎ7j6+))9jK6M$Ӵܣ*裏j7n6e./ēᝁ ;׬YQ> Oz-//zmxC )B!B!(gM 3t@9=&W$ql,gAgtqKSNS/bQ{LgL0g&jO08aÆ.]ʎ@9|͛7IWṉz3꾃@9Az`f.\`QVqv+Rg}ӛu26'O]HLzP6|lsL Yk#^ͳG#JA,( !B&de箻 ͝;cղ*PL)x,_XD^G8)6s̉Ug JKƎM2Ů9+Z=dI,b۠+e+Ό58ec-^}.c|# Fu֬YlH= g9r$sLE :'Ny`W]t x ; g/p7n؄:řqYP PvsvՅwm L{ס_/IoCۗy9m]xλ.S&gMu(n\$(g_3GL2G,ċ*_zlH,B!(" ݺu͛{ ;@`10LOlTQQ9 ٳg۸Emovпl®z@AafSxE&xW6[wj4 P1;`./rY) ѽL~ <2 dUKX&ø v}ubn`rmN}q6q "#Psfsku/w']jyasCwz*@61Oy)cK. by8q˦@ٿ3V^ux,t]|XPܰ{ 6DS>xe~M?rCc26ǡ({ׅtʤ쐿eV%Z+sɼʤG[LolHH"B!Dze0#7rÇ}&81i$"$:t0IE _۱o#ʈHjb0 jI-^>"6 <8{ \us׮] M9o>JΝ-^GΚ@OK&@mI\Ѷm۠ZG_Յēje HvNngW-,c1}ڍ$yg/9@|1ؚsxڊv^o޼=zdɒj90&K~Y !B!B!$P]O욚L0H}q{n1} :m'Z<"={xeq4HEY`a8mA=ox@۴iK̙)Ps9 "}ʺS *@KuACzsqDxzxM3&{eXp ϓIzr %^PŞ7ez Q2}g Beһ}vЄeVѺw^l^e_`q@nLy8;p][wi˗/VQB!@χ|ޢE  Ҿ?/P6OvҚ0q]WC_͓h y¸ y=Y4s&w^"6\rL`˖-=O8s0Pb;;gy03#4lժU0}OS2͆x4s裏"(;vMT"}~6]ƝdvFlsx v_l"7oro3f͚em J6aL=hҤ boH7g6#e-B!B!BH\͛5Cr߽LXTvi&\k41g s6A6?p0? ڢoecs&}X3,e1..,.g1SNeQr&gts/y>[XήgbQp1,6pbv7o 4\ѣs熻?gЯ_?bqK/g^0_ru>ݻ7f|;O~di/B0;;^&(C0ṞepM /$%5;}k_pߨ`6sHGk.^XTF- kf@EPŠqa]fCX\cm),: 1eM?rCdmO[/P I!CʆSrH]ê(Gu6W';G?(Gn3]sTR!B!B!rK"&.ȔQ\kyGa-thז@oq 6фGBa`1u ᷿mްLt5k|g[vI+3Δ-(xs9+1,x3(s$0u?`!.nw=vy>sL$;#-ˀ@١H88MZʕ?z2%C/ڗ97ȧMޗ99m]@ٽ._&gMu(.U&lȑ ;C~?yzuFd  RB!1;vWR~Ѻukyd(Vm)\gsB £"(]z?GD|“5h>5mجS,ȆY\[^^@&sh]leq)ƍs -7y;Xy.ҥK΀͛"W ﱪ 9=kv#e{عrêݨ'ӧ-εعG} N:_~%jѥB!B!Bew7z[0nς'+_^>?xm`Ƌcg33@D3ޒYЌ+&() 뵊x³Ș!&/]N.mܸwXY{&xskx^:x qf)\[o1yrŖI ǏM}Ǝlݺ 8oދ/"Bcs3"lg5WG؛o)/_zyTwaM[`xszkn={x(d"{8Ă!8TQQQ vBvH{3xJ"ez12 z]e. ي kW[!; 1eM?CemC[#Pv I!CʆZL`'!u[ hҤIвeKգ,zPfaѢEA۶mU VqTrp/Yߌ5(ʲsr;u9YF(;%ݻ׫@ E7mijAY!B!B!rP ՂިL !Dvam.;CUTD2fڜz, &UB!(ղO0drnҤI[ :tP{F-SlF{gè|FeA T\eKyf(eUn]g3ݶm`#BƋ}4leD @bg ͜Aom~`ԣ,C>CaTϞ=n&N KH & ?~<^F1mۘ'<^zo%P.0Fy;L G; TpaZ]ЩS'\ġ_~a9YdIzG ~O<skLzݷo=O? {oՈ@0o޼j/bgy&Mⅆ;&R{2>@\I|ee%mĺgu0`=p٠Rj!M,_ҜW2'?3~9v Dq-[U4&ets…3gFг̙3lDɭuMy })qǫq}ׯ_-Dvʓ9Jo߾=Ϡ' ?18p@N?>s GQ$j7 yzyօjOeٱcǠ]vx܅jՈ[FF> |?um:i潊/ҷB!B!B %PB!B!B!B PȤIBÇe3f (O6I V!B!B!Be B!B!ػ_0A"((h26FM6 6A:<0 y.{a ^O9ys mllDLES0tzoPHh4eϓxm7PPVPPPf;;;NX-˩df̶nvqwwN]YYI3w~~ {q^8;s{{q}}njE#PPL&3)1N|(J6 )_)f@AYAzC8;;ZU ÔÃ=#y2twJ*jii).//#G^hV+r\lnnhJqssrV?`UC n\XXivQ,4((x ^VB [u>Qj?RLrΩQ,ʟ7Pn{$^OXerZ-}sͳ@^r^Y̤} P@山/W͘'=榆'3W [cvYf{+{>< %5LGeʣ(-v6VRX7P'Mw3N'~͍2ˬՃFB%,P<異3i(z\(:?g[)*(3Ώvd/.w!*ar^a&#2 g+`{1J^]Z(Fnan*N7eaP WI@,P惕JV P[vŠ( g5(f3Sf;M!,,,$Y)DBWck1#{]"/Ol$ x :oy}qp p"\;Nzjn3fnG @>Gl6n#:"Y V~EF^SBr:Llؘ8fp8XjT($ b6 .oc> \. c-lRsҷdB0DRQW12s}zNjA}ǙM\Df=?x99S B6CnUu$Ab3~noomJg֮F^-^ɑW.׾ݶYׯ<(Kv>|L!B! =ׯ|peB!B!B!Bֈʟ>~PP2Ic(___ч[uB&9?^*0Np xl8%Z5ʉrbwqq@.P.%eE9y`d21^Z|ZonnT<G-uA,caWMWwg4vZ-tNt:Mv㎿pk///yљjUEQ=9.@`㫹D"ab-`Zf/-r9{٬JӘi *~)-WPG(uu  ch4,Jf0JSӶV WrA/Ggm<'++򜔟|B!{g@EaJ QRM؊v X.Z)XZB§xH9耜CeǙ;pϞ!Bl26+N65}yngeT*Ի]*pJ%]4qi_`Pމ)w}uc(NSXסBy_.C~>gZ9{Q\1o\u>@C|VŅM?NRV7~_ş }|>n2໢],CYo!B!B!A9A" >?AA O4< L0AFճ_/{je)la|~ǴZ6vp8tCz$ux<'-^(#.W $I ]κq}6]ܚc4 73,fԍ01OYbsY}&7yēf`u+"o3nPnZeneiB_ J}㵙g"&Kf*&] !B!Bd=읱nAJFe~;XVZ [TibVȲݹrm H]Nb LV>'{nٛY3HbG߬@ŵ$Pټ"Ot@L@bGg,%q?'@pQ8~`ϐX\nx-U///m~$ؕD+@9w8ΎgB!B!B ?Kq/ϴ3(_,FɉUdWA]@I m#1(3(#\!)m5V]/g2 97q%PqݜVT9(iK8pUA| fӮf\uQ~exzzu*38mϞ1fm WpuuW(<Þ6B v=,B!B &Dߏ9::bp||,H]2X&!iwėf&-Jc?Ć8nI雄y^bRDs ⊻΢luR]||̹=ym_^^*Iш <'A2r/Icm=cB!B!BHFQǷy fY6gid2IV% k4:NOOd'Zx2n$or6n||0MjsCگB\zs r//@ VhZ~4NNY҅:(_rRەߞ=kcpBxtwPcvس&=[|rrmCB3p;hrI\܋( DU& t@&AdjfP Bn?|S}uT۾?v*ߌ7nv3fL\?Xӏʖ -Pܹ3͜99pv߾}V 6}_!CM@zu^{]SS1WWW mϞ=k*vN|bI`9Nɚrm4(7;g -[pM:xǬB!B!B!rOwUgw $Lh‰* vk@>'e1͔)S8@իW[={ւH87(_ 9 Kc.ߑq5Ǐ'[1ob?(5W~u1;|'PNa.^trt;p91;ᓞѵ]! !B!B& [nn>}7S6{e\3' a}ý>,:t(wi&lA`lHӸ~׏0v9s1S*oڸNe?DII2Z󬈰L~TaMZj߿ιp,|/>ymmmTL;Pњ($s7MI,T:sGVx'+m1AEAL˗/6/\?g=cGB!B!B!r&@Ι̇@xUߗ/_ZA!U 8pQo߾ 9s }$"! nog@ʫiڵo"P1alA0]f}R_ s~;G)//^yk\{:L|u-3>;rp  zʕ+(Qeދ_gZ7|BUx,~;5f?;|1S('( !B!BK\p!qbqseCl}CŲ}vԪ$8zٙ ܇_ ̡8B爛 $AK$pfvZ:tsNJ[bVA9K.A/f1'~ׯ_wŠBcx~=HL4>E +&kwP!B!B!$P.@vɼTʈ* 'qw=nd HӜc阿.=*f]7},B!B 3DZ!іeeeфM81kcL4Gnnj2˗֭QAJ{K!1* }J*_^uh@d\\=f#P 1&VUVÇ#JTLUuKomvo<{lu| xv1ec7^ Lt@3fo_9ޯV)scsDĨYbNbmט!B!B!BH(\% zِCT 7/Y6mD[Re@ak I ۵k,yjC^w矓8N@㷕" 2!C9Ko|̸1}S w34&Zug_}Ղ /PXՔw˟=kc S iW yUTT-PNΩt__In_, !B!B <*Җ3Ν<tM\SScV0ňw,챽KC=U1FpobW[ׯG*dxb6ܡCEx2~M% [7[߅([H-7>>ݑkPeMTv_agǘ/bhw$來@Pb$MaʉjǎT'k(TNXwlY !B!B!Mm6CJʃ+ʭuf!P~ PǶ!&MqƍòD @ 68au}_T9 ʮ\2!k,$!p9'O O?4 ^[/~hB48|Afe>(@ЅP4Cel:qdkDQ+Mh"Y`Af͚Tw!D_c#xvݹs߉d0B +w| /Pf-&W<;Ap;FPWu1;|/PNc.^/P1cĉ̙3 ^as0mit__Ixc`Y!B!(gj@+% }Cge<(3 >s75$4[m2K{ne? ҥK^{ڼ_;e`O?4#GThj8%KH~/~޲gϚ}nt}m'ɳVxʼnY~(ovqm0FFU4hP|r\p1Ĥ8،15J,B!B!(3p?K!&e@yVu޽ێO \ܹs'}d„ -@*ZٳT􌻖#,˷` XDcijcwXo+%@ *f.3 $^w.v^dp%C6"ޣG65j<ʻ`@hϝ;9c&-U[QQߘߌ7ƕ+Wh# xQ>|0y2;dc7G~;sk6lN>qySdHR9{1#$f|^1o߾=صk}/ $,+YcvW\HyJ_sa1KjY׿࣏> |4ž;6f?|1~'kA>: UB!B!$P#}ĄPTT~TZa֭[;v T?sm,Q"F@`yHΝjoڰ3{qV20Qu;T8okSNbϬן6m}H@9 ;'ӧ]v% ~޽U̥n~y[% ]7[U<7n\6mscp!B!B!@YeQX֯_O :e!DTĦN! !B!BH|Ν;7o 1cځ4ULPܮӽ{wb7o^ڵkA۶mi@ea6Zu"7ltRĞ&LPs\/w֭gΜiVRRʎ ?:fg͚EsOpxmm-mƂ 1'NhUT/`?f}ݻw/܏1+ r_(m{}'N)1#B!B!B!@Ye\dR'HηnjݻwVBL"6uB]!B!B(iƄZ;g'MT2cN[,"W^&QSSc> >۩S.^؎u)"B!B!B %P T^BL"6uB]!B!B,e\٥Ko߾ԩS3gp,:… 7n3f@.;`1LUMdܛ}pxwy~`,\O?Q4UTT E+VhTT{1a<3\i7|8?~|裏رc0]\\\\|>cYYDD@E9Gg}ǎQ.] ƍX*ݡ7n1@KlгgO|۷oﱳk'b=mIeE^ |WUegT)&Y69$$%T=?Hj[o yܪ;o:nB!B!BH,?E"Po;: XVBl"6uB]!B!Bܺu a}32|pۯ1Q&lZ:"BkU85R6"6heٳgLjv}vޫV;/CZl|Lj(>odI:0bF֖Xo۶ QWW米gIݯW^%%h_WUeW 1bD6wĸ{ƬB!B!Be B!B!B! xPPtPyʕ&'P=qNb ^`/Ġql1Xf5' p6:|3ɠ Ӫ ~咛o&RC~vLWs$̄uXU/"Ӟ\qM?gٷ۟ ө<zRO ejv 7e^Z=|VpM{vl6YcZߢDyLONR~ 5nxwo2z 09uJՙw.]qQ =rX?z8Ν>olzcmq&m(e(~UM6VKqTk(e;9P~l<h/|)Ju=6Ie@ `ߎiw=;H A90(?H`P6( eA A٠ `PlP0(`P6(2eA0(`P6( eADuqOԶ [WMb@)&P|E#AS(]t&ՠքbl ݔ`jE.FĝC67x 7Fi& t-~!33̹=g ( (S2LtvvƑ#G7c. 144|644D{{{:t蕯\.wv ػ@@eef1::MWWײǟ?>ӧ{NNN&{}c^AY-c{?G^͉y~}PMbUUj߾}qƍ(1==vZr9s  (7Ǣ>즇޽,Ţ1 (/HͨҒP~ssswm3jC@YMCQk-fff9[ɓ' @@ (oZ&g~>|/Z6ǩ|g\xY(<3~+utww'7}/Ν;i?~;vАrkN>^W^-~zrK~:~V&y===ˮˍwHJ^ۗёvڵR!&+Çʕ+ӟgggdj3e (744OV[jΝp2汱4~3sg93{g!B!Bʡ('%./YQ8?('&J6|9ϙ3ǭA~㿾=5H(&ٻɑ缽 -ZDqž(;֏^/ Puuu_B2]QL@̙3S6r%B! ! _ B!B!B@y D>JX7 ߋjq7+f #Tnp̲(777Ch>yD^x!}ڶs7'*ٸqz/^(Hw {-|@Y 6Rd``@FGGQ`.0Q215ڛ]?Ͷ^oߖFDw_5߇?~,Ϟ=obCP,IEpk&5e5W]✯Cvc.Wؕ)> B! ]gҥ+3õ[hioo3"ѣGf淋UhWg/sLǗ~v|I!B!B! (~F"%P^rW^R`-P$L6״ʮ6$0*MI/9 #}}R^za+M&RFa O333m]VGSSұtZ<Hdžcexxxϝ;7c26!A!R90 ^|5554Cz @Y7_9z7_!œ@yG Vy;ǐo^|ܰa ڍv`m,xjfM*ʆܮ_C56)}ͽ;?fddA'PVؕ)> B! ]… ^-_oRR~'@ ;w\Yp!jeB!B!B! 픠{w|EY}s)Bv'C7ʖX|cM<|#P޶mlٲEV^m)))RZZ*w9Yi;qMTzrv 4Lkkke߾})x$>>ނ,PCtDvEu5)// "ʗca䟘0QdFTUTTd"HH7oqDD8lE'OJaa! .6 m`,]z6mƉV8[nb(_P4mغbM6o=yy-xaEPes{ll-{nK{]ʲ~>]";anYuG`?[ZZl6kw[[ڵOiwC4F&eynׯZԔ^]c1vwuMv_5vA!B!g x~C<½)_̏#&''O[ٳ{hz2| -hqz!>?Tz/B_b###v'}eB!B!B!PeD^F]5ΣF|8?%%S7qM1D(HSF2rJ`zN/O"])7R]]4cA6ŀA++[/6e P!ҥKN΋My{^EBB N03zӶa4n)DAvcǎV<ۑmv||/ȃDMY}?^UU4BGY]/DexwORYE^,̈ :*#/FeՅ!4>FFqy c֑hD |::"Q!FCPA>#?ȭoVuuyVџ><}o;>s/P$e%֡Clytq$Vʴ/e@^cGU[N:f3c-1}e [¿,zY<ԢLC}Wde?"g.w_K9=iҤFI;~k1'VIKroֆ^x!|:Uu:Μ9~"ϻ+1q}ѣCY{M~6gK+̘1gԨQ%أp!q'NΝ=zTQ9m3,$~ajxի[C4k}hz!\tI*o]SMBj.ܲ%K{jO}VuĨ7x#e@\Q#Yzt˚?ƍiq(+"d: Ǎ&ON:okNBO U&+u믷EY/Vƍ-✧{ # BJWJ"Uk{(qo;C"i;򝄥ygd(hqJ4*F+GwFE BIQXCTv#P/p>NוՋ/GxgeK/P_^zFc]RIK}feM$Mkݯ% Pּi+;?F9bʡ9m$9rܾJj۽{w;3/PN͹l5,Q;e@\A]yn?&7ZԞ]CCC<ڌ_&S [ #Dm$,>td @oUVXA2?^ϱEU$_J>7vԩE]v5.AFʣOE*D+GwFkw>m4;FS|7ސ 6=8\{MSy빢ʳg϶>}|_WW|΢&RhIW@[Owhc.Y*NY >@rI<~_* j; #;wl< [n!ϑz}իW&A5}kغl23裏~38xۡCDϞ=uMzNeM ^bn/&\V5tPGkoO>d;v!lO6l߾}s gM& 2D%޶U`kVudռܙ3gߙ@9Utgsӯg+*&L^;l9Sr% {tf&e3g洘hV(*_lZZLl)N%W>m2/׭['[EQu2=:< Jei#ٮ\[fu/PRZb-x+ʬI *+V`\s9%~.H_fG\~5&>%Mt*c)\em|_WWzEzje˖wY mTy>鱭I;~k1VQjNQ`jֶL+y,r"s'mW_}U 9m(ʗv,arOz@z`  gU}C.k?qѯvr GHBuMv}Ye5c̘1:[97{ . &LDh.;[8o?Ы(wqt*v"^{-̜9SZfl%-j!R e#P(k!"*dCCClSL?ׂ6+:tN+ό2'ko]ZDֻXC_D0tJM(o$(xلڄR6HV{h\>wyg6OAU, > F}o/ Q6Ge.خŋ5fE-Z6noB4=/P)bY´ib_>}i\p_oJהfg߽w]By wBYi>鱭I{~k1Vc[o}M.0MC+ۄ77ozk+槼eg4^.H?6,>\L_V>}dGʣ. i'N(]곱}v]ל>W@\ѣGW^>鱭I{~k1Vh+δQ4˯( PnS:wW;U/b P@Bs-|E/b3ym%˙gС6j}j]V+WW[lid@(W@Y⚫=![V-^_zDCG}T7]PBmi*"&EH\Z46F?;w\8yZ0MMMѣ6+JCXm?~jQhuc((ɰa$HU:QSx:u*^[zDYQz.TYQ-Bk"?H)o6o9ڀ˼n:E-(D#ZDkՋY{?*EMMo#_y7Ш*e;@i7c^}Um8m_u8uoB4=/P޶m"Hu+ _oʞͅ$1lԨQ~eUPyKLtV5*|T[fo|iKׯ( PNza"PzӅ&Pl6Nk^z nE릺֡ ռln*lfEA{!fy:ہ@9zС(!eUUUCL"P)+64T0K.Ÿ6P:gϖ? m>@_tKm[mdlN ;O} P8֯_6z|V+PlBEe҅SHFo߾hO~BuرEm/o z*VV #A=sUO}'Mdխ[7_k=kY[[V}l/e@@r%3zhk\;w.u"'G@HuS__ Pʅ#a8vX,Y8qPr@)?>t%-[=z4[}K#PV}ܹsO>7,j+ ]p~ٸq;u&{.PE߾}=~U<@cGܼ2elhtJrMMM;vl>|8.~l@u%Yz@9AK"K.Ļvj>uuӟMʍ?߿lݺϞ=;oW\Lwoij>(.^8|'N,_t!8pĭFjmUUDvӦM?H_m&m۶бc#;{ǯJ\wqGlٳg#(+o>fL." XjEvl`N2 PNN >:@V"XO'OdŖW_}ኊ$;f̘p]w+VDHŠg1"̙3'CHmF!A}e@͡fmdÆ ~Obĉe{i8 I!Bhn6$]dcCTD(j] E @]h]t!.t!/ "xAܸ ޾˿,"V<1A8xyqCʙxy<{,>}}zzZ@{uD]]ݙtwwG.3_d=Fq@򕘚JOi\ZZj-@Y|(ﷵq2gu388h>6ޥ{%JyZBillY$ϻ^WY0&''cuu5vwwƥN{/mbb"677.//@ܽ{:@YitttArLUUU,jjj"ϕZKsss{WWW]+{{{|l/@%%wk3{49>~X|N>"充xEю; ]shkk\.'B=\^Eի>z]$Oygg'z{{6N 9(_oxӏIP,PNOOOodD+6(sxxR3K,..Z^W>ۿB^( P@\\['(>yΒ΅7<dmr;cccGGGI$P ('9/B|>ۿ@2 Uį,Jht|)5_ѣk"w^|333e@x @J e7iHsG|^Q%%wsއ@!=Ƿ_W%߁r |lmm׿ٻ{T V"  b)DJZV.AlE;BTXAD,_ s-a值3[szT,պKl&M&gvxLR~V'XVt:{n)L^m|>sp1~_w2+`:JW.s~kCF -KjF\`v;wբD"k3Xɶm^ϽUuV xZ JTo6:48 XS:3hve>]J> XEnש5GRrn̂7X@@PF@?<=ҟ;ߕ˳}MSA iq]ѨRR=ϔ\8$t4}h^=B6: ŽCZ|$߶/lYf&k%+k7xfv/<,AS,@@Y^+v9,(qZ,_Kz.̂7XdYӊ8Cf,KH 4,4S! @$$UCV"8.bN |z ;|=; P'J@׏CƢKB' .{xxx2 l6j}MPP>]ݝZuXuvONNiT+QZu j՝|'`` j5_t^F@پΖ}nccCNNx@eg[EkB_:Օ;??wӟk7UΙ_::[~Q.̫ʾFX i:ֺx<AEeLv?777Jng? gh)7\^w777G ...|k< c@@@@9_>oX:]*ĢWҮ rYc5 ZZmmmh7X i\ktrMa,L&Py_kd2aR?\.kJeR߯4o6 *dZU ϾVY`hx_U*eLj?Jng? _b~g7b,((?׀+}Pn!WU#8~7)ZH4A,La*vBE?.+QRADDD{L1=+#˿Ξ9T>2 t]'dћ=]=7:)Pcv '=ڻOj\0es}e!L&W4vZ~\t]>oh6V ,qo}R{9lCeoO@@T{]P.~-kc@@@@U߿}q>O (J%d:NQӉ5%\.u]ȍMӻT*7ΝL&nlXsl!\.SPWBx7)t:j4l6vBg 8糛^w߻v;l}yxycxLc@@@@~rJ?sn'Ao>Gj5JRT>u~\}{ejrc0۵zB!e{}e!fs/a]~nZ* ?{RUqh4v.fCI1"I*K ̒i2/DCb¢EdEtщ d yg{]nZM|/>kz}޷}{!x@Yי{oXPP_}uiܹS!#O۷ozR555h2LtmjjJA|{M4Ė1˖-u6|,**螦F}WUU=ŋݪU܃>+--tӧzIhqb!0Q!Aǽ{* :[*ZM6埈m Ɔ t[+{_xޠw^i 'N(=|07<3# ++?] 5?z@ʕ+ҥKͭXeUVV:c|d)Z4-ѭ]uvv**Ԭr]6oV^֭[vء28p޼yӵZob 6I˾Fm6'e,Yl+{mj'O`UGG^;k.744XXPi7999UٳP=o]=Q؟:={]Rlk]ssZgM69{0T?w:^6n֯_f~67nдvz*ng ~ׯכr###KUԛϬ &ƽ`B?EZPPΧMX[_Z]j@؇gfrRqQ%.EՋm*Tsر )nzSX)r~>Pf_g{_kӠP`k+dXPv?mCm*϶7>w^',Ě/v{k#teY~sZV~{$?_Qo햎c{k<^g~U:: t:M>nس8vuFw!ZPRX,oef|Fwe[.xN3( ( (rUUfc0PPPm4뵡PcQ0 cr1x E`Rb` v0(  e A٠lP  elP0( e e2e2ArQ~\տ6~68yi'raPgnZ_-DudFQ!,$[ - zeZY)R Aw"" pn,u|.?((j|R9Nn&PTx=wW (R\E Tc7({K8_)ŝ[7g )P[!P~7~}|%޾j*P.b%(e΍J%Ef[C9"O֢vJs|}ԝO|@(_PXOSzk)vSlXYN1;bxކ;])S@AgG1{EqBHj 4^B'`DHACT.=gd'^G{;w&C>^ZvD6D(NI욗Ϣ p8k06;?K௦SGc=?3#ky& reeoY,sǝ[HۆEA4(WSv?ujUQjj4vcO&2c Wڶ1vrcϞ6>o.46=]D֣K=v؛׍-;2zoHwݶ]c֫혵U'-M\wk+}">Ǐk>FQ{۪-5:yPx׳~]coߒ:u`M*F 8GyIIƎn޿cG޸j+jYUcx~Ή~]y@@K[̶WK [l͚5m-ڥ x% r5>زƖ3WϗP{Geơ!l޸flVV#?;UU%c4̍vڪƞ:!׉%<|> k?ft]_1׹Ʀׯ5X1sb99ŸYÇikYx~5\^P&-,ڵna,)jXQ{^]! 2"?nO} 4N'; _Ogۍ17~}ݱ %᳧\ߋN}zmlÆuSH솘Xrr-?kvjlvn_nǼifncVܿݕG8d} ^q9촙RNF$Ǔ!ce+sQY?漷\ۭwzw5X1sbpk=MBZzVcu帞_WS&jY #{v͓>+KխYte>#'Jx㏧n!\cq;-xJK`az: ޾凔3Bnγr ;jmmPJo$nR'ӽdN-uX}fhH^vƔ]0t1OEO`Y]v}Э;j>Cիgl׮n*(t?}zR"~垫ƬՏ9HPn]Uz7^c!cEQCx_1޿+)2!t1}3prbM*ֆӻN|b[^& gekYݺnݻu1C cx~\?NeʕK,Y8#O\aU1e>a/$&ciLUR牂aG !.sƎ&4P!R'RS#(Agxi ImЗn^wG|jH* {@N=;s2Ďe V?f}@9;ߺcӧije d^=VآBo-cγ;'uTP:k:ui^@s^wG=yD. .n$Hpvݲkek$%PPʥސ 1/츫>K1h eEprc/KNdgNt((cGxdZ_e :gm( 9CqWW_n QWqm^pB%E1+jUcڀCn˦~!cEկ8x׳~/_{(+֤bm9߿1H?`ͼଡ଼Ih7/OS_w}[r\ϯf59ǩ L@hP?͎:ЖLUEcm\PP~+NzhoP nG; 8`A45BM[R*ݨ(dJ)N7IzB\.ÓzI}y3s>M:TJbs2u 7 1TCi4*=fыqn߰ڟk.  m}ֲZ A*+%YSsifk]u\~:ʻs~^?u< !x@OO:z}q|p#:]N-ſQg=6~w#ׯg=9TP/P@@9,oI:T_h7Xc|@Ǩ CP)!cGmnmegϺ|-1,TS8G;f]3v-5͌3yR1/]VˎktرqΏ=ZւQs_>ÎW]u]册sg~(VW]'8sϑcn;R;j>~=y2e9ube~v/b70CBbSzi?P~=ZF գ%=wZL;GK8[Fe|s('-=wIuPu_?guvIgo9us_%m_{XvnM>_ (uz9.?Ne r АIox{=X,XGߢa}eP~ɸaZ6 uY>Cwt tޑux>Za1 )f^Z?jkZ)c@eIڸ_|!}W'P OgZmk++K8l&Mt7o:9Lt"I{l|UTds~=y2e>#[_՟jCEK. MϕMeZ|ia: O/ˀMq?h;#y Z/;wDm#aD[$,5Ј`kzjyj_cS7NJzW_*T~ޮTRKߥmgCHpC&kXmoΞe[GkvwYm 3۽K+7jOr5yNN0o7ۜ$6F}1盳?gs5<3 {/;O? ({9(~-ؘ;$̜!a8au7o:9seo? ᑇjiH]g=vYk' >b\WgW,Ār>ׯg=9TP&L@?)8RuZ?81 '%(G}RjvI`ky+q y%93@5曒v#Q^nIԾ+>s*.o}}vԨ:`!/S%8fϊjkK@y]GfUn K8z{7{Ӳr_gX{ڦ~=xq2eu̳`Cu[}-[Q۲Ʌ. r |w@+%4Kkӝ^Xh^tmWPk]j;]O¦o$,^;^m +av^yHrm VPiý''$D"fݏT*EXjXLC :´Ge=ݮ |>/m{~@@LcbYcBx7ui6&EXjdҙ ewN P(s CRʑ}S?wogKY,ƺᜤ>̘e3m w7n4)Fx@ 5m;?;A:,H7O8E=jURV1pVR4'(B! _:\.sUO<ׯp8̘ށ2s>A5 e{Z (Z}}zgY0NI+@ рWFS16lN AU#hg'b0E$*"Tt|on^y3 :9Os\5e,t:y\VBI.(YߧrLtZL9ȴD ظX+qZJOӾfZI W?MW ɔti2hDeh-}CG)4ADZĔA(5G1.AM?ުۭ{>>dzO^k5묵wɓ'˗/c0ׯzs%PB!B!rs[_Fj G zv=/̏Qw8Q6N̟\ Y}NU!B!B!@9YrwoԬQJ =1 3qacQC(C'[i҂`Ԣ'$|0|֪kSC!͚UrvXXn˂ b_ &Mda&ݺu,[x1˱Y?~^Uty&P ߓشHH| Ts?_ҽ{k{Z2@_vm\MWWǽ{oۑgeZjl߾[|mU*~yrW8o}g#Gi ɓ'.G2AHE3g,i}Bxbr dJ9iTex' ܹspڵ0U"]zڽ^yn:.Nl۟hѢE(mճgOhcڴboV~Emf^[9p+oQ&69_%PB!BQ oD-K87^p̧<{?kp]U^,XF I90cjk a[[[˻w֭+;K\=9( !B!B!$Pe&>7[ nU+hP^DA)G%.P0~gzw<ٮC(o۶XEx2srxv:׎tt҅*#5yxM>Ss) *.#N19ײ!(ǃ!tgw\($ڶ ={S(Ϙ1r7^r(̟?߼Y֓T~[Y:76@Q6X * rmf^[9+owOyW>v B!BT ߏXC*b@j*PO>ms /M_,8g={^z6m/믃e˖Ih(rYyف#G 6lԩS'xg;*唝%P@Ye!B!B!C 6jxue˜(i҂`щ ƯuyO@vZʃ ,)ׂnٲ%7Ć>|St @y޽%P2d"VŤkٳgsm]xMyǏ@'ڴiW_=oSN!F4/]`O#Hg (S ~󻟋={yRм* MpYx[4H ^-QF {!; C׮]- a .m].[{a$7n$N!2MRZ]v/_nRi -_~e`[r>#i 3ׯ/|mlU4~ͼrWc\tyv8( !B!(@}رcG^0x9<BFpΝvGdKZ,lܸ1Y6l;w!v8sOxw_0p@W}'A۶mQh8 (u]}_MV^׋~5Ύ>[ݛ4rGr>m\6S&c#Gs΍CTϜ>^1V\ɞ$ 8EH)B!B!@9r|Om( ALTE,. 성4oޜ g0Ba0׮]cҽ ݻwwIG,ةEƈa+bʔ)#N@9;qDШQ-Dt~٤Id x`Ucq7>+ Y>"^zQT)B!B!@*N;fDw5We&0G.J)t+WT,6D6oݺ5|_G~Xཛྷ.y-2dd[>}X~8/P~ذa1aR(;7iQ|8".ӧ#bÃ?raegplX#uo`?}ߏ A ixNxE-$mРAgϞcLjs;ȑtxo L ܬYd &0H}nժ\WF+Vo7:jsm |W(w0Ν;O9a9ԝʋW+B!B!(%Pnx=F^ y;}|!ϟ?_Ի3\z,d?Y}GnHaB[,X,aoq  ֥xg4nܸBoH\1v@7z ߖGWʈvb%f{ċǏ|DlQϠq%]@5 ^_'ϟ>pK.!iӦ}V52LH rBFÆ W諶|r,,: dAŝ3gN7b3@|=ؚgxβ%KNX0Ok9'iR!B!B!(ۣ& O8:WNy Ř<ψlmeƍ&@ȍ3W}D,6tpz-Fe{y#^Y&n>"956m/ ؁(2M^¦N=MӪ c_|"EG+7r}NΛ7o&|;/#6V F+D"2mmܸqazmڴlkPW^ ̙3S6X ;W0@^ql浕J߃رcK)Pv@Y!B!$P+P!sQԩck7H=]&e(|~[\\ =2/GXа^zȑ##GdiģiG}Ayx_sHdyϸk V,/̣AN[ wp(=c̘10VskIsڵk#d9wޝR!B!B!r.n,f#L3rVkGqAz#5 :wh6bŝ61U2i^2ȩT&M  ݳgO}vQ™:v@ 5jTa8~x0eNeRq&9Ÿe˖ 1]t O>m ?,mgqM62bĉ&4t*Q†o؇ R׮]Y쪔@]v@VeE!Q{Z}N_ ›8Μm6CV;ez֔ 䰻?ɗQf\;l' ,mIڊIyV&cZS6X o*a o}irymQ<ߛGaL9l1)mN)Pv߃׳;ɠѭ3ɗQs|n[EEc*N&R|СC$ xS6X o* zRi86ʁ^O~ll__aɅң&~!B!IʫW ^I~̷r(R|l>aQu(]Θ?5yfd'ݳQ|v޿JUQ"C,9\Y8eI&yP.B!B!('%PooƟ/ ]{M [O=pljxHKc0/ZxfcԩSvun,6z3gNжm[OI{Yp` 1r+srA38ηuF8J&B8N%]ǎ#f@AeʀGLyL>,P2b?i?-b3ϫ@9>'37ؚt7 y.emxSfqǏؔA`I<ȳmz}/uroVwp>.]Fwql浕w8xV䝱;BM;1r{Pe!B!yʷr.ĸq㈓-gVwrU*#$PY>|81A[!B<47VX0gn@W_R('bg"B5Ļ|k8)aأQFAڵu&UEʧO&N4lذ~a V !~;L?k(ŊoP1u %( {Ť%B!B!B PnFNfg!O>|x*6@6 Q!B!: 9@n۶8` ٰaî[ ڣGKN:qnDY@Llh9qUf_A?{+.^\m@X{]n@D;C!PyWl6Ao.Oì18`BetÄ GSBۡCx'?m is=c 93N,;og,˕~#Ҏ>ED߿ l[fMmB!B!BH,F}a}U(c.]Jċ2mPxDxˬhLdW_B!B e-Y$UVN:wl2āĺfͺ^1 (/-7>0K%PFX4jԨe9r=Ó%©GaO4a4xb„ yƫ (ǎ3 ʉ?{y"º!'Kh8i$aK͛Is!CRZhРAsN?^N/2lٲ:ȼ 7 @Yv;; SrNʕ+N"A#aR Xmőx?xpU{^)cƌo\.PիW̏$m+sy~ux}EoC`_D1b5gkԨOy̙0ヿ6 ^ ~A(n(͚5 ^\bDCÛHL֟ƈ\]\g (lN1p0m۶!qo7?f@LlvDl8` ;ƎwjsB!B!Be D0Bؾ};1D4,> Qկ~e8pgʔ)aǏs2l+46t+G!B!D(s=&~jӦMuѿaٳgsmt֍!v 2ʡ0UVx7D`SH KH_|yҐA9)pb ~h ϝ;7R ;{. 2B8] 2(´~C&Mcʑ;wމaaA޽'||5Noݺꫯ={mc>?"PNoVxٴ8 y}pB0lذGtZh㏕x%07k'N>#TW{ZDM8đπdw!Pὗ/_y, o ˻vs2{삾}"7cBpƻR!B!B!"%Ge$P?$D,&#dcpیDZݼyi߾='Ov("6n\x>UxB`l#B! yYķv[u OWEA|'*6Btٮ];–LfK) PUfĉIʘ3g9{շʲlٲJ L}-NSNaÆBi#P.)̜93_x,| q(w2*=ؓ5 ĕM߿7_[ tͳ:tRL_j*Ύ>J}cC<#W^!b tOτiӦIs{k#B!B!Be B!B!B!* Mt'‚뢃&, 0wl۶ x^+iG4yf7"BPGZD]EѣbRD\0СCԩS, 8w!3ωi a˖-&[V|:yN0!8|0Hz=ztnd:+WC4)ɓ'ƥ;Yڗ.] '-D OVx(-QFA?O?pjOtԩ{FveݱKja`)h+ Epr7]AEpL]Hמ;9ylPRd2ivq;tqݖƾ9t[ 1X|qO989N:Ǎ2PP=|>z>>6jB\.܄Pcfi@AYAyh4BPpS{>d"9|+=>>\.kBet:_%T,ȇ0Xk8<< *W 7PPVPY*ʷ?nL&zrL_V~a:?K[gq얩u+gWꂨX6 -[G:.8ǜJ E+AJQQ  #*|s'5m`ŋOsףy>yyzrN[@ hT"FޖD{^%j8ܯRzPg$O~,/k>!PCz Ύ7M鑬,j . 2;;+PH%vj222Ө֖ڪ}@@u (JEE L\s})$u6_qZ/K ({<),,T? tjÖgff ڙי%PJU+]]]RUU%'װKwwx[+e GVnskkkellLսƘMx}}]TxGGGյn= ƬuuuQP]\\-'l}?)//$c Wq-'|HS%&5՟ʳ_埿z?wo=ʊ y&̸rkk$@xб<|r~ښ a:Pҵ|! $wW/I<ۜe={7Fpj3΂䧎e}oe osC0q_K#OroKԌ:hoÜln=As?;M߹ (N ,x^]D $AA@rR###z_WVV͘1/+*SSS굦塡!)++RfYZZ-('' ((k*XlGWFٟ-V7zS>+/w (P&rm I L@9i΍Fg3`Z  (sNg@@WnWpPK|2~S˲gI~Pϖ˥w0W nŻ5z-_AH75?x ~jT8ׯd5ܷyHl+ (W|d93sn.ɘ9tZ\^'5z3b\Bi4Xp.q.y&B!B28|>OB!B!B!B(}bʟ>~Ō}6cL~@nj82֊@BDjeK(Ccv} rQbL/Cyet矶fwH,XWJ`0FJ~,Swr9^/_AdH5?xse "h4vW|˄9Xn u=ns{g>;ks͏7q?n[K e7Tcc=~c_Vi$ b;p8(>2)¡U-٣^/_m^P|,ơhaU*$#@Q uIiWodRS^'tZ1BxUB(!_d]EQYղ%(F:("v('^uTC6c߇ l'cךּ$ڑΚ2kmZ_@z7 I6Žq֙Y(R_E)#=NRfk^'\u7m{z+U_={k6Ľ%e|~t~.ӣαm lv:}jjSF׆؟L!B!;(;LSx'3WWW!Gqm θqL&`uPVGV>d8b>ִZδ.K2}(BA)%p~T*|!S\D"Z7Z-fFE9==EѕYog۳&mDOm{xfi\.Q́2s'B!B!:(QH𧻓ȍ{糚VFhKtX,vQOQBһ[и)Y@ OØYx5&'N.,~wgf9 7IBܗϟ(R׹َ^V0l0>6䑎 zAx2(J+ڙx b+޿=׆W- H,z)иn[EP1 TܻX,Bp8C JpHGfVxmEia=dNf1" W#e=҉h:Ÿ׃_:^ue ' tr?u?ok5!UG6 EZ al"|~@c1cb 6?X.P@Ą,P@Y xΞVŏۦvPH-6|>P,aFwk1hTZOӍv/(T*^b$Iov,)fdzbG0&Ut[Uջ!,Y{\Ni~c1c1c(s ,N~|Kא-ӓzq^<@^)vH?=!Bbzʚ@y<.1K[wVT]32^҂ Ć/FEA4\f/$lP& 3 "( !gS!+ u{9$ڻjתU{k@aͳ2Xhex*@2bgܥ}M2UGl2^;^췕?'s2 }22H#*[dyfz_5r@l"_?'e%so-ԲdwwN׹w(3A9}q\FYʖY${졌,%PB!Be;t[\W^@ҬY32kM<9AT gǷlg5}GYhbB@4 "gu>#?yeN:en61HK:uT<7`SNQVBd%fݛ:6 }r6Jgε'm5vظΝ;yw~v<{O83xVr} m&z !B!B!$P@9 BUbQqQÆ 2_-P_?5y#E}<)z&Z.'ݻ-g:FӶ»m۶@?{ve(PfbL(-\0J1o@92!5wŋSD7 ŋSػwo\~1+S$e:t(/; y>}/PqѣGW?S-؎q\#?e/Pٸq 3a¹s瀿c6XA-P N '/ϟ/xQʳf-B!B(;<Ղ{@cc}T csݶi&^BP89Q&e!IV^:%flpYz!B!B!(QV-*cAI͚d|\)c2yWO@j_MK.PfA8uu D`"Ӷ;eF+S2`˗/J<o^|KPdɒtL&)Ux9_H[-3g hg :yMz_;_^f;_5@ +V<[robr772?e˖QX] dDp/xQ@Y!B!B 4u L`*ƾ;.e }afdLq\~[.zꩧ؞Ν;[۷q8@͵˵o>mFV~g_5nܘ`iZ{gϞ3>zA:t]]RfdrhӦM:!!Ym2?&n,m5_1hӦMm,>o"ElBw}de,~;gdpfoʠ>\ϟoe^ƍ͛Z(B!B!@Y嘨UMAq2y@:gWE-@ĉe2(D-ED/o&V @y"|#p=X滔S4}v K>fL6&cyZ+zD鎻yfHWAv9L:D;\d,pED:? NXd";֣>s:}'{?t̜9cMʜO<$/A5khGli>Y?'ÿsfuYtI6M9^\b -B!B@'Red{:Xe~?fj8 i @P-rnF޳gϲ(W)>}:͖/_N vvQfTUUY6LK X~m6JAի2G;vdW{ԩq]vb Zn%c>ÇӏDbʕ+ӦM2'`Y B!B!(K̄e,#r>ٍَ1-y:oGDBݻwya"zEx7p@JҥKMdIw`ƍ q)P/"+?~|ڶD"&s$~VliӦLu"E}vۊ쬔6p{}ႅBMf7mf=.?~u\#O~>2d#k*Pϓmsg95xr772?w;Be~:beq=:5*P/$1e}{K,B!e$P- JG :ul2zʂ\h"+Q3 €ȰاOn2l,m8W u6b1ނ1jNO:3B]jJ@;nl=QM o/]T>VW jvKdOe|/}~ʳeľիV6c }ǵ`<k}2@"pi7oۗ 6wZV>kB!B!B{]? ?`BԺe4k$Z8gZܮމu֍LouCl eeܹ6AI֩4u,J(Lj&?>Wx0KyEz>;HLR?JYxQ*ӯoSnmy$ŗ" _*;;찕y巕EѣG.5aYfY 1K7|>~߿~ɻlyebRep 9]c :"ڄ>l#PNPCs DǞ%6B!B" {A7>eyѥK~K/Jfs\6m_&M`('bĶ#G  +~[:U3nI`ɸ^,ɓ-rrK\ f~A}֛8p <.A/q[7ް2Dāsio>^\[cw֭ؗlc91ge;wgK~;jÆ dg|; "#Gd իMZ(B!B!@^(xcp,"8歨qF@Vܼv)cΝ*Qoj-6m{/ZtmXM}@,&ʨMh6wL8x`<Nj,XP -Ld;nʏd$e¢E=v '+RNTUڰ(QYYKhv|Q_e_7)ڵkH(B_ԳDq=O~e7o q"#F,@`!Rtg+دMf i+?de)&mu^#O~>СCw0¢g}``?x!~2mg~ѧ\_ B!B!Bhs2;U/]~+Z9M'׸Xe@i D}oܸ1c& B]O\&Paɓ'dL沕ݺuKsN˰~:nlཾ(Lp<Ew͚5A~Uz2}AbYx3!Rtg+,s!eL>f.b>ؙ lٲŶ1>~߿~xwݻ71!Py1ak_?72?Ey'; B\e}{g(@|.B!BOL^!/5X6f̘Q0#G7]vb" W-_Zß~E;)ҥQ8q"j۶m6dK&&ג9ƅ~gl_+@2>… ϟ2'tٲeVwŖU6 jJ {w>ޟv>vZ7={ dv.eBD[GB!B!BH| t(CLɟ~81|{FG԰AU 8pO""!',| ~|Q>}um b9"D&LΞ=>WGD@v\!"Ȯ:LFDXŋ,n'#87;] ~?_B#q"!D֌gQvO[Жqz -PΝeHçOF`9h%B1 ="E=}*yԻ8J9pl)~wڕ.3ygK~>{{2W ڳ[ne-#Ε9}Ydwb~琱\9YFE(;7! gQ_Il B!E(:СC+S )g܍}id5k ߩ([H~S"rT-F(@tח얔A+2Nbeر9.&![fLͮQZtuUļyZӧ[LNm7o콾z !B!B!$P{ -hGa֭[`хl"b=V6BQC?!B!B@9̙,@yРAMRK]r@L:re?jܸq̶lْyk0 *۷o_e'SUUvA[U_!rlkJgΥI?8=\@[%Yz5m _ݶdk>]/B!B!@Ye Ey(ljLn=Z6Bco߾Br,B!BR2YT](3RsƔǩ@Iqσ>HYVL׽|rԸqc\ɼH6] (PpBm޼yU޽͇ǎKYPVZű{{ݺupMY-M~z;нoيɦ֦M6>zzE.]{D}9?'C *w۷W-ANiKOQ!B!B!((ѿ@tmEQpm-Z(1bY#^wM^Ö-[d+!e / 0 ߿?}p'!B!LܨQ#DBp ʯzM:x8eY7oތ/Ya5b„ vlӢE $P^n]ŋQ&M:u >mO>@6WH;3@9/s+Ĭv w~/ Դ>8]mx'Kg}2@9֜xڴii܂wĉ>kB!B!Be %P̙3m£R6BݻۂL/L^B!ʒ={B̢LG!B!(cgl۶mGÇN8aÆQuFgϞ_z59rdԵkX4WQQ#:sL.2̇c3oV4k֬D;CcCl8ȑ#ڳgO<{u ŇB!B!B %P@Y/}%:.ز(0駟Ɠ.\_NTmޱ GyD"eYe躉9bp5_," d!?( pioi!X76 PF HBҧ)Be.Oޱx97p~v@ytM8+ng z}Ht{cS6'''7a@rq@ f빼ΪlmzwrMtCslBkǭV0Mr.c]8!_aF6pH]mۜ86\ms:V*3[Vkc/?RzxxHwτNMXitz4ר G79==7k6;t|FpwfPyKp. u6N~_=>>Fslz.+EtޙC^#s~k dnL7眗dR}~~V׹&Ԁr{>su0:>>n)þ:$d-6O\ms:S+_||G]w`7=88ʿ&LiXt./( (|@zeeeeeee޽DFf9ds"Q,BK<,%߽b }.{oe@@@ @Y P@,P(ew|f[Lǯ_bci*^=kPs]@Y PLr&p+\rk»Rl,M'@@|J5Ӡx~}vF[^|M;b@(e2@ʩ}qnn]،\6[{ (y~rL (_lh_Lw_棯# @( W5P|P Kź}9rS& @@|7il2{Y2 P(r|n{>e2@YXR44Ife2@Y\Z42<\V P@\F[Ki`:Ŗd.P( P>Z.8Y P@|V>4,[[bd.P( P716vRNfe2zwoעG@@ @4(^YsrX k\6+P};D  ë׮Y.Ibb"&0(0(PURX-{2`P|àk4(0(Š 0( eA٠ `PlP0(`P6(0( AϒPp5R"B%DAC VDQTRSQ#8iC)7.\{3?@,P(ee2 P(e2e2@@*t׹ǽs|@@,P_t*bkᡇ@9.7K鹮s[?ο WrX?WC L&c`@4黗(jh|* 7WSa{3Ώia)VhپʥW쌅嫍|⻞/ٻ{צ(äj@*jVDLKC%F4uPPJPciq+.]] Bicn!nj3I'[&,ۗfvO>%7|go eGUUi6JhZ` ?J%t:>_(z.@@~T*B>Tx8=> xԵ@9Ɉ)((+:jEƇغ(AyQz}go٩N]n e$I(yeX,(!lgvoh4ե+z9#> l,Ql6{LV#R#iJ|f`N( pȔ`#шx(66o[׷rsb\Jaڊ'No;w&.̯ʒܙ:oV'Iqo="xC԰ pg/FP`(GB1B$HB4vA&0}Ep;{>z:}ҁ:Uu;_ʌT9~,erT暲2l2ȵ ,s11P#4pWcׯ_o6lh^zՇ½~ u;o*\ֿ(KKx,5FLgUhԷÇMQQծyi-##\~~6z"qL{}҇a-} vz:AmUzs'm5,+]o^0,uhQޭ'{P[MyNlL{K@ u"??0r-(KAA^SLo:vhڵkg6mtWcWZ绐ccʎ;L|| &'0֭{_U=s7}tױʾؾ}t$ew bb3]:piٲ%~CX@@ 웷~G4Bɵ ,&Lwr&۶mKĀ=| ' f*\zAMŹsƎփni]ܨkf&p2ec%ܢYS3ݱ6`33:ӻwH{tJJJdͮ϶mnz)=;4 G2i]9o5- f+{HssW?nʒ͍ ׭kn~S>}ڂxn,~1ʓ'O6;v0)--֯|IhˮaY&dO>1ڔ[/xٴiIMMsvʕ+Z'ӿvl׮]? >0v^Vkzz5_tI9kfʔ)fڵHSn?cǎi}r4͚4v<0X'P>,oP& <eyw5u%H"\_U 2t9RW)]PPr˗/_(A!ptj999j]ff^G*NQaiƜ:uJ9woj k?#*tQTӵk׈hm0-LCkC\ύPڲ){s#.N OhWl٢y|nTMAT@yժUnCH>s 믿t#GSQQ0okN8.`ŋ|ӧOW(|r 7eS'dxu/1x;?ڪ+-I!d4)=mz=ӑ2eA (B ajWHΫBW`Veߕ+Wھ{յo/n9s3K..k ʞkwzZ&B*pǫƌ=Z(Q (+ݥKOTFwGQ+}DF#~ !gLݦX䵽0 S(:`Q.MH0l Ps#'׿V04|m?|T:˜cZVVA1fkf&++˜]:XP \p}6DjN?WxpIׯ7Ǐ7ӦMsm7nT?4͛}wί`;v۶mU?3]6mΜ9c.\O '*gϚ֮];wrr* (s>VF+!*HF6lϡBYQ[u뚊];db6<7Q(F/PXX܆Q(k]] k^]6uTNrr)**֮]jѦCz~aoٲ$&&y\Y}C!ɀo🿋XIww^R[JV8Y׍x=~of͛CIwazZ |սyLb22pV *\;astNߺÒ%KR.S'}Z;:Ym:صYFCٹQa(+NLֵNPVZ}bNx@ѣ:1^wYs_7m|Wjsv""5͖ۀi-`6*C- (m\^1P\5P)Cd=P>p[Cӵ9dĻܹs ر+))ǾܭjQ'=- Z#+ڀ7ͯ~  G`ldOyƹnޫ/=!u7,60hLצeԟz13$Srn|"=Eߟ` :6{>/G793ݛgd砳RÏ% 弼<.yf85k $tа¹F|Oo֩ɺΟ?6qAٳgM/>HBa~ѺfΜinܸрLӦM^݃{[@YZc&Hm۶uMQpڝ| 6lHٴ.`0PM[ZyӔoeMN|(_gϞ #u!9sH (k,(zR (B@yw2(K^0ƌT Y'ۀiT!ƌ|9^0ќ8,y=xW٧|_6(d*'2def'W:v1'r:)!K@K,4BR\~LsY#uZ駟*+Vi >bĈӗi@[Ïx@gU5H;ۻS#PdW\yu€λ`3j~qqqQ)S/`T0^{@?vj=Ka 2ڏ`ZP...iii{.~޼y7n %?~ܼ{nWƺ j~>進N oB ' skJ@Y#}0ֵ˗/deeyի5N`mz4nРAVP^7j}|Suz/uqmќi}҆MVc}!|j (zLONVHWkVzoNQQ=zmγpB?޵s͡6_*޼ys'yhB?p߉=U}P6yz u?֭kF=dT{r1]kϼtlٲ}V2eڪ[[v^ec-\>u$vrܢA9me (+ \ZZPjTs)&m'vcNuU42gzWXa^v|Ύz@Y222ӧuOש^޻w|(ˏ?h:t`5iD^W9 lWt~ WfUt]PiӦdΝ'|b2V E[YYzܨj׀i"` M&e߹6l5|BCL=r?>U!jԨe[}W1ٿmԂ/^rRSSc>ܵkW)6ɦo߾_~fСܹs!{Ѽ6Xr՚.Q_  8(to9kno (//׶m4ژ5hM#2b+^Lam۶M!d EMϵPsaaz (s2ʵŸq7ny~- 8x`={.\rÇBΪ^vZ}_꾵aENjvp@}~/cǎr.>z'ԫWOsӧ-BZPq\\۳gO3{h){(R'yW'`wŗi}sy &N_*/<8$%%KGӌ͕+WӧZoV۪ULQQBv\iiLkrpƨ͇\m}i-&C 1W6'Ou.\hC@2ek4SRRbu:[V1.y橖A3Q ~:t@bYZPFl#L@2{wpQ3h4uImEE[(&b]buKhA#Y 池!z.Toy0V$MC0'k׮%ͼE < ŃD' +**]+Wgݹs'jjj,䁟%"wj2e@(ŋ#r\<~X4fgJwG*()ɓ'իhmmhۭXH\6}fGnPneV̘M?|wl>ONEmس}K2wx,(Q{O>۷oGoood2ѭ@,j`rn%%%Tk*bWr{r-[arqQh#ͱ S1\ ̘1#._Ęcl6+(0L @99ˢIhww9|g{/vb QTX(PCΞ=/_XbETUUESSSܻwo$R?V,P 礣Pi‚w98-ߎӧ :P^xq<< 1sk߇VVVƣG.] 7{ΚVq|` ͔p,1]LIMM }$APA7/ A~y1O9(zDdy>q( y$?}/.t@ ajyeeeٙpwR)Vve0HX+}KnMh4z.OOOykkk/wwwNlp8VK٬Ϭ=??X,&rYY|>ޞoVG vӑJ"`P-g%H{mZmߟ= RդK>g0 t777oJ{J2;P(H$;Ucihr-y|||ޟF!@@vvvx&c (/b9g*o& M3ЧՙƆyݝ~b^ h v[kxtN:99Ucd:frT*ԺO. `sv:^ `l{@Rw/QfqƚJu7QMZXmIWڢUr,KBj ].ꦛ~UI(( ?@_{qt|؅9<>ߙg>|c]b;k@@K t]n0qfܺ2=e6e#ȆEggg.~թ:\rE&.|(پ>sI ڱ1ɳAeFO8a:ḏcݺמ9sF533A~STTd>~(gJO%$+z%!SmZF3 dyϟr}Ga{_kLbb 6d{=[UU]޻ݻwe_D LzzN^x񢩮^s=sBk%$$^)ٳgzez3$?e/$FI ~܎= l׀>mbZ߿^UBkKX35{DG 9-T9Nov+ ܜ9~yoCC]++{y,...k޽ӵg}~ݿii˞={<$0ms_gx7t 58{Pm!̳gn^N}ᧆ_5MMM}UwwX{- Ne]Ȕ^ڻw=6W^Z]]]{ɤcg}~^oI@NIHfjj2(+\\Cėk̮]Roi0ſ`sF>/4%Bwo{zD' w@Y'zc.4|AM{{y5:===RWͲRSSjrrR;::",Ӣc]YdaVʊk@Zŋ5EEEP(D@P=ݾzk_j%ݷiRO@ (AVuhhHo޼\ u~kcW>33^ ۰W ( OLL)eee;(yFj?YNNePG@;MߋM&ssܹf.57~ 2P֠‚"#}XF?+:885ܲᰓ03>|toKK0;v0IIIȑ#flll:խ[4`G* &K%g9 (}Vk Pw*DYvbVjf bhw9aXz-\_ʙq<`9 PN P~42yj QbL4#}@9tؽ=[7Z+vH @Y,P^6P#NFz0S6p}6=P.˗O&0 fae^uRy@9}[mh4{=~"8|Nw2e@x@9VX`caelME,vQAbM j(:1("})w@~z^0rk*OOO e4ajU\.GZiwwwŠ2u|>x<͍ۛ e{zweh)^Wɤd2s FD5tJYz:zF%&J;~yaH(`޲,e(((8@RƑ?8'(Sr?>>V%عmn[94Pvd-˲cjFG>FtoNfDC @LL e; :}v6f1S`uÃsBƣJEݮ, 3UXCN#tZcAu?m3eל[Iٔb(WJDۭ~hdײs gB Vfz˾4 _) YDqb$DA:J My]gJoT(Z ;NarY8(mq;?euQUUoM4M}}nt溮#22e@8@=@Y,P(e2e2@@@ @Y,PO;PIL?c9O}}%./>Vip'P588OmJbM\C]*~ l{Xv˳7o[>s˚ci D<=o('IO?~`KV|r!@( (WWA' ?{J~zlوoO"\oz>8'PeʙB:Oo_#,6pua=_@Y|.\.jZlmmkkkh4T*3!G^?$It:H4*.&OQv5ȭuϞ(e Fnb* z^h6 S 7jY<!{4sZܳbhe@y>Iuٚ͝ݏRþ6vQRupS(v() hR&2fCd@Ȟ)$K9rL4n|3OY.?( ɬ,pjdn[Y *5e~#(g3P>y"FƟ߿[|czYr7@YrppZ-assSt+PgUgVeOV(F8筭-r f38T4>G\e~v9*ysʏ>+bdk‡7q'ge@(r0$މ0㺫+ѭ@9 f3t:pxxhOV8gr f38U*`T*|? X,9G PV|P,˥㚳o2 P鷗j5 @b?[t~~L&#(er;y8CZ(XI6"*  !E4FpXDQą B Dr@TDDp瀢"8rsmQ]|P_z}ŸϕP:3p-#kuzz*Ѭ/'Mȿ& M^vwwz ( rFF~ hY}@;z?[B(3V?aqq(,,ZFV/#M?,Xכ%1c_~ZQ?P@@9lFFF,rpp 111z,4t=2s d8=)//w'>WWWK__Yޞ}yy)Эw||,CCChdffFvwwZgK~~cZXX6%)))A?w||Ą'K]]Xܜ\\\hVww$''kPҹb}>sXUfukkxss±leuuU{uu%KKKOC>ΙZϳLOO7"66Vt|tz8Xa\΁wrr"cccRPP񩯯Z677-**GGGנr焛VGZZCk=%dTTTTTh;vvvdrrJjj~C{/s5?٦R?q.nsuttT瞞w7ߏ"eq?'# ^@8d|e ><2 ffc|^/\݀E:w``,ocՋs0 s9V+s<ު޽FUq(wDhъZԨ JRiBhZm(mՀ*`F$A>' d ̞=֬$_aU 6o>BԽ GUa?ZSBYkBmku=: ծ^;~͎;XR@YaQFEUUU7n555vL79k/P[RۚƆxO<\2 *Tr2U}2Dzw.s˖- (+8U.{N!U󝝝׬ϥ6hg2[|̓BH5͞=1V]bY櫥p|W&< ٵyf ʪ:f~~*%ܮvmíAXM8QESJOCBG͕ɺu뢯J!E;ڪӻcU=/ZSsdvٴih}/Kv뭷zW7g@yɒ%z,,?oc}?*잓|MEPr%Eq?Ju2(6J>P˅ TOgR*e;G* R_+V]pĈ׬?g YHkʕ:fqQEŐcu7{ե& j􀲴j <7o͚5VTP{s Ǫ*'TּU 63gδ3*+r?Z(p@*>b"|/Kl2[[7tUױg}֎}:J}?*ԞC@ ހ.hذ @@Y!sYO?\Vv9tP9ͳpLEEuP3gNrGm~^xh޽ɓ'S@HU-PvګIM}}M+^z饴 ?>O2%ee59cƌ=wL 7+~Z{w jΝ{X7 eZeYUM6>|YGy$49~Pݻs8XGvd(O=U9!7B9 (ܠ qN-/7PeYtO? Gy|I9wuB-{@;Ay^ɫz}>rH׮[NU95/cu1 f$l3guf߾}Oۜj^ʋhȐ! Zc|)w@~[FX9MG@Y4555 F0}}Oh>emc_oh\Wmoo8qB_>|xG}}}P9~Bʪ@ ?gu &( {erG@,~+̮]t*]Α 3/^Xz ( ;WUB\ƍe Y+~9'N OZ=s̱{ > ,5jov߿` [5hѢݮ2ƍɓ'AwwwTVV?x`5ï}?Gs?({ ?}Q^3lj}фsߏ P(P& _|EW\\~[ao1keÆ K@Y%:0|^=c>s2Ԥ64voOo:1bD&'Md*З{k0Xz*o O?5 (4(pܬY\@yҥVz„ η`֦^!ׂUV;;; >5:xpJnO?n^JBzoC ?[\QoGk5PA)}Jkoyg׌3&kz[Gv:UUUv]M2%e`Io@>;vX  (@ʢPΕ?8WYY`qܲV,;wv ŁrUSp+e}?;k׮mσ~uk:uB7N>=:qD耲UQ֭߭[ŋA衇իW2,ڐ}<-[,Zfe޽[A3⹽[4 {=A_ 6 Z5Ă *nܹsj6Z~}}#͹{hѢEь3ⶫ3ٽCg|kz)W:63̺9k3V1z-9svK`(@Aw9 ۱ej_.Yp!_He eT>/͜93Ч5Dccʂg϶J-v+6RY!ZW>S۟k!5ЭecV87-[<_J k^x햖7|S[\\cfVv֠kb@ٻSsd՝URq+8kAzWcm|~T='eQ/2e€Y`Aե0UX:rH?>5ݫ {O:WlmmgHMFA;vD.\z{{)3fEEESUTEDcǢ8x%7rhʕqٳȫϭy1JeXpС=zT_կZgCC5~6l

g|=q@rmwn$"~d5{]i`\}PK͑"uI[l@^k~cMM( @,P(o@9Pu;[򹾞I||qKEŒw溻P(Dgg(.]Ej%y=u(P8F|>q6Ο6~r)n\_\ kQqݪABLMJ)D$Fk!& .l(n,JօBE…("JqB#Aw)s0L2Ň@=3wܴTeovK{+s3#}'=.j"o%r^YM&ԐEP5JΕ8ٮk96G'wW8D@@Y EIR%I>P(āT {L&hd L@y);o+nlH4{vPd@ܷmF[-kuuB(k\\8ecvP@ʳOˇV8H.KnUX0NB]lJy怆Ә$6x! Qְt$i?>RS得O4xeў3Y$$?yc#Eܩ5zr}6l]0á3xeҍ?{D ^=neKuk(l@jiij90Y͙2)N0r! xÞ$P(WrҶ\z;T`*Ve.kd\H{ eir3{LƯ\֍EZhyO588Z;KHa2#aҒ6Ak TP2Q0QI_5Ƃ "Ox9xҵ>Nyχׯ^biHZ e >c_fgTկyԖ>flmwWv1 2,H/ʜIP&""""""""""{xPt^7Hz]%Ԫ>NN%].z |K`j;_q ‚XpCg5s[u80h (' e<f2t:"IEUPX9lٔ`0 Ӊkfbl6u{@@YrXw>sP6b9C޹IgmV!_kn (zC+Rg_ (٦FVL>ϩ{"I!e9߿W8|N`hJѓ:C7sxVߞs%## e~k<̍FěN|8gIRQFAXL si4A[=- eY]nmnnj6i B )ϫ3wSëkWUaAC{~E@a6 gWCDYVQ2ʃT*ՐqX$J;c(O&؇P%St1HCIFpqp!(!\!m?ιwygyٗuTg2ؐ;E&U]'ȃ=ϯRI5]~WL^GL4#܋'5^vLWEM*s/t0q^/]{ ;nS(x?eGq^$^np$D\%PjխYVnZ͚}-Edvw>k@m(B!B!B!S \L `PnF.u:Z 6'T >?f^ԥ>+fj\{2hł@}0cD,"t:i]"[߷l6UԤ"E Wb/˦B(J?BL.e@ `*LNfY&9B> E\݆04hd7MM{Vv[[OHq[JuJ(:5ݚ Kr*s46!jnMBH( ƢCt](jAt\RY݊:?<Fs9lcKVMNP+ωn6i{R*\"Jrx<ݺdL qvPoO1'-SuttD'i}ZחjY^9Co^?r(H^3E3k. K| ͊2@aH$j,Ja~Z0c 72 D\@pSM q)Tfh1@LlcVFC!܈*C_m8] ,~0=sgvf4M3jX$q"FfIU^V'6,(__T*Zl+de2cj_GQ5n/q=\*jfs3gj}x$}?jv)P٤``0ܪB$_"@@yvOr(kuι\"lj/qY(?(pJP쯃#?vk-;yww>veҗ1Kֻ7B!zfVНF- |>?nRp8l0M (}stX,찠|} xnZ(+ɄbY-jo%}v~Z# '_"@@yv+3]^ӭ7)ys1m)}?IF- (A%ivou3,c^_<8O`Ϣa1;ieFAt$PZ$=hfA[I$V ( PӜNyz8cp۟/}Ƀ9Y;og K^ֻ?cWukkiЮoU?k\ŷnZZ[O{/zպ/>lV.]t577E\]MUժfM'  (xYY{lb l uٯC9.̑׬P?m5PfbsCCt^gرc5-eaGG;v;wŋ-sc+Is/]uD"3e HG@ec`r\1STƨ1~̄ 1b T?}t8‚lY^^P}}QB@MBƖ=ioڵKX^^222sιdyeeKMM=~ KWg Ν;:wF~oc! ]g;˾ ~-[k4F@@(P& ?N8z)))s/]c^zF0F).cԬY5/$u81+++찠9k|rk2265IC}cK]QQb uZšRw9~ek}('O=jzݻW7i$ g={V` *s...UWWիiOlmnn-!!Ak|_qa]˗/s27^߻wOݫs'5ŋݨQšw]C h7rAyϞ=2e()0$䜺qvȐ\Okq̙2>P||kii:Sw͛/Pek׮5>}C^W?KGe ڟ΢ݾ;miiѵ 6G˚cP6Ic}Ia I|]uN/)))[šrN叙~@YmݺU ;F݄ϟ?jjj}!Կ*++ti:oܸq#WWD"}n%[wɓ':ĭ[2P&L@//G J(KHБgXοׯ_}(C @@2e (TEEEpӦM 4٩{1++g;1kZQaDI(v*nZ)E vVK1CJ;hV:;O89utW ]23<qre2 P(t:hz=*JJj1O/N&uXקs8e2 P(S6nw o$I gpZE2;( @Y|n|>AH4ml61͢nG.3+΢X,F\L&cv P( n;ܻry|GVwTVdXD'fMq?uy.?LJY]\}˸w@Y׋$Iļ~Fa~<sE*lh8Qs$X c9&0l QGhF!ctd1O7~ҹщu] ߋOJy޶nxow)iii:D}__\\L ?/|*{PߑzeͺR6WقPMM !p+//IeIii)BTT8 I ATrbM~k'8eř&{үxse!cngk.=1Ab (i9{,!p#]P[ HM 8 ;v %P^Yږ%Y[UŲjU92{ v]>@r+DovA}nsΙ͞8qBddd]]]l, bbb$33SRFzĻ5P^~^ʆu ߳XYuqe PV.\Y&B^ē'Otc{/^ZVZ~ww;˒*IIIryBEh3⿍ /<6@hX,irKK|1"eee;@9f]0X}@ (kHJ0a(x}O@?.333hp2^Zrss55-Pnkk x=)}v)**G(;wTettXi<+N"D@Y;vL={raaÐQ5>zȄ&/HKDYd&P8hԴ@Dz]Pk vEs\S{SPP@崔29 ƭktlQ (9ml"F@Y{ @ʕ+#CǃZ;22MXR.^(6͔]#У*nFMɓ'ɯimm J zd#0./*;ds&en ͮ;( Psr=%H@kzr`Az$''(VX@@@9Tu^o-/K稀㺮?@rqTTT%Aw@yJ&SRR+n C%PN'Zkqn|g8gߗ{:e@}}.]$66*.:Ϙ{255%?ksllL? n۸c[Ç!߾}[N>T\WWo߽{W\.7MSSܹsǴ@YG~kt[ٵkV?͛ߛϙcџ @92.BFHh|0=ɿF6{Tuq|'"yAM_K;XGF8濫azdЯ׸xE$%RѰ JR_Pl&uk9afb?z]7|p\S]]m~g֦ _Z]ê޵k9֦zYss9zIKK |ݳĮjLugϚ۷kƹs>ۛ՚޻}{Y\~zSYYi>z| (# ]Q*Ι8qb({ZsEt.Ѻ U@/tW\};ɓŋ~<|PaM6ܸq#X8,={|͛7oָ(|rqq}Gًmxeprsƿ[51޲7P@@PT!L ,Z(رvz↢"gۧ]H9;;5G eYkwzZ$B*pUV=j,Yb~7m?Q Z}F ({߿_DsjG;j$&&fҵ?}t]G; Əh/zL1}&xxL 7j(C]@ᄈ퍍a (codddZVVٳgcŊv#{ώwMffydf}敗m۶;ߛ{>ɓC ש/555YlIOO7uuuniܹnN;:qmZ=mjeƌfZ{!rZ $ޒe& 5G1P~:Z|W! FΝ;ms֭ޟ+`ƌPhToUXX܆a(k]] k^]ԶaÆN{̙ZOH}}ѦL:5dq'N0IIIjwz-@C'|YCP/#\X"wenvOlwϩP~r ]zfq#L!eB{[ς]kwsxFj)Ǻ g>GuP֮:1Y:AYkt tʗ.]҉7ѣGݜcǎ9svڢEQ(7Q8x*eP^^].{q8ݲe $ˑа¹/n߾m_iiujF :^w .嚚zK,QPTTd~g̘a޺{Z@YvҥK&HGu(8PhNczo&LrZZuL0[D*3m|o;v455iH`ZOs#{l2j>s5|i;~;&33ScDa`!\.SA :ӑVڼ8fP@@ ݻwZ'?>=ǩS4&b~U4B~u] Ԯ@vkQ0/|k(ꀲ  E뵅2<3dP9Æ ]>jiߺc7z@Ycӽ(t]~믿h4(,B"7o43gT (f/,n XQrRRRʗ.]gN@;K>U@Y^zo;ۆwMk3̼0C2Oƍm!B[[ Y:tHIE͝;w(s/w6]y@… gU(PC 8pu€~]0XEcbbM@Y~LN{LRH躸8kE2J]XD۷O'*kL (O8\~]b3}t3i$ͯsDeu)΍7LjjjHʗ/_Xgi/rG@eD{]f#-Z{B&;;ۆ^j>#+}@B')NMu@Y'jx]!ޓDZP ZA/t5]yW_}y{檨GfϞku||DNz Q (3c7+9 ]k^yH՚&כ9s1 (+@+?YjO@9o|gn-$''rJ޿ڎ=|;2e(dNQL?Odm)*Vh?…Uu1^00{=?IٝF-a (뺥$%%=)Qa1+w`ihhܽ{'snX5ka7 Qxx߫Xq]Nh-_{PwN\@G֨&)))(;Zњ[RRkeWXTj ( ǎө:%8,WhX o@ݮW!vsJV𲨈M:uJma (]i&]zz9q℞3=_޺pϝ;E<,yWmuuFcE'0O;wUV 麠pBB-69s挵k.gŹ?PQ1_bkh1skJ(fڋҟKO_9%>4%;g~ג`rY8wo?QSSZ[[۷ՌB___~~zr/g׮]Y!ḭ-ܺu+~~^[ &cJɯ1/N7^v-ދwl۶-:u*oq>Irǎ dP__ݻq%c1I,`=z42|' WVV*.(//2M@Y@Y@P.ϟf\߿?<|PhxpYYYRp̙;,X@QReȑ#axx8x8;G߿p k c0̅G5c]>ܽ{59(_t)z400 <?YYiغi]·͍_/^6}9QgoePpBZ9;wLBK.͵oa+WmժUիWI޽{P@@9墢iMcP2{PΌw!sշ!?-ϱoa+WQ4}f(1X״0lQ(WP~-HWQW7uKfJWf^ (WUU+,_f|1ÏsV y|\j~눿7#6u:ulxLve*JB{1L27^O0Z)0'Z3יg^;   Vw|_ 뇟d,']ayzd,ڋ[?ZŢ#:|"-}CO `%FzO>xv \.Q{^  W  @Ys.P{l7gjη{326xcPid Rp8@c)[`g?DfYVH$8nxK\ټzsxh^SMmc:fNq:d@g8F &qq+ҤB$P_M+ >Y1>B0En"7}κg~\{?Rcy2_g`P>7e{T j͡#P]]xa"[X")PLjDoBPKϣ̟R_W\Թsr_Aܜe>D{9 F/_nEGZ}?u_I B!J>4!Y&3Js%9×Q83墷GB1Ir}`k }AN8ao?44$!Bl&_MCrss݂ _C!@y^hkKywGEh E Ux5n2hm8p QP5v/|mێ}Y v?^Zv+Vp|w˖-UɶmnA3/ VYY]N$~E0~U&Q) M̂ x?Dh.n:/XjkllDPQ v$b^b. #~\SS>CJp&C%Pqb_pUUU~0ox_|ݵkLi&Çs@F<|6n܈ D痢K:wآ4ڙy'PtMMM m}FER!|EAٞaHF2@ F~^ ?`_ "|?׺}W=!B!$PY'*/Z2s*D8j<m"t^9]y_I B!@9d2QX+\~yQ$PB$P B!B!Bb_ ,P~tɋMʶ,tٳg]|)|{  .\hUBx/p߿xx_֚ӧ^yxR}ڵk%.9^σ[nY/B+(a}E( .{RoUUUt|nDOwn/ˍ7B۷s˓A+*F<|%KΝ;݃ W}ۻw/=26K>?y$ $|A76l`7[|>rm3P=+.ߏص.}B!B q-sH,K"B!$PB!$PBH+@9J纮-^dkw%œ;L4g%>[lk5 `q/++v7Il/?~v͛7]cc1ӧ'+~ ixy?{O<ݻw}"bqٳҾy2( S̯x3-TB0S+ȭWLxAxh}1Ye3JWd|| Uvs![|>Ϲs|bO/^mvÇ}[uu5Wd>k]>+ B!Y-P.X+=抿ѕt͗]-.'/m>mρ-qg߻ൎ{ɉyrߍn?@Y!$P1agcB69/޶^:d69Y)O?w=/JEQ l$j#Bj$qhNbmfaB )PBPP hs)s .Xڼ3:3[|<}s߹?yTשV`WuͿR L"|dH5^Gr47Z&q*1p8NC|NRS"R j{F#칪~%ll6Tt:-vVa& BR°j{OY⛰|>B鞐$VnEh}mBʝM}u3`d2j68|`\Ⴣ_yzw8H}V 2m<`[/Vuu8Ʈ32@Xd2 HQW,"?U$ ObmcAmnvPv\'GcqȽay<= ]{Ѝ󗸶]7DOr|2]M6>(d}&ҘOCg>eKP rWOBORͿkINP UIN>9j1ӛ/Uۋ (;%(!尀{>f"_WF>!/4ulj޵ZW);(+/-ɾs&(sFgN:7 Md2ye+IR:Ay 2嗗ׯmI?_1pqm]$QDžx<hw~f{ 2v $ՉpY x 1tr^4X,sJxf&sIf$@7"r岻ej ŠnX Ѐh\\ obEOS5=3;;y~x }n}ޞ>#ȷl2ER#+EQEQE1? E)|H޳KPO_gF *Z?>oh_t$6^Xh+4A_^|<H?νqFd:p\Pex[+ּJlPEL,Mb"f;S@/;lb5Fjl &e@E9rD|(@`~u]}o}zAr]q$E@9 J65; sJ=@ ,@rL׻^aVh:3_H7m[7 (Ǭ׵;p m89s4ODZ۶'h2@c8-K VZ7@Yg+Noo<ו?ʏQ(u ;Uln R^}=h>ʊRJ1(lQP~ 9 e PPhGQ>8%}uۋEbw߹ ʫg #+ui=#^<FXc'bn߾}}h_@-=h˫W6 nݚF*]@Y߾6Z|eq͂uu]߾ 1c,رñz.w @}rE-HׯW+uG uE(((9v^kJ TZXoYmWpOZ$%Kd,ʱ .8ǖ-[G% /^53<@; # @Pm.oss36uN}  ☠W;_jJ b-pj"Qz#rGξ%'zs(ZmE7YHZj%" Q(:{,HhԋU֫Kmm&0ҍ5!v* nȐ!SO= oVW[Wz߻5oekS+ {.ty&frZ]u``_$\fasTwK9fj Pxܹs gvpX/iم찃p ; a7"u}NI_W M{1cZƍw=eCy>]$݆ `P%)+u=k<8Vc`>ݣ(оg~o#쀆t$5WZz5ʀ]1 oe}I$Tsq͂uu ]yϹ ~W7O?-;.w۷#qB|KN]g|]W(((" (J6cSQJP6 gP(*$3y WB:,Jl(H Ur|cجU8b:+W` ~''Ν;};cz@IqLPeDYbѬFբ=?(N4:{ۍ4_WZߛHqq~(rʹ"&쎰lg;#]9}r;,Ωg|^{opmM;#ҥKǽ$/.aay6;-/l륯c™$P{"CŽĠ-o!$ D#X ) 'M]H/=6xaZhu.[PVo/_3!2KZR Jl"!bvk>Ν;?kW^yźr 68455!I~ $=O9JFz lb :½N2E(x G|7N$={Xg.G@Y׾ʊg~KM8DBq u @Wʇ!fq PַQ6DowCl)ƌv|e]]Ǡo3|oܸ޹O>~@Bꫯ?EXHvd{CDly?o޽~!y_?/z(((r: UL@y_i&w2إ6\v४%=g_ʡPGp̜oҍV4R@|_XQVV(cDB ۆ*o@91 (k}={@yJ;ۚ||Vz?Ac]X60kfR#G(#Vuqnrl~Ov>|8@GrPN1y kؕ6uUrx',lOzå[eN~H@r4؂:::,e}P^+ KN(th(?$?XN(f3EQEQEQEQ({GŚaw}v-_܋wy U՘.EQ>B?e9˼|ts|uχZߑ?RY[66 I7Ae5Jy (zPi lʚ)ϩSc`T9޾̴UY:IlG* >ڴiỌ FGʜuնzLʋ ٢:#י>iyW%wN&yFG|.ResP՞5];1>.]{Ϲ%M̓cٱ&C (Nw+WN(Ouww  넢(J'l)f3EQEQEQEQE (g75gF>ʡ3J /08̴.kCjEQ>UXe8΅roEW+X6nŮUۮRV-*Ѣ5hc61ZnYK/Ͼa;2=2|d99=;GDz]pmBڹg?q4F$[բ^;ٟ,vPǏǸusgϞQZZ/¹b^l[Pn u_=p]1ÚeXEdeeZZYYi(d"UU͸-dfjhhXVN=f9r|>|{O8 RW^L(G iL(i]?X}oF7hcCW(982^e@<~E1O=>+ !(CeD(FH7oɓT^^;^ХKd~U u~b3 I(ۖmB]g vSBͰ,cfJ[~/3E1BlgeU=c w2#C&g|[{H[:mdXVl+QzQA)d-@"-P}µl YB#sL6Y|u?qJ` YJJJT|Lx@yΝw'%%}3n6r9? Fâ޽{ihh{sw dI/~{1ߚjzP]E_c{z뼕7q H!mZZ[[kJӧbLXYFF9s&T+#ܯ̈[WW'wtt+va~ܽ{D\ZZ*Mbb"ѣGxIyk% eުuF r5Jحww6GZ|x>!B f´- ݕB ( d:6!’ۚ_C` 9ܝ;wߧ 3gSNN%(5kדr˗ӆ SN|-8@Yf?lŋϥ?NUUSz=.\1Q] ] go80455q $čͣq9ju|͚5 i=hڵ,ej\ŋe?7MwXT:ǮO<1nܸPGW(;rxNx^2ׯCqk1x1yk% \, Ę9o,],YHو=iMᑸ/p;&F]bB 2Qȵk$mbg\<x!Okk+sP ,6eE-[;΀، fr0ٖ~a$r}Ҫ*PlQ2xީbk˙> 9D_83kۿ Gږ͘p:MaAm$Do7a ϝ;o' B?W\1|G;!Pg-dS( رc8ʯସ"da& b#388Yx Kk: )M&x"ٳUU͸g쓳/YDW+#'Nv###d @y %K}eC7}Xf޲ey% 8~6n?7 +;6L!%t7ZurZc13 6bA 2e=l6ɵ r?Jjɜ:'x6KA=+<,ޟ91?#N} Y& TeI?Lzzqgb8dѮScbk*67.PXf_8[> t\[q{$K 5{x#ZZZjX@ۈ$222ÜB-":uC3VWWSWWg fBc(Y&p۷of2[\8eS?… A.egg18O䂈1=dN&mKſTg}l0 qtC\ @؃㗺:jkkN@!}}},NAB yekGG=zo8j߾} V_u[Lj*jllXP)T.@Y=22sL)&^ru_A Ɖ_|qg2f_E^C3f/x%EosA bsʨĻfN~x`q=}}>@ ե.MG.DIQRK&PuE[[[l\|\(@:|LNN`D{{{ ę3g|9_[M6@YM6XرHlذ!FGGchh< pI))3RK.%D---@uR*K~z477qr\^s( ]|MLR~/͛7hf+ݻwQcjjݍ7] ۷ X&YxMzaݒ鏉Vlq,|U{$eʍ_cWhz>V#5Xr Ν;r+V)徏]J544\n݊{ţGb~~>t: xr/5v6lM4f*P(WE7oN>.@ydd䉯YevZk·U[r%fX]B}rWWO P@\ rk-1}K=;;?NC3'Go㓝ۢՊt-@9 v?yevĢӇ (3ŀ2C+"W rDDDDDDDDC?#X5lv8DDDDD4x<]ADDDDĀ>ssHXy\@޾y}T?}6- 6jրrVC^d2Qa~l6 +1 ZԽ( Rn]ƻ5t PT0\.tT`vA0xc!uŞSuáN}Frh6j], .2 |>ߝva5hf|^rt:-p jI{oy.Clf+n^1NUT .4rlY8ND"J%lμb(mH={sR~dFC_(cH$dw?/Ja_GTlEu$K2՛xZ؋dԔ'Hx(Eг-DERӮ }>ky9 md2V[[ըp85m|>^g/_ AܐWUsRuI@@9r(AgEşOjwDQ/Vq[7S{Ⅲs0K=Q{g~Pt磢{E{bi@X=e_r%O:'y1ٌ,-3?DiX,sYz]Z+g,RD7gɗ˥{ֳS@ J8o> ru (#}/3{}N&jǕ/ϞyoPX@yhHCP:䈸YD8+D&HMKK ay᠌wUD]|W^HjjjP^K[VV60֢6! ~ }\D[2AIEJIB$Nx~ʕ+Z'=$u ,|W!lŋ͹sGY'O dgg544vItD^AA|c>zˉ'.Ú(Ev322h` d,--53f0fÆ v #k!FS w~'Põ@@e?Ӌd}&BrEyi23O3{{lM'|l&OlJhU?1(<2w(U wwwA"5Qp>}|4-9D;6"Bׯ_Gf Q!#ij˖-7.l| UiQagiWDe(P#@'f}?nWJIsw Gڵ+Y.{b|n|r4UԶҭI:B!B!@9)-d I_^zr/xAi ip>'M!7O甊bq:^ Ar<#sӿMhRg I#HuL!(`cmW2ܫW/|s( Vbf÷aDNr ={6>XSe"x0 "V63 \ q@{!e 6_o,X4{  }/]$mI4[VK [iđ.{b|n|r߯(P<')P&B!B!(Iyx4yFʮq?z>,L2H3L&4 gB@T\TTd#ۋ<(>JgϞSȆ"  HEw,[WWў?}ppЦ߼yS!&|}p4ض4մ#y ߷^>x۷xμyL__vy1. \~~8jSTVQ~ErseB!B!B@ĆM7!Z(Pf !ekr͛7K~yyy`WG PQ8ZL2%P 𼬬,HJ?|#}D<ڕ'OC( i+5p|)PZxhB(kw7)Yկ~YedrڴIIIf'V>tMÇ# E͵G|Q3!xr&ڕ1_d8 ݿ HsM?}td"͢iWƶ܊%hW1F[3~F:"'N_^>{ 7Lss3L̩cFLq)Wq*We+ȿ_Q@seB!B!^ s2CLѷ!Sϐj ԑu~Ä7}BܿGF+2 *!XAwd֍%C߱w}IQ\tmwp5bbl`V$5 ``DI=hE2n=|εoUq{=_sgzsmWx]'d:vi\)w[ç໘^oyVY bgEGpLԟRܯo _fmw +"`#'onfw"i%lr *ו~O g^Wί;sÛ]gGAYuDîl^]]b=7(|>M8% #qT[θH$OO5(c4*n 6RT*%MiL M`b5>n(c)Tmo4tl6G`LN+A4 C^ Y\ qibX3rN+ CI&NMd2"tz5f699-{k^ +j5芺a^WhT*m;R]A%tfrYr9>[]~`x(=ξV8QA_,O:FlH$p>(#e:D;qH}.A&YYYv/XgX`5(AyVɱ.I!0߆ۄޙWQ}<%M {XfC afP : 1 " "2Q&" 2p`d9g9zco{+n+>?|ꧪg~f@WZ?5m4wq.ZHo3"" 2&^*N0ȑ#-ڀHFt9 $*훛5(gΜQ7>ŽL@W[q3upgֻ獪>ę6_(|`*;DF@q&3w1@??Cb g#XC5/qקڵk)LsA "He?h4b_؂77,3mjFWZeƍeynK.'OZGqU~ڌ-U@34Y6\i ^oK+朲6K.\E p^Lgl]~.}`_|Q/X槟~^Ós礹.?tx3$E<u)ueտblePIE"H$D"H$Dez<\)`;;eA3Hs(wݾnm^ΘBgPI?<J~N,##CYFQwu<(cD"ԍaX R*@Ymv[Hs(\UmwՏe9\WP;6<9֥D2PˣZ]:jŔ6tPFлgΜIݻ-  8 p4~[Y ܏?N8 2cԩ@A%&m߾|OXV%KPߦ"09#F `DA_{N mID}Q8աU`%iow}۷o 1}}WL~>~8#:C.)/ 3]i Bm_ ;ۭ[0/9~叫*ճ m.ك9TVV"Çn>g$@MLcr[4w\JØa#ZCu/噂6@;cH$n@~t $޶qQ\ӕ,Mk_9 ei@^!pW$"bej36 LKK `a CkܹXQ'Öfu9ɉŋ-Xs֭V믿lGSS}qE_l L3EWxiz}6~ĘwQs. #~`QN ~ʔ)~ʺ}:~2}FjXc($g*#pD"H$D"H$@Sr,#2 șn=+hl @ m{lOeϙr72_e$ՎG%^m"S Pg@IQ 4鋑lCRŇPOՃV7J_!DNEl/槻8EdkxC}ͼZ?倌HPN4̫NVފu=|}ğfNXK{^Hc=l͙ϊϻF޲)/"5bϭ?l e°-2qD͌z3 P:쁏rv`Cьuf4_ }lMWp->dCdsط`C6 [+R0S׶~݂=?w̺_ύ 67'A-wLqւ/ԉ'On8 xÇS555(bh85UesрxHٳϟ'XWqUG0gR8ӕӧA2l< _u_Uyy9@@G^C}^gװQ6qh `ïq`I*i|%@ӿ9H2BJGVڼy.Pm~^xomw<D(Dli#x׳e),zUѢt*@'`󅯡uq@/krUkdDY1gیxAآoذ67/"̪ځDm˓O `F/j V}ՀC0?0Al@ڱcGȕ#;èG<_o3+ҥ=KsXO_[#5ku)ueհ2yPI4&zgaKP^D"H$D"H$ Pa9"nuk@S* (sf š y (ۡܲK}J0E'Mmi%PΝ!rB;vz( P=?]?C@y=H.|c(2D6 Wm rά|*<v827om!۬7^!ih cg7tv~Pw_Tt#T1lYXʝJOԌ=?w̽_ ͥ֜j8PʈȀsK "rέ "1&zsFw(e3μ _/6PB: ʕ+MG%+Ee77@"[lHSJqn' `2AFL2Tqj*aާ ep[Ю]xCqEǙhD{@'n8ѿ9}![2c]`8M3 yLyf$j"Heδ X4ш?ieu jsa1ЫĘ?w o!cݺu:-566O*ȃr!Xu,ڀf >60~gN luuuFmm-^~#6O` B6s^t aMuk4 y d |hʕƌ3k5GWDK5!rqèG|_o3(Ӹ駟#Gx>Zm7 =g.xzΝ3N:P\#ǣ`I/9@}ᄈơDz3e]kg>5x^ic7IZ|D"H$D"H$@~ ( e 6 -\e{hW$(Gڣ! P3y- @Y4~6H(K^~\F'}K}̮׷-.أmfԛـrdcX9 m뼇|ޛƞ!B}2lXME9<vcpl~s܈owE̩U‡ `Qʀ\9Y 38Y@ӝ&$# wY#_rnB~{`ܯϷG{8x@aR\#77o6 a}˖-{-))cPg(-XoZ/^LgD>R*CW8_ 'OsQQQwqv[Vٳgo߿9P~B(3c]``gpb>˗/D(ݰ@b19jP-{~t?@f8"ڦ.^z7RYf1&ðȋZJBEyx GJ؄?(24|D-M&LJ6|/`4. e9r+m&~L' 2d+P?2 /[!.kM/cgu16+ 2Lr1X`D"H$D"H$@@̞ (p@8LK};3R|KN?z(*҂@̻H(;nL e(mğbl#YhiPFj*VqK}St_Ӛl#3w^Qofʡ$rcg7v;#V\>  M~*~#ˈMgj=U*(}c8{~uP9Hx{N/Hx@4Pnu!|Ȓtsȑ FÇ[i .$hڵkVZuu5~=;}uԫ֭[BP_ӧz3W޵eZZZ: VLF:1b |W*#Wg4hP_9Cql}WP@?92g1|sgヒt'P (?캻i3֯^l?ւ-cTW3޾̦/`—P? /?͸r '`m/\`ܹzuA?Ja,1vX* ~@[lg-͈\8q"<\>hjj2+j39ͼq|su@WH2| uE(eQ|sr@F$0lG!e(6m9͔(G;(]N^وF |m3 6n'|}sYu)j4 [2W[ʌ=?Pޯc[{~mc9fKʶtAΔx (#"3H gΜI}tDNDڼy(_~?+Rĉʈ Xqx<hj\s@j/3DIqޑv@P㷳`%QZZVXs8˟ʨ(P~g<)hޛ62E"L@$9 m/^dE"H$D"H$D"H$ QHOكv_'R@= &7*=:(03X~G@(3WLM*X'dDyFlq嬎S:e25Pɱc*ؔ1\ȈDnP]v{ Rn=ϼ%'.oHmW~H?i% ï޶90DѧsuiW}LF=6sx@9G{~ufڝ=p7ƙ"FFQϖ#=Yg~ϝ=!cz^A{݇l@ċ .ڷoRj„ }[@2۷o!"4aY>X[[lee$o! @٩?cH{/ջ|rֽ_޺=Иtz?g\\o߾́***\` ;<h 9A$k-W-mOʖ]Hh\[+D>qyc_{~{Nz%AʭrQe.FB>ddIH 97r4E`uDzn ŋ(Z6UGUҦ͛7ir_tܱc.*--Ͷw>RN۽{[Z _uc$_D-@cčMKK #:<׮]*r4yt+> "ꬹWPPhĘ 9}G ,@9kЫW/y3๟#q-ܺundމD"P@YСC1;< ,D"H$D"H$D"H$+<%LB:hUto (3Uy;;aPڮN'_̀22|ԝcSpH@ W?fw&K')"yJj= sS_:6#qc)O!K ݍ=s̨7e 6r}~C{kwtA**<حrm9?r<[Tⲋ-;=?g̽_9˗A ~i2 >| P0-h4#V]]l'|r(8DN NPBɛ=me6q#g^MZSx羀Y?`ی"[ii} #smL5|hwxʛnI"m%3(TnWPd<vov̫.jZEcOm5=itluq~^ɱ= X|7I~v|έ7##ç|~ʾןWXQT.UQFB`@\GRdIU?w_s7z>=8P&{y{7dr̉'E1iΤd ;};1?QqիWׇcw@g^^~erʑ& Wf,bm9@*զ__+uΝ;wxpB+[, pEP?O4I]N!e}.F L@iKLekXdmڵX>صkW[rSNTRǏw~/6zmcǎǎ?fΜq}@ŬYlܹ֦M>Ms}ڴilŶ`}1Gy /oPK:ㄯZ4TMT(PznP@T8mX˭D~V䖻P& M@bJv<.}=w} #XYD 8NW۾}}|ΘbŊug͍ߺu޽۲wޱo>@@29kZRPݮ\֭[GHD{o&TuhqСΤyEHu _\Um%"{˪Up 6LEװ=z9A˴BzOþVzOdhR?k[PF kԨcAO?dfF@2e Y'm1c7iq6UmZc7 v˰V|PUo.I㭵 UR}Xg?Ozlk[NuQwIW֧=}[[=&zn}8K]* %Ś6K i@7ް]vٞ={n N˄ mTի9sƴh"]_t ,jb&~>mvA;}Wɓx2335YQxEwwq\W7ꫯv)Gqn&0`<դK>f/e>Xtw@%'|Dc2eʔ<OlĉjZ(e˖nVZ^Rm76l4hq%tK{ZKug :9sX~T5ׯq!?nݺ9>}\u}]W[>#m;dǎv95֙c/Zȴ 1g-PXt?[T\C_s=P}b4m۶PP$\rpQxw]Imi`0=}!Vƫ;ݶ #Ky_|bS6eYlD(+(f̘k= \FDa@Y rNIoO&6^m_mРA (k*jkgMuɓŧOݨrTm]%/-@%͛[ՙDM4IcV{?A7RVBm۶ʵ5nW2d/W˖-s~ݖj׮prl߾}π6^p9G5-ݻwW;֪6:f*% wlBrU||egg[0U߿ (ͫRry- Pv]*̝ܧ6W/}sXmy{߭}Oo?>lʲ~_{{meʅF&M4Y5lB Ѯ]k U{_`W;Ta;W6msUֱcG_>&zm;|lѵKz^}x (_ɮ%\Z5j0 l+V6mH (m֭}8kֳgO' 1>_DM@7O!+VxĈNe]-({F]X~ZZi׍(%11QUrn݂v:>jƆ?jW2eȑ#A R]V`2ePPW[ڬI}X׫:#*PsM?= _b-3"xi-Q|xo5k֤?P&GfV+WxmHpc=֬x|;sg R$2bRQLMSu\B8)OW^2-M4Lk}cͥR;umh4 bh4Rjuk$;qz*JnxL}`UцbO;zr ЎkwyC}c>OG-&0L"j}mF^z8 s!yM`0XjʡP5D)1F{<cZ^ H$J1s& cD6p~1$YՇu5bwd#NeE?gX8}r&p8n'l@ Wt:r1bHȷڤ-ڼm6.e2T.aAݱ9D"qߺpcf/d2dü?Հ|SG( W.8"jbHi^O7Z ^c+x\yf}a C_j\]]W6Ncd2|@t2.ttpL =0?NpsDU!ΙDHʈm66$P%Mbo8I7xi:㻆_|>@`2:k%x{¥gEfQ~Y'6 @pxb_wouf'"&n z~p2@qڡ~KkQI`+>҅ņMAXt F].".C@AP,RJ& |,`M[B!%c23 L ͝3s3|s;nu?~! ]/N~Ƃ.>%IQEe_ JƮrImjl09CI7ApfɅҭ9.t }8Ҷ ?F+xeO8(Y`;A&ۣuYu@o6SItj?J`콠LӘ)v=g5VXie$M )"ʾ&h鱠L CEks6-OSerDXd_bzzو"TSʩTJ\"Ϫ}Y)B:f)j7q"h A&\eCPpz窮]588r((4@Pr <"cf?d2g#xddm^UPPPFɞfe$˵[ZapnnΩ?\w Smunދ]$I38W^ <Qv͂2#ˢEhN&BpNipP Yxbb b9mGAyllF Kΐ&610kiixLKφqޟ+0~d_(`w/b2(Ăr0H$OEQH's߀ u cw9#]REQA^k'A9@oUV)Bΐxݔ|N+%t ۏ>wtܞjG˧LX.zMJ?ڀ\CAw&VTW1."C  oI"u#Fd (-'N4D^^t)dRH,5<hOTDھ};} ,J4W̙~Dd لʧ~:us)3(#*׀1e`֌FPްag,ܣP5k{|p'?q_ (M]w]`A>DIaG@m۶E㏓w s^{5;6w4\#q ( .r0WY/(JRT*J#@˨Qg5PT2=z4[Ě/18q|,OTPϞ=}238+ܼL߾}q&}Iƃ=Uc馱ɓvaL{.Gi bb3fɳ}Hm/v76,H( W^y1b-O e殅sD F(Y# -(>`;# Y6dN/CގڵcʡwIO޽UFԓrl$~g(! ۴(cM3) LE:a*kʹ> ;@qLT`qx p<׭[1G m_䝥<-y/ /k. ldo2^qYP%cRR@YRyB8W^mxo.+yW 3G,))n&b/-- ;h 1b"O?mț:uOK:t@ʱ#:<V|q*22SWWp (tRDS9b[S9f{=z=9yd";=RT (@y? E9 P^X+§!?ER5D>g&<}lR&wKs[MQK*2Gx2o9/e_|?=r)Q:I'zZ@W2'M/<>0z̑I8j<ӂu&M ,vZK8\k-E{3sG8i{=Oę~0DZ9nɒ%ac(Y[Ü1^q8 Nc=\E-O?enUELT*JRTatt2$8X#Ł 6-"{'SoYs}151Grzۺ@#\Op'B]_^5z+VGac1_msس@)xeO_|vEl͓gFl\iL6yqMs27< [_6mqg`6$; Pƙ2w1g6G QH"oٲe&$<&c8_UQ4mSa7W\w{% (f,Z\XXh}W_MV Z׀2!sqe[l>]v (T (T*¶ތlLB;w/ō+ڱcv]>eaÆq PΒReUUqGW_>ߒrS}91E%Yj2:Vw>Д'=RT (.@GIdd0@FZP5eR't+W@,jsM\zC:4~榗~,GklDGK 2g%Diǹ&a'Y5p^0"3-os|05+ Kd,@}⤙F!QS[)X8 Jh@Yo#@I&:,K>QXH5&8^<$N\D_ij|3)wVB%v$R0eQ@mSO=%(D;ZeS8XNܾ@4'\^|w+`RT*JR cF@;LTS]3r; }"-癯׽ؘf@ٵ^+ND;9s&y(Q z,|?D(2c̕kX[l&}]6H_~ i] 'FleNܯo?o٥ĦYJ P q&ϓ|ׯUH@x"F\%6zU@Y9VQ[XOSדʪo1L@+O9%&DCyO>uUl*՘0~ %5|gH[6vn׮];:lomɘTPVTIvwٰY -ZH ˖;v4l@uV` 2 rn ۷/vl]![9!6C*e|PV@Yv$J$*jkfb +oYke{*JPkG ( ? @9T4+^Ш{.0^Dt_jƃwt|? Z%͈QGԩyĈG]vR`İ%@G2?AƊh8屋W_}eHo̙cM"j-?vr)N҆չKǏom2$"Xrx"Z,w (^:Q@}0, NsQC '?> ?b"u](E4hO[dGS:[@ėpč"Mc4D<눔JRT*J?2bC$Yfp;來5!emX( |V@9M=ڌ;6 "`a5*gIdtblRy kD# l~e#? 4(?l AP.}|,ȽrTuјs^D1e$٨!]@'=_¹9pI&K~ԏ=zPDRT*J?@M(gݙ1qܸqE9' 0z޼yAP}XDf @R18g`~}Qcc#!vm5{laskeB 2Wy]s^x jr oL¾Lڴi rt~@2®+0k@C #eI9CDd!l2TR`*%6xU= o_>k{u{S޿k:Va# LVvOx>RU.jWwapnq伽uەm8nf7Cy#N_Ƿ|֘ls5.o\U~U^k8.׫njn꒴-Q^e>mz/.0%DiG_MQ7k՘s,ם~t߶!Mͽ4Ҟr˹p=[|U@y7e~k.D Pf3'6 (K3<3U? (²*CTQ M;LʹM9 g>`*u?6q~'RWؕm(s*.5?e*G_e>?:fSTSXQgv܏1CWw?Ī\5vS}[L͌LkM#形TЦ sJ@(R;@0ee[j\@uԉ;!UK>MUf̘#eNl6Ϛ_|`N޵kd9.-~V[6dIlk׮5l12۷o7xV@;rM6Ny뷧lIV{kX7vtޏvvgω^[K_ޜ>sQ}SzЄS25?[O{ƶ˻T*k#cBky)1cm=2/uYL kb5go飬_G鸇ߪ;": ؗcD&#@gBJW0T :rJ@Q…~%8@%^#G;w8 b ԉ}?,G&/2?l{"r()yD'o nz޹%"t|@Y\r-m۶pyںg@GwS@ߟ6o1Wy#NVT*JRZ_bZz9;ʬ{y_ר):E~B96V}q|ʼ0\^zbzdt+D8 Je?Pfr=5l퀲 'D>mFJFI0/ I8e6y @y=0_}Θ.ecNwy$hʔ#OPuqpv 8- ߔT>m})VxM}wc)OO{m+Ԝʌu\_g23 rtE v޳2EgksRHal__cճ?לl[S^Ns6@y?>=) ( ̻"J23SOʪ| Y4eO 3 U$yaeCãq~e"UzMT*#Rvpޛ*Uve9^{{AQQyc? ( C79!`Rf|s=|D9(d6ݶ\+c*.,snUFegϥMߢ.a=k҅U.e@ªߤw);$U*J(Qe5ĝ]ǾK (?Hُ|ѓM.7V|CU=ރs9?R'K2N30S_J@CS"aIv_j*k.CωZhq| X6<; [p̘2?Ne PfB_G}26+jG\GcDp+@P`cH/Y#gذa/kp9sZ5O~?wlNP^^n_XO=TKeH>Kc-S)hɲOe" 8NxR]D XNa]8"-:8|Dԍv+)V1I8o.1YpoEG`yjB#5{aʻ()̽>25H$@Yޭ({WxT@Ydt8GgI JʬroդUq_;7\ k:%shJe@Z(_ha_#EEEʼGȸt"0̙3-`o>w2~f[>}L޽ ՝;wRo@AFa!6,)ɳTRRPf~<{i.ߍ;6譀wHmDPz9Y[jxn^ 8W%Z0ʌ9k%;_eYktgyϐPqgfEŦjʴ.U*JȻOx]Ul|~Gr8 PF[uh zG3SKW5ؼY96#BK.5D6l+PM$-"GhҊ+BaH'Fcp@-Ι3̟?`mV$C $Kwv0t([`YK܏qERp3?KYzoz<q $Dzw@)eШC" rX  |_Xx'ԾxܫQAJRT*ʍ$:_#@Y@cƒ:G6wމ#M {|F1c|ya-|t!uppj-kݖ"~4hg|e#Yۊo9Pks衇x@&8,:+"2 Hkqn4Y9vR>Gm1$`mNe4.eDaFq%1%ҍ7(8b% ܹlp裏3rdۉipvۆED)!_|} pC Tp]V}~˼\Pc-0ud LP{Mdluy|}_RfPvq}mP.^ q07r[`\qݶjL[+gCϝ-}֜}sx}9ς~;J3 \mg$]b{P@ކ Z6pgVE2h|)YT (?Vf9L{vwsR^yoqˌ.)|T(c Cw/@Cdc+Aw|Pac|`[*--5&U+@qӤ q!`XWUUѧʜamvђo (GTXfoWXzj[TOž`؞u*ϸȻ!\?e&g`fXT5הP:a9ko}qUda=Q1r+?dg"@|Og)tZcbq*K> ߢ6-к:ERP=Sw5*v7[Y.I P~B)R潑qiey]IZܛ[ҏ)-Ӡ_S̼dYÁ?]}/, (5:e| M1>iQ㫀}sLk1d ,.>Nh'UдiZ(j9H#+@Oʗ,Y8vؘqqD/E7駡c[|P&D-P|<[Q+{hTH,ͣ-Ӗ䃘qqZ1Htc|g@ 0.'mq;vp< 8jrx,u7Wo)DRT*JR9ԢE$b-ie@PPگ_1tA6$[DH༱P::S@Yc ':KZ#( ( H"6D-6>@?%rF5-7j؍~mnHscs3WX/Z+Mۡa874~(3#Gm۶eP >K3XC_f &-Zl޼ټek" `|/;(Վ\&]E4`2QᎇU?̴]A@rutsrDZ__wrv+qTMYY؄#]^;>vŏF m9 }sr}9ς~+~Fm~i֬R߽w (.{8\ʈ:* PF3=ͼ?U9K{ pjʋoTʈƅúgou/6p0jm?87r ˪P&e@ WIYԇDz(@O*2ww̝;Wߎ;_1"ZeuForg;ck p߾}sNPf:N ,u%&TM86 _]wޟ2} ,@3y%{ <>MF{@9z P:Vzn3?Y僦Q&*~VT*s (V "*ntz\(oxC]>(u5فRo#³O @'?@GD`&rtn|dDf2_{@3}ƍg0p{2&FӎsDZLb޼yab ܄Pc`4"N6zw!:-F4Bp * ("OKDDNDA-RNTP]{L "!hsdAh!sB 2m$Qs 7$s.e2ki& 1: L:,=-Nh~kf܋u QT*JR o=Xpr„ @} $cf \z(CF15k@=ɱa#*pN o߾( ,\2Tk\/{ApΔ=GD4e1@_ v\Wh@[삏>h"0΃H,E[gP%2ig iG]H\g7ͤ8 "lK~{s=6N> }y[M̙#yKTg ҳ>DvU@kK{D@ SN~ I0FػӀv,mn*c (wsJ;2 f:s;NAe"$+Qi[w@.e*]VovmڽHm)>vϛ|:sw.%Gv9c (&Fܮe %lIPfC9@}R†k`ȧr(wl}W#ɀ=#ϷE~L"IHhXY"u6Wէ 7Ye5?[/eՓ`n8?U*rJa,))q;(yvGy_ (wM\6lRoݺuRyw88xdPQ@;-ڍ,⮮9(׿V9?;L0@#n2S;kyE8+YRP=x_l[Չ*_TLTby{U~$t%7Jm}鶬&P/NzoS/ 8їX=I4шqn˖-RB jpG@КU8\#lH;ٽЗ0Yn# O~@@ĦikD`'005[o WhN`D8Nu]7:eҍvD⺦~ܳlPF82!(u1W1 T*JRTҥ^ʚ;Lkd "W %ؠ:IPI2Q0*092,;P/^,׶__@HsH'璴oTy'Εu(񞋻$(+l__WGO.lS;#ɵ(ʶ y8d#`_cݳ_Y= L}ߵ7Wwp'O4[9PFÓ{|ܰnxh Ƭ{ʅ59ϲQTbjf/1r#}]S}*,_gsUum~{ /OSt{'v~zM$z3/:$ɟg\7oLL:8ߌՔcV@YYמn)09(׿V9]?;hdcmׂzk 'rQ]g{7xF^q1dRT (P~Mǥ2bC\M!b2` 0>K2,rl2 ;_# 4y:1i!wwmlB;l:w8N- (#6fLT`˷~ @8/ [إv}o 2|NKI`hDn zo1Nj7q2fC̹}"p@Dz Nv,`k3X ] s/!fΜihޱc!=@g tDaIvKP4Dpill(skw`PJ863f\ٳgO9&b :;VZ%h^]Lt O"2]k#/eԿ֘ր+Ykk^h\|<\s#F .bC;ٵkŘP/08sν=(-y~dիWv?s<<8? +K;ShsSj!! P|%p b?mtpHlL؂׉f- י, [vVz7<eM%Q  N{キNLe~;Q5ZzR 7~AetHLC<ЮSP^_@#Qoo1Y/@.A \r eޅǻP3f ElsN+*rE]J@U5Wkflr+iC'ReP򱟑;]=JnݺCvtc9섵]@ (={ʪurA.a`Pvhω^[Pv׀˵F2Nyz?T*iʪipd%RYpF0'XK>TpZ%J#*JRT**qH t>T@ڼ3/"gPfS޽{[&)2/E:- yKLKV:DT Q` Lâ}s4^u%4;ΘcvMk#ٺsm.}sy}9ς~^ܡW3؎e+@_~;(;=݇\ejRD!XAbRӀ\ J)eDPd#]rq`+0@|gv<<9'C9A'ς2Le^"x e'Ay{ei^qV&49xۤT'/hu ggHҪyE&?^&@PD[h^5/ mQ, 2dݳgsFLTQPVc?ϱvG>}pq,xܫDcvA/mwhT?g~<3!PPL2 PP&ENF!')",6 5=0ؘB!2@V_drE^_Br ?s;+Cf0X>.d5< 7d}YV;-VoeeKP6ЮACS3#!rP?R>T3MotK2鳖CwÅSVe}ml/|a )d*(.ى/(؂yc/>B((SP&e^tPP&'rʕ+E%Jmm- !B!CˡCחR__/ߧe6n(׮]KAcAY-Ècbt=/X9A7 6#zoל\A?GB~.Gn۷}1X~x 3v_ZsݏUơS}'`^>qeaˡ;eC?NII>k> ! 3j yEϥ[FA5? J_`J[y&:d|H˰l=!G !2[O.KBA f̘!mmmCQT[ !'c K7 ̙3Gjjj\L!Br`.{nבy>x} 9ǏZxA6okI=wAGY<3v (Uɑ{5@\̶TG ,A*֕]}}Rff ۄԛȥ&s^ܟ}nҙQy?J?|e'EEEB!n8]eTY(߿_}a E0g:q]PhQP~zzz_GB ` Qg;Ȟ.k')u 8 ٨E;-)ۑ+qLBZւخi%3ޛPP4mW^E#߿pĉ6lؐ"(2fAɓ' t._,6u~=rA$xѣ؇9ۂ2ƆΝSR4WCC@"eMP7n:}/'OƘIp,1c2B>6070w6>弛x'r|qՋ_Ak݀{B((SP&e̞֜=rACdw;v젼I-رcTL!Bpº/!kṁ/s $?kK[)~]C2 pӭMw9Y^*s \cx (N~('-i&#іiW-?wǃky{댌K];@ :ΌXש8χbH*R߃΁3gWA`GA˨M6Ayf@1&(솄rʳZCF7GALMV>ͥ 7Юi_`Inm" MBr+(CjU/^"b| 2?|,Aى/Jkkk<*TeL_D9ȳYoڵK+ I8ۂ2@Ҽ v.!FtZf];ZqrM$_4u9Gy?6R?xSdR;cB@B!B!B!BOoGRmv^ӊ^I+MWv !qx~Ǔ?fo{'{L__`OJfɅ|*~&3nyK/\põT>꣼ʸD4٧hz_ HsBbGWݏK^ĹIއU~ A޳o((}vZK7o,v DP{M+(y͚5lϟK.]SNɶmۤ [T#*= FR7m |t֋j2n/yG"2V(R1FohÅ'ǶJVA|2+DmJFpݣk_vخs:Ygg>Jm6^Bh׬4A ChK b{ >V2,555U1^TD"!Q#|WBOO>Ccd6N/˲Ծv<((vڬ7F-ǎ .ŹC/}r8/ɚFøQ1cGG8˽I$>>=nIt:(k`o`lj=}y7a!Ǘ'kX_:OJ|>)m~'(@();045&"ִECqlvKCA`?; 88CN"Z}]Z~!Ti~r=?~|Y;o=X'i/p&wxik;Wn>_'/ 5x>̿neeJSDzfL%EFU@@H3Eq"SOQ]ήyN|j z .Gɯus2lPP` g^goS>(T-0b]{ ((l6gw333V~pl w.'_xVnҥٹi 7z*(ǯ/'?܊ĴPP6wrNȏmll󱽽󺴴=ߏX[[x4z7)R42;)6c/RTn( }bsޙZU 8jTil!!_Pb(ԦD @UT0Z(hAE c I@ hvr/;}?rl{﹇ɽ{\y׳䖓xtSi,A#&}\?Trݓk\y-"""СCS_W͙3'-۵kUزe `Ç h0﯈e$()SEkLaH""""""""""Rw?$vޝ9r$ׯryxAٳg ۷W"ٓ9s&ꫯҺSN%7oN&MywmXԯĉ9eݱcטʜ_~%}͞=ۗ7޸`~oIO0RشiSˡC h8roƚdO?I /br=Tm;nܸwa̘1Q?~:s~S&я~^g['OL֮]'W@M6-ٿ?-?u]5sŋ1>K>6W_%:-?m`})"""""""""" "ȣ>۠c۷EpW$wq%wࡇRЕxO8eO=TZ~z/Lֽ{fmc9jԨ uCڬرcNRvǎ) LǙ;wr ( =z)(翿 /R_s,X+cE ,R )(ƍ+2O" !y??=YȢ=\U}.\uVLqw;Nr3zhE:gĈz'^,""""""""""RW^yeҡCDReiQA' ={ mx(#eF_ɐ!CիW#EYl;K.ϤG$yizzwreDd2p€9s3gY`rbaŊrN(Fk}&زeK _~L`1f̘A$Jܷ˻IpeQssdիG:QرcK_h_Z*k3G8:u*gZk.d!s>rÆ KUݲed8>`ҿzws$"""""""""" m>"pC]qapUW6}H ld'9`ާ~֩S'ڳgOrYS~m۶\1'? PV9'2"Hf~F|Y/my3g"))se/G mQ&N}_2+uǎ1qV1 2 " Eɓd:^dgHƘYG;E'r8/AUBN,N(v+@1mnz&[Db ^v'7xco*"~W*(qW':uBm1A'գ۶L*RrAFA9@ q`Y-0GYkX)/(?~jժRtի^z?Oyr\Geٳg_O>!F3(\sLrAו6 :{DKyyK$fB d#kyyڴim|d5k<۷o:0<#dv&Msא#(M}@B'k-ˀ[MP&3mG2dHvԩ<!)wU>JuC峪ʳLYC ʰX0/J? ƪw}ХK*>O$MX5s̻&Ih"""""""""" 2|צm?q """"""""""rӮ];\pHH\rde%OA咄{>?((O>=uU*ڕn6oS{1r郊4,sMNAfbꄸ|^hy)=ĉ+e^F֭яŋC2n!eVd&Md ȡcL K#r?FXѣ Ξ=^QسgO3۰2|Bär 賌cGzz2vS}1_yN@aɒ%=#+#Af̘5յL| 6Җ-ɸضۓ+(e ]4ڂs̚T~QP>OP!nhAt٥2^}U)(  L͔ȴӟtQrs<7st{ڵ\YB)Gpi0z|-[a9O ջ`&T!(zx^&[!!#U[ ݑ-w߭ZwӦM|tK&8~8Rwfgy&uV]P#3Tͻоb,?vXqC={&:uJ^$?3LTȀ޽{w)G;(S)ga'r ʰm۶ɘ1c^PČCPGAFX?\v|{KndQeSP:' "futEQtʔ)i;v\oرjL3XћmFO>$P -K2ڵ+9q#")`K.\xAGy ;_|1ݻw"{6QML޽{ٮIP~?b[%@ss:TӳܱcG DYtK/A^lc`1b+>8Ox*qqy.,@uEDDDDDDDDDDAG}49|prdȑJ/( L*(7 v\6|ܦe\bbFy9nѲƍv3f(c~jɄᓌB4۝;wFL-ZN< 0HDGYQϩPI=Gۂ$k)߰aCŶ})1dMȑ#"Z@,{Fիپ,>B7Y>>Kƛ" Ȗ|r` u:⣏>"C~|ׯn0m4ʁ12+Wd?c%:A h""""""""""ܠ27c1j gٲe֝;w.ud?~4._|w?~<1tcǎ鯾 xo-2و(5q5['j1AgC??,My9,`wNˍ9yQmGL2unJYKgKRAYDDDDDDDDDDn)$Fqoߎ{D|'&AQEmϞ=Ħ"^SCGbj/69dLd;wę*oõE?WE{"'Ŷ%(H"([eP bp&k &SBS6y(իW_)7lذV# 1$k2O >ڵ+7`UAO &_<@,9 QPds;*lYCfϐj-dn˂2*_bXBF6bO*ݻ9]ʡgϞYc I 0k,96$4QPnPAL/׭[WKtbĔ#Ex1b#ؗf1=s ѣGʱR°.2S[nu)o˂2wb… 3u1х9ݺuSPF1_XP󒤐^R17Y`\tj׌D,‘ ׅ\.'7][тСC]H5 ʯZVvn+/AV#&LȪ-(g#8\."Μ93^{$^dfm@Jݻw((G[]+f?$УGBoGmHPfbx7W5euF0fbeu)yOVfO9PDDDDDDDDDDDAqe@2Ed"D9Z݈ #F On3gN2udĚCR&q\&L&:yꩧӧ:= !'5jqfs@,!=xVXA; .-|i_lC,v_$y' """"""""""pz뭈u'(/ 9e<{7?~<ۀ Q6p@A0vXIŴ~UMcǎv+WxD Hiٳgsv,JL ,A%}8OWʹz ><ٻwoę~dW8m藒J{QPn^A9DZ#+.ii!x/(GfPn, #4XlW M%GbGf Y# ѾfԩSٖ{grv6YjuT@Fܾ"[: ;.8ݾ}{ "k09jsyҮXgĉ],ra2**eGRϓ4~2t19qDol;MDDDDDDDDDD qY`Ax:&G"0_( GٻK=~FmXm۶JdݺukUAy͚5lW"WP&1ϸօYeb(СCdN΁ 5r.L0Owo߾}d7o&($h ϡU\ -!(;x`oݺuʂbz+(CΝ X̓!*}/G:GePaFA6omYg !wРA_ "04S[7ng#(g]zu5Uk:#[Bg5Žǻ[VP~ǙB%cw9*ux[Pf##W̑tB_.0-d:NR@fDF'MľŢɎK`ٲeXcj,6,2dGĹ2}!VGdfYlA:1edLfk׮n$AyÆ ԇرrE[ ei$X<JrjIO!_f8NNFgٳi\_IPf dN-=g#5y恀oVDx7IN[VI 5ȲZҨy>4=x`>3"Rp^gm̘1!! @ԀI;P &VGF,]ޖ)yE8@,"Yt[;ȾHQ8軘$ {u}]}JEb1+00n1"`">tv$$N/Lyp _k}E ɾ 1)'yԭ8QMDDDDDDDDDD%%KfX4-[P >2i _2}.NO&4P}6 &*7uHry|!}1pb\[rpWDfcrx㍱@60rLg5}꫹eiHZ"!;v~'Aĝy[%A"HfJ&aтk病AAlGO>( tLC.PG;v`#`Z>߫kPP?奪.8!Ԥ۠IDaAee7(, ]щx(@D( Hq ~Fp^l8k֣C©yE DnB9@e2Q߿n߾<Oр2 (ʺ噙2_IO>=u@ ~I-egAk$I$I$I:|fVG}}A_BlJ(yvv cW]cc=G{6Hjkk;L%[v' )xM7_oh̅PXrQ8i+ftt4;00m%P6,I fḟλw'$I$I$ɀrLթL~b'vq &?qp2aۨbځT WgPީ0Mp7O52 (xuu5yfw*^Ɯ?G0 mooO pws7 ʒ$I$I#+WdD/~"peehǏ#z4cݣE_Jm@ـ$8;|x_$I$I$I2 Ru*Y0 ,& ?!XBɄ 8b0mt, =s- [";22B{QmmmO/--E{)ӌ-Ϟ=tܹsP$I$I$YǎK.]en>~QAl \v@߬kkc;;;C0>mb9>eʒtI$I$Id@L777 fyshk}Xq444Ϸ;Y__= >%2>|sSvdz\~==v)^= l *0#\ܜ|XH7§ONZ[[Lϟ؊x9-p]ʒ$I$I# P/^Yuyyׯ|@ƍ),zQrΝ2yYg@yrr⪪*K_{܋߿ӿP<ը|ҫWs;(P{aa!illL>}|26"%l@ـ$=kw޽5 (K$I$I+|…,lZ8qʨf^cELvcc丑ػ{]آ0 Fqɔz@)Zb4S&~BA#:bɻ 90NrΌ!O4m+Cw}Bn6YwC͛T;ak(~t@yaaN=<cff쿥~I;ollF!, ΖryypItA$3]PFFFzP ( ((f΅byxxk^Wk]__P044$r{@?oWVVki6}hii<>> wY"~wwWx;o4]j2!7{wFq8ȊIFH슕:|]Wx6"x9'%1$vɲqyz`X^2q`0PrL!m6cZt:נ,)?铫w]|y*@A1P@@@n6X, |>d>nZViZn@Ch/@(׃t@vH$I$I$Iy/~ 2ʒ$I$I$I(g/|? %I$I$I$I(86 6P4p2P?e )(V@h@y2ʯe<ޕeyg_$hxHq _ʱ6P|EQ!>TU5)c g.6m}r9bMwM\QUJ9C9RHbj M{d,M0,#{oW+M庾tttE {,(in4³rZ4+ k`S^4i >@EH(Fv-A}eo$XiI ppKӁr?jtU }~S3`}N \ӓ$PP~p<_^w5rՂ~~]# pZ4iP(Onn}Y{u%888N68pZ4i/(c~\ UC^Wʍr'#k1ONݿ`}N &iˍr%p ;NO<'NnLJʓRF5p(}F;{I>/?K68pZ4iNO>1)GS>nh4^)ٓ:o{I>OZ'ɼHy<_' es;Ψ烃:~XozϺ*뿣y'kmq[V7n>ַ5o^p6NN3<7N[euUvk{XE_&:L=ӭ^Sϓys\;٭gOK\?ݲ(NٹvOOR$Znm-xs:zn/y8oo[V7nHǛwnM{8lNR&Ӕ'z#9<<|>.I`^w"oMӛ7o}U?I@y$庾ֆ&VTHwf~7-oDɓ44g(cQ<=I\bbiP$`r[;ֳ[q̱Iʉ*F@i$l;Mi~I-Tnrd#zV{9W;I9rEX9Ȧ~i{[wɫfё L˦vi{[wɫYtdS)ڻaJV Hw09<I5yM.'_R.rM<zh}{K=N͇oO9Y6c g,'(euo%tEXtdate:create2021-11-25T05:38:33+00:00/)|e%tEXtdate:modify2021-11-25T05:38:33+00:00^tIENDB`kitty-0.41.1/docs/screenshots/select-window.png0000664000175000017510000031560114773370543021077 0ustar nileshnileshPNG  IHDR WM&HIDATx1 0@ц T :ƄX XI$0]O@8`?{x}^?s =?|RUp.9v }ִ1Kr.& ]g-A# {_D^:TR\3砷i(Q(\yFg-4~ zyED!ա0+I NCy5>~q!a|_Tx( zUYh񋙆KLdDm0R7e;NR ?=jtbtR[㍰=|jam= >{$5DqЬY} FgŠѲuCϡhzTѲov%LWz n={rk%*/?uWT_(AJLgTƟgM퇳jWFQ_J--=@{̫ѵA+[/&(UziN }'GYI[w(hIX5kZI>韟 scyY^ʾ!Xi(v͝sw. |z(Q)(>={hh,xWk^ % s! }3#≤ID(hxCo<\y8Ǹ;9Þ"J0h$6v7NeX͔]M6~ţ\9wxYW2؁BKT3 l=&Z0DW7@<{̫ѵAorpW]wW\:WٍBw ){l@OƎ |jĭ{aڝ!*vMxgR[7sZS=FyЫGaVNA%*~6WkGJInѸkCWyDeJϴ,Qy|e6iP BK/͘UMy?Uv>֟se^Q(\`oWkc^uWDD4,Qڠ?O-g(Q8o^M߳A6wӽ2a_=9DAq;gʩ7Ie[{#Jcsg^@,zO,MvDžܼR[ sDм[7eeGL0 zڸz5^wE~A}yIq@T2dc8Wk'JϫCʵ~-++YC`?E'<(fWmM/ Z|/nqc;ʞ;+ZV 4bYKݢR3%]sg^@, zUWQMkh))mam^X^k,bvvxpR=ϳ.Sh$#=M>$n9Y93F IԟB㈁CKJa%Rڛ@@AaYhpIxdD^.Kw @{_@l z-]GweX*ɠ<|_tIW-mo$J2t]TEr n={F DnQ*]GS66xfLSʱsv5pGCrׯL5G4U-v=i\IF|r5I<`˾aWkCދ'E˫۲{0w xw3zw,ӽ7: n={ݿ+q,$|S X,N&rPg? + ɢlNWV7:QW>D}z0޵ubMwWfl57}d?ޫ}/\zڢRz !\kaw^{ j{W}jw ` lc\C > Сwy!*ݕy2zZmktL5y+ivA @^0^0z {Л#mno]\Q|$ISo:H$B_S?-Q$`-=r  y$+I$C-Q$C7$I2J$Л|;8z?z%IRʆ7鑽Y6S!IW^ٳ&8mmPX HRQAO Hd46mZZkP)A|hłG^7:&d;~JC)3yRך 'd@WWn'H$^P[%@[iLgv|Z_F$SIZ뾕E/(z2cPٿ>@nIE<]|fo߯b12c.ͬ]6BD@\U)J;|:ugS'.oom>3ٿSƞCm\Woׯ\Hΰ(-vlOq筳Mpܗ==JrKDD_SjM昙Ϫ ,nK[Ei'{[)z5M;3C\'Z;N7:ip_~Cުi{9K%TRJ#i![%%ԢwhxH}Oܞ0jz8Kr;o`8vJGQk(z5G[`c_#|o<*3 (c I&h$ ȦFfb4*ƨ({rT۴Ich?ڦ=ӽi= p2asw}w9s}O9sgQ5$zm{>˖K[\"%AޥK8Ջ_`Dg48# -2b{|D/a&t OG杙nly%)[Aq`g/{_륹DK$ l݋ކ '1)..>v8^̅ ¨gAAw%xDqiTέp8WP(=,(MI̐|e>Ҝ¢eWߺfٴinBب(-o0`*"'8ğ(.TmA嫣,F0dNi銗gA؄_>1 4@,n H5FS.nQe%,KĮ;gƽQ/6-̐4V0i|C e;3Jzw:GM,2ȲPʋ퀇ŹnZ U)?}|֚e'ej<߀ƴ&z9ΐop8̙*FX)t_T{1,2@sv TDV98?W.pHp߾<eQ!T4CKfx}y)S?sg[-6_5nѲP_ k[k4xGq/~qݸ;wp5oIs4HA Dȉ¢³βYͩlׯ\z ulԱcJK.K4mrj4eˋ۷w$$& M zxK v؇DFpHH];Qk|UV0$8ҊM i]  qwҋ#+ﺗO A7홉148# |`W K"0 `~٭A7kњ؋-{ !;+~t\;j\I +6[WSf&19ѻ<ӈlX8&[U -,1W)Dz#3Ӎ_f}Q傕 b!hA;G۵Y/ȀD\Y]&z9ΐopxyE8/֏2XX3b@-^fmQ ^ke-e*:'X[,/D 泅X9֗f!3֌( b3 7$kB< W{TqzS!b:M]4QLʔ*%m% {a\%@ 蟩7=Otueo<{΁>;w'_+BakqD %;5_VZ E&3H$( !Tt7FO4nx19U4> `KPY B"z6YY(()r8ʖ҂_)*jyGmR%ku+Eh<5Ey ǫjл_l˸B`cz?j+@G1@a5G16GRuSv36SK]"s!G]^؇2}̀]i]j4]Tt1F2z%ĭve^257abW/bs+c'nyev?g,Q$Dn']|^T$4X+: z_˟>bdۺ n)]-~32U̎&q]G1hm%]H63~I;'#4|{,70Q-xf@|Y*+3H$-Q}5sHU-[]im<{en̋0h T 7၀]Q{)װS7sÍ%ЙT462>بb΄!^P<; oJȘwOhjjܚ_!6g^2j^L^{gTՕ,( \@DD@DYDq7}dDwQMAET}ƸMK:N2T,=[tLTOWfK?.w#>ރ|:e=Ϲsw}WN#&mmHd^NrPR󉢆ؗ&gN484.ź(g;mû׼˜=zVeiri{}e)Ia5viָW௺:4wֆ! klay} y;C0w^:2|k:D>KdX,V y-~/^d"y2:z1Zq OCk,̠QmۯVTߒ)JlPfoƱ!3DOƠ{b*񾞢'Z5EaX,/IuC tĜ΂"E(.Qy甌!6η%EEO? X<:᭭A5 B 0ݭWn n\@-ZµId_ۡOE+bbFqW<'{k`y7IEFWwj S!3[rT ڪ,З!܈82ghETXbhQa[&"}V>3/q57@/<5# V$}w&&G Yma%x$wlmț3b-ɠx`deU W:y .x>JV5@C\)N7督>Wz &)`*c\@% BJ}]KQ7t`ִ Ee@Ц].DGH_˻Ir`a8u&ƆYϬ6Z/1 vJ{d }os3C\KI+v0$-;1$e pNi-G2Fs'[㆝_~!GZ&^(,Y^Nly?A0nuHe5-,Y,%Aowޢ'kZ{H-gNc=zO5+O2zTPaQÆub$Eō{ū7guݎmbk*fsV[5&Ŗ]gb >՚n7$( ZVS;- {j}TCL0_YWdݣ>0 B S$tAM,׽zOqq;~ԖY|db_#Ak|![7l4n=~[vTs:PTWG蚙*S1-ChYd ;qbNu7I,o1(U9p$5'tK_%ӂk;Ѭt=\ArK_Y&^546Y^%%Y"bĠ[A_Χ?[~vKt{夸^2(0_~;Ϟxzzuuvf8liv)2d(HϚ? 3E+-J4fP[Бj2l:X,KAor(*$LhKtZּ4ن8iʱj2[_>g=zAyWEEBcZgۻ+\f7Xy~ b:0*\/| z6wNAv7Tm8#n9"Z"v;F,>,UNEWz|;T&Δ)qsܢSMoyÚKdX,v-Y_- ~ݱ4=Քjsj8++ko@݇čyc5&ڙAٲD1DkFV~d[}̈HʓE@䰻8m$vJWWCk&ꋶ(z;}h-͛3b-ɠwDAZMŰ>nzۚl:H|yʐN!Q6UrΟ>@/,ml2`+W@hE3ԢWFd_*w3͊1Uk( k`yٞAG]nT}WL1(: X@?ˁپ}W(ňh4bS;6.jۍƈt,Mъ@'_WE4ې6z'T ~gwژ0?ER[ jC,A/|Wz+v4%%Y"bA;p.uGh"USW aR,rE\d7f8<{h1B]W*ݪؑ5r떺T4moHsOL.0fM)7Vo<*D OC=.y=wX,՗$^pwm éF5 r޲h`j%XpH.>"{+>Y$.Cf E2|fQT=G+;+F ,#bFi.:GH[{9:kzWxUt yPFfV:,9K(ʳ?ʊ[a(F^y^^gKiS#^Kt=uTk\Zݎ_ @6 U A '#7/F_ Z>6:B8mk拾L&=C:^$EHlu7㯃ܒ,bXA/Meeuvc(o?}\ۣAxokךڡK Yvf$m}ttucPx<*VUsF-]~y]bӆn{ mO=M59cX,Vߒ zՃA\RARp#AXFWdT]r+i]ue:@\/y{ !R\̲ k"ި2L?E̕K0xøyE=';8KW9%-d7tf͢Ar2%6wAuDE͢U-_JDS"("f&}CWYd/V^<)`#ToDŽ/*-L!?66usw!ˣ<Ȑt`ozV]ZE @ IkLWwXÈO0ES&l"C,@dburrrStۏJKKxng|bX^.]$6frɚ%6O3f(X^pdg>kGweۜEDD IeUQؼmvf QV'͘;j̑K+S,WT},CܘWVG5~|^D}V&! ~obX,V zyRDvp!x)d逥xA!T/c]@8JTc؊s!:kQVȠ0B0`yS^!|PHgIwNLxˁIKDŽ:cZD`j5e쪁\kPJ@WƬ's[ybۃ~ڱ8qڎc #@6 wsX _F-09E65k拰 L&;+..6By:w᜚¶lk}C];7m\ZVjsK>KDb}vM[ڶG^;b%k ޒ'+^kWࠠC\.\dds&l&!u9q5UYG)Ƥ >>r>hLSX,cA8ؒgm8M.j9rD324W. (~?- ;2@mF":gW7*w/[q`w  Oߦe>,n<\Y㍾S[f &XzC,@n~_|vm_y^OksK>KDb.ڍclLy)ȼ|ĤĐ!!>>Z5 X,AA&fn۱ /QzgmsG3J*Rf/FDp"J֞xTXEOc"%"G}K&B̩_SvNWX,{AT !(tnlvr|,EO/O%^t3V+f3RU#dM0N%˧='z_Eehu/AI7xajr60xrӸ wUdkƊZ<+Mp " vhdE[-F6,ֿJ #$ d{A/p5:R1żoNKx] v̐ |?jiDO X͸cCYWrQd7\@Rֆ5.]G,kjW? qΑb`q03? ֠agz{槼2>v,<{߼asK>KDbj 3x@P^m**լ;3hDms IaX,}^Y1#b"|1\"5vDuO[^58B/1{\a,*gbD>KdX,rOWϭ;NRvf'MR:+ޜX,ĠlBX]|#y!/fq*7"v dTs1UZRK zų2M B=qQX*Л{rǔ[-,Y,2]zwBfkqgƛ3bzIx{,u~YmQaH<24k/gFBdí>SgYm[Y"%X,cVWT[%[bK)]ߙbX ,v~v-4 ؘbFJ=BWZﵥi*%^}#/dOV^9 e)jjkDw`5sK>KDbLzK֗457]{v̒@q8Z{ qk%,A%I|E[]6D{wqUWDS:W<+y|h4$-4  ~T^-du- QOkKW^˴ ~Ӧ8o[ O @{=^ßwp ` B/?Bݽ-_GR)`^f・{Du ۛ(υB!`^X4ݜL6pSxeY ~X\t:~4iZ<ljJ%{n^/{D GQOY+ B\;;o{OWqe\)ҞVF/]  2C0(!/Q@gD$Y:\XE`D> 6r>y>4'hJJ\NN"ϗ:"S7t A.ݻ7v߾XIkWZg?^XެJJJpWQzz:B/pO+ t:%O2LX_r,G_U*[II3g"7NLNM74-#TWo .=QU)+WB+_ДK>qo}b=EUfٷycG׮9Om69a;!ZXy_Lmmxgg**ZA-//hrr_ؘM*..l6Jj zwU <"{} g=s0+)-! p$~OQn?Mr((x˾#G=dTZ@ٺYY&23/^v ܺU.>ҫ+~ƸC߽[~qND B/3CoffݾfKKK#p lUVV"~aw~ЫT*;ltdfh4KKs8\SÇo461/77FxO_'),y3DI70#CJrsǏG-[$gCooo`QQ4=zx/-},E{ [\[lVVbB/9&3Sj2IL?>=Ws"B!Vp*O왟ݾ855Z,VzazR$^Ћ%{}g-" Bzx?G*iN'i;vB P'&|lY'pȺpY@SS W. ª@Q]\v' 6c_\ /+"j9/^Ev8"o' :?􎍍э755hIt!yd#{\}gUwz;*7 *KmE΁ޞ^BHomᡆ.zS c}aOV{=濛8g4ƽz<سGJ$% am~Ԕ# o^R?}'ղFjh26,&BU_&;7X,q?z+ !eeQ饍v5m0P ċ`śq"(Uʊ !bclD-"(2HO*$;d8--$yߐORsp!F ~qي{ϯk&z.crN'}D"빿b2 *N^/ZVN|>/I0x<^* <4Bbs،腇pxSpErjR)?F* X,8lF, nfDhczz+J2^/d2TD"!5 BtFdtQZm8Z㟢vyGd㵽ȯ?7pE(4.nӺvj6ݶ-}`0 D^z E ^z/%w!Nʽo|_o#“V `0*xJ޻ ׏hͮR]eOfBI-D/f5poӢYa޵ Pfh6ӹKi'!8L>UZyƝ=ƖDe4MGwE\䨬BVηn'!¶8$zPbzKݸ4Ѣ!54t4z2 R$8qLMY%ÝӬY]g슥oio5'?…~q?*OӨ7)3P&ZJʕ+oܸ!JJJL8hd)qhI&sʥ8nEQEQkÇGk b´xBZr۶:pWH;~7l޽{U&7&4[n%󰁐 8H?-c_7! 2Ƣ#GjB3Cl°5zm %#cG'꺮$n777WZkkk]F޽6륿$NFq_Qr?H7,Ŋj%hRsZVOa@[[ڵ+ ~cNJ&^z4>Zq~}.QEQEQҚ3_tg_8=J$Oۗ)YbZ]h4!n8sLcU4wZQEQEQ?n_J"_>݌=- P<c,F; ?͛zٲG!z$~}7#ޔ$D\/K] '?.z㍧a̴PR ni/Kr邂QE ;h/;;XHZd.uFYD<`Cpp%I߳mV6Ţy7XF)6wf)8`;tLˀ'Zˎ;kHecqIl'R/^!kW#._ŗUVZ%-)))3Do{KEQEQ(G+2ApqsРA>}ZޅڢsY1N/pY|r((XR5X+ǙDzᮺK#}b)ڃ_lTZl^ 1^HjH*fڴiƂEY{hRVKRcA"\Y+??DbyM[PSLk~wƂ1\D}.$6!؉׬Y0mŊ/ׯ} رc/zVqƵ*,iiivfi;tÆ ш^z9n\iJwuusK=EQEQ@EDٞi8sՎOv߃ zғtC1^ Ȋs5޼mQEQEQo?ڙ4o6tJ}[GRW~dq޼Ċ^ aa=e T|ȫ{퍃LrȄ,58رݍ~D/Nhܱ!Y&33S%Z} ҍ8fS C50#*H_|IKQ[ } IKXUI^^^nBزe d"ߟfÇry+B ?}vU&h~/}((JТwUj/6 6,#GC I`G*(((>W ћp{2ne:}܄_~Dͨ>ϼcJ'8UUfJl'u^dEƲu„޴f;rdE/OZ˗?bA^<7nxٳg7WR-DzBZC5%Ui>qmʧțbbn2rA3gθ`z.yE3`#t޼y~-[x˗/o]#z &wi mKd !f)A|$b^bߋu_:((}}D#,yJ1ғޘD/oȊK-5Fnj((bOef6ҭhwg$?V ,O5ڢ6ZW(*z ::.m>Xz2H~(H,m# b1%;B^5K=(; u+uY%q5zW*@h8q^^z-Ӆ#Ag0uTs N by#lDI&Ig}VzϘ1/R:[.B2s̥ z 9nf2` I3JZ^=EQEQD/)Vx"JM {swO)=)޲?ˢ[QEQŞ,Aݙ8vX%$Rb]؁vB{y&Ki[~_gσ&)Suƍ]r_v X_//mgS7bE`[MzZEoRba]paj֬YNJÚ zX*k+4[ғlƂHG53>/Qh7nȱ޽ݻwo"}HPX.''/FF|=7 zy{2D þV0t~OQEQ% ;tƋMgßli*hzQV}eZbԨa*M>CkYc%X6:x={P \N!z-ZH]K_Fj?:/=fjϞ=[އh Z544ƍG%c.#oPPNUfjjS/ĉ\qP^8w7Ndm۶I\du#OEcw"Egk䮖`z}% `~/}((J@w秥5%sZ>ú^v‚m+vx#qٲGp^ORV:<`~<Dy]/Z8qv~p9cFVa`dofqdrrll*7{-"1+S}},XB$I;bZMMM-KW:{QM_hR@;"z%|g;e ,86_}aXd,>|86g9yKVVVb+Mi? gj" I}YFI b%5%fN,}E9s{W%v#?tۂU ƍV2T %þy/X{((A^BlrDF^i';>w݉^FQEQE"_jEqϥB(dEE簝O_6G/z1mA^"hHRbO#lӿӶLuVHp-Z^ӠAHhp^׋҇((;u|!Clj`;q~;AQ$5Hӻ֍("?@;ŋ&Rka]HV:u07 1Ql`Pw^F?2UYc ]j-' ݪ1״r A)m\2%/kUWWhKD۵O>1G}$Ss[ a_I^=EQEQ D{x9Ջ/h].:Quڽ"Ooc:^*:ŶfnEQEQDg@ׯBCtzhg{J3gvmS7l'0 ^T $Dc=R8pKړO2Dܯ^F3W?D:33gVcOtscЫWFy8˾{R&Q)`SB/lQP6ZV*-zjjB/i,eq=x~y.zxX޽{D̿@ ; {:O <{/{x( 7a%;&:bm$& Лo?[[[5ͤB/BnzW?^["Borv4O^^niK>^^8HZWז<ЛL?kSgr[B/BoƢ(JZo}'\niKOMDpצy>|X,  jvK["ٷ&8*YZ/ [/@"ʢ j15`r*"I }FuݻtNaR=6]}_gbz|'ZIz%bk7̃׫nOSI  ʟEF#bBZ[2KлGOQ7TdlXkqMx@EFN5W^Qr9ڜbjعR/UZtg6.JglwfU9h-%`@l7؟BoI-M!kn&Br zh^)B93~zv1uV6.HT[~Q![2kE4T6G-_=VGY{@4?յY.B/$^ B/ W|/u7%&Ta *[2KzGodlR-Խ;^_SyN zsxKlSе4s-Zy}ǬV7?sN+A !JֻdF9^1dJ/js3~0r$CoߑD_X;p֯U̿ZU3Jxe!!W@$(C@IHYaAM==KgvNOtcOt;3=j͙%37u!իWus}W?nPBD=W]r.B휚q\Μ;KѫR^{bNn.{ !BKѫ lLijWe|N-$`ELKEWz~+gD_Ј^9oɹDB!?<\83uUΘ![&&%%&&k֮1F\\\n^Ͽ~y %K_`>KNN>zd:4:&=gB::>/2WsK+# j+~W5&?x:|q,Y gOUp#[,%P 5eA<?,' >i uMˢJu_\&P^.q`r *agd$4FK~9쯗G?@R6xm$p=QeellA3ޭMmاM!5̢W}kk%1s޽)o-F#OE [qr8zusjs7]boh.ľG aaѢk\|6fB!$E0O?3ǥp-R[$Qr+ojqک2=,PYǧV{k"(lkClמQ`|/7;}Td5PVMID%b>PlEԧZ{dp64@)zZ99(N|ﮊ< ^MY/<@q3njNqPv8#㆐NFx;kc_qW^;kz-9H!P߸(<)(oPukQovyi"Ẇoqy\Αwlǟii[t1WTܳ /, +*Jm#YYY6BHP^}>԰1ZuԖGDf[lMGk#܆+e18 k`)Y^Y{ Es6.-QMs6jv5|nU){6lRY?l 9 U튒r/k(%st"Aam[;ű=,zυSb_NG5&+Cg@#}B{bDrzm+xM@[r.B'z57{|uW}^Eqyٵwz=>ql0,+ WʲknhcBg|#>B!DQ;,n-,??>U6bC^`BOdE]szh6$54KHrs4id=4NkO$lkCfF~Be:hֵۑwPd+&'FZCN>W_- zoUD( 1s_-AN˂Uqk)ɣRU(Ƣ8'΅6mGU"ѱqC/EwVF?tim^ yŮBᮬL%كc^yK%B!o$-'OuTo,zrqO,!_S[8}RzΙ7W(sϲB!A&z!l8⺪uM;fiB;uQsi7r}xh8Z?b{p&6&ѫ>oɹDB!Wݾ9qk_fG?r1sg5;sy9trWۉ%ev{7ā++o{iwsu\B!$Dwb\ʞrUbߔGuQ(FZ$ x#w7 z!c^ڀCL؎ B9$;Tцu_kg$ =CU(ѫ{ $d͆SThfl_E t;gV:s0B_vSΣ"؈4ral$)rj{k{ak1S{t.Z_vSΣ#d|t_\/6OL7W~W}ޒsBEEoii長zs|["zB˾^/()-Ӱ7'/WϓB!A)z^- YR (6BN9 0Lr_]_*ƔQrH)كSbtUc}, P@ꆔRGsOc`]VrJ0>g8<*P:=\Z#z^|X6 k[yy'| m FV?oɹDB!w߁޹lٲ{ZR/K+ػOfBQXSNnty>PޑdMnyu{vOL>(MR? XѫXdg;,}LV"n 0cLC]9E*\Q:E枂Wʣ< +C3Z9yTQտ-U4F9^OqC/+^~v[.H;>^nSG\"!B_ѻtR<(OoqyihjW"^B!DE6McJ =UgXUp"^xUS|ѻcDtw[62;0Ebm`KTi(H-5 W>ѫbVr8嵒Xի0Fu.zgbqC/^D #TW$yЧ.BgyŢ85b\"!BG[^Qx &ˋxkr?L5L@;?_qC4ّحE|n^g=>9˔[ Icai2<o+a/%a6GQ&Ҽuazm@Yu+ &gxP|a=QsI1P*&"O_T[dQcಔVYp.VZ9;"S}\dPɓgh^kid潞)zq~̯WrAmKkK}C+WZ^< -9H!PK^sa_Wrw벁_ `tS:&~ǎ{B!_ J$}9=/AY/Ʋ&_*Y.d!&9tPE/2*UX@"j`b?K\d}HmJ[!I *ΰ"u|ge1WԉYfPFO)'CHe̹Z+z-a :Fu.z@o}nոؗE/|l/򨥓x'Dހ+**:Y=⢣'gK$B(z%zO~ ׯ2͖WOOAB玸sDrJrDd$d{MPILď(dA!赁szH|Un?$b`>x:BIgB!qȥ k2X?mj aV Y2m)tɂ8E/>-,/r@P/'l{6F/̋\Owˆ烑Gٚ#A^ϫ{(։XRWEjܪ;"Tar5uYG@T<+HcxM$"kqώQH]ho>pM!dY]=/µoḡؗE/.x2$4ݼ"Evk.|YL]~O9K$B(z"z֯mMay г.>B!? cHXB"g̿ kk=%h`Zk۫ao=ilqB|7N:)鱼-9H!g{i*8]BA7 6A 3B iHefto\L(JpOQѪ? Lhzy8 7:9̙'3os]Ӓ;ĸ ^~OӾ<{2S?."hW)G R[%1]w‚~{Bo5'*@zY{Lmv2VSyeoi FQ4~GK* g&3@z]fS/"ph.'*CoL{žbyCu}.XЛΗ쳹lj%dB/KdsgO.m<*L7\>W~Cj {KD 6vᡗ_EQZl@@塚vkZRy%/|)oXO%W~*Vyl2wQTiKv@Pz =^? =^,0 ``@B/z⯶P\[|B/B/B/B/  X `@@@zA@@@^^Wmܻq&f%0^yƋWݽ[ސZ7]P^mOO7ѱ>lnr(Zɬx{ݍ%#GZZ3 Sp`5K/%D={]tÇbmn[OlD__ȢM3prj}EW mͭ[ Wl=u{Ua<@[R w(zm,ʥZ䦈/4@DT+V@P%oӮr2 3x~83gα&Z'y>{IN^\1?zvwI$I2$IRC؈gw>{4 zGًЙ3' hÇL=p_VrK@/hܺh[] _]\60TZۄ;zw߼?zhyod+I$^I$eloda >ҥ7'Sf>#SSG9o'dhLPmSdF>sZŋ)361AG˃q<(J^I2zXuSBdڬki)µIxWF\krѣGS(>AoM?̰V}1nW$dSrcQHI$I2$IroMz2^|>Qл=^ᇺ7>9e) ]'NPL8! c"M 1ܪRw? mYy!*6>zsUAo1}tʐ߰:!%/d+I$^I$7_d"2g)|a26nIz&7īQ2mR5vnsj\+ћ87 %bon!˴7ɠW$I$In=Mb bǃ |H$m?y#=uJo󓣢ĄAI/@ѴɫׯK#3j OO#*Z\%Ao~n9?'Mz9Q~$^I$J$4r,s'{s*5~=o E.߬!ρ1ҒNƽ^DkBfzoX@/?'VW3RGλw{ثKKyK<,禟$ɠW$I$Inm%+'oR(xCǽ{Fl'k||'Ĥ(}6%D^Br2K_{J41)S5s<h I㣪* z_͓۟<ٹq>?10ؘA,4mT} z%Id+IS125#RBfǓ'ڵjey6VVR˗of~D #*>IZd z\ir E27d6hzq.Tf&ė|7 -8Wi<ɕ0"\Y"FL'O} z%Id+I~4CNW^ۺncnbB8tT*1>G:9^I쫰6<*gBWYly'sf6t=KW#Gɬ*%$J$ɠW$I[_ 䕾6Xo6OVG!B  ս/{^m2UQ5(uܹT)^Em$)C,RS[pҠ z%Id+I-lEULL!kI{ϴfʼn){:/1[#ͳ'JV rQs,IBԤ5RZV坃gPEChל^Y؂sh%J$ɠW$I[c:olSUWu7r 􊄕I&&&})?/E@[*SeLM U=.u wELo>\v|A+_EYy~W1zXÇqkNJ^"#vd+I$^I$maСgn;xqdjjI"pǔYX>&v*q]*7k frє!*\A/ni{8?Q?M2$IA$IkR rY Fgߟ{FFΞ۷^'!H 9ü^Hb4?3lȿٻ0uQЅ23LAhfW5C"TPB\(J&7B 3l;2ppw\q)rr?Ycڀ8H{{Dfwe15*6Oߌbrzܽ [Fkf#8LZ/r-n&m Pwzh\\>r6]jIdIx)f0v-NF~ݹۂ =?`W;s揳WsVA+W&O󍇑e3=h7rMG::ҝUe{T ]XKY=@ kW'M{zgvD]]'ndF@?z@ =h@ =A/zA/h@ =hzA/[_?oʳҚWx)@?UuuuBNSS{櫅\.S3-ݼunӵ׊b>7q;of[?;KЋzA/[>"hd2"ua2"]n}Rsooqa|b<_.:3}vfoƒ>|(ڕ㿝Ow^47MOznGg466VzS By_o Gp(_l6c@ ˗Ζ~sAQUaw>eAc9"FIDb0&#Z$ ,*e[4K2Ҭifz|3;ck\~s{yǙolVY-foIMwTUP`}ú56H]S3ni|qܹ<?eIoGUm6.s+81: dId(2Gt! "VW&: uKJwBF+RH ""#6&ă5cVe.[XOnge` ܧ^=pKVSX腎D"-xj;m]\]B!dlE/=AAE/  r?/}w92"wRH6^z$+IxBT腍PYSR99O@#AA υGXYJL}  fc^:B]1E#Y휣-= *oMJr÷MbVۄ,vp@A J ww$rnXn)Un4@ɽgǿuEnLDKO *iI\fVS}DݯƗyl>g,D+_tmȥQ3=™_5fJ#h UH~i,kxj% s5t'}ÂmB@>0(򐂂T /%jJSS袷@ZW_+DDEe;","+Q"j$11/id3ݝ鮩+IM&;z]3}w~u*{.nݟk@v=~,Da+*>H3;_pHHgTv6b]  Z} %ީn̐_.sKB?xQm$-K.Ō:NzLvx~+ҥjȞ[Lbl+""?ÀeZzŅ 0ߴf=rAf8?Q}T/xh7$?bpiWZȳ*^6=Ȣwނ(h/r7m!;:'gJ9&ykͱ5))z!@_8Iy#̌@=dD`nYʰ#EO;-+ wBz^m{¹E9ys 0=WB&1,m7ZFl6nxajgHAA$z G%Eк7@<[~ه|0[VsC a/\#*/;#I(,kU_#2*UƗnprCD\TΊEZ + 1%TO :Ȭ'8̉epplٱ;(`Dw!aTb:9ӊ/GK!X2SBygCĖBV/3+էÅwt1 !A[P?C_/y櫾 `u|QeQY/8|nMdA] pȲ\|9w{p6أ76.r#-g 6Q]e_n>'N{UTUbWw譕D/=g%Os`٠gzޣ@AA ªD l?rB'*Bcwܩёtq ݄P]f6K4U\b_j:8ґ^z D/L=9=8{lmQ0⠄'C`fȁi"s ~6зW!!O&T?. v8ў" +H[vGY{_/0p,Lj Eo.mk񜃮^톇(kזM2fH=AȔEE xYlӹ^X@Ӣ7HfyV0qu;cDuxD83 5N6#zorV_C2 i;nA:^ %6?=!t D/AD>(p454u߷znm.hCa_ lTWU=;ٝыLM: EC4.ŗwa-D`!I{Pq@WZ#'M/ ~b22}ԏK}gC'fHf{U}nW?Cuԯh?_%&|R+{ڑ?o-+) zNK;YoXXD rXFt>c{ӢWQQQ+M%FcT63΋,vfD8 ײ1,Ggf~JJK=$z 9}?~ُ#A%6)~wgJ{q/A]/7NJޔTWGi҄|9.seϨlmqIgk$96}&,)DEif!b8E.bȁLC7j@&ve'SѢrIe~\3T?a5*>G>Ǔo,&җQV:KfF(㌒|aviI~5:ߝ$˖^'kC0 m&ښp3ƥXNCFfk1t ;C%0ܬ~%K^ ]c+{DsLJ`Qךn ,O+LTrX. Lhtj:dxbNoE/ʼ탇Vlt/ޢv.ܞr s;&k99|՛=B@qgC+<΃=rJwUghQsYj4ߵp߻olϟ$ ʢY^^·"߬pn~s@?{$f4V;߿K/^ht&r/D#'zbE/!"L#1`,*z*Q103dو~Lv;ՠ]^0K>C!<3)$%|Jwgh%`nO;2m۽Tz۽yv_"Ҝ\ڥOEǐ. /¯ $Pף6_n."Ǭ'ZnmmqiWFr:PlsA dHA ##6z^v+Q;n- ,O(]0;:4Ʒ0Ί4\Kz=V.z,(zC>&3C5ih_nbL˂^.jʓBY µbDT*of (Py_+)Kfu-9>=zu3CG Y=F`Ir4dyEYff6H 4O&&lM􂣍 0ҽL̅X>f;X#F`֬ª&=$z Lm<%D/A}e;TgoO pc'V9+Ӱk9iǶ]'L濗w<1aRʨPl\p-b&ak?[N݂QV?C,쑢7'/Wo$&&2mT.gr/76:::))ii[j@^,O2̌#RBoΟsnb8hydQHkW # .4 9kfzz @Kc&Q]Sמx~Y# nVaCqjgHẢk%D/A{~։^Ys|[zŐ%^ DssE.t4H0}]dwvz yDF-*z'$gΊ~B4Sˡi+3}.@Q* ~t[_V:2zGY28]%{gyLZ_N?O%Xlw|q+WU96-zM4fŠ**>'|i˯^6h%;&hBlE [^btK-!֛׵gח?z;=CcFoظصmA@$z ~D_HA _߶۲}`H ݍAX߭dLY>^_wk}jꨇ {68b2satN6K(KJKPv] ȕj0G3Ch?+ע)/ab3[N1ic| e؇eK/ 7.l75㸐MU^0f녝E;{TqSm\+؂XAFFaQƺW]Qx&+0.]**2t]Qt1$k~xe><{O |5(V:TqJ,^NJ1*}+5;?o[7Fz ^͟m]vu7:5,Y )m iR8|W)n^*xnP;؎đFOJiYm[*iJ)ϫA􄮊+^*n]]nuO4/VsROt7ٔZ!A/;Dkc/nox3o.E>¡kxz=@ ,Rj*^mk7[ױ|L@ n5[7_{W b9vb#3q+qY>do]}n)) "@ l0uaZYp rk튴.x_cQ7;DI-[_FN ;w3"J[cm@ H*zpΕG^V+ wp@MA+f铻놷4G;=r_5=kXPl[F`S恋RWzBW%:^3KTD CW.Gt u'^.|8@{vJ {wZe|gӭf{s;mEX9mKH{cFfFEHL2LL40"mΝ Ԛ]tѥ͚Ne7ng7?~{\\f|pR^ j}%ZOOʡ E!ʩLw=kKP^ t>vgIS[Xox;5f ]<13{̜w9緊^@ IiiyY5yVss]ox[Tuʋ6BߏFf8 y]<}i@*^, >z+l#mi%=;M}+>)zE/Fv,uyÞd8 `{n{}L5AŎ`3 ^Pp]%[W{ğ\Nl,m 1S/ (q:jsÊ^P0vTzz=gD&5гX^阍+ ޴ xl@8|G:ޤaw@!+f|lϋ{_Xh'ʽ2-˧dBH#W~gtE,xxڵ}}Ë~=hS;+JqimO./p??.Jn+nSW9[\ S4!gE*E/=HO (z!d\?k;,`Gwavz:gG;҄ͫqnkh4А//oؘMfƌ%iiI;k ]Ō[TI>ѫY_g+H6{cƪ~F}{m+)K!A.O__kqO)HN01z{{7(%.w"!wJe D^5wҤ0&`6 m$^LşleeuV|l߮>|OWE!TbZp$D/K4ß)0@൴j{cD5cUA0z>8׮Awd*&RО>\$[|de"R3s^wnF9\h=: <&ąhA }PjD % WS"lWS–=yv%8;a_*csQ Dc@^8#Ϝ9]j%f3`@wBlmܨ?xpA.ɹJ̞8uīWҪýzË}D6O U°t7[ zx/Yc>;!.ڶڐ_RYtR?L6K6RGR7sѱB/O4TԜ s۩s 9,HT+cydMXZȏ֬}GشzKXJ.W8bfM\ȣPQb|}X[a*Rt~5wGm4=}iIƢOE'rTP^7T!6̅0 ZͮGhψ^b#-\-Ws*zwWzz{lfӖ7e̞KhŋrkY z`6=+0@?j⭕V)LރV𗴢-[w+*;E Sy>.g`:ˤ`+ &i]VKY 'ۅ}CxKMr~][yaRKsaLWSk}|~3gϢU*mb/Iusi 6? D2{>rlfii.SRo[]O˖ܖ L-+gPB21ee6 zxfr^yB1A1zvFG&̌};ZwqPF8QT %x=3LQwX [N KJu,L۝f@1;E2 aL⚐eױ+0s%?nE%&wApu3yFT;Ro uYJmz= Tӧ^Z6:3ѻv-~ zV0j ~ 7fJ61A+oGG&m6tPk]% mX>3U'^D#z~ؒ| @|l&1bUT_1[CF1ν~M(e,Ÿ04͚s%Z8K& `,c7!t߿_B-sT zfv6.Y:D/k;2 zM (pctAR/>1 ߴkNI<# HZВE)*K25'/UWS5_g]UƟ˝V!4⿛w]ba7*1AF:ψ^bҤ0 Ňʨ3sVћkZѵ11h^ڻ~=D/QcY ƴ^|a0A+} _h[O7mT% }ClǴZ흹 zLUr%kBT:%rʮ!(,9&)efIy+)KgqoMN0:s`WynUrn=u& CR/Be&zS̞ǩ]g^T?qee}?_νJǴEcFB\djnZљksHfFi|^fIa1?]ѻ0ǦgetBZo}pYJn/jv_Rt7g"XY.O2{ ?t۪2ud838Of+tsg*{ㆬUxuoWobz>Ʊ^|쌘{d wJe)SxߛCm_Vo0H.pp]x@EW(Ъ6 xE$eȞ9 ʚ7eX,mz_ҟ 5gNX&z}gx _|*}"mUI7}zL 1>-͋Lή%RƏ>SJڻs'D/wv4&6!z:B1A%f C'q鹕G˔X&5?(Hv4_0'\#MJܤ2́ !JY:L:l%%&'xhv^ǘ3JqfF:DT-"B& w1W OYOgصMq~d/złms?"lrzR$ 3eѸ{ݺn.M>jmF5X&baǎqm}W5bDBȸ@ ҜqD/f/O1AT?u0K7B;]ְ@ͥeY}wLu eΔeg2B4)^q]{z &&ƪE>6ZëזA|+A=pՅe,7s“ z$̜[wD(Ѵ4C^K=Q>lD/Ѻďءǎ< z#r#1AT?^N)L9dsҽW{hdͩTָ3W DmzDrdIg^O)BKՌf.N@N_|{x #p_Di*ɢw F)(Bc.^M)Ѵ*0$]c߈@v]V:L_ݿRE X˭jM;YVVd,[愌 ѣʢHJ2ѵ5oڤKGۗ- &'N(@'0,3^D/"p'Gf̰Z~--@i=eR./ x sBEuœ0 ӺFVi!ۋ{Uj(xFԑCݹEF9b63V4@HF_]\Y}{p`쌘o=^D/ ћ?Ŵ֐㗌7HxʞdD`Ye*1UzƿI_꿪 xpIm`$b웪/atgvzt zfqZY f/!z'PBýKMZ՛Q"iIԇDx! 1΄o =)zJ_RR5[XbaY mak~&]S ߞyqC"h`ck=s1޽T]qz.=(r DLB(\Flj *SlZ5H-j܌FL00`%prV[Q[& FB@p3y~t= _=^jg0'w{NTWRS1j0!XPBf7vߪR2=Q POk9h=$D+(zPX6Nae<ڹ&-cOΌ4,N_8eY~B0>()_BPQU1ᢖ{Mvs:^E/EKJ)YCAՕ6y#s?#/,(\s]`{о C&Q䀿2^v{5G@ >)#4io1K''Ѡq+/kz-'(x{tEKyIWڃ 3^Վ(F w̌SRd\+# 6ɷm)0 P`@Z/.aFjT >{bgƷ@/HO@ `Ii&̺Ο )/Qnǘ*|s+//|ߧ(&贩-<^n f.x!S]n @ 3̿h|'̸B L2m+Ҩ!22>?JIvVGKv5 Nhv \5zX^<]Q>Rv6$21O0;?J4^U;( ? 5d yf[.E/:۠[{&dz$5Iֱsw{W18?>SYdE}'1@9C԰"E/KN~vYW]e[b%dYnhm. M{|O}o!3ޛ/c>tR E3^?^nDkXו `@ ߮8z`i9&^k ? @SR CIwN'%$UWDSCF`3Y7ڿ?pޮY,36zk_w%Xpv_>E/_{{+E/^r8ArG/ۃ Lj\h55M25?^@ ?;{Ju^%گ&[ S PXvy@ @~d ^?_>nT;0)s3e{e{=X.}"mm,u ˲tL JE1FE1"Ebܨ&v#Mgٙovaϰs<|eSLN߼̇5^W]#!|GK.#GY͚>f̰D@}V$23̨6xSv D%\PB,9g4/E/!X,*,q OҵS&AjSM/E/5򘫣!&`%ddX^rBK:f<0]:~yڄA_B⏢P'|]dg戩*lHYcц'rBK,KJK%46y}?9@^}&UYB⏢4HJʠu .F㝛-m4!4 %PB&^8eӲ 593K_bG*ۛ^60΋Ta㒯6^^(NǓs>^ov /e*n‚G>=7Tzs4~aEIndvxowJ032J7V J0<եdCлpΗr%Skg35ú$yZiXdԈ!ۤ F4Pc44cB$&ԜP9GLn_-#gަI[&F+ :f+# zVh9i7kBpeƀU6T^|=1m"[ 0H̩7-:$ lY;C.y S#{W,<0oޒqr*o2{^;%K-^Bȕ$+K7mß{&jb=BBLO/7ڼS+:laXu\p%KO_)zۤ"^Y~`V`՝njE<`' ۦyU1{"";]7|!t.ƊL|^%h8M^mjf7 @_Ohsy/Oz[!$޹e7/~t4uAR>(W!ZwTaˆшolBnpwpɆhWbVl*-ᕁ/*>zwL_1/{XVKU+Eqӊ^lşw B.i߼NJK%\iz_Z- 7^tH!f{2;]"qPк v‡yb.W鎳33:"-2B,'uVnwO(X~dm[ /dʕ*;*3Kuf!wN:!QEs،`S5`nSo)eܶ|Ȉzۜtf\֌U 2 +aE uHd4󄾏>D 7Nɘe|]hcÀ)&/L#3dernv/6,6npeÉ"p6um tӫ?<5$?I#Chy/ cw|xê9j5CRsZ>A*iE/!NN %PBΌqq+~GL1+SPUI< PWhpK(J •GE8fԨJ/GѻeԷ i~աٕ֕D<ڡ1޹m|-]͍! 9WntDE^4󄾏>Tk@m۔5/I}}/*'[q J+ucGGɣL`quc{'7EY= ʻ='LIQ!X=;gTAmHŘ蚉 BK!!^|8BKxЋn dwT; glXf z ]PV~ofGh`J UpeNx]KuO;%C//qiB/yLYPRV&u]Pƪк•i_כ*~(<@^j` /'}4el{B?n)$wIpV Ā"W#z5[WU7^jŽ m9Fh !K/zGv#]K-MK(z !QEJK%\]^vG ЖEy/5fni%/ 6BR緎NJ# j~q#6~Z̨+o‰hi[㋴E};Q^B&^8KJK%\}Kz[=7dgֹC*ӼR^Do' OT{FJK%\XoaMGwz様lߙRfhfx// EOBBblpK d\u%.M S8şAmѫmR{K{vn(>!ʸȝȴp藗깙uY0Ox{w%ӊͯU>_st/wQ_>hT(#'|NH8ŰN㟬/'}4atu~eq +KL7nQ4|5_O)MңA&-tA':wp#^kF"^^BK!: iKS}~R+@^BE/!,I˸ q 3^9 CH;O|4N- vU]e{4m<8NA6E̮]VsbŠXu@Zx(F9_H\kĿvSr-Ŋ͈uet+$Pxӧq 3:?a|BR精;FՉ^t0Y5W 29[iEH?$?fiV7D/9%|{4o^%z::5;E/%"&-*Ew(z !k!A,XR%T/teV"@ZcUGx4d%>ՍNh| V YwN@_t4Cm``Ycel?r&'] Yz9ĕ~GϗzCο8 O<@uT[s\TqD+wo,NJyןXnA1F/[*VEE,P*]#i ;@jK uGGEAzB@_K zq ۄxr/Uxu %Jk '>:f5oONaWUE %}:sbsv*6|ІN]_@K(z !!tjQ[S||ڳE/!BŹ{[DoNSB/8"_6/{Ʌ |f^蛫hC6`7arHUuOc뗑'焩$r8B_.BE.nQx-"<7'f€|6[6cpRg 5riP?^on ;c% L 2tX\P,Z_ZO?G 2LYD/Aۨ`\ mZ<[ _(( zTm3ÐzM_o1+y\x$RyCxW>&M P@ӒK{2uBP4WDEL&F ȹ35 zZޜܱ J{ѹE/OL#(R3M6lPUJ=Ň=B^ؙ*ϧJ.~HqkS#G{Z)k^&UVZ?Q<b/?ۓiЄP1ŷ !(zPZ`4E5 o7:Us|VcooTe+xE/O,IۑڙiЄ7JS(z-sb4Eow ȷk՚eGY5?Ow.Y=n֜Opʎ cE/r&ڽwψAw.8E/>SJTາȔf)@Ysh^^{Ojxj3X˚;5@hWgԷ9Oy)zPջgȝOT2m׽e?gcҡ0hlCwAm,B>cTU)ڥX±bbb/IH{=-3;F{ǘ]EV޽y)zP@2mmƒ?buV5i?E/<L>BL'so^a~k{ƚ kzd{ޝffrʑ ,--kkx`xΘ=S7΄qޮl9k7۲c+fΙYY]Mң`NQ0 ]$N3ɍNNQ0:; F(:`n Q0 4@}59 ,=ld.̹ EŸtC-(ij$H*1c+uR̆5|$U:[ND/p8 . 'zsN6 |mbz|XاmCwގ4.MeO eLTGeYFQKY>,x)g[I2&ˣf͛KW.CadRJg0NWo7\ &Mգ`NQ0 TeϮgmJ>99щщQ0 F'zG(:d+YolC!a$@K @D*T@VKe)C@C Cpz{UX==tz;?wr3S203Or=O~d̐;7]mBVr'p)^-`klA+_x\*!s~9ҁ8XGHô8Mo&x-g-aw-[?m˱vL ey*Sco,{C5^l}̽V lm`E:tAJc)34%hč@(T:gwCX%^rI _I'zyX\RL2Fd I-.?H}d2>K2gC`sLdfel=e}T@Nbs@@z]?1&w :oɇq]G"bX̍b=&/Tڕ嚁e9Fwśsgi9q6IEY/J΅-JS$O ߂5>7?)l]qdΈ߮il'?9,% FFދ]4lIţ3'?dv2%#Fw֎p5oQ ^Gɡ1ŢγFG7ވ\g''FC`Fjs>Sjv 3۟ڱbՊꪊ:S6ءbqnZnj,ⅸb[NbW=sAB N<0OO FG}fM+d^nr `)kkDXj*畒9G*VvKZ 2{NJ=&"e`Bwjo,!sXJOLWvDZl+ )fH!dΡ9q"ۛix>$ GVI^׹'tu+۬;t7uh^B}t 9zJ_ʒ%]N222po ?FH"@}VM^8'tDum>5 ՌI|8PY+{r//q'S@Y2gȚU$1/+`Rp)[{$d1dۏe};uD<[-zc+zY+ƫ_<3-Dd*I/KEαBK(6#:%;]"IJw㤘sxwP-BaX9%)cI& 6Ӳ&ĩ_lv7D/D/V'/rԺ'&fe~[$圮*8 |*T?%ywjvbfp.fD/WVzS%Ǧ0Ǭԥb9o.*X6^MY3:d7ɡW9^fgYtd)͚.J﨎j)MB.jG==gV0ի*Gx$P>1Ȳ~966q7@ƍL@ɘBSwi Q,%~[ղ{RԸIҫܦɥbQ""^^D/؇v}U;]k%r$5mފX\TƆ `rgR*zrT?I|[,_\Z/Vm%'4dQglJS5KtsN~$*LQ.ox<,'Ks}we)qp{KǛTyg+/LkYˏM5ϼl5)w`?Siid-۷59?'ڸ8@AE/;]̫Y S4 z}X`0AB z>dfꊗN ~) 3PC.&n"uJv Rpi058X-z,z+o<ޙrQJ 8֊a\暃5'CŇ9~GYwܲ/gQQ3-&޹}E/O)r-֝3YeWy|WIu怹"7?rHN2)/CΈ1b֦R:zXɜ3tBB4 z6жy_l=@D^qO r^n Aep$=>/e>/-\}fZ9]EaJ9P|ÒNѬzZ7ڪ=51Cim "˴=-빋/ggޗ^K_LLE=V[[ +ɉ[Z7=& !"^^D/؇g]{fp2刿*<37vY1,S94*Z0Bg;דC1 k˹zXxqSD/h t Kˆ aeUk UDywCIx~~$W ܟwLj\~.=a_n2BΌNbE͟ǹlyE_9qi[4*~Ksq-?E?Zzp7^zBaSN.\RznG ^@0 tx @B z>L מyrpӉQJ>+kgFoߚ= tzoO]C}ʜ9x\75cawB v 3#]~~15^cOQ`=9}wJ'^^D/؁d]~~˛&]LDx`#Nn5,5D/D/MRA0E9 &׉1 ]b~VψhV8GǙI"P 1 7AVA( Af"heHD((ET{[o3p3ٝiyXQ{>89?^ 6^zV{#u ~]H5tz+<W6t)Շ^zVkuY^:@E[U.ϥ8ߩ+B/@mUy'k9BoŞS7/oޓ{b޿)W^hwj4Abzpɽş V0=?=-υ !W^+FkIyz++=+rUqX^z֮X=vM[??@&6=`u8:!B/ U$jc<kv@_#U.yyprvMI8 xp;@DAB `$ թT[uzJbq,R5 ֱut UVi@fs&κ<}yߝyeڳG}nte ~ѳ1m W_y'9KB/@^;~A]yCh|*]`X&T% ^̽#@ó#EK ~ϳP|(v=s8YYv˖P\Lh9fxQS&pu^)͇/']I%6*ߦSj3ZF._y^`H Ʃ ?s8 4w`<ٹ)QD^"Ǝu<ww?=^o8񊱥6!;΁ ;G"!z 7ۧBp*pD>B/BltXɜt=^ Ԩ[90:Wgz D>]QhB>r_eŮZVWꩱQSbEzGlؠR_~-QŕNfw?V;8rf]G( 8ی,HtȰ,TEH֏Ekdevj4e[f5Q95_ @08hD0mB/6?<ܹ\tu:ᵵzöo}cVI?%K_H 5>0|zzp}T.1YoYE ][_.ZoOg)N^$5OW^{2,;N% `.hs`(V^2^3)ւKCCgpmi[f?kz 3:dɾO0ğj[+",(P׋/W}1jI]{zB/CO_4|y2d~,NGٳTYN~ֶ({=>K]YoWɝmnƻgn3@04͵ xr'FD^"F)47rg/i\JOFaزb^-~R)ӧ7zSY"B~G =BsW-oM&5*n&wm,l+Jҏrrf `Z1NNyTKB/\0ڑ{ln{ZJՅE^ۭAoѕqX>Bo 7B/x`_\ߵWf?j4azӫޛm6^xZ}4tk05ktIM(.z.TQ+wUj ]xղ`]٭8lA[I7꟟67O8%lbmaRB/@}!.O? ~J:},t'=e00?mڳGg芊W\as:)S:k{0^*aMTUj޵KOVCHVf^Fw´)jnk>x]Yuڴˍ--a^ƀ̫_96C ]mZ{/9) @9x9VD^^ؾ.ЈjUU~VYZDcv+@e9>=l8{V_vg-w-Lv?OWEwxޒWzzmr0fސ$"^Bػ8{wǃ 45ҐؚR(SQD*"\M43|(tZ+k=+Yw믮.QLq~h?~}y{wp/6x2X빖yW\Iv.?PJJxbUkq3 mʷ3B[S_K6A% f[wsrr{>4z4O z'̛h=3䇒oZݼ@l^)˯*! vHw* &7(++8'G.-PYչTBo=3 nRB+)~AB/hѨ!^^TS:W}ܾM]E uUs? k ~czU}OztɁAr)̄^B/B/t*%*|wpkOşpKJg6ZccyJJ8 4Bo͏gtUYQ  #[eBz бxkݮSy@x'e&_>R$Z^^!7{s.`g-Nf`feFqR2HA% @[. P:geǽ{z&7$J% @R-Wz=_rRy@y5%τqXWdBz ؔ/S+|vF`@ojqJaD˘䨇>ٜ1?۸47lmAhA{+ҟ\Wߺ"vhT Wub$B/?6n;[`]P yEOԺߛIq=b2UC^B/B/6>3J_^^,K1մ:?f[Хc-tN"->y?ٍ _թ mO4_mSA- 8n* p$7}j{,DcCp! w̘j+o~*/##GX١}d V{|fTfOK@;qCj+ |ŷFLJm6Gl xQB/nMj]N*/ЛRET6ʇܙFRlD~'v zz_󲍿^Tz}g ҆ee^ 0r K@?׋J>T^YYlcNN_0oݺq9)WYXz-!ZbеGL-uiUx@dщf9 _ 'za-JGvT^rZBy(MQQ?'<-Mtz=;TZ]//+s?˹*}Uk8$, K#dfͳ O%] ş&9i1dfv/kʀJ~ѼLNȘbvBf[T-.G[P zq$K`; ݧI2蛲Mr.TG;$B/D2f[] _iO-Z$z=bRUio1uf<z}4:~_z{#Uyqwwve »*# P&" Ddp. ,PombjkM6TkԤZmIӦo ݙ=q883g>!z3g,FJ{AT@D 99(yW^y?^ pƳѷU5 ,TƎ!U۶9/'OǯQ]}#i׮lvD/ƥʘ z D/:R/T`no޲̙KN,޸y޼E\Rv[mq|ڡhYHER͆ }Z;zT'lK$2nezD/}D/ z ?-﷿r& k7ݤLCI/X_]FŶ[]O󡬯O;TA@қ"cSe'MRzhQ*#K I)*kNu2:>wId:I=;|]K'kgb9z~tfv-67m|T WNұR20<~ƭLɱ~@8/ ^ٗ0prm⯭364[48@)Im57jWլ\t67 VurZ< +jc~X/4-ZME! K^S\Cnʟ5kHt^-Ho;NDorvwա9TClWn+ZҺrpOʨ'CUWX U Unjq˗+}E xU4`q;^m鷗h9SfuNnZPλNiZ^ һ1 ;Of4yC4FD!^WMWm|0SC@jXd@ _?[}2&vˋ~gRTbq 0ͫCt(aXkgW)#Ӌ5I JYw̼CkgBgE%鷗(ʊI{-Y12Fp^ oy? 7p3@N)O"̘RPҬ^ حܹ T8hYPlun mt>q#h~oUG9>]y: EmZkgg,Vː֨Q;ۭ<`(zuےR. Y3hez!D/ʿ/ )^@@p/?[YA|XȦFn͉?[\bn'rRjڢ# j{d%B DJ |_:sѩy]iCZR~:XmASi+ V,tOz BgQԆ\>2D/ z˛忿.ںrsOllASއeQjqMV~yr;P;sJege1{Z,l7cѫS:ڵSw4DV+]{VKafjӯG]Nek7 .cdܷ/[};ڡ7VA&^ Q.^(eڕ #l9+zvSԑe㫜vihYAEz"zϴzwGZEo@kZi5/2n*g4 e@ \mV/ KƎƢA-*|lǿˎq,];{tzހ1-q]"x죏*!@x kӷh@鋕XK @ty2Z]6V&z#X{~Kʈ^D/ z)^@@9{\?坒[ 'l& 6oyѫ^F}4>Y x\=FlH}+mٵ1z_і>!zk>F=c 2E+kIeO\|vܹӯ^@R*^voi죂~۱y] U= \ēu(Y^FYfH)fy,g߽QȗDBdP5{~mJ9hԬ^-mh{6,[fyxtX|6* & ifrVf|"z=D #5C^\׭Y|`4<J*DZR5k:ULcǎifܹk׭Sf~--W(ve*kҬwu̜)C6a,y_b.7JNTZڰDo{"n/eJo1TU? X n/6!Zd͵:^@RGS) A絯< QS*ǍDOQpi6-57HY$slG3f(?0ڞlInjW~8=JOӾy`~'t^fFa Eu>'ҫyU<0vWخQ|m@E/]@aޤR߯JhE\6kVN-^^R;ޒٸ*φyz-z0)XPvJ8`6F'>]6{k Ib8YaR߱|u35kl(z͍^ 8`ʌ̐G/z 1-nSKm=9qDFd'={k ŏmd9mpڍ zz{hǎo.}XϽ|0w;G}_``^K^ uۼr-˾nIJ3B/D0<(2I8E⒞uyz-z)3Ãw˵n]q0n۳Ң<̡Eq|MzKGGV\;w n:o5)8jvL2w N$܊A. }v@^K^ EψդU{w@M6^p"N*nTݽlddMm&Uه] B/&nZyI~[C%m{ B/ ZB/ X),3Lr@`<@cCo@Qx&U  AoPAˠkX.饇w!dfwTwmQ892-mu.T7{ZqзQZ(NGBKkP_OLIq%ޤnJ6kRh4+sBKePB(z !NP63F감 ^wB_ vV a%dDցjɀ?mL^20 cW"7W._d;w*% L~>>>x=7+mb/BKePB(z !h,gcmx';&z?+ldd شI^FD{%ZDoj *?}j!4F3$EoRC 7n9 ^t;;k|<0!gz ?l=E/Q0Q[m=^rRckgPm7M^w=xP&2[^y.#sd v5շt6\l,,.jPNF?ThCӭɎOЀ& mz='w$nTSWTTU&^BKgn-ettB_4CGEoL8Q~k*c;>~8Ebc>jj1aIq\.=zޠ&E/Q2aÎ8A(PDoVvS%O9_SeL4)|;B1vKY/7_ڒKdY!\p| %%PBr뼽 M~kv88!/"zNժlSQlˊ^F7K0IWv$+{mFbݼlkE jvXIOQB>ՄPB`PB(z !ܰAӆ? !|7p @/a Q*_oTq5e׭s,ѻk 5ݳ=4>KL!JBB]5RLK̛7NRKhBàAzɜEndr-!KXÕ4>i 3^h7ubqv/^4qD>҄PB`PB(z !Fٹy Jr?&şYDÇVb7'gvoܰp&D/2ţD`}لbZh/6C5;DE:',z33eD/Eak1{#DI[+G'\牣0WF{F QG˶D/rvYbm$ clq | %%PB2_Z[ˆM!Dop/J/wڵDf;=z_mFы j;(CNg}8IreeDQ`  Sx39CDf|!Jߋ²2'Gv6^|lkK􂐐K.~ c#+;3!P2^B){(G "Ǐ^:q8SS$z\ol&xKb`IHӇXCk__:=74T'l%Ɂ{qY^4bIay EhNɓ'ݺm<[Q.>Qzٲskխrri ^(ETTUI&^BK1I/?ʼ[oU !Č+} dӧV(W+ܭTU9K;j&bb%zѶ.11,oI!\jE!mcFngdnK6\@asʚH-9]ҸJac>5Ȩ(8LJ0!P2^BQ ?yfɾ&gϬ$030 ++֬Zs8+,sٺb6iQXZ76VlwѫM'qoGKݻbeRUm0'O1W_!c†OnJ`E/VAZq-[BKE/AK%3= bb93ׄ2آwEL:dE/.bj/!%zȋ[~ыIM{ A,a] nH`[PX &O7Rވ) z띦+}nG^B(z ^BE/!DbBcЍM!!z?yۄPBE/E/!K_kG^&??W8c__/!dpҹHjȵ%/r8!g.8F=)=--rl+82㈒h1&@q 2arc]3x!xa:&sn%3s7Nd3m^i9<^A/ "cL\P8[k-1$ .jH1[Zg ʠ zA/ a7կ jՂ%0 ]Phn2H]u7)ƎР zA/ w|&sʬ/$St_ScEwUw 7/rxoiW zx$C(gAb~ һ/yld[+^z rAb~:GAB]77KɉK!{cYxUP3Xqz-`j `K#Ot^r̸^kH%$}'ߑ9 zA/ ٷoչ-mq 7 HٓZժ1ߏۇsW^֑)o.z%wWo4Z+ ` eu@4Zg{^*^Gf,8}$&qn3^@ DJb6{:~v^? {#5}-ƲÛFZa_{r$Q@MmW zhɸv^?p^Wv6|洚11 /,;urur4P\ zA/ fVoo۸\E0nZ)مWelOTzyJ zAᑇwpVD$U7NQ[{*q ^AQޔ^Aoڜή\Л;8@T?޼pSz{=VJ z}חfcg|"/ [:ՏNY ^AQ1ur^Ao۫Brcm ;jS{zu68\QlA/W zXJ zAnt}psb1::v^zS^ޱM/+ ۹)u 7<@K;JL~Ȓ;3wv/MqwKWmE% ^WD#Y sً :MFD/63),P_?8d= /~Wp>vF- , I+}7dC7zM,ϯۻZ?[VM*t˛2a@U$<(_*kW/-쥙 =)Bqk &vcP 0h>.l=I~_ANakkBb6 ^8̥Kpc*<&o?f4KE/!PRB(z !s3;[1(z !rW}里-*mjRIMO>i<.FB ^5n&Oՠ=Rv'=SG݆ v#3 fr.#VL#kn7RmBK!^B:V ^BmQhq8aMH[6N_ZU^wfY+vug+Յv,Qz_Cusb͞_:nA7b%:ڜgZxuR1ى7쉦TY{LV ȢEP qX aU0 0$T$Ocoȯ483p\穷.ݧϙ޹O[9fxQeѿҊCCٿﭜ1>FjIƩDwׯܯԾp|st5E*5B%7kG[#*ˍV?V֡mSg}ł'T̛GSOn?_?C EIU<" zǎvU5xګJȷ 1qKTvPh"g͖7./H#gjԎpӥnBx[$|իY4#/;wr¾%xؼD i>ݹtIחTѮ#2-6{Wt(z1">:qSnŸX#ыD/e +ynE z [۳G"SIRrgT(1RZӖO{y87)LJhYcƃ2tmn*(o NݶijueIvy6y>}6ϯv6kϢw׺";>|_/9tb/V/ \^D/ z `l_O6 Y޷~Sdk,Tc$..I:eL[DńEn'ޖ8E?| 1)42]ikyE7t{G jk2 rX)J0M%z|>^P]OOާHs~}k]/9̞qA\ytYSb\^D/ z pU+ ^C|IM <_~Ӽ iךVlZZ ᴱ -w.<`Eumh6{SԴ5U`[:[fFNlA͆Zڄu;Y*OK:~A{Z/9̞OpA\y|"ыD/@@&W~Κ*EPVXǜ;RUT%;ў~;Wf_BxPR[5ת5+IW}ܸ>B]0=[${dng}j:Ҟ}&9b.14+lb>^P;dqYSyrѻ9 mf~dmDy? qA\a}"ыD/w<D/d*zUROuMӾ=x*dzʡʤ%_PYٻg}o_Ud[RqW-Qm.1n|Jrw >^qc"W4ҧg^`g`6s|X/9̆ |(D/d+z%?_Il=`Ȁ=3.7UZXFn#Lh+۝;"qZQNQIFKUͫlc9ز.?vT z ˓>ZDq=wX/9̒ۊ $8 ^@@PYt_ga^p2^6GⱜĖ7T*,)tIQpua#srzz4gLTxXv}{(ԷW7SY嵕ݛ`WDo쮭+"={ W;gRT̓U;f͊v^/9̒)9y\ D/O@2sӷxՈjSQ:f{Id|}F&ܣwWflwΜ3<`9Ek=mc\&VwaZ^.mePcNmXl̝]Hz6Sou~:fÈ! 7̝RD/ z X_ʓ, nWHXZbCcF w<_fQL=Sǔ Lm(k!:F-¬cr^{ nT$+J.nɉUi>I q GЧ{xvg)QI4GD_!6n{a }4G +ޏE:"Iy:{^7u  z >$ۃ@@Y|8DUSzq,@{'vDlUꠞJn$ _o?;-m=@u2Zoן-?W}rAI cգe,n߷e-+Q/vm?` MϿ Պo|tǪw^)8kه˖՝ ÃcyD/ z!l^΃oyW)cvKrWhM߁#ծC |:  z?%8xw]whuye!5XCCA A$" (Ot z jmK @x2AOՇ﬍hr~ 5՝vs$8 zt՝ T,(xRqvfR[^ Dцz? 0kW//{zC/A [b N~|7, e5= 'pp87LgKł` @T=)z 8A>e)o jt@0^ Қz 8A|mW6䒔Wr^ zC/`1"߻*rQnq=C/` "^pBf8lȹȾh{#` W @Φ 9js{^J0^C/8a!g5y][{^C/zCirA_Mi ^+b? Vkr屴Cs.QǿyfE?NRB%:Bt@cPBgjkjhֆڢhpohD?^/>v=<PnWq~vo goz{GFjgftt@z^ =zMK=X;5r_kyȪ{PUݦp6 b1tٮB '. & UKKIua:;@c{{(os'ǿv88f~CN$pBX:xf^Wz m^zJav?e?swl ˥8{˪ƍ{dt9 @ .d& ٮ5 B/TF!߲X<,o7$ó5B/+;@J & R/^<Iwn5?9^ߧ7 ̨Kb<60- % &B/_ jB}}IW71-ݝĸVk`1Y]BA-EjG`Wj-j{X(X*PP3cNc.c0q4Nn {$``by_!y|7ygh-f=:~.sXY:=^*{݅H κr6fx|̫fe+?󥪓7"W 5"AUVc>{'f@7W*` [OEO %^^BE/!d$EK1!z:_O];;le.<)[]4:ebccVqN,BA97ⷦ}/;~| ];H֌߇y3: GMddY4[-CpnhxBK!^BpϢכ`?jsyQ;?Ն p (:m9橒e]]օx!jsYBb8:Nb\NF4[ h.Qe^_Ż#v@umΓ{6M"ޞ0rj*Vk*\?t33VKC_J0޽6Uq.[8 P7'6q8d.Q"^dN. ftdEL/]5HbbLx'|+SR,^lo  p[ ЫŇ_4uԩa F"p':14^J阡E-IæV@4MCi u^/[>} օӣSV%U7*˗;_K  ]b 8?nkcG3洵Y4\Yi/@;$}v47481-GKO>9v̬+rLE:^v{6?Ǡm8v<5pʊSK3gM>PMndszm X  A`JvEHچ^uk\J Pkuw{oM!V$z,8L!eeͶM&mmՔhoHSS&O^ɍm6jSgc'^һ`WxZ҉#z܇ޒ8# f @@<-@WA3֭vz3nEgzz{Gt*f^oOnhεjy%zb!)i4mɼN2abu'r?T<691J&:F.|zU|Y{G(\357;#[Y  K7ݩW M(Ν\*++dtuj?^f֌oYXtg7ou{Ǩ/_xW4iYf%^I RzݏlC>b{:!lW@){v^h_T_T?KNVpIv bj:ǽiMk>>o#9_?N{WU+nn9%: ȺĚk#*܇^I {`S @*6 UW #GE9ųWa-ќė(7}oSytiZx颱c{ၜݟy$ݟ>nXw޾`igecw4n$%C׻3[[~dsz6-Fe!핮@P^ &\Y{Nm0iܯPUcR,,˴՞ .pzc{v6sB󊯺hՒ 6=Y[^ofO{zQձw=߷xjϛ?~]Wof|igdˏfߟ{+wnQk/| iInǍX܆DC̙) G6׾V_pyŞ^=To@Az:qfԆ^ t_,D V1T=%[>-KdhQCø:=+U9Dþ 蒚ꂑHv[}ػF( ddft͠B7!n\NDP7q! ]F@`K2KD(cš?,xP ՍT(z5|5 z(}66b$lmީ3u|lNA/a-djfno5ǧwv^L[A(+k#pm\^> Iλ␟c8XEFbA&X7Ê5,z^ zKR-.yl>mYwwG^9 ^TA5v|K8Yfޞ:9i\\nn"҈ knuuvswW1lg~?7cv++LRuqv6 D[OpzvwW @ /'kN'~ zA/6@ `^l`X {A( (kBaBC 44, nװsBX~y z0@@{=^ =^ z0@sp ` \U0&,phzI`лK: 0 :w% <~9>, ~$>?^L`Лc ȨB/  ػ=^ޔ7\BRD HXRkKh }WV(p˲e.q̖e&sf23јl˒es眯i֪ٯýn<%^S9ù,Л*7wC B/:ZzQ%+=>% SQGSQ)wZԣGE|mm񶌞::##Z8\\W'޲EB!=`JWd߽I"=ޓ!ٰ\v7=sos|896\[lYt<']uFgBs[s%=/Oz}/?PSjgϚB@z{{!hYeeLݱ9<Hd7{ֹ$[JLNw8\'Ne>W/?vF]7}!ߛoyp^+6Юovg >{,۲mnO/"J0YNx{Qәr^$PPIħ\+%Z- WnlT{7ø,䙎Y׫K]|p{ yEbN;~38䗻lܡ]_Z: k3<*=G.;3ůIQGg@zU a?kv1IǏЛ 1XaJra0cu2\/o,V헏qbs-褐Ⱥ<"Julܡ]m>_] yř-DDyVޙpTuʲxn3@ z=Z߯Ƃ J5`+!`g&@5z.ŵ D ⽒v'ȶp~=Bd+z~mԩ>}4Ez~0Iz&OMxn3@ zUom҂ z g(Kw;ef@N<ґ"ҖkJ^84l7MhWƜ= z?ߙmޛ+$kU}W~LHS;w61B/CIR^'zͦE"ebI3g&f@5ɠT86Akd}72T'o“7Ǽe~]zdRu\dUS9=t-K\kxb^$@uQE嗮!OEB55s!ٝj{*a˪ Z[+cO:e 8(l)dC6O+?ΝN=qj]YL0 B/7ݤzjzoJrsdD7 !D]˴ TF]|zEﷺ3WUUz=>+WY[(wk^M.>Z`k]aC/ kͱG\W  QB{Jyz[Z ׬ʼ*jlTd'^ctbО :b|^Vam¦šK.whdTZD_gY<_}/-1h9ܧ̸wf09Gmߡum6鹄*YS(WF?Y0tièWW1B/Hk>xrRz{{U|&&2dv74XsdDR[Im-sX$=6#eL<VWX'4^5sԛAڿr㎏b i[Vmޡ|xr:"pTM^jygG١T@ zNG ZZDp8zS0PXX&L W^$nUG55bJr\6E|URs iВHDތP썲QS+-Lc|VvYP_rdY?ؗO':<t i/tXF}c$9J.mdP-#6&ĜBs='Xk|oOs) B/ 1.7~|6"6y\Ml5po[>ǁy{ ?W\_rл@KC۲^8dh yy{ `/=;ip|t-X#3_<y zs7y`Ia?42Gʪ] ~Ds(a A`^A0- 6c^EQe N`{)wzp_ym1n`>!/˽@D{==^ z0z0@{==^ z0z0@{VrR**:K)?Ry|!9!z'{wTUvܷ}yyH# b+bh[EA֦U@AM2L2It&ILu&LL:T,}TN'tbgO*^{OR@S{9>ۿ߉ ^""""5==ݼ7(XF Q3+$x<'=^"""DDDD4Ao aѣ zB"bKD z(^ zhv^K \!1%"DDDD/DDDDc;[+,( X\DĠCAo…ޱ1\ *}p~Ut6H=|XTNG%ի*ݻ1[ UV8i.13>5{.^DիXgO0]=+.9cC>AXԑmK?>1/'Kݫ 3 o]_I|0;bWvf}|?\sڂAs̅"Epľ6%%wwc>{1S1KH>)W|ދ9< \R[T$՗Id57z2A~3znc-ˊfqoMgNsʐ v* 1%"""Ġ zSq31QRULE*鞜DwAofwwqƨjd8۫^RQt>(+/ǥC:;UKN6"WAESd z<&?s* {2ݑ>=LER,3ZD z+er秆1H7wл)ϑrPgCQDBЮl>5PVКZẚS.Eʤn_E>Ϳ>bKDDDDA/9fwtHv-|Q4V+A8[~rS1<͑Q=5%TyTކ J E9b]2~p_͗VU!eU"޾=3b:7^z)oIwyTeSϟK)q32 9@uKSVٿXx&d_L:;=[Y} Thg‘IQ!^"vg^""""bKDDDD" E?AΑ=zD9JUf?72fIqm-"j\J>v,F1;;s^}?IRWnQ՝YF,6[( ŮS;Qٰd_SO$s-(cWl-B[7zҟ9 0J-m$kUꉱcs1h\t2Ny&xhw3 f4(M5ܒׂ.(Ekfk>J6I46at6ODDDDVVJn3Bc l!Kcg Z+D1"^܌hQkJ/VV4Ԏ0\D/w|[NuF]B1 ;*[F,vx>0Cq#vu$EbIZ m5Vxx}}=EcݙFr|G~uW1geM<*[6 \uJT[:!6m&DDDDA/ih+}kjϠ7'zpJeR ҠBYʦU lPA+3Ur74^a칈;f z[޿ߝiT[csVc]eҕ?<.,Sϗ!?q$rSӔU0nN8FibYM >[ @1 ѣfЋ(PftBsX\MEτ?=WWY6Cc([ߴE+ g>5gVhwӷ^бMA/9A/_лuٔWU0^t9YJ ND0Zd' {/_FTv`Ν8k1 zm 9m{vZ~t#S!yƐJX)^gZUXD3E+wJKDEd~LwyL5j%O]uNZwGY/CIĄVĈ]l-|zGsaHfW%2N޷tWŝ)%IO6~4N=t(ƣ!Z6ξe:+{.!2yծ,Snz"T;4DEjIdؼH3mYV~Ѭ;C!V:;j(P6OrW@kzgel^}lNl_1%""""g0%""""V 5ϥKB_ǯHR+z*Mi18V7^A7%(wᠷ֖~d'>:S!r!=ꕦ-<夿vI荞ֺV;+~nA%^^]]R_Uح6vp]oFټ?E(Vtc^ c,o](1%""""0%""""͠z{%~3n$[^$q@s^sңԾ>xR 16(cs=t+S-wuk\i2bV//4󳲺3@PEc{Zeuuv m c3gi)+;rurA+4Qs,7aOMϟ{4tql-e)]T:šXoܓ?=bKDDDDA/96aU+55*74r *V^Ӄޱ14 c1J8: Cd=UfbY;w<(.T:ma*;:(VsE4硿1__qc[OJ¯CaVq)nި]})Zw A7 ߢ!0z-*nZYYҾy.C'NF{=2 }c8VoٟH"QZe?;U!i]wM_ٹ8%J$/-6+b%.f$#/i.DYhs \QvAM)-#.W\οVGeg<'b+/}0vb}Rޞ|yZ|0X'jq;.yZE8^f/F7_$`T*!9 @Iii|S_}.(t8!0 `ͫ)dn+=6lAU菅B/ P^,B4nzc㔎읇gׇ⭉㟍$E/DQmκu!z|d g2Eӛf!I + yϼ `y?{X`z =^,0X`@B/^,0 `y_U,]~zS},Iozkt' e%NHa|>J^^`Wq+B/B/B/B//)-+-([T^- @GG=)γ@z kw B/ ""  + B/B/B/B b@z[giK+.[TFH{uo jgM=~5IB6g/l8{9U,=Բ[}q󅛹BoĿvG{E]6Dz{g |Z .qpltCrdBc4kags@溮ɡZ/ACӆ$. 7\|g:͌J| #k2ne3hN'E iJÏ [ W393Eh: @]@$pHa듸W|l`ZhI͊z[3j]60'zi7uL+(O]dyil*`Q0 F(`twQ0 F(` Нpvӵg 9[ףOCtP֜ƴ4tU9@;8-wF{pM:w.X}$U<mi։3p5!;nʑ Ѭ1 FDo ՒX!ߝ{O+RlUп≱C:[B#;76* gֿ+젢`:."LXb 5Ƭ:XῐֿF}"w߅X<$VeHli RRB!^B!tE"} ~&5HBbYUAB128Uh$O[!#&Xԅ^kuB` y EX0*FY\<ؐXki΢azyHJaAjֺaaZv E ZD35%DQ'{ZXQp }?Mh,qO^^pZ YX@QdُhTagmY{5k\rMS^oBN㬊^쐢B!PB!%z{%2 0merV˜ B#g9 *, \tW26Ws[ZCgžScdYW /m9+|~޹}5خn+"z_R޽_S^YQE ^aa'P[OŒ~o XE\^T3!-HxMNU_ Zfav7LvHKK!B(z !BH..~a=*ܼ_AKFƍwQTb4j.zʿwR:=Y) /v _?iwv^ҧ=FusZ<7_!Ԕ`av荪K Cmn fAdaA8Ck'\ T LXX6|Kn7%wNSz[-!vF&vp?Zf\/yYq!E:eԂGd SJƣ: UD Dl,zWhw`-0F;%B!B!= #J@Ke$z ijbxaaw(9+ic?(^W4xIgv]WWӛGՈ>qřwZu^QM),bya|xvm]UrHwW䋆Du2ݺd^P+D CFhTu Sg.m+G1_Xx6n8ޭڪfk0!E/E/!B%B!MEܬa`t  //!$=-zGY/5&3_yxBG$% #pO/UK-l!LͪW,,,xYPaF[[I^Bg5%#k^ ;)yP/ ξMjX`y>ߟ/GX-6n8^ʡ]hi  E/!B%B!MA4ˣ-,y3a'nT|)14,|.W+|gU+L#E3 gï`:r> p M UQ K9)ɋl{l|;֬Qݻ$@C/XmgVoGRFwE+O :4zxDBӃTTQp'(wY.qb~K evfwlBK!B(z !BHyϔ>"*mhw}kaQl]paYWN,d~|r3~tШȮZ ս拹E=65eKsp8 rKq܃3[A"3Q4W^ʃb{깪̎^V#;#KQT Z*?㯆0񒴠=ϱPlZm(PпŽZuxmz"%[#diziz_YAعע6wC(E\!ꧪ7{m҂'Pd[z$cKZ8|+9Q擋ԽQm6݀u|i RRB!^B!isowJF5c.6Yj3yBG-=xsY62QA؉&Û딯Lvcٛƽ~\jCZqZHv-o;WJ?+4hCo]/4HSb]y8ꏓt=r?2* /TG9{ˋm kWN;O^Ȋ ۬VB}#CWwb7UZ$Dxyk|lL0ʢ6#NjQE^a`-0;^B!BK!B~u/}[ta+:mVAxЈā֚Xsϕj{~ҊW[FQ?{" 7yQF$ךa1j/n7|  `@@8B/`@@ }@  } Of{t/M6 fZf$  h8x)B/B/Bjnn;o`qeY{lu PF^^^Pmzz^^3{7s/Þ4q!Aܯ݊ĆVDЭ[qyPtjt@0bt ;%i?ѹ : Sm{^V`|~^B/B/zO!L&I$!OGԍea#RۣJe~ߦgu_Ey5o}OV7D|7^@ &$Ny|\oooLHdpɉf2D6s=. \zksBzDž972YuWoGzuh՚V"S~e˂'Jzs4ZJLI+ˌipg՚7k|x=)sP~_\'GϼpkS \+ 7W6z\|E\={+(;g4[6*v6ݹ{+M2=իweOQɣ -""HY/ sk bp䟬xfxrv4{v\IΨXΡ}/^Y, 4{%[ѻ|~/6+3VɊ=':ζ%>~`B8]c'z25:<сCze+Ѝ(^ZkI`jţlK~"66V)Wu\*4V=*Tq+%kx+>{6==,yObS v z鿼ȇʰeZzSXy˫"`wza➯9T0&M1-ذi#q{i#K}G zKh;hrdԛ_; y>mҙe&e ~Y6-3i)۞١7#ɥ+ꯏx_[^)DnxjټeR̵J)h8h3ui&GJ7_,-XXb-exg7y;!s(88)/QY_?-1'x,Pق)m7!Aݗا-oz) \֋P̝9G_^yS~ʊko>z Mj>y("7g|ΈD-^Y&ԴTArp틦tNDEguqЋq^U";w!re|-̘!{Du.{εA -7ddpPZuUO9%W=ȶ zsʃW˥{rygܸ?vGU,VVpf^{;,-V[cP3=3$<^^^:cQ"Slku[O"™2_ Gj2͛6?rÅ\kJOo\ٷ6Q09B}`Bd^OTo\#zұn\ 9N';MY_̪}t~ݾ:_ hWw%oj^@ 8X󼏱{>h8i۠Y2׊{xNwXdѤ)Ja*0EEy z5zW8$TK?9n҉`OUVp0E~jrȮݞyC4_&,QyF!Y6 ze K!#wwynXu{ %znB wf;*y9܂peSKKv,){J=Y|4B'ˉS^޸[7V:i ߗte- E) #R뙾ƿ\pÃ+7XXGf=WzA/8`+cFL2qmI"aWRVZ{hdue1kd+ދ4`0(;L^.ĩPyG`z+((D=P0#eLXSߥ/CoPE}a^vͻڲp떒0. ?ۇp(o=;kG6q.?l'y"|ގhL{w7-{9/\QaMp畼h7q}@`k&}ét?f/[ҵ m#i2 |֬km,af^6.ʛH$2zwT^z+**^^^Wi<|f{M2]yUW{fz^^^L,[3`i-gT^2z^^^LtJKKlkj>syèZЋ l}k= B/BQyȾ+"fO]}GRT4֗ؕvgZzz=~M{\IENDB`kitty-0.41.1/docs/screenshots/splits.png0000664000175000017510000010770214773370543017632 0ustar nileshnileshPNG  IHDR (ΊYgAMA a cHRMz&u0`:pQ< pHYs+bKGDIDATxܽ 8в-0."Dz'?e,$@(E@(Pb q@?D,1~ {CĔ7\lbOc|cG8}4ܬ{OZzH9oAbYo>KOQ+*]4ko ??!Z )MxO:sgֿXkbɉ-L}!h4[}n3k^_毅ktbbaod2F4"ŵ991%e Bpx^fL}5x1_ڿ4}bɉ4g-:<99y6l(E'@uxKפKŮH8$ŕX(eVmi2^K˗ōiosH1nAF}Y{Uׯcmmjvj]{r>/^K×/M_ھݡ[eLxe''A 1[럎>^Գ/5ߢ/T}n^gzՋt{iǴ}i+F3` duMa]><<&Գ" y./^KknڿW~rb l/{D`FC"bxa1Ͳ %R~y~)P.< 2/3Mqm[~Ͽ|wx}^)9kw["R +91' i29q4ʼnN ziosD@`x060l ov l H񼅁G;@`x {u],Eq@q~mv-^cVUURʛ?y "d-`6f#`6fCp :9wa\ "u <Ͽ.=1,ORʻߏ5 " YW>ml61" s4&1 c{#@`x0,P p|VJygNz9H'l 60lf=Lnm<$P^8zH'l^+ a|xyUU9D@`bdm`6}Flv~eb)" PlC> .Do߾J7n8p >,2yذaCT/\(ԡPB`@,ܹs'\cB"n݊aAP=mϚ%*F.PC 0Hn߾]TKc۶mB"/_{ ;v, (Kcccel 66ŏ_=w7κ PC: ,"YH FFF")i&TsĄ (@l߾]\.|>*Aȃhn$ '!5u. PC 0H.]ߏ/REcffF dժUv!h{:^Z~~>5 PC 0H&Ǒ=zT :tP8q"z{{+z[ +V(wۜƄڵ+*őⓎx=O :P (SNBԤ?2Ǐٳg9vlttToދg"~mqiPPPPPPd9ҫŢ@v9իaQ Ǣ g%Zn]\v WJ$Ox^D27W.L~<#ΞOB u u u@EJҫJ~4P-*211ϟ`~~>#Y~}vgϞ# PCNN,0H^57{Un|O* 5AP "Ay MCbj`*]E% ^ґ^BB)R=k)kLqgs=hժJ rA\7;NWxY}Q7NBDDUj2NP:5ԉhn:E#22˗/hwJ ׯ͌]O=7m^xׁ)PBxzd5 D ujSC@:L&(?k׮oCarFǏ7ߏ=[nȍ;5k~aȑxꫯ]v(]4rӳgOU<ghݺ5^zz1o>};lPs{nABBc9y.] ʵΠA@Ŀ~_B… ~0 /\_Y*H UlY=fᖅТtNH,z ~b(|2Lծ߷O| 7`{X4!+G?噉qGK9 0s˂]4o7.܅mo9y8kفImWn|Ѥ~"&<|3=uoEf{/=gu'cpq7D ujSC@@hg)Ji&P׮]MJ9<==$88؄x T^ ,C/]6rrU }KxWCf͚7^ر)'&&f.]Ǐ >'O4UŅ!]|N8'źNv5-^y lҼ5jH""% A]f@a*呅JP? 1Nඪ)& 1,="\|3x3NT)J t .=焹R10A^OEX6'N߹ញULEAp2!Y"ЦY<*Om<~ba8sxW~"%g}VCP:5ԉh@hĵ P@nPV-TXa.VZ+1o@ddrݻ7o| ϟ7bzaٙ;w.BCC1zhɂUiӦ HVX=>>^b!E;5k.\m͚5,1mQal߾,u!,9 +x_DmWm'O.TRńiŊq݋cǎ|IɲeJ$EDJnra ('099f,C6?Pq1CSkBydg✪ޙ+cr8|2`|϶Œ&eM1s5Bن_gT@v>:M70ۨ@҄vj͇q+>Z{S UaМ7"`ШkC8wC@1èr ƍ3E%KZ}A >|v4e}aĉ Nxf^2[,(ZSºi u?ݻw%%%aʕ _Ă'w _6)"RB΢M CphB(;UelAIɮZ 6i}><>{_cgVCuĠzX9y,3'96 *_ ?%k*=f[Z3[zۮ う &T:-|殪FݎdPV1 O/ic~?vyA:v4y7 ajzN ujSCP'"""""""ƌ!ުYROL<;_g"aÆ 2ybƌN@9ŋ\*ƍZj PeIWbA`Ν ߎptxN:łu&:w" [veNJCy4wm+ae q QddUծR[C//O;۰ě!JP:5ԩN u""""""""Q@ /^tѣG* j Θ.WYv N($pU@M+vpo b4VQ O?sI$.MeƏ4b.eBӦM3E[n Nٵk R`W!!!V)"rcuӜ9A% ڜqQ3t)ŜTLGgf$X׻ /m=)\tvƙR\݆ɯ;@Yh\/1ńD7$$ :5ԩN ujSCh@hĭ= '[r+bm޼9p&O?d6z衇pZN;v.vj-} _WM;uֵ%ooo\={6HOOq |x71o1(,, qqq3NR)*|,ZȚ\IձcG-XWZz5F{`CӷJӤ%qD~>/|2|3@ R,L#ruAa^pñӥ9a7 ; ]r~ U,AE?E(k@KUO 4zN ujSCP'"""""""PdڑG'`I bч9'/k=Xг}vW5"!! >^^^4 'X뮻|@WdPXt)"Ó\^;N"={v֯_?sl wq)=ښFI>k-ױ5vԩS'.Yo""%eƮ~xE}MV\-#duѧC4fwǖ}Nx'jTIxg4)a*fiL|@PT+1Xn8M>0Y{anh?2UE5ԩN ujSCDDDDDDDD 4B ñ9 .hօ+V`~4h aBA-8ZK;Qa|R?J)\ 5czs|Xd388؄2GQDg=|o@Zу=Xx1pkfȐ!ӷI^tnoEZ'Naje(A:vȸ~^ `xNQfwl{|qਗuQ33ϓy*W%8,t|4a.鑉}-` n8i~|2XGY|k9:ϋ< (N ujSCP'"""""""X@3mڴ +3!vk9UV%{,X(RRRk,c Ԕ3%ʖ-Y?O|Μ9PL8S߶nڄk p͛7cD\ ꭷB "0'XAa] bRJ. })qiCH'fy4}.nGQrXc a.l]^6/Gq1`bOAui/bNXQӫx h3GDzH11끎oEauu962nWX W?P:5ԩN uj  [+" :9XLY&M´il1 $$_~ X"k7N`Hnj"'{*d*URGGE^x\Arh[l'ZvҴiS.VDufVqtRgϞrp=, [Sd… RD@RL0o`Ӳm wO7iU9A~\j wտsC뚓TjX' ]3:ɿGNb@( r.ZY ujSCP:  uˑGصX"^|E/]͹XٻwU :vR l rn9+0)OxUki /Xj̘1fB qC(ըQy9bOfPNܹsa3acTDDF~o+NM(Z)p`g8P+G_:4G [yAVADD'jWKƵiuQ`EMPTCP:5ԩNDDDDDDD4B 4 :TXP27˗p͢UlHNN6Oĩ\QFNo>>>RժU"/".ȂT ,pFΝĐdn`<~)'# 2UQa 93g'~wVpxȑVq;`o߾ 9rs5,D)uţ:Jen Jw`jzE;U ysHѠ";4AQ ujSCP:  P@=t9̞=EKCAAA\yR"56uiT U{1׏S/8|N` ]K.)@L:ǝ˧OQk3<^{ gQPpl?B_';m۶+Y~+R,*;C+ah Peia(@N\ݵP;8!USPz2C׏b8w,"_ʕIp8:r/Xxر/I|LN߇^;ϝAGcp ~"h5ԩN ujSCDDDDDDDD 4B JrpS@&B ,0V]\qƥ8r (ʕ+qF z?̋ @2 f~ <ƍ6^'D۝6ooo\/X&Mp"GSyVrɒ%Xz5 *\fæ[f ,j``a)f-Ew AÆ !*""PL:]Lr-8p#f. VGn>|Lx4>c , '1X6 wK&2Ƿ^XX r 1&_v1e=\օ ۹E7AP:5ԩN u""""""""h@?{wDpˡC:!Y,i5JXXkcS&xCv y 2!/ m:a8^@{MܚJg~hxz2Jՠ>l7 xLFf(lVH&|>orkv(J&F.Tڤe9d.YO{$X-6ڣ9K._Ug$|myTN/Y&|~g;C?N (: HR oA5l-U,u,&ezh'D:DQi&HJLr,KN`p=L&7+ɏ L: (4,t?НI"pXN'i AD"l6E% X?uxpA IGE!xvZfJqwwp2|>F/uxbIrrb spp`ZREa u sF^8{u:AC3MuMk4 ’=nI"`P0(E`P A"A0(E"`P A"A0(E"`P ٵ FPUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUvX`u"{UUUA:(VUUUUUUUUUŪbUUUUUUUUUuPUUUUUUUUUUŪXUUUUUUUUUUUUUUUUUUA:(VUUUUUUUUUŪbUUUUUUUUUuPUUUUUUUUUUŪXUUUUUUUUUUUUUUUUUUA:(VUUUUUUUUUbUUUUUUUUUuP:(VUUUUUUUUUŪXUUUUUUUUU'*ځk#tjPŪjPAŪjPŪAAjPŪjPAAjPŪjPAŪjPŪA v AŪjPŪjPAŪjPŪAAjPŪjPAŪjPŪjPAŪ® NvbUUUUUUUUUuP:(VUUUUUUUUUŪXUUUUUUUUUuPUUUUUUUUUAXUUUUUUUUUbUUUUUUUUUuP:(VUUUUUUUUUŪXUUUUUUUUUuPUUUUUUUUUAXUUUUUUUUUbUUUUUUUUUA:(VUUUUUUUUUŪbUUUUUUUUUuPUUUUUUUUUAqꠈ];mUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUv@:T5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUU];oH!^UUuPUUUUUUUUUUŪXUUUUUUUUUUUUUUUUUUA:(VUUUUUUUUUbUUUUUUUUUuPUUUUUUUUUUŪXUUUUUUUUUUUUUUUUUUA:(VUUUUUUUUUbUUUUUUUUUuP:(VUUUUUUUUUŪXUUUUUUUUUuPUUUUUUUUUA:(NPUUUUUUUUU v AjPŪjPAŪjPŪAAjPŪjPŪAAjPŪjPAŪjP®6BjPAAjPŪjPAŪjPŪAAjPŪjPAAjPŪjPAk)ݫXUUUUUUUUUuPUUUUUUUUUAXUUUUUUUUUbUUUUUUUUUA:(VUUUUUUUUUŪXUUUUUUUUUuPUUUUUUUUUAXUUUUUUUUUbUUUUUUUUUA:(VUUUUUUUUUŪbUUUUUUUUUuPUUUUUUUUUUŪXUUUUUUUUUUUUUUUUUUA:(Va_CUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUU];m UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUa׎['RWUUUUUUUUUUA:(VUUUUUUUUUbUUUUUUUUUuP:(VUUUUUUUUUŪXUUUUUUUUUUUUUUUUUUA:(VUUUUUUUUUbUUUUUUUUUuP:(VUUUUUUUUUŪXUUUUUUUUUuPUUUUUUUUUAXUUUUUUUUUbUUUUUUUUUuP:(VUUUUUUUUUŪ®6BŪAAjPŪjPAŪjPŪAAŪjPŪAAjPŪjPAkQ_h [g@/M3w.dyfy(EOk&r;Mݾ2*l6x{v{;u PVU=>t(|O9RJQ@w)0 K)b8G"@۶}#D<c<#N"eY³u] Pٛ@;aرÆ;* PxjfE@\E*#@E(E" PE@@(" P(E@@(" PeM$([ۊ-|+cm\0_tGeaXx<_7)2B7,u,3n7A w}߇+0mcm y*|iɮE@Aov@:TbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUU];mUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUa_CUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUص FPUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU UUUUUUUUUUbUUUUUUUUU5(VUUUUUUUUUbUUUUUUUUUՠXUUUUUUUUU UUUUUUUUUՠXUUUUUUUUU5(VUUUUUUUUUbUUv WQQ EE@0P0P EE@0P0P EE.j+I$I$I"D$I^$I41EE@@0P  E@@0PǾ& Q'A\ ;]C& ]{ CxOұ 9Aɒ$A(P|~vq>giY.H(L&eIa^++b"5jzX.hjJ;NJ3UG```+@x\ߏbI4ۭ"(%Ij7({^{W"dYͦw6)EkEPt:z6 ݮ"eA>?&/)G Tt`%["H i3eH.Z":(R(Nbmĥ)?hR=U8/+ W 'VVV"w7oF[[[ܾ}_"]-ϧ1`.]'|I>~@W W GGGGDEEE )3>fuu5l6_iC˾ߌEz~#ɣ_O~SC^+z+z\POmmmHY__Oӎ ثrص9N:###~HJkҺRkϳH*#=/>5 zE@ P666"3I:+C)#GvEri\p! O d +W.(jee%zzz"ijjC)#sss188]Zޞ]z5߿o?[a ^+^\Pٻ| 7 !P4) EW@P(R,\Dq-(b'*"EC BBBHg&H2~Ι?msgWDDXZ ԫW/\{:(5W{mܸ38p O uV{ƍӁKXXBBBt .snn %ps+yEѼ"WDPKsN_4`mUN:5k9]wu(%#F@Ycbʔ)x[oEZPQF2;uѷo_=z4.L8a.Emddd0x?6b_+4|pTCnwƏ"}'|-[ǃy߿yyk999pI&yCO"u˅g CX^_4w*>}~|w7^y(M'{},IGX`>}#w%biδs=~ [>-ۅmJ3/gs4g|x>޹ǣ"KůKwV(ˢgb^=O^&xlIs=|6V^+W4h^Ѽ""W4h^Ѽ(HUc+Ŗ-[@w}YwR DFFp[nA&Moh(&iٲ%ʒw^y>xGгgO4k <ؼ!Ű\+Xr%lO?4+dvXǏW"??˗/GyV^ ̟?;wH 5rRv( U׻ RôѼQ n&Ç3[\b,iVZň_ۓL?vceUD,cйMB^68ZUbBƠ_t+x,KVJJ@/b`l!s$߫+W4h^+W4h^EDDDDDD.?Bhh(U|طo!TPP~‘ `̘1 BRR>s,\K./b´9,Y3fs=ǯlİCǖ+uOKK3u1-^ ~pn֭cǰʝSbؾ};8QdfxYD|wiܹUv> 64}e{A\\n+O_f$+KDU*(t֐1zsUbgBzʬ D 1P͛111&PDA>ULڿ,^mfBP׮]e:tȜ;SNb0ԹA > W4o<έݻ/믿:i~<7Oj?{6l@vv6֬YaÆ>XZunzaaWt˄b*1<3U>y(% #;ψZ4ؒ1o ‘!ecM)WL[fs#iqPn`&D7wOCN&|K$xh~4 j0O ~6Jcɀ04s{+]j1?^O+W4h^ѼyED4h^ѼyE """""""Tw>& S :uꄈB*|MTwqMخн{ ~]tAƍaؤb}XᅢW\q#{ ,v'N\tǿC)cV'\> ;t耉'f믿qE CEW;U1YmLl{jE<| D*(%jI9p/897?>kj]2Pf V_i >{>==sc7wğa+W4h^Ѽ""W4h^Ѽ(\~Zծ]G5}pѧOz`?~gϞ`#Iᚰ?ݺuoq~쑒lڴ D Vaz `4VoC~~>V\i.ÅU{#ɳ>x/X5ш#@\[k.<ӠAH"R5)1F񃈍O? _('ݴTTbFOlp " ! 6`+u="8˭ןAhBuQw<(""""""rycC@=?͉zkj#4ڱc\Wq]͝;-33d2+_n:\uf4h>}EEE 7/ocl٠իW̙3pe'OFu?ր͜94T6?Ĺ+PӦMqUWO/"5^ǎ (qǰ#_ FЯ*q; ݀tOĝWJr}~6[L MtJ˧e'AZ w=P5h^ѼyE+W4h^qO= T.b "r͗Y V~}V 7L  mZk222Z䚥͛}lcAG$AAA +'TbF%c9}tYvm.W6vXslWٵiZsG\FM;K,1!36t#G}ϼy|rm۶ IIIW3a>E@6fX,z& Zo|`LqץvMD<$x᷽uwm?gmKvx}E8xm X~L~҄nU-˪X`ɴ O$$i=~?`_`xE+W4h^ѼyED+WԠ(""""""`ӦM&9tfO?M%fVnn.~X3K}}طo ?_ֺ<Ġnݺ\e*RRR.??vFY?@ZSNa…C A:u0h "ׄՅkx?> &M‰'LǢEPb}ܸq EPbl4r%AZZYyڰaCWg`~&/hi^.%v+pI5%r@ޫO ]WHf_k_tILxE6nwT\PR7Ol]m0мyE+"yE+jPRäs \bV-̚&ڸq#lق*d8z+Q\=WjHǀ*bڵvG<|bsLeرcn j׮ {]InгgO8 5ZnќϨQ@o`s̕,[ /֓8 v_-Qa_7]ꮡ703#aG%%6ɭMujRP6^濷\# 4 -@e@ZyE+WDD4h^Ѽ(H }ڹs'֯_foބq{J64xPFF`S) `x9UH$-܂06vmpiӦ4sLp[k5C1`in>%XgZ7/kMC@}δ&뒃bԠyE+WDD4h^Ѽ(3~'Q) ! I.5Bbl,;f N!6G4xV=BO1JӮE:F;wi ؤPk׮lb7_{5Xy)Wmٲ~7m>|8N̞=ܮ?zjs"⺲[RÖQFa޼ykٮSNzS7^f\[ .[q|ؽuǬw"cä{O۫%W=5*rP~!*ŷ#M1~>6fNDg2Fa "s?pLeW4h^ѼyED4h^ѼyEPJ$444c 86mʵX\A~V8XVڵkWlc8|0*}Є8bƍ5uָۑ`B2m426ڵ }Y֡C/ۜkZys[Ysٶm۰a^jȡj8ѣ :v'QЯ}p5Ipiҳ<0+0_ zw=Ȱ&z8⍿v; "_2 乛-&{cjaïA; yxN \*]l'|P"7|!Ĝh:dYe] 8?z5Aݥ]At&']p9 nb( "yӉJޫq8>lŵ\h4)ޡD"A#N\ᖠh4*rYRɲ,38z-`[W@^!WW+{wl@qmrq^p {-ӊ] ,,E"DcZ\vGк,NO fmMP}??~YMK)u@E8A >0[ls܅ys l ū5Rce"\y~ŗ/_<ѹ}!7czZq]"8=f <-&fC 77r7ܕym 3>A_v=xEpzzum`690>db߯7tcϞ=}3J\n5d"a\'(f+z5R̶0??^w][#9v^Zp~~of0cw>&P(nB[<@}iUqw]z !e;x~cw >|㇆W^߿K|4媵na8/,Y1um4M{geYp9_ͭk?cw~k/^/_͛v{k #ׯ__~ctc yr'ƃ0Za4MCb|>ln*qam~u i"vwʼn?10(߷;ć۷c}[kۜ?'q^6E3<3J\e饔j_^-P wMR1ɓ'C3B{wBExxxzR+,K'^\\Xj+R<Ǐa=ݤ(Ea64#/EwH1H nMULY|ƂbĊi$>AUUEOmi$">ĶmUU=2N98|b]ױn5i$`Qo~^T8+P|XE^"Vufnۏ{\cAX Po۪mUzXq*G",$Bq>ná?Ni"N,(XR8i"VZ 1D7~rb@19BςĦi? #LljKe3.ZP !b.ĸ3 H1*f"\(@ay01' PJ wSXlZYȔXY,fj|c#v6=WUP_$Rd2Ik$ !r ru}8F#]Mt]D,↪}:s\ln69FiaXep皦)DD0jg<ER1Mh:lV<@fs:ժL&xn7i=nwQUp8X,<t:~/>L\.zN(~k?bhDEl"B!JV%JϧO޴EaWey80nee<{jk3[W*fcX{"@M6,˂ ;ٌ"B!Ĕ{^Cb1DQG=/~w~R1QuPor俒$)o|T(t+QZpP((6dMUb^Ϭ~w Q}DA; {{mKA{ +-,l,l}A|/]2 K =u`63;{;4ͼE#a)p8`XjX%σ< u|#t"RzuIu A^TbG?ooN,J,VV|ۙ2J/')qGHpƿ0`(:& z|V6;3ɄddO BZHc^֫ZzT0 0^Ib}*s_$|CeIgb&IbtZѺQP)d:@Q,#RD_#"-7G.‘ij{$92OQ^j.[,2)ѹKWqyQ%# i96֋J, >og5RldY4Ez,FWDzMI=>q/, } nA<=V}Lb 0 oK޷Fa"To%h:Z4: BXnHR q!w*nа0-.zt\hE)CFw[zR&#w6/;maքL1) '(/f ct:+wzoz~׳g_~%:R 9+ܹSZN޼y߈+=&ᆹ/{9CD,anQݍy B00blßo -T$߿ssBE?}T#|Elf" 62lR, O"b[Y/'ŋcB3׋%1cIĒ`lDr޽;*~2r. HkхqTSǏ˛] |1|Çv"D +J{${J;AWxG8yyy^0t_!V@pIq$1㥹&I_Ɇ&Z]2ԭ{5@9;v$b n3Ρ˺D6[z`bgH0ޓϸDRoFF\`Ξ=[^5ĉXqO$`iiI#=yd&3r1+bl{{.0|Ub"Z*bzE&^mNjH&.5rPj!=4uEZvm bY!ATP Íb/ExOjYD=Ss=j$yTB s5xzYc1fE,ֈKW0DQGĮH;q?" rbpݺu@M_}U >Q6Be,Qap!CJc/Nb1:yxcqfTI45Kk^,ŵ"#Jg{7$d6V?*sGկE1cYĆ!1ZDĪשL5K# A2ЊDؖU?h7rʕJ^|t{iZ1qiqBSqYJWCLA*T`Ϟ=i2:4p)~ʋrpT|PIk׮[֝BYE+ fB~Ltl?xOWĪ{E,O Q(A>$`s;P^ƣ䭴űh;w_/c1fDիWOhYnpxy[nڴ):I.--~I/@uرޞ. RV3enܸAz6/"rYްL˧[J}w_GsfmUy]ͦ]vcAr3F3jaJx P [wfU;7*2gbv݋MttE, 30xyjX lE,М|pFZY$ⱢdgϞeژww]Ssy؉Y\J"}_c;q I?QreuPYV;a*b:HJp.+++HA% icNu^vWVOU G-_";q-[q[6 %t:pqY*~CM7E,ս%8"8LTS ,PPT،GIj''Np\%0Me/_އ۶m۴#H s_c}"gaZ5QRH.+F9`>Z"~'";ϼ1cED,El$+c1""X ${///Ƕo>Lc13"6"vf/z/͔Y2e:.1Ƙٷ{]L: q×;UAp%*888u B~BQrTIV z#CHd bE,"WۭjY"V"bX5 bE,X(K x}=@ " b@"D,X@ X bWWWy +b`"6MɅŕz>11QVXxQQ%nMϼo͍ildn?t+J(Wŭ4i?[g{Px6>oo;xވ &b7v`vm6 > %"V[VѨ[[[GGG777Pn" /aĶ;퇳ه, 8 %puq6I0?b(J4bG<ɻ$PMSͩ/7qx6Q%J&uPxp׮]a~~> 477BΏZbeل%3˞B(b LD,\EbW^m5Q=nn7Ì)}?D ֦]X]v)JcmKTl\F=5gC JPsssrHD12͛7gϞD4ttnFRۆS$0Fgu499O>&t=zk/D8Η'kĖ l;BȂ%$Xz5"ɜWO0] rT;iQ,;hE{Q:%Z 'l_FU]KH D,={x)Ē(޵W^_VOeS__/111aٴ:Lc) a;44d6wN>xbe5 аD|s|-ΝÝ@(b I \Uաms3iw_"h.7mhGA\^z WP9cǎ-]Tqㆩ_^@D޹s yxEV0dijf~c3A_r[+)b zb [ZZpF-YL.PH"d8Y6 W\7ƠP a8:6Mi^ݡ f:03ެi---xN>mbQ'ٲAc\E ģsk02G| C/W0v4x"t,E,!XBRM#v:NwЯBHdN / -A,ā*FӶnZQ! yk~<-bEK/"b5^-((lbL? (Pݛ׏k?qℸ5jڵ P%AÇ-Q񠽽ݲ]!BKHJk ĊϑFyXfDMJJ^gH@B~EP$DE̗/_-h7\vMr- _KϞ=7o8?==-n wTV&ct-\Yqp} Lcz.᫋ě;wQv4B!$ Qa9/F/M8J\`tMu Qlĵ`.x-Z.WĦ:tȲD"׽y(޺ ̤͋᭮6Vx_Ζx***n߾mănXFZllDtpMM ݻa=8Ǻ߿߲~<\RRى]A6Z|s+ FB-k AD4Y6FݻqMYYYj^zj_qSWQl|#G /B!#u} u]6FϣO,¢ G+~")k5-nm|rv΅a?{e69D(bBD^x.)Q\o߾>/HL&\2d B"b*@"bAG,ܧ#6ɰNNN MSE"bUE ~ĒT*5;<$6 CSC-u:/ZQzA‚ℐ}!Y糰ŻI/J C "FOGV3;aG$%?1,zd򶢣@\ %$'ݞd:7>ŠĊw~Så3.dD>l|r^+IS[/r9JW#'[o[yqu;?TN%j\o6oDLXߌ60|QӪ ˍjMOT*jjڳGVW  N Ja ~VSRҝW'es((-O#NҡFCwʙSZ&l*pӸp}`'c }\;WE6Ib:Ux<AVVVk֢fN,û4D"P'錌;6-2LWhF]]]f+ǭ+Fqh /؁Xp…/xLɠ:μm6f%զM+VY*Q+ BCWV'ti>rȪ:J[ jit٧߇:D͖bX1S?7;^gEy SOݘ8$f]e.1!3vlЉH- zY;-*ؙ$#iy& ~HqF}QI\i~x-u@`3S$'+HbI͋4w$Y,^I S֭~\^.LV>{`uK ;eeN!vkwm)S]ԙ/+# V VAh@D I|UnIB/_-+"v*6QɖX[\\l2!TٳgtwWhNxRRp%Lj[.WT2xbj+%K,|؉_2˷jD`V U{ #}Qp[qU[OD-ST<l[] 99#tz}b()#F0GJELŐV)qlLؖbZΟ }26:4vZ6mNlݩAykMMQNնgl*s/tK 3Xay$хcIbpK5dcu +c=`mHpGP-o fyn$ l #El %ɞu/$cǎ2mbutt(;[ =zT! 3:g |퀣Wrjll8ķ"z0,Ys}B ] =PNcN8FD. Msg^2݌̝9nP+Zk HyAmTu Yihl+(tU)We}•5a7KWh .LڵkԩSN@!z >|M 0b6aw2"dɒo!Q,Of@$阫KZI \7:Lm.0Ufx,9Saw%X1"v6r|+V2`rGs6 NJ};J=cE.|5!TlWQ`C,RVAv'Q!YIv-Pv Ls@, '$=.Pհ:;H鬲 bqr%O)3e`}m'O yKJbȈ0 bVn޼yBE("RG;p555)Xg])g:sb X'N$һy&`v>K尌⨗߽{b`P*7 {msonmm5,Y}%MX WHSf[ bOyʻC}%Ð!t١ؕX[˜VH8nP/2{ 0U1oes=e ]|r2a;ߡԖ]po#ڼ6OU&gۍ1lC+N}=f*@3'tr=$ ׼gA6n-gF;keN͘4 v>;ZW89[-+rMeel|ap3kM=ҥ$f`0n1[":Jl6&z!s bu'~jMr!j&[$ʅ3ݹs!zySs AyO mו䠉G܅fJ[!X8\$tq߾} K,Ya>JԼH,yCb6$i霆g<h RF ±=49(Yůؾ@R{G;f@lpXDbK0֜a ++dh!XB]һ=3WLRB|ԽLO OX'wzZ=~#W|鼿.ۻ][~8Wb# xb33+5-#Q2'5~)8NzWk'+ ^kAB~ ۢr$(XTHe~9ⷘ.pLC׵K®r2C+ F^?gOO5~i] hr8QYR! T=FF5zI+;O5`]d/IIҥTveGe|VnXd,-ͪ`VwӁ bh{H,1vB6+fz `]6/0 5{3y *" *#K%hv̭vlSi8Sܴ̀X^ b | wLJt,8w9[lbbi~.5m;WVРFx7myޫ 0ΥKL$oY 2>6kAKX[! =]]f&NvqND_!fN9F ⨗/sDnXdɒ!(Y8C,uM(YeNkhF!,Dy ږ.blPRIY҉_C%ʚoj'52~2Yƽvpc®Ta^Fcs,<&PfSc.˝l&@,XXBKfIWhVthS= j8ΩŎ+K_Rƽv(5ׅ|fBN?Qh r'L X<$=-iAˆXy vcq1{$N Q:}&@l8Jc !VX19 Zf9޸AG"G0vp￿n"V{;+|956ڵo,8nTVJ2C pV+2~|ߚW>YR1Ad-}K>- :{lBTcb(++ o$IRqZSSt*--uM6 /^J>K6W^vD8.JQԵR e%K,|bS°@xDaH,l}Ae0rZC-WܛBuRS2Io& :+]򗕼X65R@+5Ծ"TEv1V;WF|b̅!NL d79܇с`bQ g4 E;hŊ#똽CS4X9l`lNh*4[YCY::{֭M5n2C!!8A;羭x;҅ =K-D% edd𜃬 1> (e &C޽[Ή.$mذӧr ޽{oܸnww7!Y#Gr[la*J;%%;~K.K)33S5GeB]kDoߦX0==ݣaɒ%K>!߇Xj% ^HS !b Shc l׸grPJߝa uVF]_ްaOpYWȓ燚p޸q`Qa]][p]zq:&;􏥆;/r7,YdK 1BK#$`AÔ3 %<i5 `]nR>pcRFX5lCI-DV0p W{0=cG?}M|L{\_z'9]rέo_ƌռKWgk Ji]m>_nip@ Z<܈DS)cqoWBU.s~px=;Z3_3´kKF\>sX>sTӞ!95򵾲b jFZ,;ND,B;iuKݝE|c1ar'7ʽ.?"aZ|r8~Iq14^d,f@D,D,D, b b b   XXX@@@"""}xvsi.לB5!%sHB[ 7uwqwAA7uy`rFj=|à OnFx[~n+<>NӻrrrBp8oQbt~..̳3܊8X?~BӗJ(ٚ >WWWl% wt:1J"V|;(_!b,#bSÇ q#v00ƪ*-kF 0MT*t*IQJeYVUUӴH$b-WXD+iRʷXD@(%,i49=$JSv*Kծ"X7bgc,?b].sVck XP(C"k:"+PH?#A#RV4#l?erd2HhF" "" ?Wnnn www1X,LӼ=::"ElE"bmEDXX;LcFSl6;;NRIRXQUU>!^FL&뺢(x_nt:5 CerBp׈p8_Q,~w1mw_UM]uo]i ؜>ߙMdJVP# R%[E2XIuDi Z@t $[ H(@KIc|rPsK#Q>:E~[}A>Ω)= VXXhHdK,EQ7777$t}%`0Q<MuL&Yz֜3{'t3qqA.>.NC; $݁ q!ie(#{ogrsrr5g.= :4 D'nB~hg޾-4ΈN~.oa}CIb˯sھsm߾[Gv#wko 4t~?$-gN&޳Y4*sE~ەiTzInrA荦hrmvwx`#ƅ752Q:JLU%?jӷ.?&I7=h/_g/n|w;inٳhp$$*~=cccͪJ&!_~7o|>rx{X{vI HH$b}}}$ $ D <+F|ZߪnVMMMhdHEҪސX=x$z:JAT%zؒU^U>px P# YbO\"L':t$Oyo,0iS :z}mvm2LGw;O.D~$se gߟ%I_9_<3Sg_Ň8qeP(J8php%$:uJUbp'|5 %P&QBSϴ 娩!㸍Vp8/++Ѐ===@EEzK<$'ettsN4?|0?555Z+C:|\ A^Iqqq$?+Jk```evi+R#$)tnٲ# w8j]8L&Q\m sacA$s>xZLMJ,7,Q+>)_h%;O,++J5- Di' ]p(rbZlZarPGE#Mǚ  j}U0b/.M'XnHDzzp%^ e4]ȇe-.#;8MJJ *+sRݶ7??\{ز2Y?>jp5wx{=s9FW#5Օv&4B+Qk^#g_BQk,+´̆f&alF51U. l|koữ{H[܄BʷWOi'(x<ڲTxWV:s0 vJ4S$?U%΢9nmF탣 4= %nW|r2d2$WL=J /y"r9W~D |;a/.WJcp[T[j9zCb t౑Xa!)Ňdu:R :ʎfQh3BXLtlۺ*X,ݾ%i3'm/{8㓘8Lꤩ38>WТ]X=VZ-/2aFF6Ftal1P1dPJA0 :8!yG&~YݽW{ˈfuιwWwwto߲|`cJu_Ug⴦ΰSolD ,իW|`Sۛbmn 'O4n8\GF*Z5|J=#kU^y>}sc'Jp-"+]UhyyhN~_fϪW[9B6}ؽęsrR GN,n >SFekIԝ.g)}BaI98996Z h"q0 $=ZclLSB|/h<=iĬe20=;eGLQ2ָqἫw6nFuzg[(ǜ\245;ы$ dv_>Z}$":ucIbp->?UZx(7$֭+]r%H;7vcv۝H'r\Wx)Xƹu;p&C, oc! ;X1I,` #DE}X---tBy*Rk ŁF+1[KUX,Ԯ*aN%>y ]ĆXC 6507{%fNGBfzO햽С*AÇ Q8W#!W.Þ[m_݂XR8yW=}L;l^ZDeMSԻ2Z٪P!WʱIsuĸS 4[I%U!r ^?HXa :ؒqM bqgϞ$YApbcїW!VKKK+30fk$e2j_i#o`%OWd0u%VY)Px,9Ew;qY#FݫbSE7& EXj\ec'kSSڼ\ټ bf',ޘmŗ8V*%Ih4s5!)Ŀ]ګڻK V2z>ǩUj:naGJgU5fc'ǜrBg mذA}W Q(VjJ+< g`\H09GgDq6gkM0y׮]v|N1jrrp6O> ˜E---b2Mx/ [$aK;RvOĶ;X<}%I8L])iI8W*Mb_f7 7-؎+ܪgCo#"ڂpß9 6ܧrI6x1{Ի2h3L1SNEv`]> DbӖ O>bW!юX# G|m ojњX)[rL*JY`w;\g&@,rι/?~z+ x7B=VE]̐]!d?ikklb(KԒE(,#ܘ"Zp4Uꚲ2AD˜ ime͚5kT7=JW d(Le1u2&ؕovVG0R bV«Bp,_UEÞJ@5~ʼn-gbgx8A\^ \3wYJJ2e,˾UA>sƊ 5K ΂Ĕ`dW#Bp߯ںr ZYB,}wO!vL*c /.`pBlqN1HL;Ytkֶ6a}3u bj*Wi o3Q. xcŔX[;޺s늻Vv,ZY؎Ă#}bStOc7>‡_pa$UQ G'NDrs ~]B Db!XJ=bp{tqbkaK^mgjꚃ*c -*˃ ={R66)S?1 Ŧ8;_DbW鋾{bWr=UZsJރ{-Ulcy@,\2{"p 实aÆg5ĺf{*Ǣ(T"ЬJKf9DADH6--; H%s%:bd$g d[ ZZZZYM\@e!` WhA³m;#8HTCX44ZK 6*p%x^H RYS:5+ :: RyRk18s-2V l\?gw2֐!BǦ;6w%8ٸlYyFRAhriKZd!0X]_.Z>߾~d[/֜N|N_2KIK!j<3ŋgΜ`[[ج@ȑ#9r{='AHJc䓋=s挩uɞ9Z26ak^°N 43gvTHg簌hʆnf)Ąa]X^&9KY*5@qVjh}e$e7)Xjbqr/5m6QEE(J -eg(Ģ˖9`]Yb c9NL- T˲'>`fq]W<$HH#iAG11ߙct|0t33aDžcMI8&_i͎ݴ?W~sYl9ng^0?m̞i`qdĿ}5ki ĢiӦ9wzWJL:alLX-_򆱆9FvZxK$ ^z\b}g8q"Ȃk?5'NPKAƌ3h ZZZZX(fEEE\$j)(ˠYްeqD VUY-RyΪm}18RJߝE>_SiiCIɘAI5J9 aYϾu_ϋ$3'Ox'>)yab!b]d7W*{!559z"9D;EҦҒ1E3J4ys/fG,Zkj*mh(3h@,ŨM!HC񃞜Yz~(dU  G'F]OUWP'^;iAe?/ɋL=mmZj1u!zAY)p0>OG?ybbBƛ+7+c(̖ݾ߿>eG /R}(},^>nY;G|'>m½4܎[K"T.'vW7bA~o#§kD,Gw2LbmćFkǏW ;f;lZGBx^ q?ҺA!FdJnmW.ASxreЅhTX-ןlj[c}5fX, شT#yyrsr#_K$((Ofy lg.8Snk,Ӫ>t~6Nc7؟q¿E{kXTS27:]:CKK+s+cS䰰r&,eqE*-~62s @pOo}KnURPunoedد[rCW\bՙBU=]H w'>Sk',wnh!aVZli#w CAH`C֟_74jiiii+ ZYP7SkRpd5ܱKaa.ΗĆcZ$R |n% tR n5HGJqJᦏǮjt1r4{Y\|< ڛ_Gzڤ/RxsUܛUw\ڮ\0mo+ۼZ1Ul7J[{{g\L`>2zivDlD,}Nh[<Z7GR֠NOY]ʭ>ё>etf{uu7<dh'K匌 "r}}{ (b5;v 赩Kb1Qjkk[ZZXt[PPPFB$SX\b)**DNNNֺٟj3_sZݦhϳojx@\ bpQĮF/ѱ}a'~8߾}ӵ"z .(b@(bu<+F1z7%["VH01xA࢈齸ˆՓb>l!Ԯ;-mmmbill48B࢈Ս8";88Ahood>??ʹFl8/t"EښXӝXV>JlH|yy ~7hA999۹]˚ͩGX\bw:bus,//-..wNlssXtI"E.nΎi5kh[CĢc ^(bNe؍ <=333c"611Q[]DB1@ஈ-..~~~|vvCu>we EιU"_d]wl$ћCCCUUU喴?ؑx^䌌\p8/$3RG#VUTT/KMLL@#bp]ڳPWRR~ 5;::Ē` *X ~RWWg1k(AMk?M]+P(hItkhM"! !װI>~<۳<~FN&|~d2Ys||\ץEa6T*EQT Ka lvM$N',q?p#vQ2<88`WX b "D, b@D,X "p"6*^"D,X@ b7M XD즉X b 4 "D, b۰RR$NXat:jtkLBݎAIJޛdSٗ/VEiW,xm\N}у٨J_lZ"v#P(X b;GܩD٨mSfjzxk֬ .\b9K,KD&Z[뫃ǚY˘l₀\b9_>79[_~%==_)`̙"Hrp8\b9\b8%cJbdq|+J&W|xU'ׯ8p`흝r.%%K,XXH( 30b] {R|d&$$dڴiZ{ĊX%K,KH$W'ŋJʕ+ӑvqdl>|ի˗//[ W%y[Ο?cǎ(lkffCX-//?w?7""B2,0SN ~쎎TWƒXo_X9.c;Q%/zEFZx'OT @f2$P" 02ϟ$vXzPbUÇMMM$B<==޽Jo,5z?}}}ɁaJ r8/%| >?>q"`x1Zw2=H[9}WWb;^AG|PbV׿=1b0-P,RldRO""xy=Ylfzx*u%0(8j'kBƈ3gGl|JI&&p A=wW^Ys- ơVk yL?6Z~ḴM7$߶684qt=iiLa_mTUa^kmmݚyf"aogCZۂ %:%v㻚^whּ']oX TPѹhC\. *--Eh櫫%㳳p#koooj;"kZ_:1xbAAZ[[߾};LXEY,mgAqE|iRwv)˗/(!]u%Cj pjwef^ə vue"LM9L.Pbi>WmM /M|Ke*ӏ +]>)U@>Cb7mڄZ'DE8.AdBJ,xs&v͚5ZWoo/_b dӧOc IZ t?-EEE(1-źuɓ'pWgbn*p8%vr ~w'Mj93j<ŋ.gfx% p*=2iXz:aU?DXTѧ=*.+`l(yXF +Um0H7R]uCfxϐg98U?RL&qd%LʥXW`m-eC򇽽~Pb,ٚ4$>|WvYx( K{ͬe_HbĐ̄ ΄? J_̼i˲\uqZ!&kͲ@d&VR)HH,R|㼼d[ ח̳:< LNҳF13[RSɟG((4rBN:]nYU9b࿥3=m~ 6!>%C$FH73Θ1SLia'u355Us/9DBtg6(ٳg6o'1o%|iĎSn$O?9/ZĆ~"e233&h)p$@)otlL)5PGKK%Ŕrllc e1lTįdz`d0grt&#l{| sEncSU\Z{/0nObScHbxҌ=~9U)A/34g\;Y[w}kCx0vbhDDTČ˙{dc'$21k˘ k+O?׉ UD ;Q1.;HaU*$YZ4JgSՇlM'hjdQҒV4Q H֕YtUmmo8JAc}#p8%vruDh$bufa'εʘ*b+'JW''B>Y,U+1Xce1 X( Jpdpy|y *B w 6ڍ.jq"d%%,5H & s';-]Z<"uwq{"ˆ?дӆΌ f4Tމ3ӓiI䟍],fݦ8xi ǬĢ51ĮO00˨J,ROQ*+DwթUpWTíL )meX:F\j n҃рroQzPWwDa)74UB#>сi !?l N:`o%VdmiiQHJJ2Ģ/tV2!`&:rR^.0:' _E*'6ºmnnXp8I;n9X訑X,30ǘ y b@bYre?ObitH, }A kTu#&2CMI5ASK캄9X&AbcBIbxK{1 ?7e*mcCsl)-ZbKCBHb58UOMUb#]*-ņㅅA..oCն&@ ||q#NՊDLb)*W%VdER 䦚ZbKJJM"n՛7o.Zmf~ĺ*"ze!nmRs_Ν;wJt 0pLq BT5H_3loTiꉵ"Y]VjlM$^4kerOXR\3Dڞ4z31^;ƄrMHTouF]ⴐVngQb-5s/5"$;"m.I^Y&RBYP̧L5"Jl;I,ⰑCvH-jD jD0]4GGcWج;55L E`DY/oI TKh+5"h#Ŷm(tpu8RݷoњG]brK lA16(\}3,{n1 \b+ TIńX(~&KQw`b`f[R]n`2؜9{KЃ6 Yb9 3M7.STVHv'҆үObc+^b#,i[|ϊ]ibHlY%ǎHb)jRLL=kJ??NOD*rt7ja,{_'J{T_On`6rUT %%$cùbo#;8Rە$'&ZoSY^&qJKI$zTk.Ƙ$Gk888 Nb… 7m4#%Ly<҉sT q4<<0D7ӗxPQj5:-/|JhŸF S`iT}W!KchaEXON1j\SuvoQb"F$}=|vvP{P%VϬbcONcQh*l6חP\Kg\zݾ2lxhc6\]WV*Q &ygסW&`f^%V|%d.]4''',,,HŜ9sF/!666x4z@qpb&XLι~z} D萐RWeeeIָhV"^^^x "Vq95hm {P_Z u١=4FGEE˗/Ғ9pND&fn .1V)λTsS,,B6c#ǵeffeϙ9m+*hLai/!G`yyQñbE|™y0s8#:~3[W[%V{r ݉YRZ3JK~\\lPXҦRŒtwB{pXحيyqnyKw,AVĐX+:NM\jh <2ب$Ln^C^!=>!׭(#bjJ=sKo/atsQxh<c;%1>UDnPl Ic|mRRA` `~e2U_>ͩrYY?Eh҃RO԰mk+OvsnYUvj2ĚJb)G֧__K,iUB~)S$444 ~9o*$11Q >TS7ە%Ĩ'? 0d\sQ9$pWܘ8GUi|Pi$Tp][ /5* X0tT,>qp3Ǣ?>^1u$(ˆR|e-uUЀ2]Ȭe5謃OCu0rc":p6wPZB\Wu>(GY4^P4H,8*Da[0ׅl(Vzfpʐ|0).v //tT4'Џ'kHt&ST F 2ٰ,6]^]lm?VxU:ɔL/ekj#"ߨR7DFXpLXׂ։VeJٖyrbZ_:5^DTPtq41߻;5.-9JI& )Zႆ.HZ$2~=!<>~o~W.ϟ$3눿4+ L@D,7퉭[Taj{Qrv+7??nW;t4z|+b 5"D "M݈ZK XDlX b 6 "D, b&b@D,"6|>+666eff撳HoRdDwj5BF+$x]R2sU,--s8>>!$Oň-d.ryvv6AXDloVIXz&bONNBv[HVoZGGGElᑑfّ%bQTBõdS, b*X(k\|'w.PU]gfҤiNtfvN;Җ6v&$1ω~Q RQ5JEQP|#o|D (  _ʙ{s޻ý,}{tm^}IZ%VsJ,`$%Ჱc;vPxꩧEbU&N聎ضX<+֭%F%V%3gNǒX sjQefCgWZZjظ}SvؑtR3jZ$ވ#&O~7n@$kz4XX-Zb5Zb=z@qJcܼyYġO<#Zb5-X~kqq!شi(F%V%VKXXQMM ĉӖvtY<@̶ڵkG1eK.>}֭;wappؠgB<F.]`40h322JJJ._Ë߿O> wi82eO{]vĂ/~9s31MƟ!Ch4K(d6ytD+wm̕uU;#y +E̷K%^|W)*8r `p`HT:>ܑA7wo7C۶qe%>ql[~Z^{O$u\:GV8aPW㺬>~Y# ^ȎIkuE;%7d|+{h,a͞}#_21^8REh  ӝ2srr2]p6qɬ,|Y% W8p>Xi8կJKCk( 3zv' d,Dbq.ӧ(xTaalp[n)-B|W\Aa#55y7 *l>CޗP0 9j/rF -Nb2ۉ[HxM/IQ.Z g[HnatJV>īln%J:"Tz% arCy9))DSn.Zz^Hyc7+L$6بBbW%JS1`\7^{OYkZ:R`|-vV%d5ßd. ~+XCaxYO 돜Gc#=nOJ0-"7 6oRb׮]{!C/m#WF_9%֧q]KBs NjӈS"jf̘2\6rxxʕ+{[CO;"#Ø`@CIS#ј?>fF_FH paZr6/^W42qD/[Ib{՞/{a aø7 >?Rh4V%3D}saβS09_%=1DD :& vDw+_bf)vU'ƜŔ,^,/vQ?M 79 `pPYB^EjkhmjQDRٳTSPVc ^e%!CH,'B&9pD|،eoRDbOX%hT|=)>b.BS(2_ƟgяA+FL2,^ ys!қLɱB)+yEY_7[6Xeޯ~4w񿮜%czȑej DFyN'1OΣ1qj7/-߮?6"d jŊ䆲2j*Ub%X&W|wŗy5~EJ&Ō%2i=X Ye >&@\qZ,Ҿ;fY|W_uZv&dD;u o| c~4#GH:"W Uf~APu Ie֒X_™={_B!+ֶE={b> O>$h΃&Mf"*ێ(.H:5o2?s֭JEbBh[מ/{upm|4%hׄF / Jb{;KٽnFbmSX S %7ÁVle坤LMDD4/.ձ/DE5t|*=ְB_фR5H Fa$@$v!-lP<'H `heEa?1Fw9zdGİgaϐi*{3|4~xB# ɤ$U<EK[Չ7 O %VF&jJA#yib&*G- z-C;h UdJ"+TOwo_Eg^(!(dF-<9͑ADjc&"v%[wX"=ewY{I 5ۆ R3 X'ͤ= t޼*:rVUGЄx,yMEIDh{ebl/ѥo2x/kBbiLVG%`6lmWSg5Nv"s0cNBǢOOV}v](dPp*lP<ȼb!۷XT\s-XiZbx G;ׂbb,ԌW܎T5FNݖOS{Ư!5j>@m)^^JX5Yuͳ}/WPhGXN ^H9XnN23Wɮvŀ.D;U.֝HlK 5J#XF7or@I ;]F1.]b.)'[Yɗ/3|N:FgB똕Z[bǬ<&,C^3HN=P E>W՘jK崌5iw|v:v.з^Z4VǷ<`_ba)-!k R%c68м._\KllEJ-#.kgPw/,Ԏ+ҸLp#"?DhiYqG0`!@!O2l2pXRUKFh4%rXAyVL(1f=\\Kdoa I,rEњgEb׳kj67MVJ eIOwH  bB`BBJi{9%6==" 3cnݺ׬Y#*$5?Qbe\aC$ dFSGCK/Z 0׮]]4zdzy*^(EthrF.?_N%~iCU@,g1 n;wF-=N󛁲m52S&K}i;} J$bJ2of>\m&%6dn8p2OwM%, H%%wFǼ<8bbw;F|E"&:i^(4aw=&@bG=! ȤETv';Bذ?~%4BwEHb?Oe.I@ BC)9s*_ESב+p0°g q%!Y)OŖe? fHB^ uÜMG?ǔln{F'<9 A{"dIH60=MU -VJ ݿ|K,"`d(@oXtž>v4ߏЙǽ> }ٳ!h+JA z~г6(8 GZU1!^Ī W|4hL16Y?Tj}Luaާ(r6Db29&v?d=( EElFzSSyb+V0#q+ȥ}GG[):`8r2}^׸Oߴ? a8酰':3 cYZ %6[$9R-צPJ?NNe$v)/%`~4 f 5k>cX$MyL)-@FS 6DbuX ~%VApĦ0mJ mG]XSO$,f^,FF}Qy|M{YXJQD4X"檠t!@xyr͘&A4# \!*wEwJi4۩8l40aÉm̗Ds ;\"Vd3ʙ "GN spP+ gp?6mbWT0B][+\gn~Ò~=bH,Ѹm[Z2~OrÐKTU^o &>oȏR9(>'wP"BWf/`yX$ v#|+R.]˃)XLNGLaKőcn*^Z&+`NMҧg J7QфدڴhA5HCgEG_q;i+0ܣݵkW" WXi!}i4mXͽ-O4=MϞ 'N0h5AvM߳Ǜ=aHy5=!NBcƌk n1hjڍDɶ5]o1E= &ZMosDt5 "-9ŕsӏ`o%bسiO Jogffbə2-' -FKM J'a|Vq[-Sݣ̞MsYl޹s'N:pnލd빾q2mRgO?)HcܤOMKy GP` S:_XF%Vք:捌..Ru\uy#w7n9bkȴjL9zٷ40÷PR BCptfɖBSo0(..( 9-BBk|\{~V+ ggg"b˿K}R)ݥ~bs0Q34ݧiŷ/oJ}z%b`GD,X "D,i"DF"D, b@ X b "D, b@D,aۙnXm<v%b`#6˲F1 fAb1VvKFMӉ/$?wxx7- ^/~^~R)#F>O`D,]^]]-jʲ8@~El4vs\ BzlU.'p8lZOOO_ٻWX8w!E!ܲ)lf)µ\ca+eaa4.!n{Ŝgfkǘ""JX"""E,*4k"'bt~K<,|[/lqNnll3U677Upi$DD6DDDXgJEb[QQ1>>5˙ 6v$CqV'v~~,jHYqTAb5#CuD,.b)16;˓.<^__K|5ƒmU\*~Wsi< Ѣ`ѱ^|(<뫫W,DD6DDDi{X |||\OLxOĄXdI⹹9q|UUhX%""Pŭ fyX>7bqԙsfł9H<VoW#tOg55Y`y bGX>1bqTU$P(1v`0h/r G";i{]-cBDD#[Ѩ?~d] QOPFw00E,F&*`eX_RVQZYR^ {E({^g\.~^O k(*,w$ yp8hhUvhT[VK>^bRQO z\*7sne]˿Q(l:VWBvkfT? _b'kbtgrnv-YIZ-Pۭİr#{2q~3ezXTZl6xXKFRdzxSY}'~?L~?N@^  ;FS0Jh PGhErW@@x'Dx'Dx'Dx'D,D,D, b b b`Y8\ͥ-iYlA`i RA*[^*UT(tim8%y%t<ɒX $e+I $#6kF,$+I2bf+I $#6kF,$+I2b6#voo/5̇hF{-//GV0q#[xYk(eɔN_ߵq_oWNq<5TRǧsJwoŇįFlzQu:0VVVҎ2b1bj~,XiXǪ_ćOz^E;;;)4(j:??_777Fl{cLƈM ?bUfj*хAX\<|x'_#6iŠ_ N.Fwv;!Os1J|?#6ݽXdĦMjjL;jՈЈո }GFl ^F,@ZF+#ֈ2F\U#61bM䈭T*sss!0bXF4ru,..omm~Ş4q`C'v)_DI gX8+bb+e6`a4 a+f\Ck"k4tݾgܲcBpܽ<χ{qkk[[[f9c0 PH2 &r0DPlnnF"|Ti$Ii@,U__o4WWW ڶs:h$ "D3pvx<3Ns8Xl?dww7#FJ rf.*j+!&*Md;^e2\5Ww寴s?r_)ک`blk\s7}5‚>&΁U&A/fAĝqN Ë *\/rQŴތd6VV«/wuo?E Ӗ ky@yI[R!v'reOZ/ -N%nJc:ڑ݉e$K!pqD0h@jT VT5kϼ07=v)* =ri?[kuwޫx|F@$JAdU`Co޼ZFhnn6 { Y+;瑂魪AdGGlPJwr sA4ͫW第DZWX!=s^N#(_g 1\'6/qgow;6`eWAvh$L/Q"?ZV )HPyZKK$Wr!hV lRK$-$=CL;h4R2ޘaMX^7>^J-QF @8Llbg35˧I5\] /bwŪ"E:ԁXӧO P4t.B N zQq5v*^|("DIQ+iӘ'''6v:p]jDḙ͔oCLvѦ%RzY"G;N4rEvSH3HlW~acӺ|ıAA\JJخKnDBub#/%j썝c,kḃX>o&Ģn_d #{!a^5077߼y^!B&OuO/Nb?XY; ++HՈ>iJ*',ɽk -Bxw`rp{\)N" ¼Sb{%˅ CZ-& 8N*!vˊAIg~ WegI -\HNzۛq1nRlU\@ԿdӇ옥`_6vٷyQs+ vTR@єX\{{a"ap @,N b1i;ǭoܸxVe2Y" V[t*7L;^|BKdALe{,L7Fzc E kDl\/^L6r8ɠĕU+ۻ ˆNrp, xݛC)ֶ}Rb 85 ?K3Fxm;AB^͜=/ EM,]<KyyiMnbΠG[4p4f!ٰ!3eAXcoTSb!GeGv^o޼3B?&{djkk'bY.>$X'tŸ5Bb^+-z/b_TTH?(& >>VGS2n.MrLnw>ݯM=d|.u x=`W)O6guͨP la=ScFŤԶ ĦLYmHV(WC'Al BڍǐS F$SJoY WYo F笪T麭[q&)Ѐsc؟YbLvtSJ8>NkZrЭK+AAASb٬fbbѓebSb2?3uqq"D3Q 䔉DSbi_f:MhSNhgo2TNbO{ bir;+f=QF EJkolgx!ٵ?J@bnm_PZ8HA7GB!IiAHɶ~>Sc;<,bcn}dX{[4%P=4Kٵ5tD\a2 6<\GWqUQQ#0*oULQ6=ry-1Htou`\l~ǣ@_ GrXݤ Bݻwle+Q'&ŋyRSbuYYو]ݻw "\" Vb+R^5>>2}sd&ﴷ7MYhS<1e11fbA)w[)fbA&o;5!vol&bQ}Ṃ|b|PנHbۯKpkeiv o",l: 8 ,e ȏ\})p6dgE>o2 BtܤAljj*4>>iz \cgӧOz1br eϝ;7"" Vb1o#g=d\A,-.kTXB0ܯIE*1jrZWe1YZ`0^'OcX0!՚ym8@lImkm{?a v2i֫܃/U߾wYUbu3]cb&\ѕbz={|l Ӈ0`ѓI:EU gbQY[BHggYBir.,,.MMMXu޽X\Y(ŠZptظ+W3ʟ.fn{!B1mӧõթÈY7Jym'I+HՈv9xe^ [ oKK{JLB٫MΒe$!mcbFڮrJR_\|j`YYC]9' lli:݆m/*Wz-Q\A,sz+>owQҎeiA{;buUeeD<3˲2{׭B>5&hbGKxY5K`DX Dfcbb&bBl1VgׯZYY gXHBBXy\G1KJJP*^!B b vS'F `ڮ^Fq}r&X==&ݸ@b#Qjt% hY -j_'e{fTMxڳ'D, nWX*zi v;Y!}֬n皥2D3>T@CxKC2;. fw*IA @@Μ9I UFI%lbخ][Rlt֋%u,1%GRx1c!EHBs ( keܹ|!%Y~dcXX㯨% >XȌ3Ξ=܌>O]]]16H644O?!BEtCW~U(jpw6kLJqiXZꅍ/NN==de،MXN]7Ep&rF6~Cf!%+76*6) )iv6A`p0A*iFOP[n,8.Nٱm ؀!=Si)u#9{J范*'6!ڛ/ ?8677@҉z׮]) 7+XώQ;zzz^~]~/Mym#a=Ä%-ِvtE\_1O[(vb^r!V^k4vto/:_19AE)VKh:6TV#~ cWyʮj smNkҶm)\P+b67|Hk fb>Q6boYP^YDL8\؈2Z\l|9 W{{m?dZZry( e4}+Y'.}bWÕ%g/buiii.\@i[[S?z7o$c"#;FFF:t{[ZZuuu_BvZ#aȂ .AVpNN;dL0*˗H "D˄XXTzz"HFh4{s0y5` Ҡ1ꊁ:ȻB|}5\W R+3\nDM!5hfc~lykFy1D6uܺ:Dhq띭tZ֕;g~_D&YPd&.HXY|GIV^@8Bܼĩ˅!%a;} m1`WHvpm襩0uX-!a-j( 665 %*"]af4 eĠ/,lNƙsyx{gs}XQ!Ծ}/TUuwf#__WUϽg(`Ldg2jYtQSvnlVUJ.R,)i3ZUzF.~}Ыz,VC,(m*_,EOE6!>{O\2_9 )kiim !x FQmr):Fzx<.d2MMM bP6+w±2l<`ǑSsm5?|x&د/%ɺj>_6 Nb J$b^Eglp8DVVVdx< bQT~p>w}1θJZ}th˯O]ӼioƜ-~mat6߿7rH`uuUn?vO: ĢYm;: ~ߖ P_w\gE{? *>). m3eXð ѶUUܙ dVUE!ĢQ*/[ } 5R}lnHgjKԘ MȈxH xDCQy>_6:޽XH%b ņH%8:rbX """!bsiqK _p5K-$H*(#HH0*(t:A匵>Nh~nrce1F. ce1F. ce1FľGLycɼ^ >b)8kOr;[Ȭ].ZV}A 8<+EDo'bΈX"eŪGC䂈eo'b-KV(W*zvƆ"Vx<;;;\nF2 f2U, t:-S DD'lJ}RenT_I@EޏcӧSD,sqڶhww[[[Xlޞ")LlÃT*EEsX]V:5qM2F<]5MnoɤjjZ.uJKXP(8o"eD,ˈ3MTwuDFJEl5H$X bXF3|~/"VĘnL&h4,˲mJ%׈xR.*g?;<kTnz/S"B:U.(`OeTM1׬ BҎfnii1ԔfffĀh[QQaH\׮]Eza~5pqyy9JtgbtމhAlNNvp= /FBm6,T_[rN'*E5Gw-[l֒"S/ѧڠ(L,pXGYwmLBꊺݼ.u? QjԗM$cr Z5)J[-:$=2ƈͿsnGgպ( ΐ2s}F0)ya `%N;ss#V1(`4Y%~^Tw3![VsW-z ʆÍW4~FN;-xSkCCN!_a Cq\@`B,&].RzQ/선%',BYYY)idcU'-6Xҕ+W>Ȑjkk C-[l%X6%󼰓_ &՘?:%nnƷf K]S)Ay7%]@eu-Jq Maox"]ة*?0KM?ZXQ\|5Tw}=&Hu%k7.b zZb}bbcs ʹ6ɴNgL;m3MTMZX=D"!>⋪ j`щ,jJT " cD$ĠQ3Ee}wYy×klTGCIt9KKPmCPaad1j'ʤ֐Vx_^"b!v,bŊ XQXN=CȲ,B*5 }&d>uHCc]ma+դĴ:2lkm/tlD9x#3<{<%-Nw/ߥ{-nnµB Ħ[l vuos_1^Cl4qOC[N; _ jRrJ UIVfmiXdIb} %~_ҿBW1(K@ Ŏ̆ݹsg0̒doD("Z~ׯ||5--bş5:?P+!!RRR VPKN6Zx0Ob}[K8ӛoub%j;l{:Va%biIpCoʻ*VѾpC,U ͳs uu@+ؔ~nzB= ,Yq5`w= f5n窪brrViRrHH+(6lXnn.򟛚nyUBX4P][[cd ;"e)iL ._ [쮿qƱ̙3K04M9J!< Ø`Qfhm&6r0^BlXn~Z^if7w bk#B3#q5]Gu^l`fko[t3n}cs\hX;Ttwa;NW8UJ _Nh&yyz0>osyo$c9[jEaw%Nk+"l⤤ vF U]]BbePmFW/Z!AD);#a6S4C[ 9UF/#bm+Q`GDs<5Y`(Ȅ.Bك viiiII n B(%!VKK.R_rs102 #C(1!ַub41fgAXaϜ=c"q@)w*_ckαiw/Ģln].0JrV;i\mz鰠/'GY˺b(J,WwQy#Y]݇Ac<ղ] '--&&'TlX|r.z'DEhW><"=XWqCOTD12#F=B֭~" X}N[- ZZZX"(]b[fiYq]$\5?AA 2 g|($p;1A |w)ĶYs^tQ I77`*kCLɶV Wmaجb3eK"F1v9}ȜG 02,izK.ɓB,HbJwB,Rw눈Pe! F3F]PFFX--KCh&aX%;Cc\gX f!8(<``Еphci݅U6"G58 #}o`*&bry|<ײ*ksCؓb:{2}$לPrͮX-Ꭶfg|i}=l u';&wƳy3 8aFbڵKCzJMMmXbʕ+&O1 $S -ޗ0uѩY\1 .mFV&R0Nə0'̴tDe7ً 0SuU) bZ O0Ȕ߿?عjA#=rW=!mQۂRbUB=ynB{z1 bu}ljV- C`xz^顶3`6q?upݽ{M͆b`?ػw ;wm}o߳X---W!֮g C6-ko-G,;꺨ulZѾKu'5P+:*#E:N1ŴH^v-FBpU9Vh-<D/ﰘn[3oyXYWU/flс[guXjŜNX hKBX&gZa}{QLqf݀c$F /}wy?x/e6CҨTV׸(L++<غ: 0~v/) V*** ;$ѽԊ\8;v`O.ڟ"H,:"ya,XЧOշo߼<^B^Ԑ!CX(K 7iL\bNYTաfI"uQP7Q1$! bI/\N!|/uZt> {zC^]} OKD,kff&bE,X333 533"XG"D, b@ X b "D, b@D,711:vvvrE@_"c#vffF b]Gl>_^^.J777j5JE3bFFFX6~U.--/\\\jr|tt#'b`H#vttX,^^^Q"6!kkkF]xI\~~I0;55u}}vR9>> FDl' CF#l6KB&J8Vkzz: "+b'''BWWWmL&qvvv_+~.tzkk{waCHP5(Y1bTDPHTF!88'$"4?U۝N޾t6o%v|rm&yM7oǏ{zz6oެˮev" "tEݻw]:6~ 577ׯ_Wz>| Rkϣѱ9Օ1b: "E΃X)!p5JS"UXyn666ѣG={,Ϻuv}…oOŋ+bkjj&;v𠩩iBZ+wvk׮2XRB%|䉻߻woıޖ]uwb{ܹ5k ׯ_6 Ϟ=TMS׌AU%:ww Bհa'Nx)%s!Z[[3b2XR:z6Jw}tuu%9sxk)4ˆxƍ"Euwd͘1N6 {RT>"!m #ԩSdΝk!bHQjCN gJ8+dO>igyeEݻ[XR`֬Y̊+<ТW+Ò"VXɓ'랭0#7W*bu7~۽{ˆXR===(,iӦyU]wtR8_ˢNJ5эV-ZdTg`r~a#bHQjϟ?Ć .щ4Z4b-[X&Wm6CZ{a @"Vt(%w gQc#֭[ ˥3c+qy#vtvvZ2j+W/D,صkjrիWnƋw>scYV\勇$X 0uu8SZ%K{.vcy#VEwYbc~ ];u͛gqD۷ǎ]cǎ}ݍ7fΜiΟ?۷oMMMGSiUy-[lذAUUayΜ9>uuu .xV}exxŋZZZ kժU۷E̞=@SͧOzʾ>/D-M4$?bP)PIy2[Amme%UE+(~wQ8gGcnw|$\U,H|eow_܌ɪ'WVɼ'_d}"{o,OF23 Cn #B#6:ˈeC#v6#fӳ#F23V[ m[l62Q ם2bjcX-fnF8V oՓX 5ѪH$N;F b^+1bg#\rezn"MJD2{>g2a>vylZI@z*  AiLZT 2 ƀm6[<8is.FZsee>6FZ|jShIA?tR0`OċiK},bՔ% ]i0چ]RPLI^Gu%hLAŰ(\P02`/>ZbE!֘KOT-4F$wTR:yzz9/hӾ_,:~8۷oGôDn@\DT81a*%͜P33ziO|[WW'4777w#(Μ9kTb wC1'00XĄ &L$*-m:'h&6)/JiqGvq:0]~eƟr#|W5L݅`p=,[e0r-Ut&ɌThR_(d+0U)p0[a"bӯY͉3szr /Mp\WGZcK%VaPsPiK>,K=?TB۞P7x7drB#.pp.Cpۅ؇؈A%=%\ c#>O4Ģ9ma9`NZDؙج'5)ɎҟaRPVpY~/ަ~J r!b1@K^i̠f@2{l3'lL{C^xARވyWz#nCCCQFb7nH/ڳg*w57++ Fګ$_̄ &LĎ*6û 0n r <+e ҙHZDX+ m7>@;8 ƒcY-TNJ* bK=3F,v7I[ho_非 6Û94+ƣQEiTUP1c zMnaL*JVL,c1X44HlHgrIHbvSbF:bDCJcTHz v899}A@qq4#@1J^*I}6>ؓldBVز2Z@@Yonn@,MC0a„ Q%xW]%#޳e떹,t\ힲ8{3vvwf|;Xbj׿+;v@dN=)5y\x+0LSqa0F1\-ʍA@@.Xx ͚.N88U0< q QGhA0"I8jPA^d;/j#]HS0aAbTuM xSB8=7u"Utob;9> Lryu:{H\ă O$Į]EM!lnj,8^Bc߼y}NL0a„AbCL!XJLָ޿zݯ\ v]FwowA{`dcFb_1"5hA("a\W]e?/bۓwc-[廀1?tb@~(* ĹJCeciw^^{hPモ bނ:Ă`0a„0S J).|:bűn/;.:'f$HJ,VyF}oQ( ӶaZD\fAĂ`%9zhEѓ8W }'vzc-ޞlIoVćaȱy(bQCqçjyUۧJyŰABWnd I̙j+8;mxb_5a鱭Rhg)$˗/D+Vcę$񾾾Ş'`[ZZnݺ֚ࠓC4,`!c&XI5^}Hpp0*h-,\e„ &S bcP!BQ]r ptR%;8Q"RQuyMtQ BEH)sGw6KeET"uEAl\^T\*~>|n󜳝s/y~W%w:Q= H=ޭ:ʎKvX!f1ugn;lbd)ITt};|Փj-9jο[gA"&ʪxpt.yD5uRUbO5;-V#v:j ?X8|xy6xVXUA<хiH[VM/\Ptutfڱg6m?;7%ÕXFbM+:X&M,&4iu}9, Unz6uQc"rbrGb5PG)[U_FkN[dK(O6}U fm9YD;P*Ē9=CXtXJl51Z=V^u?Lg> @E,^L5{5w27 bUK۪+;K5 o|`RӘ'E-X:fT_$EBۂ;SvԄq5@d9^ xx)*%^M~|mx<3?88I$ƓƯ#b5bJ}WT]O%b]4KԬ9Q!Qz;+p7k{XވmJSD2zĎy0>>n)LNNN}~q\\\\LnɐEloOX҈ H@r鈵,P(vX,󳳳 }g4D"뇇tZ8٬i܏X`/矎"K2"e5b-H/*QF.(۶Ejb1L&Jj*}Gl86gȈ~@?;+Mft:t2&J'qp{tV0*؀BF:E ԱB56Y#X\@ǂ.˲yX;dx߹3 ?pνBk@k4iBq:%p\ގԧ2ڵk{ RCCC}1Á2ر1CC iɀXzIl,*!v 4+b)BQ":7KǿlCg?tsJ.'o$ Sm6O>!*75#|&π1eڪsṘLft_S-S,Xq(ve${q=!qKkVށGVLtźYڹ:aTEQE~w66ېgkvvΔjȊDOqΤGMx#ȺZ~eO\7,+ig߯yaoS[=u޾s>m۝v=,KђOFȒQ=lXC7A,s9A{:ˣYsFn+Xtԇ8N>|k֬ijjl6b 8Y(#T,/YD=nI=uTop@ٳs=V gآ6UퟞΒ*͛p`k?#lٲzeee(֍!C44!VbP RAAfB rb1_t) zǝ_nsq5jx=g@1 #?j" g no_zSaBia;wՌ%ұc|Ϲc 8Zu*dU#>,g_h Ks/d595tl]*|+ݷC8\ye4lϢ_Ѽ-ߩy!ntYbf3K:'|l% |37J]ɑKUre?;9ʣF Œ80KE+**‐!תJIIٵkxmmm2)AHjiVۍt_GFWa2LY8*##o}3%C@!io)@,VY5φrb$,X .Z+qFvrrIXNZZZC^V# МXh DDB,8dKU$'e P6eGesGg"kȯj[>8 $~dϴh 1=@z2>1zEFtWzC2$d}it;0$sv@,Zjsƾ5Ù:QC_~  bc*˴d𧻓{H"6'ZLܳGU+'O# DUgi62Wrz8?Bo V*]5wr/*=bПoo6]VuEu:_RFJ𼃃Dw0`3I%Z}|9tr' Rښ\FeI%\d^b9{.&Y(bZ60Bf3:WZ\oI'ًӕQ7oބ %͊:DWJ,^&Qx"o5Ϟ=HNb XjNbhȐ! y=ʸLqOlOln4zDFJmBrv~b&'t*|-cï]~AZF$ P?/4қ؟$Lgn"LVE b[B}c0`yܓ|7GV,?WNNpӮ[!IN𶧴fV`3L(N#3瀑X9,&y{]?4xhx^kUL=oj*=NPʥ{/**S8~lp/RRn@b-($# 6hJ$7,ukkkRW-"b˗W\eyC,ſ53TʂXkPTT%?A,XeʼX/],JƎ1-K6iZ6C,x+ڳeqh# 퀲jWC]Q9t-1&{x@a8-hh3"8 mۇk.xXױ= 6۟>ڱF;?T{*]-;g=enf߼Gqi#0JĢ_NwoW'ዻ΋5cof$?σ>[^r^Plrux-!=uPz_L x{PkmH–.]:#PUU'EPI:!B@ ؕpҮLtt4U{@Y CTsh'O"96<{ſx]d+ŚX h '&A 1Us+UL)5X껓/ b Qbr[4-[ӲDp1'bGp-o\_4AK3+r/􊧵%HD;?Wj=b9#)O81#QO*8̜d78U9 XݡAJQ``Ũ~ 'rMk {$ !SyՏEMqdFn;ug]3< bܐ܀j.[?m3 X4+d|e"W8¦V7O,mO$p8# m܉WXJXK3;A_TӡDЬq… X6h=MZ#i3===@Pb'H} eO:Ņ޽[A2eLA,^Fk.C-ܼaV E>"dždkO@+ډu^'Huz !XDS  sb)M>+UL)5?D諼V+1!&Kk}FH6H [o'"5E&KM K=gԍtQYoHt]W 5Ă$msf_,e޺7JlJk؀ b2*82#Rchv'O$B,Q:^??ܽ_o捾' _qھۭ_a%cb\bu|`U b Ă Å&ioj-=m61~ ijj"q ^غuF³h)хsaYYYAAp^] \*c*S&DOP ʀVۘX7rDP9*}-:PH)1m _hN\T'~9.gQ:f Ć N0 n13KY}• tnMM|=:V7 zA ,h*h6{tqwKO׈nSpix!Jϫ1kXnxAI0]9獼Xh;LE~QA|%KNN9vjj 999mdo{)T-ߥő! j*5ڷPI">Q]iغUPP֦hh3)P5""m 2^9 M#GG܋/c9*S&ϞwCc b)IFjdSRObi|ʣ>~9obVCc zwuj,h܂z}d %T1I̊Nk9p#xߟgnQJDr&g;bm[{=q8g˽Ҕ:1?BVIXkfD[ 6d *oGˏ"$K`L]d%c5-7ԱlX?;_wj}84;:ğp¡_I=j$c#>o0$-bFd"JΗ:}^<􊩯, +;9g N4d[ܽ}Ͳkk(hA,bSDkww7)s6>>nzŋ#vD39ƍƖ۷obccQGN*a;wfv^+KK|@,Lf>ʔ}u0V)b p4 12c]cybдK vT7.G'ٿz3";޾[#` tu_3˪!YzWBFػ8خwXnXZZtZu°R:]71*蘻+2Fj^32aէMjms]BM#y rn>7eLtQ\C֔nmJWqzBP8;e&VfAT rv{TmR8cƹv V۲ůG?s2k^rOAZ^ޅ*;5Ӻ|%rı%6gg]N՜5#G ǸzW7YI{^iY|[PG_ l?PWOzQBގWyR+j\K)pSLHSc*>w*I?i5{p8~܂hs vzz,-K,M39) %2a{sIMN83;I8\`s2Wo̤P?)㚋E].F(ė/"g5~!˒~Y\sWI$Aui^%vhoo*h`KH4(WVaXHԷ"n_o`VvL[=Tޥw{pխ%w~O?,..C{U <Pb #KQ5ی JlUtc^WoqIJ(4۽;<Pb />{Jf,$iL_%e7kYPb !J,%J,!B (B% @K,C%J,@ %Pb@X(Pb%J,X@)&''5@ǏW;q: [rѣG( @XL-9_b|>ɓ'dY'J,Xb[[[^{TSNie+?KKKe9qqqqmmH(R á%%%T,R_(R\~2x^-l_b/j麾 GKY,%9--M~@<(Pb7edd,,,(dOOOcccQQlv788"O477|KJeNy[ߞK񽌆¦&y sEYYS]^^^WWL4K.QbkX( v@`O8[]YY *An%q/}}}wa Ƣdrd1ؔɆRYӹ{;/s^N]88P ,UJb]K,OKjҒ%\,c%P5Л[^^޾Qxxx%?BdJ,Z699*;;;޲TD4t'ӥ<55 wvvUb{zzzKfffT211ZS;X,e},~yyQ XcLCc1؊,.}f$yHyC+IqۧH_MbćRUDC.vbOUNWItD[b1 k1Xb+Ԍ.uJz.bS___h0}<֒Ob 9i$?G~Nb)ӶHwS EQHzc%cVEn2s~~. K8222R/l v\?::LٮSnmm8Ć?==___3[^M#J-Ƙ&b5c,mm1K".%T-[ %JK⧾%rM'Qx{%z=99Xj!W5v`~P%XeQ40$"cٻ_U( DEP)(MQ "X MŰ-}^u^a󋲮˘=sv;<$9)rg>V Dbb0'xNqhZ3ͫf."Jvʇ%w\r"+v:\|9b% VxN,}.X"""*γ N$<M-;P5~!B,LSCX`dfq5(*p8V|ZMYVQ!C,CczUJmۗ{ye^]bzPZD !jwcggⵂF#yl*88%C,} X"""XIR:%wgQ6FY6=J&fz*t b!Lb.>E"1b-9s3=z +oG%&d݇Db[0;LzRAl6sCBX%&kq5 BXD% _;w/N|J3t˩#6T*QTnH0b<`87fwv+ַmC$~nZ׋.jݙO3nT"nĆ\.VX,DUh(fd2矆8,v{Ne;Y87/M4dr^ὄs fE8w_|[.[Vpzlvt^""t:CtRG 5#uM'X"D,X@ X b "D, b@⛙=kff&bkff"6i"DDؤXfff"ff&b&b@ĚX@ĚM`x<n>K?ٹ$Nl8 z|ŋixk'E$xRQ)*EWh 5oߞ{Z||/.OZ'B!!yԻkG;x';kdex"\u[WuJq{wHK[>,מ9/!WJ BכL&VRׁأ:!P(!$4 B,*- b˯~##bf3i8R)ݾ&rB}@,B vw>n@,&;nCb+kρؓER)ZRh4 "X b3+l@,vm/jAfZN'5h4"|DZ,+Zt@,B bX ŰFlwHԈ|ee\.Gb vuuuccC"?ybBH`@, b@,C,N|dVEj5sxD"blnn2H$\.'z ϗf∕JGRFzfs<tȼ2 XrvizdBDh ÍFc4qG``B- bU{gh#Y22KEsX7]N/c=Tm)PZ mA ڸE n0^ݾZzibrOy !'{3bUjؚ_-/TC75+vZWdQԺ婍@誒{ӅKm;97|PB1~EݥE46Yh‘4ㆡ<3)"7/?e5fݛJ6`$MdV\{\[+GGe]SB GUbƴϙp0}8SplF7[᭥ $TЊݰjޔ.:?Dr@Χ]SCTwcʜlN#r LG3E։mYqsΔ0:{3SH͢',jӐ~'Q< 7W <i[E,|OlE2`ർ^/+0XWWWQQFQaFlp}so]8g OaEbٲe<}{{;HpNB,=&(VπP0 `xr!>Pn^S27JKKDգ) -%(+[=ɾbQή.gɳ>o(y.-,,%O> ؤUC.bkkk9ޫ "к_VTi\SSAXcؼ`p![PPx{aL[[[9ż /]n$GJRL௳D9CWbUGG.~9b,++g* }:R,G۾^ NbOo^=9u<<~>*02OAX,__^6<\Q.%Khs o P6}ā.x5nQ=u C9I '!hpX:XW#z{oKFs>,ZRD+e*\SŅ d;%,ߞA,r( z0'te׎ՃC_btkБv&MpA@m"aW +&Ѕ`\e|."lLD7P66A`(61VIǁYdl6Q?<=kdyM:n9W`&Φ,3ݍܓ쟋IxbhF HX Z`,@) 6!6?"8[>sbi=Kx9^Ur] vڴi\"d[)))D='O4Zn=z9k{ QCllJ@%  m |;TSG^,'ޯ\} Q6 |;.< V^sA]OأBV+ m]!J*RLJJ|e5rnnСC4x``ꐑL-o={V]^TV? @,!4a-Ȝ]ÇQSSSSwJ#+:iE_bG #C_bszYIv7G? #Dk]>} $x{&VӒ(sT W߼CJ"}Xl .%wD*6ة0=]XKMt T O9VNk9˒QX@4sMHQZa%S,Bw}z3="fQZKҺN"ZU*Wz_$\TĞ?^d-^*Qeݼy4S r1cKCKTXZQ)4#""DÉb= QSSSSw{$~P!`sQ][_]C,DyBlO!\g{z$ B,oټy|fqܢ8Y!秺b E 57Hd[#Fx0ď!KDW,n#45eGf~wر]O$($#Y{;6!H̖NhuiJxu .zEhLnX0Ċ{bw$X#5`'4ĺg#_V<.+h"*B,.L3f\a08j蘕s ˌ%Xٗ-o>-1a?㎶!z-&,Hjf+bAlEBloClUU%mPbRFȞ®^ޛCMGhlٕ!y|HlXcl&wΙ%1,τcDz@>qC#IJwGw0X8͚9׮fLukC줡L?F "rm0yuH[a^YXf[C@/rK=b3\9d|e2m,,we,L3fM]e, 6IJl+wbٔ^,r4ۉHIfOo@!,{+{IʾKradP];#l^]{]K"JE1Q^X Ye9͍7544ԲP]pnxzb7l_MoUK|*  _ʕ-ݻ's-X6DMMMMXfRƝCo[.Fں5m4DdB0 ਟJ,k^cܼy5 9o]d0hM+9~+h ZtY;!X@PB7F)FJ+jަÞ3Ⱞs=x5^ !7v) 9òW#ۏt&HmG{ ZbY_d԰Db{bcbb$>$TDU d1H8u5{oEޔqqq=Xzt9se +su7=gsora}!6}RrP\Ys[sfO$Re:Lf_`"WZX Sl+˲Vs+9ؓX [Vye@!CIQ vȳs4XCEk^[j'ݝΎLnWQWhm=Ã@,] ±]UİfAM^X x˽;YYYD`wanׂV[G<+^YÌߢnNO={ גX]7418pZJ-zڻǍCMVj .(:UTKIWu½6Bt [FҼc) Psʉam{l$G_)T ]/ͷ91oq)J-1DZgpNn0ŝC 7b}j]_IHSWjTb73M+RPc1r(_gbK.]p! ι`-xb&?~ڵlLEt8r{)%FC,ƧM[3f̘={6:-%YFǶbٲ#iY~ԩS-[$;N9,~'*M;qrrM/_>5555gba}_,k.]G38dSIbK?8s~Ɠ"> E'#4n!v`$,71|q:Zٻ4gƌ% ߬po |:S;xO:BSΥ.c;ǕdW99M8tAl{OmIdAc~]\ҌZtj^+.>P,UYIbq6H&G‹pγ#Ce\fXQvuauefƭE֦U [z<pJYX:WŽ]/FOsCg,-ßH.F=žQHRQ,^_G!6k6ӧ[b,LH2 FK/yB2m2!Ɏ'mذaC,)Bؚ5k !=ei e'*'Ȇ4ϋ0wC^phZF@M[5555gbFeo5=IǏcdM0S~+s&_*ڣWr~m 9?_x$_8j}tsgá-[k>n5Мܲ:^='WVnRd*XKω~*[E̽1*"zècx'<0=Z>X҄B Egٮ$O'Y9UHS!: uH嵮$M^ম9R sճXtby`:%ʞN?l(^.2dǎ,q ܳgEȞ8{5&$uK\Z[ \z-O7L>3㔽v6flРA (;y%ļKDlTbeU+mSZGe,*.3BԞMe҉#+#6N:BY{s꒭$XzJ⢷O noIMYBtWS/Vů6@/^iL>qmS̬c)-~Kl|EYeŎerMFYJ[G4|xfKw, ~iŁXGte?-+wd[)z󚲭eL OfG ;ܢ-OuD8X`W`ߤEF ó6px!VV!Vß4j,K4?? Bn'?PSYKQQ,p` v &o:ˢv*`'U'qԟdU{+Ox4(WwӟUSSSSSSSUի"sǤav#I+bCv~,Ց)X55555Blgm;g;{\)q_Y_Rk4E̅j+ճ">xK[|xq+Īb՟/WOjy_~WOi4*d/D=SEb)Ī)Ī?+j+G Q_,?~푈hEE}5 [vm91+BY jjjjjje fώY8CPtqtPr hTHD(":2&"šE f $x=N6BzIy~tNF F$IF,͈$Ɉ5b( %IaAw`JdF$ɈM F$IF,`Jش`JdF$ɈM F$IF,`Jش pccT*_\\ Ƣrш=; 36>;0sr7YK( -6^}O~?::~Ԕ .#[.F:5bOkXG#\GlPZÕVWWf8??^X z$~䡡ᑑͶFRaĶ3b2Z- X#Vؤf~z#\Ո]\ :cfffdY[[3bɈMF?kJgĶG;bD>-[\\\__o4QRP*ONNnF+#VFFVқ| ?haAApp\qdp/hH tI FУYk٢T AY?%sDc kZL~qK_\"fh4$ nX`0e$ X6Lb߉!FGtN'g tlv:UU3azX,644MKoT$)vΜH$V*,["pHR`0vD ?6ƏjF/|)zeuzsPX(kOIOgł{B-&@'s잳%8f3j5xn]?i[#wd[ z|k}Atal,UXEb=MMj*"YT,eZVUJԷ^x,uT b|>NR_%6(mmm(%}}}fj4JdpP0`~^Bښo":05%abaU`uXXqȂRY&D*1ddZmHѕ n/Đ[" Y-A;(,TrGnή;S;;qǔR.QdxHB`0$!TAAf@ȋ@$NEsۡ ߇#n9uWQs94WA .Cb֩xMk* -V()X/<ŤĒl^uw+ŏ狛WǗ>lߊSXz\ϤopL 2+۶SN`X;_iDk[&cJ±+2{$[^?_-r>oLy*B}p܏K*?vO'Q< '@ףhHj~3n3vR@,C^ @76o40h`k@Gyx w٧g}{Z^|.Z˗GѣGoݺ} 4T[ZZwbY,rq={V#] 6Ӫ;bZ%p ,+p^?Hp+mw)[VQ%A.3] f)zLh1xӕkOHT11 B\@{|qjh_/ZF֩>ri5~ˢzXgj ¥ߑu<kiM`@N؉f]m(I> خgo#E%6h|P?{[ѩ-  'SBbBɓ'V @zzqɆ 焟~ik Vk͚5ZT!b߉}ͭÇ-씗7*,%F^|(w';_9,%k!6B( M-[qnpVoߺgZWX +Ě-  @!V'r>Ov0ʎ0(v9'KJ@liMLDݹ)OySMJj|Ф*&@gfR5!f!N{ORӝa pԨQPPAC= .ꫯ‚XK !b,ClEIoVUr]aĦ(ZB*25XX5xƄqz3m MQ1 Caoض :fX;T X +Oú&@+Zͧ _qƛ?;RcXiXq.E_@-- ̰#*yZԟc֯bobacB٪Z=NKe"ľ T$p#@ӧ[0IJX,b T{4o(>H,X`2eVJ;GVFI eg $ LV]ZNmf"Mn$ ]Cs!lRIJWm= 7k+jɻlJrQޫr^x!q>ڂ1ɧGᮍ^n&vX/'5l2DR+rrE:x~!V_vuTkEum3G:Ыwk#%#vZ4)f&Hbr4Zq b2S>:K~C,^8/L3ƃ͔ƍ0@uu5C,bY.@;8(ܙ?4bN1g ̴{I@ly!gbyxoZ%(sel:@8kr&6Y2u* /u1bSU-ڄ:7Gi:c1J>pK ??Q@؟k<L"nW6X#{e@ + 9KPC^[ P3f~ {j/ѬKY_igRFFLF_S!655JӧO`X,[ nJwF {-, sC\!ҦpZю䡲R&*lg  6C aeGgClIEFsɵa{OêAuWgBY d__ULXcOצk]#j ílS{/ae@k"؃S.R Boss3?WJJ 0Xb}O2JTHٸH75b bX,bAoJ6MOzmvIk>K,0k f<=Tj8m@K# L"|WxU5ި v\S0;(Djn>a|{k;dZwK&Wl=iZEfBׯ '!E'-5h[-w#w5G4 oT}I10æAk%{ˀ~`JboĎ=@ooXXFRtzfXӵsĈ~H: Ck*R]Ԫ>[b""b vIWTLÚt4~eWm1Cz'Lb3hڲPvgI*y̱c-L ҆]i`h2FY3!64Olj) k ĶMaz )W5+L eOÊ vv50 S73pbWO,gdC`lG=jyKCft⫢baBa R 3 JH!BKNN:uw% &N 𩧞RV(gKSsss333gΜ9yhS >h;:3ϼKsݳgzvN#fWVVR'$$PV>TrWW6bǛw^z}&LbH%ǏSܯΚ a;p@@{JM6q((˗ˍ+&])ņ N"gj Ku\{H.pB`X\3ߨMUTJTuƪy*%;EhU̬6H^!CaR bs6nXj<~hrI."Uݑt=;\8JN3!<=<]WSϣxpq:dcaDˊ7CHlcdؼS`Țd68| ]Iᖻ^;Cˇ n|гŽ2m|xp/K=K:0Zuk26X !(o<a,_Ht /ˣv^5rH J?vDNիWoذD^RiU{>H&i= (7bX;bz[QضmhcDGۼ|VuwZFQ+?{O20 X_ _`c3xN@k<ɎLC Xmq$}+ UFKgR bL[u‫ }9dOdKk-$jhx~-7 ȸ^_=N~u_Ye:#@ɱ?EHUu®ukx:1T'YF6ʡ;Z+L"k.H 8u\MMM1c<eh-<$ ^tl8 'xbʕP[j#_<{%z4.:|05#޾}bX1_ڵk^|ňa5iҤ7ҷp8hkڨ$bXw:!ʅLĉGeۛ|qۖ-Pp/;vsqqfͲmdknc4-Q@ kcBxH. \ LR՜ }*a;_ N!~KDͯR%YKw;mp%jWPkTڕ \BLRI&am{55_P&ϴ;UԜ?8&تÎF5Z[_qmS-oJe7_JO/^^òaÎ<)*mV >S;:g|>YO/4%ET{w qˏUCU9Z}Ba! DίVI[P嶇X݅uԨQ6P3Y,2Ow 2`N69'@{nQX& lWGB,ubEJCsvoSj\12ߦjV65;OcXWy P!RFcKc!7KږƶfH?j*j/ IDckzdhușevxz P!YpK,YJI~c'|wIN5VW \W>bucRwmN;X_ڄC췄X >^;9=9όWOsvIAx^%pjhn|½n,RbroҕGݶRZ5B,ud݂u~;$Ī6`YF8aB筣R{){clk_}Օ!7}AAF˖tI!V3oUԿ*n}eS|F%6:TGMMٿЯ&z^cUfݯV4NOj,+*'#9CLeY ؾپ,})569)#FH&QăʥAo rr*Y5*6{~&c-3z\_:_~~t }_B,gM#8 ;؀:uqQRBhpʐ INݺ8t::q22^~8 q N>q˗\}x5{;7sӋӭo[rw~^I}SeO_6kϊiv:t9Lxh,zd!!+jݰw?/FaiܚfS35cG,B!YY)6XX([6VJ!bcI) {h3;{N/'<|{Ksonn&.VU,4ïABbf\.HRqP菖T* u 'I$NSRd2D¸^=r1d2f3Ajip8b)ĞKB,!W>Rb?$ɰ-'WT(fPO*>>>Rl6SYT'0wpu:`0|tj2a:I)@ɏLHIWJ&XD`2JrOv};_[{:=O9r:ft3r7&zx\.j&`LPEe ?= H`Ebbb$a^Wj*mZ}:L6ɤx/7 Dbl^D  1 Ɂm׼IUvS0<(\5wbgbe  LgfS@fR)dJ(u ?J?iaM~=~r^zRИ)L&HøZ" , kRj5@j& C:eF,NI\/hŴ$ J]HPqXN)Ğs:w6Ud[6 j?>x`yyn1cI(p$ضK\D*i MH:;lOi |ѿzӦMrn 5ENpP. x2yzDѣGig]7I#t2xu@G4s٢ډ=L$S߫ZO>M[*j珢"˞/`/ӦDV[(X kDl}O7cN e܅s6lVÊzDǒqܹh{nndu.KN\ j4&o޼@|FrZڵkрXMmH_O4kW4\~=ek?} /^lzCdÜH2f& 0Lt\JT4HBӈyE,YV^vJ:1T _Iy;tt;+_%>Z7 eL9j+js4&YK߻whn=J+t|Ç#?y]֡Њ^7tc1X$͍a9ƍ sWܦ@!XѺc/h cx .!wkp xtזj2P|Ả6KC= 8p "Zy1J^t" |6zyt(,L}n1cU"Ї1hp?nw+QYVd\#֍/_B4Q!pjvIe$$n޼a'^E,FiT]BYWؐ3 ,MMCN 7n܈6p&PtR~ UW5_VBٳCXV gwZǺL0PC͍1c,bM;Xy߿8țvF2xwՁ hjӴ|` 4DO@R _9Cc1c"_^ۆ*e 1iLuV PH?`8;|1cE? .DgޫW*ჯEl:,:4c1*|+p,G#L`*j\HR ,szUXVauc1muO>M| 4|VdQ6&&&XRs\c1"$ eKt^|رcGZW^=۶mjYX3n1cgb7333?Aס1c5c1c~.dJ%tEXtdate:create2021-11-25T05:38:33+00:00/)|e%tEXtdate:modify2021-11-25T05:38:33+00:00^tIENDB`kitty-0.41.1/docs/screenshots/transfer.png0000664000175000017510000005007714773370543020142 0ustar nileshnileshPNG  IHDR9DgAMA a cHRMz&u0`:pQ<bKGDOVIDATxJQaNömmo՝!Pq 8i`31M !ùy7Rb) Z.h]кh]кu@@ Z.Z.h]кuкu@x:Il6'iB4AG+QoԵ.JϪ8^ƣy])rT/4nnCtg7wTfaOI)JIH%]\JD$L%B)n柛iu}Zkt.k>v><{w޾~zʁشi͛l^yK!Buo~#rq$(AX[WnD$''~ĄNu !B!ߌƾQ!AD=KOrT`JP$rzqOITVK!BػCqbXv]8|hXhHH !B R׍Wi(Dy<99u}Oc-bpA7*ZC( T{<8 ;ems_ͽ^N%/?Ԏ" }3~TõYl+{6^o|| o}z?= / d ~5]pjj{gK<'ϿtRd!B!tܹ-C`UFs_ Ƨijnx<⏂S dDhh躌C~.ekx:]=z2Tփ3+jT8v&Y {ŎVmuW;+khO{V^!B!(DUI1p7o5HY0W/ e*,.p[d:gglGxf]Aϊ h6(i#""&\rWh]~OMOC4PO:mJyKmVuMMKER=JW !7&x_SV^q" )x !B!$u3Odp'fn%\,SU;DD76]/!: ȿ]uDF(75~.]Q4 @zŗ쉉o[uJ ,Vusr I=- ϛ୊~>B!bɥ3 M}TQXZg?uB|9z69 @Qvg1WRF&$$5jوX\oXPgn~PQF{g4@7b[B!B=vݿZN=}4n--DEEآI|I?MHI YυZ\[ZV>Q~P?L/Bzp,!B!B~QUY$EÊ_>Q}Wҹk-^pQ۝\("˵n;,{˥>yΝ!0[haCmE3RiB!|k'*'uo$\> /HrĤ$vvŝ;þޗ3z<^\˫OqLb>)U⏁uGFgn !`6B ٧~ l-bccB!B]?Ryy6ڽK 24q&-A9dBq1n>1!`+ nw]˹Cnaet]#u?jOEALiwކe F79Q Ua&({׵k u{%ׅXL&@֭2 ĝ_h0k(n KX~S?nUS .fߊk,c( ud]Ⱥu @u @ Y@֥.LA&KGAsK?y*Nm95Nw#q9T;f&gZK/.(lP k>ڐu*u}>"Nd3!-,ǡdj{W[[_ʼwcTԐ P;x=UҼ;rw}4r+qJ7k)9{YCʹՕdT׊ߊmv~ӽopi[[TzoΔQsHbd3IU}gL%\컨OQٶDEq!) -_Ѻf(edB,[\]WL24|B+vzu+,;sg=sΙ3\ڇ;Ok.wNyk,o+n YS^?~ \Q$QXQ aR#ztzIwQׁebm(6TmtZ75 0 0*Of2I{_sÀKgv{M<=<ġa'\ӹ(Dž Hu.gQBvÉ+vrxܬNLKge^ZJӺ'K8g/w|Ǥ  SE/ i*PK)V†:(Gxx]>xF9FY" w i 0 0JҺO; T@$џc0[I(z@(nCfo?'g`\F)BOd ʦ##|`lnmM$#!0|%'w\o?iF%K :"fUEK>_zZ $;+% klώ߽D}JƝϘmM>?\{466&*ݣPԩX rQ KfZW^qqrh]&-Y\Z FJ0AX~isaެq`$J[϶~ZGy/=$+n+dKv,‰*ZS`U!P`ɨ| Scĵ: cctYul͇t!2H\P&G巡i{ۓèQ KfZW^qqrhݙٙ3&qVG=s [!K>9%VQ(c_:HKֵ9Ó5NYKy9l\%~>O)8rWW&Po¼됂.h (hoDSYqq'ZRsyi$ҐK.!7-2be3f5>(WEG4NZ sܚNtꪮ.HyE#lx*"mCȑ3Qr%ɵާ X}%O|ptZEkA`𤿺ŢAz"HΗЯr+R&%hiصe5Cސ/K n(($Zlnoy\Y =vpRr{wYs&X}/JE[K}"̵‰0/J[!}6EZP D gh]#(ma`S*G@m{kM>6ED5ʕGm(O9n`}.5Cސ/K c EQEQk])$J&iD}rkD)_=S[5:Mhtw{#~9u'$IҜeY\GŐUN?O#*iiӳJd#ßߑȿri=u^YO]3sW6m\x\DǯqjvJ;ǜ\#⓷J1L}Wqu"o4 t$Z@ 8'[FbCH4<^0m×^xǩN I9uAq>攒ښQLH}7O)D&ۢ>yΜo?f4RI;֧R{Ҿ8W1YA4xAH W8)ohmJxm>@8l7\f6 N Ka9؁;*ø+g>̀H0a]vƿm1K/M Iu3:;)C"#1;PI5uc""FUѐ$IuQ@v̦{>L:P;yjL$I$)B ~v^HM5@+`:B^J .#I$I\݀ВΨkfFnѨG$IZ\1KL)~^J4[Va$I$s]I$I$I$I溒$I$J$Id+I$I$I;Tq?9;AÜ^vӏI4tIh]h9͖5Rh5- $MXv\[P.~>||@ ?u3_ .djEs(z(*P@=Ԙ۝TUU:bP[W"> J57mXDΦ(6vR<d|TBqF-&b@͹oꁠzBYJKٗtpP @ȺG|E.pȺE zYVUU5Vwzj=.L*e7'eqdVLCb\~ F=oŁu5fۥ~D;3v.n VrycUH{Ѥq k Cnml/l ?dI_9ؠ_LFQ__/ף_mJg}-f=%mj&tCn| KC!54xdBLiNY_@G@t:U WU|4$iƾ"]oÉ8l=aY/?O_cyaK9^tQ/AgW$,E%DJf$׌{7;]zo7}^C]-dq`/(czgu d'MN-7x~YJP'01mt [Xc[}G)}]{Y~oGJKJ C,0.TT'2+/NYHA=R؀KIHؠyM(72 ]bYi\C #xsb{ =* G*U;!5N{Ʒ 8'Tί!\Ʉ&v-KK ۞˒b*BXYŀdIQغ1ԓg}da]u͋udHo{KQz?Z"frbMnMuz?<}} f+B,\d}-@b/ Hf%뢰 HHR΁jEwwꃔgWf2%utWgӇc]J Ekt2%"1 :Tc]=q`]W5ȈD8Ԩ=[ZW5t+{1F g-CCp2uE"K)k/h&rk cɝ".BLXWX/#YEV:fHAauuI}!h+CFV^/7܂b*1']%Jt [nXWy,)vk%ߘR@bm ;<D`|k",S8% vGOc]4ouݲ!tXwS&~/Nq@&Zчl}qmf]bֽ~&>Wu%<xj>/@KJd{LDc]=\]w"U8dVF痹F [>X r܂ueB\NYWIe#>ELoG@JRC smK XwAt]DXft=q,.$@XїXkvwLˈ*C܅rj!.]{q1 QTlX766j˚| ºMaOd܇Y+%ڿ.\ItGI;C1R'7V%豮gB@`7uoh8a66խ@rSa5DxO`)keY+^6E 1u,Ne"So1\t",tPO~a]d\ĸq(HHd,Wˍ627p|kԑHDCXWɵuN]$ź Zk΂iޭRSEʩ@W)2ì b]Ӡ:̩2dkq>>"u}Xrgo:kUFŲ/{  (Gp–W3D[)ǔ˂FJ;޲EcŴ0na]t7d p`\NoI390oHϷ궚Ou`ɶDZ%"\h 0COtq͚=۰K?<}Jm1@zR'9ºApR Fdd|]4y16iN|UJI0x/0Hח˸+WJ4Վ]{If(#,[.FsEc|>"1ȯQwѫ6=*H]ūekp–E-Vؘ+ެ^<,T!\ wtºNƓso bMJL3EZ=&$-X,n1,K\{ph{"w&(%4RT%.뙺%s|Wg =fQ"dCZƵ솊 _fUN굶 uxq]SST8)օ^K ǚBe.PvIJ(+/D `C˽+VJ2Kۡo%#録tm=_I|P1Ȫ ?? ;+_{MB6{ԆEp–,v52^G 15 -›,]#PVXt FJ,HJ;Md׮[lP l 4%Jb9ʌ!_s軛GzϲU*' F?xӒ0{q(-]/<^!"t!aqP+WNPYx_~E##>/xisgW')|u%4۱ܓZD)4tv/"Z⦈WP\W$b(en}^K̺źo&UC[ښT .ԑݝV~uCeMA/i׵bz=/gNБyf.]7ǕgG|Wm56(@d."."ZA}Ј/'}} Y5*34ڝClAi6)C.NA'Wo67~\}V ۨHhX=J?t=ˁAzh쩲Q}(Eڢ[eYΪpmSIx'l=e|̺RAօ:BS!kj<<Ž:nOc|I<>f' z>cx>FXYwH*ɺᛞ3\"O.1R@r;f++6 ץ<"F4'M026u,;9;Az`Q\KaV-L/p_wB58؃EFMٕX4)hh_w쬂S&Gq qT+!)brmC#p؝h>DƣO8o|HIy`̰p`4499mt]nRPP []W&eaa--/60737JIK0yDKZz<vhw51SD2U<(J03:6zy9#D*2: ȥM,#rJ͓Z[QD$(H2 lQKWŁe>P"lP]憵ѕ+֥Qss [XZ`ʎuG(/@4DH!|ښ*&Afte=(WJo  q$oUSWu{M4% Ah}ݬ,4qKKn.jgf4{QXeOw&LQ$i) Ms- J*^*r ⅳ1x+`ȸl n:: ٻ*whN7&nzgJXI=fZՍM9BumXז\f=>1)gEϿ{hNlf>}.݀":33?͡g`x}.~CxGENF򁵘ź֗rjpk֮e:k!jls9G9764nУu^kcpxJL[sº21d2iP5K#6 +Ydr}#~\~+ <~ 3%KZ`WocWhs,y-ֵb][,ⱥ =!ଈW= t7 9K*6'pqXgޖ!GźPK@j@^Ekk[A t4f7(kk M:ܔ:408u۾iG2hP,OVKͿqHJoE0*]u Sr2(n٭[:yb-Ǻ&h|b48+JUrΐF*z4h_jjnfg,)A¾!jb|I<@};x݅5Z*yz/y5Kiߏ+&eW CHɡ#dt*?h#Dzz⍌JtXIۂ۶vrźȅpt_Me;[Fis)L-&yc]ź yzj W-< Sܔ-m_M_}4^i-{KbVr6G$"@hxf? ;LM~,+/7簱źz6R7jֿ^uE[k)$idLXIuMhv\^_N,^ӵAOEX?]. ohP=Af6DR7z(?,s}WCXOKo%uyc]źAVM]1߶$%um)NR\B6rBUó$M`(=$$ަ0$}9-LdIqmyźź%YQl4d=1za4 W{?C3TXB( '8qrź [6 ^Տ_A\.@rk׮=y̞L 'rr)8HNJCe]]SG=G>F0?41qb-ʼnuli%'qj=n5ҋR~'P,/ -U"}7;C e/[6b\J\W[QQ Xk)־piu&k(-[t34#\3d5Su]o2ʁ [prf|jwYkź!%R6 wIHIl*_S¿ Cew x 8lLsaޭ6gΞɼC"T.Sr jE L{R|ZaB\umXזºb7i'24xN`~SC*x"_!1Yp!d=]mހLImKDkBGE$lu-Ř'Oc+*5UCCLVUUɎ`,>DYW#sź{@DGa@oWcoWڥ4.qaWFǺ3ŋue<q 0%&KGVj|`Z`Dm-R]i(\׵ NNgE̿ Cu#d 8l.TjC$tmPmӀͫ9օ&PDXR\ŅBr/e1BS:"_"ϊڰb`A]Ͱxt tKW.+Eºht0rӱE`]ތ~Z}}]^n.{g2wXHЀ.6: YxY}ST½vܞkOzb]K#$XȼqZ>nYY}:1n!co$B]XjX1i1\85΂i=ujSE9WAͽɈa!nL ƺeeGvY28D8SoI'sYXkźbm'уoCd2%Y/O ׀ݤf ={C33eee"fc):C"W8u-ŌFSɱoɓ3+ߣ4qV/-&1ΏF{C9ºNprtFdqz]X2>TB/:`p%f?Qh,;``0S.@GQMݲ|n}794+&|.,Y=u~Fb_Ȅ̀ Ί"kMyG6XAŹCK f}ۙD>eI>!.9Ru-ʼn*G`ED0FZugW)} j 6b|i ȰsS.YP\WVV:" /ǚQi]e\ǵԨpTY& $0` 3%*g_uH"QsG3GBoIK,Ş8 ~CQO!vY^U=~,TU"6E;dWuurxP+3{|%hp8_@;D<"O์}'bښ뒦,S1&Y]ȆŜp۷*Uz@;Wdϡ(x!V`qN- -R+Y fJ0% 95Q(%AիW̳֒nk!4*FvdR1VNJX *%d1?KL¥ !{=uܡCXJp=Lź -@KoAk =U Zb F2;mGg)NѮZ8]D,{y* 8ޟfANHi#zh-tz,EڃDfѮAh"OZtΥ3ӜaVr:z= хB,n1L^ kOOL־J^Y1)Eoݳ?<ϡ/ʍ!Fu-Ffu_/\36Nnq)KE_e1󴡔F\$g}NJ?s4ij>45Mŵ/K-/&]b]texS ZdH&2y?!ĖtHMA=?/M/.*ǬgBF_43 UĬD_яZf4ErC s_3$fF+nhrA0&O=3G:R-86{%gFшY5n--[pӧ2˺J+įsg]0:pr|<*(̑rTGp!Zc. )ib)ۻ0o DWBj_ c^.IHY&u.zYI/F5gy/0<8Ur޹^VȺTYʒ*3,չKb!N-yIR̺Zס[zVnOGN,o> N?0$u%)oc_v-^^oc\D{095:uYVҐ$u%)k~Gѕ=Dz9 `.Sml}$u%)oQ:uY՛GߴeؗOG3,;sle7>KCd֕ՎXo3, He"wN$u%I$I2J$Id֕$I$ɬ+I$IYW$Id֕$I$ɬ+I$IYW$I|8 Q%tEXtdate:create2021-11-25T05:38:33+00:00/)|e%tEXtdate:modify2021-11-25T05:38:33+00:00^tIENDB`kitty-0.41.1/docs/screenshots/unicode.png0000664000175000017510000020305214773370543017735 0ustar nileshnileshPNG  IHDR t]ZgAMA a cHRMz&u0`:pQ< pHYs+tIME '{IbKGDIDATxYA\8c8+bYq#ne"*h(4>5՜ '6|j:K)eY.uy+8WYp {2zDܘ<Ϸ2Ca=R|/3Z8<gî8 wqf>Yf>j/"eWJyz(쁰Z|w{ZkZIYipxx'F."Lj[k_"k)[־Tkaw?n80AM4 A4DCDC@4 A4lqDPP Ad "8(88#r<7$H>E>C}!  4@h!BC1Nx,v?bkZ`|XLW>?N3Ʀ m~uF#q8(zp Clb6h[nR,pH$Qk*kT*k`P;99wG?8?аbQL&Ap5eP5IV#4ķ|2sL&Xpx~k<??k؟p>DBCYhl6U}zz*|ްV*U^]]i_ _B| 9;;3Z* L$Kr99::"4bhtlkR㞟?6?XX`0L&#G^^^p{Ih5ժ) vp VKfZJ||4݃l6=!OOOX."4N_o2>PRϴ;Q p84M&z=mߩ?_՛ (E!QQfd Yj eZB]^","KFPAs}\yY/sf朙={w^߹Kxyw^yBGUoQi}:DZRpҐhB<'xbl%IF )S(eNV(:&GC!X6X9HA' Cό?:EHÙ3g&:rKN--9ʕ+N:ix4pp'pB=ܓ-?wy3f<7xٽ{w?|>N6?ʃ1⡑U:O=Tg֭Nw=_ dt\ve)\]Ot/^7?Й8q"s_(4Ըua1i:V4ꫯbP+*3tͿKx! 1?8\oi`oKrG?>ϝ4lcNrJc|Q{I1pg0x 185 /<26o<ёM3׆>C5d8HÇ(ixgwOgD]yÆ ݈.(T't ム}Ed4;% Ǿk=+Eڕi1FAs퀤YUNHm,3gNcs"*>8STf<:IG~(%G `籓AbzZ[^{M'|p8N>õmħ(01$Iz)=xI 7o%X<7ٮ `X{.SDj<IP$ Og 0Q^Y5*!zc(vfDv=S do&8F9H,0O'ĈFn߿̵_|s5t1T_?%ߓ3Fd5Xt ]hQ6i(0"qCoݜûzgZ=읽~[$p `#! #~%Y߂DUM¤avт/2rC=a<=;lGu _p~Go '+uS)@G⋣םuY:}#u PpҰN(s;_2r_H-?[vm窫viFt`5vICa0.r^@P黂:P˖-s1ASX P!T1Z! J &<'I 9}…1Ö2%IC=6(IK ;1,YF-r۶m#g}_yd0#o9SRsOna„*!d;:QyizA 41'b.!W'T%GՋ\|@$klڴȳ\ǕW^9p8iGal9Bwx0 t 6Wk.Bp&5AS 6t_}պO%u1S= x0IXv4`E97{t+JN6 (麼CӼ[ȘŻQ`T {)?'M7oape7OAic1v"pҤIR<$㊒Z!Z@Ptr; Dh~f 4?1%bCHCSɨ1vۺYaHfCAd0dDBB)BDo5ɏ4>CfDDH')?4mޔzjȺ*\BT$:%fcll ?V'h"2cEG2$矟G3 VƍGnn: ZӐ1k@$ 5I駟qBXj!c0.>IEnƣII Jz)JSr#dmėCug}ϚBrkthpwyBc񠷝4dt,҆3'D w:g=HCj?bkTZG6iULkR[NFu`Q:" !H0I6"] @lGѿ4(Ş~ǎuoPI!CkA) }p8N B RQ̈ n`kx^D>L4XɌHaId jpK z.KUO)tbyJ*QKr'2G h*4N$uICƋ< $ 4ϳ~駤&35oȱsJRN(=S?p\PX DL64$%Z*!"Nud2ͤ!a6iJ1(rӧw;_W=B 8F4"΋?k?44,eT {r gJ[}PʪHC? :G}3f)VDOu"r aR4d2SuB]tT4iHy)}曢ԨwiȜy칤~1M^ط8Hjϕ5pꩧ<Mdk9*Pj DHecؤaGX^R8)2凓B;2KuXFk 1ai.d ˮ2dwa2%h@v&5kVP9JKAdi4B&ΐF+WӸ`ǍdrMJ BL4d) X4TwF涓7,i.a{hσT(ި DiX+} N u$U$tVNi-?J_~xG)?4,/]TYs*cQ@˴ʓD-?u]79Mm?=2|p8NpX?W% ss;ϗbߵm۶k꫺/m$ Mg2xL]v5ur}UϏwu"AQEi4j0 MZwHdPm! 19)cJA8ih#Ah5rA}T"$  uHGRjuih$طٳgQD풢Fd#畁-&|.MbI2~r' ::deuȏ `R%kxvj`bSXO?"fr䇓B}E|")}~˷ʯ?tXt@hEu|dd-“kG鰾Ӱg.ip^" `܌́P"LT OR[}N 9HvBɛY`I'cSnӤf^YDpwp(oG^O%rD{gN:mLb7" TiL9i8@=15Zr럾8R#C]c4~kK b7I%Du$y' 9$4I6mZS9 Y^ 4lxT?5ڵkyA1.??}p8' CQ4Mϣ&oPCQA{O P𴓆\PϧNj46W48rO ǜ4l6kl/F O.??}p8' íG̙3By޽>.3?PTafNN:3$UYEQ{4i;i8h"uu͟?tR"y.?\~8p8N:^/^F2GM( ,\{p . Zh+Y(XYrZDq-iB x.35!7H^_6Smӳ?8?З~! 'CBC@h ! 4HӉ%RoRRF{^\,1BS^a^*|YgBCCyаl.8_X*~. N_RVv[|h4z&}\B(fV9??~C;Z~+~!pp8PSY: vvZ7~!pа: v64NEaYp0L&ׇ%B) {gr@VV$);6(@J,|ي"6BYX(V)lDy[IyߚtLWcqmΝsϙsg朙B@6nZxhxX,t:-u:D"@FvX%1)헓`0HF O1?BFȆBu\0Nl ?z8J% GO&`gբjo;1/~Kz(7|>_:i6˸n#gQn?4Ͱ[7>z1_0 0 0\4yjh4k)^$Hq8ЖbǑH$hhq _ףpdEbP*z}s\._Kza{RnzW?U?~\v-o6r8 t}-?۳g2o/..}WVV:CٳgݻwG5 8>>_F&:w\k׮رc͛7ys 5aYz9>?#D^x1}sps>b|Ǿ]~ !B!a.WH Һbz"S(qG0pd #d>}T8^^^!p2ӧ:H߶m[ZSt[qeX=߼yJ; ~s}~?FCCCRGX}}B!b; `LQY 'P)AMEd0Ɛ卢0zld`пYJ?^(tR}hjjc>RK8}e2sVLLJ; #?ǏG\XǾ]~ !B*u Y槠:c0*B2ZIifȃ{Hho֟? WP;eCRG4_ZYR;n~M wA;~jNCps~O 5I}}B!B&OOӐ_~dd$~L %1k5OA,8Ң֧џP3POZN^sjV___j[n! 8hoc8 <Gsssn̗/_B?]z??d˖-Dy.|iD-߿36c_߮?}B!0t#DTiH)uZ[[FѱW۷oϟ]֭[c~zaM6F((S933CCVYfieم,q@iŌ$w0 v??9 oܸAML"pxq744K(`B!riXUU8/׬+W0cy?c׮?;蝹v~wGGGQ+ŬCԲ'N@q̙J?ڵk828 Qb.~J; v~ADQ33/q׷Oo!B!4L51M"%iNC 3X]PQQu5ͩǑ#Gg^zܹC3OL@츫W1DQr?!/y ;pʈd QEqN c 퉉 7ڠd_ӑEeRrc:;;C?[!B4GI`!kd`tG=nE h?J;' QGDabH"-CǾ^v?ͼZZq7{.ƤqEZI48 ~ DvY92ӗpNLMk_?skYaNw^.s'Ʉ_*N;mߧMM~[ϻu///B|9vvvJJ<J:G"g>mﳳx||}(z/ˊciI];VWP&7Hζ2[$ cmmG!s%i/fZ__OT k1ã/fyy(mhzWY>sl~,kToN2Զy}6󊥯$q=Y_ioooxss3kaJn?\]]hG}?^??Hnooooo`|s͌ -OGˎ̯ƔVS.f z=rh +l\!E;{|>fO8t:Q, VqqqV+zbxs;wӧOr ikkVVXN:,X5yC$#!#Y49=4hYpMMMY~||[jAyu3ѵiXRR9[ZZPRMNN=cccz=SUU3>ohmK6伿xCoCä,{\y Q?~!ȈYf^$ _x!R__AݻR zeղ~z)//@ }}}ze'`ᇆ)vլ*ԈwP?.^ǚ8eٲerm ^|)۷oϝk.k+Ӑ0--ͱ߼ySkB!x<Xء;; <:#Lֈ7 oHN 3 _aڙ3g-ZEEEVmyfBC|%o6.O&ߴihDô'O:\_OϞ=g"`idL^#iN 2}蘽{F3322q?{{{o*Z[ÇV}UWWGh=9وwHn&Qء]c<[ѰQ;{q9(++Gɛ7o޽{.MMMr9pLhNBdKpZ#Ee__lVVtuuEۆXu9_ZZ*HDs%k g644 3AO^d%oC̣-tʕֶ^ bCSEE5ZnG߯۷c.^hѷWGǵ ?Lm=wҌIl[nXjݙ.Or.0njkr߿?uwwOm۶M^zONNJKK%''GvǻWhзaÆyyyRYY)nݲ:1׮][PH-]V|>r0 S xkd/FR xd5NuMn5b\s]\\ N> >7t4\ --ɓ'w$Ѧ&@@>cǎo~f8Áza?NnQNC pkjxyΝv#GĻOA 211ams֦'(;vF'YGAA~ms]q9|03X#,2B@T̃BC۱jPᘂCSH_APQPGG8Aйr)9oM8C@4DC@4DC    bZ-z4 kGr9NEÕfiX,w,b2fjvz7q!xH!K"} vi.wp8\vG,|yn[hHYҧ$ߓ?x}$f7ɲ7y~wFbzV+:Np8,|vfL(0޲) 9v qk H @PdS!,B$.+Wd HD_<9ԝ9sTWwu©{9眓rd2L&d2h(n˖-)yfP&`AL<3q]o۟6= V&-ݓO>YUhعsg7i$]zu. >$ڵ\bǁ=%ԱcG²ap9$_1ŏT95,{,;d2L&d2hXPʕ+ 6mr3fpFr={G 63<.sꜸV&~o?mr{T9r[`33408tgq{q=?d<6}9£Tڪtd[no T} Pw{qQE]dsb2L&d24,8yvؑ_~=󄋝9 OrΩXI.:S9D*J97y5l֭mT?p/zwΐ!CL6ml^L&d2L&?Ó*r7NJ͑#G֭[݄ g]v_,?Cw^RCpܮ];`egBI@4}渒ƌSߴi˗WR}jl[G%ĵx"4lsc3ACj6$@IPۿ?P ݿܛAx 7.-fȀ`, {]J܊\u[GF;tn> ..rDb )ۆ$_"חbO?hx!}cpB掱EAJԟgϞwjPL&d2L ~^&AC9_:uijD|1/pg!?FEXK\1ƈc!./1;Ӱh8˝9-WpeNH^C>wcw ʳ4S-֭;z(uSQ q[ViЈ {׏^zERxu<7ϭg*PKJqmoժ{ߎx, iUߟ[w-׌k4ktӮkm#r;r?y'@ a9|Җ-[=B63(X+~Tz+(\l\('vDb>%vRW"'8oEȜH7~s=JaYyϿ/d2L&d24L1rVrA3ڥDÇَ`(UjrT}nÆ ,zf?;v“+8SB-d9 gB&'Bن'xQ9Jξ* lgS!CCI!zi"* DNQn/V&0NR @UELP]ܧ'b|~@^mNyG>@oۘq妐bOӎ8"!~S8WhŽJ+ =Evc)E9pO ?,|=DxyrU*@L=W'?gHMN@`fh8Dp~rBREqucMsT|8 q@&X_αѣad2L&d24LCKLp% C0T%_,$'N( bD ځMXrvACm9 q~B(m.|n7vrph)S۬]664$ܜ$(ŋ*CU]5 "\?-?hlUaDZ _ `Fu/hHtV}|^~ KI8}P9K-q 68p"^)4ĥF;U?P'ԟMUpV9Y|*DTʆBU%M0{Typ}~:=2}8<8\0p9y8~c7i&d2L&ɠ!p` qݷ%nMMc\q,cAC?'СCvR$f Ϡp G? /" srΛ7/먊ɪ0 2$GϞ=#)z*ΰӐDxa[q`ӎPé\`"w [.:UC3j F>_+ohuEJĞ'YnEd2L&d2h()p`#9qeHG_EKV4*PIeiDQ8,3Iy qӦM`!`!hSH WX\L.ŝej26rNJ\;\}sM($ O 'r:y ZjWwߥsH@ 4$oՃ<_5g#:tp\\BAq5p4%) CxW޸ GCFŜSa9)9/Ɩ;wFXL>NGOy,9.sȱ;@*a>o'qt]!qNTn1|kο3z ́HK~g8o6dz?L&d2L ȹ  ǖEh(wХBT5J6'@I-#pQ~psEWTjuaw$ĵϗ%͉4)<;q5cr *O>A<{ku>*A=zTÎ;9rO>=E+&*YrfEY8l(Tz=F!oW^j S:ď'1y|k S"L$ B3qU?\3wCsۣgbYum4$5@^z)d2L&tTYx$dN P~f*^pfVjp#  y**\byUl(,Bs hHgk-rHx:O'Dc"$0r p@:})lw2'J࣎cѣ)Ҩs $T3w8g4PN!V{H+#/#W|XfǛe7I YCX;Х0`kw(YQ;@W_羽#4X|\۴fY mk֐lu=ϵ6XgAiӔ{]~N&ǎ{ tt OShϴktݿsk6~mdDpw`Ӕ~gr~_g DcaSN|BC^S kLvӏ[+oY֌kS[ZX3nlx 4x OC@h ! 4BC@h oέ%( T(n":DA< QQQ@F_VxqfΚ^g8N5 x<xL<`i兘* bZt:FuEޘ#4.4ZUFl6S{{{&ZLC4.4s&eBeCmR'^WU* Ã:;;4FC`2̧ۄaMݩ R}LC{bi9t^>ad C*Rhc#LC4\5zӐ 0 i1}ic0 @Diix||NOO!K3d !!!M;t j4LMzVR*ȯVi~x4ɤA>GiP^UXNSl6<ы FUݖ}e)GۚOrPY\61bjCY*dy_o9˩^'M9s\\-i1I1*|+=D"!2'_6]www*N[mZDH1f|7ן:<5qi4ـۓ7UU FQ IW8#z V^y~:s>-,H]u]740g)@\$:^2d!4lDf(XIaz?zvaߙ4rpl]O\.{񫱟9Ep>؏jm~uiO$H!$~O{bay~//}pi' -O@ab&/qV( Yi&>iHVr$ *r^'#cw[G~>;EEqԍ繀x;N$ Ci;m`E`$0d l#(a V؁( UC c ;l0?gqyts,eע3jUJR)i_~'Ϗ/G?ow^3hH# L8~noo}Lpbz]=g&XCo_g8~[gu>~퇩?fI꥾{?c˵ܝ{𺼼?+?>hXͿ!oiiiA"AҞb ʖS%A^)! x8D 3yDbӧOHd)ɱz}c`u!WWW??~BcLgR}\{?6Ꝣ~;}}uV ]_^J|89K?Sm?lW4B8?+"?" W;----9h|md9;;۫B J`<V4yqgO?a?cvIlV $a7}23hH$c=왨p?C޲q߯ǏdƱ|my~^x&˂날?"hXοoiiiYAF~ .wUB?ϕSm?lW4UzAC~Dа3Ҳr"r 8ql\3;Z0D:YSH3#9SGB=2Ȋ+sO9tύ/7~УXAI0ꂼ[7O =V}ݒϊA~[g}2~퇭?ꃆT?*hXϿoiiiyYaZ1@OKٚpA!' |.9ՋrׯE^N#"f;0sϗKCN5VڅЦf w*&ɭ`F_%+}i3>suT[?<~} ?|eK{APw4_팷u#n8(}A~LQ[ȑ̔AX81t<HaJC=ْ,x6ut_8]V+͜@2F3IޅYF rഗϏ/zCN?kl{Š!6$N(ʦl.#w%3NN*[¸8_h(/\?_Az". .𣂆񖖖9b$c4o o/c!- ,IiK;ﻻ 1HHp@ک/C!`ÉY2lxtmõ:]o [\bAMZix`5+RC\ߜCǧA{a7GόN=6~:g{Gs}ooO-XQ=mە꺩WOH.o(ωﶍ 8en^oXp蓏*z[KYjSӪby{{k*V֓was<;O*^ B4U@Ne}ct5;8|C bkjK񻏿ᅬ1 6tg$ ~^6!O$})1R]u"iϟ1DB׼Ο!yP(`llnե4>>9r7DP_U*r9.Dsss_< ehx31aͅa5&M8G 9 ) X]] M<܄`iix<5!H0 [4s   ]ikkC0DsssCFÞ5o}EC"{DN </ ϑ|UX,bttT…躎-hve\똙Jmwvv "G\tۍ .smfw?:?CAD6n w>幚Vȅ&&&D0Ѱ"^vwwr wOhh/ /gE{G>f Pq| (JB?l2%ɿng&Ѩ.shhhHDvCCCECFFjѐ׃zI|7R:o8TunjZJ* S|>0J000ٚͲ,,..B~wQs .Wxxŝ큇]ekAF\-QH"""X F$_ -D4b7W|@ [\3)HW.' d2?M, Y4d}YA@3HCIGV+\.eyz, Y4d3vJ}׾Ds-r9mӷEd?$aaXls8"=} W"Fl&<͆M ǣ;'Z݋Bs 64Ҟ6vu"cȷn N'*v ˥@kBCPU?psF%ZXhiiihxg[kVE!p@f޵ g@.V V:$Ltr-+ d@6+XoxAi;P(`Rol}x*I$yo?x<tZ Ͱ&^vt؟hi>>2|{xsHO|r:Z hD$ކٻW9;؆F-J'IC&c -BK) ՈIy4,T >Īh`S_e;NMΙ3y^H7oUbAyr 9pqNJ ҶN|N;m-i<h*{>Ӑӎ?Y:rHW<;|A]NChC/&s,ΣaKxrQQQW04t/7mڴ~ W4 GN7h`ܺW_/ήNg͚,X-[x J]1~RL::}8=Lk6!Wbq~̘1#ٷ>̟??`ŋ:x^pl0>=൜]of T>hXTTT O>AT89CCaBۻAC]DA6+V\ pG4 0f1~BۭI!GFW;wk#,7^6ܨzY[Y{ 4,*** Z3gCw;Bp"qypl!!Jٖ@'9 }umgӆ?bnֵ Qײ>q<>{hm]' 뱯LSaQ(Cý_Nn3'36=y'PdߝͰZR=ю)W_\Z uO&Ze[ɵ#Z=-r5'|:dz4?p5mEEE^y,* zo;4RaѢEKㆊIBC!׬A$tBn99 DjOB9׷ܹs?#+$6\`m?!rfsko@úZroU 4,*ehsoDo$}~:tnZ8~}~-i(WD6m9:!Sgé> Phˡ! m~!w^m+5f Rm۶ ]vEASGq 4$Ք/W]@:G+_;쥳/ 5G" JMɍobnۡa5[jcP.>ysؾА` K/={ݻw{^y啦0oL*LY1̋I^u3 /:;hx@C9p GQPn3ͫWv\ rDAAycɾ/_VZu; [Қ5kI gОz5Ɯ9hDt铳ϼmc)Fc9r?)q6@akSaQ(CΧc_Onw)Ǧ97-fy*%o/>rNa 7朆Ä}]wmy ϐSG[f8.:y Q*B͕ڧ Ӧ%.^N*EjJuTF!@C>XQTTTaJ_o]6aeY/ )U ?@.@Ƀk]J8d*0x=ELڪ^`Bnb>CTB8}={'}4xh9陋+W4 ׫c ^e3/ռ53SO=UU' f ?ͮr Ғff]bb(NF 9FnݺUf0+^Oovl$qqP$\\gs=hKvNT: (Z dÜ_8mk\I 36WP`QTTT!qswv}>poPGcm̙Zt =URBu} ERyDkϵOjori_\v-e`rUV: 昿)(5ي`~rm/^qseߞؾ} +9z뭀]l׉cUlR/'/$ڷo_/c 8uEEE6L?6nܘ9)qsL? he8tOVt+R}I'd}Ԥp̙%|mW[ui2?@'P$`;mTmqH+VHO<㰿P_5k U X 朿>>(..4uTdffʹf4$BMm&퇐 mob/~MZP{C|͵P9eJFgB6BHj1"$D:%pLk]znj#BHf"BSՕH>4h50B!@3:)WqMCI_z 1L|E-mt޽zfֶ1c>=111|/Bi<ٱ RMi~J5p<}rh]$nEdQѵ:D]HR9o!BFI,Ui-t^]ugDқ5V*DVrL91N'L0AGm_[7*4kIt uP߿ݻ7Bh^=0wv87}_cp?$i4Xif$\L*˧:nd,r A&?g0j(Z1geeO|OI[n jnrrZj36eb[i()BM?`0; D?dEA~?yץ2Ш?eX`;N +R=iA a0yS=G/mVBa箖N:@45%X8?amQ<5POꏡC㣪l=msi{{{C%a{ߧO뙾m7% Bh^yHX^E♲X0~ՊCC7fξ{T_<6;g^KBnx$% :_C4i BaGh yXBu1dDaX oE=m)jjΚG/M™0?s(vsh)2Bh6g2 n`YAx"NDD}xrX_Iv.BK1/W!'%N|,v1!3TrQfJUI]\\, !!B!,;8\xǶYanm^k4^9yORt LCg_vHoґ?,'Nq=,,,ie///!4c'EdvT"w 0+11q遨۬mFf ⤮KsyF||Mu`ut#Fп4hMCB!8*qԱmp@7 qC+fA~[#`Ŋ{{B_)?# b>zoV0 OsiKw|-oLKّZ/2*%W^Q|/Bihoҏ3okHrIg%!qB\$^!`(1 n Gz3v58]WɅb߱MWio}ّMdJΝ;*Ĕ@iH![(;ֆ1|pq_n3p8 ('[oSxmsMCg_L=LHR?//:!҆0ER6L7PJOEP3?Kw8 >_ꍁ79%,>[f'[_zV2wqQ3^KhB8nW.D3kGvHA=B!Ϋ j^{ɲ0 X %]RRph"TTTR>ձOLLr7G6⯕<w?\闝SJ[j rQ㏊BQQt%T? r뚆?+^Mhu"_OwϘqlej*q& Iz>Pm]I!jvSfxeU Ց#GĤIdTjO "iSf3dfVjRۗ!B$9- 6jZ69IѨW.NKbi_F7x`Α㗕ʄ>)#Yܤ_›սI)}RF+-- B!fn8Մ=9r0ag2\扆 8..w껿?K=ÉpL FEmyx !BMCk|Qb5G uxǞ @Ú8_f| _.ĶFDG@!S˨QVςͲZ, é_r !B-eа5 dy.MЩ_‘qqu<.R2~|B!" sk5 ʽqY?T?skpo#4u"B%MCՅxm76&,9X>ֈa?|s?;ܿjQ+j$bDV%P2d̐dD\8 n^x^8Kl!==/~x9l:(d)JR-Lq&BC@h ! 4BCafL&R+QբjEߏ`.+tgF#?C+JٓTU0vvvu{Аh4f7n d:Fejt>KB?y>/`~7)ΟfO.x8rBvJQOR<Ȳf3ov^Y:R)n(jZ u:c뿿N'z8꿄_,S_0?=ʏ Orm_R|__}،ŏO_\w矉_kRa_wK6,MI 1PRp^nnFPDcuQF] b]t+͇"=ys H|Z@Au% %3X5`c{pDET`0nSN$tMd uL4p8dA$Z_J}_Y՞JӐ/SQ~ui?ohZmnPjpnq +?V+o°iX , CtI.Ǐ~>ޖ0+ӻa<Ƃ +O{׆>,ks7Wz䧵4j5N'?1y02l```b"/lզ!?.홆Ro3jwGXDvRE/DLZ5M) b"pf~ip`5 0' &~sWAsFd$!t`vR]>#XsIPnf36dB2, X,K,  "FMl6! qGæ!`IAsM&sS1eF6GһJr hhpfӐ0kvM6g?y.]q$cj^~{w.:WTTTDc~ <@ q`pVF%K0,ƁWM:TD-O G(}X ž]F|49IIe>V-noȠ&*hcC͇pBؑ(@Q:/w>Q}_w|iBJ+wUCyfJ  <\PW ԗiܤ!CQA?ZCc6\$`3PmreE_<:p0֬x(=sfKٳg2ѰV@[ k0NCvw~glk& ߾x?qȉ^~{ml;_~ v҈+WTyaO  گypgbDoxc䍆oFH:*Yfep:^ z%~(즼\ łG%VCk" 9s>|oo]A&OeqRP;Pu @pWw]/<ŝ 1\z>+d:&zqf(P8A  k0Ne€edh' lfxjy巗^~;x5"=3Ң8Ә8)FCWUU~goRoѰh2 ߔѰrpF^|,['O GY'Vӎ~d1B@5c6,C>ן yۿ@u=hȹzx&S4T!А35zWa k0SNi= |xOz0LduQ}2@ q1 s4|o/60Şh ŷySPz=hh~hT̃oh?h=1SF]Fpޤw(} K,ON.X=N=SȾfL|t\2/c!8nܸJ;AFC|,ݨaBiKJא_yp"â_[0gITMD dt;K_YCC@R (XOЯ7r eDI'= Muh YAr-sgrYltlo/ Q?̿_$~eEpxK z̵{ȹ YzѰ|B)iOhrZF^zJ]i#M{UScMs_#ޖeM2{LY{Nk M2/wuA|0(BX瓯?L'%P9؅e\h!ǒ&Ħ˼ѝ)w%G)! (>编sx/:U8w uM_ bhk}]>-^i17bbNW6h+.G\׺rT9UWօSvgy1͔5̖=:/0$wo0C~1l-t^Ш|BanNoyCa4\AIhj:@Cweeg MlflÏJRY=jrF<b)|^e߸̤A'd..hޏo1 &YOC]<=F2ݨrq|C?<^z4)wa >EЈ}(4g#N"~S;phx\gBlͽ P앎Hs@QzqrɢwPIHЯ ߴ Ā﷝;dمOUߑO^.Dpxܟ߯O~zҲ|,Y_>}we] sz巗)0:, {FƝj>P O>{;1`!S0/a4~ r1^GxrޯN[#;Ǖi{NɺD!O*h8f]`4izbWBן-F_{!E.roQ@ij &EJE]?Dd&?OS# v;E'cB+'1w[T0t$^ i17@1"硩ѐHfq?$+?"K.<,oiNw/08K_w]zSjQogv'巗^~'o44+RXlQ$xXi" 7x9FÒg4aRA*XSkmI| Ɲ[&UO\Đg}GmYQV/&3'[9]0HQ.;RMp >|!u(w('Pbd r3] IL`AE?W8$AݸCN2U]]!t6JGҦtz؏|Z짴 ==X ǍbbK hE?r\-_s7[]+_ ok*q巗^~oǍŵ ]ʛk$7VخT OжOf"z~y[Ӏu'oVbW~2WJw*w ?^ 6@^3X=B\B6g߸(%dfO,li2X.?Qp-4W٬_w|S9./BEۋH"#B Ѹ}9yc0Iۭɔ*wau߿}?ODLuݩTDTOO_*x$4xh_wޑo$xM*kT$5)Y췷 ݊姴K^5-y뇈@aw^Mnw|'}Jx̸Xc^~{工߰g_\y~84ڷzJ7xA4oT(ɜ\qŹ)}dT[ٿ9r_ />_uG9d{\f\>E`Ӝ*UyiJ hKHb!1+ˮ[NMq{ t78RP跑S;VTFp@ |;T`)0-Š9\ < b= r<7n'y@.bea4t41v@q #w__0ӱhPӑNC3 ڌQ{B{wĪ i?k(?t[bW <.k$~gH=y'3JU {re/V%gU2?kSQPlնRHxE"dcҭ8I>EAp bET38-|^x (~ νK9QNB7MS8Ҿ1nCk។eu\o4y;ǝؽ=16sp}YƻSq^NǟqtT$ /lW{7{[HWUT%> nj-4Tp&^ǫ|pD,}6npƢ3^YyY%tX]].H/?N|Rj V(hUUI*oO7 A<*4<\7V3>nl>ޣn3]-bb3~??x<#_&5V10wa6ix'߭JQ|vjZ`Ƶih4x"J曅뱶+++a=p5T-//Gղ.H! iH! iH?;8^RV(ڰj HDaj%P!?&Ö}ق܋qjB430]2>p,"~=]nܖ'!=9|[ !B!B!EBD"t:7re>QTUU Bd@_SSlknn}Ov_BG\_ECʉ8557B4f>QX,h m| Qn?}B8~ ?Bgѐп( qH !]@1\ s>p Df n4UmZOJgO(.p?Bgѐп>ECCrTM$^XƉץ-c<XxI# j⯗ 8Ąrb`cՍk_|h4vc||&,,}}}Zr/LTi̶}Nܡ9]]]yH |p'_1ob~vYB`+GD/cѐcQ&`[b zBϏڗ^=ߎʩljb\^l>+Zp=jv1~9,[Ow}eGT%=o^qJG!J#'p82UU5d^Xt!wr+-_}n߹d%xNѡsΒAڇfľnf)k'4W?ƥEII\?I3b+ 9 _KxH ;NyfCmm-(d/2QH.F06l=>h=+TJWi?aѐcѐR44l?Ѳ$`.ȉǥܥ"w7/4%piH.Wg􉻦&)Ygc)d/@@ϜP ˩|?f5pEEQX4,K]06Hr(IzҒ233#w3@166&wF#mUU.i< eiB!1;::d$|.J$!?{W0R3@@TpE f.:8Ȓ!O(;)$>@Ir$4!57$ss^sMi[fažljU˖eag9ti8fdOȮdX:Sk\ˀ6^s۶ƥI1'Xxځ<K){%zZa 5 <H`R(qNm𻀺<{W\U/ $Y CIJ1* h0 :*nX! 8 XЙrJ-k\AJ7ŝKܗJ޻sn>ӧhXϯ翓(/o λヵ>`;ˏ OKS鬒83O ?a[G=NB>\jlg`Ƿ᧥"t1m;VgQO߿~p\IM\~uf3dOΟ'TXXhݑ( 1kq)aq>w?H6I ]=3`[^u>lmw']II _N뷌څPY8 wI|Vn hOEaW,|$yKLL\w*RQ<{ls(MW9'/۶mpPq{mϯ?)33Ѧ2Gc5tdNgv%ׯD~.SNI`@W;TY̮322:%4;v,0s$7)܇ ;Фy 8fo5Q=/v%5^Y1?þW q:? 3Her>בD=Gbc*?ks@~k׮!O-7NCRTmYwrr( [h󀅸)f.wYR~ߝ;K'Z^2UEV+EnDzGDy <8@D `.++s\2ȝ>@{1%?A"ߑ.Ϩ:4dϯ?Y@IP^^ސZV[x:ZFmzsss0L 5 G; a"OMZZHzׇ [^:=؜>5QS1; ;nm)d nl9 r٨2m*)z6? 3`[ty5 +_ӐAO[n6gٮ,@%X}=&(bAWR\rQ)>Y`hߐ0m&̐-5VґĻ.Q^"4R喃@/PFFDdKiH"x @$Ш~=YSm]q] (mhAv(yF3[/ׯxocz:ɖgM=#Z?폖ug0ȧ>)ϴeo87/ d O':'n7gSӿw_G <[!2(Voiic0a,4m;圎Z>@/c)_#J: _;vδo[.hʾ7u1vFnڐR `zrr hL1eE騉R%@i"g"8鈑ð7ht_z^qȗv~(kGt?ZikixjBM>]~gY0J:vEuIAU 3? ߻ׯ_['ܜ<$SUɲg0N?-8 2ːوqkcC~/,.8].OLK£U@^Li;w)Ohb\zӃ(|/,42#.heCz( MEcR*g)h̬*3{Hrtf-|a4f҆0Z1bKC+t@IF%g}^R!|vnm{F_z=4dp߈' _ m,|6cor8$spC(<>5 Uu}AXp,mg_O]j!NCfOrn0r˰n)!q0`i(nӼېXۚ6Ƿ8mIq_~wxG6fik Z8|iySINh`(g5F7PJ@} :҆C "{As#D XNr2Qz;e}pR3qT9(}*F)EYP[ܤ#+eHSNɉ,ӑh__!k I8 =&Q $+NLVP ꁏ2unole6#&a"ggqz1cv_: q8e[?z 75 f$}$8$jIw'gV^[rLlT~y ~}|,]kFD u78DQ"j ؔm0ȭytM)gDL=3am5gm|.4_:$DVE^pr6aF؆[O!?a 7n`[uj0 ?S/i$8]5N3}X/+o/TAX=J캦;NV~:|W ޝa%&U:D]+*>s$"Tl 3?"~*W̿u(j'*ֿw$oϟZo1W"P屓Ņmx~x<_/5qh]'~3*{翥3uk_ŚR|azNLwv 9[Ac~~>2~JùEQ:*F c +HN+/|99*TIm$֌p{9b0cccCt2f0,׸SHr&3"ϱ~4v2!`K4 ӑ/7˟0̌ȯq֓<gVQ~Z_>u)^Q_9 {u>8q4y}6qun5[fVbrZ4V#?? 3evb{G'8lF9z?۹^IRSz~G{wM⚓'OO,Bߵxw_M%*ŅykKb߮e8cj7Fmt0>Wڱq",]3ґ^əʭYO- Ds%5ѥ40۲),ENK3ӣo`4>>ʄ< `$Q==3;wz@1BB٤4_g]%Z sKh\{lFkןF~4rl[^< <y27K'3|nI^ȏBIo'ol-&5[7yM@F~Ѕ"c}31#*_\=KcR̽kc^7b˰o~64F^ashg_OUrJJ169V6u,A6$Οz$G"VxNCRT PǏNa\=T{'E˙غ4 ku%|?>/_իWcvk/f%ib@b^E`ş3 )hdx. ;qgaeƢvcƗ:ziDy/JEO&[X2O(%%?,0M:-|}gs%hzK. Qϯ4z10}(O!?ae4*@[ .u?έz?=폞$3)kq&:n὘rŽw'| _?Y@;J9遪>w1~_ =P>qg_/{J[5qx0τ﴿̟z/;`V}^#i(1vv[Hz6Xhتk-B;JBEyV#aVy_ !8\~L*":(@G]VVFs0Ԝ)hQ`1n!~tF=SY`JsO ے5//{H-a78gzڟ i_/yid8&a  S׃EPO!?atat@rPX'daP8H)?z'_-tXq=PЦ3Vaj5dx&1;wDŕbv=AKfյ}\yu/2 3?")οV~pomQOG8|gsN%&۱%@aswEQJŁ)x"Sݴ1qXr~wduKQ)|*.ra4zv z{;''{9n,? bZ,%O "cf2{8'+A ?p-I t#g$P!HC `Px 5sN?{aqyx82vżf;dp|bնjtPp"Y]dʤbtz ?.^wSA*Aؖ@<ó>~|=( 3b-Ix1+9 'ʱՅKzޡϓjpR MzFaNhqv!N3#J1*+)JQ{_߮ޛ\||\'wS\Pk=N'_6nFhT^?ogwR\dVO!iQ*R\^Iqjw9 "f#2cd6h, KC4, KC4, KCW\hZt˔jF1 Y[[[?=mll保hss<O? y|l6џjf7SXO?7Ф8sjԵ9 =DaHXBC!tPDХVA.AVkTD AԡChVVxX`gS~O,Z^Bq3nARC8"Hh8!-2@ɤ}Tbi`D"rbp:,m ImY|/O^24g9G&zN#($q'!||8w|xu~5F5܄=QBu;.¡&°uN80sʆF ףYa~tEQDR)*~d }b(;w,Y8rqL:/ O`Xld#OAH>HݝUߨi(}^9e]ZkhW?ϰ'/ #]CowOђ$! }K( 4AC\40,m ,KW{eQ7IKǬj&/ u'TUU; 7E(WsLgLc\%FQ0AD!)Em"Z (Uh.hh18uY|aw߽{.9ޫH_S ".03]Ut1ʠ4N̊WzCv MGo"_o,>.ِx|pOGp\0okmւrۥZaN<4E D"6hC *{gKE;7"(TU20og}2747G 9H_S &,:m|^6хF޿ [2[P%MqaplF\ARl 4V~6Hp|sH*ʹ<4|eHo:  ٭46'h}2rY6'yYoqW_;j0`ǡ#hgqEѠr b'>_V^J^ + uL\}!w=vL&jZJt\kK(rl~nBSxFqBC"zNшǐ 8~;n}>rz2\9"V?N8Ӥ:Q. Eǯa<)544~]]]^Y"RPf|9Š@(h.}<>GXNG;62|vh1Vc\g FC;L?` ¿vO1"BY/`-fڦ8Ԙ;̝w =yAs&G3P&urÏH{2j K) >|_ yԓ2<\yQ z JoQ(<}@@rYRD>'CLT40(ЂJX N;v(h>et=lP}X~{zGFFo $YqPr<FCMDf!=92hX_P aSFC?N(+?1 ' T7W ;O}"ujjj D6ϿkSz]]]/{pgq&˾ɪ1dmg{%ϯOtaƢ6Hk 5a!rƐJIMQQ'!@w1`<!2&|.cub9) ((4^JH̳v]Z03[IÌ_FVVFCMI%A5kZm&5}81SVLvyC^ySL9^}Sn6k c䔧.ֈ(m0&~:c6et.8ufs/VxQxo}YFêbÓ@x&53 erԚ?#',8E.-03Y^/>-O|G~|Kx~M!כFeאR5\$r'p{h"a>"Rk+4p]/Z@=D2nϞ=^kc r.!h~(Qdb^~7I} >W{됃 'v܀\4忽w ,a|wRg4tL?}u='!ZɻIqA/mhySs2wПb4rH㳙TmP4c2>[3qc}|_'y]g:$N+'ZC;B9]x|3.ʭ̄V?N։GЖŪ,Ԓ^IU!>x2֌!B(C-Z z90`01DE Os_@VhQݬyABXXr:/j4XP(,Z*dۓO>k$.'):) @}Gn||WʍP_WO:_R7ɄC t.=nVlltw6τO[b,*_u_nfM/ Wy?eLa֠VgKӆ%ܶL.1C_1go$_ceݿTyrcx7{D~ӽ_noӐdzQ\> W.p32wl+ 4?e+4mZQQH0OhSkb&9 UW^91h8?5 61pgřTi]xPx״j4XH( a8y<8?n9a9ty侏Y}׃B$/|T^Gdž3=17cY,֜uV@=~h3ώ?cٯX9^lllϾi>o4ij (\z{i:I&cVy/mty|<-;-χg^;$矺ISOtC1/;kNDxv0Y1@JsD9q)? zw8Үc@[rIIUQQ}wQ]*|RT~N+bE`-MAŠh (BԊEKbTT.JL/M&)RtU)?SKT6.C[eaQ,_kD8 c^S:fީ; -?%3ٖ_/2\n6/_; '#$3泾ϗ /3w.ڕx{ xp:~*#(fYAk=}>lCKW8hwZ75*n,j .Așe` 3LxjC /F[4>i|1v8kR~I`DOi 0|^M@h\'NƣxiE8m&7)bۓѓr'%㷂׼\~k}ό?8AFi-r+aO깔ԨQ,8µ^/eHPM:" W)ߠD@xq^+t%<EOHv.oc=4uw 98$ORѺ? _ZO7of|pѝYoY>6{'a8=$ұs'wzPS2Uvg2;PȪ>s^&!gݳԯK oVXK>Gc%\ّArs' ?T:#zH_ONz%6ɑɁWihm$A(W15 :S\gjo~9n@y/w[2 DdgcJRM588pR$ˑt" QbD5.<ֿE$Pv*A{fb/or>^u:ҟBS]zp>Ӧ$e l3ɰl# veNC"Gaxið8Y9̕}[E?kSQsK6\A ] AHN5C)d : p"q~,Rߒ{RՆ_cʑa{ǧWWH3 Kf9kii\gхJOǷe|sزVh+VzM~5\[T/߃v}c佅pW󗪭V|Μ8gY9oL8x1__l|~6NŽ)uj:d}}=n>4_M#;ďAtb|3pU\XV<2WEyJus\ vڊvDl4p4PJC@i( !4L c•e˱N'?0Nqi/3 77!IՊ:wvvcB-,,fGQ?س$8ϔ\ ҭZ D /XdD.Ath+tDĈ:Ċ:wl5[m 0f ޗkDDĦ!1itNGN*kXZG{Za0( vdKWׯɀF@VDB `^WNN't:?=kFoDDTMC(}p 3O2A9vE[APw tn(on+0o@[u`FW}L&m Wb*'u.&sHDu~\GUU>|TFy,C0 e[ܒzf3ׇP($߉@ZFDD4<}@}fmŸ1'wﯬBzx~nԝ5ȍnLF|*iӾ;q~=oEz،pdpljjbh_{{C<PօM忷Ztw>]r+&c=\$#7""bӰgl웭H5"3s;eLkΓg[=BbuRCy2L|v (X,xE껨y4ŕuqj4uTf* LZ]cߗB+Uϻ*~0m|>Ol\d_luD~7.nWpynK:"'}9ȆrYg8ʏT*lIT*9|\k B|n#j`瓆eE/?<+:QO't:dp7eyv/ZeYs,˱#M[o7f̃8~A !  =$ ELdV.Khu+EB`":uiQeKϼ/ 1?3{ww9wɻT]8rdEr]0GB㲮H;sQ<#9 ɓiêès 8 7PA͍4&1jhiWWWDE jlt+<Ïm&o.its"&ź?QPhs. bE;B[Ǡä?=@Hub djNnos$vӮ$O'pN-x)+l G!PϞ=9F:tvO>ooo&d:Ǥ8HpG:V9JGs$mc{mp$4n)xԣ~3_ F|&ue!n˓7 sd|G{$%zUsg:J=: )>=\ZiϏsر#P aÆȀq:h;yF>ykKr6$~DJ&}i8`sM k`Xmƌ#/))1~3~3~3L&S0tPj~= `r9+1aqG}\U;Rhwe=kʙyGiTV FN)( NW!Fж9 W,3<0tה{S)>҂FfPL&QJǓ )e@#;W&+}>__!NeGpUo,-+S*aG2^lGW;K3AeW!MNE777o2L힜b;;Ӈ9gm߈TT@d^qL`t0?[[5H?E&"`,D1Mjx,XT(i92!)'PuiCZieyݻw 4 ;FtiC?QwU;fPL&Q4lY)ّudpMWNmg^VgʵlŠzk>D 庼5ƹj7777d4lg;s#);Ƒ^oU\=DMC P elyTgZ۠%?lkJmWH8m=/p0@;E]4594WXXxNC2F ljfGA'9ctJШNCAWO 92c2uqrYe4ξTh7qB9m۲[iH*FK"[͉,d.j7Ơ2n:)qĪ#8bl_u<|wN:$fWm=ǻnͿJp/!h]ԃ"RA1LGӰ6[pߛn߾'NGs,c>Z] S/1cϑٳO_;% ߵ>4J+pL&ӡ; I$!ǥ.=#N@B;'ڷ-W@lc}mLH-Od걃?Wv,sn"Dڈ~5Z ?iii{z2`v=fV4d<1#t8nICºFڽ{[wFt9MA?8k*0L4lczjT+?/ob?ն.)(/.)9(J}6}QFy7F`nooo&d4L^֘vr{nsiêIT(?KjK7KeWԄ3+f~H(JeI6[v# 1[WQ@';~xڰ 0ӐҴGDj&3nJTO)JOOx"uDRxAgաwՊdL&s6eg`oxPR]"4߳˔>oro fM~b?װj{veEd2|8 ۳0Ǻ}iHCm@Mtǻʧ/sʚ ϖK+9Qmy:_Y$ȓMZt#O8s-7t˱[lݺuUCa]8QF jkkqі \<7;MdnN`Vǭ:=dqkGmT't/i<ͮ#߬5KЎ5CJ`(]rHsՇN3/ ĉ;Apaع0]eVZi֓tu|d:ta*u bԿ9 뵎tiXp0bE8F8qQ"~N.qDm5^1~3~3~3L&5Hq|GR^ Ab*6=xc$L-3[/%S&Y9?C~`>GEV|GpKK$PZ!ԩ~LK]]Pl*DЈJsF$T@#},]PP 'Lc*//wً"Rrss9Fޠ{ `±{~bbu( 8g~hai}?B b?˘infLL&S].u"?Α'_&(-?ٓR c)?)d Ι%*~W4]4bԑfGTa2Gk;"i.p'ߌߌL&䮟@Y ڬDٹ8Mv/=v-IItJzzo1?DͿd2\o`FY7+7 Hguڑ?-fHlLd>ߌL&Z#g^,0|"U>ع֦0 Jkh kk+ܩP)B?h?-CE_=hEߒ =7.wDkQ?B}䐑GǾss)>/keY<:~a_go@-Jñ|7SWh&^ӋToN7$pݼ2s;;9""daŹ F}5<+ mP_mEXf ,X)S'!CcO$#z1 g2q69Ffp,|dQapHhJÙ vga镡3C3rEւmkjjBxҤIaCDD89s&݈H o`pDzn_jK8s0c[b{ZLq)o~3 o}Oq2|{o }֗cu^H;v,؇ ;.pρ`bȩ]uw]oCØ^Jz!zfƌ`̙DDDDDDDDBS 5HAf]/5]$[]⚈}zfeCä`m:sNMBB&O%Kp3Jf=^ÝpW\ ^jYf:m?rkkq+èoG窫i7tSo, ~p džc4bĈ\{Y_޻]<HqpEd=g{!I6{ Dnmm;-{ή}mnWUU1Lrehv݋ ֕W^u{І82 ><_^^˗Š(--DFFbժU`Djj*XanݺƂSwC8~6 eE]82%g>]ƺyP_?""""""" cmXW R6$| :A3 "wt|\_@lIGMRnt[G>׮\LKw 7$DŽ `ht X}c /OII x]YY ?r 1c}(**:0clЀsk9h H ,`;OljKV@]f r!sp9~az}usރw1Uuh ;CB_Lx:v+Y%J4oC*<5 '8ތ6Fkٖ- 6l1lذ.wvBVc N Y*jE'VZt?N6f-RjY[[ H zСC5ݶƍ{`(3bH5p =~zs I/34:h~Ai@#>?_/y[';1hd!ϝIvJ%W<ƪ)86iÔې..6ԿٶZ,Xo*Ֆ.]XYv(*Rtt4:W܏_ ?w]DDDDDD/BØ$ Gk N }ooNE{[# |Z1;U#s.gj=]oȑ̟? mV^,ڇ]`]Ջ88Țr]vz)u:<;]_ ~H_u?o'> 6@*[k3oe:|ɹ8{=ghu#?.w1~)4ݏ_?"""""""}rdOc #]18] ْwxwG:ެ5XIi"{uw0ڹ2p=Ó}8' {Z1䰁 l"2=TcІ.>;2F8XĀ' Ө/q [F\0TlƯ?|C-""""""F`hi`U:5EcpDzRly"%h*;Gt?XRyMw+ ñ*|riFxZ1a(k1(9v.F<ؠʮdbVr7pv9cJ o?wDDDDDDD)4 2H|3oI,6 @ڝ@G_*׏)sm/cƖDlߐN@-?!#>@qwײ2G `} |\ WQQc;Ney=kӳ9^`90ܼgqpԩSَpjvCC-""""""w h]ie/va?:+OޓPT`dž<`$N5qt^)V_ɓ'[K˥8\.Zѱ/mMçVTba=y!ճ5xX@N hz %XPEt*߬"cu#]vgZK,a;ns|K Q$qC\nn86 Nj}~(qnZr%?8N\wӃw1RFvV&~ =0wvd˱Abq$Lvah8Tmgcx÷bV' ʆyV Vx,Ngn: XU^^5bΜ9lvrvcR~>CSKyW-Zԛ`5? P6C0\j&L ) C\w!gu<~EDDDDDDB-_T'}GSSSw+K 獱Vi8)-rҘ@S}˶xԌfhǵxΆv VSjEDDDDDDBAFF]qrmbnÁo qvgwk%r]4ϧcg`u4&"""""""" Ck :z,-%BqZ7uu*P@Ѫ By7-d'P-ӹnXu 0` @@ `0` @  0`vUSF򸬚#$I4Ͳ,Tw=MQ&CHGkkb}EDT S_<("P^oPbZ/InҬ /Nl;3gN!Bp\J[Q`J7g#g)-Po}%_؀62nu."V͍|B!%䪨PtDӓj#P@0kA ` `+U5b9B!pnnxZ `(ClMܮl 2B!Ĉ Hu94ڿÅzgbWA0#lTo営[F&&Н{|,''?Ln}FIi /kO•}y":oji4?%x<Т[{꾯 5t9PAx 3fLmliJ%F8;(j}Cՙșx,+5EnA@tsdl=%e%o&BEbU+B!bB{ iIE@eOۃ_+XDxIU7Tyk[ fRyPqT7ڈ~ up[{].@0xtrLw(? 0%ۆP-NmLM5g{qbE85]^ڭa'fȩ|Kg,{gUD t.DnHHRnҼhrd_8)AT{eLX 0g,΁׻^ȘJrT 8~W';n@DȉG#Ny\Xku0[&diK UZ['._#Ay2c10~&L^rƒ 2/zWW,S41tFwZCSd:i@JJ\!Fp<wͨ `?ux2w.Xh8Zk6 `c13T81 Ev>kɫHQӍ)\DQKZ`_}RpF#ڭ5vQ~q|@wsEp,ajj _%9ɍp(QԍYT W.xm f7zn1cL!,+^7SqRaP }Gvwpcavv6&\s]zɘYϼu킾9땍͈gִeiWB<77hġTC6yрOKTQor? -\T IJD "-|=trq&'xC093zȩ|=Gzjƅ\YI3'odK32Z5) {^jRsk?v>)瑷=ظ M{=.˧g,)Y֙)}NxVH=ޛ<g.3p7R|1CN8wnq.nz6-a v#V[jG-\5߷#|fP :a~|9wJU.2/cvGY(,Vls\Ռ`?s%nGsgBoe!|V=mKN+Ƽ[X9@Ӫ(ni{4/ɑ]Y֞v#hˌlV~?K7U?Dm!p.R67T U#S)*jTD\ J")[n>`J?_f-=0  0  0?ٹ8ZyA"m)dZ8SL Js^Jz@D Qq;~p`;;}|p`uTNԩq\zIQuDx=LjjvS@OO~-f]-#dpjk--g#nVye={TgL8)c/GS^Q!DF.K@q"0*mvӭ{sgneUHNFb_@O?ronuk3JQ@WuXG =v| 5%}>5ӳ:URjT2@@I)Vm!`'OͤԜR \! K -]ej8l`cuj^XJiYfs7FY8J3FEDzR Mw٭Ӝٱ8W[{{"9ÓT2ekFD;4yTiJq.o=*>Xq]5=䭬-Gtj6 }l6' 3;;O_ ;n8X+>:U%0/ afnvov( 0H)ɌZоCmIKf)  $::.b4.{~paD(s 5m~V `37 W2"BԸO5|NTB.iOsZ{ԫwu7Vwn1/?#nڼ kcr#-`؝:T%t"I}h6V4(O߃N0j\QVdkGI]cʵ˦7Hm:2`D8 #jniA >EL% wAqJ) m}K_#C^Ͼ7ի5eiQQӗ\T^Q v%3iX *sh% aَAc}SQYԼlPɉSgNexȞ5=ji/_q5j(u(, :!= HX[ 5p55c85<:yG(a@@@@@@ ecg'C+++m%]Q42D9~i{1964a1Xl'CeliT!\PAΓV̓t#o։Z̼y0COL7,Z[>o|&5QPDZanʨ ,M;W(FE$/nk&:J7%s=z]L4#ݸYP/;w22y!W(mK !FD,`l⒴]ԿoR?6>&57q+Q'iŧC$SVIȽEG߲?Rxv"X@2K1곱OBb/ׯ|˝I(yDJ1-%OJ殶#jbx\f=H7\Sb x0'_^Q X^ ?s-ZV|bm$F6y=mz:͋ ~Q`;(`?J 8 X9==H6l,m5Uʟ~~sog?}H?(&&.׊A5$LBL$*"#tBռCcs8T-jpbʤX.x}X0O>~K+∉kV+ϡmy,L22odݣV/>6Ѣ$oQSLɐ4y 5I\V:x-[j nrHtmFE+_^<=EL6J+HYN_APul\%Y7Q<@rQK՞O&5t;#5QBK ZPÿ\Q|ˎ̴܋0FϟzFyrY9$x٘>&"zٸu(%˓8 [^XW$<MDk8 `! Cc>{ZZ2^[E'ϟJ Gjrb&5HҭmoY|~6 AkgwI7B$4S&T];a4"A[i:˚4* rY9$I܂G`=ؤ}#׼E9Y#+2( [Džaxq}M>8 `!30Z3U0OHPŤWTY'NWH=!]?GLHJT XwjRKŒ0>R43i}s1J߫]Tw]5ڮ='dHzMZ7q+Qݧ*$5zaqB'Ȗ|ּ[lu"쎉Mi6J457ʸ>Zl)#3sɽ0Y; P%k7J=fƁ2.ȑn"K54`JO]#gh}7[\ GWwm"uXS@ԡi$UX ,iB`M;^ Xo ũ[6=۸s pAAsPݐh'wΝ8e2jWq9YU p&bD)F &Y.<}ԿyHdęgfxٷפ1F4zpaξkECCã'EUX Jh޾a(f: M0ar)4odW٣ʗH`}m%JuV&cw+ xy*m#~w!;=ɯg⮯۸sLCCDI7rupf&IĚkׯo30Ra(k O9m0N$TVI:PZzCaʚ7<1_P脍?ּ[VCMM  y=`7նeȶPCj?ۤ?ۼ&'*GX>ǜ0;G n8[5 y6!Ə2v/Bޮd!&z R$u*ŗeKb0lށMۊ14Pg1E`M 4mm  wPԘjPPXȪÔ]% K0R]( 9B^ߗdEj_̃oT K3sN|KE,}#ּX$gɊd,N WSNtŤ!v^ߨxšx#s) b||;wTpGݺ|eƼ5pEj7= )2&44`5aW{OBM^d? KkM3~ 6$I"o('CF{ֿu.CAa'Z#MA @# ``0` @@ 0`4>׏pܼWW<{Sީ|GͲ7Çp"cF/USN 4>xh ;fddd,=675=d$闯_ɃQppX 'ͻQ/F_Nfm {P䬬63qXviq} ˅o_Jxzq&7:+{q$ۥq$I*^{G] IQ `3Z~ֵ2mN7{g\EoҖ %<Y$[ %Fh$l '.ޗ{nݾ3Nm2xՕ{N9s}~0o޼ƣlmk {NDgaaMNFF%.1`B 0 WNTsss/08.7UAž\j[x9ÏMo2~n`p<^SizxQѠ#E „ fΚx߼p8=QEEDr>(7ԗCC}C ˩Ͼ_qS! `K/y g~HnJȣoho|Q?:7vrpAQ=?땦;w _~1WKۗZ8?pֳyRSC߄l#~͞]aP%5`4?|3 R׮d׾|h VAH@xvD3a,!٘Ұ)h#I}źg]70e)A7&?Iq zݸm ~oB N0_qS\"H<,B7'-Z2'VO zN2%/X-QRsg̣Ҡf=,e,C8vBF @ɾGJGg| V?ڙeJlJ4l>;Gh|/cIYxѶx#w ?x0j=x"*G  )ֆ@xax.,@U_9tp^`B\z ]dQ i8hmm;r-/OuD8 Y 3<)+~ }NYgnυ {[LzO8Y!o+Pڲ6tl@'Z~Mߕ! EV[|tA]+!G2@qQǧ>>t11Jn,x|pa4s3c'pGSQx)Bc.^s6*0$3#"L;Ga8MrN F*TЄkׯRRUzS5N?Tjhx4[e/k_חD,g6nRXGp4 qU-I0cJ?d.)1I衁_3 `8|wXEZr=* TϰteI5* aò2D;@|rƑQl~z4N>6J4L00 @\['QNGQ1ZdpJIʫD `5#˥!N$7tcrT ǎ{'?C&"{Z!T%xLR'N? ?T`}Lc߿OΙRl"!`5C ՝3W1Lt~iP@PxfCKEE<*0 3n8&8v8p< s,PUeZA|Z#! `R` u)\иP _dW׮ 9---rv߁}9ϮJ!/.L&] ` XN"=xOB ~o&`˖ɓ'kJ|+(AQ6aYc'$,?=qD9"Rr7+>I"4epf`(yTaIgRe'QNGAr,dK řDDzL_凸T).tDr5ʫرrW%:1B~W)HN͟F-v=,N)T3Ui3pNN".HJmUR+$E,:PGT|lRƲs}FQYy!W[rE  >'@nFi`}H'Q ?qmX6 EnlPp" ;rޝ`@$h4#7nSndv2@RIE^탼EQsIjDzeWCt_K%S)?Aj9ab-ohmV>a)cY2R *~E"t K]/XZ wͣ( #2p8"WjGqV. eޗYMUО+7`|,;o{26Sň_|ג95pd poKT:s8I&4UmD=Ij!BAᢺ١4-Bsx䷤#~{yR',"$`5D_bacOKeP-8QYR<[=sQivgpp8ON|XE!ƞS%>Rԉ `h2pAaN~5z@NbdNTB )yhIL+ƚCj}T#Ed]@ bNawJ'XQpoٓ&;Ks$엦rԞKc4ip_Y$vT1 KK5'˶1S9I?Mqcec a3fH6*aò2![P]z%X '[mfg3GEfeh p<840$yR'Lk*3~=Ye `|kce` 9Ⱥx}UݔN:7!!֦[De0I!Mn$O=D<XՔb⺩SWZxaR+!cDn &.ͨ]ըu -^-Anv"og6R*@^I嵍J4L0 o^|NcTpl 9q!#T>UpaWQOeᢅXZ!ü{WS+K[L/k$%ڍ@2 Yҁm .L(ۓ{-.Xp,a4Dq)ʐۨN74 _ukqj-D R sp9~![E+ķc0I!Gp,~7 ] M׌#EmZZa (E pf"6`oH5kVH 2p0sLk"̚\B7H 0~\pQi 5VQB++k=]UW W`9s0@"Qi 6&f͞z՗\RX< p\ S'kc7<FBYʣȁ 㖄I)PF;[ VإU'u2G֯#[X}Qi 5Sʤ' qx9I" ܕZzj3|5m˚a<8R.vK m3@@ N{; K!'vCWl1ج.v0; mEDMvl vQ\jŊPHVPt|/.miÑ%?0 (Jї[+iٿ.p,JŻu 0ɎgWn5LO=1p|֯' AkT.MNM Ly{xxqq`  HNVm- 0 @0 @0 /p_ g=CC> ;DXvQ?(,[S =f"7ҵ###> ;D@WXks}݌N̈́Ag {Z7Q<Ԫ1Hˍu/]tx?xS|6^d2A8<:|[v4a?|sˍN=ZwTK77>τ曤g+{Eܡwδ+㟤'R[HkQY,/ ˆ Ed2ZEq"uEd:v|QwE@3I0MB|~s^,y9,'6>NڧL/C* ,asn}K4G>ڡu2/fiiҙػomM#7 lJ Z^7Lf =~sn `59 \C 0 o]_A"9t;?lR;`:WC1D~.~-KGiԊd `F#|Y]>,i#G &ZP(/e<a0 0%!b=U>5s ~그3\JluWš8@!:Z,2i`<}PDkr 7 ,aFu<"#,E?|$q'*0Vʭv8ߍȣp8֜-  o`4t:, ϒ,i\!'Pˋ!aaTuϸiK+~.g%;I.cf盾g՞ d[]ÏFf{|Cd_llSDEfgFu=1{/rrspi$bg+,*~y‹S>sq3rH{86<=7#dfe†xSU[m2V9Xc XT\t8.=tn)γVk `})nNi۝<;@UD#\KmPUD0TN$`; CIiIwrf J(z3OLJIVT?J Q3ү*<KCz˹y+1hFQaaaRB?OPi}/,a.k,g\o΅p,/Ko\$,'V.O&.כ,j`n_` NO **+6Fg+=^&w;4o^OM ^gd#V!6vr8p[C~T?:1?aM @Rl Q2 ˥({SA)WyEϡ{;Ben8Ӱm6|JxP ^ DeQ|mPnk Lk)~^5X3 0 ]Xozjr͙2SsZΨB7ޤ uE 1 #!L7|p ŸD.ZHGɁg0a|B~9x/~@;Ib/w[U×xipA +>Z Xn:VV]km/6!cP%瘽34wޕ/'O@; v# ʇ hG"^K5Ņ>uj8}G c'$`gh3Lz0E(+ĵ!!<`VOK1P^yUy5929~C9rG`(ܖ-rMWwd6!kĩLk)~^5X3 0 ]?cט۵B**Oȿ4-=3`,F /"ĸ59|LeE@@XUHSgxoBM&{|I!wvcS[G}II2qv7d~_Fv]zhr $R ϧ'(Pl,V(.4R)03k%pJVBDa}luf ZL!5ʊӓGtkJR<z'4kfa5 N>Ups%){ְ=ٹ&(qЍM!%q#) *~@BV\).DKp% =X't8$ts|=.>lx65^.AH/A`gr"sy1yx_q(cXC`lhh ^4ҹ(/_* )4h4lCxQ]V ݋]^ҞmZ4꽬P7 bfs͉3s|/Ec^{h@@'ՊWqCoWu}c[;m^:N9ͿvTB*Xf\A 5JD֗}Jmp6RϬ:#\.r"sY1o eu" w{w\az4p~Ayuz{īB L,zs\&.^^bjkKvGJPeA3ojgO:P SeKy^ @ ,=\X4Vlg?l;Ʋz2jC_| z'7ޚ#W?}u-`y9ǣLJPb-K{oMdx91 Pg]t HBdM 0>"Kpeپ#(YC.Ïn%+>1{CaJD_QHϻ{y3>bcq̩eC8O.=xP#k D\IҎ/Py5 2DY5tP$&) N'GEv ``t+dOT% # /JMkZ(B}ppV{Y):X߹>1/5L ' 3Y<,*o pŔ_٧xR,1a쩈JaF{PCGvm [Y(cT qAPpaȃypSPǗg+,_}lL7t,!,A3YCnÀΞ}Sm-*˘UMy@'壀0s O ,pF40+eYChz u+Z `,W/g*M\c^X{k6 AƂ `Dgpqv#on[,:~Эa-1i `08ߞ]ͪf*M-O TuaphԒe$63 `B)c>0l0 "]\^Pp5/||OQ M wu u.^2%ryA^1dZT=[Zh3c& `M ^ptKч~bW q@~5a(Mlu}bi{"IP\U"8*((x8]:wǟm? BP%x=Zi:-gþ\.,{^ )|k`%wjhS4yM[ )|k`ߟd9.pBI,@, @ 0` @ 0`0` @ 0ٻ崭(o$-! c03wt&> EҋX|߬6_ @ 0` @ @ E|Y&e (REyjh\ƴNq͆1*貪fzQ,R1 N Ed]1.r8XtiY4%Ay>6+l5:M慃|nZzٽYiՔsV2~vY ^HuTi{y)e?O˞g=YjZh!:l\;K .nZvRa Z`.+ ·QaVeBOh':nDWy7s`;D0@E8O8A\؆XJ76U`e)Ȳ,6)8?g-0ے+zyCzzo\i6j~8~&\br{9|1\Y|Һ3Y/(zy(",eefkJ/O/7Ww??xxxϯ迾w?}kTy^SG)륬x=>uv{}9vՋ}7ɖlZX~)k֗vOqٻD. gHnhƸEQ%3q@3{_RPi=Yݫ] <6Iio]_Ǫ{ ]H=RYno^zlpjd`O ]U ߚ=Gj}wc|d%lO3nK9 𵖫^C`|iV4 N_T#`@wazT6ǃvPnׯ~C.y`Wl ޼:h}$㗧'{Mvh {hM^{ XW5R>je>RC'^j)R|)-%|Nvst)m00oJ@/Myh_˲veY [ lv`1Zo/V0:q}WI)pǶf۶8R%冶p XQ/Jj`ỈI(&0PBA9 ߶,>#^pa';͍h"@0p~0p~0p~0p~0p~0p~0p~0v=aN7بΰ,\*7͙t$"u]~CVTU%Hv@E1ߐe"$8(_iv 0z %8 4X 0X 0X 0@ ` 0X 0XEZEQ`~We+eYY?yɕH4,0`  @ 0`88JIENDB`kitty-0.41.1/docs/shell-integration.rst0000664000175000017510000004336214773370543017431 0ustar nileshnilesh.. _shell_integration: Shell integration ------------------- kitty has the ability to integrate closely within common shells, such as `zsh `__, `fish `__ and `bash `__ to enable features such as jumping to previous prompts in the scrollback, viewing the output of the last command in :program:`less`, using the mouse to move the cursor while editing prompts, etc. .. versionadded:: 0.24.0 Features ------------- * Open the output of the last command in a pager such as :program:`less` (:sc:`show_last_command_output`) * Jump to the previous/next prompt in the scrollback (:sc:`scroll_to_previous_prompt` / :sc:`scroll_to_next_prompt`) * Click with the mouse anywhere in the current command to move the cursor there * Hold :kbd:`Ctrl+Shift` and right-click on any command output in the scrollback to view it in a pager * The current working directory or the command being executed are automatically displayed in the kitty window titlebar/tab title * The text cursor is changed to a bar when editing commands at the shell prompt * :ref:`clone_shell` with all environment variables and the working directory copied * :ref:`Edit files in new kitty windows ` even over SSH * Glitch free window resizing even with complex prompts. Achieved by erasing the prompt on resize and allowing the shell to redraw it cleanly. * Sophisticated completion for the :program:`kitty` command in the shell. * When confirming a quit command if a window is sitting at a shell prompt, it is not counted (for details, see :opt:`confirm_os_window_close`) Configuration --------------- Shell integration is controlled by the :opt:`shell_integration` option. By default, all integration features are enabled. Individual features can be turned off or it can be disabled entirely as well. The :opt:`shell_integration` option takes a space separated list of keywords: disabled Turn off all shell integration. The shell's launch environment is not modified and :envvar:`KITTY_SHELL_INTEGRATION` is not set. Useful for :ref:`manual integration `. no-rc Do not modify the shell's launch environment to enable integration. Useful if you prefer to load the kitty shell integration code yourself, either as part of :ref:`manual integration ` or because you have some other software that sets up shell integration. This will still set the :envvar:`KITTY_SHELL_INTEGRATION` environment variable when kitty runs the shell. no-cursor Turn off changing of the text cursor to a bar when editing shell command line. no-title Turn off setting the kitty window/tab title based on shell state. Note that for the fish shell kitty relies on fish's native title setting functionality instead. no-cwd Turn off reporting the current working directory. This is used to allow :ac:`new_window_with_cwd` and similar to open windows logged into remote machines using the :doc:`ssh kitten ` automatically with the same working directory as the current window. Note that for the fish shell this will not disable its built-in current working directory reporting. no-prompt-mark Turn off marking of prompts. This disables jumping to prompt, browsing output of last command and click to move cursor functionality. Note that for the fish shell this does not take effect, since fish always marks prompts. no-complete Turn off completion for the kitty command. Note that for the fish shell this does not take effect, since fish already comes with a kitty completion script. no-sudo Do not alias :program:`sudo` to ensure the kitty terminfo files are available in the sudo environment. This is needed if you have sudo configured to disable setting of environment variables on the command line. By default, if sudo is configured to allow all commands for the current user, setting of environment variables at the command line is also allowed. Only if commands are restricted is this needed. More ways to browse command output ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can add further key and mouse bindings to browse the output of commands easily. For example to select the output of a command by right clicking the mouse on the output, define the following in :file:`kitty.conf`: .. code:: conf mouse_map right press ungrabbed mouse_select_command_output Now, when you right click on the output, the entire output is selected, ready to be copied. The feature to jump to previous prompts ( :sc:`scroll_to_previous_prompt` and :sc:`scroll_to_next_prompt`) and mouse actions (:ac:`mouse_select_command_output` and :ac:`mouse_show_command_output`) can be integrated with browsing command output as well. For example, define the following mapping in :file:`kitty.conf`: .. code:: conf map f1 show_last_visited_command_output Now, pressing :kbd:`F1` will cause the output of the last jumped to command or the last mouse clicked command output to be opened in a pager for easy browsing. In addition, You can define shortcut to get the first command output on screen. For example, define the following in :file:`kitty.conf`: .. code:: conf map f1 show_first_command_output_on_screen Now, pressing :kbd:`F1` will cause the output of the first command output on screen to be opened in a pager. You can also add shortcut to scroll to the last jumped position. For example, define the following in :file:`kitty.conf`: .. code:: conf map f1 scroll_to_prompt 0 How it works ----------------- At startup, kitty detects if the shell you have configured (either system wide or the :opt:`shell` option in :file:`kitty.conf`) is a supported shell. If so, kitty injects some shell specific code into the shell, to enable shell integration. How it does so varies for different shells. .. tab:: zsh For zsh, kitty sets the :envvar:`ZDOTDIR` environment variable to make zsh load kitty's :file:`.zshenv` which restores the original value of :envvar:`ZDOTDIR` and sources the original :file:`.zshenv`. It then loads the shell integration code. The remainder of zsh's startup process proceeds as normal. .. tab:: fish For fish, to make it automatically load the integration code provided by kitty, the integration script directory path is prepended to the :envvar:`XDG_DATA_DIRS` environment variable. This is only applied to the fish process and will be cleaned up by the integration script after startup. No files are added or modified. .. tab:: bash For bash, kitty starts bash in POSIX mode, using the environment variable :envvar:`ENV` to load the shell integration script. This prevents bash from loading any startup files itself. The loading of the startup files is done by the integration script, after disabling POSIX mode. From the perspective of those scripts there should be no difference to running vanilla bash. Then, when launching the shell, kitty sets the environment variable :envvar:`KITTY_SHELL_INTEGRATION` to the value of the :opt:`shell_integration` option. The shell integration code reads the environment variable, turns on the specified integration functionality and then unsets the variable so as to not pollute the system. The actual shell integration code uses hooks provided by each shell to send special escape codes to kitty, to perform the various tasks. You can see the code used for each shell below: .. raw:: html

Click to toggle shell integration code .. tab:: zsh .. literalinclude:: ../shell-integration/zsh/kitty-integration :language: zsh .. tab:: fish .. literalinclude:: ../shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish :language: fish :force: .. tab:: bash .. literalinclude:: ../shell-integration/bash/kitty.bash :language: bash .. raw:: html
Shell integration over SSH ---------------------------- The easiest way to have shell integration work when SSHing into remote systems is to use the :doc:`ssh kitten `. Simply run:: kitten ssh hostname And, by magic, you will be logged into the remote system with fully functional shell integration. Alternately, you can :ref:`setup shell integration manually `, by copying the kitty shell integration scripts to the remote server and editing the shell rc files there, as described below. Shell integration in a container ---------------------------------- Install the kitten `standalone binary `__ in the container somewhere in the PATH, then you can log into the container with: .. code-block:: sh docker exec -ti container-id kitten run-shell --shell=/path/to/your/shell/in/the/container The kitten will even take care of making the kitty terminfo database available in the container automatically. .. _clone_shell: Clone the current shell into a new window ----------------------------------------------- You can clone the current shell into a new kitty window by simply running the :command:`clone-in-kitty` command, for example: .. code-block:: sh clone-in-kitty clone-in-kitty --type=tab clone-in-kitty --title "I am a clone" This will open a new window running a new shell instance but with all environment variables and the current working directory copied. This even works over SSH when using :doc:`kittens/ssh`. The :command:`clone-in-kitty` command takes almost all the same arguments as the :doc:`launch ` command, so you can open a new tab instead or a new OS window, etc. Arguments of launch that can cause code execution or that don't make sense when cloning are ignored. Most prominently, the following options are ignored: :option:`--allow-remote-control `, :option:`--copy-cmdline `, :option:`--copy-env `, :option:`--stdin-source `, :option:`--marker ` and :option:`--watcher `. :command:`clone-in-kitty` can be configured to source arbitrary code in the cloned window using environment variables. It will automatically clone virtual environments created by the :link:`Python venv module ` or :link:`Conda `. In addition, setting the env var :envvar:`KITTY_CLONE_SOURCE_CODE` to some shell code will cause that code to be run in the cloned window with :code:`eval`. Similarly, setting :envvar:`KITTY_CLONE_SOURCE_PATH` to the path of a file will cause that file to be sourced in the cloned window. This can be controlled by :opt:`clone_source_strategies`. :command:`clone-in-kitty` works by asking the shell to serialize its internal state (mainly CWD and env vars) and this state is transmitted to kitty and restored by the shell integration scripts in the cloned window. .. _edit_file: Edit files in new kitty windows even over SSH ------------------------------------------------ .. code-block:: sh edit-in-kitty myfile.txt edit-in-kitty --type tab --title "Editing My File" myfile.txt # open myfile.txt at line 75 (works with vim, neovim, emacs, nano, micro) edit-in-kitty +75 myfile.txt The :command:`edit-in-kitty` command allows you to seamlessly edit files in your default :opt:`editor` in new kitty windows. This works even over SSH (if you use the :doc:`ssh kitten `), allowing you to easily edit remote files in your local editor with all its bells and whistles. The :command:`edit-in-kitty` command takes almost all the same arguments as the :doc:`launch ` command, so you can open a new tab instead or a new OS window, etc. Not all arguments are supported, see the discussion in the :ref:`clone_shell` section above. In order to avoid remote code execution, kitty will only execute the configured editor and pass the file path to edit to it. .. note:: To edit files using sudo the best method is to set the :code:`SUDO_EDITOR` environment variable to ``kitten edit-in-kitty`` and then edit the file using the ``sudoedit`` or ``sudo -e`` commands. .. _run_shell: Using shell integration in sub-shells, containers, etc. ----------------------------------------------------------- .. versionadded:: 0.29.0 To start a sub-shell with shell integration automatically setup, simply run:: kitten run-shell This will start a sub-shell using the same binary as the currently running shell, with shell-integration enabled. To start a particular shell use:: kitten run-shell --shell=/bin/bash To run a command before starting the shell use:: kitten run-shell ls . This will run ``ls .`` before starting the shell. This will even work on remote systems where kitty itself is not installed, provided you use the :doc:`SSH kitten ` to connect to the system. Use ``kitten run-shell --help`` to learn more. .. _manual_shell_integration: Manual shell integration ---------------------------- The automatic shell integration is designed to be minimally intrusive, as such it won't work for sub-shells, terminal multiplexers, containers, etc. For such systems, you should either use the :ref:`run-shell ` command described above or setup manual shell integration by adding some code to your shells startup files to load the shell integration script. First, in :file:`kitty.conf` set: .. code-block:: conf shell_integration disabled Then in your shell's rc file, add the lines: .. tab:: zsh .. code-block:: sh if test -n "$KITTY_INSTALLATION_DIR"; then export KITTY_SHELL_INTEGRATION="enabled" autoload -Uz -- "$KITTY_INSTALLATION_DIR"/shell-integration/zsh/kitty-integration kitty-integration unfunction kitty-integration fi .. tab:: fish .. code-block:: fish if set -q KITTY_INSTALLATION_DIR set --global KITTY_SHELL_INTEGRATION enabled source "$KITTY_INSTALLATION_DIR/shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish" set --prepend fish_complete_path "$KITTY_INSTALLATION_DIR/shell-integration/fish/vendor_completions.d" end .. tab:: bash .. code-block:: sh if test -n "$KITTY_INSTALLATION_DIR"; then export KITTY_SHELL_INTEGRATION="enabled" source "$KITTY_INSTALLATION_DIR/shell-integration/bash/kitty.bash" fi The value of :envvar:`KITTY_SHELL_INTEGRATION` is the same as that for :opt:`shell_integration`, except if you want to disable shell integration completely, in which case simply do not set the :envvar:`KITTY_SHELL_INTEGRATION` variable at all. In a container, you will need to install the kitty shell integration scripts and make sure the :envvar:`KITTY_INSTALLATION_DIR` environment variable is set to point to the location of the scripts. Integration with other shells ------------------------------- There exist third-party integrations to use these features for various other shells: * Jupyter console and IPython via a patch (:iss:`4475`) * `xonsh `__ * `Nushell `__: Set ``$env.config.shell_integration = true`` in your ``config.nu`` to enable it. Notes for shell developers ----------------------------- The protocol used for marking the prompt is very simple. You should consider adding it to your shell as a builtin. Many modern terminals make use of it, for example: kitty, iTerm2, WezTerm, DomTerm Just before starting to draw the PS1 prompt send the escape code: .. code-block:: none 133;A Just before starting to draw the PS2 prompt send the escape code: .. code-block:: none 133;A;k=s Just before running a command/program, send the escape code: .. code-block:: none 133;C Optionally, when a command is finished its "exit status" can be reported as: .. code-block:: none 133;D;exit status as base 10 integer Here ```` is the bytes ``0x1b 0x5d`` and ```` is the bytes ``0x1b 0x5c``. This is exactly what is needed for shell integration in kitty. For the full protocol, that also marks the command region, see `the iTerm2 docs `_. kitty additionally supports several extra fields for the ``133;A`` command to control its behavior, separated by semi-colons. They are: ``redraw=0`` this tells kitty that the shell will not redraw the prompt on resize so it should not erase it ``special_key=1`` this tells kitty to use a special key instead of arrow keys to move the cursor on mouse click. Useful if arrow keys have side-effects like triggering auto complete. The shell integration script then binds the special key, as needed. ``k=s`` this tells kitty that the secondary (PS2) prompt is starting at the current line. ``click_events=1`` this tells kitty that the shell is capable of handling mouse click events. kitty will thus send a click event to the shell when the user clicks somewhere in the prompt. The shell can then move the cursor to that position or perform some other appropriate action. Without this, kitty will instead generate a number of fake key events to move the cursor to the clicked location, which is not fully robust. kitty also optionally supports sending the cmdline going to be executed with ``133;C`` as: .. code-block:: none 133;C;cmdline=cmdline encoded by %q or 133;C;cmdline_url=cmdline as UTF-8 URL %-escaped text Here, *encoded by %q* means the encoding produced by the %q format to printf in bash and similar shells. Which is basically shell escaping with the addition of using `ANSI C quoting `__ for control characters (``$''`` quoting). kitty-0.41.1/docs/support.html0000664000175000017510000000367314773370543015652 0ustar nileshnilesh kitty-0.41.1/docs/support.rst0000664000175000017510000000170614773370543015511 0ustar nileshnileshSupport kitty development ❤️ ============================== My goal with |kitty| is to move the stagnant terminal ecosystem forward. To that end kitty has many foundational features, such as: :doc:`image support `, :doc:`superlative performance `, :doc:`various enhancements to the terminal protocol `, etc. These features allow the development of rich terminal applications, such as :doc:`Side-by-side diff ` and :doc:`Unicode input `. If you wish to support this mission and see the terminal ecosystem evolve, consider donating so that I can devote more time to |kitty| development. I have personally written `almost all kitty code `_. You can choose to make either a one-time payment via PayPal or become a *patron* of kitty development via one of the services below: .. raw:: html :file: support.html kitty-0.41.1/docs/text-sizing-protocol.rst0000664000175000017510000004164514773370543020127 0ustar nileshnileshThe text sizing protocol ============================================== .. versionadded:: 0.40.0 Classically, because the terminal is a grid of equally sized characters, only a single text size was supported in terminals, with one minor exception, some characters were allowed to be rendered in two cells, to accommodate East Asian square aspect ratio characters and Emoji. Here, by single text size we mean the font size of all text on the screen is the same. This protocol allows text to be displayed in the terminal in different sizes both larger and smaller than the base text. It also solves the long standing problem of robustly determining the width (in cells) a character should have. Applications can interleave text of different sizes on the screen allowing for typographic niceties like headlines, superscripts, etc. Note that this protocol is fully backwards compatible, terminals that implement it will continue to work just the same with applications that do not use it. Because of this, it is not fully flexible in the font sizes it allows, as it still has to work with the character cell grid based fundamental nature of the terminal. Public discussion of this protocol is :iss:`here <8226>`. Quickstart -------------- Using this protocol to display different sized text is very simple, let's illustrate with a few examples to give us a flavor: .. code-block:: sh printf "\e]_text_size_code;s=2;Double sized text\a\n\n" printf "\e]_text_size_code;s=3;Triple sized text\a\n\n\n" printf "\e]_text_size_code;n=1:d=2;Half sized text\a\n" Note that the last example, of half sized text, has half height characters, but they still each take one cell, this can be fixed with a little more work: .. code-block:: sh printf "\e]_text_size_code;n=1:d=2:w=1;Ha\a\e]66;n=1:d=2:w=1;lf\a\n" The ``w=1`` mechanism allows the program to tell the terminal what width the text should take. This not only fixes using smaller text but also solves the long standing terminal ecosystem bugs caused by the client program not knowing how many cells the terminal will render some text in. The escape code ----------------- There is a single escape code used by this protocol. It is sent by client programs to the terminal emulator to tell it to render the specified text at the specified size. It is an ``OSC`` code of the form:: _text_size_code ; metadata ; text Here, ``OSC`` is the bytes ``ESC ] (0x1b 0x5b)``. The ``metadata`` is a colon separated list of ``key=value`` pairs. The final part of the escape code is the text which is simply plain text encoded as :ref:`safe_utf8`, the text must be no longer than ``4096`` bytes. Longer strings than that must be broken up into multiple escape codes. Spaces in this definition are for clarity only and should be ignored. The ``terminator`` is either the byte ``BEL (0x7)`` or the bytes ``ESC ST (0x1b 0x5c)``. There are only a handful of metadata keys, defined in the table below: .. csv-table:: The text sizing metadata keys :header: "Key", "Value", "Default", "Description" "s", "Integer from 1 to 7", "1", "The overall scale, the text will be rendered in a block of ``s * w`` by ``s`` cells" "w", "Integer from 0 to 7", "0", "The width, in cells, in which the text should be rendered. When zero, the terminal should calculate the width as it would for normal text, splitting it up into scaled cells." "n", "Integer from 0 to 15", "0", "The numerator for the fractional scale." "d", "Integer from 0 to 15", "0", "The denominator for the fractional scale. Must be ``> n`` when non-zero." "v", "Integer from 0 to 2", "0", "The vertical alignment to use for fractionally scaled text. ``0`` - top, ``1`` - bottom, ``2`` - centered" "h", "Integer from 0 to 2", "0", "The horizontal alignment to use for fractionally scaled text. ``0`` - left, ``1`` - right, ``2`` - centered" How it works ------------------ This protocol works by allowing the client program to tell the terminal to render text in multiple cells. The terminal can then adjust the actual font size used to render the specified text as appropriate for the specified space. The space to render is controlled by four metadata keys, ``s (scale)``, ``w (width)``, ``n (numerator)`` and ``d (denominator)``. The most important are the ``s`` and ``w`` keys. The text will be rendered in a block of ``s * w`` by ``s`` cells. A special case is ``w=0`` (the default), which means the terminal splits up the text into cells as it would normally without this protocol, but now each cell is an ``s by s`` block of cells instead. So, for example, if the text is ``abc`` and ``s=2`` the terminal would normally split it into three cells:: │a│b│c│ But, because ``s=2`` it instead gets split as:: │a░│b░│c░│ │░░│░░│░░│ The terminal multiplies the font size by ``s`` when rendering these characters and thus ends up rendering text at twice the base size. When ``w`` is a non-zero value, it specifies the width in scaled cells of the following text. Note that **all** the text in that escape code must be rendered in ``s * w`` cells. When both ``s`` and ``w`` are present, the resulting multicell contains all the text in the escape code rendered in a grid of ``(s * w, s)`` cells, i.e. the multicell is ``s*w`` cells wide and ``s`` cells high. If the text does not fit, the terminal is free to do whatever it feels is best, including truncating the text or downsizing the font size when rendering it. It is up to client applications to use the ``w`` key wisely and not try to render too much text in too few cells. When sending a string of text with non zero ``w`` to the terminal emulator, the way to do it is to split up the text into chunks that fit in ``w`` cells and send one escape code per chunk. So for the string: ``cool-🐈`` the actual escape codes would be (ignoring the header and trailers):: w=1;c w=1;o w=1;o w=1;l w=1;- w=2:🐈 Note, in particular, how the last character, the cat emoji, ``🐈`` has ``w=2``. In practice client applications can assume that terminal emulators get the width of all ASCII characters correct and use the ``w=0`` form for efficient transmission, so that the above becomes:: cool- w=2:🐈 The use of non-zero ``w`` should mainly be restricted to non-ASCII characters and when using fractional scaling, as described below. .. note:: Text sizes specified by scale are relative to the base font size, thus if the base font size is changed, these sizes are changed as well. So if the terminal emulator is using a base font size of ``11pt``, then ``s=2`` will be rendered in approximately ``22pt`` (approx. because the terminal may need to slightly adjust font size to ensure it fits as not all fonts scale sizes linearly). If the user changes the base font size of the terminal emulator to ``12pt`` then the scaled font size becomes ``~24pt`` and so on. Fractional scaling ^^^^^^^^^^^^^^^^^^^^^^^ Using the main scale parameter (``s``) gives us only 7 font sizes. Fortunately, this protocol allows specifying fractional scaling, fractional scaling is applied on top of the main scale specified by ``s``. It allows niceties like: * Normal sized text but with half a line of blank space above and half a line below (``s=2:n=1:d=2:v=2``) * Superscripts (``n=1:d=2``) * Subscripts (``n=1:d=2:v=1``) * ... The fractional scale **does not** affect the number of cells the text occupies, instead, it just adjusts the rendered font size within those cells. The fraction is specified using an integer numerator and denominator (``n`` and ``d``). In addition, by using the ``v`` key one can vertically align the fractionally scaled text at top, bottom or middle. Similarly, the ``h`` key does horizontal alignment — left, right or centered. When using fractional scaling one often wants to fit more than a single character per cell. To accommodate that, there is the ``w`` key. This specifies the number of cells in which to render the text. For example, for a superscript one would typically split the string into pairs of characters and use the following for each pair:: OSC _text_size_code ; n=1:d=2:w=1 ; ab ... repeat for each pair of characters Fixing the character width issue for the terminal ecosystem --------------------------------------------------------------------- Terminals create user interfaces using text displayed in a cell grid. For terminal software that creates sophisticated user interfaces it is particularly important that the client program running in the terminal and the terminal itself agree on how many cells a particular string should be rendered in. If the two disagree, then the entire user interface can be broken, leading to catastrophic failures. Fundamentally, this is a co-ordination problem. Both the client program and the terminal have to somehow share the same database of character properties and the same algorithm for computing string lengths in cells based on that shared database. Sadly, there is no such shared database in reality. The closest we have is the Unicode standard. Unfortunately, the Unicode standard has a new version almost every year and actually changes the width assigned to some characters in different versions. Furthermore, to actually get the "correct" width for a string using that standard one has to do grapheme segmentation, which is an `extremely complex algorithm `__. Expecting all terminals and all terminal programs to have both up-to-date character databases and a bug free implementation of this algorithm is not realistic. So instead, this protocol solves the issue robustly by removing the co-ordination problem and putting only one actor in charge of determining string width. The client becomes responsible for doing whatever level of grapheme segmentation it is comfortable with using whatever Unicode database is at its disposal and then it can transmit the segmented string to the terminal with the appropriate ``w`` values so that the terminal renders the text in the exact number of cells the client expects. .. note:: It is possible for a terminal to implement only the width part of this spec and ignore the scale part. This escape code works with only the `w` key as well, as a means of specifying how many cells each piece of text occupies. In such cases ``s`` defaults to 1. See the section on :ref:`detect_text_sizing` on how client applications can query for terminal emulator support. Wrapping and overwriting behavior ------------------------------------- If the multicell block (``s * w by s`` cells) is larger than the screen size in either dimension, the terminal must discard the character. Note that in particular this means that resizing a terminal screen so that it is too small to fit a multicell character can cause the character to be lost. When drawing a multicell character, if wrapping is enabled (DECAWM is set) and the character's width (``s * w``) does not fit on the current line, the cursor is moved to the start of the next line and the character is drawn there. If wrapping is disabled and the character's width does not fit on the current line, the cursor is moved back as far as needed to fit ``s * w`` cells and then the character is drawn, following the overwriting rules described below. When drawing text either normal text or text specified via this escape code, and this text would overwrite an existing multicell character, the following rules must be followed, in decreasing order of precedence: #. If the text is a combining character it is added to the existing multicell character #. If the text will overwrite the top-left cell of the multicell character, the entire multicell character must be erased #. If the text will overwrite any cell in the topmost row of the multicell character, the entire multicell character must be replaced by spaces (this rule is present for backwards compatibility with how overwriting works for wide characters) #. If the text will overwrite cells from a row after the first row, then cursor should be moved past the cells of the multicell character on that row and only then the text should be written. Note that this behavior is independent of the value of DECAWM. This is done for simplicity of implementation. The skipping behavior of the last rule can be complex requiring the terminal to skip over lots of cells, but it is needed to allow wrapping in the presence of multicell characters that extend over more than a single line. .. _detect_text_sizing: Detecting if the terminal supports this protocol ----------------------------------------------------- To detect support for this protocol use the `CPR (Cursor Position Report) `__ escape code. Send a ``CPR`` followed by ``\e]_text_size_code;w=2; \a`` which will draw a space character in two cells, followed by another ``CPR``. Then send ``\e]_text_size_code;s=2; \a`` which will draw a space in a ``2 by 2`` block of cells, followed by another ``CPR``. Then wait for the three responses from the terminal to the three CPR queries. If the cursor position in the three responses is the same, the terminal does not support this protocol at all, if the second response has a different cursor position then the width part is supported and if the third response has yet another position, the scale part is supported. Interaction with other terminal controls -------------------------------------------------- This protocol does not change the character grid based nature of the terminal. Most terminal controls assume one character per cell so it is important to specify how these controls interact with the multicell characters created by this protocol. Cursor movement ^^^^^^^^^^^^^^^^^^^ Cursor movement is unaffected by multicell characters, all cursor movement commands move the cursor position by single cell increments, as has always been the case for terminals. This means that the cursor can be placed at any individual single cell inside a larger multicell character. When a multicell character is created using this protocol, the cursor moves `s * w` cells to the right, in the same row it was in. Terminals *should* display a large cursor covering the entire multicell block when the actual cursor position is on any cell within the block. Block cursors cover all the cells of the multicell character, bar cursors appear in all the cells in the first column of the character and so on. Editing controls ^^^^^^^^^^^^^^^^^^^^^^^^^ There are many controls used to edit existing screen content such as inserting characters, deleting characters and lines, etc. These were all originally specified for the one character per cell paradigm. Here we specify their interactions with multicell characters. **Insert characters** (``CSI @`` aka ``ICH``) When inserting ``n`` characters at cursor position ``x, y`` all characters after ``x`` on line ``y`` are supposed to be right shifted. This means that any multi-line character that intersects with the cells on line ``y`` at ``x`` and beyond must be erased. Any single line multicell character that is split by the cells at ``x`` and ``x + n - 1`` must also be erased. **Delete characters** (``CSI P`` aka ``DCH``) When deleting ``n`` characters at cursor position ``x, y`` all characters after ``x`` on line ``y`` are supposed to be left shifted. This means that any multi-line character that intersects with the cells on line ``y`` at ``x`` and beyond must be erased. Any single line multicell character that is split by the cells at ``x`` and ``x + n - 1`` must also be erased. **Erase characters** (``CSI X`` aka ``ECH``) When erasing ``n`` characters at cursor position ``x, y`` the ``n`` cells starting at ``x`` are supposed to be cleared. This means that any multicell character that intersects with the ``n`` cells starting at ``x`` must be erased. **Erase display** (``CSI J`` aka ``ED``) Any multicell character intersecting with the erased region of the screen must be erased. When using mode ``22`` the contents of the screen are first copied into the history, including all multicell characters. **Erase in line** (``CSI K`` aka ``EL``) Works just like erase characters above. Any multicell character intersecting with the erased cells in the line is erased. **Insert lines** (``CSI L`` aka ``IL``) When inserting ``n`` lines at cursor position ``y`` any multi-line characters that are split at the line ``y`` must be erased. A split happens when the second or subsequent row of the multi-line character is on the line ``y``. The insertion causes ``n`` lines to be removed from the bottom of the screen, any multi-line characters are split at the bottom of the screen must be erased. A split is when any row of the multi-line character except the last row is on the last line of the screen after the insertion of ``n`` lines. **Delete lines** (``CSI M`` aka ``DL``) When deleting ``n`` lines at cursor position ``y`` any multicell character that intersects the deleted lines must be erased. kitty-0.41.1/docs/underlines.rst0000664000175000017510000000261514773370543016145 0ustar nileshnileshColored and styled underlines ================================ |kitty| supports colored and styled (wavy) underlines. This is of particular use in terminal based text editors such as :program:`vim` and :program:`emacs` to display red, wavy underlines under mis-spelled words and/or syntax errors. This is done by re-purposing some SGR escape codes that are not used in modern terminals (`CSI codes `__) To set the underline style:: [4:0m # no underline [4:1m # straight underline [4:2m # double underline [4:3m # curly underline [4:4m # dotted underline [4:5m # dashed underline [4m # straight underline (for backwards compat) [24m # no underline (for backwards compat) To set the underline color (this is reserved and as far as I can tell not actually used for anything):: [58...m This works exactly like the codes ``38, 48`` that are used to set foreground and background color respectively. To reset the underline color (also previously reserved and unused):: [59m The underline color must remain the same under reverse video, if it has a color, if not, it should follow the foreground color. To detect support for this feature in a terminal emulator, query the terminfo database for the ``Su`` boolean capability. kitty-0.41.1/docs/unscroll.rst0000664000175000017510000000317114773370543015634 0ustar nileshnilesh.. _unscroll: Unscrolling the screen ======================== This is a small extension to the `SD (Pan up) escape code `_ from the VT-420 terminal. The ``SD`` escape code normally causes the text on screen to scroll down by the specified number of lines, with empty lines appearing at the top of the screen. This extension allows the new lines to be filled in from the scrollback buffer instead of being blank. The motivation for this is that many modern shells will show completions in a block of lines under the cursor, this causes some of the on-screen text to be lost even after the completion is completed, because it has scrolled off screen. This escape code allows that text to be restored. If the scrollback buffer is empty or there is no scrollback buffer, such as for the alternate screen, then the newly inserted lines must be empty, just as with the original ``SD`` escape code. The maximum number of lines that can be scrolled down is implementation defined, but must be at least one screen worth. The syntax of the escape code is identical to that of ``SD`` except that it has a trailing ``+`` modifier. This is legal under the `ECMA 48 standard `__ and unused for any other purpose as far as I can tell. So for example, to unscroll three lines, the escape code would be:: CSI 3 + T See `discussion here `__. .. versionadded:: 0.20.2 Also supported by the terminals: * `mintty `__ kitty-0.41.1/gen/0000775000175000017510000000000014773370543013060 5ustar nileshnileshkitty-0.41.1/gen/README.rst0000664000175000017510000000041414773370543014546 0ustar nileshnileshScripts to generate code for various things like keys, mouse cursors, unicode data etc. Some of these generate code that is checked into version control. Some generate ephemeral code used during builds. Ephemeral code is in files with a _generated.[h|go|c] extension. kitty-0.41.1/gen/__init__.py0000664000175000017510000000000014773370543015157 0ustar nileshnileshkitty-0.41.1/gen/__main__.py0000664000175000017510000000211314773370543015147 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2023, Kovid Goyal import os import sys def main(args: list[str]=sys.argv) -> None: os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.getcwd()) if len(args) == 1: raise SystemExit('usage: python gen which') which = args[1] del args[1] if which == 'apc-parsers': from gen.apc_parsers import main main(args) elif which == 'config': from gen.config import main main(args) elif which == 'srgb-lut': from gen.srgb_lut import main main(args) elif which == 'key-constants': from gen.key_constants import main main(args) elif which == 'go-code': from gen.go_code import main main(args) elif which == 'wcwidth': from gen.wcwidth import main main(args) elif which == 'cursors': from gen.cursors import main main(args) else: raise SystemExit(f'Unknown which: {which}') if __name__ == '__main__': main() kitty-0.41.1/gen/apc_parsers.py0000775000175000017510000002753314773370543015751 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2018, Kovid Goyal import os import subprocess import sys from collections import defaultdict from typing import Any, DefaultDict, Union if __name__ == '__main__' and not __package__: import __main__ __main__.__package__ = 'gen' sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) KeymapType = dict[str, tuple[str, Union[frozenset[str], str]]] def resolve_keys(keymap: KeymapType) -> DefaultDict[str, list[str]]: ans: DefaultDict[str, list[str]] = defaultdict(list) for ch, (attr, atype) in keymap.items(): if isinstance(atype, str) and atype in ('int', 'uint'): q = atype else: q = 'flag' ans[q].append(ch) return ans def enum(keymap: KeymapType) -> str: lines = [] for ch, (attr, atype) in keymap.items(): lines.append(f"{attr}='{ch}'") return ''' enum KEYS {{ {} }}; '''.format(',\n'.join(lines)) def parse_key(keymap: KeymapType) -> str: lines = [] for attr, atype in keymap.values(): vs = atype.upper() if isinstance(atype, str) and atype in ('uint', 'int') else 'FLAG' lines.append(f'case {attr}: value_state = {vs}; break;') return ' \n'.join(lines) def parse_flag(keymap: KeymapType, type_map: dict[str, Any], command_class: str) -> str: lines = [] for ch in type_map['flag']: attr, allowed_values = keymap[ch] q = ' && '.join(f"g.{attr} != '{x}'" for x in sorted(allowed_values)) lines.append(f''' case {attr}: {{ g.{attr} = parser_buf[pos++]; if ({q}) {{ REPORT_ERROR("Malformed {command_class} control block, unknown flag value for {attr}: 0x%x", g.{attr}); return; }}; }} break; ''') return ' \n'.join(lines) def parse_number(keymap: KeymapType) -> tuple[str, str]: int_keys = [f'I({attr})' for attr, atype in keymap.values() if atype == 'int'] uint_keys = [f'U({attr})' for attr, atype in keymap.values() if atype == 'uint'] return '; '.join(int_keys), '; '.join(uint_keys) def cmd_for_report(report_name: str, keymap: KeymapType, type_map: dict[str, Any], payload_allowed: bool, payload_is_base64: bool) -> str: def group(atype: str, conv: str) -> tuple[str, str]: flag_fmt, flag_attrs = [], [] cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype] for ch in type_map[atype]: flag_fmt.append(f's{cv}') attr = keymap[ch][0] flag_attrs.append(f'"{attr}", {conv}g.{attr}') return ' '.join(flag_fmt), ', '.join(flag_attrs) flag_fmt, flag_attrs = group('flag', '') int_fmt, int_attrs = group('int', '(int)') uint_fmt, uint_attrs = group('uint', '(unsigned int)') fmt = f'{flag_fmt} {uint_fmt} {int_fmt}' if payload_allowed: ans = [f'REPORT_VA_COMMAND("K s {{{fmt} ss#}}", self->window_id, "{report_name}",\n'] else: ans = [f'REPORT_VA_COMMAND("K s {{{fmt}}}", self->window_id, "{report_name}",\n'] if flag_attrs: ans.append(f'{flag_attrs},\n') if uint_attrs: ans.append(f'{uint_attrs},\n') if int_attrs: ans.append(f'{int_attrs},\n') if payload_allowed: if payload_is_base64: ans.append('"", (char*)parser_buf, g.payload_sz') else: ans.append('"", (char*)parser_buf + payload_start, g.payload_sz') ans.append(');') return '\n'.join(ans) def generate( function_name: str, callback_name: str, report_name: str, keymap: KeymapType, command_class: str, initial_key: str = 'a', payload_allowed: bool = True, payload_is_base64: bool = True, start_parsing_at: int = 1, field_sep: str = ',', ) -> str: type_map = resolve_keys(keymap) keys_enum = enum(keymap) handle_key = parse_key(keymap) flag_keys = parse_flag(keymap, type_map, command_class) int_keys, uint_keys = parse_number(keymap) report_cmd = cmd_for_report(report_name, keymap, type_map, payload_allowed, payload_is_base64) extra_init = '' if payload_allowed: payload_after_value = "case ';': state = PAYLOAD; break;" payload = ', PAYLOAD' if payload_is_base64: payload_case = f''' case PAYLOAD: {{ sz = parser_buf_pos - pos; g.payload_sz = MAX(BUF_EXTRA, sz); if (!base64_decode8(parser_buf + pos, sz, parser_buf, &g.payload_sz)) {{ g.payload_sz = MAX(BUF_EXTRA, sz); REPORT_ERROR("Failed to parse {command_class} command payload with error: \ invalid base64 data in chunk of size: %zu with output buffer size: %zu", sz, g.payload_sz); return; }} pos = parser_buf_pos; }} break; ''' callback = f'{callback_name}(self->screen, &g, parser_buf)' else: payload_case = ''' case PAYLOAD: { sz = parser_buf_pos - pos; payload_start = pos; g.payload_sz = sz; pos = parser_buf_pos; } break; ''' extra_init = 'size_t payload_start = 0;' callback = f'{callback_name}(self->screen, &g, parser_buf + payload_start)' else: payload_after_value = payload = payload_case = '' callback = f'{callback_name}(self->screen, &g)' return f''' #include "base64.h" static inline void {function_name}(PS *self, uint8_t *parser_buf, const size_t parser_buf_pos) {{ unsigned int pos = {start_parsing_at}; {extra_init} enum PARSER_STATES {{ KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE {payload} }}; enum PARSER_STATES state = KEY, value_state = FLAG; {command_class} g = {{0}}; unsigned int i, code; uint64_t lcode; int64_t accumulator; bool is_negative; (void)is_negative; size_t sz; {keys_enum} enum KEYS key = '{initial_key}'; if (parser_buf[pos] == ';') state = AFTER_VALUE; while (pos < parser_buf_pos) {{ switch(state) {{ case KEY: key = parser_buf[pos++]; state = EQUAL; switch(key) {{ {handle_key} default: REPORT_ERROR("Malformed {command_class} control block, invalid key character: 0x%x", key); return; }} break; case EQUAL: if (parser_buf[pos++] != '=') {{ REPORT_ERROR("Malformed {command_class} control block, no = after key, found: 0x%x instead", parser_buf[pos-1]); return; }} state = value_state; break; case FLAG: switch(key) {{ {flag_keys} default: break; }} state = AFTER_VALUE; break; case INT: #define READ_UINT \\ for (i = pos, accumulator=0; i < MIN(parser_buf_pos, pos + 10); i++) {{ \\ int64_t n = parser_buf[i] - '0'; if (n < 0 || n > 9) break; \\ accumulator += n * digit_multipliers[i - pos]; \\ }} \\ if (i == pos) {{ REPORT_ERROR("Malformed {command_class} control block, expecting an integer value for key: %c", key & 0xFF); return; }} \\ lcode = accumulator / digit_multipliers[i - pos - 1]; pos = i; \\ if (lcode > UINT32_MAX) {{ REPORT_ERROR("Malformed {command_class} control block, number is too large"); return; }} \\ code = lcode; is_negative = false; if(parser_buf[pos] == '-') {{ is_negative = true; pos++; }} #define I(x) case x: g.x = is_negative ? 0 - (int32_t)code : (int32_t)code; break READ_UINT; switch(key) {{ {int_keys}; default: break; }} state = AFTER_VALUE; break; #undef I case UINT: READ_UINT; #define U(x) case x: g.x = code; break switch(key) {{ {uint_keys}; default: break; }} state = AFTER_VALUE; break; #undef U #undef READ_UINT case AFTER_VALUE: switch (parser_buf[pos++]) {{ default: REPORT_ERROR("Malformed {command_class} control block, expecting a {field_sep} or semi-colon after a value, found: 0x%x", parser_buf[pos - 1]); return; case '{field_sep}': state = KEY; break; {payload_after_value} }} break; {payload_case} }} // end switch }} // end while switch(state) {{ case EQUAL: REPORT_ERROR("Malformed {command_class} control block, no = after key"); return; case INT: case UINT: REPORT_ERROR("Malformed {command_class} control block, expecting an integer value"); return; case FLAG: REPORT_ERROR("Malformed {command_class} control block, expecting a flag value"); return; default: break; }} {report_cmd} {callback}; }} ''' def write_header(text: str, path: str) -> None: with open(path, 'w') as f: print(f'// This file is generated by {os.path.basename(__file__)} do not edit!', file=f, end='\n\n') print('#pragma once', file=f) print(text, file=f) subprocess.check_call(['clang-format', '-i', path]) def parsers() -> None: flag = frozenset keymap: KeymapType = { 'a': ('action', flag('tTqpdfac')), 'd': ('delete_action', flag('aAiIcCfFnNpPqQrRxXyYzZ')), 't': ('transmission_type', flag('dfts')), 'o': ('compressed', flag('z')), 'f': ('format', 'uint'), 'm': ('more', 'uint'), 'i': ('id', 'uint'), 'I': ('image_number', 'uint'), 'p': ('placement_id', 'uint'), 'q': ('quiet', 'uint'), 'w': ('width', 'uint'), 'h': ('height', 'uint'), 'x': ('x_offset', 'uint'), 'y': ('y_offset', 'uint'), 'v': ('data_height', 'uint'), 's': ('data_width', 'uint'), 'S': ('data_sz', 'uint'), 'O': ('data_offset', 'uint'), 'c': ('num_cells', 'uint'), 'r': ('num_lines', 'uint'), 'X': ('cell_x_offset', 'uint'), 'Y': ('cell_y_offset', 'uint'), 'z': ('z_index', 'int'), 'C': ('cursor_movement', 'uint'), 'U': ('unicode_placement', 'uint'), 'P': ('parent_id', 'uint'), 'Q': ('parent_placement_id', 'uint'), 'H': ('offset_from_parent_x', 'int'), 'V': ('offset_from_parent_y', 'int'), } text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand') write_header(text, 'kitty/parse-graphics-command.h') keymap = { 'w': ('width', 'uint'), 's': ('scale', 'uint'), 'n': ('subscale_n', 'uint'), 'd': ('subscale_d', 'uint'), 'v': ('vertical_align', 'uint'), 'h': ('horizontal_align', 'uint'), } text = generate( 'parse_multicell_code', 'screen_handle_multicell_command', 'multicell_command', keymap, 'MultiCellCommand', payload_is_base64=False, start_parsing_at=0, field_sep=':') write_header(text, 'kitty/parse-multicell-command.h') def main(args: list[str]=sys.argv) -> None: parsers() if __name__ == '__main__': import runpy m = runpy.run_path(os.path.dirname(os.path.abspath(__file__))) m['main']([sys.executable, 'apc-parsers']) kitty-0.41.1/gen/bitfields.py0000664000175000017510000000361214773370543015401 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2025, Kovid Goyal import os from typing import NamedTuple class BitField(NamedTuple): name: str bits: int def typename_for_bitsize(bits: int) -> str: if bits <= 8: return 'uint8' if bits <= 16: return 'uint16' if bits <= 32: return 'uint32' return 'uint64' def make_bitfield(dest: str, typename: str, *fields_: str, add_package: bool = True) -> tuple[str, str]: output_path = os.path.join(dest, f'{typename.lower()}_generated.go') ans = [f'package {os.path.basename(dest)}', ''] a = ans.append if not add_package: del ans[0] def fieldify(spec: str) -> BitField: name, num = spec.partition(' ')[::2] return BitField(name, int(num)) fields = tuple(map(fieldify, fields_)) total_size = sum(x.bits for x in fields) if total_size > 64: raise ValueError(f'Total size of bit fields: {total_size} for {typename} is larger than 64 bits') a(f'// Total number of bits used: {total_size}') itype = typename_for_bitsize(total_size) a(f'type {typename} {itype}') a('') shift = 0 for bf in reversed(fields): tn = typename_for_bitsize(bf.bits) mask = '0b' + '1' * bf.bits a(f'func (s {typename}) {bf.name.capitalize()}() {tn} {{') # }} if shift: a(f' return {tn}((s >> {shift}) & {mask})') else: a(f' return {tn}(s & {mask})') a('}') a('') a(f'func (s *{typename}) Set_{bf.name}(val {tn}) {{') # }} if shift: a(f' *s &^= {mask} << {shift}') a(f' *s |= {typename}(val&{mask}) << {shift}') else: a(f' *s &^= {mask}') a(f' *s |= {typename}(val & {mask})') a('}') a('') shift += bf.bits return output_path, '\n'.join(ans) kitty-0.41.1/gen/config.py0000775000175000017510000000447314773370543014712 0ustar nileshnilesh#!./kitty/launcher/kitty +launch # License: GPLv3 Copyright: 2021, Kovid Goyal import os import re import subprocess import sys from kitty.conf.generate import write_output if __name__ == '__main__' and not __package__: import __main__ __main__.__package__ = 'gen' sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) def patch_color_list(path: str, colors: list[str], name: str, spc: str = ' ') -> None: with open(path, 'r+') as f: raw = f.read() colors = sorted(colors) if path.endswith('.go'): spc = '\t' nraw = re.sub( fr'(// {name}_COLORS_START).+?(\s+// {name}_COLORS_END)', r'\1' + f'\n{spc}' + f'\n{spc}'.join(map(lambda x: f'"{x}":true,', colors)) + r'\2', raw, flags=re.DOTALL | re.MULTILINE) else: nraw = re.sub( fr'(# {name}_COLORS_START).+?(\s+# {name}_COLORS_END)', r'\1' + f'\n{spc}' + f'\n{spc}'.join(map(lambda x: f'{x!r},', colors)) + r'\2', raw, flags=re.DOTALL | re.MULTILINE) if nraw != raw: f.seek(0) f.truncate() f.write(nraw) f.flush() if path.endswith('.go'): subprocess.check_call(['gofmt', '-w', path]) def main(args: list[str]=sys.argv) -> None: from kitty.options.definition import definition nullable_colors = [] all_colors = [] for opt in definition.iter_all_options(): if callable(opt.parser_func): if opt.parser_func.__name__ in ('to_color_or_none', 'cursor_text_color'): nullable_colors.append(opt.name) all_colors.append(opt.name) elif opt.parser_func.__name__ in ('to_color', 'titlebar_color', 'macos_titlebar_color'): all_colors.append(opt.name) patch_color_list('tools/cmd/at/set_colors.go', nullable_colors, 'NULLABLE') patch_color_list('tools/themes/collection.go', all_colors, 'ALL') nc = ',\n '.join(f'{x!r}' for x in nullable_colors) write_output('kitty', definition, f'\nnullable_colors = frozenset({{\n {nc}\n}})\n') if __name__ == '__main__': import runpy m = runpy.run_path(os.path.dirname(os.path.abspath(__file__))) m['main']([sys.executable, 'config']) kitty-0.41.1/gen/cursors.py0000775000175000017510000002203714773370543015141 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2023, Kovid Goyal import os import subprocess import sys if __name__ == '__main__' and not __package__: import __main__ __main__.__package__ = 'gen' sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from .key_constants import patch_file # References for these names: # CSS:choices_for_{option.name} https://developer.mozilla.org/en-US/docs/Web/CSS/cursor # XCursor: https://tronche.com/gui/x/xlib/appendix/b/ + Absolute chaos # Wayland: https://wayland.app/protocols/cursor-shape-v1 # Cocoa: https://developer.apple.com/documentation/appkit/nscursor + secret apple selectors + SDL_cocoamouse.m # kitty_names CSS_name XCursor_names Wayland_name Cocoa_name cursors = '''\ arrow default default,!left_ptr default arrowCursor beam,text text text,!xterm,ibeam text IBeamCursor pointer,hand pointer pointing_hand,pointer,!hand2,hand pointer pointingHandCursor help help help,!question_arrow,whats_this help help:arrowCursor wait wait wait,!clock,watch wait busybutclickable:arrowCursor progress progress progress,half-busy,left_ptr_watch progress busybutclickable:arrowCursor crosshair crosshair crosshair,!tcross crosshair crosshairCursor cell cell cell,!plus,!cross cell cell:crosshairCursor vertical-text vertical-text vertical-text vertical-text IBeamCursorForVerticalLayout move move move,!fleur,pointer-move move move:openHandCursor e-resize e-resize e-resize,!right_side e_resize resizeRightCursor ne-resize ne-resize ne-resize,!top_right_corner ne_resize resizenortheast:_windowResizeNorthEastSouthWestCursor nw-resize nw-resize nw-resize,!top_left_corner nw_resize resizenorthwest:_windowResizeNorthWestSouthEastCursor n-resize n-resize n-resize,!top_side n_resize resizeUpCursor se-resize se-resize se-resize,!bottom_right_corner se_resize resizesoutheast:_windowResizeNorthWestSouthEastCursor sw-resize sw-resize sw-resize,!bottom_left_corner sw_resize resizesouthwest:_windowResizeNorthEastSouthWestCursor s-resize s-resize s-resize,!bottom_side s_resize resizeDownCursor w-resize w-resize w-resize,!left_side w_resize resizeLeftCursor ew-resize ew-resize ew-resize,!sb_h_double_arrow,split_h ew_resize resizeLeftRightCursor ns-resize ns-resize ns-resize,!sb_v_double_arrow,split_v ns_resize resizeUpDownCursor nesw-resize nesw-resize nesw-resize,size_bdiag,size-bdiag nesw_resize _windowResizeNorthEastSouthWestCursor nwse-resize nwse-resize nwse-resize,size_fdiag,size-fdiag nwse_resize _windowResizeNorthWestSouthEastCursor zoom-in zoom-in zoom-in,zoom_in zoom_in zoomin:arrowCursor zoom-out zoom-out zoom-out,zoom_out zoom_out zoomout:arrowCursor alias alias dnd-link alias dragLinkCursor copy copy dnd-copy copy dragCopyCursor not-allowed not-allowed not-allowed,forbidden,crossed_circle not_allowed operationNotAllowedCursor no-drop no-drop no-drop,dnd-no-drop no_drop operationNotAllowedCursor grab grab grab,openhand,!hand1 grab openHandCursor grabbing grabbing grabbing,closedhand,dnd-none grabbing closedHandCursor ''' def main(args: list[str]=sys.argv) -> None: glfw_enum = [] css_names = [] glfw_xc_map = {} glfw_xfont_map = [] kitty_to_enum_map = {} enum_to_glfw_map = {} enum_to_css_map = {} glfw_cocoa_map = {} glfw_css_map = {} css_to_enum = {} xc_to_enum = {} glfw_wayland = {} for line in cursors.splitlines(): line = line.strip() if line: names_, css, xc_, wayland, cocoa = line.split() names, xc = names_.split(','), xc_.split(',') base = css.replace('-', '_').upper() glfw_name = 'GLFW_' + base + '_CURSOR' enum_name = base + '_POINTER' enum_to_glfw_map[enum_name] = glfw_name enum_to_css_map[enum_name] = css glfw_css_map[glfw_name] = css css_to_enum[css] = enum_name css_names.append(css) glfw_wayland[glfw_name] = 'WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_' + wayland.replace('-', '_').upper() for n in names: kitty_to_enum_map[n] = enum_name glfw_enum.append(glfw_name) glfw_xc_map[glfw_name] = ', '.join(f'''"{x.replace('!', '')}"''' for x in xc) for x in xc: if x.startswith('!'): glfw_xfont_map.append(f"case {glfw_name}: return set_cursor_from_font(cursor, {'XC_' + x[1:]});") break else: items = tuple('"' + x.replace('!', '') + '"' for x in xc) glfw_xfont_map.append(f'case {glfw_name}: return try_cursor_names(cursor, {len(items)}, {", ".join(items)});') for x in xc: x = x.lstrip('!') xc_to_enum[x] = enum_name parts = cocoa.split(':', 1) if len(parts) == 1: if parts[0].startswith('_'): glfw_cocoa_map[glfw_name] = f'U({glfw_name}, {parts[0]});' else: glfw_cocoa_map[glfw_name] = f'C({glfw_name}, {parts[0]});' else: glfw_cocoa_map[glfw_name] = f'S({glfw_name}, {parts[0]}, {parts[1]});' for x, v in xc_to_enum.items(): if x not in css_to_enum: css_to_enum[x] = v glfw_enum.append('GLFW_INVALID_CURSOR') patch_file('glfw/glfw3.h', 'mouse cursor shapes', '\n'.join(f' {x},' for x in glfw_enum)) patch_file('glfw/wl_window.c', 'glfw to wayland mapping', '\n'.join(f' C({g}, {x});' for g, x in glfw_wayland.items())) patch_file('glfw/wl_window.c', 'glfw to xc mapping', '\n'.join(f' C({g}, {x});' for g, x in glfw_xc_map.items())) patch_file('glfw/x11_window.c', 'glfw to xc mapping', '\n'.join(f' {x}' for x in glfw_xfont_map)) patch_file('kitty/data-types.h', 'mouse shapes', '\n'.join(f' {x},' for x in enum_to_glfw_map)) patch_file( 'kitty/options/utils.py', 'pointer shape names', '\n'.join(f' {x!r},' for x in kitty_to_enum_map), start_marker='# ', end_marker='', ) patch_file('kitty/options/to-c.h', 'pointer shapes', '\n'.join( f' else if (strcmp(name, "{k}") == 0) return {v};' for k, v in kitty_to_enum_map.items())) patch_file('kitty/glfw.c', 'enum to glfw', '\n'.join( f' case {k}: set_glfw_mouse_cursor(w, {v}); break;' for k, v in enum_to_glfw_map.items())) patch_file('kitty/glfw.c', 'name to glfw', '\n'.join( f' if (strcmp(name, "{k}") == 0) return {enum_to_glfw_map[v]};' for k, v in kitty_to_enum_map.items())) patch_file('kitty/glfw.c', 'glfw to css', '\n'.join( f' case {g}: return "{c}";' for g, c in glfw_css_map.items() )) patch_file('kitty/screen.c', 'enum to css', '\n'.join( f' case {e}: ans = "{c}"; break;' for e, c in enum_to_css_map.items())) patch_file('kitty/screen.c', 'css to enum', '\n'.join( f' else if (strcmp("{c}", css_name) == 0) s = {e};' for c, e in css_to_enum.items())) patch_file('glfw/cocoa_window.m', 'glfw to cocoa', '\n'.join(f' {x}' for x in glfw_cocoa_map.values())) patch_file('docs/pointer-shapes.rst', 'list of shape css names', '\n'.join( f'#. {x}' if x else '' for x in [''] + sorted(css_names) + ['']), start_marker='.. ', end_marker='') patch_file('tools/tui/loop/mouse.go', 'pointer shape enum', '\n'.join( f'\t{x} PointerShape = {i}' for i, x in enumerate(enum_to_glfw_map)), start_marker='// ', end_marker='') patch_file('tools/tui/loop/mouse.go', 'pointer shape tostring', '\n'.join( f'''\tcase {x}: return "{x.lower().rpartition('_')[0].replace('_', '-')}"''' for x in enum_to_glfw_map), start_marker='// ', end_marker='') patch_file('tools/cmd/mouse_demo/main.go', 'all pointer shapes', '\n'.join( f'\tloop.{x},' for x in enum_to_glfw_map), start_marker='// ', end_marker='') subprocess.check_call(['glfw/glfw.py']) if __name__ == '__main__': import runpy m = runpy.run_path(os.path.dirname(os.path.abspath(__file__))) m['main']([sys.executable, 'cursors']) kitty-0.41.1/gen/go_code.py0000775000175000017510000010647214773370543015046 0ustar nileshnilesh#!./kitty/launcher/kitty +launch # License: GPLv3 Copyright: 2022, Kovid Goyal import argparse import bz2 import io import json import os import re import shlex import struct import subprocess import sys import tarfile from collections.abc import Iterator, Sequence from contextlib import contextmanager, suppress from functools import lru_cache from itertools import chain from typing import ( Any, BinaryIO, Optional, TextIO, Union, ) import kitty.constants as kc from kittens.tui.operations import Mode from kittens.tui.spinners import spinners from kitty.actions import get_all_actions from kitty.cli import ( CompletionSpec, GoOption, go_options_for_seq, parse_option_spec, serialize_as_go_string, ) from kitty.conf.generate import gen_go_code from kitty.conf.types import Definition from kitty.config import commented_out_default_config from kitty.guess_mime_type import known_extensions, text_mimes from kitty.key_encoding import config_mod_map from kitty.key_names import character_key_name_aliases, functional_key_name_aliases from kitty.options.types import Options from kitty.rc.base import RemoteCommand, all_command_names, command_for_name from kitty.remote_control import global_options_spec from kitty.rgb import color_names if __name__ == '__main__' and not __package__: import __main__ __main__.__package__ = 'gen' sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) changed: list[str] = [] def newer(dest: str, *sources: str) -> bool: try: dtime = os.path.getmtime(dest) except OSError: return True for s in chain(sources, (__file__,)): with suppress(FileNotFoundError): if os.path.getmtime(s) >= dtime: return True return False # Utils {{{ def serialize_go_dict(x: Union[dict[str, int], dict[int, str], dict[int, int], dict[str, str]]) -> str: ans = [] def s(x: Union[int, str]) -> str: if isinstance(x, int): return str(x) return f'"{serialize_as_go_string(x)}"' for k, v in x.items(): ans.append(f'{s(k)}: {s(v)}') return '{' + ', '.join(ans) + '}' def replace(template: str, **kw: str) -> str: for k, v in kw.items(): template = template.replace(k, v) return template # }}} # {{{ Stringer @lru_cache(maxsize=1) def enum_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser() p.add_argument('--from-string-func-name') return p def stringify_file(path: str) -> None: with open(path) as f: src = f.read() types = {} constant_name_maps = {} for m in re.finditer(r'^type +(\S+) +\S+ +// *enum *(.*?)$', src, re.MULTILINE): args = m.group(2) types[m.group(1)] = enum_parser().parse_args(args=shlex.split(args) if args else []) def get_enum_def(src: str) -> None: type_name = q = '' constants = {} for line in src.splitlines(): line = line.strip() if not line: continue parts = line.split() if not type_name: if len(parts) < 2 or parts[1] not in types: return type_name = parts[1] q = type_name + '_' constant_name = parts[0] a, sep, b = line.partition('//') if sep: string_val = b.strip() else: string_val = constant_name if constant_name.startswith(q): string_val = constant_name[len(q):] constants[constant_name] = serialize_as_go_string(string_val) if constants and type_name: constant_name_maps[type_name] = constants for m in re.finditer(r'^const +\((.+?)^\)', src, re.MULTILINE|re.DOTALL): get_enum_def(m.group(1)) with replace_if_needed(path.replace('.go', '_stringer_generated.go')): print('package', os.path.basename(os.path.dirname(path))) print ('import "fmt"') print ('import "encoding/json"') print() for type_name, constant_map in constant_name_maps.items(): print(f'func (self {type_name}) String() string ''{') print('switch self {') is_first = True for constant_name, string_val in constant_map.items(): if is_first: print(f'default: return "{string_val}"') is_first = False else: print(f'case {constant_name}: return "{string_val}"') print('}}') print(f'func (self {type_name}) MarshalJSON() ([]byte, error) {{ return json.Marshal(self.String()) }}') fsname = types[type_name].from_string_func_name or (type_name + '_from_string') print(f'func {fsname}(x string) (ans {type_name}, err error) ''{') print('switch x {') for constant_name, string_val in constant_map.items(): print(f'case "{string_val}": return {constant_name}, nil') print('}') print(f'err = fmt.Errorf("unknown value for enum {type_name}: %#v", x)') print('return') print('}') print(f'func (self *{type_name}) SetString(x string) error ''{') print(f's, err := {fsname}(x); if err == nil {{ *self = s }}; return err''}') print(f'func (self *{type_name}) UnmarshalJSON(data []byte) (err error)''{') print('var x string') print('if err = json.Unmarshal(data, &x); err != nil {return err}') print('return self.SetString(x)}') def stringify() -> None: for path in ( 'tools/tui/graphics/command.go', 'tools/rsync/algorithm.go', 'kittens/transfer/ftc.go', ): stringify_file(path) # }}} # {{{ Bitfields def make_bitfields() -> None: from kitty.fast_data_types import SCALE_BITS, SUBSCALE_BITS, WIDTH_BITS from .bitfields import make_bitfield def mb(*args: str) -> None: output_path, ans = make_bitfield(*args) with replace_if_needed(output_path) as buf: print(ans, file=buf) mb( 'tools/vt', 'CellAttrs', 'decoration 3', 'bold 1', 'italic 1', 'reverse 1', 'strike 1', 'dim 1', 'hyperlink_id 16', ) mb('tools/vt', 'Ch', 'is_idx 1', 'ch_or_idx 31') mb( 'tools/vt', 'MultiCell', 'is_multicell 1', 'natural_width 1', f'scale {SCALE_BITS}', f'subscale_n {SUBSCALE_BITS}', f'subscale_d {SUBSCALE_BITS}', f'width {WIDTH_BITS}', f'x {WIDTH_BITS + SCALE_BITS + 1}', f'y {SCALE_BITS + 1}', 'vertical_align 3', ) mb('tools/vt', 'CellColor', 'is_idx 1', 'red 8', 'green 8', 'blue 8') mb('tools/vt', 'LineAttrs', 'prompt_kind 2',) # }}} # Completions {{{ @lru_cache def kitten_cli_docs(kitten: str) -> Any: from kittens.runner import get_kitten_cli_docs return get_kitten_cli_docs(kitten) @lru_cache def go_options_for_kitten(kitten: str) -> tuple[Sequence[GoOption], Optional[CompletionSpec]]: kcd = kitten_cli_docs(kitten) if kcd: ospec = kcd['options'] return (tuple(go_options_for_seq(parse_option_spec(ospec())[0])), kcd.get('args_completion')) return (), None def generate_kittens_completion() -> None: from kittens.runner import all_kitten_names, get_kitten_wrapper_of for kitten in sorted(all_kitten_names()): kn = 'kitten_' + kitten print(f'{kn} := plus_kitten.AddSubCommand(&cli.Command{{Name:"{kitten}", Group: "Kittens"}})') wof = get_kitten_wrapper_of(kitten) if wof: print(f'{kn}.ArgCompleter = cli.CompletionForWrapper("{serialize_as_go_string(wof)}")') print(f'{kn}.OnlyArgsAllowed = true') continue gopts, ac = go_options_for_kitten(kitten) if gopts or ac: for opt in gopts: print(opt.as_option(kn)) if ac is not None: print(''.join(ac.as_go_code(kn + '.ArgCompleter', ' = '))) else: print(f'{kn}.HelpText = ""') @lru_cache def clone_safe_launch_opts() -> Sequence[GoOption]: from kitty.launch import clone_safe_opts, options_spec ans = [] allowed = clone_safe_opts() for o in go_options_for_seq(parse_option_spec(options_spec())[0]): if o.obj_dict['name'] in allowed: ans.append(o) return tuple(ans) def completion_for_launch_wrappers(*names: str) -> None: for o in clone_safe_launch_opts(): for name in names: print(o.as_option(name)) def generate_completions_for_kitty() -> None: print('package completion\n') print('import "kitty/tools/cli"') print('import "kitty/tools/cmd/tool"') print('import "kitty/tools/cmd/at"') print('func kitty(root *cli.Command) {') # The kitty exe print('k := root.AddSubCommand(&cli.Command{' 'Name:"kitty", SubCommandIsOptional: true, ArgCompleter: cli.CompleteExecutableFirstArg, SubCommandMustBeFirst: true })') print('kt := root.AddSubCommand(&cli.Command{Name:"kitten", SubCommandMustBeFirst: true })') print('tool.KittyToolEntryPoints(kt)') for opt in go_options_for_seq(parse_option_spec()[0]): print(opt.as_option('k')) # kitty + print('plus := k.AddSubCommand(&cli.Command{Name:"+", Group:"Entry points", ShortDescription: "Various special purpose tools and kittens"})') # kitty +launch print('plus_launch := plus.AddSubCommand(&cli.Command{' 'Name:"launch", Group:"Entry points", ShortDescription: "Launch Python scripts", ArgCompleter: complete_plus_launch})') print('k.AddClone("", plus_launch).Name = "+launch"') # kitty +list-fonts print('plus_list_fonts := plus.AddSubCommand(&cli.Command{' 'Name:"list-fonts", Group:"Entry points", ShortDescription: "List all available monospaced fonts"})') print('k.AddClone("", plus_list_fonts).Name = "+list-fonts"') # kitty +runpy print('plus_runpy := plus.AddSubCommand(&cli.Command{' 'Name: "runpy", Group:"Entry points", ArgCompleter: complete_plus_runpy, ShortDescription: "Run Python code"})') print('k.AddClone("", plus_runpy).Name = "+runpy"') # kitty +open print('plus_open := plus.AddSubCommand(&cli.Command{' 'Name:"open", Group:"Entry points", ArgCompleter: complete_plus_open, ShortDescription: "Open files and URLs"})') print('for _, og := range k.OptionGroups { plus_open.OptionGroups = append(plus_open.OptionGroups, og.Clone(plus_open)) }') print('k.AddClone("", plus_open).Name = "+open"') # kitty +kitten print('plus_kitten := plus.AddSubCommand(&cli.Command{Name:"kitten", Group:"Kittens", SubCommandMustBeFirst: true})') generate_kittens_completion() print('k.AddClone("", plus_kitten).Name = "+kitten"') # @ print('at.EntryPoint(k)') # clone-in-kitty, edit-in-kitty print('cik := root.AddSubCommand(&cli.Command{Name:"clone-in-kitty"})') completion_for_launch_wrappers('cik') print('}') print('func init() {') print('cli.RegisterExeForCompletion(kitty)') print('}') # }}} # rc command wrappers {{{ json_field_types: dict[str, str] = { 'bool': 'bool', 'str': 'escaped_string', 'list.str': '[]escaped_string', 'dict.str': 'map[escaped_string]escaped_string', 'float': 'float64', 'int': 'int', 'scroll_amount': 'any', 'spacing': 'any', 'colors': 'any', } def go_field_type(json_field_type: str) -> str: json_field_type = json_field_type.partition('=')[0] q = json_field_types.get(json_field_type) if q: return q if json_field_type.startswith('choices.'): return 'string' if '.' in json_field_type: p, r = json_field_type.split('.', 1) p = {'list': '[]', 'dict': 'map[string]'}[p] return p + go_field_type(r) raise TypeError(f'Unknown JSON field type: {json_field_type}') class JSONField: def __init__(self, line: str, field_to_option_map: dict[str, str], option_map: dict[str, GoOption]) -> None: field_def = line.split(':', 1)[0] self.required = False self.field, self.field_type = field_def.split('/', 1) self.go_option_name = field_to_option_map.get(self.field, self.field) self.go_option_name = ''.join(x.capitalize() for x in self.go_option_name.split('_')) self.omitempty = True if fo := option_map.get(self.go_option_name): if fo.type in ('int', 'float') and float(fo.default or 0) != 0: self.omitempty = False self.field_type, self.special_parser = self.field_type.partition('=')[::2] if self.field.endswith('+'): self.required = True self.field = self.field[:-1] self.struct_field_name = self.field[0].upper() + self.field[1:] def go_declaration(self) -> str: omitempty = ',omitempty' if self.omitempty else '' return self.struct_field_name + ' ' + go_field_type(self.field_type) + f'`json:"{self.field}{omitempty}"`' def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) -> str: template = '\n' + template[len('//go:build exclude'):] af: list[str] = [] a = af.append af.extend(cmd.args.as_go_completion_code('ans')) od: list[str] = [] option_map: dict[str, GoOption] = {} for o in rc_command_options(name): option_map[o.go_var_name] = o a(o.as_option('ans')) if o.go_var_name in ('NoResponse', 'ResponseTimeout'): continue od.append(o.struct_declaration()) jd: list[str] = [] json_fields = [] field_types: dict[str, str] = {} for line in cmd.protocol_spec.splitlines(): line = line.strip() if ':' not in line: continue f = JSONField(line, cmd.field_to_option_map or {}, option_map) json_fields.append(f) field_types[f.field] = f.field_type jd.append(f.go_declaration()) jc: list[str] = [] handled_fields: set[str] = set() jc.extend(cmd.args.as_go_code(name, field_types, handled_fields)) unhandled = {} used_options = set() for field in json_fields: if field.go_option_name in option_map: o = option_map[field.go_option_name] used_options.add(field.go_option_name) optstring = f'options_{name}.{o.go_var_name}' if field.special_parser: optstring = f'{field.special_parser}({optstring})' if field.field_type == 'str': jc.append(f'payload.{field.struct_field_name} = escaped_string({optstring})') elif field.field_type == 'list.str': jc.append(f'payload.{field.struct_field_name} = escape_list_of_strings({optstring})') elif field.field_type == 'dict.str': jc.append(f'payload.{field.struct_field_name} = escape_dict_of_strings({optstring})') else: jc.append(f'payload.{field.struct_field_name} = {optstring}') elif field.field in handled_fields: pass else: unhandled[field.field] = field for x in tuple(unhandled): if x == 'match_window' and 'Match' in option_map and 'Match' not in used_options: used_options.add('Match') o = option_map['Match'] field = unhandled[x] if field.field_type == 'str': jc.append(f'payload.{field.struct_field_name} = escaped_string(options_{name}.{o.go_var_name})') else: jc.append(f'payload.{field.struct_field_name} = options_{name}.{o.go_var_name}') del unhandled[x] if unhandled: raise SystemExit(f'Cant map fields: {", ".join(unhandled)} for cmd: {name}') if name != 'send_text': unused_options = set(option_map) - used_options - {'NoResponse', 'ResponseTimeout'} if unused_options: raise SystemExit(f'Unused options: {", ".join(unused_options)} for command: {name}') argspec = cmd.args.spec if argspec: argspec = ' ' + argspec NO_RESPONSE = 'true' if cmd.disallow_responses else 'false' ans = replace( template, CMD_NAME=name, __FILE__=__file__, CLI_NAME=name.replace('_', '-'), SHORT_DESC=serialize_as_go_string(cmd.short_desc), LONG_DESC=serialize_as_go_string(cmd.desc.strip()), IS_ASYNC='true' if cmd.is_asynchronous else 'false', NO_RESPONSE_BASE=NO_RESPONSE, ADD_FLAGS_CODE='\n'.join(af), WAIT_TIMEOUT=str(cmd.response_timeout), OPTIONS_DECLARATION_CODE='\n'.join(od), JSON_DECLARATION_CODE='\n'.join(jd), JSON_INIT_CODE='\n'.join(jc), ARGSPEC=argspec, STRING_RESPONSE_IS_ERROR='true' if cmd.string_return_is_error else 'false', STREAM_WANTED='true' if cmd.reads_streaming_data else 'false', ) return ans # }}} # kittens {{{ @lru_cache def wrapped_kittens() -> tuple[str, ...]: with open('shell-integration/ssh/kitty') as f: for line in f: if line.startswith(' wrapped_kittens="'): val = line.strip().partition('"')[2][:-1] return tuple(sorted(filter(None, val.split()))) raise Exception('Failed to read wrapped kittens from kitty wrapper script') def generate_conf_parser(kitten: str, defn: Definition) -> None: with replace_if_needed(f'kittens/{kitten}/conf_generated.go'): print(f'package {kitten}') print(gen_go_code(defn)) def generate_extra_cli_parser(name: str, spec: str) -> None: print('import "kitty/tools/cli"') go_opts = tuple(go_options_for_seq(parse_option_spec(spec)[0])) print(f'type {name}_options struct ''{') for opt in go_opts: print(opt.struct_declaration()) print('}') print(f'func parse_{name}_args(args []string) (*{name}_options, []string, error) ''{') print(f'root := cli.Command{{Name: `{name}` }}') for opt in go_opts: print(opt.as_option('root')) print('cmd, err := root.ParseArgs(args)') print('if err != nil { return nil, nil, err }') print(f'var opts {name}_options') print('err = cmd.GetOptionValues(&opts)') print('if err != nil { return nil, nil, err }') print('return &opts, cmd.Args, nil') print('}') def kittens_needing_cli_parsers() -> Iterator[str]: for d in os.scandir('kittens'): if not d.is_dir(follow_symlinks=False): continue if os.path.exists(os.path.join(d.path, 'main.py')) and os.path.exists(os.path.join(d.path, 'main.go')): with open(os.path.join(d.path, 'main.py')) as f: raw = f.read() if 'options' in raw: yield d.name def kitten_clis() -> None: from kittens.runner import get_kitten_conf_docs, get_kitten_extra_cli_parsers for kitten in kittens_needing_cli_parsers(): defn = get_kitten_conf_docs(kitten) if defn is not None: generate_conf_parser(kitten, defn) ecp = get_kitten_extra_cli_parsers(kitten) if ecp: for name, spec in ecp.items(): with replace_if_needed(f'kittens/{kitten}/{name}_cli_generated.go'): print(f'package {kitten}') generate_extra_cli_parser(name, spec) with replace_if_needed(f'kittens/{kitten}/cli_generated.go'): od = [] kcd = kitten_cli_docs(kitten) has_underscore = '_' in kitten print(f'package {kitten}') print('import "kitty/tools/cli"') print('func create_cmd(root *cli.Command, run_func func(*cli.Command, *Options, []string)(int, error)) {') print('ans := root.AddSubCommand(&cli.Command{') print(f'Name: "{kitten}",') if kcd: print(f'ShortDescription: "{serialize_as_go_string(kcd["short_desc"])}",') if kcd['usage']: print(f'Usage: "[options] {serialize_as_go_string(kcd["usage"])}",') print(f'HelpText: "{serialize_as_go_string(kcd["help_text"])}",') print('Run: func(cmd *cli.Command, args []string) (int, error) {') print('opts := Options{}') print('err := cmd.GetOptionValues(&opts)') print('if err != nil { return 1, err }') print('return run_func(cmd, &opts, args)},') if has_underscore: print('Hidden: true,') print('})') gopts, ac = go_options_for_kitten(kitten) for opt in gopts: print(opt.as_option('ans')) od.append(opt.struct_declaration()) if ac is not None: print(''.join(ac.as_go_code('ans.ArgCompleter', ' = '))) if not kcd: print('specialize_command(ans)') if has_underscore: print("clone := root.AddClone(ans.Group, ans)") print('clone.Hidden = false') print(f'clone.Name = "{serialize_as_go_string(kitten.replace("_", "-"))}"') print('}') print('type Options struct {') print('\n'.join(od)) print('}') # }}} # Constants {{{ def generate_spinners() -> str: ans = ['package tui', 'import "time"', 'func NewSpinner(name string) *Spinner {', 'var ans *Spinner', 'switch name {'] a = ans.append for name, spinner in spinners.items(): a(f'case "{serialize_as_go_string(name)}":') a('ans = &Spinner{') a(f'Name: "{serialize_as_go_string(name)}",') a(f'interval: {spinner["interval"]},') frames = ', '.join(f'"{serialize_as_go_string(x)}"' for x in spinner['frames']) a(f'frames: []string{{{frames}}},') a('}') a('}') a('if ans != nil {') a('ans.interval *= time.Millisecond') a('ans.current_frame = -1') a('ans.last_change_at = time.Now().Add(-ans.interval)') a('}') a('return ans}') return '\n'.join(ans) def generate_color_names() -> str: selfg = "" if Options.selection_foreground is None else Options.selection_foreground.as_sharp selbg = "" if Options.selection_background is None else Options.selection_background.as_sharp cursor = "" if Options.cursor is None else Options.cursor.as_sharp return 'package style\n\nvar ColorNames = map[string]RGBA{' + '\n'.join( f'\t"{name}": RGBA{{ Red:{val.red}, Green:{val.green}, Blue:{val.blue} }},' for name, val in color_names.items() ) + '\n}' + '\n\nvar ColorTable = [256]uint32{' + ', '.join( f'{x}' for x in Options.color_table) + '}\n' + f''' var DefaultColors = struct {{ Foreground, Background, Cursor, SelectionFg, SelectionBg string }}{{ Foreground: "{Options.foreground.as_sharp}", Background: "{Options.background.as_sharp}", Cursor: "{cursor}", SelectionFg: "{selfg}", SelectionBg: "{selbg}", }} ''' def load_ref_map() -> dict[str, dict[str, str]]: with open('kitty/docs_ref_map_generated.h') as f: raw = f.read() raw = raw.split('{', 1)[1].split('}', 1)[0] data = json.loads(bytes(bytearray(json.loads(f'[{raw}]')))) return data # type: ignore def generate_constants() -> str: from kittens.hints.main import DEFAULT_REGEX from kittens.query_terminal.main import all_queries from kitty.colors import ThemeFile from kitty.config import option_names_for_completion from kitty.fast_data_types import FILE_TRANSFER_CODE from kitty.options.utils import allowed_shell_integration_values, url_style_map del sys.modules['kittens.hints.main'] del sys.modules['kittens.query_terminal.main'] ref_map = load_ref_map() with open('kitty/data-types.h') as dt: m = re.search(r'^#define IMAGE_PLACEHOLDER_CHAR (\S+)', dt.read(), flags=re.M) assert m is not None placeholder_char = int(m.group(1), 16) dp = ", ".join(map(lambda x: f'"{serialize_as_go_string(x)}"', kc.default_pager_for_help)) url_prefixes = ','.join(f'"{x}"' for x in Options.url_prefixes) option_names = '`' + '\n'.join(option_names_for_completion()) + '`' url_style = {v:k for k, v in url_style_map.items()}[Options.url_style] query_names = ', '.join(f'"{name}"' for name in all_queries) return f'''\ package kitty type VersionType struct {{ Major, Minor, Patch int }} const VersionString string = "{kc.str_version}" const WebsiteBaseURL string = "{kc.website_base_url}" const FileTransferCode int = {FILE_TRANSFER_CODE} const ImagePlaceholderChar rune = {placeholder_char} const SSHControlMasterTemplate = "{kc.ssh_control_master_template}" const RC_ENCRYPTION_PROTOCOL_VERSION string = "{kc.RC_ENCRYPTION_PROTOCOL_VERSION}" var VCSRevision string = "" var IsFrozenBuild string = "" var IsStandaloneBuild string = "" const HandleTermiosSignals = {Mode.HANDLE_TERMIOS_SIGNALS.value[0]} const HintsDefaultRegex = `{DEFAULT_REGEX}` const DefaultTermName = `{Options.term}` const DefaultUrlStyle = `{url_style}` const DefaultUrlColor = `{Options.url_color.as_sharp}` var Version VersionType = VersionType{{Major: {kc.version.major}, Minor: {kc.version.minor}, Patch: {kc.version.patch},}} var DefaultPager []string = []string{{ {dp} }} var FunctionalKeyNameAliases = map[string]string{serialize_go_dict(functional_key_name_aliases)} var CharacterKeyNameAliases = map[string]string{serialize_go_dict(character_key_name_aliases)} var ConfigModMap = map[string]uint16{serialize_go_dict(config_mod_map)} var RefMap = map[string]string{serialize_go_dict(ref_map['ref'])} var DocTitleMap = map[string]string{serialize_go_dict(ref_map['doc'])} var AllowedShellIntegrationValues = []string{{ {str(sorted(allowed_shell_integration_values))[1:-1].replace("'", '"')} }} var QueryNames = []string{{ {query_names} }} var CommentedOutDefaultConfig = "{serialize_as_go_string(commented_out_default_config())}" var KittyConfigDefaults = struct {{ Term, Shell_integration, Select_by_word_characters, Url_excluded_characters, Shell string Wheel_scroll_multiplier int Url_prefixes []string }}{{ Term: "{Options.term}", Shell_integration: "{' '.join(Options.shell_integration)}", Url_prefixes: []string{{ {url_prefixes} }}, Select_by_word_characters: `{Options.select_by_word_characters}`, Wheel_scroll_multiplier: {Options.wheel_scroll_multiplier}, Shell: "{Options.shell}", Url_excluded_characters: "{Options.url_excluded_characters}", }} const OptionNames = {option_names} const DarkThemeFileName = "{ThemeFile.dark.value}" const LightThemeFileName = "{ThemeFile.light.value}" const NoPreferenceThemeFileName = "{ThemeFile.no_preference.value}" ''' # }}} # Boilerplate {{{ @contextmanager def replace_if_needed(path: str, show_diff: bool = False) -> Iterator[io.StringIO]: buf = io.StringIO() origb = sys.stdout sys.stdout = buf try: yield buf finally: sys.stdout = origb orig = '' with suppress(FileNotFoundError), open(path) as f: orig = f.read() new = buf.getvalue() new = f'// Code generated by {os.path.basename(__file__)}; DO NOT EDIT.\n\n' + new if orig != new: changed.append(path) if show_diff: with open(path + '.new', 'w') as f: f.write(new) subprocess.run(['diff', '-Naurp', path, f.name], stdout=open('/dev/tty', 'w')) os.remove(f.name) with open(path, 'w') as f: f.write(new) @lru_cache(maxsize=256) def rc_command_options(name: str) -> tuple[GoOption, ...]: cmd = command_for_name(name) return tuple(go_options_for_seq(parse_option_spec(cmd.options_spec or '\n\n')[0])) def update_at_commands() -> None: with open('tools/cmd/at/template.go') as f: template = f.read() for name in all_command_names(): cmd = command_for_name(name) code = go_code_for_remote_command(name, cmd, template) dest = f'tools/cmd/at/cmd_{name}_generated.go' with replace_if_needed(dest) as f: f.write(code) struct_def = [] opt_def = [] for o in go_options_for_seq(parse_option_spec(global_options_spec())[0]): struct_def.append(o.struct_declaration()) opt_def.append(o.as_option(depth=1, group="Global options")) sdef = '\n'.join(struct_def) odef = '\n'.join(opt_def) code = f''' package at import "kitty/tools/cli" type rc_global_options struct {{ {sdef} }} var rc_global_opts rc_global_options func add_rc_global_opts(cmd *cli.Command) {{ {odef} }} ''' with replace_if_needed('tools/cmd/at/global_opts_generated.go') as f: f.write(code) def update_completion() -> None: with replace_if_needed('tools/cmd/completion/kitty_generated.go'): generate_completions_for_kitty() with replace_if_needed('tools/cmd/at/kitty_actions_generated.go'): print("package at") print("const KittyActionNames = `", end='') for grp, actions in get_all_actions().items(): for ac in actions: print(ac.name) print('`') with replace_if_needed('tools/cmd/edit_in_kitty/launch_generated.go'): print('package edit_in_kitty') print('import "kitty/tools/cli"') print('func AddCloneSafeOpts(cmd *cli.Command) {') completion_for_launch_wrappers('cmd') print(''.join(CompletionSpec.from_string('type:file mime:text/* group:"Text files"').as_go_code('cmd.ArgCompleter', ' = '))) print('}') def define_enum(package_name: str, type_name: str, items: str, underlying_type: str = 'uint') -> str: actions = [] for x in items.splitlines(): x = x.strip() if x: actions.append(x) ans = [f'package {package_name}', 'import "strconv"', f'type {type_name} {underlying_type}', 'const ('] stringer = [f'func (ac {type_name}) String() string ''{', 'switch(ac) {'] for i, ac in enumerate(actions): stringer.append(f'case {ac}: return "{ac}"') if i == 0: ac = ac + f' {type_name} = iota' ans.append(ac) ans.append(')') stringer.append('}\nreturn strconv.Itoa(int(ac)) }') return '\n'.join(ans + stringer) def generate_readline_actions() -> str: return define_enum('readline', 'Action', '''\ ActionNil ActionBackspace ActionDelete ActionMoveToStartOfLine ActionMoveToEndOfLine ActionMoveToStartOfDocument ActionMoveToEndOfDocument ActionMoveToEndOfWord ActionMoveToStartOfWord ActionCursorLeft ActionCursorRight ActionEndInput ActionAcceptInput ActionCursorUp ActionHistoryPreviousOrCursorUp ActionCursorDown ActionHistoryNextOrCursorDown ActionHistoryNext ActionHistoryPrevious ActionHistoryFirst ActionHistoryLast ActionHistoryIncrementalSearchBackwards ActionHistoryIncrementalSearchForwards ActionTerminateHistorySearchAndApply ActionTerminateHistorySearchAndRestore ActionClearScreen ActionAddText ActionAbortCurrentLine ActionStartKillActions ActionKillToEndOfLine ActionKillToStartOfLine ActionKillNextWord ActionKillPreviousWord ActionKillPreviousSpaceDelimitedWord ActionEndKillActions ActionYank ActionPopYank ActionNumericArgumentDigit0 ActionNumericArgumentDigit1 ActionNumericArgumentDigit2 ActionNumericArgumentDigit3 ActionNumericArgumentDigit4 ActionNumericArgumentDigit5 ActionNumericArgumentDigit6 ActionNumericArgumentDigit7 ActionNumericArgumentDigit8 ActionNumericArgumentDigit9 ActionNumericArgumentDigitMinus ActionCompleteForward ActionCompleteBackward ''') def generate_mimetypes() -> str: import mimetypes if not mimetypes.inited: mimetypes.init() ans = ['package utils', 'import "sync"', 'var only_once sync.Once', 'var builtin_types_map map[string]string', 'func set_builtins() {', 'builtin_types_map = map[string]string{',] for k, v in mimetypes.types_map.items(): ans.append(f' "{serialize_as_go_string(k)}": "{serialize_as_go_string(v)}",') ans.append('}}') return '\n'.join(ans) def generate_textual_mimetypes() -> str: ans = ['package utils', 'var KnownTextualMimes = map[string]bool{',] for k in text_mimes: ans.append(f' "{serialize_as_go_string(k)}": true,') ans.append('}') ans.append('var KnownExtensions = map[string]string{') for k, v in known_extensions.items(): ans.append(f' ".{serialize_as_go_string(k)}": "{serialize_as_go_string(v)}",') ans.append('}') return '\n'.join(ans) def write_compressed_data(data: bytes, d: BinaryIO) -> None: d.write(struct.pack(' None: num_names, num_of_words = map(int, next(src).split()) gob = io.BytesIO() gob.write(struct.pack(' None: files = { 'terminfo/kitty.terminfo', 'terminfo/x/' + Options.term, } for dirpath, dirnames, filenames in os.walk('shell-integration'): for f in filenames: path = os.path.join(dirpath, f) files.add(path.replace(os.sep, '/')) dest = 'tools/tui/shell_integration/data_generated.bin' def normalize(t: tarfile.TarInfo) -> tarfile.TarInfo: t.uid = t.gid = 0 t.uname = t.gname = '' t.mtime = 0 return t if newer(dest, *files): buf = io.BytesIO() with tarfile.open(fileobj=buf, mode='w') as tf: for f in sorted(files): tf.add(f, filter=normalize) with open(dest, 'wb') as d: write_compressed_data(buf.getvalue(), d) def start_simdgen() -> 'subprocess.Popen[bytes]': return subprocess.Popen(['go', 'run', 'generate.go'], cwd='tools/simdstring', stdout=subprocess.PIPE, stderr=subprocess.PIPE) def main(args: list[str]=sys.argv) -> None: simdgen_process = start_simdgen() with replace_if_needed('constants_generated.go') as f: f.write(generate_constants()) with replace_if_needed('tools/utils/style/color-names_generated.go') as f: f.write(generate_color_names()) with replace_if_needed('tools/tui/readline/actions_generated.go') as f: f.write(generate_readline_actions()) with replace_if_needed('tools/tui/spinners_generated.go') as f: f.write(generate_spinners()) with replace_if_needed('tools/utils/mimetypes_generated.go') as f: f.write(generate_mimetypes()) with replace_if_needed('tools/utils/mimetypes_textual_generated.go') as f: f.write(generate_textual_mimetypes()) if newer('tools/unicode_names/data_generated.bin', 'tools/unicode_names/names.txt'): with open('tools/unicode_names/data_generated.bin', 'wb') as dest, open('tools/unicode_names/names.txt') as src: generate_unicode_names(src, dest) generate_ssh_kitten_data() update_completion() update_at_commands() kitten_clis() stringify() make_bitfields() print(json.dumps(changed, indent=2)) stdout, stderr = simdgen_process.communicate() if simdgen_process.wait() != 0: print('Failed to generate SIMD ASM', file=sys.stderr) sys.stdout.buffer.write(stdout) sys.stderr.buffer.write(stderr) raise SystemExit(simdgen_process.returncode) if __name__ == '__main__': import runpy m = runpy.run_path(os.path.dirname(os.path.abspath(__file__))) m['main']([sys.executable, 'go-code']) # }}} kitty-0.41.1/gen/key_constants.py0000775000175000017510000004671314773370543016334 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import os import string import subprocess import sys from pprint import pformat from typing import Any, Union if __name__ == '__main__' and not __package__: import __main__ __main__.__package__ = 'gen' sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) functional_key_defs = '''# {{{ # kitty XKB macVK macU escape Escape 0x35 - enter Return 0x24 NSCarriageReturnCharacter tab Tab 0x30 NSTabCharacter backspace BackSpace 0x33 NSBackspaceCharacter insert Insert 0x72 Insert delete Delete 0x75 Delete left Left 0x7B LeftArrow right Right 0x7C RightArrow up Up 0x7E UpArrow down Down 0x7D DownArrow page_up Page_Up 0x74 PageUp page_down Page_Down 0x79 PageDown home Home 0x73 Home end End 0x77 End caps_lock Caps_Lock 0x39 - scroll_lock Scroll_Lock - ScrollLock num_lock Num_Lock 0x47 ClearLine print_screen Print - PrintScreen pause Pause - Pause menu Menu 0x6E Menu f1 F1 0x7A F1 f2 F2 0x78 F2 f3 F3 0x63 F3 f4 F4 0x76 F4 f5 F5 0x60 F5 f6 F6 0x61 F6 f7 F7 0x62 F7 f8 F8 0x64 F8 f9 F9 0x65 F9 f10 F10 0x6D F10 f11 F11 0x67 F11 f12 F12 0x6F F12 f13 F13 0x69 F13 f14 F14 0x6B F14 f15 F15 0x71 F15 f16 F16 0x6A F16 f17 F17 0x40 F17 f18 F18 0x4F F18 f19 F19 0x50 F19 f20 F20 0x5A F20 f21 F21 - F21 f22 F22 - F22 f23 F23 - F23 f24 F24 - F24 f25 F25 - F25 f26 F26 - F26 f27 F27 - F27 f28 F28 - F28 f29 F29 - F29 f30 F30 - F30 f31 F31 - F31 f32 F32 - F32 f33 F33 - F33 f34 F34 - F34 f35 F35 - F35 kp_0 KP_0 0x52 - kp_1 KP_1 0x53 - kp_2 KP_2 0x54 - kp_3 KP_3 0x55 - kp_4 KP_4 0x56 - kp_5 KP_5 0x57 - kp_6 KP_6 0x58 - kp_7 KP_7 0x59 - kp_8 KP_8 0x5B - kp_9 KP_9 0x5C - kp_decimal KP_Decimal 0x41 - kp_divide KP_Divide 0x4B - kp_multiply KP_Multiply 0x43 - kp_subtract KP_Subtract 0x4E - kp_add KP_Add 0x45 - kp_enter KP_Enter 0x4C NSEnterCharacter kp_equal KP_Equal 0x51 - kp_separator KP_Separator - - kp_left KP_Left - - kp_right KP_Right - - kp_up KP_Up - - kp_down KP_Down - - kp_page_up KP_Page_Up - - kp_page_down KP_Page_Down - - kp_home KP_Home - - kp_end KP_End - - kp_insert KP_Insert - - kp_delete KP_Delete - - kp_begin KP_Begin - - media_play XF86AudioPlay - - media_pause XF86AudioPause - - media_play_pause - - - media_reverse - - - media_stop XF86AudioStop - - media_fast_forward XF86AudioForward - - media_rewind XF86AudioRewind - - media_track_next XF86AudioNext - - media_track_previous XF86AudioPrev - - media_record XF86AudioRecord - - lower_volume XF86AudioLowerVolume - - raise_volume XF86AudioRaiseVolume - - mute_volume XF86AudioMute - - left_shift Shift_L 0x38 - left_control Control_L 0x3B - left_alt Alt_L 0x3A - left_super Super_L 0x37 - left_hyper Hyper_L - - left_meta Meta_L - - right_shift Shift_R 0x3C - right_control Control_R 0x3E - right_alt Alt_R 0x3D - right_super Super_R 0x36 - right_hyper Hyper_R - - right_meta Meta_R - - iso_level3_shift ISO_Level3_Shift - - iso_level5_shift ISO_Level5_Shift - - ''' # }}} shift_map = {x[0]: x[1] for x in '`~ 1! 2@ 3# 4$ 5% 6^ 7& 8* 9( 0) -_ =+ [{ ]} \\| ;: \'" ,< .> /?'.split()} shift_map.update({x: x.upper() for x in string.ascii_lowercase}) functional_encoding_overrides = { 'insert': 2, 'delete': 3, 'page_up': 5, 'page_down': 6, 'home': 7, 'end': 8, 'tab': 9, 'f1': 11, 'f2': 12, 'f3': 13, 'enter': 13, 'f4': 14, 'f5': 15, 'f6': 17, 'f7': 18, 'f8': 19, 'f9': 20, 'f10': 21, 'f11': 23, 'f12': 24, 'escape': 27, 'backspace': 127 } different_trailer_functionals = { 'up': 'A', 'down': 'B', 'right': 'C', 'left': 'D', 'kp_begin': 'E', 'end': 'F', 'home': 'H', 'f1': 'P', 'f2': 'Q', 'f3': '~', 'f4': 'S', 'enter': 'u', 'tab': 'u', 'backspace': 'u', 'escape': 'u' } macos_ansi_key_codes = { # {{{ 0x1D: ord('0'), 0x12: ord('1'), 0x13: ord('2'), 0x14: ord('3'), 0x15: ord('4'), 0x17: ord('5'), 0x16: ord('6'), 0x1A: ord('7'), 0x1C: ord('8'), 0x19: ord('9'), 0x00: ord('a'), 0x0B: ord('b'), 0x08: ord('c'), 0x02: ord('d'), 0x0E: ord('e'), 0x03: ord('f'), 0x05: ord('g'), 0x04: ord('h'), 0x22: ord('i'), 0x26: ord('j'), 0x28: ord('k'), 0x25: ord('l'), 0x2E: ord('m'), 0x2D: ord('n'), 0x1F: ord('o'), 0x23: ord('p'), 0x0C: ord('q'), 0x0F: ord('r'), 0x01: ord('s'), 0x11: ord('t'), 0x20: ord('u'), 0x09: ord('v'), 0x0D: ord('w'), 0x07: ord('x'), 0x10: ord('y'), 0x06: ord('z'), 0x27: ord('\''), 0x2A: ord('\\'), 0x2B: ord(','), 0x18: ord('='), 0x32: ord('`'), 0x21: ord('['), 0x1B: ord('-'), 0x2F: ord('.'), 0x1E: ord(']'), 0x29: ord(';'), 0x2C: ord('/'), 0x31: ord(' '), } # }}} functional_key_names: list[str] = [] name_to_code: dict[str, int] = {} name_to_xkb: dict[str, str] = {} name_to_vk: dict[str, int] = {} name_to_macu: dict[str, str] = {} start_code = 0xe000 for line in functional_key_defs.splitlines(): line = line.strip() if not line or line.startswith('#'): continue parts = line.split() name = parts[0] functional_key_names.append(name) name_to_code[name] = len(name_to_code) + start_code if parts[1] != '-': name_to_xkb[name] = parts[1] if parts[2] != '-': name_to_vk[name] = int(parts[2], 16) if parts[3] != '-': val = parts[3] if not val.startswith('NS'): val = f'NS{val}FunctionKey' name_to_macu[name] = val last_code = start_code + len(functional_key_names) - 1 ctrl_mapping = { ' ': 0, '@': 0, 'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7, 'h': 8, 'i': 9, 'j': 10, 'k': 11, 'l': 12, 'm': 13, 'n': 14, 'o': 15, 'p': 16, 'q': 17, 'r': 18, 's': 19, 't': 20, 'u': 21, 'v': 22, 'w': 23, 'x': 24, 'y': 25, 'z': 26, '[': 27, '\\': 28, ']': 29, '^': 30, '~': 30, '/': 31, '_': 31, '?': 127, '0': 48, '1': 49, '2': 0, '3': 27, '4': 28, '5': 29, '6': 30, '7': 31, '8': 127, '9': 57 } def patch_file(path: str, what: str, text: str, start_marker: str = '/* ', end_marker: str = ' */') -> None: simple_start_q = f'{start_marker}start {what}{end_marker}' start_q = f'{start_marker}start {what} (auto generated by gen-key-constants.py do not edit){end_marker}' end_q = f'{start_marker}end {what}{end_marker}' with open(path, 'r+') as f: raw = f.read() try: start = raw.index(start_q) except ValueError: try: start = raw.index(simple_start_q) except ValueError: raise SystemExit(f'Failed to find "{simple_start_q}" in {path}') try: end = raw.index(end_q) except ValueError: raise SystemExit(f'Failed to find "{end_q}" in {path}') raw = f'{raw[:start]}{start_q}\n{text}\n{raw[end:]}' f.seek(0) f.truncate(0) f.write(raw) if path.endswith('.go'): subprocess.check_call(['go', 'fmt', path]) def serialize_dict(x: dict[Any, Any]) -> str: return pformat(x, indent=4).replace('{', '{\n ', 1) def serialize_go_dict(x: Union[dict[str, int], dict[int, str], dict[int, int]]) -> str: ans = [] def s(x: Union[int, str]) -> str: if isinstance(x, int): return str(x) return f'"{x}"' for k, v in x.items(): ans.append(f'{s(k)}: {s(v)}') return '{' + ', '.join(ans) + '}' def generate_glfw_header() -> None: lines = [ 'typedef enum {', f' GLFW_FKEY_FIRST = 0x{start_code:x}u,', ] klines, pyi, names, knames = [], [], [], [] for name, code in name_to_code.items(): lines.append(f' GLFW_FKEY_{name.upper()} = 0x{code:x}u,') klines.append(f' ADDC(GLFW_FKEY_{name.upper()});') pyi.append(f'GLFW_FKEY_{name.upper()}: int') names.append(f' case GLFW_FKEY_{name.upper()}: return "{name.upper()}";') knames.append(f' case GLFW_FKEY_{name.upper()}: return PyUnicode_FromString("{name}");') lines.append(f' GLFW_FKEY_LAST = 0x{last_code:x}u') lines.append('} GLFWFunctionKey;') patch_file('glfw/glfw3.h', 'functional key names', '\n'.join(lines)) patch_file('kitty/glfw.c', 'glfw functional keys', '\n'.join(klines)) patch_file('kitty/fast_data_types.pyi', 'glfw functional keys', '\n'.join(pyi), start_marker='# ', end_marker='') patch_file('glfw/input.c', 'functional key names', '\n'.join(names)) patch_file('kitty/glfw.c', 'glfw functional key names', '\n'.join(knames)) def generate_xkb_mapping() -> None: lines, rlines = [], [] for name, xkb in name_to_xkb.items(): lines.append(f' case XKB_KEY_{xkb}: return GLFW_FKEY_{name.upper()};') rlines.append(f' case GLFW_FKEY_{name.upper()}: return XKB_KEY_{xkb};') patch_file('glfw/xkb_glfw.c', 'xkb to glfw', '\n'.join(lines)) patch_file('glfw/xkb_glfw.c', 'glfw to xkb', '\n'.join(rlines)) def generate_functional_table() -> None: lines = [ '', '.. csv-table:: Functional key codes', ' :header: "Name", "CSI", "Name", "CSI"', '' ] line_items = [] enc_lines = [] tilde_trailers = set() for name, code in name_to_code.items(): if name in functional_encoding_overrides or name in different_trailer_functionals: trailer = different_trailer_functionals.get(name, '~') if trailer == '~': tilde_trailers.add(code) code = oc = functional_encoding_overrides.get(name, code) code = code if trailer in '~u' else 1 enc_lines.append((' ' * 8) + f"case GLFW_FKEY_{name.upper()}: S({code}, '{trailer}');") if code == 1 and name not in ('up', 'down', 'left', 'right'): trailer += f' or {oc} ~' else: trailer = 'u' line_items.append(name.upper()) line_items.append(f'``{code}\xa0{trailer}``') for li in chunks(line_items, 4): lines.append(' ' + ', '.join(f'"{x}"' for x in li)) lines.append('') patch_file('docs/keyboard-protocol.rst', 'functional key table', '\n'.join(lines), start_marker='.. ', end_marker='') patch_file('kitty/key_encoding.c', 'special numbers', '\n'.join(enc_lines)) code_to_name = {v: k.upper() for k, v in name_to_code.items()} csi_map = {v: name_to_code[k] for k, v in functional_encoding_overrides.items()} letter_trailer_codes: dict[str, int] = { v: functional_encoding_overrides.get(k, name_to_code.get(k, 0)) for k, v in different_trailer_functionals.items() if v in 'ABCDEHFPQRSZ'} text = f'functional_key_number_to_name_map = {serialize_dict(code_to_name)}' text += f'\ncsi_number_to_functional_number_map = {serialize_dict(csi_map)}' text += f'\nletter_trailer_to_csi_number_map = {letter_trailer_codes!r}' text += f'\ntilde_trailers = {tilde_trailers!r}' patch_file('kitty/key_encoding.py', 'csi mapping', text, start_marker='# ', end_marker='') text = f'var functional_key_number_to_name_map = map[int]string{serialize_go_dict(code_to_name)}\n' text += f'\nvar csi_number_to_functional_number_map = map[int]int{serialize_go_dict(csi_map)}\n' text += f'\nvar letter_trailer_to_csi_number_map = map[string]int{serialize_go_dict(letter_trailer_codes)}\n' tt = ', '.join(f'{x}: true' for x in tilde_trailers) text += '\nvar tilde_trailers = map[int]bool{' + f'{tt}' + '}\n' patch_file('tools/tui/loop/key-encoding.go', 'csi mapping', text, start_marker='// ', end_marker='') def generate_legacy_text_key_maps() -> None: tests = [] tp = ' ' * 8 shift, alt, ctrl = 1, 2, 4 def simple(c: str) -> None: shifted = shift_map.get(c, c) ctrled = chr(ctrl_mapping.get(c, ord(c))) call = f'enc(ord({c!r}), shifted_key=ord({shifted!r})' for m in range(16): if m == 0: tests.append(f'{tp}ae({call}), {c!r})') elif m == shift: tests.append(f'{tp}ae({call}, mods=shift), {shifted!r})') elif m == alt: tests.append(f'{tp}ae({call}, mods=alt), "\\x1b" + {c!r})') elif m == ctrl: tests.append(f'{tp}ae({call}, mods=ctrl), {ctrled!r})') elif m == shift | alt: tests.append(f'{tp}ae({call}, mods=shift | alt), "\\x1b" + {shifted!r})') elif m == ctrl | alt: tests.append(f'{tp}ae({call}, mods=ctrl | alt), "\\x1b" + {ctrled!r})') for k in shift_map: simple(k) patch_file('kitty_tests/keys.py', 'legacy letter tests', '\n'.join(tests), start_marker='# ', end_marker='') def chunks(lst: list[Any], n: int) -> Any: """Yield successive n-sized chunks from lst.""" for i in range(0, len(lst), n): yield lst[i:i + n] def generate_ctrl_mapping() -> None: lines = [ '.. csv-table:: Emitted bytes when :kbd:`ctrl` is held down and a key is pressed', ' :header: "Key", "Byte", "Key", "Byte", "Key", "Byte"', '' ] items = [] mi = [] for k in sorted(ctrl_mapping): prefix = '\\' if k == '\\' else ('SPC' if k == ' ' else '') items.append(prefix + k) val = str(ctrl_mapping[k]) items.append(val) if k in "\\'": k = f'\\{k}' mi.append(f" case '{k}': return {val};") for line_items in chunks(items, 6): lines.append(' ' + ', '.join(f'"{x}"' for x in line_items)) lines.append('') patch_file('docs/keyboard-protocol.rst', 'ctrl mapping', '\n'.join(lines), start_marker='.. ', end_marker='') patch_file('kitty/key_encoding.c', 'ctrl mapping', '\n'.join(mi)) def generate_macos_mapping() -> None: lines = [] for k in sorted(macos_ansi_key_codes): v = macos_ansi_key_codes[k] lines.append(f' case 0x{k:x}: return 0x{v:x};') patch_file('glfw/cocoa_window.m', 'vk to unicode', '\n'.join(lines)) lines = [] for name, vk in name_to_vk.items(): lines.append(f' case 0x{vk:x}: return GLFW_FKEY_{name.upper()};') patch_file('glfw/cocoa_window.m', 'vk to functional', '\n'.join(lines)) lines = [] for name, mac in name_to_macu.items(): lines.append(f' case {mac}: return GLFW_FKEY_{name.upper()};') patch_file('glfw/cocoa_window.m', 'macu to functional', '\n'.join(lines)) lines = [] for name, mac in name_to_macu.items(): lines.append(f' case GLFW_FKEY_{name.upper()}: return {mac};') patch_file('glfw/cocoa_window.m', 'functional to macu', '\n'.join(lines)) def main(args: list[str]=sys.argv) -> None: generate_glfw_header() generate_xkb_mapping() generate_functional_table() generate_legacy_text_key_maps() generate_ctrl_mapping() generate_macos_mapping() if __name__ == '__main__': import runpy m = runpy.run_path(os.path.dirname(os.path.abspath(__file__))) m['main']([sys.executable, 'key-constants']) kitty-0.41.1/gen/nerd-fonts-glyphs.txt0000664000175000017510000077607114773370543017226 0ustar nileshnileshea60 cod add ea61 cod lightbulb ea62 cod repo ea63 cod repo forked ea64 cod git pull request ea65 cod record keys ea66 cod tag ea67 cod person ea68 cod source control ea69 cod mirror ea6a cod star empty ea6b cod comment ea6c cod warning ea6d cod search ea6e cod sign out ea6f cod sign in ea70 cod eye ea71 cod circle filled ea72 cod primitive square ea73 cod edit ea74 cod info ea75 cod lock ea76 cod close ea77 cod sync ea78 cod desktop download ea79 cod beaker ea7a cod vm ea7b cod file ea7c cod ellipsis ea7d cod reply ea7e cod organization ea7f cod new file ea80 cod new folder ea81 cod trash ea82 cod history ea83 cod folder ea84 cod github ea85 cod terminal ea86 cod symbol event ea87 cod error ea88 cod symbol variable ea8a cod symbol array ea8b cod symbol namespace ea8c cod symbol method ea8f cod symbol boolean ea90 cod symbol numeric ea91 cod symbol structure ea92 cod symbol parameter ea93 cod symbol key ea94 cod go to file ea95 cod symbol enum ea96 cod symbol ruler ea97 cod activate breakpoints ea98 cod archive ea99 cod arrow both ea9a cod arrow down ea9b cod arrow left ea9c cod arrow right ea9d cod arrow small down ea9e cod arrow small left ea9f cod arrow small right eaa0 cod arrow small up eaa1 cod arrow up eaa2 cod bell eaa3 cod bold eaa4 cod book eaa5 cod bookmark eaa6 cod debug breakpoint conditional unverified eaa7 cod debug breakpoint conditional eaa8 cod debug breakpoint data unverified eaa9 cod debug breakpoint data eaaa cod debug breakpoint log unverified eaab cod debug breakpoint log eaac cod briefcase eaad cod broadcast eaae cod browser eaaf cod bug eab0 cod calendar eab1 cod case sensitive eab2 cod check eab3 cod checklist eab4 cod chevron down eab5 cod chevron left eab6 cod chevron right eab7 cod chevron up eab8 cod chrome close eab9 cod chrome maximize eaba cod chrome minimize eabb cod chrome restore eabc cod circle eabd cod circle slash eabe cod circuit board eabf cod clear all eac0 cod clippy eac1 cod close all eac2 cod cloud download eac3 cod cloud upload eac4 cod code eac5 cod collapse all eac6 cod color mode eac7 cod comment discussion eac9 cod credit card eacc cod dash eacd cod dashboard eace cod database eacf cod debug continue ead0 cod debug disconnect ead1 cod debug pause ead2 cod debug restart ead3 cod debug start ead4 cod debug step into ead5 cod debug step out ead6 cod debug step over ead7 cod debug stop ead8 cod debug ead9 cod device camera video eada cod device camera eadb cod device mobile eadc cod diff added eadd cod diff ignored eade cod diff modified eadf cod diff removed eae0 cod diff renamed eae1 cod diff eae2 cod discard eae3 cod editor layout eae4 cod empty window eae5 cod exclude eae6 cod extensions eae7 cod eye closed eae8 cod file binary eae9 cod file code eaea cod file media eaeb cod file pdf eaec cod file submodule eaed cod file symlink directory eaee cod file symlink file eaef cod file zip eaf0 cod files eaf1 cod filter eaf2 cod flame eaf3 cod fold down eaf4 cod fold up eaf5 cod fold eaf6 cod folder active eaf7 cod folder opened eaf8 cod gear eaf9 cod gift eafa cod gist secret eafc cod git commit eafd cod git compare eafe cod git merge eaff cod github action eb00 cod github alt eb01 cod globe eb02 cod grabber eb03 cod graph eb04 cod gripper eb05 cod heart eb06 cod home eb07 cod horizontal rule eb08 cod hubot eb09 cod inbox eb0b cod issue reopened eb0c cod issues eb0d cod italic eb0e cod jersey eb0f cod json eb10 cod kebab vertical eb11 cod key eb12 cod law eb13 cod lightbulb autofix eb14 cod link external eb15 cod link eb16 cod list ordered eb17 cod list unordered eb18 cod live share eb19 cod loading eb1a cod location eb1b cod mail read eb1c cod mail eb1d cod markdown eb1e cod megaphone eb1f cod mention eb20 cod milestone eb21 cod mortar board eb22 cod move eb23 cod multiple windows eb24 cod mute eb25 cod no newline eb26 cod note eb27 cod octoface eb28 cod open preview eb29 cod package eb2a cod paintcan eb2b cod pin eb2c cod play eb2d cod plug eb2e cod preserve case eb2f cod preview eb30 cod project eb31 cod pulse eb32 cod question eb33 cod quote eb34 cod radio tower eb35 cod reactions eb36 cod references eb37 cod refresh eb38 cod regex eb39 cod remote explorer eb3a cod remote eb3b cod remove eb3c cod replace all eb3d cod replace eb3e cod repo clone eb3f cod repo force push eb40 cod repo pull eb41 cod repo push eb42 cod report eb43 cod request changes eb44 cod rocket eb45 cod root folder opened eb46 cod root folder eb47 cod rss eb48 cod ruby eb49 cod save all eb4a cod save as eb4b cod save eb4c cod screen full eb4d cod screen normal eb4e cod search stop eb50 cod server eb51 cod settings gear eb52 cod settings eb53 cod shield eb54 cod smiley eb55 cod sort precedence eb56 cod split horizontal eb57 cod split vertical eb58 cod squirrel eb59 cod star full eb5a cod star half eb5b cod symbol class eb5c cod symbol color eb5d cod symbol constant eb5e cod symbol enum member eb5f cod symbol field eb60 cod symbol file eb61 cod symbol interface eb62 cod symbol keyword eb63 cod symbol misc eb64 cod symbol operator eb65 cod symbol property eb66 cod symbol snippet eb67 cod tasklist eb68 cod telescope eb69 cod text size eb6a cod three bars eb6b cod thumbsdown eb6c cod thumbsup eb6d cod tools eb6e cod triangle down eb6f cod triangle left eb70 cod triangle right eb71 cod triangle up eb72 cod twitter eb73 cod unfold eb74 cod unlock eb75 cod unmute eb76 cod unverified eb77 cod verified eb78 cod versions eb79 cod vm active eb7a cod vm outline eb7b cod vm running eb7c cod watch eb7d cod whitespace eb7e cod whole word eb7f cod window eb80 cod word wrap eb81 cod zoom in eb82 cod zoom out eb83 cod list filter eb84 cod list flat eb85 cod list selection eb86 cod list tree eb87 cod debug breakpoint function unverified eb88 cod debug breakpoint function eb89 cod debug stackframe active eb8a cod circle small filled eb8b cod debug stackframe eb8c cod debug breakpoint unsupported eb8d cod symbol string eb8e cod debug reverse continue eb8f cod debug step back eb90 cod debug restart frame eb91 cod debug alt eb92 cod call incoming eb93 cod call outgoing eb94 cod menu eb95 cod expand all eb96 cod feedback eb97 cod group by ref type eb98 cod ungroup by ref type eb99 cod account eb9a cod bell dot eb9b cod debug console eb9c cod library eb9d cod output eb9e cod run all eb9f cod sync ignored eba0 cod pinned eba1 cod github inverted eba2 cod server process eba3 cod server environment eba4 cod pass eba5 cod stop circle eba6 cod play circle eba7 cod record eba8 cod debug alt small eba9 cod vm connect ebaa cod cloud ebab cod merge ebac cod export ebad cod graph left ebae cod magnet ebaf cod notebook ebb0 cod redo ebb1 cod check all ebb2 cod pinned dirty ebb3 cod pass filled ebb4 cod circle large filled ebb5 cod circle large ebb6 cod combine ebb7 cod table ebb8 cod variable group ebb9 cod type hierarchy ebba cod type hierarchy sub ebbb cod type hierarchy super ebbc cod git pull request create ebbd cod run above ebbe cod run below ebbf cod notebook template ebc0 cod debug rerun ebc1 cod workspace trusted ebc2 cod workspace untrusted ebc3 cod workspace unknown ebc4 cod terminal cmd ebc5 cod terminal debian ebc6 cod terminal linux ebc7 cod terminal powershell ebc8 cod terminal tmux ebc9 cod terminal ubuntu ebca cod terminal bash ebcb cod arrow swap ebcc cod copy ebcd cod person add ebce cod filter filled ebcf cod wand ebd0 cod debug line by line ebd1 cod inspect ebd2 cod layers ebd3 cod layers dot ebd4 cod layers active ebd5 cod compass ebd6 cod compass dot ebd7 cod compass active ebd8 cod azure ebd9 cod issue draft ebda cod git pull request closed ebdb cod git pull request draft ebdc cod debug all ebdd cod debug coverage ebde cod run errors ebdf cod folder library ebe0 cod debug continue small ebe1 cod beaker stop ebe2 cod graph line ebe3 cod graph scatter ebe4 cod pie chart ebe5 cod bracket dot ebe6 cod bracket error ebe7 cod lock small ebe8 cod azure devops ebe9 cod verified filled ebea cod newline ebeb cod layout e5fa custom folder npm e5fb custom folder git branch e5fc custom folder config e5fd custom folder github e5fe custom folder open e5ff custom folder e602 custom play arrow e612 custom default text e617 custom home e61d custom cpp e61e custom c e626 custom go e629 custom msdos e62a custom windows e62b custom vim e62c custom elm e62d custom elixir e62e custom electron e62f custom crystal e630 custom purescript e631 custom puppet e632 custom emacs e633 custom orgmode e634 custom kotlin e700 dev bing small e701 dev css tricks e702 dev git e703 dev bitbucket e704 dev mysql e705 dev streamline e706 dev database e707 dev dropbox e708 dev github alt e709 dev github badge e70a dev github e70b dev wordpress e70c dev visualstudio e70d dev jekyll small e70e dev android e70f dev windows e710 dev stackoverflow e711 dev apple e712 dev linux e713 dev appstore e714 dev ghost small e715 dev yahoo e716 dev codepen e717 dev github full e718 dev nodejs small e719 dev nodejs e71a dev hackernews e71b dev ember e71c dev dojo e71d dev django e71e dev npm e71f dev ghost e720 dev modernizr e721 dev unity small e722 dev rasberry pi e723 dev blackberry e724 dev go e725 dev git branch e726 dev git pull request e727 dev git merge e728 dev git compare e729 dev git commit e72a dev cssdeck e72b dev yahoo small e72c dev techcrunch e72d dev smashing magazine e72e dev netmagazine e72f dev codrops e730 dev phonegap e731 dev google drive e732 dev html5 multimedia e733 dev html5 device access e734 dev html5 connectivity e735 dev html5 3d effects e736 dev html5 e737 dev scala e738 dev java e739 dev ruby e73a dev ubuntu e73b dev ruby on rails e73c dev python e73d dev php e73e dev markdown e73f dev laravel e740 dev magento e741 dev joomla e742 dev drupal e743 dev chrome e744 dev ie e745 dev firefox e746 dev opera e747 dev bootstrap e748 dev safari e749 dev css3 e74a dev css3 full e74b dev sass e74c dev grunt e74d dev bower e74e dev javascript e74f dev javascript shield e750 dev jquery e751 dev coffeescript e752 dev backbone e753 dev angular e754 dev jquery ui e755 dev swift e756 dev symfony e757 dev symfony badge e758 dev less e759 dev stylus e75a dev trello e75b dev atlassian e75c dev jira e75d dev envato e75e dev snap svg e75f dev raphael e760 dev chart e761 dev compass e762 dev onedrive e763 dev gulp e764 dev atom e765 dev cisco e766 dev nancy e767 dev jenkins e768 dev clojure e769 dev perl e76a dev clojure alt e76b dev celluloid e76c dev w3c e76d dev redis e76e dev postgresql e76f dev webplatform e770 dev requirejs e771 dev opensource e772 dev typo3 e773 dev uikit e774 dev doctrine e775 dev groovy e776 dev nginx e777 dev haskell e778 dev zend e779 dev gnu e77a dev yeoman e77b dev heroku e77c dev msql server e77d dev debian e77e dev travis e77f dev dotnet e780 dev codeigniter e781 dev javascript badge e782 dev yii e783 dev composer e784 dev krakenjs badge e785 dev krakenjs e786 dev mozilla e787 dev firebase e788 dev sizzlejs e789 dev creativecommons e78a dev creativecommons badge e78b dev mitlicence e78c dev senchatouch e78d dev bugsense e78e dev extjs e78f dev mootools badge e790 dev mootools e791 dev ruby rough e792 dev komodo e793 dev coda e794 dev bintray e795 dev terminal e796 dev code e797 dev responsive e798 dev dart e799 dev aptana e79a dev mailchimp e79b dev netbeans e79c dev dreamweaver e79d dev brackets e79e dev eclipse e79f dev cloud9 e7a0 dev scrum e7a1 dev prolog e7a2 dev terminal badge e7a3 dev code badge e7a4 dev mongodb e7a5 dev meteor e7a6 dev meteorfull e7a7 dev fsharp e7a8 dev rust e7a9 dev ionic e7aa dev sublime e7ab dev appcelerator e7ac dev asterisk e7ad dev aws e7ae dev digital ocean e7af dev dlang e7b0 dev docker e7b1 dev erlang e7b2 dev google cloud platform e7b3 dev grails e7b4 dev illustrator e7b5 dev intellij e7b6 dev materializecss e7b7 dev openshift e7b8 dev photoshop e7b9 dev rackspace e7ba dev react e7bb dev redhat e7bc dev scriptcs e7c4 dev sqllite e7c5 dev vim f000 fa glass f001 fa music f002 fa search f003 fa envelope o f004 fa heart f005 fa star f006 fa star o f007 fa user f008 fa film f009 fa th large f00a fa th f00b fa th list f00c fa check f00d fa close remove times f00e fa search plus f010 fa search minus f011 fa power off f012 fa signal f013 fa cog gear f014 fa trash o f015 fa home f016 fa file o f017 fa clock o f018 fa road f019 fa download f01a fa arrow circle o down f01b fa arrow circle o up f01c fa inbox f01d fa play circle o f01e fa repeat rotate right f021 fa refresh f022 fa list alt f023 fa lock f024 fa flag f025 fa headphones f026 fa volume off f027 fa volume down f028 fa volume up f029 fa qrcode f02a fa barcode f02b fa tag f02c fa tags f02d fa book f02e fa bookmark f02f fa print f030 fa camera f031 fa font f032 fa bold f033 fa italic f034 fa text height f035 fa text width f036 fa align left f037 fa align center f038 fa align right f039 fa align justify f03a fa list f03b fa dedent outdent f03c fa indent f03d fa video camera f03e fa picture o image photo f040 fa pencil f041 fa map marker f042 fa adjust f043 fa tint f044 fa pencil square o edit f045 fa share square o f046 fa check square o f047 fa arrows f048 fa step backward f049 fa fast backward f04a fa backward f04b fa play f04c fa pause f04d fa stop f04e fa forward f050 fa fast forward f051 fa step forward f052 fa eject f053 fa chevron left f054 fa chevron right f055 fa plus circle f056 fa minus circle f057 fa times circle f058 fa check circle f059 fa question circle f05a fa info circle f05b fa crosshairs f05c fa times circle o f05d fa check circle o f05e fa ban f060 fa arrow left f061 fa arrow right f062 fa arrow up f063 fa arrow down f064 fa share mail forward f065 fa expand f066 fa compress f067 fa plus f068 fa minus f069 fa asterisk f06a fa exclamation circle f06b fa gift f06c fa leaf f06d fa fire f06e fa eye f070 fa eye slash f071 fa exclamation triangle warning f072 fa plane f073 fa calendar f074 fa random f075 fa comment f076 fa magnet f077 fa chevron up f078 fa chevron down f079 fa retweet f07a fa shopping cart f07b fa folder f07c fa folder open f07d fa arrows v f07e fa arrows h f080 fa bar chart f081 fa twitter square f082 fa facebook square f083 fa camera retro f084 fa key f085 fa cogs gears f086 fa comments f087 fa thumbs o up f088 fa thumbs o down f089 fa star half f08a fa heart o f08b fa sign out f08c fa linkedin square f08d fa thumb tack f08e fa external link f090 fa sign in f091 fa trophy f092 fa github square f093 fa upload f094 fa lemon o f095 fa phone f096 fa square o f097 fa bookmark o f098 fa phone square f099 fa twitter f09a fa facebook f09b fa github f09c fa unlock f09d fa credit card f09e fa rss feed f0a0 fa hdd o f0a1 fa bullhorn f0a2 fa bell o f0a3 fa certificate f0a4 fa hand o right f0a5 fa hand o left f0a6 fa hand o up f0a7 fa hand o down f0a8 fa arrow circle left f0a9 fa arrow circle right f0aa fa arrow circle up f0ab fa arrow circle down f0ac fa globe f0ad fa wrench f0ae fa tasks f0b0 fa filter f0b1 fa briefcase f0b2 fa arrows alt f0c0 fa users group f0c1 fa link chain f0c2 fa cloud f0c3 fa flask f0c4 fa scissors cut f0c5 fa files o copy f0c6 fa paperclip f0c7 fa floppy o save f0c8 fa square f0c9 fa bars navicon reorder f0ca fa list ul f0cb fa list ol f0cc fa strikethrough f0cd fa underline f0ce fa table f0d0 fa magic f0d1 fa truck f0d2 fa pinterest f0d3 fa pinterest square f0d4 fa google plus square f0d5 fa google plus f0d6 fa money f0d7 fa caret down f0d8 fa caret up f0d9 fa caret left f0da fa caret right f0db fa columns f0dc fa sort unsorted f0dd fa sort desc down f0de fa sort asc up f0e0 fa envelope f0e1 fa linkedin f0e2 fa undo rotate left f0e3 fa gavel legal f0e4 fa tachometer dashboard f0e5 fa comment o f0e6 fa comments o f0e7 fa bolt flash f0e8 fa sitemap f0e9 fa umbrella f0ea fa clipboard paste f0eb fa lightbulb o f0ec fa exchange f0ed fa cloud download f0ee fa cloud upload f0f0 fa user md f0f1 fa stethoscope f0f2 fa suitcase f0f3 fa bell f0f4 fa coffee f0f5 fa cutlery f0f6 fa file text o f0f7 fa building o f0f8 fa hospital o f0f9 fa ambulance f0fa fa medkit f0fb fa fighter jet f0fc fa beer f0fd fa h square f0fe fa plus square f100 fa angle double left f101 fa angle double right f102 fa angle double up f103 fa angle double down f104 fa angle left f105 fa angle right f106 fa angle up f107 fa angle down f108 fa desktop f109 fa laptop f10a fa tablet f10b fa mobile phone f10c fa circle o f10d fa quote left f10e fa quote right f110 fa spinner f111 fa circle f112 fa mail reply f113 fa github alt f114 fa folder o f115 fa folder open o f118 fa smile o f119 fa frown o f11a fa meh o f11b fa gamepad f11c fa keyboard o f11d fa flag o f11e fa flag checkered f120 fa terminal f121 fa code f122 fa mail reply all f123 fa star half o f124 fa location arrow f125 fa crop f126 fa code fork f127 fa chain broken unlink f128 fa question f129 fa info f12a fa exclamation f12b fa superscript f12c fa subscript f12d fa eraser f12e fa puzzle piece f130 fa microphone f131 fa microphone slash f132 fa shield f133 fa calendar o f134 fa fire extinguisher f135 fa rocket f136 fa maxcdn f137 fa chevron circle left f138 fa chevron circle right f139 fa chevron circle up f13a fa chevron circle down f13b fa html5 f13c fa css3 f13d fa anchor f13e fa unlock alt f140 fa bullseye f141 fa ellipsis h f142 fa ellipsis v f143 fa rss square f144 fa play circle f145 fa ticket f146 fa minus square f147 fa minus square o f148 fa level up f149 fa level down f14a fa check square f14b fa pencil square f14c fa external link square f14d fa share square f14e fa compass f150 fa caret square o down f151 fa caret square o up f152 fa caret square o right f153 fa eur euro f154 fa gbp f155 fa usd dollar f156 fa inr rupee f157 fa cny rmb jpy yen f158 fa rub rouble f159 fa krw won f15a fa btc bitcoin f15b fa file f15c fa file text f15d fa sort alpha asc f15e fa sort alpha desc f160 fa sort amount asc f161 fa sort amount desc f162 fa sort numeric asc f163 fa sort numeric desc f164 fa thumbs up f165 fa thumbs down f166 fa youtube square f167 fa youtube f168 fa xing f169 fa xing square f16a fa youtube play f16b fa dropbox f16c fa stack overflow f16d fa instagram f16e fa flickr f170 fa adn f171 fa bitbucket f172 fa bitbucket square f173 fa tumblr f174 fa tumblr square f175 fa long arrow down f176 fa long arrow up f177 fa long arrow left f178 fa long arrow right f179 fa apple f17a fa windows f17b fa android f17c fa linux f17d fa dribbble f17e fa skype f180 fa foursquare f181 fa trello f182 fa female f183 fa male f184 fa gratipay f185 fa sun o f186 fa moon o f187 fa archive f188 fa bug f189 fa vk f18a fa weibo f18b fa renren f18c fa pagelines f18d fa stack exchange f18e fa arrow circle o right f190 fa arrow circle o left f191 fa caret square o left f192 fa dot circle o f193 fa wheelchair f194 fa vimeo square f195 fa try turkish lira f196 fa plus square o f197 fa space shuttle f198 fa slack f199 fa envelope square f19a fa wordpress f19b fa openid f19c fa university institution bank f19d fa graduation cap mortar board f19e fa yahoo f1a0 fa google f1a1 fa reddit f1a2 fa reddit square f1a3 fa stumbleupon circle f1a4 fa stumbleupon f1a5 fa delicious f1a6 fa digg f1a7 fa pied piper pp f1a8 fa pied piper alt f1a9 fa drupal f1aa fa joomla f1ab fa language f1ac fa fax f1ad fa building f1ae fa child f1b0 fa paw f1b1 fa spoon f1b2 fa cube f1b3 fa cubes f1b4 fa behance f1b5 fa behance square f1b6 fa steam f1b7 fa steam square f1b8 fa recycle f1b9 fa car automobile f1ba fa taxi cab f1bb fa tree f1bc fa spotify f1bd fa deviantart f1be fa soundcloud f1c0 fa database f1c1 fa file pdf o f1c2 fa file word o f1c3 fa file excel o f1c4 fa file powerpoint o f1c5 fa file image o photo picture f1c6 fa file archive o zip f1c7 fa file audio o sound f1c8 fa file video o movie f1c9 fa file code o f1ca fa vine f1cb fa codepen f1cc fa jsfiddle f1cd fa life ring support f1ce fa circle o notch f1d0 fa rebel f1d1 fa empire f1d2 fa git square f1d3 fa git f1d4 fa hacker news y combinator f1d5 fa tencent weibo f1d6 fa qq f1d7 fa weixin wechat f1d8 fa paper plane send f1d9 fa paper plane o send f1da fa history f1db fa circle thin f1dc fa header f1dd fa paragraph f1de fa sliders f1e0 fa share alt f1e1 fa share alt square f1e2 fa bomb f1e3 fa futbol o soccer ball f1e4 fa tty f1e5 fa binoculars f1e6 fa plug f1e7 fa slideshare f1e8 fa twitch f1e9 fa yelp f1ea fa newspaper o f1eb fa wifi f1ec fa calculator f1ed fa paypal f1ee fa google wallet f1f0 fa cc visa f1f1 fa cc mastercard f1f2 fa cc discover f1f3 fa cc amex f1f4 fa cc paypal f1f5 fa cc stripe f1f6 fa bell slash f1f7 fa bell slash o f1f8 fa trash f1f9 fa copyright f1fa fa at f1fb fa eyedropper f1fc fa paint brush f1fd fa birthday cake f1fe fa area chart f200 fa pie chart f201 fa line chart f202 fa lastfm f203 fa lastfm square f204 fa toggle off f205 fa toggle on f206 fa bicycle f207 fa bus f208 fa ioxhost f209 fa angellist f20a fa cc f20b fa ils shekel sheqel f20c fa meanpath f20d fa buysellads f20e fa connectdevelop f210 fa dashcube f211 fa forumbee f212 fa leanpub f213 fa sellsy f214 fa shirtsinbulk f215 fa simplybuilt f216 fa skyatlas f217 fa cart plus f218 fa cart arrow down f219 fa diamond f21a fa ship f21b fa user secret f21c fa motorcycle f21d fa street view f21e fa heartbeat f221 fa venus f222 fa mars f223 fa mercury f224 fa intersex transgender f225 fa transgender alt f226 fa venus double f227 fa mars double f228 fa venus mars f229 fa mars stroke f22a fa mars stroke v f22b fa mars stroke h f22c fa neuter f22d fa genderless f230 fa facebook official f231 fa pinterest p f232 fa whatsapp f233 fa server f234 fa user plus f235 fa user times f236 fa bed hotel f237 fa viacoin f238 fa train f239 fa subway f23a fa medium f23b fa y combinator f23c fa optin monster f23d fa opencart f23e fa expeditedssl f240 fa battery full f241 fa battery three quarters f242 fa battery half f243 fa battery quarter f244 fa battery empty f245 fa mouse pointer f246 fa i cursor f247 fa object group f248 fa object ungroup f249 fa sticky note f24a fa sticky note o f24b fa cc jcb f24c fa cc diners club f24d fa clone f24e fa balance scale f250 fa hourglass o f251 fa hourglass start f252 fa hourglass half f253 fa hourglass end f254 fa hourglass f255 fa hand rock o f256 fa hand paper o f257 fa hand scissors o f258 fa hand lizard o f259 fa hand spock o f25a fa hand pointer o f25b fa hand peace o f25c fa trademark f25d fa registered f25e fa creative commons f260 fa gg f261 fa gg circle f262 fa tripadvisor f263 fa odnoklassniki f264 fa odnoklassniki square f265 fa get pocket f266 fa wikipedia w f267 fa safari f268 fa chrome f269 fa firefox f26a fa opera f26b fa internet explorer f26c fa television tv f26d fa contao f26e fa 500px f270 fa amazon f271 fa calendar plus o f272 fa calendar minus o f273 fa calendar times o f274 fa calendar check o f275 fa industry f276 fa map pin f277 fa map signs f278 fa map o f279 fa map f27a fa commenting f27b fa commenting o f27c fa houzz f27d fa vimeo f27e fa black tie f280 fa fonticons f281 fa reddit alien f282 fa edge f283 fa credit card alt f284 fa codiepie f285 fa modx f286 fa fort awesome f287 fa usb f288 fa product hunt f289 fa mixcloud f28a fa scribd f28b fa pause circle f28c fa pause circle o f28d fa stop circle f28e fa stop circle o f290 fa shopping bag f291 fa shopping basket f292 fa hashtag f293 fa bluetooth f294 fa bluetooth b f295 fa percent f296 fa gitlab f297 fa wpbeginner f298 fa wpforms f299 fa envira f29a fa universal access f29b fa wheelchair alt f29c fa question circle o f29d fa blind f29e fa audio description f2a0 fa volume control phone f2a1 fa braille f2a2 fa assistive listening systems f2a3 fa american sign language interpreting f2a4 fa deaf f2a5 fa glide f2a6 fa glide g f2a7 fa sign language f2a8 fa low vision f2a9 fa viadeo f2aa fa viadeo square f2ab fa snapchat f2ac fa snapchat ghost f2ad fa snapchat square f2ae fa pied piper f2b0 fa first order f2b1 fa yoast f2b2 fa themeisle f2b3 fa google plus f2b4 fa font awesome f2b5 fa handshake o f2b6 fa envelope open f2b7 fa envelope open o f2b8 fa linode f2b9 fa address book f2ba fa address book o f2bb fa address card vcard f2bc fa address card o vcard f2bd fa user circle f2be fa user circle o f2c0 fa user o f2c1 fa id badge f2c2 fa id card f2c3 fa id card o f2c4 fa quora f2c5 fa free code camp f2c6 fa telegram f2c7 fa thermometer full f2c8 fa thermometer three quarters f2c9 fa thermometer half f2ca fa thermometer quarter f2cb fa thermometer empty f2cc fa shower f2cd fa bath f2ce fa podcast f2d0 fa window maximize f2d1 fa window minimize f2d2 fa window restore f2d3 fa window close times rectangle f2d4 fa window close o times rectangle f2d5 fa bandcamp f2d6 fa grav f2d7 fa etsy f2d8 fa imdb f2d9 fa ravelry f2da fa eercast f2db fa microchip f2dc fa snowflake o f2dd fa superpowers f2de fa wpexplorer f2e0 fa meetup e200 fae smaller e201 fae snowing e202 fae soda e203 fae sofa e204 fae soup e205 fae spermatozoon e206 fae spin double e207 fae stomach e208 fae storm e209 fae telescope e20a fae thermometer e20b fae thermometer high e20c fae thermometer low e20d fae thin close e20e fae toilet e20f fae tools e210 fae tooth e211 fae uterus e212 fae w3c e213 fae walking e214 fae virus e215 fae telegram circle e216 fae slash e217 fae telegram e218 fae shirt e219 fae tacos e21a fae sushi e21b fae triangle ruler e21c fae tree e21d fae sun cloud e21e fae ruby o e21f fae ruler e220 fae umbrella e221 fae medicine e222 fae microscope e223 fae milk bottle e224 fae minimize e225 fae molecule e226 fae moon cloud e227 fae mushroom e228 fae mustache e229 fae mysql e22a fae nintendo e22b fae palette color e22c fae pi e22d fae pizza e22e fae planet e22f fae plant e230 fae playstation e231 fae poison e232 fae popcorn e233 fae popsicle e234 fae pulse e235 fae python e236 fae quora circle e237 fae quora square e238 fae radioactive e239 fae raining e23a fae real heart e23b fae refrigerator e23c fae restore e23d fae ring e23e fae ruby e23f fae fingerprint e240 fae floppy e241 fae footprint e242 fae freecodecamp e243 fae galaxy e244 fae galery e245 fae glass e246 fae google drive e247 fae google play e248 fae gps e249 fae grav e24a fae guitar e24b fae gut e24c fae halter e24d fae hamburger e24e fae hat e24f fae hexagon e250 fae high heel e251 fae hotdog e252 fae ice cream e253 fae id card e254 fae imdb e255 fae infinity e256 fae java e257 fae layers e258 fae lips e259 fae lipstick e25a fae liver e25b fae lung e25c fae makeup brushes e25d fae maximize e25e fae wallet e25f fae chess horse e260 fae chess king e261 fae chess pawn e262 fae chess queen e263 fae chess tower e264 fae cheese e265 fae chilli e266 fae chip e267 fae cicling e268 fae cloud e269 fae cockroach e26a fae coffe beans e26b fae coins e26c fae comb e26d fae comet e26e fae crown e26f fae cup coffe e270 fae dice e271 fae disco e272 fae dna e273 fae donut e274 fae dress e275 fae drop e276 fae ello e277 fae envelope open e278 fae envelope open o e279 fae equal e27a fae equal bigger e27b fae feedly e27c fae file export e27d fae file import e27e fae wind e27f fae atom e280 fae bacteria e281 fae banana e282 fae bath e283 fae bed e284 fae benzene e285 fae bigger e286 fae biohazard e287 fae blogger circle e288 fae blogger square e289 fae bones e28a fae book open e28b fae book open o e28c fae brain e28d fae bread e28e fae butterfly e28f fae carot e290 fae cc by e291 fae cc cc e292 fae cc nc e293 fae cc nc eu e294 fae cc nc jp e295 fae cc nd e296 fae cc remix e297 fae cc sa e298 fae cc share e299 fae cc zero e29a fae checklist o e29b fae cherry e29c fae chess bishop e29d fae xbox e29e fae apple fruit e29f fae chicken thigh e2a0 fae gift card e2a1 fae injection e2a2 fae isle e2a3 fae lollipop e2a4 fae loyalty card e2a5 fae meat e2a6 fae mountains e2a7 fae orange e2a8 fae peach e2a9 fae pear 23fb iec power 23fc iec toggle power 23fd iec power on 23fe iec sleep mode 2b58 iec power off e621 indent line dotted guide f300 linux alpine f301 linux aosc f302 linux apple f303 linux archlinux f304 linux centos f305 linux coreos f306 linux debian f307 linux devuan f308 linux docker f309 linux elementary f30a linux fedora f30b linux fedora inverse f30c linux freebsd f30d linux gentoo f30e linux linuxmint f30f linux linuxmint inverse f310 linux mageia f311 linux mandriva f312 linux manjaro f313 linux nixos f314 linux opensuse f315 linux raspberry pi f316 linux redhat f317 linux sabayon f318 linux slackware f319 linux slackware inverse f31a linux tux f31b linux ubuntu f31c linux ubuntu inverse f31d linux almalinux f31e linux archlabs f31f linux artix f320 linux budgie f321 linux deepin f322 linux endeavour f323 linux ferris f324 linux flathub f325 linux gnu guix f326 linux illumos f327 linux kali linux f328 linux openbsd f329 linux parrot f32a linux pop os f32b linux rocky linux f32c linux snappy f32d linux solus f32e linux void f32f linux zorin f0001 md vector square f0002 md access point network f0003 md access point f0004 md account f0005 md account alert f0006 md account box f0007 md account box outline f0008 md account check f0009 md account circle f000a md account convert f000b md account key f000c md tooltip account f000d md account minus f000e md account multiple f000f md account multiple outline f0010 md account multiple plus f0011 md account network f0012 md account off f0013 md account outline f0014 md account plus f0015 md account remove f0016 md account search f0017 md account star f0018 md orbit f0019 md account switch f001a md adjust f001b md air conditioner f001c md airballoon f001d md airplane f001e md airplane off f001f md cast variant f0020 md alarm f0021 md alarm check f0022 md alarm multiple f0023 md alarm off f0024 md alarm plus f0025 md album f0026 md alert f0027 md alert box f0028 md alert circle f0029 md alert octagon f002a md alert outline f002b md alpha f002c md alphabetical f002d md greenhouse f002e md rollerblade off f002f md ambulance f0030 md amplifier f0031 md anchor f0032 md android f0033 md web plus f0034 md android studio f0035 md apple f0036 md apple finder f0037 md apple ios f0038 md apple icloud f0039 md apple safari f003a md font awesome f003b md apps f003c md archive f003d md arrange bring forward f003e md arrange bring to front f003f md arrange send backward f0040 md arrange send to back f0041 md arrow all f0042 md arrow bottom left f0043 md arrow bottom right f0044 md arrow collapse all f0045 md arrow down f0046 md arrow down thick f0047 md arrow down bold circle f0048 md arrow down bold circle outline f0049 md arrow down bold hexagon outline f004a md arrow down drop circle f004b md arrow down drop circle outline f004c md arrow expand all f004d md arrow left f004e md arrow left thick f004f md arrow left bold circle f0050 md arrow left bold circle outline f0051 md arrow left bold hexagon outline f0052 md arrow left drop circle f0053 md arrow left drop circle outline f0054 md arrow right f0055 md arrow right thick f0056 md arrow right bold circle f0057 md arrow right bold circle outline f0058 md arrow right bold hexagon outline f0059 md arrow right drop circle f005a md arrow right drop circle outline f005b md arrow top left f005c md arrow top right f005d md arrow up f005e md arrow up thick f005f md arrow up bold circle f0060 md arrow up bold circle outline f0061 md arrow up bold hexagon outline f0062 md arrow up drop circle f0063 md arrow up drop circle outline f0064 md assistant f0065 md at f0066 md attachment f0067 md book music f0068 md auto fix f0069 md auto upload f006a md autorenew f006b md av timer f006c md baby f006d md backburger f006e md backspace f006f md backup restore f0070 md bank f0071 md barcode f0072 md barcode scan f0073 md barley f0074 md barrel f0075 md incognito off f0076 md basket f0077 md basket fill f0078 md basket unfill f0079 md battery f007a md battery 10 f007b md battery 20 f007c md battery 30 f007d md battery 40 f007e md battery 50 f007f md battery 60 f0080 md battery 70 f0081 md battery 80 f0082 md battery 90 f0083 md battery alert f0084 md battery charging f0085 md battery charging 100 f0086 md battery charging 20 f0087 md battery charging 30 f0088 md battery charging 40 f0089 md battery charging 60 f008a md battery charging 80 f008b md battery charging 90 f008c md battery minus variant f008d md battery negative f008e md battery outline f008f md battery plus variant f0090 md battery positive f0091 md battery unknown f0092 md beach f0093 md flask f0094 md flask empty f0095 md flask empty outline f0096 md flask outline f0097 md bunk bed outline f0098 md beer f0099 md bed outline f009a md bell f009b md bell off f009c md bell outline f009d md bell plus f009e md bell ring f009f md bell ring outline f00a0 md bell sleep f00a1 md beta f00a2 md book cross f00a3 md bike f00a4 md microsoft bing f00a5 md binoculars f00a6 md bio f00a7 md biohazard f00a8 md bitbucket f00a9 md black mesa f00aa md shield refresh f00ab md blender software f00ac md blinds f00ad md block helper f00ae md application edit f00af md bluetooth f00b0 md bluetooth audio f00b1 md bluetooth connect f00b2 md bluetooth off f00b3 md bluetooth settings f00b4 md bluetooth transfer f00b5 md blur f00b6 md blur linear f00b7 md blur off f00b8 md blur radial f00b9 md bone f00ba md book f00bb md book multiple f00bc md book variant multiple f00bd md book open f00be md book open blank variant f00bf md book variant f00c0 md bookmark f00c1 md bookmark check f00c2 md bookmark music f00c3 md bookmark outline f00c4 md bookmark plus outline f00c5 md bookmark plus f00c6 md bookmark remove f00c7 md border all f00c8 md border bottom f00c9 md border color f00ca md border horizontal f00cb md border inside f00cc md border left f00cd md border none f00ce md border outside f00cf md border right f00d0 md border style f00d1 md border top f00d2 md border vertical f00d3 md bowling f00d4 md box f00d5 md box cutter f00d6 md briefcase f00d7 md briefcase check f00d8 md briefcase download f00d9 md briefcase upload f00da md brightness 1 f00db md brightness 2 f00dc md brightness 3 f00dd md brightness 4 f00de md brightness 5 f00df md brightness 6 f00e0 md brightness 7 f00e1 md brightness auto f00e2 md broom f00e3 md brush f00e4 md bug f00e5 md bulletin board f00e6 md bullhorn f00e7 md bus f00e8 md cached f00e9 md cake f00ea md cake layered f00eb md cake variant f00ec md calculator f00ed md calendar f00ee md calendar blank f00ef md calendar check f00f0 md calendar clock f00f1 md calendar multiple f00f2 md calendar multiple check f00f3 md calendar plus f00f4 md calendar remove f00f5 md calendar text f00f6 md calendar today f00f7 md call made f00f8 md call merge f00f9 md call missed f00fa md call received f00fb md call split f00fc md camcorder f00fd md video box f00fe md video box off f00ff md camcorder off f0100 md camera f0101 md camera enhance f0102 md camera front f0103 md camera front variant f0104 md camera iris f0105 md camera party mode f0106 md camera rear f0107 md camera rear variant f0108 md camera switch f0109 md camera timer f010a md candycane f010b md car f010c md car battery f010d md car connected f010e md car wash f010f md carrot f0110 md cart f0111 md cart outline f0112 md cart plus f0113 md case sensitive alt f0114 md cash f0115 md cash 100 f0116 md cash multiple f0117 md checkbox blank badge outline f0118 md cast f0119 md cast connected f011a md castle f011b md cat f011c md cellphone f011d md tray arrow up f011e md cellphone basic f011f md cellphone dock f0120 md tray arrow down f0121 md cellphone link f0122 md cellphone link off f0123 md cellphone settings f0124 md certificate f0125 md chair school f0126 md chart arc f0127 md chart areaspline f0128 md chart bar f0129 md chart histogram f012a md chart line f012b md chart pie f012c md check f012d md check all f012e md checkbox blank f0131 md checkbox blank outline f0132 md checkbox marked f0133 md checkbox marked circle f0134 md checkbox marked circle outline f0135 md checkbox marked outline f0136 md checkbox multiple blank f0137 md checkbox multiple blank outline f0138 md checkbox multiple marked f0139 md checkbox multiple marked outline f013a md checkerboard f013b md chemical weapon f013c md chevron double down f013d md chevron double left f013e md chevron double right f013f md chevron double up f0140 md chevron down f0141 md chevron left f0142 md chevron right f0143 md chevron up f0144 md church f0145 md roller skate off f0146 md city f0147 md clipboard f0148 md clipboard account f0149 md clipboard alert f014a md clipboard arrow down f014b md clipboard arrow left f014c md clipboard outline f014d md clipboard text f014e md clipboard check f014f md clippy f0150 md clock outline f0151 md clock end f0152 md clock fast f0153 md clock in f0154 md clock out f0155 md clock start f0156 md close f0157 md close box f0158 md close box outline f0159 md close circle f015a md close circle outline f015b md close network f015c md close octagon f015d md close octagon outline f015e md closed caption f015f md cloud f0160 md cloud check f0161 md cloud circle f0162 md cloud download f0163 md cloud outline f0164 md cloud off outline f0165 md cloud print f0166 md cloud print outline f0167 md cloud upload f0168 md code array f0169 md code braces f016a md code brackets f016b md code equal f016c md code greater than f016d md code greater than or equal f016e md code less than f016f md code less than or equal f0170 md code not equal f0171 md code not equal variant f0172 md code parentheses f0173 md code string f0174 md code tags f0175 md codepen f0176 md coffee f0177 md coffee to go f0178 md bell badge outline f0179 md color helper f017a md comment f017b md comment account f017c md comment account outline f017d md comment alert f017e md comment alert outline f017f md comment check f0180 md comment check outline f0181 md comment multiple outline f0182 md comment outline f0183 md comment plus outline f0184 md comment processing f0185 md comment processing outline f0186 md comment question outline f0187 md comment remove outline f0188 md comment text f0189 md comment text outline f018a md compare f018b md compass f018c md compass outline f018d md console f018e md card account mail f018f md content copy f0190 md content cut f0191 md content duplicate f0192 md content paste f0193 md content save f0194 md content save all f0195 md contrast f0196 md contrast box f0197 md contrast circle f0198 md cookie f0199 md counter f019a md cow f019b md credit card outline f019c md credit card multiple outline f019d md credit card scan outline f019e md crop f019f md crop free f01a0 md crop landscape f01a1 md crop portrait f01a2 md crop square f01a3 md crosshairs f01a4 md crosshairs gps f01a5 md crown f01a6 md cube f01a7 md cube outline f01a8 md cube send f01a9 md cube unfolded f01aa md cup f01ab md cup water f01ac md currency btc f01ad md currency eur f01ae md currency gbp f01af md currency inr f01b0 md currency ngn f01b1 md currency rub f01b2 md currency try f01b3 md delete variant f01b4 md delete f01b5 md decimal increase f01b6 md decimal decrease f01b7 md debug step over f01b8 md debug step out f01b9 md debug step into f01ba md database plus f01bb md database minus f01bc md database f01bd md cursor pointer f01be md cursor move f01bf md cursor default outline f01c0 md cursor default f01c1 md currency usd f01c2 md delta f01c3 md deskphone f01c4 md desktop mac f01c5 md desktop tower f01c6 md details f01c7 md deviantart f01c8 md diamond stone f01c9 md ab testing f01ca md dice 1 f01cb md dice 2 f01cc md dice 3 f01cd md dice 4 f01ce md dice 5 f01cf md dice 6 f01d0 md directions f01d1 md disc alert f01d2 md disqus f01d3 md video plus outline f01d4 md division f01d5 md division box f01d6 md dns f01d7 md domain f01d8 md dots horizontal f01d9 md dots vertical f01da md download f01db md drag f01dc md drag horizontal f01dd md drag vertical f01de md drawing f01df md drawing box f01e0 md shield refresh outline f01e1 md calendar refresh f01e2 md drone f01e3 md dropbox f01e4 md drupal f01e5 md duck f01e6 md dumbbell f01e7 md earth f01e8 md earth off f01e9 md microsoft edge f01ea md eject f01eb md elevation decline f01ec md elevation rise f01ed md elevator f01ee md email f01ef md email open f01f0 md email outline f01f1 md email lock f01f2 md emoticon outline f01f3 md emoticon cool outline f01f4 md emoticon devil outline f01f5 md emoticon happy outline f01f6 md emoticon neutral outline f01f7 md emoticon poop f01f8 md emoticon sad outline f01f9 md emoticon tongue f01fa md engine f01fb md engine outline f01fc md equal f01fd md equal box f01fe md eraser f01ff md escalator f0200 md ethernet f0201 md ethernet cable f0202 md ethernet cable off f0203 md calendar refresh outline f0204 md evernote f0205 md exclamation f0207 md export f0208 md eye f0209 md eye off f020a md eyedropper f020b md eyedropper variant f020c md facebook f020d md order alphabetical ascending f020e md facebook messenger f020f md factory f0210 md fan f0211 md fast forward f0212 md fax f0213 md ferry f0214 md file f0215 md file chart f0216 md file check f0217 md file cloud f0218 md file delimited f0219 md file document f021a md text box f021b md file excel f021c md file excel box f021d md file export f021e md file find f021f md file image f0220 md file import f0221 md file lock f0222 md file multiple f0223 md file music f0224 md file outline f0225 md file jpg box f0226 md file pdf box f0227 md file powerpoint f0228 md file powerpoint box f0229 md file presentation box f022a md file send f022b md file video f022c md file word f022d md file word box f022e md file code f022f md film f0230 md filmstrip f0231 md filmstrip off f0232 md filter f0233 md filter outline f0234 md filter remove f0235 md filter remove outline f0236 md filter variant f0237 md fingerprint f0238 md fire f0239 md firefox f023a md fish f023b md flag f023c md flag checkered f023d md flag outline f023e md flag variant outline f023f md flag triangle f0240 md flag variant f0241 md flash f0242 md flash auto f0243 md flash off f0244 md flashlight f0245 md flashlight off f0246 md star half f0247 md flip to back f0248 md flip to front f0249 md floppy f024a md flower f024b md folder f024c md folder account f024d md folder download f024e md folder google drive f024f md folder image f0250 md folder lock f0251 md folder lock open f0252 md folder move f0253 md folder multiple f0254 md folder multiple image f0255 md folder multiple outline f0256 md folder outline f0257 md folder plus f0258 md folder remove f0259 md folder upload f025a md food f025b md food apple f025c md food variant f025d md football f025e md football australian f025f md football helmet f0260 md format align center f0261 md format align justify f0262 md format align left f0263 md format align right f0264 md format bold f0265 md format clear f0266 md format color fill f0267 md format float center f0268 md format float left f0269 md format float none f026a md format float right f026b md format header 1 f026c md format header 2 f026d md format header 3 f026e md format header 4 f026f md format header 5 f0270 md format header 6 f0271 md format header decrease f0272 md format header equal f0273 md format header increase f0274 md format header pound f0275 md format indent decrease f0276 md format indent increase f0277 md format italic f0278 md format line spacing f0279 md format list bulleted f027a md format list bulleted type f027b md format list numbered f027c md format paint f027d md format paragraph f027e md format quote close f027f md format size f0280 md format strikethrough f0281 md format strikethrough variant f0282 md format subscript f0283 md format superscript f0284 md format text f0285 md format textdirection l to r f0286 md format textdirection r to l f0287 md format underline f0288 md format wrap inline f0289 md format wrap square f028a md format wrap tight f028b md format wrap top bottom f028c md forum f028d md forward f028e md bowl f028f md fridge outline f0290 md fridge f0291 md fridge top f0292 md fridge bottom f0293 md fullscreen f0294 md fullscreen exit f0295 md function f0296 md gamepad f0297 md gamepad variant f0298 md gas station f0299 md gate f029a md gauge f029b md gavel f029c md gender female f029d md gender male f029e md gender male female f029f md gender transgender f02a0 md ghost f02a1 md gift outline f02a2 md git f02a3 md card account details star f02a4 md github f02a5 md glass flute f02a6 md glass mug f02a7 md glass stange f02a8 md glass tulip f02a9 md bowl outline f02aa md glasses f02ab md gmail f02ac md gnome f02ad md google f02ae md google cardboard f02af md google chrome f02b0 md google circles f02b1 md google circles communities f02b2 md google circles extended f02b3 md google circles group f02b4 md google controller f02b5 md google controller off f02b6 md google drive f02b7 md google earth f02b8 md google glass f02b9 md google nearby f02ba md video minus outline f02bb md microsoft teams f02bc md google play f02bd md google plus f02be md order bool ascending f02bf md google translate f02c0 md google classroom f02c1 md grid f02c2 md grid off f02c3 md group f02c4 md guitar electric f02c5 md guitar pick f02c6 md guitar pick outline f02c7 md hand pointing right f02c8 md hanger f02c9 md google hangouts f02ca md harddisk f02cb md headphones f02cc md headphones box f02cd md headphones settings f02ce md headset f02cf md headset dock f02d0 md headset off f02d2 md heart box f02d3 md heart box outline f02d4 md heart broken f02d6 md help f02d7 md help circle f02d8 md hexagon f02d9 md hexagon outline f02da md history f02db md hololens f02dc md home f02dd md home modern f02de md home variant f02df md hops f02e0 md hospital box f02e1 md hospital building f02e2 md hospital marker f02e3 md bed f02e4 md bowl mix outline f02e5 md pot f02e6 md human f02e7 md human child f02e8 md human male female f02e9 md image f02ea md image album f02eb md image area f02ec md image area close f02ed md image broken f02ee md image broken variant f02ef md image multiple outline f02f0 md image filter black white f02f1 md image filter center focus f02f2 md image filter center focus weak f02f3 md image filter drama f02f4 md image filter frames f02f6 md image filter none f02f7 md image filter tilt shift f02f8 md image filter vintage f02f9 md image multiple f02fa md import f02fb md inbox arrow down f02fc md information f02fd md information outline f02fe md instagram f02ff md pot outline f0300 md microsoft internet explorer f0301 md invert colors f0302 md jeepney f0303 md jira f0304 md jsfiddle f0305 md keg f0306 md key f0307 md key change f0308 md key minus f0309 md key plus f030a md key remove f030b md key variant f030c md keyboard f030d md keyboard backspace f030e md keyboard caps f030f md keyboard close f0310 md keyboard off f0311 md keyboard return f0312 md keyboard tab f0313 md keyboard variant f0314 md kodi f0315 md label f0316 md label outline f0317 md lan f0318 md lan connect f0319 md lan disconnect f031a md lan pending f031b md language csharp f031c md language css3 f031d md language html5 f031e md language javascript f031f md language php f0320 md language python f0321 md contactless payment circle f0322 md laptop f0323 md magazine rifle f0324 md magazine pistol f0325 md keyboard tab reverse f0326 md pot steam outline f0327 md launch f0328 md layers f0329 md layers off f032a md leaf f032b md led off f032c md led on f032d md led outline f032e md led variant off f032f md led variant on f0330 md led variant outline f0331 md library f0332 md filmstrip box f0333 md music box multiple f0334 md plus box multiple f0335 md lightbulb f0336 md lightbulb outline f0337 md link f0338 md link off f0339 md link variant f033a md link variant off f033b md linkedin f033c md sort reverse variant f033d md linux f033e md lock f033f md lock open f0340 md lock open outline f0341 md lock outline f0342 md login f0343 md logout f0344 md looks f0345 md loupe f0346 md lumx f0347 md magnet f0348 md magnet on f0349 md magnify f034a md magnify minus f034b md magnify plus f034c md plus circle multiple f034d md map f034e md map marker f034f md map marker circle f0350 md map marker multiple f0351 md map marker off f0352 md map marker radius f0353 md margin f0354 md language markdown f0355 md marker check f0356 md glass cocktail f0357 md material ui f0358 md math compass f0359 md stackpath f035a md minus circle multiple f035b md memory f035c md menu f035d md menu down f035e md menu left f035f md menu right f0360 md menu up f0361 md message f0362 md message alert f0363 md message draw f0364 md message image f0365 md message outline f0366 md message processing f0367 md message reply f0368 md message reply text f0369 md message text f036a md message text outline f036b md message video f036c md microphone f036d md microphone off f036e md microphone outline f036f md microphone settings f0370 md microphone variant f0371 md microphone variant off f0372 md microsoft f0373 md minecraft f0374 md minus f0375 md minus box f0376 md minus circle f0377 md minus circle outline f0378 md minus network f0379 md monitor f037a md monitor multiple f037b md more f037c md motorbike f037d md mouse f037e md mouse off f037f md mouse variant f0380 md mouse variant off f0381 md movie f0382 md multiplication f0383 md multiplication box f0384 md music box f0385 md music box outline f0386 md music circle f0388 md music note f0389 md music note half f038a md music note off f038b md music note quarter f038c md music note sixteenth f038d md music note whole f038e md nature f038f md nature people f0390 md navigation f0391 md needle f0392 md smoke detector f0393 md thermostat f0394 md new box f0395 md newspaper f0396 md nfc f0397 md nfc tap f0398 md nfc variant f0399 md nodejs f039a md note f039b md note outline f039c md note plus f039d md note plus outline f039e md note text f039f md notification clear all f03a0 md numeric f03a1 md numeric 0 box f03a2 md numeric 0 box multiple outline f03a3 md numeric 0 box outline f03a4 md numeric 1 box f03a5 md numeric 1 box multiple outline f03a6 md numeric 1 box outline f03a7 md numeric 2 box f03a8 md numeric 2 box multiple outline f03a9 md numeric 2 box outline f03aa md numeric 3 box f03ab md numeric 3 box multiple outline f03ac md numeric 3 box outline f03ad md numeric 4 box f03ae md numeric 4 box outline f03af md numeric 5 box multiple outline f03b0 md numeric 5 box outline f03b1 md numeric 5 box f03b2 md numeric 4 box multiple outline f03b3 md numeric 6 box f03b4 md numeric 6 box multiple outline f03b5 md numeric 6 box outline f03b6 md numeric 7 box f03b7 md numeric 7 box multiple outline f03b8 md numeric 7 box outline f03b9 md numeric 8 box f03ba md numeric 8 box multiple outline f03bb md numeric 8 box outline f03bc md numeric 9 box f03bd md numeric 9 box multiple outline f03be md numeric 9 box outline f03bf md numeric 9 plus box f03c0 md numeric 9 plus box multiple outline f03c1 md numeric 9 plus box outline f03c2 md nutrition f03c3 md octagon f03c4 md octagon outline f03c5 md odnoklassniki f03c6 md microsoft office f03c7 md oil f03c8 md coolant temperature f03c9 md omega f03ca md microsoft onedrive f03cb md open in app f03cc md open in new f03cd md openid f03ce md opera f03cf md ornament f03d0 md ornament variant f03d1 md inbox arrow up f03d2 md owl f03d3 md package f03d4 md package down f03d5 md package up f03d6 md package variant f03d7 md package variant closed f03d8 md palette f03d9 md palette advanced f03da md panda f03db md pandora f03dc md panorama f03dd md panorama fisheye f03de md panorama horizontal outline f03df md panorama vertical outline f03e0 md panorama wide angle outline f03e1 md paper cut vertical f03e2 md paperclip f03e3 md parking f03e4 md pause f03e5 md pause circle f03e6 md pause circle outline f03e7 md pause octagon f03e8 md pause octagon outline f03e9 md paw f03ea md pen f03eb md pencil f03ec md pencil box f03ed md pencil box outline f03ee md pencil lock f03ef md pencil off f03f0 md percent f03f1 md mortar pestle plus f03f2 md phone f03f3 md phone bluetooth f03f4 md phone forward f03f5 md phone hangup f03f6 md phone in talk f03f7 md phone incoming f03f8 md phone lock f03f9 md phone log f03fa md phone missed f03fb md phone outgoing f03fc md phone paused f03fd md phone settings f03fe md phone voip f03ff md pi f0400 md pi box f0401 md pig f0402 md pill f0403 md pin f0404 md pin off f0405 md pine tree f0406 md pine tree box f0407 md pinterest f0408 md contactless payment circle outline f0409 md pizza f040a md play f040b md play box outline f040c md play circle f040d md play circle outline f040e md play pause f040f md play protected content f0410 md playlist minus f0411 md playlist play f0412 md playlist plus f0413 md playlist remove f0414 md sony playstation f0415 md plus f0416 md plus box f0417 md plus circle f0418 md plus circle multiple outline f0419 md plus circle outline f041a md plus network f041b md sledding f041c md wall sconce flat variant f041d md pokeball f041e md polaroid f041f md poll f0420 md account eye f0421 md polymer f0422 md popcorn f0423 md pound f0424 md pound box f0425 md power f0426 md power settings f0427 md power socket f0428 md presentation f0429 md presentation play f042a md printer f042b md printer 3d f042c md printer alert f042d md professional hexagon f042e md projector f042f md projector screen f0430 md pulse f0431 md puzzle f0432 md qrcode f0433 md qrcode scan f0434 md quadcopter f0435 md quality high f0436 md book multiple outline f0437 md radar f0438 md radiator f0439 md radio f043a md radio handheld f043b md radio tower f043c md radioactive f043e md radiobox marked f043f md raspberry pi f0440 md ray end f0441 md ray end arrow f0442 md ray start f0443 md ray start arrow f0444 md ray start end f0445 md ray vertex f0446 md lastpass f0447 md read f0448 md youtube tv f0449 md receipt f044a md record f044b md record rec f044c md recycle f044d md reddit f044e md redo f044f md redo variant f0450 md refresh f0451 md regex f0452 md relative scale f0453 md reload f0454 md remote f0455 md rename box f0456 md repeat f0457 md repeat off f0458 md repeat once f0459 md replay f045a md reply f045b md reply all f045c md reproduction f045d md resize bottom right f045e md responsive f045f md rewind f0460 md ribbon f0461 md road f0462 md road variant f0463 md rocket f0464 md rotate 3d variant f0465 md rotate left f0466 md rotate left variant f0467 md rotate right f0468 md rotate right variant f0469 md router wireless f046a md routes f046b md rss f046c md rss box f046d md ruler f046e md run fast f046f md sale f0470 md satellite f0471 md satellite variant f0472 md scale f0473 md scale bathroom f0474 md school f0475 md screen rotation f0476 md screwdriver f0477 md script outline f0478 md screen rotation lock f0479 md sd f047a md seal f047b md seat flat f047c md seat flat angled f047d md seat individual suite f047e md seat legroom extra f047f md seat legroom normal f0480 md seat legroom reduced f0481 md seat recline extra f0482 md seat recline normal f0483 md security f0484 md security network f0485 md select f0486 md select all f0487 md select inverse f0488 md select off f0489 md selection f048a md send f048b md server f048c md server minus f048d md server network f048e md server network off f048f md server off f0490 md server plus f0491 md server remove f0492 md server security f0493 md cog f0494 md cog box f0495 md shape plus f0496 md share f0497 md share variant f0498 md shield f0499 md shield outline f049a md shopping f049b md shopping music f049c md shredder f049d md shuffle f049e md shuffle disabled f049f md shuffle variant f04a0 md sigma f04a1 md sign caution f04a2 md signal f04a3 md silverware f04a4 md silverware fork f04a5 md silverware spoon f04a6 md silverware variant f04a7 md sim f04a8 md sim alert f04a9 md sim off f04aa md sitemap f04ab md skip backward f04ac md skip forward f04ad md skip next f04ae md skip previous f04af md skype f04b0 md skype business f04b1 md slack f04b2 md sleep f04b3 md sleep off f04b4 md smoking f04b5 md smoking off f04b6 md snapchat f04b7 md snowman f04b8 md soccer f04b9 md sofa f04ba md sort f04bb md sort alphabetical variant f04bc md sort ascending f04bd md sort descending f04be md sort numeric variant f04bf md sort variant f04c0 md soundcloud f04c1 md source fork f04c2 md source pull f04c3 md speaker f04c4 md speaker off f04c5 md speedometer f04c6 md spellcheck f04c7 md spotify f04c8 md spotlight f04c9 md spotlight beam f04ca md book remove multiple outline f04cb md account switch outline f04cc md stack overflow f04cd md stairs f04ce md star f04cf md star circle f04d0 md star half full f04d1 md star off f04d2 md star outline f04d3 md steam f04d4 md steering f04d5 md step backward f04d6 md step backward 2 f04d7 md step forward f04d8 md step forward 2 f04d9 md stethoscope f04da md stocking f04db md stop f04dc md store f04dd md store 24 hour f04de md stove f04df md subway variant f04e0 md sunglasses f04e1 md swap horizontal f04e2 md swap vertical f04e3 md swim f04e4 md switch f04e5 md sword f04e6 md sync f04e7 md sync alert f04e8 md sync off f04e9 md tab f04ea md tab unselected f04eb md table f04ec md table column plus after f04ed md table column plus before f04ee md table column remove f04ef md table column width f04f0 md table edit f04f1 md table large f04f2 md table row height f04f3 md table row plus after f04f4 md table row plus before f04f5 md table row remove f04f6 md tablet f04f7 md tablet android f04f8 md tangram f04f9 md tag f04fa md tag faces f04fb md tag multiple f04fc md tag outline f04fd md tag text outline f04fe md target f04ff md taxi f0500 md teamviewer f0501 md skateboarding f0502 md television f0503 md television guide f0504 md temperature celsius f0505 md temperature fahrenheit f0506 md temperature kelvin f0507 md tennis ball f0508 md tent f0509 md image filter hdr f050a md text to speech f050b md text to speech off f050c md texture f050d md theater f050e md theme light dark f050f md thermometer f0510 md thermometer lines f0511 md thumb down f0512 md thumb down outline f0513 md thumb up f0514 md thumb up outline f0515 md thumbs up down f0516 md ticket f0517 md ticket account f0518 md ticket confirmation f0519 md tie f051a md timelapse f051b md timer outline f051c md timer 10 f051d md timer 3 f051e md timer off outline f051f md timer sand f0520 md timetable f0521 md toggle switch f0522 md toggle switch off f0523 md tooltip f0524 md tooltip edit f0525 md tooltip image f0526 md tooltip outline f0527 md tooltip plus outline f0528 md tooltip text f0529 md tooth outline f052a md cloud refresh f052b md traffic light f052c md train f052d md tram f052e md transcribe f052f md transcribe close f0530 md transfer right f0531 md tree f0532 md trello f0533 md trending down f0534 md trending neutral f0535 md trending up f0536 md triangle f0537 md triangle outline f0538 md trophy f0539 md trophy award f053a md trophy outline f053b md trophy variant f053c md trophy variant outline f053d md truck f053e md truck delivery f053f md tshirt crew outline f0540 md tshirt v outline f0541 md file refresh outline f0542 md folder refresh outline f0543 md twitch f0544 md twitter f0545 md order numeric ascending f0546 md order numeric descending f0547 md repeat variant f0548 md ubuntu f0549 md umbraco f054a md umbrella f054b md umbrella outline f054c md undo f054d md undo variant f054e md unfold less horizontal f054f md unfold more horizontal f0550 md ungroup f0551 md web remove f0552 md upload f0553 md usb f0554 md vector arrange above f0555 md vector arrange below f0556 md vector circle f0557 md vector circle variant f0558 md vector combine f0559 md vector curve f055a md vector difference f055b md vector difference ab f055c md vector difference ba f055d md vector intersection f055e md vector line f055f md vector point f0560 md vector polygon f0561 md vector polyline f0562 md vector selection f0563 md vector triangle f0564 md vector union f0565 md shield check f0566 md vibrate f0567 md video f0568 md video off f0569 md video switch f056a md view agenda f056b md view array f056c md view carousel f056d md view column f056e md view dashboard f056f md view day f0570 md view grid f0571 md view headline f0572 md view list f0573 md view module f0574 md view quilt f0575 md view stream f0576 md view week f0577 md vimeo f0578 md buffet f0579 md hands pray f057a md credit card wireless off f057b md credit card wireless off outline f057c md vlc f057d md voicemail f057e md volume high f057f md volume low f0580 md volume medium f0581 md volume off f0582 md vpn f0583 md walk f0584 md wallet f0585 md wallet giftcard f0586 md wallet membership f0587 md wallet travel f0588 md wan f0589 md watch f058a md watch export f058b md watch import f058c md water f058d md water off f058e md water percent f058f md water pump f0590 md weather cloudy f0591 md weather fog f0592 md weather hail f0593 md weather lightning f0594 md weather night f0595 md weather partly cloudy f0596 md weather pouring f0597 md weather rainy f0598 md weather snowy f0599 md weather sunny f059a md weather sunset f059b md weather sunset down f059c md weather sunset up f059d md weather windy f059e md weather windy variant f059f md web f05a0 md webcam f05a1 md weight f05a2 md weight kilogram f05a3 md whatsapp f05a4 md wheelchair accessibility f05a5 md white balance auto f05a6 md white balance incandescent f05a7 md white balance iridescent f05a8 md white balance sunny f05a9 md wifi f05aa md wifi off f05ab md nintendo wii f05ac md wikipedia f05ad md window close f05ae md window closed f05af md window maximize f05b0 md window minimize f05b1 md window open f05b2 md window restore f05b3 md microsoft windows f05b4 md wordpress f05b5 md account hard hat f05b6 md wrap f05b7 md wrench f05b8 md contacts outline f05b9 md microsoft xbox f05ba md microsoft xbox controller f05bb md microsoft xbox controller off f05bc md table furniture f05bd md sort alphabetical ascending f05be md firewire f05bf md sort alphabetical descending f05c0 md xml f05c1 md yeast f05c2 md database refresh f05c3 md youtube f05c4 md zip box f05c5 md surround sound f05c6 md vector rectangle f05c7 md playlist check f05c8 md format line style f05c9 md format line weight f05ca md translate f05cb md account voice f05cc md opacity f05ce md clock alert outline f05cf md human pregnant f05d0 md sticker circle outline f05d1 md scale balance f05d2 md card account details f05d3 md account multiple minus f05d4 md airplane landing f05d5 md airplane takeoff f05d6 md alert circle outline f05d7 md altimeter f05d8 md animation f05d9 md book minus f05da md book open page variant f05db md book plus f05dc md boombox f05dd md bullseye f05de md comment remove f05df md camera off f05e0 md check circle f05e1 md check circle outline f05e2 md candle f05e3 md chart bubble f05e4 md credit card off outline f05e5 md cup off f05e6 md copyright f05e7 md cursor text f05e8 md delete forever f05e9 md delete sweep f05ea md dice d20 outline f05eb md dice d4 outline f05ec md dice d8 outline f05ed md dice d6 outline f05ee md disc f05ef md email open outline f05f0 md email variant f05f1 md ev station f05f2 md food fork drink f05f3 md food off f05f4 md format title f05f5 md google maps f05f6 md heart pulse f05f7 md highway f05f8 md home map marker f05f9 md incognito f05fa md kettle f05fb md lock plus f05fc md exit to app f05fd md logout variant f05fe md music note bluetooth f05ff md music note bluetooth off f0600 md page first f0601 md page last f0602 md phone classic f0603 md priority high f0604 md priority low f0605 md qqchat f0606 md pool f0607 md rounded corner f0608 md rowing f0609 md saxophone f060a md signal variant f060b md stack exchange f060c md subdirectory arrow left f060d md subdirectory arrow right f060e md form textbox f060f md violin f0610 md microsoft visual studio f0611 md wechat f0612 md watermark f0613 md file hidden f0614 md application outline f0615 md arrow collapse f0616 md arrow expand f0617 md bowl mix f0618 md bridge f0619 md application edit outline f061a md chip f061b md content save settings f061c md dialpad f061d md book alphabet f061e md format horizontal align center f061f md format horizontal align left f0620 md format horizontal align right f0621 md format vertical align bottom f0622 md format vertical align center f0623 md format vertical align top f0624 md line scan f0625 md help circle outline f0626 md code json f0627 md lambda f0628 md matrix f0629 md meteor f062a md close circle multiple f062b md sigma lower f062c md source branch f062d md source merge f062e md tune f062f md webhook f0630 md account settings f0631 md account details f0632 md apple keyboard caps f0633 md apple keyboard command f0634 md apple keyboard control f0635 md apple keyboard option f0636 md apple keyboard shift f0637 md box shadow f0638 md cards f0639 md cards outline f063a md cards playing outline f063b md checkbox multiple blank circle f063c md checkbox multiple blank circle outline f063d md checkbox multiple marked circle f063e md checkbox multiple marked circle outline f063f md cloud sync f0640 md collage f0641 md directions fork f0642 md eraser variant f0643 md face man f0644 md face man profile f0645 md file tree f0646 md format annotation plus f0647 md gas cylinder f0648 md grease pencil f0649 md human female f064a md human greeting variant f064b md human handsdown f064c md human handsup f064d md human male f064e md information variant f064f md lead pencil f0650 md map marker minus f0651 md map marker plus f0652 md marker f0653 md message plus f0654 md microscope f0655 md move resize f0656 md move resize variant f0657 md paw off f0658 md phone minus f0659 md phone plus f065a md pot steam f065b md pot mix f065c md serial port f065d md shape circle plus f065e md shape polygon plus f065f md shape rectangle plus f0660 md shape square plus f0661 md skip next circle f0662 md skip next circle outline f0663 md skip previous circle f0664 md skip previous circle outline f0665 md spray f0666 md stop circle f0667 md stop circle outline f0668 md test tube f0669 md text shadow f066a md tune vertical f066b md cart off f066c md chart gantt f066d md chart scatter plot hexbin f066e md chart timeline f066f md discord f0670 md file restore f0671 md language c f0672 md language cpp f0673 md language xaml f0674 md creation f0675 md application cog f0676 md credit card plus outline f0677 md pot mix outline f0678 md bow tie f0679 md calendar range f067a md currency usd off f067b md flash red eye f067c md oar f067d md piano f067e md weather lightning rainy f067f md weather snowy rainy f0680 md yin yang f0681 md tower beach f0682 md tower fire f0683 md delete circle f0684 md dna f0685 md hamburger f0686 md gondola f0687 md inbox f0688 md reorder horizontal f0689 md reorder vertical f068a md shield home f068b md tag heart f068c md skull f068d md solid f068e md alarm snooze f068f md baby carriage f0690 md beaker outline f0691 md bomb f0692 md calendar question f0693 md camera burst f0694 md code tags check f0695 md circle multiple outline f0696 md crop rotate f0697 md developer board f0698 md piano off f0699 md skate off f069a md message star f069b md emoticon dead outline f069c md emoticon excited outline f069d md folder star f069e md format color text f069f md format section f06a0 md gradient vertical f06a1 md home outline f06a2 md message bulleted f06a3 md message bulleted off f06a4 md nuke f06a5 md power plug f06a6 md power plug off f06a7 md publish f06a8 md credit card marker f06a9 md robot f06aa md format rotate 90 f06ab md scanner f06ac md subway f06ad md timer sand empty f06ae md transit transfer f06af md unity f06b0 md update f06b1 md watch vibrate f06b2 md angular f06b3 md dolby f06b4 md emby f06b5 md lamp f06b6 md menu down outline f06b7 md menu up outline f06b8 md note multiple f06b9 md note multiple outline f06ba md plex f06bb md shield airplane f06bc md account edit f06bd md alert decagram f06be md all inclusive f06bf md angularjs f06c0 md arrow down box f06c1 md arrow left box f06c2 md arrow right box f06c3 md arrow up box f06c4 md asterisk f06c5 md bomb off f06c6 md bootstrap f06c7 md cards variant f06c8 md clipboard flow f06c9 md close outline f06ca md coffee outline f06cb md contacts f06cc md delete empty f06cd md earth box f06ce md earth box off f06cf md email alert f06d0 md eye outline f06d1 md eye off outline f06d2 md fast forward outline f06d3 md feather f06d4 md find replace f06d5 md flash outline f06d6 md format font f06d7 md format page break f06d8 md format pilcrow f06d9 md garage f06da md garage open f06db md card account details star outline f06dc md google keep f06dd md snowmobile f06de md heart half full f06df md heart half f06e0 md heart half outline f06e1 md hexagon multiple f06e2 md hook f06e3 md hook off f06e4 md infinity f06e5 md language swift f06e6 md language typescript f06e7 md laptop off f06e8 md lightbulb on f06e9 md lightbulb on outline f06ea md lock pattern f06eb md folder zip f06ec md magnify minus outline f06ed md magnify plus outline f06ee md mailbox f06ef md medical bag f06f0 md message settings f06f1 md message cog f06f2 md minus box outline f06f3 md network f06f4 md download network f06f5 md help network f06f6 md upload network f06f7 md npm f06f8 md nut f06f9 md octagram f06fa md page layout body f06fb md page layout footer f06fc md page layout header f06fd md page layout sidebar left f06fe md page layout sidebar right f06ff md pencil circle f0700 md pentagon outline f0701 md pentagon f0702 md pillar f0703 md pistol f0704 md plus box outline f0705 md plus outline f0706 md prescription f0707 md printer settings f0708 md react f0709 md restart f070a md rewind outline f070b md rhombus f070c md rhombus outline f070d md robot vacuum f070e md run f070f md search web f0710 md shovel f0711 md shovel off f0712 md signal 2g f0713 md signal 3g f0714 md signal 4g f0715 md signal hspa f0716 md signal hspa plus f0717 md snowflake f0718 md source commit f0719 md source commit end f071a md source commit end local f071b md source commit local f071c md source commit next local f071d md source commit start f071e md source commit start next local f071f md speaker wireless f0720 md stadium variant f0721 md svg f0722 md tag plus f0723 md tag remove f0724 md ticket percent f0725 md tilde f0726 md treasure chest f0727 md truck trailer f0728 md view parallel f0729 md view sequential f072a md washing machine f072b md webpack f072c md widgets f072d md nintendo wiiu f072e md arrow down bold f072f md arrow down bold box f0730 md arrow down bold box outline f0731 md arrow left bold f0732 md arrow left bold box f0733 md arrow left bold box outline f0734 md arrow right bold f0735 md arrow right bold box f0736 md arrow right bold box outline f0737 md arrow up bold f0738 md arrow up bold box f0739 md arrow up bold box outline f073a md cancel f073b md file account f073c md gesture double tap f073d md gesture swipe down f073e md gesture swipe left f073f md gesture swipe right f0740 md gesture swipe up f0741 md gesture tap f0742 md gesture two double tap f0743 md gesture two tap f0744 md humble bundle f0745 md kickstarter f0746 md netflix f0747 md microsoft onenote f0748 md wall sconce round f0749 md folder refresh f074a md vector radius f074b md microsoft xbox controller battery alert f074c md microsoft xbox controller battery empty f074d md microsoft xbox controller battery full f074e md microsoft xbox controller battery low f074f md microsoft xbox controller battery medium f0750 md microsoft xbox controller battery unknown f0751 md clipboard plus f0752 md file plus f0753 md format align bottom f0754 md format align middle f0755 md format align top f0756 md format list checks f0757 md format quote open f0758 md grid large f0759 md heart off f075a md music f075b md music off f075c md tab plus f075d md volume plus f075e md volume minus f075f md volume mute f0760 md unfold less vertical f0761 md unfold more vertical f0762 md taco f0763 md square outline f0764 md square f0765 md checkbox blank circle f0766 md checkbox blank circle outline f0767 md alert octagram f0768 md atom f0769 md ceiling light f076a md chart bar stacked f076b md chart line stacked f076c md decagram f076d md decagram outline f076e md dice multiple f076f md dice d10 outline f0770 md folder open f0771 md guitar acoustic f0772 md loading f0773 md lock reset f0774 md ninja f0775 md octagram outline f0776 md pencil circle outline f0777 md selection off f0778 md set all f0779 md set center f077a md set center right f077b md set left f077c md set left center f077d md set left right f077e md set none f077f md set right f0780 md shield half full f0781 md sign direction f0782 md sign text f0783 md signal off f0784 md square root f0785 md sticker emoji f0786 md summit f0787 md sword cross f0788 md truck fast f0789 md web check f078a md cast off f078b md help box f078c md timer sand full f078d md waves f078e md alarm bell f078f md alarm light f0790 md video switch outline f0791 md check decagram f0792 md arrow collapse down f0793 md arrow collapse left f0794 md arrow collapse right f0795 md arrow collapse up f0796 md arrow expand down f0797 md arrow expand left f0798 md arrow expand right f0799 md arrow expand up f079a md book lock f079b md book lock open f079c md bus articulated end f079d md bus articulated front f079e md bus double decker f079f md bus school f07a0 md bus side f07a1 md camera gopro f07a2 md camera metering center f07a3 md camera metering matrix f07a4 md camera metering partial f07a5 md camera metering spot f07a6 md cannabis f07a7 md car convertible f07a8 md car estate f07a9 md car hatchback f07aa md car pickup f07ab md car side f07ac md car sports f07ad md caravan f07ae md cctv f07af md chart donut f07b0 md chart donut variant f07b1 md chart line variant f07b2 md chili hot f07b3 md chili medium f07b4 md chili mild f07b5 md cloud braces f07b6 md cloud tags f07b7 md console line f07b8 md corn f07b9 md folder zip outline f07ba md currency cny f07bb md currency eth f07bc md currency jpy f07bd md currency krw f07be md currency sign f07bf md currency twd f07c0 md desktop classic f07c1 md dip switch f07c2 md donkey f07c3 md dots horizontal circle f07c4 md dots vertical circle f07c5 md ear hearing f07c6 md elephant f07c7 md storefront f07c8 md food croissant f07c9 md forklift f07ca md fuel f07cb md gesture f07cc md google analytics f07cd md google assistant f07ce md headphones off f07cf md high definition f07d0 md home assistant f07d1 md home automation f07d2 md home circle f07d3 md language go f07d4 md language r f07d5 md lava lamp f07d6 md led strip f07d7 md locker f07d8 md locker multiple f07d9 md map marker outline f07da md metronome f07db md metronome tick f07dc md micro sd f07dd md facebook gaming f07de md movie roll f07df md mushroom f07e0 md mushroom outline f07e1 md nintendo switch f07e2 md null f07e3 md passport f07e4 md molecule co2 f07e5 md pipe f07e6 md pipe disconnected f07e7 md power socket eu f07e8 md power socket uk f07e9 md power socket us f07ea md rice f07eb md ring f07ec md sass f07ed md send lock f07ee md soy sauce f07ef md standard definition f07f0 md surround sound 2 0 f07f1 md surround sound 3 1 f07f2 md surround sound 5 1 f07f3 md surround sound 7 1 f07f4 md television classic f07f5 md form textbox password f07f6 md thought bubble f07f7 md thought bubble outline f07f8 md trackpad f07f9 md ultra high definition f07fa md van passenger f07fb md van utility f07fc md vanish f07fd md video 3d f07fe md wall f07ff md xmpp f0800 md account multiple plus outline f0801 md account plus outline f0802 md credit card wireless f0803 md account music f0804 md atlassian f0805 md microsoft azure f0806 md basketball f0807 md battery charging wireless f0808 md battery charging wireless 10 f0809 md battery charging wireless 20 f080a md battery charging wireless 30 f080b md battery charging wireless 40 f080c md battery charging wireless 50 f080d md battery charging wireless 60 f080e md battery charging wireless 70 f080f md battery charging wireless 80 f0810 md battery charging wireless 90 f0811 md battery charging wireless alert f0812 md battery charging wireless outline f0813 md bitcoin f0814 md briefcase outline f0815 md cellphone wireless f0816 md clover f0817 md comment question f0818 md content save outline f0819 md delete restore f081a md door f081b md door closed f081c md door open f081d md fan off f081e md file percent f081f md finance f0820 md lightning bolt circle f0821 md floor plan f0822 md forum outline f0823 md golf f0824 md google home f0825 md guy fawkes mask f0826 md home account f0827 md home heart f0828 md hot tub f0829 md hulu f082a md ice cream f082b md image off f082c md karate f082d md ladybug f082e md notebook f082f md phone return f0830 md poker chip f0831 md shape f0832 md shape outline f0833 md ship wheel f0834 md soccer field f0835 md table column f0836 md table of contents f0837 md table row f0838 md table settings f0839 md television box f083a md television classic off f083b md television off f083c md tow truck f083d md upload multiple f083e md video 4k box f083f md video input antenna f0840 md video input component f0841 md video input hdmi f0842 md video input svideo f0843 md view dashboard variant f0844 md vuejs f0845 md xamarin f0846 md human male board poll f0847 md youtube studio f0848 md youtube gaming f0849 md account group f084a md camera switch outline f084b md airport f084c md arrow collapse horizontal f084d md arrow collapse vertical f084e md arrow expand horizontal f084f md arrow expand vertical f0850 md augmented reality f0851 md badminton f0852 md baseball f0853 md baseball bat f0854 md bottle wine f0855 md check outline f0856 md checkbox intermediate f0857 md chess king f0858 md chess knight f0859 md chess pawn f085a md chess queen f085b md chess rook f085c md chess bishop f085d md clipboard pulse f085e md clipboard pulse outline f085f md comment multiple f0860 md comment text multiple f0861 md comment text multiple outline f0862 md crane f0863 md curling f0864 md currency bdt f0865 md currency kzt f0866 md database search f0867 md dice d12 outline f0868 md docker f0869 md doorbell video f086a md ethereum f086b md eye plus f086c md eye plus outline f086d md eye settings f086e md eye settings outline f086f md file question f0870 md folder network f0871 md function variant f0872 md garage alert f0873 md gauge empty f0874 md gauge full f0875 md gauge low f0876 md glass wine f0877 md graphql f0878 md high definition box f0879 md hockey puck f087a md hockey sticks f087b md home alert f087c md image plus f087d md jquery f087e md lifebuoy f087f md mixed reality f0880 md nativescript f0881 md onepassword f0882 md patreon f0883 md close circle multiple outline f0884 md peace f0885 md phone rotate landscape f0886 md phone rotate portrait f0887 md pier f0888 md pier crane f0889 md pipe leak f088a md piston f088b md play network f088c md reminder f088d md room service f088e md salesforce f088f md shield account f0890 md human male board f0891 md thermostat box f0892 md tractor f0893 md vector ellipse f0894 md virtual reality f0895 md watch export variant f0896 md watch import variant f0897 md watch variant f0898 md weather hurricane f0899 md account heart f089a md alien f089b md anvil f089c md battery charging 10 f089d md battery charging 50 f089e md battery charging 70 f089f md battery charging outline f08a0 md bed empty f08a1 md border all variant f08a2 md border bottom variant f08a3 md border left variant f08a4 md border none variant f08a5 md border right variant f08a6 md border top variant f08a7 md calendar edit f08a8 md clipboard check outline f08a9 md console network f08aa md file compare f08ab md fire truck f08ac md folder key f08ad md folder key network f08ae md expansion card f08af md kayaking f08b0 md inbox multiple f08b1 md language lua f08b2 md lock smart f08b3 md microphone minus f08b4 md microphone plus f08b5 md palette swatch f08b6 md periodic table f08b7 md pickaxe f08b8 md qrcode edit f08b9 md remote desktop f08ba md sausage f08bb md cog outline f08bc md signal cellular 1 f08bd md signal cellular 2 f08be md signal cellular 3 f08bf md signal cellular outline f08c0 md ssh f08c1 md swap horizontal variant f08c2 md swap vertical variant f08c3 md tooth f08c4 md train variant f08c5 md account multiple check f08c6 md application f08c7 md arch f08c8 md axe f08c9 md bullseye arrow f08ca md bus clock f08cb md camera account f08cc md camera image f08cd md car limousine f08ce md cards club f08cf md cards diamond f08d0 md heart f08d1 md cards spade f08d2 md cellphone text f08d3 md cellphone message f08d4 md chart multiline f08d5 md circle edit outline f08d6 md cogs f08d7 md credit card settings outline f08d8 md death star f08d9 md death star variant f08da md debian f08db md fedora f08dc md file undo f08dd md floor lamp f08de md folder edit f08df md format columns f08e0 md freebsd f08e1 md gate and f08e2 md gate nand f08e3 md gate nor f08e4 md gate not f08e5 md gate or f08e6 md gate xnor f08e7 md gate xor f08e8 md gentoo f08e9 md globe model f08ea md hammer f08eb md home lock f08ec md home lock open f08ed md linux mint f08ee md lock alert f08ef md lock question f08f0 md map marker distance f08f1 md midi f08f2 md midi port f08f3 md nas f08f4 md network strength 1 f08f5 md network strength 1 alert f08f6 md network strength 2 f08f7 md network strength 2 alert f08f8 md network strength 3 f08f9 md network strength 3 alert f08fa md network strength 4 f08fb md network strength 4 alert f08fc md network strength off f08fd md network strength off outline f08fe md network strength outline f08ff md play speed f0900 md playlist edit f0901 md power cycle f0902 md power off f0903 md power on f0904 md power sleep f0905 md power socket au f0906 md power standby f0907 md rabbit f0908 md robot vacuum variant f0909 md satellite uplink f090a md scanner off f090b md book minus multiple outline f090c md square edit outline f090d md sort numeric ascending variant f090e md steering off f090f md table search f0910 md tag minus f0911 md test tube empty f0912 md test tube off f0913 md ticket outline f0914 md track light f0915 md transition f0916 md transition masked f0917 md tumble dryer f0918 md file refresh f0919 md video account f091a md video image f091b md video stabilization f091c md wall sconce f091d md wall sconce flat f091e md wall sconce round variant f091f md wifi strength 1 f0920 md wifi strength 1 alert f0921 md wifi strength 1 lock f0922 md wifi strength 2 f0923 md wifi strength 2 alert f0924 md wifi strength 2 lock f0925 md wifi strength 3 f0926 md wifi strength 3 alert f0927 md wifi strength 3 lock f0928 md wifi strength 4 f0929 md wifi strength 4 alert f092a md wifi strength 4 lock f092b md wifi strength alert outline f092c md wifi strength lock outline f092d md wifi strength off f092e md wifi strength off outline f092f md wifi strength outline f0930 md pin off outline f0931 md pin outline f0932 md share outline f0933 md trackpad lock f0934 md account box multiple f0935 md account search outline f0936 md account filter f0937 md angle acute f0938 md angle obtuse f0939 md angle right f093a md animation play f093b md arrow split horizontal f093c md arrow split vertical f093d md audio video f093e md battery 10 bluetooth f093f md battery 20 bluetooth f0940 md battery 30 bluetooth f0941 md battery 40 bluetooth f0942 md battery 50 bluetooth f0943 md battery 60 bluetooth f0944 md battery 70 bluetooth f0945 md battery 80 bluetooth f0946 md battery 90 bluetooth f0947 md battery alert bluetooth f0948 md battery bluetooth f0949 md battery bluetooth variant f094a md battery unknown bluetooth f094b md dharmachakra f094c md calendar search f094d md cellphone remove f094e md cellphone key f094f md cellphone lock f0950 md cellphone off f0951 md cellphone cog f0952 md cellphone sound f0953 md cross f0954 md clock f0955 md clock alert f0956 md cloud search f0957 md cloud search outline f0958 md cordova f0959 md cryengine f095a md cupcake f095b md sine wave f095c md current dc f095d md database import f095e md database export f095f md desk lamp f0960 md disc player f0961 md email search f0962 md email search outline f0963 md exponent f0964 md exponent box f0965 md file download f0966 md file download outline f0967 md firebase f0968 md folder search f0969 md folder search outline f096a md format list checkbox f096b md fountain f096c md google fit f096d md greater than f096e md greater than or equal f096f md hard hat f0970 md headphones bluetooth f0971 md heart circle f0972 md heart circle outline f0973 md om f0974 md home minus f0975 md home plus f0976 md image outline f0977 md image search f0978 md image search outline f0979 md star crescent f097a md star david f097b md keyboard outline f097c md less than f097d md less than or equal f097e md light switch f097f md lock clock f0980 md magnify close f0981 md map minus f0982 md map outline f0983 md map plus f0984 md map search f0985 md map search outline f0986 md material design f0987 md medal f0988 md microsoft dynamics 365 f0989 md monitor cellphone f098a md monitor cellphone star f098b md mouse bluetooth f098c md muffin f098d md not equal f098e md not equal variant f098f md order bool ascending variant f0990 md order bool descending variant f0991 md office building f0992 md plus minus f0993 md plus minus box f0994 md podcast f0995 md progress check f0996 md progress clock f0997 md progress download f0998 md progress upload f0999 md qi f099a md record player f099b md restore f099c md shield off outline f099d md shield lock f099e md shield off f099f md set top box f09a0 md shower f09a1 md shower head f09a2 md speaker bluetooth f09a3 md square root box f09a4 md star circle outline f09a5 md star face f09a6 md table merge cells f09a7 md tablet cellphone f09a8 md text f09a9 md text short f09aa md text long f09ab md toilet f09ac md toolbox f09ad md toolbox outline f09ae md tournament f09af md two factor authentication f09b0 md umbrella closed f09b1 md unreal f09b2 md video minus f09b3 md video plus f09b4 md volleyball f09b5 md weight pound f09b6 md whistle f09b7 md arrow bottom left bold outline f09b8 md arrow bottom left thick f09b9 md arrow bottom right bold outline f09ba md arrow bottom right thick f09bb md arrow decision f09bc md arrow decision auto f09bd md arrow decision auto outline f09be md arrow decision outline f09bf md arrow down bold outline f09c0 md arrow left bold outline f09c1 md arrow left right bold outline f09c2 md arrow right bold outline f09c3 md arrow top left bold outline f09c4 md arrow top left thick f09c5 md arrow top right bold outline f09c6 md arrow top right thick f09c7 md arrow up bold outline f09c8 md arrow up down bold outline f09c9 md ballot f09ca md ballot outline f09cb md betamax f09cc md bookmark minus f09cd md bookmark minus outline f09ce md bookmark off f09cf md bookmark off outline f09d0 md braille f09d1 md brain f09d2 md calendar heart f09d3 md calendar star f09d4 md cassette f09d5 md cellphone arrow down f09d6 md chevron down box f09d7 md chevron down box outline f09d8 md chevron left box f09d9 md chevron left box outline f09da md chevron right box f09db md chevron right box outline f09dc md chevron up box f09dd md chevron up box outline f09de md circle medium f09df md circle small f09e0 md cloud alert f09e1 md comment arrow left f09e2 md comment arrow left outline f09e3 md comment arrow right f09e4 md comment arrow right outline f09e5 md comment plus f09e6 md currency php f09e7 md delete outline f09e8 md desktop mac dashboard f09e9 md download multiple f09ea md eight track f09eb md email plus f09ec md email plus outline f09ed md text box outline f09ee md file document outline f09ef md floppy variant f09f0 md flower outline f09f1 md flower tulip f09f2 md flower tulip outline f09f3 md format font size decrease f09f4 md format font size increase f09f5 md ghost off f09f6 md google lens f09f7 md google spreadsheet f09f8 md image move f09f9 md keyboard settings f09fa md keyboard settings outline f09fb md knife f09fc md knife military f09fd md layers off outline f09fe md layers outline f09ff md lighthouse f0a00 md lighthouse on f0a01 md map legend f0a02 md menu left outline f0a03 md menu right outline f0a04 md message alert outline f0a05 md mini sd f0a06 md minidisc f0a07 md monitor dashboard f0a08 md pirate f0a09 md pokemon go f0a0a md powershell f0a0b md printer wireless f0a0c md quality low f0a0d md quality medium f0a0e md reflect horizontal f0a0f md reflect vertical f0a10 md rhombus medium f0a11 md rhombus split f0a12 md shield account outline f0a13 md square medium f0a14 md square medium outline f0a15 md square small f0a16 md subtitles f0a17 md subtitles outline f0a18 md table border f0a19 md toggle switch off outline f0a1a md toggle switch outline f0a1b md vhs f0a1c md video vintage f0a1d md view dashboard outline f0a1e md microsoft visual studio code f0a1f md vote f0a20 md vote outline f0a21 md microsoft windows classic f0a22 md microsoft xbox controller battery charging f0a23 md zip disk f0a24 md aspect ratio f0a25 md babel f0a26 md balloon f0a27 md bank transfer f0a28 md bank transfer in f0a29 md bank transfer out f0a2a md briefcase minus f0a2b md briefcase plus f0a2c md briefcase remove f0a2d md briefcase search f0a2e md bug check f0a2f md bug check outline f0a30 md bug outline f0a31 md calendar alert f0a32 md calendar multiselect f0a33 md calendar week f0a34 md calendar week begin f0a35 md cellphone screenshot f0a36 md city variant f0a37 md city variant outline f0a38 md clipboard text outline f0a39 md cloud question f0a3a md comment eye f0a3b md comment eye outline f0a3c md comment search f0a3d md comment search outline f0a3e md contain f0a3f md contain end f0a40 md contain start f0a41 md dlna f0a42 md doctor f0a43 md dog f0a44 md dog side f0a45 md ear hearing off f0a46 md engine off f0a47 md engine off outline f0a48 md exit run f0a49 md feature search f0a4a md feature search outline f0a4b md file alert f0a4c md file alert outline f0a4d md file upload f0a4e md file upload outline f0a4f md hand front right f0a50 md hand okay f0a51 md hand peace f0a52 md hand peace variant f0a53 md hand pointing down f0a54 md hand pointing left f0a55 md hand pointing up f0a56 md heart multiple f0a57 md heart multiple outline f0a58 md horseshoe f0a59 md human female boy f0a5a md human female female f0a5b md human female girl f0a5c md human male boy f0a5d md human male girl f0a5e md human male male f0a5f md ip f0a60 md ip network f0a61 md litecoin f0a62 md magnify minus cursor f0a63 md magnify plus cursor f0a64 md menu swap f0a65 md menu swap outline f0a66 md puzzle outline f0a67 md registered trademark f0a68 md resize f0a69 md router wireless settings f0a6a md safe f0a6b md scissors cutting f0a6c md select drag f0a6d md selection drag f0a6e md settings helper f0a6f md signal 5g f0a70 md silverware fork knife f0a71 md smog f0a72 md solar power f0a73 md star box f0a74 md star box outline f0a75 md table plus f0a76 md table remove f0a77 md target variant f0a78 md trademark f0a79 md trash can f0a7a md trash can outline f0a7b md tshirt crew f0a7c md tshirt v f0a7d md zodiac aquarius f0a7e md zodiac aries f0a7f md zodiac cancer f0a80 md zodiac capricorn f0a81 md zodiac gemini f0a82 md zodiac leo f0a83 md zodiac libra f0a84 md zodiac pisces f0a85 md zodiac sagittarius f0a86 md zodiac scorpio f0a87 md zodiac taurus f0a88 md zodiac virgo f0a89 md account child f0a8a md account child circle f0a8b md account supervisor f0a8c md account supervisor circle f0a8d md ampersand f0a8e md web off f0a8f md animation outline f0a90 md animation play outline f0a91 md bell off outline f0a92 md bell plus outline f0a93 md bell sleep outline f0a94 md book minus multiple f0a95 md book plus multiple f0a96 md book remove multiple f0a97 md book remove f0a98 md briefcase edit f0a99 md bus alert f0a9a md calculator variant f0a9b md caps lock f0a9c md cash refund f0a9d md checkbook f0a9e md circle slice 1 f0a9f md circle slice 2 f0aa0 md circle slice 3 f0aa1 md circle slice 4 f0aa2 md circle slice 5 f0aa3 md circle slice 6 f0aa4 md circle slice 7 f0aa5 md circle slice 8 f0aa6 md collapse all f0aa7 md collapse all outline f0aa8 md credit card refund outline f0aa9 md database check f0aaa md database lock f0aab md desktop tower monitor f0aac md dishwasher f0aad md dog service f0aae md dot net f0aaf md egg f0ab0 md egg easter f0ab1 md email check f0ab2 md email check outline f0ab3 md et f0ab4 md expand all f0ab5 md expand all outline f0ab6 md file cabinet f0ab7 md text box multiple f0ab8 md text box multiple outline f0ab9 md file move f0aba md folder clock f0abb md folder clock outline f0abc md format annotation minus f0abd md gesture pinch f0abe md gesture spread f0abf md gesture swipe horizontal f0ac0 md gesture swipe vertical f0ac1 md hail f0ac2 md helicopter f0ac3 md hexagon slice 1 f0ac4 md hexagon slice 2 f0ac5 md hexagon slice 3 f0ac6 md hexagon slice 4 f0ac7 md hexagon slice 5 f0ac8 md hexagon slice 6 f0ac9 md hexagram f0aca md hexagram outline f0acb md label off f0acc md label off outline f0acd md label variant f0ace md label variant outline f0acf md language ruby on rails f0ad0 md laravel f0ad1 md mastodon f0ad2 md sort numeric descending variant f0ad3 md minus circle multiple outline f0ad4 md music circle outline f0ad5 md pinwheel f0ad6 md pinwheel outline f0ad7 md radiator disabled f0ad8 md radiator off f0ad9 md select compare f0ada md shield plus f0adb md shield plus outline f0adc md shield remove f0add md shield remove outline f0ade md book plus multiple outline f0adf md sina weibo f0ae0 md spray bottle f0ae1 md squeegee f0ae2 md star four points f0ae3 md star four points outline f0ae4 md star three points f0ae5 md star three points outline f0ae6 md symfony f0ae7 md variable f0ae8 md vector bezier f0ae9 md wiper f0aea md z wave f0aeb md zend f0aec md account minus outline f0aed md account remove outline f0aee md alpha a f0aef md alpha b f0af0 md alpha c f0af1 md alpha d f0af2 md alpha e f0af3 md alpha f f0af4 md alpha g f0af5 md alpha h f0af7 md alpha j f0af8 md alpha k f0afa md alpha m f0afb md alpha n f0afd md alpha p f0afe md alpha q f0aff md alpha r f0b00 md alpha s f0b01 md alpha t f0b02 md alpha u f0b04 md alpha w f0b06 md alpha y f0b07 md alpha z f0b08 md alpha a box f0b09 md alpha b box f0b0a md alpha c box f0b0b md alpha d box f0b0c md alpha e box f0b0d md alpha f box f0b0e md alpha g box f0b0f md alpha h box f0b10 md alpha i box f0b11 md alpha j box f0b12 md alpha k box f0b13 md alpha l box f0b14 md alpha m box f0b15 md alpha n box f0b16 md alpha o box f0b17 md alpha p box f0b18 md alpha q box f0b19 md alpha r box f0b1a md alpha s box f0b1b md alpha t box f0b1c md alpha u box f0b1d md alpha v box f0b1e md alpha w box f0b1f md alpha x box f0b20 md alpha y box f0b21 md alpha z box f0b22 md bulldozer f0b23 md bullhorn outline f0b24 md calendar export f0b25 md calendar import f0b26 md chevron down circle f0b27 md chevron down circle outline f0b28 md chevron left circle f0b29 md chevron left circle outline f0b2a md chevron right circle f0b2b md chevron right circle outline f0b2c md chevron up circle f0b2d md chevron up circle outline f0b2e md content save settings outline f0b2f md crystal ball f0b30 md ember f0b31 md facebook workplace f0b32 md file replace f0b33 md file replace outline f0b34 md format letter case f0b35 md format letter case lower f0b36 md format letter case upper f0b37 md language java f0b38 md circle multiple f0b39 md alpha o f0b3a md numeric 1 f0b3b md numeric 2 f0b3c md numeric 3 f0b3d md numeric 4 f0b3e md numeric 5 f0b3f md numeric 6 f0b40 md numeric 7 f0b41 md numeric 8 f0b42 md numeric 9 f0b43 md origin f0b44 md resistor f0b45 md resistor nodes f0b46 md robot industrial f0b47 md shoe formal f0b48 md shoe heel f0b49 md silo f0b4a md box cutter off f0b4b md tab minus f0b4c md tab remove f0b4d md tape measure f0b4e md telescope f0b4f md yahoo f0b50 md account alert outline f0b51 md account arrow left f0b52 md account arrow left outline f0b53 md account arrow right f0b54 md account arrow right outline f0b55 md account circle outline f0b56 md account clock f0b57 md account clock outline f0b58 md account group outline f0b59 md account question f0b5a md account question outline f0b5b md artstation f0b5c md backspace outline f0b5d md barley off f0b5e md barn f0b5f md bat f0b60 md application settings f0b61 md billiards f0b62 md billiards rack f0b63 md book open outline f0b64 md book outline f0b65 md boxing glove f0b66 md calendar blank outline f0b67 md calendar outline f0b68 md calendar range outline f0b69 md camera control f0b6a md camera enhance outline f0b6b md car door f0b6c md car electric f0b6d md car key f0b6e md car multiple f0b6f md card f0b70 md card bulleted f0b71 md card bulleted off f0b72 md card bulleted off outline f0b73 md card bulleted outline f0b74 md card bulleted settings f0b75 md card bulleted settings outline f0b76 md card outline f0b77 md card text f0b78 md card text outline f0b79 md chat f0b7a md chat alert f0b7b md chat processing f0b7c md chef hat f0b7d md cloud download outline f0b7e md cloud upload outline f0b7f md coffin f0b80 md compass off f0b81 md compass off outline f0b82 md controller classic f0b83 md controller classic outline f0b84 md cube scan f0b85 md currency brl f0b86 md database edit f0b87 md deathly hallows f0b88 md delete circle outline f0b89 md delete forever outline f0b8a md diamond f0b8b md diamond outline f0b8c md dns outline f0b8d md dots horizontal circle outline f0b8e md dots vertical circle outline f0b8f md download outline f0b90 md drag variant f0b91 md eject outline f0b92 md email mark as unread f0b93 md export variant f0b94 md eye circle f0b95 md eye circle outline f0b96 md face man outline f0b97 md file find outline f0b98 md file remove f0b99 md flag minus f0b9a md flag plus f0b9b md flag remove f0b9c md folder account outline f0b9d md folder plus outline f0b9e md folder remove outline f0b9f md folder star outline f0ba0 md gitlab f0ba1 md gog f0ba2 md grave stone f0ba3 md halloween f0ba4 md hat fedora f0ba5 md help rhombus f0ba6 md help rhombus outline f0ba7 md home variant outline f0ba8 md inbox multiple outline f0ba9 md library shelves f0baa md mapbox f0bab md menu open f0bac md molecule f0bad md one up f0bae md open source initiative f0baf md pac man f0bb0 md page next f0bb1 md page next outline f0bb2 md page previous f0bb3 md page previous outline f0bb4 md pan f0bb5 md pan bottom left f0bb6 md pan bottom right f0bb7 md pan down f0bb8 md pan horizontal f0bb9 md pan left f0bba md pan right f0bbb md pan top left f0bbc md pan top right f0bbd md pan up f0bbe md pan vertical f0bbf md pumpkin f0bc0 md rollupjs f0bc1 md script f0bc2 md script text f0bc3 md script text outline f0bc4 md shield key f0bc5 md shield key outline f0bc6 md skull crossbones f0bc7 md skull crossbones outline f0bc8 md skull outline f0bc9 md space invaders f0bca md spider web f0bcb md view split horizontal f0bcc md view split vertical f0bcd md swap horizontal bold f0bce md swap vertical bold f0bcf md tag heart outline f0bd0 md target account f0bd1 md timeline f0bd2 md timeline outline f0bd3 md timeline text f0bd4 md timeline text outline f0bd5 md tooltip image outline f0bd6 md tooltip plus f0bd7 md tooltip text outline f0bd8 md train car f0bd9 md triforce f0bda md ubisoft f0bdb md video off outline f0bdc md video outline f0bdd md wallet outline f0bde md waze f0bdf md wrap disabled f0be0 md wrench outline f0be1 md access point network off f0be2 md account check outline f0be3 md account heart outline f0be4 md account key outline f0be5 md account multiple minus outline f0be6 md account network outline f0be7 md account off outline f0be8 md account star outline f0be9 md airbag f0bea md alarm light outline f0beb md alpha a box outline f0bec md alpha a circle f0bed md alpha a circle outline f0bee md alpha b box outline f0bef md alpha b circle f0bf0 md alpha b circle outline f0bf1 md alpha c box outline f0bf2 md alpha c circle f0bf3 md alpha c circle outline f0bf4 md alpha d box outline f0bf5 md alpha d circle f0bf6 md alpha d circle outline f0bf7 md alpha e box outline f0bf8 md alpha e circle f0bf9 md alpha e circle outline f0bfa md alpha f box outline f0bfb md alpha f circle f0bfc md alpha f circle outline f0bfd md alpha g box outline f0bfe md alpha g circle f0bff md alpha g circle outline f0c00 md alpha h box outline f0c01 md alpha h circle f0c02 md alpha h circle outline f0c03 md alpha i box outline f0c04 md alpha i circle f0c05 md alpha i circle outline f0c06 md alpha j box outline f0c07 md alpha j circle f0c08 md alpha j circle outline f0c09 md alpha k box outline f0c0a md alpha k circle f0c0b md alpha k circle outline f0c0c md alpha l box outline f0c0d md alpha l circle f0c0e md alpha l circle outline f0c0f md alpha m box outline f0c10 md alpha m circle f0c11 md alpha m circle outline f0c12 md alpha n box outline f0c13 md alpha n circle f0c14 md alpha n circle outline f0c15 md alpha o box outline f0c18 md alpha p box outline f0c19 md alpha p circle f0c1a md alpha p circle outline f0c1b md alpha q box outline f0c1c md alpha q circle f0c1d md alpha q circle outline f0c1e md alpha r box outline f0c1f md alpha r circle f0c20 md alpha r circle outline f0c21 md alpha s box outline f0c22 md alpha s circle f0c23 md alpha s circle outline f0c24 md alpha t box outline f0c25 md alpha t circle f0c26 md alpha t circle outline f0c27 md alpha u box outline f0c28 md alpha u circle f0c29 md alpha u circle outline f0c2a md alpha v box outline f0c2b md alpha v circle f0c2c md alpha v circle outline f0c2d md alpha w box outline f0c2e md alpha w circle f0c2f md alpha w circle outline f0c30 md alpha x box outline f0c31 md alpha x circle f0c32 md alpha x circle outline f0c33 md alpha y box outline f0c34 md alpha y circle f0c35 md alpha y circle outline f0c36 md alpha z box outline f0c37 md alpha z circle f0c38 md alpha z circle outline f0c39 md ballot recount f0c3a md ballot recount outline f0c3b md basketball hoop f0c3c md basketball hoop outline f0c3d md briefcase download outline f0c3e md briefcase edit outline f0c3f md briefcase minus outline f0c40 md briefcase plus outline f0c41 md briefcase remove outline f0c42 md briefcase search outline f0c43 md briefcase upload outline f0c44 md calendar check outline f0c45 md calendar remove outline f0c46 md calendar text outline f0c47 md car brake abs f0c48 md car brake alert f0c49 md car esp f0c4a md car light dimmed f0c4b md car light fog f0c4c md car light high f0c4d md car tire alert f0c4e md cart arrow right f0c4f md charity f0c50 md chart bell curve f0c51 md checkbox multiple outline f0c52 md checkbox outline f0c53 md check network f0c54 md check network outline f0c55 md clipboard account outline f0c56 md clipboard arrow down outline f0c57 md clipboard arrow up f0c58 md clipboard arrow up outline f0c59 md clipboard play f0c5a md clipboard play outline f0c5b md clipboard text play f0c5c md clipboard text play outline f0c5d md close box multiple f0c5e md close box multiple outline f0c5f md close network outline f0c60 md console network outline f0c61 md currency ils f0c62 md delete sweep outline f0c63 md diameter f0c64 md diameter outline f0c65 md diameter variant f0c66 md download network outline f0c67 md dump truck f0c68 md emoticon f0c69 md emoticon angry f0c6a md emoticon angry outline f0c6b md emoticon cool f0c6c md emoticon cry f0c6d md emoticon cry outline f0c6e md emoticon dead f0c6f md emoticon devil f0c70 md emoticon excited f0c71 md emoticon happy f0c72 md emoticon kiss f0c73 md emoticon kiss outline f0c74 md emoticon neutral f0c75 md emoticon poop outline f0c76 md emoticon sad f0c77 md emoticon tongue outline f0c78 md emoticon wink f0c79 md emoticon wink outline f0c7a md eslint f0c7b md face recognition f0c7c md file search f0c7d md file search outline f0c7e md file table f0c7f md file table outline f0c80 md folder key network outline f0c81 md folder network outline f0c82 md folder text f0c83 md folder text outline f0c84 md food apple outline f0c85 md fuse f0c86 md fuse blade f0c87 md google ads f0c88 md google street view f0c89 md hazard lights f0c8a md help network outline f0c8b md application brackets f0c8c md application brackets outline f0c8d md image size select actual f0c8e md image size select large f0c8f md image size select small f0c90 md ip network outline f0c91 md ipod f0c92 md language haskell f0c93 md leaf maple f0c94 md link plus f0c95 md map marker check f0c96 md math cos f0c97 md math sin f0c98 md math tan f0c99 md microwave f0c9a md minus network outline f0c9b md network off f0c9c md network off outline f0c9d md network outline f0c9e md alpha o circle f0c9f md alpha o circle outline f0ca0 md numeric 1 circle f0ca1 md numeric 1 circle outline f0ca2 md numeric 2 circle f0ca3 md numeric 2 circle outline f0ca4 md numeric 3 circle f0ca5 md numeric 3 circle outline f0ca6 md numeric 4 circle f0ca7 md numeric 4 circle outline f0ca8 md numeric 5 circle f0ca9 md numeric 5 circle outline f0caa md numeric 6 circle f0cab md numeric 6 circle outline f0cac md numeric 7 circle f0cad md numeric 7 circle outline f0cae md numeric 8 circle f0caf md numeric 8 circle outline f0cb0 md numeric 9 circle f0cb1 md numeric 9 circle outline f0cb2 md numeric 9 plus circle f0cb3 md numeric 9 plus circle outline f0cb4 md parachute f0cb5 md parachute outline f0cb6 md pencil outline f0cb7 md play network outline f0cb8 md playlist music f0cb9 md playlist music outline f0cba md plus network outline f0cbb md postage stamp f0cbc md progress alert f0cbd md progress wrench f0cbe md radio am f0cbf md radio fm f0cc0 md radius f0cc1 md radius outline f0cc2 md ruler square f0cc3 md seat f0cc4 md seat outline f0cc5 md seatbelt f0cc6 md sheep f0cc7 md shield airplane outline f0cc8 md shield check outline f0cc9 md shield cross f0cca md shield cross outline f0ccb md shield home outline f0ccc md shield lock outline f0ccd md sort variant lock f0cce md sort variant lock open f0ccf md source repository f0cd0 md source repository multiple f0cd1 md spa f0cd2 md spa outline f0cd3 md toaster oven f0cd4 md truck check f0cd5 md turnstile f0cd6 md turnstile outline f0cd7 md turtle f0cd8 md upload network outline f0cd9 md vibrate off f0cda md watch vibrate off f0cdb md arrow down circle f0cdc md arrow down circle outline f0cdd md arrow left circle f0cde md arrow left circle outline f0cdf md arrow right circle f0ce0 md arrow right circle outline f0ce1 md arrow up circle f0ce2 md arrow up circle outline f0ce3 md account tie f0ce4 md alert box outline f0ce5 md alert decagram outline f0ce6 md alert octagon outline f0ce7 md alert octagram outline f0ce8 md ammunition f0ce9 md account music outline f0cea md beaker f0ceb md blender f0cec md blood bag f0ced md cross bolnisi f0cee md bread slice f0cef md bread slice outline f0cf0 md briefcase account f0cf1 md briefcase account outline f0cf2 md brightness percent f0cf3 md bullet f0cf4 md cash register f0cf5 md cross celtic f0cf6 md cross outline f0cf7 md clipboard alert outline f0cf8 md clipboard arrow left outline f0cf9 md clipboard arrow right f0cfa md clipboard arrow right outline f0cfb md content save edit f0cfc md content save edit outline f0cfd md cursor default click f0cfe md cursor default click outline f0cff md database sync f0d00 md database remove f0d01 md database settings f0d02 md drama masks f0d03 md email box f0d04 md eye check f0d05 md eye check outline f0d06 md fast forward 30 f0d07 md order alphabetical descending f0d08 md flower poppy f0d09 md folder pound f0d0a md folder pound outline f0d0b md folder sync f0d0c md folder sync outline f0d0d md format list numbered rtl f0d0e md format text wrapping clip f0d0f md format text wrapping overflow f0d10 md format text wrapping wrap f0d11 md format textbox f0d12 md fountain pen f0d13 md fountain pen tip f0d14 md heart broken outline f0d15 md home city f0d16 md home city outline f0d17 md hubspot f0d18 md filmstrip box multiple f0d19 md play box multiple f0d1a md link box f0d1b md link box outline f0d1c md link box variant f0d1d md link box variant outline f0d1e md map clock f0d1f md map clock outline f0d20 md map marker path f0d21 md mother nurse f0d22 md microsoft outlook f0d23 md perspective less f0d24 md perspective more f0d25 md podium f0d26 md podium bronze f0d27 md podium gold f0d28 md podium silver f0d29 md quora f0d2a md rewind 10 f0d2b md roller skate f0d2c md rollerblade f0d2d md language ruby f0d2e md sack f0d2f md sack percent f0d30 md safety goggles f0d31 md select color f0d32 md selection ellipse f0d33 md shield link variant f0d34 md shield link variant outline f0d35 md skate f0d36 md skew less f0d37 md skew more f0d38 md speaker multiple f0d39 md stamper f0d3a md tank f0d3b md tortoise f0d3c md transit connection f0d3d md transit connection variant f0d3e md transmission tower f0d3f md weight gram f0d40 md youtube subscription f0d41 md zigbee f0d42 md email alert outline f0d43 md air filter f0d44 md air purifier f0d45 md android messages f0d46 md apps box f0d47 md atm f0d48 md axis f0d49 md axis arrow f0d4a md axis arrow lock f0d4b md axis lock f0d4c md axis x arrow f0d4d md axis x arrow lock f0d4e md axis x rotate clockwise f0d4f md axis x rotate counterclockwise f0d50 md axis x y arrow lock f0d51 md axis y arrow f0d52 md axis y arrow lock f0d53 md axis y rotate clockwise f0d54 md axis y rotate counterclockwise f0d55 md axis z arrow f0d56 md axis z arrow lock f0d57 md axis z rotate clockwise f0d58 md axis z rotate counterclockwise f0d59 md bell alert f0d5a md bell circle f0d5b md bell circle outline f0d5c md calendar minus f0d5d md camera outline f0d5e md car brake hold f0d5f md car brake parking f0d60 md car cruise control f0d61 md car defrost front f0d62 md car defrost rear f0d63 md car parking lights f0d64 md car traction control f0d65 md bag carry on check f0d66 md cart arrow down f0d67 md cart arrow up f0d68 md cart minus f0d69 md cart remove f0d6a md contactless payment f0d6b md creative commons f0d6c md credit card wireless outline f0d6d md cricket f0d6e md dev to f0d6f md domain off f0d70 md face agent f0d71 md fast forward 10 f0d72 md flare f0d73 md format text rotation down f0d74 md format text rotation none f0d75 md forwardburger f0d76 md gesture swipe f0d77 md gesture tap hold f0d78 md file gif box f0d79 md go kart f0d7a md go kart track f0d7b md goodreads f0d7c md grain f0d7d md hdr f0d7e md hdr off f0d7f md hiking f0d80 md home floor 1 f0d81 md home floor 2 f0d82 md home floor 3 f0d83 md home floor a f0d84 md home floor b f0d85 md home floor g f0d86 md home floor l f0d87 md kabaddi f0d88 md mailbox open f0d89 md mailbox open outline f0d8a md mailbox open up f0d8b md mailbox open up outline f0d8c md mailbox outline f0d8d md mailbox up f0d8e md mailbox up outline f0d8f md mixed martial arts f0d90 md monitor off f0d91 md motion sensor f0d92 md point of sale f0d93 md racing helmet f0d94 md racquetball f0d95 md restart off f0d96 md rewind 30 f0d97 md room service outline f0d98 md rotate orbit f0d99 md rugby f0d9a md shield search f0d9b md solar panel f0d9c md solar panel large f0d9d md subway alert variant f0d9e md tea f0d9f md tea outline f0da0 md tennis f0da1 md transfer down f0da2 md transfer left f0da3 md transfer up f0da4 md trophy broken f0da5 md wind turbine f0da6 md wiper wash f0da7 md badge account f0da8 md badge account alert f0da9 md badge account alert outline f0daa md badge account outline f0dab md card account details outline f0dac md air horn f0dad md application export f0dae md application import f0daf md bandage f0db0 md bank minus f0db1 md bank plus f0db2 md bank remove f0db3 md bolt f0db4 md bugle f0db5 md cactus f0db6 md camera wireless f0db7 md camera wireless outline f0db8 md cash marker f0db9 md chevron triple down f0dba md chevron triple left f0dbb md chevron triple right f0dbc md chevron triple up f0dbd md closed caption outline f0dbe md credit card marker outline f0dbf md diving flippers f0dc0 md diving helmet f0dc1 md diving scuba f0dc2 md diving scuba flag f0dc3 md diving scuba tank f0dc4 md diving scuba tank multiple f0dc5 md diving snorkel f0dc6 md file cancel f0dc7 md file cancel outline f0dc8 md file document edit f0dc9 md file document edit outline f0dca md file eye f0dcb md file eye outline f0dcc md folder alert f0dcd md folder alert outline f0dce md folder edit outline f0dcf md folder open outline f0dd0 md format list bulleted square f0dd1 md gantry crane f0dd2 md home floor 0 f0dd3 md home floor negative 1 f0dd4 md home group f0dd5 md jabber f0dd6 md key outline f0dd7 md leak f0dd8 md leak off f0dd9 md marker cancel f0dda md mine f0ddb md monitor lock f0ddc md monitor star f0ddd md movie outline f0dde md music note plus f0ddf md nail f0de0 md ocarina f0de1 md passport biometric f0de2 md pen lock f0de3 md pen minus f0de4 md pen off f0de5 md pen plus f0de6 md pen remove f0de7 md pencil lock outline f0de8 md pencil minus f0de9 md pencil minus outline f0dea md pencil off outline f0deb md pencil plus f0dec md pencil plus outline f0ded md pencil remove f0dee md pencil remove outline f0def md phone off f0df0 md phone outline f0df1 md pi hole f0df2 md playlist star f0df3 md screw flat top f0df4 md screw lag f0df5 md screw machine flat top f0df6 md screw machine round top f0df7 md screw round top f0df8 md send circle f0df9 md send circle outline f0dfa md shoe print f0dfb md signature f0dfc md signature freehand f0dfd md signature image f0dfe md signature text f0dff md slope downhill f0e00 md slope uphill f0e01 md thermometer alert f0e02 md thermometer chevron down f0e03 md thermometer chevron up f0e04 md thermometer minus f0e05 md thermometer plus f0e06 md translate off f0e07 md upload outline f0e08 md volume variant off f0e09 md wallpaper f0e0a md water outline f0e0b md wifi star f0e0c md palette outline f0e0d md badge account horizontal f0e0e md badge account horizontal outline f0e0f md aws f0e10 md bag personal f0e11 md bag personal off f0e12 md bag personal off outline f0e13 md bag personal outline f0e14 md biathlon f0e15 md bookmark multiple f0e16 md bookmark multiple outline f0e17 md calendar month f0e18 md calendar month outline f0e19 md camera retake f0e1a md camera retake outline f0e1b md car back f0e1c md car off f0e1d md cast education f0e1e md check bold f0e1f md check underline f0e20 md check underline circle f0e21 md check underline circle outline f0e22 md circular saw f0e23 md comma f0e24 md comma box outline f0e25 md comma circle f0e26 md comma circle outline f0e27 md content save move f0e28 md content save move outline f0e29 md file check outline f0e2a md file music outline f0e2b md comma box f0e2c md file video outline f0e2d md file png box f0e2e md fireplace f0e2f md fireplace off f0e30 md firework f0e31 md format color highlight f0e32 md format text variant f0e33 md gamepad circle f0e34 md gamepad circle down f0e35 md gamepad circle left f0e36 md gamepad circle outline f0e37 md gamepad circle right f0e38 md gamepad circle up f0e39 md gamepad down f0e3a md gamepad left f0e3b md gamepad right f0e3c md gamepad round f0e3d md gamepad round down f0e3e md gamepad round left f0e3f md gamepad round outline f0e40 md gamepad round right f0e41 md gamepad round up f0e42 md gamepad up f0e43 md gatsby f0e44 md gift f0e45 md grill f0e46 md hand back left f0e47 md hand back right f0e48 md hand saw f0e49 md image frame f0e4a md invert colors off f0e4b md keyboard off outline f0e4c md layers minus f0e4d md layers plus f0e4e md layers remove f0e4f md lightbulb off f0e50 md lightbulb off outline f0e51 md monitor screenshot f0e52 md ice cream off f0e53 md nfc search variant f0e54 md nfc variant off f0e55 md notebook multiple f0e56 md hoop house f0e57 md picture in picture bottom right f0e58 md picture in picture bottom right outline f0e59 md picture in picture top right f0e5a md picture in picture top right outline f0e5b md printer 3d nozzle f0e5c md printer 3d nozzle outline f0e5d md printer off f0e5e md rectangle f0e5f md rectangle outline f0e60 md rivet f0e61 md saw blade f0e62 md seed f0e63 md seed outline f0e64 md signal distance variant f0e65 md spade f0e66 md sprout f0e67 md sprout outline f0e68 md table tennis f0e69 md tree outline f0e6a md view comfy f0e6b md view compact f0e6c md view compact outline f0e6d md vuetify f0e6e md weather cloudy arrow right f0e6f md microsoft xbox controller menu f0e70 md microsoft xbox controller view f0e71 md alarm note f0e72 md alarm note off f0e73 md arrow left right f0e74 md arrow left right bold f0e75 md arrow top left bottom right f0e76 md arrow top left bottom right bold f0e77 md arrow top right bottom left f0e78 md arrow top right bottom left bold f0e79 md arrow up down f0e7a md arrow up down bold f0e7b md atom variant f0e7c md baby face f0e7d md baby face outline f0e7e md backspace reverse f0e7f md backspace reverse outline f0e80 md bank outline f0e81 md bell alert outline f0e82 md book play f0e83 md book play outline f0e84 md book search f0e85 md book search outline f0e86 md boom gate f0e87 md boom gate alert f0e88 md boom gate alert outline f0e89 md boom gate arrow down f0e8a md boom gate arrow down outline f0e8b md boom gate outline f0e8c md boom gate arrow up f0e8d md boom gate arrow up outline f0e8e md calendar sync f0e8f md calendar sync outline f0e90 md cellphone nfc f0e91 md chart areaspline variant f0e92 md chart scatter plot f0e93 md chart timeline variant f0e94 md chart tree f0e95 md circle double f0e96 md circle expand f0e97 md clock digital f0e98 md card account mail outline f0e99 md card account phone f0e9a md card account phone outline f0e9b md account cowboy hat f0e9c md currency rial f0e9d md delete empty outline f0e9e md dolly f0e9f md electric switch f0ea0 md ellipse f0ea1 md ellipse outline f0ea2 md equalizer f0ea3 md equalizer outline f0ea4 md ferris wheel f0ea5 md file delimited outline f0ea6 md text box check f0ea7 md text box check outline f0ea8 md text box minus f0ea9 md text box minus outline f0eaa md text box plus f0eab md text box plus outline f0eac md text box remove f0ead md text box remove outline f0eae md text box search f0eaf md text box search outline f0eb0 md file image outline f0eb1 md fingerprint off f0eb2 md format list bulleted triangle f0eb3 md format overline f0eb4 md frequently asked questions f0eb5 md gamepad square f0eb6 md gamepad square outline f0eb7 md gamepad variant outline f0eb8 md gas station outline f0eb9 md google podcast f0eba md home analytics f0ebb md mail f0ebc md map check f0ebd md map check outline f0ebe md ruler square compass f0ebf md notebook outline f0ec0 md penguin f0ec1 md radioactive off f0ec2 md record circle f0ec3 md record circle outline f0ec4 md remote off f0ec5 md remote tv f0ec6 md remote tv off f0ec7 md rotate 3d f0ec8 md sail boat f0ec9 md scatter plot f0eca md scatter plot outline f0ecb md segment f0ecc md shield alert f0ecd md shield alert outline f0ece md tablet dashboard f0ecf md television play f0ed0 md unicode f0ed1 md video 3d variant f0ed2 md video wireless f0ed3 md video wireless outline f0ed4 md account voice off f0ed5 md bacteria f0ed6 md bacteria outline f0ed7 md calendar account f0ed8 md calendar account outline f0ed9 md calendar weekend f0eda md calendar weekend outline f0edb md camera plus f0edc md camera plus outline f0edd md campfire f0ede md chat outline f0edf md cpu 32 bit f0ee0 md cpu 64 bit f0ee1 md credit card clock f0ee2 md credit card clock outline f0ee3 md email edit f0ee4 md email edit outline f0ee5 md email minus f0ee6 md email minus outline f0ee7 md email multiple f0ee8 md email multiple outline f0ee9 md email open multiple f0eea md email open multiple outline f0eeb md file cad f0eec md file cad box f0eed md file plus outline f0eee md filter minus f0eef md filter minus outline f0ef0 md filter plus f0ef1 md filter plus outline f0ef2 md fire extinguisher f0ef3 md fishbowl f0ef4 md fishbowl outline f0ef5 md fit to page f0ef6 md fit to page outline f0ef7 md flash alert f0ef8 md flash alert outline f0ef9 md heart flash f0efa md home flood f0efb md human male height f0efc md human male height variant f0efd md ice pop f0efe md identifier f0eff md image filter center focus strong f0f00 md image filter center focus strong outline f0f01 md jellyfish f0f02 md jellyfish outline f0f03 md lasso f0f04 md music box multiple outline f0f05 md map marker alert f0f06 md map marker alert outline f0f07 md map marker question f0f08 md map marker question outline f0f09 md map marker remove f0f0a md map marker remove variant f0f0b md necklace f0f0c md newspaper minus f0f0d md newspaper plus f0f0e md numeric 0 box multiple f0f0f md numeric 1 box multiple f0f10 md numeric 2 box multiple f0f11 md numeric 3 box multiple f0f12 md numeric 4 box multiple f0f13 md numeric 5 box multiple f0f14 md numeric 6 box multiple f0f15 md numeric 7 box multiple f0f16 md numeric 8 box multiple f0f17 md numeric 9 box multiple f0f18 md numeric 9 plus box multiple f0f19 md oil lamp f0f1a md phone alert f0f1b md play outline f0f1c md purse f0f1d md purse outline f0f1e md railroad light f0f1f md reply all outline f0f20 md reply outline f0f21 md rss off f0f22 md selection ellipse arrow inside f0f23 md share off f0f24 md share off outline f0f25 md skip backward outline f0f26 md skip forward outline f0f27 md skip next outline f0f28 md skip previous outline f0f29 md snowflake alert f0f2a md snowflake variant f0f2b md stretch to page f0f2c md stretch to page outline f0f2d md typewriter f0f2e md wave f0f2f md weather cloudy alert f0f30 md weather hazy f0f31 md weather night partly cloudy f0f32 md weather partly lightning f0f33 md weather partly rainy f0f34 md weather partly snowy f0f35 md weather partly snowy rainy f0f36 md weather snowy heavy f0f37 md weather sunny alert f0f38 md weather tornado f0f39 md baby bottle f0f3a md baby bottle outline f0f3b md bag carry on f0f3c md bag carry on off f0f3d md bag checked f0f3e md baguette f0f3f md bus multiple f0f40 md car shift pattern f0f41 md cellphone information f0f42 md content save alert f0f43 md content save alert outline f0f44 md content save all outline f0f45 md crosshairs off f0f46 md cupboard f0f47 md cupboard outline f0f48 md chair rolling f0f49 md draw f0f4a md dresser f0f4b md dresser outline f0f4c md emoticon frown f0f4d md emoticon frown outline f0f4e md focus auto f0f4f md focus field f0f50 md focus field horizontal f0f51 md focus field vertical f0f52 md foot print f0f53 md handball f0f54 md home thermometer f0f55 md home thermometer outline f0f56 md kettle outline f0f57 md latitude f0f58 md layers triple f0f59 md layers triple outline f0f5a md longitude f0f5b md language markdown outline f0f5c md merge f0f5d md middleware f0f5e md middleware outline f0f5f md monitor speaker f0f60 md monitor speaker off f0f61 md moon first quarter f0f62 md moon full f0f63 md moon last quarter f0f64 md moon new f0f65 md moon waning crescent f0f66 md moon waning gibbous f0f67 md moon waxing crescent f0f68 md moon waxing gibbous f0f69 md music accidental double flat f0f6a md music accidental double sharp f0f6b md music accidental flat f0f6c md music accidental natural f0f6d md music accidental sharp f0f6e md music clef alto f0f6f md music clef bass f0f70 md music clef treble f0f71 md music note eighth dotted f0f72 md music note half dotted f0f73 md music note off outline f0f74 md music note outline f0f75 md music note quarter dotted f0f76 md music note sixteenth dotted f0f77 md music note whole dotted f0f78 md music rest eighth f0f79 md music rest half f0f7a md music rest quarter f0f7b md music rest sixteenth f0f7c md music rest whole f0f7d md numeric 10 box f0f7e md numeric 10 box outline f0f7f md page layout header footer f0f80 md patio heater f0f81 md warehouse f0f82 md select group f0f83 md shield car f0f84 md shopping search f0f85 md speedometer medium f0f86 md speedometer slow f0f87 md table large plus f0f88 md table large remove f0f89 md television pause f0f8a md television stop f0f8b md transit detour f0f8c md video input scart f0f8d md view grid plus f0f8e md wallet plus f0f8f md wallet plus outline f0f90 md wardrobe f0f91 md wardrobe outline f0f92 md water boiler f0f93 md water pump off f0f94 md web box f0f95 md timeline alert f0f96 md timeline plus f0f97 md timeline plus outline f0f98 md timeline alert outline f0f99 md timeline help f0f9a md timeline help outline f0f9b md home export outline f0f9c md home import outline f0f9d md account filter outline f0f9e md approximately equal f0f9f md approximately equal box f0fa0 md baby carriage off f0fa1 md bee f0fa2 md bee flower f0fa3 md car child seat f0fa4 md car seat f0fa5 md car seat cooler f0fa6 md car seat heater f0fa7 md chart bell curve cumulative f0fa8 md clock check f0fa9 md clock check outline f0faa md coffee off f0fab md coffee off outline f0fac md credit card minus f0fad md credit card minus outline f0fae md credit card remove f0faf md credit card remove outline f0fb0 md devices f0fb1 md email newsletter f0fb2 md expansion card variant f0fb3 md power socket ch f0fb4 md file swap f0fb5 md file swap outline f0fb6 md folder swap f0fb7 md folder swap outline f0fb8 md format letter ends with f0fb9 md format letter matches f0fba md format letter starts with f0fbb md format text rotation angle down f0fbc md format text rotation angle up f0fbd md format text rotation down vertical f0fbe md format text rotation up f0fbf md format text rotation vertical f0fc0 md id card f0fc1 md image auto adjust f0fc2 md key wireless f0fc3 md license f0fc4 md location enter f0fc5 md location exit f0fc6 md lock open variant f0fc7 md lock open variant outline f0fc8 md math integral f0fc9 md math integral box f0fca md math norm f0fcb md math norm box f0fcc md message lock f0fcd md message text lock f0fce md movie open f0fcf md movie open outline f0fd0 md bed queen f0fd1 md bed king outline f0fd2 md bed king f0fd3 md bed double outline f0fd4 md bed double f0fd5 md microsoft azure devops f0fd6 md arm flex outline f0fd7 md arm flex f0fd8 md protocol f0fd9 md seal variant f0fda md select place f0fdb md bed queen outline f0fdc md sign direction plus f0fdd md sign direction remove f0fde md silverware clean f0fdf md slash forward f0fe0 md slash forward box f0fe1 md swap horizontal circle f0fe2 md swap horizontal circle outline f0fe3 md swap vertical circle f0fe4 md swap vertical circle outline f0fe5 md tanker truck f0fe6 md texture box f0fe7 md tram side f0fe8 md vector link f0fe9 md numeric 10 f0fea md numeric 10 box multiple f0feb md numeric 10 box multiple outline f0fec md numeric 10 circle f0fed md numeric 10 circle outline f0fee md numeric 9 plus f0fef md credit card f0ff0 md credit card multiple f0ff1 md credit card off f0ff2 md credit card plus f0ff3 md credit card refund f0ff4 md credit card scan f0ff5 md credit card settings f0ff6 md hospital f0ff7 md hospital box outline f0ff8 md oil temperature f0ff9 md stadium f0ffa md zip box outline f0ffb md account edit outline f0ffc md peanut f0ffd md peanut off f0ffe md peanut outline f0fff md peanut off outline f1000 md sign direction minus f1001 md newspaper variant f1002 md newspaper variant multiple f1003 md newspaper variant multiple outline f1004 md newspaper variant outline f1005 md overscan f1006 md pig variant f1007 md piggy bank f1008 md post f1009 md post outline f100a md account box multiple outline f100b md airballoon outline f100c md alphabetical off f100d md alphabetical variant f100e md alphabetical variant off f100f md apache kafka f1010 md billboard f1011 md blinds open f1012 md bus stop f1013 md bus stop covered f1014 md bus stop uncovered f1015 md car 2 plus f1016 md car 3 plus f1017 md car brake retarder f1018 md car clutch f1019 md car coolant level f101a md car turbocharger f101b md car windshield f101c md car windshield outline f101d md cards diamond outline f101e md cast audio f101f md cellphone play f1020 md coach lamp f1021 md comment quote f1022 md comment quote outline f1023 md domino mask f1024 md electron framework f1025 md excavator f1026 md eye minus f1027 md eye minus outline f1028 md file account outline f1029 md file chart outline f102a md file cloud outline f102b md file code outline f102c md file excel box outline f102d md file excel outline f102e md file export outline f102f md file import outline f1030 md file lock outline f1031 md file move outline f1032 md file multiple outline f1033 md file percent outline f1034 md file powerpoint box outline f1035 md file powerpoint outline f1036 md file question outline f1037 md file remove outline f1038 md file restore outline f1039 md file send outline f103a md file star f103b md file star outline f103c md file undo outline f103d md file word box outline f103e md file word outline f103f md filter variant remove f1040 md floor lamp dual f1041 md floor lamp torchiere variant f1042 md fruit cherries f1043 md fruit citrus f1044 md fruit grapes f1045 md fruit grapes outline f1046 md fruit pineapple f1047 md fruit watermelon f1048 md google my business f1049 md graph f104a md graph outline f104b md harddisk plus f104c md harddisk remove f104d md home circle outline f104e md instrument triangle f104f md island f1050 md keyboard space f1051 md led strip variant f1052 md numeric negative 1 f1053 md oil level f1054 md outdoor lamp f1055 md palm tree f1056 md party popper f1057 md printer pos f1058 md robber f1059 md routes clock f105a md scale off f105b md cog transfer f105c md cog transfer outline f105d md shield sun f105e md shield sun outline f105f md sprinkler f1060 md sprinkler variant f1061 md table chair f1062 md terraform f1063 md toaster f1064 md tools f1065 md transfer f1066 md valve f1067 md valve closed f1068 md valve open f1069 md video check f106a md video check outline f106b md water well f106c md water well outline f106d md bed single f106e md bed single outline f106f md book information variant f1070 md bottle soda f1071 md bottle soda classic f1072 md bottle soda outline f1073 md calendar blank multiple f1074 md card search f1075 md card search outline f1076 md face woman profile f1077 md face woman f1078 md face woman outline f1079 md file settings f107a md file settings outline f107b md file cog f107c md file cog outline f107d md folder settings f107e md folder settings outline f107f md folder cog f1080 md folder cog outline f1081 md furigana horizontal f1082 md furigana vertical f1083 md golf tee f1084 md lungs f1085 md math log f1086 md moped f1087 md router network f1088 md alpha i f1089 md roman numeral 2 f108a md roman numeral 3 f108b md roman numeral 4 f108c md alpha v f108d md roman numeral 6 f108e md roman numeral 7 f108f md roman numeral 8 f1090 md roman numeral 9 f1091 md alpha x f1092 md soldering iron f1093 md stomach f1094 md table eye f1095 md form textarea f1096 md trumpet f1097 md account cash f1098 md account cash outline f1099 md air humidifier f109a md ansible f109b md api f109c md bicycle f109d md car door lock f109e md coat rack f109f md coffee maker f10a0 md web minus f10a1 md decimal f10a2 md decimal comma f10a3 md decimal comma decrease f10a4 md decimal comma increase f10a5 md delete alert f10a6 md delete alert outline f10a7 md delete off f10a8 md delete off outline f10a9 md dock bottom f10aa md dock left f10ab md dock right f10ac md dock window f10ad md domain plus f10ae md domain remove f10af md door closed lock f10b0 md download off f10b1 md download off outline f10b2 md flag minus outline f10b3 md flag plus outline f10b4 md flag remove outline f10b5 md folder home f10b6 md folder home outline f10b7 md folder information f10b8 md folder information outline f10b9 md iv bag f10ba md link lock f10bb md message plus outline f10bc md phone cancel f10bd md smart card f10be md smart card outline f10bf md smart card reader f10c0 md smart card reader outline f10c1 md storefront outline f10c2 md thermometer high f10c3 md thermometer low f10c4 md ufo f10c5 md ufo outline f10c6 md upload off f10c7 md upload off outline f10c8 md account child outline f10c9 md account settings outline f10ca md account tie outline f10cb md alien outline f10cc md battery alert variant f10cd md battery alert variant outline f10ce md beehive outline f10cf md boomerang f10d0 md briefcase clock f10d1 md briefcase clock outline f10d2 md cellphone message off f10d3 md circle off outline f10d4 md clipboard list f10d5 md clipboard list outline f10d6 md code braces box f10d7 md code parentheses box f10d8 md consolidate f10d9 md electric switch closed f10da md email receive f10db md email receive outline f10dc md email send f10dd md email send outline f10de md emoticon confused f10df md emoticon confused outline f10e0 md epsilon f10e1 md file table box f10e2 md file table box multiple f10e3 md file table box multiple outline f10e4 md file table box outline f10e5 md filter menu f10e6 md filter menu outline f10e7 md flip horizontal f10e8 md flip vertical f10e9 md folder download outline f10ea md folder heart f10eb md folder heart outline f10ec md folder key outline f10ed md folder upload outline f10ee md gamma f10ef md hair dryer f10f0 md hair dryer outline f10f1 md hand heart f10f2 md hexagon multiple outline f10f3 md horizontal rotate clockwise f10f4 md horizontal rotate counterclockwise f10f5 md application array f10f6 md application array outline f10f7 md application braces f10f8 md application braces outline f10f9 md application parentheses f10fa md application parentheses outline f10fb md application variable f10fc md application variable outline f10fd md khanda f10fe md kubernetes f10ff md link variant minus f1100 md link variant plus f1101 md link variant remove f1102 md map marker down f1103 md map marker up f1104 md monitor shimmer f1105 md nix f1106 md nuxt f1107 md power socket de f1108 md power socket fr f1109 md power socket jp f110a md progress close f110b md reload alert f110c md restart alert f110d md restore alert f110e md shaker f110f md shaker outline f1110 md television shimmer f1111 md variable box f1112 md filter variant minus f1113 md filter variant plus f1114 md slot machine f1115 md slot machine outline f1116 md glass mug variant f1117 md clipboard flow outline f1118 md sign real estate f1119 md antenna f111a md centos f111b md redhat f111c md window shutter f111d md window shutter alert f111e md window shutter open f111f md bike fast f1120 md volume source f1121 md volume vibrate f1122 md movie edit f1123 md movie edit outline f1124 md movie filter f1125 md movie filter outline f1126 md diabetes f1127 md cursor default gesture f1128 md cursor default gesture outline f1129 md toothbrush f112a md toothbrush paste f112b md home roof f112c md toothbrush electric f112d md account supervisor outline f112e md bottle tonic f112f md bottle tonic outline f1130 md bottle tonic plus f1131 md bottle tonic plus outline f1132 md bottle tonic skull f1133 md bottle tonic skull outline f1134 md calendar arrow left f1135 md calendar arrow right f1136 md crosshairs question f1137 md fire hydrant f1138 md fire hydrant alert f1139 md fire hydrant off f113a md ocr f113b md shield star f113c md shield star outline f113d md text recognition f113e md handcuffs f113f md gender male female variant f1140 md gender non binary f1141 md minus box multiple f1142 md minus box multiple outline f1143 md plus box multiple outline f1144 md pencil box multiple f1145 md pencil box multiple outline f1146 md printer check f1147 md sort variant remove f1148 md sort alphabetical ascending variant f1149 md sort alphabetical descending variant f114a md dice 1 outline f114b md dice 2 outline f114c md dice 3 outline f114d md dice 4 outline f114e md dice 5 outline f114f md dice 6 outline f1150 md dice d4 f1151 md dice d6 f1152 md dice d8 f1153 md dice d10 f1154 md dice d12 f1155 md dice d20 f1156 md dice multiple outline f1157 md paper roll f1158 md paper roll outline f1159 md home edit f115a md home edit outline f115b md arrow horizontal lock f115c md arrow vertical lock f115d md weight lifter f115e md account lock f115f md account lock outline f1160 md pasta f1161 md send check f1162 md send check outline f1163 md send clock f1164 md send clock outline f1165 md send outline f1166 md send lock outline f1167 md police badge f1168 md police badge outline f1169 md gate arrow right f116a md gate open f116b md bell badge f116c md message image outline f116d md message lock outline f116e md message minus f116f md message minus outline f1170 md message processing outline f1171 md message settings outline f1172 md message cog outline f1173 md message text clock f1174 md message text clock outline f1175 md message text lock outline f1176 md checkbox blank badge f1177 md file link f1178 md file link outline f1179 md file phone f117a md file phone outline f117b md meditation f117c md yoga f117d md leek f117e md noodles f117f md pound box outline f1180 md school outline f1181 md basket outline f1182 md phone in talk outline f1183 md bash f1184 md file key f1185 md file key outline f1186 md file certificate f1187 md file certificate outline f1188 md certificate outline f1189 md cigar f118a md grill outline f118b md qrcode plus f118c md qrcode minus f118d md qrcode remove f118e md phone alert outline f118f md phone bluetooth outline f1190 md phone cancel outline f1191 md phone forward outline f1192 md phone hangup outline f1193 md phone incoming outline f1194 md phone lock outline f1195 md phone log outline f1196 md phone message f1197 md phone message outline f1198 md phone minus outline f1199 md phone outgoing outline f119a md phone paused outline f119b md phone plus outline f119c md phone return outline f119d md phone settings outline f119e md key star f119f md key link f11a0 md shield edit f11a1 md shield edit outline f11a2 md shield sync f11a3 md shield sync outline f11a4 md golf cart f11a5 md phone missed outline f11a6 md phone off outline f11a7 md format quote open outline f11a8 md format quote close outline f11a9 md phone check f11aa md phone check outline f11ab md phone ring f11ac md phone ring outline f11ad md share circle f11ae md reply circle f11af md fridge off f11b0 md fridge off outline f11b1 md fridge alert f11b2 md fridge alert outline f11b3 md water boiler alert f11b4 md water boiler off f11b5 md amplifier off f11b6 md audio video off f11b7 md toaster off f11b8 md dishwasher alert f11b9 md dishwasher off f11ba md tumble dryer alert f11bb md tumble dryer off f11bc md washing machine alert f11bd md washing machine off f11be md car info f11bf md comment edit f11c0 md printer 3d nozzle alert f11c1 md printer 3d nozzle alert outline f11c2 md align horizontal left f11c3 md align horizontal center f11c4 md align horizontal right f11c5 md align vertical bottom f11c6 md align vertical center f11c7 md align vertical top f11c8 md distribute horizontal left f11c9 md distribute horizontal center f11ca md distribute horizontal right f11cb md distribute vertical bottom f11cc md distribute vertical center f11cd md distribute vertical top f11ce md alert rhombus f11cf md alert rhombus outline f11d0 md crown outline f11d1 md image off outline f11d2 md movie search f11d3 md movie search outline f11d4 md rv truck f11d5 md shopping outline f11d6 md strategy f11d7 md note text outline f11d8 md view agenda outline f11d9 md view grid outline f11da md view grid plus outline f11db md window closed variant f11dc md window open variant f11dd md cog clockwise f11de md cog counterclockwise f11df md chart sankey f11e0 md chart sankey variant f11e1 md vanity light f11e2 md router f11e3 md image edit f11e4 md image edit outline f11e5 md bell check f11e6 md bell check outline f11e7 md file edit f11e8 md file edit outline f11e9 md human scooter f11ea md spider f11eb md spider thread f11ec md plus thick f11ed md alert circle check f11ee md alert circle check outline f11ef md state machine f11f0 md usb port f11f1 md cloud lock f11f2 md cloud lock outline f11f3 md robot mower outline f11f4 md share all f11f5 md share all outline f11f6 md google cloud f11f7 md robot mower f11f8 md fast forward 5 f11f9 md rewind 5 f11fa md shape oval plus f11fb md timeline clock f11fc md timeline clock outline f11fd md mirror f11fe md account multiple check outline f11ff md card plus f1200 md card plus outline f1201 md checkerboard plus f1202 md checkerboard minus f1203 md checkerboard remove f1204 md select search f1205 md selection search f1206 md layers search f1207 md layers search outline f1208 md lightbulb cfl f1209 md lightbulb cfl off f120a md account multiple remove f120b md account multiple remove outline f120c md magnify remove cursor f120d md magnify remove outline f120e md archive outline f120f md battery heart f1210 md battery heart outline f1211 md battery heart variant f1212 md bus marker f1213 md chart multiple f1214 md emoticon lol f1215 md emoticon lol outline f1216 md file sync f1217 md file sync outline f1218 md handshake f1219 md language kotlin f121a md language fortran f121b md offer f121c md radio off f121d md table headers eye f121e md table headers eye off f121f md tag minus outline f1220 md tag off f1221 md tag off outline f1222 md tag plus outline f1223 md tag remove outline f1224 md tag text f1225 md vector polyline edit f1226 md vector polyline minus f1227 md vector polyline plus f1228 md vector polyline remove f1229 md beaker alert f122a md beaker alert outline f122b md beaker check f122c md beaker check outline f122d md beaker minus f122e md beaker minus outline f122f md beaker plus f1230 md beaker plus outline f1231 md beaker question f1232 md beaker question outline f1233 md beaker remove f1234 md beaker remove outline f1235 md bicycle basket f1236 md barcode off f1237 md digital ocean f1238 md exclamation thick f1239 md desk f123a md flask empty minus f123b md flask empty minus outline f123c md flask empty plus f123d md flask empty plus outline f123e md flask empty remove f123f md flask empty remove outline f1240 md flask minus f1241 md flask minus outline f1242 md flask plus f1243 md flask plus outline f1244 md flask remove f1245 md flask remove outline f1246 md folder move outline f1247 md home remove f1248 md webrtc f1249 md seat passenger f124a md web clock f124b md flask round bottom f124c md flask round bottom empty f124d md flask round bottom empty outline f124e md flask round bottom outline f124f md gold f1250 md message star outline f1251 md home lightbulb f1252 md home lightbulb outline f1253 md lightbulb group f1254 md lightbulb group outline f1255 md lightbulb multiple f1256 md lightbulb multiple outline f1257 md api off f1258 md allergy f1259 md archive arrow down f125a md archive arrow down outline f125b md archive arrow up f125c md archive arrow up outline f125d md battery off f125e md battery off outline f125f md bookshelf f1260 md cash minus f1261 md cash plus f1262 md cash remove f1263 md clipboard check multiple f1264 md clipboard check multiple outline f1265 md clipboard file f1266 md clipboard file outline f1267 md clipboard multiple f1268 md clipboard multiple outline f1269 md clipboard play multiple f126a md clipboard play multiple outline f126b md clipboard text multiple f126c md clipboard text multiple outline f126d md folder marker f126e md folder marker outline f126f md format list text f1270 md inbox arrow down outline f1271 md inbox arrow up outline f1272 md inbox full f1273 md inbox full outline f1274 md inbox outline f1275 md lightbulb cfl spiral f1276 md magnify scan f1277 md map marker multiple outline f1278 md percent outline f1279 md phone classic off f127a md play box f127b md account eye outline f127c md safe square f127d md safe square outline f127e md scoreboard f127f md scoreboard outline f1280 md select marker f1281 md select multiple f1282 md select multiple marker f1283 md selection marker f1284 md selection multiple marker f1285 md selection multiple f1286 md star box multiple f1287 md star box multiple outline f1288 md toy brick f1289 md toy brick marker f128a md toy brick marker outline f128b md toy brick minus f128c md toy brick minus outline f128d md toy brick outline f128e md toy brick plus f128f md toy brick plus outline f1290 md toy brick remove f1291 md toy brick remove outline f1292 md toy brick search f1293 md toy brick search outline f1294 md tray f1295 md tray alert f1296 md tray full f1297 md tray minus f1298 md tray plus f1299 md tray remove f129a md truck check outline f129b md truck delivery outline f129c md truck fast outline f129d md truck outline f129e md usb flash drive f129f md usb flash drive outline f12a0 md water polo f12a1 md battery low f12a2 md battery medium f12a3 md battery high f12a4 md battery charging low f12a5 md battery charging medium f12a6 md battery charging high f12a7 md hexadecimal f12a8 md gesture tap button f12a9 md gesture tap box f12aa md lan check f12ab md keyboard f1 f12ac md keyboard f2 f12ad md keyboard f3 f12ae md keyboard f4 f12af md keyboard f5 f12b0 md keyboard f6 f12b1 md keyboard f7 f12b2 md keyboard f8 f12b3 md keyboard f9 f12b4 md keyboard f10 f12b5 md keyboard f11 f12b6 md keyboard f12 f12b7 md keyboard esc f12b8 md toslink f12b9 md cheese f12ba md string lights f12bb md string lights off f12bc md whistle outline f12bd md stairs up f12be md stairs down f12bf md escalator up f12c0 md escalator down f12c1 md elevator up f12c2 md elevator down f12c3 md lightbulb cfl spiral off f12c4 md comment edit outline f12c5 md tooltip edit outline f12c6 md monitor edit f12c7 md email sync f12c8 md email sync outline f12c9 md chat alert outline f12ca md chat processing outline f12cb md snowflake melt f12cc md cloud check outline f12cd md lightbulb group off f12ce md lightbulb group off outline f12cf md lightbulb multiple off f12d0 md lightbulb multiple off outline f12d1 md chat sleep f12d2 md chat sleep outline f12d3 md garage variant f12d4 md garage open variant f12d5 md garage alert variant f12d6 md cloud sync outline f12d7 md globe light f12d8 md cellphone nfc off f12d9 md leaf off f12da md leaf maple off f12db md map marker left f12dc md map marker right f12dd md map marker left outline f12de md map marker right outline f12df md account cancel f12e0 md account cancel outline f12e1 md file clock f12e2 md file clock outline f12e3 md folder table f12e4 md folder table outline f12e5 md hydro power f12e6 md doorbell f12e7 md bulma f12e8 md iobroker f12e9 md oci f12ea md label percent f12eb md label percent outline f12ec md checkbox blank off f12ed md checkbox blank off outline f12ee md square off f12ef md square off outline f12f0 md drag horizontal variant f12f1 md drag vertical variant f12f2 md message arrow left f12f3 md message arrow left outline f12f4 md message arrow right f12f5 md message arrow right outline f12f6 md database marker f12f7 md tag multiple outline f12f8 md map marker plus outline f12f9 md map marker minus outline f12fa md map marker remove outline f12fb md map marker check outline f12fc md map marker radius outline f12fd md map marker off outline f12fe md molecule co f12ff md jump rope f1300 md kettlebell f1301 md account convert outline f1302 md bunk bed f1303 md fleur de lis f1304 md ski f1305 md ski cross country f1306 md ski water f1307 md snowboard f1308 md account tie voice f1309 md account tie voice outline f130a md account tie voice off f130b md account tie voice off outline f130c md beer outline f130d md glass pint outline f130e md coffee to go outline f130f md cup outline f1310 md bottle wine outline f1311 md earth arrow right f1312 md key arrow right f1313 md format color marker cancel f1314 md mother heart f1315 md currency eur off f1316 md semantic web f1317 md kettle alert f1318 md kettle alert outline f1319 md kettle steam f131a md kettle steam outline f131b md kettle off f131c md kettle off outline f131d md simple icons f131e md briefcase check outline f131f md clipboard plus outline f1320 md download lock f1321 md download lock outline f1322 md hammer screwdriver f1323 md hammer wrench f1324 md hydraulic oil level f1325 md hydraulic oil temperature f1326 md medal outline f1327 md rodent f1328 md abjad arabic f1329 md abjad hebrew f132a md abugida devanagari f132b md abugida thai f132c md alphabet aurebesh f132d md alphabet cyrillic f132e md alphabet greek f132f md alphabet latin f1330 md alphabet piqad f1331 md ideogram cjk f1332 md ideogram cjk variant f1333 md syllabary hangul f1334 md syllabary hiragana f1335 md syllabary katakana f1336 md syllabary katakana halfwidth f1337 md alphabet tengwar f1338 md head alert f1339 md head alert outline f133a md head check f133b md head check outline f133c md head cog f133d md head cog outline f133e md head dots horizontal f133f md head dots horizontal outline f1340 md head flash f1341 md head flash outline f1342 md head heart f1343 md head heart outline f1344 md head lightbulb f1345 md head lightbulb outline f1346 md head minus f1347 md head minus outline f1348 md head plus f1349 md head plus outline f134a md head question f134b md head question outline f134c md head remove f134d md head remove outline f134e md head snowflake f134f md head snowflake outline f1350 md head sync f1351 md head sync outline f1352 md hvac f1353 md pencil ruler f1354 md pipe wrench f1355 md widgets outline f1356 md television ambient light f1357 md propane tank f1358 md propane tank outline f1359 md folder music f135a md folder music outline f135b md klingon f135c md palette swatch outline f135d md form textbox lock f135e md head f135f md head outline f1360 md shield half f1361 md store outline f1362 md google downasaur f1363 md bottle soda classic outline f1364 md sticker f1365 md sticker alert f1366 md sticker alert outline f1367 md sticker check f1368 md sticker check outline f1369 md sticker minus f136a md sticker minus outline f136b md sticker outline f136c md sticker plus f136d md sticker plus outline f136e md sticker remove f136f md sticker remove outline f1370 md account cog f1371 md account cog outline f1372 md account details outline f1373 md upload lock f1374 md upload lock outline f1375 md label multiple f1376 md label multiple outline f1377 md refresh circle f1378 md sync circle f1379 md bookmark music outline f137a md bookmark remove outline f137b md bookmark check outline f137c md traffic cone f137d md cup off outline f137e md auto download f137f md shuriken f1380 md chart ppf f1381 md elevator passenger f1382 md compass rose f1383 md space station f1384 md order bool descending f1385 md sort bool ascending f1386 md sort bool ascending variant f1387 md sort bool descending f1388 md sort bool descending variant f1389 md sort numeric ascending f138a md sort numeric descending f138b md human baby changing table f138c md human male child f138d md human wheelchair f138e md microsoft access f138f md microsoft excel f1390 md microsoft powerpoint f1391 md microsoft sharepoint f1392 md microsoft word f1393 md nintendo game boy f1394 md cable data f1395 md circle half f1396 md circle half full f1397 md cellphone charging f1398 md close thick f1399 md escalator box f139a md lock check f139b md lock open alert f139c md lock open check f139d md recycle variant f139e md stairs box f139f md hand water f13a0 md table refresh f13a1 md table sync f13a2 md size xxs f13a3 md size xs f13a4 md size s f13a5 md size m f13a6 md alpha l f13a7 md size xl f13a8 md size xxl f13a9 md size xxxl f13aa md ticket confirmation outline f13ab md timer f13ac md timer off f13ad md book account f13ae md book account outline f13af md rocket outline f13b0 md home search f13b1 md home search outline f13b2 md car arrow left f13b3 md car arrow right f13b4 md monitor eye f13b5 md lipstick f13b6 md virus f13b7 md virus outline f13b8 md text search f13b9 md table account f13ba md table alert f13bb md table arrow down f13bc md table arrow left f13bd md table arrow right f13be md table arrow up f13bf md table cancel f13c0 md table check f13c1 md table clock f13c2 md table cog f13c3 md table eye off f13c4 md table heart f13c5 md table key f13c6 md table lock f13c7 md table minus f13c8 md table multiple f13c9 md table network f13ca md table off f13cb md table star f13cc md car cog f13cd md car settings f13ce md cog off f13cf md cog off outline f13d0 md credit card check f13d1 md credit card check outline f13d2 md file tree outline f13d3 md folder star multiple f13d4 md folder star multiple outline f13d5 md home minus outline f13d6 md home plus outline f13d7 md home remove outline f13d8 md scan helper f13d9 md video 3d off f13da md shield bug f13db md shield bug outline f13dc md eyedropper plus f13dd md eyedropper minus f13de md eyedropper remove f13df md eyedropper off f13e0 md baby buggy f13e1 md umbrella closed variant f13e2 md umbrella closed outline f13e3 md email off f13e4 md email off outline f13e5 md food variant off f13e6 md play box multiple outline f13e7 md bell cancel f13e8 md bell cancel outline f13e9 md bell minus f13ea md bell minus outline f13eb md bell remove f13ec md bell remove outline f13ed md beehive off outline f13ee md cheese off f13ef md corn off f13f0 md egg off f13f1 md egg off outline f13f2 md egg outline f13f3 md fish off f13f4 md flask empty off f13f5 md flask empty off outline f13f6 md flask off f13f7 md flask off outline f13f8 md fruit cherries off f13f9 md fruit citrus off f13fa md mushroom off f13fb md mushroom off outline f13fc md soy sauce off f13fd md seed off f13fe md seed off outline f13ff md tailwind f1400 md form dropdown f1401 md form select f1402 md pump f1403 md earth plus f1404 md earth minus f1405 md earth remove f1406 md earth box plus f1407 md earth box minus f1408 md earth box remove f1409 md gas station off f140a md gas station off outline f140b md lightning bolt f140c md lightning bolt outline f140d md smoking pipe f140e md axis arrow info f140f md chat plus f1410 md chat minus f1411 md chat remove f1412 md chat plus outline f1413 md chat minus outline f1414 md chat remove outline f1415 md bucket f1416 md bucket outline f1417 md pail f1418 md image remove f1419 md image minus f141a md pine tree fire f141b md cigar off f141c md cube off f141d md cube off outline f141e md dome light f141f md food drumstick f1420 md food drumstick outline f1421 md incognito circle f1422 md incognito circle off f1423 md microwave off f1424 md power plug off outline f1425 md power plug outline f1426 md puzzle check f1427 md puzzle check outline f1428 md smoking pipe off f1429 md spoon sugar f142a md table split cell f142b md ticket percent outline f142c md fuse off f142d md fuse alert f142e md heart plus f142f md heart minus f1430 md heart remove f1431 md heart plus outline f1432 md heart minus outline f1433 md heart remove outline f1434 md heart off outline f1435 md motion sensor off f1436 md pail plus f1437 md pail minus f1438 md pail remove f1439 md pail off f143a md pail outline f143b md pail plus outline f143c md pail minus outline f143d md pail remove outline f143e md pail off outline f143f md clock time one f1440 md clock time two f1441 md clock time three f1442 md clock time four f1443 md clock time five f1444 md clock time six f1445 md clock time seven f1446 md clock time eight f1447 md clock time nine f1448 md clock time ten f1449 md clock time eleven f144a md clock time twelve f144b md clock time one outline f144c md clock time two outline f144d md clock time three outline f144e md clock time four outline f144f md clock time five outline f1450 md clock time six outline f1451 md clock time seven outline f1452 md clock time eight outline f1453 md clock time nine outline f1454 md clock time ten outline f1455 md clock time eleven outline f1456 md clock time twelve outline f1457 md printer search f1458 md printer eye f1459 md minus circle off f145a md minus circle off outline f145b md content save cog f145c md content save cog outline f145d md set square f145e md cog refresh f145f md cog refresh outline f1460 md cog sync f1461 md cog sync outline f1462 md download box f1463 md download box outline f1464 md download circle f1465 md download circle outline f1466 md air humidifier off f1467 md chili off f1468 md food drumstick off f1469 md food drumstick off outline f146a md food steak f146b md food steak off f146c md fan alert f146d md fan chevron down f146e md fan chevron up f146f md fan plus f1470 md fan minus f1471 md fan remove f1472 md fan speed 1 f1473 md fan speed 2 f1474 md fan speed 3 f1475 md rug f1476 md lingerie f1477 md wizard hat f1478 md hours 24 f1479 md cosine wave f147a md sawtooth wave f147b md square wave f147c md triangle wave f147d md waveform f147e md folder multiple plus f147f md folder multiple plus outline f1480 md current ac f1481 md watering can f1482 md watering can outline f1483 md monitor share f1484 md laser pointer f1485 md view array outline f1486 md view carousel outline f1487 md view column outline f1488 md view comfy outline f1489 md view dashboard variant outline f148a md view day outline f148b md view list outline f148c md view module outline f148d md view parallel outline f148e md view quilt outline f148f md view sequential outline f1490 md view stream outline f1491 md view week outline f1492 md compare horizontal f1493 md compare vertical f1494 md briefcase variant f1495 md briefcase variant outline f1496 md relation many to many f1497 md relation many to one f1498 md relation many to one or many f1499 md relation many to only one f149a md relation many to zero or many f149b md relation many to zero or one f149c md relation one or many to many f149d md relation one or many to one f149e md relation one or many to one or many f149f md relation one or many to only one f14a0 md relation one or many to zero or many f14a1 md relation one or many to zero or one f14a2 md relation one to many f14a3 md relation one to one f14a4 md relation one to one or many f14a5 md relation one to only one f14a6 md relation one to zero or many f14a7 md relation one to zero or one f14a8 md relation only one to many f14a9 md relation only one to one f14aa md relation only one to one or many f14ab md relation only one to only one f14ac md relation only one to zero or many f14ad md relation only one to zero or one f14ae md relation zero or many to many f14af md relation zero or many to one f14b0 md relation zero or many to one or many f14b1 md relation zero or many to only one f14b2 md relation zero or many to zero or many f14b3 md relation zero or many to zero or one f14b4 md relation zero or one to many f14b5 md relation zero or one to one f14b6 md relation zero or one to one or many f14b7 md relation zero or one to only one f14b8 md relation zero or one to zero or many f14b9 md relation zero or one to zero or one f14ba md alert plus f14bb md alert minus f14bc md alert remove f14bd md alert plus outline f14be md alert minus outline f14bf md alert remove outline f14c0 md carabiner f14c1 md fencing f14c2 md skateboard f14c3 md polo f14c4 md tractor variant f14c5 md radiology box f14c6 md radiology box outline f14c7 md skull scan f14c8 md skull scan outline f14c9 md plus minus variant f14ca md source branch plus f14cb md source branch minus f14cc md source branch remove f14cd md source branch refresh f14ce md source branch sync f14cf md source branch check f14d0 md puzzle plus f14d1 md puzzle minus f14d2 md puzzle remove f14d3 md puzzle edit f14d4 md puzzle heart f14d5 md puzzle star f14d6 md puzzle plus outline f14d7 md puzzle minus outline f14d8 md puzzle remove outline f14d9 md puzzle edit outline f14da md puzzle heart outline f14db md puzzle star outline f14dc md rhombus medium outline f14dd md rhombus split outline f14de md rocket launch f14df md rocket launch outline f14e0 md set merge f14e1 md set split f14e2 md beekeeper f14e3 md snowflake off f14e4 md weather sunny off f14e5 md clipboard edit f14e6 md clipboard edit outline f14e7 md notebook edit f14e8 md human edit f14e9 md notebook edit outline f14ea md cash lock f14eb md cash lock open f14ec md account supervisor circle outline f14ed md car outline f14ee md cash check f14ef md filter off f14f0 md filter off outline f14f1 md spirit level f14f2 md wheel barrow f14f3 md book check f14f4 md book check outline f14f5 md notebook check f14f6 md notebook check outline f14f7 md book open variant f14f8 md sign pole f14f9 md shore f14fa md shape square rounded plus f14fb md square rounded f14fc md square rounded outline f14fd md archive alert f14fe md archive alert outline f14ff md power socket it f1500 md square circle f1501 md symbol f1502 md water alert f1503 md water alert outline f1504 md water check f1505 md water check outline f1506 md water minus f1507 md water minus outline f1508 md water off outline f1509 md water percent alert f150a md water plus f150b md water plus outline f150c md water remove f150d md water remove outline f150e md snake f150f md format text variant outline f1510 md grass f1511 md access point off f1512 md currency mnt f1513 md dock top f1514 md share variant outline f1515 md transit skip f1516 md yurt f1517 md file document multiple f1518 md file document multiple outline f1519 md ev plug ccs1 f151a md ev plug ccs2 f151b md ev plug chademo f151c md ev plug tesla f151d md ev plug type1 f151e md ev plug type2 f151f md office building outline f1520 md office building marker f1521 md office building marker outline f1522 md progress question f1523 md basket minus f1524 md basket minus outline f1525 md basket off f1526 md basket off outline f1527 md basket plus f1528 md basket plus outline f1529 md basket remove f152a md basket remove outline f152b md account reactivate f152c md account reactivate outline f152d md car lifted pickup f152e md video high definition f152f md phone remove f1530 md phone remove outline f1531 md thermometer off f1532 md timeline check f1533 md timeline check outline f1534 md timeline minus f1535 md timeline minus outline f1536 md timeline remove f1537 md timeline remove outline f1538 md access point check f1539 md access point minus f153a md access point plus f153b md access point remove f153c md data matrix f153d md data matrix edit f153e md data matrix minus f153f md data matrix plus f1540 md data matrix remove f1541 md data matrix scan f1542 md tune variant f1543 md tune vertical variant f1544 md rake f1545 md shimmer f1546 md transit connection horizontal f1547 md sort calendar ascending f1548 md sort calendar descending f1549 md sort clock ascending f154a md sort clock ascending outline f154b md sort clock descending f154c md sort clock descending outline f154d md chart box f154e md chart box outline f154f md chart box plus outline f1550 md mouse move down f1551 md mouse move up f1552 md mouse move vertical f1553 md pitchfork f1554 md vanish quarter f1555 md application settings outline f1556 md delete clock f1557 md delete clock outline f1558 md kangaroo f1559 md phone dial f155a md phone dial outline f155b md star off outline f155c md tooltip check f155d md tooltip check outline f155e md tooltip minus f155f md tooltip minus outline f1560 md tooltip remove f1561 md tooltip remove outline f1562 md pretzel f1563 md star plus f1564 md star minus f1565 md star remove f1566 md star check f1567 md star plus outline f1568 md star minus outline f1569 md star remove outline f156a md star check outline f156b md eiffel tower f156c md submarine f156d md sofa outline f156e md sofa single f156f md sofa single outline f1570 md text account f1571 md human queue f1572 md food halal f1573 md food kosher f1574 md key chain f1575 md key chain variant f1576 md lamps f1577 md application cog outline f1578 md dance pole f1579 md social distance 2 meters f157a md social distance 6 feet f157b md calendar cursor f157c md emoticon sick f157d md emoticon sick outline f157e md hand heart outline f157f md hand wash f1580 md hand wash outline f1581 md human cane f1582 md lotion f1583 md lotion outline f1584 md lotion plus f1585 md lotion plus outline f1586 md face mask f1587 md face mask outline f1588 md reiterate f1589 md butterfly f158a md butterfly outline f158b md bag suitcase f158c md bag suitcase outline f158d md bag suitcase off f158e md bag suitcase off outline f158f md motion play f1590 md motion pause f1591 md motion play outline f1592 md motion pause outline f1593 md arrow top left thin circle outline f1594 md arrow top right thin circle outline f1595 md arrow bottom right thin circle outline f1596 md arrow bottom left thin circle outline f1597 md arrow up thin circle outline f1598 md arrow right thin circle outline f1599 md arrow down thin circle outline f159a md arrow left thin circle outline f159b md human capacity decrease f159c md human capacity increase f159d md human greeting proximity f159e md hvac off f159f md inbox remove f15a0 md inbox remove outline f15a1 md handshake outline f15a2 md ladder f15a3 md router wireless off f15a4 md seesaw f15a5 md slide f15a6 md calculator variant outline f15a7 md shield account variant f15a8 md shield account variant outline f15a9 md message flash f15aa md message flash outline f15ab md list status f15ac md message bookmark f15ad md message bookmark outline f15ae md comment bookmark f15af md comment bookmark outline f15b0 md comment flash f15b1 md comment flash outline f15b2 md motion f15b3 md motion outline f15b4 md bicycle electric f15b5 md car electric outline f15b6 md chart timeline variant shimmer f15b7 md moped electric f15b8 md moped electric outline f15b9 md moped outline f15ba md motorbike electric f15bb md rickshaw f15bc md rickshaw electric f15bd md scooter f15be md scooter electric f15bf md horse f15c0 md horse human f15c1 md horse variant f15c2 md unicorn f15c3 md unicorn variant f15c4 md alarm panel f15c5 md alarm panel outline f15c6 md bird f15c7 md shoe cleat f15c8 md shoe sneaker f15c9 md human female dance f15ca md shoe ballet f15cb md numeric positive 1 f15cc md face man shimmer f15cd md face man shimmer outline f15ce md face woman shimmer f15cf md face woman shimmer outline f15d0 md home alert outline f15d1 md lock alert outline f15d2 md lock open alert outline f15d3 md sim alert outline f15d4 md sim off outline f15d5 md sim outline f15d6 md book open page variant outline f15d7 md fire alert f15d8 md ray start vertex end f15d9 md camera flip f15da md camera flip outline f15db md orbit variant f15dc md circle box f15dd md circle box outline f15de md mustache f15df md comment minus f15e0 md comment minus outline f15e1 md comment off f15e2 md comment off outline f15e3 md eye remove f15e4 md eye remove outline f15e5 md unicycle f15e6 md glass cocktail off f15e7 md glass mug off f15e8 md glass mug variant off f15e9 md bicycle penny farthing f15ea md cart check f15eb md cart variant f15ec md baseball diamond f15ed md baseball diamond outline f15ee md fridge industrial f15ef md fridge industrial alert f15f0 md fridge industrial alert outline f15f1 md fridge industrial off f15f2 md fridge industrial off outline f15f3 md fridge industrial outline f15f4 md fridge variant f15f5 md fridge variant alert f15f6 md fridge variant alert outline f15f7 md fridge variant off f15f8 md fridge variant off outline f15f9 md fridge variant outline f15fa md windsock f15fb md dance ballroom f15fc md dots grid f15fd md dots square f15fe md dots triangle f15ff md dots hexagon f1600 md card minus f1601 md card minus outline f1602 md card off f1603 md card off outline f1604 md card remove f1605 md card remove outline f1606 md torch f1607 md navigation outline f1608 md map marker star f1609 md map marker star outline f160a md manjaro f160b md fast forward 60 f160c md rewind 60 f160d md image text f160e md family tree f160f md car emergency f1610 md notebook minus f1611 md notebook minus outline f1612 md notebook plus f1613 md notebook plus outline f1614 md notebook remove f1615 md notebook remove outline f1616 md connection f1617 md language rust f1618 md clipboard minus f1619 md clipboard minus outline f161a md clipboard off f161b md clipboard off outline f161c md clipboard remove f161d md clipboard remove outline f161e md clipboard search f161f md clipboard search outline f1620 md clipboard text off f1621 md clipboard text off outline f1622 md clipboard text search f1623 md clipboard text search outline f1624 md database alert outline f1625 md database arrow down outline f1626 md database arrow left outline f1627 md database arrow right outline f1628 md database arrow up outline f1629 md database check outline f162a md database clock outline f162b md database edit outline f162c md database export outline f162d md database import outline f162e md database lock outline f162f md database marker outline f1630 md database minus outline f1631 md database off outline f1632 md database outline f1633 md database plus outline f1634 md database refresh outline f1635 md database remove outline f1636 md database search outline f1637 md database settings outline f1638 md database sync outline f1639 md minus thick f163a md database alert f163b md database arrow down f163c md database arrow left f163d md database arrow right f163e md database arrow up f163f md database clock f1640 md database off f1641 md calendar lock f1642 md calendar lock outline f1643 md content save off f1644 md content save off outline f1645 md credit card refresh f1646 md credit card refresh outline f1647 md credit card search f1648 md credit card search outline f1649 md credit card sync f164a md credit card sync outline f164b md database cog f164c md database cog outline f164d md message off f164e md message off outline f164f md note minus f1650 md note minus outline f1651 md note remove f1652 md note remove outline f1653 md note search f1654 md note search outline f1655 md bank check f1656 md bank off f1657 md bank off outline f1658 md briefcase off f1659 md briefcase off outline f165a md briefcase variant off f165b md briefcase variant off outline f165c md ghost off outline f165d md ghost outline f165e md store minus f165f md store plus f1660 md store remove f1661 md email remove f1662 md email remove outline f1663 md heart cog f1664 md heart cog outline f1665 md heart settings f1666 md heart settings outline f1667 md pentagram f1668 md star cog f1669 md star cog outline f166a md star settings f166b md star settings outline f166c md calendar end f166d md calendar start f166e md cannabis off f166f md mower f1670 md mower bag f1671 md lock off f1672 md lock off outline f1673 md shark fin f1674 md shark fin outline f1675 md paw outline f1676 md paw off outline f1677 md snail f1678 md pig variant outline f1679 md piggy bank outline f167a md robot outline f167b md robot off outline f167c md book alert f167d md book alert outline f167e md book arrow down f167f md book arrow down outline f1680 md book arrow left f1681 md book arrow left outline f1682 md book arrow right f1683 md book arrow right outline f1684 md book arrow up f1685 md book arrow up outline f1686 md book cancel f1687 md book cancel outline f1688 md book clock f1689 md book clock outline f168a md book cog f168b md book cog outline f168c md book edit f168d md book edit outline f168e md book lock open outline f168f md book lock outline f1690 md book marker f1691 md book marker outline f1692 md book minus outline f1693 md book music outline f1694 md book off f1695 md book off outline f1696 md book plus outline f1697 md book refresh f1698 md book refresh outline f1699 md book remove outline f169a md book settings f169b md book settings outline f169c md book sync f169d md robot angry f169e md robot angry outline f169f md robot confused f16a0 md robot confused outline f16a1 md robot dead f16a2 md robot dead outline f16a3 md robot excited f16a4 md robot excited outline f16a5 md robot love f16a6 md robot love outline f16a7 md robot off f16a8 md lock check outline f16a9 md lock minus f16aa md lock minus outline f16ab md lock open check outline f16ac md lock open minus f16ad md lock open minus outline f16ae md lock open plus f16af md lock open plus outline f16b0 md lock open remove f16b1 md lock open remove outline f16b2 md lock plus outline f16b3 md lock remove f16b4 md lock remove outline f16b5 md wifi alert f16b6 md wifi arrow down f16b7 md wifi arrow left f16b8 md wifi arrow left right f16b9 md wifi arrow right f16ba md wifi arrow up f16bb md wifi arrow up down f16bc md wifi cancel f16bd md wifi check f16be md wifi cog f16bf md wifi lock f16c0 md wifi lock open f16c1 md wifi marker f16c2 md wifi minus f16c3 md wifi plus f16c4 md wifi refresh f16c5 md wifi remove f16c6 md wifi settings f16c7 md wifi sync f16c8 md book sync outline f16c9 md book education f16ca md book education outline f16cb md wifi strength 1 lock open f16cc md wifi strength 2 lock open f16cd md wifi strength 3 lock open f16ce md wifi strength 4 lock open f16cf md wifi strength lock open outline f16d0 md cookie alert f16d1 md cookie alert outline f16d2 md cookie check f16d3 md cookie check outline f16d4 md cookie cog f16d5 md cookie cog outline f16d6 md cookie plus f16d7 md cookie plus outline f16d8 md cookie remove f16d9 md cookie remove outline f16da md cookie minus f16db md cookie minus outline f16dc md cookie settings f16dd md cookie settings outline f16de md cookie outline f16df md tape drive f16e0 md abacus f16e1 md calendar clock outline f16e2 md clipboard clock f16e3 md clipboard clock outline f16e4 md cookie clock f16e5 md cookie clock outline f16e6 md cookie edit f16e7 md cookie edit outline f16e8 md cookie lock f16e9 md cookie lock outline f16ea md cookie off f16eb md cookie off outline f16ec md cookie refresh f16ed md cookie refresh outline f16ee md dog side off f16ef md gift off f16f0 md gift off outline f16f1 md gift open f16f2 md gift open outline f16f3 md movie check f16f4 md movie check outline f16f5 md movie cog f16f6 md movie cog outline f16f7 md movie minus f16f8 md movie minus outline f16f9 md movie off f16fa md movie off outline f16fb md movie open check f16fc md movie open check outline f16fd md movie open cog f16fe md movie open cog outline f16ff md movie open edit f1700 md movie open edit outline f1701 md movie open minus f1702 md movie open minus outline f1703 md movie open off f1704 md movie open off outline f1705 md movie open play f1706 md movie open play outline f1707 md movie open plus f1708 md movie open plus outline f1709 md movie open remove f170a md movie open remove outline f170b md movie open settings f170c md movie open settings outline f170d md movie open star f170e md movie open star outline f170f md movie play f1710 md movie play outline f1711 md movie plus f1712 md movie plus outline f1713 md movie remove f1714 md movie remove outline f1715 md movie settings f1716 md movie settings outline f1717 md movie star f1718 md movie star outline f1719 md robot happy f171a md robot happy outline f171b md turkey f171c md food turkey f171d md fan auto f171e md alarm light off f171f md alarm light off outline f1720 md broadcast f1721 md broadcast off f1722 md fire off f1723 md firework off f1724 md projector screen outline f1725 md script text key f1726 md script text key outline f1727 md script text play f1728 md script text play outline f1729 md surround sound 2 1 f172a md surround sound 5 1 2 f172b md tag arrow down f172c md tag arrow down outline f172d md tag arrow left f172e md tag arrow left outline f172f md tag arrow right f1730 md tag arrow right outline f1731 md tag arrow up f1732 md tag arrow up outline f1733 md train car passenger f1734 md train car passenger door f1735 md train car passenger door open f1736 md train car passenger variant f1737 md webcam off f1738 md chat question f1739 md chat question outline f173a md message question f173b md message question outline f173c md kettle pour over f173d md message reply outline f173e md message reply text outline f173f md koala f1740 md check decagram outline f1741 md star shooting f1742 md star shooting outline f1743 md table picnic f1744 md kitesurfing f1745 md paragliding f1746 md surfing f1747 md floor lamp torchiere f1748 md mortar pestle f1749 md cast audio variant f174a md gradient horizontal f174b md archive cancel f174c md archive cancel outline f174d md archive check f174e md archive check outline f174f md archive clock f1750 md archive clock outline f1751 md archive cog f1752 md archive cog outline f1753 md archive edit f1754 md archive edit outline f1755 md archive eye f1756 md archive eye outline f1757 md archive lock f1758 md archive lock open f1759 md archive lock open outline f175a md archive lock outline f175b md archive marker f175c md archive marker outline f175d md archive minus f175e md archive minus outline f175f md archive music f1760 md archive music outline f1761 md archive off f1762 md archive off outline f1763 md archive plus f1764 md archive plus outline f1765 md archive refresh f1766 md archive refresh outline f1767 md archive remove f1768 md archive remove outline f1769 md archive search f176a md archive search outline f176b md archive settings f176c md archive settings outline f176d md archive star f176e md archive star outline f176f md archive sync f1770 md archive sync outline f1771 md brush off f1772 md file image marker f1773 md file image marker outline f1774 md file marker f1775 md file marker outline f1776 md hamburger check f1777 md hamburger minus f1778 md hamburger off f1779 md hamburger plus f177a md hamburger remove f177b md image marker f177c md image marker outline f177d md note alert f177e md note alert outline f177f md note check f1780 md note check outline f1781 md note edit f1782 md note edit outline f1783 md note off f1784 md note off outline f1785 md printer off outline f1786 md printer outline f1787 md progress pencil f1788 md progress star f1789 md sausage off f178a md folder eye f178b md folder eye outline f178c md information off f178d md information off outline f178e md sticker text f178f md sticker text outline f1790 md web cancel f1791 md web refresh f1792 md web sync f1793 md chandelier f1794 md home switch f1795 md home switch outline f1796 md sun snowflake f1797 md ceiling fan f1798 md ceiling fan light f1799 md smoke f179a md fence f179b md light recessed f179c md battery lock f179d md battery lock open f179e md folder hidden f179f md mirror rectangle f17a0 md mirror variant f17a1 md arrow down left f17a2 md arrow down left bold f17a3 md arrow down right f17a4 md arrow down right bold f17a5 md arrow left bottom f17a6 md arrow left bottom bold f17a7 md arrow left top f17a8 md arrow left top bold f17a9 md arrow right bottom f17aa md arrow right bottom bold f17ab md arrow right top f17ac md arrow right top bold f17ad md arrow u down left f17ae md arrow u down left bold f17af md arrow u down right f17b0 md arrow u down right bold f17b1 md arrow u left bottom f17b2 md arrow u left bottom bold f17b3 md arrow u left top f17b4 md arrow u left top bold f17b5 md arrow u right bottom f17b6 md arrow u right bottom bold f17b7 md arrow u right top f17b8 md arrow u right top bold f17b9 md arrow u up left f17ba md arrow u up left bold f17bb md arrow u up right f17bc md arrow u up right bold f17bd md arrow up left f17be md arrow up left bold f17bf md arrow up right f17c0 md arrow up right bold f17c1 md select remove f17c2 md selection ellipse remove f17c3 md selection remove f17c4 md human greeting f17c5 md ph f17c6 md water sync f17c7 md ceiling light outline f17c8 md floor lamp outline f17c9 md wall sconce flat outline f17ca md wall sconce flat variant outline f17cb md wall sconce outline f17cc md wall sconce round outline f17cd md wall sconce round variant outline f17ce md floor lamp dual outline f17cf md floor lamp torchiere variant outline f17d0 md lamp outline f17d1 md lamps outline f17d2 md candelabra f17d3 md candelabra fire f17d4 md menorah f17d5 md menorah fire f17d6 md floor lamp torchiere outline f17d7 md credit card edit f17d8 md credit card edit outline f17d9 md briefcase eye f17da md briefcase eye outline f17db md soundbar f17dc md crown circle f17dd md crown circle outline f17de md battery arrow down f17df md battery arrow down outline f17e0 md battery arrow up f17e1 md battery arrow up outline f17e2 md battery check f17e3 md battery check outline f17e4 md battery minus f17e5 md battery minus outline f17e6 md battery plus f17e7 md battery plus outline f17e8 md battery remove f17e9 md battery remove outline f17ea md chili alert f17eb md chili alert outline f17ec md chili hot outline f17ed md chili medium outline f17ee md chili mild outline f17ef md chili off outline f17f0 md cake variant outline f17f1 md card multiple f17f2 md card multiple outline f17f3 md account cowboy hat outline f17f4 md lightbulb spot f17f5 md lightbulb spot off f17f6 md fence electric f17f7 md gate arrow left f17f8 md gate alert f17f9 md boom gate up f17fa md boom gate up outline f17fb md garage lock f17fc md garage variant lock f17fd md cellphone check f17fe md sun wireless f17ff md sun wireless outline f1800 md lightbulb auto f1801 md lightbulb auto outline f1802 md lightbulb variant f1803 md lightbulb variant outline f1804 md lightbulb fluorescent tube f1805 md lightbulb fluorescent tube outline f1806 md water circle f1807 md fire circle f1808 md smoke detector outline f1809 md smoke detector off f180a md smoke detector off outline f180b md smoke detector variant f180c md smoke detector variant off f180d md projector screen off f180e md projector screen off outline f180f md projector screen variant f1810 md projector screen variant off f1811 md projector screen variant off outline f1812 md projector screen variant outline f1813 md brush variant f1814 md car wrench f1815 md account injury f1816 md account injury outline f1817 md balcony f1818 md bathtub f1819 md bathtub outline f181a md blender outline f181b md coffee maker outline f181c md countertop f181d md countertop outline f181e md door sliding f181f md door sliding lock f1820 md door sliding open f1821 md hand wave f1822 md hand wave outline f1823 md human male female child f1824 md iron f1825 md iron outline f1826 md liquid spot f1827 md mosque f1828 md shield moon f1829 md shield moon outline f182a md traffic light outline f182b md hand front left f182c md hand back left outline f182d md hand back right outline f182e md hand front left outline f182f md hand front right outline f1830 md hand back left off f1831 md hand back right off f1832 md hand back left off outline f1833 md hand back right off outline f1834 md battery sync f1835 md battery sync outline f1836 md food takeout box f1837 md food takeout box outline f1838 md iron board f1839 md police station f183a md cellphone marker f183b md tooltip cellphone f183c md table pivot f183d md tunnel f183e md tunnel outline f183f md arrow projectile multiple f1840 md arrow projectile f1841 md bow arrow f1842 md axe battle f1843 md mace f1844 md magic staff f1845 md spear f1846 md curtains f1847 md curtains closed f1848 md human non binary f1849 md waterfall f184a md egg fried f184b md food hot dog f184c md induction f184d md pipe valve f184e md shipping pallet f184f md earbuds f1850 md earbuds off f1851 md earbuds off outline f1852 md earbuds outline f1853 md circle opacity f1854 md square opacity f1855 md water opacity f1856 md vector polygon variant f1857 md vector square close f1858 md vector square open f1859 md waves arrow left f185a md waves arrow right f185b md waves arrow up f185c md cash fast f185d md radioactive circle f185e md radioactive circle outline f185f md cctv off f1860 md format list group f1861 md clock plus f1862 md clock plus outline f1863 md clock minus f1864 md clock minus outline f1865 md clock remove f1866 md clock remove outline f1867 md account arrow up f1868 md account arrow down f1869 md account arrow down outline f186a md account arrow up outline f186b md audio input rca f186c md audio input stereo minijack f186d md audio input xlr f186e md horse variant fast f186f md email fast f1870 md email fast outline f1871 md camera document f1872 md camera document off f1873 md glass fragile f1874 md magnify expand f1875 md town hall f1876 md monitor small f1877 md diversify f1878 md car wireless f1879 md car select f187a md airplane alert f187b md airplane check f187c md airplane clock f187d md airplane cog f187e md airplane edit f187f md airplane marker f1880 md airplane minus f1881 md airplane plus f1882 md airplane remove f1883 md airplane search f1884 md airplane settings f1885 md flower pollen f1886 md flower pollen outline f1887 md hammer sickle f1888 md view gallery f1889 md view gallery outline f188a md umbrella beach f188b md umbrella beach outline f188c md cabin a frame f188d md all inclusive box f188e md all inclusive box outline f188f md hand coin f1890 md hand coin outline f1891 md truck flatbed f1892 md layers edit f1893 md multicast f1894 md hydrogen station f1895 md thermometer bluetooth f1896 md tire f1897 md forest f1898 md account tie hat f1899 md account tie hat outline f189a md account wrench f189b md account wrench outline f189c md bicycle cargo f189d md calendar collapse horizontal f189e md calendar expand horizontal f189f md cards club outline f18a0 md heart outline f18a1 md cards playing f18a2 md cards playing club f18a3 md cards playing club multiple f18a4 md cards playing club multiple outline f18a5 md cards playing club outline f18a6 md cards playing diamond f18a7 md cards playing diamond multiple f18a8 md cards playing diamond multiple outline f18a9 md cards playing diamond outline f18aa md cards playing heart f18ab md cards playing heart multiple f18ac md cards playing heart multiple outline f18ad md cards playing heart outline f18ae md cards playing spade f18af md cards playing spade multiple f18b0 md cards playing spade multiple outline f18b1 md cards playing spade outline f18b2 md cards spade outline f18b3 md compare remove f18b4 md dolphin f18b5 md fuel cell f18b6 md hand extended f18b7 md hand extended outline f18b8 md printer 3d nozzle heat f18b9 md printer 3d nozzle heat outline f18ba md shark f18bb md shark off f18bc md shield crown f18bd md shield crown outline f18be md shield sword f18bf md shield sword outline f18c0 md sickle f18c1 md store alert f18c2 md store alert outline f18c3 md store check f18c4 md store check outline f18c5 md store clock f18c6 md store clock outline f18c7 md store cog f18c8 md store cog outline f18c9 md store edit f18ca md store edit outline f18cb md store marker f18cc md store marker outline f18cd md store minus outline f18ce md store off f18cf md store off outline f18d0 md store plus outline f18d1 md store remove outline f18d2 md store search f18d3 md store search outline f18d4 md store settings f18d5 md store settings outline f18d6 md sun thermometer f18d7 md sun thermometer outline f18d8 md truck cargo container f18d9 md vector square edit f18da md vector square minus f18db md vector square plus f18dc md vector square remove f18dd md ceiling light multiple f18de md ceiling light multiple outline f18df md wiper wash alert f18e0 md cart heart f18e1 md virus off f18e2 md virus off outline f18e3 md map marker account f18e4 md map marker account outline f18e5 md basket check f18e6 md basket check outline f18e7 md credit card lock f18e8 md credit card lock outline f18e9 md format underline wavy f18ea md content save check f18eb md content save check outline f18ec md filter check f18ed md filter check outline f18ee md flag off f18ef md flag off outline f18f0 md near me f18f1 md navigation variant outline f18f2 md refresh auto f18f3 md tilde off f18f4 md fit to screen f18f5 md fit to screen outline f18f6 md weather cloudy clock f18f7 md smart card off f18f8 md smart card off outline f18f9 md clipboard text clock f18fa md clipboard text clock outline f18fb md teddy bear f18fc md cow off f18fd md eye arrow left f18fe md eye arrow left outline f18ff md eye arrow right f1900 md eye arrow right outline f1901 md home battery f1902 md home battery outline f1903 md home lightning bolt f1904 md home lightning bolt outline f1905 md leaf circle f1906 md leaf circle outline f1907 md tag search f1908 md tag search outline f1909 md car brake fluid level f190a md car brake low pressure f190b md car brake temperature f190c md car brake worn linings f190d md car light alert f190e md car speed limiter f190f md credit card chip f1910 md credit card chip outline f1911 md credit card fast f1912 md credit card fast outline f1913 md integrated circuit chip f1914 md thumbs up down outline f1915 md food off outline f1916 md food outline f1917 md format page split f1918 md chart waterfall f1919 md gamepad outline f191a md network strength 4 cog f191b md account sync f191c md account sync outline f191d md bus electric f191e md liquor f191f md database eye f1920 md database eye off f1921 md database eye off outline f1922 md database eye outline f1923 md timer settings f1924 md timer settings outline f1925 md timer cog f1926 md timer cog outline f1927 md checkbox marked circle plus outline f1928 md panorama horizontal f1929 md panorama vertical f192a md advertisements f192b md advertisements off f192c md transmission tower export f192d md transmission tower import f192e md smoke detector alert f192f md smoke detector alert outline f1930 md smoke detector variant alert f1931 md coffee maker check f1932 md coffee maker check outline f1933 md cog pause f1934 md cog pause outline f1935 md cog play f1936 md cog play outline f1937 md cog stop f1938 md cog stop outline f1939 md copyleft f193a md fast forward 15 f193b md file image minus f193c md file image minus outline f193d md file image plus f193e md file image plus outline f193f md file image remove f1940 md file image remove outline f1941 md message badge f1942 md message badge outline f1943 md newspaper check f1944 md newspaper remove f1945 md publish off f1946 md rewind 15 f1947 md view dashboard edit f1948 md view dashboard edit outline f1949 md office building cog f194a md office building cog outline f194b md hand clap f194c md cone f194d md cone off f194e md cylinder f194f md cylinder off f1950 md octahedron f1951 md octahedron off f1952 md pyramid f1953 md pyramid off f1954 md sphere f1955 md sphere off f1956 md format letter spacing f1957 md french fries f1958 md scent f1959 md scent off f195a md palette swatch variant f195b md email seal f195c md email seal outline f195d md stool f195e md stool outline f195f md panorama wide angle f1960 md account lock open f1961 md account lock open outline f1962 md align horizontal distribute f1963 md align vertical distribute f1964 md arrow bottom left bold box f1965 md arrow bottom left bold box outline f1966 md arrow bottom right bold box f1967 md arrow bottom right bold box outline f1968 md arrow top left bold box f1969 md arrow top left bold box outline f196a md arrow top right bold box f196b md arrow top right bold box outline f196c md bookmark box multiple f196d md bookmark box multiple outline f196e md bullhorn variant f196f md bullhorn variant outline f1970 md candy f1971 md candy off f1972 md candy off outline f1973 md candy outline f1974 md car clock f1975 md crowd f1976 md currency rupee f1977 md diving f1978 md dots circle f1979 md elevator passenger off f197a md elevator passenger off outline f197b md elevator passenger outline f197c md eye refresh f197d md eye refresh outline f197e md folder check f197f md folder check outline f1980 md human dolly f1981 md human white cane f1982 md ip outline f1983 md key alert f1984 md key alert outline f1985 md kite f1986 md kite outline f1987 md light flood down f1988 md light flood up f1989 md microphone question f198a md microphone question outline f198b md cradle f198c md panorama outline f198d md panorama sphere f198e md panorama sphere outline f198f md panorama variant f1990 md panorama variant outline f1991 md cradle outline f1992 md fraction one half f1993 md phone refresh f1994 md phone refresh outline f1995 md phone sync f1996 md phone sync outline f1997 md razor double edge f1998 md razor single edge f1999 md rotate 360 f199a md shield lock open f199b md shield lock open outline f199c md sitemap outline f199d md sprinkler fire f199e md tab search f199f md timer sand complete f19a0 md timer sand paused f19a1 md vacuum f19a2 md vacuum outline f19a3 md wrench clock f19a4 md pliers f19a5 md sun compass f19a6 md truck snowflake f19a7 md camera marker f19a8 md camera marker outline f19a9 md video marker f19aa md video marker outline f19ab md wind turbine alert f19ac md wind turbine check f19ad md truck plus f19ae md truck minus f19af md truck remove f19b0 md arrow right thin f19b1 md arrow left thin f19b2 md arrow up thin f19b3 md arrow down thin f19b4 md arrow top right thin f19b5 md arrow top left thin f19b6 md arrow bottom left thin f19b7 md arrow bottom right thin f19b8 md scale unbalanced f19b9 md draw pen f19ba md clock edit f19bb md clock edit outline f19bc md truck plus outline f19bd md truck minus outline f19be md truck remove outline f19bf md camera off outline f19c0 md home group plus f19c1 md home group minus f19c2 md home group remove f19c3 md file sign f19c4 md attachment lock f19c5 md cellphone arrow down variant f19c6 md file chart check f19c7 md file chart check outline f19c8 md file lock open f19c9 md file lock open outline f19ca md folder question f19cb md folder question outline f19cc md message fast f19cd md message fast outline f19ce md message text fast f19cf md message text fast outline f19d0 md monitor arrow down f19d1 md monitor arrow down variant f19d2 md needle off f19d3 md numeric off f19d4 md package variant closed minus f19d5 md package variant closed plus f19d6 md package variant closed remove f19d7 md package variant minus f19d8 md package variant plus f19d9 md package variant remove f19da md paperclip lock f19db md phone clock f19dc md receipt outline f19dd md transmission tower off f19de md truck alert f19df md truck alert outline f19e0 md bone off f19e1 md lightbulb alert f19e2 md lightbulb alert outline f19e3 md lightbulb question f19e4 md lightbulb question outline f19e5 md battery clock f19e6 md battery clock outline f19e7 md autorenew off f19e8 md folder arrow down f19e9 md folder arrow down outline f19ea md folder arrow left f19eb md folder arrow left outline f19ec md folder arrow left right f19ed md folder arrow left right outline f19ee md folder arrow right f19ef md folder arrow right outline f19f0 md folder arrow up f19f1 md folder arrow up down f19f2 md folder arrow up down outline f19f3 md folder arrow up outline f19f4 md folder cancel f19f5 md folder cancel outline f19f6 md folder file f19f7 md folder file outline f19f8 md folder off f19f9 md folder off outline f19fa md folder play f19fb md folder play outline f19fc md folder wrench f19fd md folder wrench outline f19fe md image refresh f19ff md image refresh outline f1a00 md image sync f1a01 md image sync outline f1a02 md percent box f1a03 md percent box outline f1a04 md percent circle f1a05 md percent circle outline f1a06 md sale outline f1a07 md square rounded badge f1a08 md square rounded badge outline f1a09 md triangle small down f1a0a md triangle small up f1a0b md notebook heart f1a0c md notebook heart outline f1a0d md brush outline f1a0e md fruit pear f1a0f md raw f1a10 md raw off f1a11 md wall fire f1a12 md home clock f1a13 md home clock outline f1a14 md camera lock f1a15 md camera lock outline f1a16 md play box lock f1a17 md play box lock open f1a18 md play box lock open outline f1a19 md play box lock outline f1a1a md robot industrial outline f1a1b md gas burner f1a1c md video 2d f1a1d md book heart f1a1e md book heart outline f1a1f md account hard hat outline f1a20 md account school f1a21 md account school outline f1a22 md library outline f1a23 md projector off f1a24 md light switch off f1a25 md toggle switch variant f1a26 md toggle switch variant off f1a27 md asterisk circle outline f1a28 md barrel outline f1a29 md bell cog f1a2a md bell cog outline f1a2b md blinds horizontal f1a2c md blinds horizontal closed f1a2d md blinds vertical f1a2e md blinds vertical closed f1a2f md bulkhead light f1a30 md calendar today outline f1a31 md calendar week begin outline f1a32 md calendar week end f1a33 md calendar week end outline f1a34 md calendar week outline f1a35 md cloud percent f1a36 md cloud percent outline f1a37 md coach lamp variant f1a38 md compost f1a39 md currency fra f1a3a md fan clock f1a3b md file rotate left f1a3c md file rotate left outline f1a3d md file rotate right f1a3e md file rotate right outline f1a3f md filter multiple f1a40 md filter multiple outline f1a41 md gymnastics f1a42 md hand clap off f1a43 md heat pump f1a44 md heat pump outline f1a45 md heat wave f1a46 md home off f1a47 md home off outline f1a48 md landslide f1a49 md landslide outline f1a4a md laptop account f1a4b md led strip variant off f1a4c md lightbulb night f1a4d md lightbulb night outline f1a4e md lightbulb on 10 f1a4f md lightbulb on 20 f1a50 md lightbulb on 30 f1a51 md lightbulb on 40 f1a52 md lightbulb on 50 f1a53 md lightbulb on 60 f1a54 md lightbulb on 70 f1a55 md lightbulb on 80 f1a56 md lightbulb on 90 f1a57 md meter electric f1a58 md meter electric outline f1a59 md meter gas f1a5a md meter gas outline f1a5b md monitor account f1a5c md pill off f1a5d md plus lock f1a5e md plus lock open f1a5f md pool thermometer f1a60 md post lamp f1a61 md rabbit variant f1a62 md rabbit variant outline f1a63 md receipt text check f1a64 md receipt text check outline f1a65 md receipt text minus f1a66 md receipt text minus outline f1a67 md receipt text plus f1a68 md receipt text plus outline f1a69 md receipt text remove f1a6a md receipt text remove outline f1a6b md roller shade f1a6c md roller shade closed f1a6d md seed plus f1a6e md seed plus outline f1a6f md shopping search outline f1a70 md snowflake check f1a71 md snowflake thermometer f1a72 md snowshoeing f1a73 md solar power variant f1a74 md solar power variant outline f1a75 md storage tank f1a76 md storage tank outline f1a77 md sun clock f1a78 md sun clock outline f1a79 md sun snowflake variant f1a7a md tag check f1a7b md tag check outline f1a7c md text box edit f1a7d md text box edit outline f1a7e md text search variant f1a7f md thermometer check f1a80 md thermometer water f1a81 md tsunami f1a82 md turbine f1a83 md volcano f1a84 md volcano outline f1a85 md water thermometer f1a86 md water thermometer outline f1a87 md wheelchair f1a88 md wind power f1a89 md wind power outline f1a8a md window shutter cog f1a8b md window shutter settings f1a8c md account tie woman f1a8d md briefcase arrow left right f1a8e md briefcase arrow left right outline f1a8f md briefcase arrow up down f1a90 md briefcase arrow up down outline f1a91 md cash clock f1a92 md cash sync f1a93 md file arrow left right f1a94 md file arrow left right outline f1a95 md file arrow up down f1a96 md file arrow up down outline f1a97 md file document alert f1a98 md file document alert outline f1a99 md file document check f1a9a md file document check outline f1a9b md file document minus f1a9c md file document minus outline f1a9d md file document plus f1a9e md file document plus outline f1a9f md file document remove f1aa0 md file document remove outline f1aa1 md file minus f1aa2 md file minus outline f1aa3 md filter cog f1aa4 md filter cog outline f1aa5 md filter settings f1aa6 md filter settings outline f1aa7 md folder lock open outline f1aa8 md folder lock outline f1aa9 md forum minus f1aaa md forum minus outline f1aab md forum plus f1aac md forum plus outline f1aad md forum remove f1aae md forum remove outline f1aaf md heating coil f1ab0 md image lock f1ab1 md image lock outline f1ab2 md land fields f1ab3 md land plots f1ab4 md land plots circle f1ab5 md land plots circle variant f1ab6 md land rows horizontal f1ab7 md land rows vertical f1ab8 md medical cotton swab f1ab9 md rolodex f1aba md rolodex outline f1abb md sort variant off f1abc md tally mark 1 f1abd md tally mark 2 f1abe md tally mark 3 f1abf md tally mark 4 f1ac0 md tally mark 5 f1ac1 md attachment check f1ac2 md attachment minus f1ac3 md attachment off f1ac4 md attachment plus f1ac5 md attachment remove f1ac6 md paperclip check f1ac7 md paperclip minus f1ac8 md paperclip off f1ac9 md paperclip plus f1aca md paperclip remove f1acb md network pos f1acc md timer alert f1acd md timer alert outline f1ace md timer cancel f1acf md timer cancel outline f1ad0 md timer check f1ad1 md timer check outline f1ad2 md timer edit f1ad3 md timer edit outline f1ad4 md timer lock f1ad5 md timer lock open f1ad6 md timer lock open outline f1ad7 md timer lock outline f1ad8 md timer marker f1ad9 md timer marker outline f1ada md timer minus f1adb md timer minus outline f1adc md timer music f1add md timer music outline f1ade md timer pause f1adf md timer pause outline f1ae0 md timer play f1ae1 md timer play outline f1ae2 md timer plus f1ae3 md timer plus outline f1ae4 md timer refresh f1ae5 md timer refresh outline f1ae6 md timer remove f1ae7 md timer remove outline f1ae8 md timer star f1ae9 md timer star outline f1aea md timer stop f1aeb md timer stop outline f1aec md timer sync f1aed md timer sync outline f1aee md ear hearing loop f1aef md sail boat sink f1af0 md lecturn f500 mdi vector square f501 mdi access point f502 mdi access point network f503 mdi account f504 mdi account alert f505 mdi account box f506 mdi account box outline f507 mdi account check f508 mdi account circle f509 mdi account convert f50a mdi account key f50b mdi account location f50c mdi account minus f50d mdi account multiple f50e mdi account multiple outline f50f mdi account multiple plus f510 mdi account network f511 mdi account off f512 mdi account outline f513 mdi account plus f514 mdi account remove f515 mdi account search f516 mdi account star f517 mdi orbit f518 mdi account switch f519 mdi adjust f51a mdi air conditioner f51b mdi airballoon f51c mdi airplane f51d mdi airplane off f51e mdi airplay f51f mdi alarm f520 mdi alarm check f521 mdi alarm multiple f522 mdi alarm off f523 mdi alarm plus f524 mdi album f525 mdi alert f526 mdi alert box f527 mdi alert circle f528 mdi alert octagon f529 mdi alert outline f52a mdi alpha f52b mdi alphabetical f52c mdi amazon f52d mdi amazon clouddrive f52e mdi ambulance f52f mdi amplifier f530 mdi anchor f531 mdi android f532 mdi android debug bridge f533 mdi android studio f534 mdi apple f535 mdi apple finder f536 mdi apple ios f537 mdi apple mobileme f538 mdi apple safari f539 mdi font awesome f53a mdi apps f53b mdi archive f53c mdi arrange bring forward f53d mdi arrange bring to front f53e mdi arrange send backward f53f mdi arrange send to back f540 mdi arrow all f541 mdi arrow bottom left f542 mdi arrow bottom right f543 mdi arrow collapse all f544 mdi arrow down f545 mdi arrow down thick f546 mdi arrow down bold circle f547 mdi arrow down bold circle outline f548 mdi arrow down bold hexagon outline f549 mdi arrow down drop circle f54a mdi arrow down drop circle outline f54b mdi arrow expand all f54c mdi arrow left f54d mdi arrow left thick f54e mdi arrow left bold circle f54f mdi arrow left bold circle outline f550 mdi arrow left bold hexagon outline f551 mdi arrow left drop circle f552 mdi arrow left drop circle outline f553 mdi arrow right f554 mdi arrow right thick f555 mdi arrow right bold circle f556 mdi arrow right bold circle outline f557 mdi arrow right bold hexagon outline f558 mdi arrow right drop circle f559 mdi arrow right drop circle outline f55a mdi arrow top left f55b mdi arrow top right f55c mdi arrow up f55d mdi arrow up thick f55e mdi arrow up bold circle f55f mdi arrow up bold circle outline f560 mdi arrow up bold hexagon outline f561 mdi arrow up drop circle f562 mdi arrow up drop circle outline f563 mdi assistant f564 mdi at f565 mdi attachment f566 mdi audiobook f567 mdi auto fix f568 mdi auto upload f569 mdi autorenew f56a mdi av timer f56b mdi baby f56c mdi backburger f56d mdi backspace f56e mdi backup restore f56f mdi bank f570 mdi barcode f571 mdi barcode scan f572 mdi barley f573 mdi barrel f574 mdi basecamp f575 mdi basket f576 mdi basket fill f577 mdi basket unfill f578 mdi battery f579 mdi battery 10 f57a mdi battery 20 f57b mdi battery 30 f57c mdi battery 40 f57d mdi battery 50 f57e mdi battery 60 f57f mdi battery 70 f580 mdi battery 80 f581 mdi battery 90 f582 mdi battery alert f583 mdi battery charging f584 mdi battery charging 100 f585 mdi battery charging 20 f586 mdi battery charging 30 f587 mdi battery charging 40 f588 mdi battery charging 60 f589 mdi battery charging 80 f58a mdi battery charging 90 f58b mdi battery minus f58c mdi battery negative f58d mdi battery outline f58e mdi battery plus f58f mdi battery positive f590 mdi battery unknown f591 mdi beach f592 mdi flask f593 mdi flask empty f594 mdi flask empty outline f595 mdi flask outline f596 mdi beats f597 mdi beer f598 mdi behance f599 mdi bell f59a mdi bell off f59b mdi bell outline f59c mdi bell plus f59d mdi bell ring f59e mdi bell ring outline f59f mdi bell sleep f5a0 mdi beta f5a1 mdi bible f5a2 mdi bike f5a3 mdi bing f5a4 mdi binoculars f5a5 mdi bio f5a6 mdi biohazard f5a7 mdi bitbucket f5a8 mdi black mesa f5a9 mdi blackberry f5aa mdi blender f5ab mdi blinds f5ac mdi block helper f5ad mdi blogger f5ae mdi bluetooth f5af mdi bluetooth audio f5b0 mdi bluetooth connect f5b1 mdi bluetooth off f5b2 mdi bluetooth settings f5b3 mdi bluetooth transfer f5b4 mdi blur f5b5 mdi blur linear f5b6 mdi blur off f5b7 mdi blur radial f5b8 mdi bone f5b9 mdi book f5ba mdi book multiple f5bb mdi book multiple variant f5bc mdi book open f5bd mdi book open variant f5be mdi book variant f5bf mdi bookmark f5c0 mdi bookmark check f5c1 mdi bookmark music f5c2 mdi bookmark outline f5c3 mdi bookmark plus outline f5c4 mdi bookmark plus f5c5 mdi bookmark remove f5c6 mdi border all f5c7 mdi border bottom f5c8 mdi border color f5c9 mdi border horizontal f5ca mdi border inside f5cb mdi border left f5cc mdi border none f5cd mdi border outside f5ce mdi border right f5cf mdi border style f5d0 mdi border top f5d1 mdi border vertical f5d2 mdi bowling f5d3 mdi box f5d4 mdi box cutter f5d5 mdi briefcase f5d6 mdi briefcase check f5d7 mdi briefcase download f5d8 mdi briefcase upload f5d9 mdi brightness 1 f5da mdi brightness 2 f5db mdi brightness 3 f5dc mdi brightness 4 f5dd mdi brightness 5 f5de mdi brightness 6 f5df mdi brightness 7 f5e0 mdi brightness auto f5e1 mdi broom f5e2 mdi brush f5e3 mdi bug f5e4 mdi bulletin board f5e5 mdi bullhorn f5e6 mdi bus f5e7 mdi cached f5e8 mdi cake f5e9 mdi cake layered f5ea mdi cake variant f5eb mdi calculator f5ec mdi calendar f5ed mdi calendar blank f5ee mdi calendar check f5ef mdi calendar clock f5f0 mdi calendar multiple f5f1 mdi calendar multiple check f5f2 mdi calendar plus f5f3 mdi calendar remove f5f4 mdi calendar text f5f5 mdi calendar today f5f6 mdi call made f5f7 mdi call merge f5f8 mdi call missed f5f9 mdi call received f5fa mdi call split f5fb mdi camcorder f5fc mdi camcorder box f5fd mdi camcorder box off f5fe mdi camcorder off f5ff mdi camera f600 mdi camera enhance f601 mdi camera front f602 mdi camera front variant f603 mdi camera iris f604 mdi camera party mode f605 mdi camera rear f606 mdi camera rear variant f607 mdi camera switch f608 mdi camera timer f609 mdi candycane f60a mdi car f60b mdi car battery f60c mdi car connected f60d mdi car wash f60e mdi carrot f60f mdi cart f610 mdi cart outline f611 mdi cart plus f612 mdi case sensitive alt f613 mdi cash f614 mdi cash 100 f615 mdi cash multiple f616 mdi cash usd f617 mdi cast f618 mdi cast connected f619 mdi castle f61a mdi cat f61b mdi cellphone f61c mdi cellphone android f61d mdi cellphone basic f61e mdi cellphone dock f61f mdi cellphone iphone f620 mdi cellphone link f621 mdi cellphone link off f622 mdi cellphone settings f623 mdi certificate f624 mdi chair school f625 mdi chart arc f626 mdi chart areaspline f627 mdi chart bar f628 mdi chart histogram f629 mdi chart line f62a mdi chart pie f62b mdi check f62c mdi check all f62d mdi checkbox blank f62e mdi checkbox blank circle f62f mdi checkbox blank circle outline f630 mdi checkbox blank outline f631 mdi checkbox marked f632 mdi checkbox marked circle f633 mdi checkbox marked circle outline f634 mdi checkbox marked outline f635 mdi checkbox multiple blank f636 mdi checkbox multiple blank outline f637 mdi checkbox multiple marked f638 mdi checkbox multiple marked outline f639 mdi checkerboard f63a mdi chemical weapon f63b mdi chevron double down f63c mdi chevron double left f63d mdi chevron double right f63e mdi chevron double up f63f mdi chevron down f640 mdi chevron left f641 mdi chevron right f642 mdi chevron up f643 mdi church f644 mdi cisco webex f645 mdi city f646 mdi clipboard f647 mdi clipboard account f648 mdi clipboard alert f649 mdi clipboard arrow down f64a mdi clipboard arrow left f64b mdi clipboard check f64c mdi clipboard outline f64d mdi clipboard text f64e mdi clippy f64f mdi clock f650 mdi clock end f651 mdi clock fast f652 mdi clock in f653 mdi clock out f654 mdi clock start f655 mdi close f656 mdi close box f657 mdi close box outline f658 mdi close circle f659 mdi close circle outline f65a mdi close network f65b mdi close octagon f65c mdi close octagon outline f65d mdi closed caption f65e mdi cloud f65f mdi cloud check f660 mdi cloud circle f661 mdi cloud download f662 mdi cloud outline f663 mdi cloud off outline f664 mdi cloud print f665 mdi cloud print outline f666 mdi cloud upload f667 mdi code array f668 mdi code braces f669 mdi code brackets f66a mdi code equal f66b mdi code greater than f66c mdi code greater than or equal f66d mdi code less than f66e mdi code less than or equal f66f mdi code not equal f670 mdi code not equal variant f671 mdi code parentheses f672 mdi code string f673 mdi code tags f674 mdi codepen f675 mdi coffee f676 mdi coffee to go f677 mdi coin f678 mdi color helper f679 mdi comment f67a mdi comment account f67b mdi comment account outline f67c mdi comment alert f67d mdi comment alert outline f67e mdi comment check f67f mdi comment check outline f680 mdi comment multiple outline f681 mdi comment outline f682 mdi comment plus outline f683 mdi comment processing f684 mdi comment processing outline f685 mdi comment question outline f686 mdi comment remove outline f687 mdi comment text f688 mdi comment text outline f689 mdi compare f68a mdi compass f68b mdi compass outline f68c mdi console f68d mdi contact mail f68e mdi content copy f68f mdi content cut f690 mdi content duplicate f691 mdi content paste f692 mdi content save f693 mdi content save all f694 mdi contrast f695 mdi contrast box f696 mdi contrast circle f697 mdi cookie f698 mdi counter f699 mdi cow f69a mdi credit card f69b mdi credit card multiple f69c mdi credit card scan f69d mdi crop f69e mdi crop free f69f mdi crop landscape f6a0 mdi crop portrait f6a1 mdi crop square f6a2 mdi crosshairs f6a3 mdi crosshairs gps f6a4 mdi crown f6a5 mdi cube f6a6 mdi cube outline f6a7 mdi cube send f6a8 mdi cube unfolded f6a9 mdi cup f6aa mdi cup water f6ab mdi currency btc f6ac mdi currency eur f6ad mdi currency gbp f6ae mdi currency inr f6af mdi currency ngn f6b0 mdi currency rub f6b1 mdi currency try f6b2 mdi currency usd f6b3 mdi cursor default f6b4 mdi cursor default outline f6b5 mdi cursor move f6b6 mdi cursor pointer f6b7 mdi database f6b8 mdi database minus f6b9 mdi database plus f6ba mdi debug step into f6bb mdi debug step out f6bc mdi debug step over f6bd mdi decimal decrease f6be mdi decimal increase f6bf mdi delete f6c0 mdi delete variant f6c1 mdi delta f6c2 mdi deskphone f6c3 mdi desktop mac f6c4 mdi desktop tower f6c5 mdi details f6c6 mdi deviantart f6c7 mdi diamond f6c8 mdi creation f6c9 mdi dice 1 f6ca mdi dice 2 f6cb mdi dice 3 f6cc mdi dice 4 f6cd mdi dice 5 f6ce mdi dice 6 f6cf mdi directions f6d0 mdi disk alert f6d1 mdi disqus f6d2 mdi disqus outline f6d3 mdi division f6d4 mdi division box f6d5 mdi dns f6d6 mdi domain f6d7 mdi dots horizontal f6d8 mdi dots vertical f6d9 mdi download f6da mdi drag f6db mdi drag horizontal f6dc mdi drag vertical f6dd mdi drawing f6de mdi drawing box f6df mdi dribbble f6e0 mdi dribbble box f6e1 mdi drone f6e2 mdi dropbox f6e3 mdi drupal f6e4 mdi duck f6e5 mdi dumbbell f6e6 mdi earth f6e7 mdi earth off f6e8 mdi edge f6e9 mdi eject f6ea mdi elevation decline f6eb mdi elevation rise f6ec mdi elevator f6ed mdi email f6ee mdi email open f6ef mdi email outline f6f0 mdi email secure f6f1 mdi emoticon f6f2 mdi emoticon cool f6f3 mdi emoticon devil f6f4 mdi emoticon happy f6f5 mdi emoticon neutral f6f6 mdi emoticon poop f6f7 mdi emoticon sad f6f8 mdi emoticon tongue f6f9 mdi engine f6fa mdi engine outline f6fb mdi equal f6fc mdi equal box f6fd mdi eraser f6fe mdi escalator f6ff mdi ethernet f700 mdi ethernet cable f701 mdi ethernet cable off f702 mdi etsy f703 mdi evernote f704 mdi exclamation f705 mdi exit to app f706 mdi export f707 mdi eye f708 mdi eye off f709 mdi eyedropper f70a mdi eyedropper variant f70b mdi facebook f70c mdi facebook box f70d mdi facebook messenger f70e mdi factory f70f mdi fan f710 mdi fast forward f711 mdi fax f712 mdi ferry f713 mdi file f714 mdi file chart f715 mdi file check f716 mdi file cloud f717 mdi file delimited f718 mdi file document f719 mdi file document box f71a mdi file excel f71b mdi file excel box f71c mdi file export f71d mdi file find f71e mdi file image f71f mdi file import f720 mdi file lock f721 mdi file multiple f722 mdi file music f723 mdi file outline f724 mdi file pdf f725 mdi file pdf box f726 mdi file powerpoint f727 mdi file powerpoint box f728 mdi file presentation box f729 mdi file send f72a mdi file video f72b mdi file word f72c mdi file word box f72d mdi file xml f72e mdi film f72f mdi filmstrip f730 mdi filmstrip off f731 mdi filter f732 mdi filter outline f733 mdi filter remove f734 mdi filter remove outline f735 mdi filter variant f736 mdi fingerprint f737 mdi fire f738 mdi firefox f739 mdi fish f73a mdi flag f73b mdi flag checkered f73c mdi flag outline f73d mdi flag variant outline f73e mdi flag triangle f73f mdi flag variant f740 mdi flash f741 mdi flash auto f742 mdi flash off f743 mdi flashlight f744 mdi flashlight off f745 mdi flattr f746 mdi flip to back f747 mdi flip to front f748 mdi floppy f749 mdi flower f74a mdi folder f74b mdi folder account f74c mdi folder download f74d mdi folder google drive f74e mdi folder image f74f mdi folder lock f750 mdi folder lock open f751 mdi folder move f752 mdi folder multiple f753 mdi folder multiple image f754 mdi folder multiple outline f755 mdi folder outline f756 mdi folder plus f757 mdi folder remove f758 mdi folder upload f759 mdi food f75a mdi food apple f75b mdi food variant f75c mdi football f75d mdi football australian f75e mdi football helmet f75f mdi format align center f760 mdi format align justify f761 mdi format align left f762 mdi format align right f763 mdi format bold f764 mdi format clear f765 mdi format color fill f766 mdi format float center f767 mdi format float left f768 mdi format float none f769 mdi format float right f76a mdi format header 1 f76b mdi format header 2 f76c mdi format header 3 f76d mdi format header 4 f76e mdi format header 5 f76f mdi format header 6 f770 mdi format header decrease f771 mdi format header equal f772 mdi format header increase f773 mdi format header pound f774 mdi format indent decrease f775 mdi format indent increase f776 mdi format italic f777 mdi format line spacing f778 mdi format list bulleted f779 mdi format list bulleted type f77a mdi format list numbers f77b mdi format paint f77c mdi format paragraph f77d mdi format quote close f77e mdi format size f77f mdi format strikethrough f780 mdi format strikethrough variant f781 mdi format subscript f782 mdi format superscript f783 mdi format text f784 mdi format textdirection l to r f785 mdi format textdirection r to l f786 mdi format underline f787 mdi format wrap inline f788 mdi format wrap square f789 mdi format wrap tight f78a mdi format wrap top bottom f78b mdi forum f78c mdi forward f78d mdi foursquare f78e mdi fridge f78f mdi fridge filled f790 mdi fridge filled bottom f791 mdi fridge filled top f792 mdi fullscreen f793 mdi fullscreen exit f794 mdi function f795 mdi gamepad f796 mdi gamepad variant f797 mdi gas station f798 mdi gate f799 mdi gauge f79a mdi gavel f79b mdi gender female f79c mdi gender male f79d mdi gender male female f79e mdi gender transgender f79f mdi ghost f7a0 mdi gift f7a1 mdi git f7a2 mdi github box f7a3 mdi github circle f7a4 mdi glass flute f7a5 mdi glass mug f7a6 mdi glass stange f7a7 mdi glass tulip f7a8 mdi glassdoor f7a9 mdi glasses f7aa mdi gmail f7ab mdi gnome f7ac mdi google f7ad mdi google cardboard f7ae mdi google chrome f7af mdi google circles f7b0 mdi google circles communities f7b1 mdi google circles extended f7b2 mdi google circles group f7b3 mdi google controller f7b4 mdi google controller off f7b5 mdi google drive f7b6 mdi google earth f7b7 mdi google glass f7b8 mdi google nearby f7b9 mdi google pages f7ba mdi google physical web f7bb mdi google play f7bc mdi google plus f7bd mdi google plus box f7be mdi google translate f7bf mdi google wallet f7c0 mdi grid f7c1 mdi grid off f7c2 mdi group f7c3 mdi guitar electric f7c4 mdi guitar pick f7c5 mdi guitar pick outline f7c6 mdi hand pointing right f7c7 mdi hanger f7c8 mdi hangouts f7c9 mdi harddisk f7ca mdi headphones f7cb mdi headphones box f7cc mdi headphones settings f7cd mdi headset f7ce mdi headset dock f7cf mdi headset off f7d0 mdi heart f7d1 mdi heart box f7d2 mdi heart box outline f7d3 mdi heart broken f7d4 mdi heart outline f7d5 mdi help f7d6 mdi help circle f7d7 mdi hexagon f7d8 mdi hexagon outline f7d9 mdi history f7da mdi hololens f7db mdi home f7dc mdi home modern f7dd mdi home variant f7de mdi hops f7df mdi hospital f7e0 mdi hospital building f7e1 mdi hospital marker f7e2 mdi hotel f7e3 mdi houzz f7e4 mdi houzz box f7e5 mdi human f7e6 mdi human child f7e7 mdi human male female f7e8 mdi image f7e9 mdi image album f7ea mdi image area f7eb mdi image area close f7ec mdi image broken f7ed mdi image broken variant f7ee mdi image filter f7ef mdi image filter black white f7f0 mdi image filter center focus f7f1 mdi image filter center focus weak f7f2 mdi image filter drama f7f3 mdi image filter frames f7f4 mdi image filter hdr f7f5 mdi image filter none f7f6 mdi image filter tilt shift f7f7 mdi image filter vintage f7f8 mdi image multiple f7f9 mdi import f7fa mdi inbox arrow down f7fb mdi information f7fc mdi information outline f7fd mdi instagram f7fe mdi instapaper f7ff mdi internet explorer f800 mdi invert colors f801 mdi jeepney f802 mdi jira f803 mdi jsfiddle f804 mdi keg f805 mdi key f806 mdi key change f807 mdi key minus f808 mdi key plus f809 mdi key remove f80a mdi key variant f80b mdi keyboard f80c mdi keyboard backspace f80d mdi keyboard caps f80e mdi keyboard close f80f mdi keyboard off f810 mdi keyboard return f811 mdi keyboard tab f812 mdi keyboard variant f813 mdi kodi f814 mdi label f815 mdi label outline f816 mdi lan f817 mdi lan connect f818 mdi lan disconnect f819 mdi lan pending f81a mdi language csharp f81b mdi language css3 f81c mdi language html5 f81d mdi language javascript f81e mdi language php f81f mdi language python f820 mdi language python text f821 mdi laptop f822 mdi laptop chromebook f823 mdi laptop mac f824 mdi laptop windows f825 mdi lastfm f826 mdi launch f827 mdi layers f828 mdi layers off f829 mdi leaf f82a mdi led off f82b mdi led on f82c mdi led outline f82d mdi led variant off f82e mdi led variant on f82f mdi led variant outline f830 mdi library f831 mdi library books f832 mdi library music f833 mdi library plus f834 mdi lightbulb f835 mdi lightbulb outline f836 mdi link f837 mdi link off f838 mdi link variant f839 mdi link variant off f83a mdi linkedin f83b mdi linkedin box f83c mdi linux f83d mdi lock f83e mdi lock open f83f mdi lock open outline f840 mdi lock outline f841 mdi login f842 mdi logout f843 mdi looks f844 mdi loupe f845 mdi lumx f846 mdi magnet f847 mdi magnet on f848 mdi magnify f849 mdi magnify minus f84a mdi magnify plus f84b mdi mail ru f84c mdi map f84d mdi map marker f84e mdi map marker circle f84f mdi map marker multiple f850 mdi map marker off f851 mdi map marker radius f852 mdi margin f853 mdi markdown f854 mdi marker check f855 mdi martini f856 mdi material ui f857 mdi math compass f858 mdi maxcdn f859 mdi medium f85a mdi memory f85b mdi menu f85c mdi menu down f85d mdi menu left f85e mdi menu right f85f mdi menu up f860 mdi message f861 mdi message alert f862 mdi message draw f863 mdi message image f864 mdi message outline f865 mdi message processing f866 mdi message reply f867 mdi message reply text f868 mdi message text f869 mdi message text outline f86a mdi message video f86b mdi microphone f86c mdi microphone off f86d mdi microphone outline f86e mdi microphone settings f86f mdi microphone variant f870 mdi microphone variant off f871 mdi microsoft f872 mdi minecraft f873 mdi minus f874 mdi minus box f875 mdi minus circle f876 mdi minus circle outline f877 mdi minus network f878 mdi monitor f879 mdi monitor multiple f87a mdi more f87b mdi motorbike f87c mdi mouse f87d mdi mouse off f87e mdi mouse variant f87f mdi mouse variant off f880 mdi movie f881 mdi multiplication f882 mdi multiplication box f883 mdi music box f884 mdi music box outline f885 mdi music circle f886 mdi music note f887 mdi music note eighth f888 mdi music note half f889 mdi music note off f88a mdi music note quarter f88b mdi music note sixteenth f88c mdi music note whole f88d mdi nature f88e mdi nature people f88f mdi navigation f890 mdi needle f891 mdi nest protect f892 mdi nest thermostat f893 mdi new box f894 mdi newspaper f895 mdi nfc f896 mdi nfc tap f897 mdi nfc variant f898 mdi nodejs f899 mdi note f89a mdi note outline f89b mdi note plus f89c mdi note plus outline f89d mdi note text f89e mdi notification clear all f89f mdi numeric f8a0 mdi numeric 0 box f8a1 mdi numeric 0 box multiple outline f8a2 mdi numeric 0 box outline f8a3 mdi numeric 1 box f8a4 mdi numeric 1 box multiple outline f8a5 mdi numeric 1 box outline f8a6 mdi numeric 2 box f8a7 mdi numeric 2 box multiple outline f8a8 mdi numeric 2 box outline f8a9 mdi numeric 3 box f8aa mdi numeric 3 box multiple outline f8ab mdi numeric 3 box outline f8ac mdi numeric 4 box f8ad mdi numeric 4 box multiple outline f8ae mdi numeric 4 box outline f8af mdi numeric 5 box f8b0 mdi numeric 5 box multiple outline f8b1 mdi numeric 5 box outline f8b2 mdi numeric 6 box f8b3 mdi numeric 6 box multiple outline f8b4 mdi numeric 6 box outline f8b5 mdi numeric 7 box f8b6 mdi numeric 7 box multiple outline f8b7 mdi numeric 7 box outline f8b8 mdi numeric 8 box f8b9 mdi numeric 8 box multiple outline f8ba mdi numeric 8 box outline f8bb mdi numeric 9 box f8bc mdi numeric 9 box multiple outline f8bd mdi numeric 9 box outline f8be mdi numeric 9 plus box f8bf mdi numeric 9 plus box multiple outline f8c0 mdi numeric 9 plus box outline f8c1 mdi nutrition f8c2 mdi octagon f8c3 mdi octagon outline f8c4 mdi odnoklassniki f8c5 mdi office f8c6 mdi oil f8c7 mdi oil temperature f8c8 mdi omega f8c9 mdi onedrive f8ca mdi open in app f8cb mdi open in new f8cc mdi openid f8cd mdi opera f8ce mdi ornament f8cf mdi ornament variant f8d0 mdi inbox arrow up f8d1 mdi owl f8d2 mdi package f8d3 mdi package down f8d4 mdi package up f8d5 mdi package variant f8d6 mdi package variant closed f8d7 mdi palette f8d8 mdi palette advanced f8d9 mdi panda f8da mdi pandora f8db mdi panorama f8dc mdi panorama fisheye f8dd mdi panorama horizontal f8de mdi panorama vertical f8df mdi panorama wide angle f8e0 mdi paper cut vertical f8e1 mdi paperclip f8e2 mdi parking f8e3 mdi pause f8e4 mdi pause circle f8e5 mdi pause circle outline f8e6 mdi pause octagon f8e7 mdi pause octagon outline f8e8 mdi paw f8e9 mdi pen f8ea mdi pencil f8eb mdi pencil box f8ec mdi pencil box outline f8ed mdi pencil lock f8ee mdi pencil off f8ef mdi percent f8f0 mdi pharmacy f8f1 mdi phone f8f2 mdi phone bluetooth f8f3 mdi phone forward f8f4 mdi phone hangup f8f5 mdi phone in talk f8f6 mdi phone incoming f8f7 mdi phone locked f8f8 mdi phone log f8f9 mdi phone missed f8fa mdi phone outgoing f8fb mdi phone paused f8fc mdi phone settings f8fd mdi phone voip f8fe mdi pi f8ff mdi pi box f900 mdi pig f901 mdi pill f902 mdi pin f903 mdi pin off f904 mdi pine tree f905 mdi pine tree box f906 mdi pinterest f907 mdi pinterest box f908 mdi pizza f909 mdi play f90a mdi play box outline f90b mdi play circle f90c mdi play circle outline f90d mdi play pause f90e mdi play protected content f90f mdi playlist minus f910 mdi playlist play f911 mdi playlist plus f912 mdi playlist remove f913 mdi playstation f914 mdi plus f915 mdi plus box f916 mdi plus circle f917 mdi plus circle multiple outline f918 mdi plus circle outline f919 mdi plus network f91a mdi plus one f91b mdi pocket f91c mdi pokeball f91d mdi polaroid f91e mdi poll f91f mdi poll box f920 mdi polymer f921 mdi popcorn f922 mdi pound f923 mdi pound box f924 mdi power f925 mdi power settings f926 mdi power socket f927 mdi presentation f928 mdi presentation play f929 mdi printer f92a mdi printer 3d f92b mdi printer alert f92c mdi professional hexagon f92d mdi projector f92e mdi projector screen f92f mdi pulse f930 mdi puzzle f931 mdi qrcode f932 mdi qrcode scan f933 mdi quadcopter f934 mdi quality high f935 mdi quicktime f936 mdi radar f937 mdi radiator f938 mdi radio f939 mdi radio handheld f93a mdi radio tower f93b mdi radioactive f93c mdi radiobox blank f93d mdi radiobox marked f93e mdi raspberrypi f93f mdi ray end f940 mdi ray end arrow f941 mdi ray start f942 mdi ray start arrow f943 mdi ray start end f944 mdi ray vertex f945 mdi lastpass f946 mdi read f947 mdi youtube tv f948 mdi receipt f949 mdi record f94a mdi record rec f94b mdi recycle f94c mdi reddit f94d mdi redo f94e mdi redo variant f94f mdi refresh f950 mdi regex f951 mdi relative scale f952 mdi reload f953 mdi remote f954 mdi rename box f955 mdi repeat f956 mdi repeat off f957 mdi repeat once f958 mdi replay f959 mdi reply f95a mdi reply all f95b mdi reproduction f95c mdi resize bottom right f95d mdi responsive f95e mdi rewind f95f mdi ribbon f960 mdi road f961 mdi road variant f962 mdi rocket f963 mdi rotate 3d f964 mdi rotate left f965 mdi rotate left variant f966 mdi rotate right f967 mdi rotate right variant f968 mdi router wireless f969 mdi routes f96a mdi rss f96b mdi rss box f96c mdi ruler f96d mdi run fast f96e mdi sale f96f mdi satellite f970 mdi satellite variant f971 mdi scale f972 mdi scale bathroom f973 mdi school f974 mdi screen rotation f975 mdi screen rotation lock f976 mdi screwdriver f977 mdi script f978 mdi sd f979 mdi seal f97a mdi seat flat f97b mdi seat flat angled f97c mdi seat individual suite f97d mdi seat legroom extra f97e mdi seat legroom normal f97f mdi seat legroom reduced f980 mdi seat recline extra f981 mdi seat recline normal f982 mdi security f983 mdi security network f984 mdi select f985 mdi select all f986 mdi select inverse f987 mdi select off f988 mdi selection f989 mdi send f98a mdi server f98b mdi server minus f98c mdi server network f98d mdi server network off f98e mdi server off f98f mdi server plus f990 mdi server remove f991 mdi server security f992 mdi settings f993 mdi settings box f994 mdi shape plus f995 mdi share f996 mdi share variant f997 mdi shield f998 mdi shield outline f999 mdi shopping f99a mdi shopping music f99b mdi shredder f99c mdi shuffle f99d mdi shuffle disabled f99e mdi shuffle variant f99f mdi sigma f9a0 mdi sign caution f9a1 mdi signal f9a2 mdi silverware f9a3 mdi silverware fork f9a4 mdi silverware spoon f9a5 mdi silverware variant f9a6 mdi sim f9a7 mdi sim alert f9a8 mdi sim off f9a9 mdi sitemap f9aa mdi skip backward f9ab mdi skip forward f9ac mdi skip next f9ad mdi skip previous f9ae mdi skype f9af mdi skype business f9b0 mdi slack f9b1 mdi sleep f9b2 mdi sleep off f9b3 mdi smoking f9b4 mdi smoking off f9b5 mdi snapchat f9b6 mdi snowman f9b7 mdi soccer f9b8 mdi sofa f9b9 mdi sort f9ba mdi sort alphabetical f9bb mdi sort ascending f9bc mdi sort descending f9bd mdi sort numeric f9be mdi sort variant f9bf mdi soundcloud f9c0 mdi source fork f9c1 mdi source pull f9c2 mdi speaker f9c3 mdi speaker off f9c4 mdi speedometer f9c5 mdi spellcheck f9c6 mdi spotify f9c7 mdi spotlight f9c8 mdi spotlight beam f9c9 mdi square inc f9ca mdi square inc cash f9cb mdi stack overflow f9cc mdi stairs f9cd mdi star f9ce mdi star circle f9cf mdi star half f9d0 mdi star off f9d1 mdi star outline f9d2 mdi steam f9d3 mdi steering f9d4 mdi step backward f9d5 mdi step backward 2 f9d6 mdi step forward f9d7 mdi step forward 2 f9d8 mdi stethoscope f9d9 mdi stocking f9da mdi stop f9db mdi store f9dc mdi store 24 hour f9dd mdi stove f9de mdi subway variant f9df mdi sunglasses f9e0 mdi swap horizontal f9e1 mdi swap vertical f9e2 mdi swim f9e3 mdi switch f9e4 mdi sword f9e5 mdi sync f9e6 mdi sync alert f9e7 mdi sync off f9e8 mdi tab f9e9 mdi tab unselected f9ea mdi table f9eb mdi table column plus after f9ec mdi table column plus before f9ed mdi table column remove f9ee mdi table column width f9ef mdi table edit f9f0 mdi table large f9f1 mdi table row height f9f2 mdi table row plus after f9f3 mdi table row plus before f9f4 mdi table row remove f9f5 mdi tablet f9f6 mdi tablet android f9f7 mdi tablet ipad f9f8 mdi tag f9f9 mdi tag faces f9fa mdi tag multiple f9fb mdi tag outline f9fc mdi tag text outline f9fd mdi target f9fe mdi taxi f9ff mdi teamviewer fa00 mdi telegram fa01 mdi television fa02 mdi television guide fa03 mdi temperature celsius fa04 mdi temperature fahrenheit fa05 mdi temperature kelvin fa06 mdi tennis fa07 mdi tent fa08 mdi terrain fa09 mdi text to speech fa0a mdi text to speech off fa0b mdi texture fa0c mdi theater fa0d mdi theme light dark fa0e mdi thermometer fa0f mdi thermometer lines fa10 mdi thumb down fa11 mdi thumb down outline fa12 mdi thumb up fa13 mdi thumb up outline fa14 mdi thumbs up down fa15 mdi ticket fa16 mdi ticket account fa17 mdi ticket confirmation fa18 mdi tie fa19 mdi timelapse fa1a mdi timer fa1b mdi timer 10 fa1c mdi timer 3 fa1d mdi timer off fa1e mdi timer sand fa1f mdi timetable fa20 mdi toggle switch fa21 mdi toggle switch off fa22 mdi tooltip fa23 mdi tooltip edit fa24 mdi tooltip image fa25 mdi tooltip outline fa26 mdi tooltip outline plus fa27 mdi tooltip text fa28 mdi tooth fa29 mdi tor fa2a mdi traffic light fa2b mdi train fa2c mdi tram fa2d mdi transcribe fa2e mdi transcribe close fa2f mdi transfer fa30 mdi tree fa31 mdi trello fa32 mdi trending down fa33 mdi trending neutral fa34 mdi trending up fa35 mdi triangle fa36 mdi triangle outline fa37 mdi trophy fa38 mdi trophy award fa39 mdi trophy outline fa3a mdi trophy variant fa3b mdi trophy variant outline fa3c mdi truck fa3d mdi truck delivery fa3e mdi tshirt crew fa3f mdi tshirt v fa40 mdi tumblr fa41 mdi tumblr reblog fa42 mdi twitch fa43 mdi twitter fa44 mdi twitter box fa45 mdi twitter circle fa46 mdi twitter retweet fa47 mdi ubuntu fa48 mdi umbraco fa49 mdi umbrella fa4a mdi umbrella outline fa4b mdi undo fa4c mdi undo variant fa4d mdi unfold less horizontal fa4e mdi unfold more horizontal fa4f mdi ungroup fa50 mdi untappd fa51 mdi upload fa52 mdi usb fa53 mdi vector arrange above fa54 mdi vector arrange below fa55 mdi vector circle fa56 mdi vector circle variant fa57 mdi vector combine fa58 mdi vector curve fa59 mdi vector difference fa5a mdi vector difference ab fa5b mdi vector difference ba fa5c mdi vector intersection fa5d mdi vector line fa5e mdi vector point fa5f mdi vector polygon fa60 mdi vector polyline fa61 mdi vector selection fa62 mdi vector triangle fa63 mdi vector union fa64 mdi verified fa65 mdi vibrate fa66 mdi video fa67 mdi video off fa68 mdi video switch fa69 mdi view agenda fa6a mdi view array fa6b mdi view carousel fa6c mdi view column fa6d mdi view dashboard fa6e mdi view day fa6f mdi view grid fa70 mdi view headline fa71 mdi view list fa72 mdi view module fa73 mdi view quilt fa74 mdi view stream fa75 mdi view week fa76 mdi vimeo fa77 mdi venmo fa78 mdi vk fa79 mdi vk box fa7a mdi vk circle fa7b mdi vlc fa7c mdi voicemail fa7d mdi volume high fa7e mdi volume low fa7f mdi volume medium fa80 mdi volume off fa81 mdi vpn fa82 mdi walk fa83 mdi wallet fa84 mdi wallet giftcard fa85 mdi wallet membership fa86 mdi wallet travel fa87 mdi wan fa88 mdi watch fa89 mdi watch export fa8a mdi watch import fa8b mdi water fa8c mdi water off fa8d mdi water percent fa8e mdi water pump fa8f mdi weather cloudy fa90 mdi weather fog fa91 mdi weather hail fa92 mdi weather lightning fa93 mdi weather night fa94 mdi weather partlycloudy fa95 mdi weather pouring fa96 mdi weather rainy fa97 mdi weather snowy fa98 mdi weather sunny fa99 mdi weather sunset fa9a mdi weather sunset down fa9b mdi weather sunset up fa9c mdi weather windy fa9d mdi weather windy variant fa9e mdi web fa9f mdi webcam faa0 mdi weight faa1 mdi weight kilogram faa2 mdi whatsapp faa3 mdi wheelchair accessibility faa4 mdi white balance auto faa5 mdi white balance incandescent faa6 mdi white balance iridescent faa7 mdi white balance sunny faa8 mdi wifi faa9 mdi wifi off faaa mdi wii faab mdi wikipedia faac mdi window close faad mdi window closed faae mdi window maximize faaf mdi window minimize fab0 mdi window open fab1 mdi window restore fab2 mdi windows fab3 mdi wordpress fab4 mdi worker fab5 mdi wrap fab6 mdi wrench fab7 mdi wunderlist fab8 mdi xbox fab9 mdi xbox controller faba mdi xbox controller off fabb mdi xda fabc mdi xing fabd mdi xing box fabe mdi xing circle fabf mdi xml fac0 mdi yeast fac1 mdi yelp fac2 mdi youtube play fac3 mdi zip box fac4 mdi surround sound fac5 mdi vector rectangle fac6 mdi playlist check fac7 mdi format line style fac8 mdi format line weight fac9 mdi translate faca mdi voice facb mdi opacity facc mdi near me facd mdi clock alert face mdi human pregnant facf mdi sticker fad0 mdi scale balance fad1 mdi account card details fad2 mdi account multiple minus fad3 mdi airplane landing fad4 mdi airplane takeoff fad5 mdi alert circle outline fad6 mdi altimeter fad7 mdi animation fad8 mdi book minus fad9 mdi book open page variant fada mdi book plus fadb mdi boombox fadc mdi bullseye fadd mdi comment remove fade mdi camera off fadf mdi check circle fae0 mdi check circle outline fae1 mdi candle fae2 mdi chart bubble fae3 mdi credit card off fae4 mdi cup off fae5 mdi copyright fae6 mdi cursor text fae7 mdi delete forever fae8 mdi delete sweep fae9 mdi dice d20 faea mdi dice d4 faeb mdi dice d6 faec mdi dice d8 faed mdi disk faee mdi email open outline faef mdi email variant faf0 mdi ev station faf1 mdi food fork drink faf2 mdi food off faf3 mdi format title faf4 mdi google maps faf5 mdi heart pulse faf6 mdi highway faf7 mdi home map marker faf8 mdi incognito faf9 mdi kettle fafa mdi lock plus fafb mdi login variant fafc mdi logout variant fafd mdi music note bluetooth fafe mdi music note bluetooth off faff mdi page first fb00 mdi page last fb01 mdi phone classic fb02 mdi priority high fb03 mdi priority low fb04 mdi qqchat fb05 mdi pool fb06 mdi rounded corner fb07 mdi rowing fb08 mdi saxophone fb09 mdi signal variant fb0a mdi stackexchange fb0b mdi subdirectory arrow left fb0c mdi subdirectory arrow right fb0d mdi textbox fb0e mdi violin fb0f mdi visualstudio fb10 mdi wechat fb11 mdi watermark fb12 mdi file hidden fb13 mdi application fb14 mdi arrow collapse fb15 mdi arrow expand fb16 mdi bowl fb17 mdi bridge fb18 mdi buffer fb19 mdi chip fb1a mdi content save settings fb1b mdi dialpad fb1c mdi dictionary fb1d mdi format horizontal align center fb1e mdi format horizontal align left fb1f mdi format horizontal align right fb20 mdi format vertical align bottom fb21 mdi format vertical align center fb22 mdi format vertical align top fb23 mdi hackernews fb24 mdi help circle outline fb25 mdi json fb26 mdi lambda fb27 mdi matrix fb28 mdi meteor fb29 mdi mixcloud fb2a mdi sigma lower fb2b mdi source branch fb2c mdi source merge fb2d mdi tune fb2e mdi webhook fb2f mdi account settings fb30 mdi account settings variant fb31 mdi apple keyboard caps fb32 mdi apple keyboard command fb33 mdi apple keyboard control fb34 mdi apple keyboard option fb35 mdi apple keyboard shift fb36 mdi box shadow fb37 mdi cards fb38 mdi cards outline fb39 mdi cards playing outline fb3a mdi checkbox multiple blank circle fb3b mdi checkbox multiple blank circle outline fb3c mdi checkbox multiple marked circle fb3d mdi checkbox multiple marked circle outline fb3e mdi cloud sync fb3f mdi collage fb40 mdi directions fork fb41 mdi eraser variant fb42 mdi face fb43 mdi face profile fb44 mdi file tree fb45 mdi format annotation plus fb46 mdi gas cylinder fb47 mdi grease pencil fb48 mdi human female fb49 mdi human greeting fb4a mdi human handsdown fb4b mdi human handsup fb4c mdi human male fb4d mdi information variant fb4e mdi lead pencil fb4f mdi map marker minus fb50 mdi map marker plus fb51 mdi marker fb52 mdi message plus fb53 mdi microscope fb54 mdi move resize fb55 mdi move resize variant fb56 mdi paw off fb57 mdi phone minus fb58 mdi phone plus fb59 mdi pot fb5a mdi pot mix fb5b mdi serial port fb5c mdi shape circle plus fb5d mdi shape polygon plus fb5e mdi shape rectangle plus fb5f mdi shape square plus fb60 mdi skip next circle fb61 mdi skip next circle outline fb62 mdi skip previous circle fb63 mdi skip previous circle outline fb64 mdi spray fb65 mdi stop circle fb66 mdi stop circle outline fb67 mdi test tube fb68 mdi text shadow fb69 mdi tune vertical fb6a mdi cart off fb6b mdi chart gantt fb6c mdi chart scatterplot hexbin fb6d mdi chart timeline fb6e mdi discord fb6f mdi file restore fb70 mdi language c fb71 mdi language cpp fb72 mdi xaml fb73 mdi bandcamp fb74 mdi credit card plus fb75 mdi itunes fb76 mdi bow tie fb77 mdi calendar range fb78 mdi currency usd off fb79 mdi flash red eye fb7a mdi oar fb7b mdi piano fb7c mdi weather lightning rainy fb7d mdi weather snowy rainy fb7e mdi yin yang fb7f mdi tower beach fb80 mdi tower fire fb81 mdi delete circle fb82 mdi dna fb83 mdi hamburger fb84 mdi gondola fb85 mdi inbox fb86 mdi reorder horizontal fb87 mdi reorder vertical fb88 mdi security home fb89 mdi tag heart fb8a mdi skull fb8b mdi solid fb8c mdi alarm snooze fb8d mdi baby buggy fb8e mdi beaker fb8f mdi bomb fb90 mdi calendar question fb91 mdi camera burst fb92 mdi code tags check fb93 mdi coins fb94 mdi crop rotate fb95 mdi developer board fb96 mdi do not disturb fb97 mdi do not disturb off fb98 mdi douban fb99 mdi emoticon dead fb9a mdi emoticon excited fb9b mdi folder star fb9c mdi format color text fb9d mdi format section fb9e mdi gradient fb9f mdi home outline fba0 mdi message bulleted fba1 mdi message bulleted off fba2 mdi nuke fba3 mdi power plug fba4 mdi power plug off fba5 mdi publish fba6 mdi restore fba7 mdi robot fba8 mdi format rotate 90 fba9 mdi scanner fbaa mdi subway fbab mdi timer sand empty fbac mdi transit transfer fbad mdi unity fbae mdi update fbaf mdi watch vibrate fbb0 mdi angular fbb1 mdi dolby fbb2 mdi emby fbb3 mdi lamp fbb4 mdi menu down outline fbb5 mdi menu up outline fbb6 mdi note multiple fbb7 mdi note multiple outline fbb8 mdi plex fbb9 mdi plane shield fbba mdi account edit fbbb mdi alert decagram fbbc mdi all inclusive fbbd mdi angularjs fbbe mdi arrow down box fbbf mdi arrow left box fbc0 mdi arrow right box fbc1 mdi arrow up box fbc2 mdi asterisk fbc3 mdi bomb off fbc4 mdi bootstrap fbc5 mdi cards variant fbc6 mdi clipboard flow fbc7 mdi close outline fbc8 mdi coffee outline fbc9 mdi contacts fbca mdi delete empty fbcb mdi earth box fbcc mdi earth box off fbcd mdi email alert fbce mdi eye outline fbcf mdi eye off outline fbd0 mdi fast forward outline fbd1 mdi feather fbd2 mdi find replace fbd3 mdi flash outline fbd4 mdi format font fbd5 mdi format page break fbd6 mdi format pilcrow fbd7 mdi garage fbd8 mdi garage open fbd9 mdi github face fbda mdi google keep fbdb mdi google photos fbdc mdi heart half full fbdd mdi heart half fbde mdi heart half outline fbdf mdi hexagon multiple fbe0 mdi hook fbe1 mdi hook off fbe2 mdi infinity fbe3 mdi language swift fbe4 mdi language typescript fbe5 mdi laptop off fbe6 mdi lightbulb on fbe7 mdi lightbulb on outline fbe8 mdi lock pattern fbe9 mdi loop fbea mdi magnify minus outline fbeb mdi magnify plus outline fbec mdi mailbox fbed mdi medical bag fbee mdi message settings fbef mdi message settings variant fbf0 mdi minus box outline fbf1 mdi network fbf2 mdi download network fbf3 mdi help network fbf4 mdi upload network fbf5 mdi npm fbf6 mdi nut fbf7 mdi octagram fbf8 mdi page layout body fbf9 mdi page layout footer fbfa mdi page layout header fbfb mdi page layout sidebar left fbfc mdi page layout sidebar right fbfd mdi pencil circle fbfe mdi pentagon fbff mdi pentagon outline fc00 mdi pillar fc01 mdi pistol fc02 mdi plus box outline fc03 mdi plus outline fc04 mdi prescription fc05 mdi printer settings fc06 mdi react fc07 mdi restart fc08 mdi rewind outline fc09 mdi rhombus fc0a mdi rhombus outline fc0b mdi roomba fc0c mdi run fc0d mdi search web fc0e mdi shovel fc0f mdi shovel off fc10 mdi signal 2g fc11 mdi signal 3g fc12 mdi signal 4g fc13 mdi signal hspa fc14 mdi signal hspa plus fc15 mdi snowflake fc16 mdi source commit fc17 mdi source commit end fc18 mdi source commit end local fc19 mdi source commit local fc1a mdi source commit next local fc1b mdi source commit start fc1c mdi source commit start next local fc1d mdi speaker wireless fc1e mdi stadium fc1f mdi svg fc20 mdi tag plus fc21 mdi tag remove fc22 mdi ticket percent fc23 mdi tilde fc24 mdi treasure chest fc25 mdi truck trailer fc26 mdi view parallel fc27 mdi view sequential fc28 mdi washing machine fc29 mdi webpack fc2a mdi widgets fc2b mdi wiiu fc2c mdi arrow down bold fc2d mdi arrow down bold box fc2e mdi arrow down bold box outline fc2f mdi arrow left bold fc30 mdi arrow left bold box fc31 mdi arrow left bold box outline fc32 mdi arrow right bold fc33 mdi arrow right bold box fc34 mdi arrow right bold box outline fc35 mdi arrow up bold fc36 mdi arrow up bold box fc37 mdi arrow up bold box outline fc38 mdi cancel fc39 mdi file account fc3a mdi gesture double tap fc3b mdi gesture swipe down fc3c mdi gesture swipe left fc3d mdi gesture swipe right fc3e mdi gesture swipe up fc3f mdi gesture tap fc40 mdi gesture two double tap fc41 mdi gesture two tap fc42 mdi humble bundle fc43 mdi kickstarter fc44 mdi netflix fc45 mdi onenote fc46 mdi periscope fc47 mdi uber fc48 mdi vector radius fc49 mdi xbox controller battery alert fc4a mdi xbox controller battery empty fc4b mdi xbox controller battery full fc4c mdi xbox controller battery low fc4d mdi xbox controller battery medium fc4e mdi xbox controller battery unknown fc4f mdi clipboard plus fc50 mdi file plus fc51 mdi format align bottom fc52 mdi format align middle fc53 mdi format align top fc54 mdi format list checks fc55 mdi format quote open fc56 mdi grid large fc57 mdi heart off fc58 mdi music fc59 mdi music off fc5a mdi tab plus fc5b mdi volume plus fc5c mdi volume minus fc5d mdi volume mute fc5e mdi unfold less vertical fc5f mdi unfold more vertical fc60 mdi taco fc61 mdi square outline fc62 mdi square fc63 mdi circle fc64 mdi circle outline fc65 mdi alert octagram fc66 mdi atom fc67 mdi ceiling light fc68 mdi chart bar stacked fc69 mdi chart line stacked fc6a mdi decagram fc6b mdi decagram outline fc6c mdi dice multiple fc6d mdi dice d10 fc6e mdi folder open fc6f mdi guitar acoustic fc70 mdi loading fc71 mdi lock reset fc72 mdi ninja fc73 mdi octagram outline fc74 mdi pencil circle outline fc75 mdi selection off fc76 mdi set all fc77 mdi set center fc78 mdi set center right fc79 mdi set left fc7a mdi set left center fc7b mdi set left right fc7c mdi set none fc7d mdi set right fc7e mdi shield half full fc7f mdi sign direction fc80 mdi sign text fc81 mdi signal off fc82 mdi square root fc83 mdi sticker emoji fc84 mdi summit fc85 mdi sword cross fc86 mdi truck fast fc87 mdi yammer fc88 mdi cast off fc89 mdi help box fc8a mdi timer sand full fc8b mdi waves fc8c mdi alarm bell fc8d mdi alarm light fc8e mdi android head fc8f mdi approval fc90 mdi arrow collapse down fc91 mdi arrow collapse left fc92 mdi arrow collapse right fc93 mdi arrow collapse up fc94 mdi arrow expand down fc95 mdi arrow expand left fc96 mdi arrow expand right fc97 mdi arrow expand up fc98 mdi book secure fc99 mdi book unsecure fc9a mdi bus articulated end fc9b mdi bus articulated front fc9c mdi bus double decker fc9d mdi bus school fc9e mdi bus side fc9f mdi camera gopro fca0 mdi camera metering center fca1 mdi camera metering matrix fca2 mdi camera metering partial fca3 mdi camera metering spot fca4 mdi cannabis fca5 mdi car convertible fca6 mdi car estate fca7 mdi car hatchback fca8 mdi car pickup fca9 mdi car side fcaa mdi car sports fcab mdi caravan fcac mdi cctv fcad mdi chart donut fcae mdi chart donut variant fcaf mdi chart line variant fcb0 mdi chili hot fcb1 mdi chili medium fcb2 mdi chili mild fcb3 mdi cloud braces fcb4 mdi cloud tags fcb5 mdi console line fcb6 mdi corn fcb7 mdi currency chf fcb8 mdi currency cny fcb9 mdi currency eth fcba mdi currency jpy fcbb mdi currency krw fcbc mdi currency sign fcbd mdi currency twd fcbe mdi desktop classic fcbf mdi dip switch fcc0 mdi donkey fcc1 mdi dots horizontal circle fcc2 mdi dots vertical circle fcc3 mdi ear hearing fcc4 mdi elephant fcc5 mdi eventbrite fcc6 mdi food croissant fcc7 mdi forklift fcc8 mdi fuel fcc9 mdi gesture fcca mdi google analytics fccb mdi google assistant fccc mdi headphones off fccd mdi high definition fcce mdi home assistant fccf mdi home automation fcd0 mdi home circle fcd1 mdi language go fcd2 mdi language r fcd3 mdi lava lamp fcd4 mdi led strip fcd5 mdi locker fcd6 mdi locker multiple fcd7 mdi map marker outline fcd8 mdi metronome fcd9 mdi metronome tick fcda mdi micro sd fcdb mdi mixer fcdc mdi movie roll fcdd mdi mushroom fcde mdi mushroom outline fcdf mdi nintendo switch fce0 mdi null fce1 mdi passport fce2 mdi periodic table co2 fce3 mdi pipe fce4 mdi pipe disconnected fce5 mdi power socket eu fce6 mdi power socket uk fce7 mdi power socket us fce8 mdi rice fce9 mdi ring fcea mdi sass fceb mdi send secure fcec mdi soy sauce fced mdi standard definition fcee mdi surround sound 2 0 fcef mdi surround sound 3 1 fcf0 mdi surround sound 5 1 fcf1 mdi surround sound 7 1 fcf2 mdi television classic fcf3 mdi textbox password fcf4 mdi thought bubble fcf5 mdi thought bubble outline fcf6 mdi trackpad fcf7 mdi ultra high definition fcf8 mdi van passenger fcf9 mdi van utility fcfa mdi vanish fcfb mdi video 3d fcfc mdi wall fcfd mdi xmpp fcfe mdi account multiple plus outline fcff mdi account plus outline fd00 mdi allo fd01 mdi artist fd02 mdi atlassian fd03 mdi azure fd04 mdi basketball fd05 mdi battery charging wireless fd06 mdi battery charging wireless 10 fd07 mdi battery charging wireless 20 fd08 mdi battery charging wireless 30 fd09 mdi battery charging wireless 40 fd0a mdi battery charging wireless 50 fd0b mdi battery charging wireless 60 fd0c mdi battery charging wireless 70 fd0d mdi battery charging wireless 80 fd0e mdi battery charging wireless 90 fd0f mdi battery charging wireless alert fd10 mdi battery charging wireless outline fd11 mdi bitcoin fd12 mdi briefcase outline fd13 mdi cellphone wireless fd14 mdi clover fd15 mdi comment question fd16 mdi content save outline fd17 mdi delete restore fd18 mdi door fd19 mdi door closed fd1a mdi door open fd1b mdi fan off fd1c mdi file percent fd1d mdi finance fd1e mdi flash circle fd1f mdi floor plan fd20 mdi forum outline fd21 mdi golf fd22 mdi google home fd23 mdi guy fawkes mask fd24 mdi home account fd25 mdi home heart fd26 mdi hot tub fd27 mdi hulu fd28 mdi ice cream fd29 mdi image off fd2a mdi karate fd2b mdi ladybug fd2c mdi notebook fd2d mdi phone return fd2e mdi poker chip fd2f mdi shape fd30 mdi shape outline fd31 mdi ship wheel fd32 mdi soccer field fd33 mdi table column fd34 mdi table of contents fd35 mdi table row fd36 mdi table settings fd37 mdi television box fd38 mdi television classic off fd39 mdi television off fd3a mdi towing fd3b mdi upload multiple fd3c mdi video 4k box fd3d mdi video input antenna fd3e mdi video input component fd3f mdi video input hdmi fd40 mdi video input svideo fd41 mdi view dashboard variant fd42 mdi vuejs fd43 mdi xamarin fd44 mdi xamarin outline fd45 mdi youtube creator studio fd46 mdi youtube gaming 2665 oct heart 26a1 oct zap f400 oct light bulb f401 oct repo f402 oct repo forked f403 oct repo push f404 oct repo pull f405 oct book f406 oct octoface f407 oct git pull request f408 oct mark github f409 oct cloud download f40a oct cloud upload f40b oct keyboard f40c oct gist f40d oct file code f40e oct file text f40f oct file media f410 oct file zip f411 oct file pdf f412 oct tag f413 oct file directory f414 oct file submodule f415 oct person f416 oct jersey f417 oct git commit f418 oct git branch f419 oct git merge f41a oct mirror f41b oct issue opened f41c oct issue reopened f41d oct issue closed f41e oct star f41f oct comment f420 oct question f421 oct alert f422 oct search f423 oct gear f424 oct radio tower f425 oct tools f426 oct sign out f427 oct rocket f428 oct rss f429 oct clippy f42a oct sign in f42b oct organization f42c oct device mobile f42d oct unfold f42e oct check f42f oct mail f430 oct mail read f431 oct arrow up f432 oct arrow right f433 oct arrow down f434 oct arrow left f435 oct pin f436 oct gift f437 oct graph f438 oct triangle left f439 oct credit card f43a oct clock f43b oct ruby f43c oct broadcast f43d oct key f43e oct repo force push f43f oct repo clone f440 oct diff f441 oct eye f442 oct comment discussion f443 oct mail reply f444 oct primitive dot f445 oct primitive square f446 oct device camera f447 oct device camera video f448 oct pencil f449 oct info f44a oct triangle right f44b oct triangle down f44c oct link f44d oct plus f44e oct three bars f44f oct code f450 oct location f451 oct list unordered f452 oct list ordered f453 oct quote f454 oct versions f455 oct calendar f456 oct lock f457 oct diff added f458 oct diff removed f459 oct diff modified f45a oct diff renamed f45b oct horizontal rule f45c oct arrow small right f45d oct milestone f45e oct checklist f45f oct megaphone f460 oct chevron right f461 oct bookmark f462 oct settings f463 oct dashboard f464 oct history f465 oct link external f466 oct mute f467 oct x f468 oct circle slash f469 oct pulse f46a oct sync f46b oct telescope f46c oct gist secret f46d oct home f46e oct stop f46f oct bug f470 oct logo github f471 oct file binary f472 oct database f473 oct server f474 oct diff ignored f475 oct ellipsis f476 oct no newline f477 oct hubot f478 oct arrow small up f479 oct arrow small down f47a oct arrow small left f47b oct chevron up f47c oct chevron down f47d oct chevron left f47e oct triangle up f47f oct git compare f480 oct logo gist f481 oct file symlink file f482 oct file symlink directory f483 oct squirrel f484 oct globe f485 oct unmute f486 oct mention f487 oct package f488 oct browser f489 oct terminal f48a oct markdown f48b oct dash f48c oct fold f48d oct inbox f48e oct trashcan f48f oct paintcan f490 oct flame f491 oct briefcase f492 oct plug f493 oct circuit board f494 oct mortar board f495 oct law f496 oct thumbsup f497 oct thumbsdown f498 oct desktop download f499 oct beaker f49a oct bell f49b oct watch f49c oct shield f49d oct bold f49e oct text size f49f oct italic f4a0 oct tasklist f4a1 oct verified f4a2 oct smiley f4a3 oct unverified f4a4 oct ellipses f4a5 oct file f4a6 oct grabber f4a7 oct plus small f4a8 oct reply f4a9 oct device desktop e0a0 pl branch e0a1 pl line number e0a2 pl hostname readonly e0b0 pl left hard divider e0b1 pl left soft divider e0b2 pl right hard divider e0b3 pl right soft divider e0a3 ple column number e0b4 ple right half circle thick e0b5 ple right half circle thin e0b6 ple left half circle thick e0b7 ple left half circle thin e0b8 ple lower left triangle e0b9 ple backslash separator e0ba ple lower right triangle e0bb ple forwardslash separator e0bc ple upper left triangle e0bd ple forwardslash separator redundant e0be ple upper right triangle e0bf ple backslash separator redundant e0c0 ple flame thick e0c1 ple flame thin e0c2 ple flame thick mirrored e0c3 ple flame thin mirrored e0c4 ple pixelated squares small e0c5 ple pixelated squares small mirrored e0c6 ple pixelated squares big e0c7 ple pixelated squares big mirrored e0c8 ple ice waveform e0ca ple ice waveform mirrored e0cc ple honeycomb e0cd ple honeycomb outline e0ce ple lego separator e0cf ple lego separator thin e0d0 ple lego block facing e0d1 ple lego block sideways e0d2 ple trapezoid top bottom e0d4 ple trapezoid top bottom mirrored e000 pom clean code e001 pom pomodoro done e002 pom pomodoro estimated e003 pom pomodoro ticking e004 pom pomodoro squashed e005 pom short pause e006 pom long pause e007 pom away e008 pom pair programming e009 pom internal interruption e00a pom external interruption e600 seti stylus e601 seti project e603 seti sass e604 seti rails e605 seti ruby e606 seti python e607 seti heroku e608 seti php e609 seti markdown e60a seti license e60b seti json less e60c seti javascript e60d seti image e60e seti html e60f seti mustache e610 seti gulp e611 seti grunt e613 seti folder e614 seti css e615 seti config e616 seti npm e618 seti ejs e619 seti xml e61a seti bower e61b seti coffee cjsx e61c seti twig e61f seti haskell e620 seti lua e622 seti karma e623 seti favicon e624 seti julia e625 seti react e627 seti go e628 seti typescript e635 seti apple e636 seti argdown e637 seti asm e638 seti audio e639 seti babel e63a seti bazel e63b seti bicep e63c seti bsl e63d seti cake php e63e seti cake e63f seti checkbox e640 seti checkbox unchecked e641 seti clock time cop e642 seti clojure e643 seti code climate e644 seti code search e645 seti coldfusion e646 seti cpp e647 seti crystal embedded e648 seti c sharp e649 seti c e64a seti csv e64b seti cu e64c seti dart e64d seti db e64e seti default e64f seti deprecation cop e650 seti docker e651 seti d e652 seti editorconfig e653 seti elixir script e654 seti error e655 seti eslint e656 seti ethereum e657 seti firebase e658 seti firefox e659 seti font e65a seti f sharp e65b seti github e65c seti gitlab e65d seti git e65e seti go2 e65f seti godot e660 seti gradle e661 seti grails e662 seti graphql e663 seti hacklang e664 seti haml e665 seti happenings e666 seti haxe e667 seti hex e668 seti ignored e669 seti illustrator e66a seti info e66b seti ionic e66c seti jade e66d seti java e66e seti jenkins e66f seti jinja e670 seti liquid e671 seti livescript e672 seti lock e673 seti makefile e674 seti maven e675 seti mdo e676 seti new file e677 seti nim e678 seti notebook e679 seti nunjucks e67a seti ocaml e67b seti odata e67c seti pddl e67d seti pdf e67e seti perl e67f seti photoshop e680 seti pipeline e681 seti plan e682 seti platformio e683 seti powershell e684 seti prisma e685 seti prolog e686 seti pug e687 seti reasonml e688 seti rescript e689 seti rollup e68a seti r e68b seti rust e68c seti salesforce e68d seti sbt e68e seti scala e68f seti search e690 seti settings e691 seti shell e692 seti slim e693 seti smarty e694 seti spring e695 seti stylelint e696 seti sublime e697 seti svelte e698 seti svg e699 seti swift e69a seti terraform e69b seti tex e69c seti todo e69d seti tsconfig e69e seti vala e69f seti video e6a0 seti vue e6a1 seti wasm e6a2 seti wat e6a3 seti webpack e6a4 seti wgt e6a5 seti word e6a6 seti xls e6a7 seti yarn e6a8 seti yml e6a9 seti zig e6aa seti zip e300 weather day cloudy gusts e301 weather day cloudy windy e302 weather day cloudy e303 weather day fog e304 weather day hail e305 weather day lightning e306 weather day rain mix e307 weather day rain wind e308 weather day rain e309 weather day showers e30a weather day snow e30b weather day sprinkle e30c weather day sunny overcast e30d weather day sunny e30e weather day storm showers e30f weather day thunderstorm e310 weather cloudy gusts e311 weather cloudy windy e312 weather cloudy e313 weather fog e314 weather hail e315 weather lightning e316 weather rain mix e317 weather rain wind e318 weather rain e319 weather showers e31a weather snow e31b weather sprinkle e31c weather storm showers e31d weather thunderstorm e31e weather windy e31f weather night alt cloudy gusts e320 weather night alt cloudy windy e321 weather night alt hail e322 weather night alt lightning e323 weather night alt rain mix e324 weather night alt rain wind e325 weather night alt rain e326 weather night alt rain mix e327 weather night alt snow e328 weather night alt sprinkle e329 weather night alt storm showers e32a weather night alt thunderstorm e32b weather night clear e32c weather night cloudy gusts e32d weather night cloudy windy e32e weather night cloudy e32f weather night hail e330 weather night lightning e331 weather night rain mix e332 weather night rain wind e333 weather night rain e334 weather night showers e335 weather night snow e336 weather night sprinkle e337 weather night storm showers e338 weather night thunderstorm e339 weather celsius e33a weather cloud down e33b weather cloud refresh e33c weather cloud up e33d weather cloud e33e weather degrees e33f weather direction down left e340 weather direction down e341 weather fahrenheit e342 weather horizon alt e343 weather horizon e344 weather direction left e345 weather aliens e346 weather night fog e347 weather refresh alt e348 weather refresh e349 weather direction right e34a weather raindrops e34b weather strong wind e34c weather sunrise e34d weather sunset e34e weather thermometer exterior e34f weather thermometer internal e350 weather thermometer e351 weather tornado e352 weather direction up right e353 weather direction up e354 weather wind west e355 weather wind south west e356 weather wind south east e357 weather wind south e358 weather wind north west e359 weather wind north east e35a weather wind north e35b weather wind east e35c weather smoke e35d weather dust e35e weather snow wind e35f weather day snow wind e360 weather night snow wind e361 weather night alt snow wind e362 weather day sleet storm e363 weather night sleet storm e364 weather night alt sleet storm e365 weather day snow thunderstorm e366 weather night snow thunderstorm e367 weather night alt snow thunderstorm e368 weather solar eclipse e369 weather lunar eclipse e36a weather meteor e36b weather hot e36c weather hurricane e36d weather smog e36e weather alien e36f weather snowflake cold e370 weather stars e371 weather raindrop e372 weather barometer e373 weather humidity e374 weather na e375 weather flood e376 weather day cloudy high e377 weather night alt cloudy high e378 weather night cloudy high e379 weather night alt partly cloudy e37a weather sandstorm e37b weather night partly cloudy e37c weather umbrella e37d weather day windy e37e weather night alt cloudy e37f weather direction up left e380 weather direction down right e381 weather time 12 e382 weather time 1 e383 weather time 2 e384 weather time 3 e385 weather time 4 e386 weather time 5 e387 weather time 6 e388 weather time 7 e389 weather time 8 e38a weather time 9 e38b weather time 10 e38c weather time 11 e38d weather moon new e38e weather moon waxing crescent 1 e38f weather moon waxing crescent 2 e390 weather moon waxing crescent 3 e391 weather moon waxing crescent 4 e392 weather moon waxing crescent 5 e393 weather moon waxing crescent 6 e394 weather moon first quarter e395 weather moon waxing gibbous 1 e396 weather moon waxing gibbous 2 e397 weather moon waxing gibbous 3 e398 weather moon waxing gibbous 4 e399 weather moon waxing gibbous 5 e39a weather moon waxing gibbous 6 e39b weather moon full e39c weather moon waning gibbous 1 e39d weather moon waning gibbous 2 e39e weather moon waning gibbous 3 e39f weather moon waning gibbous 4 e3a0 weather moon waning gibbous 5 e3a1 weather moon waning gibbous 6 e3a2 weather moon third quarter e3a3 weather moon waning crescent 1 e3a4 weather moon waning crescent 2 e3a5 weather moon waning crescent 3 e3a6 weather moon waning crescent 4 e3a7 weather moon waning crescent 5 e3a8 weather moon waning crescent 6 e3a9 weather wind direction e3aa weather day sleet e3ab weather night sleet e3ac weather night alt sleet e3ad weather sleet e3ae weather day haze e3af weather wind beaufort 0 e3b0 weather wind beaufort 1 e3b1 weather wind beaufort 2 e3b2 weather wind beaufort 3 e3b3 weather wind beaufort 4 e3b4 weather wind beaufort 5 e3b5 weather wind beaufort 6 e3b6 weather wind beaufort 7 e3b7 weather wind beaufort 8 e3b8 weather wind beaufort 9 e3b9 weather wind beaufort 10 e3ba weather wind beaufort 11 e3bb weather wind beaufort 12 e3bc weather day light wind e3bd weather tsunami e3be weather earthquake e3bf weather fire e3c0 weather volcano e3c1 weather moonrise e3c2 weather moonset e3c3 weather train e3c4 weather small craft advisory e3c5 weather gale warning e3c6 weather storm warning e3c7 weather hurricane warning e3c8 weather moon alt waxing crescent 1 e3c9 weather moon alt waxing crescent 2 e3ca weather moon alt waxing crescent 3 e3cb weather moon alt waxing crescent 4 e3cc weather moon alt waxing crescent 5 e3cd weather moon alt waxing crescent 6 e3ce weather moon alt first quarter e3cf weather moon alt waxing gibbous 1 e3d0 weather moon alt waxing gibbous 2 e3d1 weather moon alt waxing gibbous 3 e3d2 weather moon alt waxing gibbous 4 e3d3 weather moon alt waxing gibbous 5 e3d4 weather moon alt waxing gibbous 6 e3d5 weather moon alt full e3d6 weather moon alt waning gibbous 1 e3d7 weather moon alt waning gibbous 2 e3d8 weather moon alt waning gibbous 3 e3d9 weather moon alt waning gibbous 4 e3da weather moon alt waning gibbous 5 e3db weather moon alt waning gibbous 6 e3dc weather moon alt third quarter e3dd weather moon alt waning crescent 1 e3de weather moon alt waning crescent 2 e3df weather moon alt waning crescent 3 e3e0 weather moon alt waning crescent 4 e3e1 weather moon alt waning crescent 5 e3e2 weather moon alt waning crescent 6 e3e3 weather moon alt new kitty-0.41.1/gen/rowcolumn-diacritics.txt0000664000175000017510000004321014773370543017762 0ustar nileshnilesh# This file lists the diacritics used to indicate row/column numbers for # Unicode terminal image placeholders. It is derived from UnicodeData.txt for # Unicode 6.0.0 (chosen somewhat arbitrarily: it's old enough, but still # contains more than 255 suitable combining chars) using the following # command: # # cat UnicodeData.txt | grep "Mn;230;NSM;;" | grep -v "0300\|0301\|0302\|0303\|0304\|0306\|0307\|0308\|0309\|030A\|030B\|030C\|030F\|0311\|0313\|0314\|0342\|0653\|0654" # # That is, we use combining chars of the same combining class 230 (above the # base character) that do not have decomposition mappings, and we also remove # some characters that may be fused with other characters during normalization, # like 0041 0300 -> 00C0 which is À (A with grave). # 0305;COMBINING OVERLINE;Mn;230;NSM;;;;;N;NON-SPACING OVERSCORE;;;; 030D;COMBINING VERTICAL LINE ABOVE;Mn;230;NSM;;;;;N;NON-SPACING VERTICAL LINE ABOVE;;;; 030E;COMBINING DOUBLE VERTICAL LINE ABOVE;Mn;230;NSM;;;;;N;NON-SPACING DOUBLE VERTICAL LINE ABOVE;;;; 0310;COMBINING CANDRABINDU;Mn;230;NSM;;;;;N;NON-SPACING CANDRABINDU;;;; 0312;COMBINING TURNED COMMA ABOVE;Mn;230;NSM;;;;;N;NON-SPACING TURNED COMMA ABOVE;;;; 033D;COMBINING X ABOVE;Mn;230;NSM;;;;;N;NON-SPACING X ABOVE;;;; 033E;COMBINING VERTICAL TILDE;Mn;230;NSM;;;;;N;NON-SPACING VERTICAL TILDE;;;; 033F;COMBINING DOUBLE OVERLINE;Mn;230;NSM;;;;;N;NON-SPACING DOUBLE OVERSCORE;;;; 0346;COMBINING BRIDGE ABOVE;Mn;230;NSM;;;;;N;;;;; 034A;COMBINING NOT TILDE ABOVE;Mn;230;NSM;;;;;N;;;;; 034B;COMBINING HOMOTHETIC ABOVE;Mn;230;NSM;;;;;N;;;;; 034C;COMBINING ALMOST EQUAL TO ABOVE;Mn;230;NSM;;;;;N;;;;; 0350;COMBINING RIGHT ARROWHEAD ABOVE;Mn;230;NSM;;;;;N;;;;; 0351;COMBINING LEFT HALF RING ABOVE;Mn;230;NSM;;;;;N;;;;; 0352;COMBINING FERMATA;Mn;230;NSM;;;;;N;;;;; 0357;COMBINING RIGHT HALF RING ABOVE;Mn;230;NSM;;;;;N;;;;; 035B;COMBINING ZIGZAG ABOVE;Mn;230;NSM;;;;;N;;;;; 0363;COMBINING LATIN SMALL LETTER A;Mn;230;NSM;;;;;N;;;;; 0364;COMBINING LATIN SMALL LETTER E;Mn;230;NSM;;;;;N;;;;; 0365;COMBINING LATIN SMALL LETTER I;Mn;230;NSM;;;;;N;;;;; 0366;COMBINING LATIN SMALL LETTER O;Mn;230;NSM;;;;;N;;;;; 0367;COMBINING LATIN SMALL LETTER U;Mn;230;NSM;;;;;N;;;;; 0368;COMBINING LATIN SMALL LETTER C;Mn;230;NSM;;;;;N;;;;; 0369;COMBINING LATIN SMALL LETTER D;Mn;230;NSM;;;;;N;;;;; 036A;COMBINING LATIN SMALL LETTER H;Mn;230;NSM;;;;;N;;;;; 036B;COMBINING LATIN SMALL LETTER M;Mn;230;NSM;;;;;N;;;;; 036C;COMBINING LATIN SMALL LETTER R;Mn;230;NSM;;;;;N;;;;; 036D;COMBINING LATIN SMALL LETTER T;Mn;230;NSM;;;;;N;;;;; 036E;COMBINING LATIN SMALL LETTER V;Mn;230;NSM;;;;;N;;;;; 036F;COMBINING LATIN SMALL LETTER X;Mn;230;NSM;;;;;N;;;;; 0483;COMBINING CYRILLIC TITLO;Mn;230;NSM;;;;;N;CYRILLIC NON-SPACING TITLO;;;; 0484;COMBINING CYRILLIC PALATALIZATION;Mn;230;NSM;;;;;N;CYRILLIC NON-SPACING PALATALIZATION;;;; 0485;COMBINING CYRILLIC DASIA PNEUMATA;Mn;230;NSM;;;;;N;CYRILLIC NON-SPACING DASIA PNEUMATA;;;; 0486;COMBINING CYRILLIC PSILI PNEUMATA;Mn;230;NSM;;;;;N;CYRILLIC NON-SPACING PSILI PNEUMATA;;;; 0487;COMBINING CYRILLIC POKRYTIE;Mn;230;NSM;;;;;N;;;;; 0592;HEBREW ACCENT SEGOL;Mn;230;NSM;;;;;N;;;;; 0593;HEBREW ACCENT SHALSHELET;Mn;230;NSM;;;;;N;;;;; 0594;HEBREW ACCENT ZAQEF QATAN;Mn;230;NSM;;;;;N;;;;; 0595;HEBREW ACCENT ZAQEF GADOL;Mn;230;NSM;;;;;N;;;;; 0597;HEBREW ACCENT REVIA;Mn;230;NSM;;;;;N;;;;; 0598;HEBREW ACCENT ZARQA;Mn;230;NSM;;;;;N;;;;; 0599;HEBREW ACCENT PASHTA;Mn;230;NSM;;;;;N;;;;; 059C;HEBREW ACCENT GERESH;Mn;230;NSM;;;;;N;;;;; 059D;HEBREW ACCENT GERESH MUQDAM;Mn;230;NSM;;;;;N;;;;; 059E;HEBREW ACCENT GERSHAYIM;Mn;230;NSM;;;;;N;;;;; 059F;HEBREW ACCENT QARNEY PARA;Mn;230;NSM;;;;;N;;;;; 05A0;HEBREW ACCENT TELISHA GEDOLA;Mn;230;NSM;;;;;N;;;;; 05A1;HEBREW ACCENT PAZER;Mn;230;NSM;;;;;N;;;;; 05A8;HEBREW ACCENT QADMA;Mn;230;NSM;;;;;N;;;;; 05A9;HEBREW ACCENT TELISHA QETANA;Mn;230;NSM;;;;;N;;;;; 05AB;HEBREW ACCENT OLE;Mn;230;NSM;;;;;N;;;;; 05AC;HEBREW ACCENT ILUY;Mn;230;NSM;;;;;N;;;;; 05AF;HEBREW MARK MASORA CIRCLE;Mn;230;NSM;;;;;N;;;;; 05C4;HEBREW MARK UPPER DOT;Mn;230;NSM;;;;;N;;;;; 0610;ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM;Mn;230;NSM;;;;;N;;;;; 0611;ARABIC SIGN ALAYHE ASSALLAM;Mn;230;NSM;;;;;N;;;;; 0612;ARABIC SIGN RAHMATULLAH ALAYHE;Mn;230;NSM;;;;;N;;;;; 0613;ARABIC SIGN RADI ALLAHOU ANHU;Mn;230;NSM;;;;;N;;;;; 0614;ARABIC SIGN TAKHALLUS;Mn;230;NSM;;;;;N;;;;; 0615;ARABIC SMALL HIGH TAH;Mn;230;NSM;;;;;N;;;;; 0616;ARABIC SMALL HIGH LIGATURE ALEF WITH LAM WITH YEH;Mn;230;NSM;;;;;N;;;;; 0617;ARABIC SMALL HIGH ZAIN;Mn;230;NSM;;;;;N;;;;; 0657;ARABIC INVERTED DAMMA;Mn;230;NSM;;;;;N;;;;; 0658;ARABIC MARK NOON GHUNNA;Mn;230;NSM;;;;;N;;;;; 0659;ARABIC ZWARAKAY;Mn;230;NSM;;;;;N;;;;; 065A;ARABIC VOWEL SIGN SMALL V ABOVE;Mn;230;NSM;;;;;N;;;;; 065B;ARABIC VOWEL SIGN INVERTED SMALL V ABOVE;Mn;230;NSM;;;;;N;;;;; 065D;ARABIC REVERSED DAMMA;Mn;230;NSM;;;;;N;;;;; 065E;ARABIC FATHA WITH TWO DOTS;Mn;230;NSM;;;;;N;;;;; 06D6;ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA;Mn;230;NSM;;;;;N;;;;; 06D7;ARABIC SMALL HIGH LIGATURE QAF WITH LAM WITH ALEF MAKSURA;Mn;230;NSM;;;;;N;;;;; 06D8;ARABIC SMALL HIGH MEEM INITIAL FORM;Mn;230;NSM;;;;;N;;;;; 06D9;ARABIC SMALL HIGH LAM ALEF;Mn;230;NSM;;;;;N;;;;; 06DA;ARABIC SMALL HIGH JEEM;Mn;230;NSM;;;;;N;;;;; 06DB;ARABIC SMALL HIGH THREE DOTS;Mn;230;NSM;;;;;N;;;;; 06DC;ARABIC SMALL HIGH SEEN;Mn;230;NSM;;;;;N;;;;; 06DF;ARABIC SMALL HIGH ROUNDED ZERO;Mn;230;NSM;;;;;N;;;;; 06E0;ARABIC SMALL HIGH UPRIGHT RECTANGULAR ZERO;Mn;230;NSM;;;;;N;;;;; 06E1;ARABIC SMALL HIGH DOTLESS HEAD OF KHAH;Mn;230;NSM;;;;;N;;;;; 06E2;ARABIC SMALL HIGH MEEM ISOLATED FORM;Mn;230;NSM;;;;;N;;;;; 06E4;ARABIC SMALL HIGH MADDA;Mn;230;NSM;;;;;N;;;;; 06E7;ARABIC SMALL HIGH YEH;Mn;230;NSM;;;;;N;;;;; 06E8;ARABIC SMALL HIGH NOON;Mn;230;NSM;;;;;N;;;;; 06EB;ARABIC EMPTY CENTRE HIGH STOP;Mn;230;NSM;;;;;N;;;;; 06EC;ARABIC ROUNDED HIGH STOP WITH FILLED CENTRE;Mn;230;NSM;;;;;N;;;;; 0730;SYRIAC PTHAHA ABOVE;Mn;230;NSM;;;;;N;;;;; 0732;SYRIAC PTHAHA DOTTED;Mn;230;NSM;;;;;N;;;;; 0733;SYRIAC ZQAPHA ABOVE;Mn;230;NSM;;;;;N;;;;; 0735;SYRIAC ZQAPHA DOTTED;Mn;230;NSM;;;;;N;;;;; 0736;SYRIAC RBASA ABOVE;Mn;230;NSM;;;;;N;;;;; 073A;SYRIAC HBASA ABOVE;Mn;230;NSM;;;;;N;;;;; 073D;SYRIAC ESASA ABOVE;Mn;230;NSM;;;;;N;;;;; 073F;SYRIAC RWAHA;Mn;230;NSM;;;;;N;;;;; 0740;SYRIAC FEMININE DOT;Mn;230;NSM;;;;;N;;;;; 0741;SYRIAC QUSHSHAYA;Mn;230;NSM;;;;;N;;;;; 0743;SYRIAC TWO VERTICAL DOTS ABOVE;Mn;230;NSM;;;;;N;;;;; 0745;SYRIAC THREE DOTS ABOVE;Mn;230;NSM;;;;;N;;;;; 0747;SYRIAC OBLIQUE LINE ABOVE;Mn;230;NSM;;;;;N;;;;; 0749;SYRIAC MUSIC;Mn;230;NSM;;;;;N;;;;; 074A;SYRIAC BARREKH;Mn;230;NSM;;;;;N;;;;; 07EB;NKO COMBINING SHORT HIGH TONE;Mn;230;NSM;;;;;N;;;;; 07EC;NKO COMBINING SHORT LOW TONE;Mn;230;NSM;;;;;N;;;;; 07ED;NKO COMBINING SHORT RISING TONE;Mn;230;NSM;;;;;N;;;;; 07EE;NKO COMBINING LONG DESCENDING TONE;Mn;230;NSM;;;;;N;;;;; 07EF;NKO COMBINING LONG HIGH TONE;Mn;230;NSM;;;;;N;;;;; 07F0;NKO COMBINING LONG LOW TONE;Mn;230;NSM;;;;;N;;;;; 07F1;NKO COMBINING LONG RISING TONE;Mn;230;NSM;;;;;N;;;;; 07F3;NKO COMBINING DOUBLE DOT ABOVE;Mn;230;NSM;;;;;N;;;;; 0816;SAMARITAN MARK IN;Mn;230;NSM;;;;;N;;;;; 0817;SAMARITAN MARK IN-ALAF;Mn;230;NSM;;;;;N;;;;; 0818;SAMARITAN MARK OCCLUSION;Mn;230;NSM;;;;;N;;;;; 0819;SAMARITAN MARK DAGESH;Mn;230;NSM;;;;;N;;;;; 081B;SAMARITAN MARK EPENTHETIC YUT;Mn;230;NSM;;;;;N;;;;; 081C;SAMARITAN VOWEL SIGN LONG E;Mn;230;NSM;;;;;N;;;;; 081D;SAMARITAN VOWEL SIGN E;Mn;230;NSM;;;;;N;;;;; 081E;SAMARITAN VOWEL SIGN OVERLONG AA;Mn;230;NSM;;;;;N;;;;; 081F;SAMARITAN VOWEL SIGN LONG AA;Mn;230;NSM;;;;;N;;;;; 0820;SAMARITAN VOWEL SIGN AA;Mn;230;NSM;;;;;N;;;;; 0821;SAMARITAN VOWEL SIGN OVERLONG A;Mn;230;NSM;;;;;N;;;;; 0822;SAMARITAN VOWEL SIGN LONG A;Mn;230;NSM;;;;;N;;;;; 0823;SAMARITAN VOWEL SIGN A;Mn;230;NSM;;;;;N;;;;; 0825;SAMARITAN VOWEL SIGN SHORT A;Mn;230;NSM;;;;;N;;;;; 0826;SAMARITAN VOWEL SIGN LONG U;Mn;230;NSM;;;;;N;;;;; 0827;SAMARITAN VOWEL SIGN U;Mn;230;NSM;;;;;N;;;;; 0829;SAMARITAN VOWEL SIGN LONG I;Mn;230;NSM;;;;;N;;;;; 082A;SAMARITAN VOWEL SIGN I;Mn;230;NSM;;;;;N;;;;; 082B;SAMARITAN VOWEL SIGN O;Mn;230;NSM;;;;;N;;;;; 082C;SAMARITAN VOWEL SIGN SUKUN;Mn;230;NSM;;;;;N;;;;; 082D;SAMARITAN MARK NEQUDAA;Mn;230;NSM;;;;;N;;;;; 0951;DEVANAGARI STRESS SIGN UDATTA;Mn;230;NSM;;;;;N;;;;; 0953;DEVANAGARI GRAVE ACCENT;Mn;230;NSM;;;;;N;;;;; 0954;DEVANAGARI ACUTE ACCENT;Mn;230;NSM;;;;;N;;;;; 0F82;TIBETAN SIGN NYI ZLA NAA DA;Mn;230;NSM;;;;;N;TIBETAN CANDRABINDU WITH ORNAMENT;;;; 0F83;TIBETAN SIGN SNA LDAN;Mn;230;NSM;;;;;N;TIBETAN CANDRABINDU;;;; 0F86;TIBETAN SIGN LCI RTAGS;Mn;230;NSM;;;;;N;;;;; 0F87;TIBETAN SIGN YANG RTAGS;Mn;230;NSM;;;;;N;;;;; 135D;ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK;Mn;230;NSM;;;;;N;;;;; 135E;ETHIOPIC COMBINING VOWEL LENGTH MARK;Mn;230;NSM;;;;;N;;;;; 135F;ETHIOPIC COMBINING GEMINATION MARK;Mn;230;NSM;;;;;N;;;;; 17DD;KHMER SIGN ATTHACAN;Mn;230;NSM;;;;;N;;;;; 193A;LIMBU SIGN KEMPHRENG;Mn;230;NSM;;;;;N;;;;; 1A17;BUGINESE VOWEL SIGN I;Mn;230;NSM;;;;;N;;;;; 1A75;TAI THAM SIGN TONE-1;Mn;230;NSM;;;;;N;;;;; 1A76;TAI THAM SIGN TONE-2;Mn;230;NSM;;;;;N;;;;; 1A77;TAI THAM SIGN KHUEN TONE-3;Mn;230;NSM;;;;;N;;;;; 1A78;TAI THAM SIGN KHUEN TONE-4;Mn;230;NSM;;;;;N;;;;; 1A79;TAI THAM SIGN KHUEN TONE-5;Mn;230;NSM;;;;;N;;;;; 1A7A;TAI THAM SIGN RA HAAM;Mn;230;NSM;;;;;N;;;;; 1A7B;TAI THAM SIGN MAI SAM;Mn;230;NSM;;;;;N;;;;; 1A7C;TAI THAM SIGN KHUEN-LUE KARAN;Mn;230;NSM;;;;;N;;;;; 1B6B;BALINESE MUSICAL SYMBOL COMBINING TEGEH;Mn;230;NSM;;;;;N;;;;; 1B6D;BALINESE MUSICAL SYMBOL COMBINING KEMPUL;Mn;230;NSM;;;;;N;;;;; 1B6E;BALINESE MUSICAL SYMBOL COMBINING KEMPLI;Mn;230;NSM;;;;;N;;;;; 1B6F;BALINESE MUSICAL SYMBOL COMBINING JEGOGAN;Mn;230;NSM;;;;;N;;;;; 1B70;BALINESE MUSICAL SYMBOL COMBINING KEMPUL WITH JEGOGAN;Mn;230;NSM;;;;;N;;;;; 1B71;BALINESE MUSICAL SYMBOL COMBINING KEMPLI WITH JEGOGAN;Mn;230;NSM;;;;;N;;;;; 1B72;BALINESE MUSICAL SYMBOL COMBINING BENDE;Mn;230;NSM;;;;;N;;;;; 1B73;BALINESE MUSICAL SYMBOL COMBINING GONG;Mn;230;NSM;;;;;N;;;;; 1CD0;VEDIC TONE KARSHANA;Mn;230;NSM;;;;;N;;;;; 1CD1;VEDIC TONE SHARA;Mn;230;NSM;;;;;N;;;;; 1CD2;VEDIC TONE PRENKHA;Mn;230;NSM;;;;;N;;;;; 1CDA;VEDIC TONE DOUBLE SVARITA;Mn;230;NSM;;;;;N;;;;; 1CDB;VEDIC TONE TRIPLE SVARITA;Mn;230;NSM;;;;;N;;;;; 1CE0;VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA;Mn;230;NSM;;;;;N;;;;; 1DC0;COMBINING DOTTED GRAVE ACCENT;Mn;230;NSM;;;;;N;;;;; 1DC1;COMBINING DOTTED ACUTE ACCENT;Mn;230;NSM;;;;;N;;;;; 1DC3;COMBINING SUSPENSION MARK;Mn;230;NSM;;;;;N;;;;; 1DC4;COMBINING MACRON-ACUTE;Mn;230;NSM;;;;;N;;;;; 1DC5;COMBINING GRAVE-MACRON;Mn;230;NSM;;;;;N;;;;; 1DC6;COMBINING MACRON-GRAVE;Mn;230;NSM;;;;;N;;;;; 1DC7;COMBINING ACUTE-MACRON;Mn;230;NSM;;;;;N;;;;; 1DC8;COMBINING GRAVE-ACUTE-GRAVE;Mn;230;NSM;;;;;N;;;;; 1DC9;COMBINING ACUTE-GRAVE-ACUTE;Mn;230;NSM;;;;;N;;;;; 1DCB;COMBINING BREVE-MACRON;Mn;230;NSM;;;;;N;;;;; 1DCC;COMBINING MACRON-BREVE;Mn;230;NSM;;;;;N;;;;; 1DD1;COMBINING UR ABOVE;Mn;230;NSM;;;;;N;;;;; 1DD2;COMBINING US ABOVE;Mn;230;NSM;;;;;N;;;;; 1DD3;COMBINING LATIN SMALL LETTER FLATTENED OPEN A ABOVE;Mn;230;NSM;;;;;N;;;;; 1DD4;COMBINING LATIN SMALL LETTER AE;Mn;230;NSM;;;;;N;;;;; 1DD5;COMBINING LATIN SMALL LETTER AO;Mn;230;NSM;;;;;N;;;;; 1DD6;COMBINING LATIN SMALL LETTER AV;Mn;230;NSM;;;;;N;;;;; 1DD7;COMBINING LATIN SMALL LETTER C CEDILLA;Mn;230;NSM;;;;;N;;;;; 1DD8;COMBINING LATIN SMALL LETTER INSULAR D;Mn;230;NSM;;;;;N;;;;; 1DD9;COMBINING LATIN SMALL LETTER ETH;Mn;230;NSM;;;;;N;;;;; 1DDA;COMBINING LATIN SMALL LETTER G;Mn;230;NSM;;;;;N;;;;; 1DDB;COMBINING LATIN LETTER SMALL CAPITAL G;Mn;230;NSM;;;;;N;;;;; 1DDC;COMBINING LATIN SMALL LETTER K;Mn;230;NSM;;;;;N;;;;; 1DDD;COMBINING LATIN SMALL LETTER L;Mn;230;NSM;;;;;N;;;;; 1DDE;COMBINING LATIN LETTER SMALL CAPITAL L;Mn;230;NSM;;;;;N;;;;; 1DDF;COMBINING LATIN LETTER SMALL CAPITAL M;Mn;230;NSM;;;;;N;;;;; 1DE0;COMBINING LATIN SMALL LETTER N;Mn;230;NSM;;;;;N;;;;; 1DE1;COMBINING LATIN LETTER SMALL CAPITAL N;Mn;230;NSM;;;;;N;;;;; 1DE2;COMBINING LATIN LETTER SMALL CAPITAL R;Mn;230;NSM;;;;;N;;;;; 1DE3;COMBINING LATIN SMALL LETTER R ROTUNDA;Mn;230;NSM;;;;;N;;;;; 1DE4;COMBINING LATIN SMALL LETTER S;Mn;230;NSM;;;;;N;;;;; 1DE5;COMBINING LATIN SMALL LETTER LONG S;Mn;230;NSM;;;;;N;;;;; 1DE6;COMBINING LATIN SMALL LETTER Z;Mn;230;NSM;;;;;N;;;;; 1DFE;COMBINING LEFT ARROWHEAD ABOVE;Mn;230;NSM;;;;;N;;;;; 20D0;COMBINING LEFT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING LEFT HARPOON ABOVE;;;; 20D1;COMBINING RIGHT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING RIGHT HARPOON ABOVE;;;; 20D4;COMBINING ANTICLOCKWISE ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING ANTICLOCKWISE ARROW ABOVE;;;; 20D5;COMBINING CLOCKWISE ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING CLOCKWISE ARROW ABOVE;;;; 20D6;COMBINING LEFT ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING LEFT ARROW ABOVE;;;; 20D7;COMBINING RIGHT ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING RIGHT ARROW ABOVE;;;; 20DB;COMBINING THREE DOTS ABOVE;Mn;230;NSM;;;;;N;NON-SPACING THREE DOTS ABOVE;;;; 20DC;COMBINING FOUR DOTS ABOVE;Mn;230;NSM;;;;;N;NON-SPACING FOUR DOTS ABOVE;;;; 20E1;COMBINING LEFT RIGHT ARROW ABOVE;Mn;230;NSM;;;;;N;NON-SPACING LEFT RIGHT ARROW ABOVE;;;; 20E7;COMBINING ANNUITY SYMBOL;Mn;230;NSM;;;;;N;;;;; 20E9;COMBINING WIDE BRIDGE ABOVE;Mn;230;NSM;;;;;N;;;;; 20F0;COMBINING ASTERISK ABOVE;Mn;230;NSM;;;;;N;;;;; 2CEF;COPTIC COMBINING NI ABOVE;Mn;230;NSM;;;;;N;;;;; 2CF0;COPTIC COMBINING SPIRITUS ASPER;Mn;230;NSM;;;;;N;;;;; 2CF1;COPTIC COMBINING SPIRITUS LENIS;Mn;230;NSM;;;;;N;;;;; 2DE0;COMBINING CYRILLIC LETTER BE;Mn;230;NSM;;;;;N;;;;; 2DE1;COMBINING CYRILLIC LETTER VE;Mn;230;NSM;;;;;N;;;;; 2DE2;COMBINING CYRILLIC LETTER GHE;Mn;230;NSM;;;;;N;;;;; 2DE3;COMBINING CYRILLIC LETTER DE;Mn;230;NSM;;;;;N;;;;; 2DE4;COMBINING CYRILLIC LETTER ZHE;Mn;230;NSM;;;;;N;;;;; 2DE5;COMBINING CYRILLIC LETTER ZE;Mn;230;NSM;;;;;N;;;;; 2DE6;COMBINING CYRILLIC LETTER KA;Mn;230;NSM;;;;;N;;;;; 2DE7;COMBINING CYRILLIC LETTER EL;Mn;230;NSM;;;;;N;;;;; 2DE8;COMBINING CYRILLIC LETTER EM;Mn;230;NSM;;;;;N;;;;; 2DE9;COMBINING CYRILLIC LETTER EN;Mn;230;NSM;;;;;N;;;;; 2DEA;COMBINING CYRILLIC LETTER O;Mn;230;NSM;;;;;N;;;;; 2DEB;COMBINING CYRILLIC LETTER PE;Mn;230;NSM;;;;;N;;;;; 2DEC;COMBINING CYRILLIC LETTER ER;Mn;230;NSM;;;;;N;;;;; 2DED;COMBINING CYRILLIC LETTER ES;Mn;230;NSM;;;;;N;;;;; 2DEE;COMBINING CYRILLIC LETTER TE;Mn;230;NSM;;;;;N;;;;; 2DEF;COMBINING CYRILLIC LETTER HA;Mn;230;NSM;;;;;N;;;;; 2DF0;COMBINING CYRILLIC LETTER TSE;Mn;230;NSM;;;;;N;;;;; 2DF1;COMBINING CYRILLIC LETTER CHE;Mn;230;NSM;;;;;N;;;;; 2DF2;COMBINING CYRILLIC LETTER SHA;Mn;230;NSM;;;;;N;;;;; 2DF3;COMBINING CYRILLIC LETTER SHCHA;Mn;230;NSM;;;;;N;;;;; 2DF4;COMBINING CYRILLIC LETTER FITA;Mn;230;NSM;;;;;N;;;;; 2DF5;COMBINING CYRILLIC LETTER ES-TE;Mn;230;NSM;;;;;N;;;;; 2DF6;COMBINING CYRILLIC LETTER A;Mn;230;NSM;;;;;N;;;;; 2DF7;COMBINING CYRILLIC LETTER IE;Mn;230;NSM;;;;;N;;;;; 2DF8;COMBINING CYRILLIC LETTER DJERV;Mn;230;NSM;;;;;N;;;;; 2DF9;COMBINING CYRILLIC LETTER MONOGRAPH UK;Mn;230;NSM;;;;;N;;;;; 2DFA;COMBINING CYRILLIC LETTER YAT;Mn;230;NSM;;;;;N;;;;; 2DFB;COMBINING CYRILLIC LETTER YU;Mn;230;NSM;;;;;N;;;;; 2DFC;COMBINING CYRILLIC LETTER IOTIFIED A;Mn;230;NSM;;;;;N;;;;; 2DFD;COMBINING CYRILLIC LETTER LITTLE YUS;Mn;230;NSM;;;;;N;;;;; 2DFE;COMBINING CYRILLIC LETTER BIG YUS;Mn;230;NSM;;;;;N;;;;; 2DFF;COMBINING CYRILLIC LETTER IOTIFIED BIG YUS;Mn;230;NSM;;;;;N;;;;; A66F;COMBINING CYRILLIC VZMET;Mn;230;NSM;;;;;N;;;;; A67C;COMBINING CYRILLIC KAVYKA;Mn;230;NSM;;;;;N;;;;; A67D;COMBINING CYRILLIC PAYEROK;Mn;230;NSM;;;;;N;;;;; A6F0;BAMUM COMBINING MARK KOQNDON;Mn;230;NSM;;;;;N;;;;; A6F1;BAMUM COMBINING MARK TUKWENTIS;Mn;230;NSM;;;;;N;;;;; A8E0;COMBINING DEVANAGARI DIGIT ZERO;Mn;230;NSM;;;;;N;;;;; A8E1;COMBINING DEVANAGARI DIGIT ONE;Mn;230;NSM;;;;;N;;;;; A8E2;COMBINING DEVANAGARI DIGIT TWO;Mn;230;NSM;;;;;N;;;;; A8E3;COMBINING DEVANAGARI DIGIT THREE;Mn;230;NSM;;;;;N;;;;; A8E4;COMBINING DEVANAGARI DIGIT FOUR;Mn;230;NSM;;;;;N;;;;; A8E5;COMBINING DEVANAGARI DIGIT FIVE;Mn;230;NSM;;;;;N;;;;; A8E6;COMBINING DEVANAGARI DIGIT SIX;Mn;230;NSM;;;;;N;;;;; A8E7;COMBINING DEVANAGARI DIGIT SEVEN;Mn;230;NSM;;;;;N;;;;; A8E8;COMBINING DEVANAGARI DIGIT EIGHT;Mn;230;NSM;;;;;N;;;;; A8E9;COMBINING DEVANAGARI DIGIT NINE;Mn;230;NSM;;;;;N;;;;; A8EA;COMBINING DEVANAGARI LETTER A;Mn;230;NSM;;;;;N;;;;; A8EB;COMBINING DEVANAGARI LETTER U;Mn;230;NSM;;;;;N;;;;; A8EC;COMBINING DEVANAGARI LETTER KA;Mn;230;NSM;;;;;N;;;;; A8ED;COMBINING DEVANAGARI LETTER NA;Mn;230;NSM;;;;;N;;;;; A8EE;COMBINING DEVANAGARI LETTER PA;Mn;230;NSM;;;;;N;;;;; A8EF;COMBINING DEVANAGARI LETTER RA;Mn;230;NSM;;;;;N;;;;; A8F0;COMBINING DEVANAGARI LETTER VI;Mn;230;NSM;;;;;N;;;;; A8F1;COMBINING DEVANAGARI SIGN AVAGRAHA;Mn;230;NSM;;;;;N;;;;; AAB0;TAI VIET MAI KANG;Mn;230;NSM;;;;;N;;;;; AAB2;TAI VIET VOWEL I;Mn;230;NSM;;;;;N;;;;; AAB3;TAI VIET VOWEL UE;Mn;230;NSM;;;;;N;;;;; AAB7;TAI VIET MAI KHIT;Mn;230;NSM;;;;;N;;;;; AAB8;TAI VIET VOWEL IA;Mn;230;NSM;;;;;N;;;;; AABE;TAI VIET VOWEL AM;Mn;230;NSM;;;;;N;;;;; AABF;TAI VIET TONE MAI EK;Mn;230;NSM;;;;;N;;;;; AAC1;TAI VIET TONE MAI THO;Mn;230;NSM;;;;;N;;;;; FE20;COMBINING LIGATURE LEFT HALF;Mn;230;NSM;;;;;N;;;;; FE21;COMBINING LIGATURE RIGHT HALF;Mn;230;NSM;;;;;N;;;;; FE22;COMBINING DOUBLE TILDE LEFT HALF;Mn;230;NSM;;;;;N;;;;; FE23;COMBINING DOUBLE TILDE RIGHT HALF;Mn;230;NSM;;;;;N;;;;; FE24;COMBINING MACRON LEFT HALF;Mn;230;NSM;;;;;N;;;;; FE25;COMBINING MACRON RIGHT HALF;Mn;230;NSM;;;;;N;;;;; FE26;COMBINING CONJOINING MACRON;Mn;230;NSM;;;;;N;;;;; 10A0F;KHAROSHTHI SIGN VISARGA;Mn;230;NSM;;;;;N;;;;; 10A38;KHAROSHTHI SIGN BAR ABOVE;Mn;230;NSM;;;;;N;;;;; 1D185;MUSICAL SYMBOL COMBINING DOIT;Mn;230;NSM;;;;;N;;;;; 1D186;MUSICAL SYMBOL COMBINING RIP;Mn;230;NSM;;;;;N;;;;; 1D187;MUSICAL SYMBOL COMBINING FLIP;Mn;230;NSM;;;;;N;;;;; 1D188;MUSICAL SYMBOL COMBINING SMEAR;Mn;230;NSM;;;;;N;;;;; 1D189;MUSICAL SYMBOL COMBINING BEND;Mn;230;NSM;;;;;N;;;;; 1D1AA;MUSICAL SYMBOL COMBINING DOWN BOW;Mn;230;NSM;;;;;N;;;;; 1D1AB;MUSICAL SYMBOL COMBINING UP BOW;Mn;230;NSM;;;;;N;;;;; 1D1AC;MUSICAL SYMBOL COMBINING HARMONIC;Mn;230;NSM;;;;;N;;;;; 1D1AD;MUSICAL SYMBOL COMBINING SNAP PIZZICATO;Mn;230;NSM;;;;;N;;;;; 1D242;COMBINING GREEK MUSICAL TRISEME;Mn;230;NSM;;;;;N;;;;; 1D243;COMBINING GREEK MUSICAL TETRASEME;Mn;230;NSM;;;;;N;;;;; 1D244;COMBINING GREEK MUSICAL PENTASEME;Mn;230;NSM;;;;;N;;;;; kitty-0.41.1/gen/srgb_lut.py0000775000175000017510000000261314773370543015260 0ustar nileshnilesh#!/usr/bin/env python import os import sys from functools import lru_cache if __name__ == '__main__' and not __package__: import __main__ __main__.__package__ = 'gen' sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) def to_linear(a: float) -> float: if a <= 0.04045: return a / 12.92 else: return float(pow((a + 0.055) / 1.055, 2.4)) @lru_cache def generate_srgb_lut(line_prefix: str = ' ') -> list[str]: values: list[str] = [] lines: list[str] = [] for i in range(256): values.append(f'{to_linear(i / 255.0):1.5f}f') for i in range(16): lines.append(line_prefix + ', '.join(values[i * 16:(i + 1) * 16]) + ',') lines[-1] = lines[-1].rstrip(',') return lines def generate_srgb_gamma(declaration: str = 'static const GLfloat srgb_lut[256] = {', close: str = '};') -> str: lines: list[str] = [] a = lines.append a('// Generated by gen-srgb-lut.py DO NOT edit') a('') a(declaration) lines += generate_srgb_lut() a(close) return "\n".join(lines) def main(args: list[str]=sys.argv) -> None: c = generate_srgb_gamma() with open(os.path.join('kitty', 'srgb_gamma.h'), 'w') as f: f.write(f'{c}\n') if __name__ == '__main__': import runpy m = runpy.run_path(os.path.dirname(os.path.abspath(__file__))) m['main']([sys.executable, 'srgb-lut']) kitty-0.41.1/gen/wcwidth.py0000775000175000017510000013061314773370543015112 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2017, Kovid Goyal # Imports {{{ import json import os import re import subprocess import sys from collections import defaultdict from collections.abc import Generator, Hashable, Iterable from contextlib import contextmanager from functools import lru_cache, partial from html.entities import html5 from io import StringIO from math import ceil, log from typing import ( Callable, DefaultDict, Iterator, Literal, NamedTuple, Optional, Protocol, Sequence, TypedDict, Union, ) from urllib.request import urlopen if __name__ == '__main__' and not __package__: import __main__ __main__.__package__ = 'gen' sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # }}} # Fetching data {{{ non_characters = frozenset(range(0xfffe, 0x10ffff, 0x10000)) non_characters |= frozenset(range(0xffff, 0x10ffff + 1, 0x10000)) non_characters |= frozenset(range(0xfdd0, 0xfdf0)) if len(non_characters) != 66: raise SystemExit('non_characters table incorrect') emoji_skin_tone_modifiers = frozenset(range(0x1f3fb, 0x1F3FF + 1)) def get_data(fname: str, folder: str = 'UCD') -> Iterable[str]: url = f'https://www.unicode.org/Public/{folder}/latest/{fname}' bn = os.path.basename(url) local = os.path.join('/tmp', bn) if os.path.exists(local): with open(local, 'rb') as f: data = f.read() else: data = urlopen(url).read() with open(local, 'wb') as f: f.write(data) for line in data.decode('utf-8').splitlines(): line = line.strip() if line and not line.startswith('#'): yield line @lru_cache(maxsize=2) def unicode_version() -> tuple[int, int, int]: for line in get_data("ReadMe.txt"): m = re.search(r'Version\s+(\d+)\.(\d+)\.(\d+)', line) if m is not None: return int(m.group(1)), int(m.group(2)), int(m.group(3)) raise ValueError('Could not find Unicode Version') # }}} # Parsing Unicode databases {{{ # Map of class names to set of codepoints in class class_maps: dict[str, set[int]] = {} all_symbols: set[int] = set() name_map: dict[int, str] = {} word_search_map: DefaultDict[str, set[int]] = defaultdict(set) soft_hyphen = 0xad flag_codepoints = frozenset(range(0x1F1E6, 0x1F1E6 + 26)) # See https://github.com/harfbuzz/harfbuzz/issues/169 marks = set(emoji_skin_tone_modifiers) | flag_codepoints not_assigned = set(range(0, sys.maxunicode)) property_maps: dict[str, set[int]] = defaultdict(set) grapheme_segmentation_maps: dict[str, set[int]] = defaultdict(set) grapheme_break_as_int: dict[str, int] = {} int_as_grapheme_break: tuple[str, ...] = () incb_as_int: dict[str, int] = {} int_as_incb: tuple[str, ...] = () incb_map: dict[str, set[int]] = defaultdict(set) extended_pictographic: set[int] = set() def parse_prop_list() -> None: global marks for line in get_data('ucd/PropList.txt'): if line.startswith('#'): continue cp_or_range, rest = line.split(';', 1) chars = parse_range_spec(cp_or_range.strip()) name = rest.strip().split()[0] property_maps[name] |= chars # see https://www.unicode.org/faq/unsup_char.html#3 marks |= property_maps['Other_Default_Ignorable_Code_Point'] def parse_ucd() -> None: def add_word(w: str, c: int) -> None: if c <= 32 or c == 127 or 128 <= c <= 159: return if len(w) > 1: word_search_map[w.lower()].add(c) first: Optional[int] = None for word, c in html5.items(): if len(c) == 1: add_word(word.rstrip(';'), ord(c)) word_search_map['nnbsp'].add(0x202f) for line in get_data('ucd/UnicodeData.txt'): parts = [x.strip() for x in line.split(';')] codepoint = int(parts[0], 16) name = parts[1] or parts[10] if name == '': name = parts[10] if name: name_map[codepoint] = name for word in name.lower().split(): add_word(word, codepoint) category = parts[2] s = class_maps.setdefault(category, set()) desc = parts[1] codepoints: Union[tuple[int, ...], Iterable[int]] = (codepoint,) if first is None: if desc.endswith(', First>'): first = codepoint continue else: codepoints = range(first, codepoint + 1) first = None for codepoint in codepoints: s.add(codepoint) not_assigned.discard(codepoint) if category.startswith('M'): marks.add(codepoint) elif category.startswith('S'): all_symbols.add(codepoint) elif category == 'Cf': # we add Cf to marks as it contains things like tags and zero # width chars. Not sure if *all* of Cf should be treated as # combining chars, might need to add individual exceptions in # the future. marks.add(codepoint) with open('gen/nerd-fonts-glyphs.txt') as f: for line in f: line = line.strip() if not line or line.startswith('#'): continue code, category, name = line.split(' ', 2) codepoint = int(code, 16) if name and codepoint not in name_map: name_map[codepoint] = name.upper() for word in name.lower().split(): add_word(word, codepoint) # Some common synonyms word_search_map['bee'] |= word_search_map['honeybee'] word_search_map['lambda'] |= word_search_map['lamda'] word_search_map['lamda'] |= word_search_map['lambda'] word_search_map['diamond'] |= word_search_map['gem'] def parse_range_spec(spec: str) -> set[int]: spec = spec.strip() if '..' in spec: chars_ = tuple(map(lambda x: int(x, 16), filter(None, spec.split('.')))) chars = set(range(chars_[0], chars_[1] + 1)) else: chars = {int(spec, 16)} return chars def split_two(line: str) -> tuple[set[int], str]: spec, rest = line.split(';', 1) spec, rest = spec.strip(), rest.strip().split(' ', 1)[0].strip() return parse_range_spec(spec), rest all_emoji: set[int] = set() emoji_presentation_bases: set[int] = set() narrow_emoji: set[int] = set() wide_emoji: set[int] = set() flags: dict[int, list[int]] = {} def parse_basic_emoji(spec: str) -> None: parts = list(filter(None, spec.split())) has_emoji_presentation = len(parts) < 2 chars = parse_range_spec(parts[0]) all_emoji.update(chars) emoji_presentation_bases.update(chars) (wide_emoji if has_emoji_presentation else narrow_emoji).update(chars) def parse_keycap_sequence(spec: str) -> None: base, fe0f, cc = list(filter(None, spec.split())) chars = parse_range_spec(base) all_emoji.update(chars) emoji_presentation_bases.update(chars) narrow_emoji.update(chars) def parse_flag_emoji_sequence(spec: str) -> None: a, b = list(filter(None, spec.split())) left, right = int(a, 16), int(b, 16) chars = {left, right} all_emoji.update(chars) wide_emoji.update(chars) emoji_presentation_bases.update(chars) flags.setdefault(left, []).append(right) def parse_emoji_tag_sequence(spec: str) -> None: a = int(spec.split()[0], 16) all_emoji.add(a) wide_emoji.add(a) emoji_presentation_bases.add(a) def parse_emoji_modifier_sequence(spec: str) -> None: a, b = list(filter(None, spec.split())) char, mod = int(a, 16), int(b, 16) mod all_emoji.add(char) wide_emoji.add(char) emoji_presentation_bases.add(char) def parse_emoji() -> None: for line in get_data('emoji-sequences.txt', 'emoji'): parts = [x.strip() for x in line.split(';')] if len(parts) < 2: continue data, etype = parts[:2] if etype == 'Basic_Emoji': parse_basic_emoji(data) elif etype == 'Emoji_Keycap_Sequence': parse_keycap_sequence(data) elif etype == 'RGI_Emoji_Flag_Sequence': parse_flag_emoji_sequence(data) elif etype == 'RGI_Emoji_Tag_Sequence': parse_emoji_tag_sequence(data) elif etype == 'RGI_Emoji_Modifier_Sequence': parse_emoji_modifier_sequence(data) doublewidth: set[int] = set() ambiguous: set[int] = set() def parse_eaw() -> None: global doublewidth, ambiguous seen: set[int] = set() for line in get_data('ucd/EastAsianWidth.txt'): chars, eaw = split_two(line) if eaw == 'A': ambiguous |= chars seen |= chars elif eaw in ('W', 'F'): doublewidth |= chars seen |= chars doublewidth |= set(range(0x3400, 0x4DBF + 1)) - seen doublewidth |= set(range(0x4E00, 0x9FFF + 1)) - seen doublewidth |= set(range(0xF900, 0xFAFF + 1)) - seen doublewidth |= set(range(0x20000, 0x2FFFD + 1)) - seen doublewidth |= set(range(0x30000, 0x3FFFD + 1)) - seen def parse_grapheme_segmentation() -> None: global extended_pictographic, grapheme_break_as_int, incb_as_int, int_as_grapheme_break, int_as_incb global seg_props_from_int, seg_props_as_int grapheme_segmentation_maps['AtStart'] # this is used by the segmentation algorithm, no character has it grapheme_segmentation_maps['None'] # this is used by the segmentation algorithm, no character has it for line in get_data('ucd/auxiliary/GraphemeBreakProperty.txt'): chars, category = split_two(line) grapheme_segmentation_maps[category] |= chars grapheme_segmentation_maps['Private_Expecting_RI'] # this is used by the segmentation algorithm, no character has it grapheme_break_as_int = {x: i for i, x in enumerate(grapheme_segmentation_maps)} int_as_grapheme_break = tuple(grapheme_break_as_int) incb_map['None'] # used by segmentation algorithm no character has it for line in get_data('ucd/DerivedCoreProperties.txt'): spec, rest = line.split(';', 1) category = rest.strip().split(' ', 1)[0].strip().rstrip(';') chars = parse_range_spec(spec.strip()) if category == 'InCB': # Most InCB chars also have a GBP categorization, but not all, # there exist some InCB chars that do not have a GBP category subcat = rest.strip().split(';')[1].strip().split()[0].strip() incb_map[subcat] |= chars incb_as_int = {x: i for i, x in enumerate(incb_map)} int_as_incb = tuple(incb_as_int) for line in get_data('ucd/emoji/emoji-data.txt'): chars, category = split_two(line) if 'Extended_Pictographic#' == category: extended_pictographic |= chars seg_props_from_int = {'grapheme_break': int_as_grapheme_break, 'indic_conjunct_break': int_as_incb} seg_props_as_int = {'grapheme_break': grapheme_break_as_int, 'indic_conjunct_break': incb_as_int} class GraphemeSegmentationTest(TypedDict): data: tuple[str, ...] comment: str grapheme_segmentation_tests: list[GraphemeSegmentationTest] = [] def parse_test_data() -> None: for line in get_data('ucd/auxiliary/GraphemeBreakTest.txt'): t, comment = line.split('#') t = t.lstrip('÷').strip().rstrip('÷').strip() chars: list[list[str]] = [[]] for x in re.split(r'([÷×])', t): x = x.strip() match x: case '÷': chars.append([]) case '×': pass case '': pass case _: ch = chr(int(x, 16)) chars[-1].append(ch) c = tuple(''.join(c) for c in chars) grapheme_segmentation_tests.append({'data': c, 'comment': comment.strip()}) # }}} def write_case(spec: Union[tuple[int, ...], int], p: Callable[..., None], for_go: bool = False) -> None: if isinstance(spec, tuple): if for_go: v = ', '.join(f'0x{x:x}' for x in range(spec[0], spec[1] + 1)) p(f'\t\tcase {v}:') else: p('\t\tcase 0x{:x} ... 0x{:x}:'.format(*spec)) else: p(f'\t\tcase 0x{spec:x}:') @contextmanager def create_header(path: str, include_data_types: bool = True) -> Generator[Callable[..., None], None, None]: with open(path, 'w') as f: p = partial(print, file=f) p('// Unicode data, built from the Unicode Standard', '.'.join(map(str, unicode_version()))) p(f'// Code generated by {os.path.basename(__file__)}, DO NOT EDIT.', end='\n\n') if path.endswith('.h'): p('#pragma once') if include_data_types: p('#include "data-types.h"\n') p('START_ALLOW_CASE_RANGE') p() yield p p() if include_data_types: p('END_ALLOW_CASE_RANGE') def gen_names() -> None: aliases_map: dict[int, set[str]] = {} for word, codepoints in word_search_map.items(): for cp in codepoints: aliases_map.setdefault(cp, set()).add(word) if len(name_map) > 0xffff: raise Exception('Too many named codepoints') with open('tools/unicode_names/names.txt', 'w') as f: print(len(name_map), len(word_search_map), file=f) for cp in sorted(name_map): name = name_map[cp] words = name.lower().split() aliases = aliases_map.get(cp, set()) - set(words) end = '\n' if aliases: end = '\t' + ' '.join(sorted(aliases)) + end print(cp, *words, end=end, file=f) def gofmt(*files: str) -> None: subprocess.check_call(['gofmt', '-w', '-s'] + list(files)) def gen_rowcolumn_diacritics() -> None: # codes of all row/column diacritics codes = [] with open("gen/rowcolumn-diacritics.txt") as file: for line in file.readlines(): if line.startswith('#'): continue code = int(line.split(";")[0], 16) codes.append(code) go_file = 'tools/utils/images/rowcolumn_diacritics.go' with create_header('kitty/rowcolumn-diacritics.c') as p, create_header(go_file, include_data_types=False) as g: p('int diacritic_to_num(char_type code) {') p('\tswitch (code) {') g('package images') g(f'var NumberToDiacritic = [{len(codes)}]rune''{') g(', '.join(f'0x{x:x}' for x in codes) + ',') g('}') range_start_num = 1 range_start = 0 range_end = 0 def print_range() -> None: if range_start >= range_end: return write_case((range_start, range_end), p) p('\t\treturn code - ' + hex(range_start) + ' + ' + str(range_start_num) + ';') for code in codes: if range_end == code: range_end += 1 else: print_range() range_start_num += range_end - range_start range_start = code range_end = code + 1 print_range() p('\t}') p('\treturn 0;') p('}') gofmt(go_file) def gen_test_data() -> None: with open('kitty_tests/GraphemeBreakTest.json', 'wb') as f: f.write(json.dumps(grapheme_segmentation_tests, indent=2, ensure_ascii=False).encode()) def getsize(data: Iterable[int]) -> Literal[1, 2, 4]: # return smallest possible integer size for the given array maxdata = max(data) if maxdata < 256: return 1 if maxdata < 65536: return 2 return 4 def mask_for(bits: int) -> int: return ~((~0) << bits) def splitbins[T: Hashable](t: tuple[T, ...], property_size: int, use_fixed_shift: int = 0) -> tuple[list[int], list[int], list[T], int]: if use_fixed_shift: candidates = range(use_fixed_shift, use_fixed_shift + 1) else: n = len(t)-1 # last valid index maxshift = 0 # the most we can shift n and still have something left if n > 0: while n >> 1: n >>= 1 maxshift += 1 candidates = range(maxshift + 1) t3: list[T] = [] tmap: dict[T, int] = {} seen = set() for x in t: if x not in seen: seen.add(x) tmap[x] = len(t3) t3.append(x) t_int = tuple(tmap[x] for x in t) bytesz = sys.maxsize def memsize() -> int: ans = len(t1)*getsize(t1) sz3 = len(t3)*property_size + len(t2)*getsize(t2) sz2 = len(t2) * property_size return ans + min(sz2, sz3) for shift in candidates: t1: list[int] = [] t2: list[int] = [] size = 2**shift bincache: dict[tuple[int, ...], int] = {} for i in range(0, len(t_int), size): bin = t_int[i:i+size] index = bincache.get(bin) if index is None: index = len(t2) bincache[bin] = index t2.extend(bin) t1.append(index >> shift) # determine memory size b = memsize() if b < bytesz: best = t1, t2, shift bytesz = b t1, t2, shift = best return t1, t2, t3, shift class Property(Protocol): @property def as_c(self) -> str: return '' @property def as_go(self) -> str: return '' @classmethod def bitsize(cls) -> int: return 0 def get_types(sz: int) -> tuple[str, str]: sz *= 8 return f'uint{sz}_t', f'uint{sz}' def gen_multistage_table( c: Callable[..., None], g: Callable[..., None], t1: Sequence[int], t2: Sequence[int], t3: Sequence[Property], shift: int, input_max_val: int ) -> None: t1_type_sz = getsize(t1) ctype_t1, gotype_t1 = get_types(t1_type_sz) mask = mask_for(shift) name = t3[0].__class__.__name__ t2_type_sz = getsize(tuple(range(len(t3)))) ctype_t2, gotype_t2 = get_types(t2_type_sz) t3_type_sz = t3[0].bitsize() // 8 lname = name.lower() input_type = get_types(getsize((input_max_val,)))[1] # Output t1 c(f'static const char_type {name}_mask = {mask}u;') c(f'static const char_type {name}_shift = {shift}u;') c(f'static const {ctype_t1} {name}_t1[{len(t1)}] = ''{') c(f'\t{", ".join(map(str, t1))}') c('};') g(f'const {lname}_mask = {mask}') g(f'const {lname}_shift = {shift}') g(f'var {lname}_t1 = [{len(t1)}]{gotype_t1}''{') g(f'\t{", ".join(map(str, t1))},') g('}') bytesz = len(t1) * t1_type_sz if t3_type_sz > t2_type_sz: # needs 3 levels bytesz += len(t2) * t2_type_sz + len(t3) * t3_type_sz c(f'static const {ctype_t2} {name}_t2[{len(t2)}] = ''{') c(f'\t{", ".join(map(str, t2))}') c('};') items = '\n\t'.join(x.as_c + f', // {i}' for i, x in enumerate(t3)) c(f'static const {name} {name}_t3[{len(t3)}] = ''{') c(f'\t{items}') c('};') g(f'var {lname}_t2 = [{len(t2)}]{gotype_t2}''{') g(f'\t{", ".join(map(str, t2))},') g('}') items = '\n\t'.join(x.as_go + f', // {i}' for i, x in enumerate(t3)) g(f'var {lname}_t3 = [{len(t3)}]{name}''{') g(f'\t{items}') g('}') g(f''' // Array accessor function that avoids bounds checking func {lname}_for(x {input_type}) {name} {{ t1 := uintptr(*(*{gotype_t1})(unsafe.Pointer(uintptr(unsafe.Pointer(&{lname}_t1[0])) + uintptr(x>>{lname}_shift)*{t1_type_sz}))) t1_shifted := (t1 << {lname}_shift) + (uintptr(x) & {lname}_mask) t2 := uintptr(*(*{gotype_t2})(unsafe.Pointer(uintptr(unsafe.Pointer(&{lname}_t2[0])) + t1_shifted*{t2_type_sz}))) return *(*{name})(unsafe.Pointer(uintptr(unsafe.Pointer(&{lname}_t3[0])) + t2*{t3_type_sz})) }} ''') else: t3 = tuple(t3[i] for i in t2) bytesz += len(t3) * t3_type_sz items = '\n\t'.join(x.as_c + ',' for x in t3) c(f'static const {name} {name}_t2[{len(t3)}] = ''{') c(f'\t{items}') c('};') items = '\n\t'.join(x.as_go + ',' for x in t3) g(f'var {lname}_t2 = [{len(t3)}]{name}''{') g(f'\t{items}') g('}') g(f''' // Array accessor function that avoids bounds checking func {lname}_for(x {input_type}) {name} {{ t1 := uintptr(*(*{gotype_t1})(unsafe.Pointer(uintptr(unsafe.Pointer(&{lname}_t1[0])) + uintptr(x>>{lname}_shift)*{t1_type_sz}))) t1_shifted := (t1 << {lname}_shift) + (uintptr(x) & {lname}_mask) return *(*{name})(unsafe.Pointer(uintptr(unsafe.Pointer(&{lname}_t2[0])) + t1_shifted*{t3_type_sz})) }} ''') print(f'Size of {name} table: {ceil(bytesz/1024)}KB with {shift} bit shift') width_shift = 4 def bitsize(maxval: int) -> int: # number of bits needed to store maxval return ceil(log(maxval, 2)) def clamped_bitsize(val: int) -> int: if val <= 8: return 8 if val <= 16: return 16 if val <= 32: return 32 if val <= 64: return 64 raise ValueError('Too many fields') def bitfield_from_int( fields: dict[str, int], x: int, int_to_str: dict[str, tuple[str, ...]] ) -> dict[str, str | bool]: # first field is least significant, last field is most significant args: dict[str, str | bool] = {} for f, shift in fields.items(): mask = mask_for(shift) val = x & mask if shift == 1: args[f] = bool(val) else: args[f] = int_to_str[f][val] x >>= shift return args def bitfield_as_int( fields: dict[str, int], vals: Sequence[bool | str], str_maps: dict[str, dict[str, int]] ) -> int: # first field is least significant, last field is most significant ans = shift = 0 for i, (f, width) in enumerate(fields.items()): qval = vals[i] if isinstance(qval, str): val = str_maps[f][qval] else: val = int(qval) ans |= val << shift shift += width return ans seg_props_from_int: dict[str, tuple[str, ...]] = {} seg_props_as_int: dict[str, dict[str, int]] = {} class GraphemeSegmentationProps(NamedTuple): grapheme_break: str = '' # set at runtime indic_conjunct_break: str = '' # set at runtime is_extended_pictographic: bool = True @classmethod def used_bits(cls) -> int: return sum(int(cls._field_defaults[f]) for f in cls._fields) @classmethod def bitsize(cls) -> int: return clamped_bitsize(cls.used_bits()) @classmethod def fields(cls) -> dict[str, int]: return {f: int(cls._field_defaults[f]) for f in cls._fields} @classmethod def from_int(cls, x: int) -> 'GraphemeSegmentationProps': args = bitfield_from_int(cls.fields(), x, seg_props_from_int) return cls(**args) # type: ignore def __int__(self) -> int: return bitfield_as_int(self.fields(), self, seg_props_as_int) control_grapheme_breaks = 'CR', 'LF', 'Control' linker_or_extend = 'Linker', 'Extend' def bitfield_declaration_as_c(name: str, fields: dict[str, int], *alternate_fields: dict[str, int]) -> str: base_size = clamped_bitsize(sum(fields.values())) base_type = f'uint{base_size}_t' ans = [f'// {name}Declaration: uses {sum(fields.values())} bits {{''{{', f'typedef union {name} {{'] def struct(fields: dict[str, int]) -> Iterator[str]: if not fields: return empty = base_size - sum(fields.values()) yield ' struct __attribute__((packed)) {' yield '#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__' for f, width in reversed(fields.items()): yield f' uint{clamped_bitsize(width)}_t {f} : {width};' if empty: yield f' uint{clamped_bitsize(empty)}_t : {empty};' yield '#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__' if empty: yield f' uint{clamped_bitsize(empty)}_t : {empty};' for f, width in fields.items(): yield f' uint{clamped_bitsize(width)}_t {f} : {width};' yield '#else' yield '#error "Unsupported endianness"' yield '#endif' yield ' };' ans.extend(struct(fields)) for fields in alternate_fields: ans.extend(struct(fields)) ans.append(f' {base_type} val;') ans.append(f'}} {name};') ans.append(f'static_assert(sizeof({name}) == sizeof({base_type}), "Fix the ordering of {name}");') ans.append(f'// End{name}Declaration }}''}}') return '\n'.join(ans) class GraphemeSegmentationState(NamedTuple): grapheme_break: str = '' # set at runtime # True if the last character ends a sequence of Indic_Conjunct_Break values: consonant {extend|linker}* incb_consonant_extended: bool = True # True if the last character ends a sequence of Indic_Conjunct_Break values: consonant {extend|linker}* linker incb_consonant_extended_linker: bool = True # True if the last character ends a sequence of Indic_Conjunct_Break values: consonant {extend|linker}* linker {extend|linker}* incb_consonant_extended_linker_extended: bool = True # True if the last character ends an emoji modifier sequence \p{Extended_Pictographic} Extend* emoji_modifier_sequence: bool = True # True if the last character was immediately preceded by an emoji modifier sequence \p{Extended_Pictographic} Extend* emoji_modifier_sequence_before_last_char: bool = True @classmethod def make(cls) -> 'GraphemeSegmentationState': return GraphemeSegmentationState('AtStart', False, False, False, False, False) @classmethod def fields(cls) -> dict[str, int]: return {f: int(cls._field_defaults[f]) for f in cls._fields} @classmethod def from_int(cls, x: int) -> 'GraphemeSegmentationState': args = bitfield_from_int(cls.fields(), x, {'grapheme_break': int_as_grapheme_break}) return cls(**args) # type: ignore def __int__(self) -> int: return bitfield_as_int(self.fields(), self, seg_props_as_int) @classmethod def c_declaration(cls) -> str: return bitfield_declaration_as_c(cls.__name__, cls.fields()) @classmethod def used_bits(cls) -> int: return sum(int(cls._field_defaults[f]) for f in cls._fields) @classmethod def bitsize(cls) -> int: return clamped_bitsize(cls.used_bits()) def add_to_current_cell(self, p: GraphemeSegmentationProps) -> 'GraphemeSegmentationResult': prev = self.grapheme_break prop = p.grapheme_break incb = p.indic_conjunct_break add_to_cell = False if self.grapheme_break == 'AtStart': add_to_cell = True if prop == 'Regional_Indicator': prop = 'Private_Expecting_RI' else: # No break between CR and LF (GB3). if prev == 'CR' and prop == 'LF': add_to_cell = True # Break before and after controls (GB4, GB5). elif prev in control_grapheme_breaks or prop in control_grapheme_breaks: pass # No break between Hangul syllable sequences (GB6, GB7, GB8). elif ( (prev == 'L' and prop in ('L', 'V', 'LV', 'LVT')) or (prev in ('LV', 'V') and prop in ('V', 'T')) or (prev in ('LVT', 'T') and prop == 'T') ): add_to_cell = True # No break before: extending characters or ZWJ (GB9), SpacingMarks (GB9a), Prepend characters (GB9b). elif prop in ('Extend', 'ZWJ', 'SpacingMark') or prev == 'Prepend': add_to_cell = True # No break within certain combinations of Indic_Conjunct_Break values # Between consonant {extend|linker}* linker {extend|linker}* and consonant (GB9c). elif self.incb_consonant_extended_linker_extended and incb == 'Consonant': add_to_cell = True # No break within emoji modifier sequences or emoji zwj sequences (GB11). elif prev == 'ZWJ' and self.emoji_modifier_sequence_before_last_char and p.is_extended_pictographic: add_to_cell = True # No break between RI if there is an odd number of RI characters before (GB12, GB13). elif prop == 'Regional_Indicator': if prev == 'Private_Expecting_RI': add_to_cell = True else: prop = 'Private_Expecting_RI' # Break everywhere else GB999 incb_consonant_extended_linker = self.incb_consonant_extended and incb == 'Linker' incb_consonant_extended_linker_extended = incb_consonant_extended_linker or ( self.incb_consonant_extended_linker_extended and incb in linker_or_extend) incb_consonant_extended = incb == 'Consonant' or ( self.incb_consonant_extended and incb in linker_or_extend) emoji_modifier_sequence_before_last_char = self.emoji_modifier_sequence emoji_modifier_sequence = (self.emoji_modifier_sequence and prop == 'Extend') or p.is_extended_pictographic return GraphemeSegmentationResult(GraphemeSegmentationState( grapheme_break=prop, incb_consonant_extended=incb_consonant_extended, incb_consonant_extended_linker=incb_consonant_extended_linker, incb_consonant_extended_linker_extended=incb_consonant_extended_linker_extended, emoji_modifier_sequence=emoji_modifier_sequence, emoji_modifier_sequence_before_last_char=emoji_modifier_sequence_before_last_char ), add_to_cell) def split_into_graphemes(props: Sequence[GraphemeSegmentationProps], text: str) -> Iterator[str]: s = GraphemeSegmentationState.make() pos = 0 for i, ch in enumerate(text): p = props[ord(ch)] s, add_to_cell = s.add_to_current_cell(p) if not add_to_cell: yield text[pos:i] pos = i if pos < len(text): yield text[pos:] def split_into_graphemes_with_table( props: Sequence['GraphemeSegmentationProps'], table: Sequence['GraphemeSegmentationResult'], text: str, ) -> Iterator[str]: s = GraphemeSegmentationResult.make() pos = 0 for i, ch in enumerate(text): k = int(GraphemeSegmentationKey(s.new_state, props[ord(ch)])) s = table[k] if not s.add_to_current_cell: yield text[pos:i] pos = i if pos < len(text): yield text[pos:] def test_grapheme_segmentation(split_into_graphemes: Callable[[str], Iterator[str]]) -> None: for test in grapheme_segmentation_tests: expected = test['data'] actual = tuple(split_into_graphemes(''.join(test['data']))) if expected != actual: def as_codepoints(text: str) -> str: return ' '.join(hex(ord(x))[2:] for x in text) qe = tuple(map(as_codepoints, expected)) qa = tuple(map(as_codepoints, actual)) raise SystemExit(f'Failed to split graphemes for: {test["comment"]}\n{expected!r} {qe} != {actual!r} {qa}') class GraphemeSegmentationKey(NamedTuple): state: GraphemeSegmentationState char: GraphemeSegmentationProps @classmethod def from_int(cls, x: int) -> 'GraphemeSegmentationKey': shift = GraphemeSegmentationProps.used_bits() mask = mask_for(shift) state = GraphemeSegmentationState.from_int(x >> shift) char = GraphemeSegmentationProps.from_int(x & mask) return GraphemeSegmentationKey(state, char) def __int__(self) -> int: shift = GraphemeSegmentationProps.used_bits() return int(self.state) << shift | int(self.char) def result(self) -> 'GraphemeSegmentationResult': return self.state.add_to_current_cell(self.char) @classmethod def code_to_convert_to_int(cls, for_go: bool = False) -> str: lines: list[str] = [] a = lines.append shift = GraphemeSegmentationProps.used_bits() if for_go: base_type = f'uint{GraphemeSegmentationState.bitsize()}' a(f'func grapheme_segmentation_key(r GraphemeSegmentationResult, ch CharProps) ({base_type}) ''{') a(f'\treturn (r.State() << {shift}) | ch.GraphemeSegmentationProperty()') a('}') else: base_type = f'uint{GraphemeSegmentationState.bitsize()}_t' a(f'static inline {base_type} {cls.__name__}(GraphemeSegmentationResult r, CharProps ch)' '{') a(f'\treturn (r.state << {shift}) | ch.grapheme_segmentation_property;') a('}') return '\n'.join(lines) class GraphemeSegmentationResult(NamedTuple): new_state: GraphemeSegmentationState = GraphemeSegmentationState() add_to_current_cell: bool = True @classmethod def used_bits(cls) -> int: return sum(int(GraphemeSegmentationState._field_defaults[f]) for f in GraphemeSegmentationState._fields) + 1 @classmethod def bitsize(cls) -> int: return clamped_bitsize(cls.used_bits()) @classmethod def make(cls) -> 'GraphemeSegmentationResult': return GraphemeSegmentationResult(GraphemeSegmentationState.make(), False) @classmethod def go_fields(cls) -> Sequence[str]: ans = [] ans.append('add_to_current_cell 1') for f, width in reversed(GraphemeSegmentationState.fields().items()): ans.append(f'{f} {width}') return tuple(ans) @property def as_go(self) -> str: shift = 0 parts = [] for f in reversed(GraphemeSegmentationResult.go_fields()): f, _, w = f.partition(' ') bits = int(w) if f != 'add_to_current_cell': x = getattr(self.new_state, f) if f == 'grapheme_break': x = f'GraphemeSegmentationResult(GBP_{x})' else: x = int(x) else: x = int(self.add_to_current_cell) mask = '0b' + '1' * bits parts.append(f'(({x} & {mask}) << {shift})') shift += bits return ' | '.join(parts) @classmethod def go_extra(cls) -> str: bits = GraphemeSegmentationState.used_bits() base_type = f'uint{GraphemeSegmentationState.bitsize()}' return f''' func (r GraphemeSegmentationResult) State() (ans {base_type}) {{ return {base_type}(r) & {mask_for(bits)} }} ''' @property def as_c(self) -> str: parts = [] for f in GraphemeSegmentationState._fields: x = getattr(self.new_state, f) match f: case 'grapheme_break': x = f'GBP_{x}' case _: x = int(x) parts.append(f'.{f}={x}') parts.append(f'.add_to_current_cell={int(self.add_to_current_cell)}') return '{' + ', '.join(parts) + '}' @classmethod def c_declaration(cls) -> str: fields = {'add_to_current_cell': 1} sfields = GraphemeSegmentationState.fields() fields.update(sfields) bits = sum(sfields.values()) # dont know if the alternate state access works in big endian return bitfield_declaration_as_c('GraphemeSegmentationResult', fields, {'state': bits}) class CharProps(NamedTuple): width: int = 3 is_emoji: bool = True category: str = '' # set at runtime is_emoji_presentation_base: bool = True # derived properties for fast lookup is_invalid: bool = True is_non_rendered: bool = True is_symbol: bool = True is_combining_char: bool = True is_word_char: bool = True is_punctuation: bool = True # needed for grapheme segmentation set as LSB bits for easy conversion to GraphemeSegmentationProps grapheme_break: str = '' # set at runtime indic_conjunct_break: str = '' # set at runtime is_extended_pictographic: bool = True @classmethod def bitsize(cls) -> int: ans = sum(int(cls._field_defaults[f]) for f in cls._fields) return clamped_bitsize(ans) @classmethod def go_fields(cls) -> Sequence[str]: ans = [] for f in cls._fields: bits = int(cls._field_defaults[f]) if f == 'width': f = 'shifted_width' ans.append(f'{f} {bits}') return tuple(ans) @property def as_go(self) -> str: shift = 0 parts = [] for f in reversed(self.go_fields()): f, _, w = f.partition(' ') if f == 'shifted_width': f = 'width' x = getattr(self, f) match f: case 'width': x += width_shift case 'grapheme_break': x = f'CharProps(GBP_{x})' case 'indic_conjunct_break': x = f'CharProps(ICB_{x})' case 'category': x = f'CharProps(UC_{x})' case _: x = int(x) bits = int(w) mask = '0b' + '1' * bits parts.append(f'(({x} & {mask}) << {shift})') shift += bits return ' | '.join(parts) @classmethod def go_extra(cls) -> str: base_type = f'uint{GraphemeSegmentationState.bitsize()}' f = GraphemeSegmentationProps.fields() s = f['grapheme_break'] + f['indic_conjunct_break'] return f''' func (s CharProps) Width() int {{ return int(s.Shifted_width()) - {width_shift} }} func (s CharProps) GraphemeSegmentationProperty() {base_type} {{ return {base_type}(s.Grapheme_break() | (s.Indic_conjunct_break() << {f["grapheme_break"]}) | (s.Is_extended_pictographic() << {s})) }} ''' @property def as_c(self) -> str: parts = [] for f in self._fields: x = getattr(self, f) match f: case 'width': x += width_shift f = 'shifted_width' case 'grapheme_break': x = f'GBP_{x}' case 'indic_conjunct_break': x = f'ICB_{x}' case 'category': x = f'UC_{x}' case _: x = int(x) parts.append(f'.{f}={x}') return '{' + ', '.join(parts) + '}' @classmethod def fields(cls) -> dict[str, int]: return {'shifted_width' if f == 'width' else f: int(cls._field_defaults[f]) for f in cls._fields} @classmethod def c_declaration(cls) -> str: # Dont know if grapheme_segmentation_property in alternate works on big endian alternate = { 'grapheme_segmentation_property': sum(int(cls._field_defaults[f]) for f in GraphemeSegmentationProps._fields) } return bitfield_declaration_as_c(cls.__name__, cls.fields(), alternate) def generate_enum(p: Callable[..., None], gp: Callable[..., None], name: str, *items: str, prefix: str = '') -> None: p(f'typedef enum {name} {{') # }} gp(f'type {name} uint8\n') gp('const (') # ) for i, x in enumerate(items): x = prefix + x p(f'\t{x},') if i == 0: gp(f'{x} {name} = iota') else: gp(x) p(f'}} {name};') gp(')') p('') gp('') def category_set(predicate: Callable[[str], bool]) -> set[int]: ans = set() for c, chs in class_maps.items(): if predicate(c): ans |= chs return ans def top_level_category(q: str) -> set[int]: return category_set(lambda x: x[0] in q) def patch_declaration(name: str, decl: str, raw: str) -> str: begin = f'// {name}Declaration' end = f'// End{name}Declaration }}''}}' return re.sub(rf'{begin}.+?{end}', decl.rstrip(), raw, flags=re.DOTALL) def gen_char_props() -> None: CharProps._field_defaults['grapheme_break'] = str(bitsize(len(grapheme_segmentation_maps))) CharProps._field_defaults['indic_conjunct_break'] = str(bitsize(len(incb_map))) CharProps._field_defaults['category'] = str(bitsize(len(class_maps) + 1)) GraphemeSegmentationProps._field_defaults['grapheme_break'] = CharProps._field_defaults['grapheme_break'] GraphemeSegmentationProps._field_defaults['indic_conjunct_break'] = CharProps._field_defaults['indic_conjunct_break'] GraphemeSegmentationState._field_defaults['grapheme_break'] = GraphemeSegmentationProps._field_defaults['grapheme_break'] invalid = class_maps['Cc'] | class_maps['Cs'] | non_characters non_printing = invalid | class_maps['Cf'] non_rendered = non_printing | property_maps['Other_Default_Ignorable_Code_Point'] | set(range(0xfe00, 0xfe0f + 1)) is_word_char = top_level_category('LN') is_punctuation = top_level_category('P') width_map: dict[int, int] = {} cat_map: dict[int, str] = {} for cat, chs in class_maps.items(): for ch in chs: cat_map[ch] = cat def aw(s: Iterable[int], width: int) -> None: nonlocal width_map d = dict.fromkeys(s, width) d.update(width_map) width_map = d aw(flag_codepoints, 2) aw(doublewidth, 2) aw(wide_emoji, 2) aw(marks | {0}, 0) aw(non_printing, -1) aw(ambiguous, -2) aw(class_maps['Co'], -3) # Private use aw(not_assigned, -4) gs_map: dict[int, str] = {} icb_map: dict[int, str] = {} for name, cps in grapheme_segmentation_maps.items(): gs_map.update(dict.fromkeys(cps, name)) for name, cps in incb_map.items(): icb_map.update(dict.fromkeys(cps, name)) prop_array = tuple( CharProps( width=width_map.get(ch, 1), grapheme_break=gs_map.get(ch, 'None'), indic_conjunct_break=icb_map.get(ch, 'None'), is_invalid=ch in invalid, is_non_rendered=ch in non_rendered, is_emoji=ch in all_emoji, is_symbol=ch in all_symbols, is_extended_pictographic=ch in extended_pictographic, is_emoji_presentation_base=ch in emoji_presentation_bases, is_combining_char=ch in marks, category=cat_map.get(ch, 'Cn'), is_word_char=ch in is_word_char, is_punctuation=ch in is_punctuation, ) for ch in range(sys.maxunicode + 1)) gsprops = tuple(GraphemeSegmentationProps( grapheme_break=x.grapheme_break, indic_conjunct_break=x.indic_conjunct_break, is_extended_pictographic=x.is_extended_pictographic) for x in prop_array) test_grapheme_segmentation(partial(split_into_graphemes, gsprops)) gseg_results = tuple(GraphemeSegmentationKey.from_int(i).result() for i in range(1 << 16)) test_grapheme_segmentation(partial(split_into_graphemes_with_table, gsprops, gseg_results)) t1, t2, t3, t_shift = splitbins(prop_array, CharProps.bitsize() // 8) g1, g2, g3, g_shift = splitbins(gseg_results, GraphemeSegmentationResult.bitsize() // 8) from .bitfields import make_bitfield buf = StringIO() cen = partial(print, file=buf) with create_header('kitty/char-props-data.h', include_data_types=False) as c, open('tools/wcswidth/char-props-data.go', 'w') as gof: gp = partial(print, file=gof) gp('package wcswidth') gp('import "unsafe"') gp(f'const MAX_UNICODE = {sys.maxunicode}') gp(f'const UNICODE_LIMIT = {sys.maxunicode + 1}') generate_enum(c, gp, 'GraphemeBreakProperty', *grapheme_segmentation_maps, prefix='GBP_') generate_enum(c, gp, 'IndicConjunctBreak', *incb_map, prefix='ICB_') cen('// UCBDeclaration {{''{') cen(f'#define MAX_UNICODE ({sys.maxunicode}u)') generate_enum(cen, gp, 'UnicodeCategory', 'Cn', *class_maps, prefix='UC_') cen('// EndUCBDeclaration }}''}') gp(make_bitfield('tools/wcswidth', 'CharProps', *CharProps.go_fields(), add_package=False)[1]) gp(make_bitfield('tools/wcswidth', 'GraphemeSegmentationResult', *GraphemeSegmentationResult.go_fields(), add_package=False)[1]) gp(CharProps.go_extra()) gp(GraphemeSegmentationResult.go_extra()) gen_multistage_table(c, gp, t1, t2, t3, t_shift, len(prop_array)-1) gen_multistage_table(c, gp, g1, g2, g3, g_shift, len(gseg_results)-1) c(GraphemeSegmentationKey.code_to_convert_to_int()) c(GraphemeSegmentationState.c_declaration()) gp(GraphemeSegmentationKey.code_to_convert_to_int(for_go=True)) gofmt(gof.name) with open('kitty/char-props.h', 'r+') as f: raw = f.read() nraw = re.sub(r'\d+/\*=width_shift\*/', f'{width_shift}/*=width_shift*/', raw) nraw = patch_declaration('CharProps', CharProps.c_declaration(), nraw) nraw = patch_declaration('GraphemeSegmentationResult', GraphemeSegmentationResult.c_declaration(), nraw) nraw = patch_declaration('UCB', buf.getvalue(), nraw) if nraw != raw: f.seek(0) f.truncate() f.write(nraw) def main(args: list[str]=sys.argv) -> None: parse_ucd() parse_prop_list() parse_emoji() parse_eaw() parse_grapheme_segmentation() parse_test_data() gen_names() gen_rowcolumn_diacritics() gen_test_data() gen_char_props() if __name__ == '__main__': import runpy m = runpy.run_path(os.path.dirname(os.path.abspath(__file__))) m['main']([sys.executable, 'wcwidth']) kitty-0.41.1/glad/0000775000175000017510000000000014773370543013216 5ustar nileshnileshkitty-0.41.1/glad/generate.py0000775000175000017510000000207414773370543015370 0ustar nileshnilesh#!/usr/bin/env python # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2017, Kovid Goyal import os import re import shlex import shutil import subprocess cmdline = ( 'glad --out-path {dest} --api gl:core=3.1 ' ' --extensions GL_ARB_texture_storage,GL_ARB_copy_image,GL_ARB_multisample,GL_ARB_robustness,GL_ARB_instanced_arrays,GL_KHR_debug ' 'c --header-only --debug' ) def clean(x): if os.path.exists(x): shutil.rmtree(x) def regenerate(): clean('out') subprocess.check_call( shlex.split(cmdline.format(dest='out')) ) def strip_trailing_whitespace(c): return re.sub(r'\s+$', '', c, flags=re.MULTILINE) + '\n' def export(): with open('out/include/glad/gl.h', 'r', encoding='utf-8') as source: data = source.read() data = strip_trailing_whitespace(data) with open('../kitty/gl-wrapper.h', 'w', encoding='utf-8') as dest: dest.write(data) if __name__ == '__main__': os.chdir(os.path.dirname(os.path.abspath(__file__))) regenerate() export() kitty-0.41.1/glfw/0000775000175000017510000000000014773370543013246 5ustar nileshnileshkitty-0.41.1/glfw/__init__.py0000664000175000017510000000000014773370543015345 0ustar nileshnileshkitty-0.41.1/glfw/backend_utils.c0000664000175000017510000003164614773370543016233 0ustar nileshnilesh/* * backend_utils.c * Copyright (C) 2018 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #define _GNU_SOURCE #include "backend_utils.h" #include "internal.h" #include "memfd.h" #include #include #include #include #include #include #include #ifdef __NetBSD__ #define ppoll pollts #endif void update_fds(EventLoopData *eld) { for (nfds_t i = 0; i < eld->watches_count; i++) { Watch *w = eld->watches + i; eld->fds[i].fd = w->fd; eld->fds[i].events = w->enabled ? w->events : 0; } } static id_type watch_counter = 0; id_type addWatch(EventLoopData *eld, const char* name, int fd, int events, int enabled, watch_callback_func cb, void *cb_data) { if (eld->watches_count >= sizeof(eld->watches)/sizeof(eld->watches[0])) { _glfwInputError(GLFW_PLATFORM_ERROR, "Too many watches added"); return 0; } Watch *w = eld->watches + eld->watches_count++; w->name = name; w->fd = fd; w->events = events; w->enabled = enabled; w->callback = cb; w->callback_data = cb_data; w->free = NULL; w->id = ++watch_counter; update_fds(eld); return w->id; } #define removeX(which, item_id, update_func) {\ for (nfds_t i = 0; i < eld->which##_count; i++) { \ if (eld->which[i].id == item_id) { \ eld->which##_count--; \ if (eld->which[i].callback_data && eld->which[i].free) { \ eld->which[i].free(eld->which[i].id, eld->which[i].callback_data); \ eld->which[i].callback_data = NULL; eld->which[i].free = NULL; \ } \ if (i < eld->which##_count) { \ memmove(eld->which + i, eld->which + i + 1, sizeof(eld->which[0]) * (eld->which##_count - i)); \ } \ update_func(eld); break; \ }}} void removeWatch(EventLoopData *eld, id_type watch_id) { removeX(watches, watch_id, update_fds); } void toggleWatch(EventLoopData *eld, id_type watch_id, int enabled) { for (nfds_t i = 0; i < eld->watches_count; i++) { if (eld->watches[i].id == watch_id) { if (eld->watches[i].enabled != enabled) { eld->watches[i].enabled = enabled; update_fds(eld); } break; } } } static id_type timer_counter = 0; static int compare_timers(const void *a_, const void *b_) { const Timer *a = (const Timer*)a_, *b = (const Timer*)b_; return (a->trigger_at > b->trigger_at) ? 1 : (a->trigger_at < b->trigger_at) ? -1 : 0; } static void update_timers(EventLoopData *eld) { if (eld->timers_count > 1) qsort(eld->timers, eld->timers_count, sizeof(eld->timers[0]), compare_timers); } id_type addTimer(EventLoopData *eld, const char *name, monotonic_t interval, int enabled, bool repeats, timer_callback_func cb, void *cb_data, GLFWuserdatafreefun free) { if (eld->timers_count >= sizeof(eld->timers)/sizeof(eld->timers[0])) { _glfwInputError(GLFW_PLATFORM_ERROR, "Too many timers added"); return 0; } Timer *t = eld->timers + eld->timers_count++; t->interval = interval; t->name = name; t->trigger_at = enabled ? monotonic() + interval : MONOTONIC_T_MAX; t->repeats = repeats; t->callback = cb; t->callback_data = cb_data; t->free = free; t->id = ++timer_counter; update_timers(eld); return timer_counter; } void removeTimer(EventLoopData *eld, id_type timer_id) { removeX(timers, timer_id, update_timers); } void removeAllTimers(EventLoopData *eld) { for (nfds_t i = 0; i < eld->timers_count; i++) { if (eld->timers[i].free && eld->timers[i].callback_data) eld->timers[i].free(eld->timers[i].id, eld->timers[i].callback_data); } eld->timers_count = 0; } void toggleTimer(EventLoopData *eld, id_type timer_id, int enabled) { for (nfds_t i = 0; i < eld->timers_count; i++) { if (eld->timers[i].id == timer_id) { monotonic_t trigger_at = enabled ? (monotonic() + eld->timers[i].interval) : MONOTONIC_T_MAX; if (trigger_at != eld->timers[i].trigger_at) { eld->timers[i].trigger_at = trigger_at; update_timers(eld); } break; } } } void changeTimerInterval(EventLoopData *eld, id_type timer_id, monotonic_t interval) { for (nfds_t i = 0; i < eld->timers_count; i++) { if (eld->timers[i].id == timer_id) { eld->timers[i].interval = interval; break; } } } monotonic_t prepareForPoll(EventLoopData *eld, monotonic_t timeout) { for (nfds_t i = 0; i < eld->watches_count; i++) eld->fds[i].revents = 0; if (!eld->timers_count || eld->timers[0].trigger_at == MONOTONIC_T_MAX) return timeout; monotonic_t now = monotonic(), next_repeat_at = eld->timers[0].trigger_at; if (timeout < 0 || now + timeout > next_repeat_at) { timeout = next_repeat_at <= now ? 0 : next_repeat_at - now; } return timeout; } static struct timespec calc_time(monotonic_t nsec) { struct timespec result; result.tv_sec = nsec / (1000LL * 1000LL * 1000LL); result.tv_nsec = nsec % (1000LL * 1000LL * 1000LL); return result; } int pollWithTimeout(struct pollfd *fds, nfds_t nfds, monotonic_t timeout) { struct timespec tv = calc_time(timeout); return ppoll(fds, nfds, &tv, NULL); } static void dispatchEvents(EventLoopData *eld) { for (nfds_t i = 0; i < eld->watches_count; i++) { Watch *ww = eld->watches + i; struct pollfd *pfd = eld->fds + i; if (pfd->revents & ww->events) { ww->ready = 1; if (ww->callback) ww->callback(ww->fd, pfd->revents, ww->callback_data); } else ww->ready = 0; } } unsigned dispatchTimers(EventLoopData *eld) { if (!eld->timers_count || eld->timers[0].trigger_at == MONOTONIC_T_MAX) return 0; static struct { timer_callback_func func; id_type id; void* data; bool repeats; } dispatches[sizeof(eld->timers)/sizeof(eld->timers[0])]; unsigned num_dispatches = 0; monotonic_t now = monotonic(); for (nfds_t i = 0; i < eld->timers_count && eld->timers[i].trigger_at <= now; i++) { eld->timers[i].trigger_at = now + eld->timers[i].interval; dispatches[num_dispatches].func = eld->timers[i].callback; dispatches[num_dispatches].id = eld->timers[i].id; dispatches[num_dispatches].data = eld->timers[i].callback_data; dispatches[num_dispatches].repeats = eld->timers[i].repeats; num_dispatches++; } // we dispatch separately so that the callbacks can modify timers for (unsigned i = 0; i < num_dispatches; i++) { dispatches[i].func(dispatches[i].id, dispatches[i].data); if (!dispatches[i].repeats) { removeTimer(eld, dispatches[i].id); } } if (num_dispatches) update_timers(eld); return num_dispatches; } static void drain_wakeup_fd(int fd, EventLoopData* eld) { static char drain_buf[64]; eld->wakeup_data_read = false; while(true) { ssize_t ret = read(fd, drain_buf, sizeof(drain_buf)); if (ret < 0) { if (errno == EINTR) continue; break; } if (ret > 0) { eld->wakeup_data_read = true; continue; } break; } } static void mark_wakep_fd_ready(int fd UNUSED, int events UNUSED, void *data) { ((EventLoopData*)(data))->wakeup_fd_ready = true; } bool initPollData(EventLoopData *eld, int display_fd) { if (!addWatch(eld, "display", display_fd, POLLIN, 1, NULL, NULL)) return false; #ifdef HAS_EVENT_FD eld->wakeupFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); if (eld->wakeupFd == -1) return false; const int wakeup_fd = eld->wakeupFd; #else if (pipe2(eld->wakeupFds, O_CLOEXEC | O_NONBLOCK) != 0) return false; const int wakeup_fd = eld->wakeupFds[0]; #endif if (!addWatch(eld, "wakeup", wakeup_fd, POLLIN, 1, mark_wakep_fd_ready, eld)) return false; return true; } void check_for_wakeup_events(EventLoopData *eld) { #ifdef HAS_EVENT_FD int fd = eld->wakeupFd; #else int fd = eld->wakeupFds[0]; #endif drain_wakeup_fd(fd, eld); } void wakeupEventLoop(EventLoopData *eld) { #ifdef HAS_EVENT_FD static const uint64_t value = 1; while (write(eld->wakeupFd, &value, sizeof value) < 0 && (errno == EINTR || errno == EAGAIN)); #else while (write(eld->wakeupFds[1], "w", 1) < 0 && (errno == EINTR || errno == EAGAIN)); #endif } #ifndef HAS_EVENT_FD static void closeFds(int *fds, size_t count) { while(count--) { if (*fds > 0) { close(*fds); *fds = -1; } fds++; } } #endif void finalizePollData(EventLoopData *eld) { #ifdef HAS_EVENT_FD close(eld->wakeupFd); eld->wakeupFd = -1; #else closeFds(eld->wakeupFds, arraysz(eld->wakeupFds)); #endif } int pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func display_callback) { int read_ok = 0; timeout = prepareForPoll(eld, timeout); EVDBG("pollForEvents final timeout: %.3f", monotonic_t_to_s_double(timeout)); int result; monotonic_t end_time = monotonic() + timeout; eld->wakeup_fd_ready = false; while(1) { if (timeout >= 0) { errno = 0; result = pollWithTimeout(eld->fds, eld->watches_count, timeout); int saved_errno = errno; if (display_callback) display_callback(result, eld->fds[0].revents && eld->watches[0].events, NULL); dispatchTimers(eld); if (result > 0) { dispatchEvents(eld); read_ok = eld->watches[0].ready; break; } timeout = end_time - monotonic(); if (timeout <= 0) break; if (result < 0 && (saved_errno == EINTR || saved_errno == EAGAIN)) continue; break; } else { errno = 0; result = poll(eld->fds, eld->watches_count, -1); int saved_errno = errno; if (display_callback) display_callback(result, eld->fds[0].revents && eld->watches[0].events, NULL); dispatchTimers(eld); if (result > 0) { dispatchEvents(eld); read_ok = eld->watches[0].ready; } if (result < 0 && (saved_errno == EINTR || saved_errno == EAGAIN)) continue; break; } } return read_ok; } // Duplicate a UTF-8 encoded string // but cut it so that it has at most max_length bytes plus the null byte. // This does not take combining characters into account. GLFWAPI char* utf_8_strndup(const char* source, size_t max_length) { if (!source) return NULL; size_t length = strnlen(source, max_length); if (length >= max_length) { for (length = max_length; length > 0; length--) { if ((source[length] & 0xC0) != 0x80) break; } } char* result = malloc(length + 1); memcpy(result, source, length); result[length] = 0; return result; } /* * Create a new, unique, anonymous file of the given size, and * return the file descriptor for it. The file descriptor is set * CLOEXEC. The file is immediately suitable for mmap()'ing * the given size at offset zero. * * The file should not have a permanent backing store like a disk, * but may have if XDG_RUNTIME_DIR is not properly implemented in OS. * * The file name is deleted from the file system. * * The file is suitable for buffer sharing between processes by * transmitting the file descriptor over Unix sockets using the * SCM_RIGHTS methods. * * posix_fallocate() is used to guarantee that disk space is available * for the file at the given size. If disk space is insufficient, errno * is set to ENOSPC. If posix_fallocate() is not supported, program may * receive SIGBUS on accessing mmap()'ed file contents instead. */ int createAnonymousFile(off_t size) { int ret, fd = -1, shm_anon = 0; #ifdef HAS_MEMFD_CREATE fd = glfw_memfd_create("glfw-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING); if (fd < 0) return -1; // We can add this seal before calling posix_fallocate(), as the file // is currently zero-sized anyway. // // There is also no need to check for the return value, we couldn’t do // anything with it anyway. fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL); #elif defined(SHM_ANON) fd = shm_open(SHM_ANON, O_RDWR | O_CLOEXEC, 0600); if (fd < 0) return -1; shm_anon = 1; #else static const char template[] = "/glfw-shared-XXXXXX"; const char* path; char* name; path = getenv("XDG_RUNTIME_DIR"); if (!path) { errno = ENOENT; return -1; } name = calloc(strlen(path) + sizeof(template), 1); strcpy(name, path); strcat(name, template); fd = createTmpfileCloexec(name); free(name); if (fd < 0) return -1; #endif // posix_fallocate does not work on SHM descriptors ret = shm_anon ? ftruncate(fd, size) : posix_fallocate(fd, 0, size); if (ret != 0) { close(fd); errno = ret; return -1; } return fd; } kitty-0.41.1/glfw/backend_utils.h0000664000175000017510000000673014773370543016234 0ustar nileshnilesh//======================================================================== // GLFW 3.4 //------------------------------------------------------------------------ // Copyright (c) 2014 Kovid Goyal // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #pragma once #include "../kitty/monotonic.h" #include #include #include #include #ifdef __has_include #if __has_include() #define HAS_EVENT_FD #include #endif #else #define HAS_EVENT_FD #include #endif typedef unsigned long long id_type; typedef void(*watch_callback_func)(int, int, void*); typedef void(*timer_callback_func)(id_type, void*); typedef void (* GLFWuserdatafreefun)(id_type, void*); typedef struct { int fd, events, enabled, ready; watch_callback_func callback; void *callback_data; GLFWuserdatafreefun free; id_type id; const char *name; } Watch; typedef struct { id_type id; monotonic_t interval, trigger_at; timer_callback_func callback; void *callback_data; GLFWuserdatafreefun free; const char *name; bool repeats; } Timer; typedef struct { struct pollfd fds[32]; #ifdef HAS_EVENT_FD int wakeupFd; #else int wakeupFds[2]; #endif bool wakeup_data_read, wakeup_fd_ready; nfds_t watches_count, timers_count; Watch watches[32]; Timer timers[128]; } EventLoopData; void check_for_wakeup_events(EventLoopData *eld); id_type addWatch(EventLoopData *eld, const char *name, int fd, int events, int enabled, watch_callback_func cb, void *cb_data); void removeWatch(EventLoopData *eld, id_type watch_id); void toggleWatch(EventLoopData *eld, id_type watch_id, int enabled); id_type addTimer(EventLoopData *eld, const char *name, monotonic_t interval, int enabled, bool repeats, timer_callback_func cb, void *cb_data, GLFWuserdatafreefun free); void removeTimer(EventLoopData *eld, id_type timer_id); void removeAllTimers(EventLoopData *eld); void toggleTimer(EventLoopData *eld, id_type timer_id, int enabled); void changeTimerInterval(EventLoopData *eld, id_type timer_id, monotonic_t interval); monotonic_t prepareForPoll(EventLoopData *eld, monotonic_t timeout); int pollWithTimeout(struct pollfd *fds, nfds_t nfds, monotonic_t timeout); int pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func); unsigned dispatchTimers(EventLoopData *eld); void finalizePollData(EventLoopData *eld); bool initPollData(EventLoopData *eld, int display_fd); void wakeupEventLoop(EventLoopData *eld); char* utf_8_strndup(const char* source, size_t max_length); int createAnonymousFile(off_t size); kitty-0.41.1/glfw/cocoa_displaylink.m0000664000175000017510000001436714773370543017126 0ustar nileshnilesh/* * cocoa_displaylink.m * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ // CVDisplayLink is deprecated replace with CADisplayLink via [NSScreen displayLink] once base macOS version is 14 #pragma clang diagnostic ignored "-Wdeprecated-declarations" #include "internal.h" #include #define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL s_to_monotonic_t(30ll) typedef struct _GLFWDisplayLinkNS { CVDisplayLinkRef displayLink; CGDirectDisplayID displayID; monotonic_t lastRenderFrameRequestedAt, first_unserviced_render_frame_request_at; } _GLFWDisplayLinkNS; static struct { _GLFWDisplayLinkNS entries[256]; size_t count; } displayLinks = {0}; static CGDirectDisplayID displayIDForWindow(_GLFWwindow *w) { NSWindow *nw = w->ns.object; NSDictionary *dict = [nw.screen deviceDescription]; NSNumber *displayIDns = dict[@"NSScreenNumber"]; if (displayIDns) return [displayIDns unsignedIntValue]; return (CGDirectDisplayID)-1; } void _glfwClearDisplayLinks(void) { for (size_t i = 0; i < displayLinks.count; i++) { if (displayLinks.entries[i].displayLink) { CVDisplayLinkStop(displayLinks.entries[i].displayLink); CVDisplayLinkRelease(displayLinks.entries[i].displayLink); } } memset(displayLinks.entries, 0, sizeof(_GLFWDisplayLinkNS) * displayLinks.count); displayLinks.count = 0; } static CVReturn displayLinkCallback( CVDisplayLinkRef displayLink UNUSED, const CVTimeStamp* now UNUSED, const CVTimeStamp* outputTime UNUSED, CVOptionFlags flagsIn UNUSED, CVOptionFlags* flagsOut UNUSED, void* userInfo) { CGDirectDisplayID displayID = (uintptr_t)userInfo; NSNumber *arg = [NSNumber numberWithUnsignedInt:displayID]; [NSApp performSelectorOnMainThread:@selector(render_frame_received:) withObject:arg waitUntilDone:NO]; [arg release]; return kCVReturnSuccess; } static void _glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry) { CVDisplayLinkCreateWithCGDisplay(entry->displayID, &entry->displayLink); CVDisplayLinkSetOutputCallback(entry->displayLink, &displayLinkCallback, (void*)(uintptr_t)entry->displayID); } unsigned _glfwCreateDisplayLink(CGDirectDisplayID displayID) { if (displayLinks.count >= arraysz(displayLinks.entries) - 1) { _glfwInputError(GLFW_PLATFORM_ERROR, "Too many monitors cannot create display link"); return displayLinks.count; } for (unsigned i = 0; i < displayLinks.count; i++) { // already created in this run if (displayLinks.entries[i].displayID == displayID) return i; } _GLFWDisplayLinkNS *entry = &displayLinks.entries[displayLinks.count++]; memset(entry, 0, sizeof(_GLFWDisplayLinkNS)); entry->displayID = displayID; _glfw_create_cv_display_link(entry); return displayLinks.count - 1; } static unsigned long long display_link_shutdown_timer = 0; static void _glfwShutdownCVDisplayLink(unsigned long long timer_id UNUSED, void *user_data UNUSED) { display_link_shutdown_timer = 0; for (size_t i = 0; i < displayLinks.count; i++) { _GLFWDisplayLinkNS *dl = &displayLinks.entries[i]; if (dl->displayLink) CVDisplayLinkStop(dl->displayLink); dl->lastRenderFrameRequestedAt = 0; dl->first_unserviced_render_frame_request_at = 0; } } void _glfwRequestRenderFrame(_GLFWwindow *w) { CGDirectDisplayID displayID = displayIDForWindow(w); if (display_link_shutdown_timer) { _glfwPlatformUpdateTimer(display_link_shutdown_timer, DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, true); } else { display_link_shutdown_timer = _glfwPlatformAddTimer(DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, false, _glfwShutdownCVDisplayLink, NULL, NULL); } monotonic_t now = glfwGetTime(); bool found_display_link = false; _GLFWDisplayLinkNS *dl = NULL; for (size_t i = 0; i < displayLinks.count; i++) { dl = &displayLinks.entries[i]; if (dl->displayID == displayID) { found_display_link = true; dl->lastRenderFrameRequestedAt = now; if (!dl->first_unserviced_render_frame_request_at) dl->first_unserviced_render_frame_request_at = now; if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink); else if (now - dl->first_unserviced_render_frame_request_at > s_to_monotonic_t(1ll)) { // display link is stuck need to recreate it because Apple can't even // get a simple timer right CVDisplayLinkRelease(dl->displayLink); dl->displayLink = nil; dl->first_unserviced_render_frame_request_at = now; _glfw_create_cv_display_link(dl); _glfwInputError(GLFW_PLATFORM_ERROR, "CVDisplayLink stuck possibly because of sleep/screensaver + Apple's incompetence, recreating."); if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink); } } else if (dl->displayLink && dl->lastRenderFrameRequestedAt && now - dl->lastRenderFrameRequestedAt >= DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL) { CVDisplayLinkStop(dl->displayLink); dl->lastRenderFrameRequestedAt = 0; dl->first_unserviced_render_frame_request_at = 0; } } if (!found_display_link) { unsigned idx = _glfwCreateDisplayLink(displayID); if (idx < displayLinks.count) { dl = &displayLinks.entries[idx]; dl->lastRenderFrameRequestedAt = now; dl->first_unserviced_render_frame_request_at = now; if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink); } } } void _glfwDispatchRenderFrame(CGDirectDisplayID displayID) { _GLFWwindow *w = _glfw.windowListHead; while (w) { if (w->ns.renderFrameRequested && displayID == displayIDForWindow(w)) { w->ns.renderFrameRequested = false; w->ns.renderFrameCallback((GLFWwindow*)w); } w = w->next; } for (size_t i = 0; i < displayLinks.count; i++) { _GLFWDisplayLinkNS *dl = &displayLinks.entries[i]; if (dl->displayID == displayID) { dl->first_unserviced_render_frame_request_at = 0; } } } kitty-0.41.1/glfw/cocoa_init.m0000664000175000017510000012742114773370543015542 0ustar nileshnilesh//======================================================================== // GLFW 3.4 macOS - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2009-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #include "internal.h" #include "../kitty/monotonic.h" #include // For MAXPATHLEN #include // Needed for _NSGetProgname #include // Change to our application bundle's resources directory, if present // static void changeToResourcesDirectory(void) { char resourcesPath[MAXPATHLEN]; CFBundleRef bundle = CFBundleGetMainBundle(); if (!bundle) return; CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(bundle); CFStringRef last = CFURLCopyLastPathComponent(resourcesURL); if (CFStringCompare(CFSTR("Resources"), last, 0) != kCFCompareEqualTo) { CFRelease(last); CFRelease(resourcesURL); return; } CFRelease(last); if (!CFURLGetFileSystemRepresentation(resourcesURL, true, (UInt8*) resourcesPath, MAXPATHLEN)) { CFRelease(resourcesURL); return; } CFRelease(resourcesURL); chdir(resourcesPath); } // Set up the menu bar (manually) // This is nasty, nasty stuff -- calls to undocumented semi-private APIs that // could go away at any moment, lots of stuff that really should be // localize(d|able), etc. Add a nib to save us this horror. // static void createMenuBar(void) { size_t i; NSString* appName = nil; NSDictionary* bundleInfo = [[NSBundle mainBundle] infoDictionary]; NSString* nameKeys[] = { @"CFBundleDisplayName", @"CFBundleName", @"CFBundleExecutable", }; // Try to figure out what the calling application is called for (i = 0; i < sizeof(nameKeys) / sizeof(nameKeys[0]); i++) { id name = bundleInfo[nameKeys[i]]; if (name && [name isKindOfClass:[NSString class]] && ![name isEqualToString:@""]) { appName = name; break; } } if (!appName) { char** progname = _NSGetProgname(); if (progname && *progname) appName = @(*progname); else appName = @"GLFW Application"; } NSMenu* bar = [[NSMenu alloc] init]; [NSApp setMainMenu:bar]; NSMenuItem* appMenuItem = [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""]; NSMenu* appMenu = [[NSMenu alloc] init]; [appMenuItem setSubmenu:appMenu]; [appMenu addItemWithTitle:[NSString stringWithFormat:@"About %@", appName] action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; [appMenu addItem:[NSMenuItem separatorItem]]; NSMenu* servicesMenu = [[NSMenu alloc] init]; [NSApp setServicesMenu:servicesMenu]; [[appMenu addItemWithTitle:@"Services" action:NULL keyEquivalent:@""] setSubmenu:servicesMenu]; [servicesMenu release]; [appMenu addItem:[NSMenuItem separatorItem]]; [appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", appName] action:@selector(hide:) keyEquivalent:@"h"]; [[appMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"] setKeyEquivalentModifierMask:NSEventModifierFlagOption | NSEventModifierFlagCommand]; [appMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; [appMenu addItem:[NSMenuItem separatorItem]]; [appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", appName] action:@selector(terminate:) keyEquivalent:@"q"]; NSMenuItem* windowMenuItem = [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""]; [bar release]; NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; [NSApp setWindowsMenu:windowMenu]; [windowMenuItem setSubmenu:windowMenu]; [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""]; [windowMenu addItem:[NSMenuItem separatorItem]]; [windowMenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""]; // TODO: Make this appear at the bottom of the menu (for consistency) [windowMenu addItem:[NSMenuItem separatorItem]]; [[windowMenu addItemWithTitle:@"Enter Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"] setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand]; // Prior to Snow Leopard, we need to use this oddly-named semi-private API // to get the application menu working properly. SEL setAppleMenuSelector = NSSelectorFromString(@"setAppleMenu:"); [NSApp performSelector:setAppleMenuSelector withObject:appMenu]; } // Retrieve Unicode data for the current keyboard layout // static bool updateUnicodeDataNS(void) { if (_glfw.ns.inputSource) { CFRelease(_glfw.ns.inputSource); _glfw.ns.inputSource = NULL; _glfw.ns.unicodeData = nil; } for (_GLFWwindow *window = _glfw.windowListHead; window; window = window->next) window->ns.deadKeyState = 0; _glfw.ns.inputSource = TISCopyCurrentKeyboardLayoutInputSource(); if (!_glfw.ns.inputSource) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to retrieve keyboard layout input source"); return false; } _glfw.ns.unicodeData = TISGetInputSourceProperty(_glfw.ns.inputSource, kTISPropertyUnicodeKeyLayoutData); if (!_glfw.ns.unicodeData) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to retrieve keyboard layout Unicode data"); return false; } return true; } // Load HIToolbox.framework and the TIS symbols we need from it // static bool initializeTIS(void) { // This works only because Cocoa has already loaded it properly _glfw.ns.tis.bundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.HIToolbox")); if (!_glfw.ns.tis.bundle) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to load HIToolbox.framework"); return false; } CFStringRef* kPropertyUnicodeKeyLayoutData = CFBundleGetDataPointerForName(_glfw.ns.tis.bundle, CFSTR("kTISPropertyUnicodeKeyLayoutData")); *(void **)&_glfw.ns.tis.CopyCurrentKeyboardLayoutInputSource = CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle, CFSTR("TISCopyCurrentKeyboardLayoutInputSource")); *(void **)&_glfw.ns.tis.GetInputSourceProperty = CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle, CFSTR("TISGetInputSourceProperty")); *(void **)&_glfw.ns.tis.GetKbdType = CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle, CFSTR("LMGetKbdType")); if (!kPropertyUnicodeKeyLayoutData || !TISCopyCurrentKeyboardLayoutInputSource || !TISGetInputSourceProperty || !LMGetKbdType) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to load TIS API symbols"); return false; } _glfw.ns.tis.kPropertyUnicodeKeyLayoutData = *kPropertyUnicodeKeyLayoutData; return updateUnicodeDataNS(); } static void display_reconfigured(CGDirectDisplayID display UNUSED, CGDisplayChangeSummaryFlags flags, void *userInfo UNUSED) { if (flags & kCGDisplayBeginConfigurationFlag) { return; } if (flags & kCGDisplaySetModeFlag) { // GPU possibly changed } } static NSDictionary *global_shortcuts = nil; @interface GLFWHelper : NSObject @end @implementation GLFWHelper - (void)selectedKeyboardInputSourceChanged:(NSObject* )object { (void)object; updateUnicodeDataNS(); } - (void)doNothing:(id)object { (void)object; } // watch for settings change and rebuild global_shortcuts using key/value observing on NSUserDefaults - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { (void)keyPath; (void)object; (void)change; (void)context; if (global_shortcuts != nil) { [global_shortcuts release]; global_shortcuts = nil; } } @end // GLFWHelper // Delegate for application related notifications {{{ @interface GLFWApplicationDelegate : NSObject @end @implementation GLFWApplicationDelegate - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { (void)sender; if (_glfw.callbacks.application_close) _glfw.callbacks.application_close(0); return NSTerminateCancel; } - (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app { return YES; } static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL; - (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag { (void)sender; if (!handle_reopen_callback) return YES; if (handle_reopen_callback(flag)) return YES; return NO; } - (void)applicationDidChangeScreenParameters:(NSNotification *) notification { (void)notification; _GLFWwindow* window; for (window = _glfw.windowListHead; window; window = window->next) { if (window->context.client != GLFW_NO_API) [window->context.nsgl.object update]; } _glfwPollMonitorsNS(); } static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL; - (void)applicationWillFinishLaunching:(NSNotification *)notification { (void)notification; if (_glfw.hints.init.ns.menubar) { // In case we are unbundled, make us a proper UI application [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; // Menu bar setup must go between sharedApplication and finishLaunching // in order to properly emulate the behavior of NSApplicationMain if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"]) { [[NSBundle mainBundle] loadNibNamed:@"MainMenu" owner:NSApp topLevelObjects:&_glfw.ns.nibObjects]; } else createMenuBar(); } if (finish_launching_callback) finish_launching_callback(); } - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { (void)sender; if (!filename || !_glfw.ns.url_open_callback) return NO; const char *url = NULL; @try { url = [[[NSURL fileURLWithPath:filename] absoluteString] UTF8String]; } @catch(NSException *exc) { NSLog(@"Converting openFile filename: %@ failed with error: %@", filename, exc.reason); return NO; } if (!url) return NO; return _glfw.ns.url_open_callback(url); } - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames { (void)sender; if (!_glfw.ns.url_open_callback || !filenames) return; for (id x in filenames) { NSString *filename = x; const char *url = NULL; @try { url = [[[NSURL fileURLWithPath:filename] absoluteString] UTF8String]; } @catch(NSException *exc) { NSLog(@"Converting openFiles filename: %@ failed with error: %@", filename, exc.reason); } if (url) _glfw.ns.url_open_callback(url); } } // Remove openFile and openFiles when the minimum supported macOS version is 10.13 - (void)application:(NSApplication *)sender openURLs:(NSArray *)urls { (void)sender; if (!_glfw.ns.url_open_callback || !urls) return; for (id x in urls) { NSURL *ns_url = x; const char *url = NULL; @try { url = [[ns_url absoluteString] UTF8String]; } @catch(NSException *exc) { NSLog(@"Converting openURLs url: %@ failed with error: %@", ns_url, exc.reason); } if (url) _glfw.ns.url_open_callback(url); } } - (void)applicationDidFinishLaunching:(NSNotification *)notification { (void)notification; [NSApp stop:nil]; CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL); _glfwCocoaPostEmptyEvent(); } - (void)applicationWillTerminate:(NSNotification *)aNotification { (void)aNotification; CGDisplayRemoveReconfigurationCallback(display_reconfigured, NULL); } - (void)applicationDidHide:(NSNotification *)notification { (void)notification; int i; for (i = 0; i < _glfw.monitorCount; i++) _glfwRestoreVideoModeNS(_glfw.monitors[i]); } @end // GLFWApplicationDelegate // }}} @interface GLFWApplication : NSApplication - (void)tick_callback; - (void)render_frame_received:(id)displayIDAsID; @end @implementation GLFWApplication - (void)tick_callback { _glfwDispatchTickCallback(); } - (void)render_frame_received:(id)displayIDAsID { CGDirectDisplayID displayID = [(NSNumber*)displayIDAsID unsignedIntValue]; _glfwDispatchRenderFrame(displayID); } @end ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// void* _glfwLoadLocalVulkanLoaderNS(void) { CFBundleRef bundle = CFBundleGetMainBundle(); if (!bundle) return NULL; CFURLRef url = CFBundleCopyAuxiliaryExecutableURL(bundle, CFSTR("libvulkan.1.dylib")); if (!url) return NULL; char path[PATH_MAX]; void* handle = NULL; if (CFURLGetFileSystemRepresentation(url, true, (UInt8*) path, sizeof(path) - 1)) handle = _glfw_dlopen(path); CFRelease(url); return handle; } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// /** * Apple Symbolic HotKeys Ids * To find this symbolic hot keys indices do: * 1. open Terminal * 2. restore defaults in System Preferences > Keyboard > Shortcuts * 3. defaults read com.apple.symbolichotkeys > current.txt * 4. enable/disable given symbolic hot key in System Preferences > Keyboard > Shortcuts * 5. defaults read com.apple.symbolichotkeys | diff -C 5 current.txt - * 6. restore defaults in System Preferences > Keyboard > Shortcuts */ typedef enum AppleShortcutNames { // launchpad & dock kSHKTurnDockHidingOnOrOff = 52, // Opt, Cmd, D kSHKShowLaunchpad = 160, // // display kSHKDecreaseDisplayBrightness1 = 53, // F14 (Fn) kSHKDecreaseDisplayBrightness2 = 55, // F14 (Fn, Ctrl) kSHKIncreaseDisplayBrightness1 = 54, // F15 (Fn) kSHKIncreaseDisplayBrightness2 = 56, // F15 (Fn, Ctrl) // mission control kSHKMissionControl = 32, // Ctrl, Arrow Up kSHKShowNotificationCenter = 163, // kSHKTurnDoNotDisturbOnOrOff = 175, // kSHKApplicationWindows = 33, // Ctrl, Arrow Down kSHKShowDesktop = 36, // F11 kSHKMoveLeftASpace = 79, // Ctrl, Arrow Left kSHKMoveRightASpace = 81, // Ctrl, Arrow Right kSHKSwitchToDesktop1 = 118, // Ctrl, 1 kSHKSwitchToDesktop2 = 119, // Ctrl, 2 kSHKSwitchToDesktop3 = 120, // Ctrl, 3 kSHKSwitchToDesktop4 = 121, // Ctrl, 4 kSHKQuickNote = 190, // Fn, Q // keyboard kSHKChangeTheWayTabMovesFocus = 13, // Ctrl, F7 kSHKTurnKeyboardAccessOnOrOff = 12, // Ctrl, F1 kSHKMoveFocusToTheMenuBar = 7, // Ctrl, F2 kSHKMoveFocusToTheDock = 8, // Ctrl, F3 kSHKMoveFocusToActiveOrNextWindow = 9, // Ctrl, F4 kSHKMoveFocusToTheWindowToolbar = 10, // Ctrl, F5 kSHKMoveFocusToTheFloatingWindow = 11, // Ctrl, F6 kSHKMoveFocusToNextWindow = 27, // Cmd, ` kSHKMoveFocusToStatusMenus = 57, // Ctrl, F8 // input sources kSHKSelectThePreviousInputSource = 60, // Ctrl, Space bar kSHKSelectNextSourceInInputMenu = 61, // Ctrl, Opt, Space bar // screenshots kSHKSavePictureOfScreenAsAFile = 28, // Shift, Cmd, 3 kSHKCopyPictureOfScreenToTheClipboard = 29, // Ctrl, Shift, Cmd, 3 kSHKSavePictureOfSelectedAreaAsAFile = 30, // Shift, Cmd, 4 kSHKCopyPictureOfSelectedAreaToTheClipboard = 31, // Ctrl, Shift, Cmd, 4 kSHKScreenshotAndRecordingOptions = 184, // Shift, Cmd, 5 // spotlight kSHKShowSpotlightSearch = 64, // Cmd, Space bar kSHKShowFinderSearchWindow = 65, // Opt, Cmd, Space bar // accessibility kSHKTurnZoomOnOrOff = 15, // Opt, Cmd, 8 kSHKTurnImageSmoothingOnOrOff = 23, // Opt, Cmd, Backslash "\" kSHKZoomOut = 19, // Opt, Cmd, - kSHKZoomIn = 17, // Opt, Cmd, = kSHKTurnFocusFollowingOnOrOff = 179, // kSHKIncreaseContrast = 25, // Ctrl, Opt, Cmd, . kSHKDecreaseContrast = 26, // Ctrl, Opt, Cmd, , kSHKInvertColors = 21, // Ctrl, Opt, Cmd, 8 kSHKTurnVoiceOverOnOrOff = 59, // Cmd, F5 kSHKShowAccessibilityControls = 162, // Opt, Cmd, F5 // app shortcuts kSHKShowHelpMenu = 98, // Shift, Cmd, / // deprecated (Not shown on macOS Monterey) kSHKMoveFocusToTheWindowDrawer = 51, // Opt, Cmd, ` kSHKShowDashboard = 62, // F12 kSHKLookUpInDictionary = 70, // Shift, Cmd, E kSHKHideAndShowFrontRow = 73, // Cmd, Esc kSHKActivateSpaces = 75, // F8 // unknown kSHKUnknown = 0, // } AppleShortcutNames; static bool is_shiftable_shortcut(int scv) { return scv == kSHKMoveFocusToActiveOrNextWindow || scv == kSHKMoveFocusToNextWindow; } #define USEFUL_MODS(x) (x & (NSEventModifierFlagShift | NSEventModifierFlagOption | NSEventModifierFlagCommand | NSEventModifierFlagControl | NSEventModifierFlagFunction)) static void build_global_shortcuts_lookup(void) { // dump these in a terminal with: defaults read com.apple.symbolichotkeys NSMutableDictionary *temp = [NSMutableDictionary dictionaryWithCapacity:128]; // will be autoreleased NSMutableSet *temp_configured = [NSMutableSet setWithCapacity:128]; // will be autoreleased NSMutableSet *temp_missing_value = [NSMutableSet setWithCapacity:128]; // will be autoreleased NSDictionary *apple_settings = [[NSUserDefaults standardUserDefaults] persistentDomainForName:@"com.apple.symbolichotkeys"]; if (apple_settings) { NSDictionary *symbolic_hotkeys = [apple_settings objectForKey:@"AppleSymbolicHotKeys"]; if (symbolic_hotkeys) { for (NSString *key in symbolic_hotkeys) { id obj = symbolic_hotkeys[key]; if (![key isKindOfClass:[NSString class]] || ![obj isKindOfClass:[NSDictionary class]]) continue; NSInteger sc = [key integerValue]; NSDictionary *sc_value = obj; id enabled = [sc_value objectForKey:@"enabled"]; if (!enabled || ![enabled isKindOfClass:[NSNumber class]]) continue; [temp_configured addObject:@(sc)]; if (![enabled boolValue]) continue; id v = [sc_value objectForKey:@"value"]; if (!v || ![v isKindOfClass:[NSDictionary class]]) { if ([enabled boolValue]) [temp_missing_value addObject:@(sc)]; continue; } NSDictionary *value = v; id t = [value objectForKey:@"type"]; if (!t || ![t isKindOfClass:[NSString class]] || ![t isEqualToString:@"standard"]) continue; id p = [value objectForKey:@"parameters"]; if (!p || ![p isKindOfClass:[NSArray class]] || [(NSArray*)p count] < 2) continue; NSArray *parameters = p; NSInteger ch = [parameters[0] isKindOfClass:[NSNumber class]] ? [parameters[0] integerValue] : 0xffff; NSInteger vk = [parameters[1] isKindOfClass:[NSNumber class]] ? [parameters[1] integerValue] : 0xffff; NSEventModifierFlags mods = ([parameters count] > 2 && [parameters[2] isKindOfClass:[NSNumber class]]) ? [parameters[2] unsignedIntegerValue] : 0; mods = USEFUL_MODS(mods); static char buf[64]; #define S(x, k) snprintf(buf, sizeof(buf) - 1, #x":%lx:%ld", (unsigned long)mods, (long)k) if (ch == 0xffff) { if (vk == 0xffff) continue; S(v, vk); } else S(c, ch); temp[@(buf)] = @(sc); // the move to next window shortcuts also respond to the same shortcut + shift if (is_shiftable_shortcut([key intValue]) && !(mods & NSEventModifierFlagShift)) { mods |= NSEventModifierFlagShift; if (ch == 0xffff) S(v, vk); else S(c, ch); temp[@(buf)] = @(sc); } #undef S } } } // Add global shortcut definitions when the default enabled shortcut is not defined, // or when the default enabled shortcut is not disabled and is missing a value. // Here are the shortcuts that are enabled by default in the standard ANSI (US) layout. // macOS provides separate configurations for some languages or keyboards. // In general, the rules here will not take effect. static char buf[64]; #define S(i, t, m, k) if ([temp_configured member:@(i)] == nil || [temp_missing_value member:@(i)] != nil) { \ snprintf(buf, sizeof(buf) - 1, #t":%lx:%ld", (unsigned long)m, (long)k); \ temp[@(buf)] = @(i); \ } // launchpad & dock S(kSHKTurnDockHidingOnOrOff, c, (NSEventModifierFlagOption | NSEventModifierFlagCommand), 'd'); // Opt, Cmd, D // mission control S(kSHKMissionControl, v, NSEventModifierFlagControl, 126); // Ctrl, Arrow Up S(kSHKApplicationWindows, v, NSEventModifierFlagControl, 125); // Ctrl, Arrow Down // keyboard S(kSHKMoveFocusToTheMenuBar, v, NSEventModifierFlagControl, 120); // Ctrl, F2 S(kSHKMoveFocusToTheDock, v, NSEventModifierFlagControl, 99); // Ctrl, F3 S(kSHKMoveFocusToActiveOrNextWindow, v, NSEventModifierFlagControl, 118); // Ctrl, F4 S(kSHKMoveFocusToActiveOrNextWindow, v, (NSEventModifierFlagShift | NSEventModifierFlagControl), 118); // Shift, Ctrl, F4 S(kSHKMoveFocusToNextWindow, c, NSEventModifierFlagCommand, 96); // Cmd, ` S(kSHKMoveFocusToNextWindow, c, (NSEventModifierFlagShift | NSEventModifierFlagCommand), 96); // Shift, Cmd, ` S(kSHKMoveFocusToStatusMenus, v, NSEventModifierFlagControl, 100); // Ctrl, F8 // input sources S(kSHKSelectThePreviousInputSource, c, NSEventModifierFlagControl, 32); // Ctrl, Space bar S(kSHKSelectNextSourceInInputMenu, c, (NSEventModifierFlagControl | NSEventModifierFlagOption), 32); // Ctrl, Opt, Space bar // spotlight S(kSHKShowSpotlightSearch, c, NSEventModifierFlagCommand, 32); // Cmd, Space bar S(kSHKShowFinderSearchWindow, c, (NSEventModifierFlagOption | NSEventModifierFlagCommand), 32); // Opt, Cmd, Space bar #undef S global_shortcuts = [[NSDictionary dictionaryWithDictionary:temp] retain]; /* NSLog(@"global_shortcuts: %@", global_shortcuts); */ } static int is_active_apple_global_shortcut(NSEvent *event) { if (global_shortcuts == nil) build_global_shortcuts_lookup(); NSEventModifierFlags modifierFlags = USEFUL_MODS([event modifierFlags]); static char lookup_key[64]; #define LOOKUP(t, k) \ snprintf(lookup_key, sizeof(lookup_key) - 1, #t":%lx:%ld", (unsigned long)modifierFlags, (long)k); \ NSNumber *sc = global_shortcuts[@(lookup_key)]; \ if (sc != nil) return [sc intValue]; \ if ([event.charactersIgnoringModifiers length] == 1) { if (modifierFlags & NSEventModifierFlagShift) { const uint32_t ch_without_shift = vk_to_unicode_key_with_current_layout([event keyCode]); if (ch_without_shift < GLFW_FKEY_FIRST || ch_without_shift > GLFW_FKEY_LAST) { LOOKUP(c, ch_without_shift); } } const unichar ch = [event.charactersIgnoringModifiers characterAtIndex:0]; LOOKUP(c, ch); } unsigned short vk = [event keyCode]; if (vk != 0xffff) { LOOKUP(v, vk); } #undef LOOKUP return kSHKUnknown; } static bool is_useful_apple_global_shortcut(int sc) { switch(sc) { // launchpad & dock case kSHKTurnDockHidingOnOrOff: // Opt, Cmd, D case kSHKShowLaunchpad: // // display case kSHKDecreaseDisplayBrightness1: // F14 (Fn) case kSHKDecreaseDisplayBrightness2: // F14 (Fn, Ctrl) case kSHKIncreaseDisplayBrightness1: // F15 (Fn) case kSHKIncreaseDisplayBrightness2: // F14 (Fn, Ctrl) // mission control case kSHKMissionControl: // Ctrl, Arrow Up case kSHKShowNotificationCenter: // case kSHKTurnDoNotDisturbOnOrOff: // case kSHKApplicationWindows: // Ctrl, Arrow Down case kSHKShowDesktop: // F11 case kSHKMoveLeftASpace: // Ctrl, Arrow Left case kSHKMoveRightASpace: // Ctrl, Arrow Right case kSHKSwitchToDesktop1: // Ctrl, 1 case kSHKSwitchToDesktop2: // Ctrl, 2 case kSHKSwitchToDesktop3: // Ctrl, 3 case kSHKSwitchToDesktop4: // Ctrl, 4 case kSHKQuickNote: // Fn, Q // keyboard /* case kSHKChangeTheWayTabMovesFocus: // Ctrl, F7 */ /* case kSHKTurnKeyboardAccessOnOrOff: // Ctrl, F1 */ case kSHKMoveFocusToTheMenuBar: // Ctrl, F2 case kSHKMoveFocusToTheDock: // Ctrl, F3 case kSHKMoveFocusToActiveOrNextWindow: // Ctrl, F4 /* case kSHKMoveFocusToTheWindowToolbar: // Ctrl, F5 */ /* case kSHKMoveFocusToTheFloatingWindow: // Ctrl, F6 */ case kSHKMoveFocusToNextWindow: // Cmd, ` case kSHKMoveFocusToStatusMenus: // Ctrl, F8 // input sources case kSHKSelectThePreviousInputSource: // Ctrl, Space bar case kSHKSelectNextSourceInInputMenu: // Ctrl, Opt, Space bar // screenshots /* case kSHKSavePictureOfScreenAsAFile: // Shift, Cmd, 3 */ /* case kSHKCopyPictureOfScreenToTheClipboard: // Ctrl, Shift, Cmd, 3 */ /* case kSHKSavePictureOfSelectedAreaAsAFile: // Shift, Cmd, 4 */ /* case kSHKCopyPictureOfSelectedAreaToTheClipboard: // Ctrl, Shift, Cmd, 4 */ /* case kSHKScreenshotAndRecordingOptions: // Shift, Cmd, 5 */ // spotlight case kSHKShowSpotlightSearch: // Cmd, Space bar case kSHKShowFinderSearchWindow: // Opt, Cmd, Space bar // accessibility /* case kSHKTurnZoomOnOrOff: // Opt, Cmd, 8 */ /* case kSHKTurnImageSmoothingOnOrOff: // Opt, Cmd, Backslash "\" */ /* case kSHKZoomOut: // Opt, Cmd, - */ /* case kSHKZoomIn: // Opt, Cmd, = */ /* case kSHKTurnFocusFollowingOnOrOff: // */ /* case kSHKIncreaseContrast: // Ctrl, Opt, Cmd, . */ /* case kSHKDecreaseContrast: // Ctrl, Opt, Cmd, , */ /* case kSHKInvertColors: // Ctrl, Opt, Cmd, 8 */ /* case kSHKTurnVoiceOverOnOrOff: // Cmd, F5 */ /* case kSHKShowAccessibilityControls: // Opt, Cmd, F5 */ // app shortcuts /* case kSHKShowHelpMenu: // Shift, Cmd, / */ // deprecated (Not shown on macOS Monterey) /* case kSHKMoveFocusToTheWindowDrawer: // Opt, Cmd, ` */ /* case kSHKShowDashboard: // F12 */ /* case kSHKLookUpInDictionary: // Shift, Cmd, E */ /* case kSHKHideAndShowFrontRow: // Cmd, Esc */ /* case kSHKActivateSpaces: // F8 */ return true; default: return false; } } static bool is_apple_jis_layout_function_key(NSEvent *event) { return [event keyCode] == 0x66 /* kVK_JIS_Eisu */ || [event keyCode] == 0x68 /* kVK_JIS_Kana */; } GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) { GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback; handle_reopen_callback = callback; return previous; } GLFWAPI GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback) { GLFWapplicationwillfinishlaunchingfun previous = finish_launching_callback; finish_launching_callback = callback; return previous; } int _glfwPlatformInit(bool *supports_window_occlusion) { @autoreleasepool { *supports_window_occlusion = true; _glfw.ns.helper = [[GLFWHelper alloc] init]; [NSThread detachNewThreadSelector:@selector(doNothing:) toTarget:_glfw.ns.helper withObject:nil]; if (NSApp) _glfw.ns.finishedLaunching = true; [GLFWApplication sharedApplication]; _glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init]; if (_glfw.ns.delegate == nil) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create application delegate"); return false; } [NSApp setDelegate:_glfw.ns.delegate]; static struct { unsigned short virtual_key_code; NSEventModifierFlags input_source_switch_modifiers; NSTimeInterval timestamp; } last_keydown_shortcut_event; last_keydown_shortcut_event.virtual_key_code = 0xffff; last_keydown_shortcut_event.input_source_switch_modifiers = 0; NSEvent* (^keydown_block)(NSEvent*) = ^ NSEvent* (NSEvent* event) { debug_key("---------------- key down -------------------\n"); debug_key("%s\n", [[event description] UTF8String]); if (!_glfw.ignoreOSKeyboardProcessing) { // first check if there is a global menu bar shortcut if ([[NSApp mainMenu] performKeyEquivalent:event]) { debug_key("keyDown triggered global menu bar action ignoring\n"); last_keydown_shortcut_event.virtual_key_code = [event keyCode]; last_keydown_shortcut_event.input_source_switch_modifiers = 0; last_keydown_shortcut_event.timestamp = [event timestamp]; return nil; } // now check if there is a useful apple shortcut int global_shortcut = is_active_apple_global_shortcut(event); if (is_useful_apple_global_shortcut(global_shortcut)) { debug_key("keyDown triggered global macOS shortcut ignoring\n"); last_keydown_shortcut_event.virtual_key_code = [event keyCode]; // record the modifier keys if switching to the next input source last_keydown_shortcut_event.input_source_switch_modifiers = (global_shortcut == kSHKSelectNextSourceInInputMenu) ? USEFUL_MODS([event modifierFlags]) : 0; last_keydown_shortcut_event.timestamp = [event timestamp]; return event; } // check for JIS keyboard layout function keys if (is_apple_jis_layout_function_key(event)) { debug_key("keyDown triggered JIS layout function key ignoring\n"); last_keydown_shortcut_event.virtual_key_code = [event keyCode]; last_keydown_shortcut_event.input_source_switch_modifiers = 0; last_keydown_shortcut_event.timestamp = [event timestamp]; return event; } } last_keydown_shortcut_event.virtual_key_code = 0xffff; NSWindow *kw = [NSApp keyWindow]; if (kw && kw.contentView) [kw.contentView keyDown:event]; else debug_key("keyDown ignored as no keyWindow present\n"); return nil; }; NSEvent* (^keyup_block)(NSEvent*) = ^ NSEvent* (NSEvent* event) { debug_key("----------------- key up --------------------\n"); debug_key("%s\n", [[event description] UTF8String]); if (last_keydown_shortcut_event.virtual_key_code != 0xffff && last_keydown_shortcut_event.virtual_key_code == [event keyCode]) { // ignore as the corresponding key down event triggered a menu bar or macOS shortcut last_keydown_shortcut_event.virtual_key_code = 0xffff; debug_key("keyUp ignored as corresponds to previous keyDown that triggered a shortcut\n"); return nil; } NSWindow *kw = [NSApp keyWindow]; if (kw && kw.contentView) [kw.contentView keyUp:event]; else debug_key("keyUp ignored as no keyWindow present\n"); return nil; }; NSEvent* (^flags_changed_block)(NSEvent*) = ^ NSEvent* (NSEvent* event) { debug_key("-------------- flags changed -----------------\n"); debug_key("%s\n", [[event description] UTF8String]); last_keydown_shortcut_event.virtual_key_code = 0xffff; // switching to the next input source is only confirmed when all modifier keys are released if (last_keydown_shortcut_event.input_source_switch_modifiers) { if (!([event modifierFlags] & last_keydown_shortcut_event.input_source_switch_modifiers)) last_keydown_shortcut_event.input_source_switch_modifiers = 0; return event; } NSWindow *kw = [NSApp keyWindow]; if (kw && kw.contentView) [kw.contentView flagsChanged:event]; else debug_key("flagsChanged ignored as no keyWindow present\n"); return nil; }; _glfw.ns.keyUpMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp handler:keyup_block]; _glfw.ns.keyDownMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown handler:keydown_block]; _glfw.ns.flagsChangedMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskFlagsChanged handler:flags_changed_block]; if (_glfw.hints.init.ns.chdir) changeToResourcesDirectory(); NSDictionary* defaults = @{ // Press and Hold prevents some keys from emitting repeated characters @"ApplePressAndHoldEnabled": @NO, // Dont generate openFile events from command line arguments @"NSTreatUnknownArgumentsAsOpen": @"NO", }; [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; NSUserDefaults *apple_settings = [[NSUserDefaults alloc] initWithSuiteName:@"com.apple.symbolichotkeys"]; [apple_settings addObserver:_glfw.ns.helper forKeyPath:@"AppleSymbolicHotKeys" options:NSKeyValueObservingOptionNew context:NULL]; _glfw.ns.appleSettings = apple_settings; [[NSNotificationCenter defaultCenter] addObserver:_glfw.ns.helper selector:@selector(selectedKeyboardInputSourceChanged:) name:NSTextInputContextKeyboardSelectionDidChangeNotification object:nil]; _glfw.ns.eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); if (!_glfw.ns.eventSource) return false; CGEventSourceSetLocalEventsSuppressionInterval(_glfw.ns.eventSource, 0.0); if (!initializeTIS()) return false; _glfwPollMonitorsNS(); return true; } // autoreleasepool } void _glfwPlatformTerminate(void) { @autoreleasepool { _glfwClearDisplayLinks(); if (_glfw.ns.inputSource) { CFRelease(_glfw.ns.inputSource); _glfw.ns.inputSource = NULL; _glfw.ns.unicodeData = nil; } if (_glfw.ns.eventSource) { CFRelease(_glfw.ns.eventSource); _glfw.ns.eventSource = NULL; } if (_glfw.ns.delegate) { [NSApp setDelegate:nil]; [_glfw.ns.delegate release]; _glfw.ns.delegate = nil; } if (_glfw.ns.helper) { [[NSNotificationCenter defaultCenter] removeObserver:_glfw.ns.helper name:NSTextInputContextKeyboardSelectionDidChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:_glfw.ns.helper]; if (_glfw.ns.appleSettings) [_glfw.ns.appleSettings removeObserver:_glfw.ns.helper forKeyPath:@"AppleSymbolicHotKeys"]; [_glfw.ns.helper release]; _glfw.ns.helper = nil; } if (_glfw.ns.keyUpMonitor) [NSEvent removeMonitor:_glfw.ns.keyUpMonitor]; if (_glfw.ns.keyDownMonitor) [NSEvent removeMonitor:_glfw.ns.keyDownMonitor]; if (_glfw.ns.flagsChangedMonitor) [NSEvent removeMonitor:_glfw.ns.flagsChangedMonitor]; if (_glfw.ns.appleSettings != nil) { [_glfw.ns.appleSettings release]; _glfw.ns.appleSettings = nil; } _glfwTerminateNSGL(); if (global_shortcuts != nil) { [global_shortcuts release]; global_shortcuts = nil; } } // autoreleasepool } const char* _glfwPlatformGetVersionString(void) { return _GLFW_VERSION_NUMBER " Cocoa NSGL EGL OSMesa" #if defined(_GLFW_BUILD_DLL) " dynamic" #endif ; } static GLFWtickcallback tick_callback = NULL; static void* tick_callback_data = NULL; static bool tick_callback_requested = false; static pthread_t main_thread; static NSLock *tick_lock = NULL; void _glfwDispatchTickCallback(void) { if (tick_lock && tick_callback) { while(true) { bool do_call = false; [tick_lock lock]; if (tick_callback_requested) { do_call = true; tick_callback_requested = false; } [tick_lock unlock]; if (do_call) tick_callback(tick_callback_data); else break; } } } static void request_tick_callback(void) { if (!tick_callback_requested) { tick_callback_requested = true; [NSApp performSelectorOnMainThread:@selector(tick_callback) withObject:nil waitUntilDone:NO]; } } void _glfwPlatformPostEmptyEvent(void) { if (pthread_equal(pthread_self(), main_thread)) { request_tick_callback(); } else if (tick_lock) { [tick_lock lock]; request_tick_callback(); [tick_lock unlock]; } } void _glfwPlatformStopMainLoop(void) { [NSApp stop:nil]; _glfwCocoaPostEmptyEvent(); } void _glfwPlatformRunMainLoop(GLFWtickcallback callback, void* data) { main_thread = pthread_self(); tick_callback = callback; tick_callback_data = data; tick_lock = [NSLock new]; [NSApp run]; [tick_lock release]; tick_lock = NULL; tick_callback = NULL; tick_callback_data = NULL; } typedef struct { NSTimer *os_timer; unsigned long long id; bool repeats; monotonic_t interval; GLFWuserdatafun callback; void *callback_data; GLFWuserdatafun free_callback_data; } Timer; static Timer timers[128] = {{0}}; static size_t num_timers = 0; static void remove_timer_at(size_t idx) { if (idx < num_timers) { Timer *t = timers + idx; if (t->os_timer) { [t->os_timer invalidate]; t->os_timer = NULL; } if (t->callback_data && t->free_callback_data) { t->free_callback_data(t->id, t->callback_data); t->callback_data = NULL; } remove_i_from_array(timers, idx, num_timers); } } static void schedule_timer(Timer *t) { t->os_timer = [NSTimer scheduledTimerWithTimeInterval:monotonic_t_to_s_double(t->interval) repeats:(t->repeats ? YES: NO) block:^(NSTimer *os_timer) { for (size_t i = 0; i < num_timers; i++) { if (timers[i].os_timer == os_timer) { timers[i].callback(timers[i].id, timers[i].callback_data); if (!timers[i].repeats) remove_timer_at(i); break; } } }]; } unsigned long long _glfwPlatformAddTimer(monotonic_t interval, bool repeats, GLFWuserdatafun callback, void *callback_data, GLFWuserdatafun free_callback) { static unsigned long long timer_counter = 0; if (num_timers >= sizeof(timers)/sizeof(timers[0]) - 1) { _glfwInputError(GLFW_PLATFORM_ERROR, "Too many timers added"); return 0; } Timer *t = timers + num_timers++; t->id = ++timer_counter; t->repeats = repeats; t->interval = interval; t->callback = callback; t->callback_data = callback_data; t->free_callback_data = free_callback; schedule_timer(t); return timer_counter; } void _glfwPlatformRemoveTimer(unsigned long long timer_id) { for (size_t i = 0; i < num_timers; i++) { if (timers[i].id == timer_id) { remove_timer_at(i); break; } } } void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled) { for (size_t i = 0; i < num_timers; i++) { if (timers[i].id == timer_id) { Timer *t = timers + i; if (t->os_timer) { [t->os_timer invalidate]; t->os_timer = NULL; } t->interval = interval; if (enabled) schedule_timer(t); break; } } } void _glfwPlatformInputColorScheme(GLFWColorScheme appearance UNUSED) { } kitty-0.41.1/glfw/cocoa_joystick.h0000664000175000017510000000324614773370543016427 0ustar nileshnilesh//======================================================================== // GLFW 3.4 Cocoa - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2006-2017 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #include #include #include #include #define _GLFW_PLATFORM_JOYSTICK_STATE _GLFWjoystickNS ns #define _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE #define _GLFW_PLATFORM_MAPPING_NAME "Mac OS X" // Cocoa-specific per-joystick data // typedef struct _GLFWjoystickNS { IOHIDDeviceRef device; CFMutableArrayRef axes; CFMutableArrayRef buttons; CFMutableArrayRef hats; } _GLFWjoystickNS; kitty-0.41.1/glfw/cocoa_joystick.m0000664000175000017510000003642014773370543016434 0ustar nileshnilesh//======================================================================== // GLFW 3.4 Cocoa - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2009-2019 Camilla Löwy // Copyright (c) 2012 Torsten Walluhn // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #include "internal.h" #include #include #include #include #include #include #include // Joystick element information // typedef struct _GLFWjoyelementNS { IOHIDElementRef native; uint32_t usage; int index; long minimum; long maximum; } _GLFWjoyelementNS; // Returns the value of the specified element of the specified joystick // static long getElementValue(_GLFWjoystick* js, _GLFWjoyelementNS* element) { IOHIDValueRef valueRef; long value = 0; if (js->ns.device) { if (IOHIDDeviceGetValue(js->ns.device, element->native, &valueRef) == kIOReturnSuccess) { value = IOHIDValueGetIntegerValue(valueRef); } } return value; } // Comparison function for matching the SDL element order // static CFComparisonResult compareElements(const void* fp, const void* sp, void* user UNUSED) { const _GLFWjoyelementNS* fe = fp; const _GLFWjoyelementNS* se = sp; if (fe->usage < se->usage) return kCFCompareLessThan; if (fe->usage > se->usage) return kCFCompareGreaterThan; if (fe->index < se->index) return kCFCompareLessThan; if (fe->index > se->index) return kCFCompareGreaterThan; return kCFCompareEqualTo; } // Removes the specified joystick // static void closeJoystick(_GLFWjoystick* js) { int i; if (!js->present) return; for (i = 0; i < CFArrayGetCount(js->ns.axes); i++) free((void*) CFArrayGetValueAtIndex(js->ns.axes, i)); CFRelease(js->ns.axes); for (i = 0; i < CFArrayGetCount(js->ns.buttons); i++) free((void*) CFArrayGetValueAtIndex(js->ns.buttons, i)); CFRelease(js->ns.buttons); for (i = 0; i < CFArrayGetCount(js->ns.hats); i++) free((void*) CFArrayGetValueAtIndex(js->ns.hats, i)); CFRelease(js->ns.hats); _glfwFreeJoystick(js); _glfwInputJoystick(js, GLFW_DISCONNECTED); } // Callback for user-initiated joystick addition // static void matchCallback(void* context UNUSED, IOReturn result UNUSED, void* sender UNUSED, IOHIDDeviceRef device) { int jid; char name[256]; char guid[33]; CFIndex i; CFTypeRef property; uint32_t vendor = 0, product = 0, version = 0; _GLFWjoystick* js; CFMutableArrayRef axes, buttons, hats; for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) { if (_glfw.joysticks[jid].ns.device == device) return; } axes = CFArrayCreateMutable(NULL, 0, NULL); buttons = CFArrayCreateMutable(NULL, 0, NULL); hats = CFArrayCreateMutable(NULL, 0, NULL); property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); if (property) { CFStringGetCString(property, name, sizeof(name), kCFStringEncodingUTF8); } else strncpy(name, "Unknown", sizeof(name)); property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)); if (property) CFNumberGetValue(property, kCFNumberSInt32Type, &vendor); property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)); if (property) CFNumberGetValue(property, kCFNumberSInt32Type, &product); property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVersionNumberKey)); if (property) CFNumberGetValue(property, kCFNumberSInt32Type, &version); // Generate a joystick GUID that matches the SDL 2.0.5+ one if (vendor && product) { snprintf(guid, sizeof(guid), "03000000%02x%02x0000%02x%02x0000%02x%02x0000", (uint8_t) vendor, (uint8_t) (vendor >> 8), (uint8_t) product, (uint8_t) (product >> 8), (uint8_t) version, (uint8_t) (version >> 8)); } else { snprintf(guid, sizeof(guid), "05000000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x00", name[0], name[1], name[2], name[3], name[4], name[5], name[6], name[7], name[8], name[9], name[10]); } CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone); for (i = 0; i < CFArrayGetCount(elements); i++) { IOHIDElementRef native = (IOHIDElementRef) CFArrayGetValueAtIndex(elements, i); if (CFGetTypeID(native) != IOHIDElementGetTypeID()) continue; const IOHIDElementType type = IOHIDElementGetType(native); if ((type != kIOHIDElementTypeInput_Axis) && (type != kIOHIDElementTypeInput_Button) && (type != kIOHIDElementTypeInput_Misc)) { continue; } CFMutableArrayRef target = NULL; const uint32_t usage = IOHIDElementGetUsage(native); const uint32_t page = IOHIDElementGetUsagePage(native); if (page == kHIDPage_GenericDesktop) { switch (usage) { case kHIDUsage_GD_X: case kHIDUsage_GD_Y: case kHIDUsage_GD_Z: case kHIDUsage_GD_Rx: case kHIDUsage_GD_Ry: case kHIDUsage_GD_Rz: case kHIDUsage_GD_Slider: case kHIDUsage_GD_Dial: case kHIDUsage_GD_Wheel: target = axes; break; case kHIDUsage_GD_Hatswitch: target = hats; break; case kHIDUsage_GD_DPadUp: case kHIDUsage_GD_DPadRight: case kHIDUsage_GD_DPadDown: case kHIDUsage_GD_DPadLeft: case kHIDUsage_GD_SystemMainMenu: case kHIDUsage_GD_Select: case kHIDUsage_GD_Start: target = buttons; break; } } else if (page == kHIDPage_Simulation) { switch (usage) { case kHIDUsage_Sim_Accelerator: case kHIDUsage_Sim_Brake: case kHIDUsage_Sim_Throttle: case kHIDUsage_Sim_Rudder: case kHIDUsage_Sim_Steering: target = axes; break; } } else if (page == kHIDPage_Button || page == kHIDPage_Consumer) target = buttons; if (target) { _GLFWjoyelementNS* element = calloc(1, sizeof(_GLFWjoyelementNS)); element->native = native; element->usage = usage; element->index = (int) CFArrayGetCount(target); element->minimum = IOHIDElementGetLogicalMin(native); element->maximum = IOHIDElementGetLogicalMax(native); CFArrayAppendValue(target, element); } } CFRelease(elements); CFArraySortValues(axes, CFRangeMake(0, CFArrayGetCount(axes)), compareElements, NULL); CFArraySortValues(buttons, CFRangeMake(0, CFArrayGetCount(buttons)), compareElements, NULL); CFArraySortValues(hats, CFRangeMake(0, CFArrayGetCount(hats)), compareElements, NULL); js = _glfwAllocJoystick(name, guid, (int) CFArrayGetCount(axes), (int) CFArrayGetCount(buttons), (int) CFArrayGetCount(hats)); js->ns.device = device; js->ns.axes = axes; js->ns.buttons = buttons; js->ns.hats = hats; _glfwInputJoystick(js, GLFW_CONNECTED); } // Callback for user-initiated joystick removal // static void removeCallback(void* context UNUSED, IOReturn result UNUSED, void* sender UNUSED, IOHIDDeviceRef device) { int jid; for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) { if (_glfw.joysticks[jid].ns.device == device) { closeJoystick(_glfw.joysticks + jid); break; } } } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// bool _glfwPlatformInitJoysticks(void) { CFMutableArrayRef matching; const long usages[] = { kHIDUsage_GD_Joystick, kHIDUsage_GD_GamePad, kHIDUsage_GD_MultiAxisController }; _glfw.ns.hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); matching = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (!matching) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create array"); return false; } for (size_t i = 0; i < sizeof(usages) / sizeof(long); i++) { const long page = kHIDPage_GenericDesktop; CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!dict) continue; CFNumberRef pageRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &page); CFNumberRef usageRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongType, &usages[i]); if (pageRef && usageRef) { CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), pageRef); CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), usageRef); CFArrayAppendValue(matching, dict); } if (pageRef) CFRelease(pageRef); if (usageRef) CFRelease(usageRef); CFRelease(dict); } IOHIDManagerSetDeviceMatchingMultiple(_glfw.ns.hidManager, matching); CFRelease(matching); IOHIDManagerRegisterDeviceMatchingCallback(_glfw.ns.hidManager, &matchCallback, NULL); IOHIDManagerRegisterDeviceRemovalCallback(_glfw.ns.hidManager, &removeCallback, NULL); IOHIDManagerScheduleWithRunLoop(_glfw.ns.hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); IOHIDManagerOpen(_glfw.ns.hidManager, kIOHIDOptionsTypeNone); // Execute the run loop once in order to register any initially-attached // joysticks CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); return true; } void _glfwPlatformTerminateJoysticks(void) { int jid; for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) closeJoystick(_glfw.joysticks + jid); if (_glfw.ns.hidManager) { CFRelease(_glfw.ns.hidManager); _glfw.ns.hidManager = NULL; } } int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode) { if (mode & _GLFW_POLL_AXES) { CFIndex i; for (i = 0; i < CFArrayGetCount(js->ns.axes); i++) { _GLFWjoyelementNS* axis = (_GLFWjoyelementNS*) CFArrayGetValueAtIndex(js->ns.axes, i); const long raw = getElementValue(js, axis); // Perform auto calibration if (raw < axis->minimum) axis->minimum = raw; if (raw > axis->maximum) axis->maximum = raw; const long size = axis->maximum - axis->minimum; if (size == 0) _glfwInputJoystickAxis(js, (int) i, 0.f); else { const float value = (2.f * (raw - axis->minimum) / size) - 1.f; _glfwInputJoystickAxis(js, (int) i, value); } } } if (mode & _GLFW_POLL_BUTTONS) { CFIndex i; for (i = 0; i < CFArrayGetCount(js->ns.buttons); i++) { _GLFWjoyelementNS* button = (_GLFWjoyelementNS*) CFArrayGetValueAtIndex(js->ns.buttons, i); const char value = getElementValue(js, button) - button->minimum; const int state = (value > 0) ? GLFW_PRESS : GLFW_RELEASE; _glfwInputJoystickButton(js, (int) i, state); } for (i = 0; i < CFArrayGetCount(js->ns.hats); i++) { const int states[9] = { GLFW_HAT_UP, GLFW_HAT_RIGHT_UP, GLFW_HAT_RIGHT, GLFW_HAT_RIGHT_DOWN, GLFW_HAT_DOWN, GLFW_HAT_LEFT_DOWN, GLFW_HAT_LEFT, GLFW_HAT_LEFT_UP, GLFW_HAT_CENTERED }; _GLFWjoyelementNS* hat = (_GLFWjoyelementNS*) CFArrayGetValueAtIndex(js->ns.hats, i); long state = getElementValue(js, hat) - hat->minimum; if (state < 0 || state > 8) state = 8; _glfwInputJoystickHat(js, (int) i, states[state]); } } return js->present; } void _glfwPlatformUpdateGamepadGUID(char* guid) { if ((strncmp(guid + 4, "000000000000", 12) == 0) && (strncmp(guid + 20, "000000000000", 12) == 0)) { char original[33]; strncpy(original, guid, sizeof(original) - 1); snprintf(guid, 33, "03000000%.4s0000%.4s000000000000", original, original + 16); } } kitty-0.41.1/glfw/cocoa_monitor.m0000664000175000017510000004773214773370543016274 0ustar nileshnilesh//======================================================================== // GLFW 3.4 macOS - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #include "internal.h" #include #include #include #include #include #include // Get the name of the specified display, or NULL // static char* getDisplayName(CGDirectDisplayID displayID, NSScreen* screen) { // IOKit doesn't work on Apple Silicon anymore // Luckily, 10.15 introduced -[NSScreen localizedName]. // Use it if available, and fall back to IOKit otherwise. if (screen) { if ([screen respondsToSelector:@selector(localizedName)]) { NSString* name = [screen valueForKey:@"localizedName"]; if (name) { return _glfw_strdup([name UTF8String]); } } } io_iterator_t it; io_service_t service; CFDictionaryRef info; if (IOServiceGetMatchingServices(0, IOServiceMatching("IODisplayConnect"), &it) != 0) { // This may happen if a desktop Mac is running headless return NULL; } while ((service = IOIteratorNext(it)) != 0) { info = IODisplayCreateInfoDictionary(service, kIODisplayOnlyPreferredName); CFNumberRef vendorIDRef = CFDictionaryGetValue(info, CFSTR(kDisplayVendorID)); CFNumberRef productIDRef = CFDictionaryGetValue(info, CFSTR(kDisplayProductID)); if (!vendorIDRef || !productIDRef) { CFRelease(info); continue; } unsigned int vendorID, productID; CFNumberGetValue(vendorIDRef, kCFNumberIntType, &vendorID); CFNumberGetValue(productIDRef, kCFNumberIntType, &productID); if (CGDisplayVendorNumber(displayID) == vendorID && CGDisplayModelNumber(displayID) == productID) { // Info dictionary is used and freed below break; } CFRelease(info); } IOObjectRelease(it); if (!service) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to find service port for display"); return NULL; } CFDictionaryRef names = CFDictionaryGetValue(info, CFSTR(kDisplayProductName)); CFStringRef nameRef; if (!names || !CFDictionaryGetValueIfPresent(names, CFSTR("en_US"), (const void**) &nameRef)) { // This may happen if a desktop Mac is running headless CFRelease(info); return NULL; } const CFIndex size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef), kCFStringEncodingUTF8); char* name = calloc(size + 1, 1); CFStringGetCString(nameRef, name, size, kCFStringEncodingUTF8); CFRelease(info); return name; } // Check whether the display mode should be included in enumeration // static bool modeIsGood(CGDisplayModeRef mode) { uint32_t flags = CGDisplayModeGetIOFlags(mode); if (!(flags & kDisplayModeValidFlag) || !(flags & kDisplayModeSafeFlag)) return false; if (flags & kDisplayModeInterlacedFlag) return false; if (flags & kDisplayModeStretchedFlag) return false; #if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100 CFStringRef format = CGDisplayModeCopyPixelEncoding(mode); if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) && CFStringCompare(format, CFSTR(IO32BitDirectPixels), 0)) { CFRelease(format); return false; } CFRelease(format); #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */ return true; } // Convert Core Graphics display mode to GLFW video mode // static GLFWvidmode vidmodeFromCGDisplayMode(CGDisplayModeRef mode, double fallbackRefreshRate) { GLFWvidmode result; result.width = (int) CGDisplayModeGetWidth(mode); result.height = (int) CGDisplayModeGetHeight(mode); result.refreshRate = (int) round(CGDisplayModeGetRefreshRate(mode)); if (result.refreshRate == 0) result.refreshRate = (int) round(fallbackRefreshRate); #if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100 CFStringRef format = CGDisplayModeCopyPixelEncoding(mode); if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) == 0) { result.redBits = 5; result.greenBits = 5; result.blueBits = 5; } else #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */ { result.redBits = 8; result.greenBits = 8; result.blueBits = 8; } #if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100 CFRelease(format); #endif /* MAC_OS_X_VERSION_MAX_ALLOWED */ return result; } // Starts reservation for display fading // static CGDisplayFadeReservationToken beginFadeReservation(void) { CGDisplayFadeReservationToken token = kCGDisplayFadeReservationInvalidToken; if (CGAcquireDisplayFadeReservation(5, &token) == kCGErrorSuccess) { CGDisplayFade(token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE); } return token; } // Ends reservation for display fading // static void endFadeReservation(CGDisplayFadeReservationToken token) { if (token != kCGDisplayFadeReservationInvalidToken) { CGDisplayFade(token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE); CGReleaseDisplayFadeReservation(token); } } // Finds and caches the NSScreen corresponding to the specified monitor // static bool refreshMonitorScreen(_GLFWmonitor* monitor) { if (monitor->ns.screen) return true; for (NSScreen* screen in [NSScreen screens]) { NSNumber* displayID = [screen deviceDescription][@"NSScreenNumber"]; // HACK: Compare unit numbers instead of display IDs to work around // display replacement on machines with automatic graphics // switching if (monitor->ns.unitNumber == CGDisplayUnitNumber([displayID unsignedIntValue])) { monitor->ns.screen = screen; return true; } } _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to find a screen for monitor"); return false; } // Returns the display refresh rate queried from the I/O registry // static double getFallbackRefreshRate(CGDirectDisplayID displayID) { double refreshRate = 60.0; io_iterator_t it; io_service_t service; if (IOServiceGetMatchingServices(0, IOServiceMatching("IOFramebuffer"), &it) != 0) { return refreshRate; } while ((service = IOIteratorNext(it)) != 0) { const CFNumberRef indexRef = IORegistryEntryCreateCFProperty(service, CFSTR("IOFramebufferOpenGLIndex"), kCFAllocatorDefault, kNilOptions); if (!indexRef) continue; uint32_t index = 0; CFNumberGetValue(indexRef, kCFNumberIntType, &index); CFRelease(indexRef); if (CGOpenGLDisplayMaskToDisplayID(1 << index) != displayID) continue; const CFNumberRef clockRef = IORegistryEntryCreateCFProperty(service, CFSTR("IOFBCurrentPixelClock"), kCFAllocatorDefault, kNilOptions); const CFNumberRef countRef = IORegistryEntryCreateCFProperty(service, CFSTR("IOFBCurrentPixelCount"), kCFAllocatorDefault, kNilOptions); uint32_t clock = 0, count = 0; if (clockRef) { CFNumberGetValue(clockRef, kCFNumberIntType, &clock); CFRelease(clockRef); } if (countRef) { CFNumberGetValue(countRef, kCFNumberIntType, &count); CFRelease(countRef); } if (clock > 0 && count > 0) refreshRate = clock / (double) count; break; } IOObjectRelease(it); return refreshRate; } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// // Poll for changes in the set of connected monitors void _glfwPollMonitorsNS(void) { uint32_t displayCount; CGGetOnlineDisplayList(0, NULL, &displayCount); CGDirectDisplayID* displays = calloc(displayCount, sizeof(CGDirectDisplayID)); CGGetOnlineDisplayList(displayCount, displays, &displayCount); _glfwClearDisplayLinks(); if (_glfw.hints.init.debugRendering) { fprintf(stderr, "Polling for monitors: %u found\n", displayCount); } for (int i = 0; i < _glfw.monitorCount; i++) _glfw.monitors[i]->ns.screen = nil; _GLFWmonitor** disconnected = NULL; uint32_t disconnectedCount = _glfw.monitorCount; if (disconnectedCount) { disconnected = calloc(_glfw.monitorCount, sizeof(_GLFWmonitor*)); memcpy(disconnected, _glfw.monitors, _glfw.monitorCount * sizeof(_GLFWmonitor*)); } for (uint32_t i = 0; i < displayCount; i++) { if (CGDisplayIsAsleep(displays[i])) { if (_glfw.hints.init.debugRendering) fprintf(stderr, "Ignoring sleeping display: %u", displays[i]); continue; } const uint32_t unitNumber = CGDisplayUnitNumber(displays[i]); NSScreen* screen = nil; for (screen in [NSScreen screens]) { NSNumber* screenNumber = [screen deviceDescription][@"NSScreenNumber"]; // HACK: Compare unit numbers instead of display IDs to work around // display replacement on machines with automatic graphics // switching if (CGDisplayUnitNumber([screenNumber unsignedIntValue]) == unitNumber) break; } // HACK: Compare unit numbers instead of display IDs to work around // display replacement on machines with automatic graphics // switching uint32_t j; for (j = 0; j < disconnectedCount; j++) { if (disconnected[j] && disconnected[j]->ns.unitNumber == unitNumber) { disconnected[j]->ns.displayID = displays[i]; disconnected[j]->ns.screen = screen; _glfwCreateDisplayLink(displays[i]); disconnected[j] = NULL; break; } } if (j < disconnectedCount) continue; const CGSize size = CGDisplayScreenSize(displays[i]); char* name = getDisplayName(displays[i], screen); if (!name) { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to get name for display, using generic name"); name = _glfw_strdup("Display with no name"); } _GLFWmonitor* monitor = _glfwAllocMonitor(name, (int)size.width, (int)size.height); monitor->ns.displayID = displays[i]; monitor->ns.unitNumber = unitNumber; monitor->ns.screen = screen; _glfwCreateDisplayLink(monitor->ns.displayID); free(name); CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displays[i]); if (CGDisplayModeGetRefreshRate(mode) == 0.0) monitor->ns.fallbackRefreshRate = getFallbackRefreshRate(displays[i]); CGDisplayModeRelease(mode); _glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST); } for (uint32_t i = 0; i < disconnectedCount; i++) { if (disconnected[i]) _glfwInputMonitor(disconnected[i], GLFW_DISCONNECTED, 0); } free(disconnected); free(displays); _glfwRestartDisplayLinks(); } // Change the current video mode // void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired) { GLFWvidmode current; _glfwPlatformGetVideoMode(monitor, ¤t); const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired); if (_glfwCompareVideoModes(¤t, best) == 0) return; CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL); const CFIndex count = CFArrayGetCount(modes); CGDisplayModeRef native = NULL; for (CFIndex i = 0; i < count; i++) { CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i); if (!modeIsGood(dm)) continue; const GLFWvidmode mode = vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate); if (_glfwCompareVideoModes(best, &mode) == 0) { native = dm; break; } } if (native) { if (monitor->ns.previousMode == NULL) monitor->ns.previousMode = CGDisplayCopyDisplayMode(monitor->ns.displayID); CGDisplayFadeReservationToken token = beginFadeReservation(); CGDisplaySetDisplayMode(monitor->ns.displayID, native, NULL); endFadeReservation(token); } CFRelease(modes); } // Restore the previously saved (original) video mode // void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor) { if (monitor->ns.previousMode) { CGDisplayFadeReservationToken token = beginFadeReservation(); CGDisplaySetDisplayMode(monitor->ns.displayID, monitor->ns.previousMode, NULL); endFadeReservation(token); CGDisplayModeRelease(monitor->ns.previousMode); monitor->ns.previousMode = NULL; } } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor UNUSED) { } void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos) { const CGRect bounds = CGDisplayBounds(monitor->ns.displayID); if (xpos) *xpos = (int) bounds.origin.x; if (ypos) *ypos = (int) bounds.origin.y; } void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor, float* xscale, float* yscale) { if (!refreshMonitorScreen(monitor)) return; const NSRect points = [monitor->ns.screen frame]; const NSRect pixels = [monitor->ns.screen convertRectToBacking:points]; if (xscale) *xscale = (float) (pixels.size.width / points.size.width); if (yscale) *yscale = (float) (pixels.size.height / points.size.height); } void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor, int* xpos, int* ypos, int* width, int* height) { if (!refreshMonitorScreen(monitor)) return; const NSRect frameRect = [monitor->ns.screen visibleFrame]; if (xpos) *xpos = (int)frameRect.origin.x; if (ypos) *ypos = (int)_glfwTransformYNS(frameRect.origin.y + frameRect.size.height - 1); if (width) *width = (int)frameRect.size.width; if (height) *height = (int)frameRect.size.height; } GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count) { *count = 0; CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL); const CFIndex found = CFArrayGetCount(modes); GLFWvidmode* result = calloc(found, sizeof(GLFWvidmode)); for (CFIndex i = 0; i < found; i++) { CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i); if (!modeIsGood(dm)) continue; const GLFWvidmode mode = vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate); CFIndex j; for (j = 0; j < *count; j++) { if (_glfwCompareVideoModes(result + j, &mode) == 0) break; } // Skip duplicate modes if (j < *count) continue; (*count)++; result[*count - 1] = mode; } CFRelease(modes); return result; } bool _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode) { CGDisplayModeRef native = CGDisplayCopyDisplayMode(monitor->ns.displayID); if (!native) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to query display mode"); return false; } *mode = vidmodeFromCGDisplayMode(native, monitor->ns.fallbackRefreshRate); CGDisplayModeRelease(native); return true; } bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp) { uint32_t size = CGDisplayGammaTableCapacity(monitor->ns.displayID); CGGammaValue* values = calloc(size * 3, sizeof(CGGammaValue)); CGGetDisplayTransferByTable(monitor->ns.displayID, size, values, values + size, values + size * 2, &size); _glfwAllocGammaArrays(ramp, size); for (uint32_t i = 0; i < size; i++) { ramp->red[i] = (unsigned short) (values[i] * 65535); ramp->green[i] = (unsigned short) (values[i + size] * 65535); ramp->blue[i] = (unsigned short) (values[i + size * 2] * 65535); } free(values); return true; } void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp) { CGGammaValue* values = calloc(ramp->size * 3, sizeof(CGGammaValue)); for (unsigned int i = 0; i < ramp->size; i++) { values[i] = ramp->red[i] / 65535.f; values[i + ramp->size] = ramp->green[i] / 65535.f; values[i + ramp->size * 2] = ramp->blue[i] / 65535.f; } CGSetDisplayTransferByTable(monitor->ns.displayID, ramp->size, values, values + ramp->size, values + ramp->size * 2); free(values); } ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay); return monitor->ns.displayID; } kitty-0.41.1/glfw/cocoa_platform.h0000664000175000017510000002160414773370543016412 0ustar nileshnilesh//======================================================================== // GLFW 3.4 macOS - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2009-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #include #include #include #if defined(__OBJC__) #import #else typedef void* id; #endif // NOTE: Many Cocoa enum values have been renamed and we need to build across // SDK versions where one is unavailable or the other deprecated // We use the newer names in code and these macros to handle compatibility #if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 #define NSBitmapFormatAlphaNonpremultiplied NSAlphaNonpremultipliedBitmapFormat #define NSEventMaskAny NSAnyEventMask #define NSEventMaskKeyUp NSKeyUpMask #define NSEventModifierFlagCapsLock NSAlphaShiftKeyMask #define NSEventModifierFlagCommand NSCommandKeyMask #define NSEventModifierFlagControl NSControlKeyMask #define NSEventModifierFlagDeviceIndependentFlagsMask NSDeviceIndependentModifierFlagsMask #define NSEventModifierFlagOption NSAlternateKeyMask #define NSEventModifierFlagShift NSShiftKeyMask #define NSEventTypeApplicationDefined NSApplicationDefined #define NSWindowStyleMaskBorderless NSBorderlessWindowMask #define NSWindowStyleMaskClosable NSClosableWindowMask #define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask #define NSWindowStyleMaskResizable NSResizableWindowMask #define NSWindowStyleMaskTitled NSTitledWindowMask #endif #if (MAC_OS_X_VERSION_MAX_ALLOWED < 101400) #define NSPasteboardTypeFileURL NSFilenamesPboardType #define NSBitmapFormatAlphaNonpremultiplied NSAlphaNonpremultipliedBitmapFormat #define NSPasteboardTypeString NSStringPboardType #define NSOpenGLContextParameterSurfaceOpacity NSOpenGLCPSurfaceOpacity #endif #define debug_key(...) if (_glfw.hints.init.debugKeyboard) { fprintf(stderr, __VA_ARGS__); fflush(stderr); } typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long); typedef bool (* GLFWapplicationshouldhandlereopenfun)(int); typedef bool (* GLFWhandleurlopen)(const char*); typedef void (* GLFWapplicationwillfinishlaunchingfun)(void); typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); typedef void (* GLFWcocoarenderframefun)(GLFWwindow*); typedef VkFlags VkMacOSSurfaceCreateFlagsMVK; typedef VkFlags VkMetalSurfaceCreateFlagsEXT; typedef struct VkMacOSSurfaceCreateInfoMVK { VkStructureType sType; const void* pNext; VkMacOSSurfaceCreateFlagsMVK flags; const void* pView; } VkMacOSSurfaceCreateInfoMVK; typedef struct VkMetalSurfaceCreateInfoEXT { VkStructureType sType; const void* pNext; VkMetalSurfaceCreateFlagsEXT flags; const void* pLayer; } VkMetalSurfaceCreateInfoEXT; typedef VkResult (APIENTRY *PFN_vkCreateMacOSSurfaceMVK)(VkInstance,const VkMacOSSurfaceCreateInfoMVK*,const VkAllocationCallbacks*,VkSurfaceKHR*); typedef VkResult (APIENTRY *PFN_vkCreateMetalSurfaceEXT)(VkInstance,const VkMetalSurfaceCreateInfoEXT*,const VkAllocationCallbacks*,VkSurfaceKHR*); #include "posix_thread.h" #include "cocoa_joystick.h" #include "nsgl_context.h" #define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL) #define _glfw_dlclose(handle) dlclose(handle) #define _glfw_dlsym(handle, name) dlsym(handle, name) #define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNS ns #define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryNS ns #define _GLFW_PLATFORM_LIBRARY_TIMER_STATE _GLFWtimerNS ns #define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorNS ns #define _GLFW_PLATFORM_CURSOR_STATE _GLFWcursorNS ns // HIToolbox.framework pointer typedefs #define kTISPropertyUnicodeKeyLayoutData _glfw.ns.tis.kPropertyUnicodeKeyLayoutData typedef TISInputSourceRef (*PFN_TISCopyCurrentKeyboardLayoutInputSource)(void); #define TISCopyCurrentKeyboardLayoutInputSource _glfw.ns.tis.CopyCurrentKeyboardLayoutInputSource typedef void* (*PFN_TISGetInputSourceProperty)(TISInputSourceRef,CFStringRef); #define TISGetInputSourceProperty _glfw.ns.tis.GetInputSourceProperty typedef UInt8 (*PFN_LMGetKbdType)(void); #define LMGetKbdType _glfw.ns.tis.GetKbdType // Cocoa-specific per-window data // typedef struct _GLFWwindowNS { id object; id delegate; id view; id layer; bool maximized; bool retina; bool in_traditional_fullscreen; bool in_fullscreen_transition; bool titlebar_hidden; unsigned long pre_full_screen_style_mask; // Cached window properties to filter out duplicate events int width, height; int fbWidth, fbHeight; float xscale, yscale; int blur_radius; // The total sum of the distances the cursor has been warped // since the last cursor motion event was processed // This is kept to counteract Cocoa doing the same internally double cursorWarpDeltaX, cursorWarpDeltaY; // The text input filter callback GLFWcocoatextinputfilterfun textInputFilterCallback; // The toggle fullscreen intercept callback GLFWcocoatogglefullscreenfun toggleFullscreenCallback; // Dead key state UInt32 deadKeyState; // Whether a render frame has been requested for this window bool renderFrameRequested; GLFWcocoarenderframefun renderFrameCallback; // update cursor after switching desktops with Mission Control bool delayed_cursor_update_requested; GLFWcocoarenderframefun resizeCallback; } _GLFWwindowNS; // Cocoa-specific global data // typedef struct _GLFWlibraryNS { CGEventSourceRef eventSource; id delegate; bool finishedLaunching; bool cursorHidden; TISInputSourceRef inputSource; IOHIDManagerRef hidManager; id unicodeData; id helper; id keyUpMonitor, keyDownMonitor, flagsChangedMonitor; id appleSettings; id nibObjects; char keyName[64]; char text[512]; CGPoint cascadePoint; // Where to place the cursor when re-enabled double restoreCursorPosX, restoreCursorPosY; // The window whose disabled cursor mode is active _GLFWwindow* disabledCursorWindow; struct { CFBundleRef bundle; PFN_TISCopyCurrentKeyboardLayoutInputSource CopyCurrentKeyboardLayoutInputSource; PFN_TISGetInputSourceProperty GetInputSourceProperty; PFN_LMGetKbdType GetKbdType; CFStringRef kPropertyUnicodeKeyLayoutData; } tis; // the callback to handle url open events GLFWhandleurlopen url_open_callback; } _GLFWlibraryNS; // Cocoa-specific per-monitor data // typedef struct _GLFWmonitorNS { CGDirectDisplayID displayID; CGDisplayModeRef previousMode; uint32_t unitNumber; id screen; double fallbackRefreshRate; } _GLFWmonitorNS; // Cocoa-specific per-cursor data // typedef struct _GLFWcursorNS { id object; } _GLFWcursorNS; // Cocoa-specific global timer data // typedef struct _GLFWtimerNS { uint64_t frequency; } _GLFWtimerNS; void _glfwPollMonitorsNS(void); void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired); void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor); float _glfwTransformYNS(float y); void* _glfwLoadLocalVulkanLoaderNS(void); // display links void _glfwClearDisplayLinks(void); void _glfwRestartDisplayLinks(void); unsigned _glfwCreateDisplayLink(CGDirectDisplayID); void _glfwDispatchRenderFrame(CGDirectDisplayID); void _glfwRequestRenderFrame(_GLFWwindow *w); // event loop void _glfwDispatchTickCallback(void); void _glfwCocoaPostEmptyEvent(void); uint32_t vk_to_unicode_key_with_current_layout(uint16_t keycode); kitty-0.41.1/glfw/cocoa_window.m0000664000175000017510000034424014773370543016106 0ustar nileshnilesh//======================================================================== // GLFW 3.4 macOS - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2009-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #include "../kitty/monotonic.h" #include "glfw3.h" #include "internal.h" #include #import #import #include #include #define debug debug_rendering static const char* polymorphic_string_as_utf8(id string) { if (string == nil) return "(nil)"; NSString* characters; if ([string isKindOfClass:[NSAttributedString class]]) characters = [string string]; else characters = (NSString*) string; return [characters UTF8String]; } static uint32_t vk_code_to_functional_key_code(uint8_t key_code) { // {{{ switch(key_code) { /* start vk to functional (auto generated by gen-key-constants.py do not edit) */ case 0x35: return GLFW_FKEY_ESCAPE; case 0x24: return GLFW_FKEY_ENTER; case 0x30: return GLFW_FKEY_TAB; case 0x33: return GLFW_FKEY_BACKSPACE; case 0x72: return GLFW_FKEY_INSERT; case 0x75: return GLFW_FKEY_DELETE; case 0x7b: return GLFW_FKEY_LEFT; case 0x7c: return GLFW_FKEY_RIGHT; case 0x7e: return GLFW_FKEY_UP; case 0x7d: return GLFW_FKEY_DOWN; case 0x74: return GLFW_FKEY_PAGE_UP; case 0x79: return GLFW_FKEY_PAGE_DOWN; case 0x73: return GLFW_FKEY_HOME; case 0x77: return GLFW_FKEY_END; case 0x39: return GLFW_FKEY_CAPS_LOCK; case 0x47: return GLFW_FKEY_NUM_LOCK; case 0x6e: return GLFW_FKEY_MENU; case 0x7a: return GLFW_FKEY_F1; case 0x78: return GLFW_FKEY_F2; case 0x63: return GLFW_FKEY_F3; case 0x76: return GLFW_FKEY_F4; case 0x60: return GLFW_FKEY_F5; case 0x61: return GLFW_FKEY_F6; case 0x62: return GLFW_FKEY_F7; case 0x64: return GLFW_FKEY_F8; case 0x65: return GLFW_FKEY_F9; case 0x6d: return GLFW_FKEY_F10; case 0x67: return GLFW_FKEY_F11; case 0x6f: return GLFW_FKEY_F12; case 0x69: return GLFW_FKEY_F13; case 0x6b: return GLFW_FKEY_F14; case 0x71: return GLFW_FKEY_F15; case 0x6a: return GLFW_FKEY_F16; case 0x40: return GLFW_FKEY_F17; case 0x4f: return GLFW_FKEY_F18; case 0x50: return GLFW_FKEY_F19; case 0x5a: return GLFW_FKEY_F20; case 0x52: return GLFW_FKEY_KP_0; case 0x53: return GLFW_FKEY_KP_1; case 0x54: return GLFW_FKEY_KP_2; case 0x55: return GLFW_FKEY_KP_3; case 0x56: return GLFW_FKEY_KP_4; case 0x57: return GLFW_FKEY_KP_5; case 0x58: return GLFW_FKEY_KP_6; case 0x59: return GLFW_FKEY_KP_7; case 0x5b: return GLFW_FKEY_KP_8; case 0x5c: return GLFW_FKEY_KP_9; case 0x41: return GLFW_FKEY_KP_DECIMAL; case 0x4b: return GLFW_FKEY_KP_DIVIDE; case 0x43: return GLFW_FKEY_KP_MULTIPLY; case 0x4e: return GLFW_FKEY_KP_SUBTRACT; case 0x45: return GLFW_FKEY_KP_ADD; case 0x4c: return GLFW_FKEY_KP_ENTER; case 0x51: return GLFW_FKEY_KP_EQUAL; case 0x38: return GLFW_FKEY_LEFT_SHIFT; case 0x3b: return GLFW_FKEY_LEFT_CONTROL; case 0x3a: return GLFW_FKEY_LEFT_ALT; case 0x37: return GLFW_FKEY_LEFT_SUPER; case 0x3c: return GLFW_FKEY_RIGHT_SHIFT; case 0x3e: return GLFW_FKEY_RIGHT_CONTROL; case 0x3d: return GLFW_FKEY_RIGHT_ALT; case 0x36: return GLFW_FKEY_RIGHT_SUPER; /* end vk to functional */ default: return 0; } } // }}} static uint32_t vk_code_to_unicode(uint8_t key_code) { // {{{ switch(key_code) { /* start vk to unicode (auto generated by gen-key-constants.py do not edit) */ case 0x0: return 0x61; case 0x1: return 0x73; case 0x2: return 0x64; case 0x3: return 0x66; case 0x4: return 0x68; case 0x5: return 0x67; case 0x6: return 0x7a; case 0x7: return 0x78; case 0x8: return 0x63; case 0x9: return 0x76; case 0xb: return 0x62; case 0xc: return 0x71; case 0xd: return 0x77; case 0xe: return 0x65; case 0xf: return 0x72; case 0x10: return 0x79; case 0x11: return 0x74; case 0x12: return 0x31; case 0x13: return 0x32; case 0x14: return 0x33; case 0x15: return 0x34; case 0x16: return 0x36; case 0x17: return 0x35; case 0x18: return 0x3d; case 0x19: return 0x39; case 0x1a: return 0x37; case 0x1b: return 0x2d; case 0x1c: return 0x38; case 0x1d: return 0x30; case 0x1e: return 0x5d; case 0x1f: return 0x6f; case 0x20: return 0x75; case 0x21: return 0x5b; case 0x22: return 0x69; case 0x23: return 0x70; case 0x25: return 0x6c; case 0x26: return 0x6a; case 0x27: return 0x27; case 0x28: return 0x6b; case 0x29: return 0x3b; case 0x2a: return 0x5c; case 0x2b: return 0x2c; case 0x2c: return 0x2f; case 0x2d: return 0x6e; case 0x2e: return 0x6d; case 0x2f: return 0x2e; case 0x31: return 0x20; case 0x32: return 0x60; /* end vk to unicode */ default: return 0; } } // }}} static uint32_t mac_ucode_to_functional(uint32_t key_code) { // {{{ switch(key_code) { /* start macu to functional (auto generated by gen-key-constants.py do not edit) */ case NSCarriageReturnCharacter: return GLFW_FKEY_ENTER; case NSTabCharacter: return GLFW_FKEY_TAB; case NSBackspaceCharacter: return GLFW_FKEY_BACKSPACE; case NSInsertFunctionKey: return GLFW_FKEY_INSERT; case NSDeleteFunctionKey: return GLFW_FKEY_DELETE; case NSLeftArrowFunctionKey: return GLFW_FKEY_LEFT; case NSRightArrowFunctionKey: return GLFW_FKEY_RIGHT; case NSUpArrowFunctionKey: return GLFW_FKEY_UP; case NSDownArrowFunctionKey: return GLFW_FKEY_DOWN; case NSPageUpFunctionKey: return GLFW_FKEY_PAGE_UP; case NSPageDownFunctionKey: return GLFW_FKEY_PAGE_DOWN; case NSHomeFunctionKey: return GLFW_FKEY_HOME; case NSEndFunctionKey: return GLFW_FKEY_END; case NSScrollLockFunctionKey: return GLFW_FKEY_SCROLL_LOCK; case NSClearLineFunctionKey: return GLFW_FKEY_NUM_LOCK; case NSPrintScreenFunctionKey: return GLFW_FKEY_PRINT_SCREEN; case NSPauseFunctionKey: return GLFW_FKEY_PAUSE; case NSMenuFunctionKey: return GLFW_FKEY_MENU; case NSF1FunctionKey: return GLFW_FKEY_F1; case NSF2FunctionKey: return GLFW_FKEY_F2; case NSF3FunctionKey: return GLFW_FKEY_F3; case NSF4FunctionKey: return GLFW_FKEY_F4; case NSF5FunctionKey: return GLFW_FKEY_F5; case NSF6FunctionKey: return GLFW_FKEY_F6; case NSF7FunctionKey: return GLFW_FKEY_F7; case NSF8FunctionKey: return GLFW_FKEY_F8; case NSF9FunctionKey: return GLFW_FKEY_F9; case NSF10FunctionKey: return GLFW_FKEY_F10; case NSF11FunctionKey: return GLFW_FKEY_F11; case NSF12FunctionKey: return GLFW_FKEY_F12; case NSF13FunctionKey: return GLFW_FKEY_F13; case NSF14FunctionKey: return GLFW_FKEY_F14; case NSF15FunctionKey: return GLFW_FKEY_F15; case NSF16FunctionKey: return GLFW_FKEY_F16; case NSF17FunctionKey: return GLFW_FKEY_F17; case NSF18FunctionKey: return GLFW_FKEY_F18; case NSF19FunctionKey: return GLFW_FKEY_F19; case NSF20FunctionKey: return GLFW_FKEY_F20; case NSF21FunctionKey: return GLFW_FKEY_F21; case NSF22FunctionKey: return GLFW_FKEY_F22; case NSF23FunctionKey: return GLFW_FKEY_F23; case NSF24FunctionKey: return GLFW_FKEY_F24; case NSF25FunctionKey: return GLFW_FKEY_F25; case NSF26FunctionKey: return GLFW_FKEY_F26; case NSF27FunctionKey: return GLFW_FKEY_F27; case NSF28FunctionKey: return GLFW_FKEY_F28; case NSF29FunctionKey: return GLFW_FKEY_F29; case NSF30FunctionKey: return GLFW_FKEY_F30; case NSF31FunctionKey: return GLFW_FKEY_F31; case NSF32FunctionKey: return GLFW_FKEY_F32; case NSF33FunctionKey: return GLFW_FKEY_F33; case NSF34FunctionKey: return GLFW_FKEY_F34; case NSF35FunctionKey: return GLFW_FKEY_F35; case NSEnterCharacter: return GLFW_FKEY_KP_ENTER; /* end macu to functional */ default: return 0; } } // }}} static bool is_surrogate(UniChar uc) { return (uc - 0xd800u) < 2048u; } static uint32_t get_first_codepoint(UniChar *utf16, UniCharCount num) { if (!num) return 0; if (!is_surrogate(*utf16)) return *utf16; if (CFStringIsSurrogateHighCharacter(*utf16) && num > 1 && CFStringIsSurrogateLowCharacter(utf16[1])) return CFStringGetLongCharacterForSurrogatePair(utf16[0], utf16[1]); return 0; } static bool is_pua_char(uint32_t ch) { return (0xE000 <= ch && ch <= 0xF8FF) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF); } uint32_t vk_to_unicode_key_with_current_layout(uint16_t keycode) { UInt32 dead_key_state = 0; UniChar characters[256]; UniCharCount character_count = 0; uint32_t ans = vk_code_to_functional_key_code(keycode); if (ans) return ans; if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes], keycode, kUCKeyActionDisplay, 0, LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &dead_key_state, arraysz(characters), &character_count, characters) == noErr) { uint32_t cp = get_first_codepoint(characters, character_count); if (cp) { if (cp < 32 || (0xF700 <= cp && cp <= 0xF8FF)) return mac_ucode_to_functional(cp); if (cp >= 32 && !is_pua_char(cp)) return cp; } } return vk_code_to_unicode(keycode); } // Returns the style mask corresponding to the window settings // static NSUInteger getStyleMask(_GLFWwindow* window) { NSUInteger styleMask = NSWindowStyleMaskMiniaturizable; if (window->ns.titlebar_hidden) styleMask |= NSWindowStyleMaskFullSizeContentView; if (window->monitor || !window->decorated) { styleMask |= NSWindowStyleMaskBorderless; } else { styleMask |= NSWindowStyleMaskTitled | NSWindowStyleMaskClosable; } if (window->resizable) styleMask |= NSWindowStyleMaskResizable; return styleMask; } static void requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) { if (!callback) { w->ns.renderFrameRequested = false; w->ns.renderFrameCallback = NULL; return; } w->ns.renderFrameCallback = callback; w->ns.renderFrameRequested = true; _glfwRequestRenderFrame(w); } void _glfwRestartDisplayLinks(void) { _GLFWwindow* window; for (window = _glfw.windowListHead; window; window = window->next) { if (window->ns.renderFrameRequested && window->ns.renderFrameCallback) { requestRenderFrame(window, window->ns.renderFrameCallback); } } } // Returns whether the cursor is in the content area of the specified window // static bool cursorInContentArea(_GLFWwindow* window) { const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; return [window->ns.view mouse:pos inRect:[window->ns.view frame]]; } // Hides the cursor if not already hidden // static void hideCursor(_GLFWwindow* window UNUSED) { if (!_glfw.ns.cursorHidden) { [NSCursor hide]; _glfw.ns.cursorHidden = true; } } // Shows the cursor if not already shown // static void showCursor(_GLFWwindow* window UNUSED) { if (_glfw.ns.cursorHidden) { [NSCursor unhide]; _glfw.ns.cursorHidden = false; } } // Updates the cursor image according to its cursor mode // static void updateCursorImage(_GLFWwindow* window) { if (window->cursorMode == GLFW_CURSOR_NORMAL) { showCursor(window); if (window->cursor) [(NSCursor*) window->cursor->ns.object set]; else [[NSCursor arrowCursor] set]; } else hideCursor(window); } // Apply chosen cursor mode to a focused window // static void updateCursorMode(_GLFWwindow* window) { if (window->cursorMode == GLFW_CURSOR_DISABLED) { _glfw.ns.disabledCursorWindow = window; _glfwPlatformGetCursorPos(window, &_glfw.ns.restoreCursorPosX, &_glfw.ns.restoreCursorPosY); _glfwCenterCursorInContentArea(window); CGAssociateMouseAndMouseCursorPosition(false); } else if (_glfw.ns.disabledCursorWindow == window) { _glfw.ns.disabledCursorWindow = NULL; CGAssociateMouseAndMouseCursorPosition(true); _glfwPlatformSetCursorPos(window, _glfw.ns.restoreCursorPosX, _glfw.ns.restoreCursorPosY); } if (cursorInContentArea(window)) updateCursorImage(window); } // Make the specified window and its video mode active on its monitor // static void acquireMonitor(_GLFWwindow* window) { _glfwSetVideoModeNS(window->monitor, &window->videoMode); const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID); const NSRect frame = NSMakeRect(bounds.origin.x, _glfwTransformYNS(bounds.origin.y + bounds.size.height - 1), bounds.size.width, bounds.size.height); [window->ns.object setFrame:frame display:YES]; _glfwInputMonitorWindow(window->monitor, window); } // Remove the window and restore the original video mode // static void releaseMonitor(_GLFWwindow* window) { if (window->monitor->window != window) return; _glfwInputMonitorWindow(window->monitor, NULL); _glfwRestoreVideoModeNS(window->monitor); } // Translates macOS key modifiers into GLFW ones // static int translateFlags(NSUInteger flags) { int mods = 0; if (flags & NSEventModifierFlagShift) mods |= GLFW_MOD_SHIFT; if (flags & NSEventModifierFlagControl) mods |= GLFW_MOD_CONTROL; if (flags & NSEventModifierFlagOption) mods |= GLFW_MOD_ALT; if (flags & NSEventModifierFlagCommand) mods |= GLFW_MOD_SUPER; if (flags & NSEventModifierFlagCapsLock) mods |= GLFW_MOD_CAPS_LOCK; return mods; } static const char* format_mods(int mods) { static char buf[128]; char *p = buf, *s; #define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, x) pr("mods: "); s = p; if (mods & GLFW_MOD_CONTROL) pr("ctrl+"); if (mods & GLFW_MOD_ALT) pr("alt+"); if (mods & GLFW_MOD_SHIFT) pr("shift+"); if (mods & GLFW_MOD_SUPER) pr("super+"); if (mods & GLFW_MOD_CAPS_LOCK) pr("capslock+"); if (mods & GLFW_MOD_NUM_LOCK) pr("numlock+"); if (p == s) pr("none"); else p--; pr(" "); #undef pr return buf; } static const char* format_text(const char *src) { static char buf[256]; char *p = buf; const char *last_char = buf + sizeof(buf) - 1; if (!src[0]) return ""; while (*src) { int num = snprintf(p, sizeof(buf) - (p - buf), "0x%x ", (unsigned char)*(src++)); if (num < 0) return ""; if (p + num >= last_char) break; p += num; } if (p != buf) *(--p) = 0; return buf; } static const char* safe_name_for_keycode(unsigned int keycode) { const char *ans = _glfwPlatformGetNativeKeyName(keycode); if (!ans) return ""; if ((1 <= ans[0] && ans[0] <= 31) || ans[0] == 127) ans = ""; return ans; } // Translates a macOS keycode to a GLFW keycode // static uint32_t translateKey(uint16_t vk_key, bool apply_keymap) { if (apply_keymap) return vk_to_unicode_key_with_current_layout(vk_key); uint32_t ans = vk_code_to_functional_key_code(vk_key); if (!ans) ans = vk_code_to_unicode(vk_key); return ans; } static NSRect get_window_size_without_border_in_logical_pixels(_GLFWwindow *window) { return [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; } // Defines a constant for empty ranges in NSTextInputClient // static const NSRange kEmptyRange = { NSNotFound, 0 }; // Delegate for window related notifications {{{ @interface GLFWWindowDelegate : NSObject { _GLFWwindow* window; } - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; - (void)request_delayed_cursor_update:(id)sender; @end @implementation GLFWWindowDelegate - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow { self = [super init]; if (self != nil) window = initWindow; return self; } - (BOOL)windowShouldClose:(id)sender { (void)sender; _glfwInputWindowCloseRequest(window); return NO; } - (void)windowDidResize:(NSNotification *)notification { (void)notification; if (window->context.client != GLFW_NO_API) [window->context.nsgl.object update]; if (_glfw.ns.disabledCursorWindow == window) _glfwCenterCursorInContentArea(window); const int maximized = [window->ns.object isZoomed]; if (window->ns.maximized != maximized) { window->ns.maximized = maximized; _glfwInputWindowMaximize(window, maximized); } const NSRect contentRect = get_window_size_without_border_in_logical_pixels(window); const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; if (fbRect.size.width != window->ns.fbWidth || fbRect.size.height != window->ns.fbHeight) { window->ns.fbWidth = (int)fbRect.size.width; window->ns.fbHeight = (int)fbRect.size.height; _glfwInputFramebufferSize(window, (int)fbRect.size.width, (int)fbRect.size.height); } if (contentRect.size.width != window->ns.width || contentRect.size.height != window->ns.height) { window->ns.width = (int)contentRect.size.width; window->ns.height = (int)contentRect.size.height; _glfwInputWindowSize(window, (int)contentRect.size.width, (int)contentRect.size.height); } if (window->ns.resizeCallback) window->ns.resizeCallback((GLFWwindow*)window); } - (void)windowDidMove:(NSNotification *)notification { (void)notification; if (window->context.client != GLFW_NO_API) [window->context.nsgl.object update]; if (_glfw.ns.disabledCursorWindow == window) _glfwCenterCursorInContentArea(window); int x, y; _glfwPlatformGetWindowPos(window, &x, &y); _glfwInputWindowPos(window, x, y); } - (void)windowDidChangeOcclusionState:(NSNotification *)notification { (void)notification; _glfwInputWindowOcclusion(window, !([window->ns.object occlusionState] & NSWindowOcclusionStateVisible)); } - (void)windowDidMiniaturize:(NSNotification *)notification { (void)notification; if (window->monitor) releaseMonitor(window); _glfwInputWindowIconify(window, true); } - (void)windowDidDeminiaturize:(NSNotification *)notification { (void)notification; if (window->monitor) acquireMonitor(window); _glfwInputWindowIconify(window, false); } - (void)windowDidBecomeKey:(NSNotification *)notification { (void)notification; if (_glfw.ns.disabledCursorWindow == window) _glfwCenterCursorInContentArea(window); _glfwInputWindowFocus(window, true); updateCursorMode(window); if (window->cursorMode == GLFW_CURSOR_HIDDEN) hideCursor(window); if (_glfw.ns.disabledCursorWindow != window && cursorInContentArea(window)) { double x = 0, y = 0; _glfwPlatformGetCursorPos(window, &x, &y); _glfwInputCursorPos(window, x, y); } // macOS will send a delayed event to update the cursor to arrow after switching desktops. // So we need to delay and update the cursor once after that. [self performSelector:@selector(request_delayed_cursor_update:) withObject:nil afterDelay:0.3]; } - (void)windowDidResignKey:(NSNotification *)notification { (void)notification; if (window->monitor && window->autoIconify) _glfwPlatformIconifyWindow(window); showCursor(window); _glfwInputWindowFocus(window, false); // IME is cancelled when losing the focus if ([window->ns.view hasMarkedText]) { [[window->ns.view inputContext] discardMarkedText]; [window->ns.view unmarkText]; GLFWkeyevent dummy = {.action = GLFW_RELEASE, .ime_state = GLFW_IME_PREEDIT_CHANGED}; _glfwInputKeyboard(window, &dummy); _glfw.ns.text[0] = 0; } } - (void)windowDidChangeScreen:(NSNotification *)notification { (void)notification; if (window->ns.renderFrameRequested && window->ns.renderFrameCallback) { // Ensure that if the window changed its monitor, CVDisplayLink // is running for the new monitor requestRenderFrame(window, window->ns.renderFrameCallback); } } - (void)request_delayed_cursor_update:(id)sender { (void)sender; if (window) window->ns.delayed_cursor_update_requested = true; } - (void)windowWillEnterFullScreen:(NSNotification *)notification { (void)notification; if (window) window->ns.in_fullscreen_transition = true; } - (void)windowDidEnterFullScreen:(NSNotification *)notification { (void)notification; if (window) window->ns.in_fullscreen_transition = false; [self performSelector:@selector(request_delayed_cursor_update:) withObject:nil afterDelay:0.3]; } - (void)windowWillExitFullScreen:(NSNotification *)notification { (void)notification; if (window) window->ns.in_fullscreen_transition = true; } - (void)windowDidExitFullScreen:(NSNotification *)notification { (void)notification; if (window) window->ns.in_fullscreen_transition = false; [self performSelector:@selector(request_delayed_cursor_update:) withObject:nil afterDelay:0.3]; } @end // }}} // Text input context class for the GLFW content view {{{ @interface GLFWTextInputContext : NSTextInputContext @end @implementation GLFWTextInputContext - (void)doCommandBySelector:(SEL)selector { // interpretKeyEvents: May call insertText: or doCommandBySelector:. // With the default macOS keybindings, pressing certain key combinations // (e.g. Ctrl+/, Ctrl+Cmd+Down/Left/Right) will produce a beep sound. debug_key("\n\tTextInputCtx: doCommandBySelector: (%s)\n", [NSStringFromSelector(selector) UTF8String]); } @end // }}} // Content view class for the GLFW window {{{ @interface GLFWContentView : NSView { _GLFWwindow* window; NSTrackingArea* trackingArea; GLFWTextInputContext* input_context; NSMutableAttributedString* markedText; NSRect markedRect; bool marked_text_cleared_by_insert; int in_key_handler; NSString *input_source_at_last_key_event; } - (void) removeGLFWWindow; - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; @end @implementation GLFWContentView - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow { self = [super init]; if (self != nil) { window = initWindow; trackingArea = nil; input_context = [[GLFWTextInputContext alloc] initWithClient:self]; markedText = [[NSMutableAttributedString alloc] init]; markedRect = NSMakeRect(0.0, 0.0, 0.0, 0.0); input_source_at_last_key_event = nil; in_key_handler = 0; [self updateTrackingAreas]; [self registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]]; } return self; } - (void)dealloc { [trackingArea release]; [markedText release]; if (input_source_at_last_key_event) [input_source_at_last_key_event release]; [input_context release]; [super dealloc]; } - (void) removeGLFWWindow { window = NULL; } - (_GLFWwindow*)glfwWindow { return window; } - (BOOL)isOpaque { return window && [window->ns.object isOpaque]; } - (BOOL)canBecomeKeyView { return YES; } - (BOOL)acceptsFirstResponder { return YES; } - (void) viewWillStartLiveResize { if (!window) return; _glfwInputLiveResize(window, true); } - (void)viewDidEndLiveResize { if (!window) return; _glfwInputLiveResize(window, false); } - (BOOL)wantsUpdateLayer { return YES; } - (void)updateLayer { if (!window) return; if (window->context.client != GLFW_NO_API) { @try { [window->context.nsgl.object update]; } @catch (NSException *e) { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to update NSGL Context object with error: %s (%s)", [[e name] UTF8String], [[e reason] UTF8String]); } } _glfwInputWindowDamage(window); } - (void)cursorUpdate:(NSEvent *)event { (void)event; if (window) updateCursorImage(window); } - (BOOL)acceptsFirstMouse:(NSEvent *)event { (void)event; return NO; // changed by Kovid, to follow cocoa platform conventions } - (void)mouseDown:(NSEvent *)event { if (!window) return; _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, translateFlags([event modifierFlags])); } - (void)mouseDragged:(NSEvent *)event { [self mouseMoved:event]; } - (void)mouseUp:(NSEvent *)event { if (!window) return; _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, translateFlags([event modifierFlags])); } - (void)mouseMoved:(NSEvent *)event { if (!window) return; if (window->cursorMode == GLFW_CURSOR_DISABLED) { const double dx = [event deltaX] - window->ns.cursorWarpDeltaX; const double dy = [event deltaY] - window->ns.cursorWarpDeltaY; _glfwInputCursorPos(window, window->virtualCursorPosX + dx, window->virtualCursorPosY + dy); } else { const NSRect contentRect = [window->ns.view frame]; // NOTE: The returned location uses base 0,1 not 0,0 const NSPoint pos = [event locationInWindow]; _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); } window->ns.cursorWarpDeltaX = 0; window->ns.cursorWarpDeltaY = 0; if (window->ns.delayed_cursor_update_requested) { window->ns.delayed_cursor_update_requested = false; if (cursorInContentArea(window)) updateCursorImage(window); } } - (void)rightMouseDown:(NSEvent *)event { if (!window) return; _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS, translateFlags([event modifierFlags])); } - (void)rightMouseDragged:(NSEvent *)event { [self mouseMoved:event]; } - (void)rightMouseUp:(NSEvent *)event { if (!window) return; _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_RELEASE, translateFlags([event modifierFlags])); } - (void)otherMouseDown:(NSEvent *)event { if (!window) return; _glfwInputMouseClick(window, (int) [event buttonNumber], GLFW_PRESS, translateFlags([event modifierFlags])); } - (void)otherMouseDragged:(NSEvent *)event { [self mouseMoved:event]; } - (void)otherMouseUp:(NSEvent *)event { if (!window) return; _glfwInputMouseClick(window, (int) [event buttonNumber], GLFW_RELEASE, translateFlags([event modifierFlags])); } - (void)mouseExited:(NSEvent *)event { (void)event; if (!window) return; _glfwInputCursorEnter(window, false); [[NSCursor arrowCursor] set]; } - (void)mouseEntered:(NSEvent *)event { (void)event; if (!window) return; _glfwInputCursorEnter(window, true); updateCursorImage(window); } - (void)viewDidChangeEffectiveAppearance { static GLFWColorScheme appearance = GLFW_COLOR_SCHEME_NO_PREFERENCE; GLFWColorScheme new_appearance = glfwGetCurrentSystemColorTheme(true); if (new_appearance != appearance) { appearance = new_appearance; _glfwInputColorScheme(appearance, false); } } - (void)viewDidChangeBackingProperties { if (!window) return; const NSRect contentRect = get_window_size_without_border_in_logical_pixels(window); const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; if (fbRect.size.width != window->ns.fbWidth || fbRect.size.height != window->ns.fbHeight) { window->ns.fbWidth = (int)fbRect.size.width; window->ns.fbHeight = (int)fbRect.size.height; _glfwInputFramebufferSize(window, (int)fbRect.size.width, (int)fbRect.size.height); } const float xscale = fbRect.size.width / contentRect.size.width; const float yscale = fbRect.size.height / contentRect.size.height; if (xscale != window->ns.xscale || yscale != window->ns.yscale) { window->ns.xscale = xscale; window->ns.yscale = yscale; _glfwInputWindowContentScale(window, xscale, yscale); if (window->ns.retina && window->ns.layer) [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; } } - (void)drawRect:(NSRect)rect { (void)rect; if (!window) return; _glfwInputWindowDamage(window); } - (void)updateTrackingAreas { if (window && [window->ns.object areCursorRectsEnabled]) [window->ns.object disableCursorRects]; if (trackingArea != nil) { [self removeTrackingArea:trackingArea]; [trackingArea release]; } const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingEnabledDuringMouseDrag | NSTrackingCursorUpdate | NSTrackingInVisibleRect | NSTrackingAssumeInside; trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; [self addTrackingArea:trackingArea]; [super updateTrackingAreas]; } - (NSTextInputContext *)inputContext { return input_context; } static UInt32 convert_cocoa_to_carbon_modifiers(NSUInteger flags) { UInt32 mods = 0; if (flags & NSEventModifierFlagShift) mods |= shiftKey; if (flags & NSEventModifierFlagControl) mods |= controlKey; if (flags & NSEventModifierFlagOption) mods |= optionKey; if (flags & NSEventModifierFlagCommand) mods |= cmdKey; if (flags & NSEventModifierFlagCapsLock) mods |= alphaLock; return (mods >> 8) & 0xFF; } static void convert_utf16_to_utf8(UniChar *src, UniCharCount src_length, char *dest, size_t dest_sz) { CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, src, src_length, kCFAllocatorNull); CFStringGetCString(string, dest, dest_sz, kCFStringEncodingUTF8); CFRelease(string); } static bool alternate_key_is_ok(uint32_t key, uint32_t akey) { return akey > 31 && akey != key && !is_pua_char(akey); } static void add_alternate_keys(GLFWkeyevent *ev, NSEvent *event) { ev->alternate_key = translateKey(ev->native_key, false); if (!alternate_key_is_ok(ev->key, ev->alternate_key)) ev->alternate_key = 0; if (ev->mods & GLFW_MOD_SHIFT) { NSString *ci = [event charactersIgnoringModifiers]; if (ci) { unsigned sz = [ci length]; if (sz > 0) { UniChar buf[2] = {0}; buf[0] = [ci characterAtIndex:0]; if (sz > 1) buf[1] = [ci characterAtIndex:1]; ev->shifted_key = get_first_codepoint(buf, sz); } } if (!alternate_key_is_ok(ev->key, ev->shifted_key)) ev->shifted_key = 0; } } static bool is_ascii_control_char(char x) { return x == 0 || (1 <= x && x <= 31) || x == 127; } - (void)keyDown:(NSEvent *)event { #define CLEAR_PRE_EDIT_TEXT glfw_keyevent.text = NULL; glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent); #define UPDATE_PRE_EDIT_TEXT glfw_keyevent.text = [[markedText string] UTF8String]; glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent); const bool previous_has_marked_text = [self hasMarkedText]; if (input_context && (!input_source_at_last_key_event || ![input_source_at_last_key_event isEqualToString:input_context.selectedKeyboardInputSource])) { if (input_source_at_last_key_event) { debug_key("Input source changed, clearing pre-edit text and resetting deadkey state\n"); GLFWkeyevent dummy = {.action = GLFW_RELEASE, .ime_state = GLFW_IME_PREEDIT_CHANGED}; window->ns.deadKeyState = 0; _glfwInputKeyboard(window, &dummy); // clear pre-edit text [input_source_at_last_key_event release]; input_source_at_last_key_event = nil; } input_source_at_last_key_event = [input_context.selectedKeyboardInputSource retain]; [self unmarkText]; } const unsigned int keycode = [event keyCode]; const NSUInteger flags = [event modifierFlags]; const int mods = translateFlags(flags); const uint32_t key = translateKey(keycode, true); const bool process_text = !_glfw.ignoreOSKeyboardProcessing && (!window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, flags) != 1); _glfw.ns.text[0] = 0; if (keycode == 0x33 /* backspace */ || keycode == 0x35 /* escape */) [self unmarkText]; GLFWkeyevent glfw_keyevent = {.key = key, .native_key = keycode, .native_key_id = keycode, .action = GLFW_PRESS, .mods = mods}; if (!_glfw.ns.unicodeData) { // Using the cocoa API for key handling is disabled, as there is no // reliable way to handle dead keys using it. Only use it if the // keyboard unicode data is not available. if (process_text) { // this will call insertText with the text for this event, if any [self interpretKeyEvents:@[event]]; } } else { static UniChar text[256]; UniCharCount char_count = 0; const bool in_compose_sequence = window->ns.deadKeyState != 0; if (UCKeyTranslate( [(NSData*) _glfw.ns.unicodeData bytes], keycode, kUCKeyActionDown, convert_cocoa_to_carbon_modifiers(flags), LMGetKbdType(), (process_text ? 0 : kUCKeyTranslateNoDeadKeysMask), &(window->ns.deadKeyState), sizeof(text)/sizeof(text[0]), &char_count, text ) != noErr) { debug_key("UCKeyTranslate failed for keycode: 0x%x (%s) %s\n", keycode, safe_name_for_keycode(keycode), format_mods(mods)); window->ns.deadKeyState = 0; return; } debug_key("\x1b[31mPress:\x1b[m native_key: 0x%x (%s) glfw_key: 0x%x %schar_count: %lu deadKeyState: %u repeat: %d ", keycode, safe_name_for_keycode(keycode), key, format_mods(mods), char_count, window->ns.deadKeyState, event.ARepeat); marked_text_cleared_by_insert = false; if (process_text) { in_key_handler = 1; // this will call insertText which will fill up _glfw.ns.text [self interpretKeyEvents:@[event]]; in_key_handler = 0; } else { window->ns.deadKeyState = 0; } if (window->ns.deadKeyState && (char_count == 0 || keycode == 0x75)) { // 0x75 is the delete key which needs to be ignored during a compose sequence debug_key("Sending pre-edit text for dead key (text: %s markedText: %s).\n", format_text(_glfw.ns.text), glfw_keyevent.text); UPDATE_PRE_EDIT_TEXT; return; } if (in_compose_sequence) { debug_key("Clearing pre-edit text at end of compose sequence\n"); CLEAR_PRE_EDIT_TEXT; } } if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // don't send text for ascii control codes debug_key("text: %s glfw_key: %s marked_text: (%s)\n", format_text(_glfw.ns.text), _glfwGetKeyName(key), [[markedText string] UTF8String]); bool bracketed_ime = false; if (!window->ns.deadKeyState) { if ([self hasMarkedText]) { if (!marked_text_cleared_by_insert) { UPDATE_PRE_EDIT_TEXT; } else bracketed_ime = true; } else if (previous_has_marked_text) { CLEAR_PRE_EDIT_TEXT; } if (([self hasMarkedText] || previous_has_marked_text) && !_glfw.ns.text[0]) { // do not pass keys like BACKSPACE while there's pre-edit text, let IME handle it debug_key("Ignoring key press as IME is active and it generated no text\n"); return; } } if (bracketed_ime) { // insertText followed by setMarkedText CLEAR_PRE_EDIT_TEXT; } glfw_keyevent.text = _glfw.ns.text; glfw_keyevent.ime_state = GLFW_IME_NONE; add_alternate_keys(&glfw_keyevent, event); _glfwInputKeyboard(window, &glfw_keyevent); if (bracketed_ime) { // insertText followed by setMarkedText UPDATE_PRE_EDIT_TEXT; } } static bool is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_mask, NSUInteger either_mask) { bool target_pressed = (flags & target_mask) != 0; bool other_pressed = (flags & other_mask) != 0; bool either_pressed = (flags & either_mask) != 0; if (either_pressed != (target_pressed || other_pressed)) return either_pressed; return target_pressed; } - (void)flagsChanged:(NSEvent *)event { int action = GLFW_RELEASE; const char old_first_char = _glfw.ns.text[0]; _glfw.ns.text[0] = 0; const NSUInteger modifierFlags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; const uint32_t key = vk_code_to_functional_key_code([event keyCode]); const unsigned int keycode = [event keyCode]; const int mods = translateFlags(modifierFlags); const bool process_text = !_glfw.ignoreOSKeyboardProcessing && (!window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, modifierFlags) != 1); const char *mod_name = "unknown"; // Code for handling modifier key events copied form SDL_cocoakeyboard.m, with thanks. See IsModifierKeyPressedFunction() #define action_for(modname, target_mask, other_mask, either_mask) action = is_modifier_pressed([event modifierFlags], target_mask, other_mask, either_mask) ? GLFW_PRESS : GLFW_RELEASE; mod_name = #modname; break; switch(key) { case GLFW_FKEY_CAPS_LOCK: mod_name = "capslock"; action = modifierFlags & NSEventModifierFlagCapsLock ? GLFW_PRESS : GLFW_RELEASE; break; case GLFW_FKEY_LEFT_SUPER: action_for(super, NX_DEVICELCMDKEYMASK, NX_DEVICERCMDKEYMASK, NX_COMMANDMASK); case GLFW_FKEY_RIGHT_SUPER: action_for(super, NX_DEVICERCMDKEYMASK, NX_DEVICELCMDKEYMASK, NX_COMMANDMASK); case GLFW_FKEY_LEFT_CONTROL: action_for(ctrl, NX_DEVICELCTLKEYMASK, NX_DEVICERCTLKEYMASK, NX_CONTROLMASK); case GLFW_FKEY_RIGHT_CONTROL: action_for(ctrl, NX_DEVICERCTLKEYMASK, NX_DEVICELCTLKEYMASK, NX_CONTROLMASK); case GLFW_FKEY_LEFT_ALT: action_for(alt, NX_DEVICELALTKEYMASK, NX_DEVICERALTKEYMASK, NX_ALTERNATEMASK); case GLFW_FKEY_RIGHT_ALT: action_for(alt, NX_DEVICERALTKEYMASK, NX_DEVICELALTKEYMASK, NX_ALTERNATEMASK); case GLFW_FKEY_LEFT_SHIFT: action_for(shift, NX_DEVICELSHIFTKEYMASK, NX_DEVICERSHIFTKEYMASK, NX_SHIFTMASK); case GLFW_FKEY_RIGHT_SHIFT: action_for(shift, NX_DEVICERSHIFTKEYMASK, NX_DEVICELSHIFTKEYMASK, NX_SHIFTMASK); default: return; } #undef action_for GLFWkeyevent glfw_keyevent = {.key = key, .native_key = keycode, .native_key_id = keycode, .action = action, .mods = mods}; debug_key("\x1b[33mflagsChanged:\x1b[m modifier: %s native_key: 0x%x (%s) glfw_key: 0x%x %s\n", mod_name, keycode, safe_name_for_keycode(keycode), key, format_mods(mods)); marked_text_cleared_by_insert = false; if (process_text && input_context) { // this will call insertText which will fill up _glfw.ns.text in_key_handler = 2; [input_context handleEvent:event]; in_key_handler = 0; if (marked_text_cleared_by_insert) { debug_key("Clearing pre-edit text because insertText called from flagsChanged\n"); CLEAR_PRE_EDIT_TEXT; if (_glfw.ns.text[0]) glfw_keyevent.text = _glfw.ns.text; else _glfw.ns.text[0] = old_first_char; } } glfw_keyevent.ime_state = GLFW_IME_NONE; _glfwInputKeyboard(window, &glfw_keyevent); } - (void)keyUp:(NSEvent *)event { const uint32_t keycode = [event keyCode]; const uint32_t key = translateKey(keycode, true); const int mods = translateFlags([event modifierFlags]); GLFWkeyevent glfw_keyevent = {.key = key, .native_key = keycode, .native_key_id = keycode, .action = GLFW_RELEASE, .mods = mods}; add_alternate_keys(&glfw_keyevent, event); debug_key("\x1b[32mRelease:\x1b[m native_key: 0x%x (%s) glfw_key: 0x%x %s\n", keycode, safe_name_for_keycode(keycode), key, format_mods(mods)); _glfwInputKeyboard(window, &glfw_keyevent); } #undef CLEAR_PRE_EDIT_TEXT #undef UPDATE_PRE_EDIT_TEXT - (void)scrollWheel:(NSEvent *)event { double deltaX = [event scrollingDeltaX]; double deltaY = [event scrollingDeltaY]; int flags = [event hasPreciseScrollingDeltas] ? 1 : 0; if (flags) { float xscale = 1, yscale = 1; _glfwPlatformGetWindowContentScale(window, &xscale, &yscale); if (xscale > 0) deltaX *= xscale; if (yscale > 0) deltaY *= yscale; } switch([event momentumPhase]) { case NSEventPhaseBegan: flags |= (1 << 1); break; case NSEventPhaseStationary: flags |= (2 << 1); break; case NSEventPhaseChanged: flags |= (3 << 1); break; case NSEventPhaseEnded: flags |= (4 << 1); break; case NSEventPhaseCancelled: flags |= (5 << 1); break; case NSEventPhaseMayBegin: flags |= (6 << 1); break; case NSEventPhaseNone: default: break; } _glfwInputScroll(window, deltaX, deltaY, flags, translateFlags([event modifierFlags])); } - (NSDragOperation)draggingEntered:(id )sender { (void)sender; // HACK: We don't know what to say here because we don't know what the // application wants to do with the paths return NSDragOperationGeneric; } - (BOOL)performDragOperation:(id )sender { const NSRect contentRect = [window->ns.view frame]; // NOTE: The returned location uses base 0,1 not 0,0 const NSPoint pos = [sender draggingLocation]; _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); NSPasteboard* pasteboard = [sender draggingPasteboard]; NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; NSArray* objs = [pasteboard readObjectsForClasses:@[[NSURL class], [NSString class]] options:options]; if (!objs) return NO; const NSUInteger count = [objs count]; NSMutableString *uri_list = [NSMutableString stringWithCapacity:4096]; // auto-released if (count) { for (NSUInteger i = 0; i < count; i++) { id obj = objs[i]; if ([obj isKindOfClass:[NSURL class]]) { NSURL *url = (NSURL*)obj; if ([uri_list length] > 0) [uri_list appendString:@("\n")]; if (url.fileURL) [uri_list appendString:url.filePathURL.absoluteString]; else [uri_list appendString:url.absoluteString]; } else if ([obj isKindOfClass:[NSString class]]) { const char *text = [obj UTF8String]; _glfwInputDrop(window, "text/plain;charset=utf-8", text, strlen(text)); } else { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Object is neither a URL nor a string"); } } } if ([uri_list length] > 0) _glfwInputDrop(window, "text/uri-list", uri_list.UTF8String, strlen(uri_list.UTF8String)); return YES; } - (BOOL)hasMarkedText { return [markedText length] > 0; } - (NSRange)markedRange { if ([markedText length] > 0) return NSMakeRange(0, [markedText length] - 1); else return kEmptyRange; } - (NSRange)selectedRange { return kEmptyRange; } - (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { const char *s = polymorphic_string_as_utf8(string); debug_key("\n\tsetMarkedText: %s selectedRange: (%lu, %lu) replacementRange: (%lu, %lu)\n", s, selectedRange.location, selectedRange.length, replacementRange.location, replacementRange.length); if (string == nil || !s[0]) { bool had_marked_text = [self hasMarkedText]; [self unmarkText]; if (had_marked_text && (!in_key_handler || in_key_handler == 2)) { debug_key("Clearing pre-edit because setMarkedText called from %s\n", in_key_handler ? "flagsChanged" : "event loop"); GLFWkeyevent glfw_keyevent = {.ime_state = GLFW_IME_PREEDIT_CHANGED}; _glfwInputKeyboard(window, &glfw_keyevent); _glfw.ns.text[0] = 0; } return; } if ([string isKindOfClass:[NSAttributedString class]]) { if (((NSMutableAttributedString*)string).length == 0) { [self unmarkText]; return; } [markedText release]; markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string]; } else { if (((NSString*)string).length == 0) { [self unmarkText]; return; } [markedText release]; markedText = [[NSMutableAttributedString alloc] initWithString:string]; } if (!in_key_handler || in_key_handler == 2) { debug_key("Updating IME text in kitty from setMarkedText called from %s: %s\n", in_key_handler ? "flagsChanged" : "event loop", _glfw.ns.text); GLFWkeyevent glfw_keyevent = {.text=[[markedText string] UTF8String], .ime_state = GLFW_IME_PREEDIT_CHANGED}; _glfwInputKeyboard(window, &glfw_keyevent); _glfw.ns.text[0] = 0; } } - (void)unmarkText { [[markedText mutableString] setString:@""]; } void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) { [w->ns.view updateIMEStateFor: ev->type focused:(bool)ev->focused]; } - (void)updateIMEStateFor:(GLFWIMEUpdateType)which focused:(bool)focused { if (which == GLFW_IME_UPDATE_FOCUS && !focused && [self hasMarkedText] && window) { [input_context discardMarkedText]; [self unmarkText]; GLFWkeyevent glfw_keyevent = {.ime_state = GLFW_IME_PREEDIT_CHANGED}; _glfwInputKeyboard(window, &glfw_keyevent); _glfw.ns.text[0] = 0; } if (which != GLFW_IME_UPDATE_CURSOR_POSITION) return; if (_glfwPlatformWindowFocused(window)) [[window->ns.view inputContext] invalidateCharacterCoordinates]; } - (NSArray*)validAttributesForMarkedText { return [NSArray array]; } - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange { (void)range; (void)actualRange; return nil; } - (NSUInteger)characterIndexForPoint:(NSPoint)point { (void)point; return 0; } - (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange { (void)range; (void)actualRange; if (_glfw.callbacks.get_ime_cursor_position) { GLFWIMEUpdateEvent ev = { .type = GLFW_IME_UPDATE_CURSOR_POSITION }; if (window && _glfw.callbacks.get_ime_cursor_position((GLFWwindow*)window, &ev)) { const CGFloat left = (CGFloat)ev.cursor.left / window->ns.xscale; const CGFloat top = (CGFloat)ev.cursor.top / window->ns.yscale; const CGFloat cellWidth = (CGFloat)ev.cursor.width / window->ns.xscale; const CGFloat cellHeight = (CGFloat)ev.cursor.height / window->ns.yscale; debug_key("updateIMEPosition: left=%f, top=%f, width=%f, height=%f\n", left, top, cellWidth, cellHeight); const NSRect frame = [window->ns.view frame]; const NSRect rectInView = NSMakeRect(left, frame.size.height - top - cellHeight, cellWidth, cellHeight); markedRect = [window->ns.object convertRectToScreen: rectInView]; } } return markedRect; } - (void)insertText:(id)string replacementRange:(NSRange)replacementRange { const char *utf8 = polymorphic_string_as_utf8(string); debug_key("\n\tinsertText: %s replacementRange: (%lu, %lu)\n", utf8, replacementRange.location, replacementRange.length); if ([self hasMarkedText] && !is_ascii_control_char(utf8[0])) { [self unmarkText]; marked_text_cleared_by_insert = true; if (!in_key_handler) { debug_key("Clearing pre-edit because insertText called from event loop\n"); GLFWkeyevent glfw_keyevent = {.ime_state = GLFW_IME_PREEDIT_CHANGED}; _glfwInputKeyboard(window, &glfw_keyevent); _glfw.ns.text[0] = 0; } } // insertText can be called multiple times for a single key event size_t existing_length = strnlen(_glfw.ns.text, sizeof(_glfw.ns.text)); size_t required_length = strlen(utf8) + 1; size_t available_length = sizeof(_glfw.ns.text) - existing_length; if (available_length >= required_length) { memcpy(_glfw.ns.text + existing_length, utf8, required_length); // copies the null terminator from utf8 as well _glfw.ns.text[sizeof(_glfw.ns.text) - 1] = 0; if ((!in_key_handler || in_key_handler == 2) && _glfw.ns.text[0]) { if (!is_ascii_control_char(_glfw.ns.text[0])) { debug_key("Sending text to kitty from insertText called from %s: %s\n", in_key_handler ? "flagsChanged" : "event loop", _glfw.ns.text); GLFWkeyevent glfw_keyevent = {.text=_glfw.ns.text, .ime_state=GLFW_IME_COMMIT_TEXT}; _glfwInputKeyboard(window, &glfw_keyevent); } _glfw.ns.text[0] = 0; } } } - (void)doCommandBySelector:(SEL)selector { debug_key("\n\tdoCommandBySelector: (%s)\n", [NSStringFromSelector(selector) UTF8String]); } - (BOOL)isAccessibilityElement { return YES; } - (BOOL)isAccessibilitySelectorAllowed:(SEL)selector { if (selector == @selector(accessibilityRole) || selector == @selector(accessibilitySelectedText)) return YES; return NO; } #if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) - (NSAccessibilityRole)accessibilityRole { return NSAccessibilityTextAreaRole; } #endif - (NSString *)accessibilitySelectedText { NSString *text = nil; if (_glfw.callbacks.get_current_selection) { char *s = _glfw.callbacks.get_current_selection(); if (s) { text = [NSString stringWithUTF8String:s]; free(s); } } return text; } // // Support services receiving "public.utf8-plain-text" and "NSStringPboardType" - (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType { if ( (!sendType || [sendType isEqual:NSPasteboardTypeString] || [sendType isEqual:@"NSStringPboardType"]) && (!returnType || [returnType isEqual:NSPasteboardTypeString] || [returnType isEqual:@"NSStringPboardType"]) ) { if (_glfw.callbacks.has_current_selection && _glfw.callbacks.has_current_selection()) return self; } return [super validRequestorForSendType:sendType returnType:returnType]; } // Selected text as input to be sent to Services // For example, after selecting an absolute path, open the global menu bar kitty->Services and click `Show in Finder`. - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard types:(NSArray *)types { if (!_glfw.callbacks.get_current_selection) return NO; char *text = _glfw.callbacks.get_current_selection(); if (!text) return NO; BOOL ans = NO; if (text[0]) { if ([types containsObject:NSPasteboardTypeString] == YES) { [pboard declareTypes:@[NSPasteboardTypeString] owner:self]; ans = [pboard setString:@(text) forType:NSPasteboardTypeString]; } else if ([types containsObject:@"NSStringPboardType"] == YES) { [pboard declareTypes:@[@"NSStringPboardType"] owner:self]; ans = [pboard setString:@(text) forType:@"NSStringPboardType"]; } free(text); } return ans; } // Service output to be handled // For example, open System Settings->Keyboard->Keyboard Shortcuts->Services->Text, enable `Convert Text to Full Width`, select some text and execute the service. - (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard { NSString* text = nil; NSArray *types = [pboard types]; if ([types containsObject:NSPasteboardTypeString] == YES) { text = [pboard stringForType:NSPasteboardTypeString]; // public.utf8-plain-text } else if ([types containsObject:@"NSStringPboardType"] == YES) { text = [pboard stringForType:@"NSStringPboardType"]; // for older services (need re-encode?) } else { return NO; } if (text && [text length] > 0) { // The service wants us to replace the selection, but we can't replace anything but insert text. const char *utf8 = polymorphic_string_as_utf8(text); debug_key("Sending text received in readSelectionFromPasteboard as key event\n"); GLFWkeyevent glfw_keyevent = {.text=utf8, .ime_state=GLFW_IME_COMMIT_TEXT}; _glfwInputKeyboard(window, &glfw_keyevent); // Restore pre-edit text after inserting the received text if ([self hasMarkedText]) { glfw_keyevent.text = [[markedText string] UTF8String]; glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent); } return YES; } return NO; } @end // }}} // GLFW window class {{{ @interface GLFWWindow : NSWindow { _GLFWwindow* glfw_window; } - (instancetype)initWithGlfwWindow:(NSRect)contentRect styleMask:(NSWindowStyleMask)style backing:(NSBackingStoreType)backingStoreType initWindow:(_GLFWwindow *)initWindow; - (void) removeGLFWWindow; @end @implementation GLFWWindow - (instancetype)initWithGlfwWindow:(NSRect)contentRect styleMask:(NSWindowStyleMask)style backing:(NSBackingStoreType)backingStoreType initWindow:(_GLFWwindow *)initWindow { self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:NO]; if (self != nil) { glfw_window = initWindow; self.tabbingMode = NSWindowTabbingModeDisallowed; } return self; } - (void) removeGLFWWindow { glfw_window = NULL; } - (BOOL)validateMenuItem:(NSMenuItem *)item { if (item.action == @selector(performMiniaturize:)) return YES; return [super validateMenuItem:item]; } - (void)performMiniaturize:(id)sender { if (glfw_window && (!glfw_window->decorated || glfw_window->ns.titlebar_hidden)) [self miniaturize:self]; else [super performMiniaturize:sender]; } - (BOOL)canBecomeKeyWindow { // Required for NSWindowStyleMaskBorderless windows return YES; } - (BOOL)canBecomeMainWindow { return YES; } static void update_titlebar_button_visibility_after_fullscreen_transition(_GLFWwindow* w, bool traditional, bool made_fullscreen) { // Update window button visibility if (w->ns.titlebar_hidden) { NSWindow *window = w->ns.object; // The hidden buttons might be automatically reset to be visible after going full screen // to show up in the auto-hide title bar, so they need to be set back to hidden. BOOL button_hidden = YES; // When title bar is configured to be hidden, it should be shown with buttons (auto-hide) after going to full screen. if (!traditional) { button_hidden = (BOOL) !made_fullscreen; } [[window standardWindowButton: NSWindowCloseButton] setHidden:button_hidden]; [[window standardWindowButton: NSWindowMiniaturizeButton] setHidden:button_hidden]; [[window standardWindowButton: NSWindowZoomButton] setHidden:button_hidden]; } } - (void)toggleFullScreen:(nullable id)sender { if (glfw_window) { if (glfw_window->ns.in_fullscreen_transition) return; if (glfw_window->ns.toggleFullscreenCallback && glfw_window->ns.toggleFullscreenCallback((GLFWwindow*)glfw_window) == 1) return; glfw_window->ns.in_fullscreen_transition = true; } NSWindowStyleMask sm = [self styleMask]; bool is_fullscreen_already = (sm & NSWindowStyleMaskFullScreen) != 0; // When resizeIncrements is set, Cocoa cannot restore the original window size after returning from fullscreen. const NSSize original = [self resizeIncrements]; [self setResizeIncrements:NSMakeSize(1.0, 1.0)]; [super toggleFullScreen:sender]; [self setResizeIncrements:original]; // When the window decoration is hidden, toggling fullscreen causes the style mask to be changed, // and causes the first responder to be cleared. if (glfw_window && !glfw_window->decorated && glfw_window->ns.view) [self makeFirstResponder:glfw_window->ns.view]; update_titlebar_button_visibility_after_fullscreen_transition(glfw_window, false, !is_fullscreen_already); } - (void)zoom:(id)sender { if (![self isZoomed]) { const NSSize original = [self resizeIncrements]; [self setResizeIncrements:NSMakeSize(1.0, 1.0)]; [super zoom:sender]; [self setResizeIncrements:original]; } else { [super zoom:sender]; } } @end // }}} // Create the Cocoa window // static bool createNativeWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWfbconfig* fbconfig) { window->ns.delegate = [[GLFWWindowDelegate alloc] initWithGlfwWindow:window]; if (window->ns.delegate == nil) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window delegate"); return false; } NSRect contentRect; if (window->monitor) { GLFWvidmode mode; int xpos, ypos; _glfwPlatformGetVideoMode(window->monitor, &mode); _glfwPlatformGetMonitorPos(window->monitor, &xpos, &ypos); contentRect = NSMakeRect(xpos, ypos, mode.width, mode.height); } else contentRect = NSMakeRect(0, 0, wndconfig->width, wndconfig->height); window->ns.object = [[GLFWWindow alloc] initWithGlfwWindow:contentRect styleMask:getStyleMask(window) backing:NSBackingStoreBuffered initWindow:window ]; if (window->ns.object == nil) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window"); return false; } if (window->monitor) [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; else { [(NSWindow*) window->ns.object center]; CGRect screen_frame = [[(NSWindow*) window->ns.object screen] frame]; if (CGRectContainsPoint(screen_frame, _glfw.ns.cascadePoint) || CGPointEqualToPoint(CGPointZero, _glfw.ns.cascadePoint)) { _glfw.ns.cascadePoint = NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint: NSPointFromCGPoint(_glfw.ns.cascadePoint)]); } else { _glfw.ns.cascadePoint = CGPointZero; } if (wndconfig->resizable) { const NSWindowCollectionBehavior behavior = NSWindowCollectionBehaviorFullScreenPrimary | NSWindowCollectionBehaviorManaged; [window->ns.object setCollectionBehavior:behavior]; } if (wndconfig->floating) [window->ns.object setLevel:NSFloatingWindowLevel]; if (wndconfig->maximized) [window->ns.object zoom:nil]; } if (strlen(wndconfig->ns.frameName)) [window->ns.object setFrameAutosaveName:@(wndconfig->ns.frameName)]; window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window]; window->ns.retina = wndconfig->ns.retina; if (fbconfig->transparent) { [window->ns.object setOpaque:NO]; [window->ns.object setHasShadow:NO]; [window->ns.object setBackgroundColor:[NSColor clearColor]]; } [window->ns.object setContentView:window->ns.view]; [window->ns.object makeFirstResponder:window->ns.view]; [window->ns.object setTitle:@(wndconfig->title)]; [window->ns.object setDelegate:window->ns.delegate]; [window->ns.object setAcceptsMouseMovedEvents:YES]; [window->ns.object setRestorable:NO]; _glfwPlatformGetWindowSize(window, &window->ns.width, &window->ns.height); _glfwPlatformGetFramebufferSize(window, &window->ns.fbWidth, &window->ns.fbHeight); if (wndconfig->blur_radius > 0) _glfwPlatformSetWindowBlur(window, wndconfig->blur_radius); return true; } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { window->ns.deadKeyState = 0; if (!_glfw.ns.finishedLaunching) { [NSApp run]; _glfw.ns.finishedLaunching = true; } if (!createNativeWindow(window, wndconfig, fbconfig)) return false; switch((GlfwCocoaColorSpaces)wndconfig->ns.color_space) { case SRGB_COLORSPACE: [window->ns.object setColorSpace:[NSColorSpace sRGBColorSpace]]; break; case DISPLAY_P3_COLORSPACE: [window->ns.object setColorSpace:[NSColorSpace displayP3ColorSpace]]; break; case DEFAULT_COLORSPACE: break; } if (ctxconfig->client != GLFW_NO_API) { if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) { if (!_glfwInitNSGL()) return false; if (!_glfwCreateContextNSGL(window, ctxconfig, fbconfig)) return false; } else if (ctxconfig->source == GLFW_EGL_CONTEXT_API) { // EGL implementation on macOS use CALayer* EGLNativeWindowType so we // need to get the layer for EGL window surface creation. [window->ns.view setWantsLayer:YES]; window->ns.layer = [window->ns.view layer]; if (!_glfwInitEGL()) return false; if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) return false; } else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) { if (!_glfwInitOSMesa()) return false; if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) return false; } } if (window->monitor) { // Do not show the window here until after setting the window size, maximized state, and full screen // _glfwPlatformShowWindow(window); // _glfwPlatformFocusWindow(window); acquireMonitor(window); } return true; } void _glfwPlatformDestroyWindow(_GLFWwindow* window) { if (_glfw.ns.disabledCursorWindow == window) _glfw.ns.disabledCursorWindow = NULL; [window->ns.object orderOut:nil]; if (window->monitor) releaseMonitor(window); if (window->context.destroy) window->context.destroy(window); [window->ns.object setDelegate:nil]; [window->ns.delegate release]; window->ns.delegate = nil; [window->ns.view removeGLFWWindow]; [window->ns.view release]; window->ns.view = nil; [window->ns.object removeGLFWWindow]; [window->ns.object close]; window->ns.object = nil; } void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) { if (!title) return; NSString* string = @(title); if (!string) return; // the runtime failed to convert title to an NSString [window->ns.object setTitle:string]; // HACK: Set the miniwindow title explicitly as setTitle: doesn't update it // if the window lacks NSWindowStyleMaskTitled [window->ns.object setMiniwindowTitle:string]; } void _glfwPlatformSetWindowIcon(_GLFWwindow* window UNUSED, int count UNUSED, const GLFWimage* images UNUSED) { _glfwInputError(GLFW_FEATURE_UNAVAILABLE, "Cocoa: Regular windows do not have icons on macOS"); } void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) { const NSRect contentRect = get_window_size_without_border_in_logical_pixels(window); if (xpos) *xpos = (int)contentRect.origin.x; if (ypos) *ypos = (int)_glfwTransformYNS(contentRect.origin.y + contentRect.size.height - 1); } void _glfwPlatformSetWindowPos(_GLFWwindow* window, int x, int y) { const NSRect contentRect = get_window_size_without_border_in_logical_pixels(window); const NSRect dummyRect = NSMakeRect(x, _glfwTransformYNS(y + contentRect.size.height - 1), 0, 0); const NSRect frameRect = [window->ns.object frameRectForContentRect:dummyRect]; [window->ns.object setFrameOrigin:frameRect.origin]; } void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) { const NSRect contentRect = get_window_size_without_border_in_logical_pixels(window); if (width) *width = (int)contentRect.size.width; if (height) *height = (int)contentRect.size.height; } void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) { if (window->monitor) { if (window->monitor->window == window) acquireMonitor(window); } else { // Disable window resizing in fullscreen. if ([window->ns.object styleMask] & NSWindowStyleMaskFullScreen || window->ns.in_traditional_fullscreen) return; NSRect contentRect = get_window_size_without_border_in_logical_pixels(window); contentRect.origin.y += contentRect.size.height - height; contentRect.size = NSMakeSize(width, height); [window->ns.object setFrame:[window->ns.object frameRectForContentRect:contentRect] display:YES]; } } void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, int minwidth, int minheight, int maxwidth, int maxheight) { if (minwidth == GLFW_DONT_CARE || minheight == GLFW_DONT_CARE) [window->ns.object setContentMinSize:NSMakeSize(0, 0)]; else [window->ns.object setContentMinSize:NSMakeSize(minwidth, minheight)]; if (maxwidth == GLFW_DONT_CARE || maxheight == GLFW_DONT_CARE) [window->ns.object setContentMaxSize:NSMakeSize(DBL_MAX, DBL_MAX)]; else [window->ns.object setContentMaxSize:NSMakeSize(maxwidth, maxheight)]; } void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) { if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE) [window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)]; else [window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)]; } void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr) { if (widthincr != GLFW_DONT_CARE && heightincr != GLFW_DONT_CARE) { float xscale = 1, yscale = 1; _glfwPlatformGetWindowContentScale(window, &xscale, &yscale); [window->ns.object setResizeIncrements:NSMakeSize(widthincr / xscale, heightincr / yscale)]; } else { [window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)]; } } void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) { const NSRect contentRect = get_window_size_without_border_in_logical_pixels(window); const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; if (width) *width = (int) fbRect.size.width; if (height) *height = (int) fbRect.size.height; } void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom) { const NSRect contentRect = get_window_size_without_border_in_logical_pixels(window); const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect]; if (left) *left = (int)(contentRect.origin.x - frameRect.origin.x); if (top) *top = (int)(frameRect.origin.y + frameRect.size.height - contentRect.origin.y - contentRect.size.height); if (right) *right = (int)(frameRect.origin.x + frameRect.size.width - contentRect.origin.x - contentRect.size.width); if (bottom) *bottom = (int)(contentRect.origin.y - frameRect.origin.y); } void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, float* xscale, float* yscale) { const NSRect points = get_window_size_without_border_in_logical_pixels(window); const NSRect pixels = [window->ns.view convertRectToBacking:points]; if (xscale) *xscale = (float) (pixels.size.width / points.size.width); if (yscale) *yscale = (float) (pixels.size.height / points.size.height); } monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED) { return s_double_to_monotonic_t([NSEvent doubleClickInterval]); } void _glfwPlatformIconifyWindow(_GLFWwindow* window) { [window->ns.object miniaturize:nil]; } void _glfwPlatformRestoreWindow(_GLFWwindow* window) { if ([window->ns.object isMiniaturized]) [window->ns.object deminiaturize:nil]; else if ([window->ns.object isZoomed]) [window->ns.object zoom:nil]; } void _glfwPlatformMaximizeWindow(_GLFWwindow* window) { if (![window->ns.object isZoomed]) { [window->ns.object zoom:nil]; } } void _glfwPlatformShowWindow(_GLFWwindow* window) { [window->ns.object orderFront:nil]; } void _glfwPlatformHideWindow(_GLFWwindow* window) { [window->ns.object orderOut:nil]; } void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED) { [NSApp requestUserAttention:NSInformationalRequest]; } int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED) { NSBeep(); return true; } void _glfwPlatformFocusWindow(_GLFWwindow* window) { // Make us the active application // HACK: This is here to prevent applications using only hidden windows from // being activated, but should probably not be done every time any // window is shown [NSApp activateIgnoringOtherApps:YES]; [window->ns.object makeKeyAndOrderFront:nil]; } void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate UNUSED) { if (window->monitor == monitor) { if (monitor) { if (monitor->window == window) acquireMonitor(window); } else { const NSRect contentRect = NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), width, height); const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect styleMask:getStyleMask(window)]; [window->ns.object setFrame:frameRect display:YES]; } return; } if (window->monitor) releaseMonitor(window); _glfwInputWindowMonitor(window, monitor); const NSUInteger styleMask = getStyleMask(window); [window->ns.object setStyleMask:styleMask]; // HACK: Changing the style mask can cause the first responder to be cleared [window->ns.object makeFirstResponder:window->ns.view]; if (window->monitor) { [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; [window->ns.object setHasShadow:NO]; acquireMonitor(window); } else { NSRect contentRect = NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), width, height); NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect styleMask:styleMask]; [window->ns.object setFrame:frameRect display:YES]; if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE) { [window->ns.object setContentAspectRatio:NSMakeSize(window->numer, window->denom)]; } if (window->minwidth != GLFW_DONT_CARE && window->minheight != GLFW_DONT_CARE) { [window->ns.object setContentMinSize:NSMakeSize(window->minwidth, window->minheight)]; } if (window->maxwidth != GLFW_DONT_CARE && window->maxheight != GLFW_DONT_CARE) { [window->ns.object setContentMaxSize:NSMakeSize(window->maxwidth, window->maxheight)]; } if (window->floating) [window->ns.object setLevel:NSFloatingWindowLevel]; else [window->ns.object setLevel:NSNormalWindowLevel]; [window->ns.object setHasShadow:YES]; // HACK: Clearing NSWindowStyleMaskTitled resets and disables the window // title property but the miniwindow title property is unaffected [window->ns.object setTitle:[window->ns.object miniwindowTitle]]; } } int _glfwPlatformWindowFocused(_GLFWwindow* window) { return [window->ns.object isKeyWindow]; } int _glfwPlatformWindowOccluded(_GLFWwindow* window) { return !([window->ns.object occlusionState] & NSWindowOcclusionStateVisible); } int _glfwPlatformWindowIconified(_GLFWwindow* window) { return [window->ns.object isMiniaturized]; } int _glfwPlatformWindowVisible(_GLFWwindow* window) { return [window->ns.object isVisible]; } int _glfwPlatformWindowMaximized(_GLFWwindow* window) { return [window->ns.object isZoomed]; } int _glfwPlatformWindowHovered(_GLFWwindow* window) { const NSPoint point = [NSEvent mouseLocation]; if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] != [window->ns.object windowNumber]) { return false; } return NSMouseInRect(point, [window->ns.object convertRectToScreen:[window->ns.view frame]], NO); } int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) { return ![window->ns.object isOpaque] && ![window->ns.view isOpaque]; } void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled UNUSED) { [window->ns.object setStyleMask:getStyleMask(window)]; [window->ns.object makeFirstResponder:window->ns.view]; } void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled UNUSED) { [window->ns.object setStyleMask:getStyleMask(window)]; [window->ns.object makeFirstResponder:window->ns.view]; } void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled) { if (enabled) [window->ns.object setLevel:NSFloatingWindowLevel]; else [window->ns.object setLevel:NSNormalWindowLevel]; } void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled) { [window->ns.object setIgnoresMouseEvents:enabled]; } float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) { return (float) [window->ns.object alphaValue]; } void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) { [window->ns.object setAlphaValue:opacity]; } void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window UNUSED, bool enabled UNUSED) { _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, "Cocoa: Raw mouse motion not yet implemented"); } bool _glfwPlatformRawMouseMotionSupported(void) { return false; } void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) { const NSRect contentRect = [window->ns.view frame]; // NOTE: The returned location uses base 0,1 not 0,0 const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; if (xpos) *xpos = pos.x; if (ypos) *ypos = contentRect.size.height - pos.y; } void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) { updateCursorImage(window); const NSRect contentRect = [window->ns.view frame]; // NOTE: The returned location uses base 0,1 not 0,0 const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; window->ns.cursorWarpDeltaX += x - pos.x; window->ns.cursorWarpDeltaY += y - contentRect.size.height + pos.y; if (window->monitor) { CGDisplayMoveCursorToPoint(window->monitor->ns.displayID, CGPointMake(x, y)); } else { const NSRect localRect = NSMakeRect(x, contentRect.size.height - y - 1, 0, 0); const NSRect globalRect = [window->ns.object convertRectToScreen:localRect]; const NSPoint globalPoint = globalRect.origin; CGWarpMouseCursorPosition(CGPointMake(globalPoint.x, _glfwTransformYNS(globalPoint.y))); } } void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode UNUSED) { if (_glfwPlatformWindowFocused(window)) updateCursorMode(window); } const char* _glfwPlatformGetNativeKeyName(int keycode) { UInt32 deadKeyState = 0; UniChar characters[8]; UniCharCount characterCount = 0; if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes], keycode, kUCKeyActionDisplay, 0, LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, sizeof(characters) / sizeof(characters[0]), &characterCount, characters) != noErr) { return NULL; } if (!characterCount) return NULL; convert_utf16_to_utf8(characters, characterCount, _glfw.ns.keyName, sizeof(_glfw.ns.keyName)); return _glfw.ns.keyName; } int _glfwPlatformGetNativeKeyForKey(uint32_t glfw_key) { if (GLFW_FKEY_FIRST <= glfw_key && glfw_key <= GLFW_FKEY_LAST) { // {{{ switch(glfw_key) { /* start functional to macu (auto generated by gen-key-constants.py do not edit) */ case GLFW_FKEY_ENTER: return NSCarriageReturnCharacter; case GLFW_FKEY_TAB: return NSTabCharacter; case GLFW_FKEY_BACKSPACE: return NSBackspaceCharacter; case GLFW_FKEY_INSERT: return NSInsertFunctionKey; case GLFW_FKEY_DELETE: return NSDeleteFunctionKey; case GLFW_FKEY_LEFT: return NSLeftArrowFunctionKey; case GLFW_FKEY_RIGHT: return NSRightArrowFunctionKey; case GLFW_FKEY_UP: return NSUpArrowFunctionKey; case GLFW_FKEY_DOWN: return NSDownArrowFunctionKey; case GLFW_FKEY_PAGE_UP: return NSPageUpFunctionKey; case GLFW_FKEY_PAGE_DOWN: return NSPageDownFunctionKey; case GLFW_FKEY_HOME: return NSHomeFunctionKey; case GLFW_FKEY_END: return NSEndFunctionKey; case GLFW_FKEY_SCROLL_LOCK: return NSScrollLockFunctionKey; case GLFW_FKEY_NUM_LOCK: return NSClearLineFunctionKey; case GLFW_FKEY_PRINT_SCREEN: return NSPrintScreenFunctionKey; case GLFW_FKEY_PAUSE: return NSPauseFunctionKey; case GLFW_FKEY_MENU: return NSMenuFunctionKey; case GLFW_FKEY_F1: return NSF1FunctionKey; case GLFW_FKEY_F2: return NSF2FunctionKey; case GLFW_FKEY_F3: return NSF3FunctionKey; case GLFW_FKEY_F4: return NSF4FunctionKey; case GLFW_FKEY_F5: return NSF5FunctionKey; case GLFW_FKEY_F6: return NSF6FunctionKey; case GLFW_FKEY_F7: return NSF7FunctionKey; case GLFW_FKEY_F8: return NSF8FunctionKey; case GLFW_FKEY_F9: return NSF9FunctionKey; case GLFW_FKEY_F10: return NSF10FunctionKey; case GLFW_FKEY_F11: return NSF11FunctionKey; case GLFW_FKEY_F12: return NSF12FunctionKey; case GLFW_FKEY_F13: return NSF13FunctionKey; case GLFW_FKEY_F14: return NSF14FunctionKey; case GLFW_FKEY_F15: return NSF15FunctionKey; case GLFW_FKEY_F16: return NSF16FunctionKey; case GLFW_FKEY_F17: return NSF17FunctionKey; case GLFW_FKEY_F18: return NSF18FunctionKey; case GLFW_FKEY_F19: return NSF19FunctionKey; case GLFW_FKEY_F20: return NSF20FunctionKey; case GLFW_FKEY_F21: return NSF21FunctionKey; case GLFW_FKEY_F22: return NSF22FunctionKey; case GLFW_FKEY_F23: return NSF23FunctionKey; case GLFW_FKEY_F24: return NSF24FunctionKey; case GLFW_FKEY_F25: return NSF25FunctionKey; case GLFW_FKEY_F26: return NSF26FunctionKey; case GLFW_FKEY_F27: return NSF27FunctionKey; case GLFW_FKEY_F28: return NSF28FunctionKey; case GLFW_FKEY_F29: return NSF29FunctionKey; case GLFW_FKEY_F30: return NSF30FunctionKey; case GLFW_FKEY_F31: return NSF31FunctionKey; case GLFW_FKEY_F32: return NSF32FunctionKey; case GLFW_FKEY_F33: return NSF33FunctionKey; case GLFW_FKEY_F34: return NSF34FunctionKey; case GLFW_FKEY_F35: return NSF35FunctionKey; case GLFW_FKEY_KP_ENTER: return NSEnterCharacter; /* end functional to macu */ default: return 0; } } // }}} if (!is_pua_char(glfw_key)) return glfw_key; return 0; } int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot, int count) { NSImage* native; NSBitmapImageRep* rep; native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)]; if (native == nil) return false; for (int i = 0; i < count; i++) { const GLFWimage *src = image + i; rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:src->width pixelsHigh:src->height bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace bitmapFormat:NSBitmapFormatAlphaNonpremultiplied bytesPerRow:src->width * 4 bitsPerPixel:32]; if (rep == nil) { [native release]; return false; } memcpy([rep bitmapData], src->pixels, src->width * src->height * 4); [native addRepresentation:rep]; [rep release]; } cursor->ns.object = [[NSCursor alloc] initWithImage:native hotSpot:NSMakePoint(xhot, yhot)]; [native release]; if (cursor->ns.object == nil) return false; return true; } static NSCursor* load_hidden_system_cursor(NSString *name, SEL fallback) { // this implementation comes from SDL_cocoamouse.m NSString *cursorPath = [@"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors" stringByAppendingPathComponent:name]; NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"info.plist"]]; /* we can't do animation atm. :/ */ const int frames = (int)[[info valueForKey:@"frames"] integerValue]; NSCursor *cursor; NSImage *image = [[NSImage alloc] initWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"cursor.pdf"]]; if ((image == nil) || (image.isValid == NO)) { return [NSCursor performSelector:fallback]; } if (frames > 1) { const NSCompositingOperation operation = NSCompositingOperationCopy; const NSSize cropped_size = NSMakeSize(image.size.width, (int)(image.size.height / frames)); NSImage *cropped = [[NSImage alloc] initWithSize:cropped_size]; if (cropped == nil) { return [NSCursor performSelector:fallback]; } [cropped lockFocus]; { const NSRect cropped_rect = NSMakeRect(0, 0, cropped_size.width, cropped_size.height); [image drawInRect:cropped_rect fromRect:cropped_rect operation:operation fraction:1]; } [cropped unlockFocus]; image = cropped; } cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint([[info valueForKey:@"hotx"] doubleValue], [[info valueForKey:@"hoty"] doubleValue])]; return cursor; } int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape) { #define C(name, val) case name: cursor->ns.object = [NSCursor val]; break; #define U(name, val) case name: cursor->ns.object = [NSCursor performSelector:@selector(val)]; break; #define S(name, val, fallback) case name: cursor->ns.object = load_hidden_system_cursor(@#val, @selector(val)); break; switch(shape) { /* start glfw to cocoa (auto generated by gen-key-constants.py do not edit) */ C(GLFW_DEFAULT_CURSOR, arrowCursor); C(GLFW_TEXT_CURSOR, IBeamCursor); C(GLFW_POINTER_CURSOR, pointingHandCursor); S(GLFW_HELP_CURSOR, help, arrowCursor); S(GLFW_WAIT_CURSOR, busybutclickable, arrowCursor); S(GLFW_PROGRESS_CURSOR, busybutclickable, arrowCursor); C(GLFW_CROSSHAIR_CURSOR, crosshairCursor); S(GLFW_CELL_CURSOR, cell, crosshairCursor); C(GLFW_VERTICAL_TEXT_CURSOR, IBeamCursorForVerticalLayout); S(GLFW_MOVE_CURSOR, move, openHandCursor); C(GLFW_E_RESIZE_CURSOR, resizeRightCursor); S(GLFW_NE_RESIZE_CURSOR, resizenortheast, _windowResizeNorthEastSouthWestCursor); S(GLFW_NW_RESIZE_CURSOR, resizenorthwest, _windowResizeNorthWestSouthEastCursor); C(GLFW_N_RESIZE_CURSOR, resizeUpCursor); S(GLFW_SE_RESIZE_CURSOR, resizesoutheast, _windowResizeNorthWestSouthEastCursor); S(GLFW_SW_RESIZE_CURSOR, resizesouthwest, _windowResizeNorthEastSouthWestCursor); C(GLFW_S_RESIZE_CURSOR, resizeDownCursor); C(GLFW_W_RESIZE_CURSOR, resizeLeftCursor); C(GLFW_EW_RESIZE_CURSOR, resizeLeftRightCursor); C(GLFW_NS_RESIZE_CURSOR, resizeUpDownCursor); U(GLFW_NESW_RESIZE_CURSOR, _windowResizeNorthEastSouthWestCursor); U(GLFW_NWSE_RESIZE_CURSOR, _windowResizeNorthWestSouthEastCursor); S(GLFW_ZOOM_IN_CURSOR, zoomin, arrowCursor); S(GLFW_ZOOM_OUT_CURSOR, zoomout, arrowCursor); C(GLFW_ALIAS_CURSOR, dragLinkCursor); C(GLFW_COPY_CURSOR, dragCopyCursor); C(GLFW_NOT_ALLOWED_CURSOR, operationNotAllowedCursor); C(GLFW_NO_DROP_CURSOR, operationNotAllowedCursor); C(GLFW_GRAB_CURSOR, openHandCursor); C(GLFW_GRABBING_CURSOR, closedHandCursor); /* end glfw to cocoa */ case GLFW_INVALID_CURSOR: return false; } #undef C #undef U #undef S if (!cursor->ns.object) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to retrieve standard cursor"); return false; } [cursor->ns.object retain]; return true; } void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) { if (cursor->ns.object) [(NSCursor*) cursor->ns.object release]; } void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor UNUSED) { if (cursorInContentArea(window)) updateCursorImage(window); } bool _glfwPlatformIsFullscreen(_GLFWwindow* w, unsigned int flags) { NSWindow *window = w->ns.object; bool traditional = !(flags & 1); if (traditional) { if(@available(macOS 10.15.7, *)) return w->ns.in_traditional_fullscreen; } NSWindowStyleMask sm = [window styleMask]; return sm & NSWindowStyleMaskFullScreen; } static void make_window_fullscreen_after_show(unsigned long long timer_id, void* data) { (void)timer_id; unsigned long long window_id = (uintptr_t)data; for (_GLFWwindow *w = _glfw.windowListHead; w; w = w->next) { if (w->id == window_id) { NSWindow *window = w->ns.object; [window toggleFullScreen: nil]; update_titlebar_button_visibility_after_fullscreen_transition(w, false, true); break; } } } bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) { NSWindow *window = w->ns.object; bool made_fullscreen = true; bool traditional = !(flags & 1); NSWindowStyleMask sm = [window styleMask]; if (traditional) { if (@available(macOS 10.15.7, *)) { // As of Big Turd NSWindowStyleMaskFullScreen is no longer usable // Also no longer compatible after a minor release of macOS 10.15.7 if (!w->ns.in_traditional_fullscreen) { w->ns.pre_full_screen_style_mask = sm; [window setStyleMask: NSWindowStyleMaskBorderless]; [[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock]; [window setFrame:[window.screen frame] display:YES]; w->ns.in_traditional_fullscreen = true; } else { made_fullscreen = false; [window setStyleMask: w->ns.pre_full_screen_style_mask]; [[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault]; w->ns.in_traditional_fullscreen = false; } } else { bool in_fullscreen = sm & NSWindowStyleMaskFullScreen; if (!(in_fullscreen)) { sm |= NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen; [[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock]; } else { made_fullscreen = false; sm &= ~(NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen); [[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault]; } [window setStyleMask: sm]; } // Changing the style mask causes the first responder to be cleared [window makeFirstResponder:w->ns.view]; // If the dock and menubar are hidden going from maximized to fullscreen doesn't change the window size // and macOS forgets to trigger windowDidResize, so call it ourselves NSNotification *notification = [NSNotification notificationWithName:NSWindowDidResizeNotification object:window]; [w->ns.delegate performSelector:@selector(windowDidResize:) withObject:notification afterDelay:0]; } else { bool in_fullscreen = sm & NSWindowStyleMaskFullScreen; if (!in_fullscreen && !_glfwPlatformWindowVisible(w)) { // Bug in Apple's fullscreen implementation causes fullscreen to // not work before window is shown (at creation) if another window // is already fullscreen. Le sigh. https://github.com/kovidgoyal/kitty/issues/7448 _glfwPlatformAddTimer(0, false, make_window_fullscreen_after_show, (void*)(uintptr_t)(w->id), NULL); return made_fullscreen; } if (in_fullscreen) made_fullscreen = false; [window toggleFullScreen: nil]; } update_titlebar_button_visibility_after_fullscreen_transition(w, traditional, made_fullscreen); return made_fullscreen; } // Clipboard {{{ #define UTI_ROUNDTRIP_PREFIX @"uti-is-typical-apple-nih." static NSString* mime_to_uti(const char *mime) { if (strcmp(mime, "text/plain") == 0) return NSPasteboardTypeString; if (@available(macOS 11.0, *)) { UTType *t = [UTType typeWithMIMEType:@(mime)]; // auto-released if (t != nil && !t.dynamic) return t.identifier; } return [NSString stringWithFormat:@"%@%s", UTI_ROUNDTRIP_PREFIX, mime]; // auto-released } static const char* uti_to_mime(NSString *uti) { if ([uti isEqualToString:NSPasteboardTypeString]) return "text/plain"; if ([uti hasPrefix:UTI_ROUNDTRIP_PREFIX]) return [[uti substringFromIndex:[UTI_ROUNDTRIP_PREFIX length]] UTF8String]; if (@available(macOS 11.0, *)) { UTType *t = [UTType typeWithIdentifier:uti]; // auto-released if (t.preferredMIMEType != nil) return [t.preferredMIMEType UTF8String]; } return ""; } static void list_clipboard_mimetypes(GLFWclipboardwritedatafun write_data, void *object) { #define w(x) { if (ok) ok = write_data(object, x, strlen(x)); } NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; BOOL has_file_urls = [pasteboard canReadObjectForClasses:@[[NSURL class]] options:options]; BOOL has_strings = [pasteboard canReadObjectForClasses:@[[NSString class]] options:nil]; /* NSLog(@"has_file_urls: %d has_strings: %d", has_file_urls, has_strings); */ bool ok = true; if (has_strings) w("text/plain"); if (has_file_urls) w("text/local-path-list"); for (NSPasteboardItem * item in pasteboard.pasteboardItems) { for (NSPasteboardType type in item.types) { /* NSLog(@"%@", type); */ const char *mime = uti_to_mime(type); if (mime && mime[0] && ![@(mime) hasPrefix:@"text/plain"]) { /* NSLog(@"ut: %@ mt: %@ tags: %@", ut, ut.preferredMIMEType, ut.tags); */ w(mime); } } } #undef w } static void get_text_plain(GLFWclipboardwritedatafun write_data, void *object) { NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; NSArray* objs = [pasteboard readObjectsForClasses:@[[NSURL class], [NSString class]] options:options]; bool found = false; if (objs) { const NSUInteger count = [objs count]; if (count) { NSMutableData *path_list = [NSMutableData dataWithCapacity:4096]; // auto-released NSMutableData *text_list = [NSMutableData dataWithCapacity:4096]; // auto-released for (NSUInteger i = 0; i < count; i++) { id obj = objs[i]; if ([obj isKindOfClass:[NSURL class]]) { NSURL *url = (NSURL*)obj; if (url.fileURL && url.fileSystemRepresentation) { if ([path_list length] > 0) [path_list appendBytes:"\n" length:1]; [path_list appendBytes:url.fileSystemRepresentation length:strlen(url.fileSystemRepresentation)]; } } else if ([obj isKindOfClass:[NSString class]]) { if ([text_list length] > 0) [text_list appendBytes:"\n" length:1]; [text_list appendData:[obj dataUsingEncoding:NSUTF8StringEncoding]]; } } const NSMutableData *text = nil; if (path_list.length > 0) text = path_list; else if (text_list.length > 0) text = text_list; if (text) { found = true; write_data(object, text.mutableBytes, text.length); } } } if (!found) _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to retrieve text/plain from pasteboard"); } void _glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) { if (clipboard_type != GLFW_CLIPBOARD) return; if (mime_type == NULL) { list_clipboard_mimetypes(write_data, object); return; } if (strcmp(mime_type, "text/plain") == 0) { get_text_plain(write_data, object); return; } NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; /* NSLog(@"mime: %s uti: %@", mime_type, mime_to_uti(mime_type)); */ NSPasteboardType t = [pasteboard availableTypeFromArray:@[mime_to_uti(mime_type)]]; /* NSLog(@"available type: %@", t); */ if (t != nil) { NSData *data = [pasteboard dataForType:t]; // auto-released /* NSLog(@"data: %@", data); */ if (data != nil && data.length > 0) { write_data(object, data.bytes, data.length); } } } static NSMutableData* get_clipboard_data(const _GLFWClipboardData *cd, const char *mime) { NSMutableData *ans = [NSMutableData dataWithCapacity:8192]; if (ans == nil) return nil; GLFWDataChunk chunk = cd->get_data(mime, NULL, cd->ctype); void *iter = chunk.iter; if (!iter) return ans; while (true) { chunk = cd->get_data(mime, iter, cd->ctype); if (!chunk.sz) break; [ans appendBytes:chunk.data length:chunk.sz]; if (chunk.free) chunk.free((void*)chunk.free_data); } cd->get_data(NULL, iter, cd->ctype); return ans; } void _glfwPlatformSetClipboard(GLFWClipboardType t) { if (t != GLFW_CLIPBOARD) return; NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; NSMutableArray *ptypes = [NSMutableArray arrayWithCapacity:_glfw.clipboard.num_mime_types]; // auto-released for (size_t i = 0; i < _glfw.clipboard.num_mime_types; i++) { [ptypes addObject:mime_to_uti(_glfw.clipboard.mime_types[i])]; } [pasteboard declareTypes:ptypes owner:nil]; for (size_t i = 0; i < _glfw.clipboard.num_mime_types; i++) { NSMutableData *data = get_clipboard_data(&_glfw.clipboard, _glfw.clipboard.mime_types[i]); // auto-released /* NSLog(@"putting data: %@ for: %s with UTI: %@", data, _glfw.clipboard.mime_types[i], ptypes[i]); */ if (data != nil) [pasteboard setData:data forType:ptypes[i]]; } } // }}} EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs) { if (_glfw.egl.ANGLE_platform_angle) { int type = 0; if (_glfw.egl.ANGLE_platform_angle_opengl) { if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL) type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE; } if (_glfw.egl.ANGLE_platform_angle_metal) { if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_METAL) type = EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE; } if (type) { *attribs = calloc(3, sizeof(EGLint)); (*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE; (*attribs)[1] = type; (*attribs)[2] = EGL_NONE; return EGL_PLATFORM_ANGLE_ANGLE; } } return 0; } EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void) { return EGL_DEFAULT_DISPLAY; } EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window) { return window->ns.layer; } void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) { if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface) { extensions[0] = "VK_KHR_surface"; extensions[1] = "VK_EXT_metal_surface"; } else if (_glfw.vk.KHR_surface && _glfw.vk.MVK_macos_surface) { extensions[0] = "VK_KHR_surface"; extensions[1] = "VK_MVK_macos_surface"; } } int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance UNUSED, VkPhysicalDevice device UNUSED, uint32_t queuefamily UNUSED) { return true; } VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, _GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface) { #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 // HACK: Dynamically load Core Animation to avoid adding an extra // dependency for the majority who don't use MoltenVK NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/QuartzCore.framework"]; if (!bundle) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to find QuartzCore.framework"); return VK_ERROR_EXTENSION_NOT_PRESENT; } // NOTE: Create the layer here as makeBackingLayer should not return nil window->ns.layer = [[bundle classNamed:@"CAMetalLayer"] layer]; if (!window->ns.layer) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create layer for view"); return VK_ERROR_EXTENSION_NOT_PRESENT; } if (window->ns.retina) [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; [window->ns.view setLayer:window->ns.layer]; [window->ns.view setWantsLayer:YES]; VkResult err; if (_glfw.vk.EXT_metal_surface) { VkMetalSurfaceCreateInfoEXT sci; PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT; vkCreateMetalSurfaceEXT = (PFN_vkCreateMetalSurfaceEXT) vkGetInstanceProcAddr(instance, "vkCreateMetalSurfaceEXT"); if (!vkCreateMetalSurfaceEXT) { _glfwInputError(GLFW_API_UNAVAILABLE, "Cocoa: Vulkan instance missing VK_EXT_metal_surface extension"); return VK_ERROR_EXTENSION_NOT_PRESENT; } memset(&sci, 0, sizeof(sci)); sci.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; sci.pLayer = window->ns.layer; err = vkCreateMetalSurfaceEXT(instance, &sci, allocator, surface); } else { VkMacOSSurfaceCreateInfoMVK sci; PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK; vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK) vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK"); if (!vkCreateMacOSSurfaceMVK) { _glfwInputError(GLFW_API_UNAVAILABLE, "Cocoa: Vulkan instance missing VK_MVK_macos_surface extension"); return VK_ERROR_EXTENSION_NOT_PRESENT; } memset(&sci, 0, sizeof(sci)); sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; sci.pView = window->ns.view; err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface); } if (err) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create Vulkan surface: %s", _glfwGetVulkanResultString(err)); } return err; #else return VK_ERROR_EXTENSION_NOT_PRESENT; #endif } int _glfwPlatformSetWindowBlur(_GLFWwindow *window, int radius) { int orig = window->ns.blur_radius; if (radius > -1 && radius != window->ns.blur_radius) { extern OSStatus CGSSetWindowBackgroundBlurRadius(void* connection, NSInteger windowNumber, int radius); extern void* CGSDefaultConnectionForThread(void); CGSSetWindowBackgroundBlurRadius(CGSDefaultConnectionForThread(), [window->ns.object windowNumber], radius); window->ns.blur_radius = radius; } return orig; } ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(nil); return window->ns.object; } GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *handle, GLFWcocoatextinputfilterfun callback) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(nil); GLFWcocoatextinputfilterfun previous = window->ns.textInputFilterCallback; window->ns.textInputFilterCallback = callback; return previous; } GLFWAPI GLFWhandleurlopen glfwSetCocoaURLOpenCallback(GLFWhandleurlopen callback) { _GLFW_REQUIRE_INIT_OR_RETURN(nil); GLFWhandleurlopen prev = _glfw.ns.url_open_callback; _glfw.ns.url_open_callback = callback; return prev; } GLFWAPI GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *handle, GLFWcocoatogglefullscreenfun callback) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(nil); GLFWcocoatogglefullscreenfun previous = window->ns.toggleFullscreenCallback; window->ns.toggleFullscreenCallback = callback; return previous; } GLFWAPI void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback) { requestRenderFrame((_GLFWwindow*)w, callback); } GLFWAPI GLFWcocoarenderframefun glfwCocoaSetWindowResizeCallback(GLFWwindow *w, GLFWcocoarenderframefun cb) { _GLFWwindow* window = (_GLFWwindow*)w; GLFWcocoarenderframefun current = window->ns.resizeCallback; window->ns.resizeCallback = cb; return current; } GLFWAPI void glfwCocoaSetWindowChrome(GLFWwindow *w, unsigned int color, bool use_system_color, unsigned int system_color, int background_blur, unsigned int hide_window_decorations, bool show_text_in_titlebar, int color_space, float background_opacity, bool resizable) { @autoreleasepool { _GLFWwindow* window = (_GLFWwindow*)w; const bool is_transparent = ![window->ns.object isOpaque]; if (!is_transparent) { background_opacity = 1.0; background_blur = 0; } NSColor *background = nil; NSAppearance *appearance = nil; bool titlebar_transparent = false; const NSWindowStyleMask current_style_mask = [window->ns.object styleMask]; const bool in_fullscreen = ((current_style_mask & NSWindowStyleMaskFullScreen) != 0) || window->ns.in_traditional_fullscreen; NSAppearance *light_appearance = is_transparent ? [NSAppearance appearanceNamed:NSAppearanceNameVibrantLight] : [NSAppearance appearanceNamed:NSAppearanceNameAqua]; NSAppearance *dark_appearance = is_transparent ? [NSAppearance appearanceNamed:NSAppearanceNameVibrantDark] : [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]; if (use_system_color || background_opacity < 1.0) { if (is_transparent) { // prevent blurring of shadows at window corners with desktop background by setting a low alpha background background = background_blur > 0 ? [NSColor colorWithWhite: 0 alpha: 0.001f] : [NSColor clearColor]; } else background = [NSColor windowBackgroundColor]; switch (system_color) { case 1: appearance = light_appearance; break; case 2: appearance = dark_appearance; break; } } else { // set a background color and make the title bar transparent so the background color is visible double red = ((color >> 16) & 0xFF) / 255.0; double green = ((color >> 8) & 0xFF) / 255.0; double blue = (color & 0xFF) / 255.0; double luma = 0.2126 * red + 0.7152 * green + 0.0722 * blue; background = [NSColor colorWithSRGBRed:red green:green blue:blue alpha:1.f]; appearance = luma < 0.5 ? dark_appearance : light_appearance; titlebar_transparent = true; } [window->ns.object setBackgroundColor:background]; [window->ns.object setAppearance:appearance]; _glfwPlatformSetWindowBlur(window, background_blur); bool has_shadow = false; const char *decorations_desc = "full"; window->ns.titlebar_hidden = false; switch (hide_window_decorations) { case 1: decorations_desc = "none"; window->decorated = false; break; case 2: decorations_desc = "no-titlebar"; window->decorated = true; has_shadow = true; titlebar_transparent = true; window->ns.titlebar_hidden = true; show_text_in_titlebar = false; break; case 4: decorations_desc = "no-titlebar-and-no-corners"; window->decorated = false; has_shadow = true; break; default: window->decorated = true; has_shadow = true; break; } // shadow causes burn-in/ghosting because cocoa doesnt invalidate it on OS window resize/minimize/restore. // https://github.com/kovidgoyal/kitty/issues/6439 if (is_transparent) has_shadow = false; bool hide_titlebar_buttons = !in_fullscreen && window->ns.titlebar_hidden; [window->ns.object setTitlebarAppearsTransparent:titlebar_transparent]; [window->ns.object setHasShadow:has_shadow]; [window->ns.object setTitleVisibility:(show_text_in_titlebar) ? NSWindowTitleVisible : NSWindowTitleHidden]; NSColorSpace *cs = nil; switch (color_space) { case SRGB_COLORSPACE: cs = [NSColorSpace sRGBColorSpace]; break; case DISPLAY_P3_COLORSPACE: cs = [NSColorSpace displayP3ColorSpace]; break; case DEFAULT_COLORSPACE: cs = nil; break; // using deviceRGBColorSpace causes a hang when transitioning to fullscreen } window->resizable = resizable; debug( "Window Chrome state:\n\tbackground: %s\n\tappearance: %s color_space: %s\n\t" "blur: %d has_shadow: %d resizable: %d decorations: %s (%d)\n\t" "titlebar: transparent: %d title_visibility: %d hidden: %d buttons_hidden: %d" "\n", background ? [background.description UTF8String] : "", appearance ? [appearance.name UTF8String] : "", cs ? (cs.localizedName ? [cs.localizedName UTF8String] : [cs.description UTF8String]) : "", background_blur, has_shadow, resizable, decorations_desc, window->decorated, titlebar_transparent, show_text_in_titlebar, window->ns.titlebar_hidden, hide_titlebar_buttons ); [window->ns.object setColorSpace:cs]; [[window->ns.object standardWindowButton: NSWindowCloseButton] setHidden:hide_titlebar_buttons]; [[window->ns.object standardWindowButton: NSWindowMiniaturizeButton] setHidden:hide_titlebar_buttons]; [[window->ns.object standardWindowButton: NSWindowZoomButton] setHidden:hide_titlebar_buttons]; // Apple throws a hissy fit if one attempts to clear the value of NSWindowStyleMaskFullScreen outside of a full screen transition // event. See https://github.com/kovidgoyal/kitty/issues/7106 NSWindowStyleMask fsmask = current_style_mask & NSWindowStyleMaskFullScreen; window->ns.pre_full_screen_style_mask = getStyleMask(window); if (in_fullscreen && window->ns.in_traditional_fullscreen) { [window->ns.object setStyleMask:NSWindowStyleMaskBorderless]; } else { [window->ns.object setStyleMask:window->ns.pre_full_screen_style_mask | fsmask]; } // HACK: Changing the style mask can cause the first responder to be cleared [window->ns.object makeFirstResponder:window->ns.view]; }} GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized) { (void)query_if_unintialized; int theme_type = 0; NSAppearance *changedAppearance = NSApp.effectiveAppearance; NSAppearanceName newAppearance = [changedAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]]; if([newAppearance isEqualToString:NSAppearanceNameDarkAqua]){ theme_type = 1; } else { theme_type = 2; } return theme_type; } GLFWAPI uint32_t glfwGetCocoaKeyEquivalent(uint32_t glfw_key, int glfw_mods, int *cocoa_mods) { *cocoa_mods = 0; if (glfw_mods & GLFW_MOD_SHIFT) *cocoa_mods |= NSEventModifierFlagShift; if (glfw_mods & GLFW_MOD_CONTROL) *cocoa_mods |= NSEventModifierFlagControl; if (glfw_mods & GLFW_MOD_ALT) *cocoa_mods |= NSEventModifierFlagOption; if (glfw_mods & GLFW_MOD_SUPER) *cocoa_mods |= NSEventModifierFlagCommand; if (glfw_mods & GLFW_MOD_CAPS_LOCK) *cocoa_mods |= NSEventModifierFlagCapsLock; return _glfwPlatformGetNativeKeyForKey(glfw_key); } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// // Transforms a y-coordinate between the CG display and NS screen spaces // float _glfwTransformYNS(float y) { return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1; } void _glfwCocoaPostEmptyEvent(void) { NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:0 context:nil subtype:0 data1:0 data2:0]; [NSApp postEvent:event atStart:YES]; } kitty-0.41.1/glfw/context.c0000664000175000017510000004602014773370543015100 0ustar nileshnilesh//======================================================================== // GLFW 3.4 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2016 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // Please use C89 style variable declarations in this file because VS 2010 //======================================================================== #include "internal.h" #include #include #include #include ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// // Checks whether the desired context attributes are valid // // This function checks things like whether the specified client API version // exists and whether all relevant options have supported and non-conflicting // values // bool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig) { if (ctxconfig->share) { if (ctxconfig->client == GLFW_NO_API || ctxconfig->share->context.client == GLFW_NO_API) { _glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL); return false; } } if (ctxconfig->source != GLFW_NATIVE_CONTEXT_API && ctxconfig->source != GLFW_EGL_CONTEXT_API && ctxconfig->source != GLFW_OSMESA_CONTEXT_API) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid context creation API 0x%08X", ctxconfig->source); return false; } if (ctxconfig->client != GLFW_NO_API && ctxconfig->client != GLFW_OPENGL_API && ctxconfig->client != GLFW_OPENGL_ES_API) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid client API 0x%08X", ctxconfig->client); return false; } if (ctxconfig->client == GLFW_OPENGL_API) { if ((ctxconfig->major < 1 || ctxconfig->minor < 0) || (ctxconfig->major == 1 && ctxconfig->minor > 5) || (ctxconfig->major == 2 && ctxconfig->minor > 1) || (ctxconfig->major == 3 && ctxconfig->minor > 3)) { // OpenGL 1.0 is the smallest valid version // OpenGL 1.x series ended with version 1.5 // OpenGL 2.x series ended with version 2.1 // OpenGL 3.x series ended with version 3.3 // For now, let everything else through _glfwInputError(GLFW_INVALID_VALUE, "Invalid OpenGL version %i.%i", ctxconfig->major, ctxconfig->minor); return false; } if (ctxconfig->profile) { if (ctxconfig->profile != GLFW_OPENGL_CORE_PROFILE && ctxconfig->profile != GLFW_OPENGL_COMPAT_PROFILE) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid OpenGL profile 0x%08X", ctxconfig->profile); return false; } if (ctxconfig->major <= 2 || (ctxconfig->major == 3 && ctxconfig->minor < 2)) { // Desktop OpenGL context profiles are only defined for version 3.2 // and above _glfwInputError(GLFW_INVALID_VALUE, "Context profiles are only defined for OpenGL version 3.2 and above"); return false; } } if (ctxconfig->forward && ctxconfig->major <= 2) { // Forward-compatible contexts are only defined for OpenGL version 3.0 and above _glfwInputError(GLFW_INVALID_VALUE, "Forward-compatibility is only defined for OpenGL version 3.0 and above"); return false; } } else if (ctxconfig->client == GLFW_OPENGL_ES_API) { if (ctxconfig->major < 1 || ctxconfig->minor < 0 || (ctxconfig->major == 1 && ctxconfig->minor > 1) || (ctxconfig->major == 2 && ctxconfig->minor > 0)) { // OpenGL ES 1.0 is the smallest valid version // OpenGL ES 1.x series ended with version 1.1 // OpenGL ES 2.x series ended with version 2.0 // For now, let everything else through _glfwInputError(GLFW_INVALID_VALUE, "Invalid OpenGL ES version %i.%i", ctxconfig->major, ctxconfig->minor); return false; } } if (ctxconfig->robustness) { if (ctxconfig->robustness != GLFW_NO_RESET_NOTIFICATION && ctxconfig->robustness != GLFW_LOSE_CONTEXT_ON_RESET) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid context robustness mode 0x%08X", ctxconfig->robustness); return false; } } if (ctxconfig->release) { if (ctxconfig->release != GLFW_RELEASE_BEHAVIOR_NONE && ctxconfig->release != GLFW_RELEASE_BEHAVIOR_FLUSH) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid context release behavior 0x%08X", ctxconfig->release); return false; } } return true; } // Retrieves the attributes of the current context // bool _glfwRefreshContextAttribs(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig) { int i; _GLFWwindow* previous; const char* version; const char* prefixes[] = { "OpenGL ES-CM ", "OpenGL ES-CL ", "OpenGL ES ", NULL }; window->context.source = ctxconfig->source; window->context.client = GLFW_OPENGL_API; previous = _glfwPlatformGetTls(&_glfw.contextSlot); glfwMakeContextCurrent((GLFWwindow*) window); window->context.GetIntegerv = (PFNGLGETINTEGERVPROC) window->context.getProcAddress("glGetIntegerv"); window->context.GetString = (PFNGLGETSTRINGPROC) window->context.getProcAddress("glGetString"); if (!window->context.GetIntegerv || !window->context.GetString) { _glfwInputError(GLFW_PLATFORM_ERROR, "Entry point retrieval is broken"); glfwMakeContextCurrent((GLFWwindow*) previous); return false; } version = (const char*) window->context.GetString(GL_VERSION); if (!version) { if (ctxconfig->client == GLFW_OPENGL_API) { _glfwInputError(GLFW_PLATFORM_ERROR, "OpenGL version string retrieval is broken"); } else { _glfwInputError(GLFW_PLATFORM_ERROR, "OpenGL ES version string retrieval is broken"); } glfwMakeContextCurrent((GLFWwindow*) previous); return false; } for (i = 0; prefixes[i]; i++) { const size_t length = strlen(prefixes[i]); if (strncmp(version, prefixes[i], length) == 0) { version += length; window->context.client = GLFW_OPENGL_ES_API; break; } } if (sscanf(version, "%d.%d.%d", &window->context.major, &window->context.minor, &window->context.revision) < 1) { if (window->context.client == GLFW_OPENGL_API) { _glfwInputError(GLFW_PLATFORM_ERROR, "No version found in OpenGL version string"); } else { _glfwInputError(GLFW_PLATFORM_ERROR, "No version found in OpenGL ES version string"); } glfwMakeContextCurrent((GLFWwindow*) previous); return false; } if (window->context.major < ctxconfig->major || (window->context.major == ctxconfig->major && window->context.minor < ctxconfig->minor)) { // The desired OpenGL version is greater than the actual version // This only happens if the machine lacks {GLX|WGL}_ARB_create_context // /and/ the user has requested an OpenGL version greater than 1.0 // For API consistency, we emulate the behavior of the // {GLX|WGL}_ARB_create_context extension and fail here if (window->context.client == GLFW_OPENGL_API) { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "Requested OpenGL version %i.%i, got version %i.%i", ctxconfig->major, ctxconfig->minor, window->context.major, window->context.minor); } else { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "Requested OpenGL ES version %i.%i, got version %i.%i", ctxconfig->major, ctxconfig->minor, window->context.major, window->context.minor); } glfwMakeContextCurrent((GLFWwindow*) previous); return false; } if (window->context.major >= 3) { // OpenGL 3.0+ uses a different function for extension string retrieval // We cache it here instead of in glfwExtensionSupported mostly to alert // users as early as possible that their build may be broken window->context.GetStringi = (PFNGLGETSTRINGIPROC) window->context.getProcAddress("glGetStringi"); if (!window->context.GetStringi) { _glfwInputError(GLFW_PLATFORM_ERROR, "Entry point retrieval is broken"); glfwMakeContextCurrent((GLFWwindow*) previous); return false; } } if (window->context.client == GLFW_OPENGL_API) { // Read back context flags (OpenGL 3.0 and above) if (window->context.major >= 3) { GLint flags; window->context.GetIntegerv(GL_CONTEXT_FLAGS, &flags); if (flags & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT) window->context.forward = true; if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) window->context.debug = true; else if (glfwExtensionSupported("GL_ARB_debug_output") && ctxconfig->debug) { // HACK: This is a workaround for older drivers (pre KHR_debug) // not setting the debug bit in the context flags for // debug contexts window->context.debug = true; } if (flags & GL_CONTEXT_FLAG_NO_ERROR_BIT_KHR) window->context.noerror = true; } // Read back OpenGL context profile (OpenGL 3.2 and above) if (window->context.major >= 4 || (window->context.major == 3 && window->context.minor >= 2)) { GLint mask; window->context.GetIntegerv(GL_CONTEXT_PROFILE_MASK, &mask); if (mask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) window->context.profile = GLFW_OPENGL_COMPAT_PROFILE; else if (mask & GL_CONTEXT_CORE_PROFILE_BIT) window->context.profile = GLFW_OPENGL_CORE_PROFILE; else if (glfwExtensionSupported("GL_ARB_compatibility")) { // HACK: This is a workaround for the compatibility profile bit // not being set in the context flags if an OpenGL 3.2+ // context was created without having requested a specific // version window->context.profile = GLFW_OPENGL_COMPAT_PROFILE; } } // Read back robustness strategy if (glfwExtensionSupported("GL_ARB_robustness")) { // NOTE: We avoid using the context flags for detection, as they are // only present from 3.0 while the extension applies from 1.1 GLint strategy; window->context.GetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_ARB, &strategy); if (strategy == GL_LOSE_CONTEXT_ON_RESET_ARB) window->context.robustness = GLFW_LOSE_CONTEXT_ON_RESET; else if (strategy == GL_NO_RESET_NOTIFICATION_ARB) window->context.robustness = GLFW_NO_RESET_NOTIFICATION; } } else { // Read back robustness strategy if (glfwExtensionSupported("GL_EXT_robustness")) { // NOTE: The values of these constants match those of the OpenGL ARB // one, so we can reuse them here GLint strategy; window->context.GetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_ARB, &strategy); if (strategy == GL_LOSE_CONTEXT_ON_RESET_ARB) window->context.robustness = GLFW_LOSE_CONTEXT_ON_RESET; else if (strategy == GL_NO_RESET_NOTIFICATION_ARB) window->context.robustness = GLFW_NO_RESET_NOTIFICATION; } } if (glfwExtensionSupported("GL_KHR_context_flush_control")) { GLint behavior; window->context.GetIntegerv(GL_CONTEXT_RELEASE_BEHAVIOR, &behavior); if (behavior == GL_NONE) window->context.release = GLFW_RELEASE_BEHAVIOR_NONE; else if (behavior == GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH) window->context.release = GLFW_RELEASE_BEHAVIOR_FLUSH; } glfwMakeContextCurrent((GLFWwindow*) previous); return true; } // Searches an extension string for the specified extension // bool _glfwStringInExtensionString(const char* string, const char* extensions) { const char* start = extensions; for (;;) { const char* where; const char* terminator; where = strstr(start, string); if (!where) return false; terminator = where + strlen(string); if (where == start || *(where - 1) == ' ') { if (*terminator == ' ' || *terminator == '\0') break; } start = terminator; } return true; } ////////////////////////////////////////////////////////////////////////// ////// GLFW public API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI void glfwMakeContextCurrent(GLFWwindow* handle) { _GLFW_REQUIRE_INIT(); _GLFWwindow* window = (_GLFWwindow*) handle; _GLFWwindow* previous = _glfwPlatformGetTls(&_glfw.contextSlot); if (window && window->context.client == GLFW_NO_API) { _glfwInputError(GLFW_NO_WINDOW_CONTEXT, "Cannot make current with a window that has no OpenGL or OpenGL ES context"); return; } if (previous) { if (!window || window->context.source != previous->context.source) previous->context.makeCurrent(NULL); } if (window) window->context.makeCurrent(window); } GLFWAPI GLFWwindow* glfwGetCurrentContext(void) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); return _glfwPlatformGetTls(&_glfw.contextSlot); } GLFWAPI void glfwSwapBuffers(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); if (window->context.client == GLFW_NO_API) { _glfwInputError(GLFW_NO_WINDOW_CONTEXT, "Cannot swap buffers of a window that has no OpenGL or OpenGL ES context"); return; } window->context.swapBuffers(window); #ifdef _GLFW_WAYLAND _glfwWaylandAfterBufferSwap(window); #endif } GLFWAPI void glfwSwapInterval(int interval) { _GLFWwindow* window; _GLFW_REQUIRE_INIT(); window = _glfwPlatformGetTls(&_glfw.contextSlot); if (!window) { _glfwInputError(GLFW_NO_CURRENT_CONTEXT, "Cannot set swap interval without a current OpenGL or OpenGL ES context"); return; } window->context.swapInterval(interval); } GLFWAPI int glfwExtensionSupported(const char* extension) { _GLFWwindow* window; assert(extension != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(false); window = _glfwPlatformGetTls(&_glfw.contextSlot); if (!window) { _glfwInputError(GLFW_NO_CURRENT_CONTEXT, "Cannot query extension without a current OpenGL or OpenGL ES context"); return false; } if (*extension == '\0') { _glfwInputError(GLFW_INVALID_VALUE, "Extension name cannot be an empty string"); return false; } if (window->context.major >= 3) { int i; GLint count; // Check if extension is in the modern OpenGL extensions string list window->context.GetIntegerv(GL_NUM_EXTENSIONS, &count); for (i = 0; i < count; i++) { const char* en = (const char*) window->context.GetStringi(GL_EXTENSIONS, i); if (!en) { _glfwInputError(GLFW_PLATFORM_ERROR, "Extension string retrieval is broken"); return false; } if (strcmp(en, extension) == 0) return true; } } else { // Check if extension is in the old style OpenGL extensions string const char* extensions = (const char*) window->context.GetString(GL_EXTENSIONS); if (!extensions) { _glfwInputError(GLFW_PLATFORM_ERROR, "Extension string retrieval is broken"); return false; } if (_glfwStringInExtensionString(extension, extensions)) return true; } // Check if extension is in the platform-specific string return window->context.extensionSupported(extension); } GLFWAPI GLFWglproc glfwGetProcAddress(const char* procname) { _GLFWwindow* window; assert(procname != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); window = _glfwPlatformGetTls(&_glfw.contextSlot); if (!window) { _glfwInputError(GLFW_NO_CURRENT_CONTEXT, "Cannot query entry point without a current OpenGL or OpenGL ES context"); return NULL; } return window->context.getProcAddress(procname); } kitty-0.41.1/glfw/dbus_glfw.c0000664000175000017510000003066714773370543015402 0ustar nileshnilesh//======================================================================== // GLFW 3.4 XKB - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2018 Kovid Goyal // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #include "internal.h" #include "dbus_glfw.h" #include "../kitty/monotonic.h" #include #include static void report_error(DBusError *err, const char *fmt, ...) { static char buf[4096]; va_list args; va_start(args, fmt); int n = vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); if (n >= 0 && (size_t)n < (sizeof(buf) - 256)) snprintf(buf + n, sizeof(buf) - n, ". DBUS error: %s", err->message ? err->message : "(null)"); _glfwInputError(GLFW_PLATFORM_ERROR, "%s", buf); dbus_error_free(err); } static _GLFWDBUSData *dbus_data = NULL; static DBusConnection *session_bus = NULL; bool glfw_dbus_init(_GLFWDBUSData *dbus, EventLoopData *eld) { dbus->eld = eld; dbus_data = dbus; return true; } static void on_dbus_watch_ready(int fd UNUSED, int events, void *data) { DBusWatch *watch = (DBusWatch*)data; unsigned int flags = 0; if (events & POLLERR) flags |= DBUS_WATCH_ERROR; if (events & POLLHUP) flags |= DBUS_WATCH_HANGUP; if (events & POLLIN) flags |= DBUS_WATCH_READABLE; if (events & POLLOUT) flags |= DBUS_WATCH_WRITABLE; dbus_watch_handle(watch, flags); } static int events_for_watch(DBusWatch *watch) { int events = 0; unsigned int flags = dbus_watch_get_flags(watch); if (flags & DBUS_WATCH_READABLE) events |= POLLIN; if (flags & DBUS_WATCH_WRITABLE) events |= POLLOUT; return events; } static dbus_bool_t add_dbus_watch(DBusWatch *watch, void *data) { id_type watch_id = addWatch(dbus_data->eld, data, dbus_watch_get_unix_fd(watch), events_for_watch(watch), dbus_watch_get_enabled(watch), on_dbus_watch_ready, watch); if (!watch_id) return FALSE; id_type *idp = malloc(sizeof(id_type)); if (!idp) return FALSE; *idp = watch_id; dbus_watch_set_data(watch, idp, free); return TRUE; } static void remove_dbus_watch(DBusWatch *watch, void *data UNUSED) { id_type *idp = dbus_watch_get_data(watch); if (idp) removeWatch(dbus_data->eld, *idp); } static void toggle_dbus_watch(DBusWatch *watch, void *data UNUSED) { id_type *idp = dbus_watch_get_data(watch); if (idp) toggleWatch(dbus_data->eld, *idp, dbus_watch_get_enabled(watch)); } static void on_dbus_timer_ready(id_type timer_id UNUSED, void *data) { if (data) { DBusTimeout *t = (DBusTimeout*)data; dbus_timeout_handle(t); } } static dbus_bool_t add_dbus_timeout(DBusTimeout *timeout, void *data) { int enabled = dbus_timeout_get_enabled(timeout) ? 1 : 0; monotonic_t interval = ms_to_monotonic_t(dbus_timeout_get_interval(timeout)); if (interval < 0) return FALSE; id_type timer_id = addTimer(dbus_data->eld, data, interval, enabled, true, on_dbus_timer_ready, timeout, NULL); if (!timer_id) return FALSE; id_type *idp = malloc(sizeof(id_type)); if (!idp) { removeTimer(dbus_data->eld, timer_id); return FALSE; } *idp = timer_id; dbus_timeout_set_data(timeout, idp, free); return TRUE; } static void remove_dbus_timeout(DBusTimeout *timeout, void *data UNUSED) { id_type *idp = dbus_timeout_get_data(timeout); if (idp) removeTimer(dbus_data->eld, *idp); } static void toggle_dbus_timeout(DBusTimeout *timeout, void *data UNUSED) { id_type *idp = dbus_timeout_get_data(timeout); if (idp) toggleTimer(dbus_data->eld, *idp, dbus_timeout_get_enabled(timeout)); } DBusConnection* glfw_dbus_connect_to(const char *path, const char* err_msg, const char *name, bool register_on_bus) { DBusError err; dbus_error_init(&err); DBusConnection *ans = dbus_connection_open_private(path, &err); if (!ans) { report_error(&err, err_msg); return NULL; } dbus_connection_set_exit_on_disconnect(ans, FALSE); dbus_error_free(&err); if (register_on_bus) { if (!dbus_bus_register(ans, &err)) { report_error(&err, err_msg); return NULL; } } if (!dbus_connection_set_watch_functions(ans, add_dbus_watch, remove_dbus_watch, toggle_dbus_watch, (void*)name, NULL)) { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS watches on connection to: %s", path); dbus_connection_close(ans); dbus_connection_unref(ans); return NULL; } if (!dbus_connection_set_timeout_functions(ans, add_dbus_timeout, remove_dbus_timeout, toggle_dbus_timeout, (void*)name, NULL)) { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS timeout functions on connection to: %s", path); dbus_connection_close(ans); dbus_connection_unref(ans); return NULL; } return ans; } void glfw_dbus_dispatch(DBusConnection *conn) { while(dbus_connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS); } void glfw_dbus_session_bus_dispatch(void) { if (session_bus) glfw_dbus_dispatch(session_bus); } void glfw_dbus_terminate(_GLFWDBUSData *dbus UNUSED) { if (dbus_data) { dbus_data->eld = NULL; dbus_data = NULL; } if (session_bus) { dbus_connection_unref(session_bus); session_bus = NULL; } } void glfw_dbus_close_connection(DBusConnection *conn) { dbus_connection_close(conn); dbus_connection_unref(conn); } bool glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...) { DBusError err; dbus_error_init(&err); va_list ap; va_start(ap, failmsg); int firstarg = va_arg(ap, int); bool ret = dbus_message_get_args_valist(msg, &err, firstarg, ap) ? true : false; va_end(ap); if (!ret) report_error(&err, failmsg); return ret; } typedef struct { dbus_pending_callback callback; void *user_data; } MethodResponse; static const char* format_message_error(DBusError *err) { static char buf[1024]; snprintf(buf, sizeof(buf), "[%s] %s", err->name ? err->name : "", err->message); return buf; } static void method_reply_received(DBusPendingCall *pending, void *user_data) { MethodResponse *res = (MethodResponse*)user_data; RAII_MSG(msg, dbus_pending_call_steal_reply(pending)); if (msg) { DBusError err; dbus_error_init(&err); if (dbus_set_error_from_message(&err, msg)) res->callback(NULL, format_message_error(&err), res->user_data); else res->callback(msg, NULL, res->user_data); } } bool call_method_with_msg(DBusConnection *conn, DBusMessage *msg, int timeout, dbus_pending_callback callback, void *user_data, bool block) { bool retval = false; #define REPORT(errs) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: node=%s path=%s interface=%s method=%s, with error: %s", dbus_message_get_destination(msg), dbus_message_get_path(msg), dbus_message_get_interface(msg), dbus_message_get_member(msg), errs) if (callback) { DBusPendingCall *pending = NULL; if (block) { DBusError error; dbus_error_init(&error); RAII_MSG(reply, dbus_connection_send_with_reply_and_block(session_bus, msg, timeout, &error)); if (dbus_error_is_set(&error)) { callback(reply, error.message, user_data); return false; } else if (reply) { callback(reply, NULL, user_data); } else return false; } else if (dbus_connection_send_with_reply(conn, msg, &pending, timeout)) { MethodResponse *res = malloc(sizeof(MethodResponse)); if (!res) return false; res->callback = callback; res->user_data = user_data; dbus_pending_call_set_notify(pending, method_reply_received, res, free); retval = true; } else { REPORT("out of memory"); } } else { if (dbus_connection_send(conn, msg, NULL)) { retval = true; } else { REPORT("out of memory"); } } return retval; #undef REPORT } static bool call_method(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void *user_data, bool blocking, va_list ap) { if (!conn || !path) return false; RAII_MSG(msg, dbus_message_new_method_call(node, path, interface, method)); if (!msg) return false; bool retval = false; int firstarg = va_arg(ap, int); if ((firstarg == DBUS_TYPE_INVALID) || dbus_message_append_args_valist(msg, firstarg, ap)) { retval = call_method_with_msg(conn, msg, timeout, callback, user_data, blocking); } else { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s could not add arguments", method, node, interface); } return retval; } bool glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void* user_data, ...) { bool retval; va_list ap; va_start(ap, user_data); retval = call_method(conn, node, path, interface, method, timeout, callback, user_data, false, ap); va_end(ap); return retval; } bool glfw_dbus_call_blocking_method(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void* user_data, ...) { bool retval; va_list ap; va_start(ap, user_data); retval = call_method(conn, node, path, interface, method, timeout, callback, user_data, true, ap); va_end(ap); return retval; } bool glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...) { bool retval; va_list ap; va_start(ap, method); retval = call_method(conn, node, path, interface, method, DBUS_TIMEOUT_USE_DEFAULT, NULL, NULL, false, ap); va_end(ap); return retval; } int glfw_dbus_match_signal(DBusMessage *msg, const char *interface, ...) { va_list ap; va_start(ap, interface); int ans = -1, num = -1; while(1) { num++; const char *name = va_arg(ap, const char*); if (!name) break; if (dbus_message_is_signal(msg, interface, name)) { ans = num; break; } } va_end(ap); return ans; } static void glfw_dbus_connect_to_session_bus(void) { DBusError error; dbus_error_init(&error); if (session_bus) { dbus_connection_unref(session_bus); } session_bus = dbus_bus_get(DBUS_BUS_SESSION, &error); if (dbus_error_is_set(&error)) { report_error(&error, "Failed to connect to DBUS session bus"); session_bus = NULL; return; } static const char *name = "session-bus"; if (!dbus_connection_set_watch_functions(session_bus, add_dbus_watch, remove_dbus_watch, toggle_dbus_watch, (void*)name, NULL)) { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS watches on connection to: %s", name); dbus_connection_close(session_bus); dbus_connection_unref(session_bus); return; } if (!dbus_connection_set_timeout_functions(session_bus, add_dbus_timeout, remove_dbus_timeout, toggle_dbus_timeout, (void*)name, NULL)) { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS timeout functions on connection to: %s", name); dbus_connection_close(session_bus); dbus_connection_unref(session_bus); return; } } DBusConnection * glfw_dbus_session_bus(void) { if (!session_bus) glfw_dbus_connect_to_session_bus(); return session_bus; } kitty-0.41.1/glfw/dbus_glfw.h0000664000175000017510000000550614773370543015401 0ustar nileshnilesh//======================================================================== // GLFW 3.4 XKB - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2018 Kovid Goyal // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #pragma once #include #include "backend_utils.h" static inline void cleanup_msg(void *p) { DBusMessage *m = *(DBusMessage**)p; if (m) dbus_message_unref(m); m = NULL; } #define RAII_MSG(name, initializer) __attribute__((cleanup(cleanup_msg))) DBusMessage *name = initializer typedef void(*dbus_pending_callback)(DBusMessage *msg, const char* err, void* data); typedef struct { EventLoopData* eld; } _GLFWDBUSData; bool glfw_dbus_init(_GLFWDBUSData *dbus, EventLoopData *eld); void glfw_dbus_terminate(_GLFWDBUSData *dbus); DBusConnection* glfw_dbus_connect_to(const char *path, const char* err_msg, const char* name, bool register_on_bus); void glfw_dbus_close_connection(DBusConnection *conn); bool call_method_with_msg(DBusConnection *conn, DBusMessage *msg, int timeout, dbus_pending_callback callback, void *user_data, bool block); bool glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...); bool glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout_ms, dbus_pending_callback callback, void *user_data, ...); bool glfw_dbus_call_blocking_method(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void* user_data, ...); void glfw_dbus_dispatch(DBusConnection *); void glfw_dbus_session_bus_dispatch(void); bool glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...); int glfw_dbus_match_signal(DBusMessage *msg, const char *interface, ...); DBusConnection* glfw_dbus_session_bus(void); kitty-0.41.1/glfw/egl_context.c0000664000175000017510000006424214773370543015735 0ustar nileshnilesh//======================================================================== // GLFW 3.4 EGL - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // Please use C89 style variable declarations in this file because VS 2010 //======================================================================== #include "internal.h" #include #include #include #include // Return a description of the specified EGL error // static const char* getEGLErrorString(EGLint error) { switch (error) { case EGL_SUCCESS: return "Success"; case EGL_NOT_INITIALIZED: return "EGL is not or could not be initialized"; case EGL_BAD_ACCESS: return "EGL cannot access a requested resource"; case EGL_BAD_ALLOC: return "EGL failed to allocate resources for the requested operation"; case EGL_BAD_ATTRIBUTE: return "An unrecognized attribute or attribute value was passed in the attribute list"; case EGL_BAD_CONTEXT: return "An EGLContext argument does not name a valid EGL rendering context"; case EGL_BAD_CONFIG: return "An EGLConfig argument does not name a valid EGL frame buffer configuration"; case EGL_BAD_CURRENT_SURFACE: return "The current surface of the calling thread is a window, pixel buffer or pixmap that is no longer valid"; case EGL_BAD_DISPLAY: return "An EGLDisplay argument does not name a valid EGL display connection"; case EGL_BAD_SURFACE: return "An EGLSurface argument does not name a valid surface configured for GL rendering"; case EGL_BAD_MATCH: return "Arguments are inconsistent"; case EGL_BAD_PARAMETER: return "One or more argument values are invalid"; case EGL_BAD_NATIVE_PIXMAP: return "A NativePixmapType argument does not refer to a valid native pixmap"; case EGL_BAD_NATIVE_WINDOW: return "A NativeWindowType argument does not refer to a valid native window"; case EGL_CONTEXT_LOST: return "The application must destroy all contexts and reinitialise"; default: return "ERROR: UNKNOWN EGL ERROR"; } } #ifdef _GLFW_X11 // Returns the specified attribute of the specified EGLConfig // static int getEGLConfigAttrib(EGLConfig config, int attrib) { int value; eglGetConfigAttrib(_glfw.egl.display, config, attrib, &value); return value; } #endif // Return the EGLConfig most closely matching the specified hints // static bool chooseEGLConfig(const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* desired, EGLConfig* result) { EGLConfig configs[512]; int i = 0, nativeCount = 0, ans_idx = 0; EGLint attributes[64]; #define ATTR(k, v) { attributes[i++] = k; attributes[i++] = v; } ATTR(EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER); ATTR(EGL_SURFACE_TYPE, EGL_WINDOW_BIT); if (ctxconfig->client == GLFW_OPENGL_ES_API) { if (ctxconfig->major == 1) ATTR(EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT) else ATTR(EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT); } else if (ctxconfig->client == GLFW_OPENGL_API) ATTR(EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT); if (desired->samples > 0) ATTR(EGL_SAMPLES, desired->samples); if (desired->depthBits > 0) ATTR(EGL_DEPTH_SIZE, desired->depthBits); if (desired->stencilBits > 0) ATTR(EGL_STENCIL_SIZE, desired->stencilBits); if (desired->redBits > 0) ATTR(EGL_RED_SIZE, desired->redBits); if (desired->greenBits > 0) ATTR(EGL_GREEN_SIZE, desired->greenBits); if (desired->blueBits > 0) ATTR(EGL_BLUE_SIZE, desired->blueBits); if (desired->alphaBits > 0) ATTR(EGL_ALPHA_SIZE, desired->alphaBits); ATTR(EGL_NONE, EGL_NONE); #undef ATTR if (!eglChooseConfig(_glfw.egl.display, attributes, configs, sizeof(configs)/sizeof(configs[0]), &nativeCount)) { _glfwInputError(GLFW_API_UNAVAILABLE, "EGL: eglChooseConfig failed"); return false; } if (!nativeCount) { _glfwInputError(GLFW_API_UNAVAILABLE, "EGL: No EGLConfigs returned"); return false; } for (i = 0; i < nativeCount; i++) { #if defined(_GLFW_X11) { const EGLConfig n = configs[i]; XVisualInfo vi = {0}; // Only consider EGLConfigs with associated Visuals vi.visualid = getEGLConfigAttrib(n, EGL_NATIVE_VISUAL_ID); if (!vi.visualid) continue; if (desired->transparent) { int count; XVisualInfo* vis = XGetVisualInfo(_glfw.x11.display, VisualIDMask, &vi, &count); if (vis) { bool transparent = _glfwIsVisualTransparentX11(vis[0].visual); XFree(vis); if (!transparent) continue; } } } #endif // _GLFW_X11 ans_idx = i; break; } *result = configs[ans_idx]; return true; } static void makeContextCurrentEGL(_GLFWwindow* window) { if (window) { if (!eglMakeCurrent(_glfw.egl.display, window->context.egl.surface, window->context.egl.surface, window->context.egl.handle)) { _glfwInputError(GLFW_PLATFORM_ERROR, "EGL: Failed to make context current: %s", getEGLErrorString(eglGetError())); return; } } else { if (!eglMakeCurrent(_glfw.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { _glfwInputError(GLFW_PLATFORM_ERROR, "EGL: Failed to clear current context: %s", getEGLErrorString(eglGetError())); return; } } _glfwPlatformSetTls(&_glfw.contextSlot, window); } static void swapBuffersEGL(_GLFWwindow* window) { if (window != _glfwPlatformGetTls(&_glfw.contextSlot)) { _glfwInputError(GLFW_PLATFORM_ERROR, "EGL: The context must be current on the calling thread when swapping buffers"); return; } eglSwapBuffers(_glfw.egl.display, window->context.egl.surface); } static void swapIntervalEGL(int interval) { eglSwapInterval(_glfw.egl.display, interval); } static int extensionSupportedEGL(const char* extension) { const char* extensions = eglQueryString(_glfw.egl.display, EGL_EXTENSIONS); if (extensions) { if (_glfwStringInExtensionString(extension, extensions)) return true; } return false; } static GLFWglproc getProcAddressEGL(const char* procname) { _GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot); if (window->context.egl.client) { GLFWglproc proc = NULL; glfw_dlsym(proc, window->context.egl.client, procname); if (proc) return proc; } return eglGetProcAddress(procname); } static void destroyContextEGL(_GLFWwindow* window) { #if defined(_GLFW_X11) // NOTE: Do not unload libGL.so.1 while the X11 display is still open, // as it will make XCloseDisplay segfault if (window->context.client != GLFW_OPENGL_API) #endif // _GLFW_X11 { if (window->context.egl.client) { _glfw_dlclose(window->context.egl.client); window->context.egl.client = NULL; } } if (window->context.egl.surface) { eglDestroySurface(_glfw.egl.display, window->context.egl.surface); window->context.egl.surface = EGL_NO_SURFACE; } if (window->context.egl.handle) { eglDestroyContext(_glfw.egl.display, window->context.egl.handle); window->context.egl.handle = EGL_NO_CONTEXT; } } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// // Initialize EGL // bool _glfwInitEGL(void) { int i; EGLint* attribs = NULL; const char* extensions; const char* sonames[] = { #if defined(_GLFW_EGL_LIBRARY) _GLFW_EGL_LIBRARY, #elif defined(_GLFW_WIN32) "libEGL.dll", "EGL.dll", #elif defined(_GLFW_COCOA) "libEGL.dylib", #elif defined(__CYGWIN__) "libEGL-1.so", #else "libEGL.so.1", #endif NULL }; if (_glfw.egl.handle) return true; for (i = 0; sonames[i]; i++) { _glfw.egl.handle = _glfw_dlopen(sonames[i]); if (_glfw.egl.handle) break; } if (!_glfw.egl.handle) { _glfwInputError(GLFW_API_UNAVAILABLE, "EGL: Library not found"); return false; } _glfw.egl.prefix = (strncmp(sonames[i], "lib", 3) == 0); glfw_dlsym(_glfw.egl.GetConfigAttrib, _glfw.egl.handle, "eglGetConfigAttrib"); glfw_dlsym(_glfw.egl.GetConfigs, _glfw.egl.handle, "eglGetConfigs"); glfw_dlsym(_glfw.egl.ChooseConfig, _glfw.egl.handle, "eglChooseConfig"); glfw_dlsym(_glfw.egl.GetDisplay, _glfw.egl.handle, "eglGetDisplay"); glfw_dlsym(_glfw.egl.GetError, _glfw.egl.handle, "eglGetError"); glfw_dlsym(_glfw.egl.Initialize, _glfw.egl.handle, "eglInitialize"); glfw_dlsym(_glfw.egl.Terminate, _glfw.egl.handle, "eglTerminate"); glfw_dlsym(_glfw.egl.BindAPI, _glfw.egl.handle, "eglBindAPI"); glfw_dlsym(_glfw.egl.CreateContext, _glfw.egl.handle, "eglCreateContext"); glfw_dlsym(_glfw.egl.DestroySurface, _glfw.egl.handle, "eglDestroySurface"); glfw_dlsym(_glfw.egl.DestroyContext, _glfw.egl.handle, "eglDestroyContext"); glfw_dlsym(_glfw.egl.CreateWindowSurface, _glfw.egl.handle, "eglCreateWindowSurface"); glfw_dlsym(_glfw.egl.MakeCurrent, _glfw.egl.handle, "eglMakeCurrent"); glfw_dlsym(_glfw.egl.SwapBuffers, _glfw.egl.handle, "eglSwapBuffers"); glfw_dlsym(_glfw.egl.SwapInterval, _glfw.egl.handle, "eglSwapInterval"); glfw_dlsym(_glfw.egl.QueryString, _glfw.egl.handle, "eglQueryString"); glfw_dlsym(_glfw.egl.QuerySurface, _glfw.egl.handle, "eglQuerySurface"); glfw_dlsym(_glfw.egl.GetProcAddress, _glfw.egl.handle, "eglGetProcAddress"); if (!_glfw.egl.GetConfigAttrib || !_glfw.egl.GetConfigs || !_glfw.egl.ChooseConfig || !_glfw.egl.GetDisplay || !_glfw.egl.GetError || !_glfw.egl.Initialize || !_glfw.egl.Terminate || !_glfw.egl.BindAPI || !_glfw.egl.CreateContext || !_glfw.egl.DestroySurface || !_glfw.egl.DestroyContext || !_glfw.egl.CreateWindowSurface || !_glfw.egl.MakeCurrent || !_glfw.egl.SwapBuffers || !_glfw.egl.SwapInterval || !_glfw.egl.QueryString || !_glfw.egl.GetProcAddress) { _glfwInputError(GLFW_PLATFORM_ERROR, "EGL: Failed to load required entry points"); _glfwTerminateEGL(); return false; } extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); if (extensions && eglGetError() == EGL_SUCCESS) _glfw.egl.EXT_client_extensions = true; if (_glfw.egl.EXT_client_extensions) { _glfw.egl.EXT_platform_base = _glfwStringInExtensionString("EGL_EXT_platform_base", extensions); _glfw.egl.EXT_platform_x11 = _glfwStringInExtensionString("EGL_EXT_platform_x11", extensions); _glfw.egl.EXT_platform_wayland = _glfwStringInExtensionString("EGL_EXT_platform_wayland", extensions); _glfw.egl.ANGLE_platform_angle = _glfwStringInExtensionString("EGL_ANGLE_platform_angle", extensions); _glfw.egl.ANGLE_platform_angle_opengl = _glfwStringInExtensionString("EGL_ANGLE_platform_angle_opengl", extensions); _glfw.egl.ANGLE_platform_angle_d3d = _glfwStringInExtensionString("EGL_ANGLE_platform_angle_d3d", extensions); _glfw.egl.ANGLE_platform_angle_vulkan = _glfwStringInExtensionString("EGL_ANGLE_platform_angle_vulkan", extensions); _glfw.egl.ANGLE_platform_angle_metal = _glfwStringInExtensionString("EGL_ANGLE_platform_angle_metal", extensions); } if (_glfw.egl.EXT_platform_base) { _glfw.egl.GetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC) eglGetProcAddress("eglGetPlatformDisplayEXT"); _glfw.egl.CreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); } _glfw.egl.platform = _glfwPlatformGetEGLPlatform(&attribs); if (_glfw.egl.platform) { _glfw.egl.display = eglGetPlatformDisplayEXT(_glfw.egl.platform, _glfwPlatformGetEGLNativeDisplay(), attribs); } else _glfw.egl.display = eglGetDisplay(_glfwPlatformGetEGLNativeDisplay()); free(attribs); if (_glfw.egl.display == EGL_NO_DISPLAY) { _glfwInputError(GLFW_API_UNAVAILABLE, "EGL: Failed to get EGL display: %s", getEGLErrorString(eglGetError())); _glfwTerminateEGL(); return false; } if (!eglInitialize(_glfw.egl.display, &_glfw.egl.major, &_glfw.egl.minor)) { _glfwInputError(GLFW_API_UNAVAILABLE, "EGL: Failed to initialize EGL: %s", getEGLErrorString(eglGetError())); _glfwTerminateEGL(); return false; } _glfw.egl.KHR_create_context = extensionSupportedEGL("EGL_KHR_create_context"); _glfw.egl.KHR_create_context_no_error = extensionSupportedEGL("EGL_KHR_create_context_no_error"); _glfw.egl.KHR_gl_colorspace = extensionSupportedEGL("EGL_KHR_gl_colorspace"); _glfw.egl.KHR_get_all_proc_addresses = extensionSupportedEGL("EGL_KHR_get_all_proc_addresses"); _glfw.egl.KHR_context_flush_control = extensionSupportedEGL("EGL_KHR_context_flush_control"); _glfw.egl.EXT_present_opaque = extensionSupportedEGL("EGL_EXT_present_opaque"); return true; } // Terminate EGL // void _glfwTerminateEGL(void) { if (_glfw.egl.display) { eglTerminate(_glfw.egl.display); _glfw.egl.display = EGL_NO_DISPLAY; } if (_glfw.egl.handle) { _glfw_dlclose(_glfw.egl.handle); _glfw.egl.handle = NULL; } } #define setAttrib(a, v) \ { \ assert(((size_t) index + 1) < sizeof(attribs) / sizeof(attribs[0])); \ attribs[index++] = a; \ attribs[index++] = v; \ } // Create the OpenGL or OpenGL ES context // bool _glfwCreateContextEGL(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { EGLint attribs[40]; EGLConfig config; EGLContext share = NULL; EGLNativeWindowType native; int index = 0; if (!_glfw.egl.display) { _glfwInputError(GLFW_API_UNAVAILABLE, "EGL: API not available"); return false; } if (ctxconfig->share) share = ctxconfig->share->context.egl.handle; if (!chooseEGLConfig(ctxconfig, fbconfig, &config)) { _glfwInputError(GLFW_FORMAT_UNAVAILABLE, "EGL: Failed to find a suitable EGLConfig"); return false; } if (ctxconfig->client == GLFW_OPENGL_ES_API) { if (!eglBindAPI(EGL_OPENGL_ES_API)) { _glfwInputError(GLFW_API_UNAVAILABLE, "EGL: Failed to bind OpenGL ES: %s", getEGLErrorString(eglGetError())); return false; } } else { if (!eglBindAPI(EGL_OPENGL_API)) { _glfwInputError(GLFW_API_UNAVAILABLE, "EGL: Failed to bind OpenGL: %s", getEGLErrorString(eglGetError())); return false; } } if (_glfw.egl.KHR_create_context) { int mask = 0, flags = 0; if (ctxconfig->client == GLFW_OPENGL_API) { if (ctxconfig->forward) flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR; if (ctxconfig->profile == GLFW_OPENGL_CORE_PROFILE) mask |= EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR; else if (ctxconfig->profile == GLFW_OPENGL_COMPAT_PROFILE) mask |= EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR; } if (ctxconfig->debug) flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR; if (ctxconfig->robustness) { if (ctxconfig->robustness == GLFW_NO_RESET_NOTIFICATION) { setAttrib(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, EGL_NO_RESET_NOTIFICATION_KHR); } else if (ctxconfig->robustness == GLFW_LOSE_CONTEXT_ON_RESET) { setAttrib(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, EGL_LOSE_CONTEXT_ON_RESET_KHR); } flags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR; } if (ctxconfig->noerror) { if (_glfw.egl.KHR_create_context_no_error) setAttrib(EGL_CONTEXT_OPENGL_NO_ERROR_KHR, true); } if (ctxconfig->major != 1 || ctxconfig->minor != 0) { setAttrib(EGL_CONTEXT_MAJOR_VERSION_KHR, ctxconfig->major); setAttrib(EGL_CONTEXT_MINOR_VERSION_KHR, ctxconfig->minor); } if (mask) setAttrib(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, mask); if (flags) setAttrib(EGL_CONTEXT_FLAGS_KHR, flags); } else { if (ctxconfig->client == GLFW_OPENGL_ES_API) setAttrib(EGL_CONTEXT_CLIENT_VERSION, ctxconfig->major); } if (_glfw.egl.KHR_context_flush_control) { if (ctxconfig->release == GLFW_RELEASE_BEHAVIOR_NONE) { setAttrib(EGL_CONTEXT_RELEASE_BEHAVIOR_KHR, EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR); } else if (ctxconfig->release == GLFW_RELEASE_BEHAVIOR_FLUSH) { setAttrib(EGL_CONTEXT_RELEASE_BEHAVIOR_KHR, EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR); } } setAttrib(EGL_NONE, EGL_NONE); window->context.egl.handle = eglCreateContext(_glfw.egl.display, config, share, attribs); if (window->context.egl.handle == EGL_NO_CONTEXT) { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "EGL: Failed to create context: %s", getEGLErrorString(eglGetError())); return false; } // Set up attributes for surface creation index = 0; if (fbconfig->sRGB) { if (_glfw.egl.KHR_gl_colorspace) setAttrib(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR); } // Disabled because it prevents transparency from working on NVIDIA drivers under Wayland // https://github.com/kovidgoyal/kitty/issues/5479 // We anyway dont use the alpha bits for anything. /* if (_glfw.egl.EXT_present_opaque) */ /* setAttrib(EGL_PRESENT_OPAQUE_EXT, !fbconfig->transparent); */ setAttrib(EGL_NONE, EGL_NONE); native = _glfwPlatformGetEGLNativeWindow(window); // HACK: ANGLE does not implement eglCreatePlatformWindowSurfaceEXT // despite reporting EGL_EXT_platform_base if (_glfw.egl.platform && _glfw.egl.platform != EGL_PLATFORM_ANGLE_ANGLE) { window->context.egl.surface = eglCreatePlatformWindowSurfaceEXT(_glfw.egl.display, config, native, attribs); } else { window->context.egl.surface = eglCreateWindowSurface(_glfw.egl.display, config, native, attribs); } if (window->context.egl.surface == EGL_NO_SURFACE) { _glfwInputError(GLFW_PLATFORM_ERROR, "EGL: Failed to create window surface: %s", getEGLErrorString(eglGetError())); return false; } window->context.egl.config = config; EGLint a = EGL_MIN_SWAP_INTERVAL; if (!eglGetConfigAttrib(_glfw.egl.display, config, a, &a)) { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "EGL: could not check for non-blocking buffer swap with error: %s", getEGLErrorString(eglGetError())); } else { if (a > 0) { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "EGL: non-blocking swap buffers not available, minimum swap interval is: %d", a); } } // Load the appropriate client library if (!_glfw.egl.KHR_get_all_proc_addresses) { int i; const char** sonames; const char* es1sonames[] = { #if defined(_GLFW_GLESV1_LIBRARY) _GLFW_GLESV1_LIBRARY, #elif defined(_GLFW_WIN32) "GLESv1_CM.dll", "libGLES_CM.dll", #elif defined(_GLFW_COCOA) "libGLESv1_CM.dylib", #else "libGLESv1_CM.so.1", "libGLES_CM.so.1", #endif NULL }; const char* es2sonames[] = { #if defined(_GLFW_GLESV2_LIBRARY) _GLFW_GLESV2_LIBRARY, #elif defined(_GLFW_WIN32) "GLESv2.dll", "libGLESv2.dll", #elif defined(_GLFW_COCOA) "libGLESv2.dylib", #elif defined(__CYGWIN__) "libGLESv2-2.so", #else "libGLESv2.so.2", #endif NULL }; const char* glsonames[] = { #if defined(_GLFW_OPENGL_LIBRARY) _GLFW_OPENGL_LIBRARY, #elif defined(_GLFW_WIN32) #elif defined(_GLFW_COCOA) #else "libGL.so.1", #endif NULL }; if (ctxconfig->client == GLFW_OPENGL_ES_API) { if (ctxconfig->major == 1) sonames = es1sonames; else sonames = es2sonames; } else sonames = glsonames; for (i = 0; sonames[i]; i++) { // HACK: Match presence of lib prefix to increase chance of finding // a matching pair in the jungle that is Win32 EGL/GLES if (_glfw.egl.prefix != (strncmp(sonames[i], "lib", 3) == 0)) continue; window->context.egl.client = _glfw_dlopen(sonames[i]); if (window->context.egl.client) break; } if (!window->context.egl.client) { _glfwInputError(GLFW_API_UNAVAILABLE, "EGL: Failed to load client library"); return false; } } window->context.makeCurrent = makeContextCurrentEGL; window->context.swapBuffers = swapBuffersEGL; window->context.swapInterval = swapIntervalEGL; window->context.extensionSupported = extensionSupportedEGL; window->context.getProcAddress = getProcAddressEGL; window->context.destroy = destroyContextEGL; return true; } #undef setAttrib // Returns the Visual and depth of the chosen EGLConfig // #if defined(_GLFW_X11) bool _glfwChooseVisualEGL(const _GLFWwndconfig* wndconfig UNUSED, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig, Visual** visual, int* depth) { XVisualInfo* result; XVisualInfo desired; EGLConfig native; EGLint visualID = 0, count = 0; const long vimask = VisualScreenMask | VisualIDMask; if (!chooseEGLConfig(ctxconfig, fbconfig, &native)) { _glfwInputError(GLFW_FORMAT_UNAVAILABLE, "EGL: Failed to find a suitable EGLConfig"); return false; } eglGetConfigAttrib(_glfw.egl.display, native, EGL_NATIVE_VISUAL_ID, &visualID); desired.screen = _glfw.x11.screen; desired.visualid = visualID; result = XGetVisualInfo(_glfw.x11.display, vimask, &desired, &count); if (!result) { _glfwInputError(GLFW_PLATFORM_ERROR, "EGL: Failed to retrieve Visual for EGLConfig"); return false; } *visual = result->visual; *depth = result->depth; XFree(result); return true; } #endif // _GLFW_X11 ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI EGLDisplay glfwGetEGLDisplay(void) { _GLFW_REQUIRE_INIT_OR_RETURN(EGL_NO_DISPLAY); return _glfw.egl.display; } GLFWAPI EGLContext glfwGetEGLContext(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(EGL_NO_CONTEXT); if (window->context.client == GLFW_NO_API) { _glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL); return EGL_NO_CONTEXT; } return window->context.egl.handle; } GLFWAPI EGLSurface glfwGetEGLSurface(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(EGL_NO_SURFACE); if (window->context.client == GLFW_NO_API) { _glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL); return EGL_NO_SURFACE; } return window->context.egl.surface; } kitty-0.41.1/glfw/egl_context.h0000664000175000017510000002307714773370543015743 0ustar nileshnilesh//======================================================================== // GLFW 3.4 EGL - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2017 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #if defined(_GLFW_WIN32) #define EGLAPIENTRY __stdcall #else #define EGLAPIENTRY #endif #define EGL_SUCCESS 0x3000 #define EGL_NOT_INITIALIZED 0x3001 #define EGL_BAD_ACCESS 0x3002 #define EGL_BAD_ALLOC 0x3003 #define EGL_BAD_ATTRIBUTE 0x3004 #define EGL_BAD_CONFIG 0x3005 #define EGL_BAD_CONTEXT 0x3006 #define EGL_BAD_CURRENT_SURFACE 0x3007 #define EGL_BAD_DISPLAY 0x3008 #define EGL_BAD_MATCH 0x3009 #define EGL_BAD_NATIVE_PIXMAP 0x300a #define EGL_BAD_NATIVE_WINDOW 0x300b #define EGL_BAD_PARAMETER 0x300c #define EGL_BAD_SURFACE 0x300d #define EGL_CONTEXT_LOST 0x300e #define EGL_COLOR_BUFFER_TYPE 0x303f #define EGL_RGB_BUFFER 0x308e #define EGL_SURFACE_TYPE 0x3033 #define EGL_WINDOW_BIT 0x0004 #define EGL_RENDERABLE_TYPE 0x3040 #define EGL_OPENGL_ES_BIT 0x0001 #define EGL_OPENGL_ES2_BIT 0x0004 #define EGL_OPENGL_BIT 0x0008 #define EGL_ALPHA_SIZE 0x3021 #define EGL_BLUE_SIZE 0x3022 #define EGL_GREEN_SIZE 0x3023 #define EGL_RED_SIZE 0x3024 #define EGL_DEPTH_SIZE 0x3025 #define EGL_STENCIL_SIZE 0x3026 #define EGL_SAMPLES 0x3031 #define EGL_OPENGL_ES_API 0x30a0 #define EGL_OPENGL_API 0x30a2 #define EGL_NONE 0x3038 #define EGL_EXTENSIONS 0x3055 #define EGL_CONTEXT_CLIENT_VERSION 0x3098 #define EGL_NATIVE_VISUAL_ID 0x302e #define EGL_NO_SURFACE ((EGLSurface) 0) #define EGL_NO_DISPLAY ((EGLDisplay) 0) #define EGL_NO_CONTEXT ((EGLContext) 0) #define EGL_DEFAULT_DISPLAY ((EGLNativeDisplayType) 0) #define EGL_MIN_SWAP_INTERVAL 0x303B #define EGL_MAX_SWAP_INTERVAL 0x303C #define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR 0x00000002 #define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR 0x00000001 #define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR 0x00000002 #define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR 0x00000001 #define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR 0x31bd #define EGL_NO_RESET_NOTIFICATION_KHR 0x31be #define EGL_LOSE_CONTEXT_ON_RESET_KHR 0x31bf #define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR 0x00000004 #define EGL_CONTEXT_MAJOR_VERSION_KHR 0x3098 #define EGL_CONTEXT_MINOR_VERSION_KHR 0x30fb #define EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR 0x30fd #define EGL_CONTEXT_FLAGS_KHR 0x30fc #define EGL_CONTEXT_OPENGL_NO_ERROR_KHR 0x31b3 #define EGL_GL_COLORSPACE_KHR 0x309d #define EGL_GL_COLORSPACE_SRGB_KHR 0x3089 #define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097 #define EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR 0 #define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098 #define EGL_PLATFORM_X11_EXT 0x31d5 #define EGL_PLATFORM_WAYLAND_EXT 0x31d8 #define EGL_PLATFORM_ANGLE_ANGLE 0x3202 #define EGL_PLATFORM_ANGLE_TYPE_ANGLE 0x3203 #define EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE 0x320d #define EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE 0x320e #define EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE 0x3207 #define EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE 0x3208 #define EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE 0x3450 #define EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE 0x3489 #define EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE 0x348f typedef int EGLint; typedef unsigned int EGLBoolean; typedef unsigned int EGLenum; typedef void* EGLConfig; typedef void* EGLContext; typedef void* EGLDisplay; typedef void* EGLSurface; typedef void* EGLNativeDisplayType; typedef void* EGLNativeWindowType; // EGL function pointer typedefs typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigAttrib)(EGLDisplay,EGLConfig,EGLint,EGLint*); typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigs)(EGLDisplay,EGLConfig*,EGLint,EGLint*); typedef EGLBoolean (EGLAPIENTRY * PFN_eglChooseConfig)(EGLDisplay,EGLint const*,EGLConfig*,EGLint,EGLint*); typedef EGLDisplay (EGLAPIENTRY * PFN_eglGetDisplay)(EGLNativeDisplayType); typedef EGLint (EGLAPIENTRY * PFN_eglGetError)(void); typedef EGLBoolean (EGLAPIENTRY * PFN_eglInitialize)(EGLDisplay,EGLint*,EGLint*); typedef EGLBoolean (EGLAPIENTRY * PFN_eglTerminate)(EGLDisplay); typedef EGLBoolean (EGLAPIENTRY * PFN_eglBindAPI)(EGLenum); typedef EGLContext (EGLAPIENTRY * PFN_eglCreateContext)(EGLDisplay,EGLConfig,EGLContext,const EGLint*); typedef EGLBoolean (EGLAPIENTRY * PFN_eglDestroySurface)(EGLDisplay,EGLSurface); typedef EGLBoolean (EGLAPIENTRY * PFN_eglDestroyContext)(EGLDisplay,EGLContext); typedef EGLSurface (EGLAPIENTRY * PFN_eglCreateWindowSurface)(EGLDisplay,EGLConfig,EGLNativeWindowType,const EGLint*); typedef EGLBoolean (EGLAPIENTRY * PFN_eglMakeCurrent)(EGLDisplay,EGLSurface,EGLSurface,EGLContext); typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapBuffers)(EGLDisplay,EGLSurface); typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapInterval)(EGLDisplay,EGLint); typedef const char* (EGLAPIENTRY * PFN_eglQueryString)(EGLDisplay,EGLint); typedef const char* (EGLAPIENTRY * PFN_eglQuerySurface)(EGLDisplay,EGLSurface,EGLint,EGLint*); typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*); #define eglGetConfigAttrib _glfw.egl.GetConfigAttrib #define eglGetConfigs _glfw.egl.GetConfigs #define eglChooseConfig _glfw.egl.ChooseConfig #define eglGetDisplay _glfw.egl.GetDisplay #define eglGetError _glfw.egl.GetError #define eglInitialize _glfw.egl.Initialize #define eglTerminate _glfw.egl.Terminate #define eglBindAPI _glfw.egl.BindAPI #define eglCreateContext _glfw.egl.CreateContext #define eglDestroySurface _glfw.egl.DestroySurface #define eglDestroyContext _glfw.egl.DestroyContext #define eglCreateWindowSurface _glfw.egl.CreateWindowSurface #define eglMakeCurrent _glfw.egl.MakeCurrent #define eglSwapBuffers _glfw.egl.SwapBuffers #define eglSwapInterval _glfw.egl.SwapInterval #define eglQueryString _glfw.egl.QueryString #define eglQuerySurface _glfw.egl.QuerySurface #define eglGetProcAddress _glfw.egl.GetProcAddress typedef EGLDisplay (EGLAPIENTRY * PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum,void*,const EGLint*); typedef EGLSurface (EGLAPIENTRY * PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)(EGLDisplay,EGLConfig,void*,const EGLint*); #define eglGetPlatformDisplayEXT _glfw.egl.GetPlatformDisplayEXT #define eglCreatePlatformWindowSurfaceEXT _glfw.egl.CreatePlatformWindowSurfaceEXT // EGL-specific per-context data // typedef struct _GLFWcontextEGL { EGLConfig config; EGLContext handle; EGLSurface surface; void* client; } _GLFWcontextEGL; // EGL-specific global data // typedef struct _GLFWlibraryEGL { EGLenum platform; EGLDisplay display; EGLint major, minor; bool prefix; bool KHR_create_context; bool KHR_create_context_no_error; bool KHR_gl_colorspace; bool KHR_get_all_proc_addresses; bool KHR_context_flush_control; bool EXT_client_extensions; bool EXT_platform_base; bool EXT_platform_x11; bool EXT_platform_wayland; bool EXT_present_opaque; bool ANGLE_platform_angle; bool ANGLE_platform_angle_opengl; bool ANGLE_platform_angle_d3d; bool ANGLE_platform_angle_vulkan; bool ANGLE_platform_angle_metal; void* handle; PFN_eglGetConfigAttrib GetConfigAttrib; PFN_eglGetConfigs GetConfigs; PFN_eglChooseConfig ChooseConfig; PFN_eglGetDisplay GetDisplay; PFN_eglGetError GetError; PFN_eglInitialize Initialize; PFN_eglTerminate Terminate; PFN_eglBindAPI BindAPI; PFN_eglCreateContext CreateContext; PFN_eglDestroySurface DestroySurface; PFN_eglDestroyContext DestroyContext; PFN_eglCreateWindowSurface CreateWindowSurface; PFN_eglMakeCurrent MakeCurrent; PFN_eglSwapBuffers SwapBuffers; PFN_eglSwapInterval SwapInterval; PFN_eglQueryString QueryString; PFN_eglQuerySurface QuerySurface; PFN_eglGetProcAddress GetProcAddress; PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT; PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC CreatePlatformWindowSurfaceEXT; } _GLFWlibraryEGL; bool _glfwInitEGL(void); void _glfwTerminateEGL(void); bool _glfwCreateContextEGL(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig); #if defined(_GLFW_X11) bool _glfwChooseVisualEGL(const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig, Visual** visual, int* depth); #endif /*_GLFW_X11*/ kitty-0.41.1/glfw/glfw.py0000775000175000017510000003631314773370543014570 0ustar nileshnilesh#!/usr/bin/env python # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2017, Kovid Goyal import json import os import re import subprocess import sys from enum import Enum from typing import Any, Callable, Dict, List, NamedTuple, Optional, Sequence, Tuple _plat = sys.platform.lower() is_linux = 'linux' in _plat is_openbsd = 'openbsd' in _plat base = os.path.dirname(os.path.abspath(__file__)) def null_func() -> None: return None class CompileKey(NamedTuple): src: str dest: str class Command(NamedTuple): desc: str cmd: Sequence[str] is_newer_func: Callable[[], bool] on_success: Callable[[], None] = null_func key: Optional[CompileKey] = None keyfile: Optional[str] = None class ISA(Enum): X86 = 0x03 AMD64 = 0x3e ARM64 = 0xb7 Other = 0x0 class BinaryArch(NamedTuple): bits: int = 64 isa: ISA = ISA.AMD64 class CompilerType(Enum): gcc = 'gcc' clang = 'clang' unknown = 'unknown' class Env: cc: List[str] = [] cppflags: List[str] = [] cflags: List[str] = [] ldflags: List[str] = [] library_paths: Dict[str, List[str]] = {} ldpaths: List[str] = [] ccver: Tuple[int, int] vcs_rev: str = '' binary_arch: BinaryArch = BinaryArch() native_optimizations: bool = False primary_version: int = 0 secondary_version: int = 0 xt_version: str = '' has_copy_file_range: bool = False # glfw stuff all_headers: List[str] = [] sources: List[str] = [] wayland_packagedir: str = '' wayland_scanner: str = '' wayland_scanner_code: str = '' wayland_protocols: Tuple[str, ...] = () def __init__( self, cc: List[str] = [], cppflags: List[str] = [], cflags: List[str] = [], ldflags: List[str] = [], library_paths: Dict[str, List[str]] = {}, ldpaths: Optional[List[str]] = None, ccver: Tuple[int, int] = (0, 0), vcs_rev: str = '', binary_arch: BinaryArch = BinaryArch(), native_optimizations: bool = False, ): self.cc, self.cppflags, self.cflags, self.ldflags, self.library_paths = cc, cppflags, cflags, ldflags, library_paths self.ldpaths = ldpaths or [] self.ccver = ccver self.vcs_rev = vcs_rev self.binary_arch = binary_arch self.native_optimizations = native_optimizations self._cc_version_string = '' self._compiler_type: Optional[CompilerType] = None @property def cc_version_string(self) -> str: if not self._cc_version_string: self._cc_version_string = subprocess.check_output(self.cc + ['--version']).decode() return self._cc_version_string @property def compiler_type(self) -> CompilerType: if self._compiler_type is None: raw = self.cc_version_string if 'Free Software Foundation' in raw: self._compiler_type = CompilerType.gcc elif 'clang' in raw.lower().split(): self._compiler_type = CompilerType.clang else: self._compiler_type = CompilerType.unknown return self._compiler_type def copy(self) -> 'Env': ans = Env(self.cc, list(self.cppflags), list(self.cflags), list(self.ldflags), dict(self.library_paths), list(self.ldpaths), self.ccver) ans.all_headers = list(self.all_headers) ans._cc_version_string = self._cc_version_string ans.sources = list(self.sources) ans.wayland_packagedir = self.wayland_packagedir ans.wayland_scanner = self.wayland_scanner ans.wayland_scanner_code = self.wayland_scanner_code ans.wayland_protocols = self.wayland_protocols ans.vcs_rev = self.vcs_rev ans.binary_arch = self.binary_arch ans.native_optimizations = self.native_optimizations ans.primary_version = self.primary_version ans.secondary_version = self.secondary_version ans.xt_version = self.xt_version ans.has_copy_file_range = self.has_copy_file_range return ans def wayland_protocol_file_name(base: str, ext: str = 'c') -> str: base = os.path.basename(base).rpartition('.')[0] return f'wayland-{base}-client-protocol.{ext}' def init_env( env: Env, pkg_config: Callable[..., List[str]], pkg_version: Callable[[str], Tuple[int, int]], at_least_version: Callable[..., None], test_compile: Callable[..., Any], module: str = 'x11' ) -> Env: ans = env.copy() ans.cflags.append('-fPIC') ans.cppflags.append(f'-D_GLFW_{module.upper()}') ans.cppflags.append('-D_GLFW_BUILD_DLL') with open(os.path.join(base, 'source-info.json')) as f: sinfo = json.load(f) module_sources = list(sinfo[module]['sources']) if module in ('x11', 'wayland'): remove = 'null_joystick.c' if is_linux else 'linux_joystick.c' module_sources.remove(remove) ans.sources = sinfo['common']['sources'] + module_sources ans.all_headers = [x for x in os.listdir(base) if x.endswith('.h')] if module in ('x11', 'wayland'): ans.cflags.append('-pthread') ans.ldpaths.extend('-pthread -lm'.split()) if not is_openbsd: ans.ldpaths.extend('-lrt -ldl'.split()) major, minor = pkg_version('xkbcommon') if (major, minor) < (0, 5): raise SystemExit('libxkbcommon >= 0.5 required') if major < 1: ans.cflags.append('-DXKB_HAS_NO_UTF32') if module == 'x11': for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb dbus-1'.split(): ans.cflags.extend(pkg_config(dep, '--cflags-only-I')) ans.ldpaths.extend(pkg_config(dep, '--libs')) elif module == 'cocoa': ans.cppflags.append('-DGL_SILENCE_DEPRECATION') for f_ in 'Cocoa IOKit CoreFoundation CoreVideo UniformTypeIdentifiers'.split(): ans.ldpaths.extend(('-framework', f_)) elif module == 'wayland': at_least_version('wayland-protocols', *sinfo['wayland_protocols']) ans.wayland_packagedir = os.path.abspath(pkg_config('wayland-protocols', '--variable=pkgdatadir')[0]) ans.wayland_scanner = os.path.abspath(pkg_config('wayland-scanner', '--variable=wayland_scanner')[0]) scanner_version = tuple(map(int, pkg_config('wayland-scanner', '--modversion')[0].strip().split('.'))) ans.wayland_scanner_code = 'private-code' if scanner_version >= (1, 14, 91) else 'code' ans.wayland_protocols = tuple(sinfo[module]['protocols']) for p in ans.wayland_protocols: ans.sources.append(wayland_protocol_file_name(p)) ans.all_headers.append(wayland_protocol_file_name(p, 'h')) for dep in 'wayland-client wayland-cursor xkbcommon dbus-1'.split(): ans.cflags.extend(pkg_config(dep, '--cflags-only-I')) ans.ldpaths.extend(pkg_config(dep, '--libs')) has_memfd_create = test_compile(env.cc, '-Werror', src='''#define _GNU_SOURCE #include #include int main(void) { return syscall(__NR_memfd_create, "test", 0); }''') if has_memfd_create: ans.cppflags.append('-DHAS_MEMFD_CREATE') return ans def build_wayland_protocols( env: Env, parallel_run: Callable[[List[Command]], None], emphasis: Callable[[str], str], newer: Callable[..., bool], dest_dir: str ) -> None: items = [] for protocol in env.wayland_protocols: if '/' in protocol: src = os.path.join(env.wayland_packagedir, protocol) if not os.path.exists(src): raise SystemExit(f'The wayland-protocols package on your system is missing the {protocol} protocol definition file') else: src = os.path.join(os.path.dirname(os.path.abspath(__file__)), protocol) if not os.path.exists(src): raise SystemExit(f'The local Wayland protocol {protocol} is missing from kitty sources') for ext in 'hc': dest = wayland_protocol_file_name(src, ext) dest = os.path.join(dest_dir, dest) if newer(dest, src): q = 'client-header' if ext == 'h' else env.wayland_scanner_code items.append(Command( f'Generating {emphasis(os.path.basename(dest))} ...', [env.wayland_scanner, q, src, dest], lambda: True)) if items: parallel_run(items) class Arg: def __init__(self, decl: str): self.type, self.name = decl.rsplit(' ', 1) self.type = self.type.strip() self.name = self.name.strip() while self.name.startswith('*'): self.name = self.name[1:] self.type = self.type + '*' if '[' in self.name: self.type += '[' + self.name.partition('[')[-1] def __repr__(self) -> str: return f'Arg({self.type}, {self.name})' class Function: def __init__(self, declaration: str, check_fail: bool = True): self.check_fail = check_fail m = re.match( r'(.+?)\s+(glfw[A-Z][a-zA-Z0-9]+)[(](.+)[)]$', declaration ) if m is None: raise SystemExit('Failed to parse ' + repr(declaration)) self.restype = m.group(1).strip() self.name = m.group(2) args = m.group(3).strip().split(',') args = [x.strip() for x in args] self.args = [] for a in args: if a == 'void': continue self.args.append(Arg(a)) if not self.args: self.args = [Arg('void v')] def declaration(self) -> str: return 'typedef {restype} (*{name}_func)({args});\nGFW_EXTERN {name}_func {name}_impl;\n#define {name} {name}_impl'.format( restype=self.restype, name=self.name, args=', '.join(a.type for a in self.args) ) def load(self) -> str: ans = f'*(void **) (&{self.name}_impl) = dlsym(handle, "{self.name}");' ans += f'\n if ({self.name}_impl == NULL) ' if self.check_fail: ans += f'fail("Failed to load glfw function {self.name} with error: %s", dlerror());' else: ans += 'dlerror(); // clear error indicator' return ans def generate_wrappers(glfw_header: str) -> None: with open(glfw_header) as f: src = f.read() functions = [] first = None for m in re.finditer(r'^GLFWAPI\s+(.+[)]);\s*$', src, flags=re.MULTILINE): if first is None: first = m.start() decl = m.group(1) if 'VkInstance' in decl: continue functions.append(Function(decl)) for line in '''\ void* glfwGetCocoaWindow(GLFWwindow* window) void* glfwGetNSGLContext(GLFWwindow *window) uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor) GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback) GLFWhandleurlopen glfwSetCocoaURLOpenCallback(GLFWhandleurlopen callback) GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback) GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback) uint32_t glfwGetCocoaKeyEquivalent(uint32_t glfw_key, int glfw_mods, int* cocoa_mods) void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback) GLFWcocoarenderframefun glfwCocoaSetWindowResizeCallback(GLFWwindow *w, GLFWcocoarenderframefun callback) void* glfwGetX11Display(void) unsigned long glfwGetX11Window(GLFWwindow* window) void glfwSetPrimarySelectionString(GLFWwindow* window, const char* string) void glfwCocoaSetWindowChrome(GLFWwindow* window, unsigned int color, bool use_system_color, unsigned int system_color,\ int background_blur, unsigned int hide_window_decorations, bool show_text_in_titlebar, int color_space, float background_opacity, bool resizable) const char* glfwGetPrimarySelectionString(GLFWwindow* window, void) int glfwGetNativeKeyForName(const char* key_name, int case_sensitive) void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback) void glfwWaylandActivateWindow(GLFWwindow *handle, const char *activation_token) const char* glfwWaylandMissingCapabilities(void) void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data) bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color) void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle) bool glfwWaylandIsWindowFullyCreated(GLFWwindow *handle) void glfwWaylandSetupLayerShellForNextWindow(const GLFWLayerShellConfig *c) pid_t glfwWaylandCompositorPID(void) unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data) void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc) void glfwSetX11WindowAsDock(int32_t x11_window_id) void glfwSetX11WindowStrut(int32_t x11_window_id, uint32_t dimensions[12]) '''.splitlines(): if line: functions.append(Function(line.strip(), check_fail=False)) declarations = [f.declaration() for f in functions] p = src.find(' * GLFW API tokens') p = src.find('*/', p) preamble = src[p + 2:first] header = '''\ // // THIS FILE IS GENERATED BY glfw.py // // SAVE YOURSELF SOME TIME, DO NOT MANUALLY EDIT // #pragma once #include #include #include "monotonic.h" #ifndef GFW_EXTERN #define GFW_EXTERN extern #endif {} typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long); typedef bool (* GLFWapplicationshouldhandlereopenfun)(int); typedef bool (* GLFWhandleurlopen)(const char*); typedef void (* GLFWapplicationwillfinishlaunchingfun)(void); typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); typedef void (* GLFWcocoarenderframefun)(GLFWwindow*); typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id); typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*); typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, int, const char*); {} const char* load_glfw(const char* path); '''.format(preamble, '\n\n'.join(declarations)) with open('../kitty/glfw-wrapper.h', 'w') as f: f.write(header) code = ''' // generated by glfw.py DO NOT edit #define GFW_EXTERN #include "data-types.h" #include "glfw-wrapper.h" #include static void* handle = NULL; #define fail(msg, ...) { snprintf(buf, sizeof(buf), msg, __VA_ARGS__); return buf; } const char* load_glfw(const char* path) { static char buf[2048]; handle = dlopen(path, RTLD_LAZY); if (handle == NULL) fail("Failed to dlopen %s with error: %s", path, dlerror()); dlerror(); LOAD return NULL; } void unload_glfw(void) { if (handle) { dlclose(handle); handle = NULL; } } '''.replace('LOAD', '\n\n '.join(f.load() for f in functions)) with open('../kitty/glfw-wrapper.c', 'w') as f: f.write(code) def main() -> None: os.chdir(os.path.dirname(os.path.abspath(__file__))) generate_wrappers('glfw3.h') if __name__ == '__main__': main() kitty-0.41.1/glfw/glfw3.h0000664000175000017510000063664514773370543014465 0ustar nileshnilesh/************************************************************************* * GLFW 3.4 - www.glfw.org * A library for OpenGL, window and input *------------------------------------------------------------------------ * Copyright (c) 2002-2006 Marcus Geelnard * Copyright (c) 2006-2019 Camilla Löwy * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would * be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not * be misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source * distribution. * *************************************************************************/ #ifndef _glfw3_h_ #define _glfw3_h_ #ifdef __cplusplus extern "C" { #endif /************************************************************************* * Doxygen documentation *************************************************************************/ /*! @file glfw3.h * @brief The header of the GLFW 3 API. * * This is the header file of the GLFW 3 API. It defines all its types and * declares all its functions. * * For more information about how to use this file, see @ref build_include. */ /*! @defgroup context Context reference * @brief Functions and types related to OpenGL and OpenGL ES contexts. * * This is the reference documentation for OpenGL and OpenGL ES context related * functions. For more task-oriented information, see the @ref context_guide. */ /*! @defgroup vulkan Vulkan reference * @brief Functions and types related to Vulkan. * * This is the reference documentation for Vulkan related functions and types. * For more task-oriented information, see the @ref vulkan_guide. */ /*! @defgroup init Initialization, version and error reference * @brief Functions and types related to initialization and error handling. * * This is the reference documentation for initialization and termination of * the library, version management and error handling. For more task-oriented * information, see the @ref intro_guide. */ /*! @defgroup input Input reference * @brief Functions and types related to input handling. * * This is the reference documentation for input related functions and types. * For more task-oriented information, see the @ref input_guide. */ /*! @defgroup monitor Monitor reference * @brief Functions and types related to monitors. * * This is the reference documentation for monitor related functions and types. * For more task-oriented information, see the @ref monitor_guide. */ /*! @defgroup window Window reference * @brief Functions and types related to windows. * * This is the reference documentation for window related functions and types, * including creation, deletion and event polling. For more task-oriented * information, see the @ref window_guide. */ /************************************************************************* * Compiler- and platform-specific preprocessor work *************************************************************************/ /* If we are we on Windows, we want a single define for it. */ #if !defined(_WIN32) && (defined(__WIN32__) || defined(WIN32) || defined(__MINGW32__)) #define _WIN32 #endif /* _WIN32 */ /* Include because most Windows GLU headers need wchar_t and * the macOS OpenGL header blocks the definition of ptrdiff_t by glext.h. * Include it unconditionally to avoid surprising side-effects. */ #include /* Include because it is needed by Vulkan and related functions. * Include it unconditionally to avoid surprising side-effects. */ #include #include #if defined(GLFW_INCLUDE_VULKAN) #include #endif /* Vulkan header */ /* The Vulkan header may have indirectly included windows.h (because of * VK_USE_PLATFORM_WIN32_KHR) so we offer our replacement symbols after it. */ /* It is customary to use APIENTRY for OpenGL function pointer declarations on * all platforms. Additionally, the Windows OpenGL header needs APIENTRY. */ #if !defined(APIENTRY) #if defined(_WIN32) #define APIENTRY __stdcall #else #define APIENTRY #endif #define GLFW_APIENTRY_DEFINED #endif /* APIENTRY */ /* Some Windows OpenGL headers need this. */ #if !defined(WINGDIAPI) && defined(_WIN32) #define WINGDIAPI __declspec(dllimport) #define GLFW_WINGDIAPI_DEFINED #endif /* WINGDIAPI */ /* Some Windows GLU headers need this. */ #if !defined(CALLBACK) && defined(_WIN32) #define CALLBACK __stdcall #define GLFW_CALLBACK_DEFINED #endif /* CALLBACK */ /* Include the chosen OpenGL or OpenGL ES headers. */ #if defined(GLFW_INCLUDE_ES1) #include #if defined(GLFW_INCLUDE_GLEXT) #include #endif #elif defined(GLFW_INCLUDE_ES2) #include #if defined(GLFW_INCLUDE_GLEXT) #include #endif #elif defined(GLFW_INCLUDE_ES3) #include #if defined(GLFW_INCLUDE_GLEXT) #include #endif #elif defined(GLFW_INCLUDE_ES31) #include #if defined(GLFW_INCLUDE_GLEXT) #include #endif #elif defined(GLFW_INCLUDE_ES32) #include #if defined(GLFW_INCLUDE_GLEXT) #include #endif #elif defined(GLFW_INCLUDE_GLCOREARB) #if defined(__APPLE__) #include #if defined(GLFW_INCLUDE_GLEXT) #include #endif /*GLFW_INCLUDE_GLEXT*/ #else /*__APPLE__*/ #include #endif /*__APPLE__*/ #elif defined(GLFW_INCLUDE_GLU) #if defined(__APPLE__) #if defined(GLFW_INCLUDE_GLU) #include #endif #else /*__APPLE__*/ #if defined(GLFW_INCLUDE_GLU) #include #endif #endif /*__APPLE__*/ #elif !defined(GLFW_INCLUDE_NONE) && \ !defined(__gl_h_) && \ !defined(__gles1_gl_h_) && \ !defined(__gles2_gl2_h_) && \ !defined(__gles2_gl3_h_) && \ !defined(__gles2_gl31_h_) && \ !defined(__gles2_gl32_h_) && \ !defined(__gl_glcorearb_h_) && \ !defined(__gl2_h_) /*legacy*/ && \ !defined(__gl3_h_) /*legacy*/ && \ !defined(__gl31_h_) /*legacy*/ && \ !defined(__gl32_h_) /*legacy*/ && \ !defined(__glcorearb_h_) /*legacy*/ && \ !defined(__GL_H__) /*non-standard*/ && \ !defined(__gltypes_h_) /*non-standard*/ && \ !defined(__glee_h_) /*non-standard*/ #if defined(__APPLE__) #if !defined(GLFW_INCLUDE_GLEXT) #define GL_GLEXT_LEGACY #endif #include #else /*__APPLE__*/ #include #if defined(GLFW_INCLUDE_GLEXT) #include #endif #endif /*__APPLE__*/ #endif /* OpenGL and OpenGL ES headers */ #if defined(GLFW_DLL) && defined(_GLFW_BUILD_DLL) /* GLFW_DLL must be defined by applications that are linking against the DLL * version of the GLFW library. _GLFW_BUILD_DLL is defined by the GLFW * configuration header when compiling the DLL version of the library. */ #error "You must not have both GLFW_DLL and _GLFW_BUILD_DLL defined" #endif /* GLFWAPI is used to declare public API functions for export * from the DLL / shared library / dynamic library. */ #if defined(_WIN32) && defined(_GLFW_BUILD_DLL) /* We are building GLFW as a Win32 DLL */ #define GLFWAPI __declspec(dllexport) #elif defined(_WIN32) && defined(GLFW_DLL) /* We are calling GLFW as a Win32 DLL */ #define GLFWAPI __declspec(dllimport) #elif defined(__GNUC__) && defined(_GLFW_BUILD_DLL) /* We are building GLFW as a shared / dynamic library */ #define GLFWAPI __attribute__((visibility("default"))) #else /* We are building or calling GLFW as a static library */ #define GLFWAPI #endif /************************************************************************* * GLFW API tokens *************************************************************************/ /*! @name GLFW version macros * @{ */ /*! @brief The major version number of the GLFW library. * * This is incremented when the API is changed in non-compatible ways. * @ingroup init */ #define GLFW_VERSION_MAJOR 3 /*! @brief The minor version number of the GLFW library. * * This is incremented when features are added to the API but it remains * backward-compatible. * @ingroup init */ #define GLFW_VERSION_MINOR 4 /*! @brief The revision number of the GLFW library. * * This is incremented when a bug fix release is made that does not contain any * API changes. * @ingroup init */ #define GLFW_VERSION_REVISION 0 /*! @} */ /*! @defgroup hat_state Joystick hat states * @brief Joystick hat states. * * See [joystick hat input](@ref joystick_hat) for how these are used. * * @ingroup input * @{ */ #define GLFW_HAT_CENTERED 0 #define GLFW_HAT_UP 1 #define GLFW_HAT_RIGHT 2 #define GLFW_HAT_DOWN 4 #define GLFW_HAT_LEFT 8 #define GLFW_HAT_RIGHT_UP (GLFW_HAT_RIGHT | GLFW_HAT_UP) #define GLFW_HAT_RIGHT_DOWN (GLFW_HAT_RIGHT | GLFW_HAT_DOWN) #define GLFW_HAT_LEFT_UP (GLFW_HAT_LEFT | GLFW_HAT_UP) #define GLFW_HAT_LEFT_DOWN (GLFW_HAT_LEFT | GLFW_HAT_DOWN) /*! @} */ /*! @defgroup keys Keyboard keys * @brief Keyboard key IDs. * * See [key input](@ref input_key) for how these are used. * * These key codes are inspired by the _USB HID Usage Tables v1.12_ (p. 53-60), * but re-arranged to map to 7-bit ASCII for printable keys (function keys are * put in the 256+ range). * * The naming of the key codes follow these rules: * - The US keyboard layout is used * - Names of printable alphanumeric characters are used (e.g. "A", "R", * "3", etc.) * - For non-alphanumeric characters, Unicode:ish names are used (e.g. * "COMMA", "LEFT_SQUARE_BRACKET", etc.). Note that some names do not * correspond to the Unicode standard (usually for brevity) * - Keys that lack a clear US mapping are named "WORLD_x" * - For non-printable keys, custom names are used (e.g. "F4", * "BACKSPACE", etc.) * * @ingroup input * @{ */ /* start functional key names (auto generated by gen-key-constants.py do not edit) */ typedef enum { GLFW_FKEY_FIRST = 0xe000u, GLFW_FKEY_ESCAPE = 0xe000u, GLFW_FKEY_ENTER = 0xe001u, GLFW_FKEY_TAB = 0xe002u, GLFW_FKEY_BACKSPACE = 0xe003u, GLFW_FKEY_INSERT = 0xe004u, GLFW_FKEY_DELETE = 0xe005u, GLFW_FKEY_LEFT = 0xe006u, GLFW_FKEY_RIGHT = 0xe007u, GLFW_FKEY_UP = 0xe008u, GLFW_FKEY_DOWN = 0xe009u, GLFW_FKEY_PAGE_UP = 0xe00au, GLFW_FKEY_PAGE_DOWN = 0xe00bu, GLFW_FKEY_HOME = 0xe00cu, GLFW_FKEY_END = 0xe00du, GLFW_FKEY_CAPS_LOCK = 0xe00eu, GLFW_FKEY_SCROLL_LOCK = 0xe00fu, GLFW_FKEY_NUM_LOCK = 0xe010u, GLFW_FKEY_PRINT_SCREEN = 0xe011u, GLFW_FKEY_PAUSE = 0xe012u, GLFW_FKEY_MENU = 0xe013u, GLFW_FKEY_F1 = 0xe014u, GLFW_FKEY_F2 = 0xe015u, GLFW_FKEY_F3 = 0xe016u, GLFW_FKEY_F4 = 0xe017u, GLFW_FKEY_F5 = 0xe018u, GLFW_FKEY_F6 = 0xe019u, GLFW_FKEY_F7 = 0xe01au, GLFW_FKEY_F8 = 0xe01bu, GLFW_FKEY_F9 = 0xe01cu, GLFW_FKEY_F10 = 0xe01du, GLFW_FKEY_F11 = 0xe01eu, GLFW_FKEY_F12 = 0xe01fu, GLFW_FKEY_F13 = 0xe020u, GLFW_FKEY_F14 = 0xe021u, GLFW_FKEY_F15 = 0xe022u, GLFW_FKEY_F16 = 0xe023u, GLFW_FKEY_F17 = 0xe024u, GLFW_FKEY_F18 = 0xe025u, GLFW_FKEY_F19 = 0xe026u, GLFW_FKEY_F20 = 0xe027u, GLFW_FKEY_F21 = 0xe028u, GLFW_FKEY_F22 = 0xe029u, GLFW_FKEY_F23 = 0xe02au, GLFW_FKEY_F24 = 0xe02bu, GLFW_FKEY_F25 = 0xe02cu, GLFW_FKEY_F26 = 0xe02du, GLFW_FKEY_F27 = 0xe02eu, GLFW_FKEY_F28 = 0xe02fu, GLFW_FKEY_F29 = 0xe030u, GLFW_FKEY_F30 = 0xe031u, GLFW_FKEY_F31 = 0xe032u, GLFW_FKEY_F32 = 0xe033u, GLFW_FKEY_F33 = 0xe034u, GLFW_FKEY_F34 = 0xe035u, GLFW_FKEY_F35 = 0xe036u, GLFW_FKEY_KP_0 = 0xe037u, GLFW_FKEY_KP_1 = 0xe038u, GLFW_FKEY_KP_2 = 0xe039u, GLFW_FKEY_KP_3 = 0xe03au, GLFW_FKEY_KP_4 = 0xe03bu, GLFW_FKEY_KP_5 = 0xe03cu, GLFW_FKEY_KP_6 = 0xe03du, GLFW_FKEY_KP_7 = 0xe03eu, GLFW_FKEY_KP_8 = 0xe03fu, GLFW_FKEY_KP_9 = 0xe040u, GLFW_FKEY_KP_DECIMAL = 0xe041u, GLFW_FKEY_KP_DIVIDE = 0xe042u, GLFW_FKEY_KP_MULTIPLY = 0xe043u, GLFW_FKEY_KP_SUBTRACT = 0xe044u, GLFW_FKEY_KP_ADD = 0xe045u, GLFW_FKEY_KP_ENTER = 0xe046u, GLFW_FKEY_KP_EQUAL = 0xe047u, GLFW_FKEY_KP_SEPARATOR = 0xe048u, GLFW_FKEY_KP_LEFT = 0xe049u, GLFW_FKEY_KP_RIGHT = 0xe04au, GLFW_FKEY_KP_UP = 0xe04bu, GLFW_FKEY_KP_DOWN = 0xe04cu, GLFW_FKEY_KP_PAGE_UP = 0xe04du, GLFW_FKEY_KP_PAGE_DOWN = 0xe04eu, GLFW_FKEY_KP_HOME = 0xe04fu, GLFW_FKEY_KP_END = 0xe050u, GLFW_FKEY_KP_INSERT = 0xe051u, GLFW_FKEY_KP_DELETE = 0xe052u, GLFW_FKEY_KP_BEGIN = 0xe053u, GLFW_FKEY_MEDIA_PLAY = 0xe054u, GLFW_FKEY_MEDIA_PAUSE = 0xe055u, GLFW_FKEY_MEDIA_PLAY_PAUSE = 0xe056u, GLFW_FKEY_MEDIA_REVERSE = 0xe057u, GLFW_FKEY_MEDIA_STOP = 0xe058u, GLFW_FKEY_MEDIA_FAST_FORWARD = 0xe059u, GLFW_FKEY_MEDIA_REWIND = 0xe05au, GLFW_FKEY_MEDIA_TRACK_NEXT = 0xe05bu, GLFW_FKEY_MEDIA_TRACK_PREVIOUS = 0xe05cu, GLFW_FKEY_MEDIA_RECORD = 0xe05du, GLFW_FKEY_LOWER_VOLUME = 0xe05eu, GLFW_FKEY_RAISE_VOLUME = 0xe05fu, GLFW_FKEY_MUTE_VOLUME = 0xe060u, GLFW_FKEY_LEFT_SHIFT = 0xe061u, GLFW_FKEY_LEFT_CONTROL = 0xe062u, GLFW_FKEY_LEFT_ALT = 0xe063u, GLFW_FKEY_LEFT_SUPER = 0xe064u, GLFW_FKEY_LEFT_HYPER = 0xe065u, GLFW_FKEY_LEFT_META = 0xe066u, GLFW_FKEY_RIGHT_SHIFT = 0xe067u, GLFW_FKEY_RIGHT_CONTROL = 0xe068u, GLFW_FKEY_RIGHT_ALT = 0xe069u, GLFW_FKEY_RIGHT_SUPER = 0xe06au, GLFW_FKEY_RIGHT_HYPER = 0xe06bu, GLFW_FKEY_RIGHT_META = 0xe06cu, GLFW_FKEY_ISO_LEVEL3_SHIFT = 0xe06du, GLFW_FKEY_ISO_LEVEL5_SHIFT = 0xe06eu, GLFW_FKEY_LAST = 0xe06eu } GLFWFunctionKey; /* end functional key names */ /*! @} */ /*! @defgroup mods Modifier key flags * @brief Modifier key flags. * * See [key input](@ref input_key) for how these are used. * * @ingroup input * @{ */ /*! @brief If this bit is set one or more Shift keys were held down. * * If this bit is set one or more Shift keys were held down. */ #define GLFW_MOD_SHIFT 0x0001 /*! @brief If this bit is set one or more Alt keys were held down. * * If this bit is set one or more Alt keys were held down. */ #define GLFW_MOD_ALT 0x0002 /*! @brief If this bit is set one or more Alt keys were held down. * * If this bit is set one or more Alt keys were held down. */ #define GLFW_MOD_CONTROL 0x0004 /*! @brief If this bit is set one or more Super keys were held down. * * If this bit is set one or more Super keys were held down. */ #define GLFW_MOD_SUPER 0x0008 /*! @brief If this bit is set one or more Hyper keys were held down. * * If this bit is set one or more Hyper keys were held down. */ #define GLFW_MOD_HYPER 0x0010 /*! @brief If this bit is set one or more Meta keys were held down. * * If this bit is set one or more Meta keys were held down. */ #define GLFW_MOD_META 0x0020 /*! @brief If this bit is set the Caps Lock key is enabled. * * If this bit is set the Caps Lock key is enabled and the @ref * GLFW_LOCK_KEY_MODS input mode is set. */ #define GLFW_MOD_CAPS_LOCK 0x0040 /*! @brief If this bit is set the Num Lock key is enabled. * * If this bit is set the Num Lock key is enabled and the @ref * GLFW_LOCK_KEY_MODS input mode is set. */ #define GLFW_MOD_NUM_LOCK 0x0080 #define GLFW_MOD_LAST GLFW_MOD_NUM_LOCK #define GLFW_LOCK_MASK (GLFW_MOD_NUM_LOCK | GLFW_MOD_CAPS_LOCK) /*! @} */ /*! @defgroup buttons Mouse buttons * @brief Mouse button IDs. * * See [mouse button input](@ref input_mouse_button) for how these are used. * * @ingroup input * @{ */ typedef enum GLFWMouseButton { GLFW_MOUSE_BUTTON_1 = 0, GLFW_MOUSE_BUTTON_LEFT = 0, GLFW_MOUSE_BUTTON_2 = 1, GLFW_MOUSE_BUTTON_RIGHT = 1, GLFW_MOUSE_BUTTON_3 = 2, GLFW_MOUSE_BUTTON_MIDDLE = 2, GLFW_MOUSE_BUTTON_4 = 3, GLFW_MOUSE_BUTTON_5 = 4, GLFW_MOUSE_BUTTON_6 = 5, GLFW_MOUSE_BUTTON_7 = 6, GLFW_MOUSE_BUTTON_8 = 7, GLFW_MOUSE_BUTTON_LAST = 7 } GLFWMouseButton; /*! @} */ typedef enum GLFWColorScheme { GLFW_COLOR_SCHEME_NO_PREFERENCE = 0, GLFW_COLOR_SCHEME_DARK = 1, GLFW_COLOR_SCHEME_LIGHT = 2 } GLFWColorScheme; /*! @defgroup joysticks Joysticks * @brief Joystick IDs. * * See [joystick input](@ref joystick) for how these are used. * * @ingroup input * @{ */ #define GLFW_JOYSTICK_1 0 #define GLFW_JOYSTICK_2 1 #define GLFW_JOYSTICK_3 2 #define GLFW_JOYSTICK_4 3 #define GLFW_JOYSTICK_5 4 #define GLFW_JOYSTICK_6 5 #define GLFW_JOYSTICK_7 6 #define GLFW_JOYSTICK_8 7 #define GLFW_JOYSTICK_9 8 #define GLFW_JOYSTICK_10 9 #define GLFW_JOYSTICK_11 10 #define GLFW_JOYSTICK_12 11 #define GLFW_JOYSTICK_13 12 #define GLFW_JOYSTICK_14 13 #define GLFW_JOYSTICK_15 14 #define GLFW_JOYSTICK_16 15 #define GLFW_JOYSTICK_LAST GLFW_JOYSTICK_16 /*! @} */ /*! @defgroup gamepad_buttons Gamepad buttons * @brief Gamepad buttons. * * See @ref gamepad for how these are used. * * @ingroup input * @{ */ #define GLFW_GAMEPAD_BUTTON_A 0 #define GLFW_GAMEPAD_BUTTON_B 1 #define GLFW_GAMEPAD_BUTTON_X 2 #define GLFW_GAMEPAD_BUTTON_Y 3 #define GLFW_GAMEPAD_BUTTON_LEFT_BUMPER 4 #define GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER 5 #define GLFW_GAMEPAD_BUTTON_BACK 6 #define GLFW_GAMEPAD_BUTTON_START 7 #define GLFW_GAMEPAD_BUTTON_GUIDE 8 #define GLFW_GAMEPAD_BUTTON_LEFT_THUMB 9 #define GLFW_GAMEPAD_BUTTON_RIGHT_THUMB 10 #define GLFW_GAMEPAD_BUTTON_DPAD_UP 11 #define GLFW_GAMEPAD_BUTTON_DPAD_RIGHT 12 #define GLFW_GAMEPAD_BUTTON_DPAD_DOWN 13 #define GLFW_GAMEPAD_BUTTON_DPAD_LEFT 14 #define GLFW_GAMEPAD_BUTTON_LAST GLFW_GAMEPAD_BUTTON_DPAD_LEFT #define GLFW_GAMEPAD_BUTTON_CROSS GLFW_GAMEPAD_BUTTON_A #define GLFW_GAMEPAD_BUTTON_CIRCLE GLFW_GAMEPAD_BUTTON_B #define GLFW_GAMEPAD_BUTTON_SQUARE GLFW_GAMEPAD_BUTTON_X #define GLFW_GAMEPAD_BUTTON_TRIANGLE GLFW_GAMEPAD_BUTTON_Y /*! @} */ /*! @defgroup gamepad_axes Gamepad axes * @brief Gamepad axes. * * See @ref gamepad for how these are used. * * @ingroup input * @{ */ #define GLFW_GAMEPAD_AXIS_LEFT_X 0 #define GLFW_GAMEPAD_AXIS_LEFT_Y 1 #define GLFW_GAMEPAD_AXIS_RIGHT_X 2 #define GLFW_GAMEPAD_AXIS_RIGHT_Y 3 #define GLFW_GAMEPAD_AXIS_LEFT_TRIGGER 4 #define GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER 5 #define GLFW_GAMEPAD_AXIS_LAST GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER /*! @} */ /*! @defgroup errors Error codes * @brief Error codes. * * See [error handling](@ref error_handling) for how these are used. * * @ingroup init * @{ */ /*! @brief No error has occurred. * * No error has occurred. * * @analysis Yay. */ #define GLFW_NO_ERROR 0 /*! @brief GLFW has not been initialized. * * This occurs if a GLFW function was called that must not be called unless the * library is [initialized](@ref intro_init). * * @analysis Application programmer error. Initialize GLFW before calling any * function that requires initialization. */ #define GLFW_NOT_INITIALIZED 0x00010001 /*! @brief No context is current for this thread. * * This occurs if a GLFW function was called that needs and operates on the * current OpenGL or OpenGL ES context but no context is current on the calling * thread. One such function is @ref glfwSwapInterval. * * @analysis Application programmer error. Ensure a context is current before * calling functions that require a current context. */ #define GLFW_NO_CURRENT_CONTEXT 0x00010002 /*! @brief One of the arguments to the function was an invalid enum value. * * One of the arguments to the function was an invalid enum value, for example * requesting @ref GLFW_RED_BITS with @ref glfwGetWindowAttrib. * * @analysis Application programmer error. Fix the offending call. */ #define GLFW_INVALID_ENUM 0x00010003 /*! @brief One of the arguments to the function was an invalid value. * * One of the arguments to the function was an invalid value, for example * requesting a non-existent OpenGL or OpenGL ES version like 2.7. * * Requesting a valid but unavailable OpenGL or OpenGL ES version will instead * result in a @ref GLFW_VERSION_UNAVAILABLE error. * * @analysis Application programmer error. Fix the offending call. */ #define GLFW_INVALID_VALUE 0x00010004 /*! @brief A memory allocation failed. * * A memory allocation failed. * * @analysis A bug in GLFW or the underlying operating system. Report the bug * to our [issue tracker](https://github.com/glfw/glfw/issues). */ #define GLFW_OUT_OF_MEMORY 0x00010005 /*! @brief GLFW could not find support for the requested API on the system. * * GLFW could not find support for the requested API on the system. * * @analysis The installed graphics driver does not support the requested * API, or does not support it via the chosen context creation backend. * Below are a few examples. * * @par * Some pre-installed Windows graphics drivers do not support OpenGL. AMD only * supports OpenGL ES via EGL, while Nvidia and Intel only support it via * a WGL or GLX extension. macOS does not provide OpenGL ES at all. The Mesa * EGL, OpenGL and OpenGL ES libraries do not interface with the Nvidia binary * driver. Older graphics drivers do not support Vulkan. */ #define GLFW_API_UNAVAILABLE 0x00010006 /*! @brief The requested OpenGL or OpenGL ES version is not available. * * The requested OpenGL or OpenGL ES version (including any requested context * or framebuffer hints) is not available on this machine. * * @analysis The machine does not support your requirements. If your * application is sufficiently flexible, downgrade your requirements and try * again. Otherwise, inform the user that their machine does not match your * requirements. * * @par * Future invalid OpenGL and OpenGL ES versions, for example OpenGL 4.8 if 5.0 * comes out before the 4.x series gets that far, also fail with this error and * not @ref GLFW_INVALID_VALUE, because GLFW cannot know what future versions * will exist. */ #define GLFW_VERSION_UNAVAILABLE 0x00010007 /*! @brief A platform-specific error occurred that does not match any of the * more specific categories. * * A platform-specific error occurred that does not match any of the more * specific categories. * * @analysis A bug or configuration error in GLFW, the underlying operating * system or its drivers, or a lack of required resources. Report the issue to * our [issue tracker](https://github.com/glfw/glfw/issues). */ #define GLFW_PLATFORM_ERROR 0x00010008 /*! @brief The requested format is not supported or available. * * If emitted during window creation, the requested pixel format is not * supported. * * If emitted when querying the clipboard, the contents of the clipboard could * not be converted to the requested format. * * @analysis If emitted during window creation, one or more * [hard constraints](@ref window_hints_hard) did not match any of the * available pixel formats. If your application is sufficiently flexible, * downgrade your requirements and try again. Otherwise, inform the user that * their machine does not match your requirements. * * @par * If emitted when querying the clipboard, ignore the error or report it to * the user, as appropriate. */ #define GLFW_FORMAT_UNAVAILABLE 0x00010009 /*! @brief The specified window does not have an OpenGL or OpenGL ES context. * * A window that does not have an OpenGL or OpenGL ES context was passed to * a function that requires it to have one. * * @analysis Application programmer error. Fix the offending call. */ #define GLFW_NO_WINDOW_CONTEXT 0x0001000A /*! @brief The requested feature is not provided by the platform. * * The requested feature is not provided by the platform, so GLFW is unable to * implement it. The documentation for each function notes if it could emit * this error. * * @analysis Platform or platform version limitation. The error can be ignored * unless the feature is critical to the application. * * @par * A function call that emits this error has no effect other than the error and * updating any existing out parameters. */ #define GLFW_FEATURE_UNAVAILABLE 0x0001000C /*! @brief The requested feature is not implemented for the platform. * * The requested feature has not yet been implemented in GLFW for this platform. * * @analysis An incomplete implementation of GLFW for this platform, hopefully * fixed in a future release. The error can be ignored unless the feature is * critical to the application. * * @par * A function call that emits this error has no effect other than the error and * updating any existing out parameters. */ #define GLFW_FEATURE_UNIMPLEMENTED 0x0001000D /*! @} */ /*! @addtogroup window * @{ */ /*! @brief Input focus window hint and attribute * * Input focus [window hint](@ref GLFW_FOCUSED_hint) or * [window attribute](@ref GLFW_FOCUSED_attrib). */ #define GLFW_FOCUSED 0x00020001 /*! @brief Window iconification window attribute * * Window iconification [window attribute](@ref GLFW_ICONIFIED_attrib). */ #define GLFW_ICONIFIED 0x00020002 /*! @brief Window resize-ability window hint and attribute * * Window resize-ability [window hint](@ref GLFW_RESIZABLE_hint) and * [window attribute](@ref GLFW_RESIZABLE_attrib). */ #define GLFW_RESIZABLE 0x00020003 /*! @brief Window visibility window hint and attribute * * Window visibility [window hint](@ref GLFW_VISIBLE_hint) and * [window attribute](@ref GLFW_VISIBLE_attrib). */ #define GLFW_VISIBLE 0x00020004 /*! @brief Window decoration window hint and attribute * * Window decoration [window hint](@ref GLFW_DECORATED_hint) and * [window attribute](@ref GLFW_DECORATED_attrib). */ #define GLFW_DECORATED 0x00020005 /*! @brief Window auto-iconification window hint and attribute * * Window auto-iconification [window hint](@ref GLFW_AUTO_ICONIFY_hint) and * [window attribute](@ref GLFW_AUTO_ICONIFY_attrib). */ #define GLFW_AUTO_ICONIFY 0x00020006 /*! @brief Window decoration window hint and attribute * * Window decoration [window hint](@ref GLFW_FLOATING_hint) and * [window attribute](@ref GLFW_FLOATING_attrib). */ #define GLFW_FLOATING 0x00020007 /*! @brief Window maximization window hint and attribute * * Window maximization [window hint](@ref GLFW_MAXIMIZED_hint) and * [window attribute](@ref GLFW_MAXIMIZED_attrib). */ #define GLFW_MAXIMIZED 0x00020008 /*! @brief Cursor centering window hint * * Cursor centering [window hint](@ref GLFW_CENTER_CURSOR_hint). */ #define GLFW_CENTER_CURSOR 0x00020009 /*! @brief Window framebuffer transparency hint and attribute * * Window framebuffer transparency * [window hint](@ref GLFW_TRANSPARENT_FRAMEBUFFER_hint) and * [window attribute](@ref GLFW_TRANSPARENT_FRAMEBUFFER_attrib). */ #define GLFW_TRANSPARENT_FRAMEBUFFER 0x0002000A /*! @brief Mouse cursor hover window attribute. * * Mouse cursor hover [window attribute](@ref GLFW_HOVERED_attrib). */ #define GLFW_HOVERED 0x0002000B /*! @brief Input focus on calling show window hint and attribute * * Input focus [window hint](@ref GLFW_FOCUS_ON_SHOW_hint) or * [window attribute](@ref GLFW_FOCUS_ON_SHOW_attrib). */ #define GLFW_FOCUS_ON_SHOW 0x0002000C /*! @brief Mouse input transparency window hint and attribute * * Mouse input transparency [window hint](@ref GLFW_MOUSE_PASSTHROUGH_hint) or * [window attribute](@ref GLFW_MOUSE_PASSTHROUGH_attrib). */ #define GLFW_MOUSE_PASSTHROUGH 0x0002000D /*! @brief Occlusion window attribute * * Occlusion [window attribute](@ref GLFW_OCCLUDED_attrib). */ #define GLFW_OCCLUDED 0x0002000E /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_RED_BITS). */ #define GLFW_RED_BITS 0x00021001 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_GREEN_BITS). */ #define GLFW_GREEN_BITS 0x00021002 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_BLUE_BITS). */ #define GLFW_BLUE_BITS 0x00021003 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_ALPHA_BITS). */ #define GLFW_ALPHA_BITS 0x00021004 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_DEPTH_BITS). */ #define GLFW_DEPTH_BITS 0x00021005 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_STENCIL_BITS). */ #define GLFW_STENCIL_BITS 0x00021006 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_ACCUM_RED_BITS). */ #define GLFW_ACCUM_RED_BITS 0x00021007 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_ACCUM_GREEN_BITS). */ #define GLFW_ACCUM_GREEN_BITS 0x00021008 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_ACCUM_BLUE_BITS). */ #define GLFW_ACCUM_BLUE_BITS 0x00021009 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_ACCUM_ALPHA_BITS). */ #define GLFW_ACCUM_ALPHA_BITS 0x0002100A /*! @brief Framebuffer auxiliary buffer hint. * * Framebuffer auxiliary buffer [hint](@ref GLFW_AUX_BUFFERS). */ #define GLFW_AUX_BUFFERS 0x0002100B /*! @brief OpenGL stereoscopic rendering hint. * * OpenGL stereoscopic rendering [hint](@ref GLFW_STEREO). */ #define GLFW_STEREO 0x0002100C /*! @brief Framebuffer MSAA samples hint. * * Framebuffer MSAA samples [hint](@ref GLFW_SAMPLES). */ #define GLFW_SAMPLES 0x0002100D /*! @brief Framebuffer sRGB hint. * * Framebuffer sRGB [hint](@ref GLFW_SRGB_CAPABLE). */ #define GLFW_SRGB_CAPABLE 0x0002100E /*! @brief Monitor refresh rate hint. * * Monitor refresh rate [hint](@ref GLFW_REFRESH_RATE). */ #define GLFW_REFRESH_RATE 0x0002100F /*! @brief Framebuffer double buffering hint. * * Framebuffer double buffering [hint](@ref GLFW_DOUBLEBUFFER). */ #define GLFW_DOUBLEBUFFER 0x00021010 /*! @brief Context client API hint and attribute. * * Context client API [hint](@ref GLFW_CLIENT_API_hint) and * [attribute](@ref GLFW_CLIENT_API_attrib). */ #define GLFW_CLIENT_API 0x00022001 /*! @brief Context client API major version hint and attribute. * * Context client API major version [hint](@ref GLFW_CONTEXT_VERSION_MAJOR_hint) * and [attribute](@ref GLFW_CONTEXT_VERSION_MAJOR_attrib). */ #define GLFW_CONTEXT_VERSION_MAJOR 0x00022002 /*! @brief Context client API minor version hint and attribute. * * Context client API minor version [hint](@ref GLFW_CONTEXT_VERSION_MINOR_hint) * and [attribute](@ref GLFW_CONTEXT_VERSION_MINOR_attrib). */ #define GLFW_CONTEXT_VERSION_MINOR 0x00022003 /*! @brief Context client API revision number hint and attribute. * * Context client API revision number * [attribute](@ref GLFW_CONTEXT_REVISION_attrib). */ #define GLFW_CONTEXT_REVISION 0x00022004 /*! @brief Context robustness hint and attribute. * * Context client API revision number [hint](@ref GLFW_CONTEXT_ROBUSTNESS_hint) * and [attribute](@ref GLFW_CONTEXT_ROBUSTNESS_attrib). */ #define GLFW_CONTEXT_ROBUSTNESS 0x00022005 /*! @brief OpenGL forward-compatibility hint and attribute. * * OpenGL forward-compatibility [hint](@ref GLFW_OPENGL_FORWARD_COMPAT_hint) * and [attribute](@ref GLFW_OPENGL_FORWARD_COMPAT_attrib). */ #define GLFW_OPENGL_FORWARD_COMPAT 0x00022006 /*! @brief Debug mode context hint and attribute. * * Debug mode context [hint](@ref GLFW_CONTEXT_DEBUG_hint) and * [attribute](@ref GLFW_CONTEXT_DEBUG_attrib). */ #define GLFW_CONTEXT_DEBUG 0x00022007 /*! @brief Legacy name for compatibility. * * This is an alias for compatibility with earlier versions. */ #define GLFW_OPENGL_DEBUG_CONTEXT GLFW_CONTEXT_DEBUG /*! @brief OpenGL profile hint and attribute. * * OpenGL profile [hint](@ref GLFW_OPENGL_PROFILE_hint) and * [attribute](@ref GLFW_OPENGL_PROFILE_attrib). */ #define GLFW_OPENGL_PROFILE 0x00022008 /*! @brief Context flush-on-release hint and attribute. * * Context flush-on-release [hint](@ref GLFW_CONTEXT_RELEASE_BEHAVIOR_hint) and * [attribute](@ref GLFW_CONTEXT_RELEASE_BEHAVIOR_attrib). */ #define GLFW_CONTEXT_RELEASE_BEHAVIOR 0x00022009 /*! @brief Context error suppression hint and attribute. * * Context error suppression [hint](@ref GLFW_CONTEXT_NO_ERROR_hint) and * [attribute](@ref GLFW_CONTEXT_NO_ERROR_attrib). */ #define GLFW_CONTEXT_NO_ERROR 0x0002200A /*! @brief Context creation API hint and attribute. * * Context creation API [hint](@ref GLFW_CONTEXT_CREATION_API_hint) and * [attribute](@ref GLFW_CONTEXT_CREATION_API_attrib). */ #define GLFW_CONTEXT_CREATION_API 0x0002200B /*! @brief Window content area scaling window * [window hint](@ref GLFW_SCALE_TO_MONITOR). */ #define GLFW_SCALE_TO_MONITOR 0x0002200C /*! @brief macOS specific * [window hint](@ref GLFW_COCOA_RETINA_FRAMEBUFFER_hint). */ #define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001 /*! @brief macOS specific * [window hint](@ref GLFW_COCOA_FRAME_NAME_hint). */ #define GLFW_COCOA_FRAME_NAME 0x00023002 /*! @brief macOS specific * [window hint](@ref GLFW_COCOA_GRAPHICS_SWITCHING_hint). */ #define GLFW_COCOA_GRAPHICS_SWITCHING 0x00023003 /*! @brief macOS specific * [window hint](@ref GLFW_COCOA_COLOR_SPACE_hint). */ #define GLFW_COCOA_COLOR_SPACE 0x00023004 typedef enum { DEFAULT_COLORSPACE = 0, SRGB_COLORSPACE = 1, DISPLAY_P3_COLORSPACE = 2, } GlfwCocoaColorSpaces; /*! @brief Blur Radius. On macOS the actual radius is used. On Linux it is treated as a bool. * [window hint](@ref GLFW_BLUR_RADIUS). */ #define GLFW_BLUR_RADIUS 0x0002305 /*! @brief X11 specific * [window hint](@ref GLFW_X11_CLASS_NAME_hint). */ #define GLFW_X11_CLASS_NAME 0x00024001 /*! @brief X11 specific * [window hint](@ref GLFW_X11_CLASS_NAME_hint). */ #define GLFW_X11_INSTANCE_NAME 0x00024002 #define GLFW_WAYLAND_APP_ID 0x00025001 #define GLFW_WAYLAND_BGCOLOR 0x00025002 /*! @} */ #define GLFW_NO_API 0 #define GLFW_OPENGL_API 0x00030001 #define GLFW_OPENGL_ES_API 0x00030002 #define GLFW_NO_ROBUSTNESS 0 #define GLFW_NO_RESET_NOTIFICATION 0x00031001 #define GLFW_LOSE_CONTEXT_ON_RESET 0x00031002 #define GLFW_OPENGL_ANY_PROFILE 0 #define GLFW_OPENGL_CORE_PROFILE 0x00032001 #define GLFW_OPENGL_COMPAT_PROFILE 0x00032002 #define GLFW_CURSOR 0x00033001 #define GLFW_STICKY_KEYS 0x00033002 #define GLFW_STICKY_MOUSE_BUTTONS 0x00033003 #define GLFW_LOCK_KEY_MODS 0x00033004 #define GLFW_RAW_MOUSE_MOTION 0x00033005 #define GLFW_CURSOR_NORMAL 0x00034001 #define GLFW_CURSOR_HIDDEN 0x00034002 #define GLFW_CURSOR_DISABLED 0x00034003 #define GLFW_ANY_RELEASE_BEHAVIOR 0 #define GLFW_RELEASE_BEHAVIOR_FLUSH 0x00035001 #define GLFW_RELEASE_BEHAVIOR_NONE 0x00035002 #define GLFW_NATIVE_CONTEXT_API 0x00036001 #define GLFW_EGL_CONTEXT_API 0x00036002 #define GLFW_OSMESA_CONTEXT_API 0x00036003 #define GLFW_ANGLE_PLATFORM_TYPE_NONE 0x00037001 #define GLFW_ANGLE_PLATFORM_TYPE_OPENGL 0x00037002 #define GLFW_ANGLE_PLATFORM_TYPE_OPENGLES 0x00037003 #define GLFW_ANGLE_PLATFORM_TYPE_D3D9 0x00037004 #define GLFW_ANGLE_PLATFORM_TYPE_D3D11 0x00037005 #define GLFW_ANGLE_PLATFORM_TYPE_VULKAN 0x00037007 #define GLFW_ANGLE_PLATFORM_TYPE_METAL 0x00037008 /*! @defgroup shapes Standard cursor shapes * @brief Standard system cursor shapes. * * See [standard cursor creation](@ref cursor_standard) for how these are used. * * @ingroup input * @{ */ typedef enum { /* start mouse cursor shapes (auto generated by gen-key-constants.py do not edit) */ GLFW_DEFAULT_CURSOR, GLFW_TEXT_CURSOR, GLFW_POINTER_CURSOR, GLFW_HELP_CURSOR, GLFW_WAIT_CURSOR, GLFW_PROGRESS_CURSOR, GLFW_CROSSHAIR_CURSOR, GLFW_CELL_CURSOR, GLFW_VERTICAL_TEXT_CURSOR, GLFW_MOVE_CURSOR, GLFW_E_RESIZE_CURSOR, GLFW_NE_RESIZE_CURSOR, GLFW_NW_RESIZE_CURSOR, GLFW_N_RESIZE_CURSOR, GLFW_SE_RESIZE_CURSOR, GLFW_SW_RESIZE_CURSOR, GLFW_S_RESIZE_CURSOR, GLFW_W_RESIZE_CURSOR, GLFW_EW_RESIZE_CURSOR, GLFW_NS_RESIZE_CURSOR, GLFW_NESW_RESIZE_CURSOR, GLFW_NWSE_RESIZE_CURSOR, GLFW_ZOOM_IN_CURSOR, GLFW_ZOOM_OUT_CURSOR, GLFW_ALIAS_CURSOR, GLFW_COPY_CURSOR, GLFW_NOT_ALLOWED_CURSOR, GLFW_NO_DROP_CURSOR, GLFW_GRAB_CURSOR, GLFW_GRABBING_CURSOR, GLFW_INVALID_CURSOR, /* end mouse cursor shapes */ } GLFWCursorShape; /*! @} */ #define GLFW_CONNECTED 0x00040001 #define GLFW_DISCONNECTED 0x00040002 /*! @addtogroup init * @{ */ /*! @brief Joystick hat buttons init hint. * * Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS). */ #define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001 /*! @brief ANGLE rendering backend init hint. * * ANGLE rendering backend [init hint](@ref GLFW_ANGLE_PLATFORM_TYPE_hint). */ #define GLFW_ANGLE_PLATFORM_TYPE 0x00050002 #define GLFW_DEBUG_KEYBOARD 0x00050003 #define GLFW_DEBUG_RENDERING 0x00050004 /*! @brief macOS specific init hint. * * macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint). */ #define GLFW_COCOA_CHDIR_RESOURCES 0x00051001 /*! @brief macOS specific init hint. * * macOS specific [init hint](@ref GLFW_COCOA_MENUBAR_hint). */ #define GLFW_COCOA_MENUBAR 0x00051002 #define GLFW_WAYLAND_IME 0x00051003 /*! @} */ #define GLFW_DONT_CARE -1 /************************************************************************* * GLFW API types *************************************************************************/ /*! @brief Client API function pointer type. * * Generic function pointer used for returning client API function pointers * without forcing a cast from a regular pointer. * * @sa @ref context_glext * @sa @ref glfwGetProcAddress * * @since Added in version 3.0. * * @ingroup context */ typedef void (*GLFWglproc)(void); /*! @brief Vulkan API function pointer type. * * Generic function pointer used for returning Vulkan API function pointers * without forcing a cast from a regular pointer. * * @sa @ref vulkan_proc * @sa @ref glfwGetInstanceProcAddress * * @since Added in version 3.2. * * @ingroup vulkan */ typedef void (*GLFWvkproc)(void); /*! @brief Opaque monitor object. * * Opaque monitor object. * * @see @ref monitor_object * * @since Added in version 3.0. * * @ingroup monitor */ typedef struct GLFWmonitor GLFWmonitor; /*! @brief Opaque window object. * * Opaque window object. * * @see @ref window_object * * @since Added in version 3.0. * * @ingroup window */ typedef struct GLFWwindow GLFWwindow; /*! @brief Opaque cursor object. * * Opaque cursor object. * * @see @ref cursor_object * * @since Added in version 3.1. * * @ingroup input */ typedef struct GLFWcursor GLFWcursor; typedef enum { GLFW_RELEASE = 0, GLFW_PRESS = 1, GLFW_REPEAT = 2 } GLFWKeyAction; typedef enum { GLFW_IME_NONE, GLFW_IME_PREEDIT_CHANGED, GLFW_IME_COMMIT_TEXT, GLFW_IME_WAYLAND_DONE_EVENT, } GLFWIMEState; typedef enum { GLFW_IME_UPDATE_FOCUS = 1, GLFW_IME_UPDATE_CURSOR_POSITION = 2 } GLFWIMEUpdateType; typedef struct GLFWIMEUpdateEvent { GLFWIMEUpdateType type; const char *before_text, *at_text, *after_text; bool focused; struct { int left, top, width, height; } cursor; } GLFWIMEUpdateEvent; typedef struct GLFWkeyevent { // The [keyboard key](@ref keys) that was pressed or released. uint32_t key, shifted_key, alternate_key; // The platform-specific identifier of the key. int native_key; // The event action. Either `GLFW_PRESS`, `GLFW_RELEASE` or `GLFW_REPEAT`. GLFWKeyAction action; // Bit field describing which [modifier keys](@ref mods) were held down. int mods; // UTF-8 encoded text generated by this key event or empty string or NULL const char *text; // Used for Input Method events. Zero for normal key events. // A value of GLFW_IME_PREEDIT_CHANGED means the pre-edit text for the input event has been changed. // A value of GLFW_IME_COMMIT_TEXT means the text should be committed. GLFWIMEState ime_state; // For internal use only. On Linux it is the actual keycode reported by the windowing system, in contrast // to native_key which can be the result of a compose operation. On macOS it is the same as native_key. uint32_t native_key_id; // True if this is a synthesized event on focus change bool fake_event_on_focus_change; } GLFWkeyevent; typedef enum { GLFW_LAYER_SHELL_NONE, GLFW_LAYER_SHELL_BACKGROUND, GLFW_LAYER_SHELL_PANEL, GLFW_LAYER_SHELL_TOP, GLFW_LAYER_SHELL_OVERLAY } GLFWLayerShellType; typedef enum { GLFW_EDGE_TOP, GLFW_EDGE_BOTTOM, GLFW_EDGE_LEFT, GLFW_EDGE_RIGHT, GLFW_EDGE_CENTER, GLFW_EDGE_NONE } GLFWEdge; typedef enum { GLFW_FOCUS_NOT_ALLOWED, GLFW_FOCUS_EXCLUSIVE, GLFW_FOCUS_ON_DEMAND} GLFWFocusPolicy; typedef struct GLFWLayerShellConfig { GLFWLayerShellType type; GLFWEdge edge; char output_name[64]; GLFWFocusPolicy focus_policy; unsigned x_size_in_cells; unsigned y_size_in_cells; unsigned requested_top_margin; unsigned requested_left_margin; unsigned requested_bottom_margin; unsigned requested_right_margin; int requested_exclusive_zone; unsigned override_exclusive_zone; void (*size_callback)(GLFWwindow *window, const struct GLFWLayerShellConfig *config, unsigned monitor_width, unsigned monitor_height, uint32_t *width, uint32_t *height); struct { double xdpi, ydpi, xscale, yscale; } expected; } GLFWLayerShellConfig; typedef struct GLFWDBUSNotificationData { const char *app_name, *icon, *summary, *body, *category, **actions; size_t num_actions; int32_t timeout; uint8_t urgency; uint32_t replaces; int muted; } GLFWDBUSNotificationData; /*! @brief The function pointer type for error callbacks. * * This is the function pointer type for error callbacks. An error callback * function has the following signature: * @code * void callback_name(int error_code, const char* description) * @endcode * * @param[in] error_code An [error code](@ref errors). Future releases may add * more error codes. * @param[in] description A UTF-8 encoded string describing the error. * * @pointer_lifetime The error description string is valid until the callback * function returns. * * @sa @ref error_handling * @sa @ref glfwSetErrorCallback * * @since Added in version 3.0. * * @ingroup init */ typedef void (* GLFWerrorfun)(int,const char*); /*! @brief The function pointer type for window position callbacks. * * This is the function pointer type for window position callbacks. A window * position callback function has the following signature: * @code * void callback_name(GLFWwindow* window, int xpos, int ypos) * @endcode * * @param[in] window The window that was moved. * @param[in] xpos The new x-coordinate, in screen coordinates, of the * upper-left corner of the content area of the window. * @param[in] ypos The new y-coordinate, in screen coordinates, of the * upper-left corner of the content area of the window. * * @sa @ref window_pos * @sa @ref glfwSetWindowPosCallback * * @since Added in version 3.0. * * @ingroup window */ typedef void (* GLFWwindowposfun)(GLFWwindow*,int,int); /*! @brief The function pointer type for window size callbacks. * * This is the function pointer type for window size callbacks. A window size * callback function has the following signature: * @code * void callback_name(GLFWwindow* window, int width, int height) * @endcode * * @param[in] window The window that was resized. * @param[in] width The new width, in screen coordinates, of the window. * @param[in] height The new height, in screen coordinates, of the window. * * @sa @ref window_size * @sa @ref glfwSetWindowSizeCallback * * @since Added in version 1.0. * @glfw3 Added window handle parameter. * * @ingroup window */ typedef void (* GLFWwindowsizefun)(GLFWwindow*,int,int); /*! @brief The function pointer type for window close callbacks. * * This is the function pointer type for window close callbacks. A window * close callback function has the following signature: * @code * void function_name(GLFWwindow* window) * @endcode * * @param[in] window The window that the user attempted to close. * * @sa @ref window_close * @sa @ref glfwSetWindowCloseCallback * * @since Added in version 2.5. * @glfw3 Added window handle parameter. * * @ingroup window */ typedef void (* GLFWwindowclosefun)(GLFWwindow*); /*! @brief The function pointer type for application close callbacks. * * This is the function pointer type for application close callbacks. A application * close callback function has the following signature: * @code * void function_name(int flags) * @endcode * * @param[in] flags 0 for a user requested application quit, 1 if a fatal error occurred and application should quit ASAP * * @sa @ref glfwSetApplicationCloseCallback * * @ingroup window */ typedef void (* GLFWapplicationclosefun)(int); /*! @brief The function pointer type for system color theme change callbacks. * * This is the function pointer type for system color theme changes. * @code * void function_name(GLFWColorScheme theme_type, bool is_initial_value) * @endcode * * @param[in] theme_type 0 for unknown, 1 for dark and 2 for light * @param[in] is_initial_value true if this is the initial read of the color theme on systems where it is asynchronous such as Linux * * @sa @ref glfwSetSystemColorThemeChangeCallback * * @ingroup window */ typedef void (* GLFWsystemcolorthemechangefun)(GLFWColorScheme, bool); /*! @brief The function pointer type for window content refresh callbacks. * * This is the function pointer type for window content refresh callbacks. * A window content refresh callback function has the following signature: * @code * void function_name(GLFWwindow* window); * @endcode * * @param[in] window The window whose content needs to be refreshed. * * @sa @ref window_refresh * @sa @ref glfwSetWindowRefreshCallback * * @since Added in version 2.5. * @glfw3 Added window handle parameter. * * @ingroup window */ typedef void (* GLFWwindowrefreshfun)(GLFWwindow*); /*! @brief The function pointer type for window focus callbacks. * * This is the function pointer type for window focus callbacks. A window * focus callback function has the following signature: * @code * void function_name(GLFWwindow* window, int focused) * @endcode * * @param[in] window The window that gained or lost input focus. * @param[in] focused `true` if the window was given input focus, or * `false` if it lost it. * * @sa @ref window_focus * @sa @ref glfwSetWindowFocusCallback * * @since Added in version 3.0. * * @ingroup window */ typedef void (* GLFWwindowfocusfun)(GLFWwindow*,int); /*! @brief The function signature for window occlusion callbacks. * * This is the function signature for window occlusion callback functions. * * @param[in] window The window whose occlusion state changed. * @param[in] occluded `true` if the window was occluded, or `false` * if the window is no longer occluded. * * @sa @ref window_occlusion * @sa @ref glfwSetWindowOcclusionCallback * * @since Added in version 3.3. * * @ingroup window */ typedef void (* GLFWwindowocclusionfun)(GLFWwindow*, bool); /*! @brief The function pointer type for window iconify callbacks. * * This is the function pointer type for window iconify callbacks. A window * iconify callback function has the following signature: * @code * void function_name(GLFWwindow* window, int iconified) * @endcode * * @param[in] window The window that was iconified or restored. * @param[in] iconified `true` if the window was iconified, or * `false` if it was restored. * * @sa @ref window_iconify * @sa @ref glfwSetWindowIconifyCallback * * @since Added in version 3.0. * * @ingroup window */ typedef void (* GLFWwindowiconifyfun)(GLFWwindow*,int); /*! @brief The function pointer type for window maximize callbacks. * * This is the function pointer type for window maximize callbacks. A window * maximize callback function has the following signature: * @code * void function_name(GLFWwindow* window, int maximized) * @endcode * * @param[in] window The window that was maximized or restored. * @param[in] maximized `true` if the window was maximized, or * `false` if it was restored. * * @sa @ref window_maximize * @sa glfwSetWindowMaximizeCallback * * @since Added in version 3.3. * * @ingroup window */ typedef void (* GLFWwindowmaximizefun)(GLFWwindow*,int); /*! @brief The function pointer type for framebuffer size callbacks. * * This is the function pointer type for framebuffer size callbacks. * A framebuffer size callback function has the following signature: * @code * void function_name(GLFWwindow* window, int width, int height) * @endcode * * @param[in] window The window whose framebuffer was resized. * @param[in] width The new width, in pixels, of the framebuffer. * @param[in] height The new height, in pixels, of the framebuffer. * * @sa @ref window_fbsize * @sa @ref glfwSetFramebufferSizeCallback * * @since Added in version 3.0. * * @ingroup window */ typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int); /*! @brief The function pointer type for window content scale callbacks. * * This is the function pointer type for window content scale callbacks. * A window content scale callback function has the following signature: * @code * void function_name(GLFWwindow* window, float xscale, float yscale) * @endcode * * @param[in] window The window whose content scale changed. * @param[in] xscale The new x-axis content scale of the window. * @param[in] yscale The new y-axis content scale of the window. * * @sa @ref window_scale * @sa @ref glfwSetWindowContentScaleCallback * * @since Added in version 3.3. * * @ingroup window */ typedef void (* GLFWwindowcontentscalefun)(GLFWwindow*,float,float); /*! @brief The function pointer type for mouse button callbacks. * * This is the function pointer type for mouse button callback functions. * A mouse button callback function has the following signature: * @code * void function_name(GLFWwindow* window, int button, int action, int mods) * @endcode * * @param[in] window The window that received the event. * @param[in] button The [mouse button](@ref buttons) that was pressed or * released. * @param[in] action One of `GLFW_PRESS` or `GLFW_RELEASE`. Future releases * may add more actions. * @param[in] mods Bit field describing which [modifier keys](@ref mods) were * held down. * * @sa @ref input_mouse_button * @sa @ref glfwSetMouseButtonCallback * * @since Added in version 1.0. * @glfw3 Added window handle and modifier mask parameters. * * @ingroup input */ typedef void (* GLFWmousebuttonfun)(GLFWwindow*,int,int,int); /*! @brief The function pointer type for cursor position callbacks. * * This is the function pointer type for cursor position callbacks. A cursor * position callback function has the following signature: * @code * void function_name(GLFWwindow* window, double xpos, double ypos); * @endcode * * @param[in] window The window that received the event. * @param[in] xpos The new cursor x-coordinate, relative to the left edge of * the content area. * @param[in] ypos The new cursor y-coordinate, relative to the top edge of the * content area. * * @sa @ref cursor_pos * @sa @ref glfwSetCursorPosCallback * * @since Added in version 3.0. Replaces `GLFWmouseposfun`. * * @ingroup input */ typedef void (* GLFWcursorposfun)(GLFWwindow*,double,double); /*! @brief The function pointer type for cursor enter/leave callbacks. * * This is the function pointer type for cursor enter/leave callbacks. * A cursor enter/leave callback function has the following signature: * @code * void function_name(GLFWwindow* window, int entered) * @endcode * * @param[in] window The window that received the event. * @param[in] entered `true` if the cursor entered the window's content * area, or `false` if it left it. * * @sa @ref cursor_enter * @sa @ref glfwSetCursorEnterCallback * * @since Added in version 3.0. * * @ingroup input */ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int); /*! @brief The function pointer type for scroll callbacks. * * This is the function pointer type for scroll callbacks. A scroll callback * function has the following signature: * @code * void function_name(GLFWwindow* window, double xoffset, double yoffset) * @endcode * * @param[in] window The window that received the event. * @param[in] xoffset The scroll offset along the x-axis. * @param[in] yoffset The scroll offset along the y-axis. * @param[in] flags A bit-mask providing extra data about the event. * flags & 1 will be true if and only if the offset values are "high-precision", * typically pixel values. Otherwise the offset values are number of lines. * (flags >> 1) & 7 will have value 1 for the start of momentum scrolling, * value 2 for stationary momentum scrolling, value 3 for momentum scrolling * in progress, value 4 for momentum scrolling ended, value 5 for momentum * scrolling cancelled and value 6 if scrolling may begin soon. * @param[int] mods The keyboard modifiers * * @sa @ref scrolling * @sa @ref glfwSetScrollCallback * * @since Added in version 3.0. Replaces `GLFWmousewheelfun`. * @since Changed in version 4.0. Added `flags` parameter. * * @ingroup input */ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int,int); /*! @brief The function pointer type for key callbacks. * * This is the function pointer type for key callbacks. A keyboard * key callback function has the following signature: * @code * void function_name(GLFWwindow* window, uint32_t key, int native_key, int action, int mods) * @endcode * The semantics of this function are that the key that is interacted with on the * keyboard is reported, and the text, if any generated by the key is reported. * So, for example, if on a US-ASCII keyboard the user presses Shift+= GLFW * will report the text "+" and the key as GLFW_KEY_EQUAL. The reported key takes into * account any current keyboard maps defined in the OS. So with a dvorak mapping, pressing * the "s" key will generate text "o" and GLFW_KEY_O. * * @param[in] window The window that received the event. * @param[in] ev The key event, see GLFWkeyevent. The data in this event is only valid for * the lifetime of the callback. * * @note On X11/Wayland if a modifier other than the modifiers GLFW reports * (ctrl/shift/alt/super) is used, GLFW will report the shifted key rather * than the unshifted key. So for example, if ISO_Shift_Level_5 is used to * convert the key A into UP GLFW will report the key as UP with no modifiers. * * @sa @ref input_key * @sa @ref glfwSetKeyboardCallback * * @since Added in version 4.0. * * @ingroup input */ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); /*! @brief The function pointer type for drag and drop callbacks. * * This is the function pointer type for drop callbacks. A drop * callback function has the following signature: * @code * int function_name(GLFWwindow* window, const char* mime, const char* text) * @endcode * * @param[in] window The window that received the event. * @param[in] mime The UTF-8 encoded drop mime-type * @param[in] data The dropped data or NULL for drag enter events * @param[in] sz The size of the dropped data * @return For drag events should return the priority for the specified mime type. A priority of zero * or lower means the mime type is not accepted. Highest priority will be the finally accepted mime-type. * * @pointer_lifetime The text is valid until the * callback function returns. * * @sa @ref path_drop * @sa @ref glfwSetDropCallback * * @since Added in version 3.1. * * @ingroup input */ typedef int (* GLFWdropfun)(GLFWwindow*, const char *, const char*, size_t); typedef void (* GLFWliveresizefun)(GLFWwindow*, bool); /*! @brief The function pointer type for monitor configuration callbacks. * * This is the function pointer type for monitor configuration callbacks. * A monitor callback function has the following signature: * @code * void function_name(GLFWmonitor* monitor, int event) * @endcode * * @param[in] monitor The monitor that was connected or disconnected. * @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Future * releases may add more events. * * @sa @ref monitor_event * @sa @ref glfwSetMonitorCallback * * @since Added in version 3.0. * * @ingroup monitor */ typedef void (* GLFWmonitorfun)(GLFWmonitor*,int); /*! @brief The function pointer type for joystick configuration callbacks. * * This is the function pointer type for joystick configuration callbacks. * A joystick configuration callback function has the following signature: * @code * void function_name(int jid, int event) * @endcode * * @param[in] jid The joystick that was connected or disconnected. * @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Future * releases may add more events. * * @sa @ref joystick_event * @sa @ref glfwSetJoystickCallback * * @since Added in version 3.2. * * @ingroup input */ typedef void (* GLFWjoystickfun)(int,int); typedef void (* GLFWuserdatafun)(unsigned long long, void*); typedef void (* GLFWtickcallback)(void*); typedef void (* GLFWactivationcallback)(GLFWwindow *window, const char *token, void *data); typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin, bool is_single_glyph); typedef char* (* GLFWcurrentselectionfun)(void); typedef bool (* GLFWhascurrentselectionfun)(void); typedef void (* GLFWclipboarddatafreefun)(void* data); typedef struct GLFWDataChunk { const char *data; size_t sz; GLFWclipboarddatafreefun free; void *iter, *free_data; } GLFWDataChunk; typedef enum { GLFW_CLIPBOARD, GLFW_PRIMARY_SELECTION } GLFWClipboardType; typedef GLFWDataChunk (* GLFWclipboarditerfun)(const char *mime_type, void *iter, GLFWClipboardType ctype); typedef bool (* GLFWclipboardwritedatafun)(void *object, const char *data, size_t sz); typedef bool (* GLFWimecursorpositionfun)(GLFWwindow *window, GLFWIMEUpdateEvent *ev); typedef void (* GLFWclipboardlostfun )(GLFWClipboardType); /*! @brief Video mode type. * * This describes a single video mode. * * @sa @ref monitor_modes * @sa @ref glfwGetVideoMode * @sa @ref glfwGetVideoModes * * @since Added in version 1.0. * @glfw3 Added refresh rate member. * * @ingroup monitor */ typedef struct GLFWvidmode { /*! The width, in screen coordinates, of the video mode. */ int width; /*! The height, in screen coordinates, of the video mode. */ int height; /*! The bit depth of the red channel of the video mode. */ int redBits; /*! The bit depth of the green channel of the video mode. */ int greenBits; /*! The bit depth of the blue channel of the video mode. */ int blueBits; /*! The refresh rate, in Hz, of the video mode. */ int refreshRate; } GLFWvidmode; /*! @brief Gamma ramp. * * This describes the gamma ramp for a monitor. * * @sa @ref monitor_gamma * @sa @ref glfwGetGammaRamp * @sa @ref glfwSetGammaRamp * * @since Added in version 3.0. * * @ingroup monitor */ typedef struct GLFWgammaramp { /*! An array of value describing the response of the red channel. */ unsigned short* red; /*! An array of value describing the response of the green channel. */ unsigned short* green; /*! An array of value describing the response of the blue channel. */ unsigned short* blue; /*! The number of elements in each array. */ unsigned int size; } GLFWgammaramp; /*! @brief Image data. * * This describes a single 2D image. See the documentation for each related * function what the expected pixel format is. * * @sa @ref cursor_custom * @sa @ref window_icon * * @since Added in version 2.1. * @glfw3 Removed format and bytes-per-pixel members. * * @ingroup window */ typedef struct GLFWimage { /*! The width, in pixels, of this image. */ int width; /*! The height, in pixels, of this image. */ int height; /*! The pixel data of this image, arranged left-to-right, top-to-bottom. */ unsigned char* pixels; } GLFWimage; /*! @brief Gamepad input state * * This describes the input state of a gamepad. * * @sa @ref gamepad * @sa @ref glfwGetGamepadState * * @since Added in version 3.3. * * @ingroup input */ typedef struct GLFWgamepadstate { /*! The states of each [gamepad button](@ref gamepad_buttons), `GLFW_PRESS` * or `GLFW_RELEASE`. */ unsigned char buttons[15]; /*! The states of each [gamepad axis](@ref gamepad_axes), in the range -1.0 * to 1.0 inclusive. */ float axes[6]; } GLFWgamepadstate; /************************************************************************* * GLFW API functions *************************************************************************/ /*! @brief Initializes the GLFW library. * * This function initializes the GLFW library. Before most GLFW functions can * be used, GLFW must be initialized, and before an application terminates GLFW * should be terminated in order to free any resources allocated during or * after initialization. * * If this function fails, it calls @ref glfwTerminate before returning. If it * succeeds, you should call @ref glfwTerminate before the application exits. * * Additional calls to this function after successful initialization but before * termination will return `true` immediately. * * @return `true` if successful, or `false` if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_PLATFORM_ERROR. * * @remark @macos This function will change the current directory of the * application to the `Contents/Resources` subdirectory of the application's * bundle, if present. This can be disabled with the @ref * GLFW_COCOA_CHDIR_RESOURCES init hint. * * @thread_safety This function must only be called from the main thread. * * @sa @ref intro_init * @sa @ref glfwTerminate * * @since Added in version 1.0. * * @ingroup init */ GLFWAPI int glfwInit(monotonic_t start_time, bool *supports_window_occlusion); GLFWAPI void glfwRunMainLoop(GLFWtickcallback callback, void *callback_data); GLFWAPI void glfwStopMainLoop(void); GLFWAPI unsigned long long glfwAddTimer(monotonic_t interval, bool repeats, GLFWuserdatafun callback, void * callback_data, GLFWuserdatafun free_callback); GLFWAPI void glfwUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled); GLFWAPI void glfwRemoveTimer(unsigned long long); GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun function); GLFWAPI GLFWcurrentselectionfun glfwSetCurrentSelectionCallback(GLFWcurrentselectionfun callback); GLFWAPI GLFWhascurrentselectionfun glfwSetHasCurrentSelectionCallback(GLFWhascurrentselectionfun callback); GLFWAPI GLFWimecursorpositionfun glfwSetIMECursorPositionCallback(GLFWimecursorpositionfun callback); /*! @brief Terminates the GLFW library. * * This function destroys all remaining windows and cursors, restores any * modified gamma ramps and frees any other allocated resources. Once this * function is called, you must again call @ref glfwInit successfully before * you will be able to use most GLFW functions. * * If GLFW has been successfully initialized, this function should be called * before the application exits. If initialization fails, there is no need to * call this function, as it is called by @ref glfwInit before it returns * failure. * * This function has no effect if GLFW is not initialized. * * @errors Possible errors include @ref GLFW_PLATFORM_ERROR. * * @remark This function may be called before @ref glfwInit. * * @warning The contexts of any remaining windows must not be current on any * other thread when this function is called. * * @reentrancy This function must not be called from a callback. * * @thread_safety This function must only be called from the main thread. * * @sa @ref intro_init * @sa @ref glfwInit * * @since Added in version 1.0. * * @ingroup init */ GLFWAPI void glfwTerminate(void); /*! @brief Sets the specified init hint to the desired value. * * This function sets hints for the next initialization of GLFW. * * The values you set hints to are never reset by GLFW, but they only take * effect during initialization. Once GLFW has been initialized, any values * you set will be ignored until the library is terminated and initialized * again. * * Some hints are platform specific. These may be set on any platform but they * will only affect their specific platform. Other platforms will ignore them. * Setting these hints requires no platform specific headers or functions. * * @param[in] hint The [init hint](@ref init_hints) to set. * @param[in] value The new value of the init hint. * * @errors Possible errors include @ref GLFW_INVALID_ENUM and @ref * GLFW_INVALID_VALUE. * * @remarks This function may be called before @ref glfwInit. * * @thread_safety This function must only be called from the main thread. * * @sa init_hints * @sa glfwInit * * @since Added in version 3.3. * * @ingroup init */ GLFWAPI void glfwInitHint(int hint, int value); /*! @brief Retrieves the version of the GLFW library. * * This function retrieves the major, minor and revision numbers of the GLFW * library. It is intended for when you are using GLFW as a shared library and * want to ensure that you are using the minimum required version. * * Any or all of the version arguments may be `NULL`. * * @param[out] major Where to store the major version number, or `NULL`. * @param[out] minor Where to store the minor version number, or `NULL`. * @param[out] rev Where to store the revision number, or `NULL`. * * @errors None. * * @remark This function may be called before @ref glfwInit. * * @thread_safety This function may be called from any thread. * * @sa @ref intro_version * @sa @ref glfwGetVersionString * * @since Added in version 1.0. * * @ingroup init */ GLFWAPI void glfwGetVersion(int* major, int* minor, int* rev); /*! @brief Returns a string describing the compile-time configuration. * * This function returns the compile-time generated * [version string](@ref intro_version_string) of the GLFW library binary. It * describes the version, platform, compiler and any platform-specific * compile-time options. It should not be confused with the OpenGL or OpenGL * ES version string, queried with `glGetString`. * * __Do not use the version string__ to parse the GLFW library version. The * @ref glfwGetVersion function provides the version of the running library * binary in numerical format. * * @return The ASCII encoded GLFW version string. * * @errors None. * * @remark This function may be called before @ref glfwInit. * * @pointer_lifetime The returned string is static and compile-time generated. * * @thread_safety This function may be called from any thread. * * @sa @ref intro_version * @sa @ref glfwGetVersion * * @since Added in version 3.0. * * @ingroup init */ GLFWAPI const char* glfwGetVersionString(void); /*! @brief Returns and clears the last error for the calling thread. * * This function returns and clears the [error code](@ref errors) of the last * error that occurred on the calling thread, and optionally a UTF-8 encoded * human-readable description of it. If no error has occurred since the last * call, it returns @ref GLFW_NO_ERROR (zero) and the description pointer is * set to `NULL`. * * @param[in] description Where to store the error description pointer, or `NULL`. * @return The last error code for the calling thread, or @ref GLFW_NO_ERROR * (zero). * * @errors None. * * @pointer_lifetime The returned string is allocated and freed by GLFW. You * should not free it yourself. It is guaranteed to be valid only until the * next error occurs or the library is terminated. * * @remark This function may be called before @ref glfwInit. * * @thread_safety This function may be called from any thread. * * @sa @ref error_handling * @sa @ref glfwSetErrorCallback * * @since Added in version 3.3. * * @ingroup init */ GLFWAPI int glfwGetError(const char** description); /*! @brief Sets the error callback. * * This function sets the error callback, which is called with an error code * and a human-readable description each time a GLFW error occurs. * * The error code is set before the callback is called. Calling @ref * glfwGetError from the error callback will return the same value as the error * code argument. * * The error callback is called on the thread where the error occurred. If you * are using GLFW from multiple threads, your error callback needs to be * written accordingly. * * Because the description string may have been generated specifically for that * error, it is not guaranteed to be valid after the callback has returned. If * you wish to use it after the callback returns, you need to make a copy. * * Once set, the error callback remains set even after the library has been * terminated. * * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set. * * @callback_signature * @code * void callback_name(int error_code, const char* description) * @endcode * For more information about the callback parameters, see the * [callback pointer type](@ref GLFWerrorfun). * * @errors None. * * @remark This function may be called before @ref glfwInit. * * @thread_safety This function must only be called from the main thread. * * @sa @ref error_handling * @sa @ref glfwGetError * * @since Added in version 3.0. * * @ingroup init */ GLFWAPI GLFWerrorfun glfwSetErrorCallback(GLFWerrorfun callback); /*! @brief Returns the currently connected monitors. * * This function returns an array of handles for all currently connected * monitors. The primary monitor is always first in the returned array. If no * monitors were found, this function returns `NULL`. * * @param[out] count Where to store the number of monitors in the returned * array. This is set to zero if an error occurred. * @return An array of monitor handles, or `NULL` if no monitors were found or * if an [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @pointer_lifetime The returned array is allocated and freed by GLFW. You * should not free it yourself. It is guaranteed to be valid only until the * monitor configuration changes or the library is terminated. * * @thread_safety This function must only be called from the main thread. * * @sa @ref monitor_monitors * @sa @ref monitor_event * @sa @ref glfwGetPrimaryMonitor * * @since Added in version 3.0. * * @ingroup monitor */ GLFWAPI GLFWmonitor** glfwGetMonitors(int* count); /*! @brief Returns the primary monitor. * * This function returns the primary monitor. This is usually the monitor * where elements like the task bar or global menu bar are located. * * @return The primary monitor, or `NULL` if no monitors were found or if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @remark The primary monitor is always first in the array returned by @ref * glfwGetMonitors. * * @sa @ref monitor_monitors * @sa @ref glfwGetMonitors * * @since Added in version 3.0. * * @ingroup monitor */ GLFWAPI GLFWmonitor* glfwGetPrimaryMonitor(void); /*! @brief Returns the position of the monitor's viewport on the virtual screen. * * This function returns the position, in screen coordinates, of the upper-left * corner of the specified monitor. * * Any or all of the position arguments may be `NULL`. If an error occurs, all * non-`NULL` position arguments will be set to zero. * * @param[in] monitor The monitor to query. * @param[out] xpos Where to store the monitor x-coordinate, or `NULL`. * @param[out] ypos Where to store the monitor y-coordinate, or `NULL`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref monitor_properties * * @since Added in version 3.0. * * @ingroup monitor */ GLFWAPI void glfwGetMonitorPos(GLFWmonitor* monitor, int* xpos, int* ypos); /*! @brief Retrieves the work area of the monitor. * * This function returns the position, in screen coordinates, of the upper-left * corner of the work area of the specified monitor along with the work area * size in screen coordinates. The work area is defined as the area of the * monitor not occluded by the operating system task bar where present. If no * task bar exists then the work area is the monitor resolution in screen * coordinates. * * Any or all of the position and size arguments may be `NULL`. If an error * occurs, all non-`NULL` position and size arguments will be set to zero. * * @param[in] monitor The monitor to query. * @param[out] xpos Where to store the monitor x-coordinate, or `NULL`. * @param[out] ypos Where to store the monitor y-coordinate, or `NULL`. * @param[out] width Where to store the monitor width, or `NULL`. * @param[out] height Where to store the monitor height, or `NULL`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref monitor_workarea * * @since Added in version 3.3. * * @ingroup monitor */ GLFWAPI void glfwGetMonitorWorkarea(GLFWmonitor* monitor, int* xpos, int* ypos, int* width, int* height); /*! @brief Returns the physical size of the monitor. * * This function returns the size, in millimetres, of the display area of the * specified monitor. * * Some systems do not provide accurate monitor size information, either * because the monitor * [EDID](https://en.wikipedia.org/wiki/Extended_display_identification_data) * data is incorrect or because the driver does not report it accurately. * * Any or all of the size arguments may be `NULL`. If an error occurs, all * non-`NULL` size arguments will be set to zero. * * @param[in] monitor The monitor to query. * @param[out] widthMM Where to store the width, in millimetres, of the * monitor's display area, or `NULL`. * @param[out] heightMM Where to store the height, in millimetres, of the * monitor's display area, or `NULL`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @remark @win32 calculates the returned physical size from the * current resolution and system DPI instead of querying the monitor EDID data. * * @thread_safety This function must only be called from the main thread. * * @sa @ref monitor_properties * * @since Added in version 3.0. * * @ingroup monitor */ GLFWAPI void glfwGetMonitorPhysicalSize(GLFWmonitor* monitor, int* widthMM, int* heightMM); /*! @brief Retrieves the content scale for the specified monitor. * * This function retrieves the content scale for the specified monitor. The * content scale is the ratio between the current DPI and the platform's * default DPI. This is especially important for text and any UI elements. If * the pixel dimensions of your UI scaled by this look appropriate on your * machine then it should appear at a reasonable size on other machines * regardless of their DPI and scaling settings. This relies on the system DPI * and scaling settings being somewhat correct. * * The content scale may depend on both the monitor resolution and pixel * density and on user settings. It may be very different from the raw DPI * calculated from the physical size and current resolution. * * @param[in] monitor The monitor to query. * @param[out] xscale Where to store the x-axis content scale, or `NULL`. * @param[out] yscale Where to store the y-axis content scale, or `NULL`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref monitor_scale * @sa @ref glfwGetWindowContentScale * * @since Added in version 3.3. * * @ingroup monitor */ GLFWAPI void glfwGetMonitorContentScale(GLFWmonitor* monitor, float* xscale, float* yscale); /*! @brief Returns the name of the specified monitor. * * This function returns a human-readable name, encoded as UTF-8, of the * specified monitor. The name typically reflects the make and model of the * monitor and is not guaranteed to be unique among the connected monitors. * * @param[in] monitor The monitor to query. * @return The UTF-8 encoded name of the monitor, or `NULL` if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @pointer_lifetime The returned string is allocated and freed by GLFW. You * should not free it yourself. It is valid until the specified monitor is * disconnected or the library is terminated. * * @thread_safety This function must only be called from the main thread. * * @sa @ref monitor_properties * * @since Added in version 3.0. * * @ingroup monitor */ GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* monitor); /*! @brief Sets the user pointer of the specified monitor. * * This function sets the user-defined pointer of the specified monitor. The * current value is retained until the monitor is disconnected. The initial * value is `NULL`. * * This function may be called from the monitor callback, even for a monitor * that is being disconnected. * * @param[in] monitor The monitor whose pointer to set. * @param[in] pointer The new value. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function may be called from any thread. Access is not * synchronized. * * @sa @ref monitor_userptr * @sa @ref glfwGetMonitorUserPointer * * @since Added in version 3.3. * * @ingroup monitor */ GLFWAPI void glfwSetMonitorUserPointer(GLFWmonitor* monitor, void* pointer); /*! @brief Returns the user pointer of the specified monitor. * * This function returns the current value of the user-defined pointer of the * specified monitor. The initial value is `NULL`. * * This function may be called from the monitor callback, even for a monitor * that is being disconnected. * * @param[in] monitor The monitor whose pointer to return. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function may be called from any thread. Access is not * synchronized. * * @sa @ref monitor_userptr * @sa @ref glfwSetMonitorUserPointer * * @since Added in version 3.3. * * @ingroup monitor */ GLFWAPI void* glfwGetMonitorUserPointer(GLFWmonitor* monitor); /*! @brief Sets the monitor configuration callback. * * This function sets the monitor configuration callback, or removes the * currently set callback. This is called when a monitor is connected to or * disconnected from the system. * * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWmonitor* monitor, int event) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWmonitorfun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref monitor_event * * @since Added in version 3.0. * * @ingroup monitor */ GLFWAPI GLFWmonitorfun glfwSetMonitorCallback(GLFWmonitorfun callback); /*! @brief Returns the available video modes for the specified monitor. * * This function returns an array of all video modes supported by the specified * monitor. The returned array is sorted in ascending order, first by color * bit depth (the sum of all channel depths) and then by resolution area (the * product of width and height). * * @param[in] monitor The monitor to query. * @param[out] count Where to store the number of video modes in the returned * array. This is set to zero if an error occurred. * @return An array of video modes, or `NULL` if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @pointer_lifetime The returned array is allocated and freed by GLFW. You * should not free it yourself. It is valid until the specified monitor is * disconnected, this function is called again for that monitor or the library * is terminated. * * @thread_safety This function must only be called from the main thread. * * @sa @ref monitor_modes * @sa @ref glfwGetVideoMode * * @since Added in version 1.0. * @glfw3 Changed to return an array of modes for a specific monitor. * * @ingroup monitor */ GLFWAPI const GLFWvidmode* glfwGetVideoModes(GLFWmonitor* monitor, int* count); /*! @brief Returns the current mode of the specified monitor. * * This function returns the current video mode of the specified monitor. If * you have created a full screen window for that monitor, the return value * will depend on whether that window is iconified. * * @param[in] monitor The monitor to query. * @return The current mode of the monitor, or `NULL` if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @pointer_lifetime The returned array is allocated and freed by GLFW. You * should not free it yourself. It is valid until the specified monitor is * disconnected or the library is terminated. * * @thread_safety This function must only be called from the main thread. * * @sa @ref monitor_modes * @sa @ref glfwGetVideoModes * * @since Added in version 3.0. Replaces `glfwGetDesktopMode`. * * @ingroup monitor */ GLFWAPI const GLFWvidmode* glfwGetVideoMode(GLFWmonitor* monitor); /*! @brief Generates a gamma ramp and sets it for the specified monitor. * * This function generates an appropriately sized gamma ramp from the specified * exponent and then calls @ref glfwSetGammaRamp with it. The value must be * a finite number greater than zero. * * The software controlled gamma ramp is applied _in addition_ to the hardware * gamma correction, which today is usually an approximation of sRGB gamma. * This means that setting a perfectly linear ramp, or gamma 1.0, will produce * the default (usually sRGB-like) behavior. * * For gamma correct rendering with OpenGL or OpenGL ES, see the @ref * GLFW_SRGB_CAPABLE hint. * * @param[in] monitor The monitor whose gamma ramp to set. * @param[in] gamma The desired exponent. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_VALUE and @ref GLFW_PLATFORM_ERROR. * * @remark @wayland Gamma handling is a privileged protocol, this function * will thus never be implemented and emits @ref GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref monitor_gamma * * @since Added in version 3.0. * * @ingroup monitor */ GLFWAPI void glfwSetGamma(GLFWmonitor* monitor, float gamma); /*! @brief Returns the current gamma ramp for the specified monitor. * * This function returns the current gamma ramp of the specified monitor. * * @param[in] monitor The monitor to query. * @return The current gamma ramp, or `NULL` if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @remark @wayland Gamma handling is a privileged protocol, this function * will thus never be implemented and emits @ref GLFW_PLATFORM_ERROR while * returning `NULL`. * * @pointer_lifetime The returned structure and its arrays are allocated and * freed by GLFW. You should not free them yourself. They are valid until the * specified monitor is disconnected, this function is called again for that * monitor or the library is terminated. * * @thread_safety This function must only be called from the main thread. * * @sa @ref monitor_gamma * * @since Added in version 3.0. * * @ingroup monitor */ GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* monitor); /*! @brief Sets the current gamma ramp for the specified monitor. * * This function sets the current gamma ramp for the specified monitor. The * original gamma ramp for that monitor is saved by GLFW the first time this * function is called and is restored by @ref glfwTerminate. * * The software controlled gamma ramp is applied _in addition_ to the hardware * gamma correction, which today is usually an approximation of sRGB gamma. * This means that setting a perfectly linear ramp, or gamma 1.0, will produce * the default (usually sRGB-like) behavior. * * For gamma correct rendering with OpenGL or OpenGL ES, see the @ref * GLFW_SRGB_CAPABLE hint. * * @param[in] monitor The monitor whose gamma ramp to set. * @param[in] ramp The gamma ramp to use. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @remark The size of the specified gamma ramp should match the size of the * current ramp for that monitor. * * @remark @win32 The gamma ramp size must be 256. * * @remark @wayland Gamma handling is a privileged protocol, this function * will thus never be implemented and emits @ref GLFW_PLATFORM_ERROR. * * @pointer_lifetime The specified gamma ramp is copied before this function * returns. * * @thread_safety This function must only be called from the main thread. * * @sa @ref monitor_gamma * * @since Added in version 3.0. * * @ingroup monitor */ GLFWAPI void glfwSetGammaRamp(GLFWmonitor* monitor, const GLFWgammaramp* ramp); /*! @brief Resets all window hints to their default values. * * This function resets all window hints to their * [default values](@ref window_hints_values). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_hints * @sa @ref glfwWindowHint * @sa @ref glfwWindowHintString * * @since Added in version 3.0. * * @ingroup window */ GLFWAPI void glfwDefaultWindowHints(void); /*! @brief Sets the specified window hint to the desired value. * * This function sets hints for the next call to @ref glfwCreateWindow. The * hints, once set, retain their values until changed by a call to this * function or @ref glfwDefaultWindowHints, or until the library is terminated. * * Only integer value hints can be set with this function. String value hints * are set with @ref glfwWindowHintString. * * This function does not check whether the specified hint values are valid. * If you set hints to invalid values this will instead be reported by the next * call to @ref glfwCreateWindow. * * Some hints are platform specific. These may be set on any platform but they * will only affect their specific platform. Other platforms will ignore them. * Setting these hints requires no platform specific headers or functions. * * @param[in] hint The [window hint](@ref window_hints) to set. * @param[in] value The new value of the window hint. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_INVALID_ENUM. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_hints * @sa @ref glfwWindowHintString * @sa @ref glfwDefaultWindowHints * * @since Added in version 3.0. Replaces `glfwOpenWindowHint`. * * @ingroup window */ GLFWAPI void glfwWindowHint(int hint, int value); /*! @brief Sets the specified window hint to the desired value. * * This function sets hints for the next call to @ref glfwCreateWindow. The * hints, once set, retain their values until changed by a call to this * function or @ref glfwDefaultWindowHints, or until the library is terminated. * * Only string type hints can be set with this function. Integer value hints * are set with @ref glfwWindowHint. * * This function does not check whether the specified hint values are valid. * If you set hints to invalid values this will instead be reported by the next * call to @ref glfwCreateWindow. * * Some hints are platform specific. These may be set on any platform but they * will only affect their specific platform. Other platforms will ignore them. * Setting these hints requires no platform specific headers or functions. * * @param[in] hint The [window hint](@ref window_hints) to set. * @param[in] value The new value of the window hint. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_INVALID_ENUM. * * @pointer_lifetime The specified string is copied before this function * returns. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_hints * @sa @ref glfwWindowHint * @sa @ref glfwDefaultWindowHints * * @since Added in version 3.3. * * @ingroup window */ GLFWAPI void glfwWindowHintString(int hint, const char* value); /*! @brief Creates a window and its associated context. * * This function creates a window and its associated OpenGL or OpenGL ES * context. Most of the options controlling how the window and its context * should be created are specified with [window hints](@ref window_hints). * * Successful creation does not change which context is current. Before you * can use the newly created context, you need to * [make it current](@ref context_current). For information about the `share` * parameter, see @ref context_sharing. * * The created window, framebuffer and context may differ from what you * requested, as not all parameters and hints are * [hard constraints](@ref window_hints_hard). This includes the size of the * window, especially for full screen windows. To query the actual attributes * of the created window, framebuffer and context, see @ref * glfwGetWindowAttrib, @ref glfwGetWindowSize and @ref glfwGetFramebufferSize. * * To create a full screen window, you need to specify the monitor the window * will cover. If no monitor is specified, the window will be windowed mode. * Unless you have a way for the user to choose a specific monitor, it is * recommended that you pick the primary monitor. For more information on how * to query connected monitors, see @ref monitor_monitors. * * For full screen windows, the specified size becomes the resolution of the * window's _desired video mode_. As long as a full screen window is not * iconified, the supported video mode most closely matching the desired video * mode is set for the specified monitor. For more information about full * screen windows, including the creation of so called _windowed full screen_ * or _borderless full screen_ windows, see @ref window_windowed_full_screen. * * Once you have created the window, you can switch it between windowed and * full screen mode with @ref glfwSetWindowMonitor. This will not affect its * OpenGL or OpenGL ES context. * * By default, newly created windows use the placement recommended by the * window system. To create the window at a specific position, make it * initially invisible using the [GLFW_VISIBLE](@ref GLFW_VISIBLE_hint) window * hint, set its [position](@ref window_pos) and then [show](@ref window_hide) * it. * * As long as at least one full screen window is not iconified, the screensaver * is prohibited from starting. * * Window systems put limits on window sizes. Very large or very small window * dimensions may be overridden by the window system on creation. Check the * actual [size](@ref window_size) after creation. * * The [swap interval](@ref buffer_swap) is not set during window creation and * the initial value may vary depending on driver settings and defaults. * * @param[in] width The desired width, in screen coordinates, of the window. * This must be greater than zero. * @param[in] height The desired height, in screen coordinates, of the window. * This must be greater than zero. * @param[in] title The initial, UTF-8 encoded window title. * @param[in] monitor The monitor to use for full screen mode, or `NULL` for * windowed mode. * @param[in] share The window whose context to share resources with, or `NULL` * to not share resources. * @return The handle of the created window, or `NULL` if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_ENUM, @ref GLFW_INVALID_VALUE, @ref GLFW_API_UNAVAILABLE, @ref * GLFW_VERSION_UNAVAILABLE, @ref GLFW_FORMAT_UNAVAILABLE and @ref * GLFW_PLATFORM_ERROR. * * @remark @win32 Window creation will fail if the Microsoft GDI software * OpenGL implementation is the only one available. * * @remark @win32 If the executable has an icon resource named `GLFW_ICON,` it * will be set as the initial icon for the window. If no such icon is present, * the `IDI_APPLICATION` icon will be used instead. To set a different icon, * see @ref glfwSetWindowIcon. * * @remark @win32 The context to share resources with must not be current on * any other thread. * * @remark @macos The OS only supports forward-compatible core profile contexts * for OpenGL versions 3.2 and later. Before creating an OpenGL context of * version 3.2 or later you must set the * [GLFW_OPENGL_FORWARD_COMPAT](@ref GLFW_OPENGL_FORWARD_COMPAT_hint) and * [GLFW_OPENGL_PROFILE](@ref GLFW_OPENGL_PROFILE_hint) hints accordingly. * OpenGL 3.0 and 3.1 contexts are not supported at all on macOS. * * @remark @macos The GLFW window has no icon, as it is not a document * window, but the dock icon will be the same as the application bundle's icon. * For more information on bundles, see the * [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/) * in the Mac Developer Library. * * @remark @macos The first time a window is created the menu bar is created. * If GLFW finds a `MainMenu.nib` it is loaded and assumed to contain a menu * bar. Otherwise a minimal menu bar is created manually with common commands * like Hide, Quit and About. The About entry opens a minimal about dialog * with information from the application's bundle. Menu bar creation can be * disabled entirely with the @ref GLFW_COCOA_MENUBAR init hint. * * @remark @macos On OS X 10.10 and later the window frame will not be rendered * at full resolution on Retina displays unless the * [GLFW_COCOA_RETINA_FRAMEBUFFER](@ref GLFW_COCOA_RETINA_FRAMEBUFFER_hint) * hint is `true` and the `NSHighResolutionCapable` key is enabled in the * application bundle's `Info.plist`. For more information, see * [High Resolution Guidelines for OS X](https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Explained/Explained.html) * in the Mac Developer Library. The GLFW test and example programs use * a custom `Info.plist` template for this, which can be found as * `CMake/MacOSXBundleInfo.plist.in` in the source tree. * * @remark @macos When activating frame autosaving with * [GLFW_COCOA_FRAME_NAME](@ref GLFW_COCOA_FRAME_NAME_hint), the specified * window size and position may be overridden by previously saved values. * * @remark @x11 Some window managers will not respect the placement of * initially hidden windows. * * @remark @x11 Due to the asynchronous nature of X11, it may take a moment for * a window to reach its requested state. This means you may not be able to * query the final size, position or other attributes directly after window * creation. * * @remark @x11 The class part of the `WM_CLASS` window property will by * default be set to the window title passed to this function. The instance * part will use the contents of the `RESOURCE_NAME` environment variable, if * present and not empty, or fall back to the window title. Set the * [GLFW_X11_CLASS_NAME](@ref GLFW_X11_CLASS_NAME_hint) and * [GLFW_X11_INSTANCE_NAME](@ref GLFW_X11_INSTANCE_NAME_hint) window hints to * override this. * * @remark @wayland Compositors should implement the xdg-decoration protocol * for GLFW to decorate the window properly. If this protocol isn't * supported, or if the compositor prefers client-side decorations, a very * simple fallback frame will be drawn using the wp_viewporter protocol. A * compositor can still emit close, maximize or fullscreen events, using for * instance a keybind mechanism. If neither of these protocols is supported, * the window won't be decorated. * * @remark @wayland A full screen window will not attempt to change the mode, * no matter what the requested size or refresh rate. * * @remark @wayland Screensaver inhibition requires the idle-inhibit protocol * to be implemented in the user's compositor. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_creation * @sa @ref glfwDestroyWindow * * @since Added in version 3.0. Replaces `glfwOpenWindow`. * * @ingroup window */ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share); GLFWAPI bool glfwToggleFullscreen(GLFWwindow *window, unsigned int flags); GLFWAPI bool glfwIsFullscreen(GLFWwindow *window, unsigned int flags); GLFWAPI bool glfwAreSwapsAllowed(const GLFWwindow* window); /*! @brief Destroys the specified window and its context. * * This function destroys the specified window and its context. On calling * this function, no further callbacks will be called for that window. * * If the context of the specified window is current on the main thread, it is * detached before being destroyed. * * @param[in] window The window to destroy. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @note The context of the specified window must not be current on any other * thread when this function is called. * * @reentrancy This function must not be called from a callback. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_creation * @sa @ref glfwCreateWindow * * @since Added in version 3.0. Replaces `glfwCloseWindow`. * * @ingroup window */ GLFWAPI void glfwDestroyWindow(GLFWwindow* window); /*! @brief Checks the close flag of the specified window. * * This function returns the value of the close flag of the specified window. * * @param[in] window The window to query. * @return The value of the close flag. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function may be called from any thread. Access is not * synchronized. * * @sa @ref window_close * * @since Added in version 3.0. * * @ingroup window */ GLFWAPI int glfwWindowShouldClose(GLFWwindow* window); /*! @brief Sets the close flag of the specified window. * * This function sets the value of the close flag of the specified window. * This can be used to override the user's attempt to close the window, or * to signal that it should be closed. * * @param[in] window The window whose flag to change. * @param[in] value The new value. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function may be called from any thread. Access is not * synchronized. * * @sa @ref window_close * * @since Added in version 3.0. * * @ingroup window */ GLFWAPI void glfwSetWindowShouldClose(GLFWwindow* window, int value); /*! @brief Sets the title of the specified window. * * This function sets the window title, encoded as UTF-8, of the specified * window. * * @param[in] window The window whose title to change. * @param[in] title The UTF-8 encoded window title. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @remark @macos The window title will not be updated until the next time you * process events. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_title * * @since Added in version 1.0. * @glfw3 Added window handle parameter. * * @ingroup window */ GLFWAPI void glfwSetWindowTitle(GLFWwindow* window, const char* title); /*! @brief Sets the icon for the specified window. * * This function sets the icon of the specified window. If passed an array of * candidate images, those of or closest to the sizes desired by the system are * selected. If no images are specified, the window reverts to its default * icon. * * The pixels are 32-bit, little-endian, non-premultiplied RGBA, i.e. eight * bits per channel with the red channel first. They are arranged canonically * as packed sequential rows, starting from the top-left corner. * * The desired image sizes varies depending on platform and system settings. * The selected images will be rescaled as needed. Good sizes include 16x16, * 32x32 and 48x48. * * @param[in] window The window whose icon to set. * @param[in] count The number of images in the specified array, or zero to * revert to the default window icon. * @param[in] images The images to create the icon from. This is ignored if * count is zero. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks). * * @pointer_lifetime The specified image data is copied before this function * returns. * * @remark @macos Regular windows do not have icons on macOS. This function * will emit @ref GLFW_FEATURE_UNAVAILABLE. The dock icon will be the same as * the application bundle's icon. For more information on bundles, see the * [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/) * in the Mac Developer Library. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_icon * * @since Added in version 3.2. * * @ingroup window */ GLFWAPI void glfwSetWindowIcon(GLFWwindow* window, int count, const GLFWimage* images); /*! @brief Retrieves the position of the content area of the specified window. * * This function retrieves the position, in screen coordinates, of the * upper-left corner of the content area of the specified window. * * Any or all of the position arguments may be `NULL`. If an error occurs, all * non-`NULL` position arguments will be set to zero. * * @param[in] window The window to query. * @param[out] xpos Where to store the x-coordinate of the upper-left corner of * the content area, or `NULL`. * @param[out] ypos Where to store the y-coordinate of the upper-left corner of * the content area, or `NULL`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks). * * @remark @wayland There is no way for an application to retrieve the global * position of its windows. This function will emit @ref * GLFW_FEATURE_UNAVAILABLE. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_pos * @sa @ref glfwSetWindowPos * * @since Added in version 3.0. * * @ingroup window */ GLFWAPI void glfwGetWindowPos(GLFWwindow* window, int* xpos, int* ypos); /*! @brief Sets the position of the content area of the specified window. * * This function sets the position, in screen coordinates, of the upper-left * corner of the content area of the specified windowed mode window. If the * window is a full screen window, this function does nothing. * * __Do not use this function__ to move an already visible window unless you * have very good reasons for doing so, as it will confuse and annoy the user. * * The window manager may put limits on what positions are allowed. GLFW * cannot and should not override these limits. * * @param[in] window The window to query. * @param[in] xpos The x-coordinate of the upper-left corner of the content area. * @param[in] ypos The y-coordinate of the upper-left corner of the content area. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks). * * @remark @wayland There is no way for an application to set the global * position of its windows. This function will emit @ref * GLFW_FEATURE_UNAVAILABLE. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_pos * @sa @ref glfwGetWindowPos * * @since Added in version 1.0. * @glfw3 Added window handle parameter. * * @ingroup window */ GLFWAPI void glfwSetWindowPos(GLFWwindow* window, int xpos, int ypos); /*! @brief Retrieves the size of the content area of the specified window. * * This function retrieves the size, in screen coordinates, of the content area * of the specified window. If you wish to retrieve the size of the * framebuffer of the window in pixels, see @ref glfwGetFramebufferSize. * * Any or all of the size arguments may be `NULL`. If an error occurs, all * non-`NULL` size arguments will be set to zero. * * @param[in] window The window whose size to retrieve. * @param[out] width Where to store the width, in screen coordinates, of the * content area, or `NULL`. * @param[out] height Where to store the height, in screen coordinates, of the * content area, or `NULL`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_size * @sa @ref glfwSetWindowSize * * @since Added in version 1.0. * @glfw3 Added window handle parameter. * * @ingroup window */ GLFWAPI void glfwGetWindowSize(GLFWwindow* window, int* width, int* height); /*! @brief Sets the size limits of the specified window. * * This function sets the size limits of the content area of the specified * window. If the window is full screen, the size limits only take effect * once it is made windowed. If the window is not resizable, this function * does nothing. * * The size limits are applied immediately to a windowed mode window and may * cause it to be resized. * * The maximum dimensions must be greater than or equal to the minimum * dimensions and all must be greater than or equal to zero. * * @param[in] window The window to set limits for. * @param[in] minwidth The minimum width, in screen coordinates, of the content * area, or `GLFW_DONT_CARE`. * @param[in] minheight The minimum height, in screen coordinates, of the * content area, or `GLFW_DONT_CARE`. * @param[in] maxwidth The maximum width, in screen coordinates, of the content * area, or `GLFW_DONT_CARE`. * @param[in] maxheight The maximum height, in screen coordinates, of the * content area, or `GLFW_DONT_CARE`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_VALUE and @ref GLFW_PLATFORM_ERROR. * * @remark If you set size limits and an aspect ratio that conflict, the * results are undefined. * * @remark @wayland The size limits will not be applied until the window is * actually resized, either by the user or by the compositor. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_sizelimits * @sa @ref glfwSetWindowAspectRatio * * @since Added in version 3.2. * * @ingroup window */ GLFWAPI void glfwSetWindowSizeLimits(GLFWwindow* window, int minwidth, int minheight, int maxwidth, int maxheight); /*! @brief Sets the aspect ratio of the specified window. * * This function sets the required aspect ratio of the content area of the * specified window. If the window is full screen, the aspect ratio only takes * effect once it is made windowed. If the window is not resizable, this * function does nothing. * * The aspect ratio is specified as a numerator and a denominator and both * values must be greater than zero. For example, the common 16:9 aspect ratio * is specified as 16 and 9, respectively. * * If the numerator and denominator is set to `GLFW_DONT_CARE` then the aspect * ratio limit is disabled. * * The aspect ratio is applied immediately to a windowed mode window and may * cause it to be resized. * * @param[in] window The window to set limits for. * @param[in] numer The numerator of the desired aspect ratio, or * `GLFW_DONT_CARE`. * @param[in] denom The denominator of the desired aspect ratio, or * `GLFW_DONT_CARE`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_VALUE and @ref GLFW_PLATFORM_ERROR. * * @remark If you set size limits and an aspect ratio that conflict, the * results are undefined. * * @remark @wayland The aspect ratio will not be applied until the window is * actually resized, either by the user or by the compositor. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_sizelimits * @sa @ref glfwSetWindowSizeLimits * * @since Added in version 3.2. * * @ingroup window */ GLFWAPI void glfwSetWindowSizeIncrements(GLFWwindow* window, int widthincr, int heightincr); /*! @brief Sets the size increments of the specified window. * * This function sets the size increments of the content area of the specified * window. If the window is full screen, the size limits only take effect * once it is made windowed. If the window is not resizable, this function * does nothing. * * The size increments are applied immediately to a windowed mode window and * may cause it to be resized. * * The dimension increments must be greater than zero. * * @param[in] window The window to set limits for. * @param[in] widthincr The width increments, in screen coordinates, of the * content area, or `GLFW_DONT_CARE`. * @param[in] heightincr The height increments, in screen coordinates, of the * content area, or `GLFW_DONT_CARE`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_VALUE and @ref GLFW_PLATFORM_ERROR. * * @remark If you set size limits and an aspect ratio that conflict, the * results are undefined. * * @remark @wayland The size limits will not be applied until the window is * actually resized, either by the user or by the compositor. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_sizelimits * @sa @ref glfwSetWindowSizeLimits * * @since Added in version 3.2. * * @ingroup window */ GLFWAPI void glfwSetWindowAspectRatio(GLFWwindow* window, int numer, int denom); /*! @brief Sets the size of the content area of the specified window. * * This function sets the size, in screen coordinates, of the content area of * the specified window. * * For full screen windows, this function updates the resolution of its desired * video mode and switches to the video mode closest to it, without affecting * the window's context. As the context is unaffected, the bit depths of the * framebuffer remain unchanged. * * If you wish to update the refresh rate of the desired video mode in addition * to its resolution, see @ref glfwSetWindowMonitor. * * The window manager may put limits on what sizes are allowed. GLFW cannot * and should not override these limits. * * @param[in] window The window to resize. * @param[in] width The desired width, in screen coordinates, of the window * content area. * @param[in] height The desired height, in screen coordinates, of the window * content area. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @remark @wayland A full screen window will not attempt to change the mode, * no matter what the requested size. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_size * @sa @ref glfwGetWindowSize * @sa @ref glfwSetWindowMonitor * * @since Added in version 1.0. * @glfw3 Added window handle parameter. * * @ingroup window */ GLFWAPI void glfwSetWindowSize(GLFWwindow* window, int width, int height); /*! @brief Retrieves the size of the framebuffer of the specified window. * * This function retrieves the size, in pixels, of the framebuffer of the * specified window. If you wish to retrieve the size of the window in screen * coordinates, see @ref glfwGetWindowSize. * * Any or all of the size arguments may be `NULL`. If an error occurs, all * non-`NULL` size arguments will be set to zero. * * @param[in] window The window whose framebuffer to query. * @param[out] width Where to store the width, in pixels, of the framebuffer, * or `NULL`. * @param[out] height Where to store the height, in pixels, of the framebuffer, * or `NULL`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_fbsize * @sa @ref glfwSetFramebufferSizeCallback * * @since Added in version 3.0. * * @ingroup window */ GLFWAPI void glfwGetFramebufferSize(GLFWwindow* window, int* width, int* height); /*! @brief Retrieves the size of the frame of the window. * * This function retrieves the size, in screen coordinates, of each edge of the * frame of the specified window. This size includes the title bar, if the * window has one. The size of the frame may vary depending on the * [window-related hints](@ref window_hints_wnd) used to create it. * * Because this function retrieves the size of each window frame edge and not * the offset along a particular coordinate axis, the retrieved values will * always be zero or positive. * * Any or all of the size arguments may be `NULL`. If an error occurs, all * non-`NULL` size arguments will be set to zero. * * @param[in] window The window whose frame size to query. * @param[out] left Where to store the size, in screen coordinates, of the left * edge of the window frame, or `NULL`. * @param[out] top Where to store the size, in screen coordinates, of the top * edge of the window frame, or `NULL`. * @param[out] right Where to store the size, in screen coordinates, of the * right edge of the window frame, or `NULL`. * @param[out] bottom Where to store the size, in screen coordinates, of the * bottom edge of the window frame, or `NULL`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_size * * @since Added in version 3.1. * * @ingroup window */ GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* window, int* left, int* top, int* right, int* bottom); /*! @brief Retrieves the content scale for the specified window. * * This function retrieves the content scale for the specified window. The * content scale is the ratio between the current DPI and the platform's * default DPI. This is especially important for text and any UI elements. If * the pixel dimensions of your UI scaled by this look appropriate on your * machine then it should appear at a reasonable size on other machines * regardless of their DPI and scaling settings. This relies on the system DPI * and scaling settings being somewhat correct. * * On systems where each monitors can have its own content scale, the window * content scale will depend on which monitor the system considers the window * to be on. * * @param[in] window The window to query. * @param[out] xscale Where to store the x-axis content scale, or `NULL`. * @param[out] yscale Where to store the y-axis content scale, or `NULL`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_scale * @sa @ref glfwSetWindowContentScaleCallback * @sa @ref glfwGetMonitorContentScale * * @since Added in version 3.3. * * @ingroup window */ GLFWAPI void glfwGetWindowContentScale(GLFWwindow* window, float* xscale, float* yscale); /*! @brief Returns the double click time interval. * * This function returns the maximum time between clicks to count as a * double click. * * The double click interval is a positive finite number greater than zero, * where zero means that no click is ever recognized as a double click. If the * system does not support a double click interval, this function always returns one half. * * @return The double click interval. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref double_click * @sa @ref click_interval * @sa @ref double_click_interval * * @since Added in version 3.3. * * @ingroup window */ GLFWAPI monotonic_t glfwGetDoubleClickInterval(GLFWwindow* window); /*! @brief Returns the opacity of the whole window. * * This function returns the opacity of the window, including any decorations. * * The opacity (or alpha) value is a positive finite number between zero and * one, where zero is fully transparent and one is fully opaque. If the system * does not support whole window transparency, this function always returns one. * * The initial opacity value for newly created windows is one. * * @param[in] window The window to query. * @return The opacity value of the specified window. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_transparency * @sa @ref glfwSetWindowOpacity * * @since Added in version 3.3. * * @ingroup window */ GLFWAPI float glfwGetWindowOpacity(GLFWwindow* window); /*! @brief Sets the opacity of the whole window. * * This function sets the opacity of the window, including any decorations. * * The opacity (or alpha) value is a positive finite number between zero and * one, where zero is fully transparent and one is fully opaque. * * The initial opacity value for newly created windows is one. * * A window created with framebuffer transparency may not use whole window * transparency. The results of doing this are undefined. * * @param[in] window The window to set the opacity for. * @param[in] opacity The desired opacity of the specified window. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks). * * @remark @wayland There is no way to set an opacity factor for a window. * This function will emit @ref GLFW_FEATURE_UNAVAILABLE. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_transparency * @sa @ref glfwGetWindowOpacity * * @since Added in version 3.3. * * @ingroup window */ GLFWAPI void glfwSetWindowOpacity(GLFWwindow* window, float opacity); /*! @brief Iconifies the specified window. * * This function iconifies (minimizes) the specified window if it was * previously restored. If the window is already iconified, this function does * nothing. * * If the specified window is a full screen window, the original monitor * resolution is restored until the window is restored. * * @param[in] window The window to iconify. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @remark @wayland Once a window is iconified, @ref glfwRestoreWindow won’t * be able to restore it. This is a design decision of the xdg-shell * protocol. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_iconify * @sa @ref glfwRestoreWindow * @sa @ref glfwMaximizeWindow * * @since Added in version 2.1. * @glfw3 Added window handle parameter. * * @ingroup window */ GLFWAPI void glfwIconifyWindow(GLFWwindow* window); /*! @brief Restores the specified window. * * This function restores the specified window if it was previously iconified * (minimized) or maximized. If the window is already restored, this function * does nothing. * * If the specified window is a full screen window, the resolution chosen for * the window is restored on the selected monitor. * * @param[in] window The window to restore. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_iconify * @sa @ref glfwIconifyWindow * @sa @ref glfwMaximizeWindow * * @since Added in version 2.1. * @glfw3 Added window handle parameter. * * @ingroup window */ GLFWAPI void glfwRestoreWindow(GLFWwindow* window); /*! @brief Maximizes the specified window. * * This function maximizes the specified window if it was previously not * maximized. If the window is already maximized, this function does nothing. * * If the specified window is a full screen window, this function does nothing. * * @param[in] window The window to maximize. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @par Thread Safety * This function may only be called from the main thread. * * @sa @ref window_iconify * @sa @ref glfwIconifyWindow * @sa @ref glfwRestoreWindow * * @since Added in GLFW 3.2. * * @ingroup window */ GLFWAPI void glfwMaximizeWindow(GLFWwindow* window); /*! @brief Makes the specified window visible. * * This function makes the specified window visible if it was previously * hidden. If the window is already visible or is in full screen mode, this * function does nothing. * * By default, windowed mode windows are focused when shown * Set the [GLFW_FOCUS_ON_SHOW](@ref GLFW_FOCUS_ON_SHOW_hint) window hint * to change this behavior for all newly created windows, or change the * behavior for an existing window with @ref glfwSetWindowAttrib. * * @param[in] window The window to make visible. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_hide * @sa @ref glfwHideWindow * * @since Added in version 3.0. * * @ingroup window */ GLFWAPI void glfwShowWindow(GLFWwindow* window); /*! @brief Hides the specified window. * * This function hides the specified window if it was previously visible. If * the window is already hidden or is in full screen mode, this function does * nothing. * * @param[in] window The window to hide. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_hide * @sa @ref glfwShowWindow * * @since Added in version 3.0. * * @ingroup window */ GLFWAPI void glfwHideWindow(GLFWwindow* window); /*! @brief Brings the specified window to front and sets input focus. * * This function brings the specified window to front and sets input focus. * The window should already be visible and not iconified. * * By default, both windowed and full screen mode windows are focused when * initially created. Set the [GLFW_FOCUSED](@ref GLFW_FOCUSED_hint) to * disable this behavior. * * Also by default, windowed mode windows are focused when shown * with @ref glfwShowWindow. Set the * [GLFW_FOCUS_ON_SHOW](@ref GLFW_FOCUS_ON_SHOW_hint) to disable this behavior. * * __Do not use this function__ to steal focus from other applications unless * you are certain that is what the user wants. Focus stealing can be * extremely disruptive. * * For a less disruptive way of getting the user's attention, see * [attention requests](@ref window_attention). * * @param[in] window The window to give input focus. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks). * * @remark @wayland It is not possible for an application to set the input * focus. This function will emit @ref GLFW_FEATURE_UNAVAILABLE. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_focus * @sa @ref window_attention * * @since Added in version 3.2. * * @ingroup window */ GLFWAPI void glfwFocusWindow(GLFWwindow* window); /*! @brief Requests user attention to the specified window. * * This function requests user attention to the specified window. On * platforms where this is not supported, attention is requested to the * application as a whole. * * Once the user has given attention, usually by focusing the window or * application, the system will end the request automatically. * * @param[in] window The window to request attention to. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @remark @macos Attention is requested to the application as a whole, not the * specific window. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_attention * * @since Added in version 3.3. * * @ingroup window */ GLFWAPI void glfwRequestWindowAttention(GLFWwindow* window); /*! @brief Sounds an audible bell associated with the window * * This function sounds an audible bell, on platforms where it is * supported. Currently (macOS, Windows, X11 and Wayland). * * @param[in] window The window with which the bell is associated. * @return true if the bell succeeded otherwise false * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @remark @macos Bell is associated to the application as a whole, not the * specific window. * * @thread_safety This function must only be called from the main thread. * * @since Added in version 3.3. * * @ingroup window */ GLFWAPI int glfwWindowBell(GLFWwindow* window); /*! @brief Returns the monitor that the window uses for full screen mode. * * This function returns the handle of the monitor that the specified window is * in full screen on. * * @param[in] window The window to query. * @return The monitor, or `NULL` if the window is in windowed mode or an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_monitor * @sa @ref glfwSetWindowMonitor * * @since Added in version 3.0. * * @ingroup window */ GLFWAPI GLFWmonitor* glfwGetWindowMonitor(GLFWwindow* window); /*! @brief Sets the mode, monitor, video mode and placement of a window. * * This function sets the monitor that the window uses for full screen mode or, * if the monitor is `NULL`, makes it windowed mode. * * When setting a monitor, this function updates the width, height and refresh * rate of the desired video mode and switches to the video mode closest to it. * The window position is ignored when setting a monitor. * * When the monitor is `NULL`, the position, width and height are used to * place the window content area. The refresh rate is ignored when no monitor * is specified. * * If you only wish to update the resolution of a full screen window or the * size of a windowed mode window, see @ref glfwSetWindowSize. * * When a window transitions from full screen to windowed mode, this function * restores any previous window settings such as whether it is decorated, * floating, resizable, has size or aspect ratio limits, etc. * * @param[in] window The window whose monitor, size or video mode to set. * @param[in] monitor The desired monitor, or `NULL` to set windowed mode. * @param[in] xpos The desired x-coordinate of the upper-left corner of the * content area. * @param[in] ypos The desired y-coordinate of the upper-left corner of the * content area. * @param[in] width The desired with, in screen coordinates, of the content * area or video mode. * @param[in] height The desired height, in screen coordinates, of the content * area or video mode. * @param[in] refreshRate The desired refresh rate, in Hz, of the video mode, * or `GLFW_DONT_CARE`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @remark The OpenGL or OpenGL ES context will not be destroyed or otherwise * affected by any resizing or mode switching, although you may need to update * your viewport if the framebuffer size has changed. * * @remark @wayland The desired window position is ignored, as there is no way * for an application to set this property. * * @remark @wayland Setting the window to full screen will not attempt to * change the mode, no matter what the requested size or refresh rate. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_monitor * @sa @ref window_full_screen * @sa @ref glfwGetWindowMonitor * @sa @ref glfwSetWindowSize * * @since Added in version 3.2. * * @ingroup window */ GLFWAPI void glfwSetWindowMonitor(GLFWwindow* window, GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate); /*! @brief Returns an attribute of the specified window. * * This function returns the value of an attribute of the specified window or * its OpenGL or OpenGL ES context. * * @param[in] window The window to query. * @param[in] attrib The [window attribute](@ref window_attribs) whose value to * return. * @return The value of the attribute, or zero if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR. * * @remark Framebuffer related hints are not window attributes. See @ref * window_attribs_fb for more information. * * @remark Zero is a valid value for many window and context related * attributes so you cannot use a return value of zero as an indication of * errors. However, this function should not fail as long as it is passed * valid arguments and the library has been [initialized](@ref intro_init). * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_attribs * @sa @ref glfwSetWindowAttrib * * @since Added in version 3.0. Replaces `glfwGetWindowParam` and * `glfwGetGLVersion`. * * @ingroup window */ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* window, int attrib); /*! @brief Sets an attribute of the specified window. * * This function sets the value of an attribute of the specified window. * * The supported attributes are [GLFW_DECORATED](@ref GLFW_DECORATED_attrib), * [GLFW_RESIZABLE](@ref GLFW_RESIZABLE_attrib), * [GLFW_FLOATING](@ref GLFW_FLOATING_attrib), * [GLFW_AUTO_ICONIFY](@ref GLFW_AUTO_ICONIFY_attrib) and * [GLFW_FOCUS_ON_SHOW](@ref GLFW_FOCUS_ON_SHOW_attrib). * [GLFW_MOUSE_PASSTHROUGH](@ref GLFW_MOUSE_PASSTHROUGH_attrib) * * Some of these attributes are ignored for full screen windows. The new * value will take effect if the window is later made windowed. * * Some of these attributes are ignored for windowed mode windows. The new * value will take effect if the window is later made full screen. * * @param[in] window The window to set the attribute for. * @param[in] attrib A supported window attribute. * @param[in] value `true` or `false`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_ENUM, @ref GLFW_INVALID_VALUE and @ref GLFW_PLATFORM_ERROR. * * @remark Calling @ref glfwGetWindowAttrib will always return the latest * value, even if that value is ignored by the current mode of the window. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_attribs * @sa @ref glfwGetWindowAttrib * * @since Added in version 3.3. * * @ingroup window */ GLFWAPI void glfwSetWindowAttrib(GLFWwindow* window, int attrib, int value); GLFWAPI int glfwSetWindowBlur(GLFWwindow* window, int value); /*! @brief Sets the user pointer of the specified window. * * This function sets the user-defined pointer of the specified window. The * current value is retained until the window is destroyed. The initial value * is `NULL`. * * @param[in] window The window whose pointer to set. * @param[in] pointer The new value. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function may be called from any thread. Access is not * synchronized. * * @sa @ref window_userptr * @sa @ref glfwGetWindowUserPointer * * @since Added in version 3.0. * * @ingroup window */ GLFWAPI void glfwSetWindowUserPointer(GLFWwindow* window, void* pointer); /*! @brief Returns the user pointer of the specified window. * * This function returns the current value of the user-defined pointer of the * specified window. The initial value is `NULL`. * * @param[in] window The window whose pointer to return. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function may be called from any thread. Access is not * synchronized. * * @sa @ref window_userptr * @sa @ref glfwSetWindowUserPointer * * @since Added in version 3.0. * * @ingroup window */ GLFWAPI void* glfwGetWindowUserPointer(GLFWwindow* window); /*! @brief Sets the position callback for the specified window. * * This function sets the position callback of the specified window, which is * called when the window is moved. The callback is provided with the * position, in screen coordinates, of the upper-left corner of the content * area of the window. * * @param[in] window The window whose callback to set. * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window, int xpos, int ypos) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWwindowposfun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @remark @wayland This callback will never be called, as there is no way for * an application to know its global position. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_pos * * @since Added in version 3.0. * * @ingroup window */ GLFWAPI GLFWwindowposfun glfwSetWindowPosCallback(GLFWwindow* window, GLFWwindowposfun callback); /*! @brief Sets the size callback for the specified window. * * This function sets the size callback of the specified window, which is * called when the window is resized. The callback is provided with the size, * in screen coordinates, of the content area of the window. * * @param[in] window The window whose callback to set. * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window, int width, int height) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWwindowsizefun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_size * * @since Added in version 1.0. * @glfw3 Added window handle parameter and return value. * * @ingroup window */ GLFWAPI GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow* window, GLFWwindowsizefun callback); /*! @brief Sets the close callback for the specified window. * * This function sets the close callback of the specified window, which is * called when the user attempts to close the window, for example by clicking * the close widget in the title bar. * * The close flag is set before this callback is called, but you can modify it * at any time with @ref glfwSetWindowShouldClose. * * The close callback is not triggered by @ref glfwDestroyWindow. * * @param[in] window The window whose callback to set. * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWwindowclosefun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @remark @macos Selecting Quit from the application menu will trigger the * close callback for all windows. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_close * * @since Added in version 2.5. * @glfw3 Added window handle parameter and return value. * * @ingroup window */ GLFWAPI GLFWwindowclosefun glfwSetWindowCloseCallback(GLFWwindow* window, GLFWwindowclosefun callback); GLFWAPI GLFWapplicationclosefun glfwSetApplicationCloseCallback(GLFWapplicationclosefun callback); GLFWAPI GLFWsystemcolorthemechangefun glfwSetSystemColorThemeChangeCallback(GLFWsystemcolorthemechangefun callback); GLFWAPI GLFWclipboardlostfun glfwSetClipboardLostCallback(GLFWclipboardlostfun callback); GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized); /*! @brief Sets the refresh callback for the specified window. * * This function sets the refresh callback of the specified window, which is * called when the content area of the window needs to be redrawn, for example * if the window has been exposed after having been covered by another window. * * On compositing window systems such as Aero, Compiz, Aqua or Wayland, where * the window contents are saved off-screen, this callback may be called only * very infrequently or never at all. * * @param[in] window The window whose callback to set. * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window); * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWwindowrefreshfun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_refresh * * @since Added in version 2.5. * @glfw3 Added window handle parameter and return value. * * @ingroup window */ GLFWAPI GLFWwindowrefreshfun glfwSetWindowRefreshCallback(GLFWwindow* window, GLFWwindowrefreshfun callback); /*! @brief Sets the focus callback for the specified window. * * This function sets the focus callback of the specified window, which is * called when the window gains or loses input focus. * * After the focus callback is called for a window that lost input focus, * synthetic key and mouse button release events will be generated for all such * that had been pressed. For more information, see @ref glfwSetKeyCallback * and @ref glfwSetMouseButtonCallback. * * @param[in] window The window whose callback to set. * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window, int focused) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWwindowfocusfun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_focus * * @since Added in version 3.0. * * @ingroup window */ GLFWAPI GLFWwindowfocusfun glfwSetWindowFocusCallback(GLFWwindow* window, GLFWwindowfocusfun callback); /*! @brief Sets the occlusion callback for the specified window. * * This function sets the occlusion callback of the specified window, which is * called when the window becomes (fully) occluded by other windows or when (a * part of) the window becomes visible again because an overlapping window is * moved away. * * @param[in] window The window whose callback to set. * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window, int iconified) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWwindowiconifyfun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_occlusion * * @since Added in version 3.3. * * @ingroup window */ GLFWAPI GLFWwindowocclusionfun glfwSetWindowOcclusionCallback(GLFWwindow* window, GLFWwindowocclusionfun callback); /*! @brief Sets the iconify callback for the specified window. * * This function sets the iconification callback of the specified window, which * is called when the window is iconified or restored. * * @param[in] window The window whose callback to set. * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window, int iconified) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWwindowiconifyfun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_iconify * * @since Added in version 3.0. * * @ingroup window */ GLFWAPI GLFWwindowiconifyfun glfwSetWindowIconifyCallback(GLFWwindow* window, GLFWwindowiconifyfun callback); /*! @brief Sets the maximize callback for the specified window. * * This function sets the maximization callback of the specified window, which * is called when the window is maximized or restored. * * @param[in] window The window whose callback to set. * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window, int maximized) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWwindowmaximizefun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_maximize * * @since Added in version 3.3. * * @ingroup window */ GLFWAPI GLFWwindowmaximizefun glfwSetWindowMaximizeCallback(GLFWwindow* window, GLFWwindowmaximizefun callback); /*! @brief Sets the framebuffer resize callback for the specified window. * * This function sets the framebuffer resize callback of the specified window, * which is called when the framebuffer of the specified window is resized. * * @param[in] window The window whose callback to set. * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window, int width, int height) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWframebuffersizefun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_fbsize * * @since Added in version 3.0. * * @ingroup window */ GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window, GLFWframebuffersizefun callback); /*! @brief Sets the window content scale callback for the specified window. * * This function sets the window content scale callback of the specified window, * which is called when the content scale of the specified window changes. * * @param[in] window The window whose callback to set. * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window, float xscale, float yscale) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWwindowcontentscalefun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_scale * @sa @ref glfwGetWindowContentScale * * @since Added in version 3.3. * * @ingroup window */ GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow* window, GLFWwindowcontentscalefun callback); /*! @brief Posts an empty event to the event queue. * * This function posts an empty event from the current thread to the event * queue, causing @ref glfwWaitEvents or @ref glfwWaitEventsTimeout to return. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function may be called from any thread. * * @sa @ref events * @sa @ref glfwWaitEvents * @sa @ref glfwWaitEventsTimeout * * @since Added in version 3.1. * * @ingroup window */ GLFWAPI void glfwPostEmptyEvent(void); GLFWAPI bool glfwGetIgnoreOSKeyboardProcessing(void); GLFWAPI void glfwSetIgnoreOSKeyboardProcessing(bool enabled); /*! @brief Returns the value of an input option for the specified window. * * This function returns the value of an input option for the specified window. * The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS, * @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS or * @ref GLFW_RAW_MOUSE_MOTION. * * @param[in] window The window to query. * @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`, * `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or * `GLFW_RAW_MOUSE_MOTION`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_INVALID_ENUM. * * @thread_safety This function must only be called from the main thread. * * @sa @ref glfwSetInputMode * * @since Added in version 3.0. * * @ingroup input */ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode); /*! @brief Sets an input option for the specified window. * * This function sets an input mode option for the specified window. The mode * must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS, * @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS or * @ref GLFW_RAW_MOUSE_MOTION. * * If the mode is `GLFW_CURSOR`, the value must be one of the following cursor * modes: * - `GLFW_CURSOR_NORMAL` makes the cursor visible and behaving normally. * - `GLFW_CURSOR_HIDDEN` makes the cursor invisible when it is over the * content area of the window but does not restrict the cursor from leaving. * - `GLFW_CURSOR_DISABLED` hides and grabs the cursor, providing virtual * and unlimited cursor movement. This is useful for implementing for * example 3D camera controls. * * If the mode is `GLFW_STICKY_KEYS`, the value must be either `true` to * enable sticky keys, or `false` to disable it. If sticky keys are * enabled, a key press will ensure that @ref glfwGetKey returns `GLFW_PRESS` * the next time it is called even if the key had been released before the * call. This is useful when you are only interested in whether keys have been * pressed but not when or in which order. * * If the mode is `GLFW_STICKY_MOUSE_BUTTONS`, the value must be either * `true` to enable sticky mouse buttons, or `false` to disable it. * If sticky mouse buttons are enabled, a mouse button press will ensure that * @ref glfwGetMouseButton returns `GLFW_PRESS` the next time it is called even * if the mouse button had been released before the call. This is useful when * you are only interested in whether mouse buttons have been pressed but not * when or in which order. * * If the mode is `GLFW_LOCK_KEY_MODS`, the value must be either `true` to * enable lock key modifier bits, or `false` to disable them. If enabled, * callbacks that receive modifier bits will also have the @ref * GLFW_MOD_CAPS_LOCK bit set when the event was generated with Caps Lock on, * and the @ref GLFW_MOD_NUM_LOCK bit when Num Lock was on. * * If the mode is `GLFW_RAW_MOUSE_MOTION`, the value must be either `true` * to enable raw (unscaled and unaccelerated) mouse motion when the cursor is * disabled, or `false` to disable it. If raw motion is not supported, * attempting to set this will emit @ref GLFW_FEATURE_UNAVAILABLE. Call @ref * glfwRawMouseMotionSupported to check for support. * * @param[in] window The window whose input mode to set. * @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`, * `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or * `GLFW_RAW_MOUSE_MOTION`. * @param[in] value The new value of the specified input mode. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_ENUM, @ref GLFW_PLATFORM_ERROR and @ref * GLFW_FEATURE_UNAVAILABLE (see above). * * @thread_safety This function must only be called from the main thread. * * @sa @ref glfwGetInputMode * * @since Added in version 3.0. Replaces `glfwEnable` and `glfwDisable`. * * @ingroup input */ GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value); /*! @brief Returns whether raw mouse motion is supported. * * This function returns whether raw mouse motion is supported on the current * system. This status does not change after GLFW has been initialized so you * only need to check this once. If you attempt to enable raw motion on * a system that does not support it, @ref GLFW_PLATFORM_ERROR will be emitted. * * Raw mouse motion is closer to the actual motion of the mouse across * a surface. It is not affected by the scaling and acceleration applied to * the motion of the desktop cursor. That processing is suitable for a cursor * while raw motion is better for controlling for example a 3D camera. Because * of this, raw mouse motion is only provided when the cursor is disabled. * * @return `true` if raw mouse motion is supported on the current machine, * or `false` otherwise. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref raw_mouse_motion * @sa @ref glfwSetInputMode * * @since Added in version 3.3. * * @ingroup input */ GLFWAPI int glfwRawMouseMotionSupported(void); /*! @brief Returns the layout-specific name of the specified printable key. * * This function returns the name of the specified printable key, encoded as * UTF-8. This is typically the character that key would produce without any * modifier keys, intended for displaying key bindings to the user. For dead * keys, it is typically the diacritic it would add to a character. * * __Do not use this function__ for [text input](@ref input_char). You will * break text input for many languages even if it happens to work for yours. * * If the key is `0`, the keycode is used to identify the key, * otherwise the keycode is ignored. If you specify a non-printable key, or * `0` and a keycode that maps to a non-printable key, this * function returns `NULL` but does not emit an error. * * This behavior allows you to always pass in the arguments in the * [key callback](@ref input_key) without modification. * * Names for printable keys depend on keyboard layout, while names for * non-printable keys are the same across layouts but depend on the application * language and should be localized along with other user interface text. * * @param[in] key The key to query, or `0`. * @param[in] native_key The platform-specifc identifier of the key to query. * @return The UTF-8 encoded, layout-specific name of the key, or `NULL`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @remark The contents of the returned string may change when a keyboard * layout change event is received. * * @pointer_lifetime The returned string is allocated and freed by GLFW. You * should not free it yourself. It is valid until the library is terminated. * * @thread_safety This function must only be called from the main thread. * * @sa @ref input_key_name * * @since Added in version 3.2. * * @ingroup input */ GLFWAPI const char* glfwGetKeyName(uint32_t key, int native_key); /*! @brief Returns the platform-specific identifier of the specified key. * * This function returns the platform-specific identifier of the specified key. * * If the key is `0` or does not exist on the keyboard this * method will return `-1`. * * @param[in] key Any [named key](@ref keys). * @return The platform-specific identifier for the key, or `-1` if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR. * * @thread_safety This function may be called from any thread. * * @sa @ref input_key * * @since Added in version 3.3. * * @ingroup input */ GLFWAPI int glfwGetNativeKeyForKey(uint32_t key); /*! @brief Returns the last reported state of a keyboard key for the specified * window. * * This function returns the last state reported for the specified key to the * specified window. The returned state is one of `GLFW_PRESS` or * `GLFW_RELEASE`. The higher-level action `GLFW_REPEAT` is only reported to * the key callback. * * If the @ref GLFW_STICKY_KEYS input mode is enabled, this function returns * `GLFW_PRESS` the first time you call it for a key that was pressed, even if * that key has already been released. * * The key functions deal with physical keys, with [key tokens](@ref keys) * named after their use on the standard US keyboard layout. If you want to * input text, use the Unicode character callback instead. * * The [modifier key bit masks](@ref mods) are not key tokens and cannot be * used with this function. * * __Do not use this function__ to implement [text input](@ref input_char). * * @param[in] window The desired window. * @param[in] key The desired [keyboard key](@ref keys). `0` is * not a valid key for this function. * @return One of `GLFW_PRESS` or `GLFW_RELEASE`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_INVALID_ENUM. * * @thread_safety This function must only be called from the main thread. * * @sa @ref input_key * * @since Added in version 1.0. * @glfw3 Added window handle parameter. * * @ingroup input */ GLFWAPI GLFWKeyAction glfwGetKey(GLFWwindow* window, uint32_t key); /*! @brief Returns the last reported state of a mouse button for the specified * window. * * This function returns the last state reported for the specified mouse button * to the specified window. The returned state is one of `GLFW_PRESS` or * `GLFW_RELEASE`. * * If the @ref GLFW_STICKY_MOUSE_BUTTONS input mode is enabled, this function * returns `GLFW_PRESS` the first time you call it for a mouse button that was * pressed, even if that mouse button has already been released. * * @param[in] window The desired window. * @param[in] button The desired [mouse button](@ref buttons). * @return One of `GLFW_PRESS` or `GLFW_RELEASE`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_INVALID_ENUM. * * @thread_safety This function must only be called from the main thread. * * @sa @ref input_mouse_button * * @since Added in version 1.0. * @glfw3 Added window handle parameter. * * @ingroup input */ GLFWAPI int glfwGetMouseButton(GLFWwindow* window, int button); /*! @brief Retrieves the position of the cursor relative to the content area of * the window. * * This function returns the position of the cursor, in screen coordinates, * relative to the upper-left corner of the content area of the specified * window. * * If the cursor is disabled (with `GLFW_CURSOR_DISABLED`) then the cursor * position is unbounded and limited only by the minimum and maximum values of * a `double`. * * The coordinate can be converted to their integer equivalents with the * `floor` function. Casting directly to an integer type works for positive * coordinates, but fails for negative ones. * * Any or all of the position arguments may be `NULL`. If an error occurs, all * non-`NULL` position arguments will be set to zero. * * @param[in] window The desired window. * @param[out] xpos Where to store the cursor x-coordinate, relative to the * left edge of the content area, or `NULL`. * @param[out] ypos Where to store the cursor y-coordinate, relative to the to * top edge of the content area, or `NULL`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref cursor_pos * @sa @ref glfwSetCursorPos * * @since Added in version 3.0. Replaces `glfwGetMousePos`. * * @ingroup input */ GLFWAPI void glfwGetCursorPos(GLFWwindow* window, double* xpos, double* ypos); /*! @brief Sets the position of the cursor, relative to the content area of the * window. * * This function sets the position, in screen coordinates, of the cursor * relative to the upper-left corner of the content area of the specified * window. The window must have input focus. If the window does not have * input focus when this function is called, it fails silently. * * __Do not use this function__ to implement things like camera controls. GLFW * already provides the `GLFW_CURSOR_DISABLED` cursor mode that hides the * cursor, transparently re-centers it and provides unconstrained cursor * motion. See @ref glfwSetInputMode for more information. * * If the cursor mode is `GLFW_CURSOR_DISABLED` then the cursor position is * unconstrained and limited only by the minimum and maximum values of * a `double`. * * @param[in] window The desired window. * @param[in] xpos The desired x-coordinate, relative to the left edge of the * content area. * @param[in] ypos The desired y-coordinate, relative to the top edge of the * content area. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @remark @wayland This function will only work when the cursor mode is * `GLFW_CURSOR_DISABLED`, otherwise it will do nothing. * * @thread_safety This function must only be called from the main thread. * * @sa @ref cursor_pos * @sa @ref glfwGetCursorPos * * @since Added in version 3.0. Replaces `glfwSetMousePos`. * * @ingroup input */ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos); /*! @brief Creates a custom cursor. * * Creates a new custom cursor image that can be set for a window with @ref * glfwSetCursor. The cursor can be destroyed with @ref glfwDestroyCursor. * Any remaining cursors are destroyed by @ref glfwTerminate. * * The pixels are 32-bit, little-endian, non-premultiplied RGBA, i.e. eight * bits per channel with the red channel first. They are arranged canonically * as packed sequential rows, starting from the top-left corner. * * The cursor hotspot is specified in pixels, relative to the upper-left corner * of the cursor image. Like all other coordinate systems in GLFW, the X-axis * points to the right and the Y-axis points down. * * @param[in] image The desired cursor image. * @param[in] xhot The desired x-coordinate, in pixels, of the cursor hotspot. * @param[in] yhot The desired y-coordinate, in pixels, of the cursor hotspot. * @param[in] count The number of images. Used on Cocoa for retina cursors. The first image should be the 1:1 scale image. * @return The handle of the created cursor, or `NULL` if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @pointer_lifetime The specified image data is copied before this function * returns. * * @thread_safety This function must only be called from the main thread. * * @sa @ref cursor_object * @sa @ref glfwDestroyCursor * @sa @ref glfwCreateStandardCursor * * @since Added in version 3.1. Changed in 4.0 to add the count parameter. * * @ingroup input */ GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot, int count); /*! @brief Creates a cursor with a standard shape. * * Returns a cursor with a [standard shape](@ref shapes), that can be set for * a window with @ref glfwSetCursor. * * @param[in] shape One of the [standard shapes](@ref shapes). * @return A new cursor ready to use or `NULL` if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref cursor_object * @sa @ref glfwCreateCursor * * @since Added in version 3.1. * * @ingroup input */ GLFWAPI GLFWcursor* glfwCreateStandardCursor(GLFWCursorShape shape); /*! @brief Destroys a cursor. * * This function destroys a cursor previously created with @ref * glfwCreateCursor. Any remaining cursors will be destroyed by @ref * glfwTerminate. * * If the specified cursor is current for any window, that window will be * reverted to the default cursor. This does not affect the cursor mode. * * @param[in] cursor The cursor object to destroy. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @reentrancy This function must not be called from a callback. * * @thread_safety This function must only be called from the main thread. * * @sa @ref cursor_object * @sa @ref glfwCreateCursor * * @since Added in version 3.1. * * @ingroup input */ GLFWAPI void glfwDestroyCursor(GLFWcursor* cursor); /*! @brief Sets the cursor for the window. * * This function sets the cursor image to be used when the cursor is over the * content area of the specified window. The set cursor will only be visible * when the [cursor mode](@ref cursor_mode) of the window is * `GLFW_CURSOR_NORMAL`. * * On some platforms, the set cursor may not be visible unless the window also * has input focus. * * @param[in] window The window to set the cursor for. * @param[in] cursor The cursor to set, or `NULL` to switch back to the default * arrow cursor. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref cursor_object * * @since Added in version 3.1. * * @ingroup input */ GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor); /*! @brief Sets the callback for handling keyboard events. * * @ingroup input */ GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* window, GLFWkeyboardfun callback); /*! @brief Notifies the OS Input Method Event system of changes to application input state * * Used to notify the IME system of changes in state such as focus gained/lost * and text cursor position. * * @param ev: What data to notify. * * @ingroup input * @since Added in version 4.0 */ GLFWAPI void glfwUpdateIMEState(GLFWwindow* window, const GLFWIMEUpdateEvent *ev); /*! @brief Sets the mouse button callback. * * This function sets the mouse button callback of the specified window, which * is called when a mouse button is pressed or released. * * When a window loses input focus, it will generate synthetic mouse button * release events for all pressed mouse buttons. You can tell these events * from user-generated events by the fact that the synthetic ones are generated * after the focus loss event has been processed, i.e. after the * [window focus callback](@ref glfwSetWindowFocusCallback) has been called. * * @param[in] window The window whose callback to set. * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window, int button, int action, int mods) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWmousebuttonfun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref input_mouse_button * * @since Added in version 1.0. * @glfw3 Added window handle parameter and return value. * * @ingroup input */ GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* window, GLFWmousebuttonfun callback); /*! @brief Sets the cursor position callback. * * This function sets the cursor position callback of the specified window, * which is called when the cursor is moved. The callback is provided with the * position, in screen coordinates, relative to the upper-left corner of the * content area of the window. * * @param[in] window The window whose callback to set. * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window, double xpos, double ypos); * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWcursorposfun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref cursor_pos * * @since Added in version 3.0. Replaces `glfwSetMousePosCallback`. * * @ingroup input */ GLFWAPI GLFWcursorposfun glfwSetCursorPosCallback(GLFWwindow* window, GLFWcursorposfun callback); /*! @brief Sets the cursor enter/leave callback. * * This function sets the cursor boundary crossing callback of the specified * window, which is called when the cursor enters or leaves the content area of * the window. * * @param[in] window The window whose callback to set. * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window, int entered) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWcursorenterfun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref cursor_enter * * @since Added in version 3.0. * * @ingroup input */ GLFWAPI GLFWcursorenterfun glfwSetCursorEnterCallback(GLFWwindow* window, GLFWcursorenterfun callback); /*! @brief Sets the scroll callback. * * This function sets the scroll callback of the specified window, which is * called when a scrolling device is used, such as a mouse wheel or scrolling * area of a touchpad. * * The scroll callback receives all scrolling input, like that from a mouse * wheel or a touchpad scrolling area. * * @param[in] window The window whose callback to set. * @param[in] callback The new scroll callback, or `NULL` to remove the * currently set callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window, double xoffset, double yoffset) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWscrollfun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref scrolling * * @since Added in version 3.0. Replaces `glfwSetMouseWheelCallback`. * * @ingroup input */ GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* window, GLFWscrollfun callback); /*! @brief Sets the path drop callback. * * This function sets the path drop callback of the specified window, which is * called when one or more dragged paths are dropped on the window. * * Because the path array and its strings may have been generated specifically * for that event, they are not guaranteed to be valid after the callback has * returned. If you wish to use them after the callback returns, you need to * make a deep copy. * * @param[in] window The window whose callback to set. * @param[in] callback The new file drop callback, or `NULL` to remove the * currently set callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(GLFWwindow* window, int path_count, const char* paths[]) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWdropfun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @remark @wayland File drop is currently unimplemented. * * @thread_safety This function must only be called from the main thread. * * @sa @ref path_drop * * @since Added in version 3.1. * * @ingroup input */ GLFWAPI GLFWdropfun glfwSetDropCallback(GLFWwindow* window, GLFWdropfun callback); GLFWAPI GLFWliveresizefun glfwSetLiveResizeCallback(GLFWwindow* window, GLFWliveresizefun callback); /*! @brief Returns whether the specified joystick is present. * * This function returns whether the specified joystick is present. * * There is no need to call this function before other functions that accept * a joystick ID, as they all check for presence before performing any other * work. * * @param[in] jid The [joystick](@ref joysticks) to query. * @return `true` if the joystick is present, or `false` otherwise. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR. * * @thread_safety This function must only be called from the main thread. * * @sa @ref joystick * * @since Added in version 3.0. Replaces `glfwGetJoystickParam`. * * @ingroup input */ GLFWAPI int glfwJoystickPresent(int jid); /*! @brief Returns the values of all axes of the specified joystick. * * This function returns the values of all axes of the specified joystick. * Each element in the array is a value between -1.0 and 1.0. * * If the specified joystick is not present this function will return `NULL` * but will not generate an error. This can be used instead of first calling * @ref glfwJoystickPresent. * * @param[in] jid The [joystick](@ref joysticks) to query. * @param[out] count Where to store the number of axis values in the returned * array. This is set to zero if the joystick is not present or an error * occurred. * @return An array of axis values, or `NULL` if the joystick is not present or * an [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR. * * @pointer_lifetime The returned array is allocated and freed by GLFW. You * should not free it yourself. It is valid until the specified joystick is * disconnected or the library is terminated. * * @thread_safety This function must only be called from the main thread. * * @sa @ref joystick_axis * * @since Added in version 3.0. Replaces `glfwGetJoystickPos`. * * @ingroup input */ GLFWAPI const float* glfwGetJoystickAxes(int jid, int* count); /*! @brief Returns the state of all buttons of the specified joystick. * * This function returns the state of all buttons of the specified joystick. * Each element in the array is either `GLFW_PRESS` or `GLFW_RELEASE`. * * For backward compatibility with earlier versions that did not have @ref * glfwGetJoystickHats, the button array also includes all hats, each * represented as four buttons. The hats are in the same order as returned by * __glfwGetJoystickHats__ and are in the order _up_, _right_, _down_ and * _left_. To disable these extra buttons, set the @ref * GLFW_JOYSTICK_HAT_BUTTONS init hint before initialization. * * If the specified joystick is not present this function will return `NULL` * but will not generate an error. This can be used instead of first calling * @ref glfwJoystickPresent. * * @param[in] jid The [joystick](@ref joysticks) to query. * @param[out] count Where to store the number of button states in the returned * array. This is set to zero if the joystick is not present or an error * occurred. * @return An array of button states, or `NULL` if the joystick is not present * or an [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR. * * @pointer_lifetime The returned array is allocated and freed by GLFW. You * should not free it yourself. It is valid until the specified joystick is * disconnected or the library is terminated. * * @thread_safety This function must only be called from the main thread. * * @sa @ref joystick_button * * @since Added in version 2.2. * @glfw3 Changed to return a dynamic array. * * @ingroup input */ GLFWAPI const unsigned char* glfwGetJoystickButtons(int jid, int* count); /*! @brief Returns the state of all hats of the specified joystick. * * This function returns the state of all hats of the specified joystick. * Each element in the array is one of the following values: * * Name | Value * ---- | ----- * `GLFW_HAT_CENTERED` | 0 * `GLFW_HAT_UP` | 1 * `GLFW_HAT_RIGHT` | 2 * `GLFW_HAT_DOWN` | 4 * `GLFW_HAT_LEFT` | 8 * `GLFW_HAT_RIGHT_UP` | `GLFW_HAT_RIGHT` \| `GLFW_HAT_UP` * `GLFW_HAT_RIGHT_DOWN` | `GLFW_HAT_RIGHT` \| `GLFW_HAT_DOWN` * `GLFW_HAT_LEFT_UP` | `GLFW_HAT_LEFT` \| `GLFW_HAT_UP` * `GLFW_HAT_LEFT_DOWN` | `GLFW_HAT_LEFT` \| `GLFW_HAT_DOWN` * * The diagonal directions are bitwise combinations of the primary (up, right, * down and left) directions and you can test for these individually by ANDing * it with the corresponding direction. * * @code * if (hats[2] & GLFW_HAT_RIGHT) * { * // State of hat 2 could be right-up, right or right-down * } * @endcode * * If the specified joystick is not present this function will return `NULL` * but will not generate an error. This can be used instead of first calling * @ref glfwJoystickPresent. * * @param[in] jid The [joystick](@ref joysticks) to query. * @param[out] count Where to store the number of hat states in the returned * array. This is set to zero if the joystick is not present or an error * occurred. * @return An array of hat states, or `NULL` if the joystick is not present * or an [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR. * * @pointer_lifetime The returned array is allocated and freed by GLFW. You * should not free it yourself. It is valid until the specified joystick is * disconnected, this function is called again for that joystick or the library * is terminated. * * @thread_safety This function must only be called from the main thread. * * @sa @ref joystick_hat * * @since Added in version 3.3. * * @ingroup input */ GLFWAPI const unsigned char* glfwGetJoystickHats(int jid, int* count); /*! @brief Returns the name of the specified joystick. * * This function returns the name, encoded as UTF-8, of the specified joystick. * The returned string is allocated and freed by GLFW. You should not free it * yourself. * * If the specified joystick is not present this function will return `NULL` * but will not generate an error. This can be used instead of first calling * @ref glfwJoystickPresent. * * @param[in] jid The [joystick](@ref joysticks) to query. * @return The UTF-8 encoded name of the joystick, or `NULL` if the joystick * is not present or an [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR. * * @pointer_lifetime The returned string is allocated and freed by GLFW. You * should not free it yourself. It is valid until the specified joystick is * disconnected or the library is terminated. * * @thread_safety This function must only be called from the main thread. * * @sa @ref joystick_name * * @since Added in version 3.0. * * @ingroup input */ GLFWAPI const char* glfwGetJoystickName(int jid); /*! @brief Returns the SDL compatible GUID of the specified joystick. * * This function returns the SDL compatible GUID, as a UTF-8 encoded * hexadecimal string, of the specified joystick. The returned string is * allocated and freed by GLFW. You should not free it yourself. * * The GUID is what connects a joystick to a gamepad mapping. A connected * joystick will always have a GUID even if there is no gamepad mapping * assigned to it. * * If the specified joystick is not present this function will return `NULL` * but will not generate an error. This can be used instead of first calling * @ref glfwJoystickPresent. * * The GUID uses the format introduced in SDL 2.0.5. This GUID tries to * uniquely identify the make and model of a joystick but does not identify * a specific unit, e.g. all wired Xbox 360 controllers will have the same * GUID on that platform. The GUID for a unit may vary between platforms * depending on what hardware information the platform specific APIs provide. * * @param[in] jid The [joystick](@ref joysticks) to query. * @return The UTF-8 encoded GUID of the joystick, or `NULL` if the joystick * is not present or an [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR. * * @pointer_lifetime The returned string is allocated and freed by GLFW. You * should not free it yourself. It is valid until the specified joystick is * disconnected or the library is terminated. * * @thread_safety This function must only be called from the main thread. * * @sa @ref gamepad * * @since Added in version 3.3. * * @ingroup input */ GLFWAPI const char* glfwGetJoystickGUID(int jid); /*! @brief Sets the user pointer of the specified joystick. * * This function sets the user-defined pointer of the specified joystick. The * current value is retained until the joystick is disconnected. The initial * value is `NULL`. * * This function may be called from the joystick callback, even for a joystick * that is being disconnected. * * @param[in] jid The joystick whose pointer to set. * @param[in] pointer The new value. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function may be called from any thread. Access is not * synchronized. * * @sa @ref joystick_userptr * @sa @ref glfwGetJoystickUserPointer * * @since Added in version 3.3. * * @ingroup input */ GLFWAPI void glfwSetJoystickUserPointer(int jid, void* pointer); /*! @brief Returns the user pointer of the specified joystick. * * This function returns the current value of the user-defined pointer of the * specified joystick. The initial value is `NULL`. * * This function may be called from the joystick callback, even for a joystick * that is being disconnected. * * @param[in] jid The joystick whose pointer to return. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function may be called from any thread. Access is not * synchronized. * * @sa @ref joystick_userptr * @sa @ref glfwSetJoystickUserPointer * * @since Added in version 3.3. * * @ingroup input */ GLFWAPI void* glfwGetJoystickUserPointer(int jid); /*! @brief Returns whether the specified joystick has a gamepad mapping. * * This function returns whether the specified joystick is both present and has * a gamepad mapping. * * If the specified joystick is present but does not have a gamepad mapping * this function will return `false` but will not generate an error. Call * @ref glfwJoystickPresent to check if a joystick is present regardless of * whether it has a mapping. * * @param[in] jid The [joystick](@ref joysticks) to query. * @return `true` if a joystick is both present and has a gamepad mapping, * or `false` otherwise. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_INVALID_ENUM. * * @thread_safety This function must only be called from the main thread. * * @sa @ref gamepad * @sa @ref glfwGetGamepadState * * @since Added in version 3.3. * * @ingroup input */ GLFWAPI int glfwJoystickIsGamepad(int jid); /*! @brief Sets the joystick configuration callback. * * This function sets the joystick configuration callback, or removes the * currently set callback. This is called when a joystick is connected to or * disconnected from the system. * * For joystick connection and disconnection events to be delivered on all * platforms, you need to call one of the [event processing](@ref events) * functions. Joystick disconnection may also be detected and the callback * called by joystick functions. The function will then return whatever it * returns if the joystick is not present. * * @param[in] callback The new callback, or `NULL` to remove the currently set * callback. * @return The previously set callback, or `NULL` if no callback was set or the * library had not been [initialized](@ref intro_init). * * @callback_signature * @code * void function_name(int jid, int event) * @endcode * For more information about the callback parameters, see the * [function pointer type](@ref GLFWjoystickfun). * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function must only be called from the main thread. * * @sa @ref joystick_event * * @since Added in version 3.2. * * @ingroup input */ GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun callback); /*! @brief Adds the specified SDL_GameControllerDB gamepad mappings. * * This function parses the specified ASCII encoded string and updates the * internal list with any gamepad mappings it finds. This string may * contain either a single gamepad mapping or many mappings separated by * newlines. The parser supports the full format of the `gamecontrollerdb.txt` * source file including empty lines and comments. * * See @ref gamepad_mapping for a description of the format. * * If there is already a gamepad mapping for a given GUID in the internal list, * it will be replaced by the one passed to this function. If the library is * terminated and re-initialized the internal list will revert to the built-in * default. * * @param[in] string The string containing the gamepad mappings. * @return `true` if successful, or `false` if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_INVALID_VALUE. * * @thread_safety This function must only be called from the main thread. * * @sa @ref gamepad * @sa @ref glfwJoystickIsGamepad * @sa @ref glfwGetGamepadName * * @since Added in version 3.3. * * @ingroup input */ GLFWAPI int glfwUpdateGamepadMappings(const char* string); /*! @brief Returns the human-readable gamepad name for the specified joystick. * * This function returns the human-readable name of the gamepad from the * gamepad mapping assigned to the specified joystick. * * If the specified joystick is not present or does not have a gamepad mapping * this function will return `NULL` but will not generate an error. Call * @ref glfwJoystickPresent to check whether it is present regardless of * whether it has a mapping. * * @param[in] jid The [joystick](@ref joysticks) to query. * @return The UTF-8 encoded name of the gamepad, or `NULL` if the * joystick is not present, does not have a mapping or an * [error](@ref error_handling) occurred. * * @pointer_lifetime The returned string is allocated and freed by GLFW. You * should not free it yourself. It is valid until the specified joystick is * disconnected, the gamepad mappings are updated or the library is terminated. * * @thread_safety This function must only be called from the main thread. * * @sa @ref gamepad * @sa @ref glfwJoystickIsGamepad * * @since Added in version 3.3. * * @ingroup input */ GLFWAPI const char* glfwGetGamepadName(int jid); /*! @brief Retrieves the state of the specified joystick remapped as a gamepad. * * This function retrieves the state of the specified joystick remapped to * an Xbox-like gamepad. * * If the specified joystick is not present or does not have a gamepad mapping * this function will return `false` but will not generate an error. Call * @ref glfwJoystickPresent to check whether it is present regardless of * whether it has a mapping. * * The Guide button may not be available for input as it is often hooked by the * system or the Steam client. * * Not all devices have all the buttons or axes provided by @ref * GLFWgamepadstate. Unavailable buttons and axes will always report * `GLFW_RELEASE` and 0.0 respectively. * * @param[in] jid The [joystick](@ref joysticks) to query. * @param[out] state The gamepad input state of the joystick. * @return `true` if successful, or `false` if no joystick is * connected, it has no gamepad mapping or an [error](@ref error_handling) * occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_INVALID_ENUM. * * @thread_safety This function must only be called from the main thread. * * @sa @ref gamepad * @sa @ref glfwUpdateGamepadMappings * @sa @ref glfwJoystickIsGamepad * * @since Added in version 3.3. * * @ingroup input */ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state); GLFWAPI void glfwSetClipboardDataTypes(GLFWClipboardType clipboard_type, const char* const *mime_types, size_t num_mime_types, GLFWclipboarditerfun get_iter); GLFWAPI void glfwGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object); /*! @brief Returns the GLFW time. * * This function returns the current GLFW time, in seconds. Unless the time * has been set using @ref glfwSetTime it measures time elapsed since GLFW was * initialized. * * The resolution of the timer is system dependent, but is usually on the order * of a few micro- or nanoseconds. It uses the highest-resolution monotonic * time source on each supported platform. * * @return The current time, in seconds, or zero if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function may be called from any thread. Reading and * writing of the internal base time is not atomic, so it needs to be * externally synchronized with calls to @ref glfwSetTime. * * @sa @ref time * * @since Added in version 1.0. * * @ingroup input */ GLFWAPI monotonic_t glfwGetTime(void); /*! @brief Makes the context of the specified window current for the calling * thread. * * This function makes the OpenGL or OpenGL ES context of the specified window * current on the calling thread. A context must only be made current on * a single thread at a time and each thread can have only a single current * context at a time. * * When moving a context between threads, you must make it non-current on the * old thread before making it current on the new one. * * By default, making a context non-current implicitly forces a pipeline flush. * On machines that support `GL_KHR_context_flush_control`, you can control * whether a context performs this flush by setting the * [GLFW_CONTEXT_RELEASE_BEHAVIOR](@ref GLFW_CONTEXT_RELEASE_BEHAVIOR_hint) * hint. * * The specified window must have an OpenGL or OpenGL ES context. Specifying * a window without a context will generate a @ref GLFW_NO_WINDOW_CONTEXT * error. * * @param[in] window The window whose context to make current, or `NULL` to * detach the current context. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_NO_WINDOW_CONTEXT and @ref GLFW_PLATFORM_ERROR. * * @thread_safety This function may be called from any thread. * * @sa @ref context_current * @sa @ref glfwGetCurrentContext * * @since Added in version 3.0. * * @ingroup context */ GLFWAPI void glfwMakeContextCurrent(GLFWwindow* window); /*! @brief Returns the window whose context is current on the calling thread. * * This function returns the window whose OpenGL or OpenGL ES context is * current on the calling thread. * * @return The window whose context is current, or `NULL` if no window's * context is current. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function may be called from any thread. * * @sa @ref context_current * @sa @ref glfwMakeContextCurrent * * @since Added in version 3.0. * * @ingroup context */ GLFWAPI GLFWwindow* glfwGetCurrentContext(void); /*! @brief Swaps the front and back buffers of the specified window. * * This function swaps the front and back buffers of the specified window when * rendering with OpenGL or OpenGL ES. If the swap interval is greater than * zero, the GPU driver waits the specified number of screen updates before * swapping the buffers. * * The specified window must have an OpenGL or OpenGL ES context. Specifying * a window without a context will generate a @ref GLFW_NO_WINDOW_CONTEXT * error. * * This function does not apply to Vulkan. If you are rendering with Vulkan, * see `vkQueuePresentKHR` instead. * * @param[in] window The window whose buffers to swap. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_NO_WINDOW_CONTEXT and @ref GLFW_PLATFORM_ERROR. * * @remark __EGL:__ The context of the specified window must be current on the * calling thread. * * @thread_safety This function may be called from any thread. * * @sa @ref buffer_swap * @sa @ref glfwSwapInterval * * @since Added in version 1.0. * @glfw3 Added window handle parameter. * * @ingroup window */ GLFWAPI void glfwSwapBuffers(GLFWwindow* window); /*! @brief Sets the swap interval for the current context. * * This function sets the swap interval for the current OpenGL or OpenGL ES * context, i.e. the number of screen updates to wait from the time @ref * glfwSwapBuffers was called before swapping the buffers and returning. This * is sometimes called _vertical synchronization_, _vertical retrace * synchronization_ or just _vsync_. * * A context that supports either of the `WGL_EXT_swap_control_tear` and * `GLX_EXT_swap_control_tear` extensions also accepts _negative_ swap * intervals, which allows the driver to swap immediately even if a frame * arrives a little bit late. You can check for these extensions with @ref * glfwExtensionSupported. * * A context must be current on the calling thread. Calling this function * without a current context will cause a @ref GLFW_NO_CURRENT_CONTEXT error. * * This function does not apply to Vulkan. If you are rendering with Vulkan, * see the present mode of your swapchain instead. * * @param[in] interval The minimum number of screen updates to wait for * until the buffers are swapped by @ref glfwSwapBuffers. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_NO_CURRENT_CONTEXT and @ref GLFW_PLATFORM_ERROR. * * @remark This function is not called during context creation, leaving the * swap interval set to whatever is the default on that platform. This is done * because some swap interval extensions used by GLFW do not allow the swap * interval to be reset to zero once it has been set to a non-zero value. * * @remark Some GPU drivers do not honor the requested swap interval, either * because of a user setting that overrides the application's request or due to * bugs in the driver. * * @thread_safety This function may be called from any thread. * * @sa @ref buffer_swap * @sa @ref glfwSwapBuffers * * @since Added in version 1.0. * * @ingroup context */ GLFWAPI void glfwSwapInterval(int interval); /*! @brief Returns whether the specified extension is available. * * This function returns whether the specified * [API extension](@ref context_glext) is supported by the current OpenGL or * OpenGL ES context. It searches both for client API extension and context * creation API extensions. * * A context must be current on the calling thread. Calling this function * without a current context will cause a @ref GLFW_NO_CURRENT_CONTEXT error. * * As this functions retrieves and searches one or more extension strings each * call, it is recommended that you cache its results if it is going to be used * frequently. The extension strings will not change during the lifetime of * a context, so there is no danger in doing this. * * This function does not apply to Vulkan. If you are using Vulkan, see @ref * glfwGetRequiredInstanceExtensions, `vkEnumerateInstanceExtensionProperties` * and `vkEnumerateDeviceExtensionProperties` instead. * * @param[in] extension The ASCII encoded name of the extension. * @return `true` if the extension is available, or `false` * otherwise. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_NO_CURRENT_CONTEXT, @ref GLFW_INVALID_VALUE and @ref * GLFW_PLATFORM_ERROR. * * @thread_safety This function may be called from any thread. * * @sa @ref context_glext * @sa @ref glfwGetProcAddress * * @since Added in version 1.0. * * @ingroup context */ GLFWAPI int glfwExtensionSupported(const char* extension); /*! @brief Returns the address of the specified function for the current * context. * * This function returns the address of the specified OpenGL or OpenGL ES * [core or extension function](@ref context_glext), if it is supported * by the current context. * * A context must be current on the calling thread. Calling this function * without a current context will cause a @ref GLFW_NO_CURRENT_CONTEXT error. * * This function does not apply to Vulkan. If you are rendering with Vulkan, * see @ref glfwGetInstanceProcAddress, `vkGetInstanceProcAddr` and * `vkGetDeviceProcAddr` instead. * * @param[in] procname The ASCII encoded name of the function. * @return The address of the function, or `NULL` if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_NO_CURRENT_CONTEXT and @ref GLFW_PLATFORM_ERROR. * * @remark The address of a given function is not guaranteed to be the same * between contexts. * * @remark This function may return a non-`NULL` address despite the * associated version or extension not being available. Always check the * context version or extension string first. * * @pointer_lifetime The returned function pointer is valid until the context * is destroyed or the library is terminated. * * @thread_safety This function may be called from any thread. * * @sa @ref context_glext * @sa @ref glfwExtensionSupported * * @since Added in version 1.0. * * @ingroup context */ GLFWAPI GLFWglproc glfwGetProcAddress(const char* procname); /*! @brief Returns whether the Vulkan loader and an ICD have been found. * * This function returns whether the Vulkan loader and any minimally functional * ICD have been found. * * The availability of a Vulkan loader and even an ICD does not by itself * guarantee that surface creation or even instance creation is possible. * For example, on Fermi systems Nvidia will install an ICD that provides no * actual Vulkan support. Call @ref glfwGetRequiredInstanceExtensions to check * whether the extensions necessary for Vulkan surface creation are available * and @ref glfwGetPhysicalDevicePresentationSupport to check whether a queue * family of a physical device supports image presentation. * * @return `true` if Vulkan is minimally available, or `false` * otherwise. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * * @thread_safety This function may be called from any thread. * * @sa @ref vulkan_support * * @since Added in version 3.2. * * @ingroup vulkan */ GLFWAPI int glfwVulkanSupported(void); /*! @brief Returns the Vulkan instance extensions required by GLFW. * * This function returns an array of names of Vulkan instance extensions required * by GLFW for creating Vulkan surfaces for GLFW windows. If successful, the * list will always contain `VK_KHR_surface`, so if you don't require any * additional extensions you can pass this list directly to the * `VkInstanceCreateInfo` struct. * * If Vulkan is not available on the machine, this function returns `NULL` and * generates a @ref GLFW_API_UNAVAILABLE error. Call @ref glfwVulkanSupported * to check whether Vulkan is at least minimally available. * * If Vulkan is available but no set of extensions allowing window surface * creation was found, this function returns `NULL`. You may still use Vulkan * for off-screen rendering and compute work. * * @param[out] count Where to store the number of extensions in the returned * array. This is set to zero if an error occurred. * @return An array of ASCII encoded extension names, or `NULL` if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_API_UNAVAILABLE. * * @remark Additional extensions may be required by future versions of GLFW. * You should check if any extensions you wish to enable are already in the * returned array, as it is an error to specify an extension more than once in * the `VkInstanceCreateInfo` struct. * * @remark @macos This function currently supports either the * `VK_MVK_macos_surface` extension from MoltenVK or `VK_EXT_metal_surface` * extension. * * @pointer_lifetime The returned array is allocated and freed by GLFW. You * should not free it yourself. It is guaranteed to be valid only until the * library is terminated. * * @thread_safety This function may be called from any thread. * * @sa @ref vulkan_ext * @sa @ref glfwCreateWindowSurface * * @since Added in version 3.2. * * @ingroup vulkan */ GLFWAPI const char** glfwGetRequiredInstanceExtensions(uint32_t* count); #if defined(VK_VERSION_1_0) /*! @brief Returns the address of the specified Vulkan instance function. * * This function returns the address of the specified Vulkan core or extension * function for the specified instance. If instance is set to `NULL` it can * return any function exported from the Vulkan loader, including at least the * following functions: * * - `vkEnumerateInstanceExtensionProperties` * - `vkEnumerateInstanceLayerProperties` * - `vkCreateInstance` * - `vkGetInstanceProcAddr` * * If Vulkan is not available on the machine, this function returns `NULL` and * generates a @ref GLFW_API_UNAVAILABLE error. Call @ref glfwVulkanSupported * to check whether Vulkan is at least minimally available. * * This function is equivalent to calling `vkGetInstanceProcAddr` with * a platform-specific query of the Vulkan loader as a fallback. * * @param[in] instance The Vulkan instance to query, or `NULL` to retrieve * functions related to instance creation. * @param[in] procname The ASCII encoded name of the function. * @return The address of the function, or `NULL` if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_API_UNAVAILABLE. * * @pointer_lifetime The returned function pointer is valid until the library * is terminated. * * @thread_safety This function may be called from any thread. * * @sa @ref vulkan_proc * * @since Added in version 3.2. * * @ingroup vulkan */ GLFWAPI GLFWvkproc glfwGetInstanceProcAddress(VkInstance instance, const char* procname); /*! @brief Returns whether the specified queue family can present images. * * This function returns whether the specified queue family of the specified * physical device supports presentation to the platform GLFW was built for. * * If Vulkan or the required window surface creation instance extensions are * not available on the machine, or if the specified instance was not created * with the required extensions, this function returns `false` and * generates a @ref GLFW_API_UNAVAILABLE error. Call @ref glfwVulkanSupported * to check whether Vulkan is at least minimally available and @ref * glfwGetRequiredInstanceExtensions to check what instance extensions are * required. * * @param[in] instance The instance that the physical device belongs to. * @param[in] device The physical device that the queue family belongs to. * @param[in] queuefamily The index of the queue family to query. * @return `true` if the queue family supports presentation, or * `false` otherwise. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_API_UNAVAILABLE and @ref GLFW_PLATFORM_ERROR. * * @remark @macos This function currently always returns `true`, as the * `VK_MVK_macos_surface` extension does not provide * a `vkGetPhysicalDevice*PresentationSupport` type function. * * @thread_safety This function may be called from any thread. For * synchronization details of Vulkan objects, see the Vulkan specification. * * @sa @ref vulkan_present * * @since Added in version 3.2. * * @ingroup vulkan */ GLFWAPI int glfwGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhysicalDevice device, uint32_t queuefamily); /*! @brief Creates a Vulkan surface for the specified window. * * This function creates a Vulkan surface for the specified window. * * If the Vulkan loader or at least one minimally functional ICD were not found, * this function returns `VK_ERROR_INITIALIZATION_FAILED` and generates a @ref * GLFW_API_UNAVAILABLE error. Call @ref glfwVulkanSupported to check whether * Vulkan is at least minimally available. * * If the required window surface creation instance extensions are not * available or if the specified instance was not created with these extensions * enabled, this function returns `VK_ERROR_EXTENSION_NOT_PRESENT` and * generates a @ref GLFW_API_UNAVAILABLE error. Call @ref * glfwGetRequiredInstanceExtensions to check what instance extensions are * required. * * The window surface cannot be shared with another API so the window must * have been created with the [client api hint](@ref GLFW_CLIENT_API_attrib) * set to `GLFW_NO_API` otherwise it generates a @ref GLFW_INVALID_VALUE error * and returns `VK_ERROR_NATIVE_WINDOW_IN_USE_KHR`. * * The window surface must be destroyed before the specified Vulkan instance. * It is the responsibility of the caller to destroy the window surface. GLFW * does not destroy it for you. Call `vkDestroySurfaceKHR` to destroy the * surface. * * @param[in] instance The Vulkan instance to create the surface in. * @param[in] window The window to create the surface for. * @param[in] allocator The allocator to use, or `NULL` to use the default * allocator. * @param[out] surface Where to store the handle of the surface. This is set * to `VK_NULL_HANDLE` if an error occurred. * @return `VK_SUCCESS` if successful, or a Vulkan error code if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_API_UNAVAILABLE, @ref GLFW_PLATFORM_ERROR and @ref GLFW_INVALID_VALUE * * @remark If an error occurs before the creation call is made, GLFW returns * the Vulkan error code most appropriate for the error. Appropriate use of * @ref glfwVulkanSupported and @ref glfwGetRequiredInstanceExtensions should * eliminate almost all occurrences of these errors. * * @remark @macos This function currently only supports the * `VK_MVK_macos_surface` extension from MoltenVK. * * @remark @macos This function creates and sets a `CAMetalLayer` instance for * the window content view, which is required for MoltenVK to function. * * @thread_safety This function may be called from any thread. For * synchronization details of Vulkan objects, see the Vulkan specification. * * @sa @ref vulkan_surface * @sa @ref glfwGetRequiredInstanceExtensions * * @since Added in version 3.2. * * @ingroup vulkan */ GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance, GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface); #endif /*VK_VERSION_1_0*/ /************************************************************************* * Global definition cleanup *************************************************************************/ /* ------------------- BEGIN SYSTEM/COMPILER SPECIFIC -------------------- */ #ifdef GLFW_WINGDIAPI_DEFINED #undef WINGDIAPI #undef GLFW_WINGDIAPI_DEFINED #endif #ifdef GLFW_CALLBACK_DEFINED #undef CALLBACK #undef GLFW_CALLBACK_DEFINED #endif /* Some OpenGL related headers need GLAPIENTRY, but it is unconditionally * defined by some gl.h variants (OpenBSD) so define it after if needed. */ #ifndef GLAPIENTRY #define GLAPIENTRY APIENTRY #endif /* -------------------- END SYSTEM/COMPILER SPECIFIC --------------------- */ #ifdef __cplusplus } #endif #endif /* _glfw3_h_ */ kitty-0.41.1/glfw/glx_context.c0000664000175000017510000005515014773370543015756 0ustar nileshnilesh//======================================================================== // GLFW 3.4 GLX - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #include "internal.h" #include #include #include #ifndef GLXBadProfileARB #define GLXBadProfileARB 13 #endif // Returns the specified attribute of the specified GLXFBConfig // static int getGLXFBConfigAttrib(GLXFBConfig fbconfig, int attrib) { int value; glXGetFBConfigAttrib(_glfw.x11.display, fbconfig, attrib, &value); return value; } static GLXFBConfig* choose_fb_config(const _GLFWfbconfig* desired, bool trust_window_bit, int *nelements, bool use_best_color_depth) { int attrib_list[64]; int pos = 0; #define ATTR(x, y) { attrib_list[pos++] = x; attrib_list[pos++] = y; } ATTR(GLX_DOUBLEBUFFER, desired->doublebuffer ? True : False); if (desired->stereo > 0) ATTR(GLX_STEREO, desired->stereo ? True : False); if (desired->auxBuffers > 0) ATTR(GLX_AUX_BUFFERS, desired->auxBuffers); if (_glfw.glx.ARB_multisample && desired->samples > 0) ATTR(GLX_SAMPLES, desired->samples); if (desired->depthBits != GLFW_DONT_CARE) ATTR(GLX_DEPTH_SIZE, desired->depthBits); if (desired->stencilBits != GLFW_DONT_CARE) ATTR(GLX_STENCIL_SIZE, desired->stencilBits); if (use_best_color_depth) { // we just ask for the highest available R+G+B+A color depth. This hopefully // works with 10bit (r=10, g=10, b=19, a=2) visuals ATTR(GLX_RED_SIZE, 1); ATTR(GLX_GREEN_SIZE, 1); ATTR(GLX_BLUE_SIZE, 1); ATTR(GLX_ALPHA_SIZE, 1); } else { if (desired->redBits != GLFW_DONT_CARE) ATTR(GLX_RED_SIZE, desired->redBits); if (desired->greenBits != GLFW_DONT_CARE) ATTR(GLX_GREEN_SIZE, desired->greenBits); if (desired->blueBits != GLFW_DONT_CARE) ATTR(GLX_BLUE_SIZE, desired->blueBits); if (desired->alphaBits != GLFW_DONT_CARE) ATTR(GLX_ALPHA_SIZE, desired->alphaBits); } if (desired->accumRedBits != GLFW_DONT_CARE) ATTR(GLX_ACCUM_RED_SIZE, desired->accumRedBits); if (desired->accumGreenBits != GLFW_DONT_CARE) ATTR(GLX_ACCUM_GREEN_SIZE, desired->accumGreenBits); if (desired->accumBlueBits != GLFW_DONT_CARE) ATTR(GLX_ACCUM_BLUE_SIZE, desired->accumBlueBits); if (desired->accumAlphaBits != GLFW_DONT_CARE) ATTR(GLX_ACCUM_ALPHA_SIZE, desired->accumAlphaBits); if (!trust_window_bit) ATTR(GLX_DRAWABLE_TYPE, 0); ATTR(None, None); return glXChooseFBConfig(_glfw.x11.display, _glfw.x11.screen, attrib_list, nelements); #undef ATTR } // Return the GLXFBConfig most closely matching the specified hints // static bool chooseGLXFBConfig(const _GLFWfbconfig* desired, GLXFBConfig* result) { GLXFBConfig* nativeConfigs; int i, nativeCount, ans_idx = 0; const char* vendor; bool trustWindowBit = true; static _GLFWfbconfig prev_desired = {0}; static GLXFBConfig prev_result = 0; if (prev_result != 0 && memcmp(&prev_desired, desired, sizeof(_GLFWfbconfig)) == 0) { *result = prev_result; return true; } prev_desired = *desired; // HACK: This is a (hopefully temporary) workaround for Chromium // (VirtualBox GL) not setting the window bit on any GLXFBConfigs vendor = glXGetClientString(_glfw.x11.display, GLX_VENDOR); if (vendor && strcmp(vendor, "Chromium") == 0) trustWindowBit = false; nativeConfigs = choose_fb_config(desired, trustWindowBit, &nativeCount, false); if (!nativeConfigs || !nativeCount) { nativeConfigs = choose_fb_config(desired, trustWindowBit, &nativeCount, true); if (!nativeConfigs || !nativeCount) { _glfwInputError(GLFW_API_UNAVAILABLE, "GLX: No GLXFBConfigs returned"); return false; } } for (i = 0; i < nativeCount; i++) { const GLXFBConfig n = nativeConfigs[i]; bool transparency_matches = true, srgb_matches = true; if (desired->transparent) { transparency_matches = false; XVisualInfo* vi = glXGetVisualFromFBConfig(_glfw.x11.display, n); if (vi && _glfwIsVisualTransparentX11(vi->visual)) transparency_matches = true; } if (desired->sRGB && (_glfw.glx.ARB_framebuffer_sRGB || _glfw.glx.EXT_framebuffer_sRGB)) { srgb_matches = getGLXFBConfigAttrib(n, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB) ? true : false; } if (transparency_matches && srgb_matches) { ans_idx = i; break; } } *result = nativeConfigs[ans_idx]; prev_result = nativeConfigs[ans_idx]; XFree(nativeConfigs); return true; } // Create the OpenGL context using legacy API // static GLXContext createLegacyContextGLX(_GLFWwindow* window UNUSED, GLXFBConfig fbconfig, GLXContext share) { return glXCreateNewContext(_glfw.x11.display, fbconfig, GLX_RGBA_TYPE, share, True); } static void makeContextCurrentGLX(_GLFWwindow* window) { if (window) { if (!glXMakeCurrent(_glfw.x11.display, window->context.glx.window, window->context.glx.handle)) { _glfwInputError(GLFW_PLATFORM_ERROR, "GLX: Failed to make context current"); return; } } else { if (!glXMakeCurrent(_glfw.x11.display, None, NULL)) { _glfwInputError(GLFW_PLATFORM_ERROR, "GLX: Failed to clear current context"); return; } } _glfwPlatformSetTls(&_glfw.contextSlot, window); } static void swapBuffersGLX(_GLFWwindow* window) { glXSwapBuffers(_glfw.x11.display, window->context.glx.window); } static void swapIntervalGLX(int interval) { _GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot); if (_glfw.glx.EXT_swap_control) { _glfw.glx.SwapIntervalEXT(_glfw.x11.display, window->context.glx.window, interval); } else if (_glfw.glx.MESA_swap_control) _glfw.glx.SwapIntervalMESA(interval); else if (_glfw.glx.SGI_swap_control) { if (interval > 0) _glfw.glx.SwapIntervalSGI(interval); } } static int extensionSupportedGLX(const char* extension) { const char* extensions = glXQueryExtensionsString(_glfw.x11.display, _glfw.x11.screen); if (extensions) { if (_glfwStringInExtensionString(extension, extensions)) return true; } return false; } static GLFWglproc getProcAddressGLX(const char* procname) { if (_glfw.glx.GetProcAddress) return _glfw.glx.GetProcAddress((const GLubyte*) procname); else if (_glfw.glx.GetProcAddressARB) return _glfw.glx.GetProcAddressARB((const GLubyte*) procname); else { GLFWglproc ans = NULL; glfw_dlsym(ans, _glfw.glx.handle, procname); return ans; } } static void destroyContextGLX(_GLFWwindow* window) { if (window->context.glx.window) { glXDestroyWindow(_glfw.x11.display, window->context.glx.window); window->context.glx.window = None; } if (window->context.glx.handle) { glXDestroyContext(_glfw.x11.display, window->context.glx.handle); window->context.glx.handle = NULL; } } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// // Initialize GLX // bool _glfwInitGLX(void) { int i; const char* sonames[] = { #if defined(_GLFW_GLX_LIBRARY) _GLFW_GLX_LIBRARY, #elif defined(__CYGWIN__) "libGL-1.so", #else "libGL.so.1", "libGL.so", #endif NULL }; if (_glfw.glx.handle) return true; for (i = 0; sonames[i]; i++) { _glfw.glx.handle = _glfw_dlopen(sonames[i]); if (_glfw.glx.handle) break; } if (!_glfw.glx.handle) { _glfwInputError(GLFW_API_UNAVAILABLE, "GLX: Failed to load GLX"); return false; } glfw_dlsym(_glfw.glx.GetFBConfigs, _glfw.glx.handle, "glXGetFBConfigs"); glfw_dlsym(_glfw.glx.GetFBConfigAttrib, _glfw.glx.handle, "glXGetFBConfigAttrib"); glfw_dlsym(_glfw.glx.ChooseFBConfig, _glfw.glx.handle, "glXChooseFBConfig"); glfw_dlsym(_glfw.glx.GetClientString, _glfw.glx.handle, "glXGetClientString"); glfw_dlsym(_glfw.glx.QueryExtension, _glfw.glx.handle, "glXQueryExtension"); glfw_dlsym(_glfw.glx.QueryVersion, _glfw.glx.handle, "glXQueryVersion"); glfw_dlsym(_glfw.glx.DestroyContext, _glfw.glx.handle, "glXDestroyContext"); glfw_dlsym(_glfw.glx.MakeCurrent, _glfw.glx.handle, "glXMakeCurrent"); glfw_dlsym(_glfw.glx.SwapBuffers, _glfw.glx.handle, "glXSwapBuffers"); glfw_dlsym(_glfw.glx.QueryExtensionsString, _glfw.glx.handle, "glXQueryExtensionsString"); glfw_dlsym(_glfw.glx.CreateNewContext, _glfw.glx.handle, "glXCreateNewContext"); glfw_dlsym(_glfw.glx.CreateWindow, _glfw.glx.handle, "glXCreateWindow"); glfw_dlsym(_glfw.glx.DestroyWindow, _glfw.glx.handle, "glXDestroyWindow"); glfw_dlsym(_glfw.glx.GetProcAddress, _glfw.glx.handle, "glXGetProcAddress"); glfw_dlsym(_glfw.glx.GetProcAddressARB, _glfw.glx.handle, "glXGetProcAddressARB"); glfw_dlsym(_glfw.glx.GetVisualFromFBConfig, _glfw.glx.handle, "glXGetVisualFromFBConfig"); if (!_glfw.glx.GetFBConfigs || !_glfw.glx.GetFBConfigAttrib || !_glfw.glx.GetClientString || !_glfw.glx.QueryExtension || !_glfw.glx.QueryVersion || !_glfw.glx.DestroyContext || !_glfw.glx.MakeCurrent || !_glfw.glx.SwapBuffers || !_glfw.glx.QueryExtensionsString || !_glfw.glx.CreateNewContext || !_glfw.glx.CreateWindow || !_glfw.glx.DestroyWindow || !_glfw.glx.GetProcAddress || !_glfw.glx.GetProcAddressARB || !_glfw.glx.GetVisualFromFBConfig) { _glfwInputError(GLFW_PLATFORM_ERROR, "GLX: Failed to load required entry points"); return false; } if (!glXQueryExtension(_glfw.x11.display, &_glfw.glx.errorBase, &_glfw.glx.eventBase)) { _glfwInputError(GLFW_API_UNAVAILABLE, "GLX: GLX extension not found"); return false; } if (!glXQueryVersion(_glfw.x11.display, &_glfw.glx.major, &_glfw.glx.minor)) { _glfwInputError(GLFW_API_UNAVAILABLE, "GLX: Failed to query GLX version"); return false; } if (_glfw.glx.major == 1 && _glfw.glx.minor < 3) { _glfwInputError(GLFW_API_UNAVAILABLE, "GLX: GLX version 1.3 is required"); return false; } if (extensionSupportedGLX("GLX_EXT_swap_control")) { _glfw.glx.SwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) getProcAddressGLX("glXSwapIntervalEXT"); if (_glfw.glx.SwapIntervalEXT) _glfw.glx.EXT_swap_control = true; } if (extensionSupportedGLX("GLX_SGI_swap_control")) { _glfw.glx.SwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC) getProcAddressGLX("glXSwapIntervalSGI"); if (_glfw.glx.SwapIntervalSGI) _glfw.glx.SGI_swap_control = true; } if (extensionSupportedGLX("GLX_MESA_swap_control")) { _glfw.glx.SwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) getProcAddressGLX("glXSwapIntervalMESA"); if (_glfw.glx.SwapIntervalMESA) _glfw.glx.MESA_swap_control = true; } if (extensionSupportedGLX("GLX_ARB_multisample")) _glfw.glx.ARB_multisample = true; if (extensionSupportedGLX("GLX_ARB_framebuffer_sRGB")) _glfw.glx.ARB_framebuffer_sRGB = true; if (extensionSupportedGLX("GLX_EXT_framebuffer_sRGB")) _glfw.glx.EXT_framebuffer_sRGB = true; if (extensionSupportedGLX("GLX_ARB_create_context")) { _glfw.glx.CreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC) getProcAddressGLX("glXCreateContextAttribsARB"); if (_glfw.glx.CreateContextAttribsARB) _glfw.glx.ARB_create_context = true; } if (extensionSupportedGLX("GLX_ARB_create_context_robustness")) _glfw.glx.ARB_create_context_robustness = true; if (extensionSupportedGLX("GLX_ARB_create_context_profile")) _glfw.glx.ARB_create_context_profile = true; if (extensionSupportedGLX("GLX_EXT_create_context_es2_profile")) _glfw.glx.EXT_create_context_es2_profile = true; if (extensionSupportedGLX("GLX_ARB_create_context_no_error")) _glfw.glx.ARB_create_context_no_error = true; if (extensionSupportedGLX("GLX_ARB_context_flush_control")) _glfw.glx.ARB_context_flush_control = true; return true; } // Terminate GLX // void _glfwTerminateGLX(void) { // NOTE: This function must not call any X11 functions, as it is called // after XCloseDisplay (see _glfwPlatformTerminate for details) if (_glfw.glx.handle) { _glfw_dlclose(_glfw.glx.handle); _glfw.glx.handle = NULL; } } #define setAttrib(a, v) \ { \ assert(((size_t) index + 1) < sizeof(attribs) / sizeof(attribs[0])); \ attribs[index++] = a; \ attribs[index++] = v; \ } // Create the OpenGL or OpenGL ES context // bool _glfwCreateContextGLX(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { int attribs[40]; GLXFBConfig native = NULL; GLXContext share = NULL; if (ctxconfig->share) share = ctxconfig->share->context.glx.handle; if (!chooseGLXFBConfig(fbconfig, &native)) { _glfwInputError(GLFW_FORMAT_UNAVAILABLE, "GLX: Failed to find a suitable GLXFBConfig"); return false; } if (ctxconfig->client == GLFW_OPENGL_ES_API) { if (!_glfw.glx.ARB_create_context || !_glfw.glx.ARB_create_context_profile || !_glfw.glx.EXT_create_context_es2_profile) { _glfwInputError(GLFW_API_UNAVAILABLE, "GLX: OpenGL ES requested but GLX_EXT_create_context_es2_profile is unavailable"); return false; } } if (ctxconfig->forward) { if (!_glfw.glx.ARB_create_context) { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "GLX: Forward compatibility requested but GLX_ARB_create_context_profile is unavailable"); return false; } } if (ctxconfig->profile) { if (!_glfw.glx.ARB_create_context || !_glfw.glx.ARB_create_context_profile) { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "GLX: An OpenGL profile requested but GLX_ARB_create_context_profile is unavailable"); return false; } } _glfwGrabErrorHandlerX11(); if (_glfw.glx.ARB_create_context) { int index = 0, mask = 0, flags = 0; if (ctxconfig->client == GLFW_OPENGL_API) { if (ctxconfig->forward) flags |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; if (ctxconfig->profile == GLFW_OPENGL_CORE_PROFILE) mask |= GLX_CONTEXT_CORE_PROFILE_BIT_ARB; else if (ctxconfig->profile == GLFW_OPENGL_COMPAT_PROFILE) mask |= GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; } else mask |= GLX_CONTEXT_ES2_PROFILE_BIT_EXT; if (ctxconfig->debug) flags |= GLX_CONTEXT_DEBUG_BIT_ARB; if (ctxconfig->robustness) { if (_glfw.glx.ARB_create_context_robustness) { if (ctxconfig->robustness == GLFW_NO_RESET_NOTIFICATION) { setAttrib(GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, GLX_NO_RESET_NOTIFICATION_ARB); } else if (ctxconfig->robustness == GLFW_LOSE_CONTEXT_ON_RESET) { setAttrib(GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, GLX_LOSE_CONTEXT_ON_RESET_ARB); } flags |= GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB; } } if (ctxconfig->release) { if (_glfw.glx.ARB_context_flush_control) { if (ctxconfig->release == GLFW_RELEASE_BEHAVIOR_NONE) { setAttrib(GLX_CONTEXT_RELEASE_BEHAVIOR_ARB, GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB); } else if (ctxconfig->release == GLFW_RELEASE_BEHAVIOR_FLUSH) { setAttrib(GLX_CONTEXT_RELEASE_BEHAVIOR_ARB, GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB); } } } if (ctxconfig->noerror) { if (_glfw.glx.ARB_create_context_no_error) setAttrib(GLX_CONTEXT_OPENGL_NO_ERROR_ARB, true); } // NOTE: Only request an explicitly versioned context when necessary, as // explicitly requesting version 1.0 does not always return the // highest version supported by the driver if (ctxconfig->major != 1 || ctxconfig->minor != 0) { setAttrib(GLX_CONTEXT_MAJOR_VERSION_ARB, ctxconfig->major); setAttrib(GLX_CONTEXT_MINOR_VERSION_ARB, ctxconfig->minor); } if (mask) setAttrib(GLX_CONTEXT_PROFILE_MASK_ARB, mask); if (flags) setAttrib(GLX_CONTEXT_FLAGS_ARB, flags); setAttrib(None, None); window->context.glx.handle = _glfw.glx.CreateContextAttribsARB(_glfw.x11.display, native, share, True, attribs); // HACK: This is a fallback for broken versions of the Mesa // implementation of GLX_ARB_create_context_profile that fail // default 1.0 context creation with a GLXBadProfileARB error in // violation of the extension spec if (!window->context.glx.handle) { if (_glfw.x11.errorCode == _glfw.glx.errorBase + GLXBadProfileARB && ctxconfig->client == GLFW_OPENGL_API && ctxconfig->profile == GLFW_OPENGL_ANY_PROFILE && ctxconfig->forward == false) { window->context.glx.handle = createLegacyContextGLX(window, native, share); } } } else { window->context.glx.handle = createLegacyContextGLX(window, native, share); } _glfwReleaseErrorHandlerX11(); if (!window->context.glx.handle) { _glfwInputErrorX11(GLFW_VERSION_UNAVAILABLE, "GLX: Failed to create context"); return false; } window->context.glx.window = glXCreateWindow(_glfw.x11.display, native, window->x11.handle, NULL); if (!window->context.glx.window) { _glfwInputError(GLFW_PLATFORM_ERROR, "GLX: Failed to create window"); return false; } window->context.makeCurrent = makeContextCurrentGLX; window->context.swapBuffers = swapBuffersGLX; window->context.swapInterval = swapIntervalGLX; window->context.extensionSupported = extensionSupportedGLX; window->context.getProcAddress = getProcAddressGLX; window->context.destroy = destroyContextGLX; return true; } #undef setAttrib // Returns the Visual and depth of the chosen GLXFBConfig // bool _glfwChooseVisualGLX(const _GLFWwndconfig* wndconfig UNUSED, const _GLFWctxconfig* ctxconfig UNUSED, const _GLFWfbconfig* fbconfig, Visual** visual, int* depth) { GLXFBConfig native; XVisualInfo* result; if (!chooseGLXFBConfig(fbconfig, &native)) { _glfwInputError(GLFW_FORMAT_UNAVAILABLE, "GLX: Failed to find a suitable GLXFBConfig"); return false; } result = glXGetVisualFromFBConfig(_glfw.x11.display, native); if (!result) { _glfwInputError(GLFW_PLATFORM_ERROR, "GLX: Failed to retrieve Visual for GLXFBConfig"); return false; } *visual = result->visual; *depth = result->depth; XFree(result); return true; } ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI GLXContext glfwGetGLXContext(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (window->context.client == GLFW_NO_API) { _glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL); return NULL; } return window->context.glx.handle; } GLFWAPI GLXWindow glfwGetGLXWindow(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(None); if (window->context.client == GLFW_NO_API) { _glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL); return None; } return window->context.glx.window; } kitty-0.41.1/glfw/glx_context.h0000664000175000017510000001714414773370543015764 0ustar nileshnilesh//======================================================================== // GLFW 3.4 GLX - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2017 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #define GLX_VENDOR 1 #define GLX_RGBA_BIT 0x00000001 #define GLX_WINDOW_BIT 0x00000001 #define GLX_DRAWABLE_TYPE 0x8010 #define GLX_RENDER_TYPE 0x8011 #define GLX_RGBA_TYPE 0x8014 #define GLX_DOUBLEBUFFER 5 #define GLX_STEREO 6 #define GLX_AUX_BUFFERS 7 #define GLX_RED_SIZE 8 #define GLX_GREEN_SIZE 9 #define GLX_BLUE_SIZE 10 #define GLX_ALPHA_SIZE 11 #define GLX_DEPTH_SIZE 12 #define GLX_STENCIL_SIZE 13 #define GLX_ACCUM_RED_SIZE 14 #define GLX_ACCUM_GREEN_SIZE 15 #define GLX_ACCUM_BLUE_SIZE 16 #define GLX_ACCUM_ALPHA_SIZE 17 #define GLX_SAMPLES 0x186a1 #define GLX_VISUAL_ID 0x800b #define GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20b2 #define GLX_CONTEXT_DEBUG_BIT_ARB 0x00000001 #define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 #define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 #define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 #define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 #define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 #define GLX_CONTEXT_FLAGS_ARB 0x2094 #define GLX_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 #define GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 #define GLX_LOSE_CONTEXT_ON_RESET_ARB 0x8252 #define GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 #define GLX_NO_RESET_NOTIFICATION_ARB 0x8261 #define GLX_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 #define GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB 0 #define GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098 #define GLX_CONTEXT_OPENGL_NO_ERROR_ARB 0x31b3 typedef XID GLXWindow; typedef XID GLXDrawable; typedef struct __GLXFBConfig* GLXFBConfig; typedef struct __GLXcontext* GLXContext; typedef void (*__GLXextproc)(void); typedef int (*PFNGLXGETFBCONFIGATTRIBPROC)(Display*,GLXFBConfig,int,int*); typedef GLXFBConfig* (*PFNGLXCHOOSEFBCONFIGPROC)(Display*,int,const int*,int*); typedef const char* (*PFNGLXGETCLIENTSTRINGPROC)(Display*,int); typedef Bool (*PFNGLXQUERYEXTENSIONPROC)(Display*,int*,int*); typedef Bool (*PFNGLXQUERYVERSIONPROC)(Display*,int*,int*); typedef void (*PFNGLXDESTROYCONTEXTPROC)(Display*,GLXContext); typedef Bool (*PFNGLXMAKECURRENTPROC)(Display*,GLXDrawable,GLXContext); typedef void (*PFNGLXSWAPBUFFERSPROC)(Display*,GLXDrawable); typedef const char* (*PFNGLXQUERYEXTENSIONSSTRINGPROC)(Display*,int); typedef GLXFBConfig* (*PFNGLXGETFBCONFIGSPROC)(Display*,int,int*); typedef GLXContext (*PFNGLXCREATENEWCONTEXTPROC)(Display*,GLXFBConfig,int,GLXContext,Bool); typedef __GLXextproc (* PFNGLXGETPROCADDRESSPROC)(const GLubyte *procName); typedef void (*PFNGLXSWAPINTERVALEXTPROC)(Display*,GLXDrawable,int); typedef XVisualInfo* (*PFNGLXGETVISUALFROMFBCONFIGPROC)(Display*,GLXFBConfig); typedef GLXWindow (*PFNGLXCREATEWINDOWPROC)(Display*,GLXFBConfig,Window,const int*); typedef void (*PFNGLXDESTROYWINDOWPROC)(Display*,GLXWindow); typedef int (*PFNGLXSWAPINTERVALMESAPROC)(int); typedef int (*PFNGLXSWAPINTERVALSGIPROC)(int); typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display*,GLXFBConfig,GLXContext,Bool,const int*); // libGL.so function pointer typedefs #define glXGetFBConfigs _glfw.glx.GetFBConfigs #define glXGetFBConfigAttrib _glfw.glx.GetFBConfigAttrib #define glXChooseFBConfig _glfw.glx.ChooseFBConfig #define glXGetClientString _glfw.glx.GetClientString #define glXQueryExtension _glfw.glx.QueryExtension #define glXQueryVersion _glfw.glx.QueryVersion #define glXDestroyContext _glfw.glx.DestroyContext #define glXMakeCurrent _glfw.glx.MakeCurrent #define glXSwapBuffers _glfw.glx.SwapBuffers #define glXQueryExtensionsString _glfw.glx.QueryExtensionsString #define glXCreateNewContext _glfw.glx.CreateNewContext #define glXGetVisualFromFBConfig _glfw.glx.GetVisualFromFBConfig #define glXCreateWindow _glfw.glx.CreateWindow #define glXDestroyWindow _glfw.glx.DestroyWindow #define _GLFW_PLATFORM_CONTEXT_STATE _GLFWcontextGLX glx; #define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE _GLFWlibraryGLX glx; // GLX-specific per-context data // typedef struct _GLFWcontextGLX { GLXContext handle; GLXWindow window; } _GLFWcontextGLX; // GLX-specific global data // typedef struct _GLFWlibraryGLX { int major, minor; int eventBase; int errorBase; // dlopen handle for libGL.so.1 void* handle; // GLX 1.3 functions PFNGLXGETFBCONFIGSPROC GetFBConfigs; PFNGLXGETFBCONFIGATTRIBPROC GetFBConfigAttrib; PFNGLXCHOOSEFBCONFIGPROC ChooseFBConfig; PFNGLXGETCLIENTSTRINGPROC GetClientString; PFNGLXQUERYEXTENSIONPROC QueryExtension; PFNGLXQUERYVERSIONPROC QueryVersion; PFNGLXDESTROYCONTEXTPROC DestroyContext; PFNGLXMAKECURRENTPROC MakeCurrent; PFNGLXSWAPBUFFERSPROC SwapBuffers; PFNGLXQUERYEXTENSIONSSTRINGPROC QueryExtensionsString; PFNGLXCREATENEWCONTEXTPROC CreateNewContext; PFNGLXGETVISUALFROMFBCONFIGPROC GetVisualFromFBConfig; PFNGLXCREATEWINDOWPROC CreateWindow; PFNGLXDESTROYWINDOWPROC DestroyWindow; // GLX 1.4 and extension functions PFNGLXGETPROCADDRESSPROC GetProcAddress; PFNGLXGETPROCADDRESSPROC GetProcAddressARB; PFNGLXSWAPINTERVALSGIPROC SwapIntervalSGI; PFNGLXSWAPINTERVALEXTPROC SwapIntervalEXT; PFNGLXSWAPINTERVALMESAPROC SwapIntervalMESA; PFNGLXCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; bool SGI_swap_control; bool EXT_swap_control; bool MESA_swap_control; bool ARB_multisample; bool ARB_framebuffer_sRGB; bool EXT_framebuffer_sRGB; bool ARB_create_context; bool ARB_create_context_profile; bool ARB_create_context_robustness; bool EXT_create_context_es2_profile; bool ARB_create_context_no_error; bool ARB_context_flush_control; } _GLFWlibraryGLX; bool _glfwInitGLX(void); void _glfwTerminateGLX(void); bool _glfwCreateContextGLX(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig); void _glfwDestroyContextGLX(_GLFWwindow* window); bool _glfwChooseVisualGLX(const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig, Visual** visual, int* depth); kitty-0.41.1/glfw/ibus_glfw.c0000664000175000017510000004433714773370543015406 0ustar nileshnilesh//======================================================================== // GLFW 3.4 XKB - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2018 Kovid Goyal // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== /* To test under X11 start IBUS as: * ibus-daemon -drxR * Setup the input sources you want with: * ibus-setup * Switch to the input source you want to test with: * ibus engine name * You can list available engines with: * ibus list-engine * Then run kitty as: * GLFW_IM_MODULE=ibus kitty */ #define _GNU_SOURCE #include #include #include #include #include #include #include "internal.h" #include "ibus_glfw.h" #define debug debug_input static const char IBUS_SERVICE[] = "org.freedesktop.IBus"; static const char IBUS_PATH[] = "/org/freedesktop/IBus"; static const char IBUS_INTERFACE[] = "org.freedesktop.IBus"; static const char IBUS_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext"; enum Capabilities { IBUS_CAP_PREEDIT_TEXT = 1 << 0, IBUS_CAP_AUXILIARY_TEXT = 1 << 1, IBUS_CAP_LOOKUP_TABLE = 1 << 2, IBUS_CAP_FOCUS = 1 << 3, IBUS_CAP_PROPERTY = 1 << 4, IBUS_CAP_SURROUNDING_TEXT = 1 << 5 }; typedef enum { IBUS_SHIFT_MASK = 1 << 0, IBUS_LOCK_MASK = 1 << 1, IBUS_CONTROL_MASK = 1 << 2, IBUS_MOD1_MASK = 1 << 3, IBUS_MOD2_MASK = 1 << 4, IBUS_MOD3_MASK = 1 << 5, IBUS_MOD4_MASK = 1 << 6, IBUS_MOD5_MASK = 1 << 7, IBUS_BUTTON1_MASK = 1 << 8, IBUS_BUTTON2_MASK = 1 << 9, IBUS_BUTTON3_MASK = 1 << 10, IBUS_BUTTON4_MASK = 1 << 11, IBUS_BUTTON5_MASK = 1 << 12, /* The next few modifiers are used by XKB, so we skip to the end. * Bits 15 - 23 are currently unused. Bit 29 is used internally. */ /* ibus mask */ IBUS_HANDLED_MASK = 1 << 24, IBUS_FORWARD_MASK = 1 << 25, IBUS_IGNORED_MASK = IBUS_FORWARD_MASK, IBUS_SUPER_MASK = 1 << 26, IBUS_HYPER_MASK = 1 << 27, IBUS_META_MASK = 1 << 28, IBUS_RELEASE_MASK = 1 << 30, IBUS_MODIFIER_MASK = 0x5f001fff } IBusModifierType; static uint32_t ibus_key_state_from_glfw(unsigned int glfw_modifiers, int action) { uint32_t ans = action == GLFW_RELEASE ? IBUS_RELEASE_MASK : 0; #define M(g, i) if(glfw_modifiers & GLFW_MOD_##g) ans |= i M(SHIFT, IBUS_SHIFT_MASK); M(CAPS_LOCK, IBUS_LOCK_MASK); M(CONTROL, IBUS_CONTROL_MASK); M(ALT, IBUS_MOD1_MASK); M(NUM_LOCK, IBUS_MOD2_MASK); M(SUPER, IBUS_MOD4_MASK); /* To do: figure out how to get super/hyper/meta */ #undef M return ans; } static unsigned int glfw_modifiers_from_ibus_state(uint32_t ibus_key_state) { unsigned int ans = 0; #define M(g, i) if(ibus_key_state & i) ans |= GLFW_MOD_##g M(SHIFT, IBUS_SHIFT_MASK); M(CAPS_LOCK, IBUS_LOCK_MASK); M(CONTROL, IBUS_CONTROL_MASK); M(ALT, IBUS_MOD1_MASK); M(NUM_LOCK, IBUS_MOD2_MASK); M(SUPER, IBUS_MOD4_MASK); /* To do: figure out how to get super/hyper/meta */ #undef M return ans; } static bool test_env_var(const char *name, const char *val) { const char *q = getenv(name); return (q && strcmp(q, val) == 0) ? true : false; } static size_t GLFW_MIN(size_t a, size_t b) { return a < b ? a : b; } static const char* get_ibus_text_from_message(DBusMessage *msg) { /* The message structure is (from dbus-monitor) variant struct { string "IBusText" array [ ] string "ash " variant struct { string "IBusAttrList" array [ ] array [ ] } } */ const char *text = NULL; const char *struct_id = NULL; DBusMessageIter iter, sub1, sub2; dbus_message_iter_init(msg, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) return NULL; dbus_message_iter_recurse(&iter, &sub1); if (dbus_message_iter_get_arg_type(&sub1) != DBUS_TYPE_STRUCT) return NULL; dbus_message_iter_recurse(&sub1, &sub2); if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) return NULL; dbus_message_iter_get_basic(&sub2, &struct_id); if (!struct_id || strncmp(struct_id, "IBusText", sizeof("IBusText")) != 0) return NULL; dbus_message_iter_next(&sub2); dbus_message_iter_next(&sub2); if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) return NULL; dbus_message_iter_get_basic(&sub2, &text); return text; } static void handle_ibus_forward_key_event(DBusMessage *msg) { uint32_t keysym, keycode, state; DBusMessageIter iter; dbus_message_iter_init(msg, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) return; dbus_message_iter_get_basic(&iter, &keysym); dbus_message_iter_next(&iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) return; dbus_message_iter_get_basic(&iter, &keycode); dbus_message_iter_next(&iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) return; dbus_message_iter_get_basic(&iter, &state); int mods = glfw_modifiers_from_ibus_state(state); debug("IBUS: ForwardKeyEvent: keysym=%x, keycode=%x, state=%x, glfw_mods=%x\n", keysym, keycode, state, mods); glfw_xkb_forwarded_key_from_ime(keysym, mods); } static void send_text(const char *text, GLFWIMEState ime_state) { _GLFWwindow *w = _glfwFocusedWindow(); if (w && w->callbacks.keyboard) { GLFWkeyevent fake_ev = {.action = GLFW_PRESS}; fake_ev.text = text; fake_ev.ime_state = ime_state; w->callbacks.keyboard((GLFWwindow*) w, &fake_ev); } } // Connection handling {{{ static DBusHandlerResult message_handler(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data) { // To monitor signals from IBUS, use //  dbus-monitor --address `ibus address` "type='signal',interface='org.freedesktop.IBus.InputContext'" _GLFWIBUSData *ibus = (_GLFWIBUSData*)user_data; (void)ibus; const char *text; switch(glfw_dbus_match_signal(msg, IBUS_INPUT_INTERFACE, "CommitText", "UpdatePreeditText", "HidePreeditText", "ShowPreeditText", "ForwardKeyEvent", NULL)) { case 0: text = get_ibus_text_from_message(msg); debug("IBUS: CommitText: '%s'\n", text ? text : "(nil)"); send_text(text, GLFW_IME_COMMIT_TEXT); break; case 1: text = get_ibus_text_from_message(msg); debug("IBUS: UpdatePreeditText: '%s'\n", text ? text : "(nil)"); send_text(text, GLFW_IME_PREEDIT_CHANGED); break; case 2: debug("IBUS: HidePreeditText\n"); send_text("", GLFW_IME_PREEDIT_CHANGED); break; case 3: debug("IBUS: ShowPreeditText\n"); break; case 4: handle_ibus_forward_key_event(msg); break; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static DBusHandlerResult ibus_on_owner_change(DBusConnection* conn UNUSED, DBusMessage* msg, void* user_data) { if (dbus_message_is_signal(msg, "org.freedesktop.DBus", "NameOwnerChanged")) { const char* name; const char* old_owner; const char* new_owner; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old_owner, DBUS_TYPE_STRING, &new_owner, DBUS_TYPE_INVALID )) { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (strcmp(name, "org.freedesktop.IBus") != 0) { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } _GLFWIBUSData* ibus = (_GLFWIBUSData*) user_data; ibus->name_owner_changed = true; return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static const char* get_ibus_address_file_name(void) { const char *addr; static char ans[PATH_MAX]; static char display[64] = {0}; addr = getenv("IBUS_ADDRESS"); int offset = 0; if (addr && addr[0]) { memcpy(ans, addr, GLFW_MIN(strlen(addr), sizeof(ans))); return ans; } const char* disp_num = NULL; const char *host = "unix"; // See https://github.com/ibus/ibus/commit/8ce25208c3f4adfd290a032c6aa739d2b7580eb1 for why we need this dance. const char *de = getenv("WAYLAND_DISPLAY"); if (de) { disp_num = de; } else { const char *de = getenv("DISPLAY"); if (!de || !de[0]) de = ":0.0"; strncpy(display, de, sizeof(display) - 1); char *dnum = strrchr(display, ':'); if (!dnum) { _glfwInputError(GLFW_PLATFORM_ERROR, "Could not get IBUS address file name as DISPLAY env var has no colon"); return NULL; } char *screen_num = strrchr(display, '.'); *dnum = 0; dnum++; if (screen_num) *screen_num = 0; if (*display) host = display; disp_num = dnum; } memset(ans, 0, sizeof(ans)); const char *conf_env = getenv("XDG_CONFIG_HOME"); if (conf_env && conf_env[0]) { offset = snprintf(ans, sizeof(ans), "%s", conf_env); } else { conf_env = getenv("HOME"); if (!conf_env || !conf_env[0]) { _glfwInputError(GLFW_PLATFORM_ERROR, "Could not get IBUS address file name as no HOME env var is set"); return NULL; } offset = snprintf(ans, sizeof(ans), "%s/.config", conf_env); } DBusError err; char *key = dbus_try_get_local_machine_id(&err); if (!key) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cannot connect to IBUS as could not get DBUS local machine id with error %s: %s", err.name ? err.name : "", err.message ? err.message : ""); return NULL; } snprintf(ans + offset, sizeof(ans) - offset, "/ibus/bus/%s-%s-%s", key, host, disp_num); dbus_free(key); return ans; } static bool read_ibus_address(_GLFWIBUSData *ibus) { static char buf[1024]; struct stat s; FILE *addr_file = fopen(ibus->address_file_name, "r"); if (!addr_file) { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to open IBUS address file: %s with error: %s", ibus->address_file_name, strerror(errno)); return false; } int stat_result = fstat(fileno(addr_file), &s); bool found = false; while (fgets(buf, sizeof(buf), addr_file)) { if (strncmp(buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=")-1) == 0) { size_t sz = strlen(buf); if (buf[sz-1] == '\n') buf[sz-1] = 0; if (buf[sz-2] == '\r') buf[sz-2] = 0; found = true; break; } } fclose(addr_file); addr_file = NULL; if (stat_result != 0) { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to stat IBUS address file: %s with error: %s", ibus->address_file_name, strerror(errno)); return false; } ibus->address_file_mtime = s.st_mtime; if (found) { free((void*)ibus->address); ibus->address = _glfw_strdup(buf + sizeof("IBUS_ADDRESS=") - 1); return true; } _glfwInputError(GLFW_PLATFORM_ERROR, "Could not find IBUS_ADDRESS in %s", ibus->address_file_name); return false; } void input_context_created(DBusMessage *msg, const char* errmsg, void *data) { if (errmsg) { _glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to create input context with error: %s", errmsg); return; } const char *path = NULL; if (!glfw_dbus_get_args(msg, "Failed to get IBUS context path from reply", DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return; _GLFWIBUSData *ibus = (_GLFWIBUSData*)data; free((void*)ibus->input_ctx_path); ibus->input_ctx_path = _glfw_strdup(path); if (!ibus->input_ctx_path) return; dbus_bus_add_match(ibus->conn, "type='signal',interface='org.freedesktop.DBus', member='NameOwnerChanged'", NULL); dbus_connection_add_filter(ibus->conn, ibus_on_owner_change, ibus, free); dbus_bus_add_match(ibus->conn, "type='signal',interface='org.freedesktop.IBus.InputContext'", NULL); DBusObjectPathVTable ibus_vtable = {.message_function = message_handler}; dbus_connection_try_register_object_path(ibus->conn, ibus->input_ctx_path, &ibus_vtable, ibus, NULL); enum Capabilities caps = IBUS_CAP_FOCUS | IBUS_CAP_PREEDIT_TEXT; if (!glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "SetCapabilities", DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID)) return; ibus->ok = true; glfw_ibus_set_focused(ibus, _glfwFocusedWindow() != NULL); glfw_ibus_set_cursor_geometry(ibus, 0, 0, 0, 0); debug("Connected to IBUS daemon for IME input management\n"); } static bool setup_connection(_GLFWIBUSData *ibus) { const char *client_name = "GLFW_Application"; const char *address_file_name = get_ibus_address_file_name(); ibus->ok = false; if (!address_file_name) return false; free((void*)ibus->address_file_name); ibus->address_file_name = _glfw_strdup(address_file_name); if (!read_ibus_address(ibus)) return false; if (ibus->conn) { glfw_dbus_close_connection(ibus->conn); ibus->conn = NULL; } debug("Connecting to IBUS daemon @ %s for IME input management\n", ibus->address); ibus->conn = glfw_dbus_connect_to(ibus->address, "Failed to connect to the IBUS daemon, with error", "ibus", true); if (!ibus->conn) return false; free((void*)ibus->input_ctx_path); ibus->input_ctx_path = NULL; if (!glfw_dbus_call_method_with_reply( ibus->conn, IBUS_SERVICE, IBUS_PATH, IBUS_INTERFACE, "CreateInputContext", DBUS_TIMEOUT_USE_DEFAULT, input_context_created, ibus, DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID)) { return false; } return true; } void glfw_connect_to_ibus(_GLFWIBUSData *ibus) { if (ibus->inited) return; if (!test_env_var("GLFW_IM_MODULE", "ibus")) return; ibus->inited = true; ibus->name_owner_changed = false; setup_connection(ibus); } void glfw_ibus_terminate(_GLFWIBUSData *ibus) { if (ibus->conn) { glfw_dbus_close_connection(ibus->conn); ibus->conn = NULL; } #define F(x) if (ibus->x) { free((void*)ibus->x); ibus->x = NULL; } F(input_ctx_path); F(address); F(address_file_name); #undef F ibus->ok = false; } static bool check_connection(_GLFWIBUSData *ibus) { if (!ibus->inited) return false; if (ibus->conn && dbus_connection_get_is_connected(ibus->conn) && !ibus->name_owner_changed) return ibus->ok; struct stat s; ibus->name_owner_changed = false; if (stat(ibus->address_file_name, &s) != 0 || s.st_mtime != ibus->address_file_mtime) { if (!read_ibus_address(ibus)) return false; return setup_connection(ibus); } return false; } void glfw_ibus_dispatch(_GLFWIBUSData *ibus) { if (ibus->conn) glfw_dbus_dispatch(ibus->conn); } // }}} static void simple_message(_GLFWIBUSData *ibus, const char *method) { if (check_connection(ibus)) { glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, method, DBUS_TYPE_INVALID); } } void glfw_ibus_set_focused(_GLFWIBUSData *ibus, bool focused) { simple_message(ibus, focused ? "FocusIn" : "FocusOut"); } void glfw_ibus_set_cursor_geometry(_GLFWIBUSData *ibus, int x, int y, int w, int h) { if (check_connection(ibus)) { glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "SetCursorLocation", DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INT32, &w, DBUS_TYPE_INT32, &h, DBUS_TYPE_INVALID); } } void key_event_processed(DBusMessage *msg, const char* errmsg, void *data) { uint32_t handled = 0; _GLFWIBUSKeyEvent *ev = (_GLFWIBUSKeyEvent*)data; // Restore key's text from the text embedded in the structure. ev->glfw_ev.text = ev->__embedded_text; bool is_release = ev->glfw_ev.action == GLFW_RELEASE; bool failed = false; if (errmsg) { _glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to process key with error: %s", errmsg); failed = true; } else { glfw_dbus_get_args(msg, "Failed to get IBUS handled key from reply", DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID); debug("IBUS processed native_key: 0x%x release: %d handled: %u\n", ev->glfw_ev.native_key, is_release, handled); } glfw_xkb_key_from_ime(ev, handled ? true : false, failed); free(ev); } bool ibus_process_key(const _GLFWIBUSKeyEvent *ev_, _GLFWIBUSData *ibus) { if (!check_connection(ibus)) return false; _GLFWIBUSKeyEvent *ev = calloc(1, sizeof(_GLFWIBUSKeyEvent)); if (!ev) return false; memcpy(ev, ev_, sizeof(_GLFWIBUSKeyEvent)); // Put the key's text in a field IN the structure, for proper serialization. if (ev->glfw_ev.text) strncpy(ev->__embedded_text, ev->glfw_ev.text, sizeof(ev->__embedded_text) - 1); ev->glfw_ev.text = NULL; uint32_t state = ibus_key_state_from_glfw(ev->glfw_ev.mods, ev->glfw_ev.action); if (!glfw_dbus_call_method_with_reply( ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "ProcessKeyEvent", 3000, key_event_processed, ev, DBUS_TYPE_UINT32, &ev->ibus_keysym, DBUS_TYPE_UINT32, &ev->ibus_keycode, DBUS_TYPE_UINT32, &state, DBUS_TYPE_INVALID)) { free(ev); return false; } return true; } kitty-0.41.1/glfw/ibus_glfw.h0000664000175000017510000000375014773370543015405 0ustar nileshnilesh//======================================================================== // GLFW 3.4 XKB - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2018 Kovid Goyal // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #pragma once #include "internal.h" #include "dbus_glfw.h" #include typedef struct { bool ok, inited, name_owner_changed; time_t address_file_mtime; DBusConnection *conn; const char *input_ctx_path, *address_file_name, *address; } _GLFWIBUSData; typedef struct { xkb_keycode_t ibus_keycode; xkb_keysym_t ibus_keysym; GLFWid window_id; GLFWkeyevent glfw_ev; char __embedded_text[64]; } _GLFWIBUSKeyEvent; void glfw_connect_to_ibus(_GLFWIBUSData *ibus); void glfw_ibus_terminate(_GLFWIBUSData *ibus); void glfw_ibus_set_focused(_GLFWIBUSData *ibus, bool focused); void glfw_ibus_dispatch(_GLFWIBUSData *ibus); bool ibus_process_key(const _GLFWIBUSKeyEvent *ev_, _GLFWIBUSData *ibus); void glfw_ibus_set_cursor_geometry(_GLFWIBUSData *ibus, int x, int y, int w, int h); kitty-0.41.1/glfw/init.c0000664000175000017510000003063114773370543014360 0ustar nileshnilesh//======================================================================== // GLFW 3.4 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2018 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // Please use C89 style variable declarations in this file because VS 2010 //======================================================================== #include "internal.h" #include "mappings.h" #include #include #include #include // The global variables below comprise all mutable global data in GLFW // // Any other global variable is a bug // Global state shared between compilation units of GLFW // _GLFWlibrary _glfw = { false }; // These are outside of _glfw so they can be used before initialization and // after termination // static _GLFWerror _glfwMainThreadError; static GLFWerrorfun _glfwErrorCallback; static _GLFWinitconfig _glfwInitHints = { .hatButtons = true, .angleType = GLFW_ANGLE_PLATFORM_TYPE_NONE, .debugKeyboard = false, .debugRendering = false, .ns = { .menubar = true, // macOS menu bar .chdir = true // macOS bundle chdir }, .wl = { .ime = true, // Wayland IME support }, }; // Terminate the library // static void terminate(void) { int i; memset(&_glfw.callbacks, 0, sizeof(_glfw.callbacks)); _glfw_free_clipboard_data(&_glfw.clipboard); _glfw_free_clipboard_data(&_glfw.primary); while (_glfw.windowListHead) glfwDestroyWindow((GLFWwindow*) _glfw.windowListHead); while (_glfw.cursorListHead) glfwDestroyCursor((GLFWcursor*) _glfw.cursorListHead); for (i = 0; i < _glfw.monitorCount; i++) { _GLFWmonitor* monitor = _glfw.monitors[i]; if (monitor->originalRamp.size) _glfwPlatformSetGammaRamp(monitor, &monitor->originalRamp); _glfwFreeMonitor(monitor); } free(_glfw.monitors); _glfw.monitors = NULL; _glfw.monitorCount = 0; free(_glfw.mappings); _glfw.mappings = NULL; _glfw.mappingCount = 0; _glfwTerminateVulkan(); _glfwPlatformTerminateJoysticks(); _glfwPlatformTerminate(); _glfw.initialized = false; while (_glfw.errorListHead) { _GLFWerror* error = _glfw.errorListHead; _glfw.errorListHead = error->next; free(error); } _glfwPlatformDestroyTls(&_glfw.contextSlot); _glfwPlatformDestroyTls(&_glfw.errorSlot); _glfwPlatformDestroyMutex(&_glfw.errorLock); memset(&_glfw, 0, sizeof(_glfw)); } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// char* _glfw_strdup(const char* source) { const size_t length = strlen(source); char* result = malloc(length + 1); memcpy(result, source, length); result[length] = 0; return result; } ////////////////////////////////////////////////////////////////////////// ////// GLFW event API ////// ////////////////////////////////////////////////////////////////////////// // Notifies shared code of an error // void _glfwInputError(int code, const char* format, ...) { _GLFWerror* error; char description[_GLFW_MESSAGE_SIZE]; if (format) { va_list vl; va_start(vl, format); vsnprintf(description, sizeof(description), format, vl); va_end(vl); description[sizeof(description) - 1] = '\0'; } else { if (code == GLFW_NOT_INITIALIZED) strcpy(description, "The GLFW library is not initialized"); else if (code == GLFW_NO_CURRENT_CONTEXT) strcpy(description, "There is no current context"); else if (code == GLFW_INVALID_ENUM) strcpy(description, "Invalid argument for enum parameter"); else if (code == GLFW_INVALID_VALUE) strcpy(description, "Invalid value for parameter"); else if (code == GLFW_OUT_OF_MEMORY) strcpy(description, "Out of memory"); else if (code == GLFW_API_UNAVAILABLE) strcpy(description, "The requested API is unavailable"); else if (code == GLFW_VERSION_UNAVAILABLE) strcpy(description, "The requested API version is unavailable"); else if (code == GLFW_PLATFORM_ERROR) strcpy(description, "A platform-specific error occurred"); else if (code == GLFW_FORMAT_UNAVAILABLE) strcpy(description, "The requested format is unavailable"); else if (code == GLFW_NO_WINDOW_CONTEXT) strcpy(description, "The specified window has no context"); else if (code == GLFW_FEATURE_UNAVAILABLE) strcpy(description, "The requested feature cannot be implemented for this platform"); else if (code == GLFW_FEATURE_UNIMPLEMENTED) strcpy(description, "The requested feature has not yet been implemented for this platform"); else strcpy(description, "ERROR: UNKNOWN GLFW ERROR"); } if (_glfw.initialized) { error = _glfwPlatformGetTls(&_glfw.errorSlot); if (!error) { error = calloc(1, sizeof(_GLFWerror)); _glfwPlatformSetTls(&_glfw.errorSlot, error); _glfwPlatformLockMutex(&_glfw.errorLock); error->next = _glfw.errorListHead; _glfw.errorListHead = error; _glfwPlatformUnlockMutex(&_glfw.errorLock); } } else error = &_glfwMainThreadError; error->code = code; strcpy(error->description, description); if (_glfwErrorCallback) _glfwErrorCallback(code, description); } void _glfwDebug(const char *format, ...) { if (format) { va_list vl; fprintf(stderr, "[%.3f] ", monotonic_t_to_s_double(monotonic())); va_start(vl, format); vfprintf(stderr, format, vl); va_end(vl); fprintf(stderr, "\n"); } } ////////////////////////////////////////////////////////////////////////// ////// GLFW public API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI int glfwInit(monotonic_t start_time, bool *supports_window_occlusion) { *supports_window_occlusion = false; if (_glfw.initialized) return true; monotonic_start_time = start_time; memset(&_glfw, 0, sizeof(_glfw)); _glfw.hints.init = _glfwInitHints; _glfw.ignoreOSKeyboardProcessing = false; if (!_glfwPlatformInit(supports_window_occlusion)) { terminate(); return false; } if (!_glfwPlatformCreateMutex(&_glfw.errorLock) || !_glfwPlatformCreateTls(&_glfw.errorSlot) || !_glfwPlatformCreateTls(&_glfw.contextSlot)) { terminate(); return false; } _glfwPlatformSetTls(&_glfw.errorSlot, &_glfwMainThreadError); _glfw.initialized = true; glfwDefaultWindowHints(); { int i; for (i = 0; _glfwDefaultMappings[i]; i++) { if (!glfwUpdateGamepadMappings(_glfwDefaultMappings[i])) { terminate(); return false; } } } return true; } GLFWAPI void glfwTerminate(void) { if (!_glfw.initialized) return; terminate(); } GLFWAPI void glfwInitHint(int hint, int value) { switch (hint) { case GLFW_JOYSTICK_HAT_BUTTONS: _glfwInitHints.hatButtons = value; return; case GLFW_ANGLE_PLATFORM_TYPE: _glfwInitHints.angleType = value; return; case GLFW_DEBUG_KEYBOARD: _glfwInitHints.debugKeyboard = value; return; case GLFW_DEBUG_RENDERING: _glfwInitHints.debugRendering = value; return; case GLFW_COCOA_CHDIR_RESOURCES: _glfwInitHints.ns.chdir = value; return; case GLFW_COCOA_MENUBAR: _glfwInitHints.ns.menubar = value; return; case GLFW_WAYLAND_IME: _glfwInitHints.wl.ime = value; return; } _glfwInputError(GLFW_INVALID_ENUM, "Invalid init hint 0x%08X", hint); } GLFWAPI void glfwGetVersion(int* major, int* minor, int* rev) { if (major != NULL) *major = GLFW_VERSION_MAJOR; if (minor != NULL) *minor = GLFW_VERSION_MINOR; if (rev != NULL) *rev = GLFW_VERSION_REVISION; } GLFWAPI const char* glfwGetVersionString(void) { return _glfwPlatformGetVersionString(); } GLFWAPI int glfwGetError(const char** description) { _GLFWerror* error; int code = GLFW_NO_ERROR; if (description) *description = NULL; if (_glfw.initialized) error = _glfwPlatformGetTls(&_glfw.errorSlot); else error = &_glfwMainThreadError; if (error) { code = error->code; error->code = GLFW_NO_ERROR; if (description && code) *description = error->description; } return code; } GLFWAPI GLFWerrorfun glfwSetErrorCallback(GLFWerrorfun cbfun) { _GLFW_SWAP_POINTERS(_glfwErrorCallback, cbfun); return cbfun; } GLFWAPI void glfwRunMainLoop(GLFWtickcallback callback, void *data) { _GLFW_REQUIRE_INIT(); _glfwPlatformRunMainLoop(callback, data); } GLFWAPI void glfwStopMainLoop(void) { _GLFW_REQUIRE_INIT(); _glfwPlatformStopMainLoop(); } GLFWAPI unsigned long long glfwAddTimer( monotonic_t interval, bool repeats, GLFWuserdatafun callback, void *callback_data, GLFWuserdatafun free_callback) { return _glfwPlatformAddTimer(interval, repeats, callback, callback_data, free_callback); } GLFWAPI void glfwUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled) { _glfwPlatformUpdateTimer(timer_id, interval, enabled); } GLFWAPI void glfwRemoveTimer(unsigned long long timer_id) { _glfwPlatformRemoveTimer(timer_id); } GLFWAPI GLFWapplicationclosefun glfwSetApplicationCloseCallback(GLFWapplicationclosefun cbfun) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(_glfw.callbacks.application_close, cbfun); return cbfun; } GLFWAPI GLFWsystemcolorthemechangefun glfwSetSystemColorThemeChangeCallback(GLFWsystemcolorthemechangefun cbfun) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(_glfw.callbacks.system_color_theme_change, cbfun); return cbfun; } GLFWAPI GLFWclipboardlostfun glfwSetClipboardLostCallback(GLFWclipboardlostfun cbfun) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(_glfw.callbacks.clipboard_lost, cbfun); return cbfun; } GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun cbfun) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(_glfw.callbacks.draw_text, cbfun); return cbfun; } GLFWAPI GLFWcurrentselectionfun glfwSetCurrentSelectionCallback(GLFWcurrentselectionfun cbfun) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(_glfw.callbacks.get_current_selection, cbfun); return cbfun; } GLFWAPI GLFWhascurrentselectionfun glfwSetHasCurrentSelectionCallback(GLFWhascurrentselectionfun cbfun) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(_glfw.callbacks.has_current_selection, cbfun); return cbfun; } GLFWAPI GLFWimecursorpositionfun glfwSetIMECursorPositionCallback(GLFWimecursorpositionfun cbfun) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(_glfw.callbacks.get_ime_cursor_position, cbfun); return cbfun; } kitty-0.41.1/glfw/input.c0000664000175000017510000013267714773370543014571 0ustar nileshnilesh//======================================================================== // GLFW 3.4 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // Please use C89 style variable declarations in this file because VS 2010 //======================================================================== #include "internal.h" #include "../kitty/monotonic.h" #include #include #include #include #include // Internal key state used for sticky keys #define _GLFW_STICK 3 // Internal constants for gamepad mapping source types #define _GLFW_JOYSTICK_AXIS 1 #define _GLFW_JOYSTICK_BUTTON 2 #define _GLFW_JOYSTICK_HATBIT 3 // Initializes the platform joystick API if it has not been already // static bool initJoysticks(void) { if (!_glfw.joysticksInitialized) { if (!_glfwPlatformInitJoysticks()) { _glfwPlatformTerminateJoysticks(); return false; } } return _glfw.joysticksInitialized = true; } // Finds a mapping based on joystick GUID // static _GLFWmapping* findMapping(const char* guid) { int i; for (i = 0; i < _glfw.mappingCount; i++) { if (strcmp(_glfw.mappings[i].guid, guid) == 0) return _glfw.mappings + i; } return NULL; } // Checks whether a gamepad mapping element is present in the hardware // static bool isValidElementForJoystick(const _GLFWmapelement* e, const _GLFWjoystick* js) { if (e->type == _GLFW_JOYSTICK_HATBIT && (e->index >> 4) >= js->hatCount) return false; else if (e->type == _GLFW_JOYSTICK_BUTTON && e->index >= js->buttonCount) return false; else if (e->type == _GLFW_JOYSTICK_AXIS && e->index >= js->axisCount) return false; return true; } // Finds a mapping based on joystick GUID and verifies element indices // static _GLFWmapping* findValidMapping(const _GLFWjoystick* js) { _GLFWmapping* mapping = findMapping(js->guid); if (mapping) { int i; for (i = 0; i <= GLFW_GAMEPAD_BUTTON_LAST; i++) { if (!isValidElementForJoystick(mapping->buttons + i, js)) { _glfwInputError(GLFW_INVALID_VALUE, "Invalid button in gamepad mapping %s (%s)", mapping->guid, mapping->name); return NULL; } } for (i = 0; i <= GLFW_GAMEPAD_AXIS_LAST; i++) { if (!isValidElementForJoystick(mapping->axes + i, js)) { _glfwInputError(GLFW_INVALID_VALUE, "Invalid axis in gamepad mapping %s (%s)", mapping->guid, mapping->name); return NULL; } } } return mapping; } // Parses an SDL_GameControllerDB line and adds it to the mapping list // static bool parseMapping(_GLFWmapping* mapping, const char* string) { const char* c = string; size_t i, length; struct { const char* name; _GLFWmapelement* element; } fields[] = { { "platform", NULL }, { "a", mapping->buttons + GLFW_GAMEPAD_BUTTON_A }, { "b", mapping->buttons + GLFW_GAMEPAD_BUTTON_B }, { "x", mapping->buttons + GLFW_GAMEPAD_BUTTON_X }, { "y", mapping->buttons + GLFW_GAMEPAD_BUTTON_Y }, { "back", mapping->buttons + GLFW_GAMEPAD_BUTTON_BACK }, { "start", mapping->buttons + GLFW_GAMEPAD_BUTTON_START }, { "guide", mapping->buttons + GLFW_GAMEPAD_BUTTON_GUIDE }, { "leftshoulder", mapping->buttons + GLFW_GAMEPAD_BUTTON_LEFT_BUMPER }, { "rightshoulder", mapping->buttons + GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER }, { "leftstick", mapping->buttons + GLFW_GAMEPAD_BUTTON_LEFT_THUMB }, { "rightstick", mapping->buttons + GLFW_GAMEPAD_BUTTON_RIGHT_THUMB }, { "dpup", mapping->buttons + GLFW_GAMEPAD_BUTTON_DPAD_UP }, { "dpright", mapping->buttons + GLFW_GAMEPAD_BUTTON_DPAD_RIGHT }, { "dpdown", mapping->buttons + GLFW_GAMEPAD_BUTTON_DPAD_DOWN }, { "dpleft", mapping->buttons + GLFW_GAMEPAD_BUTTON_DPAD_LEFT }, { "lefttrigger", mapping->axes + GLFW_GAMEPAD_AXIS_LEFT_TRIGGER }, { "righttrigger", mapping->axes + GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER }, { "leftx", mapping->axes + GLFW_GAMEPAD_AXIS_LEFT_X }, { "lefty", mapping->axes + GLFW_GAMEPAD_AXIS_LEFT_Y }, { "rightx", mapping->axes + GLFW_GAMEPAD_AXIS_RIGHT_X }, { "righty", mapping->axes + GLFW_GAMEPAD_AXIS_RIGHT_Y } }; length = strcspn(c, ","); if (length != 32 || c[length] != ',') { _glfwInputError(GLFW_INVALID_VALUE, NULL); return false; } memcpy(mapping->guid, c, length); c += length + 1; length = strcspn(c, ","); if (length >= sizeof(mapping->name) || c[length] != ',') { _glfwInputError(GLFW_INVALID_VALUE, NULL); return false; } memcpy(mapping->name, c, length); c += length + 1; while (*c) { // TODO: Implement output modifiers if (*c == '+' || *c == '-') return false; for (i = 0; i < sizeof(fields) / sizeof(fields[0]); i++) { length = strlen(fields[i].name); if (strncmp(c, fields[i].name, length) != 0 || c[length] != ':') continue; c += length + 1; if (fields[i].element) { _GLFWmapelement* e = fields[i].element; int8_t minimum = -1; int8_t maximum = 1; if (*c == '+') { minimum = 0; c += 1; } else if (*c == '-') { maximum = 0; c += 1; } if (*c == 'a') e->type = _GLFW_JOYSTICK_AXIS; else if (*c == 'b') e->type = _GLFW_JOYSTICK_BUTTON; else if (*c == 'h') e->type = _GLFW_JOYSTICK_HATBIT; else break; if (e->type == _GLFW_JOYSTICK_HATBIT) { const unsigned long hat = strtoul(c + 1, (char**) &c, 10); const unsigned long bit = strtoul(c + 1, (char**) &c, 10); e->index = (uint8_t) ((hat << 4) | bit); } else e->index = (uint8_t) strtoul(c + 1, (char**) &c, 10); if (e->type == _GLFW_JOYSTICK_AXIS) { e->axisScale = 2 / (maximum - minimum); e->axisOffset = -(maximum + minimum); if (*c == '~') { e->axisScale = -e->axisScale; e->axisOffset = -e->axisOffset; } } } else { length = strlen(_GLFW_PLATFORM_MAPPING_NAME); if (strncmp(c, _GLFW_PLATFORM_MAPPING_NAME, length) != 0) return false; } break; } c += strcspn(c, ","); c += strspn(c, ","); } for (i = 0; i < 32; i++) { if (mapping->guid[i] >= 'A' && mapping->guid[i] <= 'F') mapping->guid[i] += 'a' - 'A'; } _glfwPlatformUpdateGamepadGUID(mapping->guid); return true; } ////////////////////////////////////////////////////////////////////////// ////// GLFW event API ////// ////////////////////////////////////////////////////////////////////////// static void set_key_action(_GLFWwindow *window, const GLFWkeyevent *ev, int action, int idx) { const unsigned sz = arraysz(window->activated_keys); if (idx < 0) { for (unsigned i = 0; i < sz; i++) { if (window->activated_keys[i].native_key_id == 0) { idx = i; break; } } if (idx < 0) { idx = sz - 1; memmove(window->activated_keys, window->activated_keys + 1, sizeof(window->activated_keys[0]) * (sz - 1)); window->activated_keys[sz - 1].native_key_id = 0; } } if (action == GLFW_RELEASE) { memset(window->activated_keys + idx, 0, sizeof(window->activated_keys[0])); if (idx < (int)sz - 1) { memmove(window->activated_keys + idx, window->activated_keys + idx + 1, sizeof(window->activated_keys[0]) * (sz - 1 - idx)); memset(window->activated_keys + sz - 1, 0, sizeof(window->activated_keys[0])); } } else { window->activated_keys[idx] = *ev; window->activated_keys[idx].text = NULL; } } // Notifies shared code of a physical key event // void _glfwInputKeyboard(_GLFWwindow* window, GLFWkeyevent* ev) { if (ev->native_key_id > 0) { bool repeated = false; int idx = -1; int current_action = GLFW_RELEASE; const unsigned sz = arraysz(window->activated_keys); for (unsigned i = 0; i < sz; i++) { if (window->activated_keys[i].native_key_id == ev->native_key_id) { idx = i; current_action = window->activated_keys[i].action; break; } } if (ev->action == GLFW_RELEASE) { if (current_action == GLFW_RELEASE) return; if (idx > -1) { const GLFWkeyevent *press_event = window->activated_keys + idx; if (press_event->action == GLFW_PRESS || press_event->action == GLFW_REPEAT) { // Compose sequences under X11 give a different key value for press and release events // but we want the same key value so override it. ev->native_key = press_event->native_key; ev->key = press_event->key; ev->shifted_key = press_event->shifted_key; ev->alternate_key = press_event->alternate_key; } } } if (ev->action == GLFW_PRESS && current_action == GLFW_PRESS) repeated = true; set_key_action(window, ev, (ev->action == GLFW_RELEASE && window->stickyKeys) ? _GLFW_STICK : ev->action, idx); if (repeated) ev->action = GLFW_REPEAT; } // FIXME: will need to update ev->virtual_mods here too? if (window->callbacks.keyboard) { if (!window->lockKeyMods) ev->mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK); window->callbacks.keyboard((GLFWwindow*) window, ev); } } // Notifies shared code of a scroll event // void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags, int mods) { if (window->callbacks.scroll) window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset, flags, mods); } // Notifies shared code of a mouse button click event // void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods) { if (button < 0 || button > GLFW_MOUSE_BUTTON_LAST) return; if (!window->lockKeyMods) mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK); if (action == GLFW_RELEASE && window->stickyMouseButtons) window->mouseButtons[button] = _GLFW_STICK; else window->mouseButtons[button] = (char) action; if (window->callbacks.mouseButton) window->callbacks.mouseButton((GLFWwindow*) window, button, action, mods); } // Notifies shared code of a cursor motion event // The position is specified in content area relative screen coordinates // void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos) { if (window->virtualCursorPosX == xpos && window->virtualCursorPosY == ypos) return; window->virtualCursorPosX = xpos; window->virtualCursorPosY = ypos; if (window->callbacks.cursorPos) window->callbacks.cursorPos((GLFWwindow*) window, xpos, ypos); } // Notifies shared code of a cursor enter/leave event // void _glfwInputCursorEnter(_GLFWwindow* window, bool entered) { if (window->callbacks.cursorEnter) window->callbacks.cursorEnter((GLFWwindow*) window, entered); } // Notifies shared code of files or directories dropped on a window // int _glfwInputDrop(_GLFWwindow* window, const char *mime, const char *text, size_t sz) { if (window->callbacks.drop) return window->callbacks.drop((GLFWwindow*) window, mime, text, sz); return 0; } // Notifies shared code of a joystick connection or disconnection // void _glfwInputJoystick(_GLFWjoystick* js, int event) { const int jid = (int) (js - _glfw.joysticks); if (_glfw.callbacks.joystick) _glfw.callbacks.joystick(jid, event); } // Notifies shared code of the new value of a joystick axis // void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value) { js->axes[axis] = value; } // Notifies shared code of the new value of a joystick button // void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value) { js->buttons[button] = value; } // Notifies shared code of the new value of a joystick hat // void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value) { const int base = js->buttonCount + hat * 4; js->buttons[base + 0] = (value & 0x01) ? GLFW_PRESS : GLFW_RELEASE; js->buttons[base + 1] = (value & 0x02) ? GLFW_PRESS : GLFW_RELEASE; js->buttons[base + 2] = (value & 0x04) ? GLFW_PRESS : GLFW_RELEASE; js->buttons[base + 3] = (value & 0x08) ? GLFW_PRESS : GLFW_RELEASE; js->hats[hat] = value; } void _glfwInputColorScheme(GLFWColorScheme value, bool is_initial_value) { _glfwPlatformInputColorScheme(value); if (_glfw.callbacks.system_color_theme_change) _glfw.callbacks.system_color_theme_change(value, is_initial_value); } void _glfwInputClipboardLost(GLFWClipboardType which) { if (_glfw.callbacks.clipboard_lost) _glfw.callbacks.clipboard_lost(which); } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// // Returns an available joystick object with arrays and name allocated // _GLFWjoystick* _glfwAllocJoystick(const char* name, const char* guid, int axisCount, int buttonCount, int hatCount) { int jid; _GLFWjoystick* js; for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) { if (!_glfw.joysticks[jid].present) break; } if (jid > GLFW_JOYSTICK_LAST) return NULL; js = _glfw.joysticks + jid; js->present = true; js->name = _glfw_strdup(name); js->axes = calloc(axisCount, sizeof(float)); js->buttons = calloc(buttonCount + (size_t) hatCount * 4, 1); js->hats = calloc(hatCount, 1); js->axisCount = axisCount; js->buttonCount = buttonCount; js->hatCount = hatCount; strncpy(js->guid, guid, sizeof(js->guid) - 1); js->mapping = findValidMapping(js); return js; } // Frees arrays and name and flags the joystick object as unused // void _glfwFreeJoystick(_GLFWjoystick* js) { free(js->name); free(js->axes); free(js->buttons); free(js->hats); memset(js, 0, sizeof(_GLFWjoystick)); } unsigned int encode_utf8(uint32_t ch, char* dest) { if (ch < 0x80) { dest[0] = (char)ch; return 1; } if (ch < 0x800) { dest[0] = (ch>>6) | 0xC0; dest[1] = (ch & 0x3F) | 0x80; return 2; } if (ch < 0x10000) { dest[0] = (ch>>12) | 0xE0; dest[1] = ((ch>>6) & 0x3F) | 0x80; dest[2] = (ch & 0x3F) | 0x80; return 3; } if (ch < 0x110000) { dest[0] = (ch>>18) | 0xF0; dest[1] = ((ch>>12) & 0x3F) | 0x80; dest[2] = ((ch>>6) & 0x3F) | 0x80; dest[3] = (ch & 0x3F) | 0x80; return 4; } return 0; } const char* _glfwGetKeyName(int key) { switch (key) { /* start functional key names (auto generated by gen-key-constants.py do not edit) */ case GLFW_FKEY_ESCAPE: return "ESCAPE"; case GLFW_FKEY_ENTER: return "ENTER"; case GLFW_FKEY_TAB: return "TAB"; case GLFW_FKEY_BACKSPACE: return "BACKSPACE"; case GLFW_FKEY_INSERT: return "INSERT"; case GLFW_FKEY_DELETE: return "DELETE"; case GLFW_FKEY_LEFT: return "LEFT"; case GLFW_FKEY_RIGHT: return "RIGHT"; case GLFW_FKEY_UP: return "UP"; case GLFW_FKEY_DOWN: return "DOWN"; case GLFW_FKEY_PAGE_UP: return "PAGE_UP"; case GLFW_FKEY_PAGE_DOWN: return "PAGE_DOWN"; case GLFW_FKEY_HOME: return "HOME"; case GLFW_FKEY_END: return "END"; case GLFW_FKEY_CAPS_LOCK: return "CAPS_LOCK"; case GLFW_FKEY_SCROLL_LOCK: return "SCROLL_LOCK"; case GLFW_FKEY_NUM_LOCK: return "NUM_LOCK"; case GLFW_FKEY_PRINT_SCREEN: return "PRINT_SCREEN"; case GLFW_FKEY_PAUSE: return "PAUSE"; case GLFW_FKEY_MENU: return "MENU"; case GLFW_FKEY_F1: return "F1"; case GLFW_FKEY_F2: return "F2"; case GLFW_FKEY_F3: return "F3"; case GLFW_FKEY_F4: return "F4"; case GLFW_FKEY_F5: return "F5"; case GLFW_FKEY_F6: return "F6"; case GLFW_FKEY_F7: return "F7"; case GLFW_FKEY_F8: return "F8"; case GLFW_FKEY_F9: return "F9"; case GLFW_FKEY_F10: return "F10"; case GLFW_FKEY_F11: return "F11"; case GLFW_FKEY_F12: return "F12"; case GLFW_FKEY_F13: return "F13"; case GLFW_FKEY_F14: return "F14"; case GLFW_FKEY_F15: return "F15"; case GLFW_FKEY_F16: return "F16"; case GLFW_FKEY_F17: return "F17"; case GLFW_FKEY_F18: return "F18"; case GLFW_FKEY_F19: return "F19"; case GLFW_FKEY_F20: return "F20"; case GLFW_FKEY_F21: return "F21"; case GLFW_FKEY_F22: return "F22"; case GLFW_FKEY_F23: return "F23"; case GLFW_FKEY_F24: return "F24"; case GLFW_FKEY_F25: return "F25"; case GLFW_FKEY_F26: return "F26"; case GLFW_FKEY_F27: return "F27"; case GLFW_FKEY_F28: return "F28"; case GLFW_FKEY_F29: return "F29"; case GLFW_FKEY_F30: return "F30"; case GLFW_FKEY_F31: return "F31"; case GLFW_FKEY_F32: return "F32"; case GLFW_FKEY_F33: return "F33"; case GLFW_FKEY_F34: return "F34"; case GLFW_FKEY_F35: return "F35"; case GLFW_FKEY_KP_0: return "KP_0"; case GLFW_FKEY_KP_1: return "KP_1"; case GLFW_FKEY_KP_2: return "KP_2"; case GLFW_FKEY_KP_3: return "KP_3"; case GLFW_FKEY_KP_4: return "KP_4"; case GLFW_FKEY_KP_5: return "KP_5"; case GLFW_FKEY_KP_6: return "KP_6"; case GLFW_FKEY_KP_7: return "KP_7"; case GLFW_FKEY_KP_8: return "KP_8"; case GLFW_FKEY_KP_9: return "KP_9"; case GLFW_FKEY_KP_DECIMAL: return "KP_DECIMAL"; case GLFW_FKEY_KP_DIVIDE: return "KP_DIVIDE"; case GLFW_FKEY_KP_MULTIPLY: return "KP_MULTIPLY"; case GLFW_FKEY_KP_SUBTRACT: return "KP_SUBTRACT"; case GLFW_FKEY_KP_ADD: return "KP_ADD"; case GLFW_FKEY_KP_ENTER: return "KP_ENTER"; case GLFW_FKEY_KP_EQUAL: return "KP_EQUAL"; case GLFW_FKEY_KP_SEPARATOR: return "KP_SEPARATOR"; case GLFW_FKEY_KP_LEFT: return "KP_LEFT"; case GLFW_FKEY_KP_RIGHT: return "KP_RIGHT"; case GLFW_FKEY_KP_UP: return "KP_UP"; case GLFW_FKEY_KP_DOWN: return "KP_DOWN"; case GLFW_FKEY_KP_PAGE_UP: return "KP_PAGE_UP"; case GLFW_FKEY_KP_PAGE_DOWN: return "KP_PAGE_DOWN"; case GLFW_FKEY_KP_HOME: return "KP_HOME"; case GLFW_FKEY_KP_END: return "KP_END"; case GLFW_FKEY_KP_INSERT: return "KP_INSERT"; case GLFW_FKEY_KP_DELETE: return "KP_DELETE"; case GLFW_FKEY_KP_BEGIN: return "KP_BEGIN"; case GLFW_FKEY_MEDIA_PLAY: return "MEDIA_PLAY"; case GLFW_FKEY_MEDIA_PAUSE: return "MEDIA_PAUSE"; case GLFW_FKEY_MEDIA_PLAY_PAUSE: return "MEDIA_PLAY_PAUSE"; case GLFW_FKEY_MEDIA_REVERSE: return "MEDIA_REVERSE"; case GLFW_FKEY_MEDIA_STOP: return "MEDIA_STOP"; case GLFW_FKEY_MEDIA_FAST_FORWARD: return "MEDIA_FAST_FORWARD"; case GLFW_FKEY_MEDIA_REWIND: return "MEDIA_REWIND"; case GLFW_FKEY_MEDIA_TRACK_NEXT: return "MEDIA_TRACK_NEXT"; case GLFW_FKEY_MEDIA_TRACK_PREVIOUS: return "MEDIA_TRACK_PREVIOUS"; case GLFW_FKEY_MEDIA_RECORD: return "MEDIA_RECORD"; case GLFW_FKEY_LOWER_VOLUME: return "LOWER_VOLUME"; case GLFW_FKEY_RAISE_VOLUME: return "RAISE_VOLUME"; case GLFW_FKEY_MUTE_VOLUME: return "MUTE_VOLUME"; case GLFW_FKEY_LEFT_SHIFT: return "LEFT_SHIFT"; case GLFW_FKEY_LEFT_CONTROL: return "LEFT_CONTROL"; case GLFW_FKEY_LEFT_ALT: return "LEFT_ALT"; case GLFW_FKEY_LEFT_SUPER: return "LEFT_SUPER"; case GLFW_FKEY_LEFT_HYPER: return "LEFT_HYPER"; case GLFW_FKEY_LEFT_META: return "LEFT_META"; case GLFW_FKEY_RIGHT_SHIFT: return "RIGHT_SHIFT"; case GLFW_FKEY_RIGHT_CONTROL: return "RIGHT_CONTROL"; case GLFW_FKEY_RIGHT_ALT: return "RIGHT_ALT"; case GLFW_FKEY_RIGHT_SUPER: return "RIGHT_SUPER"; case GLFW_FKEY_RIGHT_HYPER: return "RIGHT_HYPER"; case GLFW_FKEY_RIGHT_META: return "RIGHT_META"; case GLFW_FKEY_ISO_LEVEL3_SHIFT: return "ISO_LEVEL3_SHIFT"; case GLFW_FKEY_ISO_LEVEL5_SHIFT: return "ISO_LEVEL5_SHIFT"; /* end functional key names */ case 0: return "UNKNOWN"; } static char buf[16]; encode_utf8(key, buf); return buf; } // Center the cursor in the content area of the specified window // void _glfwCenterCursorInContentArea(_GLFWwindow* window) { int width, height; _glfwPlatformGetWindowSize(window, &width, &height); _glfwPlatformSetCursorPos(window, width / 2.0, height / 2.0); } ////////////////////////////////////////////////////////////////////////// ////// GLFW public API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI bool glfwGetIgnoreOSKeyboardProcessing(void) { return _glfw.ignoreOSKeyboardProcessing; } GLFWAPI void glfwSetIgnoreOSKeyboardProcessing(bool enabled) { _glfw.ignoreOSKeyboardProcessing = enabled; } GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(0); switch (mode) { case GLFW_CURSOR: return window->cursorMode; case GLFW_STICKY_KEYS: return window->stickyKeys; case GLFW_STICKY_MOUSE_BUTTONS: return window->stickyMouseButtons; case GLFW_LOCK_KEY_MODS: return window->lockKeyMods; case GLFW_RAW_MOUSE_MOTION: return window->rawMouseMotion; } _glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode); return 0; } GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); if (mode == GLFW_CURSOR) { if (value != GLFW_CURSOR_NORMAL && value != GLFW_CURSOR_HIDDEN && value != GLFW_CURSOR_DISABLED) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid cursor mode 0x%08X", value); return; } if (window->cursorMode == value) return; window->cursorMode = value; _glfwPlatformGetCursorPos(window, &window->virtualCursorPosX, &window->virtualCursorPosY); _glfwPlatformSetCursorMode(window, value); } else if (mode == GLFW_STICKY_KEYS) { value = value ? true : false; if (window->stickyKeys == value) return; if (!value) { // Release all sticky keys for (unsigned i = arraysz(window->activated_keys) - 1; i-- > 0;) { if (window->activated_keys[i].action == _GLFW_STICK) { if (i < arraysz(window->activated_keys) - 1) { memmove(window->activated_keys + i, window->activated_keys + i + 1, sizeof(window->activated_keys[0]) * (arraysz(window->activated_keys) - 1 - i)); } memset(window->activated_keys + arraysz(window->activated_keys) - 1, 0, sizeof(window->activated_keys[0])); } } } window->stickyKeys = value; } else if (mode == GLFW_STICKY_MOUSE_BUTTONS) { value = value ? true : false; if (window->stickyMouseButtons == value) return; if (!value) { int i; // Release all sticky mouse buttons for (i = 0; i <= GLFW_MOUSE_BUTTON_LAST; i++) { if (window->mouseButtons[i] == _GLFW_STICK) window->mouseButtons[i] = GLFW_RELEASE; } } window->stickyMouseButtons = value; } else if (mode == GLFW_LOCK_KEY_MODS) { window->lockKeyMods = value ? true : false; } else if (mode == GLFW_RAW_MOUSE_MOTION) { if (!_glfwPlatformRawMouseMotionSupported()) { _glfwInputError(GLFW_PLATFORM_ERROR, "Raw mouse motion is not supported on this system"); return; } value = value ? true : false; if (window->rawMouseMotion == value) return; window->rawMouseMotion = value; _glfwPlatformSetRawMouseMotion(window, value); } else _glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode); } GLFWAPI int glfwRawMouseMotionSupported(void) { _GLFW_REQUIRE_INIT_OR_RETURN(false); return _glfwPlatformRawMouseMotionSupported(); } GLFWAPI const char* glfwGetKeyName(uint32_t key, int native_key) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (key) return _glfwGetKeyName(key); native_key = _glfwPlatformGetNativeKeyForKey(key); return _glfwPlatformGetNativeKeyName(native_key); } GLFWAPI int glfwGetNativeKeyForKey(uint32_t key) { _GLFW_REQUIRE_INIT_OR_RETURN(-1); return _glfwPlatformGetNativeKeyForKey(key); } GLFWAPI GLFWKeyAction glfwGetKey(GLFWwindow* handle, uint32_t key) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(GLFW_RELEASE); if (!key) return GLFW_RELEASE; int current_action = GLFW_RELEASE; const unsigned sz = arraysz(window->activated_keys); int idx = -1; for (unsigned i = 0; i < sz; i++) { if (window->activated_keys[i].key == key) { idx = i; current_action = window->activated_keys[i].action; break; } } if (current_action == _GLFW_STICK) { // Sticky mode: release key now GLFWkeyevent ev = {0}; set_key_action(window, &ev, GLFW_RELEASE, idx); current_action = GLFW_PRESS; } return current_action; } GLFWAPI int glfwGetMouseButton(GLFWwindow* handle, int button) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(GLFW_RELEASE); if (button < GLFW_MOUSE_BUTTON_1 || button > GLFW_MOUSE_BUTTON_LAST) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid mouse button %i", button); return GLFW_RELEASE; } if (window->mouseButtons[button] == _GLFW_STICK) { // Sticky mode: release mouse button now window->mouseButtons[button] = GLFW_RELEASE; return GLFW_PRESS; } return (int) window->mouseButtons[button]; } GLFWAPI void glfwGetCursorPos(GLFWwindow* handle, double* xpos, double* ypos) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); if (xpos) *xpos = 0; if (ypos) *ypos = 0; _GLFW_REQUIRE_INIT(); if (window->cursorMode == GLFW_CURSOR_DISABLED) { if (xpos) *xpos = window->virtualCursorPosX; if (ypos) *ypos = window->virtualCursorPosY; } else _glfwPlatformGetCursorPos(window, xpos, ypos); } GLFWAPI void glfwSetCursorPos(GLFWwindow* handle, double xpos, double ypos) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); if (xpos != xpos || xpos < -DBL_MAX || xpos > DBL_MAX || ypos != ypos || ypos < -DBL_MAX || ypos > DBL_MAX) { _glfwInputError(GLFW_INVALID_VALUE, "Invalid cursor position %f %f", xpos, ypos); return; } if (!_glfwPlatformWindowFocused(window)) return; if (window->cursorMode == GLFW_CURSOR_DISABLED) { // Only update the accumulated position if the cursor is disabled window->virtualCursorPosX = xpos; window->virtualCursorPosY = ypos; } else { // Update system cursor position _glfwPlatformSetCursorPos(window, xpos, ypos); } } GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot, int count) { _GLFWcursor* cursor; assert(image != NULL); assert(count > 0); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); cursor = calloc(1, sizeof(_GLFWcursor)); cursor->next = _glfw.cursorListHead; _glfw.cursorListHead = cursor; if (!_glfwPlatformCreateCursor(cursor, image, xhot, yhot, count)) { glfwDestroyCursor((GLFWcursor*) cursor); return NULL; } return (GLFWcursor*) cursor; } GLFWAPI GLFWcursor* glfwCreateStandardCursor(GLFWCursorShape shape) { _GLFWcursor* cursor; _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (shape >= GLFW_INVALID_CURSOR) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid standard cursor: %d", shape); return NULL; } cursor = calloc(1, sizeof(_GLFWcursor)); cursor->next = _glfw.cursorListHead; _glfw.cursorListHead = cursor; if (!_glfwPlatformCreateStandardCursor(cursor, shape)) { glfwDestroyCursor((GLFWcursor*) cursor); return NULL; } return (GLFWcursor*) cursor; } GLFWAPI void glfwDestroyCursor(GLFWcursor* handle) { _GLFWcursor* cursor = (_GLFWcursor*) handle; _GLFW_REQUIRE_INIT(); if (cursor == NULL) return; // Make sure the cursor is not being used by any window { _GLFWwindow* window; for (window = _glfw.windowListHead; window; window = window->next) { if (window->cursor == cursor) glfwSetCursor((GLFWwindow*) window, NULL); } } _glfwPlatformDestroyCursor(cursor); // Unlink cursor from global linked list { _GLFWcursor** prev = &_glfw.cursorListHead; while (*prev != cursor) prev = &((*prev)->next); *prev = cursor->next; } free(cursor); } GLFWAPI void glfwSetCursor(GLFWwindow* windowHandle, GLFWcursor* cursorHandle) { _GLFWwindow* window = (_GLFWwindow*) windowHandle; _GLFWcursor* cursor = (_GLFWcursor*) cursorHandle; assert(window != NULL); _GLFW_REQUIRE_INIT(); window->cursor = cursor; _glfwPlatformSetCursor(window, cursor); } GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* handle, GLFWkeyboardfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.keyboard, cbfun); return cbfun; } GLFWAPI void glfwUpdateIMEState(GLFWwindow* handle, const GLFWIMEUpdateEvent *ev) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); #if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) || defined(_GLFW_COCOA) _glfwPlatformUpdateIMEState(window, ev); #else (void)window; (void)which; (void)a; (void)b; (void)c; (void)d; #endif } GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* handle, GLFWmousebuttonfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.mouseButton, cbfun); return cbfun; } GLFWAPI GLFWcursorposfun glfwSetCursorPosCallback(GLFWwindow* handle, GLFWcursorposfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.cursorPos, cbfun); return cbfun; } GLFWAPI GLFWcursorenterfun glfwSetCursorEnterCallback(GLFWwindow* handle, GLFWcursorenterfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.cursorEnter, cbfun); return cbfun; } GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* handle, GLFWscrollfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.scroll, cbfun); return cbfun; } GLFWAPI GLFWdropfun glfwSetDropCallback(GLFWwindow* handle, GLFWdropfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.drop, cbfun); return cbfun; } GLFWAPI int glfwJoystickPresent(int jid) { _GLFWjoystick* js; assert(jid >= GLFW_JOYSTICK_1); assert(jid <= GLFW_JOYSTICK_LAST); _GLFW_REQUIRE_INIT_OR_RETURN(false); if (jid < 0 || jid > GLFW_JOYSTICK_LAST) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid joystick ID %i", jid); return false; } if (!initJoysticks()) return false; js = _glfw.joysticks + jid; if (!js->present) return false; return _glfwPlatformPollJoystick(js, _GLFW_POLL_PRESENCE); } GLFWAPI const float* glfwGetJoystickAxes(int jid, int* count) { _GLFWjoystick* js; assert(jid >= GLFW_JOYSTICK_1); assert(jid <= GLFW_JOYSTICK_LAST); assert(count != NULL); *count = 0; _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (jid < 0 || jid > GLFW_JOYSTICK_LAST) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid joystick ID %i", jid); return NULL; } if (!initJoysticks()) return NULL; js = _glfw.joysticks + jid; if (!js->present) return NULL; if (!_glfwPlatformPollJoystick(js, _GLFW_POLL_AXES)) return NULL; *count = js->axisCount; return js->axes; } GLFWAPI const unsigned char* glfwGetJoystickButtons(int jid, int* count) { _GLFWjoystick* js; assert(jid >= GLFW_JOYSTICK_1); assert(jid <= GLFW_JOYSTICK_LAST); assert(count != NULL); *count = 0; _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (jid < 0 || jid > GLFW_JOYSTICK_LAST) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid joystick ID %i", jid); return NULL; } if (!initJoysticks()) return NULL; js = _glfw.joysticks + jid; if (!js->present) return NULL; if (!_glfwPlatformPollJoystick(js, _GLFW_POLL_BUTTONS)) return NULL; if (_glfw.hints.init.hatButtons) *count = js->buttonCount + js->hatCount * 4; else *count = js->buttonCount; return js->buttons; } GLFWAPI const unsigned char* glfwGetJoystickHats(int jid, int* count) { _GLFWjoystick* js; assert(jid >= GLFW_JOYSTICK_1); assert(jid <= GLFW_JOYSTICK_LAST); assert(count != NULL); *count = 0; _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (jid < 0 || jid > GLFW_JOYSTICK_LAST) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid joystick ID %i", jid); return NULL; } if (!initJoysticks()) return NULL; js = _glfw.joysticks + jid; if (!js->present) return NULL; if (!_glfwPlatformPollJoystick(js, _GLFW_POLL_BUTTONS)) return NULL; *count = js->hatCount; return js->hats; } GLFWAPI const char* glfwGetJoystickName(int jid) { _GLFWjoystick* js; assert(jid >= GLFW_JOYSTICK_1); assert(jid <= GLFW_JOYSTICK_LAST); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (jid < 0 || jid > GLFW_JOYSTICK_LAST) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid joystick ID %i", jid); return NULL; } if (!initJoysticks()) return NULL; js = _glfw.joysticks + jid; if (!js->present) return NULL; if (!_glfwPlatformPollJoystick(js, _GLFW_POLL_PRESENCE)) return NULL; return js->name; } GLFWAPI const char* glfwGetJoystickGUID(int jid) { _GLFWjoystick* js; assert(jid >= GLFW_JOYSTICK_1); assert(jid <= GLFW_JOYSTICK_LAST); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (jid < 0 || jid > GLFW_JOYSTICK_LAST) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid joystick ID %i", jid); return NULL; } if (!initJoysticks()) return NULL; js = _glfw.joysticks + jid; if (!js->present) return NULL; if (!_glfwPlatformPollJoystick(js, _GLFW_POLL_PRESENCE)) return NULL; return js->guid; } GLFWAPI void glfwSetJoystickUserPointer(int jid, void* pointer) { _GLFWjoystick* js; assert(jid >= GLFW_JOYSTICK_1); assert(jid <= GLFW_JOYSTICK_LAST); _GLFW_REQUIRE_INIT(); js = _glfw.joysticks + jid; if (!js->present) return; js->userPointer = pointer; } GLFWAPI void* glfwGetJoystickUserPointer(int jid) { _GLFWjoystick* js; assert(jid >= GLFW_JOYSTICK_1); assert(jid <= GLFW_JOYSTICK_LAST); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); js = _glfw.joysticks + jid; if (!js->present) return NULL; return js->userPointer; } GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (!initJoysticks()) return NULL; _GLFW_SWAP_POINTERS(_glfw.callbacks.joystick, cbfun); return cbfun; } GLFWAPI int glfwUpdateGamepadMappings(const char* string) { int jid; const char* c = string; assert(string != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(false); while (*c) { if ((*c >= '0' && *c <= '9') || (*c >= 'a' && *c <= 'f') || (*c >= 'A' && *c <= 'F')) { char line[1024]; const size_t length = strcspn(c, "\r\n"); if (length < sizeof(line)) { _GLFWmapping mapping = {{0}}; memcpy(line, c, length); line[length] = '\0'; if (parseMapping(&mapping, line)) { _GLFWmapping* previous = findMapping(mapping.guid); if (previous) *previous = mapping; else { _glfw.mappingCount++; _glfw.mappings = realloc(_glfw.mappings, sizeof(_GLFWmapping) * _glfw.mappingCount); _glfw.mappings[_glfw.mappingCount - 1] = mapping; } } } c += length; } else { c += strcspn(c, "\r\n"); c += strspn(c, "\r\n"); } } for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) { _GLFWjoystick* js = _glfw.joysticks + jid; if (js->present) js->mapping = findValidMapping(js); } return true; } GLFWAPI int glfwJoystickIsGamepad(int jid) { _GLFWjoystick* js; assert(jid >= GLFW_JOYSTICK_1); assert(jid <= GLFW_JOYSTICK_LAST); _GLFW_REQUIRE_INIT_OR_RETURN(false); if (jid < 0 || jid > GLFW_JOYSTICK_LAST) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid joystick ID %i", jid); return false; } if (!initJoysticks()) return false; js = _glfw.joysticks + jid; if (!js->present) return false; if (!_glfwPlatformPollJoystick(js, _GLFW_POLL_PRESENCE)) return false; return js->mapping != NULL; } GLFWAPI const char* glfwGetGamepadName(int jid) { _GLFWjoystick* js; assert(jid >= GLFW_JOYSTICK_1); assert(jid <= GLFW_JOYSTICK_LAST); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (jid < 0 || jid > GLFW_JOYSTICK_LAST) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid joystick ID %i", jid); return NULL; } if (!initJoysticks()) return NULL; js = _glfw.joysticks + jid; if (!js->present) return NULL; if (!_glfwPlatformPollJoystick(js, _GLFW_POLL_PRESENCE)) return NULL; if (!js->mapping) return NULL; return js->mapping->name; } GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state) { int i; _GLFWjoystick* js; assert(jid >= GLFW_JOYSTICK_1); assert(jid <= GLFW_JOYSTICK_LAST); assert(state != NULL); memset(state, 0, sizeof(GLFWgamepadstate)); _GLFW_REQUIRE_INIT_OR_RETURN(false); if (jid < 0 || jid > GLFW_JOYSTICK_LAST) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid joystick ID %i", jid); return false; } if (!initJoysticks()) return false; js = _glfw.joysticks + jid; if (!js->present) return false; if (!_glfwPlatformPollJoystick(js, _GLFW_POLL_ALL)) return false; if (!js->mapping) return false; for (i = 0; i <= GLFW_GAMEPAD_BUTTON_LAST; i++) { const _GLFWmapelement* e = js->mapping->buttons + i; if (e->type == _GLFW_JOYSTICK_AXIS) { const float value = js->axes[e->index] * e->axisScale + e->axisOffset; // HACK: This should be baked into the value transform // TODO: Bake into transform when implementing output modifiers if (e->axisOffset < 0 || (e->axisOffset == 0 && e->axisScale > 0)) { if (value >= 0.f) state->buttons[i] = GLFW_PRESS; } else { if (value <= 0.f) state->buttons[i] = GLFW_PRESS; } } else if (e->type == _GLFW_JOYSTICK_HATBIT) { const unsigned int hat = e->index >> 4; const unsigned int bit = e->index & 0xf; if (js->hats[hat] & bit) state->buttons[i] = GLFW_PRESS; } else if (e->type == _GLFW_JOYSTICK_BUTTON) state->buttons[i] = js->buttons[e->index]; } for (i = 0; i <= GLFW_GAMEPAD_AXIS_LAST; i++) { const _GLFWmapelement* e = js->mapping->axes + i; if (e->type == _GLFW_JOYSTICK_AXIS) { const float value = js->axes[e->index] * e->axisScale + e->axisOffset; state->axes[i] = fminf(fmaxf(value, -1.f), 1.f); } else if (e->type == _GLFW_JOYSTICK_HATBIT) { const unsigned int hat = e->index >> 4; const unsigned int bit = e->index & 0xf; if (js->hats[hat] & bit) state->axes[i] = 1.f; else state->axes[i] = -1.f; } else if (e->type == _GLFW_JOYSTICK_BUTTON) state->axes[i] = js->buttons[e->index] * 2.f - 1.f; } return true; } void _glfw_free_clipboard_data(_GLFWClipboardData *cd) { if (cd->mime_types) { for (size_t i = 0; i < cd->num_mime_types; i++) free((void*)cd->mime_types[i]); free((void*)cd->mime_types); } memset(cd, 0, sizeof(cd[0])); } GLFWAPI void glfwGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) { _GLFW_REQUIRE_INIT(); _glfwPlatformGetClipboard(clipboard_type, mime_type, write_data, object); } GLFWAPI void glfwSetClipboardDataTypes(GLFWClipboardType clipboard_type, const char* const *mime_types, size_t num_mime_types, GLFWclipboarditerfun get_data) { assert(mime_types != NULL); assert(get_data != NULL); _GLFW_REQUIRE_INIT(); _GLFWClipboardData *cd = NULL; switch(clipboard_type) { case GLFW_CLIPBOARD: cd = &_glfw.clipboard; break; case GLFW_PRIMARY_SELECTION: cd = &_glfw.primary; break; } _glfw_free_clipboard_data(cd); cd->get_data = get_data; cd->mime_types = calloc(num_mime_types, sizeof(char*)); cd->num_mime_types = 0; cd->ctype = clipboard_type; for (size_t i = 0; i < num_mime_types; i++) { if (mime_types[i]) { cd->mime_types[cd->num_mime_types++] = _glfw_strdup(mime_types[i]); } } _glfwPlatformSetClipboard(clipboard_type); } GLFWAPI monotonic_t glfwGetTime(void) { _GLFW_REQUIRE_INIT_OR_RETURN(0); return monotonic(); } kitty-0.41.1/glfw/internal.h0000664000175000017510000007721114773370543015243 0ustar nileshnilesh//======================================================================== // GLFW 3.4 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #pragma once #include "../kitty/monotonic.h" #if defined(_GLFW_USE_CONFIG_H) #include "glfw_config.h" #endif #define arraysz(x) (sizeof(x)/sizeof(x[0])) #define MAX(x, y) __extension__ ({ \ __typeof__ (x) a = (x); __typeof__ (y) b = (y); \ a > b ? a : b;}) #if defined(GLFW_INCLUDE_GLCOREARB) || \ defined(GLFW_INCLUDE_ES1) || \ defined(GLFW_INCLUDE_ES2) || \ defined(GLFW_INCLUDE_ES3) || \ defined(GLFW_INCLUDE_ES31) || \ defined(GLFW_INCLUDE_ES32) || \ defined(GLFW_INCLUDE_NONE) || \ defined(GLFW_INCLUDE_GLEXT) || \ defined(GLFW_INCLUDE_GLU) || \ defined(GLFW_INCLUDE_VULKAN) || \ defined(GLFW_DLL) #error "You must not define any header option macros when compiling GLFW" #endif #define GLFW_INCLUDE_NONE #include "glfw3.h" #define EGL_PRESENT_OPAQUE_EXT 0x31df #define _GLFW_INSERT_FIRST 0 #define _GLFW_INSERT_LAST 1 #define _GLFW_POLL_PRESENCE 0 #define _GLFW_POLL_AXES 1 #define _GLFW_POLL_BUTTONS 2 #define _GLFW_POLL_ALL (_GLFW_POLL_AXES | _GLFW_POLL_BUTTONS) #define _GLFW_MESSAGE_SIZE 1024 typedef unsigned long long GLFWid; typedef struct _GLFWerror _GLFWerror; typedef struct _GLFWinitconfig _GLFWinitconfig; typedef struct _GLFWwndconfig _GLFWwndconfig; typedef struct _GLFWctxconfig _GLFWctxconfig; typedef struct _GLFWfbconfig _GLFWfbconfig; typedef struct _GLFWcontext _GLFWcontext; typedef struct _GLFWwindow _GLFWwindow; typedef struct _GLFWlibrary _GLFWlibrary; typedef struct _GLFWmonitor _GLFWmonitor; typedef struct _GLFWcursor _GLFWcursor; typedef struct _GLFWmapelement _GLFWmapelement; typedef struct _GLFWmapping _GLFWmapping; typedef struct _GLFWjoystick _GLFWjoystick; typedef struct _GLFWtls _GLFWtls; typedef struct _GLFWmutex _GLFWmutex; typedef void (* _GLFWmakecontextcurrentfun)(_GLFWwindow*); typedef void (* _GLFWswapbuffersfun)(_GLFWwindow*); typedef void (* _GLFWswapintervalfun)(int); typedef int (* _GLFWextensionsupportedfun)(const char*); typedef GLFWglproc (* _GLFWgetprocaddressfun)(const char*); typedef void (* _GLFWdestroycontextfun)(_GLFWwindow*); #define GL_VERSION 0x1F02 #define GL_NONE 0 #define GL_COLOR_BUFFER_BIT 0x00004000 #define GL_UNSIGNED_BYTE 0x1401 #define GL_EXTENSIONS 0x1F03 #define GL_NUM_EXTENSIONS 0x821d #define GL_CONTEXT_FLAGS 0x821e #define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x00000001 #define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002 #define GL_CONTEXT_PROFILE_MASK 0x9126 #define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 #define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001 #define GL_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 #define GL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 #define GL_NO_RESET_NOTIFICATION_ARB 0x8261 #define GL_CONTEXT_RELEASE_BEHAVIOR 0x82fb #define GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH 0x82fc #define GL_CONTEXT_FLAG_NO_ERROR_BIT_KHR 0x00000008 #define MAX(x, y) __extension__ ({ \ __typeof__ (x) a = (x); __typeof__ (y) b = (y); \ a > b ? a : b;}) #define MIN(x, y) __extension__ ({ \ __typeof__ (x) a = (x); __typeof__ (y) b = (y); \ a < b ? a : b;}) typedef int GLint; typedef unsigned int GLuint; typedef unsigned int GLenum; typedef unsigned int GLbitfield; typedef unsigned char GLubyte; typedef void (APIENTRY * PFNGLCLEARPROC)(GLbitfield); typedef const GLubyte* (APIENTRY * PFNGLGETSTRINGPROC)(GLenum); typedef void (APIENTRY * PFNGLGETINTEGERVPROC)(GLenum,GLint*); typedef const GLubyte* (APIENTRY * PFNGLGETSTRINGIPROC)(GLenum,GLuint); #define VK_NULL_HANDLE 0 typedef void* VkInstance; typedef void* VkPhysicalDevice; typedef uint64_t VkSurfaceKHR; typedef uint32_t VkFlags; typedef uint32_t VkBool32; typedef enum VkStructureType { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR = 1000004000, VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR = 1000005000, VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000, VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000, VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000, VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT = 1000217000, VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF } VkStructureType; typedef enum VkResult { VK_SUCCESS = 0, VK_NOT_READY = 1, VK_TIMEOUT = 2, VK_EVENT_SET = 3, VK_EVENT_RESET = 4, VK_INCOMPLETE = 5, VK_ERROR_OUT_OF_HOST_MEMORY = -1, VK_ERROR_OUT_OF_DEVICE_MEMORY = -2, VK_ERROR_INITIALIZATION_FAILED = -3, VK_ERROR_DEVICE_LOST = -4, VK_ERROR_MEMORY_MAP_FAILED = -5, VK_ERROR_LAYER_NOT_PRESENT = -6, VK_ERROR_EXTENSION_NOT_PRESENT = -7, VK_ERROR_FEATURE_NOT_PRESENT = -8, VK_ERROR_INCOMPATIBLE_DRIVER = -9, VK_ERROR_TOO_MANY_OBJECTS = -10, VK_ERROR_FORMAT_NOT_SUPPORTED = -11, VK_ERROR_SURFACE_LOST_KHR = -1000000000, VK_SUBOPTIMAL_KHR = 1000001003, VK_ERROR_OUT_OF_DATE_KHR = -1000001004, VK_ERROR_INCOMPATIBLE_DISPLAY_KHR = -1000003001, VK_ERROR_NATIVE_WINDOW_IN_USE_KHR = -1000000001, VK_ERROR_VALIDATION_FAILED_EXT = -1000011001, VK_RESULT_MAX_ENUM = 0x7FFFFFFF } VkResult; typedef struct VkAllocationCallbacks VkAllocationCallbacks; typedef struct VkExtensionProperties { char extensionName[256]; uint32_t specVersion; } VkExtensionProperties; typedef void (APIENTRY * PFN_vkVoidFunction)(void); #if defined(_GLFW_VULKAN_STATIC) PFN_vkVoidFunction vkGetInstanceProcAddr(VkInstance,const char*); VkResult vkEnumerateInstanceExtensionProperties(const char*,uint32_t*,VkExtensionProperties*); #else typedef PFN_vkVoidFunction (APIENTRY * PFN_vkGetInstanceProcAddr)(VkInstance,const char*); typedef VkResult (APIENTRY * PFN_vkEnumerateInstanceExtensionProperties)(const char*,uint32_t*,VkExtensionProperties*); #define vkEnumerateInstanceExtensionProperties _glfw.vk.EnumerateInstanceExtensionProperties #define vkGetInstanceProcAddr _glfw.vk.GetInstanceProcAddr #endif #if defined(_GLFW_COCOA) #include "cocoa_platform.h" #elif defined(_GLFW_WIN32) #include "win32_platform.h" #elif defined(_GLFW_X11) #include "x11_platform.h" #elif defined(_GLFW_WAYLAND) #include "wl_platform.h" #elif defined(_GLFW_OSMESA) #include "null_platform.h" #else #error "No supported window creation API selected" #endif #include "egl_context.h" #include "osmesa_context.h" #define remove_i_from_array(array, i, count) { \ (count)--; \ if ((i) < (count)) { \ memmove((array) + (i), (array) + (i) + 1, sizeof((array)[0]) * ((count) - (i))); /* NOLINT(bugprone-sizeof-expression) */ \ }} // Constructs a version number string from the public header macros #define _GLFW_CONCAT_VERSION(m, n, r) #m "." #n "." #r #define _GLFW_MAKE_VERSION(m, n, r) _GLFW_CONCAT_VERSION(m, n, r) #define _GLFW_VERSION_NUMBER _GLFW_MAKE_VERSION(GLFW_VERSION_MAJOR, \ GLFW_VERSION_MINOR, \ GLFW_VERSION_REVISION) // Checks for whether the library has been initialized #define _GLFW_REQUIRE_INIT() \ if (!_glfw.initialized) \ { \ _glfwInputError(GLFW_NOT_INITIALIZED, NULL); \ return; \ } #define _GLFW_REQUIRE_INIT_OR_RETURN(x) \ if (!_glfw.initialized) \ { \ _glfwInputError(GLFW_NOT_INITIALIZED, NULL); \ return x; \ } // Swaps the provided pointers #define _GLFW_SWAP_POINTERS(x, y) \ do{ \ __typeof__(x) t; \ t = x; \ x = y; \ y = t; \ }while(0) // Suppress some pedantic warnings #ifdef __clang__ #define START_ALLOW_CASE_RANGE _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wpedantic\"") #define END_ALLOW_CASE_RANGE _Pragma("clang diagnostic pop") #define ALLOW_UNUSED_RESULT _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wunused-result\"") #define END_ALLOW_UNUSED_RESULT _Pragma("clang diagnostic pop") #else #define START_ALLOW_CASE_RANGE _Pragma("GCC diagnostic ignored \"-Wpedantic\"") #define END_ALLOW_CASE_RANGE _Pragma("GCC diagnostic pop") #define ALLOW_UNUSED_RESULT _Pragma("GCC diagnostic ignored \"-Wunused-result\"") #define END_ALLOW_UNUSED_RESULT _Pragma("GCC diagnostic pop") #endif // dlsym that works with -Wpedantic #define glfw_dlsym(dest, handle, name) do {*(void **)&(dest) = _glfw_dlsym(handle, name);}while (0) // Mark function arguments as unused #define UNUSED __attribute__ ((unused)) // Per-thread error structure // struct _GLFWerror { _GLFWerror* next; int code; char description[_GLFW_MESSAGE_SIZE]; }; // Initialization configuration // // Parameters relating to the initialization of the library // struct _GLFWinitconfig { bool hatButtons; int angleType; bool debugKeyboard; bool debugRendering; struct { bool menubar; bool chdir; } ns; struct { bool ime; } wl; }; // Window configuration // // Parameters relating to the creation of the window but not directly related // to the framebuffer. This is used to pass window creation parameters from // shared code to the platform API. // struct _GLFWwndconfig { int width; int height; const char* title; bool resizable; bool visible; bool decorated; bool focused; bool autoIconify; bool floating; bool maximized; bool centerCursor; bool focusOnShow; bool mousePassthrough; bool scaleToMonitor; int blur_radius; struct { bool retina; int color_space; char frameName[256]; } ns; struct { char className[256]; char instanceName[256]; } x11; struct { char appId[256]; uint32_t bgcolor; } wl; }; // Context configuration // // Parameters relating to the creation of the context but not directly related // to the framebuffer. This is used to pass context creation parameters from // shared code to the platform API. // struct _GLFWctxconfig { int client; int source; int major; int minor; bool forward; bool debug; bool noerror; int profile; int robustness; int release; _GLFWwindow* share; struct { bool offline; } nsgl; }; // Framebuffer configuration // // This describes buffers and their sizes. It also contains // a platform-specific ID used to map back to the backend API object. // // It is used to pass framebuffer parameters from shared code to the platform // API and also to enumerate and select available framebuffer configs. // struct _GLFWfbconfig { int redBits; int greenBits; int blueBits; int alphaBits; int depthBits; int stencilBits; int accumRedBits; int accumGreenBits; int accumBlueBits; int accumAlphaBits; int auxBuffers; bool stereo; int samples; bool sRGB; bool doublebuffer; bool transparent; uintptr_t handle; }; // Context structure // struct _GLFWcontext { int client; int source; int major, minor, revision; bool forward, debug, noerror; int profile; int robustness; int release; PFNGLGETSTRINGIPROC GetStringi; PFNGLGETINTEGERVPROC GetIntegerv; PFNGLGETSTRINGPROC GetString; _GLFWmakecontextcurrentfun makeCurrent; _GLFWswapbuffersfun swapBuffers; _GLFWswapintervalfun swapInterval; _GLFWextensionsupportedfun extensionSupported; _GLFWgetprocaddressfun getProcAddress; _GLFWdestroycontextfun destroy; // This is defined in the context API's context.h _GLFW_PLATFORM_CONTEXT_STATE // This is defined in egl_context.h _GLFWcontextEGL egl; // This is defined in osmesa_context.h _GLFWcontextOSMesa osmesa; }; // Window and context structure // struct _GLFWwindow { struct _GLFWwindow* next; // Window settings and state bool resizable; bool decorated; bool autoIconify; bool floating; bool focusOnShow; bool mousePassthrough; bool shouldClose; void* userPointer; GLFWid id; GLFWvidmode videoMode; _GLFWmonitor* monitor; _GLFWcursor* cursor; int minwidth, minheight; int maxwidth, maxheight; int numer, denom; int widthincr, heightincr; bool stickyKeys; bool stickyMouseButtons; bool lockKeyMods; int cursorMode; char mouseButtons[GLFW_MOUSE_BUTTON_LAST + 1]; GLFWkeyevent activated_keys[16]; // Virtual cursor position when cursor is disabled double virtualCursorPosX, virtualCursorPosY; bool rawMouseMotion; _GLFWcontext context; #ifdef _GLFW_WAYLAND bool swaps_disallowed; #else const bool swaps_disallowed; #endif struct { GLFWwindowposfun pos; GLFWwindowsizefun size; GLFWwindowclosefun close; GLFWwindowrefreshfun refresh; GLFWwindowfocusfun focus; GLFWwindowocclusionfun occlusion; GLFWwindowiconifyfun iconify; GLFWwindowmaximizefun maximize; GLFWframebuffersizefun fbsize; GLFWwindowcontentscalefun scale; GLFWmousebuttonfun mouseButton; GLFWcursorposfun cursorPos; GLFWcursorenterfun cursorEnter; GLFWscrollfun scroll; GLFWkeyboardfun keyboard; GLFWdropfun drop; GLFWliveresizefun liveResize; } callbacks; // This is defined in the window API's platform.h _GLFW_PLATFORM_WINDOW_STATE; }; // Monitor structure // struct _GLFWmonitor { char* name; void* userPointer; // Physical dimensions in millimeters. int widthMM, heightMM; // The window whose video mode is current on this monitor _GLFWwindow* window; GLFWvidmode* modes; int modeCount; GLFWvidmode currentMode; GLFWgammaramp originalRamp; GLFWgammaramp currentRamp; // This is defined in the window API's platform.h _GLFW_PLATFORM_MONITOR_STATE; }; // Cursor structure // struct _GLFWcursor { _GLFWcursor* next; // This is defined in the window API's platform.h _GLFW_PLATFORM_CURSOR_STATE; }; // Gamepad mapping element structure // struct _GLFWmapelement { uint8_t type; uint8_t index; int8_t axisScale; int8_t axisOffset; }; // Gamepad mapping structure // struct _GLFWmapping { char name[128]; char guid[33]; _GLFWmapelement buttons[15]; _GLFWmapelement axes[6]; }; // Joystick structure // struct _GLFWjoystick { bool present; float* axes; int axisCount; unsigned char* buttons; int buttonCount; unsigned char* hats; int hatCount; char* name; void* userPointer; char guid[33]; _GLFWmapping* mapping; // This is defined in the joystick API's joystick.h _GLFW_PLATFORM_JOYSTICK_STATE; }; // Thread local storage structure // struct _GLFWtls { // This is defined in the platform's thread.h _GLFW_PLATFORM_TLS_STATE; }; // Mutex structure // struct _GLFWmutex { // This is defined in the platform's thread.h _GLFW_PLATFORM_MUTEX_STATE; }; typedef struct _GLFWClipboardData { const char** mime_types; size_t num_mime_types; GLFWclipboarditerfun get_data; GLFWClipboardType ctype; } _GLFWClipboardData; // Library global data // struct _GLFWlibrary { bool initialized; struct { _GLFWinitconfig init; _GLFWfbconfig framebuffer; _GLFWwndconfig window; _GLFWctxconfig context; int refreshRate; } hints; _GLFWClipboardData primary, clipboard; _GLFWerror* errorListHead; _GLFWcursor* cursorListHead; _GLFWwindow* windowListHead; GLFWid focusedWindowId; _GLFWmonitor** monitors; int monitorCount; bool joysticksInitialized; _GLFWjoystick joysticks[GLFW_JOYSTICK_LAST + 1]; _GLFWmapping* mappings; int mappingCount; _GLFWtls errorSlot; _GLFWtls contextSlot; _GLFWmutex errorLock; bool ignoreOSKeyboardProcessing; struct { bool available; void* handle; char* extensions[2]; #if !defined(_GLFW_VULKAN_STATIC) PFN_vkEnumerateInstanceExtensionProperties EnumerateInstanceExtensionProperties; PFN_vkGetInstanceProcAddr GetInstanceProcAddr; #endif bool KHR_surface; #if defined(_GLFW_WIN32) bool KHR_win32_surface; #elif defined(_GLFW_COCOA) bool MVK_macos_surface; bool EXT_metal_surface; #elif defined(_GLFW_X11) bool KHR_xlib_surface; bool KHR_xcb_surface; #elif defined(_GLFW_WAYLAND) bool KHR_wayland_surface; #endif } vk; struct { GLFWmonitorfun monitor; GLFWjoystickfun joystick; GLFWapplicationclosefun application_close; GLFWclipboardlostfun clipboard_lost; GLFWsystemcolorthemechangefun system_color_theme_change; GLFWdrawtextfun draw_text; GLFWcurrentselectionfun get_current_selection; GLFWhascurrentselectionfun has_current_selection; GLFWimecursorpositionfun get_ime_cursor_position; } callbacks; // This is defined in the window API's platform.h _GLFW_PLATFORM_LIBRARY_WINDOW_STATE; // This is defined in the context API's context.h _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE // This is defined in the platform's joystick.h _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE // This is defined in egl_context.h _GLFWlibraryEGL egl; // This is defined in osmesa_context.h _GLFWlibraryOSMesa osmesa; }; // Global state shared between compilation units of GLFW // extern _GLFWlibrary _glfw; ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// int _glfwPlatformInit(bool*); void _glfwPlatformTerminate(void); const char* _glfwPlatformGetVersionString(void); void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos); void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos); void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode); void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, bool enabled); bool _glfwPlatformRawMouseMotionSupported(void); int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot, int count); int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape); void _glfwPlatformDestroyCursor(_GLFWcursor* cursor); void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor); const char* _glfwPlatformGetNativeKeyName(int native_key); int _glfwPlatformGetNativeKeyForKey(uint32_t key); void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor); void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos); void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor, float* xscale, float* yscale); void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor, int* xpos, int* ypos, int *width, int *height); GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count); bool _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode); bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp); void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp); void _glfwPlatformSetClipboard(GLFWClipboardType t); void _glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object); bool _glfwPlatformInitJoysticks(void); void _glfwPlatformTerminateJoysticks(void); int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode); void _glfwPlatformUpdateGamepadGUID(char* guid); int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig); void _glfwPlatformDestroyWindow(_GLFWwindow* window); void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title); void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const GLFWimage* images); void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos); void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos); void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height); void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height); void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, int minwidth, int minheight, int maxwidth, int maxheight); void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom); void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr); void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height); void _glfwInputLiveResize(_GLFWwindow* window, bool started); void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom); void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, float* xscale, float* yscale); monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window); void _glfwPlatformIconifyWindow(_GLFWwindow* window); void _glfwPlatformRestoreWindow(_GLFWwindow* window); void _glfwPlatformMaximizeWindow(_GLFWwindow* window); void _glfwPlatformShowWindow(_GLFWwindow* window); void _glfwPlatformHideWindow(_GLFWwindow* window); void _glfwPlatformRequestWindowAttention(_GLFWwindow* window); int _glfwPlatformWindowBell(_GLFWwindow* window); void _glfwPlatformFocusWindow(_GLFWwindow* window); void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate); bool _glfwPlatformToggleFullscreen(_GLFWwindow *w, unsigned int flags); bool _glfwPlatformIsFullscreen(_GLFWwindow *w, unsigned int flags); int _glfwPlatformWindowFocused(_GLFWwindow* window); int _glfwPlatformWindowOccluded(_GLFWwindow* window); int _glfwPlatformWindowIconified(_GLFWwindow* window); int _glfwPlatformWindowVisible(_GLFWwindow* window); int _glfwPlatformWindowMaximized(_GLFWwindow* window); int _glfwPlatformWindowHovered(_GLFWwindow* window); int _glfwPlatformFramebufferTransparent(_GLFWwindow* window); float _glfwPlatformGetWindowOpacity(_GLFWwindow* window); void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled); void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled); void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled); void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled); void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity); void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev); void _glfwPlatformChangeCursorTheme(void); void _glfwPlatformPollEvents(void); void _glfwPlatformWaitEvents(void); void _glfwPlatformWaitEventsTimeout(monotonic_t timeout); void _glfwPlatformPostEmptyEvent(void); EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs); EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void); EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window); void _glfwPlatformGetRequiredInstanceExtensions(char** extensions); int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhysicalDevice device, uint32_t queuefamily); VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, _GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface); bool _glfwPlatformCreateTls(_GLFWtls* tls); void _glfwPlatformDestroyTls(_GLFWtls* tls); void* _glfwPlatformGetTls(_GLFWtls* tls); void _glfwPlatformSetTls(_GLFWtls* tls, void* value); bool _glfwPlatformCreateMutex(_GLFWmutex* mutex); void _glfwPlatformDestroyMutex(_GLFWmutex* mutex); void _glfwPlatformLockMutex(_GLFWmutex* mutex); void _glfwPlatformUnlockMutex(_GLFWmutex* mutex); ////////////////////////////////////////////////////////////////////////// ////// GLFW event API ////// ////////////////////////////////////////////////////////////////////////// void _glfwInputWindowFocus(_GLFWwindow* window, bool focused); void _glfwInputWindowOcclusion(_GLFWwindow* window, bool occluded); void _glfwInputWindowPos(_GLFWwindow* window, int xpos, int ypos); void _glfwInputWindowSize(_GLFWwindow* window, int width, int height); void _glfwInputFramebufferSize(_GLFWwindow* window, int width, int height); void _glfwInputWindowContentScale(_GLFWwindow* window, float xscale, float yscale); void _glfwInputWindowIconify(_GLFWwindow* window, bool iconified); void _glfwInputWindowMaximize(_GLFWwindow* window, bool maximized); void _glfwInputWindowDamage(_GLFWwindow* window); void _glfwInputWindowCloseRequest(_GLFWwindow* window); void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor); void _glfwInputKeyboard(_GLFWwindow *window, GLFWkeyevent *ev); void _glfwInputClipboardLost(GLFWClipboardType which); void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags, int mods); void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods); void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos); void _glfwInputCursorEnter(_GLFWwindow* window, bool entered); int _glfwInputDrop(_GLFWwindow* window, const char *mime, const char *text, size_t sz); void _glfwInputColorScheme(GLFWColorScheme, bool); void _glfwPlatformInputColorScheme(GLFWColorScheme); void _glfwInputJoystick(_GLFWjoystick* js, int event); void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value); void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value); void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value); void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement); void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window); #if defined(__GNUC__) || defined(__clang__) void _glfwInputError(int code, const char* format, ...) __attribute__((format(printf, 2, 3))); void _glfwDebug(const char* format, ...) __attribute__((format(printf, 1, 2))); #else void _glfwInputError(int code, const char* format, ...); void _glfwDebug(const char* format, ...); #endif #ifdef DEBUG_EVENT_LOOP #define EVDBG(...) _glfwDebug(__VA_ARGS__) #else #define EVDBG(...) #endif ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// bool _glfwStringInExtensionString(const char* string, const char* extensions); bool _glfwRefreshContextAttribs(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig); bool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig); const GLFWvidmode* _glfwChooseVideoMode(_GLFWmonitor* monitor, const GLFWvidmode* desired); int _glfwCompareVideoModes(const GLFWvidmode* first, const GLFWvidmode* second); _GLFWmonitor* _glfwAllocMonitor(const char* name, int widthMM, int heightMM); void _glfwFreeMonitor(_GLFWmonitor* monitor); void _glfwAllocGammaArrays(GLFWgammaramp* ramp, unsigned int size); void _glfwFreeGammaArrays(GLFWgammaramp* ramp); void _glfwSplitBPP(int bpp, int* red, int* green, int* blue); _GLFWjoystick* _glfwAllocJoystick(const char* name, const char* guid, int axisCount, int buttonCount, int hatCount); void _glfwFreeJoystick(_GLFWjoystick* js); const char* _glfwGetKeyName(int key); void _glfwCenterCursorInContentArea(_GLFWwindow* window); bool _glfwInitVulkan(int mode); void _glfwTerminateVulkan(void); const char* _glfwGetVulkanResultString(VkResult result); _GLFWwindow* _glfwFocusedWindow(void); _GLFWwindow* _glfwWindowForId(GLFWid id); void _glfwPlatformRunMainLoop(GLFWtickcallback, void*); void _glfwPlatformStopMainLoop(void); unsigned long long _glfwPlatformAddTimer(monotonic_t interval, bool repeats, GLFWuserdatafun callback, void *callback_data, GLFWuserdatafun free_callback); void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled); void _glfwPlatformRemoveTimer(unsigned long long timer_id); int _glfwPlatformSetWindowBlur(_GLFWwindow* handle, int value); char* _glfw_strdup(const char* source); void _glfw_free_clipboard_data(_GLFWClipboardData *cd); #define debug_rendering(...) if (_glfw.hints.init.debugRendering) { timed_debug_print(__VA_ARGS__); } #define debug_input(...) if (_glfw.hints.init.debugKeyboard) { timed_debug_print(__VA_ARGS__); } kitty-0.41.1/glfw/kwin-blur-v1.xml0000664000175000017510000000175514773370543016236 0ustar nileshnilesh kitty-0.41.1/glfw/linux_desktop_settings.c0000664000175000017510000002232214773370543020223 0ustar nileshnilesh/* * linux_cursor_settings.c * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "linux_desktop_settings.h" #include #include #include #define DESKTOP_SERVICE "org.freedesktop.portal.Desktop" #define DESKTOP_PATH "/org/freedesktop/portal/desktop" #define DESKTOP_INTERFACE "org.freedesktop.portal.Settings" #define GNOME_DESKTOP_NAMESPACE "org.gnome.desktop.interface" #define FDO_DESKTOP_NAMESPACE "org.freedesktop.appearance" static const char* supported_namespaces[2] = {FDO_DESKTOP_NAMESPACE, GNOME_DESKTOP_NAMESPACE}; #define FDO_APPEARANCE_KEY "color-scheme" static char theme_name[128] = {0}; static int theme_size = -1; static GLFWColorScheme appearance = GLFW_COLOR_SCHEME_NO_PREFERENCE; static bool cursor_theme_changed = false, appearance_initialized = false; #define HANDLER(name) static void name(DBusMessage *msg, const char* errmsg, void *data) { \ (void)data; \ if (errmsg) { \ _glfwInputError(GLFW_PLATFORM_ERROR, "%s: failed with error: %s", #name, errmsg); \ return; \ } HANDLER(get_color_scheme) uint32_t val; DBusMessageIter iter, variant_iter; if (!dbus_message_iter_init(msg, &iter)) return; dbus_message_iter_recurse(&iter, &variant_iter); int type = dbus_message_iter_get_arg_type(&variant_iter); if (type != DBUS_TYPE_UINT32) { _glfwInputError(GLFW_PLATFORM_ERROR, "ReadOne for color-scheme did not return a uint32"); return; } dbus_message_iter_get_basic(&variant_iter, &val); if (val < 3) appearance = val; } GLFWColorScheme glfw_current_system_color_theme(bool query_if_unintialized) { if (!appearance_initialized && query_if_unintialized) { appearance_initialized = true; DBusConnection *session_bus = glfw_dbus_session_bus(); if (session_bus) { const char *namespace = FDO_DESKTOP_NAMESPACE, *key = FDO_APPEARANCE_KEY; glfw_dbus_call_blocking_method(session_bus, DESKTOP_SERVICE, DESKTOP_PATH, DESKTOP_INTERFACE, "ReadOne", DBUS_TIMEOUT_USE_DEFAULT, get_color_scheme, NULL, DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID); } } return appearance; } static void process_fdo_setting(const char *key, DBusMessageIter *value) { if (strcmp(key, FDO_APPEARANCE_KEY) == 0) { if (dbus_message_iter_get_arg_type(value) == DBUS_TYPE_UINT32) { uint32_t val; dbus_message_iter_get_basic(value, &val); if (val > 2) val = 0; if (!appearance_initialized) { appearance_initialized = true; if (val != appearance) { appearance = val; _glfwInputColorScheme(appearance, true); } } } } } static void process_gnome_setting(const char *key, DBusMessageIter *value) { if (strcmp(key, "cursor-size") == 0) { if (dbus_message_iter_get_arg_type(value) == DBUS_TYPE_INT32) { int32_t sz; dbus_message_iter_get_basic(value, &sz); if (sz > 0 && sz != theme_size) { theme_size = sz; cursor_theme_changed = true; } } } else if (strcmp(key, "cursor-theme") == 0) { if (dbus_message_iter_get_arg_type(value) == DBUS_TYPE_STRING) { const char *name; dbus_message_iter_get_basic(value, &name); if (name) { strncpy(theme_name, name, sizeof(theme_name) - 1); cursor_theme_changed = true; } } } } static void process_settings_dict(DBusMessageIter *array_iter, void(process_setting)(const char *, DBusMessageIter*)) { DBusMessageIter item_iter, value_iter; while (dbus_message_iter_get_arg_type(array_iter) == DBUS_TYPE_DICT_ENTRY) { dbus_message_iter_recurse(array_iter, &item_iter); if (dbus_message_iter_get_arg_type(&item_iter) == DBUS_TYPE_STRING) { const char *key; dbus_message_iter_get_basic(&item_iter, &key); if (dbus_message_iter_next(&item_iter) && dbus_message_iter_get_arg_type(&item_iter) == DBUS_TYPE_VARIANT) { dbus_message_iter_recurse(&item_iter, &value_iter); process_setting(key, &value_iter); } } if (!dbus_message_iter_next(array_iter)) break; } } HANDLER(process_desktop_settings) cursor_theme_changed = false; DBusMessageIter root, array, item, settings; dbus_message_iter_init(msg, &root); #define die(...) { _glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); return; } if (dbus_message_iter_get_arg_type(&root) != DBUS_TYPE_ARRAY) die("Reply to request for desktop settings is not an array"); dbus_message_iter_recurse(&root, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { dbus_message_iter_recurse(&array, &item); if (dbus_message_iter_get_arg_type(&item) == DBUS_TYPE_STRING) { const char *namespace; dbus_message_iter_get_basic(&item, &namespace); if (dbus_message_iter_next(&item) && dbus_message_iter_get_arg_type(&item) == DBUS_TYPE_ARRAY) { dbus_message_iter_recurse(&item, &settings); if (strcmp(namespace, FDO_DESKTOP_NAMESPACE) == 0) { process_settings_dict(&settings, process_fdo_setting); } else if (strcmp(namespace, GNOME_DESKTOP_NAMESPACE) == 0) { process_settings_dict(&settings, process_gnome_setting); } } } if (!dbus_message_iter_next(&array)) break; } #undef die #ifndef _GLFW_X11 if (cursor_theme_changed) _glfwPlatformChangeCursorTheme(); #endif } #undef HANDLER static bool read_desktop_settings(DBusConnection *session_bus) { RAII_MSG(msg, dbus_message_new_method_call(DESKTOP_SERVICE, DESKTOP_PATH, DESKTOP_INTERFACE, "ReadAll")); if (!msg) return false; DBusMessageIter iter, array_iter; dbus_message_iter_init_append(msg, &iter); if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &array_iter)) { return false; } for (unsigned i = 0; i < arraysz(supported_namespaces); ++i) { if (!dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, &supported_namespaces[i])) return false; } if (!dbus_message_iter_close_container(&iter, &array_iter)) { return false; } return call_method_with_msg(session_bus, msg, DBUS_TIMEOUT_USE_DEFAULT, process_desktop_settings, NULL, false); } void glfw_current_cursor_theme(const char **theme, int *size) { *theme = theme_name[0] ? theme_name : NULL; *size = (theme_size > 0 && theme_size < 2048) ? theme_size : 32; } static void get_cursor_theme_from_env(void) { const char *q = getenv("XCURSOR_THEME"); if (q) strncpy(theme_name, q, sizeof(theme_name)-1); const char *env = getenv("XCURSOR_SIZE"); theme_size = 32; if (env) { const int retval = atoi(env); if (retval > 0 && retval < 2048) theme_size = retval; } } static void on_color_scheme_change(DBusMessage *message) { DBusMessageIter iter[2]; dbus_message_iter_init (message, &iter[0]); int current_type; while ((current_type = dbus_message_iter_get_arg_type (&iter[0])) != DBUS_TYPE_INVALID) { if (current_type == DBUS_TYPE_VARIANT) { dbus_message_iter_recurse(&iter[0], &iter[1]); if (dbus_message_iter_get_arg_type(&iter[1]) == DBUS_TYPE_UINT32) { uint32_t val = 0; dbus_message_iter_get_basic(&iter[1], &val); if (val > 2) val = 0; if (val != appearance) { appearance = val; appearance_initialized = true; _glfwInputColorScheme(appearance, false); } } break; } dbus_message_iter_next(&iter[0]); } } static DBusHandlerResult setting_changed(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data UNUSED) { /* printf("session_bus settings_changed invoked interface: %s member: %s\n", dbus_message_get_interface(msg), dbus_message_get_member(msg)); */ if (dbus_message_is_signal(msg, DESKTOP_INTERFACE, "SettingChanged")) { const char *namespace = NULL, *key = NULL; if (glfw_dbus_get_args(msg, "Failed to get namespace and key from SettingChanged notification signal", DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) { if (strcmp(namespace, FDO_DESKTOP_NAMESPACE) == 0) { if (strcmp(key, FDO_APPEARANCE_KEY) == 0) { on_color_scheme_change(msg); } } } } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } void glfw_initialize_desktop_settings(void) { get_cursor_theme_from_env(); DBusConnection *session_bus = glfw_dbus_session_bus(); if (session_bus) { if (!read_desktop_settings(session_bus)) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to read desktop settings, make sure you have the desktop portal running."); dbus_bus_add_match(session_bus, "type='signal',interface='" DESKTOP_INTERFACE "',member='SettingChanged'", NULL); dbus_connection_add_filter(session_bus, setting_changed, NULL, NULL); } } kitty-0.41.1/glfw/linux_desktop_settings.h0000664000175000017510000000052714773370543020233 0ustar nileshnilesh/* * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "dbus_glfw.h" #include "internal.h" void glfw_initialize_desktop_settings(void); void glfw_current_cursor_theme(const char **theme, int *size); GLFWColorScheme glfw_current_system_color_theme(bool); kitty-0.41.1/glfw/linux_joystick.c0000664000175000017510000003017014773370543016471 0ustar nileshnilesh//======================================================================== // GLFW 3.4 Linux - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2017 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #define _POSIX_C_SOURCE 200809L #include "internal.h" #include #include #include #include #include #include #include #include #include #include #include #ifndef SYN_DROPPED // < v2.6.39 kernel headers // Workaround for CentOS-6, which is supported till 2020-11-30, but still on v2.6.32 #define SYN_DROPPED 3 #endif // Apply an EV_KEY event to the specified joystick // static void handleKeyEvent(_GLFWjoystick* js, int code, int value) { _glfwInputJoystickButton(js, js->linjs.keyMap[code - BTN_MISC], value ? GLFW_PRESS : GLFW_RELEASE); } // Apply an EV_ABS event to the specified joystick // static void handleAbsEvent(_GLFWjoystick* js, int code, int value) { const int index = js->linjs.absMap[code]; if (code >= ABS_HAT0X && code <= ABS_HAT3Y) { static const char stateMap[3][3] = { { GLFW_HAT_CENTERED, GLFW_HAT_UP, GLFW_HAT_DOWN }, { GLFW_HAT_LEFT, GLFW_HAT_LEFT_UP, GLFW_HAT_LEFT_DOWN }, { GLFW_HAT_RIGHT, GLFW_HAT_RIGHT_UP, GLFW_HAT_RIGHT_DOWN }, }; const int hat = (code - ABS_HAT0X) / 2; const int axis = (code - ABS_HAT0X) % 2; int* state = js->linjs.hats[hat]; // NOTE: Looking at several input drivers, it seems all hat events use // -1 for left / up, 0 for centered and 1 for right / down if (value == 0) state[axis] = 0; else if (value < 0) state[axis] = 1; else if (value > 0) state[axis] = 2; _glfwInputJoystickHat(js, index, stateMap[state[0]][state[1]]); } else { const struct input_absinfo* info = &js->linjs.absInfo[code]; float normalized = value; const int range = info->maximum - info->minimum; if (range) { // Normalize to 0.0 -> 1.0 normalized = (normalized - info->minimum) / range; // Normalize to -1.0 -> 1.0 normalized = normalized * 2.0f - 1.0f; } _glfwInputJoystickAxis(js, index, normalized); } } // Poll state of absolute axes // static void pollAbsState(_GLFWjoystick* js) { for (int code = 0; code < ABS_CNT; code++) { if (js->linjs.absMap[code] < 0) continue; struct input_absinfo* info = &js->linjs.absInfo[code]; if (ioctl(js->linjs.fd, EVIOCGABS(code), info) < 0) continue; handleAbsEvent(js, code, info->value); } } #define isBitSet(bit, arr) (arr[(bit) / 8] & (1 << ((bit) % 8))) // Attempt to open the specified joystick device // static bool openJoystickDevice(const char* path) { for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) { if (!_glfw.joysticks[jid].present) continue; if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0) return false; } _GLFWjoystickLinux linjs = {0}; linjs.fd = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (linjs.fd == -1) return false; char evBits[(EV_CNT + 7) / 8] = {0}; char keyBits[(KEY_CNT + 7) / 8] = {0}; char absBits[(ABS_CNT + 7) / 8] = {0}; struct input_id id; if (ioctl(linjs.fd, (int32_t)EVIOCGBIT(0, sizeof(evBits)), evBits) < 0 || ioctl(linjs.fd, (int32_t)EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits) < 0 || ioctl(linjs.fd, (int32_t)EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits) < 0 || ioctl(linjs.fd, (int32_t)EVIOCGID, &id) < 0) { _glfwInputError(GLFW_PLATFORM_ERROR, "Linux: Failed to query input device: %s", strerror(errno)); close(linjs.fd); return false; } // Ensure this device supports the events expected of a joystick if (!isBitSet(EV_KEY, evBits) || !isBitSet(EV_ABS, evBits)) { close(linjs.fd); return false; } char name[256] = ""; if (ioctl(linjs.fd, (int32_t)EVIOCGNAME(sizeof(name)), name) < 0) strncpy(name, "Unknown", sizeof(name)); char guid[33] = ""; // Generate a joystick GUID that matches the SDL 2.0.5+ one if (id.vendor && id.product && id.version) { sprintf(guid, "%02x%02x0000%02x%02x0000%02x%02x0000%02x%02x0000", id.bustype & 0xff, id.bustype >> 8, id.vendor & 0xff, id.vendor >> 8, id.product & 0xff, id.product >> 8, id.version & 0xff, id.version >> 8); } else { sprintf(guid, "%02x%02x0000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x00", id.bustype & 0xff, id.bustype >> 8, name[0], name[1], name[2], name[3], name[4], name[5], name[6], name[7], name[8], name[9], name[10]); } int axisCount = 0, buttonCount = 0, hatCount = 0; for (int code = BTN_MISC; code < KEY_CNT; code++) { if (!isBitSet(code, keyBits)) continue; linjs.keyMap[code - BTN_MISC] = buttonCount; buttonCount++; } for (int code = 0; code < ABS_CNT; code++) { linjs.absMap[code] = -1; if (!isBitSet(code, absBits)) continue; if (code >= ABS_HAT0X && code <= ABS_HAT3Y) { linjs.absMap[code] = hatCount; hatCount++; // Skip the Y axis code++; } else { if (ioctl(linjs.fd, EVIOCGABS(code), &linjs.absInfo[code]) < 0) continue; linjs.absMap[code] = axisCount; axisCount++; } } _GLFWjoystick* js = _glfwAllocJoystick(name, guid, axisCount, buttonCount, hatCount); if (!js) { close(linjs.fd); return false; } strncpy(linjs.path, path, sizeof(linjs.path) - 1); memcpy(&js->linjs, &linjs, sizeof(linjs)); pollAbsState(js); _glfwInputJoystick(js, GLFW_CONNECTED); return true; } #undef isBitSet // Frees all resources associated with the specified joystick // static void closeJoystick(_GLFWjoystick* js) { close(js->linjs.fd); _glfwFreeJoystick(js); _glfwInputJoystick(js, GLFW_DISCONNECTED); } // Lexically compare joysticks by name; used by qsort // static int compareJoysticks(const void* fp, const void* sp) { const _GLFWjoystick* fj = fp; const _GLFWjoystick* sj = sp; return strcmp(fj->linjs.path, sj->linjs.path); } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// void _glfwDetectJoystickConnectionLinux(void) { if (_glfw.linjs.inotify <= 0) return; ssize_t offset = 0; char buffer[16384]; const ssize_t size = read(_glfw.linjs.inotify, buffer, sizeof(buffer)); while (size > offset) { regmatch_t match; const struct inotify_event* e = (struct inotify_event*) (buffer + offset); offset += sizeof(struct inotify_event) + e->len; if (regexec(&_glfw.linjs.regex, e->name, 1, &match, 0) != 0) continue; char path[PATH_MAX]; snprintf(path, sizeof(path), "/dev/input/%s", e->name); if (e->mask & (IN_CREATE | IN_ATTRIB)) openJoystickDevice(path); else if (e->mask & IN_DELETE) { for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) { if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0) { closeJoystick(_glfw.joysticks + jid); break; } } } } } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// bool _glfwPlatformInitJoysticks(void) { const char* dirname = "/dev/input"; _glfw.linjs.inotify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); if (_glfw.linjs.inotify > 0) { // HACK: Register for IN_ATTRIB to get notified when udev is done // This works well in practice but the true way is libudev _glfw.linjs.watch = inotify_add_watch(_glfw.linjs.inotify, dirname, IN_CREATE | IN_ATTRIB | IN_DELETE); } // Continue without device connection notifications if inotify fails if (regcomp(&_glfw.linjs.regex, "^event[0-9]\\+$", 0) != 0) { _glfwInputError(GLFW_PLATFORM_ERROR, "Linux: Failed to compile regex"); return false; } int count = 0; DIR* dir = opendir(dirname); if (dir) { struct dirent* entry; while ((entry = readdir(dir))) { regmatch_t match; if (regexec(&_glfw.linjs.regex, entry->d_name, 1, &match, 0) != 0) continue; char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", dirname, entry->d_name); if (openJoystickDevice(path)) count++; } closedir(dir); } // Continue with no joysticks if enumeration fails qsort(_glfw.joysticks, count, sizeof(_glfw.joysticks[0]), compareJoysticks); return true; } void _glfwPlatformTerminateJoysticks(void) { int jid; for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) { _GLFWjoystick* js = _glfw.joysticks + jid; if (js->present) closeJoystick(js); } if (_glfw.linjs.inotify > 0) { if (_glfw.linjs.watch > 0) inotify_rm_watch(_glfw.linjs.inotify, _glfw.linjs.watch); close(_glfw.linjs.inotify); regfree(&_glfw.linjs.regex); } } int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode UNUSED) { // Read all queued events (non-blocking) for (;;) { struct input_event e; errno = 0; if (read(js->linjs.fd, &e, sizeof(e)) < 0) { // Reset the joystick slot if the device was disconnected if (errno == ENODEV) closeJoystick(js); break; } if (e.type == EV_SYN) { if (e.code == SYN_DROPPED) _glfw.linjs.dropped = true; else if (e.code == SYN_REPORT) { _glfw.linjs.dropped = false; pollAbsState(js); } } if (_glfw.linjs.dropped) continue; if (e.type == EV_KEY) handleKeyEvent(js, e.code, e.value); else if (e.type == EV_ABS) handleAbsEvent(js, e.code, e.value); } return js->present; } void _glfwPlatformUpdateGamepadGUID(char* guid UNUSED) { } kitty-0.41.1/glfw/linux_joystick.h0000664000175000017510000000406014773370543016475 0ustar nileshnilesh//======================================================================== // GLFW 3.4 Linux - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2014 Jonas Ådahl // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #include #include #include #define _GLFW_PLATFORM_JOYSTICK_STATE _GLFWjoystickLinux linjs #define _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE _GLFWlibraryLinux linjs; #define _GLFW_PLATFORM_MAPPING_NAME "Linux" // Linux-specific joystick data // typedef struct _GLFWjoystickLinux { int fd; char path[PATH_MAX]; int keyMap[KEY_CNT - BTN_MISC]; int absMap[ABS_CNT]; struct input_absinfo absInfo[ABS_CNT]; int hats[4][2]; } _GLFWjoystickLinux; // Linux-specific joystick API data // typedef struct _GLFWlibraryLinux { int inotify; int watch; regex_t regex; bool dropped; } _GLFWlibraryLinux; void _glfwDetectJoystickConnectionLinux(void); kitty-0.41.1/glfw/linux_notify.c0000664000175000017510000002074614773370543016152 0ustar nileshnilesh/* * linux_notify.c * Copyright (C) 2019 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #define _POSIX_C_SOURCE 200809L #include "internal.h" #include "linux_notify.h" #include #include #define NOTIFICATIONS_SERVICE "org.freedesktop.Notifications" #define NOTIFICATIONS_PATH "/org/freedesktop/Notifications" #define NOTIFICATIONS_IFACE "org.freedesktop.Notifications" static inline void cleanup_free(void *p) { free(*(void**)p); } #define RAII_ALLOC(type, name, initializer) __attribute__((cleanup(cleanup_free))) type *name = initializer typedef struct { notification_id_type next_id; GLFWDBusnotificationcreatedfun callback; void *data; } NotificationCreatedData; static GLFWDBusnotificationactivatedfun activated_handler = NULL; void glfw_dbus_set_user_notification_activated_handler(GLFWDBusnotificationactivatedfun handler) { activated_handler = handler; } void notification_created(DBusMessage *msg, const char* errmsg, void *data) { if (errmsg) { _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to create notification error: %s", errmsg); if (data) free(data); return; } uint32_t id; if (!glfw_dbus_get_args(msg, "Failed to get Notification uid", DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID)) return; NotificationCreatedData *ncd = (NotificationCreatedData*)data; if (ncd) { if (ncd->callback) ncd->callback(ncd->next_id, id, ncd->data); free(ncd); } } static DBusHandlerResult message_handler(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data UNUSED) { /* printf("session_bus message_handler invoked interface: %s member: %s\n", dbus_message_get_interface(msg), dbus_message_get_member(msg)); */ if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "ActionInvoked")) { uint32_t id; const char *action = NULL; if (glfw_dbus_get_args(msg, "Failed to get args from ActionInvoked notification signal", DBUS_TYPE_UINT32, &id, DBUS_TYPE_STRING, &action, DBUS_TYPE_INVALID)) { if (activated_handler) { activated_handler(id, 2, action); return DBUS_HANDLER_RESULT_HANDLED; } } } if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "ActivationToken")) { uint32_t id; const char *token = NULL; if (glfw_dbus_get_args(msg, "Failed to get args from ActivationToken notification signal", DBUS_TYPE_UINT32, &id, DBUS_TYPE_STRING, &token, DBUS_TYPE_INVALID)) { if (activated_handler) { activated_handler(id, 1, token); return DBUS_HANDLER_RESULT_HANDLED; } } } if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "NotificationClosed")) { uint32_t id; if (glfw_dbus_get_args(msg, "Failed to get args from NotificationClosed notification signal", DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID)) { if (activated_handler) { activated_handler(id, 0, ""); return DBUS_HANDLER_RESULT_HANDLED; } } } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static bool cancel_user_notification(DBusConnection *session_bus, uint32_t *id) { return glfw_dbus_call_method_no_reply(session_bus, NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "CloseNotification", DBUS_TYPE_UINT32, id, DBUS_TYPE_INVALID); } static void got_capabilities(DBusMessage *msg, const char* err, void* data UNUSED) { if (err) { _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to get server capabilities error: %s", err); return; } #define check_call(func, err, ...) if (!func(__VA_ARGS__)) { _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: GetCapabilities: %s", err); return; } DBusMessageIter iter, array_iter; check_call(dbus_message_iter_init, "message has no parameters", msg, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRING) { _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: GetCapabilities: %s", "reply is not an array of strings"); return; } dbus_message_iter_recurse(&iter, &array_iter); char buf[2048] = {0}, *p = buf, *end = buf + sizeof(buf); while (dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRING) { const char *str; dbus_message_iter_get_basic(&array_iter, &str); size_t len = strlen(str); if (len && p + len + 2 < end) { p = stpcpy(p, str); *(p++) = '\n'; } dbus_message_iter_next(&array_iter); } if (activated_handler) activated_handler(0, -1, buf); #undef check_call } static bool get_capabilities(DBusConnection *session_bus) { return glfw_dbus_call_method_with_reply(session_bus, NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "GetCapabilities", 60, got_capabilities, NULL, DBUS_TYPE_INVALID); } notification_id_type glfw_dbus_send_user_notification(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *user_data) { DBusConnection *session_bus = glfw_dbus_session_bus(); if (!session_bus) return 0; if (n->timeout == -9999 && n->urgency == 255) return cancel_user_notification(session_bus, user_data) ? 1 : 0; if (n->timeout == -99999 && n->urgency == 255) return get_capabilities(session_bus) ? 1 : 0; static DBusConnection *added_signal_match = NULL; if (added_signal_match != session_bus) { dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='ActionInvoked'", NULL); dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='NotificationClosed'", NULL); dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='ActivationToken'", NULL); dbus_connection_add_filter(session_bus, message_handler, NULL, NULL); added_signal_match = session_bus; } RAII_ALLOC(NotificationCreatedData, data, malloc(sizeof(NotificationCreatedData))); if (!data) return 0; static notification_id_type notification_id = 0; data->next_id = ++notification_id; data->callback = callback; data->data = user_data; if (!data->next_id) data->next_id = ++notification_id; RAII_MSG(msg, dbus_message_new_method_call(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify")); if (!msg) { return 0; } DBusMessageIter args, array, variant, dict; dbus_message_iter_init_append(msg, &args); #define check_call(func, ...) if (!func(__VA_ARGS__)) { _glfwInputError(GLFW_PLATFORM_ERROR, "%s", "Out of memory allocating DBUS message for notification\n"); return 0; } #define APPEND(to, type, val) check_call(dbus_message_iter_append_basic, &to, type, &val); APPEND(args, DBUS_TYPE_STRING, n->app_name) APPEND(args, DBUS_TYPE_UINT32, n->replaces) APPEND(args, DBUS_TYPE_STRING, n->icon) APPEND(args, DBUS_TYPE_STRING, n->summary) APPEND(args, DBUS_TYPE_STRING, n->body) check_call(dbus_message_iter_open_container, &args, DBUS_TYPE_ARRAY, "s", &array); if (n->actions) { for (size_t i = 0; i < n->num_actions; i++) { APPEND(array, DBUS_TYPE_STRING, n->actions[i]); } } check_call(dbus_message_iter_close_container, &args, &array); check_call(dbus_message_iter_open_container, &args, DBUS_TYPE_ARRAY, "{sv}", &array); #define append_sv_dictionary_entry(k, val_type, val) { \ check_call(dbus_message_iter_open_container, &array, DBUS_TYPE_DICT_ENTRY, NULL, &dict); \ static const char *key = k; \ APPEND(dict, DBUS_TYPE_STRING, key); \ check_call(dbus_message_iter_open_container, &dict, DBUS_TYPE_VARIANT, val_type##_AS_STRING, &variant); \ APPEND(variant, val_type, val); \ check_call(dbus_message_iter_close_container, &dict, &variant); \ check_call(dbus_message_iter_close_container, &array, &dict); \ } append_sv_dictionary_entry("urgency", DBUS_TYPE_BYTE, n->urgency); if (n->category && n->category[0]) append_sv_dictionary_entry("category", DBUS_TYPE_STRING, n->category); if (n->muted) append_sv_dictionary_entry("suppress-sound", DBUS_TYPE_BOOLEAN, n->muted); check_call(dbus_message_iter_close_container, &args, &array); APPEND(args, DBUS_TYPE_INT32, n->timeout) #undef check_call #undef APPEND if (!call_method_with_msg(session_bus, msg, 5000, notification_created, data, false)) return 0; notification_id_type ans = data->next_id; data = NULL; return ans; } kitty-0.41.1/glfw/linux_notify.h0000664000175000017510000000115514773370543016150 0ustar nileshnilesh/* * Copyright (C) 2019 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "dbus_glfw.h" #include "internal.h" typedef unsigned long long notification_id_type; typedef void (*GLFWDBusnotificationcreatedfun)(notification_id_type, uint32_t, void*); typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, int, const char*); notification_id_type glfw_dbus_send_user_notification(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun, void*); void glfw_dbus_set_user_notification_activated_handler(GLFWDBusnotificationactivatedfun handler); kitty-0.41.1/glfw/main_loop.h0000664000175000017510000000307514773370543015401 0ustar nileshnilesh/* * Copyright (C) 2019 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "internal.h" #include "../kitty/monotonic.h" #ifndef GLFW_LOOP_BACKEND #define GLFW_LOOP_BACKEND x11 #endif static bool keep_going = false; void _glfwPlatformStopMainLoop(void) { if (keep_going) { keep_going = false; _glfwPlatformPostEmptyEvent(); } } void _glfwPlatformRunMainLoop(GLFWtickcallback tick_callback, void* data) { keep_going = 1; EventLoopData *eld = &_glfw.GLFW_LOOP_BACKEND.eventLoopData; while(keep_going) { _glfwPlatformWaitEvents(); EVDBG("--------- loop tick, wakeups_happened: %d ----------", eld->wakeup_data_read); if (eld->wakeup_data_read) { eld->wakeup_data_read = false; tick_callback(data); } } EVDBG("main loop exiting"); } unsigned long long _glfwPlatformAddTimer(monotonic_t interval, bool repeats, GLFWuserdatafreefun callback, void *callback_data, GLFWuserdatafreefun free_callback) { return addTimer(&_glfw.GLFW_LOOP_BACKEND.eventLoopData, "user timer", interval, 1, repeats, callback, callback_data, free_callback); } void _glfwPlatformRemoveTimer(unsigned long long timer_id) { removeTimer(&_glfw.GLFW_LOOP_BACKEND.eventLoopData, timer_id); } void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled) { changeTimerInterval(&_glfw.GLFW_LOOP_BACKEND.eventLoopData, timer_id, interval); toggleTimer(&_glfw.GLFW_LOOP_BACKEND.eventLoopData, timer_id, enabled); } kitty-0.41.1/glfw/mappings.h0000664000175000017510000034105514773370543015245 0ustar nileshnilesh//======================================================================== // GLFW 3.4 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2006-2018 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // As mappings.h.in, this file is used by CMake to produce the mappings.h // header file. If you are adding a GLFW specific gamepad mapping, this is // where to put it. //======================================================================== // As mappings.h, this provides all pre-defined gamepad mappings, including // all available in SDL_GameControllerDB. Do not edit this file. Any gamepad // mappings not specific to GLFW should be submitted to SDL_GameControllerDB. // This file can be re-generated from mappings.h.in and the upstream // gamecontrollerdb.txt with the GenerateMappings.cmake script. //======================================================================== // All gamepad mappings not labeled GLFW are copied from the // SDL_GameControllerDB project under the following license: // // Simple DirectMedia Layer // Copyright (C) 1997-2013 Sam Lantinga // // This software is provided 'as-is', without any express or implied warranty. // In no event will the authors be held liable for any damages arising from the // use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source distribution. const char* _glfwDefaultMappings[] = { "03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,platform:Windows,", "03000000022000000090000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,", "03000000203800000900000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,", "03000000102800000900000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,", "03000000a00500003232000000000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,", "030000008f0e00001200000000000000,Acme,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows,", "03000000341a00003608000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000c01100001352000000000000,Battalife Joystick,a:b6,b:b7,back:b2,leftshoulder:b0,leftx:a0,lefty:a1,rightshoulder:b1,start:b3,x:b4,y:b5,platform:Windows,", "030000006b1400000055000000000000,bigben ps3padstreetnew,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", "0300000066f700000500000000000000,BrutalLegendTest,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,", "03000000d81d00000b00000000000000,BUFFALO BSGP1601 Series ,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,platform:Windows,", "03000000e82000006058000000000000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", "030000005e0400008e02000000000000,Controller (XBOX 360 For Windows),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", "03000000260900008888000000000000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a4,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Windows,", "03000000a306000022f6000000000000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,", "03000000791d00000103000000000000,Dual Box WII,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", "030000004f04000023b3000000000000,Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "03000000341a00000108000000000000,EXEQ RF USB Gamepad 8206,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", "030000000d0f00008500000000000000,Fighting Commander 2016 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00008400000000000000,Fighting Commander 5,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00008800000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,", "030000000d0f00008700000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00002700000000000000,FIGHTING STICK V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", "78696e70757403000000000000000000,Fightstick TES,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Windows,", "03000000790000000600000000000000,G-Shark GS-GP702,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,", "03000000260900002625000000000000,Gamecube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,lefttrigger:a4,leftx:a0,lefty:a1,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Windows,", "030000008f0e00000d31000000000000,GAMEPAD 3 TURBO,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000280400000140000000000000,GamePad Pro USB,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", "03000000ffff00000000000000000000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", "03000000451300000010000000000000,Generic USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", "03000000341a00000302000000000000,Hama Scorpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00004900000000000000,Hatsune Miku Sho Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000d81400000862000000000000,HitBox Edition Cthulhu+,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00005f00000000000000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00005e00000000000000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00004000000000000000,Hori Fighting Stick Mini 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00006e00000000000000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00006600000000000000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f0000ee00000000000000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00004d00000000000000,HORIPAD3 A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000250900000017000000000000,HRAP2 on PS/SS/N64 Joypad to USB BOX,a:b2,b:b1,back:b9,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b8,x:b3,y:b0,platform:Windows,", "030000008f0e00001330000000000000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Windows,", "03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", "03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", "03000000830500006020000000000000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Windows,", "03000000b50700001403000000000000,IMPACT BLACK,a:b2,b:b3,back:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,", "030000006f0e00002401000000000000,INJUSTICE FightStick for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", "03000000491900000204000000000000,Ipega PG-9023,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", "030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,platform:Windows,", "030000006d04000016c2000000000000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000006d04000018c2000000000000,Logitech F510 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000006d04000019c2000000000000,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700005032000000000000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700005082000000000000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700008433000000000000,Mad Catz FightStick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700008483000000000000,Mad Catz FightStick TE S+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b6,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700008134000000000000,Mad Catz FightStick TE2+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700008184000000000000,Mad Catz FightStick TE2+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,leftstick:b10,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700008034000000000000,Mad Catz TE2 PS3 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700008084000000000000,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700008532000000000000,Madcatz Arcade Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700003888000000000000,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700001888000000000000,MadCatz SFIV FightStick PS3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", "03000000380700008081000000000000,MADCATZ SFV Arcade FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000008305000031b0000000000000,MaxfireBlaze3,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", "03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,platform:Windows,", "03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,", "03000000790000004318000000000000,Mayflash GameCube Controller Adapter,a:b1,b:b2,back:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b0,leftshoulder:b4,leftstick:b0,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b0,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,", "030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,platform:Windows,", "0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,", "03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Windows,", "03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,", "030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", "030000004b120000014d000000000000,NYKO AIRFLO,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a3,leftstick:a0,lefttrigger:b6,leftx:h0.6,lefty:h0.12,rightshoulder:b5,rightstick:a2,righttrigger:b7,rightx:h0.9,righty:h0.4,start:b9,x:b2,y:b3,platform:Windows,", "03000000362800000100000000000000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,x:b1,y:b2,platform:Windows,", "03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,", "030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,", "03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000008f0e00007530000000000000,PS (R) Gamepad,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b1,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000e30500009605000000000000,PS to USB convert cable,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,", "03000000100800000100000000000000,PS1 USB,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,", "03000000100800000300000000000000,PS2 USB,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows,", "03000000888800000803000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,", "030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Windows,", "03000000250900000500000000000000,PS3 DualShock,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows,", "03000000100000008200000000000000,PS360+ v1.66,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:h0.4,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", "030000004c050000a00b000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000004c050000cc09000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "03000000300f00000011000000000000,QanBa Arcade JoyStick 1008,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b10,x:b0,y:b3,platform:Windows,", "03000000300f00001611000000000000,QanBa Arcade JoyStick 4018,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,", "03000000222c00000020000000000000,QANBA DRONE ARCADE JOYSTICK,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,platform:Windows,", "03000000300f00001210000000000000,QanBa Joystick Plus,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,", "03000000341a00000104000000000000,QanBa Joystick Q4RAF,a:b5,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b1,y:b2,platform:Windows,", "03000000222c00000223000000000000,Qanba Obsidian Arcade Joystick PS3 Mode,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000222c00000023000000000000,Qanba Obsidian Arcade Joystick PS4 Mode,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", "030000000d0f00001100000000000000,REAL ARCADE PRO.3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00008b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00008a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00006b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00006a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00007000000000000000,REAL ARCADE PRO.4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00002200000000000000,REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00005c00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00005b00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "03000000790000001100000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,", "0300000000f000000300000000000000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,", "0300000000f00000f100000000000000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,", "030000006b140000010d000000000000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000006f0e00001e01000000000000,Rock Candy Gamepad for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000004f04000003d0000000000000,run'n'drive,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "03000000a30600001af5000000000000,Saitek Cyborg,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,", "03000000a306000023f6000000000000,Saitek Cyborg V.1 Game pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,", "03000000300f00001201000000000000,Saitek Dual Analog Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,", "03000000a30600000cff000000000000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,x:b0,y:b1,platform:Windows,", "03000000a30600000c04000000000000,Saitek P2900,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,", "03000000300f00001001000000000000,Saitek P480 Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,", "03000000a30600000b04000000000000,Saitek P990,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,", "03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Windows,", "03000000300f00001101000000000000,saitek rumble pad,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,", "0300000000050000289b000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,", "030000009b2800000500000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,", "03000000341a00000208000000000000,SL-6555-SBK,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a3,righty:a2,start:b7,x:b2,y:b3,platform:Windows,", "030000008f0e00000800000000000000,SpeedLink Strike FX Wireless,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", "03000000ff1100003133000000000000,SVEN X-PAD,a:b2,b:b3,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a4,start:b5,x:b0,y:b1,platform:Windows,", "03000000fa1900000706000000000000,Team 5,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", "03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,", "030000004f04000015b3000000000000,Thrustmaster Dual Analog 2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,", "030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Windows,", "030000004f04000004b3000000000000,Thrustmaster Firestorm Dual Power 3,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,", "03000000666600000488000000000000,TigerGame PS/PS2 Game Controller Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,", "03000000d90400000200000000000000,TwinShock PS2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,", "03000000380700006652000000000000,UnKnown,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,", "03000000632500002305000000000000,USB Vibration Joystick (BM),a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", "03000000790000001b18000000000000,Venom Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", "03000000450c00002043000000000000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", "03000000172700004431000000000000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,", "03000000786901006e70000000000000,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", "03000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,", "03000000022000000090000001000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,", "03000000102800000900000000000000,8Bitdo SFC30 GamePad Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,", "03000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,", "030000008305000031b0000000000000,Cideko AK08b,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "03000000260900008888000088020000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Mac OS X,", "03000000a306000022f6000001030000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Mac OS X,", "03000000790000000600000000000000,G-Shark GP-702,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Mac OS X,", "03000000ad1b000001f9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,", "030000000d0f00005f00000000010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00005e00000000010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00005f00000000000000,HORI Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00005e00000000000000,HORI Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00004d00000000000000,HORI Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00006e00000000010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00006600000000010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00006600000000000000,HORIPAD FPS PLUS 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000008f0e00001330000011010000,HuiJia SNES Controller,a:b4,b:b2,back:b16,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b12,rightshoulder:b14,start:b18,x:b6,y:b0,platform:Mac OS X,", "03000000830500006020000000010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,", "03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,", "030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000006d04000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000006d04000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000006d0400001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "030000006d04000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "03000000380700005032000000010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "03000000380700005082000000010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Mac OS X,", "0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Mac OS X,", "03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,platform:Mac OS X,", "03000000d8140000cecf000000000000,MC Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Mac OS X,", "030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,", "030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Mac OS X,", "030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,", "030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,", "030000004c050000a00b000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000008916000000fd000000000000,Razer Onza TE,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "03000000321500000010000000010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "03000000790000001100000000000000,Retrolink Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a3,lefty:a4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,", "03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,", "030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "03000000811700007e05000000000000,Sega Saturn,a:b2,b:b4,dpdown:b16,dpleft:b15,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,leftx:a0,lefty:a2,rightshoulder:b9,righttrigger:a4,start:b13,x:b0,y:b6,platform:Mac OS X,", "03000000b40400000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Mac OS X,", "030000003512000021ab000000000000,SFC30 Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,", "030000004c050000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000004c050000a00b000000000000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000005e0400008e02000001000000,Steam Virtual GamePad,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "03000000110100002014000000000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b12,x:b2,y:b3,platform:Mac OS X,", "03000000110100002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,", "03000000381000002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,", "03000000110100001714000000000000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,", "03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,", "030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Mac OS X,", "030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Mac OS X,", "03000000bd12000015d0000000010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,", "03000000bd12000015d0000000000000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,", "03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X,", "050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,back:b7,dpdown:b3,dpleft:b0,dpright:b1,dpup:b2,guide:b8,leftshoulder:b11,lefttrigger:b12,leftx:a0,lefty:a1,start:b6,x:b10,y:b9,platform:Mac OS X,", "050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b19,leftstick:b23,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b24,righttrigger:b22,rightx:a2,righty:a3,start:b6,x:b18,y:b17,platform:Mac OS X,", "030000005e0400008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "03000000c6240000045d000000000000,Xbox 360 Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "030000005e040000e302000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "030000005e040000d102000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "030000005e040000dd02000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "030000005e040000e002000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,", "030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,", "030000005e040000ea02000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "030000005e040000e002000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,", "03000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Mac OS X,", "03000000120c0000100e000000010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "05000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", "03000000022000000090000011010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", "05000000c82d00002038000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", "03000000c82d00000190000011010000,8Bitdo NES30 Pro 8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", "05000000c82d00003028000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,", "05000000102800000900000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,", "05000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,", "05000000a00500003232000001000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,", "030000006f0e00003901000020060000,Afterglow Wired Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,", "05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux,", "03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Linux,", "03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", "03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Linux,", "03000000a306000022f6000011010000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,", "03000000b40400000a01000000010000,CYPRESS USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux,", "03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,", "030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,", "0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", "030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:a0,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:a3,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000006f0e00001f01000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000280400000140000000010000,Gravis GamePad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,", "030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys GamePad ,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Linux,", "030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,", "030000008f0e00000300000010010000,GreenAsia Inc. USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,", "0500000047532067616d657061640000,GS gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", "06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,platform:Linux,", "03000000c9110000f055000011010000,HJC Game GAMEPAD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", "030000000d0f00000d00000000010000,hori,a:b0,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftx:b4,lefty:b5,rightshoulder:b7,start:b9,x:b1,y:b2,platform:Linux,", "030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,", "030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "030000000d0f00006b00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,", "030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Linux,", "03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,", "050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,", "03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,platform:Linux,", "030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Linux,", "03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,", "03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,", "030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000006d04000015c2000010010000,Logitech Logitech Extreme 3D,a:b0,b:b4,back:b6,guide:b8,leftshoulder:b9,leftstick:h0.8,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:h0.2,start:b7,x:b2,y:b5,platform:Linux,", "030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,platform:Linux,", "05000000380700006652000025010000,Mad Catz C.T.R.L.R ,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000380700005032000011010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux,", "03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "03000000380700008433000011010000,Mad Catz FightStick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000380700008483000011010000,Mad Catz FightStick TE S+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", "03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,", "03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,", "030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,", "030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,", "05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,", "030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Linux,", "050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", "050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,", "05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", "030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux,", "03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,", "03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,", "05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,", "05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,", "03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", "030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,", "030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,", "050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", "03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", "050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,", "030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", "030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,", "060000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,", "05000000504c415953544154494f4e00,PS3 Controller (Bluetooth),a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,", "050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", "050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", "030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", "050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", "030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", "030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,platform:Linux,", "030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux,", "030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000008916000000fd000024010000,Razer Onza Tournament,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,", "050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,", "0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,", "0300000000f000000300000000010000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,", "0300000000f00000f100000000010000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,", "030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "030000006f0e00001e01000011010000,Rock Candy Gamepad for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000006f0e00004601000001010000,Rock Candy Wired Controller for Xbox One,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,", "03000000a30600000cff000010010000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux,", "03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b12,x:b0,y:b3,platform:Linux,", "03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux,", "03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Linux,", "03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Linux,", "03000000c01600008704000011010000,Serial/Keyboard/Mouse/Joystick,a:b12,b:b10,back:b4,dpdown:b2,dpleft:b3,dpright:b1,dpup:b0,leftshoulder:b9,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b8,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b5,x:b13,y:b11,platform:Linux,", "03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", "03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,", "030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,", "05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,", "03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,", "05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,", "03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,", "03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,", "030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,", "030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,", "030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Linux,", "030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000bd12000015d0000010010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,", "03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,a:b0,b:b1,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux,", "03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,", "03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,", "03000000790000001100000000010000,USB Gamepad1,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,platform:Linux,", "05000000ac0500003232000001000000,VR-BOX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,", "030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux,", "0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,", "050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", "03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", "05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Linux,", "03000000c0160000e105000001010000,Xin-Mo Xin-Mo Dual Arcade,a:b4,b:b3,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b9,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b1,y:b0,platform:Linux,", "03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "64633436313965656664373634323364,Microsoft X-Box 360 pad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android,", "61363931656135336130663561616264,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", "4e564944494120436f72706f72617469,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", "37336435666338653565313731303834,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", "35643031303033326130316330353564,PS4 Controller,a:b1,b:b17,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,", "05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android,", "5477696e20555342204a6f7973746963,Twin USB Joystick,a:b22,b:b21,back:b28,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b26,leftstick:b30,lefttrigger:b24,leftx:a0,lefty:a1,rightshoulder:b27,rightstick:b31,righttrigger:b25,rightx:a3,righty:a2,start:b29,x:b23,y:b20,platform:Android,", "34356136633366613530316338376136,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,x:b17,y:b2,platform:Android,", "4d466947616d65706164010000000000,MFi Extended Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:iOS,", "4d466947616d65706164020000000000,MFi Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b6,x:b2,y:b3,platform:iOS,", "05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS,", "78696e70757401000000000000000000,XInput Gamepad (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,", "78696e70757402000000000000000000,XInput Wheel (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,", "78696e70757403000000000000000000,XInput Arcade Stick (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,", "78696e70757404000000000000000000,XInput Flight Stick (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,", "78696e70757405000000000000000000,XInput Dance Pad (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,", "78696e70757406000000000000000000,XInput Guitar (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,", "78696e70757408000000000000000000,XInput Drum Kit (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,", NULL }; kitty-0.41.1/glfw/memfd.h0000664000175000017510000000215514773370543014512 0ustar nileshnilesh/* * Copyright (C) 2018 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #ifdef HAS_MEMFD_CREATE #include #include static inline int glfw_memfd_create(const char *name, unsigned int flags) { return (int)syscall(__NR_memfd_create, name, flags); } #ifndef F_LINUX_SPECIFIC_BASE #define F_LINUX_SPECIFIC_BASE 1024 #endif #ifndef F_ADD_SEALS #define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) #define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) #define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ #define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ #define F_SEAL_GROW 0x0004 /* prevent file from growing */ #define F_SEAL_WRITE 0x0008 /* prevent writes */ #endif #ifndef MFD_CLOEXEC #define MFD_CLOEXEC 0x0001U #define MFD_ALLOW_SEALING 0x0002U #endif #else #include #include #include static inline int createTmpfileCloexec(char* tmpname) { int fd; fd = mkostemp(tmpname, O_CLOEXEC); if (fd >= 0) unlink(tmpname); return fd; } #endif kitty-0.41.1/glfw/monitor.c0000664000175000017510000003500414773370543015103 0ustar nileshnilesh//======================================================================== // GLFW 3.4 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // Please use C89 style variable declarations in this file because VS 2010 //======================================================================== #include "internal.h" #include #include #include #include #include #include // Lexically compare video modes, used by qsort // static int compareVideoModes(const void* fp, const void* sp) { const GLFWvidmode* fm = fp; const GLFWvidmode* sm = sp; const int fbpp = fm->redBits + fm->greenBits + fm->blueBits; const int sbpp = sm->redBits + sm->greenBits + sm->blueBits; const int farea = fm->width * fm->height; const int sarea = sm->width * sm->height; // First sort on color bits per pixel if (fbpp != sbpp) return fbpp - sbpp; // Then sort on screen area if (farea != sarea) return farea - sarea; // Then sort on width if (fm->width != sm->width) return fm->width - sm->width; // Lastly sort on refresh rate return fm->refreshRate - sm->refreshRate; } // Retrieves the available modes for the specified monitor // static bool refreshVideoModes(_GLFWmonitor* monitor) { int modeCount; GLFWvidmode* modes; if (monitor->modes) return true; modes = _glfwPlatformGetVideoModes(monitor, &modeCount); if (!modes) return false; qsort(modes, modeCount, sizeof(modes[0]), compareVideoModes); free(monitor->modes); monitor->modes = modes; monitor->modeCount = modeCount; return true; } ////////////////////////////////////////////////////////////////////////// ////// GLFW event API ////// ////////////////////////////////////////////////////////////////////////// // Notifies shared code of a monitor connection or disconnection // void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement) { if (action == GLFW_CONNECTED) { _glfw.monitorCount++; _glfw.monitors = realloc(_glfw.monitors, sizeof(_GLFWmonitor*) * _glfw.monitorCount); if (placement == _GLFW_INSERT_FIRST) { memmove(_glfw.monitors + 1, _glfw.monitors, ((size_t) _glfw.monitorCount - 1) * sizeof(_GLFWmonitor*)); _glfw.monitors[0] = monitor; } else _glfw.monitors[_glfw.monitorCount - 1] = monitor; } else if (action == GLFW_DISCONNECTED) { int i; _GLFWwindow* window; for (window = _glfw.windowListHead; window; window = window->next) { if (window->monitor == monitor) { int width, height, xoff, yoff; _glfwPlatformGetWindowSize(window, &width, &height); _glfwPlatformSetWindowMonitor(window, NULL, 0, 0, width, height, 0); _glfwPlatformGetWindowFrameSize(window, &xoff, &yoff, NULL, NULL); _glfwPlatformSetWindowPos(window, xoff, yoff); } } for (i = 0; i < _glfw.monitorCount; i++) { if (_glfw.monitors[i] == monitor) { remove_i_from_array(_glfw.monitors, i, _glfw.monitorCount); break; } } } if (_glfw.callbacks.monitor) _glfw.callbacks.monitor((GLFWmonitor*) monitor, action); if (action == GLFW_DISCONNECTED) _glfwFreeMonitor(monitor); } // Notifies shared code that a full screen window has acquired or released // a monitor // void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window) { monitor->window = window; } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// // Allocates and returns a monitor object with the specified name and dimensions // _GLFWmonitor* _glfwAllocMonitor(const char* name, int widthMM, int heightMM) { _GLFWmonitor* monitor = calloc(1, sizeof(_GLFWmonitor)); monitor->widthMM = widthMM; monitor->heightMM = heightMM; if (name) monitor->name = _glfw_strdup(name); return monitor; } // Frees a monitor object and any data associated with it // void _glfwFreeMonitor(_GLFWmonitor* monitor) { if (monitor == NULL) return; _glfwPlatformFreeMonitor(monitor); _glfwFreeGammaArrays(&monitor->originalRamp); _glfwFreeGammaArrays(&monitor->currentRamp); free(monitor->modes); free(monitor->name); free(monitor); } // Allocates red, green and blue value arrays of the specified size // void _glfwAllocGammaArrays(GLFWgammaramp* ramp, unsigned int size) { ramp->red = calloc(size, sizeof(unsigned short)); ramp->green = calloc(size, sizeof(unsigned short)); ramp->blue = calloc(size, sizeof(unsigned short)); ramp->size = size; } // Frees the red, green and blue value arrays and clears the struct // void _glfwFreeGammaArrays(GLFWgammaramp* ramp) { free(ramp->red); free(ramp->green); free(ramp->blue); memset(ramp, 0, sizeof(GLFWgammaramp)); } // Chooses the video mode most closely matching the desired one // const GLFWvidmode* _glfwChooseVideoMode(_GLFWmonitor* monitor, const GLFWvidmode* desired) { int i; unsigned int sizeDiff, leastSizeDiff = UINT_MAX; unsigned int rateDiff, leastRateDiff = UINT_MAX; unsigned int colorDiff, leastColorDiff = UINT_MAX; const GLFWvidmode* current; const GLFWvidmode* closest = NULL; if (!refreshVideoModes(monitor)) return NULL; for (i = 0; i < monitor->modeCount; i++) { current = monitor->modes + i; colorDiff = 0; if (desired->redBits != GLFW_DONT_CARE) colorDiff += abs(current->redBits - desired->redBits); if (desired->greenBits != GLFW_DONT_CARE) colorDiff += abs(current->greenBits - desired->greenBits); if (desired->blueBits != GLFW_DONT_CARE) colorDiff += abs(current->blueBits - desired->blueBits); sizeDiff = abs((current->width - desired->width) * (current->width - desired->width) + (current->height - desired->height) * (current->height - desired->height)); if (desired->refreshRate != GLFW_DONT_CARE) rateDiff = abs(current->refreshRate - desired->refreshRate); else rateDiff = UINT_MAX - current->refreshRate; if ((colorDiff < leastColorDiff) || (colorDiff == leastColorDiff && sizeDiff < leastSizeDiff) || (colorDiff == leastColorDiff && sizeDiff == leastSizeDiff && rateDiff < leastRateDiff)) { closest = current; leastSizeDiff = sizeDiff; leastRateDiff = rateDiff; leastColorDiff = colorDiff; } } return closest; } // Performs lexical comparison between two @ref GLFWvidmode structures // int _glfwCompareVideoModes(const GLFWvidmode* fm, const GLFWvidmode* sm) { return compareVideoModes(fm, sm); } // Splits a color depth into red, green and blue bit depths // void _glfwSplitBPP(int bpp, int* red, int* green, int* blue) { int delta; // We assume that by 32 the user really meant 24 if (bpp == 32) bpp = 24; // Convert "bits per pixel" to red, green & blue sizes *red = *green = *blue = bpp / 3; delta = bpp - (*red * 3); if (delta >= 1) *green = *green + 1; if (delta == 2) *red = *red + 1; } ////////////////////////////////////////////////////////////////////////// ////// GLFW public API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI GLFWmonitor** glfwGetMonitors(int* count) { assert(count != NULL); *count = 0; _GLFW_REQUIRE_INIT_OR_RETURN(NULL); *count = _glfw.monitorCount; return (GLFWmonitor**) _glfw.monitors; } GLFWAPI GLFWmonitor* glfwGetPrimaryMonitor(void) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (!_glfw.monitorCount) return NULL; return (GLFWmonitor*) _glfw.monitors[0]; } GLFWAPI void glfwGetMonitorPos(GLFWmonitor* handle, int* xpos, int* ypos) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; assert(monitor != NULL); if (xpos) *xpos = 0; if (ypos) *ypos = 0; _GLFW_REQUIRE_INIT(); _glfwPlatformGetMonitorPos(monitor, xpos, ypos); } GLFWAPI void glfwGetMonitorWorkarea(GLFWmonitor* handle, int* xpos, int* ypos, int* width, int* height) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; assert(monitor != NULL); if (xpos) *xpos = 0; if (ypos) *ypos = 0; if (width) *width = 0; if (height) *height = 0; _GLFW_REQUIRE_INIT(); _glfwPlatformGetMonitorWorkarea(monitor, xpos, ypos, width, height); } GLFWAPI void glfwGetMonitorPhysicalSize(GLFWmonitor* handle, int* widthMM, int* heightMM) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; assert(monitor != NULL); if (widthMM) *widthMM = 0; if (heightMM) *heightMM = 0; _GLFW_REQUIRE_INIT(); if (widthMM) *widthMM = monitor->widthMM; if (heightMM) *heightMM = monitor->heightMM; } GLFWAPI void glfwGetMonitorContentScale(GLFWmonitor* handle, float* xscale, float* yscale) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; assert(monitor != NULL); if (xscale) *xscale = 0.f; if (yscale) *yscale = 0.f; _GLFW_REQUIRE_INIT(); _glfwPlatformGetMonitorContentScale(monitor, xscale, yscale); } GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* handle) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; assert(monitor != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); return monitor->name; } GLFWAPI void glfwSetMonitorUserPointer(GLFWmonitor* handle, void* pointer) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; assert(monitor != NULL); _GLFW_REQUIRE_INIT(); monitor->userPointer = pointer; } GLFWAPI void* glfwGetMonitorUserPointer(GLFWmonitor* handle) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; assert(monitor != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); return monitor->userPointer; } GLFWAPI GLFWmonitorfun glfwSetMonitorCallback(GLFWmonitorfun cbfun) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(_glfw.callbacks.monitor, cbfun); return cbfun; } GLFWAPI const GLFWvidmode* glfwGetVideoModes(GLFWmonitor* handle, int* count) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; assert(monitor != NULL); assert(count != NULL); *count = 0; _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (!refreshVideoModes(monitor)) return NULL; *count = monitor->modeCount; return monitor->modes; } GLFWAPI const GLFWvidmode* glfwGetVideoMode(GLFWmonitor* handle) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; assert(monitor != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (!_glfwPlatformGetVideoMode(monitor, &monitor->currentMode)) return NULL; return &monitor->currentMode; } GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma) { unsigned int i; unsigned short* values; GLFWgammaramp ramp; const GLFWgammaramp* original; assert(handle != NULL); assert(gamma > 0.f); assert(gamma <= FLT_MAX); _GLFW_REQUIRE_INIT(); if (gamma != gamma || gamma <= 0.f || gamma > FLT_MAX) { _glfwInputError(GLFW_INVALID_VALUE, "Invalid gamma value %f", gamma); return; } original = glfwGetGammaRamp(handle); if (!original) return; values = calloc(original->size, sizeof(unsigned short)); for (i = 0; i < original->size; i++) { float value; // Calculate intensity value = i / (float) (original->size - 1); // Apply gamma curve value = powf(value, 1.f / gamma) * 65535.f + 0.5f; // Clamp to value range value = fminf(value, 65535.f); values[i] = (unsigned short) value; } ramp.red = values; ramp.green = values; ramp.blue = values; ramp.size = original->size; glfwSetGammaRamp(handle, &ramp); free(values); } GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* handle) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; assert(monitor != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _glfwFreeGammaArrays(&monitor->currentRamp); if (!_glfwPlatformGetGammaRamp(monitor, &monitor->currentRamp)) return NULL; return &monitor->currentRamp; } GLFWAPI void glfwSetGammaRamp(GLFWmonitor* handle, const GLFWgammaramp* ramp) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; assert(monitor != NULL); assert(ramp != NULL); assert(ramp->size > 0); assert(ramp->red != NULL); assert(ramp->green != NULL); assert(ramp->blue != NULL); if (ramp->size <= 0) { _glfwInputError(GLFW_INVALID_VALUE, "Invalid gamma ramp size %i", ramp->size); return; } _GLFW_REQUIRE_INIT(); if (!monitor->originalRamp.size) { if (!_glfwPlatformGetGammaRamp(monitor, &monitor->originalRamp)) return; } _glfwPlatformSetGammaRamp(monitor, ramp); } kitty-0.41.1/glfw/monotonic.c0000664000175000017510000000034714773370543015423 0ustar nileshnilesh/* * monotonic.c * Copyright (C) 2019 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #define _POSIX_C_SOURCE 200809L #define MONOTONIC_IMPLEMENTATION #include "../kitty/monotonic.h" kitty-0.41.1/glfw/nsgl_context.h0000664000175000017510000000367014773370543016134 0ustar nileshnilesh//======================================================================== // GLFW 3.4 macOS - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2009-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #define _GLFW_PLATFORM_CONTEXT_STATE _GLFWcontextNSGL nsgl; #define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE _GLFWlibraryNSGL nsgl; // NSGL-specific per-context data // typedef struct _GLFWcontextNSGL { id pixelFormat; id object; } _GLFWcontextNSGL; // NSGL-specific global data // typedef struct _GLFWlibraryNSGL { // dlopen handle for OpenGL.framework (for glfwGetProcAddress) CFBundleRef framework; } _GLFWlibraryNSGL; bool _glfwInitNSGL(void); void _glfwTerminateNSGL(void); bool _glfwCreateContextNSGL(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig); void _glfwDestroyContextNSGL(_GLFWwindow* window); kitty-0.41.1/glfw/nsgl_context.m0000664000175000017510000002460614773370543016143 0ustar nileshnilesh//======================================================================== // GLFW 3.4 macOS - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2009-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #include "internal.h" static void makeContextCurrentNSGL(_GLFWwindow* window) { if (window) [window->context.nsgl.object makeCurrentContext]; else [NSOpenGLContext clearCurrentContext]; _glfwPlatformSetTls(&_glfw.contextSlot, window); } static void swapBuffersNSGL(_GLFWwindow* window) { // ARP appears to be unnecessary, but this is future-proof [window->context.nsgl.object flushBuffer]; } static void swapIntervalNSGL(int interval UNUSED) { // As of Mojave this does not work so we use CVDisplayLink instead _glfwInputError(GLFW_API_UNAVAILABLE, "NSGL: Swap intervals do not work on macOS"); } static int extensionSupportedNSGL(const char* extension UNUSED) { // There are no NSGL extensions return false; } static GLFWglproc getProcAddressNSGL(const char* procname) { CFStringRef symbolName = CFStringCreateWithCString(kCFAllocatorDefault, procname, kCFStringEncodingASCII); GLFWglproc symbol; *(void **) &symbol = CFBundleGetFunctionPointerForName(_glfw.nsgl.framework, symbolName); CFRelease(symbolName); return symbol; } static void destroyContextNSGL(_GLFWwindow* window) { [window->context.nsgl.pixelFormat release]; window->context.nsgl.pixelFormat = nil; [window->context.nsgl.object release]; window->context.nsgl.object = nil; } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// // Initialize OpenGL support // bool _glfwInitNSGL(void) { if (_glfw.nsgl.framework) return true; _glfw.nsgl.framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); if (_glfw.nsgl.framework == NULL) { _glfwInputError(GLFW_API_UNAVAILABLE, "NSGL: Failed to locate OpenGL framework"); return false; } return true; } // Terminate OpenGL support // void _glfwTerminateNSGL(void) { } // Create the OpenGL context // bool _glfwCreateContextNSGL(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { if (ctxconfig->client == GLFW_OPENGL_ES_API) { _glfwInputError(GLFW_API_UNAVAILABLE, "NSGL: OpenGL ES is not available on macOS"); return false; } if (ctxconfig->major > 2) { if (ctxconfig->major == 3 && ctxconfig->minor < 2) { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "NSGL: The targeted version of macOS does not support OpenGL 3.0 or 3.1 but may support 3.2 and above"); return false; } } // Context robustness modes (GL_KHR_robustness) are not yet supported by // macOS but are not a hard constraint, so ignore and continue // Context release behaviors (GL_KHR_context_flush_control) are not yet // supported by macOS but are not a hard constraint, so ignore and continue // Debug contexts (GL_KHR_debug) are not yet supported by macOS but are not // a hard constraint, so ignore and continue // No-error contexts (GL_KHR_no_error) are not yet supported by macOS but // are not a hard constraint, so ignore and continue #define addAttrib(a) \ { \ assert((size_t) index < sizeof(attribs) / sizeof(attribs[0])); \ attribs[index++] = a; \ } #define setAttrib(a, v) { addAttrib(a); addAttrib(v); } NSOpenGLPixelFormatAttribute attribs[40]; int index = 0; addAttrib(NSOpenGLPFAAccelerated); addAttrib(NSOpenGLPFAClosestPolicy); if (ctxconfig->nsgl.offline) { addAttrib(NSOpenGLPFAAllowOfflineRenderers); // NOTE: This replaces the NSSupportsAutomaticGraphicsSwitching key in // Info.plist for unbundled applications // HACK: This assumes that NSOpenGLPixelFormat will remain // a straightforward wrapper of its CGL counterpart addAttrib(kCGLPFASupportsAutomaticGraphicsSwitching); } #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 if (ctxconfig->major >= 4) { setAttrib(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion4_1Core); } else #endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/ if (ctxconfig->major >= 3) { setAttrib(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core); } if (ctxconfig->major <= 2) { if (fbconfig->auxBuffers != GLFW_DONT_CARE) setAttrib(NSOpenGLPFAAuxBuffers, fbconfig->auxBuffers); if (fbconfig->accumRedBits != GLFW_DONT_CARE && fbconfig->accumGreenBits != GLFW_DONT_CARE && fbconfig->accumBlueBits != GLFW_DONT_CARE && fbconfig->accumAlphaBits != GLFW_DONT_CARE) { const int accumBits = fbconfig->accumRedBits + fbconfig->accumGreenBits + fbconfig->accumBlueBits + fbconfig->accumAlphaBits; setAttrib(NSOpenGLPFAAccumSize, accumBits); } } if (fbconfig->redBits != GLFW_DONT_CARE && fbconfig->greenBits != GLFW_DONT_CARE && fbconfig->blueBits != GLFW_DONT_CARE) { int colorBits = fbconfig->redBits + fbconfig->greenBits + fbconfig->blueBits; // macOS needs non-zero color size, so set reasonable values if (colorBits == 0) colorBits = 24; else if (colorBits < 15) colorBits = 15; setAttrib(NSOpenGLPFAColorSize, colorBits); } if (fbconfig->alphaBits != GLFW_DONT_CARE) setAttrib(NSOpenGLPFAAlphaSize, fbconfig->alphaBits); if (fbconfig->depthBits != GLFW_DONT_CARE) setAttrib(NSOpenGLPFADepthSize, fbconfig->depthBits); if (fbconfig->stencilBits != GLFW_DONT_CARE) setAttrib(NSOpenGLPFAStencilSize, fbconfig->stencilBits); if (fbconfig->stereo) { #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 _glfwInputError(GLFW_FORMAT_UNAVAILABLE, "NSGL: Stereo rendering is deprecated"); return false; #else addAttrib(NSOpenGLPFAStereo); #endif } if (fbconfig->doublebuffer) addAttrib(NSOpenGLPFADoubleBuffer); if (fbconfig->samples != GLFW_DONT_CARE) { if (fbconfig->samples == 0) { setAttrib(NSOpenGLPFASampleBuffers, 0); } else { setAttrib(NSOpenGLPFASampleBuffers, 1); setAttrib(NSOpenGLPFASamples, fbconfig->samples); } } // NOTE: All NSOpenGLPixelFormats on the relevant cards support sRGB // framebuffer, so there's no need (and no way) to request it addAttrib(0); #undef addAttrib #undef setAttrib window->context.nsgl.pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs]; if (window->context.nsgl.pixelFormat == nil) { _glfwInputError(GLFW_FORMAT_UNAVAILABLE, "NSGL: Failed to find a suitable pixel format"); return false; } NSOpenGLContext* share = nil; if (ctxconfig->share) share = ctxconfig->share->context.nsgl.object; window->context.nsgl.object = [[NSOpenGLContext alloc] initWithFormat:window->context.nsgl.pixelFormat shareContext:share]; if (window->context.nsgl.object == nil) { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "NSGL: Failed to create OpenGL context"); return false; } if (fbconfig->transparent) { GLint opaque = 0; [window->context.nsgl.object setValues:&opaque forParameter:NSOpenGLContextParameterSurfaceOpacity]; } [window->ns.view setWantsBestResolutionOpenGLSurface:window->ns.retina]; GLint interval = 0; [window->context.nsgl.object setValues:&interval forParameter:NSOpenGLContextParameterSwapInterval]; [window->context.nsgl.object setView:window->ns.view]; window->context.makeCurrent = makeContextCurrentNSGL; window->context.swapBuffers = swapBuffersNSGL; window->context.swapInterval = swapIntervalNSGL; window->context.extensionSupported = extensionSupportedNSGL; window->context.getProcAddress = getProcAddressNSGL; window->context.destroy = destroyContextNSGL; return true; } ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI id glfwGetNSGLContext(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(nil); if (window->context.client == GLFW_NO_API) { _glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL); return nil; } return window->context.nsgl.object; } kitty-0.41.1/glfw/null_init.c0000664000175000017510000000361714773370543015416 0ustar nileshnilesh//======================================================================== // GLFW 3.4 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2016 Google Inc. // Copyright (c) 2016-2017 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #include "internal.h" #include ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// int _glfwPlatformInit(void) { _glfwPollMonitorsNull(); return true; } void _glfwPlatformTerminate(void) { free(_glfw.null.clipboardString); _glfwTerminateOSMesa(); } const char* _glfwPlatformGetVersionString(void) { return _GLFW_VERSION_NUMBER " null OSMesa"; } kitty-0.41.1/glfw/null_joystick.c0000664000175000017510000000350314773370543016304 0ustar nileshnilesh//======================================================================== // GLFW 3.4 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2016-2017 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #include "internal.h" ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// bool _glfwPlatformInitJoysticks(void) { return true; } void _glfwPlatformTerminateJoysticks(void) { } int _glfwPlatformPollJoystick(_GLFWjoystick* js UNUSED, int mode UNUSED) { return false; } void _glfwPlatformUpdateGamepadGUID(char* guid UNUSED) { } kitty-0.41.1/glfw/null_joystick.h0000664000175000017510000000250614773370543016313 0ustar nileshnilesh//======================================================================== // GLFW 3.4 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2006-2017 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #define _GLFW_PLATFORM_JOYSTICK_STATE int nulljs #define _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE int nulljs; #define _GLFW_PLATFORM_MAPPING_NAME "" kitty-0.41.1/glfw/null_monitor.c0000664000175000017510000001235414773370543016140 0ustar nileshnilesh//======================================================================== // GLFW 3.4 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2016 Google Inc. // Copyright (c) 2016-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #include "internal.h" #include #include #include // The sole (fake) video mode of our (sole) fake monitor // static GLFWvidmode getVideoMode(void) { GLFWvidmode mode; mode.width = 1920; mode.height = 1080; mode.redBits = 8; mode.greenBits = 8; mode.blueBits = 8; mode.refreshRate = 60; return mode; } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// void _glfwPollMonitorsNull(void) { const float dpi = 141.f; const GLFWvidmode mode = getVideoMode(); _GLFWmonitor* monitor = _glfwAllocMonitor("Null SuperNoop 0", (int) (mode.width * 25.4f / dpi), (int) (mode.height * 25.4f / dpi)); _glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_FIRST); } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor) { _glfwFreeGammaArrays(&monitor->null.ramp); } void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor UNUSED, int* xpos, int* ypos) { if (xpos) *xpos = 0; if (ypos) *ypos = 0; } void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor UNUSED, float* xscale, float* yscale) { if (xscale) *xscale = 1.f; if (yscale) *yscale = 1.f; } void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor UNUSED, int* xpos, int* ypos, int* width, int* height) { const GLFWvidmode mode = getVideoMode(); if (xpos) *xpos = 0; if (ypos) *ypos = 10; if (width) *width = mode.width; if (height) *height = mode.height - 10; } GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor UNUSED, int* found) { GLFWvidmode* mode = calloc(1, sizeof(GLFWvidmode)); *mode = getVideoMode(); *found = 1; return mode; } void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor UNUSED, GLFWvidmode* mode) { *mode = getVideoMode(); } bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp) { if (!monitor->null.ramp.size) { _glfwAllocGammaArrays(&monitor->null.ramp, 256); for (unsigned int i = 0; i < monitor->null.ramp.size; i++) { const float gamma = 2.2f; float value; value = i / (float) (monitor->null.ramp.size - 1); value = powf(value, 1.f / gamma) * 65535.f + 0.5f; value = _glfw_fminf(value, 65535.f); monitor->null.ramp.red[i] = (unsigned short) value; monitor->null.ramp.green[i] = (unsigned short) value; monitor->null.ramp.blue[i] = (unsigned short) value; } } _glfwAllocGammaArrays(ramp, monitor->null.ramp.size); memcpy(ramp->red, monitor->null.ramp.red, sizeof(short) * ramp->size); memcpy(ramp->green, monitor->null.ramp.green, sizeof(short) * ramp->size); memcpy(ramp->blue, monitor->null.ramp.blue, sizeof(short) * ramp->size); return true; } void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp) { if (monitor->null.ramp.size != ramp->size) { _glfwInputError(GLFW_PLATFORM_ERROR, "Null: Gamma ramp size must match current ramp size"); return; } memcpy(monitor->null.ramp.red, ramp->red, sizeof(short) * ramp->size); memcpy(monitor->null.ramp.green, ramp->green, sizeof(short) * ramp->size); memcpy(monitor->null.ramp.blue, ramp->blue, sizeof(short) * ramp->size); } kitty-0.41.1/glfw/null_platform.h0000664000175000017510000000546514773370543016307 0ustar nileshnilesh//======================================================================== // GLFW 3.4 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2016 Google Inc. // Copyright (c) 2016-2017 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #include #define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNull null #define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryNull null #define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorNull null #define _GLFW_PLATFORM_CONTEXT_STATE #define _GLFW_PLATFORM_CURSOR_STATE #define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE #include "posix_time.h" #include "posix_thread.h" #include "null_joystick.h" #if defined(_GLFW_WIN32) #define _glfw_dlopen(name) LoadLibraryA(name) #define _glfw_dlclose(handle) FreeLibrary((HMODULE) handle) #define _glfw_dlsym(handle, name) GetProcAddress((HMODULE) handle, name) #else #define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL) #define _glfw_dlclose(handle) dlclose(handle) #define _glfw_dlsym(handle, name) dlsym(handle, name) #endif // Null-specific per-window data // typedef struct _GLFWwindowNull { int xpos; int ypos; int width; int height; char* title; bool visible; bool iconified; bool maximized; bool resizable; bool decorated; bool floating; bool transparent; float opacity; } _GLFWwindowNull; // Null-specific per-monitor data // typedef struct _GLFWmonitorNull { GLFWgammaramp ramp; } _GLFWmonitorNull; // Null-specific global data // typedef struct _GLFWlibraryNull { int xcursor; int ycursor; char* clipboardString; _GLFWwindow* focusedWindow; } _GLFWlibraryNull; void _glfwPollMonitorsNull(void); kitty-0.41.1/glfw/null_window.c0000664000175000017510000004327514773370543015766 0ustar nileshnilesh//======================================================================== // GLFW 3.4 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2016 Google Inc. // Copyright (c) 2016-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #include "internal.h" #include "../kitty/monotonic.h" #include static void applySizeLimits(_GLFWwindow* window, int* width, int* height) { if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE) { const float ratio = (float) window->numer / (float) window->denom; *height = (int) (*width / ratio); } if (window->minwidth != GLFW_DONT_CARE && *width < window->minwidth) *width = window->minwidth; else if (window->maxwidth != GLFW_DONT_CARE && *width > window->maxwidth) *width = window->maxwidth; if (window->minheight != GLFW_DONT_CARE && *height < window->minheight) *height = window->minheight; else if (window->maxheight != GLFW_DONT_CARE && *height > window->maxheight) *height = window->maxheight; } static void fitToMonitor(_GLFWwindow* window) { GLFWvidmode mode; _glfwPlatformGetVideoMode(window->monitor, &mode); _glfwPlatformGetMonitorPos(window->monitor, &window->null.xpos, &window->null.ypos); window->null.width = mode.width; window->null.height = mode.height; } static void acquireMonitor(_GLFWwindow* window) { _glfwInputMonitorWindow(window->monitor, window); } static void releaseMonitor(_GLFWwindow* window) { if (window->monitor->window != window) return; _glfwInputMonitorWindow(window->monitor, NULL); } static int createNativeWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWfbconfig* fbconfig) { if (window->monitor) fitToMonitor(window); else { window->null.xpos = 17; window->null.ypos = 17; window->null.width = wndconfig->width; window->null.height = wndconfig->height; } window->null.visible = wndconfig->visible; window->null.decorated = wndconfig->decorated; window->null.maximized = wndconfig->maximized; window->null.floating = wndconfig->floating; window->null.transparent = fbconfig->transparent; window->null.opacity = 1.f; return true; } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { if (!createNativeWindow(window, wndconfig, fbconfig)) return false; if (ctxconfig->client != GLFW_NO_API) { if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API || ctxconfig->source == GLFW_OSMESA_CONTEXT_API) { if (!_glfwInitOSMesa()) return false; if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) return false; } else { _glfwInputError(GLFW_API_UNAVAILABLE, "Null: EGL not available"); return false; } } if (window->monitor) { _glfwPlatformShowWindow(window); _glfwPlatformFocusWindow(window); acquireMonitor(window); } return true; } void _glfwPlatformDestroyWindow(_GLFWwindow* window) { if (window->monitor) releaseMonitor(window); if (_glfw.null.focusedWindow == window) _glfw.null.focusedWindow = NULL; if (window->context.destroy) window->context.destroy(window); } void _glfwPlatformSetWindowTitle(_GLFWwindow* window UNUSED, const char* title UNUSED) { } void _glfwPlatformSetWindowIcon(_GLFWwindow* window UNUSED, int count UNUSED, const GLFWimage* images UNUSED) { } void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate UNUSED) { if (window->monitor == monitor) { if (!monitor) { _glfwPlatformSetWindowPos(window, xpos, ypos); _glfwPlatformSetWindowSize(window, width, height); } return; } if (window->monitor) releaseMonitor(window); _glfwInputWindowMonitor(window, monitor); if (window->monitor) { window->null.visible = true; acquireMonitor(window); fitToMonitor(window); } else { _glfwPlatformSetWindowPos(window, xpos, ypos); _glfwPlatformSetWindowSize(window, width, height); } } void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) { if (xpos) *xpos = window->null.xpos; if (ypos) *ypos = window->null.ypos; } void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos) { if (window->monitor) return; if (window->null.xpos != xpos || window->null.ypos != ypos) { window->null.xpos = xpos; window->null.ypos = ypos; _glfwInputWindowPos(window, xpos, ypos); } } void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) { if (width) *width = window->null.width; if (height) *height = window->null.height; } void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) { if (window->monitor) return; if (window->null.width != width || window->null.height != height) { window->null.width = width; window->null.height = height; _glfwInputWindowSize(window, width, height); _glfwInputFramebufferSize(window, width, height); } } void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, int minwidth UNUSED, int minheight UNUSED, int maxwidth UNUSED, int maxheight UNUSED) { int width = window->null.width; int height = window->null.height; applySizeLimits(window, &width, &height); _glfwPlatformSetWindowSize(window, width, height); } void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int n UNUSED, int d UNUSED) { int width = window->null.width; int height = window->null.height; applySizeLimits(window, &width, &height); _glfwPlatformSetWindowSize(window, width, height); } void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window UNUSED, int widthincr UNUSED, int heightincr UNUSED) { } void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) { if (width) *width = window->null.width; if (height) *height = window->null.height; } void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom) { if (window->null.decorated && !window->monitor) { if (left) *left = 1; if (top) *top = 10; if (right) *right = 1; if (bottom) *bottom = 1; } else { if (left) *left = 0; if (top) *top = 0; if (right) *right = 0; if (bottom) *bottom = 0; } } void _glfwPlatformGetWindowContentScale(_GLFWwindow* window UNUSED, float* xscale, float* yscale) { if (xscale) *xscale = 1.f; if (yscale) *yscale = 1.f; } monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED) { return ms_to_monotonic_t(500ll); } void _glfwPlatformIconifyWindow(_GLFWwindow* window) { if (_glfw.null.focusedWindow == window) { _glfw.null.focusedWindow = NULL; _glfwInputWindowFocus(window, false); } if (!window->null.iconified) { window->null.iconified = true; _glfwInputWindowIconify(window, true); if (window->monitor) releaseMonitor(window); } } void _glfwPlatformRestoreWindow(_GLFWwindow* window) { if (window->null.iconified) { window->null.iconified = false; _glfwInputWindowIconify(window, false); if (window->monitor) acquireMonitor(window); } else if (window->null.maximized) { window->null.maximized = false; _glfwInputWindowMaximize(window, false); } } void _glfwPlatformMaximizeWindow(_GLFWwindow* window) { if (!window->null.maximized) { window->null.maximized = true; _glfwInputWindowMaximize(window, true); } } int _glfwPlatformWindowMaximized(_GLFWwindow* window) { return window->null.maximized; } int _glfwPlatformWindowHovered(_GLFWwindow* window) { return _glfw.null.xcursor >= window->null.xpos && _glfw.null.ycursor >= window->null.ypos && _glfw.null.xcursor <= window->null.xpos + window->null.width - 1 && _glfw.null.ycursor <= window->null.ypos + window->null.height - 1; } int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) { return window->null.transparent; } void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled) { window->null.resizable = enabled; } void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled) { window->null.decorated = enabled; } void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled) { window->null.floating = enabled; } void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window UNUSED, bool enabled UNUSED) { } float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) { return window->null.opacity; } void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) { window->null.opacity = opacity; } void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window UNUSED, bool enabled UNUSED) { } bool _glfwPlatformRawMouseMotionSupported(void) { return true; } void _glfwPlatformShowWindow(_GLFWwindow* window) { window->null.visible = true; } void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED) { } int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED) { return false; } void _glfwPlatformHideWindow(_GLFWwindow* window) { if (_glfw.null.focusedWindow == window) { _glfw.null.focusedWindow = NULL; _glfwInputWindowFocus(window, false); } window->null.visible = false; } void _glfwPlatformFocusWindow(_GLFWwindow* window) { if (_glfw.null.focusedWindow == window) return; if (!window->null.visible) return; _GLFWwindow* previous = _glfw.null.focusedWindow; _glfw.null.focusedWindow = window; if (previous) { _glfwInputWindowFocus(previous, false); if (previous->monitor && previous->autoIconify) _glfwPlatformIconifyWindow(previous); } _glfwInputWindowFocus(window, true); } int _glfwPlatformWindowFocused(_GLFWwindow* window) { return _glfw.null.focusedWindow == window; } int _glfwPlatformWindowOccluded(_GLFWwindow* window UNUSED) { return false; } int _glfwPlatformWindowIconified(_GLFWwindow* window) { return window->null.iconified; } int _glfwPlatformWindowVisible(_GLFWwindow* window) { return window->null.visible; } void _glfwPlatformPollEvents(void) { } void _glfwPlatformWaitEvents(void) { } void _glfwPlatformWaitEventsTimeout(monotonic_t timeout UNUSED) { } void _glfwPlatformPostEmptyEvent(void) { } void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) { if (xpos) *xpos = _glfw.null.xcursor - window->null.xpos; if (ypos) *ypos = _glfw.null.ycursor - window->null.ypos; } void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) { _glfw.null.xcursor = window->null.xpos + (int) x; _glfw.null.ycursor = window->null.ypos + (int) y; } void _glfwPlatformSetCursorMode(_GLFWwindow* window UNUSED, int mode UNUSED) { } int _glfwPlatformCreateCursor(_GLFWcursor* cursor UNUSED, const GLFWimage* image UNUSED, int xhot UNUSED, int yhot UNUSED, int count UNUSED) { return true; } int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor UNUSED, int shape UNUSED) { return true; } void _glfwPlatformDestroyCursor(_GLFWcursor* cursor UNUSED) { } void _glfwPlatformSetCursor(_GLFWwindow* window UNUSED, _GLFWcursor* cursor UNUSED) { } void _glfwPlatformSetClipboardString(const char* string) { char* copy = _glfw_strdup(string); free(_glfw.null.clipboardString); _glfw.null.clipboardString = copy; } const char* _glfwPlatformGetClipboardString(void) { return _glfw.null.clipboardString; } const char* _glfwPlatformGetNativeKeyName(int native_key) { switch (scancode) { case GLFW_KEY_APOSTROPHE: return "'"; case GLFW_KEY_COMMA: return ","; case GLFW_KEY_MINUS: case GLFW_KEY_KP_SUBTRACT: return "-"; case GLFW_KEY_PERIOD: case GLFW_KEY_KP_DECIMAL: return "."; case GLFW_KEY_SLASH: case GLFW_KEY_KP_DIVIDE: return "/"; case GLFW_KEY_SEMICOLON: return ";"; case GLFW_KEY_EQUAL: case GLFW_KEY_KP_EQUAL: return "="; case GLFW_KEY_LEFT_BRACKET: return "["; case GLFW_KEY_RIGHT_BRACKET: return "]"; case GLFW_KEY_KP_MULTIPLY: return "*"; case GLFW_KEY_KP_ADD: return "+"; case GLFW_KEY_BACKSLASH: case GLFW_KEY_WORLD_1: case GLFW_KEY_WORLD_2: return "\\"; case GLFW_KEY_0: case GLFW_KEY_KP_0: return "0"; case GLFW_KEY_1: case GLFW_KEY_KP_1: return "1"; case GLFW_KEY_2: case GLFW_KEY_KP_2: return "2"; case GLFW_KEY_3: case GLFW_KEY_KP_3: return "3"; case GLFW_KEY_4: case GLFW_KEY_KP_4: return "4"; case GLFW_KEY_5: case GLFW_KEY_KP_5: return "5"; case GLFW_KEY_6: case GLFW_KEY_KP_6: return "6"; case GLFW_KEY_7: case GLFW_KEY_KP_7: return "7"; case GLFW_KEY_8: case GLFW_KEY_KP_8: return "8"; case GLFW_KEY_9: case GLFW_KEY_KP_9: return "9"; case GLFW_KEY_A: return "a"; case GLFW_KEY_B: return "b"; case GLFW_KEY_C: return "c"; case GLFW_KEY_D: return "d"; case GLFW_KEY_E: return "e"; case GLFW_KEY_F: return "f"; case GLFW_KEY_G: return "g"; case GLFW_KEY_H: return "h"; case GLFW_KEY_I: return "i"; case GLFW_KEY_J: return "j"; case GLFW_KEY_K: return "k"; case GLFW_KEY_L: return "l"; case GLFW_KEY_M: return "m"; case GLFW_KEY_N: return "n"; case GLFW_KEY_O: return "o"; case GLFW_KEY_P: return "p"; case GLFW_KEY_Q: return "q"; case GLFW_KEY_R: return "r"; case GLFW_KEY_S: return "s"; case GLFW_KEY_T: return "t"; case GLFW_KEY_U: return "u"; case GLFW_KEY_V: return "v"; case GLFW_KEY_W: return "w"; case GLFW_KEY_X: return "x"; case GLFW_KEY_Y: return "y"; case GLFW_KEY_Z: return "z"; } return NULL; } int _glfwPlatformGetNativeKeyForKey(int key) { return key; } void _glfwPlatformGetRequiredInstanceExtensions(char** extensions UNUSED) { } int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance UNUSED, VkPhysicalDevice device UNUSED, uint32_t queuefamily UNUSED) { return false; } VkResult _glfwPlatformCreateWindowSurface(VkInstance instance UNUSED, _GLFWwindow* window UNUSED, const VkAllocationCallbacks* allocator UNUSED, VkSurfaceKHR* surface UNUSED) { // This seems like the most appropriate error to return here return VK_ERROR_EXTENSION_NOT_PRESENT; } kitty-0.41.1/glfw/osmesa_context.c0000664000175000017510000002566514773370543016463 0ustar nileshnilesh//======================================================================== // GLFW 3.4 OSMesa - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2016 Google Inc. // Copyright (c) 2016-2017 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // Please use C89 style variable declarations in this file because VS 2010 //======================================================================== #include #include #include #include "internal.h" static void makeContextCurrentOSMesa(_GLFWwindow* window) { if (window) { int width, height; _glfwPlatformGetFramebufferSize(window, &width, &height); // Check to see if we need to allocate a new buffer if ((window->context.osmesa.buffer == NULL) || (width != window->context.osmesa.width) || (height != window->context.osmesa.height)) { free(window->context.osmesa.buffer); // Allocate the new buffer (width * height * 8-bit RGBA) window->context.osmesa.buffer = calloc(4, (size_t) width * height); window->context.osmesa.width = width; window->context.osmesa.height = height; } if (!OSMesaMakeCurrent(window->context.osmesa.handle, window->context.osmesa.buffer, GL_UNSIGNED_BYTE, width, height)) { _glfwInputError(GLFW_PLATFORM_ERROR, "OSMesa: Failed to make context current"); return; } } _glfwPlatformSetTls(&_glfw.contextSlot, window); } static GLFWglproc getProcAddressOSMesa(const char* procname) { return (GLFWglproc) OSMesaGetProcAddress(procname); } static void destroyContextOSMesa(_GLFWwindow* window) { if (window->context.osmesa.handle) { OSMesaDestroyContext(window->context.osmesa.handle); window->context.osmesa.handle = NULL; } if (window->context.osmesa.buffer) { free(window->context.osmesa.buffer); window->context.osmesa.width = 0; window->context.osmesa.height = 0; } } static void swapBuffersOSMesa(_GLFWwindow* window UNUSED) { // No double buffering on OSMesa } static void swapIntervalOSMesa(int interval UNUSED) { // No swap interval on OSMesa } static int extensionSupportedOSMesa(const char* extension UNUSED) { // OSMesa does not have extensions return false; } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// bool _glfwInitOSMesa(void) { int i; const char* sonames[] = { #if defined(_GLFW_OSMESA_LIBRARY) _GLFW_OSMESA_LIBRARY, #elif defined(_WIN32) "libOSMesa.dll", "OSMesa.dll", #elif defined(__APPLE__) "libOSMesa.8.dylib", #elif defined(__CYGWIN__) "libOSMesa-8.so", #else "libOSMesa.so.8", "libOSMesa.so.6", #endif NULL }; if (_glfw.osmesa.handle) return true; for (i = 0; sonames[i]; i++) { _glfw.osmesa.handle = _glfw_dlopen(sonames[i]); if (_glfw.osmesa.handle) break; } if (!_glfw.osmesa.handle) { _glfwInputError(GLFW_API_UNAVAILABLE, "OSMesa: Library not found"); return false; } glfw_dlsym(_glfw.osmesa.CreateContextExt, _glfw.osmesa.handle, "OSMesaCreateContextExt"); glfw_dlsym(_glfw.osmesa.CreateContextAttribs, _glfw.osmesa.handle, "OSMesaCreateContextAttribs"); glfw_dlsym(_glfw.osmesa.DestroyContext, _glfw.osmesa.handle, "OSMesaDestroyContext"); glfw_dlsym(_glfw.osmesa.MakeCurrent, _glfw.osmesa.handle, "OSMesaMakeCurrent"); glfw_dlsym(_glfw.osmesa.GetColorBuffer, _glfw.osmesa.handle, "OSMesaGetColorBuffer"); glfw_dlsym(_glfw.osmesa.GetDepthBuffer, _glfw.osmesa.handle, "OSMesaGetDepthBuffer"); glfw_dlsym(_glfw.osmesa.GetProcAddress, _glfw.osmesa.handle, "OSMesaGetProcAddress"); if (!_glfw.osmesa.CreateContextExt || !_glfw.osmesa.DestroyContext || !_glfw.osmesa.MakeCurrent || !_glfw.osmesa.GetColorBuffer || !_glfw.osmesa.GetDepthBuffer || !_glfw.osmesa.GetProcAddress) { _glfwInputError(GLFW_PLATFORM_ERROR, "OSMesa: Failed to load required entry points"); _glfwTerminateOSMesa(); return false; } return true; } void _glfwTerminateOSMesa(void) { if (_glfw.osmesa.handle) { _glfw_dlclose(_glfw.osmesa.handle); _glfw.osmesa.handle = NULL; } } #define setAttrib(a, v) \ { \ assert(((size_t) index + 1) < sizeof(attribs) / sizeof(attribs[0])); \ attribs[index++] = a; \ attribs[index++] = v; \ } bool _glfwCreateContextOSMesa(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { OSMesaContext share = NULL; const int accumBits = fbconfig->accumRedBits + fbconfig->accumGreenBits + fbconfig->accumBlueBits + fbconfig->accumAlphaBits; if (ctxconfig->client == GLFW_OPENGL_ES_API) { _glfwInputError(GLFW_API_UNAVAILABLE, "OSMesa: OpenGL ES is not available on OSMesa"); return false; } if (ctxconfig->share) share = ctxconfig->share->context.osmesa.handle; if (OSMesaCreateContextAttribs) { int index = 0, attribs[40]; setAttrib(OSMESA_FORMAT, OSMESA_RGBA); setAttrib(OSMESA_DEPTH_BITS, fbconfig->depthBits); setAttrib(OSMESA_STENCIL_BITS, fbconfig->stencilBits); setAttrib(OSMESA_ACCUM_BITS, accumBits); if (ctxconfig->profile == GLFW_OPENGL_CORE_PROFILE) { setAttrib(OSMESA_PROFILE, OSMESA_CORE_PROFILE); } else if (ctxconfig->profile == GLFW_OPENGL_COMPAT_PROFILE) { setAttrib(OSMESA_PROFILE, OSMESA_COMPAT_PROFILE); } if (ctxconfig->major != 1 || ctxconfig->minor != 0) { setAttrib(OSMESA_CONTEXT_MAJOR_VERSION, ctxconfig->major); setAttrib(OSMESA_CONTEXT_MINOR_VERSION, ctxconfig->minor); } if (ctxconfig->forward) { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "OSMesa: Forward-compatible contexts not supported"); return false; } setAttrib(0, 0); window->context.osmesa.handle = OSMesaCreateContextAttribs(attribs, share); } else { if (ctxconfig->profile) { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "OSMesa: OpenGL profiles unavailable"); return false; } window->context.osmesa.handle = OSMesaCreateContextExt(OSMESA_RGBA, fbconfig->depthBits, fbconfig->stencilBits, accumBits, share); } if (window->context.osmesa.handle == NULL) { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "OSMesa: Failed to create context"); return false; } window->context.makeCurrent = makeContextCurrentOSMesa; window->context.swapBuffers = swapBuffersOSMesa; window->context.swapInterval = swapIntervalOSMesa; window->context.extensionSupported = extensionSupportedOSMesa; window->context.getProcAddress = getProcAddressOSMesa; window->context.destroy = destroyContextOSMesa; return true; } #undef setAttrib ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI int glfwGetOSMesaColorBuffer(GLFWwindow* handle, int* width, int* height, int* format, void** buffer) { void* mesaBuffer; GLint mesaWidth, mesaHeight, mesaFormat; _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(false); if (!OSMesaGetColorBuffer(window->context.osmesa.handle, &mesaWidth, &mesaHeight, &mesaFormat, &mesaBuffer)) { _glfwInputError(GLFW_PLATFORM_ERROR, "OSMesa: Failed to retrieve color buffer"); return false; } if (width) *width = mesaWidth; if (height) *height = mesaHeight; if (format) *format = mesaFormat; if (buffer) *buffer = mesaBuffer; return true; } GLFWAPI int glfwGetOSMesaDepthBuffer(GLFWwindow* handle, int* width, int* height, int* bytesPerValue, void** buffer) { void* mesaBuffer; GLint mesaWidth, mesaHeight, mesaBytes; _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(false); if (!OSMesaGetDepthBuffer(window->context.osmesa.handle, &mesaWidth, &mesaHeight, &mesaBytes, &mesaBuffer)) { _glfwInputError(GLFW_PLATFORM_ERROR, "OSMesa: Failed to retrieve depth buffer"); return false; } if (width) *width = mesaWidth; if (height) *height = mesaHeight; if (bytesPerValue) *bytesPerValue = mesaBytes; if (buffer) *buffer = mesaBuffer; return true; } GLFWAPI OSMesaContext glfwGetOSMesaContext(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (window->context.client == GLFW_NO_API) { _glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL); return NULL; } return window->context.osmesa.handle; } kitty-0.41.1/glfw/osmesa_context.h0000664000175000017510000000703214773370543016454 0ustar nileshnilesh//======================================================================== // GLFW 3.4 OSMesa - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2016 Google Inc. // Copyright (c) 2016-2017 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #define OSMESA_RGBA 0x1908 #define OSMESA_FORMAT 0x22 #define OSMESA_DEPTH_BITS 0x30 #define OSMESA_STENCIL_BITS 0x31 #define OSMESA_ACCUM_BITS 0x32 #define OSMESA_PROFILE 0x33 #define OSMESA_CORE_PROFILE 0x34 #define OSMESA_COMPAT_PROFILE 0x35 #define OSMESA_CONTEXT_MAJOR_VERSION 0x36 #define OSMESA_CONTEXT_MINOR_VERSION 0x37 typedef void* OSMesaContext; typedef void (*OSMESAproc)(void); typedef OSMesaContext (GLAPIENTRY * PFN_OSMesaCreateContextExt)(GLenum,GLint,GLint,GLint,OSMesaContext); typedef OSMesaContext (GLAPIENTRY * PFN_OSMesaCreateContextAttribs)(const int*,OSMesaContext); typedef void (GLAPIENTRY * PFN_OSMesaDestroyContext)(OSMesaContext); typedef int (GLAPIENTRY * PFN_OSMesaMakeCurrent)(OSMesaContext,void*,int,int,int); typedef int (GLAPIENTRY * PFN_OSMesaGetColorBuffer)(OSMesaContext,int*,int*,int*,void**); typedef int (GLAPIENTRY * PFN_OSMesaGetDepthBuffer)(OSMesaContext,int*,int*,int*,void**); typedef GLFWglproc (GLAPIENTRY * PFN_OSMesaGetProcAddress)(const char*); #define OSMesaCreateContextExt _glfw.osmesa.CreateContextExt #define OSMesaCreateContextAttribs _glfw.osmesa.CreateContextAttribs #define OSMesaDestroyContext _glfw.osmesa.DestroyContext #define OSMesaMakeCurrent _glfw.osmesa.MakeCurrent #define OSMesaGetColorBuffer _glfw.osmesa.GetColorBuffer #define OSMesaGetDepthBuffer _glfw.osmesa.GetDepthBuffer #define OSMesaGetProcAddress _glfw.osmesa.GetProcAddress // OSMesa-specific per-context data // typedef struct _GLFWcontextOSMesa { OSMesaContext handle; int width; int height; void* buffer; } _GLFWcontextOSMesa; // OSMesa-specific global data // typedef struct _GLFWlibraryOSMesa { void* handle; PFN_OSMesaCreateContextExt CreateContextExt; PFN_OSMesaCreateContextAttribs CreateContextAttribs; PFN_OSMesaDestroyContext DestroyContext; PFN_OSMesaMakeCurrent MakeCurrent; PFN_OSMesaGetColorBuffer GetColorBuffer; PFN_OSMesaGetDepthBuffer GetDepthBuffer; PFN_OSMesaGetProcAddress GetProcAddress; } _GLFWlibraryOSMesa; bool _glfwInitOSMesa(void); void _glfwTerminateOSMesa(void); bool _glfwCreateContextOSMesa(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig); kitty-0.41.1/glfw/posix_thread.c0000664000175000017510000000627314773370543016113 0ustar nileshnilesh//======================================================================== // GLFW 3.4 POSIX - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2017 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #include "internal.h" #include #include ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// bool _glfwPlatformCreateTls(_GLFWtls* tls) { assert(tls->posix.allocated == false); if (pthread_key_create(&tls->posix.key, NULL) != 0) { _glfwInputError(GLFW_PLATFORM_ERROR, "POSIX: Failed to create context TLS"); return false; } tls->posix.allocated = true; return true; } void _glfwPlatformDestroyTls(_GLFWtls* tls) { if (tls->posix.allocated) pthread_key_delete(tls->posix.key); memset(tls, 0, sizeof(_GLFWtls)); } void* _glfwPlatformGetTls(_GLFWtls* tls) { assert(tls->posix.allocated == true); return pthread_getspecific(tls->posix.key); } void _glfwPlatformSetTls(_GLFWtls* tls, void* value) { assert(tls->posix.allocated == true); pthread_setspecific(tls->posix.key, value); } bool _glfwPlatformCreateMutex(_GLFWmutex* mutex) { assert(mutex->posix.allocated == false); if (pthread_mutex_init(&mutex->posix.handle, NULL) != 0) { _glfwInputError(GLFW_PLATFORM_ERROR, "POSIX: Failed to create mutex"); return false; } return mutex->posix.allocated = true; } void _glfwPlatformDestroyMutex(_GLFWmutex* mutex) { if (mutex->posix.allocated) pthread_mutex_destroy(&mutex->posix.handle); memset(mutex, 0, sizeof(_GLFWmutex)); } void _glfwPlatformLockMutex(_GLFWmutex* mutex) { assert(mutex->posix.allocated == true); pthread_mutex_lock(&mutex->posix.handle); } void _glfwPlatformUnlockMutex(_GLFWmutex* mutex) { assert(mutex->posix.allocated == true); pthread_mutex_unlock(&mutex->posix.handle); } kitty-0.41.1/glfw/posix_thread.h0000664000175000017510000000322214773370543016107 0ustar nileshnilesh//======================================================================== // GLFW 3.4 POSIX - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2017 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #include #define _GLFW_PLATFORM_TLS_STATE _GLFWtlsPOSIX posix #define _GLFW_PLATFORM_MUTEX_STATE _GLFWmutexPOSIX posix // POSIX-specific thread local storage data // typedef struct _GLFWtlsPOSIX { bool allocated; pthread_key_t key; } _GLFWtlsPOSIX; // POSIX-specific mutex data // typedef struct _GLFWmutexPOSIX { bool allocated; pthread_mutex_t handle; } _GLFWmutexPOSIX; kitty-0.41.1/glfw/source-info.json0000664000175000017510000000756614773370543016410 0ustar nileshnilesh{ "cocoa": { "headers": [ "cocoa_platform.h", "cocoa_joystick.h", "posix_thread.h", "nsgl_context.h", "egl_context.h", "osmesa_context.h" ], "sources": [ "cocoa_init.m", "cocoa_joystick.m", "cocoa_monitor.m", "cocoa_window.m", "cocoa_displaylink.m", "posix_thread.c", "nsgl_context.m", "egl_context.c", "osmesa_context.c" ] }, "common": { "headers": [ "internal.h", "mappings.h" ], "sources": [ "context.c", "init.c", "input.c", "monitor.c", "vulkan.c", "monotonic.c", "window.c" ] }, "osmesa": { "headers": [ "null_platform.h", "null_joystick.h", "posix_thread.h", "osmesa_context.h" ], "sources": [ "null_init.c", "null_monitor.c", "null_window.c", "null_joystick.c", "posix_thread.c", "osmesa_context.c" ] }, "wayland": { "headers": [ "wl_platform.h", "posix_thread.h", "wl_cursors.h", "wl_text_input.h", "xkb_glfw.h", "dbus_glfw.h", "ibus_glfw.h", "backend_utils.h", "egl_context.h", "osmesa_context.h", "linux_joystick.h", "null_joystick.h", "linux_notify.h", "linux_desktop_settings.h", "wl_client_side_decorations.h", "main_loop.h" ], "protocols": [ "stable/xdg-shell/xdg-shell.xml", "stable/viewporter/viewporter.xml", "unstable/relative-pointer/relative-pointer-unstable-v1.xml", "unstable/pointer-constraints/pointer-constraints-unstable-v1.xml", "unstable/xdg-decoration/xdg-decoration-unstable-v1.xml", "unstable/primary-selection/primary-selection-unstable-v1.xml", "unstable/text-input/text-input-unstable-v3.xml", "staging/xdg-activation/xdg-activation-v1.xml", "unstable/tablet/tablet-unstable-v2.xml", "staging/cursor-shape/cursor-shape-v1.xml", "staging/fractional-scale/fractional-scale-v1.xml", "staging/single-pixel-buffer/single-pixel-buffer-v1.xml", "unstable/idle-inhibit/idle-inhibit-unstable-v1.xml", "staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml", "kwin-blur-v1.xml", "wlr-layer-shell-unstable-v1.xml" ], "sources": [ "wl_init.c", "wl_monitor.c", "wl_window.c", "wl_cursors.c", "wl_text_input.c", "wl_client_side_decorations.c", "posix_thread.c", "xkb_glfw.c", "dbus_glfw.c", "ibus_glfw.c", "egl_context.c", "osmesa_context.c", "backend_utils.c", "linux_joystick.c", "linux_desktop_settings.c", "null_joystick.c", "linux_notify.c" ] }, "wayland_protocols": [ 1, 17 ], "win32": { "headers": [ "win32_platform.h", "win32_joystick.h", "wgl_context.h", "egl_context.h", "osmesa_context.h" ], "sources": [ "win32_init.c", "win32_joystick.c", "win32_monitor.c", "win32_time.c", "win32_thread.c", "win32_window.c", "wgl_context.c", "egl_context.c", "osmesa_context.c" ] }, "x11": { "headers": [ "x11_platform.h", "xkb_glfw.h", "dbus_glfw.h", "ibus_glfw.h", "backend_utils.h", "posix_thread.h", "glx_context.h", "egl_context.h", "osmesa_context.h", "linux_joystick.h", "null_joystick.h", "linux_notify.h", "linux_desktop_settings.h", "main_loop.h" ], "sources": [ "x11_init.c", "x11_monitor.c", "x11_window.c", "xkb_glfw.c", "dbus_glfw.c", "ibus_glfw.c", "posix_thread.c", "glx_context.c", "egl_context.c", "osmesa_context.c", "backend_utils.c", "linux_joystick.c", "null_joystick.c", "linux_desktop_settings.c", "linux_notify.c" ] } } kitty-0.41.1/glfw/vulkan.c0000664000175000017510000002656314773370543014726 0ustar nileshnilesh//======================================================================== // GLFW 3.4 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2018 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // Please use C89 style variable declarations in this file because VS 2010 //======================================================================== #include "internal.h" #include #include #include #define _GLFW_FIND_LOADER 1 #define _GLFW_REQUIRE_LOADER 2 ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// bool _glfwInitVulkan(int mode) { VkResult err; VkExtensionProperties* ep; uint32_t i, count; if (_glfw.vk.available) return true; #if !defined(_GLFW_VULKAN_STATIC) #if defined(_GLFW_VULKAN_LIBRARY) _glfw.vk.handle = _glfw_dlopen(_GLFW_VULKAN_LIBRARY); #elif defined(_GLFW_WIN32) _glfw.vk.handle = _glfw_dlopen("vulkan-1.dll"); #elif defined(_GLFW_COCOA) _glfw.vk.handle = _glfw_dlopen("libvulkan.1.dylib"); if (!_glfw.vk.handle) _glfw.vk.handle = _glfwLoadLocalVulkanLoaderNS(); #else _glfw.vk.handle = _glfw_dlopen("libvulkan.so.1"); #endif if (!_glfw.vk.handle) { if (mode == _GLFW_REQUIRE_LOADER) _glfwInputError(GLFW_API_UNAVAILABLE, "Vulkan: Loader not found"); return false; } *(void **) &_glfw.vk.GetInstanceProcAddr = _glfw_dlsym(_glfw.vk.handle, "vkGetInstanceProcAddr"); if (!_glfw.vk.GetInstanceProcAddr) { _glfwInputError(GLFW_API_UNAVAILABLE, "Vulkan: Loader does not export vkGetInstanceProcAddr"); _glfwTerminateVulkan(); return false; } _glfw.vk.EnumerateInstanceExtensionProperties = (PFN_vkEnumerateInstanceExtensionProperties) vkGetInstanceProcAddr(NULL, "vkEnumerateInstanceExtensionProperties"); if (!_glfw.vk.EnumerateInstanceExtensionProperties) { _glfwInputError(GLFW_API_UNAVAILABLE, "Vulkan: Failed to retrieve vkEnumerateInstanceExtensionProperties"); _glfwTerminateVulkan(); return false; } #endif // _GLFW_VULKAN_STATIC err = vkEnumerateInstanceExtensionProperties(NULL, &count, NULL); if (err) { // NOTE: This happens on systems with a loader but without any Vulkan ICD if (mode == _GLFW_REQUIRE_LOADER) { _glfwInputError(GLFW_API_UNAVAILABLE, "Vulkan: Failed to query instance extension count: %s", _glfwGetVulkanResultString(err)); } _glfwTerminateVulkan(); return false; } ep = calloc(count, sizeof(VkExtensionProperties)); err = vkEnumerateInstanceExtensionProperties(NULL, &count, ep); if (err) { _glfwInputError(GLFW_API_UNAVAILABLE, "Vulkan: Failed to query instance extensions: %s", _glfwGetVulkanResultString(err)); free(ep); _glfwTerminateVulkan(); return false; } for (i = 0; i < count; i++) { if (strcmp(ep[i].extensionName, "VK_KHR_surface") == 0) _glfw.vk.KHR_surface = true; #if defined(_GLFW_WIN32) else if (strcmp(ep[i].extensionName, "VK_KHR_win32_surface") == 0) _glfw.vk.KHR_win32_surface = true; #elif defined(_GLFW_COCOA) else if (strcmp(ep[i].extensionName, "VK_MVK_macos_surface") == 0) _glfw.vk.MVK_macos_surface = true; else if (strcmp(ep[i].extensionName, "VK_EXT_metal_surface") == 0) _glfw.vk.EXT_metal_surface = true; #elif defined(_GLFW_X11) else if (strcmp(ep[i].extensionName, "VK_KHR_xlib_surface") == 0) _glfw.vk.KHR_xlib_surface = true; else if (strcmp(ep[i].extensionName, "VK_KHR_xcb_surface") == 0) _glfw.vk.KHR_xcb_surface = true; #elif defined(_GLFW_WAYLAND) else if (strcmp(ep[i].extensionName, "VK_KHR_wayland_surface") == 0) _glfw.vk.KHR_wayland_surface = true; #endif } free(ep); _glfw.vk.available = true; _glfwPlatformGetRequiredInstanceExtensions(_glfw.vk.extensions); return true; } void _glfwTerminateVulkan(void) { #if !defined(_GLFW_VULKAN_STATIC) if (_glfw.vk.handle) _glfw_dlclose(_glfw.vk.handle); #endif } const char* _glfwGetVulkanResultString(VkResult result) { switch (result) { case VK_SUCCESS: return "Success"; case VK_NOT_READY: return "A fence or query has not yet completed"; case VK_TIMEOUT: return "A wait operation has not completed in the specified time"; case VK_EVENT_SET: return "An event is signaled"; case VK_EVENT_RESET: return "An event is unsignaled"; case VK_INCOMPLETE: return "A return array was too small for the result"; case VK_ERROR_OUT_OF_HOST_MEMORY: return "A host memory allocation has failed"; case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "A device memory allocation has failed"; case VK_ERROR_INITIALIZATION_FAILED: return "Initialization of an object could not be completed for implementation-specific reasons"; case VK_ERROR_DEVICE_LOST: return "The logical or physical device has been lost"; case VK_ERROR_MEMORY_MAP_FAILED: return "Mapping of a memory object has failed"; case VK_ERROR_LAYER_NOT_PRESENT: return "A requested layer is not present or could not be loaded"; case VK_ERROR_EXTENSION_NOT_PRESENT: return "A requested extension is not supported"; case VK_ERROR_FEATURE_NOT_PRESENT: return "A requested feature is not supported"; case VK_ERROR_INCOMPATIBLE_DRIVER: return "The requested version of Vulkan is not supported by the driver or is otherwise incompatible"; case VK_ERROR_TOO_MANY_OBJECTS: return "Too many objects of the type have already been created"; case VK_ERROR_FORMAT_NOT_SUPPORTED: return "A requested format is not supported on this device"; case VK_ERROR_SURFACE_LOST_KHR: return "A surface is no longer available"; case VK_SUBOPTIMAL_KHR: return "A swapchain no longer matches the surface properties exactly, but can still be used"; case VK_ERROR_OUT_OF_DATE_KHR: return "A surface has changed in such a way that it is no longer compatible with the swapchain"; case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: return "The display used by a swapchain does not use the same presentable image layout"; case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return "The requested window is already connected to a VkSurfaceKHR, or to some other non-Vulkan API"; case VK_ERROR_VALIDATION_FAILED_EXT: return "A validation layer found an error"; default: return "ERROR: UNKNOWN VULKAN ERROR"; } } ////////////////////////////////////////////////////////////////////////// ////// GLFW public API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI int glfwVulkanSupported(void) { _GLFW_REQUIRE_INIT_OR_RETURN(false); return _glfwInitVulkan(_GLFW_FIND_LOADER); } GLFWAPI const char** glfwGetRequiredInstanceExtensions(uint32_t* count) { assert(count != NULL); *count = 0; _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (!_glfwInitVulkan(_GLFW_REQUIRE_LOADER)) return NULL; if (!_glfw.vk.extensions[0]) return NULL; *count = 2; return (const char**) _glfw.vk.extensions; } GLFWAPI GLFWvkproc glfwGetInstanceProcAddress(VkInstance instance, const char* procname) { GLFWvkproc proc; assert(procname != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (!_glfwInitVulkan(_GLFW_REQUIRE_LOADER)) return NULL; proc = (GLFWvkproc) vkGetInstanceProcAddr(instance, procname); #if defined(_GLFW_VULKAN_STATIC) if (!proc) { if (strcmp(procname, "vkGetInstanceProcAddr") == 0) return (GLFWvkproc) vkGetInstanceProcAddr; } #else if (!proc) *(void **) &proc = _glfw_dlsym(_glfw.vk.handle, procname); #endif return proc; } GLFWAPI int glfwGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhysicalDevice device, uint32_t queuefamily) { assert(instance != VK_NULL_HANDLE); assert(device != VK_NULL_HANDLE); _GLFW_REQUIRE_INIT_OR_RETURN(false); if (!_glfwInitVulkan(_GLFW_REQUIRE_LOADER)) return false; if (!_glfw.vk.extensions[0]) { _glfwInputError(GLFW_API_UNAVAILABLE, "Vulkan: Window surface creation extensions not found"); return false; } return _glfwPlatformGetPhysicalDevicePresentationSupport(instance, device, queuefamily); } GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance, GLFWwindow* handle, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(instance != VK_NULL_HANDLE); assert(window != NULL); assert(surface != NULL); *surface = VK_NULL_HANDLE; _GLFW_REQUIRE_INIT_OR_RETURN(VK_ERROR_INITIALIZATION_FAILED); if (!_glfwInitVulkan(_GLFW_REQUIRE_LOADER)) return VK_ERROR_INITIALIZATION_FAILED; if (!_glfw.vk.extensions[0]) { _glfwInputError(GLFW_API_UNAVAILABLE, "Vulkan: Window surface creation extensions not found"); return VK_ERROR_EXTENSION_NOT_PRESENT; } if (window->context.client != GLFW_NO_API) { _glfwInputError(GLFW_INVALID_VALUE, "Vulkan: Window surface creation requires the window to have the client API set to GLFW_NO_API"); return VK_ERROR_NATIVE_WINDOW_IN_USE_KHR; } return _glfwPlatformCreateWindowSurface(instance, window, allocator, surface); } kitty-0.41.1/glfw/window.c0000664000175000017510000011064414773370543014727 0ustar nileshnilesh//======================================================================== // GLFW 3.4 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2019 Camilla Löwy // Copyright (c) 2012 Torsten Walluhn // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // Please use C89 style variable declarations in this file because VS 2010 //======================================================================== #include "internal.h" #include "../kitty/monotonic.h" #include #include #include ////////////////////////////////////////////////////////////////////////// ////// GLFW event API ////// ////////////////////////////////////////////////////////////////////////// // Notifies shared code that a window has lost or received input focus // void _glfwInputWindowFocus(_GLFWwindow* window, bool focused) { if (window->callbacks.focus) window->callbacks.focus((GLFWwindow*) window, focused); if (!focused) { _glfw.focusedWindowId = 0; for (unsigned i = 0; i < arraysz(window->activated_keys); i++) { if (window->activated_keys[i].key > 0 && window->activated_keys[i].action == GLFW_PRESS) { const int native_key = _glfwPlatformGetNativeKeyForKey(window->activated_keys[i].key); GLFWkeyevent ev = {.key = window->activated_keys[i].key, .native_key = native_key, .action = GLFW_RELEASE, .fake_event_on_focus_change = true}; _glfwInputKeyboard(window, &ev); } } for (int button = 0; button <= GLFW_MOUSE_BUTTON_LAST; button++) { if (window->mouseButtons[button] == GLFW_PRESS) _glfwInputMouseClick(window, button, GLFW_RELEASE, 0); } } else _glfw.focusedWindowId = window->id; } _GLFWwindow* _glfwFocusedWindow(void) { if (_glfw.focusedWindowId) { _GLFWwindow *w = _glfw.windowListHead; while (w) { if (w->id == _glfw.focusedWindowId) return w; w = w->next; } } return NULL; } _GLFWwindow* _glfwWindowForId(GLFWid id) { _GLFWwindow *w = _glfw.windowListHead; while (w) { if (w->id == id) return w; w = w->next; } return NULL; } // Notifies shared code that a window's occlusion state has changed // void _glfwInputWindowOcclusion(_GLFWwindow* window, bool occluded) { if (window->callbacks.occlusion) window->callbacks.occlusion((GLFWwindow*) window, occluded); } // Notifies shared code that a window has moved // The position is specified in content area relative screen coordinates // void _glfwInputWindowPos(_GLFWwindow* window, int x, int y) { if (window->callbacks.pos) window->callbacks.pos((GLFWwindow*) window, x, y); } // Notifies shared code that a window has been resized // The size is specified in screen coordinates // void _glfwInputWindowSize(_GLFWwindow* window, int width, int height) { if (window->callbacks.size) window->callbacks.size((GLFWwindow*) window, width, height); } // Notifies shared code that a window has been iconified or restored // void _glfwInputWindowIconify(_GLFWwindow* window, bool iconified) { if (window->callbacks.iconify) window->callbacks.iconify((GLFWwindow*) window, iconified); } // Notifies shared code that a window has been maximized or restored // void _glfwInputWindowMaximize(_GLFWwindow* window, bool maximized) { if (window->callbacks.maximize) window->callbacks.maximize((GLFWwindow*) window, maximized); } // Notifies shared code that a window framebuffer has been resized // The size is specified in pixels // void _glfwInputFramebufferSize(_GLFWwindow* window, int width, int height) { if (window->callbacks.fbsize) window->callbacks.fbsize((GLFWwindow*) window, width, height); } // Notifies shared code that a window live resize is in progress // void _glfwInputLiveResize(_GLFWwindow* window, bool started) { if (window && window->callbacks.liveResize) window->callbacks.liveResize((GLFWwindow*) window, started); } // Notifies shared code that a window content scale has changed // The scale is specified as the ratio between the current and default DPI // void _glfwInputWindowContentScale(_GLFWwindow* window, float xscale, float yscale) { if (window->callbacks.scale) window->callbacks.scale((GLFWwindow*) window, xscale, yscale); } // Notifies shared code that the window contents needs updating // void _glfwInputWindowDamage(_GLFWwindow* window) { if (window->callbacks.refresh) window->callbacks.refresh((GLFWwindow*) window); } // Notifies shared code that the user wishes to close a window // void _glfwInputWindowCloseRequest(_GLFWwindow* window) { window->shouldClose = true; if (window->callbacks.close) window->callbacks.close((GLFWwindow*) window); } // Notifies shared code that a window has changed its desired monitor // void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor) { window->monitor = monitor; } ////////////////////////////////////////////////////////////////////////// ////// GLFW public API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share) { _GLFWfbconfig fbconfig; _GLFWctxconfig ctxconfig; _GLFWwndconfig wndconfig; _GLFWwindow* window; assert(title != NULL); assert(width >= 0); assert(height >= 0); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); if (width <= 0 || height <= 0) { _glfwInputError(GLFW_INVALID_VALUE, "Invalid window size %ix%i", width, height); return NULL; } fbconfig = _glfw.hints.framebuffer; ctxconfig = _glfw.hints.context; wndconfig = _glfw.hints.window; wndconfig.width = width; wndconfig.height = height; wndconfig.title = title; ctxconfig.share = (_GLFWwindow*) share; if (!_glfwIsValidContextConfig(&ctxconfig)) return NULL; static GLFWid windowIdCounter = 0; window = calloc(1, sizeof(_GLFWwindow)); window->next = _glfw.windowListHead; window->id = ++windowIdCounter; _glfw.windowListHead = window; window->videoMode.width = width; window->videoMode.height = height; window->videoMode.redBits = fbconfig.redBits; window->videoMode.greenBits = fbconfig.greenBits; window->videoMode.blueBits = fbconfig.blueBits; window->videoMode.refreshRate = _glfw.hints.refreshRate; window->monitor = (_GLFWmonitor*) monitor; window->resizable = wndconfig.resizable; window->decorated = wndconfig.decorated; window->autoIconify = wndconfig.autoIconify; window->floating = wndconfig.floating; window->focusOnShow = wndconfig.focusOnShow; window->mousePassthrough = wndconfig.mousePassthrough; window->cursorMode = GLFW_CURSOR_NORMAL; window->minwidth = GLFW_DONT_CARE; window->minheight = GLFW_DONT_CARE; window->maxwidth = GLFW_DONT_CARE; window->maxheight = GLFW_DONT_CARE; window->numer = GLFW_DONT_CARE; window->denom = GLFW_DONT_CARE; window->widthincr = GLFW_DONT_CARE; window->heightincr = GLFW_DONT_CARE; // Open the actual window and create its context if (!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig)) { glfwDestroyWindow((GLFWwindow*) window); return NULL; } if (ctxconfig.client != GLFW_NO_API) { if (!_glfwRefreshContextAttribs(window, &ctxconfig)) { glfwDestroyWindow((GLFWwindow*) window); return NULL; } } if (wndconfig.mousePassthrough) _glfwPlatformSetWindowMousePassthrough(window, true); if (window->monitor) { if (wndconfig.centerCursor) _glfwCenterCursorInContentArea(window); } else { if (wndconfig.visible) { _glfwPlatformShowWindow(window); #ifndef _GLFW_WAYLAND if (wndconfig.focused) _glfwPlatformFocusWindow(window); #endif } } return (GLFWwindow*) window; } void glfwDefaultWindowHints(void) { _GLFW_REQUIRE_INIT(); // The default is OpenGL with minimum version 1.0 memset(&_glfw.hints.context, 0, sizeof(_glfw.hints.context)); _glfw.hints.context.client = GLFW_OPENGL_API; _glfw.hints.context.source = GLFW_NATIVE_CONTEXT_API; _glfw.hints.context.major = 1; _glfw.hints.context.minor = 0; // The default is a focused, visible, resizable window with decorations memset(&_glfw.hints.window, 0, sizeof(_glfw.hints.window)); _glfw.hints.window.resizable = true; _glfw.hints.window.visible = true; _glfw.hints.window.decorated = true; _glfw.hints.window.focused = true; _glfw.hints.window.autoIconify = true; _glfw.hints.window.centerCursor = true; _glfw.hints.window.focusOnShow = true; _glfw.hints.window.blur_radius = 0; // The default is 24 bits of color, 24 bits of depth and 8 bits of stencil, // double buffered memset(&_glfw.hints.framebuffer, 0, sizeof(_glfw.hints.framebuffer)); _glfw.hints.framebuffer.redBits = 8; _glfw.hints.framebuffer.greenBits = 8; _glfw.hints.framebuffer.blueBits = 8; _glfw.hints.framebuffer.alphaBits = 8; _glfw.hints.framebuffer.depthBits = 24; _glfw.hints.framebuffer.stencilBits = 8; _glfw.hints.framebuffer.doublebuffer = true; // The default is to select the highest available refresh rate _glfw.hints.refreshRate = GLFW_DONT_CARE; // The default is to use full Retina resolution framebuffers _glfw.hints.window.ns.retina = true; // use the default colorspace assigned by the system _glfw.hints.window.ns.color_space = 0; } GLFWAPI void glfwWindowHint(int hint, int value) { _GLFW_REQUIRE_INIT(); switch (hint) { case GLFW_RED_BITS: _glfw.hints.framebuffer.redBits = value; return; case GLFW_GREEN_BITS: _glfw.hints.framebuffer.greenBits = value; return; case GLFW_BLUE_BITS: _glfw.hints.framebuffer.blueBits = value; return; case GLFW_ALPHA_BITS: _glfw.hints.framebuffer.alphaBits = value; return; case GLFW_DEPTH_BITS: _glfw.hints.framebuffer.depthBits = value; return; case GLFW_STENCIL_BITS: _glfw.hints.framebuffer.stencilBits = value; return; case GLFW_ACCUM_RED_BITS: _glfw.hints.framebuffer.accumRedBits = value; return; case GLFW_ACCUM_GREEN_BITS: _glfw.hints.framebuffer.accumGreenBits = value; return; case GLFW_ACCUM_BLUE_BITS: _glfw.hints.framebuffer.accumBlueBits = value; return; case GLFW_ACCUM_ALPHA_BITS: _glfw.hints.framebuffer.accumAlphaBits = value; return; case GLFW_AUX_BUFFERS: _glfw.hints.framebuffer.auxBuffers = value; return; case GLFW_STEREO: _glfw.hints.framebuffer.stereo = value ? true : false; return; case GLFW_DOUBLEBUFFER: _glfw.hints.framebuffer.doublebuffer = value ? true : false; return; case GLFW_TRANSPARENT_FRAMEBUFFER: _glfw.hints.framebuffer.transparent = value ? true : false; return; case GLFW_SAMPLES: _glfw.hints.framebuffer.samples = value; return; case GLFW_SRGB_CAPABLE: _glfw.hints.framebuffer.sRGB = value ? true : false; return; case GLFW_RESIZABLE: _glfw.hints.window.resizable = value ? true : false; return; case GLFW_DECORATED: _glfw.hints.window.decorated = value ? true : false; return; case GLFW_FOCUSED: _glfw.hints.window.focused = value ? true : false; return; case GLFW_AUTO_ICONIFY: _glfw.hints.window.autoIconify = value ? true : false; return; case GLFW_FLOATING: _glfw.hints.window.floating = value ? true : false; return; case GLFW_MAXIMIZED: _glfw.hints.window.maximized = value ? true : false; return; case GLFW_VISIBLE: _glfw.hints.window.visible = value ? true : false; return; case GLFW_COCOA_RETINA_FRAMEBUFFER: _glfw.hints.window.ns.retina = value ? true : false; return; case GLFW_COCOA_COLOR_SPACE: _glfw.hints.window.ns.color_space = value; return; case GLFW_BLUR_RADIUS: _glfw.hints.window.blur_radius = value; return; case GLFW_COCOA_GRAPHICS_SWITCHING: _glfw.hints.context.nsgl.offline = value ? true : false; return; case GLFW_SCALE_TO_MONITOR: _glfw.hints.window.scaleToMonitor = value ? true : false; return; case GLFW_CENTER_CURSOR: _glfw.hints.window.centerCursor = value ? true : false; return; case GLFW_FOCUS_ON_SHOW: _glfw.hints.window.focusOnShow = value ? true : false; return; case GLFW_MOUSE_PASSTHROUGH: _glfw.hints.window.mousePassthrough = value ? true : false; return; case GLFW_CLIENT_API: _glfw.hints.context.client = value; return; case GLFW_CONTEXT_CREATION_API: _glfw.hints.context.source = value; return; case GLFW_CONTEXT_VERSION_MAJOR: _glfw.hints.context.major = value; return; case GLFW_CONTEXT_VERSION_MINOR: _glfw.hints.context.minor = value; return; case GLFW_CONTEXT_ROBUSTNESS: _glfw.hints.context.robustness = value; return; case GLFW_OPENGL_FORWARD_COMPAT: _glfw.hints.context.forward = value ? true : false; return; case GLFW_CONTEXT_DEBUG: _glfw.hints.context.debug = value ? true : false; return; case GLFW_CONTEXT_NO_ERROR: _glfw.hints.context.noerror = value ? true : false; return; case GLFW_OPENGL_PROFILE: _glfw.hints.context.profile = value; return; case GLFW_CONTEXT_RELEASE_BEHAVIOR: _glfw.hints.context.release = value; return; case GLFW_REFRESH_RATE: _glfw.hints.refreshRate = value; return; case GLFW_WAYLAND_BGCOLOR: _glfw.hints.window.wl.bgcolor = value; return; } _glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint 0x%08X", hint); } GLFWAPI void glfwWindowHintString(int hint, const char* value) { assert(value != NULL); _GLFW_REQUIRE_INIT(); switch (hint) { case GLFW_COCOA_FRAME_NAME: strncpy(_glfw.hints.window.ns.frameName, value, sizeof(_glfw.hints.window.ns.frameName) - 1); return; case GLFW_X11_CLASS_NAME: strncpy(_glfw.hints.window.x11.className, value, sizeof(_glfw.hints.window.x11.className) - 1); return; case GLFW_X11_INSTANCE_NAME: strncpy(_glfw.hints.window.x11.instanceName, value, sizeof(_glfw.hints.window.x11.instanceName) - 1); return; case GLFW_WAYLAND_APP_ID: strncpy(_glfw.hints.window.wl.appId, value, sizeof(_glfw.hints.window.wl.appId) - 1); return; } _glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint string 0x%08X", hint); } GLFWAPI void glfwDestroyWindow(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT(); // Allow closing of NULL (to match the behavior of free) if (window == NULL) return; // Clear all callbacks to avoid exposing a half torn-down window object memset(&window->callbacks, 0, sizeof(window->callbacks)); // The window's context must not be current on another thread when the // window is destroyed if (window == _glfwPlatformGetTls(&_glfw.contextSlot)) glfwMakeContextCurrent(NULL); _glfwPlatformDestroyWindow(window); // Unlink window from global linked list { _GLFWwindow** prev = &_glfw.windowListHead; while (*prev != window) prev = &((*prev)->next); *prev = window->next; } free(window); } GLFWAPI int glfwWindowShouldClose(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(0); return window->shouldClose; } GLFWAPI void glfwSetWindowShouldClose(GLFWwindow* handle, int value) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); window->shouldClose = value; } GLFWAPI void glfwSetWindowTitle(GLFWwindow* handle, const char* title) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); assert(title != NULL); _GLFW_REQUIRE_INIT(); _glfwPlatformSetWindowTitle(window, title); } GLFWAPI void glfwSetWindowIcon(GLFWwindow* handle, int count, const GLFWimage* images) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); assert(count >= 0); assert(count == 0 || images != NULL); _GLFW_REQUIRE_INIT(); _glfwPlatformSetWindowIcon(window, count, images); } GLFWAPI void glfwGetWindowPos(GLFWwindow* handle, int* xpos, int* ypos) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); if (xpos) *xpos = 0; if (ypos) *ypos = 0; _GLFW_REQUIRE_INIT(); _glfwPlatformGetWindowPos(window, xpos, ypos); } GLFWAPI void glfwSetWindowPos(GLFWwindow* handle, int xpos, int ypos) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); if (window->monitor) return; _glfwPlatformSetWindowPos(window, xpos, ypos); } GLFWAPI void glfwGetWindowSize(GLFWwindow* handle, int* width, int* height) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); if (width) *width = 0; if (height) *height = 0; _GLFW_REQUIRE_INIT(); _glfwPlatformGetWindowSize(window, width, height); } GLFWAPI void glfwSetWindowSize(GLFWwindow* handle, int width, int height) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); assert(width >= 0); assert(height >= 0); _GLFW_REQUIRE_INIT(); window->videoMode.width = width; window->videoMode.height = height; _glfwPlatformSetWindowSize(window, width, height); } GLFWAPI void glfwSetWindowSizeLimits(GLFWwindow* handle, int minwidth, int minheight, int maxwidth, int maxheight) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); if (minwidth != GLFW_DONT_CARE && minheight != GLFW_DONT_CARE) { if (minwidth < 0 || minheight < 0) { _glfwInputError(GLFW_INVALID_VALUE, "Invalid window minimum size %ix%i", minwidth, minheight); return; } } if (maxwidth != GLFW_DONT_CARE && maxheight != GLFW_DONT_CARE) { if (maxwidth < 0 || maxheight < 0 || maxwidth < minwidth || maxheight < minheight) { _glfwInputError(GLFW_INVALID_VALUE, "Invalid window maximum size %ix%i", maxwidth, maxheight); return; } } window->minwidth = minwidth; window->minheight = minheight; window->maxwidth = maxwidth; window->maxheight = maxheight; if (window->monitor || !window->resizable) return; _glfwPlatformSetWindowSizeLimits(window, minwidth, minheight, maxwidth, maxheight); } GLFWAPI void glfwSetWindowAspectRatio(GLFWwindow* handle, int numer, int denom) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); assert(numer != 0); assert(denom != 0); _GLFW_REQUIRE_INIT(); if (numer != GLFW_DONT_CARE && denom != GLFW_DONT_CARE) { if (numer <= 0 || denom <= 0) { _glfwInputError(GLFW_INVALID_VALUE, "Invalid window aspect ratio %i:%i", numer, denom); return; } } window->numer = numer; window->denom = denom; if (window->monitor || !window->resizable) return; _glfwPlatformSetWindowAspectRatio(window, numer, denom); } GLFWAPI void glfwSetWindowSizeIncrements(GLFWwindow* handle, int widthincr, int heightincr) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); assert(widthincr >= 0 || widthincr == GLFW_DONT_CARE); assert(heightincr >= 0 || heightincr == GLFW_DONT_CARE); _GLFW_REQUIRE_INIT(); window->widthincr = widthincr; window->heightincr = heightincr; _glfwPlatformSetWindowSizeIncrements(window, window->widthincr, window->heightincr); } GLFWAPI void glfwGetFramebufferSize(GLFWwindow* handle, int* width, int* height) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); if (width) *width = 0; if (height) *height = 0; _GLFW_REQUIRE_INIT(); _glfwPlatformGetFramebufferSize(window, width, height); } GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* handle, int* left, int* top, int* right, int* bottom) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); if (left) *left = 0; if (top) *top = 0; if (right) *right = 0; if (bottom) *bottom = 0; _GLFW_REQUIRE_INIT(); _glfwPlatformGetWindowFrameSize(window, left, top, right, bottom); } GLFWAPI void glfwGetWindowContentScale(GLFWwindow* handle, float* xscale, float* yscale) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); if (xscale) *xscale = 0.f; if (yscale) *yscale = 0.f; _GLFW_REQUIRE_INIT(); _glfwPlatformGetWindowContentScale(window, xscale, yscale); } GLFWAPI monotonic_t glfwGetDoubleClickInterval(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(ms_to_monotonic_t(500ll)); return _glfwPlatformGetDoubleClickInterval(window); } GLFWAPI float glfwGetWindowOpacity(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(1.f); return _glfwPlatformGetWindowOpacity(window); } GLFWAPI void glfwSetWindowOpacity(GLFWwindow* handle, float opacity) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); assert(opacity == opacity); assert(opacity >= 0.f); assert(opacity <= 1.f); _GLFW_REQUIRE_INIT(); if (opacity != opacity || opacity < 0.f || opacity > 1.f) { _glfwInputError(GLFW_INVALID_VALUE, "Invalid window opacity %f", opacity); return; } _glfwPlatformSetWindowOpacity(window, opacity); } GLFWAPI void glfwIconifyWindow(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); _glfwPlatformIconifyWindow(window); } GLFWAPI void glfwRestoreWindow(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); _glfwPlatformRestoreWindow(window); } GLFWAPI void glfwMaximizeWindow(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); if (window->monitor) return; _glfwPlatformMaximizeWindow(window); } GLFWAPI void glfwShowWindow(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); if (window->monitor) return; _glfwPlatformShowWindow(window); #ifndef _GLFW_WAYLAND if (window->focusOnShow) _glfwPlatformFocusWindow(window); #endif } GLFWAPI void glfwRequestWindowAttention(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); _glfwPlatformRequestWindowAttention(window); } GLFWAPI int glfwWindowBell(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(false); return _glfwPlatformWindowBell(window); } GLFWAPI void glfwHideWindow(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); if (window->monitor) return; _glfwPlatformHideWindow(window); } GLFWAPI void glfwFocusWindow(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); _glfwPlatformFocusWindow(window); } GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(0); switch (attrib) { case GLFW_FOCUSED: return _glfwPlatformWindowFocused(window); case GLFW_ICONIFIED: return _glfwPlatformWindowIconified(window); case GLFW_VISIBLE: return _glfwPlatformWindowVisible(window); case GLFW_MAXIMIZED: return _glfwPlatformWindowMaximized(window); case GLFW_HOVERED: return _glfwPlatformWindowHovered(window); case GLFW_FOCUS_ON_SHOW: return window->focusOnShow; case GLFW_MOUSE_PASSTHROUGH: return window->mousePassthrough; case GLFW_TRANSPARENT_FRAMEBUFFER: return _glfwPlatformFramebufferTransparent(window); case GLFW_OCCLUDED: return _glfwPlatformWindowOccluded(window); case GLFW_RESIZABLE: return window->resizable; case GLFW_DECORATED: return window->decorated; case GLFW_FLOATING: return window->floating; case GLFW_AUTO_ICONIFY: return window->autoIconify; case GLFW_CLIENT_API: return window->context.client; case GLFW_CONTEXT_CREATION_API: return window->context.source; case GLFW_CONTEXT_VERSION_MAJOR: return window->context.major; case GLFW_CONTEXT_VERSION_MINOR: return window->context.minor; case GLFW_CONTEXT_REVISION: return window->context.revision; case GLFW_CONTEXT_ROBUSTNESS: return window->context.robustness; case GLFW_OPENGL_FORWARD_COMPAT: return window->context.forward; case GLFW_CONTEXT_DEBUG: return window->context.debug; case GLFW_OPENGL_PROFILE: return window->context.profile; case GLFW_CONTEXT_RELEASE_BEHAVIOR: return window->context.release; case GLFW_CONTEXT_NO_ERROR: return window->context.noerror; } _glfwInputError(GLFW_INVALID_ENUM, "Invalid window attribute 0x%08X", attrib); return 0; } GLFWAPI void glfwSetWindowAttrib(GLFWwindow* handle, int attrib, int value) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); value = value ? true : false; if (attrib == GLFW_AUTO_ICONIFY) window->autoIconify = value; else if (attrib == GLFW_RESIZABLE) { if (window->resizable == value) return; window->resizable = value; if (!window->monitor) _glfwPlatformSetWindowResizable(window, value); } else if (attrib == GLFW_DECORATED) { if (window->decorated == value) return; window->decorated = value; if (!window->monitor) _glfwPlatformSetWindowDecorated(window, value); } else if (attrib == GLFW_FLOATING) { if (window->floating == value) return; window->floating = value; if (!window->monitor) _glfwPlatformSetWindowFloating(window, value); } else if (attrib == GLFW_FOCUS_ON_SHOW) window->focusOnShow = value; else if (attrib == GLFW_MOUSE_PASSTHROUGH) { if (window->mousePassthrough == value) return; window->mousePassthrough = value; _glfwPlatformSetWindowMousePassthrough(window, value); } else _glfwInputError(GLFW_INVALID_ENUM, "Invalid window attribute 0x%08X", attrib); } GLFWAPI int glfwSetWindowBlur(GLFWwindow* handle, int value) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(0); return _glfwPlatformSetWindowBlur(window, value); } GLFWAPI GLFWmonitor* glfwGetWindowMonitor(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); return (GLFWmonitor*) window->monitor; } GLFWAPI void glfwSetWindowMonitor(GLFWwindow* wh, GLFWmonitor* mh, int xpos, int ypos, int width, int height, int refreshRate) { _GLFWwindow* window = (_GLFWwindow*) wh; _GLFWmonitor* monitor = (_GLFWmonitor*) mh; assert(window != NULL); assert(width >= 0); assert(height >= 0); _GLFW_REQUIRE_INIT(); if (width <= 0 || height <= 0) { _glfwInputError(GLFW_INVALID_VALUE, "Invalid window size %ix%i", width, height); return; } if (refreshRate < 0 && refreshRate != GLFW_DONT_CARE) { _glfwInputError(GLFW_INVALID_VALUE, "Invalid refresh rate %i", refreshRate); return; } window->videoMode.width = width; window->videoMode.height = height; window->videoMode.refreshRate = refreshRate; _glfwPlatformSetWindowMonitor(window, monitor, xpos, ypos, width, height, refreshRate); } GLFWAPI bool glfwToggleFullscreen(GLFWwindow* wh, unsigned int flags) { _GLFWwindow* window = (_GLFWwindow*) wh; if (window) return _glfwPlatformToggleFullscreen(window, flags); return false; } GLFWAPI bool glfwIsFullscreen(GLFWwindow* wh, unsigned int flags) { return _glfwPlatformIsFullscreen((_GLFWwindow*)wh, flags); } GLFWAPI bool glfwAreSwapsAllowed(const GLFWwindow* wh) { return !(((_GLFWwindow*)wh)->swaps_disallowed); } GLFWAPI void glfwSetWindowUserPointer(GLFWwindow* handle, void* pointer) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT(); window->userPointer = pointer; } GLFWAPI void* glfwGetWindowUserPointer(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); return window->userPointer; } GLFWAPI GLFWwindowposfun glfwSetWindowPosCallback(GLFWwindow* handle, GLFWwindowposfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.pos, cbfun); return cbfun; } GLFWAPI GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow* handle, GLFWwindowsizefun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.size, cbfun); return cbfun; } GLFWAPI GLFWwindowclosefun glfwSetWindowCloseCallback(GLFWwindow* handle, GLFWwindowclosefun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.close, cbfun); return cbfun; } GLFWAPI GLFWwindowrefreshfun glfwSetWindowRefreshCallback(GLFWwindow* handle, GLFWwindowrefreshfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.refresh, cbfun); return cbfun; } GLFWAPI GLFWwindowfocusfun glfwSetWindowFocusCallback(GLFWwindow* handle, GLFWwindowfocusfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.focus, cbfun); return cbfun; } GLFWAPI GLFWwindowocclusionfun glfwSetWindowOcclusionCallback(GLFWwindow* handle, GLFWwindowocclusionfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.occlusion, cbfun); return cbfun; } GLFWAPI GLFWwindowiconifyfun glfwSetWindowIconifyCallback(GLFWwindow* handle, GLFWwindowiconifyfun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.iconify, cbfun); return cbfun; } GLFWAPI GLFWwindowmaximizefun glfwSetWindowMaximizeCallback(GLFWwindow* handle, GLFWwindowmaximizefun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.maximize, cbfun); return cbfun; } GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* handle, GLFWframebuffersizefun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.fbsize, cbfun); return cbfun; } GLFWAPI GLFWliveresizefun glfwSetLiveResizeCallback(GLFWwindow* handle, GLFWliveresizefun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.liveResize, cbfun); return cbfun; } GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow* handle, GLFWwindowcontentscalefun cbfun) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_SWAP_POINTERS(window->callbacks.scale, cbfun); return cbfun; } GLFWAPI void glfwPostEmptyEvent(void) { _GLFW_REQUIRE_INIT(); _glfwPlatformPostEmptyEvent(); } kitty-0.41.1/glfw/wl_client_side_decorations.c0000664000175000017510000011566214773370543021003 0ustar nileshnilesh/* * wl_client_side_decorations.c * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "wl_client_side_decorations.h" #include "backend_utils.h" #include #include #include #include #define decs window->wl.decorations #define debug debug_rendering #define ARGB(a, r, g, b) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b)) #define A(x) (((x) >> 24) & 0xff) #define R(x) (((x) >> 16) & 0xff) #define G(x) (((x) >> 8) & 0xff) #define B(x) ((x) & 0xff) #define SWAP(x, y) do { __typeof__(x) SWAP = x; x = y; y = SWAP; } while (0) // shadow tile {{{ typedef float kernel_type; static void build_blur_kernel(kernel_type *blur_kernel, const size_t size, kernel_type sigma) { // 1D Normalized Gaussian const kernel_type half = size / (kernel_type)2; kernel_type sum = 0; for (size_t i = 0; i < size; i++) { kernel_type f = (i - half); blur_kernel[i] = (kernel_type)exp(- f * f / sigma); sum += blur_kernel[i]; } for (size_t i = 0; i < size; i++) blur_kernel[i] /= sum; } static void blur_mask(kernel_type *image_data, ssize_t width, ssize_t height, ssize_t kernel_size, kernel_type sigma, kernel_type *scratch, kernel_type *blur_kernel, ssize_t margin) { (void)margin; build_blur_kernel(blur_kernel, kernel_size, sigma); const size_t half = kernel_size / 2; for (ssize_t y = 0; y < height; y++) { kernel_type *s = image_data + y * width, *d = scratch + y * width; for (ssize_t x = 0; x < width; x++) { kernel_type a = 0; for (ssize_t k = 0; k < kernel_size; k++) { const ssize_t px = x + k - half; if (0 <= px && px < width) a += s[px] * blur_kernel[k]; } d[x] = a; } } for (ssize_t y = 0; y < height; y++) { kernel_type *d = image_data + y * width; for (ssize_t x = 0; x < width; x++) { kernel_type a = 0; for (ssize_t k = 0; k < kernel_size; k++) { const ssize_t py = y + k - half; if (0 <= py && py < height) { kernel_type *s = scratch + py * width; a += s[x] * blur_kernel[k]; } } d[x] = a; } } } static kernel_type* create_shadow_mask(size_t width, size_t height, size_t margin, size_t kernel_size, kernel_type base_alpha, kernel_type sigma) { kernel_type *mask = calloc(2 * width * height + kernel_size, sizeof(kernel_type)); if (!mask) return NULL; for (size_t y = margin; y < height - margin; y++) { kernel_type *row = mask + y * width; for (size_t x = margin; x < width - margin; x++) row[x] = base_alpha; } blur_mask(mask, width, height, kernel_size, sigma, mask + width * height, (kernel_type*)(mask + 2 * width * height), margin); return mask; } #define st decs.shadow_tile static size_t create_shadow_tile(_GLFWwindow *window) { const size_t margin = (size_t)round(decs.metrics.width * decs.for_window_state.fscale); if (st.data && st.for_decoration_size == margin) return margin; st.for_decoration_size = margin; free(st.data); st.segments = 7; st.stride = st.segments * margin; st.corner_size = margin * (st.segments - 1) / 2; kernel_type* mask = create_shadow_mask(st.stride, st.stride, margin, 2 * margin + 1, (kernel_type)0.7, 32 * margin); st.data = malloc(sizeof(uint32_t) * st.stride * st.stride); if (st.data) for (size_t i = 0; i < st.stride * st.stride; i++) st.data[i] = ((uint8_t)(mask[i] * 255)) << 24; free(mask); return margin; } // }}} static bool window_needs_shadows(_GLFWwindow *w) { return !(w->wl.current.toplevel_states & TOPLEVEL_STATE_DOCKED); } static void swap_buffers(_GLFWWaylandBufferPair *pair) { SWAP(pair->front, pair->back); SWAP(pair->data.front, pair->data.back); } static size_t init_buffer_pair(_GLFWWaylandBufferPair *pair, size_t width, size_t height, double scale) { memset(pair, 0, sizeof(_GLFWWaylandBufferPair)); pair->width = (int)round(width * scale); pair->height = (int)round(height * scale); pair->viewport_width = width; pair->viewport_height = height; pair->stride = 4 * pair->width; pair->size_in_bytes = pair->stride * pair->height; return 2 * pair->size_in_bytes; } #define all_shadow_surfaces(Q) Q(shadow_left); Q(shadow_top); Q(shadow_right); Q(shadow_bottom); \ Q(shadow_upper_left); Q(shadow_upper_right); Q(shadow_lower_left); Q(shadow_lower_right); #define all_surfaces(Q) Q(titlebar); all_shadow_surfaces(Q); static bool window_has_buffer(_GLFWwindow *window, struct wl_buffer *q) { #define Q(which) if (decs.which.buffer.a == q) { decs.which.buffer.a_needs_to_be_destroyed = false; return true; } if (decs.which.buffer.b == q) { decs.which.buffer.b_needs_to_be_destroyed = false; return true; } all_surfaces(Q); #undef Q return false; } static void buffer_release_event(void *data, struct wl_buffer *buffer) { wl_buffer_destroy(buffer); _GLFWwindow *window = _glfwWindowForId((uintptr_t)data); if (window && window_has_buffer(window, buffer)) decs.buffer_destroyed = true; } static struct wl_buffer_listener handle_buffer_events = {.release = buffer_release_event}; static void alloc_buffer_pair(uintptr_t window_id, _GLFWWaylandBufferPair *pair, struct wl_shm_pool *pool, uint8_t *data, size_t *offset) { pair->data.a = data + *offset; pair->a = wl_shm_pool_create_buffer(pool, *offset, pair->width, pair->height, pair->stride, WL_SHM_FORMAT_ARGB8888); pair->a_needs_to_be_destroyed = true; wl_buffer_add_listener(pair->a, &handle_buffer_events, (void*)window_id); *offset += pair->size_in_bytes; pair->data.b = data + *offset; pair->b = wl_shm_pool_create_buffer(pool, *offset, pair->width, pair->height, pair->stride, WL_SHM_FORMAT_ARGB8888); pair->b_needs_to_be_destroyed = true; wl_buffer_add_listener(pair->b, &handle_buffer_events, (void*)window_id); *offset += pair->size_in_bytes; pair->front = pair->a; pair->back = pair->b; pair->data.front = pair->data.a; pair->data.back = pair->data.b; } void csd_initialize_metrics(_GLFWwindow *window) { decs.metrics.width = 12; decs.metrics.top = 36; decs.metrics.visible_titlebar_height = decs.metrics.top - decs.metrics.width; decs.metrics.horizontal = 2 * decs.metrics.width; decs.metrics.vertical = decs.metrics.width + decs.metrics.top; } static void patch_titlebar_with_alpha_mask(uint32_t *dest, uint8_t *src, unsigned height, unsigned dest_stride, unsigned src_width, unsigned dest_left, uint32_t bg, uint32_t fg) { for (unsigned y = 0; y < height; y++, src += src_width, dest += dest_stride) { uint32_t *d = dest + dest_left; for (unsigned i = 0; i < src_width; i++) { const uint8_t alpha = src[i], calpha = 255 - alpha; // Blend the red and blue components uint32_t ans = ((bg & 0xff00ff) * calpha + (fg & 0xff00ff) * alpha) & 0xff00ff00; // Blend the green component ans += ((bg & 0xff00) * calpha + (fg & 0xff00) * alpha) & 0xff0000; ans >>= 8; d[i] = ans | 0xff000000; } } } static void render_hline(uint8_t *out, unsigned width, unsigned thickness, unsigned bottom, unsigned left, unsigned right) { for (unsigned y = bottom - thickness; y < bottom; y++) { uint8_t *dest = out + width * y; for (unsigned x = left; x < right; x++) dest[x] = 255; } } static void render_vline(uint8_t *out, unsigned width, unsigned thickness, unsigned left, unsigned top, unsigned bottom) { for (unsigned y = top; y < bottom; y++) { uint8_t *dest = out + width * y; for (unsigned x = left; x < left + thickness; x++) dest[x] = 255; } } static int scale(unsigned thickness, float factor) { return (unsigned)(roundf(thickness * factor)); } static void render_minimize(uint8_t *out, unsigned width, unsigned height) { memset(out, 0, width * height); unsigned thickness = height / 12; unsigned baseline = height - thickness * 2; unsigned side_margin = scale(thickness, 3.8f); if (!thickness || width <= side_margin || height < baseline + 2 * thickness) return; render_hline(out, width, thickness, baseline, side_margin, width - side_margin); } static void render_maximize(uint8_t *out, unsigned width, unsigned height) { memset(out, 0, width * height); unsigned thickness = height / 12, half_thickness = thickness / 2; unsigned baseline = height - thickness * 2; unsigned side_margin = scale(thickness, 3.0f); unsigned top = 4 * thickness; if (!half_thickness || width <= side_margin || height < baseline + 2 * thickness || top >= baseline) return; render_hline(out, width, half_thickness, baseline, side_margin, width - side_margin); render_hline(out, width, thickness, top + thickness, side_margin, width - side_margin); render_vline(out, width, half_thickness, side_margin, top, baseline); render_vline(out, width, half_thickness, width - side_margin, top, baseline); } static void render_restore(uint8_t *out, unsigned width, unsigned height) { memset(out, 0, width * height); unsigned thickness = height / 12, half_thickness = thickness / 2; unsigned baseline = height - thickness * 2; unsigned side_margin = scale(thickness, 3.0f); unsigned top = 4 * thickness; if (!half_thickness || width <= side_margin || height < baseline + 2 * thickness || top >= baseline) return; unsigned box_height = ((baseline - top) * 3) / 4; if (box_height < 2*thickness) return; unsigned box_width = ((width - 2 * side_margin) * 3) / 4; // bottom box unsigned box_top = baseline - box_height, left = side_margin, right = side_margin + box_width, bottom = baseline; render_hline(out, width, thickness, box_top + thickness, left, right); render_hline(out, width, half_thickness, bottom, left, right); render_vline(out, width, half_thickness, left, box_top, bottom); render_vline(out, width, half_thickness, side_margin + box_width, baseline - box_height, baseline); // top box unsigned box_x_shift = 2 * thickness, box_y_shift = 2 * thickness; box_x_shift = MIN(width - right, box_x_shift); box_y_shift = MIN(box_top, box_y_shift); unsigned left2 = left + box_x_shift, right2 = right + box_x_shift, top2 = box_top - box_y_shift, bottom2 = bottom - box_y_shift; render_hline(out, width, thickness, top2 + thickness, left2, right2); render_vline(out, width, half_thickness, right2, top2, bottom2); render_hline(out, width, half_thickness, bottom2, right, right2); render_vline(out, width, half_thickness, left2, top2, box_top); } static void render_line(uint8_t *buf, unsigned width, unsigned height, unsigned thickness, int x1, int y1, int x2, int y2) { float m = (y2 - y1) / (float)(x2 - x1); float c = y1 - m * x1; unsigned delta = thickness / 2, extra = thickness % 2; for (int x = MAX(0, MIN(x1, x2)); x < MIN((int)width, MAX(x1, x2) + 1); x++) { float ly = m * x + c; for (int y = MAX(0, (int)(ly - delta)); y < MIN((int)height, (int)(ly + delta + extra + 1)); y++) buf[x + y * width] = 255; } for (int y = MAX(0, MIN(y1, y2)); y < MIN((int)height, MAX(y1, y2) + 1); y++) { float lx = (y - c) / m; for (int x = MAX(0, (int)(lx - delta)); x < MIN((int)width, (int)(lx + delta + extra + 1)); x++) buf[x + y * width] = 255; } } static void render_close(uint8_t *out, unsigned width, unsigned height) { memset(out, 0, width * height); unsigned thickness = height / 12; unsigned baseline = height - thickness * 2; unsigned side_margin = scale(thickness, 3.3f); int top = baseline - (width - 2 * side_margin); if (top <= 0) return; unsigned line_thickness = scale(thickness, 1.5f); render_line(out, width, height, line_thickness, side_margin, top, width - side_margin, baseline); render_line(out, width, height, line_thickness, side_margin, baseline, width - side_margin, top); } static uint32_t average_intensity_in_src(uint8_t *src, unsigned src_width, unsigned src_x, unsigned src_y, unsigned factor) { uint32_t ans = 0; for (unsigned y = src_y; y < src_y + factor; y++) { uint8_t *s = src + src_width * y; for (unsigned x = src_x; x < src_x + factor; x++) ans += s[x]; } return ans / (factor * factor); } static void downsample(uint8_t *dest, uint8_t *src, unsigned dest_width, unsigned dest_height, unsigned factor) { unsigned src_width = factor * dest_width; for (unsigned y = 0; y < dest_height; y++) { uint8_t *d = dest + dest_width * y; for (unsigned x = 0; x < dest_width; x++) { d[x] = MIN(255u, (uint32_t)d[x] + average_intensity_in_src(src, src_width, x * factor, y * factor, factor)); } } } static void render_button(void(*which)(uint8_t *, unsigned, unsigned), bool antialias, uint32_t *dest, uint8_t *src, unsigned height, unsigned dest_stride, unsigned src_width, unsigned dest_left, uint32_t bg, uint32_t fg) { if (antialias) { static const unsigned factor = 4; uint8_t *big_src = malloc(factor * factor * height * src_width); if (big_src) { which(big_src, src_width * factor, height * factor); memset(src, 0, src_width * height); downsample(src, big_src, src_width, height, factor); free(big_src); } else which(src, src_width, height); } else which(src, src_width, height); patch_titlebar_with_alpha_mask(dest, src, height, dest_stride, src_width, dest_left, bg, fg); } static void render_title_bar(_GLFWwindow *window, bool to_front_buffer) { const bool is_focused = window->id == _glfw.focusedWindowId; const bool is_maximized = window->wl.current.toplevel_states & TOPLEVEL_STATE_MAXIMIZED; const uint32_t light_fg = is_focused ? 0xff444444 : 0xff888888, light_bg = is_focused ? 0xffdddad6 : 0xffeeeeee; const uint32_t dark_fg = is_focused ? 0xffffffff : 0xffcccccc, dark_bg = is_focused ? 0xff303030 : 0xff242424; static const uint32_t hover_dark_bg = 0xff444444, hover_light_bg = 0xffbbbbbb; uint32_t bg_color = light_bg, fg_color = light_fg, hover_bg = hover_light_bg; GLFWColorScheme appearance = glfwGetCurrentSystemColorTheme(false); bool is_dark = false; if (decs.use_custom_titlebar_color || appearance == GLFW_COLOR_SCHEME_NO_PREFERENCE) { bg_color = 0xff000000 | (decs.titlebar_color & 0xffffff); double red = ((bg_color >> 16) & 0xFF) / 255.0; double green = ((bg_color >> 8) & 0xFF) / 255.0; double blue = (bg_color & 0xFF) / 255.0; double luma = 0.2126 * red + 0.7152 * green + 0.0722 * blue; if (luma < 0.5) { fg_color = dark_fg; hover_bg = hover_dark_bg; is_dark = true; } if (!decs.use_custom_titlebar_color) bg_color = luma < 0.5 ? dark_bg : light_bg; } else if (appearance == GLFW_COLOR_SCHEME_DARK) { bg_color = dark_bg; fg_color = dark_fg; hover_bg = hover_dark_bg; is_dark = true; } uint8_t *output = to_front_buffer ? decs.titlebar.buffer.data.front : decs.titlebar.buffer.data.back; // render text part int button_size = decs.titlebar.buffer.height; int num_buttons = 1; if (window->wl.wm_capabilities.maximize) num_buttons++; if (window->wl.wm_capabilities.minimize) num_buttons++; if (window->wl.title && window->wl.title[0] && _glfw.callbacks.draw_text) { if (_glfw.callbacks.draw_text((GLFWwindow*)window, window->wl.title, fg_color, bg_color, output, decs.titlebar.buffer.width, decs.titlebar.buffer.height, 0, 0, num_buttons * button_size, false)) goto render_buttons; } // rendering of text failed, blank the buffer for (uint32_t *px = (uint32_t*)output, *end = (uint32_t*)(output + decs.titlebar.buffer.size_in_bytes); px < end; px++) *px = bg_color; render_buttons: decs.maximize.width = 0; decs.minimize.width = 0; decs.close.width = 0; if (!button_size) return; uint8_t *alpha_mask = malloc(button_size * button_size); int left = decs.titlebar.buffer.width - num_buttons * button_size; if (!alpha_mask || left <= 0) return; #define drawb(which, antialias, func, hover_bg) { \ render_button(func, antialias, (uint32_t*)output, alpha_mask, button_size, decs.titlebar.buffer.width, button_size, left, decs.which.hovered ? hover_bg : bg_color, fg_color); decs.which.left = left; decs.which.width = button_size; left += button_size; } if (window->wl.wm_capabilities.minimize) drawb(minimize, false, render_minimize, hover_bg); if (window->wl.wm_capabilities.maximize) { if (is_maximized) { drawb(maximize, false, render_restore, hover_bg); } else { drawb(maximize, false, render_maximize, hover_bg); } } drawb(close, true, render_close, is_dark ? 0xff880000: 0xffc80000); free(alpha_mask); #undef drawb } static void update_title_bar(_GLFWwindow *window) { render_title_bar(window, false); swap_buffers(&decs.titlebar.buffer); } static void render_horizontal_shadow(_GLFWwindow *window, ssize_t scaled_shadow_size, ssize_t src_y_offset, ssize_t y, _GLFWWaylandBufferPair *buf) { // left region ssize_t src_y = src_y_offset + y; const ssize_t src_leftover_corner = st.corner_size - scaled_shadow_size; uint32_t *src = st.data + st.stride * src_y + scaled_shadow_size; uint32_t *d_start = (uint32_t*)(buf->data.front + y * buf->stride); uint32_t *d_end = (uint32_t*)(buf->data.front + (y+1) * buf->stride); uint32_t *left_region_end = d_start + MIN(d_end - d_start, src_leftover_corner); memcpy(d_start, src, sizeof(uint32_t) * (left_region_end - d_start)); // right region uint32_t *right_region_start = MAX(d_start, d_end - src_leftover_corner); src = st.data + st.stride * (src_y+1) - st.corner_size; memcpy(right_region_start, src, sizeof(uint32_t) * MIN(src_leftover_corner, d_end - right_region_start)); src = st.data + st.stride * src_y + st.corner_size; // middle region for (uint32_t *d = left_region_end; d < right_region_start; d += scaled_shadow_size) memcpy(d, src, sizeof(uint32_t) * MIN(right_region_start - d, scaled_shadow_size)); } static void copy_vertical_region( _GLFWwindow *window, ssize_t src_y_start, ssize_t src_y_limit, ssize_t y_start, ssize_t y_limit, ssize_t src_x_offset, _GLFWWaylandBufferPair *buf ) { for (ssize_t dy = y_start, sy = src_y_start; dy < y_limit && sy < src_y_limit; dy++, sy++) memcpy(buf->data.front + dy * buf->stride, st.data + sy * st.stride + src_x_offset, sizeof(uint32_t) * buf->width); } static void render_shadows(_GLFWwindow *window) { if (!window_needs_shadows(window)) return; const ssize_t scaled_shadow_size = create_shadow_tile(window); if (!st.data || !scaled_shadow_size) return; // out of memory // upper and lower shadows for (ssize_t y = 0; y < scaled_shadow_size; y++) { _GLFWWaylandBufferPair *buf = &decs.shadow_upper_left.buffer; uint32_t *src = st.data + st.stride * y; uint32_t *d = (uint32_t*)(buf->data.front + y * buf->stride); memcpy(d, src, sizeof(uint32_t) * scaled_shadow_size); buf = &decs.shadow_upper_right.buffer; src += st.stride - scaled_shadow_size; d = (uint32_t*)(buf->data.front + y * buf->stride); memcpy(d, src, sizeof(uint32_t) * scaled_shadow_size); const size_t tile_bottom_start = st.stride - scaled_shadow_size; buf = &decs.shadow_lower_left.buffer; src = st.data + (tile_bottom_start + y) * st.stride; d = (uint32_t*)(buf->data.front + y * buf->stride); memcpy(d, src, sizeof(uint32_t) * scaled_shadow_size); buf = &decs.shadow_lower_right.buffer; src += st.stride - scaled_shadow_size; d = (uint32_t*)(buf->data.front + y * buf->stride); memcpy(d, src, sizeof(uint32_t) * scaled_shadow_size); render_horizontal_shadow(window, scaled_shadow_size, 0, y, &decs.shadow_top.buffer); render_horizontal_shadow(window, scaled_shadow_size, st.stride - scaled_shadow_size, y, &decs.shadow_bottom.buffer); } // side shadows // top region const ssize_t src_leftover_corner = st.corner_size - scaled_shadow_size; ssize_t y_start = 0, y_end = decs.shadow_left.buffer.height, top_end = MIN(y_end, src_leftover_corner); ssize_t right_src_start = st.stride - scaled_shadow_size; #define c(src_y_start, src_y_limit, dest_y_start, dest_y_limit) { \ copy_vertical_region(window, src_y_start, src_y_limit, dest_y_start, dest_y_limit, 0, &decs.shadow_left.buffer); \ copy_vertical_region(window, src_y_start, src_y_limit, dest_y_start, dest_y_limit, right_src_start, &decs.shadow_right.buffer); \ } c(scaled_shadow_size, st.corner_size, y_start, top_end); // bottom region ssize_t bottom_start = MAX(0, y_end - src_leftover_corner); c(st.stride - st.corner_size, st.stride - scaled_shadow_size, bottom_start, y_end); // middle region for (ssize_t dest_y = top_end; dest_y < bottom_start; dest_y += scaled_shadow_size) c(st.corner_size, st.corner_size + scaled_shadow_size, dest_y, MIN(dest_y + scaled_shadow_size, bottom_start)); #undef c #define copy(which) for (uint32_t *src = (uint32_t*)decs.which.buffer.data.front, *dest = (uint32_t*)decs.which.buffer.data.back; src < (uint32_t*)(decs.which.buffer.data.front + decs.which.buffer.size_in_bytes); src++, dest++) *dest = (A(*src) / 2 ) << 24; all_shadow_surfaces(copy); #undef copy } #undef st static bool create_shm_buffers(_GLFWwindow* window) { decs.mapping.size = 0; #define bp(which, width, height) decs.mapping.size += init_buffer_pair(&decs.which.buffer, width, height, decs.for_window_state.fscale); bp(titlebar, window->wl.width, decs.metrics.visible_titlebar_height); bp(shadow_top, window->wl.width, decs.metrics.width); bp(shadow_bottom, window->wl.width, decs.metrics.width); bp(shadow_left, decs.metrics.width, window->wl.height + decs.metrics.visible_titlebar_height); bp(shadow_right, decs.metrics.width, window->wl.height + decs.metrics.visible_titlebar_height); bp(shadow_upper_left, decs.metrics.width, decs.metrics.width); bp(shadow_upper_right, decs.metrics.width, decs.metrics.width); bp(shadow_lower_left, decs.metrics.width, decs.metrics.width); bp(shadow_lower_right, decs.metrics.width, decs.metrics.width); #undef bp int fd = createAnonymousFile(decs.mapping.size); if (fd < 0) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Creating a buffer file for %zu B failed: %s", decs.mapping.size, strerror(errno)); return false; } decs.mapping.data = mmap(NULL, decs.mapping.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (decs.mapping.data == MAP_FAILED) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: mmap failed: %s", strerror(errno)); close(fd); return false; } struct wl_shm_pool* pool = wl_shm_create_pool(_glfw.wl.shm, fd, decs.mapping.size); close(fd); size_t offset = 0; #define Q(which) alloc_buffer_pair(window->id, &decs.which.buffer, pool, decs.mapping.data, &offset) all_surfaces(Q); #undef Q wl_shm_pool_destroy(pool); render_title_bar(window, true); render_shadows(window); debug("Created decoration buffers at scale: %f\n", decs.for_window_state.fscale); return true; } static void free_csd_surface(_GLFWWaylandCSDSurface *s) { if (s->subsurface) wl_subsurface_destroy(s->subsurface); s->subsurface = NULL; if (s->wp_viewport) wp_viewport_destroy(s->wp_viewport); s->wp_viewport = NULL; if (s->surface) wl_surface_destroy(s->surface); s->surface = NULL; } static void free_csd_surfaces(_GLFWwindow *window) { #define Q(which) free_csd_surface(&decs.which) all_surfaces(Q); #undef Q } static void free_csd_buffers(_GLFWwindow *window) { #define Q(which) { \ if (decs.which.buffer.a_needs_to_be_destroyed && decs.which.buffer.a) wl_buffer_destroy(decs.which.buffer.a); \ if (decs.which.buffer.b_needs_to_be_destroyed && decs.which.buffer.b) wl_buffer_destroy(decs.which.buffer.b); \ memset(&decs.which.buffer, 0, sizeof(_GLFWWaylandBufferPair)); \ } all_surfaces(Q); #undef Q if (decs.mapping.data) munmap(decs.mapping.data, decs.mapping.size); decs.mapping.data = NULL; decs.mapping.size = 0; } static void position_csd_surface(_GLFWWaylandCSDSurface *s, int x, int y) { if (s->surface) { wl_surface_set_buffer_scale(s->surface, 1); s->x = x; s->y = y; wl_subsurface_set_position(s->subsurface, s->x, s->y); } } static void create_csd_surfaces(_GLFWwindow *window, _GLFWWaylandCSDSurface *s) { if (s->surface) wl_surface_destroy(s->surface); s->surface = wl_compositor_create_surface(_glfw.wl.compositor); wl_surface_set_user_data(s->surface, window); if (s->subsurface) wl_subsurface_destroy(s->subsurface); s->subsurface = wl_subcompositor_get_subsurface(_glfw.wl.subcompositor, s->surface, window->wl.surface); if (_glfw.wl.wp_viewporter) { if (s->wp_viewport) wp_viewport_destroy(s->wp_viewport); s->wp_viewport = wp_viewporter_get_viewport(_glfw.wl.wp_viewporter, s->surface); } } #define damage_csd(which, xbuffer) if (decs.which.surface) { \ wl_surface_attach(decs.which.surface, (xbuffer), 0, 0); \ if (decs.which.wp_viewport) wp_viewport_set_destination(decs.which.wp_viewport, decs.which.buffer.viewport_width, decs.which.buffer.viewport_height); \ wl_surface_damage(decs.which.surface, 0, 0, decs.which.buffer.width, decs.which.buffer.height); \ wl_surface_commit(decs.which.surface); \ if (decs.which.buffer.a == (xbuffer)) { decs.which.buffer.a_needs_to_be_destroyed = false; } else { decs.which.buffer.b_needs_to_be_destroyed = false; }} static bool window_is_csd_capable(_GLFWwindow *window) { return window->decorated && !decs.serverSide && window->wl.xdg.toplevel; } static bool ensure_csd_resources(_GLFWwindow *window) { if (!window_is_csd_capable(window)) return false; const bool is_focused = window->id == _glfw.focusedWindowId; const bool focus_changed = is_focused != decs.for_window_state.focused; const double current_scale = _glfwWaylandWindowScale(window); const bool size_changed = ( decs.for_window_state.width != window->wl.width || decs.for_window_state.height != window->wl.height || decs.for_window_state.fscale != current_scale || !decs.mapping.data ); const bool state_changed = decs.for_window_state.toplevel_states != window->wl.current.toplevel_states; const bool needs_update = focus_changed || size_changed || !decs.titlebar.surface || decs.buffer_destroyed || state_changed; debug("CSD: old.size: %dx%d new.size: %dx%d needs_update: %d size_changed: %d state_changed: %d buffer_destroyed: %d\n", decs.for_window_state.width, decs.for_window_state.height, window->wl.width, window->wl.height, needs_update, size_changed, state_changed, decs.buffer_destroyed); if (!needs_update) return false; decs.for_window_state.fscale = current_scale; // used in create_shm_buffers if (size_changed || decs.buffer_destroyed) { free_csd_buffers(window); if (!create_shm_buffers(window)) return false; decs.buffer_destroyed = false; } #define setup_surface(which, x, y) \ if (!decs.which.surface) create_csd_surfaces(window, &decs.which); \ position_csd_surface(&decs.which, x, y); setup_surface(titlebar, 0, -decs.metrics.visible_titlebar_height); setup_surface(shadow_top, decs.titlebar.x, decs.titlebar.y - decs.metrics.width); setup_surface(shadow_bottom, decs.titlebar.x, window->wl.height); setup_surface(shadow_left, -decs.metrics.width, decs.titlebar.y); setup_surface(shadow_right, window->wl.width, decs.shadow_left.y); setup_surface(shadow_upper_left, decs.shadow_left.x, decs.shadow_top.y); setup_surface(shadow_upper_right, decs.shadow_right.x, decs.shadow_top.y); setup_surface(shadow_lower_left, decs.shadow_left.x, decs.shadow_bottom.y); setup_surface(shadow_lower_right, decs.shadow_right.x, decs.shadow_bottom.y); if (focus_changed || state_changed) update_title_bar(window); damage_csd(titlebar, decs.titlebar.buffer.front); #define d(which) damage_csd(which, is_focused ? decs.which.buffer.front : decs.which.buffer.back); d(shadow_left); d(shadow_right); d(shadow_top); d(shadow_bottom); d(shadow_upper_left); d(shadow_upper_right); d(shadow_lower_left); d(shadow_lower_right); #undef d decs.for_window_state.width = window->wl.width; decs.for_window_state.height = window->wl.height; decs.for_window_state.focused = is_focused; decs.for_window_state.toplevel_states = window->wl.current.toplevel_states; return true; } void csd_set_visible(_GLFWwindow *window, bool visible) { // When setting to visible will only take effect if window currently has // CSD and will also ensure CSD is of correct size and type for current window. // When hiding CSD simply destroys all CSD surfaces. if (visible) ensure_csd_resources(window); else free_csd_surfaces(window); } void csd_free_all_resources(_GLFWwindow *window) { free_csd_surfaces(window); free_csd_buffers(window); if (decs.shadow_tile.data) free(decs.shadow_tile.data); decs.shadow_tile.data = NULL; } bool csd_change_title(_GLFWwindow *window) { if (!window_is_csd_capable(window)) return false; if (ensure_csd_resources(window)) return true; // CSD were re-rendered for other reasons if (decs.titlebar.surface) { update_title_bar(window); damage_csd(titlebar, decs.titlebar.buffer.front); return true; } return false; } void csd_set_window_geometry(_GLFWwindow *window, int32_t *width, int32_t *height) { bool has_csd = window_is_csd_capable(window) && decs.titlebar.surface && !(window->wl.current.toplevel_states & TOPLEVEL_STATE_FULLSCREEN); bool size_specified_by_compositor = *width > 0 && *height > 0; if (!size_specified_by_compositor) { *width = window->wl.user_requested_content_size.width; *height = window->wl.user_requested_content_size.height; if (window->wl.xdg.top_level_bounds.width > 0) *width = MIN(*width, window->wl.xdg.top_level_bounds.width); if (window->wl.xdg.top_level_bounds.height > 0) *height = MIN(*height, window->wl.xdg.top_level_bounds.height); if (has_csd) *height += decs.metrics.visible_titlebar_height; } decs.geometry.x = 0; decs.geometry.y = 0; decs.geometry.width = *width; decs.geometry.height = *height; if (has_csd) { decs.geometry.y = -decs.metrics.visible_titlebar_height; *height -= decs.metrics.visible_titlebar_height; } } bool csd_set_titlebar_color(_GLFWwindow *window, uint32_t color, bool use_system_color) { bool use_custom_color = !use_system_color; decs.use_custom_titlebar_color = use_custom_color; decs.titlebar_color = color; return csd_change_title(window); } #define x window->wl.allCursorPosX #define y window->wl.allCursorPosY static void set_cursor(GLFWCursorShape shape, _GLFWwindow* window) { if (_glfw.wl.wp_cursor_shape_device_v1) { wayland_cursor_shape s = glfw_cursor_shape_to_wayland_cursor_shape(shape); if (s.which > -1) { debug("Changing cursor shape to: %s with serial: %u\n", s.name, _glfw.wl.pointer_enter_serial); wp_cursor_shape_device_v1_set_shape(_glfw.wl.wp_cursor_shape_device_v1, _glfw.wl.pointer_enter_serial, (uint32_t)s.which); return; } } struct wl_buffer* buffer; struct wl_cursor* cursor; struct wl_cursor_image* image; struct wl_surface* surface = _glfw.wl.cursorSurface; const int scale = _glfwWaylandIntegerWindowScale(window); struct wl_cursor_theme *theme = glfw_wlc_theme_for_scale(scale); if (!theme) return; cursor = _glfwLoadCursor(shape, theme); if (!cursor || !cursor->images) return; image = cursor->images[0]; if (!image) return; if (image->width % scale || image->height % scale) { static uint32_t warned_width = 0, warned_height = 0; if (warned_width != image->width || warned_height != image->height) { _glfwInputError(GLFW_PLATFORM_ERROR, "WARNING: Cursor image size: %dx%d is not a multiple of window scale: %d. This will" " cause some compositors such as GNOME to crash. See https://github.com/kovidgoyal/kitty/issues/4878", image->width, image->height, scale); warned_width = image->width; warned_height = image->height; } } buffer = wl_cursor_image_get_buffer(image); if (!buffer) return; debug("Calling wl_pointer_set_cursor in set_cursor with surface: %p\n", (void*)surface); wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial, surface, image->hotspot_x / scale, image->hotspot_y / scale); wl_surface_set_buffer_scale(surface, scale); wl_surface_attach(surface, buffer, 0, 0); wl_surface_damage(surface, 0, 0, image->width, image->height); wl_surface_commit(surface); _glfw.wl.cursorPreviousShape = shape; } static bool update_hovered_button(_GLFWwindow *window) { bool has_hovered_button = false; int scaled_x = (int)round(decs.for_window_state.fscale * x); #define c(which) \ if (decs.which.left <= scaled_x && scaled_x < decs.which.left + decs.which.width) { \ has_hovered_button = true; \ if (!decs.which.hovered) { decs.titlebar_needs_update = true; decs.which.hovered = true; } \ } else if (decs.which.hovered) { decs.titlebar_needs_update = true; decs.which.hovered = false; } c(minimize); c(maximize); c(close); #undef c update_title_bar(window); return has_hovered_button; } static bool has_hovered_button(_GLFWwindow *window) { return decs.minimize.hovered || decs.maximize.hovered || decs.close.hovered; } static void handle_pointer_leave(_GLFWwindow *window, struct wl_surface *surface) { #define c(which) if (decs.which.hovered) { decs.titlebar_needs_update = true; decs.which.hovered = false; } if (surface == decs.titlebar.surface) { c(minimize); c(maximize); c(close); } #undef c decs.focus = CENTRAL_WINDOW; decs.dragging = false; } static void handle_pointer_move(_GLFWwindow *window) { GLFWCursorShape cursorShape = GLFW_DEFAULT_CURSOR; switch (decs.focus) { case CENTRAL_WINDOW: break; case CSD_titlebar: { if (decs.dragging) { if (window->wl.xdg.toplevel) xdg_toplevel_move(window->wl.xdg.toplevel, _glfw.wl.seat, _glfw.wl.pointer_serial); } else if (update_hovered_button(window)) cursorShape = GLFW_POINTER_CURSOR; } break; case CSD_shadow_top: cursorShape = GLFW_N_RESIZE_CURSOR; break; case CSD_shadow_bottom: cursorShape = GLFW_S_RESIZE_CURSOR; break; case CSD_shadow_left: cursorShape = GLFW_W_RESIZE_CURSOR; break; case CSD_shadow_right: cursorShape = GLFW_E_RESIZE_CURSOR; break; case CSD_shadow_upper_left: cursorShape = GLFW_NW_RESIZE_CURSOR; break; case CSD_shadow_upper_right: cursorShape = GLFW_NE_RESIZE_CURSOR; break; case CSD_shadow_lower_left: cursorShape = GLFW_SW_RESIZE_CURSOR; break; case CSD_shadow_lower_right: cursorShape = GLFW_SE_RESIZE_CURSOR; break; } if (_glfw.wl.cursorPreviousShape != cursorShape) set_cursor(cursorShape, window); } static void handle_pointer_enter(_GLFWwindow *window, struct wl_surface *surface) { #define Q(which) if (decs.which.surface == surface) { \ decs.focus = CSD_##which; handle_pointer_move(window); return; } // enter is also a move all_surfaces(Q) #undef Q decs.focus = CENTRAL_WINDOW; decs.dragging = false; } static void handle_pointer_button(_GLFWwindow *window, uint32_t button, uint32_t state) { uint32_t edges = XDG_TOPLEVEL_RESIZE_EDGE_NONE; if (button == BTN_LEFT) { switch (decs.focus) { case CENTRAL_WINDOW: break; case CSD_titlebar: if (state == WL_POINTER_BUTTON_STATE_PRESSED) { monotonic_t last_click_at = decs.last_click_on_top_decoration_at; decs.last_click_on_top_decoration_at = monotonic(); if (decs.last_click_on_top_decoration_at - last_click_at <= _glfwPlatformGetDoubleClickInterval(window)) { decs.last_click_on_top_decoration_at = 0; if (window->wl.current.toplevel_states & TOPLEVEL_STATE_MAXIMIZED) _glfwPlatformRestoreWindow(window); else _glfwPlatformMaximizeWindow(window); return; } } else { if (decs.minimize.hovered) _glfwPlatformIconifyWindow(window); else if (decs.maximize.hovered) { if (window->wl.current.toplevel_states & TOPLEVEL_STATE_MAXIMIZED) _glfwPlatformRestoreWindow(window); else _glfwPlatformMaximizeWindow(window); // hack otherwise on GNOME maximize button remains hovered sometimes decs.maximize.hovered = false; decs.titlebar_needs_update = true; } else if (decs.close.hovered) _glfwInputWindowCloseRequest(window); } decs.dragging = !has_hovered_button(window); break; case CSD_shadow_left: edges = XDG_TOPLEVEL_RESIZE_EDGE_LEFT; break; case CSD_shadow_upper_left: edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; break; case CSD_shadow_right: edges = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; break; case CSD_shadow_upper_right: edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; break; case CSD_shadow_top: edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP; break; case CSD_shadow_lower_left: edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; break; case CSD_shadow_bottom: edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; break; case CSD_shadow_lower_right: edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; break; } if (edges != XDG_TOPLEVEL_RESIZE_EDGE_NONE) xdg_toplevel_resize(window->wl.xdg.toplevel, _glfw.wl.seat, _glfw.wl.pointer_serial, edges); } else if (button == BTN_RIGHT) { if (decs.focus == CSD_titlebar && window->wl.xdg.toplevel) { if (window->wl.wm_capabilities.window_menu) xdg_toplevel_show_window_menu( window->wl.xdg.toplevel, _glfw.wl.seat, _glfw.wl.pointer_serial, (int32_t)x, (int32_t)y - decs.metrics.top); else _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland compositor does not support showing wndow menu"); return; } } } void csd_handle_pointer_event(_GLFWwindow *window, int button, int state, struct wl_surface *surface) { if (!window_is_csd_capable(window)) return; decs.titlebar_needs_update = false; switch (button) { case -1: handle_pointer_move(window); break; case -2: handle_pointer_enter(window, surface); break; case -3: handle_pointer_leave(window, surface); break; default: handle_pointer_button(window, button, state); break; } if (decs.titlebar_needs_update) { csd_change_title(window); if (!window->wl.waiting_for_swap_to_commit) wl_surface_commit(window->wl.surface); } } #undef x #undef y kitty-0.41.1/glfw/wl_client_side_decorations.h0000664000175000017510000000117114773370543020775 0ustar nileshnilesh/* * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "internal.h" void csd_initialize_metrics(_GLFWwindow *window); void csd_free_all_resources(_GLFWwindow *window); bool csd_change_title(_GLFWwindow *window); void csd_set_window_geometry(_GLFWwindow *window, int32_t *width, int32_t *height); bool csd_set_titlebar_color(_GLFWwindow *window, uint32_t color, bool use_system_color); void csd_set_visible(_GLFWwindow *window, bool visible); void csd_handle_pointer_event(_GLFWwindow *window, int button, int state, struct wl_surface* surface); kitty-0.41.1/glfw/wl_cursors.c0000664000175000017510000000360414773370543015617 0ustar nileshnilesh// Future devs supporting whatever Wayland protocol stabilizes for cursor selection: see _themeAdd. #include "internal.h" #include "linux_desktop_settings.h" #include #include #include #include #include static GLFWWLCursorThemes cursor_themes; static int pixels_from_scale(int scale) { int factor; const char* name; glfw_current_cursor_theme(&name, &factor); return factor * scale; } struct wl_cursor_theme* glfw_wlc_theme_for_scale(int scale) { for (size_t i = 0; i < cursor_themes.count; i++) { if (cursor_themes.themes[i].scale == scale) return cursor_themes.themes[i].theme; } if (cursor_themes.count >= cursor_themes.capacity) { cursor_themes.themes = realloc(cursor_themes.themes, sizeof(GLFWWLCursorTheme) * (cursor_themes.count + 16)); if (!cursor_themes.themes) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Out of memory allocating space for cursor themes"); return NULL; } cursor_themes.capacity = cursor_themes.count + 16; } int factor; const char* name; glfw_current_cursor_theme(&name, &factor); struct wl_cursor_theme *ans = wl_cursor_theme_load(name, pixels_from_scale(scale), _glfw.wl.shm); if (!ans) { _glfwInputError( GLFW_PLATFORM_ERROR, "Wayland: wl_cursor_theme_load failed at scale: %d pixels: %d", scale, pixels_from_scale(scale) ); return NULL; } GLFWWLCursorTheme *theme = cursor_themes.themes + cursor_themes.count++; theme->scale = scale; theme->theme = ans; return ans; } void glfw_wlc_destroy(void) { for (size_t i = 0; i < cursor_themes.count; i++) { wl_cursor_theme_destroy(cursor_themes.themes[i].theme); } free(cursor_themes.themes); cursor_themes.themes = NULL; cursor_themes.capacity = 0; cursor_themes.count = 0; } kitty-0.41.1/glfw/wl_cursors.h0000664000175000017510000000055614773370543015627 0ustar nileshnilesh// Declarations for a HiDPI-aware cursor theme manager. #include typedef struct { struct wl_cursor_theme *theme; int scale; } GLFWWLCursorTheme; typedef struct { GLFWWLCursorTheme *themes; size_t count, capacity; } GLFWWLCursorThemes; struct wl_cursor_theme* glfw_wlc_theme_for_scale(int scale); void glfw_wlc_destroy(void); kitty-0.41.1/glfw/wl_init.c0000664000175000017510000010710014773370543015056 0ustar nileshnilesh//======================================================================== // GLFW 3.4 Wayland - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2014 Jonas Ådahl // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #define _GNU_SOURCE #include "internal.h" #include "backend_utils.h" #include "wl_client_side_decorations.h" #include "linux_desktop_settings.h" #include "../kitty/monotonic.h" #include "wl_text_input.h" #include "wayland-text-input-unstable-v3-client-protocol.h" #include #include #include #include #include #include #include #include #include // errno.h needed for BSD code paths #include // Needed for the BTN_* defines #ifdef __has_include #if __has_include() #include #elif __has_include() #include #endif #else #include #endif #define debug debug_rendering #define x window->wl.allCursorPosX #define y window->wl.allCursorPosY static _GLFWwindow* get_window_from_surface(struct wl_surface* surface) { if (!surface) return NULL; _GLFWwindow *ans = wl_surface_get_user_data(surface); if (ans) { const _GLFWwindow *w = _glfw.windowListHead; while (w) { if (w == ans) return ans; w = w->next; } } return NULL; } static void pointerHandleEnter( void* data UNUSED, struct wl_pointer* pointer UNUSED, uint32_t serial, struct wl_surface* surface, wl_fixed_t sx, wl_fixed_t sy ) { _GLFWwindow* window = get_window_from_surface(surface); if (!window) return; _glfw.wl.serial = serial; _glfw.wl.input_serial = serial; _glfw.wl.pointer_serial = serial; _glfw.wl.pointer_enter_serial = serial; _glfw.wl.pointerFocus = window; window->wl.allCursorPosX = wl_fixed_to_double(sx); window->wl.allCursorPosY = wl_fixed_to_double(sy); if (surface != window->wl.surface) { csd_handle_pointer_event(window, -2, -2, surface); } else { window->wl.decorations.focus = CENTRAL_WINDOW; window->wl.hovered = true; window->wl.cursorPosX = x; window->wl.cursorPosY = y; _glfwPlatformSetCursor(window, window->wl.currentCursor); _glfwInputCursorEnter(window, true); } } static void pointerHandleLeave(void* data UNUSED, struct wl_pointer* pointer UNUSED, uint32_t serial, struct wl_surface* surface) { _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window) return; _glfw.wl.serial = serial; _glfw.wl.pointerFocus = NULL; if (window->wl.surface == surface) { window->wl.hovered = false; _glfwInputCursorEnter(window, false); _glfw.wl.cursorPreviousShape = GLFW_INVALID_CURSOR; } else csd_handle_pointer_event(window, -3, -3, surface); } static void pointerHandleMotion(void* data UNUSED, struct wl_pointer* pointer UNUSED, uint32_t time UNUSED, wl_fixed_t sx, wl_fixed_t sy) { _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window || window->cursorMode == GLFW_CURSOR_DISABLED) return; window->wl.allCursorPosX = wl_fixed_to_double(sx); window->wl.allCursorPosY = wl_fixed_to_double(sy); if (window->wl.decorations.focus != CENTRAL_WINDOW) { csd_handle_pointer_event(window, -1, -1, NULL); } else { window->wl.cursorPosX = x; window->wl.cursorPosY = y; _glfwInputCursorPos(window, x, y); _glfw.wl.cursorPreviousShape = GLFW_INVALID_CURSOR; } } static void pointerHandleButton(void* data UNUSED, struct wl_pointer* pointer UNUSED, uint32_t serial, uint32_t time UNUSED, uint32_t button, uint32_t state) { _glfw.wl.serial = serial; _glfw.wl.input_serial = serial; _glfw.wl.pointer_serial = serial; _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window) return; if (window->wl.decorations.focus != CENTRAL_WINDOW) { csd_handle_pointer_event(window, button, state, NULL); return; } /* Makes left, right and middle 0, 1 and 2. Overall order follows evdev * codes. */ int glfwButton = button - BTN_LEFT; _glfwInputMouseClick( window, glfwButton, state == WL_POINTER_BUTTON_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE, _glfw.wl.xkb.states.modifiers); } #undef x #undef y #define info (window->wl.pointer_curr_axis_info) static void pointer_handle_axis_common(enum _GLFWWaylandAxisEvent type, uint32_t axis, wl_fixed_t value) { _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window || window->wl.decorations.focus != CENTRAL_WINDOW) return; float fval = (float) wl_fixed_to_double(value); #define CASE(type, type_const, axis, fval) \ case type_const: \ if (info.type.axis##_axis_type == AXIS_EVENT_UNKNOWN) { \ info.type.axis##_axis_type = type_const; info.type.axis = 0.f; } \ info.type.axis += fval; break; if (window) { switch ((enum wl_pointer_axis)axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: switch (type) { case AXIS_EVENT_UNKNOWN: break; CASE(discrete, AXIS_EVENT_DISCRETE, y, -fval); // wheel event CASE(discrete, AXIS_EVENT_VALUE120, y, -fval); // wheel event higher res than plain discrete CASE(continuous, AXIS_EVENT_CONTINUOUS, y, -fval); // touchpad, etc. high res } break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: switch (type) { case AXIS_EVENT_UNKNOWN: break; CASE(discrete, AXIS_EVENT_DISCRETE, x, fval); // wheel event CASE(discrete, AXIS_EVENT_VALUE120, x, fval); // wheel event higher res than plain discrete CASE(continuous, AXIS_EVENT_CONTINUOUS, x, fval); // touchpad, etc. high res } break; } } #undef CASE } static void pointer_handle_axis(void *data UNUSED, struct wl_pointer *pointer UNUSED, uint32_t time, uint32_t axis, wl_fixed_t value) { _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window) return; if (!info.timestamp_ns) info.timestamp_ns = ms_to_monotonic_t(time); pointer_handle_axis_common(AXIS_EVENT_CONTINUOUS, axis, value); } static void pointer_handle_frame(void *data UNUSED, struct wl_pointer *pointer UNUSED) { _GLFWwindow* window = _glfw.wl.pointerFocus; if (!window) return; float x = 0, y = 0; int highres = 0; if (info.discrete.y_axis_type != AXIS_EVENT_UNKNOWN) { y = info.discrete.y; if (info.discrete.y_axis_type == AXIS_EVENT_VALUE120) y /= 120.f; } else if (info.continuous.y_axis_type != AXIS_EVENT_UNKNOWN) { highres = 1; y = info.continuous.y; } if (info.discrete.x_axis_type != AXIS_EVENT_UNKNOWN) { x = info.discrete.x; if (info.discrete.x_axis_type == AXIS_EVENT_VALUE120) x /= 120.f; } else if (info.continuous.x_axis_type != AXIS_EVENT_UNKNOWN) { highres = 1; x = info.continuous.x; } /* clear pointer_curr_axis_info for next frame */ memset(&info, 0, sizeof(info)); if (x != 0.0f || y != 0.0f) { float scale = (float)_glfwWaylandWindowScale(window); y *= scale; x *= scale; _glfwInputScroll(window, -x, y, highres, _glfw.wl.xkb.states.modifiers); } } static void pointer_handle_axis_source(void* data UNUSED, struct wl_pointer* pointer UNUSED, uint32_t source UNUSED) { } static void pointer_handle_axis_stop(void *data UNUSED, struct wl_pointer *wl_pointer UNUSED, uint32_t time UNUSED, uint32_t axis UNUSED) { } static void pointer_handle_axis_discrete(void *data UNUSED, struct wl_pointer *pointer UNUSED, uint32_t axis, int32_t discrete) { pointer_handle_axis_common(AXIS_EVENT_DISCRETE, axis, wl_fixed_from_int(discrete)); } static void pointer_handle_axis_value120(void *data UNUSED, struct wl_pointer *pointer UNUSED, uint32_t axis, int32_t value120) { pointer_handle_axis_common(AXIS_EVENT_VALUE120, axis, wl_fixed_from_int(value120)); } static void pointer_handle_axis_relative_direction(void *data UNUSED, struct wl_pointer *pointer UNUSED, uint32_t axis UNUSED, uint32_t axis_relative_direction UNUSED) { } #undef info static const struct wl_pointer_listener pointerListener = { .enter = pointerHandleEnter, .leave = pointerHandleLeave, .motion = pointerHandleMotion, .button = pointerHandleButton, .axis = pointer_handle_axis, .frame = pointer_handle_frame, .axis_source = pointer_handle_axis_source, .axis_stop = pointer_handle_axis_stop, .axis_discrete = pointer_handle_axis_discrete, #ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION .axis_value120 = pointer_handle_axis_value120, #endif #ifdef WL_POINTER_AXIS_RELATIVE_DIRECTION_SINCE_VERSION .axis_relative_direction = pointer_handle_axis_relative_direction, #endif }; static void keyboardHandleKeymap(void* data UNUSED, struct wl_keyboard* keyboard UNUSED, uint32_t format, int fd, uint32_t size) { char* mapStr; if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { _glfwInputError(GLFW_PLATFORM_ERROR, "Unknown keymap format: %u", format); close(fd); return; } mapStr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (mapStr == MAP_FAILED) { close(fd); _glfwInputError(GLFW_PLATFORM_ERROR, "Mapping of keymap file descriptor failed: %u", format); return; } glfw_xkb_compile_keymap(&_glfw.wl.xkb, mapStr); munmap(mapStr, size); close(fd); } static void keyboardHandleEnter(void* data UNUSED, struct wl_keyboard* keyboard UNUSED, uint32_t serial, struct wl_surface* surface, struct wl_array* keys) { _GLFWwindow* window = get_window_from_surface(surface); if (!window) return; _glfw.wl.serial = serial; _glfw.wl.input_serial = serial; _glfw.wl.keyboard_enter_serial = serial; _glfw.wl.keyboardFocusId = window->id; _glfwInputWindowFocus(window, true); uint32_t* key; if (keys && _glfw.wl.keyRepeatInfo.key) { wl_array_for_each(key, keys) { if (*key == _glfw.wl.keyRepeatInfo.key) { toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1); break; } } } } static void keyboardHandleLeave(void* data UNUSED, struct wl_keyboard* keyboard UNUSED, uint32_t serial, struct wl_surface* surface UNUSED) { _GLFWwindow* window = _glfwWindowForId(_glfw.wl.keyboardFocusId); if (!window) return; _glfw.wl.serial = serial; _glfw.wl.keyboardFocusId = 0; _glfwInputWindowFocus(window, false); toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0); } static void dispatchPendingKeyRepeats(id_type timer_id UNUSED, void *data UNUSED) { if (_glfw.wl.keyRepeatInfo.keyboardFocusId != _glfw.wl.keyboardFocusId || _glfw.wl.keyboardRepeatRate == 0) return; _GLFWwindow* window = _glfwWindowForId(_glfw.wl.keyboardFocusId); if (!window) return; glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT); changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (s_to_monotonic_t(1ll) / (monotonic_t)_glfw.wl.keyboardRepeatRate)); toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1); } static void keyboardHandleKey(void* data UNUSED, struct wl_keyboard* keyboard UNUSED, uint32_t serial, uint32_t time UNUSED, uint32_t key, uint32_t state) { _GLFWwindow* window = _glfwWindowForId(_glfw.wl.keyboardFocusId); if (!window) return; int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE; _glfw.wl.serial = serial; _glfw.wl.input_serial = serial; glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action); if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key)) { _glfw.wl.keyRepeatInfo.key = key; _glfw.wl.keyRepeatInfo.keyboardFocusId = window->id; changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, _glfw.wl.keyboardRepeatDelay); toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1); } else if (action == GLFW_RELEASE && key == _glfw.wl.keyRepeatInfo.key) { _glfw.wl.keyRepeatInfo.key = 0; toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0); } } static void keyboardHandleModifiers(void* data UNUSED, struct wl_keyboard* keyboard UNUSED, uint32_t serial, uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { _glfw.wl.serial = serial; _glfw.wl.input_serial = serial; glfw_xkb_update_modifiers(&_glfw.wl.xkb, modsDepressed, modsLatched, modsLocked, 0, 0, group); } static void keyboardHandleRepeatInfo(void* data UNUSED, struct wl_keyboard* keyboard, int32_t rate, int32_t delay) { if (keyboard != _glfw.wl.keyboard) return; _glfw.wl.keyboardRepeatRate = rate; _glfw.wl.keyboardRepeatDelay = ms_to_monotonic_t(delay); } static const struct wl_keyboard_listener keyboardListener = { keyboardHandleKeymap, keyboardHandleEnter, keyboardHandleLeave, keyboardHandleKey, keyboardHandleModifiers, keyboardHandleRepeatInfo, }; static void seatHandleCapabilities(void* data UNUSED, struct wl_seat* seat, enum wl_seat_capability caps) { if ((caps & WL_SEAT_CAPABILITY_POINTER) && !_glfw.wl.pointer) { _glfw.wl.pointer = wl_seat_get_pointer(seat); wl_pointer_add_listener(_glfw.wl.pointer, &pointerListener, NULL); if (_glfw.wl.wp_cursor_shape_manager_v1) { if (_glfw.wl.wp_cursor_shape_device_v1) wp_cursor_shape_device_v1_destroy(_glfw.wl.wp_cursor_shape_device_v1); _glfw.wl.wp_cursor_shape_device_v1 = NULL; _glfw.wl.wp_cursor_shape_device_v1 = wp_cursor_shape_manager_v1_get_pointer(_glfw.wl.wp_cursor_shape_manager_v1, _glfw.wl.pointer); } } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && _glfw.wl.pointer) { if (_glfw.wl.wp_cursor_shape_device_v1) wp_cursor_shape_device_v1_destroy(_glfw.wl.wp_cursor_shape_device_v1); _glfw.wl.wp_cursor_shape_device_v1 = NULL; wl_pointer_destroy(_glfw.wl.pointer); _glfw.wl.pointer = NULL; if (_glfw.wl.cursorAnimationTimer) toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0); } if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !_glfw.wl.keyboard) { _glfw.wl.keyboard = wl_seat_get_keyboard(seat); wl_keyboard_add_listener(_glfw.wl.keyboard, &keyboardListener, NULL); } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && _glfw.wl.keyboard) { wl_keyboard_destroy(_glfw.wl.keyboard); _glfw.wl.keyboard = NULL; _glfw.wl.keyboardFocusId = 0; if (_glfw.wl.keyRepeatInfo.keyRepeatTimer) toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0); } } static void seatHandleName(void* data UNUSED, struct wl_seat* seat UNUSED, const char* name UNUSED) { } static const struct wl_seat_listener seatListener = { seatHandleCapabilities, seatHandleName, }; static void wmBaseHandlePing(void* data UNUSED, struct xdg_wm_base* wmBase, uint32_t serial) { xdg_wm_base_pong(wmBase, serial); } static const struct xdg_wm_base_listener wmBaseListener = { wmBaseHandlePing }; static void registryHandleGlobal(void* data UNUSED, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { #define is(x) strcmp(interface, x##_interface.name) == 0 if (is(wl_compositor)) { #ifdef WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION _glfw.wl.compositorVersion = MIN(WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION, (int)version); _glfw.wl.has_preferred_buffer_scale = _glfw.wl.compositorVersion >= WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION; #else _glfw.wl.compositorVersion = MIN(3, (int)version); #endif _glfw.wl.compositor = wl_registry_bind(registry, name, &wl_compositor_interface, _glfw.wl.compositorVersion); } else if (is(wl_subcompositor)) { _glfw.wl.subcompositor = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1); } else if (is(wl_shm)) { _glfw.wl.shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); } else if (is(wl_output)) { _glfwAddOutputWayland(name, version); } else if (is(wl_seat)) { if (!_glfw.wl.seat) { #ifdef WL_POINTER_AXIS_RELATIVE_DIRECTION_SINCE_VERSION _glfw.wl.seatVersion = MIN(WL_POINTER_AXIS_RELATIVE_DIRECTION_SINCE_VERSION, (int)version); #elif defined(WL_POINTER_AXIS_VALUE120_SINCE_VERSION) _glfw.wl.seatVersion = MIN(WL_POINTER_AXIS_VALUE120_SINCE_VERSION, (int)version); #else _glfw.wl.seatVersion = MIN(WL_POINTER_AXIS_DISCRETE_SINCE_VERSION, version); #endif _glfw.wl.seat = wl_registry_bind(registry, name, &wl_seat_interface, _glfw.wl.seatVersion); wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL); } if (_glfw.wl.seat) { if (_glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) _glfwSetupWaylandDataDevice(); if (_glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) { _glfwSetupWaylandPrimarySelectionDevice(); } } } else if (is(xdg_wm_base)) { _glfw.wl.xdg_wm_base_version = 1; #ifdef XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION _glfw.wl.xdg_wm_base_version = MIN(XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION, (int)version); #elif defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) _glfw.wl.xdg_wm_base_version = MIN(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION, (int)version); #endif _glfw.wl.wmBase = wl_registry_bind(registry, name, &xdg_wm_base_interface, _glfw.wl.xdg_wm_base_version); xdg_wm_base_add_listener(_glfw.wl.wmBase, &wmBaseListener, NULL); } else if (is(zxdg_decoration_manager_v1)) { _glfw.wl.decorationManager = wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1); } else if (is(zwp_relative_pointer_manager_v1)) { _glfw.wl.relativePointerManager = wl_registry_bind(registry, name, &zwp_relative_pointer_manager_v1_interface, 1); } else if (is(zwp_pointer_constraints_v1)) { _glfw.wl.pointerConstraints = wl_registry_bind(registry, name, &zwp_pointer_constraints_v1_interface, 1); } else if (is(zwp_text_input_manager_v3)) { _glfwWaylandBindTextInput(registry, name); } else if (is(wl_data_device_manager)) { _glfw.wl.dataDeviceManager = wl_registry_bind(registry, name, &wl_data_device_manager_interface, 1); if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) { _glfwSetupWaylandDataDevice(); } } else if (is(zwp_primary_selection_device_manager_v1)) { _glfw.wl.primarySelectionDeviceManager = wl_registry_bind(registry, name, &zwp_primary_selection_device_manager_v1_interface, 1); if (_glfw.wl.seat && _glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) { _glfwSetupWaylandPrimarySelectionDevice(); } } else if (is(wp_single_pixel_buffer_manager_v1)) { _glfw.wl.wp_single_pixel_buffer_manager_v1 = wl_registry_bind(registry, name, &wp_single_pixel_buffer_manager_v1_interface, 1); } else if (is(xdg_activation_v1)) { _glfw.wl.xdg_activation_v1 = wl_registry_bind(registry, name, &xdg_activation_v1_interface, 1); } else if (is(wp_cursor_shape_manager_v1)) { _glfw.wl.wp_cursor_shape_manager_v1 = wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, 1); } else if (is(wp_fractional_scale_manager_v1)) { _glfw.wl.wp_fractional_scale_manager_v1 = wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, 1); } else if (is(wp_viewporter)) { _glfw.wl.wp_viewporter = wl_registry_bind(registry, name, &wp_viewporter_interface, 1); } else if (is(org_kde_kwin_blur_manager)) { _glfw.wl.org_kde_kwin_blur_manager = wl_registry_bind(registry, name, &org_kde_kwin_blur_manager_interface, 1); } else if (is(zwlr_layer_shell_v1)) { if (version >= 4) { _glfw.wl.zwlr_layer_shell_v1_version = version; _glfw.wl.zwlr_layer_shell_v1 = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version); } } else if (is(zwp_idle_inhibit_manager_v1)) { _glfw.wl.idle_inhibit_manager = wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1); } else if (is(xdg_toplevel_icon_manager_v1)) { _glfw.wl.xdg_toplevel_icon_manager_v1 = wl_registry_bind(registry, name, &xdg_toplevel_icon_manager_v1_interface, 1); } #undef is } static void registryHandleGlobalRemove(void *data UNUSED, struct wl_registry *registry UNUSED, uint32_t name) { _GLFWmonitor* monitor; for (int i = 0; i < _glfw.monitorCount; ++i) { monitor = _glfw.monitors[i]; if (monitor->wl.name == name) { for (_GLFWwindow *window = _glfw.windowListHead; window; window = window->next) { for (int m = window->wl.monitorsCount - 1; m >= 0; m--) { if (window->wl.monitors[m] == monitor) { remove_i_from_array(window->wl.monitors, m, window->wl.monitorsCount); } } } _glfwInputMonitor(monitor, GLFW_DISCONNECTED, 0); return; } } } static const struct wl_registry_listener registryListener = { registryHandleGlobal, registryHandleGlobalRemove }; GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized) { return glfw_current_system_color_theme(query_if_unintialized); } static pid_t get_socket_peer_pid(int fd) { (void)fd; #ifdef __linux__ struct ucred ucred; socklen_t len = sizeof(struct ucred); return (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) ? -1 : ucred.pid; #elif defined(LOCAL_PEERCRED) && defined(XUCRED_VERSION) struct xucred peercred; socklen_t peercredlen = sizeof(peercred); return (getsockopt(c->fd, LOCAL_PEERCRED, 1, (void *)&peercred, &peercredlen) == 0 && peercred.cr_version == XUCRED_VERSION) ? peercred.cr_pid : -1; #elif defined(LOCAL_PEERPID) pid_t pid; socklen_t pid_size = sizeof(pid); return getsockopt(client, SOL_LOCAL, LOCAL_PEERPID, &pid, &pid_size) == -1 ? -1 : pid; #else errno = ENOSYS; return -1; #endif } GLFWAPI pid_t glfwWaylandCompositorPID(void) { if (!_glfw.wl.display) return -1; int fd = wl_display_get_fd(_glfw.wl.display); if (fd < 0) return -1; return get_socket_peer_pid(fd); } const char* _glfwWaylandCompositorName(void) { static bool probed = false; if (!probed) { probed = true; static const size_t sz = 1024; _glfw.wl.compositor_name = malloc(sz); if (!_glfw.wl.compositor_name) return ""; char *ans = _glfw.wl.compositor_name; ans[0] = 0; pid_t cpid = glfwWaylandCompositorPID(); if (cpid < 0) return ans; snprintf(ans, sz, "/proc/%d/cmdline", cpid); int fd = open(ans, O_RDONLY | O_CLOEXEC); if (fd < 0) { ans[0] = 0; } else { ssize_t n; while (true) { n = read(fd, ans, sz-1); if (n < 0 && errno == EINTR) continue; close(fd); break; } ans[n < 0 ? 0 : n] = 0; } } return _glfw.wl.compositor_name ? _glfw.wl.compositor_name : ""; } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// static const char* get_compositor_missing_capabilities(void) { #define C(title, x) if (!_glfw.wl.x) p += snprintf(p, sizeof(buf) - (p - buf), "%s ", #title); static char buf[512]; char *p = buf; *p = 0; C(viewporter, wp_viewporter); C(fractional_scale, wp_fractional_scale_manager_v1); C(blur, org_kde_kwin_blur_manager); C(server_side_decorations, decorationManager); C(cursor_shape, wp_cursor_shape_manager_v1); C(layer_shell, zwlr_layer_shell_v1); C(single_pixel_buffer, wp_single_pixel_buffer_manager_v1); C(preferred_scale, has_preferred_buffer_scale); C(idle_inhibit, idle_inhibit_manager); C(icon, xdg_toplevel_icon_manager_v1); if (_glfw.wl.xdg_wm_base_version < 6) p += snprintf(p, sizeof(buf) - (p - buf), "%s ", "window-state-suspended"); if (_glfw.wl.xdg_wm_base_version < 5) p += snprintf(p, sizeof(buf) - (p - buf), "%s ", "window-capabilities"); #undef C while (p > buf && (p - 1)[0] == ' ') { p--; *p = 0; } return buf; } GLFWAPI const char* glfwWaylandMissingCapabilities(void) { return get_compositor_missing_capabilities(); } int _glfwPlatformInit(bool *supports_window_occlusion) { int i; _GLFWmonitor* monitor; _glfw.wl.cursor.handle = _glfw_dlopen("libwayland-cursor.so.0"); if (!_glfw.wl.cursor.handle) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to open libwayland-cursor"); return false; } glfw_dlsym(_glfw.wl.cursor.theme_load, _glfw.wl.cursor.handle, "wl_cursor_theme_load"); glfw_dlsym(_glfw.wl.cursor.theme_destroy, _glfw.wl.cursor.handle, "wl_cursor_theme_destroy"); glfw_dlsym(_glfw.wl.cursor.theme_get_cursor, _glfw.wl.cursor.handle, "wl_cursor_theme_get_cursor"); glfw_dlsym(_glfw.wl.cursor.image_get_buffer, _glfw.wl.cursor.handle, "wl_cursor_image_get_buffer"); _glfw.wl.egl.handle = _glfw_dlopen("libwayland-egl.so.1"); if (!_glfw.wl.egl.handle) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to open libwayland-egl"); return false; } glfw_dlsym(_glfw.wl.egl.window_create, _glfw.wl.egl.handle, "wl_egl_window_create"); glfw_dlsym(_glfw.wl.egl.window_destroy, _glfw.wl.egl.handle, "wl_egl_window_destroy"); glfw_dlsym(_glfw.wl.egl.window_resize, _glfw.wl.egl.handle, "wl_egl_window_resize"); _glfw.wl.display = wl_display_connect(NULL); if (!_glfw.wl.display) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to connect to display"); return false; } if (!initPollData(&_glfw.wl.eventLoopData, wl_display_get_fd(_glfw.wl.display))) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to initialize event loop data"); } glfw_dbus_init(&_glfw.wl.dbus, &_glfw.wl.eventLoopData); glfw_initialize_desktop_settings(); _glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", ms_to_monotonic_t(500ll), 0, true, dispatchPendingKeyRepeats, NULL, NULL); _glfw.wl.cursorAnimationTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-cursor-animation", ms_to_monotonic_t(500ll), 0, true, animateCursorImage, NULL, NULL); _glfw.wl.registry = wl_display_get_registry(_glfw.wl.display); wl_registry_add_listener(_glfw.wl.registry, ®istryListener, NULL); if (!glfw_xkb_create_context(&_glfw.wl.xkb)) return false; // Sync so we got all registry objects wl_display_roundtrip(_glfw.wl.display); _glfwWaylandInitTextInput(); // Sync so we got all initial output events wl_display_roundtrip(_glfw.wl.display); for (i = 0; i < _glfw.monitorCount; ++i) { monitor = _glfw.monitors[i]; if (monitor->widthMM <= 0 || monitor->heightMM <= 0) { // If Wayland does not provide a physical size, assume the default 96 DPI monitor->widthMM = (int) (monitor->modes[monitor->wl.currentMode].width * 25.4f / 96.f); monitor->heightMM = (int) (monitor->modes[monitor->wl.currentMode].height * 25.4f / 96.f); } } if (!_glfw.wl.wmBase) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to find xdg-shell in your compositor"); return false; } if (_glfw.wl.shm) { _glfw.wl.cursorSurface = wl_compositor_create_surface(_glfw.wl.compositor); } else { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to find Wayland SHM"); return false; } if (_glfw.hints.init.debugRendering) { const char *mc = get_compositor_missing_capabilities(); if (mc && mc[0]) debug("Compositor missing capabilities: %s\n", mc); } *supports_window_occlusion = _glfw.wl.xdg_wm_base_version > 5; return true; } void _glfwPlatformTerminate(void) { if (_glfw.wl.activation_requests.array) { for (size_t i=0; i < _glfw.wl.activation_requests.sz; i++) { glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + i; if (r->callback) r->callback(NULL, NULL, r->callback_data); xdg_activation_token_v1_destroy(r->token); } free(_glfw.wl.activation_requests.array); } _glfwTerminateEGL(); if (_glfw.wl.egl.handle) { _glfw_dlclose(_glfw.wl.egl.handle); _glfw.wl.egl.handle = NULL; } glfw_xkb_release(&_glfw.wl.xkb); glfw_dbus_terminate(&_glfw.wl.dbus); glfw_wlc_destroy(); if (_glfw.wl.cursor.handle) { _glfw_dlclose(_glfw.wl.cursor.handle); _glfw.wl.cursor.handle = NULL; } if (_glfw.wl.cursorSurface) wl_surface_destroy(_glfw.wl.cursorSurface); if (_glfw.wl.subcompositor) wl_subcompositor_destroy(_glfw.wl.subcompositor); if (_glfw.wl.compositor) wl_compositor_destroy(_glfw.wl.compositor); if (_glfw.wl.shm) wl_shm_destroy(_glfw.wl.shm); if (_glfw.wl.decorationManager) zxdg_decoration_manager_v1_destroy(_glfw.wl.decorationManager); if (_glfw.wl.wmBase) xdg_wm_base_destroy(_glfw.wl.wmBase); if (_glfw.wl.pointer) wl_pointer_destroy(_glfw.wl.pointer); if (_glfw.wl.keyboard) wl_keyboard_destroy(_glfw.wl.keyboard); if (_glfw.wl.seat) wl_seat_destroy(_glfw.wl.seat); if (_glfw.wl.relativePointerManager) zwp_relative_pointer_manager_v1_destroy(_glfw.wl.relativePointerManager); if (_glfw.wl.pointerConstraints) zwp_pointer_constraints_v1_destroy(_glfw.wl.pointerConstraints); _glfwWaylandDestroyTextInput(); if (_glfw.wl.dataSourceForClipboard) wl_data_source_destroy(_glfw.wl.dataSourceForClipboard); if (_glfw.wl.dataSourceForPrimarySelection) zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection); for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) { if (_glfw.wl.dataOffers[doi].id) { destroy_data_offer(&_glfw.wl.dataOffers[doi]); } } if (_glfw.wl.dataDevice) wl_data_device_destroy(_glfw.wl.dataDevice); if (_glfw.wl.dataDeviceManager) wl_data_device_manager_destroy(_glfw.wl.dataDeviceManager); if (_glfw.wl.primarySelectionDevice) zwp_primary_selection_device_v1_destroy(_glfw.wl.primarySelectionDevice); if (_glfw.wl.primarySelectionDeviceManager) zwp_primary_selection_device_manager_v1_destroy(_glfw.wl.primarySelectionDeviceManager); if (_glfw.wl.xdg_activation_v1) xdg_activation_v1_destroy(_glfw.wl.xdg_activation_v1); if (_glfw.wl.xdg_toplevel_icon_manager_v1) xdg_toplevel_icon_manager_v1_destroy(_glfw.wl.xdg_toplevel_icon_manager_v1); if (_glfw.wl.wp_single_pixel_buffer_manager_v1) wp_single_pixel_buffer_manager_v1_destroy(_glfw.wl.wp_single_pixel_buffer_manager_v1); if (_glfw.wl.wp_cursor_shape_manager_v1) wp_cursor_shape_manager_v1_destroy(_glfw.wl.wp_cursor_shape_manager_v1); if (_glfw.wl.wp_viewporter) wp_viewporter_destroy(_glfw.wl.wp_viewporter); if (_glfw.wl.wp_fractional_scale_manager_v1) wp_fractional_scale_manager_v1_destroy(_glfw.wl.wp_fractional_scale_manager_v1); if (_glfw.wl.org_kde_kwin_blur_manager) org_kde_kwin_blur_manager_destroy(_glfw.wl.org_kde_kwin_blur_manager); if (_glfw.wl.zwlr_layer_shell_v1) zwlr_layer_shell_v1_destroy(_glfw.wl.zwlr_layer_shell_v1); if (_glfw.wl.idle_inhibit_manager) zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idle_inhibit_manager); if (_glfw.wl.registry) wl_registry_destroy(_glfw.wl.registry); if (_glfw.wl.display) { wl_display_flush(_glfw.wl.display); wl_display_disconnect(_glfw.wl.display); _glfw.wl.display = NULL; } finalizePollData(&_glfw.wl.eventLoopData); if (_glfw.wl.compositor_name) { free(_glfw.wl.compositor_name); _glfw.wl.compositor_name = NULL; } } #define GLFW_LOOP_BACKEND wl #include "main_loop.h" const char* _glfwPlatformGetVersionString(void) { (void)keep_going; return _GLFW_VERSION_NUMBER " Wayland EGL OSMesa" #if defined(_POSIX_TIMERS) && defined(_POSIX_MONOTONIC_CLOCK) " clock_gettime" #else " gettimeofday" #endif " evdev" #if defined(_GLFW_BUILD_DLL) " shared" #endif ; } kitty-0.41.1/glfw/wl_monitor.c0000664000175000017510000001754414773370543015616 0ustar nileshnilesh//======================================================================== // GLFW 3.4 Wayland - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2014 Jonas Ådahl // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #include "internal.h" #include #include #include #include static void outputHandleGeometry(void* data, struct wl_output* output UNUSED, int32_t x, int32_t y, int32_t physicalWidth, int32_t physicalHeight, int32_t subpixel UNUSED, const char* make, const char* model, int32_t transform UNUSED) { struct _GLFWmonitor *monitor = data; char name[1024]; monitor->wl.x = x; monitor->wl.y = y; monitor->widthMM = physicalWidth; monitor->heightMM = physicalHeight; snprintf(name, sizeof(name), "%s %s", make, model); monitor->name = _glfw_strdup(name); } static void outputHandleMode(void* data, struct wl_output* output UNUSED, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { struct _GLFWmonitor *monitor = data; GLFWvidmode mode; mode.width = width; mode.height = height; mode.redBits = 8; mode.greenBits = 8; mode.blueBits = 8; mode.refreshRate = (int) round(refresh / 1000.0); monitor->modeCount++; monitor->modes = realloc(monitor->modes, monitor->modeCount * sizeof(GLFWvidmode)); monitor->modes[monitor->modeCount - 1] = mode; if (flags & WL_OUTPUT_MODE_CURRENT) monitor->wl.currentMode = monitor->modeCount - 1; } static void outputHandleDone(void* data, struct wl_output* output UNUSED) { struct _GLFWmonitor *monitor = data; for (int i = 0; i < _glfw.monitorCount; i++) { if (_glfw.monitors[i] == monitor) return; } _glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST); } static void outputHandleScale(void* data, struct wl_output* output UNUSED, int32_t factor) { struct _GLFWmonitor *monitor = data; if (factor > 0 && factor < 24) monitor->wl.scale = factor; } static void outputHandleName(void* data, struct wl_output* output UNUSED, const char* name) { struct _GLFWmonitor *monitor = data; if (name) strncpy(monitor->wl.friendly_name, name, sizeof(monitor->wl.friendly_name)-1); } static void outputHandleDescription(void* data, struct wl_output* output UNUSED, const char* description) { struct _GLFWmonitor *monitor = data; if (description) strncpy(monitor->wl.description, description, sizeof(monitor->wl.description)-1); } static const struct wl_output_listener outputListener = { outputHandleGeometry, outputHandleMode, outputHandleDone, outputHandleScale, outputHandleName, outputHandleDescription, }; ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// void _glfwAddOutputWayland(uint32_t name, uint32_t version) { _GLFWmonitor *monitor; struct wl_output *output; if (version < 2) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Unsupported output interface version"); return; } // The actual name of this output will be set in the geometry handler. monitor = _glfwAllocMonitor(NULL, 0, 0); output = wl_registry_bind(_glfw.wl.registry, name, &wl_output_interface, MIN(version, (unsigned)WL_OUTPUT_NAME_SINCE_VERSION)); if (!output) { _glfwFreeMonitor(monitor); return; } monitor->wl.scale = 1; monitor->wl.output = output; monitor->wl.name = name; wl_output_add_listener(output, &outputListener, monitor); } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor) { if (monitor->wl.output) wl_output_destroy(monitor->wl.output); } void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos) { if (xpos) *xpos = monitor->wl.x; if (ypos) *ypos = monitor->wl.y; } void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor, float* xscale, float* yscale) { if (xscale) *xscale = (float) monitor->wl.scale; if (yscale) *yscale = (float) monitor->wl.scale; } void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor, int* xpos, int* ypos, int* width, int* height) { if (xpos) *xpos = monitor->wl.x; if (ypos) *ypos = monitor->wl.y; if (width) *width = monitor->modes[monitor->wl.currentMode].width; if (height) *height = monitor->modes[monitor->wl.currentMode].height; } GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* found) { *found = monitor->modeCount; return monitor->modes; } bool _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode) { if (monitor->modeCount > monitor->wl.currentMode) { *mode = monitor->modes[monitor->wl.currentMode]; return true; } return false; } bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor UNUSED, GLFWgammaramp* ramp UNUSED) { _glfwInputError(GLFW_FEATURE_UNAVAILABLE, "Wayland: Gamma ramp access is not available"); return false; } void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor UNUSED, const GLFWgammaramp* ramp UNUSED) { _glfwInputError(GLFW_FEATURE_UNAVAILABLE, "Wayland: Gamma ramp access is not available"); } ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI struct wl_output* glfwGetWaylandMonitor(GLFWmonitor* handle) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(NULL); return monitor->wl.output; } kitty-0.41.1/glfw/wl_platform.h0000664000175000017510000003731514773370543015756 0ustar nileshnilesh//======================================================================== // GLFW 3.4 Wayland - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2014 Jonas Ådahl // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #include #include #include typedef VkFlags VkWaylandSurfaceCreateFlagsKHR; typedef struct VkWaylandSurfaceCreateInfoKHR { VkStructureType sType; const void* pNext; VkWaylandSurfaceCreateFlagsKHR flags; struct wl_display* display; struct wl_surface* surface; } VkWaylandSurfaceCreateInfoKHR; typedef VkResult (APIENTRY *PFN_vkCreateWaylandSurfaceKHR)(VkInstance,const VkWaylandSurfaceCreateInfoKHR*,const VkAllocationCallbacks*,VkSurfaceKHR*); typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR)(VkPhysicalDevice,uint32_t,struct wl_display*); #include "posix_thread.h" #ifdef __linux__ #include "linux_joystick.h" #else #include "null_joystick.h" #endif #include "backend_utils.h" #include "xkb_glfw.h" #include "wl_cursors.h" #include "wayland-xdg-shell-client-protocol.h" #include "wayland-xdg-decoration-unstable-v1-client-protocol.h" #include "wayland-relative-pointer-unstable-v1-client-protocol.h" #include "wayland-pointer-constraints-unstable-v1-client-protocol.h" #include "wayland-primary-selection-unstable-v1-client-protocol.h" #include "wayland-primary-selection-unstable-v1-client-protocol.h" #include "wayland-xdg-activation-v1-client-protocol.h" #include "wayland-cursor-shape-v1-client-protocol.h" #include "wayland-fractional-scale-v1-client-protocol.h" #include "wayland-viewporter-client-protocol.h" #include "wayland-kwin-blur-v1-client-protocol.h" #include "wayland-wlr-layer-shell-unstable-v1-client-protocol.h" #include "wayland-single-pixel-buffer-v1-client-protocol.h" #include "wayland-idle-inhibit-unstable-v1-client-protocol.h" #include "wayland-xdg-toplevel-icon-v1-client-protocol.h" #define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL) #define _glfw_dlclose(handle) dlclose(handle) #define _glfw_dlsym(handle, name) dlsym(handle, name) #define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowWayland wl #define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryWayland wl #define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorWayland wl #define _GLFW_PLATFORM_CURSOR_STATE _GLFWcursorWayland wl #define _GLFW_PLATFORM_CONTEXT_STATE #define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE typedef struct wl_cursor_theme* (* PFN_wl_cursor_theme_load)(const char*, int, struct wl_shm*); typedef void (* PFN_wl_cursor_theme_destroy)(struct wl_cursor_theme*); typedef struct wl_cursor* (* PFN_wl_cursor_theme_get_cursor)(struct wl_cursor_theme*, const char*); typedef struct wl_buffer* (* PFN_wl_cursor_image_get_buffer)(struct wl_cursor_image*); #define wl_cursor_theme_load _glfw.wl.cursor.theme_load #define wl_cursor_theme_destroy _glfw.wl.cursor.theme_destroy #define wl_cursor_theme_get_cursor _glfw.wl.cursor.theme_get_cursor #define wl_cursor_image_get_buffer _glfw.wl.cursor.image_get_buffer typedef struct wl_egl_window* (* PFN_wl_egl_window_create)(struct wl_surface*, int, int); typedef void (* PFN_wl_egl_window_destroy)(struct wl_egl_window*); typedef void (* PFN_wl_egl_window_resize)(struct wl_egl_window*, int, int, int, int); #define wl_egl_window_create _glfw.wl.egl.window_create #define wl_egl_window_destroy _glfw.wl.egl.window_destroy #define wl_egl_window_resize _glfw.wl.egl.window_resize typedef enum _GLFWCSDSurface { CENTRAL_WINDOW, CSD_titlebar, CSD_shadow_top, CSD_shadow_left, CSD_shadow_bottom, CSD_shadow_right, CSD_shadow_upper_left, CSD_shadow_upper_right, CSD_shadow_lower_left, CSD_shadow_lower_right, } _GLFWCSDSurface; typedef struct _GLFWWaylandBufferPair { struct wl_buffer *a, *b, *front, *back; struct { uint8_t *a, *b, *front, *back; } data; bool has_pending_update; size_t size_in_bytes, width, height, viewport_width, viewport_height, stride; bool a_needs_to_be_destroyed, b_needs_to_be_destroyed; } _GLFWWaylandBufferPair; typedef struct _GLFWWaylandCSDSurface { struct wl_surface *surface; struct wl_subsurface *subsurface; struct wp_viewport *wp_viewport; _GLFWWaylandBufferPair buffer; int x, y; } _GLFWWaylandCSDSurface; typedef enum WaylandWindowState { TOPLEVEL_STATE_NONE = 0, TOPLEVEL_STATE_MAXIMIZED = 1, TOPLEVEL_STATE_FULLSCREEN = 2, TOPLEVEL_STATE_RESIZING = 4, TOPLEVEL_STATE_ACTIVATED = 8, TOPLEVEL_STATE_TILED_LEFT = 16, TOPLEVEL_STATE_TILED_RIGHT = 32, TOPLEVEL_STATE_TILED_TOP = 64, TOPLEVEL_STATE_TILED_BOTTOM = 128, TOPLEVEL_STATE_SUSPENDED = 256, } WaylandWindowState; typedef struct glfw_wl_xdg_activation_request { GLFWid window_id; GLFWactivationcallback callback; void *callback_data; uintptr_t request_id; void *token; } glfw_wl_xdg_activation_request; static const WaylandWindowState TOPLEVEL_STATE_DOCKED = TOPLEVEL_STATE_MAXIMIZED | TOPLEVEL_STATE_FULLSCREEN | TOPLEVEL_STATE_TILED_TOP | TOPLEVEL_STATE_TILED_LEFT | TOPLEVEL_STATE_TILED_RIGHT | TOPLEVEL_STATE_TILED_BOTTOM; enum WaylandWindowPendingState { PENDING_STATE_TOPLEVEL = 1, PENDING_STATE_DECORATION = 2 }; enum _GLFWWaylandAxisEvent { AXIS_EVENT_UNKNOWN = 0, AXIS_EVENT_CONTINUOUS = 1, AXIS_EVENT_DISCRETE = 2, AXIS_EVENT_VALUE120 = 3 }; // Wayland-specific per-window data // typedef struct _GLFWwindowWayland { int width, height; bool visible; bool hovered; bool transparent; struct wl_surface* surface; bool waiting_for_swap_to_commit; struct wl_egl_window* native; struct wl_callback* callback; struct { struct xdg_surface* surface; struct xdg_toplevel* toplevel; struct zxdg_toplevel_decoration_v1* decoration; struct { int width, height; } top_level_bounds; } xdg; struct wp_fractional_scale_v1 *wp_fractional_scale_v1; struct wp_viewport *wp_viewport; struct org_kde_kwin_blur *org_kde_kwin_blur; bool has_blur, expect_scale_from_compositor, window_fully_created; struct { bool surface_configured, preferred_scale_received, fractional_scale_received; } once; struct wl_buffer *temp_buffer_used_during_window_creation; struct { GLFWLayerShellConfig config; struct zwlr_layer_surface_v1* zwlr_layer_surface_v1; } layer_shell; /* information about axis events on current frame */ struct { struct { enum _GLFWWaylandAxisEvent x_axis_type; float x; enum _GLFWWaylandAxisEvent y_axis_type; float y; } discrete, continuous; /* Event timestamp in nanoseconds */ monotonic_t timestamp_ns; } pointer_curr_axis_info; _GLFWcursor* currentCursor; double cursorPosX, cursorPosY, allCursorPosX, allCursorPosY; char* title; char appId[256]; // We need to track the monitors the window spans on to calculate the // optimal scaling factor. struct { uint32_t deduced, preferred; } integer_scale; uint32_t fractional_scale; bool initial_scale_notified; _GLFWmonitor** monitors; int monitorsCount; int monitorsSize; struct { struct zwp_relative_pointer_v1* relativePointer; struct zwp_locked_pointer_v1* lockedPointer; } pointerLock; struct { bool serverSide, buffer_destroyed, titlebar_needs_update, dragging; _GLFWCSDSurface focus; _GLFWWaylandCSDSurface titlebar, shadow_left, shadow_right, shadow_top, shadow_bottom, shadow_upper_left, shadow_upper_right, shadow_lower_left, shadow_lower_right; struct { uint8_t *data; size_t size; } mapping; struct { int width, height; bool focused; double fscale; WaylandWindowState toplevel_states; } for_window_state; struct { unsigned int width, top, horizontal, vertical, visible_titlebar_height; } metrics; struct { int32_t x, y, width, height; } geometry; struct { bool hovered; int width, left; } minimize, maximize, close; struct { uint32_t *data; size_t for_decoration_size, stride, segments, corner_size; } shadow_tile; monotonic_t last_click_on_top_decoration_at; uint32_t titlebar_color; bool use_custom_titlebar_color; } decorations; struct { unsigned long long id; void(*callback)(unsigned long long id); struct wl_callback *current_wl_callback; } frameCallbackData; struct { int32_t width, height; } user_requested_content_size; struct { bool minimize, maximize, fullscreen, window_menu; } wm_capabilities; bool maximize_on_first_show; uint32_t pending_state; struct { int width, height; WaylandWindowState toplevel_states; uint32_t decoration_mode; } current, pending; } _GLFWwindowWayland; typedef enum _GLFWWaylandOfferType { EXPIRED, CLIPBOARD, DRAG_AND_DROP, PRIMARY_SELECTION }_GLFWWaylandOfferType ; typedef struct _GLFWWaylandDataOffer { void *id; _GLFWWaylandOfferType offer_type; size_t idx; bool is_self_offer; bool is_primary; const char *mime_for_drop; uint32_t source_actions; uint32_t dnd_action; struct wl_surface *surface; const char **mimes; size_t mimes_capacity, mimes_count; } _GLFWWaylandDataOffer; // Wayland-specific global data // typedef struct _GLFWlibraryWayland { struct wl_display* display; struct wl_registry* registry; struct wl_compositor* compositor; struct wl_subcompositor* subcompositor; struct wl_shm* shm; struct wl_seat* seat; struct wl_pointer* pointer; struct wl_keyboard* keyboard; struct wl_data_device_manager* dataDeviceManager; struct wl_data_device* dataDevice; struct xdg_wm_base* wmBase; int xdg_wm_base_version; struct zxdg_decoration_manager_v1* decorationManager; struct zwp_relative_pointer_manager_v1* relativePointerManager; struct zwp_pointer_constraints_v1* pointerConstraints; struct wl_data_source* dataSourceForClipboard; struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager; struct zwp_primary_selection_device_v1* primarySelectionDevice; struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection; struct xdg_activation_v1* xdg_activation_v1; struct xdg_toplevel_icon_manager_v1* xdg_toplevel_icon_manager_v1; struct wp_cursor_shape_manager_v1* wp_cursor_shape_manager_v1; struct wp_cursor_shape_device_v1* wp_cursor_shape_device_v1; struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1; struct wp_viewporter *wp_viewporter; struct org_kde_kwin_blur_manager *org_kde_kwin_blur_manager; struct zwlr_layer_shell_v1* zwlr_layer_shell_v1; uint32_t zwlr_layer_shell_v1_version; struct wp_single_pixel_buffer_manager_v1 *wp_single_pixel_buffer_manager_v1; struct zwp_idle_inhibit_manager_v1* idle_inhibit_manager; int compositorVersion; int seatVersion; struct wl_surface* cursorSurface; GLFWCursorShape cursorPreviousShape; uint32_t serial, input_serial, pointer_serial, pointer_enter_serial, keyboard_enter_serial; int32_t keyboardRepeatRate; monotonic_t keyboardRepeatDelay; struct { uint32_t key; id_type keyRepeatTimer; GLFWid keyboardFocusId; } keyRepeatInfo; id_type cursorAnimationTimer; _GLFWXKBData xkb; _GLFWDBUSData dbus; _GLFWwindow* pointerFocus; GLFWid keyboardFocusId; struct { void* handle; PFN_wl_cursor_theme_load theme_load; PFN_wl_cursor_theme_destroy theme_destroy; PFN_wl_cursor_theme_get_cursor theme_get_cursor; PFN_wl_cursor_image_get_buffer image_get_buffer; } cursor; struct { void* handle; PFN_wl_egl_window_create window_create; PFN_wl_egl_window_destroy window_destroy; PFN_wl_egl_window_resize window_resize; } egl; struct { glfw_wl_xdg_activation_request *array; size_t capacity, sz; } activation_requests; EventLoopData eventLoopData; size_t dataOffersCounter; _GLFWWaylandDataOffer dataOffers[8]; bool has_preferred_buffer_scale; char *compositor_name; } _GLFWlibraryWayland; // Wayland-specific per-monitor data // typedef struct _GLFWmonitorWayland { struct wl_output* output; uint32_t name; char friendly_name[64], description[64]; int currentMode; int x; int y; int scale; } _GLFWmonitorWayland; // Wayland-specific per-cursor data // typedef struct _GLFWcursorWayland { struct wl_cursor* cursor; struct wl_buffer* buffer; int width, height; int xhot, yhot; unsigned int currentImage; /** The scale of the cursor, or 0 if the cursor should be loaded late, or -1 if the cursor variable itself is unused. */ int scale; /** Cursor shape stored to allow late cursor loading in setCursorImage. */ GLFWCursorShape shape; } _GLFWcursorWayland; void _glfwAddOutputWayland(uint32_t name, uint32_t version); void _glfwWaylandBeforeBufferSwap(_GLFWwindow *window); void _glfwWaylandAfterBufferSwap(_GLFWwindow *window); void _glfwSetupWaylandDataDevice(void); void _glfwSetupWaylandPrimarySelectionDevice(void); double _glfwWaylandWindowScale(_GLFWwindow*); int _glfwWaylandIntegerWindowScale(_GLFWwindow*); void animateCursorImage(id_type timer_id, void *data); struct wl_cursor* _glfwLoadCursor(GLFWCursorShape, struct wl_cursor_theme*); void destroy_data_offer(_GLFWWaylandDataOffer*); const char* _glfwWaylandCompositorName(void); typedef struct wayland_cursor_shape { int which; const char *name; } wayland_cursor_shape; wayland_cursor_shape glfw_cursor_shape_to_wayland_cursor_shape(GLFWCursorShape g); kitty-0.41.1/glfw/wl_text_input.c0000664000175000017510000001672514773370543016332 0ustar nileshnilesh/* * wl_text_input.c * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "wl_text_input.h" #include "internal.h" #include "wayland-text-input-unstable-v3-client-protocol.h" #include #include #define debug debug_input static struct zwp_text_input_v3* text_input; static struct zwp_text_input_manager_v3* text_input_manager; static char *pending_pre_edit = NULL; static char *current_pre_edit = NULL; static char *pending_commit = NULL; static bool ime_focused = false; static int last_cursor_left = 0, last_cursor_top = 0, last_cursor_width = 0, last_cursor_height = 0; uint32_t commit_serial = 0; static void commit(void) { if (text_input) { zwp_text_input_v3_commit (text_input); commit_serial++; } } static void text_input_enter(void *data UNUSED, struct zwp_text_input_v3 *txt_input, struct wl_surface *surface UNUSED) { debug("text-input: enter event\n"); if (txt_input) { ime_focused = true; zwp_text_input_v3_enable(txt_input); zwp_text_input_v3_set_content_type(txt_input, ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL); commit(); } } static void text_input_leave(void *data UNUSED, struct zwp_text_input_v3 *txt_input, struct wl_surface *surface UNUSED) { debug("text-input: leave event\n"); if (txt_input) { ime_focused = false; zwp_text_input_v3_disable(txt_input); commit(); } } static void send_text(const char *text, GLFWIMEState ime_state) { _GLFWwindow *w = _glfwFocusedWindow(); if (w && w->callbacks.keyboard) { GLFWkeyevent fake_ev = {.action = text ? GLFW_PRESS : GLFW_RELEASE}; fake_ev.text = text; fake_ev.ime_state = ime_state; w->callbacks.keyboard((GLFWwindow*) w, &fake_ev); } } static void text_input_preedit_string( void *data UNUSED, struct zwp_text_input_v3 *txt_input UNUSED, const char *text, int32_t cursor_begin, int32_t cursor_end ) { debug("text-input: preedit_string event: text: %s cursor_begin: %d cursor_end: %d\n", text, cursor_begin, cursor_end); free(pending_pre_edit); pending_pre_edit = text ? _glfw_strdup(text) : NULL; } static void text_input_commit_string(void *data UNUSED, struct zwp_text_input_v3 *txt_input UNUSED, const char *text) { debug("text-input: commit_string event: text: %s\n", text); free(pending_commit); pending_commit = text ? _glfw_strdup(text) : NULL; } static void text_input_delete_surrounding_text( void *data UNUSED, struct zwp_text_input_v3 *txt_input UNUSED, uint32_t before_length, uint32_t after_length) { debug("text-input: delete_surrounding_text event: before_length: %u after_length: %u\n", before_length, after_length); } static void text_input_done(void *data UNUSED, struct zwp_text_input_v3 *txt_input UNUSED, uint32_t serial) { debug("text-input: done event: serial: %u current_commit_serial: %u\n", serial, commit_serial); const bool bad_event = serial != commit_serial; // See https://wayland.app/protocols/text-input-unstable-v3#zwp_text_input_v3:event:done // for handling of bad events. As best as I can tell spec says we perform all client side actions as usual // but send nothing back to the compositor, aka no cursor position update. // See https://github.com/kovidgoyal/kitty/pull/7283 for discussion if ((pending_pre_edit == NULL && current_pre_edit == NULL) || (pending_pre_edit && current_pre_edit && strcmp(pending_pre_edit, current_pre_edit) == 0)) { free(pending_pre_edit); pending_pre_edit = NULL; } else { free(current_pre_edit); current_pre_edit = pending_pre_edit; pending_pre_edit = NULL; if (current_pre_edit) { send_text(current_pre_edit, bad_event ? GLFW_IME_WAYLAND_DONE_EVENT : GLFW_IME_PREEDIT_CHANGED); } else { // Clear pre-edit text send_text(NULL, GLFW_IME_WAYLAND_DONE_EVENT); } } if (pending_commit) { send_text(pending_commit, GLFW_IME_COMMIT_TEXT); free(pending_commit); pending_commit = NULL; } } void _glfwWaylandBindTextInput(struct wl_registry* registry, uint32_t name) { if (!text_input_manager && _glfw.hints.init.wl.ime) text_input_manager = wl_registry_bind(registry, name, &zwp_text_input_manager_v3_interface, 1); } void _glfwWaylandInitTextInput(void) { static const struct zwp_text_input_v3_listener text_input_listener = { .enter = text_input_enter, .leave = text_input_leave, .preedit_string = text_input_preedit_string, .commit_string = text_input_commit_string, .delete_surrounding_text = text_input_delete_surrounding_text, .done = text_input_done, }; if (_glfw.hints.init.wl.ime && !text_input && text_input_manager && _glfw.wl.seat) { text_input = zwp_text_input_manager_v3_get_text_input(text_input_manager, _glfw.wl.seat); if (text_input) zwp_text_input_v3_add_listener(text_input, &text_input_listener, NULL); } } void _glfwWaylandDestroyTextInput(void) { if (text_input) zwp_text_input_v3_destroy(text_input); if (text_input_manager) zwp_text_input_manager_v3_destroy(text_input_manager); text_input = NULL; text_input_manager = NULL; free(pending_pre_edit); pending_pre_edit = NULL; free(current_pre_edit); current_pre_edit = NULL; free(pending_commit); pending_commit = NULL; } void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) { if (!text_input) return; switch(ev->type) { case GLFW_IME_UPDATE_FOCUS: debug("\ntext-input: updating IME focus state, ime_focused: %d ev->focused: %d\n", ime_focused, ev->focused); if (ime_focused) { zwp_text_input_v3_enable(text_input); zwp_text_input_v3_set_content_type(text_input, ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL); } else { free(pending_pre_edit); pending_pre_edit = NULL; if (current_pre_edit) { // Clear pre-edit text send_text(NULL, GLFW_IME_PREEDIT_CHANGED); free(current_pre_edit); current_pre_edit = NULL; } if (pending_commit) { free(pending_commit); pending_commit = NULL; } zwp_text_input_v3_disable(text_input); } commit(); break; case GLFW_IME_UPDATE_CURSOR_POSITION: { const double scale = _glfwWaylandWindowScale(w); #define s(x) (int)round((x) / scale) const int left = s(ev->cursor.left), top = s(ev->cursor.top), width = s(ev->cursor.width), height = s(ev->cursor.height); #undef s if (left != last_cursor_left || top != last_cursor_top || width != last_cursor_width || height != last_cursor_height) { last_cursor_left = left; last_cursor_top = top; last_cursor_width = width; last_cursor_height = height; debug("\ntext-input: updating cursor position: left=%d top=%d width=%d height=%d\n", left, top, width, height); zwp_text_input_v3_set_cursor_rectangle(text_input, left, top, width, height); commit(); } } break; } } kitty-0.41.1/glfw/wl_text_input.h0000664000175000017510000000047514773370543016332 0ustar nileshnilesh/* * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include void _glfwWaylandBindTextInput(struct wl_registry* registry, uint32_t name); void _glfwWaylandInitTextInput(void); void _glfwWaylandDestroyTextInput(void); kitty-0.41.1/glfw/wl_window.c0000664000175000017510000033651314773370543015436 0ustar nileshnilesh//======================================================================== // GLFW 3.4 Wayland - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2014 Jonas Ådahl // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #define _GNU_SOURCE #include "internal.h" #include "backend_utils.h" #include "linux_notify.h" #include "wl_client_side_decorations.h" #include "../kitty/monotonic.h" #include #include #include #include #include #include #define debug debug_rendering static GLFWLayerShellConfig layer_shell_config_for_next_window = {0}; static bool is_layer_shell(_GLFWwindow *window) { return window->wl.layer_shell.config.type != GLFW_LAYER_SHELL_NONE; } static void activation_token_done(void *data, struct xdg_activation_token_v1 *xdg_token, const char *token) { for (size_t i = 0; i < _glfw.wl.activation_requests.sz; i++) { glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + i; if (r->request_id == (uintptr_t)data) { _GLFWwindow *window = _glfwWindowForId(r->window_id); if (r->callback) r->callback((GLFWwindow*)window, token, r->callback_data); remove_i_from_array(_glfw.wl.activation_requests.array, i, _glfw.wl.activation_requests.sz); break; } } xdg_activation_token_v1_destroy(xdg_token); } static const struct xdg_activation_token_v1_listener activation_token_listener = { .done = &activation_token_done, }; static bool get_activation_token( _GLFWwindow *window, uint32_t serial, GLFWactivationcallback cb, void *cb_data ) { #define fail(msg) { _glfwInputError(GLFW_PLATFORM_ERROR, msg); if (cb) cb((GLFWwindow*)window, NULL, cb_data); return false; } if (_glfw.wl.xdg_activation_v1 == NULL) fail("Wayland: activation requests not supported by this Wayland compositor"); struct xdg_activation_token_v1 *token = xdg_activation_v1_get_activation_token(_glfw.wl.xdg_activation_v1); if (token == NULL) fail("Wayland: failed to create activation request token"); if (_glfw.wl.activation_requests.capacity < _glfw.wl.activation_requests.sz + 1) { _glfw.wl.activation_requests.capacity = MAX(64u, _glfw.wl.activation_requests.capacity * 2); _glfw.wl.activation_requests.array = realloc(_glfw.wl.activation_requests.array, _glfw.wl.activation_requests.capacity * sizeof(_glfw.wl.activation_requests.array[0])); if (!_glfw.wl.activation_requests.array) { _glfw.wl.activation_requests.capacity = 0; fail("Wayland: Out of memory while allocation activation request"); } } glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + _glfw.wl.activation_requests.sz++; memset(r, 0, sizeof(*r)); static uintptr_t rq = 0; r->window_id = window->id; r->callback = cb; r->callback_data = cb_data; r->request_id = ++rq; r->token = token; if (serial != 0) xdg_activation_token_v1_set_serial(token, serial, _glfw.wl.seat); xdg_activation_token_v1_set_surface(token, window->wl.surface); xdg_activation_token_v1_add_listener(token, &activation_token_listener, (void*)r->request_id); xdg_activation_token_v1_commit(token); return true; #undef fail } static void convert_glfw_image_to_wayland_image(const GLFWimage* image, unsigned char *target) { // convert RGBA non-premultiplied to ARGB pre-multiplied unsigned char* source = (unsigned char*) image->pixels; for (int i = 0; i < image->width * image->height; i++, source += 4) { unsigned int alpha = source[3]; *target++ = (unsigned char) ((source[2] * alpha) / 255); *target++ = (unsigned char) ((source[1] * alpha) / 255); *target++ = (unsigned char) ((source[0] * alpha) / 255); *target++ = (unsigned char) alpha; } } static struct wl_buffer* createShmBuffer(const GLFWimage* image, bool is_opaque, bool init_data) { struct wl_shm_pool* pool; struct wl_buffer* buffer; int stride = image->width * 4; int length = image->width * image->height * 4; void* data; int fd; fd = createAnonymousFile(length); if (fd < 0) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Creating a buffer file for %d B failed: %s", length, strerror(errno)); return NULL; } data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: mmap failed: %s", strerror(errno)); close(fd); return NULL; } pool = wl_shm_create_pool(_glfw.wl.shm, fd, length); close(fd); if (init_data) convert_glfw_image_to_wayland_image(image, data); buffer = wl_shm_pool_create_buffer(pool, 0, image->width, image->height, stride, is_opaque ? WL_SHM_FORMAT_XRGB8888 : WL_SHM_FORMAT_ARGB8888); munmap(data, length); wl_shm_pool_destroy(pool); return buffer; } wayland_cursor_shape glfw_cursor_shape_to_wayland_cursor_shape(GLFWCursorShape g) { wayland_cursor_shape ans = {-1, ""}; #define C(g, w) case g: ans.which = w; ans.name = #w; return ans; switch(g) { /* start glfw to wayland mapping (auto generated by gen-key-constants.py do not edit) */ C(GLFW_DEFAULT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); C(GLFW_TEXT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT); C(GLFW_POINTER_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER); C(GLFW_HELP_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP); C(GLFW_WAIT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT); C(GLFW_PROGRESS_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS); C(GLFW_CROSSHAIR_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR); C(GLFW_CELL_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CELL); C(GLFW_VERTICAL_TEXT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_VERTICAL_TEXT); C(GLFW_MOVE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE); C(GLFW_E_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE); C(GLFW_NE_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE); C(GLFW_NW_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE); C(GLFW_N_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE); C(GLFW_SE_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE); C(GLFW_SW_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE); C(GLFW_S_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE); C(GLFW_W_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE); C(GLFW_EW_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE); C(GLFW_NS_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE); C(GLFW_NESW_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE); C(GLFW_NWSE_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE); C(GLFW_ZOOM_IN_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN); C(GLFW_ZOOM_OUT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT); C(GLFW_ALIAS_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS); C(GLFW_COPY_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY); C(GLFW_NOT_ALLOWED_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED); C(GLFW_NO_DROP_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP); C(GLFW_GRAB_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB); C(GLFW_GRABBING_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING); /* end glfw to wayland mapping */ default: return ans; } #undef C } static void commit_window_surface(_GLFWwindow *window) { // debug("Window %llu surface committed\n", window->id); dont log as every frame request causes a surface commit wl_surface_commit(window->wl.surface); } static void commit_window_surface_if_safe(_GLFWwindow *window) { // we only commit if the buffer attached to the surface is the correct size, // which means that at least one frame is drawn after resizeFramebuffer() if (!window->wl.waiting_for_swap_to_commit) commit_window_surface(window); } static void set_cursor_surface(struct wl_surface *surface, int hotspot_x, int hotspot_y, const char *from_where) { debug("Calling wl_pointer_set_cursor in %s with surface: %p and serial: %u\n", from_where, (void*)surface, _glfw.wl.pointer_enter_serial); wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointer_enter_serial, surface, hotspot_x, hotspot_y); } static void setCursorImage(_GLFWwindow* window, bool on_theme_change) { _GLFWcursorWayland defaultCursor = {.shape = GLFW_DEFAULT_CURSOR}; _GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor; if (_glfw.wl.wp_cursor_shape_device_v1) { wayland_cursor_shape s = glfw_cursor_shape_to_wayland_cursor_shape(cursorWayland->shape); if (s.which > -1) { debug("Changing cursor shape to: %s with serial: %u\n", s.name, _glfw.wl.pointer_enter_serial); wp_cursor_shape_device_v1_set_shape(_glfw.wl.wp_cursor_shape_device_v1, _glfw.wl.pointer_enter_serial, (uint32_t)s.which); return; } } struct wl_cursor_image* image = NULL; struct wl_buffer* buffer = NULL; struct wl_surface* surface = _glfw.wl.cursorSurface; const int scale = _glfwWaylandIntegerWindowScale(window); if (!_glfw.wl.pointer) return; if (cursorWayland->scale < 0) { buffer = cursorWayland->buffer; toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0); } else { if (on_theme_change || cursorWayland->scale != scale) { struct wl_cursor *newCursor = NULL; struct wl_cursor_theme *theme = glfw_wlc_theme_for_scale(scale); if (theme) newCursor = _glfwLoadCursor(cursorWayland->shape, theme); if (newCursor != NULL) { cursorWayland->cursor = newCursor; cursorWayland->scale = scale; cursorWayland->currentImage = 0; } else { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: late cursor load failed; proceeding with existing cursor"); } } if (!cursorWayland->cursor || !cursorWayland->cursor->image_count || !cursorWayland->cursor->images) return; if (cursorWayland->currentImage >= cursorWayland->cursor->image_count) cursorWayland->currentImage = 0; image = cursorWayland->cursor->images[cursorWayland->currentImage]; if (!image) image = cursorWayland->cursor->images[0]; if (!image) return; buffer = wl_cursor_image_get_buffer(image); if (image->delay && window->cursor) { changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ms_to_monotonic_t(image->delay)); toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 1); } else { toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0); } if (!buffer) return; cursorWayland->width = image->width; cursorWayland->height = image->height; cursorWayland->xhot = image->hotspot_x; cursorWayland->yhot = image->hotspot_y; } set_cursor_surface(surface, cursorWayland->xhot / scale, cursorWayland->yhot / scale, "setCursorImage"); wl_surface_set_buffer_scale(surface, scale); wl_surface_attach(surface, buffer, 0, 0); wl_surface_damage(surface, 0, 0, cursorWayland->width, cursorWayland->height); wl_surface_commit(surface); } static bool checkScaleChange(_GLFWwindow* window) { if (window->wl.expect_scale_from_compositor) return false; unsigned int scale = 1, monitorScale; int i; // Check if we will be able to set the buffer scale or not. if (_glfw.wl.compositorVersion < 3) return false; // Get the scale factor from the highest scale monitor that this window is on for (i = 0; i < window->wl.monitorsCount; ++i) { monitorScale = window->wl.monitors[i]->wl.scale; if (scale < monitorScale) scale = monitorScale; } if (window->wl.monitorsCount < 1 && _glfw.monitorCount > 0) { // The window has not yet been assigned to any monitors, use the primary monitor _GLFWmonitor *m = _glfw.monitors[0]; if (m && m->wl.scale > (int)scale) scale = m->wl.scale; } // Only change the framebuffer size if the scale changed. if (scale != window->wl.integer_scale.deduced && !window->wl.fractional_scale) { window->wl.integer_scale.deduced = scale; setCursorImage(window, false); return true; } if (window->wl.monitorsCount > 0 && !window->wl.initial_scale_notified) { window->wl.initial_scale_notified = true; return true; } return false; } static void update_regions(_GLFWwindow* window) { if (!window->wl.transparent) { struct wl_region* region = wl_compositor_create_region(_glfw.wl.compositor); if (!region) return; wl_region_add(region, 0, 0, window->wl.width, window->wl.height); // Makes the surface considered as XRGB instead of ARGB. wl_surface_set_opaque_region(window->wl.surface, region); wl_region_destroy(region); } // Set blur region if (_glfw.wl.org_kde_kwin_blur_manager) { if (window->wl.has_blur) { if (!window->wl.org_kde_kwin_blur) window->wl.org_kde_kwin_blur = org_kde_kwin_blur_manager_create(_glfw.wl.org_kde_kwin_blur_manager, window->wl.surface); if (window->wl.org_kde_kwin_blur) { // NULL means entire window org_kde_kwin_blur_set_region(window->wl.org_kde_kwin_blur, NULL); org_kde_kwin_blur_commit(window->wl.org_kde_kwin_blur); } } else { org_kde_kwin_blur_manager_unset(_glfw.wl.org_kde_kwin_blur_manager, window->wl.surface); if (window->wl.org_kde_kwin_blur) { org_kde_kwin_blur_release(window->wl.org_kde_kwin_blur); window->wl.org_kde_kwin_blur = NULL; } } } } int _glfwWaylandIntegerWindowScale(_GLFWwindow *window) { int ans = (window->wl.integer_scale.preferred) ? window->wl.integer_scale.preferred : window->wl.integer_scale.deduced; if (ans < 1) ans = 1; return ans; } double _glfwWaylandWindowScale(_GLFWwindow *window) { double ans = _glfwWaylandIntegerWindowScale(window); if (window->wl.fractional_scale) ans = window->wl.fractional_scale / 120.; return ans; } static void wait_for_swap_to_commit(_GLFWwindow *window) { window->wl.waiting_for_swap_to_commit = true; debug("Waiting for swap to commit Wayland surface for window: %llu\n", window->id); } static void resizeFramebuffer(_GLFWwindow* window) { GLFWwindow *ctx = glfwGetCurrentContext(); bool ctx_changed = false; if (ctx != (GLFWwindow*)window && window->context.client != GLFW_NO_API) { ctx_changed = true; glfwMakeContextCurrent((GLFWwindow*)window); } double scale = _glfwWaylandWindowScale(window); int scaled_width = (int)round(window->wl.width * scale); int scaled_height = (int)round(window->wl.height * scale); debug("Resizing framebuffer of window: %llu to: %dx%d window size: %dx%d at scale: %.3f\n", window->id, scaled_width, scaled_height, window->wl.width, window->wl.height, scale); wl_egl_window_resize(window->wl.native, scaled_width, scaled_height, 0, 0); update_regions(window); wait_for_swap_to_commit(window); if (ctx_changed) glfwMakeContextCurrent(ctx); _glfwInputFramebufferSize(window, scaled_width, scaled_height); } void _glfwWaylandAfterBufferSwap(_GLFWwindow* window) { if (window->wl.temp_buffer_used_during_window_creation) { wl_buffer_destroy(window->wl.temp_buffer_used_during_window_creation); window->wl.temp_buffer_used_during_window_creation = NULL; } if (window->wl.waiting_for_swap_to_commit) { debug("Window %llu swapped committing surface\n", window->id); window->wl.waiting_for_swap_to_commit = false; // this is not really needed, since I think eglSwapBuffers() calls wl_surface_commit() // but lets be safe. See https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/egl/drivers/dri2/platform_wayland.c#L1510 commit_window_surface(window); } } static const char* clipboard_mime(void) { static char buf[128] = {0}; if (buf[0] == 0) { snprintf(buf, sizeof(buf), "application/glfw+clipboard-%d", getpid()); } return buf; } static void apply_scale_changes(_GLFWwindow *window, bool resize_framebuffer, bool update_csd) { double scale = _glfwWaylandWindowScale(window); if (resize_framebuffer) resizeFramebuffer(window); _glfwInputWindowContentScale(window, (float)scale, (float)scale); if (update_csd) csd_set_visible(window, true); // resize the csd iff the window currently has CSD int buffer_scale = window->wl.fractional_scale ? 1 : (int)scale; wl_surface_set_buffer_scale(window->wl.surface, buffer_scale); } static bool dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height) { bool size_changed = width != window->wl.width || height != window->wl.height; bool scale_changed = checkScaleChange(window); if (size_changed) { _glfwInputWindowSize(window, width, height); window->wl.width = width; window->wl.height = height; resizeFramebuffer(window); } if (scale_changed) { debug("Scale changed to %.3f in dispatchChangesAfterConfigure for window: %llu\n", _glfwWaylandWindowScale(window), window->id); apply_scale_changes(window, !size_changed, false); } _glfwInputWindowDamage(window); return size_changed || scale_changed; } static void inform_compositor_of_window_geometry(_GLFWwindow *window, const char *event) { #define geometry window->wl.decorations.geometry debug("Setting window %llu \"visible area\" geometry in %s event: x=%d y=%d %dx%d viewport: %dx%d\n", window->id, event, geometry.x, geometry.y, geometry.width, geometry.height, window->wl.width, window->wl.height); xdg_surface_set_window_geometry(window->wl.xdg.surface, geometry.x, geometry.y, geometry.width, geometry.height); if (window->wl.wp_viewport) wp_viewport_set_destination(window->wl.wp_viewport, window->wl.width, window->wl.height); #undef geometry } static void xdgDecorationHandleConfigure(void* data, struct zxdg_toplevel_decoration_v1* decoration UNUSED, uint32_t mode) { _GLFWwindow* window = data; window->wl.pending.decoration_mode = mode; window->wl.pending_state |= PENDING_STATE_DECORATION; debug("XDG decoration configure event received for window %llu: has_server_side_decorations: %d\n", window->id, (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE)); } static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener = { xdgDecorationHandleConfigure, }; static void surfaceHandleEnter(void *data, struct wl_surface *surface UNUSED, struct wl_output *output) { _GLFWwindow* window = data; _GLFWmonitor* monitor = wl_output_get_user_data(output); if (window->wl.monitorsCount + 1 > window->wl.monitorsSize) { ++window->wl.monitorsSize; window->wl.monitors = realloc(window->wl.monitors, window->wl.monitorsSize * sizeof(_GLFWmonitor*)); } window->wl.monitors[window->wl.monitorsCount++] = monitor; if (checkScaleChange(window)) { debug("Scale changed to %.3f for window %llu in surfaceHandleEnter\n", _glfwWaylandWindowScale(window), window->id); apply_scale_changes(window, true, true); } } static void surfaceHandleLeave(void *data, struct wl_surface *surface UNUSED, struct wl_output *output) { _GLFWwindow* window = data; _GLFWmonitor* monitor = wl_output_get_user_data(output); bool found; int i; for (i = 0, found = false; i < window->wl.monitorsCount - 1; ++i) { if (monitor == window->wl.monitors[i]) found = true; if (found) window->wl.monitors[i] = window->wl.monitors[i + 1]; } window->wl.monitors[--window->wl.monitorsCount] = NULL; if (checkScaleChange(window)) { debug("Scale changed to %.3f for window %llu in surfaceHandleLeave\n", _glfwWaylandWindowScale(window), window->id); apply_scale_changes(window, true, true); } } #ifdef WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION static void surface_preferred_buffer_scale(void *data, struct wl_surface *surface UNUSED, int32_t scale) { _GLFWwindow* window = data; window->wl.once.preferred_scale_received = true; if ((int)window->wl.integer_scale.preferred == scale && window->wl.window_fully_created) return; debug("Preferred integer buffer scale changed to: %d for window %llu\n", scale, window->id); window->wl.integer_scale.preferred = scale; window->wl.window_fully_created = window->wl.once.surface_configured; if (!window->wl.fractional_scale) apply_scale_changes(window, true, true); } static void surface_preferred_buffer_transform(void *data, struct wl_surface *surface, uint32_t transform) { (void)data; (void)surface; (void)transform; } #endif static const struct wl_surface_listener surfaceListener = { .enter = surfaceHandleEnter, .leave = surfaceHandleLeave, #ifdef WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION .preferred_buffer_scale = &surface_preferred_buffer_scale, .preferred_buffer_transform = &surface_preferred_buffer_transform, #endif }; static void fractional_scale_preferred_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1 UNUSED, uint32_t scale) { _GLFWwindow *window = data; window->wl.once.fractional_scale_received = true; if (scale == window->wl.fractional_scale && window->wl.window_fully_created) return; debug("Fractional scale requested: %u/120 = %.2f for window %llu\n", scale, scale / 120., window->id); window->wl.fractional_scale = scale; // niri and up-to-date mutter and up-to-date kwin all send the fractional // scale before configure (as of Jan 2025). sway as of 1.10 and Hyprland send it after configure. // https://github.com/hyprwm/Hyprland/issues/9126 // labwc doesnt support preferred buffer scale and seems to send only a // single fraction scale event before configure https://github.com/kovidgoyal/kitty/issues/7540 window->wl.window_fully_created = window->wl.once.surface_configured; apply_scale_changes(window, true, true); } static const struct wp_fractional_scale_v1_listener fractional_scale_listener = { .preferred_scale = &fractional_scale_preferred_scale, }; static bool createSurface(_GLFWwindow* window, const _GLFWwndconfig* wndconfig) { window->wl.surface = wl_compositor_create_surface(_glfw.wl.compositor); if (!window->wl.surface) return false; wl_surface_add_listener(window->wl.surface, &surfaceListener, window); wl_surface_set_user_data(window->wl.surface, window); // If we already have been notified of the primary monitor scale, assume // the window will be created on it and so avoid a rescale roundtrip in the common // case of the window being shown on the primary monitor or all monitors having the same scale. // If you change this also change get_window_content_scale() in the kitty code. GLFWmonitor* monitor = glfwGetPrimaryMonitor(); float xscale = 1.0, yscale = 1.0; int scale = 1; if (monitor) { glfwGetMonitorContentScale(monitor, &xscale, &yscale); // see wl_monitor.c xscale is always == yscale if (xscale <= 0.0001 || xscale != xscale || xscale >= 24) xscale = 1.0; if (xscale > 1) scale = (int)xscale; } window->wl.expect_scale_from_compositor = _glfw.wl.has_preferred_buffer_scale; if (_glfw.wl.wp_fractional_scale_manager_v1 && _glfw.wl.wp_viewporter) { window->wl.wp_fractional_scale_v1 = wp_fractional_scale_manager_v1_get_fractional_scale(_glfw.wl.wp_fractional_scale_manager_v1, window->wl.surface); if (window->wl.wp_fractional_scale_v1) { window->wl.wp_viewport = wp_viewporter_get_viewport(_glfw.wl.wp_viewporter, window->wl.surface); if (window->wl.wp_viewport) { wp_fractional_scale_v1_add_listener(window->wl.wp_fractional_scale_v1, &fractional_scale_listener, window); window->wl.expect_scale_from_compositor = true; } } } window->wl.window_fully_created = !window->wl.expect_scale_from_compositor; if (_glfw.wl.org_kde_kwin_blur_manager && wndconfig->blur_radius > 0) _glfwPlatformSetWindowBlur(window, wndconfig->blur_radius); window->wl.integer_scale.deduced = scale; if (_glfw.wl.has_preferred_buffer_scale) { scale = 1; window->wl.integer_scale.preferred = 1; } debug("Creating window %llu at size: %dx%d and scale %d\n", window->id, wndconfig->width, wndconfig->height, scale); window->wl.native = wl_egl_window_create(window->wl.surface, wndconfig->width * scale, wndconfig->height * scale); if (!window->wl.native) return false; window->wl.width = wndconfig->width; window->wl.height = wndconfig->height; window->wl.user_requested_content_size.width = wndconfig->width; window->wl.user_requested_content_size.height = wndconfig->height; update_regions(window); wl_surface_set_buffer_scale(window->wl.surface, scale); return true; } static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, bool on) { if (!window->wl.xdg.toplevel) return; if (!window->wl.wm_capabilities.fullscreen) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland compositor does not support fullscreen"); return; } if (on) xdg_toplevel_set_fullscreen(window->wl.xdg.toplevel, monitor ? monitor->wl.output : NULL); else xdg_toplevel_unset_fullscreen(window->wl.xdg.toplevel); } bool _glfwPlatformIsFullscreen(_GLFWwindow *window, unsigned int flags UNUSED) { return window->wl.current.toplevel_states & TOPLEVEL_STATE_FULLSCREEN; } bool _glfwPlatformToggleFullscreen(_GLFWwindow *window, unsigned int flags UNUSED) { bool already_fullscreen = _glfwPlatformIsFullscreen(window, flags); setFullscreen(window, NULL, !already_fullscreen); return !already_fullscreen; } static void report_live_resize(_GLFWwindow *w, bool started) { // disabled as mutter, for instance, does not send a configure event when the user stops resizing (aka releases the mouse button) if (false) _glfwInputLiveResize(w, started); } static void xdgToplevelHandleConfigure(void* data, struct xdg_toplevel* toplevel UNUSED, int32_t width, int32_t height, struct wl_array* states) { _GLFWwindow* window = data; float aspectRatio; float targetRatio; enum xdg_toplevel_state* state; uint32_t new_states = 0; debug("XDG top-level configure event for window %llu: size: %dx%d states: ", window->id, width, height); wl_array_for_each(state, states) { switch (*state) { #define C(x) case XDG_##x: new_states |= x; debug("%s ", #x); break C(TOPLEVEL_STATE_RESIZING); C(TOPLEVEL_STATE_MAXIMIZED); C(TOPLEVEL_STATE_FULLSCREEN); C(TOPLEVEL_STATE_ACTIVATED); C(TOPLEVEL_STATE_TILED_LEFT); C(TOPLEVEL_STATE_TILED_RIGHT); C(TOPLEVEL_STATE_TILED_TOP); C(TOPLEVEL_STATE_TILED_BOTTOM); #ifdef XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION C(TOPLEVEL_STATE_SUSPENDED); #endif #undef C } } debug("\n"); if (new_states & TOPLEVEL_STATE_RESIZING) { if (width) window->wl.user_requested_content_size.width = width; if (height) window->wl.user_requested_content_size.height = height; if (!(window->wl.current.toplevel_states & TOPLEVEL_STATE_RESIZING)) report_live_resize(window, true); } if (width != 0 && height != 0) { if (!(new_states & TOPLEVEL_STATE_DOCKED)) { if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE) { aspectRatio = (float)width / (float)height; targetRatio = (float)window->numer / (float)window->denom; if (aspectRatio < targetRatio) height = (int32_t)((float)width / targetRatio); else if (aspectRatio > targetRatio) width = (int32_t)((float)height * targetRatio); } } } window->wl.pending.toplevel_states = new_states; window->wl.pending.width = width; window->wl.pending.height = height; window->wl.pending_state |= PENDING_STATE_TOPLEVEL; } static void xdgToplevelHandleClose(void* data, struct xdg_toplevel* toplevel UNUSED) { _GLFWwindow* window = data; window->wl.window_fully_created = true; _glfwInputWindowCloseRequest(window); } #if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) static void xdg_toplevel_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel UNUSED, struct wl_array *caps) { _GLFWwindow *window = data; #define c (window->wl.wm_capabilities) memset(&c, 0, sizeof(c)); enum xdg_toplevel_wm_capabilities *cap; wl_array_for_each(cap, caps) { switch (*cap) { case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: c.maximize = true; break; case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: c.minimize = true; break; case XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU: c.window_menu = true; break; case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: c.fullscreen = true; break; } } debug("Compositor top-level capabilities: maximize=%d minimize=%d window_menu=%d fullscreen=%d\n", c.maximize, c.minimize, c.window_menu, c.fullscreen); #undef c } #endif static void xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel UNUSED, int32_t width, int32_t height) { _GLFWwindow *window = data; window->wl.xdg.top_level_bounds.width = width; window->wl.xdg.top_level_bounds.height = height; debug("Compositor set top-level bounds of: %dx%d for window %llu\n", width, height, window->id); } static const struct xdg_toplevel_listener xdgToplevelListener = { .configure = xdgToplevelHandleConfigure, .close = xdgToplevelHandleClose, #ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION .configure_bounds = xdg_toplevel_configure_bounds, .wm_capabilities = xdg_toplevel_wm_capabilities, #endif }; static void update_fully_created_on_configure(_GLFWwindow *window) { // See fractional_scale_preferred_scale() for logic if (!window->wl.window_fully_created) { window->wl.window_fully_created = window->wl.once.fractional_scale_received; if (window->wl.window_fully_created) debug("Marked window as fully created in configure event\n"); } } static void apply_xdg_configure_changes(_GLFWwindow *window) { bool suspended_changed = false; if (window->wl.pending_state & PENDING_STATE_TOPLEVEL) { uint32_t new_states = window->wl.pending.toplevel_states; int width = window->wl.pending.width; int height = window->wl.pending.height; if (!window->wl.once.surface_configured) { window->swaps_disallowed = false; wait_for_swap_to_commit(window); window->wl.once.surface_configured = true; update_fully_created_on_configure(window); } #ifdef XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION suspended_changed = ((new_states & TOPLEVEL_STATE_SUSPENDED) != (window->wl.current.toplevel_states & TOPLEVEL_STATE_SUSPENDED)); #endif if (new_states != window->wl.current.toplevel_states || width != window->wl.current.width || height != window->wl.current.height) { bool live_resize_done = !(new_states & TOPLEVEL_STATE_RESIZING) && (window->wl.current.toplevel_states & TOPLEVEL_STATE_RESIZING); window->wl.current.toplevel_states = new_states; window->wl.current.width = width; window->wl.current.height = height; _glfwInputWindowFocus(window, window->wl.current.toplevel_states & TOPLEVEL_STATE_ACTIVATED); if (live_resize_done) report_live_resize(window, false); } } if (window->wl.pending_state & PENDING_STATE_DECORATION) { uint32_t mode = window->wl.pending.decoration_mode; bool has_server_side_decorations = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); window->wl.decorations.serverSide = has_server_side_decorations; window->wl.current.decoration_mode = mode; } if (window->wl.pending_state) { int width = window->wl.pending.width, height = window->wl.pending.height; csd_set_window_geometry(window, &width, &height); bool resized = dispatchChangesAfterConfigure(window, width, height); csd_set_visible(window, !(window->wl.decorations.serverSide || window->monitor || window->wl.current.toplevel_states & TOPLEVEL_STATE_FULLSCREEN)); debug("Final window %llu content size: %dx%d resized: %d\n", window->id, width, height, resized); } inform_compositor_of_window_geometry(window, "configure"); commit_window_surface_if_safe(window); window->wl.pending_state = 0; #ifdef XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION if (suspended_changed) { _glfwInputWindowOcclusion(window, window->wl.current.toplevel_states & TOPLEVEL_STATE_SUSPENDED); } #endif } typedef union pixel { struct { uint8_t blue, green, red, alpha; }; uint32_t value; } pixel; static struct wl_buffer* create_single_color_buffer(int width, int height, pixel color) { // convert to pre-multiplied alpha as that's what wayland wants if (width == 1 && height == 1 && _glfw.wl.wp_single_pixel_buffer_manager_v1) { #define C(x) (uint32_t)(((double)((uint64_t)color.alpha * color.x * UINT32_MAX)) / (255 * 255)) struct wl_buffer *ans = wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer( _glfw.wl.wp_single_pixel_buffer_manager_v1, C(red), C(green), C(blue), (uint32_t)((color.alpha / 255.) * UINT32_MAX)); #undef C if (!ans) _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: failed to create single pixel buffer"); return ans; } float alpha = color.alpha / 255.f; color.red = (uint8_t)(alpha * color.red); color.green = (uint8_t)(alpha * color.green); color.blue = (uint8_t)(alpha * color.blue); int shm_format = color.alpha == 0xff ? WL_SHM_FORMAT_XRGB8888 : WL_SHM_FORMAT_ARGB8888; const size_t size = 4 * width * height; int fd = createAnonymousFile(size); if (fd < 0) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: failed to create anonymous file"); return NULL; } uint32_t *shm_data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (color.value) for (size_t i = 0; i < size/4; i++) shm_data[i] = color.value; else memset(shm_data, 0, size); if (!shm_data) { close(fd); _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: failed to mmap anonymous file"); return NULL; } struct wl_shm_pool *pool = wl_shm_create_pool(_glfw.wl.shm, fd, size); if (!pool) { close(fd); munmap(shm_data, size); _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: failed to create wl_shm_pool of size: %zu", size); return NULL; } struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height, width * 4, shm_format); wl_shm_pool_destroy(pool); munmap(shm_data, size); close(fd); if (!buffer) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: failed to create wl_buffer of size: %zu", size); return NULL; } return buffer; } static bool attach_temp_buffer_during_window_creation(_GLFWwindow *window) { pixel color; color.value = _glfw.hints.window.wl.bgcolor; if (!window->wl.transparent) color.alpha = 0xff; else if (color.alpha == 0) color.value = 0; // fully transparent blends best with black and we can use memset if (window->wl.temp_buffer_used_during_window_creation) { wl_buffer_destroy(window->wl.temp_buffer_used_during_window_creation); window->wl.temp_buffer_used_during_window_creation = NULL; } int width, height; _glfwPlatformGetFramebufferSize(window, &width, &height); if (window->wl.wp_viewport) { window->wl.temp_buffer_used_during_window_creation = create_single_color_buffer(1, 1, color); wl_surface_set_buffer_scale(window->wl.surface, 1); wp_viewport_set_destination(window->wl.wp_viewport, window->wl.width, window->wl.height); } else { window->wl.temp_buffer_used_during_window_creation = create_single_color_buffer(width, height, color); wl_surface_set_buffer_scale(window->wl.surface, window->wl.fractional_scale ? 1: _glfwWaylandIntegerWindowScale(window)); } if (!window->wl.temp_buffer_used_during_window_creation) return false; wl_surface_attach(window->wl.surface, window->wl.temp_buffer_used_during_window_creation, 0, 0); debug("Attached temp buffer during window %llu creation of size: %dx%d and rgba(%u, %u, %u, %u)\n", window->id, width, height, color.red, color.green, color.blue, color.alpha); commit_window_surface(window); return true; } static void loop_till_window_fully_created(_GLFWwindow *window) { if (!window->wl.window_fully_created) { GLFWwindow *ctx = glfwGetCurrentContext(); debug("Waiting for compositor to send fractional scale for window %llu\n", window->id); monotonic_t start = monotonic(); while (!window->wl.window_fully_created && monotonic() - start < ms_to_monotonic_t(300)) { if (wl_display_roundtrip(_glfw.wl.display) == -1) { window->wl.window_fully_created = true; } } window->wl.window_fully_created = true; // If other OS windows were resized when this window is shown, the ctx might have been changed by // user code, restore it to whatever it was at the start. if (glfwGetCurrentContext() != ctx) glfwMakeContextCurrent(ctx); } } static void xdgSurfaceHandleConfigure(void* data, struct xdg_surface* surface, uint32_t serial) { // The poorly documented pattern Wayland requires is: // 1) ack the configure, // 2) set the window geometry // 3) attach a new buffer of the correct size to the surface // 4) only then commit the surface. // buffer is attached only by eglSwapBuffers, // so we set a flag to not commit the surface till the next swapbuffers. Note that // wl_egl_window_resize() does not actually resize the buffer until the next draw call // or buffer state query. _GLFWwindow* window = data; xdg_surface_ack_configure(surface, serial); debug("XDG surface configure event received and acknowledged for window %llu\n", window->id); apply_xdg_configure_changes(window); if (!window->wl.window_fully_created) { if (!attach_temp_buffer_during_window_creation(window)) window->wl.window_fully_created = true; } } static const struct xdg_surface_listener xdgSurfaceListener = { xdgSurfaceHandleConfigure }; static void setXdgDecorations(_GLFWwindow* window) { if (window->wl.xdg.decoration) { window->wl.decorations.serverSide = true; zxdg_toplevel_decoration_v1_set_mode(window->wl.xdg.decoration, window->decorated ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); } else { window->wl.decorations.serverSide = false; csd_set_visible(window, window->decorated); } } void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled UNUSED) { setXdgDecorations(window); inform_compositor_of_window_geometry(window, "SetWindowDecorated"); commit_window_surface_if_safe(window); } static struct wl_output* find_output_by_name(const char* name) { if (!name || !name[0]) return NULL; for (int i = 0; i < _glfw.monitorCount; i++) { _GLFWmonitor *m = _glfw.monitors[i]; if (strcmp(m->wl.friendly_name, name) == 0) return m->wl.output; } return NULL; } static void layer_set_properties(_GLFWwindow *window) { enum zwlr_layer_surface_v1_anchor which_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; int exclusive_zone = window->wl.layer_shell.config.requested_exclusive_zone; enum zwlr_layer_surface_v1_keyboard_interactivity focus_policy = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; switch(window->wl.layer_shell.config.focus_policy) { case GLFW_FOCUS_NOT_ALLOWED: focus_policy = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; break; case GLFW_FOCUS_EXCLUSIVE: focus_policy = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE; break; case GLFW_FOCUS_ON_DEMAND: focus_policy = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND; break; } int panel_width = 0, panel_height = 0; switch (window->wl.layer_shell.config.type) { case GLFW_LAYER_SHELL_NONE: break; case GLFW_LAYER_SHELL_BACKGROUND: exclusive_zone = -1; break; case GLFW_LAYER_SHELL_TOP: case GLFW_LAYER_SHELL_OVERLAY: case GLFW_LAYER_SHELL_PANEL: switch (window->wl.layer_shell.config.edge) { case GLFW_EDGE_TOP: which_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; panel_height = window->wl.height; if (!window->wl.layer_shell.config.override_exclusive_zone) exclusive_zone = window->wl.height; break; case GLFW_EDGE_BOTTOM: which_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; panel_height = window->wl.height; if (!window->wl.layer_shell.config.override_exclusive_zone) exclusive_zone = window->wl.height; break; case GLFW_EDGE_LEFT: which_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; panel_width = window->wl.width; if (!window->wl.layer_shell.config.override_exclusive_zone) exclusive_zone = window->wl.width; break; case GLFW_EDGE_RIGHT: which_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; panel_width = window->wl.width; if (!window->wl.layer_shell.config.override_exclusive_zone) exclusive_zone = window->wl.width; break; case GLFW_EDGE_CENTER: which_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; break; case GLFW_EDGE_NONE: which_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; panel_width = window->wl.width; panel_height = window->wl.height; break; } } #define surface window->wl.layer_shell.zwlr_layer_surface_v1 zwlr_layer_surface_v1_set_size(surface, panel_width, panel_height); if (window->wl.wp_viewport) wp_viewport_set_destination(window->wl.wp_viewport, window->wl.width, window->wl.height); debug("Compositor will be informed that layer size: %dx%d viewport: %dx%d at next surface commit\n", panel_width, panel_height, window->wl.width, window->wl.height); zwlr_layer_surface_v1_set_anchor(surface, which_anchor); zwlr_layer_surface_v1_set_exclusive_zone(surface, exclusive_zone); zwlr_layer_surface_v1_set_margin(surface, window->wl.layer_shell.config.requested_top_margin, window->wl.layer_shell.config.requested_right_margin, window->wl.layer_shell.config.requested_bottom_margin, window->wl.layer_shell.config.requested_left_margin); zwlr_layer_surface_v1_set_keyboard_interactivity(surface, focus_policy); #undef surface } static void layer_surface_handle_configure(void* data, struct zwlr_layer_surface_v1* surface, uint32_t serial, uint32_t width, uint32_t height) { debug("Layer shell configure event: width: %u height: %u\n", width, height); _GLFWwindow* window = data; if (!window->wl.once.surface_configured) { window->swaps_disallowed = false; wait_for_swap_to_commit(window); window->wl.once.surface_configured = true; update_fully_created_on_configure(window); } GLFWvidmode m = {0}; if (window->wl.monitorsCount) _glfwPlatformGetVideoMode(window->wl.monitors[0], &m); window->wl.layer_shell.config.size_callback( (GLFWwindow*)window, &window->wl.layer_shell.config, m.width, m.height, &width, &height); zwlr_layer_surface_v1_ack_configure(surface, serial); if ((int)width != window->wl.width || (int)height != window->wl.height) { debug("Layer shell size changed to %ux%u in layer_surface_handle_configure\n", width, height); _glfwInputWindowSize(window, width, height); window->wl.width = width; window->wl.height = height; resizeFramebuffer(window); _glfwInputWindowDamage(window); layer_set_properties(window); } commit_window_surface_if_safe(window); if (!window->wl.window_fully_created) { if (!attach_temp_buffer_during_window_creation(window)) window->wl.window_fully_created = true; } } static void layer_surface_handle_close_requested(void* data, struct zwlr_layer_surface_v1* surface UNUSED) { _GLFWwindow* window = data; window->wl.window_fully_created = true; _glfwInputWindowCloseRequest(window); } static const struct zwlr_layer_surface_v1_listener zwlr_layer_surface_v1_listener = { .configure=layer_surface_handle_configure, .closed=layer_surface_handle_close_requested, }; static bool create_layer_shell_surface(_GLFWwindow *window) { if (!_glfw.wl.zwlr_layer_shell_v1) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: wlr-layer-shell protocol unsupported by compositor"); return false; } window->decorated = false; // shell windows must not have decorations struct wl_output *wl_output = find_output_by_name(window->wl.layer_shell.config.output_name); enum zwlr_layer_shell_v1_layer which_layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; // Default to background switch (window->wl.layer_shell.config.type) { case GLFW_LAYER_SHELL_BACKGROUND: break; case GLFW_LAYER_SHELL_NONE: break; case GLFW_LAYER_SHELL_PANEL: which_layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; break; case GLFW_LAYER_SHELL_TOP: which_layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP; break; case GLFW_LAYER_SHELL_OVERLAY: which_layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; break; } #define ls window->wl.layer_shell.zwlr_layer_surface_v1 ls = zwlr_layer_shell_v1_get_layer_surface( _glfw.wl.zwlr_layer_shell_v1, window->wl.surface, wl_output, which_layer, "kitty"); if (!ls) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: layer-surface creation failed"); return false; } zwlr_layer_surface_v1_add_listener(ls, &zwlr_layer_surface_v1_listener, window); layer_set_properties(window); commit_window_surface(window); wl_display_roundtrip(_glfw.wl.display); #undef ls return true; } static bool create_window_desktop_surface(_GLFWwindow* window) { if (is_layer_shell(window)) return create_layer_shell_surface(window); window->wl.xdg.surface = xdg_wm_base_get_xdg_surface(_glfw.wl.wmBase, window->wl.surface); if (!window->wl.xdg.surface) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: xdg-surface creation failed"); return false; } xdg_surface_add_listener(window->wl.xdg.surface, &xdgSurfaceListener, window); window->wl.xdg.toplevel = xdg_surface_get_toplevel(window->wl.xdg.surface); if (!window->wl.xdg.toplevel) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: xdg-toplevel creation failed"); return false; } #ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION if (_glfw.wl.xdg_wm_base_version < XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) { window->wl.wm_capabilities.maximize = true; window->wl.wm_capabilities.minimize = true; window->wl.wm_capabilities.fullscreen = true; window->wl.wm_capabilities.window_menu = true; } #endif xdg_toplevel_add_listener(window->wl.xdg.toplevel, &xdgToplevelListener, window); if (_glfw.wl.decorationManager) { window->wl.xdg.decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( _glfw.wl.decorationManager, window->wl.xdg.toplevel); zxdg_toplevel_decoration_v1_add_listener(window->wl.xdg.decoration, &xdgDecorationListener, window); } if (strlen(window->wl.appId)) xdg_toplevel_set_app_id(window->wl.xdg.toplevel, window->wl.appId); if (window->wl.title) xdg_toplevel_set_title(window->wl.xdg.toplevel, window->wl.title); if (window->minwidth != GLFW_DONT_CARE && window->minheight != GLFW_DONT_CARE) xdg_toplevel_set_min_size(window->wl.xdg.toplevel, window->minwidth, window->minheight); if (window->maxwidth != GLFW_DONT_CARE && window->maxheight != GLFW_DONT_CARE) xdg_toplevel_set_max_size(window->wl.xdg.toplevel, window->maxwidth, window->maxheight); if (window->monitor) { if (window->wl.wm_capabilities.fullscreen) xdg_toplevel_set_fullscreen(window->wl.xdg.toplevel, window->monitor->wl.output); else _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland compositor does not support fullscreen"); } else { if (window->wl.maximize_on_first_show) { window->wl.maximize_on_first_show = false; xdg_toplevel_set_maximized(window->wl.xdg.toplevel); } setXdgDecorations(window); } commit_window_surface(window); wl_display_roundtrip(_glfw.wl.display); return true; } static void incrementCursorImage(_GLFWwindow* window) { if (window && window->wl.decorations.focus == CENTRAL_WINDOW && window->cursorMode != GLFW_CURSOR_HIDDEN) { _GLFWcursor* cursor = window->wl.currentCursor; if (cursor && cursor->wl.cursor && cursor->wl.cursor->image_count) { cursor->wl.currentImage += 1; cursor->wl.currentImage %= cursor->wl.cursor->image_count; setCursorImage(window, false); toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, cursor->wl.cursor->image_count > 1); return; } } toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 1); } void animateCursorImage(id_type timer_id UNUSED, void *data UNUSED) { incrementCursorImage(_glfw.wl.pointerFocus); } static void abortOnFatalError(int last_error) { static bool abort_called = false; if (!abort_called) { abort_called = true; _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: fatal display error: %s", strerror(last_error)); if (_glfw.callbacks.application_close) _glfw.callbacks.application_close(1); else { _GLFWwindow* window = _glfw.windowListHead; while (window) { _glfwInputWindowCloseRequest(window); window = window->next; } } } // ensure the tick callback is called _glfw.wl.eventLoopData.wakeup_data_read = true; } static void wayland_read_events(int poll_result, int events, void *data UNUSED) { EVDBG("wayland_read_events poll_result: %d events: %d", poll_result, events); if (poll_result > 0 && events) wl_display_read_events(_glfw.wl.display); else wl_display_cancel_read(_glfw.wl.display); } static void handleEvents(monotonic_t timeout) { struct wl_display* display = _glfw.wl.display; errno = 0; EVDBG("starting handleEvents(%.2f)", monotonic_t_to_s_double(timeout)); while (wl_display_prepare_read(display) != 0) { if (wl_display_dispatch_pending(display) == -1) { abortOnFatalError(errno); return; } } // If an error different from EAGAIN happens, we have likely been // disconnected from the Wayland session, try to handle that the best we // can. errno = 0; if (wl_display_flush(display) < 0 && errno != EAGAIN) { wl_display_cancel_read(display); abortOnFatalError(errno); return; } // we pass in wayland_read_events to ensure that the above wl_display_prepare_read call // is followed by either wl_display_cancel_read or wl_display_read_events // before any events/timers are dispatched. This allows other wayland functions // to be called in the event/timer handlers without causing a deadlock bool display_read_ok = pollForEvents(&_glfw.wl.eventLoopData, timeout, wayland_read_events); EVDBG("display_read_ok: %d", display_read_ok); if (display_read_ok) { int num = wl_display_dispatch_pending(display); (void)num; EVDBG("dispatched %d Wayland events", num); } glfw_ibus_dispatch(&_glfw.wl.xkb.ibus); glfw_dbus_session_bus_dispatch(); EVDBG("other dispatch done"); if (_glfw.wl.eventLoopData.wakeup_fd_ready) check_for_wakeup_events(&_glfw.wl.eventLoopData); } static struct wl_cursor* try_cursor_names(struct wl_cursor_theme* theme, int arg_count, ...) { struct wl_cursor* ans = NULL; va_list ap; va_start(ap, arg_count); for (int i = 0; i < arg_count && !ans; i++) { const char *name = va_arg(ap, const char *); ans = wl_cursor_theme_get_cursor(theme, name); } va_end(ap); return ans; } struct wl_cursor* _glfwLoadCursor(GLFWCursorShape shape, struct wl_cursor_theme* theme) { static bool warnings[GLFW_INVALID_CURSOR] = {0}; if (!theme) return NULL; #define NUMARGS(...) (sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*)) #define C(name, ...) case name: { \ ans = try_cursor_names(theme, NUMARGS(__VA_ARGS__), __VA_ARGS__); \ if (!ans && !warnings[name]) {\ _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Could not find standard cursor: %s", #name); \ warnings[name] = true; \ } \ break; } struct wl_cursor* ans = NULL; switch (shape) { /* start glfw to xc mapping (auto generated by gen-key-constants.py do not edit) */ C(GLFW_DEFAULT_CURSOR, "default", "left_ptr"); C(GLFW_TEXT_CURSOR, "text", "xterm", "ibeam"); C(GLFW_POINTER_CURSOR, "pointing_hand", "pointer", "hand2", "hand"); C(GLFW_HELP_CURSOR, "help", "question_arrow", "whats_this"); C(GLFW_WAIT_CURSOR, "wait", "clock", "watch"); C(GLFW_PROGRESS_CURSOR, "progress", "half-busy", "left_ptr_watch"); C(GLFW_CROSSHAIR_CURSOR, "crosshair", "tcross"); C(GLFW_CELL_CURSOR, "cell", "plus", "cross"); C(GLFW_VERTICAL_TEXT_CURSOR, "vertical-text"); C(GLFW_MOVE_CURSOR, "move", "fleur", "pointer-move"); C(GLFW_E_RESIZE_CURSOR, "e-resize", "right_side"); C(GLFW_NE_RESIZE_CURSOR, "ne-resize", "top_right_corner"); C(GLFW_NW_RESIZE_CURSOR, "nw-resize", "top_left_corner"); C(GLFW_N_RESIZE_CURSOR, "n-resize", "top_side"); C(GLFW_SE_RESIZE_CURSOR, "se-resize", "bottom_right_corner"); C(GLFW_SW_RESIZE_CURSOR, "sw-resize", "bottom_left_corner"); C(GLFW_S_RESIZE_CURSOR, "s-resize", "bottom_side"); C(GLFW_W_RESIZE_CURSOR, "w-resize", "left_side"); C(GLFW_EW_RESIZE_CURSOR, "ew-resize", "sb_h_double_arrow", "split_h"); C(GLFW_NS_RESIZE_CURSOR, "ns-resize", "sb_v_double_arrow", "split_v"); C(GLFW_NESW_RESIZE_CURSOR, "nesw-resize", "size_bdiag", "size-bdiag"); C(GLFW_NWSE_RESIZE_CURSOR, "nwse-resize", "size_fdiag", "size-fdiag"); C(GLFW_ZOOM_IN_CURSOR, "zoom-in", "zoom_in"); C(GLFW_ZOOM_OUT_CURSOR, "zoom-out", "zoom_out"); C(GLFW_ALIAS_CURSOR, "dnd-link"); C(GLFW_COPY_CURSOR, "dnd-copy"); C(GLFW_NOT_ALLOWED_CURSOR, "not-allowed", "forbidden", "crossed_circle"); C(GLFW_NO_DROP_CURSOR, "no-drop", "dnd-no-drop"); C(GLFW_GRAB_CURSOR, "grab", "openhand", "hand1"); C(GLFW_GRABBING_CURSOR, "grabbing", "closedhand", "dnd-none"); /* end glfw to xc mapping */ case GLFW_INVALID_CURSOR: break; } return ans; #undef NUMARGS #undef C } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { window->wl.layer_shell.config = layer_shell_config_for_next_window; memset(&layer_shell_config_for_next_window, 0, sizeof(layer_shell_config_for_next_window)); csd_initialize_metrics(window); window->wl.transparent = fbconfig->transparent; strncpy(window->wl.appId, wndconfig->wl.appId, sizeof(window->wl.appId)); window->swaps_disallowed = true; if (!createSurface(window, wndconfig)) return false; if (wndconfig->title) window->wl.title = _glfw_strdup(wndconfig->title); if (wndconfig->maximized) window->wl.maximize_on_first_show = true; if (wndconfig->visible) { if (!create_window_desktop_surface(window)) return false; window->wl.visible = true; } else { window->wl.xdg.surface = NULL; window->wl.xdg.toplevel = NULL; window->wl.layer_shell.zwlr_layer_surface_v1 = NULL; window->wl.visible = false; } window->wl.currentCursor = NULL; // Don't set window->wl.cursorTheme to NULL here. window->wl.monitors = calloc(1, sizeof(_GLFWmonitor*)); window->wl.monitorsCount = 0; window->wl.monitorsSize = 1; // looping till window fully created attaches a single pixel buffer to the window, // this cannot be done once a OpenGL context is created for the window. So first loop // and only then create the OpenGL context. if (window->wl.visible) loop_till_window_fully_created(window); debug("Creating OpenGL context and attaching it to window\n"); if (ctxconfig->client != GLFW_NO_API) { if (ctxconfig->source == GLFW_EGL_CONTEXT_API || ctxconfig->source == GLFW_NATIVE_CONTEXT_API) { if (!_glfwInitEGL()) return false; if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) return false; } else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) { if (!_glfwInitOSMesa()) return false; if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) return false; } } return true; } void _glfwPlatformDestroyWindow(_GLFWwindow* window) { if (window == _glfw.wl.pointerFocus) { _glfw.wl.pointerFocus = NULL; _glfwInputCursorEnter(window, false); } if (window->id == _glfw.wl.keyboardFocusId) { _glfw.wl.keyboardFocusId = 0; _glfwInputWindowFocus(window, false); } if (window->id == _glfw.wl.keyRepeatInfo.keyboardFocusId) { _glfw.wl.keyRepeatInfo.keyboardFocusId = 0; } if (window->wl.temp_buffer_used_during_window_creation) wl_buffer_destroy(window->wl.temp_buffer_used_during_window_creation); if (window->wl.wp_fractional_scale_v1) wp_fractional_scale_v1_destroy(window->wl.wp_fractional_scale_v1); if (window->wl.wp_viewport) wp_viewport_destroy(window->wl.wp_viewport); if (window->wl.org_kde_kwin_blur) org_kde_kwin_blur_release(window->wl.org_kde_kwin_blur); if (window->context.destroy) window->context.destroy(window); csd_free_all_resources(window); if (window->wl.xdg.decoration) zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration); if (window->wl.native) wl_egl_window_destroy(window->wl.native); if (window->wl.xdg.toplevel) xdg_toplevel_destroy(window->wl.xdg.toplevel); if (window->wl.xdg.surface) xdg_surface_destroy(window->wl.xdg.surface); if (window->wl.surface) wl_surface_destroy(window->wl.surface); free(window->wl.title); free(window->wl.monitors); if (window->wl.frameCallbackData.current_wl_callback) wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback); } void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) { if (window->wl.title) { if (title && strcmp(title, window->wl.title) == 0) return; free(window->wl.title); } else if (!title) return; // Wayland cannot handle requests larger than ~8200 bytes. Sending // one causes an abort(). Since titles this large are meaningless anyway // ensure they do not happen. window->wl.title = utf_8_strndup(title, 2048); if (window->wl.xdg.toplevel) { xdg_toplevel_set_title(window->wl.xdg.toplevel, window->wl.title); csd_change_title(window); commit_window_surface_if_safe(window); } } void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const GLFWimage* images) { if (!_glfw.wl.xdg_toplevel_icon_manager_v1) { static bool warned_once = false; if (!warned_once) { _glfwInputError(GLFW_FEATURE_UNAVAILABLE, "Wayland: The compositor does not support changing window icons"); warned_once = true; } return; } if (!count) { xdg_toplevel_icon_manager_v1_set_icon(_glfw.wl.xdg_toplevel_icon_manager_v1, window->wl.xdg.toplevel, NULL); return; } struct wl_buffer* *buffers = malloc(sizeof(struct wl_buffer*) * count); if (!buffers) return; size_t total_data_size = 0; for (int i = 0; i < count; i++) total_data_size += images[i].width * images[i].height * 4; int fd = createAnonymousFile(total_data_size); if (fd < 0) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Creating a buffer file for %ld B failed: %s", (long)total_data_size, strerror(errno)); free(buffers); return; } unsigned char *data = mmap(NULL, total_data_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: mmap failed: %s", strerror(errno)); free(buffers); close(fd); return; } struct wl_shm_pool* pool = wl_shm_create_pool(_glfw.wl.shm, fd, total_data_size); struct xdg_toplevel_icon_v1 *icon = xdg_toplevel_icon_manager_v1_create_icon(_glfw.wl.xdg_toplevel_icon_manager_v1); size_t pos = 0; for (int i = 0; i < count; i++) { const size_t sz = images[i].width * images[i].height * 4; convert_glfw_image_to_wayland_image(images + i, data + pos); buffers[i] = wl_shm_pool_create_buffer( pool, pos, images[i].width, images[i].height, images[i].width * 4, WL_SHM_FORMAT_ARGB8888); xdg_toplevel_icon_v1_add_buffer(icon, buffers[i], 1); pos += sz; } xdg_toplevel_icon_manager_v1_set_icon(_glfw.wl.xdg_toplevel_icon_manager_v1, window->wl.xdg.toplevel, icon); xdg_toplevel_icon_v1_destroy(icon); for (int i = 0; i < count; i++) wl_buffer_destroy(buffers[i]); free(buffers); wl_shm_pool_destroy(pool); munmap(data, total_data_size); close(fd); } void _glfwPlatformGetWindowPos(_GLFWwindow* window UNUSED, int* xpos UNUSED, int* ypos UNUSED) { // A Wayland client is not aware of its position, so just warn and leave it // as (0, 0) static bool warned_once = false; if (!warned_once) { _glfwInputError(GLFW_FEATURE_UNAVAILABLE, "Wayland: The platform does not provide the window position"); warned_once = true; } } void _glfwPlatformSetWindowPos(_GLFWwindow* window UNUSED, int xpos UNUSED, int ypos UNUSED) { // A Wayland client can not set its position, so just warn _glfwInputError(GLFW_FEATURE_UNAVAILABLE, "Wayland: The platform does not support setting the window position"); } void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) { if (width) *width = window->wl.width; if (height) *height = window->wl.height; } void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) { if (width != window->wl.width || height != window->wl.height) { window->wl.user_requested_content_size.width = width; window->wl.user_requested_content_size.height = height; int32_t w = 0, h = 0; csd_set_window_geometry(window, &w, &h); window->wl.width = w; window->wl.height = h; resizeFramebuffer(window); csd_set_visible(window, true); // resizes the csd iff the window currently has csd commit_window_surface_if_safe(window); inform_compositor_of_window_geometry(window, "SetWindowSize"); } } void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, int minwidth, int minheight, int maxwidth, int maxheight) { if (window->wl.xdg.toplevel) { if (minwidth == GLFW_DONT_CARE || minheight == GLFW_DONT_CARE) minwidth = minheight = 0; if (maxwidth == GLFW_DONT_CARE || maxheight == GLFW_DONT_CARE) maxwidth = maxheight = 0; xdg_toplevel_set_min_size(window->wl.xdg.toplevel, minwidth, minheight); xdg_toplevel_set_max_size(window->wl.xdg.toplevel, maxwidth, maxheight); commit_window_surface_if_safe(window); } } void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window UNUSED, int numer UNUSED, int denom UNUSED) { // TODO: find out how to trigger a resize. // The actual limits are checked in the xdg_toplevel::configure handler. _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, "Wayland: Window aspect ratio not yet implemented"); } void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window UNUSED, int widthincr UNUSED, int heightincr UNUSED) { // TODO: find out how to trigger a resize. // The actual limits are checked in the xdg_toplevel::configure handler. } void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) { _glfwPlatformGetWindowSize(window, width, height); double fscale = _glfwWaylandWindowScale(window); if (width) *width = (int)round(*width * fscale); if (height) *height = (int)round(*height * fscale); } void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom) { if (window->decorated && !window->monitor && !window->wl.decorations.serverSide) { if (top) *top = window->wl.decorations.metrics.top - window->wl.decorations.metrics.visible_titlebar_height; if (left) *left = window->wl.decorations.metrics.width; if (right) *right = window->wl.decorations.metrics.width; if (bottom) *bottom = window->wl.decorations.metrics.width; } } void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, float* xscale, float* yscale) { float fscale = (float)_glfwWaylandWindowScale(window); if (xscale) *xscale = fscale; if (yscale) *yscale = fscale; } monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED) { return ms_to_monotonic_t(500ll); } void _glfwPlatformIconifyWindow(_GLFWwindow* window) { if (window->wl.xdg.toplevel) { if (window->wl.wm_capabilities.minimize) xdg_toplevel_set_minimized(window->wl.xdg.toplevel); else _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland compositor does not support minimizing windows"); } } void _glfwPlatformRestoreWindow(_GLFWwindow* window) { if (window->wl.xdg.toplevel) { if (window->monitor) xdg_toplevel_unset_fullscreen(window->wl.xdg.toplevel); if (window->wl.current.toplevel_states & TOPLEVEL_STATE_MAXIMIZED) xdg_toplevel_unset_maximized(window->wl.xdg.toplevel); // There is no way to unset minimized, or even to know if we are // minimized, so there is nothing to do in this case. } _glfwInputWindowMonitor(window, NULL); } void _glfwPlatformMaximizeWindow(_GLFWwindow* window) { if (window->wl.xdg.toplevel) { if (window->wl.wm_capabilities.maximize) xdg_toplevel_set_maximized(window->wl.xdg.toplevel); else _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland compositor does not support maximizing windows"); } } void _glfwPlatformShowWindow(_GLFWwindow* window) { if (!window->wl.visible) { create_window_desktop_surface(window); window->wl.visible = true; } } void _glfwPlatformHideWindow(_GLFWwindow* window) { if (window->wl.xdg.toplevel) { xdg_toplevel_destroy(window->wl.xdg.toplevel); xdg_surface_destroy(window->wl.xdg.surface); window->wl.xdg.toplevel = NULL; window->wl.xdg.surface = NULL; window->wl.once.surface_configured = false; window->swaps_disallowed = true; } window->wl.visible = false; } static void request_attention(GLFWwindow *window, const char *token, void *data UNUSED) { if (window && token && token[0] && _glfw.wl.xdg_activation_v1) xdg_activation_v1_activate(_glfw.wl.xdg_activation_v1, token, ((_GLFWwindow*)window)->wl.surface); } static bool has_activation_in_flight(_GLFWwindow* window, GLFWactivationcallback callback) { for (size_t i = 0; i < _glfw.wl.activation_requests.sz; i++) { glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + i; if (r->window_id == window->id && r->callback == callback) return true; } return false; } void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) { if (!has_activation_in_flight(window, request_attention)) get_activation_token(window, 0, request_attention, NULL); } int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED) { // TODO: Use an actual Wayland API to implement this when one becomes available return false; } static void focus_window(GLFWwindow *window, const char *token, void *data UNUSED) { if (!window) return; if (token && token[0] && _glfw.wl.xdg_activation_v1) xdg_activation_v1_activate(_glfw.wl.xdg_activation_v1, token, ((_GLFWwindow*)window)->wl.surface); else { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Window focus request via xdg-activation protocol was denied or is unsupported by the compositor. Use a better compositor."); } } void _glfwPlatformFocusWindow(_GLFWwindow* window UNUSED) { // Attempt to focus the window by using the activation protocol, whether it works // is entirely compositor dependent and as we all know Wayland and its ecosystem is // the product of morons. if (_glfw.wl.input_serial && !has_activation_in_flight(window, focus_window)) get_activation_token(window, _glfw.wl.input_serial, focus_window, NULL); } void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos UNUSED, int ypos UNUSED, int width UNUSED, int height UNUSED, int refreshRate UNUSED) { setFullscreen(window, monitor, monitor != NULL); _glfwInputWindowMonitor(window, monitor); } int _glfwPlatformWindowFocused(_GLFWwindow* window) { return _glfw.wl.keyboardFocusId == (window ? window->id : 0); } int _glfwPlatformWindowOccluded(_GLFWwindow* window UNUSED) { #ifdef XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION return (window->wl.current.toplevel_states & TOPLEVEL_STATE_SUSPENDED) != 0; #endif return false; } int _glfwPlatformWindowIconified(_GLFWwindow* window UNUSED) { // xdg-shell doesn’t give any way to request whether a surface is // iconified. return false; } int _glfwPlatformWindowVisible(_GLFWwindow* window) { return window->wl.visible; } int _glfwPlatformWindowMaximized(_GLFWwindow* window) { return window->wl.current.toplevel_states & TOPLEVEL_STATE_MAXIMIZED; } int _glfwPlatformWindowHovered(_GLFWwindow* window) { return window->wl.hovered; } int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) { return window->wl.transparent; } void _glfwPlatformSetWindowResizable(_GLFWwindow* window UNUSED, bool enabled UNUSED) { // TODO _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, "Wayland: Window attribute setting not implemented yet"); } void _glfwPlatformSetWindowFloating(_GLFWwindow* window UNUSED, bool enabled UNUSED) { // TODO _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, "Wayland: Window attribute setting not implemented yet"); } void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled) { if (enabled) { struct wl_region* region = wl_compositor_create_region(_glfw.wl.compositor); wl_surface_set_input_region(window->wl.surface, region); wl_region_destroy(region); } else wl_surface_set_input_region(window->wl.surface, 0); commit_window_surface_if_safe(window); } float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED) { return 1.f; } void _glfwPlatformSetWindowOpacity(_GLFWwindow* window UNUSED, float opacity UNUSED) { _glfwInputError(GLFW_FEATURE_UNAVAILABLE, "Wayland: The platform does not support setting the window opacity"); } void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window UNUSED, bool enabled UNUSED) { // This is handled in relativePointerHandleRelativeMotion } bool _glfwPlatformRawMouseMotionSupported(void) { return true; } void _glfwPlatformPollEvents(void) { wl_display_dispatch_pending(_glfw.wl.display); handleEvents(0); } void _glfwPlatformWaitEvents(void) { monotonic_t timeout = wl_display_dispatch_pending(_glfw.wl.display) > 0 ? 0 : -1; handleEvents(timeout); } void _glfwPlatformWaitEventsTimeout(monotonic_t timeout) { if (wl_display_dispatch_pending(_glfw.wl.display) > 0) timeout = 0; handleEvents(timeout); } void _glfwPlatformPostEmptyEvent(void) { wakeupEventLoop(&_glfw.wl.eventLoopData); } void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) { if (xpos) *xpos = window->wl.cursorPosX; if (ypos) *ypos = window->wl.cursorPosY; } static bool isPointerLocked(_GLFWwindow* window); void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) { if (isPointerLocked(window)) { zwp_locked_pointer_v1_set_cursor_position_hint( window->wl.pointerLock.lockedPointer, wl_fixed_from_double(x), wl_fixed_from_double(y)); commit_window_surface_if_safe(window); } } void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode UNUSED) { _glfwPlatformSetCursor(window, window->wl.currentCursor); } const char* _glfwPlatformGetNativeKeyName(int native_key) { return glfw_xkb_keysym_name(native_key); } int _glfwPlatformGetNativeKeyForKey(uint32_t key) { return glfw_xkb_sym_for_key(key); } int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot, int count UNUSED) { cursor->wl.buffer = createShmBuffer(image, false, true); if (!cursor->wl.buffer) return false; cursor->wl.width = image->width; cursor->wl.height = image->height; cursor->wl.xhot = xhot; cursor->wl.yhot = yhot; cursor->wl.scale = -1; cursor->wl.shape = GLFW_INVALID_CURSOR; return true; } int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape) { // Don't actually load the cursor at this point, // because there's not enough info to be properly HiDPI aware. cursor->wl.cursor = NULL; cursor->wl.currentImage = 0; cursor->wl.scale = 0; cursor->wl.shape = shape; return true; } void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) { // If it's a standard cursor we don't need to do anything here if (cursor->wl.cursor) return; if (cursor->wl.buffer) wl_buffer_destroy(cursor->wl.buffer); } static void relativePointerHandleRelativeMotion(void* data, struct zwp_relative_pointer_v1* pointer UNUSED, uint32_t timeHi UNUSED, uint32_t timeLo UNUSED, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dxUnaccel, wl_fixed_t dyUnaccel) { _GLFWwindow* window = data; double xpos = window->virtualCursorPosX; double ypos = window->virtualCursorPosY; if (window->cursorMode != GLFW_CURSOR_DISABLED) return; if (window->rawMouseMotion) { xpos += wl_fixed_to_double(dxUnaccel); ypos += wl_fixed_to_double(dyUnaccel); } else { xpos += wl_fixed_to_double(dx); ypos += wl_fixed_to_double(dy); } _glfwInputCursorPos(window, xpos, ypos); } static const struct zwp_relative_pointer_v1_listener relativePointerListener = { relativePointerHandleRelativeMotion }; static void lockedPointerHandleLocked(void* data UNUSED, struct zwp_locked_pointer_v1* lockedPointer UNUSED) { } static void unlockPointer(_GLFWwindow* window) { struct zwp_relative_pointer_v1* relativePointer = window->wl.pointerLock.relativePointer; struct zwp_locked_pointer_v1* lockedPointer = window->wl.pointerLock.lockedPointer; zwp_relative_pointer_v1_destroy(relativePointer); zwp_locked_pointer_v1_destroy(lockedPointer); window->wl.pointerLock.relativePointer = NULL; window->wl.pointerLock.lockedPointer = NULL; } static void lockPointer(_GLFWwindow* window UNUSED); static void lockedPointerHandleUnlocked(void* data UNUSED, struct zwp_locked_pointer_v1* lockedPointer UNUSED) { } static const struct zwp_locked_pointer_v1_listener lockedPointerListener = { lockedPointerHandleLocked, lockedPointerHandleUnlocked }; static void lockPointer(_GLFWwindow* window) { struct zwp_relative_pointer_v1* relativePointer; struct zwp_locked_pointer_v1* lockedPointer; if (!_glfw.wl.relativePointerManager) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: no relative pointer manager"); return; } relativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer( _glfw.wl.relativePointerManager, _glfw.wl.pointer); zwp_relative_pointer_v1_add_listener(relativePointer, &relativePointerListener, window); lockedPointer = zwp_pointer_constraints_v1_lock_pointer( _glfw.wl.pointerConstraints, window->wl.surface, _glfw.wl.pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); zwp_locked_pointer_v1_add_listener(lockedPointer, &lockedPointerListener, window); window->wl.pointerLock.relativePointer = relativePointer; window->wl.pointerLock.lockedPointer = lockedPointer; set_cursor_surface(NULL, 0, 0, "lockPointer"); } static bool isPointerLocked(_GLFWwindow* window) { return window->wl.pointerLock.lockedPointer != NULL; } void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) { if (!_glfw.wl.pointer) return; window->wl.currentCursor = cursor; // If we're not in the correct window just save the cursor // the next time the pointer enters the window the cursor will change if (window != _glfw.wl.pointerFocus || window->wl.decorations.focus != CENTRAL_WINDOW) return; // Unlock possible pointer lock if no longer disabled. if (window->cursorMode != GLFW_CURSOR_DISABLED && isPointerLocked(window)) unlockPointer(window); if (window->cursorMode == GLFW_CURSOR_NORMAL) { setCursorImage(window, false); } else if (window->cursorMode == GLFW_CURSOR_DISABLED) { if (!isPointerLocked(window)) lockPointer(window); } else if (window->cursorMode == GLFW_CURSOR_HIDDEN) { set_cursor_surface(NULL, 0, 0, "_glfwPlatformSetCursor"); } } static bool write_all(int fd, const char *data, size_t sz) { monotonic_t start = glfwGetTime(); size_t pos = 0; while (pos < sz && glfwGetTime() - start < s_to_monotonic_t(2ll)) { ssize_t ret = write(fd, data + pos, sz - pos); if (ret < 0) { if (errno == EAGAIN || errno == EINTR) continue; _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Could not copy writing to destination fd failed with error: %s", strerror(errno)); return false; } if (ret > 0) { start = glfwGetTime(); pos += ret; } } return pos >= sz; } static void send_clipboard_data(const _GLFWClipboardData *cd, const char *mime, int fd) { if (strcmp(mime, "text/plain;charset=utf-8") == 0 || strcmp(mime, "UTF8_STRING") == 0 || strcmp(mime, "TEXT") == 0 || strcmp(mime, "STRING") == 0) mime = "text/plain"; GLFWDataChunk chunk = cd->get_data(mime, NULL, cd->ctype); void *iter = chunk.iter; if (!iter) return; bool keep_going = true; while (keep_going) { chunk = cd->get_data(mime, iter, cd->ctype); if (!chunk.sz) break; if (!write_all(fd, chunk.data, chunk.sz)) keep_going = false; if (chunk.free) chunk.free((void*)chunk.free_data); } cd->get_data(NULL, iter, cd->ctype); } static void _glfwSendClipboardText(void *data UNUSED, struct wl_data_source *data_source UNUSED, const char *mime_type, int fd) { send_clipboard_data(&_glfw.clipboard, mime_type, fd); close(fd); } static void _glfwSendPrimarySelectionText(void *data UNUSED, struct zwp_primary_selection_source_v1 *primary_selection_source UNUSED, const char *mime_type, int fd) { send_clipboard_data(&_glfw.primary, mime_type, fd); close(fd); } static void read_offer(int data_pipe, GLFWclipboardwritedatafun write_data, void *object) { wl_display_flush(_glfw.wl.display); struct pollfd fds; fds.fd = data_pipe; fds.events = POLLIN; monotonic_t start = glfwGetTime(); #define bail(...) { \ _glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \ close(data_pipe); \ return; \ } char buf[8192]; while (glfwGetTime() - start < s_to_monotonic_t(2ll)) { int ret = poll(&fds, 1, 2000); if (ret == -1) { if (errno == EINTR) continue; bail("Wayland: Failed to poll clipboard data from pipe with error: %s", strerror(errno)); } if (!ret) { bail("Wayland: Failed to read clipboard data from pipe (timed out)"); } ret = read(data_pipe, buf, sizeof(buf)); if (ret == -1) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue; bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno)); } if (ret == 0) { close(data_pipe); return; } if (!write_data(object, buf, ret)) bail("Wayland: call to write_data() failed with data from data offer"); start = glfwGetTime(); } bail("Wayland: Failed to read clipboard data from pipe (timed out)"); #undef bail } typedef struct chunked_writer { char *buf; size_t sz, cap; } chunked_writer; static bool write_chunk(void *object, const char *data, size_t sz) { chunked_writer *cw = object; if (cw->cap < cw->sz + sz) { cw->cap = MAX(cw->cap * 2, cw->sz + 8*sz); cw->buf = realloc(cw->buf, cw->cap * sizeof(cw->buf[0])); } memcpy(cw->buf + cw->sz, data, sz); cw->sz += sz; return true; } static char* read_offer_string(int data_pipe, size_t *sz) { chunked_writer cw = {0}; read_offer(data_pipe, write_chunk, &cw); if (cw.buf) { *sz = cw.sz; return cw.buf; } *sz = 0; return NULL; } static void read_clipboard_data_offer(struct wl_data_offer *data_offer, const char *mime, GLFWclipboardwritedatafun write_data, void *object) { int pipefd[2]; if (pipe2(pipefd, O_CLOEXEC) != 0) return; wl_data_offer_receive(data_offer, mime, pipefd[1]); close(pipefd[1]); read_offer(pipefd[0], write_data, object); } static void read_primary_selection_offer(struct zwp_primary_selection_offer_v1 *primary_selection_offer, const char *mime, GLFWclipboardwritedatafun write_data, void *object) { int pipefd[2]; if (pipe2(pipefd, O_CLOEXEC) != 0) return; zwp_primary_selection_offer_v1_receive(primary_selection_offer, mime, pipefd[1]); close(pipefd[1]); read_offer(pipefd[0], write_data, object); } static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime, size_t *sz) { int pipefd[2]; if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL; wl_data_offer_receive(data_offer, mime, pipefd[1]); close(pipefd[1]); return read_offer_string(pipefd[0], sz); } static void data_source_canceled(void *data UNUSED, struct wl_data_source *wl_data_source) { if (_glfw.wl.dataSourceForClipboard == wl_data_source) { _glfw.wl.dataSourceForClipboard = NULL; _glfw_free_clipboard_data(&_glfw.clipboard); _glfwInputClipboardLost(GLFW_CLIPBOARD); } wl_data_source_destroy(wl_data_source); } static void primary_selection_source_canceled(void *data UNUSED, struct zwp_primary_selection_source_v1 *primary_selection_source) { if (_glfw.wl.dataSourceForPrimarySelection == primary_selection_source) { _glfw.wl.dataSourceForPrimarySelection = NULL; _glfw_free_clipboard_data(&_glfw.primary); _glfwInputClipboardLost(GLFW_PRIMARY_SELECTION); } zwp_primary_selection_source_v1_destroy(primary_selection_source); } // KWin aborts if we don't define these even though they are not used for copy/paste static void dummy_data_source_target(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, const char* mime_type UNUSED) { } static void dummy_data_source_action(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, uint dnd_action UNUSED) { } static const struct wl_data_source_listener data_source_listener = { .send = _glfwSendClipboardText, .cancelled = data_source_canceled, .target = dummy_data_source_target, .action = dummy_data_source_action, }; static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = { .send = _glfwSendPrimarySelectionText, .cancelled = primary_selection_source_canceled, }; void destroy_data_offer(_GLFWWaylandDataOffer *offer) { if (offer->id) { if (offer->is_primary) zwp_primary_selection_offer_v1_destroy(offer->id); else wl_data_offer_destroy(offer->id); } if (offer->mimes) { for (size_t i = 0; i < offer->mimes_count; i++) free((char*)offer->mimes[i]); free(offer->mimes); } memset(offer, 0, sizeof(_GLFWWaylandDataOffer)); } static void prune_unclaimed_data_offers(void) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) { destroy_data_offer(&_glfw.wl.dataOffers[i]); } } } static void mark_selection_offer(void *data UNUSED, struct wl_data_device *data_device UNUSED, struct wl_data_offer *data_offer) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].id == data_offer) { _glfw.wl.dataOffers[i].offer_type = CLIPBOARD; } else if (_glfw.wl.dataOffers[i].offer_type == CLIPBOARD) { _glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous selection offer } } prune_unclaimed_data_offers(); } static void mark_primary_selection_offer(void *data UNUSED, struct zwp_primary_selection_device_v1* primary_selection_device UNUSED, struct zwp_primary_selection_offer_v1 *primary_selection_offer) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].id == primary_selection_offer) { _glfw.wl.dataOffers[i].offer_type = PRIMARY_SELECTION; } else if (_glfw.wl.dataOffers[i].offer_type == PRIMARY_SELECTION) { _glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous selection offer } } prune_unclaimed_data_offers(); } static void set_offer_mimetype(_GLFWWaylandDataOffer* offer, const char* mime) { if (strcmp(mime, clipboard_mime()) == 0) { offer->is_self_offer = true; } if (!offer->mimes || offer->mimes_count >= offer->mimes_capacity - 1) { offer->mimes = realloc(offer->mimes, sizeof(char*) * (offer->mimes_capacity + 64)); if (offer->mimes) offer->mimes_capacity += 64; else return; } offer->mimes[offer->mimes_count++] = _glfw_strdup(mime); } static void handle_offer_mimetype(void *data UNUSED, struct wl_data_offer* id, const char *mime) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].id == id) { set_offer_mimetype(&_glfw.wl.dataOffers[i], mime); break; } } } static void handle_primary_selection_offer_mimetype(void *data UNUSED, struct zwp_primary_selection_offer_v1* id, const char *mime) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].id == id) { set_offer_mimetype((_GLFWWaylandDataOffer*)&_glfw.wl.dataOffers[i], mime); break; } } } static void data_offer_source_actions(void *data UNUSED, struct wl_data_offer* id, uint32_t actions) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].id == id) { _glfw.wl.dataOffers[i].source_actions = actions; break; } } } static void data_offer_action(void *data UNUSED, struct wl_data_offer* id, uint32_t action) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].id == id) { _glfw.wl.dataOffers[i].dnd_action = action; break; } } } static const struct wl_data_offer_listener data_offer_listener = { .offer = handle_offer_mimetype, .source_actions = data_offer_source_actions, .action = data_offer_action, }; static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = { .offer = handle_primary_selection_offer_mimetype, }; static size_t handle_data_offer_generic(void *id, bool is_primary) { size_t smallest_idx = SIZE_MAX, pos = 0; for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].idx && _glfw.wl.dataOffers[i].idx < smallest_idx) { smallest_idx = _glfw.wl.dataOffers[i].idx; pos = i; } if (_glfw.wl.dataOffers[i].id == NULL) { pos = i; goto end; } } if (_glfw.wl.dataOffers[pos].id) destroy_data_offer(&_glfw.wl.dataOffers[pos]); end: _glfw.wl.dataOffers[pos].id = id; _glfw.wl.dataOffers[pos].is_primary = is_primary; _glfw.wl.dataOffers[pos].idx = ++_glfw.wl.dataOffersCounter; return pos; } static void handle_data_offer(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, struct wl_data_offer *id) { handle_data_offer_generic(id, false); wl_data_offer_add_listener(id, &data_offer_listener, NULL); } static void handle_primary_selection_offer(void *data UNUSED, struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1 UNUSED, struct zwp_primary_selection_offer_v1 *id) { handle_data_offer_generic(id, true); zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, NULL); } static void drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t serial, struct wl_surface *surface, wl_fixed_t x UNUSED, wl_fixed_t y UNUSED, struct wl_data_offer *id) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { _GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i; if (d->id == id) { d->offer_type = DRAG_AND_DROP; d->surface = surface; _GLFWwindow* window = _glfw.windowListHead; int format_priority = 0; while (window) { if (window->wl.surface == surface) { for (size_t j = 0; j < d->mimes_count; j++) { int prio = _glfwInputDrop(window, d->mimes[j], NULL, 0); if (prio > format_priority) d->mime_for_drop = d->mimes[j]; } break; } window = window->next; } wl_data_offer_accept(id, serial, d->mime_for_drop); } else if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) { _glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous drag offer } } prune_unclaimed_data_offers(); } static void drag_leave(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) { destroy_data_offer(&_glfw.wl.dataOffers[i]); } } } static void drop(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP && _glfw.wl.dataOffers[i].mime_for_drop) { size_t sz = 0; char *d = read_data_offer(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime_for_drop, &sz); if (d) { // We dont do finish as this requires version 3 for wl_data_device_manager // which then requires more work with calling set_actions for drag and drop to function // wl_data_offer_finish(_glfw.wl.dataOffers[i].id); _GLFWwindow* window = _glfw.windowListHead; while (window) { if (window->wl.surface == _glfw.wl.dataOffers[i].surface) { _glfwInputDrop(window, _glfw.wl.dataOffers[i].mime_for_drop, d, sz); break; } window = window->next; } free(d); } destroy_data_offer(&_glfw.wl.dataOffers[i]); break; } } } static void motion(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t time UNUSED, wl_fixed_t x UNUSED, wl_fixed_t y UNUSED) { } static const struct wl_data_device_listener data_device_listener = { .data_offer = handle_data_offer, .selection = mark_selection_offer, .enter = drag_enter, .motion = motion, .drop = drop, .leave = drag_leave, }; static const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = { .data_offer = handle_primary_selection_offer, .selection = mark_primary_selection_offer, }; void _glfwSetupWaylandDataDevice(void) { _glfw.wl.dataDevice = wl_data_device_manager_get_data_device(_glfw.wl.dataDeviceManager, _glfw.wl.seat); if (_glfw.wl.dataDevice) wl_data_device_add_listener(_glfw.wl.dataDevice, &data_device_listener, NULL); } void _glfwSetupWaylandPrimarySelectionDevice(void) { _glfw.wl.primarySelectionDevice = zwp_primary_selection_device_manager_v1_get_device(_glfw.wl.primarySelectionDeviceManager, _glfw.wl.seat); if (_glfw.wl.primarySelectionDevice) zwp_primary_selection_device_v1_add_listener(_glfw.wl.primarySelectionDevice, &primary_selection_device_listener, NULL); } static bool _glfwEnsureDataDevice(void) { if (!_glfw.wl.dataDeviceManager) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Cannot use clipboard, data device manager is not ready"); return false; } if (!_glfw.wl.dataDevice) { if (!_glfw.wl.seat) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Cannot use clipboard, seat is not ready"); return false; } if (!_glfw.wl.dataDevice) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Cannot use clipboard, failed to create data device"); return false; } } return true; } typedef void(*add_offer_func)(void*, const char *mime); void _glfwPlatformSetClipboard(GLFWClipboardType t) { _GLFWClipboardData *cd = NULL; void *data_source; add_offer_func f; if (t == GLFW_CLIPBOARD) { if (!_glfwEnsureDataDevice()) return; cd = &_glfw.clipboard; f = (add_offer_func)wl_data_source_offer; if (_glfw.wl.dataSourceForClipboard) wl_data_source_destroy(_glfw.wl.dataSourceForClipboard); _glfw.wl.dataSourceForClipboard = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager); if (!_glfw.wl.dataSourceForClipboard) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Cannot copy failed to create data source"); return; } wl_data_source_add_listener(_glfw.wl.dataSourceForClipboard, &data_source_listener, NULL); data_source = _glfw.wl.dataSourceForClipboard; } else { if (!_glfw.wl.primarySelectionDevice) { static bool warned_about_primary_selection_device = false; if (!warned_about_primary_selection_device) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Cannot copy no primary selection device available"); warned_about_primary_selection_device = true; } return; } cd = &_glfw.primary; f = (add_offer_func)zwp_primary_selection_source_v1_offer; if (_glfw.wl.dataSourceForPrimarySelection) zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection); _glfw.wl.dataSourceForPrimarySelection = zwp_primary_selection_device_manager_v1_create_source(_glfw.wl.primarySelectionDeviceManager); if (!_glfw.wl.dataSourceForPrimarySelection) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Cannot copy failed to create primary selection source"); return; } zwp_primary_selection_source_v1_add_listener(_glfw.wl.dataSourceForPrimarySelection, &primary_selection_source_listener, NULL); data_source = _glfw.wl.dataSourceForPrimarySelection; } f(data_source, clipboard_mime()); for (size_t i = 0; i < cd->num_mime_types; i++) { if (strcmp(cd->mime_types[i], "text/plain") == 0) { f(data_source, "TEXT"); f(data_source, "STRING"); f(data_source, "UTF8_STRING"); f(data_source, "text/plain;charset=utf-8"); } f(data_source, cd->mime_types[i]); } if (t == GLFW_CLIPBOARD) { // According to some interpretations of the Wayland spec only the application that has keyboard focus can set the clipboard. // Hurray for the Wayland nanny state! // // However in wl-roots based compositors, using the serial from the keyboard enter event doesn't work. No clue what // the correct serial to use here is. Given this Wayland there probably isn't one. What a joke. // Bug report: https://github.com/kovidgoyal/kitty/issues/6890 // Ironically one of the contributors to wl_roots claims the keyboard enter serial is the correct one to use: // https://emersion.fr/blog/2020/wayland-clipboard-drag-and-drop/ // The Wayland spec itself says "serial number of the event that triggered this request" // https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_data_device // So who the fuck knows. Just use the latest received serial and ask anybody that uses Wayland // to get their head examined. wl_data_device_set_selection(_glfw.wl.dataDevice, _glfw.wl.dataSourceForClipboard, _glfw.wl.serial); } else { // According to the Wayland spec we can only set the primary selection in response to a pointer button event // Hurray for the Wayland nanny state! zwp_primary_selection_device_v1_set_selection( _glfw.wl.primarySelectionDevice, _glfw.wl.dataSourceForPrimarySelection, _glfw.wl.pointer_serial); } } static bool offer_has_mime(const _GLFWWaylandDataOffer *d, const char *mime) { for (unsigned i = 0; i < d->mimes_count; i++) { if (strcmp(d->mimes[i], mime) == 0) return true; } return false; } static const char* plain_text_mime_for_offer(const _GLFWWaylandDataOffer *d) { #define A(x) if (offer_has_mime(d, x)) return x; A("text/plain;charset=utf-8"); A("text/plain"); A("UTF8_STRING"); A("STRING"); A("TEXT"); #undef A return NULL; } void _glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) { _GLFWWaylandOfferType offer_type = clipboard_type == GLFW_PRIMARY_SELECTION ? PRIMARY_SELECTION : CLIPBOARD; for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { _GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i; if (d->id && d->offer_type == offer_type) { if (d->is_self_offer) { write_data(object, NULL, 1); return; } if (mime_type == NULL) { bool ok = true; for (size_t o = 0; o < d->mimes_count; o++) { const char *q = d->mimes[o]; if (strchr(d->mimes[0], '/')) { if (strcmp(q, clipboard_mime()) == 0) continue; if (strcmp(q, "text/plain;charset=utf-8") == 0) q = "text/plain"; } else { if (strcmp(q, "UTF8_STRING") == 0 || strcmp(q, "STRING") == 0 || strcmp(q, "TEXT") == 0) q = "text/plain"; } if (ok) ok = write_data(object, q, strlen(q)); } return; } if (strcmp(mime_type, "text/plain") == 0) { mime_type = plain_text_mime_for_offer(d); if (!mime_type) return; } if (d->is_primary) { read_primary_selection_offer(d->id, mime_type, write_data, object); } else { read_clipboard_data_offer(d->id, mime_type, write_data, object); } break; } } } EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs UNUSED) { if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_wayland) return EGL_PLATFORM_WAYLAND_EXT; else return 0; } EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void) { return _glfw.wl.display; } EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window) { return window->wl.native; } void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) { if (!_glfw.vk.KHR_surface || !_glfw.vk.KHR_wayland_surface) return; extensions[0] = "VK_KHR_surface"; extensions[1] = "VK_KHR_wayland_surface"; } int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhysicalDevice device, uint32_t queuefamily) { PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR = (PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR) vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceWaylandPresentationSupportKHR"); if (!vkGetPhysicalDeviceWaylandPresentationSupportKHR) { _glfwInputError(GLFW_API_UNAVAILABLE, "Wayland: Vulkan instance missing VK_KHR_wayland_surface extension"); return VK_NULL_HANDLE; } return vkGetPhysicalDeviceWaylandPresentationSupportKHR(device, queuefamily, _glfw.wl.display); } VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, _GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface) { VkResult err; VkWaylandSurfaceCreateInfoKHR sci; PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR; vkCreateWaylandSurfaceKHR = (PFN_vkCreateWaylandSurfaceKHR) vkGetInstanceProcAddr(instance, "vkCreateWaylandSurfaceKHR"); if (!vkCreateWaylandSurfaceKHR) { _glfwInputError(GLFW_API_UNAVAILABLE, "Wayland: Vulkan instance missing VK_KHR_wayland_surface extension"); return VK_ERROR_EXTENSION_NOT_PRESENT; } memset(&sci, 0, sizeof(sci)); sci.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR; sci.display = _glfw.wl.display; sci.surface = window->wl.surface; err = vkCreateWaylandSurfaceKHR(instance, &sci, allocator, surface); if (err) { _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Failed to create Vulkan surface: %s", _glfwGetVulkanResultString(err)); } return err; } static void frame_handle_redraw(void *data, struct wl_callback *callback, uint32_t time UNUSED) { _GLFWwindow* window = (_GLFWwindow*) data; if (callback == window->wl.frameCallbackData.current_wl_callback) { window->wl.frameCallbackData.callback(window->wl.frameCallbackData.id); window->wl.frameCallbackData.current_wl_callback = NULL; } wl_callback_destroy(callback); } void _glfwPlatformChangeCursorTheme(void) { glfw_wlc_destroy(); _GLFWwindow *w = _glfw.windowListHead; while (w) { setCursorImage(w, true); w = w->next; } } int _glfwPlatformSetWindowBlur(_GLFWwindow *window, int blur_radius) { if (!window->wl.transparent) return 0; bool has_blur = window->wl.has_blur; bool new_has_blur = blur_radius > 0; if (new_has_blur != has_blur) { window->wl.has_blur = new_has_blur; update_regions(window); } return has_blur ? 1 : 0; } ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI struct wl_display* glfwGetWaylandDisplay(void) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); return _glfw.wl.display; } GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(NULL); return window->wl.surface; } GLFWAPI void glfwWaylandActivateWindow(GLFWwindow* handle, const char *activation_token) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT(); if (activation_token && activation_token[0] && _glfw.wl.xdg_activation_v1) xdg_activation_v1_activate(_glfw.wl.xdg_activation_v1, activation_token, window->wl.surface); } GLFWAPI void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT(); get_activation_token(window, _glfw.wl.input_serial, cb, cb_data); } GLFWAPI int glfwGetNativeKeyForName(const char* keyName, bool caseSensitive) { return glfw_xkb_keysym_from_name(keyName, caseSensitive); } GLFWAPI void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, void(*callback)(unsigned long long id)) { _GLFWwindow* window = (_GLFWwindow*) handle; static const struct wl_callback_listener frame_listener = { .done = frame_handle_redraw }; if (window->wl.frameCallbackData.current_wl_callback) wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback); if (window->wl.waiting_for_swap_to_commit) { callback(id); window->wl.frameCallbackData.id = 0; window->wl.frameCallbackData.callback = NULL; window->wl.frameCallbackData.current_wl_callback = NULL; } else { window->wl.frameCallbackData.id = id; window->wl.frameCallbackData.callback = callback; window->wl.frameCallbackData.current_wl_callback = wl_surface_frame(window->wl.surface); if (window->wl.frameCallbackData.current_wl_callback) { wl_callback_add_listener(window->wl.frameCallbackData.current_wl_callback, &frame_listener, window); commit_window_surface_if_safe(window); } } } GLFWAPI unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data) { return glfw_dbus_send_user_notification(n, callback, data); } GLFWAPI void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) { glfw_dbus_set_user_notification_activated_handler(handler); } GLFWAPI bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color) { _GLFWwindow* window = (_GLFWwindow*) handle; if (!window->wl.decorations.serverSide) { csd_set_titlebar_color(window, color, use_system_color); return true; } return false; } GLFWAPI void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle) { _GLFWwindow* window = (_GLFWwindow*) handle; if (csd_change_title(window)) commit_window_surface_if_safe(window); } GLFWAPI void glfwWaylandSetupLayerShellForNextWindow(const GLFWLayerShellConfig *c) { layer_shell_config_for_next_window = *c; } GLFWAPI bool glfwWaylandIsWindowFullyCreated(GLFWwindow *handle) { return handle != NULL && ((_GLFWwindow*)handle)->wl.window_fully_created; } void _glfwPlatformInputColorScheme(GLFWColorScheme appearance UNUSED) { _GLFWwindow* window = _glfw.windowListHead; while (window) { glfwWaylandRedrawCSDWindowTitle((GLFWwindow*)window); window = window->next; } } kitty-0.41.1/glfw/wlr-layer-shell-unstable-v1.xml0000664000175000017510000004557614773370543021173 0ustar nileshnilesh Copyright © 2017 Drew DeVault Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and rendered with a defined z-depth respective to each other. They may also be anchored to the edges and corners of a screen and specify input handling semantics. This interface should be suitable for the implementation of many desktop shell components, and a broad number of other applications that interact with the desktop. Create a layer surface for an existing surface. This assigns the role of layer_surface, or raises a protocol error if another role is already assigned. Creating a layer surface from a wl_surface which has a buffer attached or committed is a client error, and any attempts by a client to attach or manipulate a buffer prior to the first layer_surface.configure call must also be treated as errors. After creating a layer_surface object and setting it up, the client must perform an initial commit without any buffer attached. The compositor will reply with a layer_surface.configure event. The client must acknowledge it and is then allowed to attach a buffer to map the surface. You may pass NULL for output to allow the compositor to decide which output to use. Generally this will be the one that the user most recently interacted with. Clients can specify a namespace that defines the purpose of the layer surface. These values indicate which layers a surface can be rendered in. They are ordered by z depth, bottom-most first. Traditional shell surfaces will typically be rendered between the bottom and top layers. Fullscreen shell surfaces are typically rendered at the top layer. Multiple surfaces can share a single layer, and ordering within a single layer is undefined. This request indicates that the client will not use the layer_shell object any more. Objects that have been created through this instance are not affected. An interface that may be implemented by a wl_surface, for surfaces that are designed to be rendered as a layer of a stacked desktop-like environment. Layer surface state (layer, size, anchor, exclusive zone, margin, interactivity) is double-buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. Attaching a null buffer to a layer surface unmaps it. Unmapping a layer_surface means that the surface cannot be shown by the compositor until it is explicitly mapped again. The layer_surface returns to the state it had right after layer_shell.get_layer_surface. The client can re-map the surface by performing a commit without any buffer attached, waiting for a configure event and handling it as usual. Sets the size of the surface in surface-local coordinates. The compositor will display the surface centered with respect to its anchors. If you pass 0 for either value, the compositor will assign it and inform you of the assignment in the configure event. You must set your anchor to opposite edges in the dimensions you omit; not doing so is a protocol error. Both values are 0 by default. Size is double-buffered, see wl_surface.commit. Requests that the compositor anchor the surface to the specified edges and corners. If two orthogonal edges are specified (e.g. 'top' and 'left'), then the anchor point will be the intersection of the edges (e.g. the top left corner of the output); otherwise the anchor point will be centered on that edge, or in the center if none is specified. Anchor is double-buffered, see wl_surface.commit. Requests that the compositor avoids occluding an area with other surfaces. The compositor's use of this information is implementation-dependent - do not assume that this region will not actually be occluded. A positive value is only meaningful if the surface is anchored to one edge or an edge and both perpendicular edges. If the surface is not anchored, anchored to only two perpendicular edges (a corner), anchored to only two parallel edges or anchored to all edges, a positive value will be treated the same as zero. A positive zone is the distance from the edge in surface-local coordinates to consider exclusive. Surfaces that do not wish to have an exclusive zone may instead specify how they should interact with surfaces that do. If set to zero, the surface indicates that it would like to be moved to avoid occluding surfaces with a positive exclusive zone. If set to -1, the surface indicates that it would not like to be moved to accommodate for other surfaces, and the compositor should extend it all the way to the edges it is anchored to. For example, a panel might set its exclusive zone to 10, so that maximized shell surfaces are not shown on top of it. A notification might set its exclusive zone to 0, so that it is moved to avoid occluding the panel, but shell surfaces are shown underneath it. A wallpaper or lock screen might set their exclusive zone to -1, so that they stretch below or over the panel. The default value is 0. Exclusive zone is double-buffered, see wl_surface.commit. Requests that the surface be placed some distance away from the anchor point on the output, in surface-local coordinates. Setting this value for edges you are not anchored to has no effect. The exclusive zone includes the margin. Margin is double-buffered, see wl_surface.commit. Types of keyboard interaction possible for layer shell surfaces. The rationale for this is twofold: (1) some applications are not interested in keyboard events and not allowing them to be focused can improve the desktop experience; (2) some applications will want to take exclusive keyboard focus. This value indicates that this surface is not interested in keyboard events and the compositor should never assign it the keyboard focus. This is the default value, set for newly created layer shell surfaces. This is useful for e.g. desktop widgets that display information or only have interaction with non-keyboard input devices. Request exclusive keyboard focus if this surface is above the shell surface layer. For the top and overlay layers, the seat will always give exclusive keyboard focus to the top-most layer which has keyboard interactivity set to exclusive. If this layer contains multiple surfaces with keyboard interactivity set to exclusive, the compositor determines the one receiving keyboard events in an implementation- defined manner. In this case, no guarantee is made when this surface will receive keyboard focus (if ever). For the bottom and background layers, the compositor is allowed to use normal focus semantics. This setting is mainly intended for applications that need to ensure they receive all keyboard events, such as a lock screen or a password prompt. This requests the compositor to allow this surface to be focused and unfocused by the user in an implementation-defined manner. The user should be able to unfocus this surface even regardless of the layer it is on. Typically, the compositor will want to use its normal mechanism to manage keyboard focus between layer shell surfaces with this setting and regular toplevels on the desktop layer (e.g. click to focus). Nevertheless, it is possible for a compositor to require a special interaction to focus or unfocus layer shell surfaces (e.g. requiring a click even if focus follows the mouse normally, or providing a keybinding to switch focus between layers). This setting is mainly intended for desktop shell components (e.g. panels) that allow keyboard interaction. Using this option can allow implementing a desktop shell that can be fully usable without the mouse. Set how keyboard events are delivered to this surface. By default, layer shell surfaces do not receive keyboard events; this request can be used to change this. This setting is inherited by child surfaces set by the get_popup request. Layer surfaces receive pointer, touch, and tablet events normally. If you do not want to receive them, set the input region on your surface to an empty region. Keyboard interactivity is double-buffered, see wl_surface.commit. This assigns an xdg_popup's parent to this layer_surface. This popup should have been created via xdg_surface::get_popup with the parent set to NULL, and this request must be invoked before committing the popup's initial state. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. When a configure event is received, if a client commits the surface in response to the configure event, then the client must make an ack_configure request sometime before the commit request, passing along the serial of the configure event. If the client receives multiple configure events before it can respond to one, it only has to ack the last configure event. A client is not required to commit immediately after sending an ack_configure request - it may even ack_configure several times before its next surface commit. A client may send multiple ack_configure requests before committing, but only the last request sent before a commit indicates which configure event the client really is responding to. This request destroys the layer surface. The configure event asks the client to resize its surface. Clients should arrange their surface for the new states, and then send an ack_configure request with the serial sent in this configure event at some point before committing the new surface. The client is free to dismiss all but the last configure event it received. The width and height arguments specify the size of the window in surface-local coordinates. The size is a hint, in the sense that the client is free to ignore it if it doesn't resize, pick a smaller size (to satisfy aspect ratio or resize in steps of NxM pixels). If the client picks a smaller size and is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the surface will be centered on this axis. If the width or height arguments are zero, it means the client should decide its own window dimension. The closed event is sent by the compositor when the surface will no longer be shown. The output may have been destroyed or the user may have asked for it to be removed. Further changes to the surface will be ignored. The client should destroy the resource after receiving this event, and create a new surface if they so choose. Change the layer that the surface is rendered on. Layer is double-buffered, see wl_surface.commit. Requests an edge for the exclusive zone to apply. The exclusive edge will be automatically deduced from anchor points when possible, but when the surface is anchored to a corner, it will be necessary to set it explicitly to disambiguate, as it is not possible to deduce which one of the two corner edges should be used. The edge must be one the surface is anchored to, otherwise the invalid_exclusive_edge protocol error will be raised. kitty-0.41.1/glfw/x11_init.c0000664000175000017510000006727314773370543015065 0ustar nileshnilesh//======================================================================== // GLFW 3.4 X11 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #define _GNU_SOURCE #include "internal.h" #include "backend_utils.h" #include "linux_desktop_settings.h" #include #include #include #include #include #include #include #include // Return the atom ID only if it is listed in the specified array // static Atom getAtomIfSupported(Atom* supportedAtoms, unsigned long atomCount, const char* atomName) { const Atom atom = XInternAtom(_glfw.x11.display, atomName, False); for (unsigned long i = 0; i < atomCount; i++) { if (supportedAtoms[i] == atom) return atom; } return None; } // Check whether the running window manager is EWMH-compliant // static void detectEWMH(void) { // First we read the _NET_SUPPORTING_WM_CHECK property on the root window Window* windowFromRoot = NULL; if (!_glfwGetWindowPropertyX11(_glfw.x11.root, _glfw.x11.NET_SUPPORTING_WM_CHECK, XA_WINDOW, (unsigned char**) &windowFromRoot)) { return; } _glfwGrabErrorHandlerX11(); // If it exists, it should be the XID of a top-level window // Then we look for the same property on that window Window* windowFromChild = NULL; if (!_glfwGetWindowPropertyX11(*windowFromRoot, _glfw.x11.NET_SUPPORTING_WM_CHECK, XA_WINDOW, (unsigned char**) &windowFromChild)) { XFree(windowFromRoot); return; } _glfwReleaseErrorHandlerX11(); // If the property exists, it should contain the XID of the window if (*windowFromRoot != *windowFromChild) { XFree(windowFromRoot); XFree(windowFromChild); return; } XFree(windowFromRoot); XFree(windowFromChild); // We are now fairly sure that an EWMH-compliant WM is currently running // We can now start querying the WM about what features it supports by // looking in the _NET_SUPPORTED property on the root window // It should contain a list of supported EWMH protocol and state atoms Atom* supportedAtoms = NULL; const unsigned long atomCount = _glfwGetWindowPropertyX11(_glfw.x11.root, _glfw.x11.NET_SUPPORTED, XA_ATOM, (unsigned char**) &supportedAtoms); if (!supportedAtoms) return; // See which of the atoms we support that are supported by the WM _glfw.x11.NET_WM_STATE = getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE"); _glfw.x11.NET_WM_STATE_ABOVE = getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_ABOVE"); _glfw.x11.NET_WM_STATE_FULLSCREEN = getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_FULLSCREEN"); _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT = getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_VERT"); _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ = getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_HORZ"); _glfw.x11.NET_WM_STATE_DEMANDS_ATTENTION = getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_DEMANDS_ATTENTION"); _glfw.x11.NET_WM_FULLSCREEN_MONITORS = getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_FULLSCREEN_MONITORS"); _glfw.x11.NET_WM_WINDOW_TYPE = getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE"); _glfw.x11.NET_WM_WINDOW_TYPE_NORMAL = getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL"); _glfw.x11.NET_WM_WINDOW_TYPE_DOCK = getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_DOCK"); _glfw.x11.NET_WORKAREA = getAtomIfSupported(supportedAtoms, atomCount, "_NET_WORKAREA"); _glfw.x11.NET_CURRENT_DESKTOP = getAtomIfSupported(supportedAtoms, atomCount, "_NET_CURRENT_DESKTOP"); _glfw.x11.NET_ACTIVE_WINDOW = getAtomIfSupported(supportedAtoms, atomCount, "_NET_ACTIVE_WINDOW"); _glfw.x11.NET_FRAME_EXTENTS = getAtomIfSupported(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS"); _glfw.x11.NET_REQUEST_FRAME_EXTENTS = getAtomIfSupported(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS"); _glfw.x11.NET_WM_STRUT_PARTIAL = getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STRUT_PARTIAL"); XFree(supportedAtoms); } // Look for and initialize supported X11 extensions // static bool initExtensions(void) { _glfw.x11.vidmode.handle = _glfw_dlopen("libXxf86vm.so.1"); if (_glfw.x11.vidmode.handle) { glfw_dlsym(_glfw.x11.vidmode.QueryExtension, _glfw.x11.vidmode.handle, "XF86VidModeQueryExtension"); glfw_dlsym(_glfw.x11.vidmode.GetGammaRamp, _glfw.x11.vidmode.handle, "XF86VidModeGetGammaRamp"); glfw_dlsym(_glfw.x11.vidmode.SetGammaRamp, _glfw.x11.vidmode.handle, "XF86VidModeSetGammaRamp"); glfw_dlsym(_glfw.x11.vidmode.GetGammaRampSize, _glfw.x11.vidmode.handle, "XF86VidModeGetGammaRampSize"); _glfw.x11.vidmode.available = XF86VidModeQueryExtension(_glfw.x11.display, &_glfw.x11.vidmode.eventBase, &_glfw.x11.vidmode.errorBase); } #if defined(__CYGWIN__) _glfw.x11.xi.handle = _glfw_dlopen("libXi-6.so"); #else _glfw.x11.xi.handle = _glfw_dlopen("libXi.so.6"); #endif if (_glfw.x11.xi.handle) { glfw_dlsym(_glfw.x11.xi.QueryVersion, _glfw.x11.xi.handle, "XIQueryVersion"); glfw_dlsym(_glfw.x11.xi.SelectEvents, _glfw.x11.xi.handle, "XISelectEvents"); if (XQueryExtension(_glfw.x11.display, "XInputExtension", &_glfw.x11.xi.majorOpcode, &_glfw.x11.xi.eventBase, &_glfw.x11.xi.errorBase)) { _glfw.x11.xi.major = 2; _glfw.x11.xi.minor = 0; if (XIQueryVersion(_glfw.x11.display, &_glfw.x11.xi.major, &_glfw.x11.xi.minor) == Success) { _glfw.x11.xi.available = true; } } } #if defined(__CYGWIN__) _glfw.x11.randr.handle = _glfw_dlopen("libXrandr-2.so"); #else _glfw.x11.randr.handle = _glfw_dlopen("libXrandr.so.2"); #endif if (_glfw.x11.randr.handle) { glfw_dlsym(_glfw.x11.randr.AllocGamma, _glfw.x11.randr.handle, "XRRAllocGamma"); glfw_dlsym(_glfw.x11.randr.FreeGamma, _glfw.x11.randr.handle, "XRRFreeGamma"); glfw_dlsym(_glfw.x11.randr.FreeCrtcInfo, _glfw.x11.randr.handle, "XRRFreeCrtcInfo"); glfw_dlsym(_glfw.x11.randr.FreeGamma, _glfw.x11.randr.handle, "XRRFreeGamma"); glfw_dlsym(_glfw.x11.randr.FreeOutputInfo, _glfw.x11.randr.handle, "XRRFreeOutputInfo"); glfw_dlsym(_glfw.x11.randr.FreeScreenResources, _glfw.x11.randr.handle, "XRRFreeScreenResources"); glfw_dlsym(_glfw.x11.randr.GetCrtcGamma, _glfw.x11.randr.handle, "XRRGetCrtcGamma"); glfw_dlsym(_glfw.x11.randr.GetCrtcGammaSize, _glfw.x11.randr.handle, "XRRGetCrtcGammaSize"); glfw_dlsym(_glfw.x11.randr.GetCrtcInfo, _glfw.x11.randr.handle, "XRRGetCrtcInfo"); glfw_dlsym(_glfw.x11.randr.GetOutputInfo, _glfw.x11.randr.handle, "XRRGetOutputInfo"); glfw_dlsym(_glfw.x11.randr.GetOutputPrimary, _glfw.x11.randr.handle, "XRRGetOutputPrimary"); glfw_dlsym(_glfw.x11.randr.GetScreenResourcesCurrent, _glfw.x11.randr.handle, "XRRGetScreenResourcesCurrent"); glfw_dlsym(_glfw.x11.randr.QueryExtension, _glfw.x11.randr.handle, "XRRQueryExtension"); glfw_dlsym(_glfw.x11.randr.QueryVersion, _glfw.x11.randr.handle, "XRRQueryVersion"); glfw_dlsym(_glfw.x11.randr.SelectInput, _glfw.x11.randr.handle, "XRRSelectInput"); glfw_dlsym(_glfw.x11.randr.SetCrtcConfig, _glfw.x11.randr.handle, "XRRSetCrtcConfig"); glfw_dlsym(_glfw.x11.randr.SetCrtcGamma, _glfw.x11.randr.handle, "XRRSetCrtcGamma"); glfw_dlsym(_glfw.x11.randr.UpdateConfiguration, _glfw.x11.randr.handle, "XRRUpdateConfiguration"); if (XRRQueryExtension(_glfw.x11.display, &_glfw.x11.randr.eventBase, &_glfw.x11.randr.errorBase)) { if (XRRQueryVersion(_glfw.x11.display, &_glfw.x11.randr.major, &_glfw.x11.randr.minor)) { // The GLFW RandR path requires at least version 1.3 if (_glfw.x11.randr.major > 1 || _glfw.x11.randr.minor >= 3) _glfw.x11.randr.available = true; } else { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to query RandR version"); } } } if (_glfw.x11.randr.available) { XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root); if (!sr->ncrtc || !XRRGetCrtcGammaSize(_glfw.x11.display, sr->crtcs[0])) { // This is likely an older Nvidia driver with broken gamma support // Flag it as useless and fall back to xf86vm gamma, if available _glfw.x11.randr.gammaBroken = true; } if (!sr->ncrtc) { // A system without CRTCs is likely a system with broken RandR // Disable the RandR monitor path and fall back to core functions _glfw.x11.randr.monitorBroken = true; } XRRFreeScreenResources(sr); } if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken) { XRRSelectInput(_glfw.x11.display, _glfw.x11.root, RROutputChangeNotifyMask); } #if defined(__CYGWIN__) _glfw.x11.xcursor.handle = _glfw_dlopen("libXcursor-1.so"); #else _glfw.x11.xcursor.handle = _glfw_dlopen("libXcursor.so.1"); #endif if (_glfw.x11.xcursor.handle) { glfw_dlsym(_glfw.x11.xcursor.ImageCreate, _glfw.x11.xcursor.handle, "XcursorImageCreate"); glfw_dlsym(_glfw.x11.xcursor.ImageDestroy, _glfw.x11.xcursor.handle, "XcursorImageDestroy"); glfw_dlsym(_glfw.x11.xcursor.ImageLoadCursor, _glfw.x11.xcursor.handle, "XcursorImageLoadCursor"); } #if defined(__CYGWIN__) _glfw.x11.xinerama.handle = _glfw_dlopen("libXinerama-1.so"); #else _glfw.x11.xinerama.handle = _glfw_dlopen("libXinerama.so.1"); #endif if (_glfw.x11.xinerama.handle) { glfw_dlsym(_glfw.x11.xinerama.IsActive, _glfw.x11.xinerama.handle, "XineramaIsActive"); glfw_dlsym(_glfw.x11.xinerama.QueryExtension, _glfw.x11.xinerama.handle, "XineramaQueryExtension"); glfw_dlsym(_glfw.x11.xinerama.QueryScreens, _glfw.x11.xinerama.handle, "XineramaQueryScreens"); if (XineramaQueryExtension(_glfw.x11.display, &_glfw.x11.xinerama.major, &_glfw.x11.xinerama.minor)) { if (XineramaIsActive(_glfw.x11.display)) _glfw.x11.xinerama.available = true; } } #if defined(__CYGWIN__) _glfw.x11.xrender.handle = _glfw_dlopen("libXrender-1.so"); #else _glfw.x11.xrender.handle = _glfw_dlopen("libXrender.so.1"); #endif if (_glfw.x11.xrender.handle) { glfw_dlsym(_glfw.x11.xrender.QueryExtension, _glfw.x11.xrender.handle, "XRenderQueryExtension"); glfw_dlsym(_glfw.x11.xrender.QueryVersion, _glfw.x11.xrender.handle, "XRenderQueryVersion"); glfw_dlsym(_glfw.x11.xrender.FindVisualFormat, _glfw.x11.xrender.handle, "XRenderFindVisualFormat"); if (XRenderQueryExtension(_glfw.x11.display, &_glfw.x11.xrender.errorBase, &_glfw.x11.xrender.eventBase)) { if (XRenderQueryVersion(_glfw.x11.display, &_glfw.x11.xrender.major, &_glfw.x11.xrender.minor)) { _glfw.x11.xrender.available = true; } } } #if defined(__CYGWIN__) _glfw.x11.xshape.handle = _glfw_dlopen("libXext-6.so"); #else _glfw.x11.xshape.handle = _glfw_dlopen("libXext.so.6"); #endif if (_glfw.x11.xshape.handle) { glfw_dlsym(_glfw.x11.xshape.QueryExtension, _glfw.x11.xshape.handle, "XShapeQueryExtension"); glfw_dlsym(_glfw.x11.xshape.ShapeCombineRegion, _glfw.x11.xshape.handle, "XShapeCombineRegion"); glfw_dlsym(_glfw.x11.xshape.QueryVersion, _glfw.x11.xshape.handle, "XShapeQueryVersion"); if (XShapeQueryExtension(_glfw.x11.display, &_glfw.x11.xshape.errorBase, &_glfw.x11.xshape.eventBase)) { if (XShapeQueryVersion(_glfw.x11.display, &_glfw.x11.xshape.major, &_glfw.x11.xshape.minor)) { _glfw.x11.xshape.available = true; } } } _glfw.x11.xkb.major = 1; _glfw.x11.xkb.minor = 0; _glfw.x11.xkb.available = XkbQueryExtension(_glfw.x11.display, &_glfw.x11.xkb.majorOpcode, &_glfw.x11.xkb.eventBase, &_glfw.x11.xkb.errorBase, &_glfw.x11.xkb.major, &_glfw.x11.xkb.minor); if (!_glfw.x11.xkb.available) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to load Xkb extension"); return false; } Bool supported; if (XkbSetDetectableAutoRepeat(_glfw.x11.display, True, &supported)) { if (supported) _glfw.x11.xkb.detectable = true; } if (!glfw_xkb_set_x11_events_mask()) return false; if (!glfw_xkb_create_context(&_glfw.x11.xkb)) return false; if (!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return false; if (!glfw_xkb_compile_keymap(&_glfw.x11.xkb, NULL)) return false; // String format atoms _glfw.x11.NULL_ = XInternAtom(_glfw.x11.display, "NULL", False); _glfw.x11.UTF8_STRING = XInternAtom(_glfw.x11.display, "UTF8_STRING", False); _glfw.x11.ATOM_PAIR = XInternAtom(_glfw.x11.display, "ATOM_PAIR", False); // Custom selection property atom _glfw.x11.GLFW_SELECTION = XInternAtom(_glfw.x11.display, "GLFW_SELECTION", False); // ICCCM standard clipboard atoms _glfw.x11.TARGETS = XInternAtom(_glfw.x11.display, "TARGETS", False); _glfw.x11.MULTIPLE = XInternAtom(_glfw.x11.display, "MULTIPLE", False); _glfw.x11.PRIMARY = XInternAtom(_glfw.x11.display, "PRIMARY", False); _glfw.x11.INCR = XInternAtom(_glfw.x11.display, "INCR", False); _glfw.x11.CLIPBOARD = XInternAtom(_glfw.x11.display, "CLIPBOARD", False); // Clipboard manager atoms _glfw.x11.CLIPBOARD_MANAGER = XInternAtom(_glfw.x11.display, "CLIPBOARD_MANAGER", False); _glfw.x11.SAVE_TARGETS = XInternAtom(_glfw.x11.display, "SAVE_TARGETS", False); // Xdnd (drag and drop) atoms _glfw.x11.XdndAware = XInternAtom(_glfw.x11.display, "XdndAware", False); _glfw.x11.XdndEnter = XInternAtom(_glfw.x11.display, "XdndEnter", False); _glfw.x11.XdndPosition = XInternAtom(_glfw.x11.display, "XdndPosition", False); _glfw.x11.XdndStatus = XInternAtom(_glfw.x11.display, "XdndStatus", False); _glfw.x11.XdndActionCopy = XInternAtom(_glfw.x11.display, "XdndActionCopy", False); _glfw.x11.XdndDrop = XInternAtom(_glfw.x11.display, "XdndDrop", False); _glfw.x11.XdndFinished = XInternAtom(_glfw.x11.display, "XdndFinished", False); _glfw.x11.XdndSelection = XInternAtom(_glfw.x11.display, "XdndSelection", False); _glfw.x11.XdndTypeList = XInternAtom(_glfw.x11.display, "XdndTypeList", False); // ICCCM, EWMH and Motif window property atoms // These can be set safely even without WM support // The EWMH atoms that require WM support are handled in detectEWMH _glfw.x11.WM_PROTOCOLS = XInternAtom(_glfw.x11.display, "WM_PROTOCOLS", False); _glfw.x11.WM_STATE = XInternAtom(_glfw.x11.display, "WM_STATE", False); _glfw.x11.WM_DELETE_WINDOW = XInternAtom(_glfw.x11.display, "WM_DELETE_WINDOW", False); _glfw.x11.NET_SUPPORTED = XInternAtom(_glfw.x11.display, "_NET_SUPPORTED", False); _glfw.x11.NET_SUPPORTING_WM_CHECK = XInternAtom(_glfw.x11.display, "_NET_SUPPORTING_WM_CHECK", False); _glfw.x11.NET_WM_ICON = XInternAtom(_glfw.x11.display, "_NET_WM_ICON", False); _glfw.x11.NET_WM_PING = XInternAtom(_glfw.x11.display, "_NET_WM_PING", False); _glfw.x11.NET_WM_PID = XInternAtom(_glfw.x11.display, "_NET_WM_PID", False); _glfw.x11.NET_WM_NAME = XInternAtom(_glfw.x11.display, "_NET_WM_NAME", False); _glfw.x11.NET_WM_ICON_NAME = XInternAtom(_glfw.x11.display, "_NET_WM_ICON_NAME", False); _glfw.x11.NET_WM_BYPASS_COMPOSITOR = XInternAtom(_glfw.x11.display, "_NET_WM_BYPASS_COMPOSITOR", False); _glfw.x11.NET_WM_WINDOW_OPACITY = XInternAtom(_glfw.x11.display, "_NET_WM_WINDOW_OPACITY", False); _glfw.x11.MOTIF_WM_HINTS = XInternAtom(_glfw.x11.display, "_MOTIF_WM_HINTS", False); // The compositing manager selection name contains the screen number { char name[32]; snprintf(name, sizeof(name), "_NET_WM_CM_S%u", _glfw.x11.screen); _glfw.x11.NET_WM_CM_Sx = XInternAtom(_glfw.x11.display, name, False); } // Detect whether an EWMH-conformant window manager is running detectEWMH(); return true; } // Retrieve system content scale via folklore heuristics // void _glfwGetSystemContentScaleX11(float* xscale, float* yscale, bool bypass_cache) { // Start by assuming the default X11 DPI // NOTE: Some desktop environments (KDE) may remove the Xft.dpi field when it // would be set to 96, so assume that is the case if we cannot find it float xdpi = 96.f, ydpi = 96.f; // NOTE: Basing the scale on Xft.dpi where available should provide the most // consistent user experience (matches Qt, Gtk, etc), although not // always the most accurate one char* rms = NULL; char* owned_rms = NULL; if (bypass_cache) { _glfwGetWindowPropertyX11(_glfw.x11.root, _glfw.x11.RESOURCE_MANAGER, XA_STRING, (unsigned char**) &owned_rms); rms = owned_rms; } else { rms = XResourceManagerString(_glfw.x11.display); } if (rms) { XrmDatabase db = XrmGetStringDatabase(rms); if (db) { XrmValue value; char* type = NULL; if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value)) { if (type && strcmp(type, "String") == 0) xdpi = ydpi = (float)atof(value.addr); } XrmDestroyDatabase(db); } XFree(owned_rms); } *xscale = xdpi / 96.f; *yscale = ydpi / 96.f; } // Create a blank cursor for hidden and disabled cursor modes // static Cursor createHiddenCursor(void) { unsigned char pixels[16 * 16 * 4] = { 0 }; GLFWimage image = { 16, 16, pixels }; return _glfwCreateCursorX11(&image, 0, 0); } // Create a helper window for IPC // static Window createHelperWindow(void) { XSetWindowAttributes wa; wa.event_mask = PropertyChangeMask; return XCreateWindow(_glfw.x11.display, _glfw.x11.root, 0, 0, 1, 1, 0, 0, InputOnly, DefaultVisual(_glfw.x11.display, _glfw.x11.screen), CWEventMask, &wa); } // X error handler // static int errorHandler(Display *display, XErrorEvent* event) { if (_glfw.x11.display != display) return 0; _glfw.x11.errorCode = event->error_code; return 0; } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// // Sets the X error handler callback // void _glfwGrabErrorHandlerX11(void) { _glfw.x11.errorCode = Success; XSetErrorHandler(errorHandler); } // Clears the X error handler callback // void _glfwReleaseErrorHandlerX11(void) { // Synchronize to make sure all commands are processed XSync(_glfw.x11.display, False); XSetErrorHandler(NULL); } // Reports the specified error, appending information about the last X error // void _glfwInputErrorX11(int error, const char* message) { char buffer[_GLFW_MESSAGE_SIZE]; XGetErrorText(_glfw.x11.display, _glfw.x11.errorCode, buffer, sizeof(buffer)); _glfwInputError(error, "%s: %s", message, buffer); } // Creates a native cursor object from the specified image and hotspot // Cursor _glfwCreateCursorX11(const GLFWimage* image, int xhot, int yhot) { int i; Cursor cursor; if (!_glfw.x11.xcursor.handle) return None; XcursorImage* native = XcursorImageCreate(image->width, image->height); if (native == NULL) return None; native->xhot = xhot; native->yhot = yhot; unsigned char* source = (unsigned char*) image->pixels; XcursorPixel* target = native->pixels; for (i = 0; i < image->width * image->height; i++, target++, source += 4) { unsigned int alpha = source[3]; *target = (alpha << 24) | ((unsigned char) ((source[0] * alpha) / 255) << 16) | ((unsigned char) ((source[1] * alpha) / 255) << 8) | ((unsigned char) ((source[2] * alpha) / 255) << 0); } cursor = XcursorImageLoadCursor(_glfw.x11.display, native); XcursorImageDestroy(native); return cursor; } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized) { return glfw_current_system_color_theme(query_if_unintialized); } void _glfwPlatformInputColorScheme(GLFWColorScheme appearance UNUSED) { } int _glfwPlatformInit(bool *supports_window_occlusion) { *supports_window_occlusion = false; XInitThreads(); XrmInitialize(); _glfw.x11.display = XOpenDisplay(NULL); if (!_glfw.x11.display) { const char* display = getenv("DISPLAY"); if (display) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to open display %s", display); } else { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: The DISPLAY environment variable is missing"); } return false; } if (!initPollData(&_glfw.x11.eventLoopData, ConnectionNumber(_glfw.x11.display))) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to initialize event loop data"); } glfw_dbus_init(&_glfw.x11.dbus, &_glfw.x11.eventLoopData); glfw_initialize_desktop_settings(); // needed for color scheme change notification _glfw.x11.screen = DefaultScreen(_glfw.x11.display); _glfw.x11.root = RootWindow(_glfw.x11.display, _glfw.x11.screen); _glfw.x11.context = XUniqueContext(); _glfw.x11.RESOURCE_MANAGER = XInternAtom(_glfw.x11.display, "RESOURCE_MANAGER", True); _glfw.x11._KDE_NET_WM_BLUR_BEHIND_REGION = None; XSelectInput(_glfw.x11.display, _glfw.x11.root, PropertyChangeMask); _glfwGetSystemContentScaleX11(&_glfw.x11.contentScaleX, &_glfw.x11.contentScaleY, false); if (!initExtensions()) return false; _glfw.x11.helperWindowHandle = createHelperWindow(); _glfw.x11.hiddenCursorHandle = createHiddenCursor(); _glfwPollMonitorsX11(); return true; } void _glfwPlatformTerminate(void) { removeAllTimers(&_glfw.x11.eventLoopData); if (_glfw.x11.helperWindowHandle) { if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD) == _glfw.x11.helperWindowHandle) { _glfwPushSelectionToManagerX11(); } XDestroyWindow(_glfw.x11.display, _glfw.x11.helperWindowHandle); _glfw.x11.helperWindowHandle = None; } if (_glfw.x11.hiddenCursorHandle) { XFreeCursor(_glfw.x11.display, _glfw.x11.hiddenCursorHandle); _glfw.x11.hiddenCursorHandle = (Cursor) 0; } glfw_xkb_release(&_glfw.x11.xkb); glfw_dbus_terminate(&_glfw.x11.dbus); if (_glfw.x11.mime_atoms.array) { for (size_t i = 0; i < _glfw.x11.mime_atoms.sz; i++) { free((void*)_glfw.x11.mime_atoms.array[i].mime); } free(_glfw.x11.mime_atoms.array); } if (_glfw.x11.clipboard_atoms.array) { free(_glfw.x11.clipboard_atoms.array); } if (_glfw.x11.primary_atoms.array) { free(_glfw.x11.primary_atoms.array); } if (_glfw.x11.display) { XCloseDisplay(_glfw.x11.display); _glfw.x11.display = NULL; _glfw.x11.eventLoopData.fds[0].fd = -1; } if (_glfw.x11.xcursor.handle) { _glfw_dlclose(_glfw.x11.xcursor.handle); _glfw.x11.xcursor.handle = NULL; } if (_glfw.x11.randr.handle) { _glfw_dlclose(_glfw.x11.randr.handle); _glfw.x11.randr.handle = NULL; } if (_glfw.x11.xinerama.handle) { _glfw_dlclose(_glfw.x11.xinerama.handle); _glfw.x11.xinerama.handle = NULL; } if (_glfw.x11.xrender.handle) { _glfw_dlclose(_glfw.x11.xrender.handle); _glfw.x11.xrender.handle = NULL; } if (_glfw.x11.vidmode.handle) { _glfw_dlclose(_glfw.x11.vidmode.handle); _glfw.x11.vidmode.handle = NULL; } if (_glfw.x11.xi.handle) { _glfw_dlclose(_glfw.x11.xi.handle); _glfw.x11.xi.handle = NULL; } // NOTE: These need to be unloaded after XCloseDisplay, as they register // cleanup callbacks that get called by that function _glfwTerminateEGL(); _glfwTerminateGLX(); finalizePollData(&_glfw.x11.eventLoopData); } const char* _glfwPlatformGetVersionString(void) { return _GLFW_VERSION_NUMBER " X11 GLX EGL OSMesa" #if defined(_POSIX_TIMERS) && defined(_POSIX_MONOTONIC_CLOCK) " clock_gettime" #else " gettimeofday" #endif #if defined(__linux__) " evdev" #endif #if defined(_GLFW_BUILD_DLL) " shared" #endif ; } #include "main_loop.h" kitty-0.41.1/glfw/x11_monitor.c0000664000175000017510000004674414773370543015611 0ustar nileshnilesh//======================================================================== // GLFW 3.4 X11 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #include "internal.h" #include #include #include #include // Check whether the display mode should be included in enumeration // static bool modeIsGood(const XRRModeInfo* mi) { return (mi->modeFlags & RR_Interlace) == 0; } // Calculates the refresh rate, in Hz, from the specified RandR mode info // static int calculateRefreshRate(const XRRModeInfo* mi) { if (mi->hTotal && mi->vTotal) return (int) round((double) mi->dotClock / ((double) mi->hTotal * (double) mi->vTotal)); else return 0; } // Returns the mode info for a RandR mode XID // static const XRRModeInfo* getModeInfo(const XRRScreenResources* sr, RRMode id) { for (int i = 0; i < sr->nmode; i++) { if (sr->modes[i].id == id) return sr->modes + i; } return NULL; } // Convert RandR mode info to GLFW video mode // static GLFWvidmode vidmodeFromModeInfo(const XRRModeInfo* mi, const XRRCrtcInfo* ci) { GLFWvidmode mode; if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270) { mode.width = mi->height; mode.height = mi->width; } else { mode.width = mi->width; mode.height = mi->height; } mode.refreshRate = calculateRefreshRate(mi); _glfwSplitBPP(DefaultDepth(_glfw.x11.display, _glfw.x11.screen), &mode.redBits, &mode.greenBits, &mode.blueBits); return mode; } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// // Poll for changes in the set of connected monitors // void _glfwPollMonitorsX11(void) { if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken) { int disconnectedCount, screenCount = 0; _GLFWmonitor** disconnected = NULL; XineramaScreenInfo* screens = NULL; XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root); RROutput primary = XRRGetOutputPrimary(_glfw.x11.display, _glfw.x11.root); if (_glfw.x11.xinerama.available) screens = XineramaQueryScreens(_glfw.x11.display, &screenCount); disconnectedCount = _glfw.monitorCount; if (disconnectedCount) { disconnected = calloc(_glfw.monitorCount, sizeof(_GLFWmonitor*)); memcpy(disconnected, _glfw.monitors, _glfw.monitorCount * sizeof(_GLFWmonitor*)); } for (int i = 0; i < sr->noutput; i++) { int j, type, widthMM, heightMM; XRROutputInfo* oi = XRRGetOutputInfo(_glfw.x11.display, sr, sr->outputs[i]); if (oi->connection != RR_Connected || oi->crtc == None) { XRRFreeOutputInfo(oi); continue; } for (j = 0; j < disconnectedCount; j++) { if (disconnected[j] && disconnected[j]->x11.output == sr->outputs[i]) { disconnected[j] = NULL; break; } } if (j < disconnectedCount) { XRRFreeOutputInfo(oi); continue; } XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, oi->crtc); if (!ci) { XRRFreeOutputInfo(oi); continue; } if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270) { widthMM = oi->mm_height; heightMM = oi->mm_width; } else { widthMM = oi->mm_width; heightMM = oi->mm_height; } if (widthMM <= 0 || heightMM <= 0) { // HACK: If RandR does not provide a physical size, assume the // X11 default 96 DPI and calculate from the CRTC viewport // NOTE: These members are affected by rotation, unlike the mode // info and output info members widthMM = (int) (ci->width * 25.4f / 96.f); heightMM = (int) (ci->height * 25.4f / 96.f); } _GLFWmonitor* monitor = _glfwAllocMonitor(oi->name, widthMM, heightMM); monitor->x11.output = sr->outputs[i]; monitor->x11.crtc = oi->crtc; for (j = 0; j < screenCount; j++) { if (screens[j].x_org == ci->x && screens[j].y_org == ci->y && screens[j].width == (short int)ci->width && screens[j].height == (short int)ci->height) { monitor->x11.index = j; break; } } if (monitor->x11.output == primary) type = _GLFW_INSERT_FIRST; else type = _GLFW_INSERT_LAST; _glfwInputMonitor(monitor, GLFW_CONNECTED, type); XRRFreeOutputInfo(oi); XRRFreeCrtcInfo(ci); } XRRFreeScreenResources(sr); if (screens) XFree(screens); for (int i = 0; i < disconnectedCount; i++) { if (disconnected[i]) _glfwInputMonitor(disconnected[i], GLFW_DISCONNECTED, 0); } free(disconnected); } else { const int widthMM = DisplayWidthMM(_glfw.x11.display, _glfw.x11.screen); const int heightMM = DisplayHeightMM(_glfw.x11.display, _glfw.x11.screen); _glfwInputMonitor(_glfwAllocMonitor("Display", widthMM, heightMM), GLFW_CONNECTED, _GLFW_INSERT_FIRST); } } // Set the current video mode for the specified monitor // void _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired) { if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken) { GLFWvidmode current; RRMode native = None; const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired); _glfwPlatformGetVideoMode(monitor, ¤t); if (_glfwCompareVideoModes(¤t, best) == 0) return; XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root); XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc); XRROutputInfo* oi = XRRGetOutputInfo(_glfw.x11.display, sr, monitor->x11.output); for (int i = 0; i < oi->nmode; i++) { const XRRModeInfo* mi = getModeInfo(sr, oi->modes[i]); if (!modeIsGood(mi)) continue; const GLFWvidmode mode = vidmodeFromModeInfo(mi, ci); if (_glfwCompareVideoModes(best, &mode) == 0) { native = mi->id; break; } } if (native) { if (monitor->x11.oldMode == None) monitor->x11.oldMode = ci->mode; XRRSetCrtcConfig(_glfw.x11.display, sr, monitor->x11.crtc, CurrentTime, ci->x, ci->y, native, ci->rotation, ci->outputs, ci->noutput); } XRRFreeOutputInfo(oi); XRRFreeCrtcInfo(ci); XRRFreeScreenResources(sr); } } // Restore the saved (original) video mode for the specified monitor // void _glfwRestoreVideoModeX11(_GLFWmonitor* monitor) { if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken) { if (monitor->x11.oldMode == None) return; XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root); XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc); XRRSetCrtcConfig(_glfw.x11.display, sr, monitor->x11.crtc, CurrentTime, ci->x, ci->y, monitor->x11.oldMode, ci->rotation, ci->outputs, ci->noutput); XRRFreeCrtcInfo(ci); XRRFreeScreenResources(sr); monitor->x11.oldMode = None; } } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor UNUSED) { } void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos) { if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken) { XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root); XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc); if (ci) { if (xpos) *xpos = ci->x; if (ypos) *ypos = ci->y; XRRFreeCrtcInfo(ci); } XRRFreeScreenResources(sr); } } void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor UNUSED, float* xscale, float* yscale) { if (xscale) *xscale = _glfw.x11.contentScaleX; if (yscale) *yscale = _glfw.x11.contentScaleY; } void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor, int* xpos, int* ypos, int* width, int* height) { int areaX = 0, areaY = 0, areaWidth = 0, areaHeight = 0; if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken) { XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root); XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc); areaX = ci->x; areaY = ci->y; const XRRModeInfo* mi = getModeInfo(sr, ci->mode); if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270) { areaWidth = mi->height; areaHeight = mi->width; } else { areaWidth = mi->width; areaHeight = mi->height; } XRRFreeCrtcInfo(ci); XRRFreeScreenResources(sr); } else { areaWidth = DisplayWidth(_glfw.x11.display, _glfw.x11.screen); areaHeight = DisplayHeight(_glfw.x11.display, _glfw.x11.screen); } if (_glfw.x11.NET_WORKAREA && _glfw.x11.NET_CURRENT_DESKTOP) { Atom* extents = NULL; Atom* desktop = NULL; const unsigned long extentCount = _glfwGetWindowPropertyX11(_glfw.x11.root, _glfw.x11.NET_WORKAREA, XA_CARDINAL, (unsigned char**) &extents); if (_glfwGetWindowPropertyX11(_glfw.x11.root, _glfw.x11.NET_CURRENT_DESKTOP, XA_CARDINAL, (unsigned char**) &desktop) > 0) { if (extentCount >= 4 && *desktop < extentCount / 4) { const int globalX = extents[*desktop * 4 + 0]; const int globalY = extents[*desktop * 4 + 1]; const int globalWidth = extents[*desktop * 4 + 2]; const int globalHeight = extents[*desktop * 4 + 3]; if (areaX < globalX) { areaWidth -= globalX - areaX; areaX = globalX; } if (areaY < globalY) { areaHeight -= globalY - areaY; areaY = globalY; } if (areaX + areaWidth > globalX + globalWidth) areaWidth = globalX - areaX + globalWidth; if (areaY + areaHeight > globalY + globalHeight) areaHeight = globalY - areaY + globalHeight; } } if (extents) XFree(extents); if (desktop) XFree(desktop); } if (xpos) *xpos = areaX; if (ypos) *ypos = areaY; if (width) *width = areaWidth; if (height) *height = areaHeight; } GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count) { GLFWvidmode* result; *count = 0; if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken) { XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root); XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc); XRROutputInfo* oi = XRRGetOutputInfo(_glfw.x11.display, sr, monitor->x11.output); result = calloc(oi->nmode, sizeof(GLFWvidmode)); for (int i = 0; i < oi->nmode; i++) { const XRRModeInfo* mi = getModeInfo(sr, oi->modes[i]); if (!modeIsGood(mi)) continue; const GLFWvidmode mode = vidmodeFromModeInfo(mi, ci); int j; for (j = 0; j < *count; j++) { if (_glfwCompareVideoModes(result + j, &mode) == 0) break; } // Skip duplicate modes if (j < *count) continue; (*count)++; result[*count - 1] = mode; } XRRFreeOutputInfo(oi); XRRFreeCrtcInfo(ci); XRRFreeScreenResources(sr); } else { *count = 1; result = calloc(1, sizeof(GLFWvidmode)); _glfwPlatformGetVideoMode(monitor, result); } return result; } bool _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode) { bool ok = false; if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken) { XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root); XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc); if (ci) { const XRRModeInfo* mi = getModeInfo(sr, ci->mode); if (mi) { // mi can be NULL if the monitor has been disconnected *mode = vidmodeFromModeInfo(mi, ci); ok = true; } XRRFreeCrtcInfo(ci); } XRRFreeScreenResources(sr); } else { ok = true; mode->width = DisplayWidth(_glfw.x11.display, _glfw.x11.screen); mode->height = DisplayHeight(_glfw.x11.display, _glfw.x11.screen); mode->refreshRate = 0; _glfwSplitBPP(DefaultDepth(_glfw.x11.display, _glfw.x11.screen), &mode->redBits, &mode->greenBits, &mode->blueBits); } return ok; } bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp) { if (_glfw.x11.randr.available && !_glfw.x11.randr.gammaBroken) { const size_t size = XRRGetCrtcGammaSize(_glfw.x11.display, monitor->x11.crtc); XRRCrtcGamma* gamma = XRRGetCrtcGamma(_glfw.x11.display, monitor->x11.crtc); _glfwAllocGammaArrays(ramp, size); memcpy(ramp->red, gamma->red, size * sizeof(unsigned short)); memcpy(ramp->green, gamma->green, size * sizeof(unsigned short)); memcpy(ramp->blue, gamma->blue, size * sizeof(unsigned short)); XRRFreeGamma(gamma); return true; } else if (_glfw.x11.vidmode.available) { int size; XF86VidModeGetGammaRampSize(_glfw.x11.display, _glfw.x11.screen, &size); _glfwAllocGammaArrays(ramp, size); XF86VidModeGetGammaRamp(_glfw.x11.display, _glfw.x11.screen, ramp->size, ramp->red, ramp->green, ramp->blue); return true; } else { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Gamma ramp access not supported by server"); return false; } } void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp) { if (_glfw.x11.randr.available && !_glfw.x11.randr.gammaBroken) { if (XRRGetCrtcGammaSize(_glfw.x11.display, monitor->x11.crtc) != (int)ramp->size) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Gamma ramp size must match current ramp size"); return; } XRRCrtcGamma* gamma = XRRAllocGamma(ramp->size); memcpy(gamma->red, ramp->red, ramp->size * sizeof(unsigned short)); memcpy(gamma->green, ramp->green, ramp->size * sizeof(unsigned short)); memcpy(gamma->blue, ramp->blue, ramp->size * sizeof(unsigned short)); XRRSetCrtcGamma(_glfw.x11.display, monitor->x11.crtc, gamma); XRRFreeGamma(gamma); } else if (_glfw.x11.vidmode.available) { XF86VidModeSetGammaRamp(_glfw.x11.display, _glfw.x11.screen, ramp->size, (unsigned short*) ramp->red, (unsigned short*) ramp->green, (unsigned short*) ramp->blue); } else { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Gamma ramp access not supported by server"); } } ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI RRCrtc glfwGetX11Adapter(GLFWmonitor* handle) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(None); return monitor->x11.crtc; } GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* handle) { _GLFWmonitor* monitor = (_GLFWmonitor*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(None); return monitor->x11.output; } kitty-0.41.1/glfw/x11_platform.h0000664000175000017510000004074614773370543015747 0ustar nileshnilesh//======================================================================== // GLFW 3.4 X11 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #include #include #include #include #include #include #include #include #include // The xcb library is needed to work with libxkb #include // The XRandR extension provides mode setting and gamma control #include // The Xkb extension provides improved keyboard support #include // The Xinerama extension provides legacy monitor indices #include // The XInput extension provides raw mouse motion input #include // The Shape extension provides custom window shapes #include // The libxkb library is used for improved keyboard support #include "xkb_glfw.h" #include "backend_utils.h" typedef XRRCrtcGamma* (* PFN_XRRAllocGamma)(int); typedef void (* PFN_XRRFreeCrtcInfo)(XRRCrtcInfo*); typedef void (* PFN_XRRFreeGamma)(XRRCrtcGamma*); typedef void (* PFN_XRRFreeOutputInfo)(XRROutputInfo*); typedef void (* PFN_XRRFreeScreenResources)(XRRScreenResources*); typedef XRRCrtcGamma* (* PFN_XRRGetCrtcGamma)(Display*,RRCrtc); typedef int (* PFN_XRRGetCrtcGammaSize)(Display*,RRCrtc); typedef XRRCrtcInfo* (* PFN_XRRGetCrtcInfo) (Display*,XRRScreenResources*,RRCrtc); typedef XRROutputInfo* (* PFN_XRRGetOutputInfo)(Display*,XRRScreenResources*,RROutput); typedef RROutput (* PFN_XRRGetOutputPrimary)(Display*,Window); typedef XRRScreenResources* (* PFN_XRRGetScreenResourcesCurrent)(Display*,Window); typedef Bool (* PFN_XRRQueryExtension)(Display*,int*,int*); typedef Status (* PFN_XRRQueryVersion)(Display*,int*,int*); typedef void (* PFN_XRRSelectInput)(Display*,Window,int); typedef Status (* PFN_XRRSetCrtcConfig)(Display*,XRRScreenResources*,RRCrtc,Time,int,int,RRMode,Rotation,RROutput*,int); typedef void (* PFN_XRRSetCrtcGamma)(Display*,RRCrtc,XRRCrtcGamma*); typedef int (* PFN_XRRUpdateConfiguration)(XEvent*); #define XRRAllocGamma _glfw.x11.randr.AllocGamma #define XRRFreeCrtcInfo _glfw.x11.randr.FreeCrtcInfo #define XRRFreeGamma _glfw.x11.randr.FreeGamma #define XRRFreeOutputInfo _glfw.x11.randr.FreeOutputInfo #define XRRFreeScreenResources _glfw.x11.randr.FreeScreenResources #define XRRGetCrtcGamma _glfw.x11.randr.GetCrtcGamma #define XRRGetCrtcGammaSize _glfw.x11.randr.GetCrtcGammaSize #define XRRGetCrtcInfo _glfw.x11.randr.GetCrtcInfo #define XRRGetOutputInfo _glfw.x11.randr.GetOutputInfo #define XRRGetOutputPrimary _glfw.x11.randr.GetOutputPrimary #define XRRGetScreenResourcesCurrent _glfw.x11.randr.GetScreenResourcesCurrent #define XRRQueryExtension _glfw.x11.randr.QueryExtension #define XRRQueryVersion _glfw.x11.randr.QueryVersion #define XRRSelectInput _glfw.x11.randr.SelectInput #define XRRSetCrtcConfig _glfw.x11.randr.SetCrtcConfig #define XRRSetCrtcGamma _glfw.x11.randr.SetCrtcGamma #define XRRUpdateConfiguration _glfw.x11.randr.UpdateConfiguration typedef XcursorImage* (* PFN_XcursorImageCreate)(int,int); typedef void (* PFN_XcursorImageDestroy)(XcursorImage*); typedef Cursor (* PFN_XcursorImageLoadCursor)(Display*,const XcursorImage*); #define XcursorImageCreate _glfw.x11.xcursor.ImageCreate #define XcursorImageDestroy _glfw.x11.xcursor.ImageDestroy #define XcursorImageLoadCursor _glfw.x11.xcursor.ImageLoadCursor typedef Bool (* PFN_XineramaIsActive)(Display*); typedef Bool (* PFN_XineramaQueryExtension)(Display*,int*,int*); typedef XineramaScreenInfo* (* PFN_XineramaQueryScreens)(Display*,int*); #define XineramaIsActive _glfw.x11.xinerama.IsActive #define XineramaQueryExtension _glfw.x11.xinerama.QueryExtension #define XineramaQueryScreens _glfw.x11.xinerama.QueryScreens typedef Bool (* PFN_XF86VidModeQueryExtension)(Display*,int*,int*); typedef Bool (* PFN_XF86VidModeGetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*); typedef Bool (* PFN_XF86VidModeSetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*); typedef Bool (* PFN_XF86VidModeGetGammaRampSize)(Display*,int,int*); #define XF86VidModeQueryExtension _glfw.x11.vidmode.QueryExtension #define XF86VidModeGetGammaRamp _glfw.x11.vidmode.GetGammaRamp #define XF86VidModeSetGammaRamp _glfw.x11.vidmode.SetGammaRamp #define XF86VidModeGetGammaRampSize _glfw.x11.vidmode.GetGammaRampSize typedef Status (* PFN_XIQueryVersion)(Display*,int*,int*); typedef int (* PFN_XISelectEvents)(Display*,Window,XIEventMask*,int); #define XIQueryVersion _glfw.x11.xi.QueryVersion #define XISelectEvents _glfw.x11.xi.SelectEvents typedef Bool (* PFN_XRenderQueryExtension)(Display*,int*,int*); typedef Status (* PFN_XRenderQueryVersion)(Display*dpy,int*,int*); typedef XRenderPictFormat* (* PFN_XRenderFindVisualFormat)(Display*,Visual const*); #define XRenderQueryExtension _glfw.x11.xrender.QueryExtension #define XRenderQueryVersion _glfw.x11.xrender.QueryVersion #define XRenderFindVisualFormat _glfw.x11.xrender.FindVisualFormat typedef Bool (* PFN_XShapeQueryExtension)(Display*,int*,int*); typedef Status (* PFN_XShapeQueryVersion)(Display*dpy,int*,int*); typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int); typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int); #define XShapeQueryExtension _glfw.x11.xshape.QueryExtension #define XShapeQueryVersion _glfw.x11.xshape.QueryVersion #define XShapeCombineRegion _glfw.x11.xshape.ShapeCombineRegion #define XShapeCombineMask _glfw.x11.xshape.ShapeCombineMask typedef VkFlags VkXlibSurfaceCreateFlagsKHR; typedef VkFlags VkXcbSurfaceCreateFlagsKHR; typedef struct VkXlibSurfaceCreateInfoKHR { VkStructureType sType; const void* pNext; VkXlibSurfaceCreateFlagsKHR flags; Display* dpy; Window window; } VkXlibSurfaceCreateInfoKHR; typedef struct VkXcbSurfaceCreateInfoKHR { VkStructureType sType; const void* pNext; VkXcbSurfaceCreateFlagsKHR flags; xcb_connection_t* connection; xcb_window_t window; } VkXcbSurfaceCreateInfoKHR; typedef VkResult (APIENTRY *PFN_vkCreateXlibSurfaceKHR)(VkInstance,const VkXlibSurfaceCreateInfoKHR*,const VkAllocationCallbacks*,VkSurfaceKHR*); typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR)(VkPhysicalDevice,uint32_t,Display*,VisualID); typedef VkResult (APIENTRY *PFN_vkCreateXcbSurfaceKHR)(VkInstance,const VkXcbSurfaceCreateInfoKHR*,const VkAllocationCallbacks*,VkSurfaceKHR*); typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(VkPhysicalDevice,uint32_t,xcb_connection_t*,xcb_visualid_t); #include "posix_thread.h" #include "glx_context.h" #if defined(__linux__) #include "linux_joystick.h" #else #include "null_joystick.h" #endif #define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL) #define _glfw_dlclose(handle) dlclose(handle) #define _glfw_dlsym(handle, name) dlsym(handle, name) #define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowX11 x11 #define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryX11 x11 #define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorX11 x11 #define _GLFW_PLATFORM_CURSOR_STATE _GLFWcursorX11 x11 // X11-specific per-window data // typedef struct _GLFWwindowX11 { Colormap colormap; Window handle; Window parent; bool iconified; bool maximized; // Whether the visual supports framebuffer transparency bool transparent; // Cached position and size used to filter out duplicate events int width, height; int xpos, ypos; // The last received cursor position, regardless of source int lastCursorPosX, lastCursorPosY; // The last position the cursor was warped to by GLFW int warpCursorPosX, warpCursorPosY; } _GLFWwindowX11; typedef struct MimeAtom { Atom atom; const char* mime; } MimeAtom; typedef struct AtomArray { MimeAtom *array; size_t sz, capacity; } AtomArray; // X11-specific global data // typedef struct _GLFWlibraryX11 { Display* display; int screen; Window root; // System content scale float contentScaleX, contentScaleY; // Helper window for IPC Window helperWindowHandle; // Invisible cursor for hidden cursor mode Cursor hiddenCursorHandle; // Context for mapping window XIDs to _GLFWwindow pointers XContext context; // Most recent error code received by X error handler int errorCode; // Where to place the cursor when re-enabled double restoreCursorPosX, restoreCursorPosY; // The window whose disabled cursor mode is active _GLFWwindow* disabledCursorWindow; // Window manager atoms Atom NET_SUPPORTED; Atom NET_SUPPORTING_WM_CHECK; Atom WM_PROTOCOLS; Atom WM_STATE; Atom WM_DELETE_WINDOW; Atom NET_WM_NAME; Atom NET_WM_ICON_NAME; Atom NET_WM_ICON; Atom NET_WM_PID; Atom NET_WM_PING; Atom NET_WM_WINDOW_TYPE; Atom NET_WM_WINDOW_TYPE_NORMAL; Atom NET_WM_WINDOW_TYPE_DOCK; Atom NET_WM_STATE; Atom NET_WM_STATE_ABOVE; Atom NET_WM_STATE_FULLSCREEN; Atom NET_WM_STATE_MAXIMIZED_VERT; Atom NET_WM_STATE_MAXIMIZED_HORZ; Atom NET_WM_STATE_DEMANDS_ATTENTION; Atom NET_WM_BYPASS_COMPOSITOR; Atom NET_WM_FULLSCREEN_MONITORS; Atom NET_WM_WINDOW_OPACITY; Atom NET_WM_CM_Sx; Atom NET_WORKAREA; Atom NET_CURRENT_DESKTOP; Atom NET_ACTIVE_WINDOW; Atom NET_FRAME_EXTENTS; Atom NET_REQUEST_FRAME_EXTENTS; Atom NET_WM_STRUT_PARTIAL; Atom MOTIF_WM_HINTS; // Xdnd (drag and drop) atoms Atom XdndAware; Atom XdndEnter; Atom XdndPosition; Atom XdndStatus; Atom XdndActionCopy; Atom XdndDrop; Atom XdndFinished; Atom XdndSelection; Atom XdndTypeList; // Selection (clipboard) atoms Atom TARGETS; Atom MULTIPLE; Atom INCR; Atom CLIPBOARD; Atom PRIMARY; Atom CLIPBOARD_MANAGER; Atom SAVE_TARGETS; Atom NULL_; Atom UTF8_STRING; Atom COMPOUND_STRING; Atom ATOM_PAIR; Atom GLFW_SELECTION; // XRM database atom Atom RESOURCE_MANAGER; // KDE window blur Atom _KDE_NET_WM_BLUR_BEHIND_REGION; // Atoms for MIME types AtomArray mime_atoms, clipboard_atoms, primary_atoms; struct { bool available; void* handle; int eventBase; int errorBase; int major; int minor; bool gammaBroken; bool monitorBroken; PFN_XRRAllocGamma AllocGamma; PFN_XRRFreeCrtcInfo FreeCrtcInfo; PFN_XRRFreeGamma FreeGamma; PFN_XRRFreeOutputInfo FreeOutputInfo; PFN_XRRFreeScreenResources FreeScreenResources; PFN_XRRGetCrtcGamma GetCrtcGamma; PFN_XRRGetCrtcGammaSize GetCrtcGammaSize; PFN_XRRGetCrtcInfo GetCrtcInfo; PFN_XRRGetOutputInfo GetOutputInfo; PFN_XRRGetOutputPrimary GetOutputPrimary; PFN_XRRGetScreenResourcesCurrent GetScreenResourcesCurrent; PFN_XRRQueryExtension QueryExtension; PFN_XRRQueryVersion QueryVersion; PFN_XRRSelectInput SelectInput; PFN_XRRSetCrtcConfig SetCrtcConfig; PFN_XRRSetCrtcGamma SetCrtcGamma; PFN_XRRUpdateConfiguration UpdateConfiguration; } randr; _GLFWXKBData xkb; _GLFWDBUSData dbus; struct { int count; int timeout; int interval; int blanking; int exposure; } saver; struct { int version; Window source; char format[128]; int format_priority; } xdnd; struct { void* handle; PFN_XcursorImageCreate ImageCreate; PFN_XcursorImageDestroy ImageDestroy; PFN_XcursorImageLoadCursor ImageLoadCursor; } xcursor; struct { bool available; void* handle; int major; int minor; PFN_XineramaIsActive IsActive; PFN_XineramaQueryExtension QueryExtension; PFN_XineramaQueryScreens QueryScreens; } xinerama; struct { bool available; void* handle; int eventBase; int errorBase; PFN_XF86VidModeQueryExtension QueryExtension; PFN_XF86VidModeGetGammaRamp GetGammaRamp; PFN_XF86VidModeSetGammaRamp SetGammaRamp; PFN_XF86VidModeGetGammaRampSize GetGammaRampSize; } vidmode; struct { bool available; void* handle; int majorOpcode; int eventBase; int errorBase; int major; int minor; PFN_XIQueryVersion QueryVersion; PFN_XISelectEvents SelectEvents; } xi; struct { bool available; void* handle; int major; int minor; int eventBase; int errorBase; PFN_XRenderQueryExtension QueryExtension; PFN_XRenderQueryVersion QueryVersion; PFN_XRenderFindVisualFormat FindVisualFormat; } xrender; struct { bool available; void* handle; int major; int minor; int eventBase; int errorBase; PFN_XShapeQueryExtension QueryExtension; PFN_XShapeCombineRegion ShapeCombineRegion; PFN_XShapeQueryVersion QueryVersion; PFN_XShapeCombineMask ShapeCombineMask; } xshape; EventLoopData eventLoopData; } _GLFWlibraryX11; // X11-specific per-monitor data // typedef struct _GLFWmonitorX11 { RROutput output; RRCrtc crtc; RRMode oldMode; // Index of corresponding Xinerama screen, // for EWMH full screen window placement int index; } _GLFWmonitorX11; // X11-specific per-cursor data // typedef struct _GLFWcursorX11 { Cursor handle; } _GLFWcursorX11; void _glfwPollMonitorsX11(void); void _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired); void _glfwRestoreVideoModeX11(_GLFWmonitor* monitor); Cursor _glfwCreateCursorX11(const GLFWimage* image, int xhot, int yhot); unsigned long _glfwGetWindowPropertyX11(Window window, Atom property, Atom type, unsigned char** value); bool _glfwIsVisualTransparentX11(Visual* visual); void _glfwGrabErrorHandlerX11(void); void _glfwReleaseErrorHandlerX11(void); void _glfwInputErrorX11(int error, const char* message); void _glfwGetSystemContentScaleX11(float* xscale, float* yscale, bool bypass_cache); void _glfwPushSelectionToManagerX11(void); kitty-0.41.1/glfw/x11_window.c0000664000175000017510000032517114773370543015423 0ustar nileshnilesh//======================================================================== // GLFW 3.4 X11 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2002-2006 Marcus Geelnard // Copyright (c) 2006-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== // It is fine to use C99 in this file because it will not be built with VS //======================================================================== #define _GNU_SOURCE #include "internal.h" #include "backend_utils.h" #include "linux_notify.h" #include "../kitty/monotonic.h" #include #include #include #include #include #include #include // Action for EWMH client messages #define _NET_WM_STATE_REMOVE 0 #define _NET_WM_STATE_ADD 1 #define _NET_WM_STATE_TOGGLE 2 // Additional mouse button names for XButtonEvent #define Button6 6 #define Button7 7 // Motif WM hints flags #define MWM_HINTS_DECORATIONS 2 #define MWM_DECOR_ALL 1 #define _GLFW_XDND_VERSION 5 // Wait for data to arrive using poll // This avoids blocking other threads via the per-display Xlib lock that also // covers GLX functions // static unsigned _glfwDispatchX11Events(void); static void handleEvents(monotonic_t timeout) { EVDBG("starting handleEvents(%.2f)", monotonic_t_to_s_double(timeout)); int display_read_ok = pollForEvents(&_glfw.x11.eventLoopData, timeout, NULL); EVDBG("display_read_ok: %d", display_read_ok); if (display_read_ok) { unsigned dispatched = _glfwDispatchX11Events(); (void)dispatched; EVDBG("dispatched %u X11 events", dispatched); } glfw_ibus_dispatch(&_glfw.x11.xkb.ibus); glfw_dbus_session_bus_dispatch(); EVDBG("other dispatch done"); if (_glfw.x11.eventLoopData.wakeup_fd_ready) check_for_wakeup_events(&_glfw.x11.eventLoopData); } static bool waitForX11Event(monotonic_t timeout) { // returns true if there is X11 data waiting to be read, does not run watches and timers monotonic_t end_time = glfwGetTime() + timeout; while(true) { if (timeout >= 0) { const int result = pollWithTimeout(_glfw.x11.eventLoopData.fds, 1, timeout); if (result > 0) return true; timeout = end_time - glfwGetTime(); if (timeout <= 0) return false; if (result < 0 && (errno == EINTR || errno == EAGAIN)) continue; return false; } else { const int result = poll(_glfw.x11.eventLoopData.fds, 1, -1); if (result > 0) return true; if (result < 0 && (errno == EINTR || errno == EAGAIN)) continue; return false; } } } // Waits until a VisibilityNotify event arrives for the specified window or the // timeout period elapses (ICCCM section 4.2.2) // static bool waitForVisibilityNotify(_GLFWwindow* window) { XEvent dummy; while (!XCheckTypedWindowEvent(_glfw.x11.display, window->x11.handle, VisibilityNotify, &dummy)) { if (!waitForX11Event(ms_to_monotonic_t(100ll))) return false; } return true; } // Returns whether the window is iconified // static int getWindowState(_GLFWwindow* window) { int result = WithdrawnState; struct { CARD32 state; Window icon; } *state = NULL; if (_glfwGetWindowPropertyX11(window->x11.handle, _glfw.x11.WM_STATE, _glfw.x11.WM_STATE, (unsigned char**) &state) >= 2) { result = state->state; } if (state) XFree(state); return result; } // Returns whether the event is a selection event // static Bool isSelectionEvent(Display* display UNUSED, XEvent* event, XPointer pointer UNUSED) { if (event->xany.window != _glfw.x11.helperWindowHandle) return False; return event->type == SelectionRequest || event->type == SelectionNotify || event->type == SelectionClear; } // Returns whether it is a _NET_FRAME_EXTENTS event for the specified window // static Bool isFrameExtentsEvent(Display* display UNUSED, XEvent* event, XPointer pointer) { _GLFWwindow* window = (_GLFWwindow*) pointer; return event->type == PropertyNotify && event->xproperty.state == PropertyNewValue && event->xproperty.window == window->x11.handle && event->xproperty.atom == _glfw.x11.NET_FRAME_EXTENTS; } // Returns whether it is a property event for the specified selection transfer // static Bool isSelPropNewValueNotify(Display* display UNUSED, XEvent* event, XPointer pointer) { XEvent* notification = (XEvent*) pointer; return event->type == PropertyNotify && event->xproperty.state == PropertyNewValue && event->xproperty.window == notification->xselection.requestor && event->xproperty.atom == notification->xselection.property; } // Translates an X event modifier state mask // static int translateState(int state) { int mods = 0; /* Need some way to expose hyper and meta without xkbcommon-x11 */ if (state & ShiftMask) mods |= GLFW_MOD_SHIFT; if (state & ControlMask) mods |= GLFW_MOD_CONTROL; if (state & Mod1Mask) mods |= GLFW_MOD_ALT; if (state & Mod4Mask) mods |= GLFW_MOD_SUPER; if (state & LockMask) mods |= GLFW_MOD_CAPS_LOCK; if (state & Mod2Mask) mods |= GLFW_MOD_NUM_LOCK; return mods; } // Sends an EWMH or ICCCM event to the window manager // static void sendEventToWM(_GLFWwindow* window, Atom type, long a, long b, long c, long d, long e) { XEvent event = { ClientMessage }; event.xclient.window = window->x11.handle; event.xclient.format = 32; // Data is 32-bit longs event.xclient.message_type = type; event.xclient.data.l[0] = a; event.xclient.data.l[1] = b; event.xclient.data.l[2] = c; event.xclient.data.l[3] = d; event.xclient.data.l[4] = e; XSendEvent(_glfw.x11.display, _glfw.x11.root, False, SubstructureNotifyMask | SubstructureRedirectMask, &event); } // Updates the normal hints according to the window settings // static void updateNormalHints(_GLFWwindow* window, int width, int height) { XSizeHints* hints = XAllocSizeHints(); if (!window->monitor) { if (window->resizable) { if (window->minwidth != GLFW_DONT_CARE && window->minheight != GLFW_DONT_CARE) { hints->flags |= PMinSize; hints->min_width = window->minwidth; hints->min_height = window->minheight; } if (window->maxwidth != GLFW_DONT_CARE && window->maxheight != GLFW_DONT_CARE) { hints->flags |= PMaxSize; hints->max_width = window->maxwidth; hints->max_height = window->maxheight; } if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE) { hints->flags |= PAspect; hints->min_aspect.x = hints->max_aspect.x = window->numer; hints->min_aspect.y = hints->max_aspect.y = window->denom; } if (window->widthincr != GLFW_DONT_CARE && window->heightincr != GLFW_DONT_CARE && !window->x11.maximized) { hints->flags |= PResizeInc; hints->width_inc = window->widthincr; hints->height_inc = window->heightincr; } } else { hints->flags |= (PMinSize | PMaxSize); hints->min_width = hints->max_width = width; hints->min_height = hints->max_height = height; } } hints->flags |= PWinGravity; hints->win_gravity = StaticGravity; XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints); XFree(hints); } static bool is_window_fullscreen(_GLFWwindow* window) { Atom* states; unsigned long i; bool ans = false; if (!_glfw.x11.NET_WM_STATE || !_glfw.x11.NET_WM_STATE_FULLSCREEN) return ans; const unsigned long count = _glfwGetWindowPropertyX11(window->x11.handle, _glfw.x11.NET_WM_STATE, XA_ATOM, (unsigned char**) &states); for (i = 0; i < count; i++) { if (states[i] == _glfw.x11.NET_WM_STATE_FULLSCREEN) { ans = true; break; } } if (states) XFree(states); return ans; } static void set_fullscreen(_GLFWwindow *window, bool on) { if (_glfw.x11.NET_WM_STATE && _glfw.x11.NET_WM_STATE_FULLSCREEN) { sendEventToWM(window, _glfw.x11.NET_WM_STATE, on ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE, _glfw.x11.NET_WM_STATE_FULLSCREEN, 0, 1, 0); // Enable compositor bypass if (!window->x11.transparent) { if (on) { const unsigned long value = 1; XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &value, 1); } else { XDeleteProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_BYPASS_COMPOSITOR); } } } else { static bool warned = false; if (!warned) { warned = true; _glfwInputErrorX11(GLFW_PLATFORM_ERROR, "X11: Failed to toggle fullscreen, the window manager does not support it"); } } } bool _glfwPlatformIsFullscreen(_GLFWwindow *window, unsigned int flags UNUSED) { return is_window_fullscreen(window); } bool _glfwPlatformToggleFullscreen(_GLFWwindow *window, unsigned int flags UNUSED) { bool already_fullscreen = is_window_fullscreen(window); set_fullscreen(window, !already_fullscreen); return !already_fullscreen; } // Updates the full screen status of the window // static void updateWindowMode(_GLFWwindow* window) { if (window->monitor) { if (_glfw.x11.xinerama.available && _glfw.x11.NET_WM_FULLSCREEN_MONITORS) { sendEventToWM(window, _glfw.x11.NET_WM_FULLSCREEN_MONITORS, window->monitor->x11.index, window->monitor->x11.index, window->monitor->x11.index, window->monitor->x11.index, 0); } set_fullscreen(window, true); } else { if (_glfw.x11.xinerama.available && _glfw.x11.NET_WM_FULLSCREEN_MONITORS) { XDeleteProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_FULLSCREEN_MONITORS); } set_fullscreen(window, false); } } // Encode a Unicode code point to a UTF-8 stream // Based on cutef8 by Jeff Bezanson (Public Domain) // static size_t encodeUTF8(char* s, unsigned int ch) { size_t count = 0; if (ch < 0x80) s[count++] = (char) ch; else if (ch < 0x800) { s[count++] = (ch >> 6) | 0xc0; s[count++] = (ch & 0x3f) | 0x80; } else if (ch < 0x10000) { s[count++] = (ch >> 12) | 0xe0; s[count++] = ((ch >> 6) & 0x3f) | 0x80; s[count++] = (ch & 0x3f) | 0x80; } else if (ch < 0x110000) { s[count++] = (ch >> 18) | 0xf0; s[count++] = ((ch >> 12) & 0x3f) | 0x80; s[count++] = ((ch >> 6) & 0x3f) | 0x80; s[count++] = (ch & 0x3f) | 0x80; } return count; } // Convert the specified Latin-1 string to UTF-8 // static char* convertLatin1toUTF8(const char* source) { size_t size = 1; const char* sp; if (source) { for (sp = source; *sp; sp++) size += (*sp & 0x80) ? 2 : 1; } char* target = calloc(size, 1); char* tp = target; if (source) { for (sp = source; *sp; sp++) tp += encodeUTF8(tp, *sp); } return target; } // Updates the cursor image according to its cursor mode // static void updateCursorImage(_GLFWwindow* window) { if (window->cursorMode == GLFW_CURSOR_NORMAL) { if (window->cursor) { XDefineCursor(_glfw.x11.display, window->x11.handle, window->cursor->x11.handle); } else XUndefineCursor(_glfw.x11.display, window->x11.handle); } else { XDefineCursor(_glfw.x11.display, window->x11.handle, _glfw.x11.hiddenCursorHandle); } } // Enable XI2 raw mouse motion events // static void enableRawMouseMotion(_GLFWwindow* window UNUSED) { XIEventMask em; unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; em.deviceid = XIAllMasterDevices; em.mask_len = sizeof(mask); em.mask = mask; XISetMask(mask, XI_RawMotion); XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1); } // Disable XI2 raw mouse motion events // static void disableRawMouseMotion(_GLFWwindow* window UNUSED) { XIEventMask em; unsigned char mask[] = { 0 }; em.deviceid = XIAllMasterDevices; em.mask_len = sizeof(mask); em.mask = mask; XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1); } // Apply disabled cursor mode to a focused window // static void disableCursor(_GLFWwindow* window) { if (window->rawMouseMotion) enableRawMouseMotion(window); _glfw.x11.disabledCursorWindow = window; _glfwPlatformGetCursorPos(window, &_glfw.x11.restoreCursorPosX, &_glfw.x11.restoreCursorPosY); updateCursorImage(window); _glfwCenterCursorInContentArea(window); XGrabPointer(_glfw.x11.display, window->x11.handle, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, window->x11.handle, _glfw.x11.hiddenCursorHandle, CurrentTime); } // Exit disabled cursor mode for the specified window // static void enableCursor(_GLFWwindow* window) { if (window->rawMouseMotion) disableRawMouseMotion(window); _glfw.x11.disabledCursorWindow = NULL; XUngrabPointer(_glfw.x11.display, CurrentTime); _glfwPlatformSetCursorPos(window, _glfw.x11.restoreCursorPosX, _glfw.x11.restoreCursorPosY); updateCursorImage(window); } // Create the X11 window (and its colormap) // static bool createNativeWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, Visual* visual, int depth) { int width = wndconfig->width; int height = wndconfig->height; if (wndconfig->scaleToMonitor) { width *= (int)_glfw.x11.contentScaleX; height *= (int)_glfw.x11.contentScaleY; } // Create a colormap based on the visual used by the current context window->x11.colormap = XCreateColormap(_glfw.x11.display, _glfw.x11.root, visual, AllocNone); window->x11.transparent = _glfwIsVisualTransparentX11(visual); XSetWindowAttributes wa = { 0 }; wa.colormap = window->x11.colormap; wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask | ExposureMask | FocusChangeMask | VisibilityChangeMask | EnterWindowMask | LeaveWindowMask | PropertyChangeMask; _glfwGrabErrorHandlerX11(); window->x11.parent = _glfw.x11.root; window->x11.handle = XCreateWindow(_glfw.x11.display, _glfw.x11.root, 0, 0, // Position width, height, 0, // Border width depth, // Color depth InputOutput, visual, CWBorderPixel | CWColormap | CWEventMask, &wa); _glfwReleaseErrorHandlerX11(); if (!window->x11.handle) { _glfwInputErrorX11(GLFW_PLATFORM_ERROR, "X11: Failed to create window"); return false; } XSaveContext(_glfw.x11.display, window->x11.handle, _glfw.x11.context, (XPointer) window); if (!wndconfig->decorated) _glfwPlatformSetWindowDecorated(window, false); if (_glfw.x11.NET_WM_STATE && !window->monitor) { Atom states[3]; int count = 0; if (wndconfig->floating) { if (_glfw.x11.NET_WM_STATE_ABOVE) states[count++] = _glfw.x11.NET_WM_STATE_ABOVE; } if (wndconfig->maximized) { if (_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT && _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) { states[count++] = _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT; states[count++] = _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ; window->x11.maximized = true; } } if (count) { XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_STATE, XA_ATOM, 32, PropModeReplace, (unsigned char*) states, count); } } // Declare the WM protocols supported by GLFW { Atom protocols[] = { _glfw.x11.WM_DELETE_WINDOW, _glfw.x11.NET_WM_PING }; XSetWMProtocols(_glfw.x11.display, window->x11.handle, protocols, sizeof(protocols) / sizeof(Atom)); } // Declare our PID { const long pid = getpid(); XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_PID, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &pid, 1); } if (_glfw.x11.NET_WM_WINDOW_TYPE && _glfw.x11.NET_WM_WINDOW_TYPE_NORMAL) { Atom type = _glfw.x11.NET_WM_WINDOW_TYPE_NORMAL; XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_WINDOW_TYPE, XA_ATOM, 32, PropModeReplace, (unsigned char*) &type, 1); } // Set ICCCM WM_HINTS property { XWMHints* hints = XAllocWMHints(); if (!hints) { _glfwInputError(GLFW_OUT_OF_MEMORY, "X11: Failed to allocate WM hints"); return false; } hints->flags = StateHint; hints->initial_state = NormalState; XSetWMHints(_glfw.x11.display, window->x11.handle, hints); XFree(hints); } updateNormalHints(window, width, height); // Set ICCCM WM_CLASS property { XClassHint* hint = XAllocClassHint(); if (strlen(wndconfig->x11.instanceName) && strlen(wndconfig->x11.className)) { hint->res_name = (char*) wndconfig->x11.instanceName; hint->res_class = (char*) wndconfig->x11.className; } else { const char* resourceName = getenv("RESOURCE_NAME"); if (resourceName && strlen(resourceName)) hint->res_name = (char*) resourceName; else if (strlen(wndconfig->title)) hint->res_name = (char*) wndconfig->title; else hint->res_name = (char*) "glfw-application"; if (strlen(wndconfig->title)) hint->res_class = (char*) wndconfig->title; else hint->res_class = (char*) "GLFW-Application"; } XSetClassHint(_glfw.x11.display, window->x11.handle, hint); XFree(hint); } // Announce support for Xdnd (drag and drop) { const Atom version = _GLFW_XDND_VERSION; XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.XdndAware, XA_ATOM, 32, PropModeReplace, (unsigned char*) &version, 1); } _glfwPlatformSetWindowTitle(window, wndconfig->title); _glfwPlatformGetWindowPos(window, &window->x11.xpos, &window->x11.ypos); _glfwPlatformGetWindowSize(window, &window->x11.width, &window->x11.height); if (_glfw.hints.window.blur_radius > 0) _glfwPlatformSetWindowBlur(window, _glfw.hints.window.blur_radius); return true; } static size_t get_clipboard_data(const _GLFWClipboardData *cd, const char *mime, char **data) { *data = NULL; if (cd->get_data == NULL) { return 0; } GLFWDataChunk chunk = cd->get_data(mime, NULL, cd->ctype); char *buf = NULL; size_t sz = 0, cap = 0; void *iter = chunk.iter; if (!iter) return 0; while (true) { chunk = cd->get_data(mime, iter, cd->ctype); if (!chunk.sz) break; if (cap < sz + chunk.sz) { cap = MAX(cap * 2, sz + 4 * chunk.sz); buf = realloc(buf, cap * sizeof(buf[0])); } memcpy(buf + sz, chunk.data, chunk.sz); sz += chunk.sz; if (chunk.free) chunk.free((void*)chunk.free_data); } *data = buf; cd->get_data(NULL, iter, cd->ctype); return sz; } static void get_atom_names(const Atom *atoms, int count, char **atom_names) { _glfwGrabErrorHandlerX11(); XGetAtomNames(_glfw.x11.display, (Atom*)atoms, count, atom_names); _glfwReleaseErrorHandlerX11(); if (_glfw.x11.errorCode != Success) { for (int i = 0; i < count; i++) { _glfwGrabErrorHandlerX11(); atom_names[i] = XGetAtomName(_glfw.x11.display, atoms[i]); _glfwReleaseErrorHandlerX11(); if (_glfw.x11.errorCode != Success) atom_names[i] = NULL; } } } // Set the specified property to the selection converted to the requested target // static Atom writeTargetToProperty(const XSelectionRequestEvent* request) { const AtomArray *aa; const _GLFWClipboardData *cd; if (request->selection == _glfw.x11.PRIMARY) { aa = &_glfw.x11.primary_atoms; cd = &_glfw.primary; } else { aa = &_glfw.x11.clipboard_atoms; cd = &_glfw.clipboard; } if (request->property == None) { // The requester is a legacy client (ICCCM section 2.2) // We don't support legacy clients, so fail here return None; } if (request->target == _glfw.x11.TARGETS) { // The list of supported targets was requested Atom *targets = calloc(aa->sz + 2, sizeof(Atom)); targets[0] = _glfw.x11.TARGETS; targets[1] = _glfw.x11.MULTIPLE; for (size_t i = 0; i < aa->sz; i++) targets[i+2] = aa->array[i].atom; XChangeProperty(_glfw.x11.display, request->requestor, request->property, XA_ATOM, 32, PropModeReplace, (unsigned char*) targets, aa->sz + 2); free(targets); return request->property; } if (request->target == _glfw.x11.MULTIPLE) { // Multiple conversions were requested Atom* targets; size_t i, j, count; count = _glfwGetWindowPropertyX11(request->requestor, request->property, _glfw.x11.ATOM_PAIR, (unsigned char**) &targets); for (i = 0; i < count; i += 2) { for (j = 0; j < aa->sz; j++) { if (targets[i] == aa->array[j].atom) break; } if (j < aa->sz) { char *data = NULL; size_t sz = get_clipboard_data(cd, aa->array[j].mime, &data); if (data) XChangeProperty(_glfw.x11.display, request->requestor, targets[i + 1], targets[i], 8, PropModeReplace, (unsigned char *) data, sz); free(data); } else targets[i + 1] = None; } XChangeProperty(_glfw.x11.display, request->requestor, request->property, _glfw.x11.ATOM_PAIR, 32, PropModeReplace, (unsigned char*) targets, count); XFree(targets); return request->property; } if (request->target == _glfw.x11.SAVE_TARGETS) { // The request is a check whether we support SAVE_TARGETS // It should be handled as a no-op side effect target XChangeProperty(_glfw.x11.display, request->requestor, request->property, _glfw.x11.NULL_, 32, PropModeReplace, NULL, 0); return request->property; } // Conversion to a data target was requested for (size_t i = 0; i < aa->sz; i++) { if (request->target == aa->array[i].atom) { // The requested target is one we support char *data = NULL; size_t sz = get_clipboard_data(cd, aa->array[i].mime, &data); if (data) XChangeProperty(_glfw.x11.display, request->requestor, request->property, request->target, 8, PropModeReplace, (unsigned char *) data, sz); free(data); return request->property; } } // The requested target is not supported return None; } static void handleSelectionClear(XEvent* event) { if (event->xselectionclear.selection == _glfw.x11.PRIMARY) { _glfw_free_clipboard_data(&_glfw.primary); _glfwInputClipboardLost(GLFW_PRIMARY_SELECTION); } else { _glfw_free_clipboard_data(&_glfw.clipboard); _glfwInputClipboardLost(GLFW_CLIPBOARD); } } static void handleSelectionRequest(XEvent* event) { const XSelectionRequestEvent* request = &event->xselectionrequest; XEvent reply = { SelectionNotify }; reply.xselection.property = writeTargetToProperty(request); reply.xselection.display = request->display; reply.xselection.requestor = request->requestor; reply.xselection.selection = request->selection; reply.xselection.target = request->target; reply.xselection.time = request->time; XSendEvent(_glfw.x11.display, request->requestor, False, 0, &reply); } static void getSelectionString(Atom selection, Atom *targets, size_t num_targets, GLFWclipboardwritedatafun write_data, void *object, bool report_not_found) { #define XFREE(x) { if (x) XFree(x); x = NULL; } if (XGetSelectionOwner(_glfw.x11.display, selection) == _glfw.x11.helperWindowHandle) { write_data(object, NULL, 1); return; } bool found = false; for (size_t i = 0; !found && i < num_targets; i++) { char* data = NULL; Atom actualType = None; int actualFormat = 0; unsigned long itemCount = 0, bytesAfter = 0; monotonic_t start = glfwGetTime(); XEvent notification, dummy; XConvertSelection(_glfw.x11.display, selection, targets[i], _glfw.x11.GLFW_SELECTION, _glfw.x11.helperWindowHandle, CurrentTime); while (!XCheckTypedWindowEvent(_glfw.x11.display, _glfw.x11.helperWindowHandle, SelectionNotify, ¬ification)) { monotonic_t time = glfwGetTime(); if (time - start > s_to_monotonic_t(2ll)) return; waitForX11Event(s_to_monotonic_t(2ll) - (time - start)); } if (notification.xselection.property == None) continue; XCheckIfEvent(_glfw.x11.display, &dummy, isSelPropNewValueNotify, (XPointer) ¬ification); XGetWindowProperty(_glfw.x11.display, notification.xselection.requestor, notification.xselection.property, 0, LONG_MAX, True, AnyPropertyType, &actualType, &actualFormat, &itemCount, &bytesAfter, (unsigned char**) &data); if (actualType == _glfw.x11.INCR) { for (;;) { start = glfwGetTime(); while (!XCheckIfEvent(_glfw.x11.display, &dummy, isSelPropNewValueNotify, (XPointer) ¬ification)) { monotonic_t time = glfwGetTime(); if (time - start > s_to_monotonic_t(2ll)) { return; } waitForX11Event(s_to_monotonic_t(2ll) - (time - start)); } XFREE(data); XGetWindowProperty(_glfw.x11.display, notification.xselection.requestor, notification.xselection.property, 0, LONG_MAX, True, AnyPropertyType, &actualType, &actualFormat, &itemCount, &bytesAfter, (unsigned char**) &data); if (itemCount) { const char *string = data; if (targets[i] == XA_STRING) { string = convertLatin1toUTF8(data); itemCount = strlen(string); } bool ok = write_data(object, string, itemCount); if (string != data) free((void*)string); if (!ok) { XFREE(data); break; } } else { found = true; break; } } } else if (actualType == targets[i]) { if (targets[i] == XA_STRING) { const char *string = convertLatin1toUTF8(data); write_data(object, string, strlen(string)); free((void*)string); } else write_data(object, data, itemCount); found = true; } else if (actualType == XA_ATOM && targets[i] == _glfw.x11.TARGETS) { found = true; write_data(object, data, sizeof(Atom) * itemCount); } XFREE(data); } if (!found && report_not_found) { _glfwInputError(GLFW_FORMAT_UNAVAILABLE, "X11: Failed to convert selection to data from clipboard"); } #undef XFREE } // Make the specified window and its video mode active on its monitor // static void acquireMonitor(_GLFWwindow* window) { if (_glfw.x11.saver.count == 0) { // Remember old screen saver settings XGetScreenSaver(_glfw.x11.display, &_glfw.x11.saver.timeout, &_glfw.x11.saver.interval, &_glfw.x11.saver.blanking, &_glfw.x11.saver.exposure); // Disable screen saver XSetScreenSaver(_glfw.x11.display, 0, 0, DontPreferBlanking, DefaultExposures); } if (!window->monitor->window) _glfw.x11.saver.count++; _glfwSetVideoModeX11(window->monitor, &window->videoMode); _glfwInputMonitorWindow(window->monitor, window); } // Remove the window and restore the original video mode // static void releaseMonitor(_GLFWwindow* window) { if (window->monitor->window != window) return; _glfwInputMonitorWindow(window->monitor, NULL); _glfwRestoreVideoModeX11(window->monitor); _glfw.x11.saver.count--; if (_glfw.x11.saver.count == 0) { // Restore old screen saver settings XSetScreenSaver(_glfw.x11.display, _glfw.x11.saver.timeout, _glfw.x11.saver.interval, _glfw.x11.saver.blanking, _glfw.x11.saver.exposure); } } static void onConfigChange(void) { float xscale, yscale; _glfwGetSystemContentScaleX11(&xscale, &yscale, true); if (xscale != _glfw.x11.contentScaleX || yscale != _glfw.x11.contentScaleY) { _GLFWwindow* window = _glfw.windowListHead; _glfw.x11.contentScaleX = xscale; _glfw.x11.contentScaleY = yscale; while (window) { _glfwInputWindowContentScale(window, xscale, yscale); window = window->next; } } } // Process the specified X event // static void processEvent(XEvent *event) { static bool keymap_dirty = false; #define UPDATE_KEYMAP_IF_NEEDED if (keymap_dirty) { keymap_dirty = false; glfw_xkb_compile_keymap(&_glfw.x11.xkb, NULL); } if (_glfw.x11.randr.available) { if (event->type == _glfw.x11.randr.eventBase + RRNotify) { XRRUpdateConfiguration(event); _glfwPollMonitorsX11(); return; } } if (event->type == PropertyNotify && event->xproperty.window == _glfw.x11.root && event->xproperty.atom == _glfw.x11.RESOURCE_MANAGER) { onConfigChange(); return; } if (event->type == GenericEvent) { if (_glfw.x11.xi.available) { _GLFWwindow* window = _glfw.x11.disabledCursorWindow; if (window && window->rawMouseMotion && event->xcookie.extension == _glfw.x11.xi.majorOpcode && XGetEventData(_glfw.x11.display, &event->xcookie) && event->xcookie.evtype == XI_RawMotion) { XIRawEvent* re = event->xcookie.data; if (re->valuators.mask_len) { const double* values = re->raw_values; double xpos = window->virtualCursorPosX; double ypos = window->virtualCursorPosY; if (XIMaskIsSet(re->valuators.mask, 0)) { xpos += *values; values++; } if (XIMaskIsSet(re->valuators.mask, 1)) ypos += *values; _glfwInputCursorPos(window, xpos, ypos); } } XFreeEventData(_glfw.x11.display, &event->xcookie); } return; } if (event->type == SelectionClear) { handleSelectionClear(event); return; } else if (event->type == SelectionRequest) { handleSelectionRequest(event); return; } else if (event->type == _glfw.x11.xkb.eventBase) { XkbEvent *kb_event = (XkbEvent*)event; if (kb_event->any.device != (unsigned int)_glfw.x11.xkb.keyboard_device_id) return; switch(kb_event->any.xkb_type) { case XkbNewKeyboardNotify: { XkbNewKeyboardNotifyEvent *newkb_event = (XkbNewKeyboardNotifyEvent*)kb_event; if (_glfw.hints.init.debugKeyboard) printf( "Got XkbNewKeyboardNotify event with changes: key codes: %d geometry: %d device id: %d\n", !!(newkb_event->changed & XkbNKN_KeycodesMask), !!(newkb_event->changed & XkbNKN_GeometryMask), !!(newkb_event->changed & XkbNKN_DeviceIDMask)); if (newkb_event->changed & XkbNKN_DeviceIDMask) { keymap_dirty = true; if (!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return; } if (newkb_event->changed & XkbNKN_KeycodesMask) { keymap_dirty = true; } return; } case XkbMapNotify: { if (_glfw.hints.init.debugKeyboard) printf("Got XkbMapNotify event, keymaps will be reloaded\n"); keymap_dirty = true; return; } case XkbStateNotify: { UPDATE_KEYMAP_IF_NEEDED; XkbStateNotifyEvent *state_event = (XkbStateNotifyEvent*)kb_event; glfw_xkb_update_modifiers( &_glfw.x11.xkb, state_event->base_mods, state_event->latched_mods, state_event->locked_mods, state_event->base_group, state_event->latched_group, state_event->locked_group ); return; } } return; } _GLFWwindow* window = NULL; if (XFindContext(_glfw.x11.display, event->xany.window, _glfw.x11.context, (XPointer*) &window) != 0) { // This is an event for a window that has already been destroyed return; } switch (event->type) { case ReparentNotify: { window->x11.parent = event->xreparent.parent; return; } case KeyPress: { UPDATE_KEYMAP_IF_NEEDED; glfw_xkb_handle_key_event(window, &_glfw.x11.xkb, event->xkey.keycode, GLFW_PRESS); return; } case KeyRelease: { UPDATE_KEYMAP_IF_NEEDED; if (!_glfw.x11.xkb.detectable) { // HACK: Key repeat events will arrive as KeyRelease/KeyPress // pairs with similar or identical time stamps // The key repeat logic in _glfwInputKey expects only key // presses to repeat, so detect and discard release events if (XEventsQueued(_glfw.x11.display, QueuedAfterReading)) { XEvent next; XPeekEvent(_glfw.x11.display, &next); if (next.type == KeyPress && next.xkey.window == event->xkey.window && next.xkey.keycode == event->xkey.keycode) { // HACK: The time of repeat events sometimes doesn't // match that of the press event, so add an // epsilon // Toshiyuki Takahashi can press a button // 16 times per second so it's fairly safe to // assume that no human is pressing the key 50 // times per second (value is ms) if ((next.xkey.time - event->xkey.time) < 20) { // This is very likely a server-generated key repeat // event, so ignore it return; } } } } glfw_xkb_handle_key_event(window, &_glfw.x11.xkb, event->xkey.keycode, GLFW_RELEASE); return; } case ButtonPress: { const int mods = translateState(event->xbutton.state); if (event->xbutton.button == Button1) _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, mods); else if (event->xbutton.button == Button2) _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, mods); else if (event->xbutton.button == Button3) _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS, mods); // Modern X provides scroll events as mouse button presses else if (event->xbutton.button == Button4) _glfwInputScroll(window, 0.0, 1.0, 0, mods); else if (event->xbutton.button == Button5) _glfwInputScroll(window, 0.0, -1.0, 0, mods); else if (event->xbutton.button == Button6) _glfwInputScroll(window, 1.0, 0.0, 0, mods); else if (event->xbutton.button == Button7) _glfwInputScroll(window, -1.0, 0.0, 0, mods); else { // Additional buttons after 7 are treated as regular buttons // We subtract 4 to fill the gap left by scroll input above _glfwInputMouseClick(window, event->xbutton.button - Button1 - 4, GLFW_PRESS, mods); } return; } case ButtonRelease: { const int mods = translateState(event->xbutton.state); if (event->xbutton.button == Button1) { _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, mods); } else if (event->xbutton.button == Button2) { _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_RELEASE, mods); } else if (event->xbutton.button == Button3) { _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_RELEASE, mods); } else if (event->xbutton.button > Button7) { // Additional buttons after 7 are treated as regular buttons // We subtract 4 to fill the gap left by scroll input above _glfwInputMouseClick(window, event->xbutton.button - Button1 - 4, GLFW_RELEASE, mods); } return; } case EnterNotify: { // XEnterWindowEvent is XCrossingEvent const int x = event->xcrossing.x; const int y = event->xcrossing.y; // HACK: This is a workaround for WMs (KWM, Fluxbox) that otherwise // ignore the defined cursor for hidden cursor mode if (window->cursorMode == GLFW_CURSOR_HIDDEN) updateCursorImage(window); _glfwInputCursorEnter(window, true); _glfwInputCursorPos(window, x, y); window->x11.lastCursorPosX = x; window->x11.lastCursorPosY = y; return; } case LeaveNotify: { _glfwInputCursorEnter(window, false); return; } case MotionNotify: { const int x = event->xmotion.x; const int y = event->xmotion.y; if (x != window->x11.warpCursorPosX || y != window->x11.warpCursorPosY) { // The cursor was moved by something other than GLFW if (window->cursorMode == GLFW_CURSOR_DISABLED) { if (_glfw.x11.disabledCursorWindow != window) return; if (window->rawMouseMotion) return; const int dx = x - window->x11.lastCursorPosX; const int dy = y - window->x11.lastCursorPosY; _glfwInputCursorPos(window, window->virtualCursorPosX + dx, window->virtualCursorPosY + dy); } else _glfwInputCursorPos(window, x, y); } window->x11.lastCursorPosX = x; window->x11.lastCursorPosY = y; return; } case ConfigureNotify: { if (event->xconfigure.width != window->x11.width || event->xconfigure.height != window->x11.height) { _glfwInputFramebufferSize(window, event->xconfigure.width, event->xconfigure.height); _glfwInputWindowSize(window, event->xconfigure.width, event->xconfigure.height); window->x11.width = event->xconfigure.width; window->x11.height = event->xconfigure.height; } int xpos = event->xconfigure.x; int ypos = event->xconfigure.y; // NOTE: ConfigureNotify events from the server are in local // coordinates, so if we are reparented we need to translate // the position into root (screen) coordinates if (!event->xany.send_event && window->x11.parent != _glfw.x11.root) { Window dummy; _glfwGrabErrorHandlerX11(); XTranslateCoordinates(_glfw.x11.display, window->x11.parent, _glfw.x11.root, xpos, ypos, &xpos, &ypos, &dummy); _glfwReleaseErrorHandlerX11(); if (_glfw.x11.errorCode != Success) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to translate ConfigureNotiy co-ords for reparented window"); return; } } if (xpos != window->x11.xpos || ypos != window->x11.ypos) { _glfwInputWindowPos(window, xpos, ypos); window->x11.xpos = xpos; window->x11.ypos = ypos; } return; } case ClientMessage: { // Custom client message, probably from the window manager if (event->xclient.message_type == None) return; if (event->xclient.message_type == _glfw.x11.WM_PROTOCOLS) { const Atom protocol = event->xclient.data.l[0]; if (protocol == None) return; if (protocol == _glfw.x11.WM_DELETE_WINDOW) { // The window manager was asked to close the window, for // example by the user pressing a 'close' window decoration // button _glfwInputWindowCloseRequest(window); } else if (protocol == _glfw.x11.NET_WM_PING) { // The window manager is pinging the application to ensure // it's still responding to events XEvent reply = *event; reply.xclient.window = _glfw.x11.root; XSendEvent(_glfw.x11.display, _glfw.x11.root, False, SubstructureNotifyMask | SubstructureRedirectMask, &reply); } } else if (event->xclient.message_type == _glfw.x11.XdndEnter) { // A drag operation has entered the window unsigned long i, count; Atom* formats = NULL; const bool list = event->xclient.data.l[1] & 1; _glfw.x11.xdnd.source = event->xclient.data.l[0]; _glfw.x11.xdnd.version = event->xclient.data.l[1] >> 24; memset(_glfw.x11.xdnd.format, 0, sizeof(_glfw.x11.xdnd.format)); _glfw.x11.xdnd.format_priority = 0; if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) return; if (list) { count = _glfwGetWindowPropertyX11(_glfw.x11.xdnd.source, _glfw.x11.XdndTypeList, XA_ATOM, (unsigned char**) &formats); } else { count = 3; formats = (Atom*) event->xclient.data.l + 2; } char **atom_names = calloc(count, sizeof(char*)); if (atom_names) { get_atom_names(formats, count, atom_names); for (i = 0; i < count; i++) { if (atom_names[i]) { int prio = _glfwInputDrop(window, atom_names[i], NULL, 0); if (prio > _glfw.x11.xdnd.format_priority) { _glfw.x11.xdnd.format_priority = prio; strncpy(_glfw.x11.xdnd.format, atom_names[i], arraysz(_glfw.x11.xdnd.format) - 1); } XFree(atom_names[i]); } } free(atom_names); } if (list && formats) XFree(formats); } else if (event->xclient.message_type == _glfw.x11.XdndDrop) { // The drag operation has finished by dropping on the window Time time = CurrentTime; if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) return; if (_glfw.x11.xdnd.format_priority > 0) { if (_glfw.x11.xdnd.version >= 1) time = event->xclient.data.l[2]; // Request the chosen format from the source window XConvertSelection(_glfw.x11.display, _glfw.x11.XdndSelection, XInternAtom(_glfw.x11.display, _glfw.x11.xdnd.format, 0), _glfw.x11.XdndSelection, window->x11.handle, time); } else if (_glfw.x11.xdnd.version >= 2) { XEvent reply = { ClientMessage }; reply.xclient.window = _glfw.x11.xdnd.source; reply.xclient.message_type = _glfw.x11.XdndFinished; reply.xclient.format = 32; reply.xclient.data.l[0] = window->x11.handle; reply.xclient.data.l[1] = 0; // The drag was rejected reply.xclient.data.l[2] = None; XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source, False, NoEventMask, &reply); XFlush(_glfw.x11.display); } } else if (event->xclient.message_type == _glfw.x11.XdndPosition) { // The drag operation has moved over the window const int xabs = (event->xclient.data.l[2] >> 16) & 0xffff; const int yabs = (event->xclient.data.l[2]) & 0xffff; Window dummy; int xpos = 0, ypos = 0; if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) return; _glfwGrabErrorHandlerX11(); XTranslateCoordinates(_glfw.x11.display, _glfw.x11.root, window->x11.handle, xabs, yabs, &xpos, &ypos, &dummy); _glfwReleaseErrorHandlerX11(); if (_glfw.x11.errorCode != Success) _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to get DND event position"); _glfwInputCursorPos(window, xpos, ypos); XEvent reply = { ClientMessage }; reply.xclient.window = _glfw.x11.xdnd.source; reply.xclient.message_type = _glfw.x11.XdndStatus; reply.xclient.format = 32; reply.xclient.data.l[0] = window->x11.handle; reply.xclient.data.l[2] = 0; // Specify an empty rectangle reply.xclient.data.l[3] = 0; if (_glfw.x11.xdnd.format_priority > 0) { // Reply that we are ready to copy the dragged data reply.xclient.data.l[1] = 1; // Accept with no rectangle if (_glfw.x11.xdnd.version >= 2) reply.xclient.data.l[4] = _glfw.x11.XdndActionCopy; } XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source, False, NoEventMask, &reply); XFlush(_glfw.x11.display); } return; } case SelectionNotify: { if (event->xselection.property == _glfw.x11.XdndSelection) { // The converted data from the drag operation has arrived char* data; const unsigned long result = _glfwGetWindowPropertyX11(event->xselection.requestor, event->xselection.property, event->xselection.target, (unsigned char**) &data); if (result) { _glfwInputDrop(window, _glfw.x11.xdnd.format, data, result); } if (data) XFree(data); if (_glfw.x11.xdnd.version >= 2) { XEvent reply = { ClientMessage }; reply.xclient.window = _glfw.x11.xdnd.source; reply.xclient.message_type = _glfw.x11.XdndFinished; reply.xclient.format = 32; reply.xclient.data.l[0] = window->x11.handle; reply.xclient.data.l[1] = result; reply.xclient.data.l[2] = _glfw.x11.XdndActionCopy; XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source, False, NoEventMask, &reply); XFlush(_glfw.x11.display); } } return; } case FocusIn: { if (event->xfocus.mode == NotifyGrab || event->xfocus.mode == NotifyUngrab) { // Ignore focus events from popup indicator windows, window menu // key chords and window dragging return; } if (window->cursorMode == GLFW_CURSOR_DISABLED) disableCursor(window); _glfwInputWindowFocus(window, true); return; } case FocusOut: { if (event->xfocus.mode == NotifyGrab || event->xfocus.mode == NotifyUngrab) { // Ignore focus events from popup indicator windows, window menu // key chords and window dragging return; } if (window->cursorMode == GLFW_CURSOR_DISABLED) enableCursor(window); if (window->monitor && window->autoIconify) _glfwPlatformIconifyWindow(window); _glfwInputWindowFocus(window, false); return; } case Expose: { _glfwInputWindowDamage(window); return; } case PropertyNotify: { if (event->xproperty.state != PropertyNewValue) return; if (event->xproperty.atom == _glfw.x11.WM_STATE) { const int state = getWindowState(window); if (state != IconicState && state != NormalState) return; const bool iconified = (state == IconicState); if (window->x11.iconified != iconified) { if (window->monitor) { if (iconified) releaseMonitor(window); else acquireMonitor(window); } window->x11.iconified = iconified; _glfwInputWindowIconify(window, iconified); } } else if (event->xproperty.atom == _glfw.x11.NET_WM_STATE) { const bool maximized = _glfwPlatformWindowMaximized(window); if (window->x11.maximized != maximized) { window->x11.maximized = maximized; int width, height; _glfwPlatformGetWindowSize(window, &width, &height); updateNormalHints(window, width, height); _glfwInputWindowMaximize(window, maximized); } } return; } case DestroyNotify: return; } #undef UPDATE_KEYMAP_IF_NEEDED } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// // Retrieve a single window property of the specified type // Inspired by fghGetWindowProperty from freeglut // unsigned long _glfwGetWindowPropertyX11(Window window, Atom property, Atom type, unsigned char** value) { Atom actualType; int actualFormat; unsigned long itemCount, bytesAfter; XGetWindowProperty(_glfw.x11.display, window, property, 0, LONG_MAX, False, type, &actualType, &actualFormat, &itemCount, &bytesAfter, value); return itemCount; } bool _glfwIsVisualTransparentX11(Visual* visual) { if (!_glfw.x11.xrender.available) return false; XRenderPictFormat* pf = XRenderFindVisualFormat(_glfw.x11.display, visual); return pf && pf->direct.alphaMask; } // Push contents of our selection to clipboard manager // void _glfwPushSelectionToManagerX11(void) { XConvertSelection(_glfw.x11.display, _glfw.x11.CLIPBOARD_MANAGER, _glfw.x11.SAVE_TARGETS, None, _glfw.x11.helperWindowHandle, CurrentTime); for (;;) { XEvent event; while (XCheckIfEvent(_glfw.x11.display, &event, isSelectionEvent, NULL)) { switch (event.type) { case SelectionRequest: handleSelectionRequest(&event); break; case SelectionClear: handleSelectionClear(&event); break; case SelectionNotify: { if (event.xselection.target == _glfw.x11.SAVE_TARGETS) { // This means one of two things; either the selection // was not owned, which means there is no clipboard // manager, or the transfer to the clipboard manager has // completed // In either case, it means we are done here return; } break; } } } waitForX11Event(-1); } } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { Visual* visual = NULL; int depth; if (ctxconfig->client != GLFW_NO_API) { if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) { if (!_glfwInitGLX()) return false; if (!_glfwChooseVisualGLX(wndconfig, ctxconfig, fbconfig, &visual, &depth)) return false; } else if (ctxconfig->source == GLFW_EGL_CONTEXT_API) { if (!_glfwInitEGL()) return false; if (!_glfwChooseVisualEGL(wndconfig, ctxconfig, fbconfig, &visual, &depth)) return false; } else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) { if (!_glfwInitOSMesa()) return false; } } if (!visual) { visual = DefaultVisual(_glfw.x11.display, _glfw.x11.screen); depth = DefaultDepth(_glfw.x11.display, _glfw.x11.screen); } if (!createNativeWindow(window, wndconfig, visual, depth)) return false; if (ctxconfig->client != GLFW_NO_API) { if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) { if (!_glfwCreateContextGLX(window, ctxconfig, fbconfig)) return false; } else if (ctxconfig->source == GLFW_EGL_CONTEXT_API) { if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) return false; } else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) { if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) return false; } } if (window->monitor) { _glfwPlatformShowWindow(window); updateWindowMode(window); acquireMonitor(window); } XFlush(_glfw.x11.display); return true; } void _glfwPlatformDestroyWindow(_GLFWwindow* window) { if (_glfw.x11.disabledCursorWindow == window) _glfw.x11.disabledCursorWindow = NULL; if (window->monitor) releaseMonitor(window); if (window->context.destroy) window->context.destroy(window); if (window->x11.handle) { XDeleteContext(_glfw.x11.display, window->x11.handle, _glfw.x11.context); XUnmapWindow(_glfw.x11.display, window->x11.handle); XDestroyWindow(_glfw.x11.display, window->x11.handle); window->x11.handle = (Window) 0; } if (window->x11.colormap) { XFreeColormap(_glfw.x11.display, window->x11.colormap); window->x11.colormap = (Colormap) 0; } XFlush(_glfw.x11.display); } void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) { #if defined(X_HAVE_UTF8_STRING) Xutf8SetWMProperties(_glfw.x11.display, window->x11.handle, title, title, NULL, 0, NULL, NULL, NULL); #else // This may be a slightly better fallback than using XStoreName and // XSetIconName, which always store their arguments using STRING XmbSetWMProperties(_glfw.x11.display, window->x11.handle, title, title, NULL, 0, NULL, NULL, NULL); #endif XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_NAME, _glfw.x11.UTF8_STRING, 8, PropModeReplace, (unsigned char*) title, strlen(title)); XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_ICON_NAME, _glfw.x11.UTF8_STRING, 8, PropModeReplace, (unsigned char*) title, strlen(title)); XFlush(_glfw.x11.display); } void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const GLFWimage* images) { if (count) { int i, j, longCount = 0; for (i = 0; i < count; i++) longCount += 2 + images[i].width * images[i].height; unsigned long* icon = calloc(longCount, sizeof(unsigned long)); unsigned long* target = icon; for (i = 0; i < count; i++) { *target++ = images[i].width; *target++ = images[i].height; for (j = 0; j < images[i].width * images[i].height; j++) { unsigned char *p = images->pixels + j * 4; const unsigned char r = *p++, g = *p++, b = *p++, a = *p++; *target++ = a << 24 | (r << 16) | (g << 8) | b; } } XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) icon, longCount); free(icon); } else { XDeleteProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_ICON); } XFlush(_glfw.x11.display); } void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) { Window dummy; int x = 0, y = 0; _glfwGrabErrorHandlerX11(); XTranslateCoordinates(_glfw.x11.display, window->x11.handle, _glfw.x11.root, 0, 0, &x, &y, &dummy); _glfwReleaseErrorHandlerX11(); if (_glfw.x11.errorCode != Success) _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to get window position"); if (xpos) *xpos = x; if (ypos) *ypos = y; } void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos) { // HACK: Explicitly setting PPosition to any value causes some WMs, notably // Compiz and Metacity, to honor the position of unmapped windows if (!_glfwPlatformWindowVisible(window)) { long supplied; XSizeHints* hints = XAllocSizeHints(); if (XGetWMNormalHints(_glfw.x11.display, window->x11.handle, hints, &supplied)) { hints->flags |= PPosition; hints->x = hints->y = 0; XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints); } XFree(hints); } XMoveWindow(_glfw.x11.display, window->x11.handle, xpos, ypos); XFlush(_glfw.x11.display); } void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) { XWindowAttributes attribs; XGetWindowAttributes(_glfw.x11.display, window->x11.handle, &attribs); if (width) *width = attribs.width; if (height) *height = attribs.height; } void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) { if (window->monitor) { if (window->monitor->window == window) acquireMonitor(window); } else { if (!window->resizable) updateNormalHints(window, width, height); XResizeWindow(_glfw.x11.display, window->x11.handle, width, height); } XFlush(_glfw.x11.display); } void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, int minwidth UNUSED, int minheight UNUSED, int maxwidth UNUSED, int maxheight UNUSED) { int width, height; _glfwPlatformGetWindowSize(window, &width, &height); updateNormalHints(window, width, height); XFlush(_glfw.x11.display); } void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer UNUSED, int denom UNUSED) { int width, height; _glfwPlatformGetWindowSize(window, &width, &height); updateNormalHints(window, width, height); XFlush(_glfw.x11.display); } void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr UNUSED, int heightincr UNUSED) { int width, height; _glfwPlatformGetWindowSize(window, &width, &height); updateNormalHints(window, width, height); XFlush(_glfw.x11.display); } void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) { _glfwPlatformGetWindowSize(window, width, height); } void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom) { long* extents = NULL; if (window->monitor || !window->decorated) return; if (_glfw.x11.NET_FRAME_EXTENTS == None) return; if (!_glfwPlatformWindowVisible(window) && _glfw.x11.NET_REQUEST_FRAME_EXTENTS) { XEvent event; // Ensure _NET_FRAME_EXTENTS is set, allowing glfwGetWindowFrameSize to // function before the window is mapped sendEventToWM(window, _glfw.x11.NET_REQUEST_FRAME_EXTENTS, 0, 0, 0, 0, 0); // HACK: Use a timeout because earlier versions of some window managers // (at least Unity, Fluxbox and Xfwm) failed to send the reply // They have been fixed but broken versions are still in the wild // If you are affected by this and your window manager is NOT // listed above, PLEASE report it to their and our issue trackers while (!XCheckIfEvent(_glfw.x11.display, &event, isFrameExtentsEvent, (XPointer) window)) { if (!waitForX11Event(ms_to_monotonic_t(500ll))) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: The window manager has a broken _NET_REQUEST_FRAME_EXTENTS implementation; please report this issue"); return; } } } if (_glfwGetWindowPropertyX11(window->x11.handle, _glfw.x11.NET_FRAME_EXTENTS, XA_CARDINAL, (unsigned char**) &extents) == 4) { if (left) *left = extents[0]; if (top) *top = extents[2]; if (right) *right = extents[1]; if (bottom) *bottom = extents[3]; } if (extents) XFree(extents); } void _glfwPlatformGetWindowContentScale(_GLFWwindow* window UNUSED, float* xscale, float* yscale) { if (xscale) *xscale = _glfw.x11.contentScaleX; if (yscale) *yscale = _glfw.x11.contentScaleY; } monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED) { return ms_to_monotonic_t(500ll); } void _glfwPlatformIconifyWindow(_GLFWwindow* window) { XIconifyWindow(_glfw.x11.display, window->x11.handle, _glfw.x11.screen); XFlush(_glfw.x11.display); } void _glfwPlatformRestoreWindow(_GLFWwindow* window) { if (_glfwPlatformWindowIconified(window)) { XMapWindow(_glfw.x11.display, window->x11.handle); waitForVisibilityNotify(window); } else if (_glfwPlatformWindowVisible(window)) { if (_glfw.x11.NET_WM_STATE && _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT && _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) { sendEventToWM(window, _glfw.x11.NET_WM_STATE, _NET_WM_STATE_REMOVE, _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT, _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ, 1, 0); } } XFlush(_glfw.x11.display); } void _glfwPlatformMaximizeWindow(_GLFWwindow* window) { if (!_glfw.x11.NET_WM_STATE || !_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT || !_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) { return; } if (_glfwPlatformWindowVisible(window)) { sendEventToWM(window, _glfw.x11.NET_WM_STATE, _NET_WM_STATE_ADD, _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT, _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ, 1, 0); } else { Atom* states = NULL; unsigned long count = _glfwGetWindowPropertyX11(window->x11.handle, _glfw.x11.NET_WM_STATE, XA_ATOM, (unsigned char**) &states); // NOTE: We don't check for failure as this property may not exist yet // and that's fine (and we'll create it implicitly with append) Atom missing[2] = { _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT, _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ }; unsigned long missingCount = 2; for (unsigned long i = 0; i < count; i++) { for (unsigned long j = 0; j < missingCount; j++) { if (states[i] == missing[j]) { missing[j] = missing[missingCount - 1]; missingCount--; } } } if (states) XFree(states); if (!missingCount) return; XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_STATE, XA_ATOM, 32, PropModeAppend, (unsigned char*) missing, missingCount); } XFlush(_glfw.x11.display); } void _glfwPlatformShowWindow(_GLFWwindow* window) { if (_glfwPlatformWindowVisible(window)) return; XMapWindow(_glfw.x11.display, window->x11.handle); waitForVisibilityNotify(window); } void _glfwPlatformHideWindow(_GLFWwindow* window) { XUnmapWindow(_glfw.x11.display, window->x11.handle); XFlush(_glfw.x11.display); } void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) { if (!_glfw.x11.NET_WM_STATE || !_glfw.x11.NET_WM_STATE_DEMANDS_ATTENTION) return; sendEventToWM(window, _glfw.x11.NET_WM_STATE, _NET_WM_STATE_ADD, _glfw.x11.NET_WM_STATE_DEMANDS_ATTENTION, 0, 1, 0); } int _glfwPlatformWindowBell(_GLFWwindow* window) { return XkbBell(_glfw.x11.display, window->x11.handle, 100, (Atom)0) ? true : false; } void _glfwPlatformFocusWindow(_GLFWwindow* window) { if (_glfw.x11.NET_ACTIVE_WINDOW) sendEventToWM(window, _glfw.x11.NET_ACTIVE_WINDOW, 1, 0, 0, 0, 0); else if (_glfwPlatformWindowVisible(window)) { XRaiseWindow(_glfw.x11.display, window->x11.handle); XSetInputFocus(_glfw.x11.display, window->x11.handle, RevertToParent, CurrentTime); } XFlush(_glfw.x11.display); } void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate UNUSED) { if (window->monitor == monitor) { if (monitor) { if (monitor->window == window) acquireMonitor(window); } else { if (!window->resizable) updateNormalHints(window, width, height); XMoveResizeWindow(_glfw.x11.display, window->x11.handle, xpos, ypos, width, height); } XFlush(_glfw.x11.display); return; } if (window->monitor) releaseMonitor(window); _glfwInputWindowMonitor(window, monitor); updateNormalHints(window, width, height); if (window->monitor) { if (!_glfwPlatformWindowVisible(window)) { XMapRaised(_glfw.x11.display, window->x11.handle); waitForVisibilityNotify(window); } updateWindowMode(window); acquireMonitor(window); } else { updateWindowMode(window); XMoveResizeWindow(_glfw.x11.display, window->x11.handle, xpos, ypos, width, height); } XFlush(_glfw.x11.display); } int _glfwPlatformWindowFocused(_GLFWwindow* window) { Window focused; int state; XGetInputFocus(_glfw.x11.display, &focused, &state); return window->x11.handle == focused; } int _glfwPlatformWindowOccluded(_GLFWwindow* window UNUSED) { return false; } int _glfwPlatformWindowIconified(_GLFWwindow* window) { return getWindowState(window) == IconicState; } int _glfwPlatformWindowVisible(_GLFWwindow* window) { XWindowAttributes wa; XGetWindowAttributes(_glfw.x11.display, window->x11.handle, &wa); return wa.map_state == IsViewable; } int _glfwPlatformWindowMaximized(_GLFWwindow* window) { Atom* states; unsigned long i; bool maximized = false; if (!_glfw.x11.NET_WM_STATE || !_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT || !_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) { return maximized; } const unsigned long count = _glfwGetWindowPropertyX11(window->x11.handle, _glfw.x11.NET_WM_STATE, XA_ATOM, (unsigned char**) &states); for (i = 0; i < count; i++) { if (states[i] == _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT || states[i] == _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ) { maximized = true; break; } } if (states) XFree(states); return maximized; } int _glfwPlatformWindowHovered(_GLFWwindow* window) { Window w = _glfw.x11.root; while (w) { Window root; int rootX, rootY, childX, childY; unsigned int mask; _glfwGrabErrorHandlerX11(); const Bool result = XQueryPointer(_glfw.x11.display, w, &root, &w, &rootX, &rootY, &childX, &childY, &mask); _glfwReleaseErrorHandlerX11(); if (_glfw.x11.errorCode == BadWindow) w = _glfw.x11.root; else if (!result) return false; else if (w == window->x11.handle) return true; } return false; } int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) { if (!window->x11.transparent) return false; return XGetSelectionOwner(_glfw.x11.display, _glfw.x11.NET_WM_CM_Sx) != None; } void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled UNUSED) { int width, height; _glfwPlatformGetWindowSize(window, &width, &height); updateNormalHints(window, width, height); } void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled) { struct { unsigned long flags; unsigned long functions; unsigned long decorations; long input_mode; unsigned long status; } hints = {0}; hints.flags = MWM_HINTS_DECORATIONS; hints.decorations = enabled ? MWM_DECOR_ALL : 0; XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.MOTIF_WM_HINTS, _glfw.x11.MOTIF_WM_HINTS, 32, PropModeReplace, (unsigned char*) &hints, sizeof(hints) / sizeof(long)); } void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled) { if (!_glfw.x11.NET_WM_STATE || !_glfw.x11.NET_WM_STATE_ABOVE) return; if (_glfwPlatformWindowVisible(window)) { const long action = enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; sendEventToWM(window, _glfw.x11.NET_WM_STATE, action, _glfw.x11.NET_WM_STATE_ABOVE, 0, 1, 0); } else { Atom* states = NULL; unsigned long i, count; count = _glfwGetWindowPropertyX11(window->x11.handle, _glfw.x11.NET_WM_STATE, XA_ATOM, (unsigned char**) &states); // NOTE: We don't check for failure as this property may not exist yet // and that's fine (and we'll create it implicitly with append) if (enabled) { for (i = 0; i < count; i++) { if (states[i] == _glfw.x11.NET_WM_STATE_ABOVE) break; } if (i < count) return; XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_STATE, XA_ATOM, 32, PropModeAppend, (unsigned char*) &_glfw.x11.NET_WM_STATE_ABOVE, 1); } else if (states) { for (i = 0; i < count; i++) { if (states[i] == _glfw.x11.NET_WM_STATE_ABOVE) break; } if (i == count) return; states[i] = states[count - 1]; count--; XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_STATE, XA_ATOM, 32, PropModeReplace, (unsigned char*) states, count); } if (states) XFree(states); } XFlush(_glfw.x11.display); } void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled) { if (!_glfw.x11.xshape.available) return; if (enabled) { Region region = XCreateRegion(); XShapeCombineRegion(_glfw.x11.display, window->x11.handle, ShapeInput, 0, 0, region, ShapeSet); XDestroyRegion(region); } else { XShapeCombineMask(_glfw.x11.display, window->x11.handle, ShapeInput, 0, 0, None, ShapeSet); } } float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) { float opacity = 1.f; if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.NET_WM_CM_Sx)) { CARD32* value = NULL; if (_glfwGetWindowPropertyX11(window->x11.handle, _glfw.x11.NET_WM_WINDOW_OPACITY, XA_CARDINAL, (unsigned char**) &value)) { opacity = (float) (*value / (double) 0xffffffffu); } if (value) XFree(value); } return opacity; } void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) { const CARD32 value = (CARD32) (0xffffffffu * (double) opacity); XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &value, 1); } static unsigned dispatch_x11_queued_events(int num_events) { unsigned dispatched = num_events > 0 ? num_events : 0; while (num_events-- > 0) { XEvent event; XNextEvent(_glfw.x11.display, &event); processEvent(&event); } return dispatched; } static unsigned _glfwDispatchX11Events(void) { _GLFWwindow* window; unsigned dispatched = 0; #if defined(__linux__) if (_glfw.joysticksInitialized) _glfwDetectJoystickConnectionLinux(); #endif dispatched += dispatch_x11_queued_events(XEventsQueued(_glfw.x11.display, QueuedAfterFlush)); window = _glfw.x11.disabledCursorWindow; if (window) { int width, height; _glfwPlatformGetWindowSize(window, &width, &height); // NOTE: Re-center the cursor only if it has moved since the last call, // to avoid breaking glfwWaitEvents with MotionNotify if (window->x11.lastCursorPosX != width / 2 || window->x11.lastCursorPosY != height / 2) { _glfwPlatformSetCursorPos(window, width / 2.f, height / 2.f); } } XFlush(_glfw.x11.display); // XFlush can cause events to be queued, we don't use QueuedAfterFlush here // as something might have inserted events into the queue, but we want to guarantee // a flush. dispatched += dispatch_x11_queued_events(XEventsQueued(_glfw.x11.display, QueuedAlready)); return dispatched; } void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, bool enabled) { if (!_glfw.x11.xi.available) return; if (_glfw.x11.disabledCursorWindow != window) return; if (enabled) enableRawMouseMotion(window); else disableRawMouseMotion(window); } bool _glfwPlatformRawMouseMotionSupported(void) { return _glfw.x11.xi.available; } void _glfwPlatformPollEvents(void) { _glfwDispatchX11Events(); handleEvents(0); } void _glfwPlatformWaitEvents(void) { monotonic_t timeout = _glfwDispatchX11Events() ? 0 : -1; handleEvents(timeout); } void _glfwPlatformWaitEventsTimeout(monotonic_t timeout) { if (_glfwDispatchX11Events()) timeout = 0; handleEvents(timeout); } void _glfwPlatformPostEmptyEvent(void) { wakeupEventLoop(&_glfw.x11.eventLoopData); } void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) { Window root, child; int rootX, rootY, childX, childY; unsigned int mask; XQueryPointer(_glfw.x11.display, window->x11.handle, &root, &child, &rootX, &rootY, &childX, &childY, &mask); if (xpos) *xpos = childX; if (ypos) *ypos = childY; } void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) { // Store the new position so it can be recognized later window->x11.warpCursorPosX = (int) x; window->x11.warpCursorPosY = (int) y; XWarpPointer(_glfw.x11.display, None, window->x11.handle, 0,0,0,0, (int) x, (int) y); XFlush(_glfw.x11.display); } void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) { if (mode == GLFW_CURSOR_DISABLED) { if (_glfwPlatformWindowFocused(window)) disableCursor(window); } else if (_glfw.x11.disabledCursorWindow == window) enableCursor(window); else updateCursorImage(window); XFlush(_glfw.x11.display); } const char* _glfwPlatformGetNativeKeyName(int native_key) { return glfw_xkb_keysym_name(native_key); } int _glfwPlatformGetNativeKeyForKey(uint32_t key) { return glfw_xkb_sym_for_key(key); } int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot, int count UNUSED) { cursor->x11.handle = _glfwCreateCursorX11(image, xhot, yhot); if (!cursor->x11.handle) return false; return true; } static int set_cursor_from_font(_GLFWcursor* cursor, int native) { cursor->x11.handle = XCreateFontCursor(_glfw.x11.display, native); if (!cursor->x11.handle) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to create standard cursor"); return false; } return true; } static bool try_cursor_names(_GLFWcursor *cursor, int arg_count, ...) { va_list ap; va_start(ap, arg_count); const char *first_name = ""; for (int i = 0; i < arg_count; i++) { const char *name = va_arg(ap, const char *); first_name = name; cursor->x11.handle = XcursorLibraryLoadCursor(_glfw.x11.display, name); if (cursor->x11.handle) break; } va_end(ap); if (!cursor->x11.handle) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to load standard cursor: %s with %d aliases via Xcursor library", first_name, arg_count); return false; } return true; } int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape) { switch(shape) { /* start glfw to xc mapping (auto generated by gen-key-constants.py do not edit) */ case GLFW_DEFAULT_CURSOR: return set_cursor_from_font(cursor, XC_left_ptr); case GLFW_TEXT_CURSOR: return set_cursor_from_font(cursor, XC_xterm); case GLFW_POINTER_CURSOR: return set_cursor_from_font(cursor, XC_hand2); case GLFW_HELP_CURSOR: return set_cursor_from_font(cursor, XC_question_arrow); case GLFW_WAIT_CURSOR: return set_cursor_from_font(cursor, XC_clock); case GLFW_PROGRESS_CURSOR: return try_cursor_names(cursor, 3, "progress", "half-busy", "left_ptr_watch"); case GLFW_CROSSHAIR_CURSOR: return set_cursor_from_font(cursor, XC_tcross); case GLFW_CELL_CURSOR: return set_cursor_from_font(cursor, XC_plus); case GLFW_VERTICAL_TEXT_CURSOR: return try_cursor_names(cursor, 1, "vertical-text"); case GLFW_MOVE_CURSOR: return set_cursor_from_font(cursor, XC_fleur); case GLFW_E_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_right_side); case GLFW_NE_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_top_right_corner); case GLFW_NW_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_top_left_corner); case GLFW_N_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_top_side); case GLFW_SE_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_bottom_right_corner); case GLFW_SW_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_bottom_left_corner); case GLFW_S_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_bottom_side); case GLFW_W_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_left_side); case GLFW_EW_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_sb_h_double_arrow); case GLFW_NS_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_sb_v_double_arrow); case GLFW_NESW_RESIZE_CURSOR: return try_cursor_names(cursor, 3, "nesw-resize", "size_bdiag", "size-bdiag"); case GLFW_NWSE_RESIZE_CURSOR: return try_cursor_names(cursor, 3, "nwse-resize", "size_fdiag", "size-fdiag"); case GLFW_ZOOM_IN_CURSOR: return try_cursor_names(cursor, 2, "zoom-in", "zoom_in"); case GLFW_ZOOM_OUT_CURSOR: return try_cursor_names(cursor, 2, "zoom-out", "zoom_out"); case GLFW_ALIAS_CURSOR: return try_cursor_names(cursor, 1, "dnd-link"); case GLFW_COPY_CURSOR: return try_cursor_names(cursor, 1, "dnd-copy"); case GLFW_NOT_ALLOWED_CURSOR: return try_cursor_names(cursor, 3, "not-allowed", "forbidden", "crossed_circle"); case GLFW_NO_DROP_CURSOR: return try_cursor_names(cursor, 2, "no-drop", "dnd-no-drop"); case GLFW_GRAB_CURSOR: return set_cursor_from_font(cursor, XC_hand1); case GLFW_GRABBING_CURSOR: return try_cursor_names(cursor, 3, "grabbing", "closedhand", "dnd-none"); /* end glfw to xc mapping */ case GLFW_INVALID_CURSOR: return false; } return false; } void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) { if (cursor->x11.handle) XFreeCursor(_glfw.x11.display, cursor->x11.handle); } void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor UNUSED) { if (window->cursorMode == GLFW_CURSOR_NORMAL) { updateCursorImage(window); XFlush(_glfw.x11.display); } } static MimeAtom atom_for_mime(const char *mime) { for (size_t i = 0; i < _glfw.x11.mime_atoms.sz; i++) { MimeAtom ma = _glfw.x11.mime_atoms.array[i]; if (strcmp(ma.mime, mime) == 0) { return ma; } } MimeAtom ma = {.mime=_glfw_strdup(mime), .atom=XInternAtom(_glfw.x11.display, mime, 0)}; if (_glfw.x11.mime_atoms.capacity < _glfw.x11.mime_atoms.sz + 1) { _glfw.x11.mime_atoms.capacity += 32; _glfw.x11.mime_atoms.array = realloc(_glfw.x11.mime_atoms.array, _glfw.x11.mime_atoms.capacity * sizeof(_glfw.x11.mime_atoms.array[0])); } _glfw.x11.mime_atoms.array[_glfw.x11.mime_atoms.sz++] = ma; return ma; } void _glfwPlatformSetClipboard(GLFWClipboardType t) { Atom which = None; _GLFWClipboardData *cd = NULL; AtomArray *aa = NULL; switch (t) { case GLFW_CLIPBOARD: which = _glfw.x11.CLIPBOARD; cd = &_glfw.clipboard; aa = &_glfw.x11.clipboard_atoms; break; case GLFW_PRIMARY_SELECTION: which = _glfw.x11.PRIMARY; cd = &_glfw.primary; aa = &_glfw.x11.primary_atoms; break; } XSetSelectionOwner(_glfw.x11.display, which, _glfw.x11.helperWindowHandle, CurrentTime); if (XGetSelectionOwner(_glfw.x11.display, which) != _glfw.x11.helperWindowHandle) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to become owner of clipboard selection"); } if (aa->capacity < cd->num_mime_types + 32) { aa->capacity = cd->num_mime_types + 32; aa->array = reallocarray(aa->array, aa->capacity, sizeof(aa->array[0])); } aa->sz = 0; for (size_t i = 0; i < cd->num_mime_types; i++) { MimeAtom *a = aa->array + aa->sz++; *a = atom_for_mime(cd->mime_types[i]); if (strcmp(cd->mime_types[i], "text/plain") == 0) { a = aa->array + aa->sz++; a->atom = _glfw.x11.UTF8_STRING; a->mime = "text/plain"; } } } typedef struct chunked_writer { char *buf; size_t sz, cap; bool is_self_offer; } chunked_writer; static bool write_chunk(void *object, const char *data, size_t sz) { chunked_writer *cw = object; if (data) { if (cw->cap < cw->sz + sz) { cw->cap = MAX(cw->cap * 2, cw->sz + 8*sz); cw->buf = realloc(cw->buf, cw->cap * sizeof(cw->buf[0])); } memcpy(cw->buf + cw->sz, data, sz); cw->sz += sz; } else if (sz == 1) cw->is_self_offer = true; return true; } static void get_available_mime_types(Atom which_clipboard, GLFWclipboardwritedatafun write_data, void *object) { chunked_writer cw = {0}; getSelectionString(which_clipboard, &_glfw.x11.TARGETS, 1, write_chunk, &cw, false); if (cw.is_self_offer) { write_data(object, NULL, 1); return; } size_t count = 0; bool ok = true; if (cw.buf) { Atom *atoms = (Atom*)cw.buf; count = cw.sz / sizeof(Atom); char **names = calloc(count, sizeof(char*)); get_atom_names(atoms, count, names); for (size_t i = 0; i < count; i++) { if (strchr(names[i], '/')) { if (ok) ok = write_data(object, names[i], strlen(names[i])); } else { if (atoms[i] == _glfw.x11.UTF8_STRING || atoms[i] == XA_STRING) { if (ok) ok = write_data(object, "text/plain", strlen("text/plain")); } } XFree(names[i]); } free(cw.buf); free(names); } } void _glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) { Atom atoms[4], which = clipboard_type == GLFW_PRIMARY_SELECTION ? _glfw.x11.PRIMARY : _glfw.x11.CLIPBOARD; if (mime_type == NULL) { get_available_mime_types(which, write_data, object); return; } size_t count = 0; if (strcmp(mime_type, "text/plain") == 0) { // UTF8_STRING is what xclip uses by default, and there are people out there that expect to be able to paste from it with a single read operation. See https://github.com/kovidgoyal/kitty/issues/5842 // Also ancient versions of GNOME use DOS line endings even for text/plain;charset=utf-8. See https://github.com/kovidgoyal/kitty/issues/5528#issuecomment-1325348218 atoms[count++] = _glfw.x11.UTF8_STRING; // we need to do this because GTK/GNOME is moronic they convert text/plain to DOS line endings, see // https://gitlab.gnome.org/GNOME/gtk/-/issues/2307 atoms[count++] = atom_for_mime("text/plain;charset=utf-8").atom; atoms[count++] = atom_for_mime("text/plain").atom; atoms[count++] = XA_STRING; } else { atoms[count++] = atom_for_mime(mime_type).atom; } getSelectionString(which, atoms, count, write_data, object, true); } EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs) { if (_glfw.egl.ANGLE_platform_angle) { int type = 0; if (_glfw.egl.ANGLE_platform_angle_opengl) { if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL) type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE; } if (_glfw.egl.ANGLE_platform_angle_vulkan) { if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_VULKAN) type = EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE; } if (type) { *attribs = calloc(5, sizeof(EGLint)); (*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE; (*attribs)[1] = type; (*attribs)[2] = EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE; (*attribs)[3] = EGL_PLATFORM_X11_EXT; (*attribs)[4] = EGL_NONE; return EGL_PLATFORM_ANGLE_ANGLE; } } if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_x11) return EGL_PLATFORM_X11_EXT; return 0; } EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void) { return _glfw.x11.display; } EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window) { if (_glfw.egl.platform) return &window->x11.handle; else return (EGLNativeWindowType) window->x11.handle; } void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) { if (!_glfw.vk.KHR_surface) return; if (!_glfw.vk.KHR_xcb_surface) { if (!_glfw.vk.KHR_xlib_surface) return; } extensions[0] = "VK_KHR_surface"; // NOTE: VK_KHR_xcb_surface is preferred due to some early ICDs exposing but // not correctly implementing VK_KHR_xlib_surface if (_glfw.vk.KHR_xcb_surface) extensions[1] = "VK_KHR_xcb_surface"; else extensions[1] = "VK_KHR_xlib_surface"; } int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhysicalDevice device, uint32_t queuefamily) { VisualID visualID = XVisualIDFromVisual(DefaultVisual(_glfw.x11.display, _glfw.x11.screen)); if (_glfw.vk.KHR_xcb_surface) { PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR vkGetPhysicalDeviceXcbPresentationSupportKHR = (PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR) vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceXcbPresentationSupportKHR"); if (!vkGetPhysicalDeviceXcbPresentationSupportKHR) { _glfwInputError(GLFW_API_UNAVAILABLE, "X11: Vulkan instance missing VK_KHR_xcb_surface extension"); return false; } xcb_connection_t* connection = XGetXCBConnection(_glfw.x11.display); if (!connection) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to retrieve XCB connection"); return false; } return vkGetPhysicalDeviceXcbPresentationSupportKHR(device, queuefamily, connection, visualID); } else { PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR = (PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR) vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceXlibPresentationSupportKHR"); if (!vkGetPhysicalDeviceXlibPresentationSupportKHR) { _glfwInputError(GLFW_API_UNAVAILABLE, "X11: Vulkan instance missing VK_KHR_xlib_surface extension"); return false; } return vkGetPhysicalDeviceXlibPresentationSupportKHR(device, queuefamily, _glfw.x11.display, visualID); } } VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, _GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface) { if (_glfw.vk.KHR_xcb_surface) { VkResult err; VkXcbSurfaceCreateInfoKHR sci; PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR; xcb_connection_t* connection = XGetXCBConnection(_glfw.x11.display); if (!connection) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to retrieve XCB connection"); return VK_ERROR_EXTENSION_NOT_PRESENT; } vkCreateXcbSurfaceKHR = (PFN_vkCreateXcbSurfaceKHR) vkGetInstanceProcAddr(instance, "vkCreateXcbSurfaceKHR"); if (!vkCreateXcbSurfaceKHR) { _glfwInputError(GLFW_API_UNAVAILABLE, "X11: Vulkan instance missing VK_KHR_xcb_surface extension"); return VK_ERROR_EXTENSION_NOT_PRESENT; } memset(&sci, 0, sizeof(sci)); sci.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; sci.connection = connection; sci.window = window->x11.handle; err = vkCreateXcbSurfaceKHR(instance, &sci, allocator, surface); if (err) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to create Vulkan XCB surface: %s", _glfwGetVulkanResultString(err)); } return err; } else { VkResult err; VkXlibSurfaceCreateInfoKHR sci; PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR; vkCreateXlibSurfaceKHR = (PFN_vkCreateXlibSurfaceKHR) vkGetInstanceProcAddr(instance, "vkCreateXlibSurfaceKHR"); if (!vkCreateXlibSurfaceKHR) { _glfwInputError(GLFW_API_UNAVAILABLE, "X11: Vulkan instance missing VK_KHR_xlib_surface extension"); return VK_ERROR_EXTENSION_NOT_PRESENT; } memset(&sci, 0, sizeof(sci)); sci.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; sci.dpy = _glfw.x11.display; sci.window = window->x11.handle; err = vkCreateXlibSurfaceKHR(instance, &sci, allocator, surface); if (err) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to create Vulkan X11 surface: %s", _glfwGetVulkanResultString(err)); } return err; } } void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) { glfw_xkb_update_ime_state(w, &_glfw.x11.xkb, ev); } int _glfwPlatformSetWindowBlur(_GLFWwindow *window, int blur_radius) { if (_glfw.x11._KDE_NET_WM_BLUR_BEHIND_REGION == None) { _glfw.x11._KDE_NET_WM_BLUR_BEHIND_REGION = XInternAtom(_glfw.x11.display, "_KDE_NET_WM_BLUR_BEHIND_REGION", False); } if (_glfw.x11._KDE_NET_WM_BLUR_BEHIND_REGION != None) { uint32_t data = 0; if (blur_radius > 0) { XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11._KDE_NET_WM_BLUR_BEHIND_REGION, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &data, 1); } else { XDeleteProperty(_glfw.x11.display, window->x11.handle, _glfw.x11._KDE_NET_WM_BLUR_BEHIND_REGION); } return 1; } return 0; } ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI Display* glfwGetX11Display(void) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); return _glfw.x11.display; } GLFWAPI unsigned long glfwGetX11Window(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(None); return window->x11.handle; } GLFWAPI int glfwGetNativeKeyForName(const char* keyName, bool caseSensitive) { return glfw_xkb_keysym_from_name(keyName, caseSensitive); } GLFWAPI unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data) { return glfw_dbus_send_user_notification(n, callback, data); } GLFWAPI void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) { glfw_dbus_set_user_notification_activated_handler(handler); } GLFWAPI int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc) { _GLFW_REQUIRE_INIT_OR_RETURN(0); _GLFWwindow* window = (_GLFWwindow*) handle; return XSetCommand(_glfw.x11.display, window->x11.handle, argv, argc); } GLFWAPI void glfwSetX11WindowAsDock(int32_t x11_window_id) { _GLFW_REQUIRE_INIT(); Atom type = _glfw.x11.NET_WM_WINDOW_TYPE_DOCK; XChangeProperty(_glfw.x11.display, x11_window_id, _glfw.x11.NET_WM_WINDOW_TYPE, XA_ATOM, 32, PropModeReplace, (unsigned char*) &type, 1); } GLFWAPI void glfwSetX11WindowStrut(int32_t x11_window_id, uint32_t dimensions[12]) { _GLFW_REQUIRE_INIT(); XChangeProperty(_glfw.x11.display, x11_window_id, _glfw.x11.NET_WM_STRUT_PARTIAL, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) dimensions, 12); } kitty-0.41.1/glfw/xkb-compat-shim.h0000664000175000017510000020556614773370543016440 0ustar nileshnilesh/* * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include #include // needed for xkbcommon < 1.0 /* We don't use the uint32_t types here, to save some space. */ struct codepair { uint16_t keysym; uint16_t ucs; }; static const struct codepair keysymtab[] = { { 0x01a1, 0x0104 }, /* Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */ { 0x01a2, 0x02d8 }, /* breve ˘ BREVE */ { 0x01a3, 0x0141 }, /* Lstroke Ł LATIN CAPITAL LETTER L WITH STROKE */ { 0x01a5, 0x013d }, /* Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */ { 0x01a6, 0x015a }, /* Sacute Ś LATIN CAPITAL LETTER S WITH ACUTE */ { 0x01a9, 0x0160 }, /* Scaron Š LATIN CAPITAL LETTER S WITH CARON */ { 0x01aa, 0x015e }, /* Scedilla Ş LATIN CAPITAL LETTER S WITH CEDILLA */ { 0x01ab, 0x0164 }, /* Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */ { 0x01ac, 0x0179 }, /* Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */ { 0x01ae, 0x017d }, /* Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */ { 0x01af, 0x017b }, /* Zabovedot Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE */ { 0x01b1, 0x0105 }, /* aogonek ą LATIN SMALL LETTER A WITH OGONEK */ { 0x01b2, 0x02db }, /* ogonek ˛ OGONEK */ { 0x01b3, 0x0142 }, /* lstroke ł LATIN SMALL LETTER L WITH STROKE */ { 0x01b5, 0x013e }, /* lcaron ľ LATIN SMALL LETTER L WITH CARON */ { 0x01b6, 0x015b }, /* sacute ś LATIN SMALL LETTER S WITH ACUTE */ { 0x01b7, 0x02c7 }, /* caron ˇ CARON */ { 0x01b9, 0x0161 }, /* scaron š LATIN SMALL LETTER S WITH CARON */ { 0x01ba, 0x015f }, /* scedilla ş LATIN SMALL LETTER S WITH CEDILLA */ { 0x01bb, 0x0165 }, /* tcaron ť LATIN SMALL LETTER T WITH CARON */ { 0x01bc, 0x017a }, /* zacute ź LATIN SMALL LETTER Z WITH ACUTE */ { 0x01bd, 0x02dd }, /* doubleacute ˝ DOUBLE ACUTE ACCENT */ { 0x01be, 0x017e }, /* zcaron ž LATIN SMALL LETTER Z WITH CARON */ { 0x01bf, 0x017c }, /* zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */ { 0x01c0, 0x0154 }, /* Racute Ŕ LATIN CAPITAL LETTER R WITH ACUTE */ { 0x01c3, 0x0102 }, /* Abreve Ă LATIN CAPITAL LETTER A WITH BREVE */ { 0x01c5, 0x0139 }, /* Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */ { 0x01c6, 0x0106 }, /* Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */ { 0x01c8, 0x010c }, /* Ccaron Č LATIN CAPITAL LETTER C WITH CARON */ { 0x01ca, 0x0118 }, /* Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */ { 0x01cc, 0x011a }, /* Ecaron Ě LATIN CAPITAL LETTER E WITH CARON */ { 0x01cf, 0x010e }, /* Dcaron Ď LATIN CAPITAL LETTER D WITH CARON */ { 0x01d0, 0x0110 }, /* Dstroke Đ LATIN CAPITAL LETTER D WITH STROKE */ { 0x01d1, 0x0143 }, /* Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */ { 0x01d2, 0x0147 }, /* Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */ { 0x01d5, 0x0150 }, /* Odoubleacute Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */ { 0x01d8, 0x0158 }, /* Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */ { 0x01d9, 0x016e }, /* Uring Ů LATIN CAPITAL LETTER U WITH RING ABOVE */ { 0x01db, 0x0170 }, /* Udoubleacute Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */ { 0x01de, 0x0162 }, /* Tcedilla Ţ LATIN CAPITAL LETTER T WITH CEDILLA */ { 0x01e0, 0x0155 }, /* racute ŕ LATIN SMALL LETTER R WITH ACUTE */ { 0x01e3, 0x0103 }, /* abreve ă LATIN SMALL LETTER A WITH BREVE */ { 0x01e5, 0x013a }, /* lacute ĺ LATIN SMALL LETTER L WITH ACUTE */ { 0x01e6, 0x0107 }, /* cacute ć LATIN SMALL LETTER C WITH ACUTE */ { 0x01e8, 0x010d }, /* ccaron č LATIN SMALL LETTER C WITH CARON */ { 0x01ea, 0x0119 }, /* eogonek ę LATIN SMALL LETTER E WITH OGONEK */ { 0x01ec, 0x011b }, /* ecaron ě LATIN SMALL LETTER E WITH CARON */ { 0x01ef, 0x010f }, /* dcaron ď LATIN SMALL LETTER D WITH CARON */ { 0x01f0, 0x0111 }, /* dstroke đ LATIN SMALL LETTER D WITH STROKE */ { 0x01f1, 0x0144 }, /* nacute ń LATIN SMALL LETTER N WITH ACUTE */ { 0x01f2, 0x0148 }, /* ncaron ň LATIN SMALL LETTER N WITH CARON */ { 0x01f5, 0x0151 }, /* odoubleacute ő LATIN SMALL LETTER O WITH DOUBLE ACUTE */ { 0x01f8, 0x0159 }, /* rcaron ř LATIN SMALL LETTER R WITH CARON */ { 0x01f9, 0x016f }, /* uring ů LATIN SMALL LETTER U WITH RING ABOVE */ { 0x01fb, 0x0171 }, /* udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */ { 0x01fe, 0x0163 }, /* tcedilla ţ LATIN SMALL LETTER T WITH CEDILLA */ { 0x01ff, 0x02d9 }, /* abovedot ˙ DOT ABOVE */ { 0x02a1, 0x0126 }, /* Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */ { 0x02a6, 0x0124 }, /* Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */ { 0x02a9, 0x0130 }, /* Iabovedot İ LATIN CAPITAL LETTER I WITH DOT ABOVE */ { 0x02ab, 0x011e }, /* Gbreve Ğ LATIN CAPITAL LETTER G WITH BREVE */ { 0x02ac, 0x0134 }, /* Jcircumflex Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */ { 0x02b1, 0x0127 }, /* hstroke ħ LATIN SMALL LETTER H WITH STROKE */ { 0x02b6, 0x0125 }, /* hcircumflex ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX */ { 0x02b9, 0x0131 }, /* idotless ı LATIN SMALL LETTER DOTLESS I */ { 0x02bb, 0x011f }, /* gbreve ğ LATIN SMALL LETTER G WITH BREVE */ { 0x02bc, 0x0135 }, /* jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */ { 0x02c5, 0x010a }, /* Cabovedot Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE */ { 0x02c6, 0x0108 }, /* Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */ { 0x02d5, 0x0120 }, /* Gabovedot Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE */ { 0x02d8, 0x011c }, /* Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */ { 0x02dd, 0x016c }, /* Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */ { 0x02de, 0x015c }, /* Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */ { 0x02e5, 0x010b }, /* cabovedot ċ LATIN SMALL LETTER C WITH DOT ABOVE */ { 0x02e6, 0x0109 }, /* ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */ { 0x02f5, 0x0121 }, /* gabovedot ġ LATIN SMALL LETTER G WITH DOT ABOVE */ { 0x02f8, 0x011d }, /* gcircumflex ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX */ { 0x02fd, 0x016d }, /* ubreve ŭ LATIN SMALL LETTER U WITH BREVE */ { 0x02fe, 0x015d }, /* scircumflex ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX */ { 0x03a2, 0x0138 }, /* kra ĸ LATIN SMALL LETTER KRA */ { 0x03a3, 0x0156 }, /* Rcedilla Ŗ LATIN CAPITAL LETTER R WITH CEDILLA */ { 0x03a5, 0x0128 }, /* Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */ { 0x03a6, 0x013b }, /* Lcedilla Ļ LATIN CAPITAL LETTER L WITH CEDILLA */ { 0x03aa, 0x0112 }, /* Emacron Ē LATIN CAPITAL LETTER E WITH MACRON */ { 0x03ab, 0x0122 }, /* Gcedilla Ģ LATIN CAPITAL LETTER G WITH CEDILLA */ { 0x03ac, 0x0166 }, /* Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */ { 0x03b3, 0x0157 }, /* rcedilla ŗ LATIN SMALL LETTER R WITH CEDILLA */ { 0x03b5, 0x0129 }, /* itilde ĩ LATIN SMALL LETTER I WITH TILDE */ { 0x03b6, 0x013c }, /* lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */ { 0x03ba, 0x0113 }, /* emacron ē LATIN SMALL LETTER E WITH MACRON */ { 0x03bb, 0x0123 }, /* gcedilla ģ LATIN SMALL LETTER G WITH CEDILLA */ { 0x03bc, 0x0167 }, /* tslash ŧ LATIN SMALL LETTER T WITH STROKE */ { 0x03bd, 0x014a }, /* ENG Ŋ LATIN CAPITAL LETTER ENG */ { 0x03bf, 0x014b }, /* eng ŋ LATIN SMALL LETTER ENG */ { 0x03c0, 0x0100 }, /* Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */ { 0x03c7, 0x012e }, /* Iogonek Į LATIN CAPITAL LETTER I WITH OGONEK */ { 0x03cc, 0x0116 }, /* Eabovedot Ė LATIN CAPITAL LETTER E WITH DOT ABOVE */ { 0x03cf, 0x012a }, /* Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */ { 0x03d1, 0x0145 }, /* Ncedilla Ņ LATIN CAPITAL LETTER N WITH CEDILLA */ { 0x03d2, 0x014c }, /* Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */ { 0x03d3, 0x0136 }, /* Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */ { 0x03d9, 0x0172 }, /* Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */ { 0x03dd, 0x0168 }, /* Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */ { 0x03de, 0x016a }, /* Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */ { 0x03e0, 0x0101 }, /* amacron ā LATIN SMALL LETTER A WITH MACRON */ { 0x03e7, 0x012f }, /* iogonek į LATIN SMALL LETTER I WITH OGONEK */ { 0x03ec, 0x0117 }, /* eabovedot ė LATIN SMALL LETTER E WITH DOT ABOVE */ { 0x03ef, 0x012b }, /* imacron ī LATIN SMALL LETTER I WITH MACRON */ { 0x03f1, 0x0146 }, /* ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */ { 0x03f2, 0x014d }, /* omacron ō LATIN SMALL LETTER O WITH MACRON */ { 0x03f3, 0x0137 }, /* kcedilla ķ LATIN SMALL LETTER K WITH CEDILLA */ { 0x03f9, 0x0173 }, /* uogonek ų LATIN SMALL LETTER U WITH OGONEK */ { 0x03fd, 0x0169 }, /* utilde ũ LATIN SMALL LETTER U WITH TILDE */ { 0x03fe, 0x016b }, /* umacron ū LATIN SMALL LETTER U WITH MACRON */ { 0x047e, 0x203e }, /* overline ‾ OVERLINE */ { 0x04a1, 0x3002 }, /* kana_fullstop 。 IDEOGRAPHIC FULL STOP */ { 0x04a2, 0x300c }, /* kana_openingbracket 「 LEFT CORNER BRACKET */ { 0x04a3, 0x300d }, /* kana_closingbracket 」 RIGHT CORNER BRACKET */ { 0x04a4, 0x3001 }, /* kana_comma 、 IDEOGRAPHIC COMMA */ { 0x04a5, 0x30fb }, /* kana_conjunctive ・ KATAKANA MIDDLE DOT */ { 0x04a6, 0x30f2 }, /* kana_WO ヲ KATAKANA LETTER WO */ { 0x04a7, 0x30a1 }, /* kana_a ァ KATAKANA LETTER SMALL A */ { 0x04a8, 0x30a3 }, /* kana_i ィ KATAKANA LETTER SMALL I */ { 0x04a9, 0x30a5 }, /* kana_u ゥ KATAKANA LETTER SMALL U */ { 0x04aa, 0x30a7 }, /* kana_e ェ KATAKANA LETTER SMALL E */ { 0x04ab, 0x30a9 }, /* kana_o ォ KATAKANA LETTER SMALL O */ { 0x04ac, 0x30e3 }, /* kana_ya ャ KATAKANA LETTER SMALL YA */ { 0x04ad, 0x30e5 }, /* kana_yu ュ KATAKANA LETTER SMALL YU */ { 0x04ae, 0x30e7 }, /* kana_yo ョ KATAKANA LETTER SMALL YO */ { 0x04af, 0x30c3 }, /* kana_tsu ッ KATAKANA LETTER SMALL TU */ { 0x04b0, 0x30fc }, /* prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */ { 0x04b1, 0x30a2 }, /* kana_A ア KATAKANA LETTER A */ { 0x04b2, 0x30a4 }, /* kana_I イ KATAKANA LETTER I */ { 0x04b3, 0x30a6 }, /* kana_U ウ KATAKANA LETTER U */ { 0x04b4, 0x30a8 }, /* kana_E エ KATAKANA LETTER E */ { 0x04b5, 0x30aa }, /* kana_O オ KATAKANA LETTER O */ { 0x04b6, 0x30ab }, /* kana_KA カ KATAKANA LETTER KA */ { 0x04b7, 0x30ad }, /* kana_KI キ KATAKANA LETTER KI */ { 0x04b8, 0x30af }, /* kana_KU ク KATAKANA LETTER KU */ { 0x04b9, 0x30b1 }, /* kana_KE ケ KATAKANA LETTER KE */ { 0x04ba, 0x30b3 }, /* kana_KO コ KATAKANA LETTER KO */ { 0x04bb, 0x30b5 }, /* kana_SA サ KATAKANA LETTER SA */ { 0x04bc, 0x30b7 }, /* kana_SHI シ KATAKANA LETTER SI */ { 0x04bd, 0x30b9 }, /* kana_SU ス KATAKANA LETTER SU */ { 0x04be, 0x30bb }, /* kana_SE セ KATAKANA LETTER SE */ { 0x04bf, 0x30bd }, /* kana_SO ソ KATAKANA LETTER SO */ { 0x04c0, 0x30bf }, /* kana_TA タ KATAKANA LETTER TA */ { 0x04c1, 0x30c1 }, /* kana_CHI チ KATAKANA LETTER TI */ { 0x04c2, 0x30c4 }, /* kana_TSU ツ KATAKANA LETTER TU */ { 0x04c3, 0x30c6 }, /* kana_TE テ KATAKANA LETTER TE */ { 0x04c4, 0x30c8 }, /* kana_TO ト KATAKANA LETTER TO */ { 0x04c5, 0x30ca }, /* kana_NA ナ KATAKANA LETTER NA */ { 0x04c6, 0x30cb }, /* kana_NI ニ KATAKANA LETTER NI */ { 0x04c7, 0x30cc }, /* kana_NU ヌ KATAKANA LETTER NU */ { 0x04c8, 0x30cd }, /* kana_NE ネ KATAKANA LETTER NE */ { 0x04c9, 0x30ce }, /* kana_NO ノ KATAKANA LETTER NO */ { 0x04ca, 0x30cf }, /* kana_HA ハ KATAKANA LETTER HA */ { 0x04cb, 0x30d2 }, /* kana_HI ヒ KATAKANA LETTER HI */ { 0x04cc, 0x30d5 }, /* kana_FU フ KATAKANA LETTER HU */ { 0x04cd, 0x30d8 }, /* kana_HE ヘ KATAKANA LETTER HE */ { 0x04ce, 0x30db }, /* kana_HO ホ KATAKANA LETTER HO */ { 0x04cf, 0x30de }, /* kana_MA マ KATAKANA LETTER MA */ { 0x04d0, 0x30df }, /* kana_MI ミ KATAKANA LETTER MI */ { 0x04d1, 0x30e0 }, /* kana_MU ム KATAKANA LETTER MU */ { 0x04d2, 0x30e1 }, /* kana_ME メ KATAKANA LETTER ME */ { 0x04d3, 0x30e2 }, /* kana_MO モ KATAKANA LETTER MO */ { 0x04d4, 0x30e4 }, /* kana_YA ヤ KATAKANA LETTER YA */ { 0x04d5, 0x30e6 }, /* kana_YU ユ KATAKANA LETTER YU */ { 0x04d6, 0x30e8 }, /* kana_YO ヨ KATAKANA LETTER YO */ { 0x04d7, 0x30e9 }, /* kana_RA ラ KATAKANA LETTER RA */ { 0x04d8, 0x30ea }, /* kana_RI リ KATAKANA LETTER RI */ { 0x04d9, 0x30eb }, /* kana_RU ル KATAKANA LETTER RU */ { 0x04da, 0x30ec }, /* kana_RE レ KATAKANA LETTER RE */ { 0x04db, 0x30ed }, /* kana_RO ロ KATAKANA LETTER RO */ { 0x04dc, 0x30ef }, /* kana_WA ワ KATAKANA LETTER WA */ { 0x04dd, 0x30f3 }, /* kana_N ン KATAKANA LETTER N */ { 0x04de, 0x309b }, /* voicedsound ゛ KATAKANA-HIRAGANA VOICED SOUND MARK */ { 0x04df, 0x309c }, /* semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ { 0x05ac, 0x060c }, /* Arabic_comma ، ARABIC COMMA */ { 0x05bb, 0x061b }, /* Arabic_semicolon ؛ ARABIC SEMICOLON */ { 0x05bf, 0x061f }, /* Arabic_question_mark ؟ ARABIC QUESTION MARK */ { 0x05c1, 0x0621 }, /* Arabic_hamza ء ARABIC LETTER HAMZA */ { 0x05c2, 0x0622 }, /* Arabic_maddaonalef آ ARABIC LETTER ALEF WITH MADDA ABOVE */ { 0x05c3, 0x0623 }, /* Arabic_hamzaonalef أ ARABIC LETTER ALEF WITH HAMZA ABOVE */ { 0x05c4, 0x0624 }, /* Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */ { 0x05c5, 0x0625 }, /* Arabic_hamzaunderalef إ ARABIC LETTER ALEF WITH HAMZA BELOW */ { 0x05c6, 0x0626 }, /* Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */ { 0x05c7, 0x0627 }, /* Arabic_alef ا ARABIC LETTER ALEF */ { 0x05c8, 0x0628 }, /* Arabic_beh ب ARABIC LETTER BEH */ { 0x05c9, 0x0629 }, /* Arabic_tehmarbuta ة ARABIC LETTER TEH MARBUTA */ { 0x05ca, 0x062a }, /* Arabic_teh ت ARABIC LETTER TEH */ { 0x05cb, 0x062b }, /* Arabic_theh ث ARABIC LETTER THEH */ { 0x05cc, 0x062c }, /* Arabic_jeem ج ARABIC LETTER JEEM */ { 0x05cd, 0x062d }, /* Arabic_hah ح ARABIC LETTER HAH */ { 0x05ce, 0x062e }, /* Arabic_khah خ ARABIC LETTER KHAH */ { 0x05cf, 0x062f }, /* Arabic_dal د ARABIC LETTER DAL */ { 0x05d0, 0x0630 }, /* Arabic_thal ذ ARABIC LETTER THAL */ { 0x05d1, 0x0631 }, /* Arabic_ra ر ARABIC LETTER REH */ { 0x05d2, 0x0632 }, /* Arabic_zain ز ARABIC LETTER ZAIN */ { 0x05d3, 0x0633 }, /* Arabic_seen س ARABIC LETTER SEEN */ { 0x05d4, 0x0634 }, /* Arabic_sheen ش ARABIC LETTER SHEEN */ { 0x05d5, 0x0635 }, /* Arabic_sad ص ARABIC LETTER SAD */ { 0x05d6, 0x0636 }, /* Arabic_dad ض ARABIC LETTER DAD */ { 0x05d7, 0x0637 }, /* Arabic_tah ط ARABIC LETTER TAH */ { 0x05d8, 0x0638 }, /* Arabic_zah ظ ARABIC LETTER ZAH */ { 0x05d9, 0x0639 }, /* Arabic_ain ع ARABIC LETTER AIN */ { 0x05da, 0x063a }, /* Arabic_ghain غ ARABIC LETTER GHAIN */ { 0x05e0, 0x0640 }, /* Arabic_tatweel ـ ARABIC TATWEEL */ { 0x05e1, 0x0641 }, /* Arabic_feh ف ARABIC LETTER FEH */ { 0x05e2, 0x0642 }, /* Arabic_qaf ق ARABIC LETTER QAF */ { 0x05e3, 0x0643 }, /* Arabic_kaf ك ARABIC LETTER KAF */ { 0x05e4, 0x0644 }, /* Arabic_lam ل ARABIC LETTER LAM */ { 0x05e5, 0x0645 }, /* Arabic_meem م ARABIC LETTER MEEM */ { 0x05e6, 0x0646 }, /* Arabic_noon ن ARABIC LETTER NOON */ { 0x05e7, 0x0647 }, /* Arabic_ha ه ARABIC LETTER HEH */ { 0x05e8, 0x0648 }, /* Arabic_waw و ARABIC LETTER WAW */ { 0x05e9, 0x0649 }, /* Arabic_alefmaksura ى ARABIC LETTER ALEF MAKSURA */ { 0x05ea, 0x064a }, /* Arabic_yeh ي ARABIC LETTER YEH */ { 0x05eb, 0x064b }, /* Arabic_fathatan ً ARABIC FATHATAN */ { 0x05ec, 0x064c }, /* Arabic_dammatan ٌ ARABIC DAMMATAN */ { 0x05ed, 0x064d }, /* Arabic_kasratan ٍ ARABIC KASRATAN */ { 0x05ee, 0x064e }, /* Arabic_fatha َ ARABIC FATHA */ { 0x05ef, 0x064f }, /* Arabic_damma ُ ARABIC DAMMA */ { 0x05f0, 0x0650 }, /* Arabic_kasra ِ ARABIC KASRA */ { 0x05f1, 0x0651 }, /* Arabic_shadda ّ ARABIC SHADDA */ { 0x05f2, 0x0652 }, /* Arabic_sukun ْ ARABIC SUKUN */ { 0x06a1, 0x0452 }, /* Serbian_dje ђ CYRILLIC SMALL LETTER DJE */ { 0x06a2, 0x0453 }, /* Macedonia_gje ѓ CYRILLIC SMALL LETTER GJE */ { 0x06a3, 0x0451 }, /* Cyrillic_io ё CYRILLIC SMALL LETTER IO */ { 0x06a4, 0x0454 }, /* Ukrainian_ie є CYRILLIC SMALL LETTER UKRAINIAN IE */ { 0x06a5, 0x0455 }, /* Macedonia_dse ѕ CYRILLIC SMALL LETTER DZE */ { 0x06a6, 0x0456 }, /* Ukrainian_i і CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */ { 0x06a7, 0x0457 }, /* Ukrainian_yi ї CYRILLIC SMALL LETTER YI */ { 0x06a8, 0x0458 }, /* Cyrillic_je ј CYRILLIC SMALL LETTER JE */ { 0x06a9, 0x0459 }, /* Cyrillic_lje љ CYRILLIC SMALL LETTER LJE */ { 0x06aa, 0x045a }, /* Cyrillic_nje њ CYRILLIC SMALL LETTER NJE */ { 0x06ab, 0x045b }, /* Serbian_tshe ћ CYRILLIC SMALL LETTER TSHE */ { 0x06ac, 0x045c }, /* Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */ { 0x06ad, 0x0491 }, /* Ukrainian_ghe_with_upturn ґ CYRILLIC SMALL LETTER GHE WITH UPTURN */ { 0x06ae, 0x045e }, /* Byelorussian_shortu ў CYRILLIC SMALL LETTER SHORT U */ { 0x06af, 0x045f }, /* Cyrillic_dzhe џ CYRILLIC SMALL LETTER DZHE */ { 0x06b0, 0x2116 }, /* numerosign № NUMERO SIGN */ { 0x06b1, 0x0402 }, /* Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */ { 0x06b2, 0x0403 }, /* Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */ { 0x06b3, 0x0401 }, /* Cyrillic_IO Ё CYRILLIC CAPITAL LETTER IO */ { 0x06b4, 0x0404 }, /* Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */ { 0x06b5, 0x0405 }, /* Macedonia_DSE Ѕ CYRILLIC CAPITAL LETTER DZE */ { 0x06b6, 0x0406 }, /* Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */ { 0x06b7, 0x0407 }, /* Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */ { 0x06b8, 0x0408 }, /* Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */ { 0x06b9, 0x0409 }, /* Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */ { 0x06ba, 0x040a }, /* Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */ { 0x06bb, 0x040b }, /* Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */ { 0x06bc, 0x040c }, /* Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */ { 0x06bd, 0x0490 }, /* Ukrainian_GHE_WITH_UPTURN Ґ CYRILLIC CAPITAL LETTER GHE WITH UPTURN */ { 0x06be, 0x040e }, /* Byelorussian_SHORTU Ў CYRILLIC CAPITAL LETTER SHORT U */ { 0x06bf, 0x040f }, /* Cyrillic_DZHE Џ CYRILLIC CAPITAL LETTER DZHE */ { 0x06c0, 0x044e }, /* Cyrillic_yu ю CYRILLIC SMALL LETTER YU */ { 0x06c1, 0x0430 }, /* Cyrillic_a а CYRILLIC SMALL LETTER A */ { 0x06c2, 0x0431 }, /* Cyrillic_be б CYRILLIC SMALL LETTER BE */ { 0x06c3, 0x0446 }, /* Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */ { 0x06c4, 0x0434 }, /* Cyrillic_de д CYRILLIC SMALL LETTER DE */ { 0x06c5, 0x0435 }, /* Cyrillic_ie е CYRILLIC SMALL LETTER IE */ { 0x06c6, 0x0444 }, /* Cyrillic_ef ф CYRILLIC SMALL LETTER EF */ { 0x06c7, 0x0433 }, /* Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */ { 0x06c8, 0x0445 }, /* Cyrillic_ha х CYRILLIC SMALL LETTER HA */ { 0x06c9, 0x0438 }, /* Cyrillic_i и CYRILLIC SMALL LETTER I */ { 0x06ca, 0x0439 }, /* Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */ { 0x06cb, 0x043a }, /* Cyrillic_ka к CYRILLIC SMALL LETTER KA */ { 0x06cc, 0x043b }, /* Cyrillic_el л CYRILLIC SMALL LETTER EL */ { 0x06cd, 0x043c }, /* Cyrillic_em м CYRILLIC SMALL LETTER EM */ { 0x06ce, 0x043d }, /* Cyrillic_en н CYRILLIC SMALL LETTER EN */ { 0x06cf, 0x043e }, /* Cyrillic_o о CYRILLIC SMALL LETTER O */ { 0x06d0, 0x043f }, /* Cyrillic_pe п CYRILLIC SMALL LETTER PE */ { 0x06d1, 0x044f }, /* Cyrillic_ya я CYRILLIC SMALL LETTER YA */ { 0x06d2, 0x0440 }, /* Cyrillic_er р CYRILLIC SMALL LETTER ER */ { 0x06d3, 0x0441 }, /* Cyrillic_es с CYRILLIC SMALL LETTER ES */ { 0x06d4, 0x0442 }, /* Cyrillic_te т CYRILLIC SMALL LETTER TE */ { 0x06d5, 0x0443 }, /* Cyrillic_u у CYRILLIC SMALL LETTER U */ { 0x06d6, 0x0436 }, /* Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */ { 0x06d7, 0x0432 }, /* Cyrillic_ve в CYRILLIC SMALL LETTER VE */ { 0x06d8, 0x044c }, /* Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */ { 0x06d9, 0x044b }, /* Cyrillic_yeru ы CYRILLIC SMALL LETTER YERU */ { 0x06da, 0x0437 }, /* Cyrillic_ze з CYRILLIC SMALL LETTER ZE */ { 0x06db, 0x0448 }, /* Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */ { 0x06dc, 0x044d }, /* Cyrillic_e э CYRILLIC SMALL LETTER E */ { 0x06dd, 0x0449 }, /* Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */ { 0x06de, 0x0447 }, /* Cyrillic_che ч CYRILLIC SMALL LETTER CHE */ { 0x06df, 0x044a }, /* Cyrillic_hardsign ъ CYRILLIC SMALL LETTER HARD SIGN */ { 0x06e0, 0x042e }, /* Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */ { 0x06e1, 0x0410 }, /* Cyrillic_A А CYRILLIC CAPITAL LETTER A */ { 0x06e2, 0x0411 }, /* Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */ { 0x06e3, 0x0426 }, /* Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */ { 0x06e4, 0x0414 }, /* Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */ { 0x06e5, 0x0415 }, /* Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */ { 0x06e6, 0x0424 }, /* Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */ { 0x06e7, 0x0413 }, /* Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */ { 0x06e8, 0x0425 }, /* Cyrillic_HA Х CYRILLIC CAPITAL LETTER HA */ { 0x06e9, 0x0418 }, /* Cyrillic_I И CYRILLIC CAPITAL LETTER I */ { 0x06ea, 0x0419 }, /* Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */ { 0x06eb, 0x041a }, /* Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */ { 0x06ec, 0x041b }, /* Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */ { 0x06ed, 0x041c }, /* Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */ { 0x06ee, 0x041d }, /* Cyrillic_EN Н CYRILLIC CAPITAL LETTER EN */ { 0x06ef, 0x041e }, /* Cyrillic_O О CYRILLIC CAPITAL LETTER O */ { 0x06f0, 0x041f }, /* Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */ { 0x06f1, 0x042f }, /* Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */ { 0x06f2, 0x0420 }, /* Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */ { 0x06f3, 0x0421 }, /* Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */ { 0x06f4, 0x0422 }, /* Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */ { 0x06f5, 0x0423 }, /* Cyrillic_U У CYRILLIC CAPITAL LETTER U */ { 0x06f6, 0x0416 }, /* Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */ { 0x06f7, 0x0412 }, /* Cyrillic_VE В CYRILLIC CAPITAL LETTER VE */ { 0x06f8, 0x042c }, /* Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */ { 0x06f9, 0x042b }, /* Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */ { 0x06fa, 0x0417 }, /* Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */ { 0x06fb, 0x0428 }, /* Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */ { 0x06fc, 0x042d }, /* Cyrillic_E Э CYRILLIC CAPITAL LETTER E */ { 0x06fd, 0x0429 }, /* Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */ { 0x06fe, 0x0427 }, /* Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */ { 0x06ff, 0x042a }, /* Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */ { 0x07a1, 0x0386 }, /* Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */ { 0x07a2, 0x0388 }, /* Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */ { 0x07a3, 0x0389 }, /* Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */ { 0x07a4, 0x038a }, /* Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */ { 0x07a5, 0x03aa }, /* Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */ { 0x07a7, 0x038c }, /* Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */ { 0x07a8, 0x038e }, /* Greek_UPSILONaccent Ύ GREEK CAPITAL LETTER UPSILON WITH TONOS */ { 0x07a9, 0x03ab }, /* Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */ { 0x07ab, 0x038f }, /* Greek_OMEGAaccent Ώ GREEK CAPITAL LETTER OMEGA WITH TONOS */ { 0x07ae, 0x0385 }, /* Greek_accentdieresis ΅ GREEK DIALYTIKA TONOS */ { 0x07af, 0x2015 }, /* Greek_horizbar ― HORIZONTAL BAR */ { 0x07b1, 0x03ac }, /* Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */ { 0x07b2, 0x03ad }, /* Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */ { 0x07b3, 0x03ae }, /* Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */ { 0x07b4, 0x03af }, /* Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */ { 0x07b5, 0x03ca }, /* Greek_iotadieresis ϊ GREEK SMALL LETTER IOTA WITH DIALYTIKA */ { 0x07b6, 0x0390 }, /* Greek_iotaaccentdieresis ΐ GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */ { 0x07b7, 0x03cc }, /* Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */ { 0x07b8, 0x03cd }, /* Greek_upsilonaccent ύ GREEK SMALL LETTER UPSILON WITH TONOS */ { 0x07b9, 0x03cb }, /* Greek_upsilondieresis ϋ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */ { 0x07ba, 0x03b0 }, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */ { 0x07bb, 0x03ce }, /* Greek_omegaaccent ώ GREEK SMALL LETTER OMEGA WITH TONOS */ { 0x07c1, 0x0391 }, /* Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */ { 0x07c2, 0x0392 }, /* Greek_BETA Β GREEK CAPITAL LETTER BETA */ { 0x07c3, 0x0393 }, /* Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */ { 0x07c4, 0x0394 }, /* Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */ { 0x07c5, 0x0395 }, /* Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */ { 0x07c6, 0x0396 }, /* Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */ { 0x07c7, 0x0397 }, /* Greek_ETA Η GREEK CAPITAL LETTER ETA */ { 0x07c8, 0x0398 }, /* Greek_THETA Θ GREEK CAPITAL LETTER THETA */ { 0x07c9, 0x0399 }, /* Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */ { 0x07ca, 0x039a }, /* Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */ { 0x07cb, 0x039b }, /* Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMBDA */ { 0x07cc, 0x039c }, /* Greek_MU Μ GREEK CAPITAL LETTER MU */ { 0x07cd, 0x039d }, /* Greek_NU Ν GREEK CAPITAL LETTER NU */ { 0x07ce, 0x039e }, /* Greek_XI Ξ GREEK CAPITAL LETTER XI */ { 0x07cf, 0x039f }, /* Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */ { 0x07d0, 0x03a0 }, /* Greek_PI Π GREEK CAPITAL LETTER PI */ { 0x07d1, 0x03a1 }, /* Greek_RHO Ρ GREEK CAPITAL LETTER RHO */ { 0x07d2, 0x03a3 }, /* Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */ { 0x07d4, 0x03a4 }, /* Greek_TAU Τ GREEK CAPITAL LETTER TAU */ { 0x07d5, 0x03a5 }, /* Greek_UPSILON Υ GREEK CAPITAL LETTER UPSILON */ { 0x07d6, 0x03a6 }, /* Greek_PHI Φ GREEK CAPITAL LETTER PHI */ { 0x07d7, 0x03a7 }, /* Greek_CHI Χ GREEK CAPITAL LETTER CHI */ { 0x07d8, 0x03a8 }, /* Greek_PSI Ψ GREEK CAPITAL LETTER PSI */ { 0x07d9, 0x03a9 }, /* Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */ { 0x07e1, 0x03b1 }, /* Greek_alpha α GREEK SMALL LETTER ALPHA */ { 0x07e2, 0x03b2 }, /* Greek_beta β GREEK SMALL LETTER BETA */ { 0x07e3, 0x03b3 }, /* Greek_gamma γ GREEK SMALL LETTER GAMMA */ { 0x07e4, 0x03b4 }, /* Greek_delta δ GREEK SMALL LETTER DELTA */ { 0x07e5, 0x03b5 }, /* Greek_epsilon ε GREEK SMALL LETTER EPSILON */ { 0x07e6, 0x03b6 }, /* Greek_zeta ζ GREEK SMALL LETTER ZETA */ { 0x07e7, 0x03b7 }, /* Greek_eta η GREEK SMALL LETTER ETA */ { 0x07e8, 0x03b8 }, /* Greek_theta θ GREEK SMALL LETTER THETA */ { 0x07e9, 0x03b9 }, /* Greek_iota ι GREEK SMALL LETTER IOTA */ { 0x07ea, 0x03ba }, /* Greek_kappa κ GREEK SMALL LETTER KAPPA */ { 0x07eb, 0x03bb }, /* Greek_lambda λ GREEK SMALL LETTER LAMBDA */ { 0x07ec, 0x03bc }, /* Greek_mu μ GREEK SMALL LETTER MU */ { 0x07ed, 0x03bd }, /* Greek_nu ν GREEK SMALL LETTER NU */ { 0x07ee, 0x03be }, /* Greek_xi ξ GREEK SMALL LETTER XI */ { 0x07ef, 0x03bf }, /* Greek_omicron ο GREEK SMALL LETTER OMICRON */ { 0x07f0, 0x03c0 }, /* Greek_pi π GREEK SMALL LETTER PI */ { 0x07f1, 0x03c1 }, /* Greek_rho ρ GREEK SMALL LETTER RHO */ { 0x07f2, 0x03c3 }, /* Greek_sigma σ GREEK SMALL LETTER SIGMA */ { 0x07f3, 0x03c2 }, /* Greek_finalsmallsigma ς GREEK SMALL LETTER FINAL SIGMA */ { 0x07f4, 0x03c4 }, /* Greek_tau τ GREEK SMALL LETTER TAU */ { 0x07f5, 0x03c5 }, /* Greek_upsilon υ GREEK SMALL LETTER UPSILON */ { 0x07f6, 0x03c6 }, /* Greek_phi φ GREEK SMALL LETTER PHI */ { 0x07f7, 0x03c7 }, /* Greek_chi χ GREEK SMALL LETTER CHI */ { 0x07f8, 0x03c8 }, /* Greek_psi ψ GREEK SMALL LETTER PSI */ { 0x07f9, 0x03c9 }, /* Greek_omega ω GREEK SMALL LETTER OMEGA */ { 0x08a1, 0x23b7 }, /* leftradical ⎷ ??? */ { 0x08a2, 0x250c }, /* topleftradical ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */ { 0x08a3, 0x2500 }, /* horizconnector ─ BOX DRAWINGS LIGHT HORIZONTAL */ { 0x08a4, 0x2320 }, /* topintegral ⌠ TOP HALF INTEGRAL */ { 0x08a5, 0x2321 }, /* botintegral ⌡ BOTTOM HALF INTEGRAL */ { 0x08a6, 0x2502 }, /* vertconnector │ BOX DRAWINGS LIGHT VERTICAL */ { 0x08a7, 0x23a1 }, /* topleftsqbracket ⎡ ??? */ { 0x08a8, 0x23a3 }, /* botleftsqbracket ⎣ ??? */ { 0x08a9, 0x23a4 }, /* toprightsqbracket ⎤ ??? */ { 0x08aa, 0x23a6 }, /* botrightsqbracket ⎦ ??? */ { 0x08ab, 0x239b }, /* topleftparens ⎛ ??? */ { 0x08ac, 0x239d }, /* botleftparens ⎝ ??? */ { 0x08ad, 0x239e }, /* toprightparens ⎞ ??? */ { 0x08ae, 0x23a0 }, /* botrightparens ⎠ ??? */ { 0x08af, 0x23a8 }, /* leftmiddlecurlybrace ⎨ ??? */ { 0x08b0, 0x23ac }, /* rightmiddlecurlybrace ⎬ ??? */ /* 0x08b1 topleftsummation ? ??? */ /* 0x08b2 botleftsummation ? ??? */ /* 0x08b3 topvertsummationconnector ? ??? */ /* 0x08b4 botvertsummationconnector ? ??? */ /* 0x08b5 toprightsummation ? ??? */ /* 0x08b6 botrightsummation ? ??? */ /* 0x08b7 rightmiddlesummation ? ??? */ { 0x08bc, 0x2264 }, /* lessthanequal ≤ LESS-THAN OR EQUAL TO */ { 0x08bd, 0x2260 }, /* notequal ≠ NOT EQUAL TO */ { 0x08be, 0x2265 }, /* greaterthanequal ≥ GREATER-THAN OR EQUAL TO */ { 0x08bf, 0x222b }, /* integral ∫ INTEGRAL */ { 0x08c0, 0x2234 }, /* therefore ∴ THEREFORE */ { 0x08c1, 0x221d }, /* variation ∝ PROPORTIONAL TO */ { 0x08c2, 0x221e }, /* infinity ∞ INFINITY */ { 0x08c5, 0x2207 }, /* nabla ∇ NABLA */ { 0x08c8, 0x223c }, /* approximate ∼ TILDE OPERATOR */ { 0x08c9, 0x2243 }, /* similarequal ≃ ASYMPTOTICALLY EQUAL TO */ { 0x08cd, 0x21d4 }, /* ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */ { 0x08ce, 0x21d2 }, /* implies ⇒ RIGHTWARDS DOUBLE ARROW */ { 0x08cf, 0x2261 }, /* identical ≡ IDENTICAL TO */ { 0x08d6, 0x221a }, /* radical √ SQUARE ROOT */ { 0x08da, 0x2282 }, /* includedin ⊂ SUBSET OF */ { 0x08db, 0x2283 }, /* includes ⊃ SUPERSET OF */ { 0x08dc, 0x2229 }, /* intersection ∩ INTERSECTION */ { 0x08dd, 0x222a }, /* union ∪ UNION */ { 0x08de, 0x2227 }, /* logicaland ∧ LOGICAL AND */ { 0x08df, 0x2228 }, /* logicalor ∨ LOGICAL OR */ { 0x08ef, 0x2202 }, /* partialderivative ∂ PARTIAL DIFFERENTIAL */ { 0x08f6, 0x0192 }, /* function ƒ LATIN SMALL LETTER F WITH HOOK */ { 0x08fb, 0x2190 }, /* leftarrow ← LEFTWARDS ARROW */ { 0x08fc, 0x2191 }, /* uparrow ↑ UPWARDS ARROW */ { 0x08fd, 0x2192 }, /* rightarrow → RIGHTWARDS ARROW */ { 0x08fe, 0x2193 }, /* downarrow ↓ DOWNWARDS ARROW */ /* 0x09df blank ? ??? */ { 0x09e0, 0x25c6 }, /* soliddiamond ◆ BLACK DIAMOND */ { 0x09e1, 0x2592 }, /* checkerboard ▒ MEDIUM SHADE */ { 0x09e2, 0x2409 }, /* ht ␉ SYMBOL FOR HORIZONTAL TABULATION */ { 0x09e3, 0x240c }, /* ff ␌ SYMBOL FOR FORM FEED */ { 0x09e4, 0x240d }, /* cr ␍ SYMBOL FOR CARRIAGE RETURN */ { 0x09e5, 0x240a }, /* lf ␊ SYMBOL FOR LINE FEED */ { 0x09e8, 0x2424 }, /* nl ␤ SYMBOL FOR NEWLINE */ { 0x09e9, 0x240b }, /* vt ␋ SYMBOL FOR VERTICAL TABULATION */ { 0x09ea, 0x2518 }, /* lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */ { 0x09eb, 0x2510 }, /* uprightcorner ┐ BOX DRAWINGS LIGHT DOWN AND LEFT */ { 0x09ec, 0x250c }, /* upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */ { 0x09ed, 0x2514 }, /* lowleftcorner └ BOX DRAWINGS LIGHT UP AND RIGHT */ { 0x09ee, 0x253c }, /* crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ { 0x09ef, 0x23ba }, /* horizlinescan1 ⎺ HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */ { 0x09f0, 0x23bb }, /* horizlinescan3 ⎻ HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */ { 0x09f1, 0x2500 }, /* horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */ { 0x09f2, 0x23bc }, /* horizlinescan7 ⎼ HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */ { 0x09f3, 0x23bd }, /* horizlinescan9 ⎽ HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */ { 0x09f4, 0x251c }, /* leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ { 0x09f5, 0x2524 }, /* rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */ { 0x09f6, 0x2534 }, /* bott ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL */ { 0x09f7, 0x252c }, /* topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ { 0x09f8, 0x2502 }, /* vertbar │ BOX DRAWINGS LIGHT VERTICAL */ { 0x0aa1, 0x2003 }, /* emspace   EM SPACE */ { 0x0aa2, 0x2002 }, /* enspace   EN SPACE */ { 0x0aa3, 0x2004 }, /* em3space   THREE-PER-EM SPACE */ { 0x0aa4, 0x2005 }, /* em4space   FOUR-PER-EM SPACE */ { 0x0aa5, 0x2007 }, /* digitspace   FIGURE SPACE */ { 0x0aa6, 0x2008 }, /* punctspace   PUNCTUATION SPACE */ { 0x0aa7, 0x2009 }, /* thinspace   THIN SPACE */ { 0x0aa8, 0x200a }, /* hairspace   HAIR SPACE */ { 0x0aa9, 0x2014 }, /* emdash — EM DASH */ { 0x0aaa, 0x2013 }, /* endash – EN DASH */ { 0x0aac, 0x2423 }, /* signifblank ␣ OPEN BOX */ { 0x0aae, 0x2026 }, /* ellipsis … HORIZONTAL ELLIPSIS */ { 0x0aaf, 0x2025 }, /* doubbaselinedot ‥ TWO DOT LEADER */ { 0x0ab0, 0x2153 }, /* onethird ⅓ VULGAR FRACTION ONE THIRD */ { 0x0ab1, 0x2154 }, /* twothirds ⅔ VULGAR FRACTION TWO THIRDS */ { 0x0ab2, 0x2155 }, /* onefifth ⅕ VULGAR FRACTION ONE FIFTH */ { 0x0ab3, 0x2156 }, /* twofifths ⅖ VULGAR FRACTION TWO FIFTHS */ { 0x0ab4, 0x2157 }, /* threefifths ⅗ VULGAR FRACTION THREE FIFTHS */ { 0x0ab5, 0x2158 }, /* fourfifths ⅘ VULGAR FRACTION FOUR FIFTHS */ { 0x0ab6, 0x2159 }, /* onesixth ⅙ VULGAR FRACTION ONE SIXTH */ { 0x0ab7, 0x215a }, /* fivesixths ⅚ VULGAR FRACTION FIVE SIXTHS */ { 0x0ab8, 0x2105 }, /* careof ℅ CARE OF */ { 0x0abb, 0x2012 }, /* figdash ‒ FIGURE DASH */ { 0x0abc, 0x27e8 }, /* leftanglebracket ⟨ MATHEMATICAL LEFT ANGLE BRACKET */ { 0x0abd, 0x002e }, /* decimalpoint . FULL STOP */ { 0x0abe, 0x27e9 }, /* rightanglebracket ⟩ MATHEMATICAL RIGHT ANGLE BRACKET */ /* 0x0abf marker ? ??? */ { 0x0ac3, 0x215b }, /* oneeighth ⅛ VULGAR FRACTION ONE EIGHTH */ { 0x0ac4, 0x215c }, /* threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */ { 0x0ac5, 0x215d }, /* fiveeighths ⅝ VULGAR FRACTION FIVE EIGHTHS */ { 0x0ac6, 0x215e }, /* seveneighths ⅞ VULGAR FRACTION SEVEN EIGHTHS */ { 0x0ac9, 0x2122 }, /* trademark ™ TRADE MARK SIGN */ { 0x0aca, 0x2613 }, /* signaturemark ☓ SALTIRE */ /* 0x0acb trademarkincircle ? ??? */ { 0x0acc, 0x25c1 }, /* leftopentriangle ◁ WHITE LEFT-POINTING TRIANGLE */ { 0x0acd, 0x25b7 }, /* rightopentriangle ▷ WHITE RIGHT-POINTING TRIANGLE */ { 0x0ace, 0x25cb }, /* emopencircle ○ WHITE CIRCLE */ { 0x0acf, 0x25af }, /* emopenrectangle ▯ WHITE VERTICAL RECTANGLE */ { 0x0ad0, 0x2018 }, /* leftsinglequotemark ‘ LEFT SINGLE QUOTATION MARK */ { 0x0ad1, 0x2019 }, /* rightsinglequotemark ’ RIGHT SINGLE QUOTATION MARK */ { 0x0ad2, 0x201c }, /* leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */ { 0x0ad3, 0x201d }, /* rightdoublequotemark ” RIGHT DOUBLE QUOTATION MARK */ { 0x0ad4, 0x211e }, /* prescription ℞ PRESCRIPTION TAKE */ { 0x0ad5, 0x2030 }, /* permille ‰ PER MILLE SIGN */ { 0x0ad6, 0x2032 }, /* minutes ′ PRIME */ { 0x0ad7, 0x2033 }, /* seconds ″ DOUBLE PRIME */ { 0x0ad9, 0x271d }, /* latincross ✝ LATIN CROSS */ /* 0x0ada hexagram ? ??? */ { 0x0adb, 0x25ac }, /* filledrectbullet ▬ BLACK RECTANGLE */ { 0x0adc, 0x25c0 }, /* filledlefttribullet ◀ BLACK LEFT-POINTING TRIANGLE */ { 0x0add, 0x25b6 }, /* filledrighttribullet ▶ BLACK RIGHT-POINTING TRIANGLE */ { 0x0ade, 0x25cf }, /* emfilledcircle ● BLACK CIRCLE */ { 0x0adf, 0x25ae }, /* emfilledrect ▮ BLACK VERTICAL RECTANGLE */ { 0x0ae0, 0x25e6 }, /* enopencircbullet ◦ WHITE BULLET */ { 0x0ae1, 0x25ab }, /* enopensquarebullet ▫ WHITE SMALL SQUARE */ { 0x0ae2, 0x25ad }, /* openrectbullet ▭ WHITE RECTANGLE */ { 0x0ae3, 0x25b3 }, /* opentribulletup △ WHITE UP-POINTING TRIANGLE */ { 0x0ae4, 0x25bd }, /* opentribulletdown ▽ WHITE DOWN-POINTING TRIANGLE */ { 0x0ae5, 0x2606 }, /* openstar ☆ WHITE STAR */ { 0x0ae6, 0x2022 }, /* enfilledcircbullet • BULLET */ { 0x0ae7, 0x25aa }, /* enfilledsqbullet ▪ BLACK SMALL SQUARE */ { 0x0ae8, 0x25b2 }, /* filledtribulletup ▲ BLACK UP-POINTING TRIANGLE */ { 0x0ae9, 0x25bc }, /* filledtribulletdown ▼ BLACK DOWN-POINTING TRIANGLE */ { 0x0aea, 0x261c }, /* leftpointer ☜ WHITE LEFT POINTING INDEX */ { 0x0aeb, 0x261e }, /* rightpointer ☞ WHITE RIGHT POINTING INDEX */ { 0x0aec, 0x2663 }, /* club ♣ BLACK CLUB SUIT */ { 0x0aed, 0x2666 }, /* diamond ♦ BLACK DIAMOND SUIT */ { 0x0aee, 0x2665 }, /* heart ♥ BLACK HEART SUIT */ { 0x0af0, 0x2720 }, /* maltesecross ✠ MALTESE CROSS */ { 0x0af1, 0x2020 }, /* dagger † DAGGER */ { 0x0af2, 0x2021 }, /* doubledagger ‡ DOUBLE DAGGER */ { 0x0af3, 0x2713 }, /* checkmark ✓ CHECK MARK */ { 0x0af4, 0x2717 }, /* ballotcross ✗ BALLOT X */ { 0x0af5, 0x266f }, /* musicalsharp ♯ MUSIC SHARP SIGN */ { 0x0af6, 0x266d }, /* musicalflat ♭ MUSIC FLAT SIGN */ { 0x0af7, 0x2642 }, /* malesymbol ♂ MALE SIGN */ { 0x0af8, 0x2640 }, /* femalesymbol ♀ FEMALE SIGN */ { 0x0af9, 0x260e }, /* telephone ☎ BLACK TELEPHONE */ { 0x0afa, 0x2315 }, /* telephonerecorder ⌕ TELEPHONE RECORDER */ { 0x0afb, 0x2117 }, /* phonographcopyright ℗ SOUND RECORDING COPYRIGHT */ { 0x0afc, 0x2038 }, /* caret ‸ CARET */ { 0x0afd, 0x201a }, /* singlelowquotemark ‚ SINGLE LOW-9 QUOTATION MARK */ { 0x0afe, 0x201e }, /* doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */ /* 0x0aff cursor ? ??? */ { 0x0ba3, 0x003c }, /* leftcaret < LESS-THAN SIGN */ { 0x0ba6, 0x003e }, /* rightcaret > GREATER-THAN SIGN */ { 0x0ba8, 0x2228 }, /* downcaret ∨ LOGICAL OR */ { 0x0ba9, 0x2227 }, /* upcaret ∧ LOGICAL AND */ { 0x0bc0, 0x00af }, /* overbar ¯ MACRON */ { 0x0bc2, 0x22a4 }, /* downtack ⊤ DOWN TACK */ { 0x0bc3, 0x2229 }, /* upshoe ∩ INTERSECTION */ { 0x0bc4, 0x230a }, /* downstile ⌊ LEFT FLOOR */ { 0x0bc6, 0x005f }, /* underbar _ LOW LINE */ { 0x0bca, 0x2218 }, /* jot ∘ RING OPERATOR */ { 0x0bcc, 0x2395 }, /* quad ⎕ APL FUNCTIONAL SYMBOL QUAD (Unicode 3.0) */ { 0x0bce, 0x22a5 }, /* uptack ⊥ UP TACK */ { 0x0bcf, 0x25cb }, /* circle ○ WHITE CIRCLE */ { 0x0bd3, 0x2308 }, /* upstile ⌈ LEFT CEILING */ { 0x0bd6, 0x222a }, /* downshoe ∪ UNION */ { 0x0bd8, 0x2283 }, /* rightshoe ⊃ SUPERSET OF */ { 0x0bda, 0x2282 }, /* leftshoe ⊂ SUBSET OF */ { 0x0bdc, 0x22a2 }, /* lefttack ⊢ RIGHT TACK */ { 0x0bfc, 0x22a3 }, /* righttack ⊣ LEFT TACK */ { 0x0cdf, 0x2017 }, /* hebrew_doublelowline ‗ DOUBLE LOW LINE */ { 0x0ce0, 0x05d0 }, /* hebrew_aleph א HEBREW LETTER ALEF */ { 0x0ce1, 0x05d1 }, /* hebrew_bet ב HEBREW LETTER BET */ { 0x0ce2, 0x05d2 }, /* hebrew_gimel ג HEBREW LETTER GIMEL */ { 0x0ce3, 0x05d3 }, /* hebrew_dalet ד HEBREW LETTER DALET */ { 0x0ce4, 0x05d4 }, /* hebrew_he ה HEBREW LETTER HE */ { 0x0ce5, 0x05d5 }, /* hebrew_waw ו HEBREW LETTER VAV */ { 0x0ce6, 0x05d6 }, /* hebrew_zain ז HEBREW LETTER ZAYIN */ { 0x0ce7, 0x05d7 }, /* hebrew_chet ח HEBREW LETTER HET */ { 0x0ce8, 0x05d8 }, /* hebrew_tet ט HEBREW LETTER TET */ { 0x0ce9, 0x05d9 }, /* hebrew_yod י HEBREW LETTER YOD */ { 0x0cea, 0x05da }, /* hebrew_finalkaph ך HEBREW LETTER FINAL KAF */ { 0x0ceb, 0x05db }, /* hebrew_kaph כ HEBREW LETTER KAF */ { 0x0cec, 0x05dc }, /* hebrew_lamed ל HEBREW LETTER LAMED */ { 0x0ced, 0x05dd }, /* hebrew_finalmem ם HEBREW LETTER FINAL MEM */ { 0x0cee, 0x05de }, /* hebrew_mem מ HEBREW LETTER MEM */ { 0x0cef, 0x05df }, /* hebrew_finalnun ן HEBREW LETTER FINAL NUN */ { 0x0cf0, 0x05e0 }, /* hebrew_nun נ HEBREW LETTER NUN */ { 0x0cf1, 0x05e1 }, /* hebrew_samech ס HEBREW LETTER SAMEKH */ { 0x0cf2, 0x05e2 }, /* hebrew_ayin ע HEBREW LETTER AYIN */ { 0x0cf3, 0x05e3 }, /* hebrew_finalpe ף HEBREW LETTER FINAL PE */ { 0x0cf4, 0x05e4 }, /* hebrew_pe פ HEBREW LETTER PE */ { 0x0cf5, 0x05e5 }, /* hebrew_finalzade ץ HEBREW LETTER FINAL TSADI */ { 0x0cf6, 0x05e6 }, /* hebrew_zade צ HEBREW LETTER TSADI */ { 0x0cf7, 0x05e7 }, /* hebrew_qoph ק HEBREW LETTER QOF */ { 0x0cf8, 0x05e8 }, /* hebrew_resh ר HEBREW LETTER RESH */ { 0x0cf9, 0x05e9 }, /* hebrew_shin ש HEBREW LETTER SHIN */ { 0x0cfa, 0x05ea }, /* hebrew_taw ת HEBREW LETTER TAV */ { 0x0da1, 0x0e01 }, /* Thai_kokai ก THAI CHARACTER KO KAI */ { 0x0da2, 0x0e02 }, /* Thai_khokhai ข THAI CHARACTER KHO KHAI */ { 0x0da3, 0x0e03 }, /* Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */ { 0x0da4, 0x0e04 }, /* Thai_khokhwai ค THAI CHARACTER KHO KHWAI */ { 0x0da5, 0x0e05 }, /* Thai_khokhon ฅ THAI CHARACTER KHO KHON */ { 0x0da6, 0x0e06 }, /* Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */ { 0x0da7, 0x0e07 }, /* Thai_ngongu ง THAI CHARACTER NGO NGU */ { 0x0da8, 0x0e08 }, /* Thai_chochan จ THAI CHARACTER CHO CHAN */ { 0x0da9, 0x0e09 }, /* Thai_choching ฉ THAI CHARACTER CHO CHING */ { 0x0daa, 0x0e0a }, /* Thai_chochang ช THAI CHARACTER CHO CHANG */ { 0x0dab, 0x0e0b }, /* Thai_soso ซ THAI CHARACTER SO SO */ { 0x0dac, 0x0e0c }, /* Thai_chochoe ฌ THAI CHARACTER CHO CHOE */ { 0x0dad, 0x0e0d }, /* Thai_yoying ญ THAI CHARACTER YO YING */ { 0x0dae, 0x0e0e }, /* Thai_dochada ฎ THAI CHARACTER DO CHADA */ { 0x0daf, 0x0e0f }, /* Thai_topatak ฏ THAI CHARACTER TO PATAK */ { 0x0db0, 0x0e10 }, /* Thai_thothan ฐ THAI CHARACTER THO THAN */ { 0x0db1, 0x0e11 }, /* Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */ { 0x0db2, 0x0e12 }, /* Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */ { 0x0db3, 0x0e13 }, /* Thai_nonen ณ THAI CHARACTER NO NEN */ { 0x0db4, 0x0e14 }, /* Thai_dodek ด THAI CHARACTER DO DEK */ { 0x0db5, 0x0e15 }, /* Thai_totao ต THAI CHARACTER TO TAO */ { 0x0db6, 0x0e16 }, /* Thai_thothung ถ THAI CHARACTER THO THUNG */ { 0x0db7, 0x0e17 }, /* Thai_thothahan ท THAI CHARACTER THO THAHAN */ { 0x0db8, 0x0e18 }, /* Thai_thothong ธ THAI CHARACTER THO THONG */ { 0x0db9, 0x0e19 }, /* Thai_nonu น THAI CHARACTER NO NU */ { 0x0dba, 0x0e1a }, /* Thai_bobaimai บ THAI CHARACTER BO BAIMAI */ { 0x0dbb, 0x0e1b }, /* Thai_popla ป THAI CHARACTER PO PLA */ { 0x0dbc, 0x0e1c }, /* Thai_phophung ผ THAI CHARACTER PHO PHUNG */ { 0x0dbd, 0x0e1d }, /* Thai_fofa ฝ THAI CHARACTER FO FA */ { 0x0dbe, 0x0e1e }, /* Thai_phophan พ THAI CHARACTER PHO PHAN */ { 0x0dbf, 0x0e1f }, /* Thai_fofan ฟ THAI CHARACTER FO FAN */ { 0x0dc0, 0x0e20 }, /* Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */ { 0x0dc1, 0x0e21 }, /* Thai_moma ม THAI CHARACTER MO MA */ { 0x0dc2, 0x0e22 }, /* Thai_yoyak ย THAI CHARACTER YO YAK */ { 0x0dc3, 0x0e23 }, /* Thai_rorua ร THAI CHARACTER RO RUA */ { 0x0dc4, 0x0e24 }, /* Thai_ru ฤ THAI CHARACTER RU */ { 0x0dc5, 0x0e25 }, /* Thai_loling ล THAI CHARACTER LO LING */ { 0x0dc6, 0x0e26 }, /* Thai_lu ฦ THAI CHARACTER LU */ { 0x0dc7, 0x0e27 }, /* Thai_wowaen ว THAI CHARACTER WO WAEN */ { 0x0dc8, 0x0e28 }, /* Thai_sosala ศ THAI CHARACTER SO SALA */ { 0x0dc9, 0x0e29 }, /* Thai_sorusi ษ THAI CHARACTER SO RUSI */ { 0x0dca, 0x0e2a }, /* Thai_sosua ส THAI CHARACTER SO SUA */ { 0x0dcb, 0x0e2b }, /* Thai_hohip ห THAI CHARACTER HO HIP */ { 0x0dcc, 0x0e2c }, /* Thai_lochula ฬ THAI CHARACTER LO CHULA */ { 0x0dcd, 0x0e2d }, /* Thai_oang อ THAI CHARACTER O ANG */ { 0x0dce, 0x0e2e }, /* Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */ { 0x0dcf, 0x0e2f }, /* Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */ { 0x0dd0, 0x0e30 }, /* Thai_saraa ะ THAI CHARACTER SARA A */ { 0x0dd1, 0x0e31 }, /* Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */ { 0x0dd2, 0x0e32 }, /* Thai_saraaa า THAI CHARACTER SARA AA */ { 0x0dd3, 0x0e33 }, /* Thai_saraam ำ THAI CHARACTER SARA AM */ { 0x0dd4, 0x0e34 }, /* Thai_sarai ิ THAI CHARACTER SARA I */ { 0x0dd5, 0x0e35 }, /* Thai_saraii ี THAI CHARACTER SARA II */ { 0x0dd6, 0x0e36 }, /* Thai_saraue ึ THAI CHARACTER SARA UE */ { 0x0dd7, 0x0e37 }, /* Thai_sarauee ื THAI CHARACTER SARA UEE */ { 0x0dd8, 0x0e38 }, /* Thai_sarau ุ THAI CHARACTER SARA U */ { 0x0dd9, 0x0e39 }, /* Thai_sarauu ู THAI CHARACTER SARA UU */ { 0x0dda, 0x0e3a }, /* Thai_phinthu ฺ THAI CHARACTER PHINTHU */ { 0x0dde, 0x0e3e }, /* Thai_maihanakat_maitho ฾ ??? */ { 0x0ddf, 0x0e3f }, /* Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */ { 0x0de0, 0x0e40 }, /* Thai_sarae เ THAI CHARACTER SARA E */ { 0x0de1, 0x0e41 }, /* Thai_saraae แ THAI CHARACTER SARA AE */ { 0x0de2, 0x0e42 }, /* Thai_sarao โ THAI CHARACTER SARA O */ { 0x0de3, 0x0e43 }, /* Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */ { 0x0de4, 0x0e44 }, /* Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */ { 0x0de5, 0x0e45 }, /* Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */ { 0x0de6, 0x0e46 }, /* Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */ { 0x0de7, 0x0e47 }, /* Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */ { 0x0de8, 0x0e48 }, /* Thai_maiek ่ THAI CHARACTER MAI EK */ { 0x0de9, 0x0e49 }, /* Thai_maitho ้ THAI CHARACTER MAI THO */ { 0x0dea, 0x0e4a }, /* Thai_maitri ๊ THAI CHARACTER MAI TRI */ { 0x0deb, 0x0e4b }, /* Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */ { 0x0dec, 0x0e4c }, /* Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */ { 0x0ded, 0x0e4d }, /* Thai_nikhahit ํ THAI CHARACTER NIKHAHIT */ { 0x0df0, 0x0e50 }, /* Thai_leksun ๐ THAI DIGIT ZERO */ { 0x0df1, 0x0e51 }, /* Thai_leknung ๑ THAI DIGIT ONE */ { 0x0df2, 0x0e52 }, /* Thai_leksong ๒ THAI DIGIT TWO */ { 0x0df3, 0x0e53 }, /* Thai_leksam ๓ THAI DIGIT THREE */ { 0x0df4, 0x0e54 }, /* Thai_leksi ๔ THAI DIGIT FOUR */ { 0x0df5, 0x0e55 }, /* Thai_lekha ๕ THAI DIGIT FIVE */ { 0x0df6, 0x0e56 }, /* Thai_lekhok ๖ THAI DIGIT SIX */ { 0x0df7, 0x0e57 }, /* Thai_lekchet ๗ THAI DIGIT SEVEN */ { 0x0df8, 0x0e58 }, /* Thai_lekpaet ๘ THAI DIGIT EIGHT */ { 0x0df9, 0x0e59 }, /* Thai_lekkao ๙ THAI DIGIT NINE */ { 0x0ea1, 0x3131 }, /* Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */ { 0x0ea2, 0x3132 }, /* Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */ { 0x0ea3, 0x3133 }, /* Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */ { 0x0ea4, 0x3134 }, /* Hangul_Nieun ㄴ HANGUL LETTER NIEUN */ { 0x0ea5, 0x3135 }, /* Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */ { 0x0ea6, 0x3136 }, /* Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */ { 0x0ea7, 0x3137 }, /* Hangul_Dikeud ㄷ HANGUL LETTER TIKEUT */ { 0x0ea8, 0x3138 }, /* Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */ { 0x0ea9, 0x3139 }, /* Hangul_Rieul ㄹ HANGUL LETTER RIEUL */ { 0x0eaa, 0x313a }, /* Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */ { 0x0eab, 0x313b }, /* Hangul_RieulMieum ㄻ HANGUL LETTER RIEUL-MIEUM */ { 0x0eac, 0x313c }, /* Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */ { 0x0ead, 0x313d }, /* Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */ { 0x0eae, 0x313e }, /* Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */ { 0x0eaf, 0x313f }, /* Hangul_RieulPhieuf ㄿ HANGUL LETTER RIEUL-PHIEUPH */ { 0x0eb0, 0x3140 }, /* Hangul_RieulHieuh ㅀ HANGUL LETTER RIEUL-HIEUH */ { 0x0eb1, 0x3141 }, /* Hangul_Mieum ㅁ HANGUL LETTER MIEUM */ { 0x0eb2, 0x3142 }, /* Hangul_Pieub ㅂ HANGUL LETTER PIEUP */ { 0x0eb3, 0x3143 }, /* Hangul_SsangPieub ㅃ HANGUL LETTER SSANGPIEUP */ { 0x0eb4, 0x3144 }, /* Hangul_PieubSios ㅄ HANGUL LETTER PIEUP-SIOS */ { 0x0eb5, 0x3145 }, /* Hangul_Sios ㅅ HANGUL LETTER SIOS */ { 0x0eb6, 0x3146 }, /* Hangul_SsangSios ㅆ HANGUL LETTER SSANGSIOS */ { 0x0eb7, 0x3147 }, /* Hangul_Ieung ㅇ HANGUL LETTER IEUNG */ { 0x0eb8, 0x3148 }, /* Hangul_Jieuj ㅈ HANGUL LETTER CIEUC */ { 0x0eb9, 0x3149 }, /* Hangul_SsangJieuj ㅉ HANGUL LETTER SSANGCIEUC */ { 0x0eba, 0x314a }, /* Hangul_Cieuc ㅊ HANGUL LETTER CHIEUCH */ { 0x0ebb, 0x314b }, /* Hangul_Khieuq ㅋ HANGUL LETTER KHIEUKH */ { 0x0ebc, 0x314c }, /* Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */ { 0x0ebd, 0x314d }, /* Hangul_Phieuf ㅍ HANGUL LETTER PHIEUPH */ { 0x0ebe, 0x314e }, /* Hangul_Hieuh ㅎ HANGUL LETTER HIEUH */ { 0x0ebf, 0x314f }, /* Hangul_A ㅏ HANGUL LETTER A */ { 0x0ec0, 0x3150 }, /* Hangul_AE ㅐ HANGUL LETTER AE */ { 0x0ec1, 0x3151 }, /* Hangul_YA ㅑ HANGUL LETTER YA */ { 0x0ec2, 0x3152 }, /* Hangul_YAE ㅒ HANGUL LETTER YAE */ { 0x0ec3, 0x3153 }, /* Hangul_EO ㅓ HANGUL LETTER EO */ { 0x0ec4, 0x3154 }, /* Hangul_E ㅔ HANGUL LETTER E */ { 0x0ec5, 0x3155 }, /* Hangul_YEO ㅕ HANGUL LETTER YEO */ { 0x0ec6, 0x3156 }, /* Hangul_YE ㅖ HANGUL LETTER YE */ { 0x0ec7, 0x3157 }, /* Hangul_O ㅗ HANGUL LETTER O */ { 0x0ec8, 0x3158 }, /* Hangul_WA ㅘ HANGUL LETTER WA */ { 0x0ec9, 0x3159 }, /* Hangul_WAE ㅙ HANGUL LETTER WAE */ { 0x0eca, 0x315a }, /* Hangul_OE ㅚ HANGUL LETTER OE */ { 0x0ecb, 0x315b }, /* Hangul_YO ㅛ HANGUL LETTER YO */ { 0x0ecc, 0x315c }, /* Hangul_U ㅜ HANGUL LETTER U */ { 0x0ecd, 0x315d }, /* Hangul_WEO ㅝ HANGUL LETTER WEO */ { 0x0ece, 0x315e }, /* Hangul_WE ㅞ HANGUL LETTER WE */ { 0x0ecf, 0x315f }, /* Hangul_WI ㅟ HANGUL LETTER WI */ { 0x0ed0, 0x3160 }, /* Hangul_YU ㅠ HANGUL LETTER YU */ { 0x0ed1, 0x3161 }, /* Hangul_EU ㅡ HANGUL LETTER EU */ { 0x0ed2, 0x3162 }, /* Hangul_YI ㅢ HANGUL LETTER YI */ { 0x0ed3, 0x3163 }, /* Hangul_I ㅣ HANGUL LETTER I */ { 0x0ed4, 0x11a8 }, /* Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */ { 0x0ed5, 0x11a9 }, /* Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */ { 0x0ed6, 0x11aa }, /* Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */ { 0x0ed7, 0x11ab }, /* Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */ { 0x0ed8, 0x11ac }, /* Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */ { 0x0ed9, 0x11ad }, /* Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */ { 0x0eda, 0x11ae }, /* Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */ { 0x0edb, 0x11af }, /* Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */ { 0x0edc, 0x11b0 }, /* Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */ { 0x0edd, 0x11b1 }, /* Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */ { 0x0ede, 0x11b2 }, /* Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */ { 0x0edf, 0x11b3 }, /* Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */ { 0x0ee0, 0x11b4 }, /* Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */ { 0x0ee1, 0x11b5 }, /* Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */ { 0x0ee2, 0x11b6 }, /* Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */ { 0x0ee3, 0x11b7 }, /* Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */ { 0x0ee4, 0x11b8 }, /* Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */ { 0x0ee5, 0x11b9 }, /* Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */ { 0x0ee6, 0x11ba }, /* Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */ { 0x0ee7, 0x11bb }, /* Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */ { 0x0ee8, 0x11bc }, /* Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */ { 0x0ee9, 0x11bd }, /* Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */ { 0x0eea, 0x11be }, /* Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */ { 0x0eeb, 0x11bf }, /* Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */ { 0x0eec, 0x11c0 }, /* Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */ { 0x0eed, 0x11c1 }, /* Hangul_J_Phieuf ᇁ HANGUL JONGSEONG PHIEUPH */ { 0x0eee, 0x11c2 }, /* Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */ { 0x0eef, 0x316d }, /* Hangul_RieulYeorinHieuh ㅭ HANGUL LETTER RIEUL-YEORINHIEUH */ { 0x0ef0, 0x3171 }, /* Hangul_SunkyeongeumMieum ㅱ HANGUL LETTER KAPYEOUNMIEUM */ { 0x0ef1, 0x3178 }, /* Hangul_SunkyeongeumPieub ㅸ HANGUL LETTER KAPYEOUNPIEUP */ { 0x0ef2, 0x317f }, /* Hangul_PanSios ㅿ HANGUL LETTER PANSIOS */ /* 0x0ef3 Hangul_KkogjiDalrinIeung ? ??? */ { 0x0ef4, 0x3184 }, /* Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */ { 0x0ef5, 0x3186 }, /* Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */ { 0x0ef6, 0x318d }, /* Hangul_AraeA ㆍ HANGUL LETTER ARAEA */ { 0x0ef7, 0x318e }, /* Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */ { 0x0ef8, 0x11eb }, /* Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */ { 0x0ef9, 0x11f0 }, /* Hangul_J_KkogjiDalrinIeung ᇰ HANGUL JONGSEONG YESIEUNG */ { 0x0efa, 0x11f9 }, /* Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */ { 0x0eff, 0x20a9 }, /* Korean_Won ₩ WON SIGN */ { 0x13a4, 0x20ac }, /* Euro € EURO SIGN */ { 0x13bc, 0x0152 }, /* OE Œ LATIN CAPITAL LIGATURE OE */ { 0x13bd, 0x0153 }, /* oe œ LATIN SMALL LIGATURE OE */ { 0x13be, 0x0178 }, /* Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */ { 0x20a0, 0x20a0 }, /* EcuSign ₠ EURO-CURRENCY SIGN */ { 0x20a1, 0x20a1 }, /* ColonSign ₡ COLON SIGN */ { 0x20a2, 0x20a2 }, /* CruzeiroSign ₢ CRUZEIRO SIGN */ { 0x20a3, 0x20a3 }, /* FFrancSign ₣ FRENCH FRANC SIGN */ { 0x20a4, 0x20a4 }, /* LiraSign ₤ LIRA SIGN */ { 0x20a5, 0x20a5 }, /* MillSign ₥ MILL SIGN */ { 0x20a6, 0x20a6 }, /* NairaSign ₦ NAIRA SIGN */ { 0x20a7, 0x20a7 }, /* PesetaSign ₧ PESETA SIGN */ { 0x20a8, 0x20a8 }, /* RupeeSign ₨ RUPEE SIGN */ { 0x20a9, 0x20a9 }, /* WonSign ₩ WON SIGN */ { 0x20aa, 0x20aa }, /* NewSheqelSign ₪ NEW SHEQEL SIGN */ { 0x20ab, 0x20ab }, /* DongSign ₫ DONG SIGN */ { 0x20ac, 0x20ac }, /* EuroSign € EURO SIGN */ }; xkb_keysym_t utf32_to_keysym(uint32_t ucs) { /* first check for Latin-1 characters (1:1 mapping) */ if ((ucs >= 0x0020 && ucs <= 0x007e) || (ucs >= 0x00a0 && ucs <= 0x00ff)) return ucs; /* special keysyms */ if ((ucs >= (XKB_KEY_BackSpace & 0x7f) && ucs <= (XKB_KEY_Clear & 0x7f)) || ucs == (XKB_KEY_Return & 0x7f) || ucs == (XKB_KEY_Escape & 0x7f)) return ucs | 0xff00; if (ucs == (XKB_KEY_Delete & 0x7f)) return XKB_KEY_Delete; /* Unicode non-symbols and code points outside Unicode planes */ if ((ucs >= 0xfdd0 && ucs <= 0xfdef) || ucs > 0x10ffff || (ucs & 0xfffe) == 0xfffe) return XKB_KEY_NoSymbol; /* search main table */ for (size_t i = 0; i < sizeof(keysymtab)/sizeof(keysymtab[0]); i++) if (keysymtab[i].ucs == ucs) return keysymtab[i].keysym; /* Use direct encoding if everything else fails */ return ucs | 0x01000000; } kitty-0.41.1/glfw/xkb_glfw.c0000664000175000017510000012540514773370543015224 0ustar nileshnilesh//======================================================================== // GLFW 3.4 XKB - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2018 Kovid Goyal // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #include #include #include "internal.h" #include "xkb_glfw.h" #ifdef _GLFW_X11 #include #endif #define debug debug_input #ifdef XKB_HAS_NO_UTF32 #include "xkb-compat-shim.h" #else #define utf32_to_keysym xkb_utf32_to_keysym #endif static int glfw_key_for_sym(xkb_keysym_t key) { switch(key) { /* start xkb to glfw (auto generated by gen-key-constants.py do not edit) */ case XKB_KEY_Escape: return GLFW_FKEY_ESCAPE; case XKB_KEY_Return: return GLFW_FKEY_ENTER; case XKB_KEY_Tab: return GLFW_FKEY_TAB; case XKB_KEY_BackSpace: return GLFW_FKEY_BACKSPACE; case XKB_KEY_Insert: return GLFW_FKEY_INSERT; case XKB_KEY_Delete: return GLFW_FKEY_DELETE; case XKB_KEY_Left: return GLFW_FKEY_LEFT; case XKB_KEY_Right: return GLFW_FKEY_RIGHT; case XKB_KEY_Up: return GLFW_FKEY_UP; case XKB_KEY_Down: return GLFW_FKEY_DOWN; case XKB_KEY_Page_Up: return GLFW_FKEY_PAGE_UP; case XKB_KEY_Page_Down: return GLFW_FKEY_PAGE_DOWN; case XKB_KEY_Home: return GLFW_FKEY_HOME; case XKB_KEY_End: return GLFW_FKEY_END; case XKB_KEY_Caps_Lock: return GLFW_FKEY_CAPS_LOCK; case XKB_KEY_Scroll_Lock: return GLFW_FKEY_SCROLL_LOCK; case XKB_KEY_Num_Lock: return GLFW_FKEY_NUM_LOCK; case XKB_KEY_Print: return GLFW_FKEY_PRINT_SCREEN; case XKB_KEY_Pause: return GLFW_FKEY_PAUSE; case XKB_KEY_Menu: return GLFW_FKEY_MENU; case XKB_KEY_F1: return GLFW_FKEY_F1; case XKB_KEY_F2: return GLFW_FKEY_F2; case XKB_KEY_F3: return GLFW_FKEY_F3; case XKB_KEY_F4: return GLFW_FKEY_F4; case XKB_KEY_F5: return GLFW_FKEY_F5; case XKB_KEY_F6: return GLFW_FKEY_F6; case XKB_KEY_F7: return GLFW_FKEY_F7; case XKB_KEY_F8: return GLFW_FKEY_F8; case XKB_KEY_F9: return GLFW_FKEY_F9; case XKB_KEY_F10: return GLFW_FKEY_F10; case XKB_KEY_F11: return GLFW_FKEY_F11; case XKB_KEY_F12: return GLFW_FKEY_F12; case XKB_KEY_F13: return GLFW_FKEY_F13; case XKB_KEY_F14: return GLFW_FKEY_F14; case XKB_KEY_F15: return GLFW_FKEY_F15; case XKB_KEY_F16: return GLFW_FKEY_F16; case XKB_KEY_F17: return GLFW_FKEY_F17; case XKB_KEY_F18: return GLFW_FKEY_F18; case XKB_KEY_F19: return GLFW_FKEY_F19; case XKB_KEY_F20: return GLFW_FKEY_F20; case XKB_KEY_F21: return GLFW_FKEY_F21; case XKB_KEY_F22: return GLFW_FKEY_F22; case XKB_KEY_F23: return GLFW_FKEY_F23; case XKB_KEY_F24: return GLFW_FKEY_F24; case XKB_KEY_F25: return GLFW_FKEY_F25; case XKB_KEY_F26: return GLFW_FKEY_F26; case XKB_KEY_F27: return GLFW_FKEY_F27; case XKB_KEY_F28: return GLFW_FKEY_F28; case XKB_KEY_F29: return GLFW_FKEY_F29; case XKB_KEY_F30: return GLFW_FKEY_F30; case XKB_KEY_F31: return GLFW_FKEY_F31; case XKB_KEY_F32: return GLFW_FKEY_F32; case XKB_KEY_F33: return GLFW_FKEY_F33; case XKB_KEY_F34: return GLFW_FKEY_F34; case XKB_KEY_F35: return GLFW_FKEY_F35; case XKB_KEY_KP_0: return GLFW_FKEY_KP_0; case XKB_KEY_KP_1: return GLFW_FKEY_KP_1; case XKB_KEY_KP_2: return GLFW_FKEY_KP_2; case XKB_KEY_KP_3: return GLFW_FKEY_KP_3; case XKB_KEY_KP_4: return GLFW_FKEY_KP_4; case XKB_KEY_KP_5: return GLFW_FKEY_KP_5; case XKB_KEY_KP_6: return GLFW_FKEY_KP_6; case XKB_KEY_KP_7: return GLFW_FKEY_KP_7; case XKB_KEY_KP_8: return GLFW_FKEY_KP_8; case XKB_KEY_KP_9: return GLFW_FKEY_KP_9; case XKB_KEY_KP_Decimal: return GLFW_FKEY_KP_DECIMAL; case XKB_KEY_KP_Divide: return GLFW_FKEY_KP_DIVIDE; case XKB_KEY_KP_Multiply: return GLFW_FKEY_KP_MULTIPLY; case XKB_KEY_KP_Subtract: return GLFW_FKEY_KP_SUBTRACT; case XKB_KEY_KP_Add: return GLFW_FKEY_KP_ADD; case XKB_KEY_KP_Enter: return GLFW_FKEY_KP_ENTER; case XKB_KEY_KP_Equal: return GLFW_FKEY_KP_EQUAL; case XKB_KEY_KP_Separator: return GLFW_FKEY_KP_SEPARATOR; case XKB_KEY_KP_Left: return GLFW_FKEY_KP_LEFT; case XKB_KEY_KP_Right: return GLFW_FKEY_KP_RIGHT; case XKB_KEY_KP_Up: return GLFW_FKEY_KP_UP; case XKB_KEY_KP_Down: return GLFW_FKEY_KP_DOWN; case XKB_KEY_KP_Page_Up: return GLFW_FKEY_KP_PAGE_UP; case XKB_KEY_KP_Page_Down: return GLFW_FKEY_KP_PAGE_DOWN; case XKB_KEY_KP_Home: return GLFW_FKEY_KP_HOME; case XKB_KEY_KP_End: return GLFW_FKEY_KP_END; case XKB_KEY_KP_Insert: return GLFW_FKEY_KP_INSERT; case XKB_KEY_KP_Delete: return GLFW_FKEY_KP_DELETE; case XKB_KEY_KP_Begin: return GLFW_FKEY_KP_BEGIN; case XKB_KEY_XF86AudioPlay: return GLFW_FKEY_MEDIA_PLAY; case XKB_KEY_XF86AudioPause: return GLFW_FKEY_MEDIA_PAUSE; case XKB_KEY_XF86AudioStop: return GLFW_FKEY_MEDIA_STOP; case XKB_KEY_XF86AudioForward: return GLFW_FKEY_MEDIA_FAST_FORWARD; case XKB_KEY_XF86AudioRewind: return GLFW_FKEY_MEDIA_REWIND; case XKB_KEY_XF86AudioNext: return GLFW_FKEY_MEDIA_TRACK_NEXT; case XKB_KEY_XF86AudioPrev: return GLFW_FKEY_MEDIA_TRACK_PREVIOUS; case XKB_KEY_XF86AudioRecord: return GLFW_FKEY_MEDIA_RECORD; case XKB_KEY_XF86AudioLowerVolume: return GLFW_FKEY_LOWER_VOLUME; case XKB_KEY_XF86AudioRaiseVolume: return GLFW_FKEY_RAISE_VOLUME; case XKB_KEY_XF86AudioMute: return GLFW_FKEY_MUTE_VOLUME; case XKB_KEY_Shift_L: return GLFW_FKEY_LEFT_SHIFT; case XKB_KEY_Control_L: return GLFW_FKEY_LEFT_CONTROL; case XKB_KEY_Alt_L: return GLFW_FKEY_LEFT_ALT; case XKB_KEY_Super_L: return GLFW_FKEY_LEFT_SUPER; case XKB_KEY_Hyper_L: return GLFW_FKEY_LEFT_HYPER; case XKB_KEY_Meta_L: return GLFW_FKEY_LEFT_META; case XKB_KEY_Shift_R: return GLFW_FKEY_RIGHT_SHIFT; case XKB_KEY_Control_R: return GLFW_FKEY_RIGHT_CONTROL; case XKB_KEY_Alt_R: return GLFW_FKEY_RIGHT_ALT; case XKB_KEY_Super_R: return GLFW_FKEY_RIGHT_SUPER; case XKB_KEY_Hyper_R: return GLFW_FKEY_RIGHT_HYPER; case XKB_KEY_Meta_R: return GLFW_FKEY_RIGHT_META; case XKB_KEY_ISO_Level3_Shift: return GLFW_FKEY_ISO_LEVEL3_SHIFT; case XKB_KEY_ISO_Level5_Shift: return GLFW_FKEY_ISO_LEVEL5_SHIFT; /* end xkb to glfw */ default: return xkb_keysym_to_utf32(key); } } xkb_keysym_t glfw_xkb_sym_for_key(uint32_t key) { switch(key) { /* start glfw to xkb (auto generated by gen-key-constants.py do not edit) */ case GLFW_FKEY_ESCAPE: return XKB_KEY_Escape; case GLFW_FKEY_ENTER: return XKB_KEY_Return; case GLFW_FKEY_TAB: return XKB_KEY_Tab; case GLFW_FKEY_BACKSPACE: return XKB_KEY_BackSpace; case GLFW_FKEY_INSERT: return XKB_KEY_Insert; case GLFW_FKEY_DELETE: return XKB_KEY_Delete; case GLFW_FKEY_LEFT: return XKB_KEY_Left; case GLFW_FKEY_RIGHT: return XKB_KEY_Right; case GLFW_FKEY_UP: return XKB_KEY_Up; case GLFW_FKEY_DOWN: return XKB_KEY_Down; case GLFW_FKEY_PAGE_UP: return XKB_KEY_Page_Up; case GLFW_FKEY_PAGE_DOWN: return XKB_KEY_Page_Down; case GLFW_FKEY_HOME: return XKB_KEY_Home; case GLFW_FKEY_END: return XKB_KEY_End; case GLFW_FKEY_CAPS_LOCK: return XKB_KEY_Caps_Lock; case GLFW_FKEY_SCROLL_LOCK: return XKB_KEY_Scroll_Lock; case GLFW_FKEY_NUM_LOCK: return XKB_KEY_Num_Lock; case GLFW_FKEY_PRINT_SCREEN: return XKB_KEY_Print; case GLFW_FKEY_PAUSE: return XKB_KEY_Pause; case GLFW_FKEY_MENU: return XKB_KEY_Menu; case GLFW_FKEY_F1: return XKB_KEY_F1; case GLFW_FKEY_F2: return XKB_KEY_F2; case GLFW_FKEY_F3: return XKB_KEY_F3; case GLFW_FKEY_F4: return XKB_KEY_F4; case GLFW_FKEY_F5: return XKB_KEY_F5; case GLFW_FKEY_F6: return XKB_KEY_F6; case GLFW_FKEY_F7: return XKB_KEY_F7; case GLFW_FKEY_F8: return XKB_KEY_F8; case GLFW_FKEY_F9: return XKB_KEY_F9; case GLFW_FKEY_F10: return XKB_KEY_F10; case GLFW_FKEY_F11: return XKB_KEY_F11; case GLFW_FKEY_F12: return XKB_KEY_F12; case GLFW_FKEY_F13: return XKB_KEY_F13; case GLFW_FKEY_F14: return XKB_KEY_F14; case GLFW_FKEY_F15: return XKB_KEY_F15; case GLFW_FKEY_F16: return XKB_KEY_F16; case GLFW_FKEY_F17: return XKB_KEY_F17; case GLFW_FKEY_F18: return XKB_KEY_F18; case GLFW_FKEY_F19: return XKB_KEY_F19; case GLFW_FKEY_F20: return XKB_KEY_F20; case GLFW_FKEY_F21: return XKB_KEY_F21; case GLFW_FKEY_F22: return XKB_KEY_F22; case GLFW_FKEY_F23: return XKB_KEY_F23; case GLFW_FKEY_F24: return XKB_KEY_F24; case GLFW_FKEY_F25: return XKB_KEY_F25; case GLFW_FKEY_F26: return XKB_KEY_F26; case GLFW_FKEY_F27: return XKB_KEY_F27; case GLFW_FKEY_F28: return XKB_KEY_F28; case GLFW_FKEY_F29: return XKB_KEY_F29; case GLFW_FKEY_F30: return XKB_KEY_F30; case GLFW_FKEY_F31: return XKB_KEY_F31; case GLFW_FKEY_F32: return XKB_KEY_F32; case GLFW_FKEY_F33: return XKB_KEY_F33; case GLFW_FKEY_F34: return XKB_KEY_F34; case GLFW_FKEY_F35: return XKB_KEY_F35; case GLFW_FKEY_KP_0: return XKB_KEY_KP_0; case GLFW_FKEY_KP_1: return XKB_KEY_KP_1; case GLFW_FKEY_KP_2: return XKB_KEY_KP_2; case GLFW_FKEY_KP_3: return XKB_KEY_KP_3; case GLFW_FKEY_KP_4: return XKB_KEY_KP_4; case GLFW_FKEY_KP_5: return XKB_KEY_KP_5; case GLFW_FKEY_KP_6: return XKB_KEY_KP_6; case GLFW_FKEY_KP_7: return XKB_KEY_KP_7; case GLFW_FKEY_KP_8: return XKB_KEY_KP_8; case GLFW_FKEY_KP_9: return XKB_KEY_KP_9; case GLFW_FKEY_KP_DECIMAL: return XKB_KEY_KP_Decimal; case GLFW_FKEY_KP_DIVIDE: return XKB_KEY_KP_Divide; case GLFW_FKEY_KP_MULTIPLY: return XKB_KEY_KP_Multiply; case GLFW_FKEY_KP_SUBTRACT: return XKB_KEY_KP_Subtract; case GLFW_FKEY_KP_ADD: return XKB_KEY_KP_Add; case GLFW_FKEY_KP_ENTER: return XKB_KEY_KP_Enter; case GLFW_FKEY_KP_EQUAL: return XKB_KEY_KP_Equal; case GLFW_FKEY_KP_SEPARATOR: return XKB_KEY_KP_Separator; case GLFW_FKEY_KP_LEFT: return XKB_KEY_KP_Left; case GLFW_FKEY_KP_RIGHT: return XKB_KEY_KP_Right; case GLFW_FKEY_KP_UP: return XKB_KEY_KP_Up; case GLFW_FKEY_KP_DOWN: return XKB_KEY_KP_Down; case GLFW_FKEY_KP_PAGE_UP: return XKB_KEY_KP_Page_Up; case GLFW_FKEY_KP_PAGE_DOWN: return XKB_KEY_KP_Page_Down; case GLFW_FKEY_KP_HOME: return XKB_KEY_KP_Home; case GLFW_FKEY_KP_END: return XKB_KEY_KP_End; case GLFW_FKEY_KP_INSERT: return XKB_KEY_KP_Insert; case GLFW_FKEY_KP_DELETE: return XKB_KEY_KP_Delete; case GLFW_FKEY_KP_BEGIN: return XKB_KEY_KP_Begin; case GLFW_FKEY_MEDIA_PLAY: return XKB_KEY_XF86AudioPlay; case GLFW_FKEY_MEDIA_PAUSE: return XKB_KEY_XF86AudioPause; case GLFW_FKEY_MEDIA_STOP: return XKB_KEY_XF86AudioStop; case GLFW_FKEY_MEDIA_FAST_FORWARD: return XKB_KEY_XF86AudioForward; case GLFW_FKEY_MEDIA_REWIND: return XKB_KEY_XF86AudioRewind; case GLFW_FKEY_MEDIA_TRACK_NEXT: return XKB_KEY_XF86AudioNext; case GLFW_FKEY_MEDIA_TRACK_PREVIOUS: return XKB_KEY_XF86AudioPrev; case GLFW_FKEY_MEDIA_RECORD: return XKB_KEY_XF86AudioRecord; case GLFW_FKEY_LOWER_VOLUME: return XKB_KEY_XF86AudioLowerVolume; case GLFW_FKEY_RAISE_VOLUME: return XKB_KEY_XF86AudioRaiseVolume; case GLFW_FKEY_MUTE_VOLUME: return XKB_KEY_XF86AudioMute; case GLFW_FKEY_LEFT_SHIFT: return XKB_KEY_Shift_L; case GLFW_FKEY_LEFT_CONTROL: return XKB_KEY_Control_L; case GLFW_FKEY_LEFT_ALT: return XKB_KEY_Alt_L; case GLFW_FKEY_LEFT_SUPER: return XKB_KEY_Super_L; case GLFW_FKEY_LEFT_HYPER: return XKB_KEY_Hyper_L; case GLFW_FKEY_LEFT_META: return XKB_KEY_Meta_L; case GLFW_FKEY_RIGHT_SHIFT: return XKB_KEY_Shift_R; case GLFW_FKEY_RIGHT_CONTROL: return XKB_KEY_Control_R; case GLFW_FKEY_RIGHT_ALT: return XKB_KEY_Alt_R; case GLFW_FKEY_RIGHT_SUPER: return XKB_KEY_Super_R; case GLFW_FKEY_RIGHT_HYPER: return XKB_KEY_Hyper_R; case GLFW_FKEY_RIGHT_META: return XKB_KEY_Meta_R; case GLFW_FKEY_ISO_LEVEL3_SHIFT: return XKB_KEY_ISO_Level3_Shift; case GLFW_FKEY_ISO_LEVEL5_SHIFT: return XKB_KEY_ISO_Level5_Shift; /* end glfw to xkb */ default: return utf32_to_keysym(key); } } #ifdef _GLFW_X11 bool glfw_xkb_set_x11_events_mask(void) { if (!XkbSelectEvents(_glfw.x11.display, XkbUseCoreKbd, XkbNewKeyboardNotifyMask | XkbMapNotifyMask | XkbStateNotifyMask, XkbNewKeyboardNotifyMask | XkbMapNotifyMask | XkbStateNotifyMask)) { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set XKB events mask"); return false; } return true; } bool glfw_xkb_update_x11_keyboard_id(_GLFWXKBData *xkb) { xkb->keyboard_device_id = -1; xcb_connection_t* conn = XGetXCBConnection(_glfw.x11.display); if (!conn) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to retrieve XCB connection"); return false; } xkb->keyboard_device_id = xkb_x11_get_core_keyboard_device_id(conn); if (xkb->keyboard_device_id == -1) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to retrieve core keyboard device id"); return false; } return true; } #define xkb_glfw_load_keymap(keymap, ...) {\ xcb_connection_t* conn = XGetXCBConnection(_glfw.x11.display); \ if (conn) keymap = xkb_x11_keymap_new_from_device(xkb->context, conn, xkb->keyboard_device_id, XKB_KEYMAP_COMPILE_NO_FLAGS); \ } #define xkb_glfw_load_state(keymap, state) {\ xcb_connection_t* conn = XGetXCBConnection(_glfw.x11.display); \ if (conn) state = xkb_x11_state_new_from_device(keymap, conn, xkb->keyboard_device_id); \ } static void glfw_xkb_update_masks(_GLFWXKBData *xkb) { // See https://github.com/kovidgoyal/kitty/pull/3430 for discussion bool succeeded = false; unsigned used_bits = 0; /* To avoid using the same bit twice */ XkbDescPtr xkb_ptr = XkbGetMap( _glfw.x11.display, XkbVirtualModsMask | XkbVirtualModMapMask, XkbUseCoreKbd ); /* shift, control, and capsLock are special; they cannot be identified reliably on X11 */ #define S(a, n) xkb->a##Idx = xkb_keymap_mod_get_index(xkb->keymap, n); xkb->a##Mask = 1 << xkb->a##Idx; used_bits |= xkb->a##Mask; S(control, XKB_MOD_NAME_CTRL); S(shift, XKB_MOD_NAME_SHIFT); S(capsLock, XKB_MOD_NAME_CAPS); #undef S #define S( a ) xkb->a##Idx = XKB_MOD_INVALID; xkb->a##Mask = 0 S(alt); S(super); S(hyper); S(meta); S(numLock); #undef S if (xkb_ptr) { Status status = XkbGetNames(_glfw.x11.display, XkbVirtualModNamesMask, xkb_ptr); if (status == Success) { for (int indx = 0; indx < XkbNumVirtualMods; ++indx) { Atom atom = xkb_ptr->names->vmods[indx]; if (atom) { unsigned mask_rtn = 0; if (XkbVirtualModsToReal( xkb_ptr, 1<a##Mask = mask_rtn, used_bits |= mask_rtn /* Note that the order matters here; earlier is higher priority. */ S(alt, Alt); S(super, Super); S(numLock, NumLock); S(meta, Meta); S(hyper, Hyper); #undef S } } } succeeded = true; } XkbFreeNames(xkb_ptr, XkbVirtualModNamesMask, True); XkbFreeKeyboard(xkb_ptr, 0, True); } if (succeeded) { unsigned indx, shifted; for (indx = 0, shifted = 1; used_bits; ++indx, shifted <<= 1, used_bits >>= 1) { #define S( a ) if ( ( xkb->a##Mask & shifted ) == shifted ) xkb->a##Idx = indx S(alt); S(super); S(hyper); S(meta); S(numLock); #undef S } } #define S(a, n) xkb->a##Idx = xkb_keymap_mod_get_index(xkb->keymap, n); xkb->a##Mask = 1 << xkb->a##Idx; if (!succeeded) { S(numLock, XKB_MOD_NAME_NUM); S(alt, XKB_MOD_NAME_ALT); S(super, XKB_MOD_NAME_LOGO); } #undef S debug("Modifier indices alt: 0x%x super: 0x%x hyper: 0x%x meta: 0x%x numlock: 0x%x shift: 0x%x capslock: 0x%x\n", xkb->altIdx, xkb->superIdx, xkb->hyperIdx, xkb->metaIdx, xkb->numLockIdx, xkb->shiftIdx, xkb->capsLockIdx); } #else #define xkb_glfw_load_keymap(keymap, map_str) keymap = xkb_keymap_new_from_string(xkb->context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, 0); #define xkb_glfw_load_state(keymap, state) state = xkb_state_new(keymap); typedef struct { struct xkb_state *state; int failed; xkb_mod_mask_t used_mods; xkb_mod_mask_t shift, control, capsLock, numLock, alt, super, meta, hyper; /* Combination modifiers to try */ int try_shift; xkb_keycode_t shift_keycode; } modifier_mapping_algorithm_t; /* Algorithm for mapping virtual modifiers to real modifiers: * 1. create new state * 2. for each key in keymap * a. send key down to state * b. if it affected exactly one bit in modifier map * i) get keysym * ii) if keysym matches one of the known modifiers, save it for that modifier * iii) if modifier is latched, send key up and key down to toggle again * c. send key up to reset the state * 3. if shift key found in step 2, run step 2 with all shift+key for each key * 4. if shift, control, alt and super are not all found, declare failure * 5. if failure, use static mapping from xkbcommon-names.h * * Step 3 is needed because many popular keymaps map meta to alt+shift. * * We could do better by constructing a system of linear equations, but it should not be * needed in any sane system. We could also use this algorithm with X11, but X11 * provides XkbVirtualModsToReal which is guaranteed to be accurate, while this * algorithm is only a heuristic. * * We don't touch level3 or level5 modifiers. */ static void modifier_mapping_algorithm( struct xkb_keymap *keymap UNUSED, xkb_keycode_t key, void *data ) { modifier_mapping_algorithm_t *algorithm = ( modifier_mapping_algorithm_t * )data; if ( algorithm->failed ) return; if ( algorithm->try_shift ) { if ( key == algorithm->shift_keycode ) return; xkb_state_update_key( algorithm->state, algorithm->shift_keycode, XKB_KEY_DOWN ); } enum xkb_state_component changed_type = xkb_state_update_key( algorithm->state, key, XKB_KEY_DOWN ); if ( changed_type & ( XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_LOCKED ) ) { xkb_mod_mask_t mods = xkb_state_serialize_mods( algorithm->state, algorithm->try_shift ? XKB_STATE_MODS_EFFECTIVE : ( XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_LOCKED ) ); const xkb_keysym_t *keysyms; int num_keysyms = xkb_state_key_get_syms( algorithm->state, key, &keysyms ); /* We can handle exactly one keysym with exactly one bit set in the implementation * below; with a lot more gymnastics, we could set up an 8x8 linear system and solve * for each modifier in case there are some modifiers that are only present in * combination with others, but it is not worth the effort. */ if ( num_keysyms == 1 && mods && ( mods & ( mods-1 ) ) == 0 ) { #define S2( k, a ) \ do { \ if ( keysyms[0] == XKB_KEY_##k##_L || keysyms[0] == XKB_KEY_##k##_R ) { \ if ( !algorithm->a ) \ algorithm->a = mods; \ else if ( algorithm->a != mods ) \ algorithm->failed = 1; \ } \ } while ( 0 ) #define S1( k, a ) if ( ( keysyms[0] == XKB_KEY_##k ) && !algorithm->a ) algorithm->a = mods S2( Shift, shift ); S2( Control, control ); S1( Caps_Lock, capsLock ); S1( Shift_Lock, numLock ); S2( Alt, alt ); S2( Super, super ); S2( Meta, meta ); S2( Hyper, hyper ); #undef S1 #undef S2 } if ( !algorithm->shift_keycode && ( keysyms[0] == XKB_KEY_Shift_L || keysyms[0] == XKB_KEY_Shift_R ) ) algorithm->shift_keycode = key; /* If this is a lock, then up and down to remove lock state*/ if ( changed_type & XKB_STATE_MODS_LOCKED ) { /* What should we do for LATCHED here? */ xkb_state_update_key( algorithm->state, key, XKB_KEY_UP ); xkb_state_update_key( algorithm->state, key, XKB_KEY_DOWN ); } } xkb_state_update_key( algorithm->state, key, XKB_KEY_UP ); if ( algorithm->try_shift ) { xkb_state_update_key( algorithm->state, algorithm->shift_keycode, XKB_KEY_UP ); } } static int local_modifier_mapping(_GLFWXKBData *xkb) { modifier_mapping_algorithm_t algorithm; algorithm.failed = 0; algorithm.used_mods = 0; algorithm.shift = algorithm.control = algorithm.capsLock = algorithm.numLock = algorithm.alt = algorithm.super = algorithm.meta = algorithm.hyper = 0; algorithm.try_shift = 0; algorithm.shift_keycode = 0; algorithm.state = xkb_state_new( xkb->keymap ); if ( algorithm.state != NULL ) { xkb_keymap_key_for_each( xkb->keymap, &modifier_mapping_algorithm, &algorithm ); if ( !algorithm.shift_keycode ) algorithm.failed = 1; if ( !( algorithm.shift && algorithm.control && algorithm.alt && algorithm.super && algorithm.meta && algorithm.hyper ) && !algorithm.failed ) { algorithm.try_shift = 1; xkb_keymap_key_for_each( xkb->keymap, &modifier_mapping_algorithm, &algorithm ); } xkb_state_unref( algorithm.state ); if ( !algorithm.failed && !( algorithm.shift && algorithm.control && algorithm.alt && algorithm.super ) ) algorithm.failed = 1; /* must have found at least those 4 modifiers */ } if ( !algorithm.failed ) { #define S( a ) xkb->a##Idx = XKB_MOD_INVALID; xkb->a##Mask = 0 S(control); S(shift); S(capsLock); S(alt); S(super); S(hyper); S(meta); S(numLock); #undef S unsigned indx, shifted, used_bits = 0; for (indx = 0, shifted = 1; indx < 32; ++indx, shifted <<= 1) { #define S( a ) if ( (xkb->a##Idx == XKB_MOD_INVALID) && !( used_bits & shifted ) && algorithm.a == shifted ) xkb->a##Idx = indx, xkb->a##Mask = shifted, used_bits |= shifted S(control); S(shift); S(capsLock); S(alt); S(super); S(hyper); S(meta); S(numLock); #undef S } } if ( algorithm.failed ) debug( "Wayland modifier autodetection algorithm failed; using defaults\n" ); return !algorithm.failed; } static void glfw_xkb_update_masks(_GLFWXKBData *xkb) { // Should find better solution under Wayland // See https://github.com/kovidgoyal/kitty/pull/3943 for discussion if ( getenv( "KITTY_WAYLAND_DETECT_MODIFIERS" ) == NULL || !local_modifier_mapping( xkb ) ) { #define S( a ) xkb->a##Idx = XKB_MOD_INVALID; xkb->a##Mask = 0 S(hyper); S(meta); #undef S #define S(a, n) xkb->a##Idx = xkb_keymap_mod_get_index(xkb->keymap, n); xkb->a##Mask = 1 << xkb->a##Idx; S(control, XKB_MOD_NAME_CTRL); S(shift, XKB_MOD_NAME_SHIFT); S(capsLock, XKB_MOD_NAME_CAPS); S(numLock, XKB_MOD_NAME_NUM); S(alt, XKB_MOD_NAME_ALT); S(super, XKB_MOD_NAME_LOGO); #undef S } debug("Modifier indices alt: 0x%x super: 0x%x hyper: 0x%x meta: 0x%x numlock: 0x%x shift: 0x%x capslock: 0x%x control: 0x%x\n", xkb->altIdx, xkb->superIdx, xkb->hyperIdx, xkb->metaIdx, xkb->numLockIdx, xkb->shiftIdx, xkb->capsLockIdx, xkb->controlIdx); } #endif static void release_keyboard_data(_GLFWXKBData *xkb) { #define US(group, state, unref) if (xkb->group.state) { unref(xkb->group.state); xkb->group.state = NULL; } #define UK(keymap) if(xkb->keymap) { xkb_keymap_unref(xkb->keymap); xkb->keymap = NULL; } US(states, composeState, xkb_compose_state_unref); UK(keymap); UK(default_keymap); US(states, state, xkb_state_unref); US(states, clean_state, xkb_state_unref); US(states, default_state, xkb_state_unref); #undef US #undef UK } void glfw_xkb_release(_GLFWXKBData *xkb) { release_keyboard_data(xkb); if (xkb->context) { xkb_context_unref(xkb->context); xkb->context = NULL; } glfw_ibus_terminate(&xkb->ibus); } bool glfw_xkb_create_context(_GLFWXKBData *xkb) { xkb->context = xkb_context_new(0); if (!xkb->context) { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to initialize XKB context"); return false; } #ifndef _GLFW_WAYLAND glfw_connect_to_ibus(&xkb->ibus); #endif return true; } static const char* load_keymaps(_GLFWXKBData *xkb, const char *map_str) { (void)(map_str); // not needed on X11 xkb_glfw_load_keymap(xkb->keymap, map_str); if (!xkb->keymap) return "Failed to compile XKB keymap"; // The system default keymap, can be overridden by the XKB_DEFAULT_RULES // env var, see // https://xkbcommon.org/doc/current/structxkb__rule__names.html static struct xkb_rule_names default_rule_names = {0}; xkb->default_keymap = xkb_keymap_new_from_names(xkb->context, &default_rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS); if (!xkb->default_keymap) return "Failed to create default XKB keymap"; return NULL; } static const char* load_states(_GLFWXKBData *xkb) { xkb_glfw_load_state(xkb->keymap, xkb->states.state); xkb->states.clean_state = xkb_state_new(xkb->keymap); xkb->states.default_state = xkb_state_new(xkb->default_keymap); if (!xkb->states.state || !xkb->states.clean_state || !xkb->states.default_state) return "Failed to create XKB state"; return NULL; } static void load_compose_tables(_GLFWXKBData *xkb) { /* Look up the preferred locale, falling back to "C" as default. */ struct xkb_compose_table* compose_table = NULL; const char *locale = getenv("LC_ALL"); if (!locale) locale = getenv("LC_CTYPE"); if (!locale) locale = getenv("LANG"); if (!locale) locale = "C"; // See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=903373 if (strcmp(locale, "en_IN") == 0) locale = "en_IN.UTF-8"; compose_table = xkb_compose_table_new_from_locale(xkb->context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS); if (!compose_table) { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose table for locale %s", locale); return; } xkb->states.composeState = xkb_compose_state_new(compose_table, XKB_COMPOSE_STATE_NO_FLAGS); if (!xkb->states.composeState) { _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose state"); } xkb_compose_table_unref(compose_table); } static xkb_mod_mask_t active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) { size_t i = 0; xkb_mod_mask_t ans = 0; while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) { if (xkb_state_mod_index_is_active(state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]); i++; } return ans; } static unsigned int update_one_modifier(XKBStateGroup *group, xkb_mod_mask_t mask, xkb_mod_index_t idx, unsigned int mod) { if ( idx == XKB_MOD_INVALID ) return 0; /* Optimization in the case of a single real modifier */ if ( mask && ( ( mask & ( mask-1 ) ) == 0 ) ) return (xkb_state_mod_index_is_active(group->state, idx, XKB_STATE_MODS_EFFECTIVE) == 1) ? mod : 0; /* Multiple real mods map to the same virtual mod */ for ( unsigned indx = 0; indx < 32 && mask; ++indx, mask >>= 1 ) if ( ( mask & 1 ) && xkb_state_mod_index_is_active(group->state, indx, XKB_STATE_MODS_EFFECTIVE) == 1) return mod; return 0; } static void update_modifiers(_GLFWXKBData *xkb) { XKBStateGroup *group = &xkb->states; #define S(attr, name) group->modifiers |= update_one_modifier( group, xkb->attr##Mask, xkb->attr##Idx, GLFW_MOD_##name ) S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(hyper, HYPER); S(meta, META); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK); #undef S xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state); } bool glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) { const char *err; debug("Loading new XKB keymaps\n"); release_keyboard_data(xkb); err = load_keymaps(xkb, map_str); if (err) { _glfwInputError(GLFW_PLATFORM_ERROR, "%s", err); release_keyboard_data(xkb); return false; } err = load_states(xkb); if (err) { _glfwInputError(GLFW_PLATFORM_ERROR, "%s", err); release_keyboard_data(xkb); return false; } load_compose_tables(xkb); glfw_xkb_update_masks(xkb); size_t capacity = arraysz(xkb->unknownModifiers), j = 0; for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID; for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) { if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->hyperIdx && i != xkb->metaIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i; } xkb->states.modifiers = 0; xkb->states.activeUnknownModifiers = 0; update_modifiers(xkb); return true; } void glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group) { if (!xkb->keymap) return; xkb->states.modifiers = 0; xkb_state_update_mask(xkb->states.state, depressed, latched, locked, base_group, latched_group, locked_group); // We have to update the groups in clean_state, as they change for // different keyboard layouts, see https://github.com/kovidgoyal/kitty/issues/488 xkb_state_update_mask(xkb->states.clean_state, 0, 0, 0, base_group, latched_group, locked_group); update_modifiers(xkb); } bool glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t keycode) { #ifdef _GLFW_WAYLAND keycode += 8; #endif return xkb_keymap_key_repeats(xkb->keymap, keycode); } static xkb_keysym_t compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym, int *compose_completed, char *key_text, int n) { *compose_completed = 0; if (sym == XKB_KEY_NoSymbol || !composeState) return sym; if (xkb_compose_state_feed(composeState, sym) != XKB_COMPOSE_FEED_ACCEPTED) return sym; switch (xkb_compose_state_get_status(composeState)) { case XKB_COMPOSE_COMPOSED: xkb_compose_state_get_utf8(composeState, key_text, n); *compose_completed = 1; return xkb_compose_state_get_one_sym(composeState); case XKB_COMPOSE_COMPOSING: case XKB_COMPOSE_CANCELLED: return XKB_KEY_NoSymbol; case XKB_COMPOSE_NOTHING: default: return sym; } } const char* glfw_xkb_keysym_name(xkb_keysym_t sym) { static char name[256]; name[0] = 0; xkb_keysym_get_name(sym, name, sizeof(name)); return name; } int glfw_xkb_keysym_from_name(const char *name, bool case_sensitive) { return (int)xkb_keysym_from_name(name, case_sensitive ? XKB_KEYSYM_NO_FLAGS : XKB_KEYSYM_CASE_INSENSITIVE); } static const char* format_mods(unsigned int mods) { static char buf[128]; char *p = buf, *s; #define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, "%s", x) pr("mods: "); s = p; if (mods & GLFW_MOD_CONTROL) pr("ctrl+"); if (mods & GLFW_MOD_ALT) pr("alt+"); if (mods & GLFW_MOD_SHIFT) pr("shift+"); if (mods & GLFW_MOD_SUPER) pr("super+"); if (mods & GLFW_MOD_META) pr("meta+"); if (mods & GLFW_MOD_HYPER) pr("hyper+"); if (mods & GLFW_MOD_CAPS_LOCK) pr("capslock+"); if (mods & GLFW_MOD_NUM_LOCK) pr("numlock+"); if (p == s) pr("none"); else p--; pr(" "); #undef pr return buf; } static const char* format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) { static char buf[512]; char *p = buf, *s; #define pr(x) { \ int num_needed = -1; \ ssize_t space_left = sizeof(buf) - (p - buf) - 1; \ if (space_left > 0) num_needed = snprintf(p, space_left, "%s", x); \ if (num_needed > 0) p += num_needed; \ } pr(name); pr(": "); s = p; for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap); i++) { xkb_mod_mask_t m = 1 << i; if (m & mods) { pr(xkb_keymap_mod_get_name(xkb->keymap, i)); pr("+"); } } if (p == s) { pr("none"); } else p--; pr(" "); #undef pr return buf; } void glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, const GLFWIMEUpdateEvent *ev) { int x = 0, y = 0; switch(ev->type) { case GLFW_IME_UPDATE_FOCUS: glfw_ibus_set_focused(&xkb->ibus, ev->focused); break; case GLFW_IME_UPDATE_CURSOR_POSITION: _glfwPlatformGetWindowPos(w, &x, &y); x += ev->cursor.left; y += ev->cursor.top; glfw_ibus_set_cursor_geometry(&xkb->ibus, x, y, ev->cursor.width, ev->cursor.height); break; } } void glfw_xkb_key_from_ime(_GLFWIBUSKeyEvent *ev, bool handled_by_ime, bool failed) { _GLFWwindow *window = _glfwWindowForId(ev->window_id); if (failed && window && window->callbacks.keyboard) { // notify application to remove any existing pre-edit text GLFWkeyevent fake_ev = {.action = GLFW_PRESS}; fake_ev.ime_state = GLFW_IME_PREEDIT_CHANGED; window->callbacks.keyboard((GLFWwindow*) window, &fake_ev); } static xkb_keycode_t last_handled_press_keycode = 0; // We filter out release events that correspond to the last press event // handled by the IME system. This won't fix the case of multiple key // presses before a release, but is better than nothing. For that case // you'd need to implement a ring buffer to store pending key presses. xkb_keycode_t prev_handled_press = last_handled_press_keycode; last_handled_press_keycode = 0; bool is_release = ev->glfw_ev.action == GLFW_RELEASE; debug("From IBUS: native_key: 0x%x name: %s is_release: %d handled_by_ime: %d\n", ev->glfw_ev.native_key, glfw_xkb_keysym_name(ev->glfw_ev.key), is_release, handled_by_ime); if (window && !handled_by_ime && !(is_release && ev->glfw_ev.native_key == (int) prev_handled_press)) { debug("↳ to application: glfw_keycode: 0x%x (%s) keysym: 0x%x (%s) action: %s %s text: %s\n", ev->glfw_ev.native_key, _glfwGetKeyName(ev->glfw_ev.native_key), ev->glfw_ev.key, glfw_xkb_keysym_name(ev->glfw_ev.key), (ev->glfw_ev.action == GLFW_RELEASE ? "RELEASE" : (ev->glfw_ev.action == GLFW_PRESS ? "PRESS" : "REPEAT")), format_mods(ev->glfw_ev.mods), ev->glfw_ev.text ); ev->glfw_ev.ime_state = GLFW_IME_NONE; _glfwInputKeyboard(window, &ev->glfw_ev); } else debug("↳ discarded\n"); if (!is_release && handled_by_ime) last_handled_press_keycode = ev->glfw_ev.native_key; } void glfw_xkb_forwarded_key_from_ime(xkb_keysym_t keysym, unsigned int glfw_mods) { _GLFWwindow *w = _glfwFocusedWindow(); if (w && w->callbacks.keyboard) { GLFWkeyevent fake_ev = {.action = GLFW_PRESS}; fake_ev.native_key = keysym; fake_ev.key = glfw_key_for_sym(keysym); fake_ev.mods = glfw_mods; fake_ev.ime_state = GLFW_IME_NONE; w->callbacks.keyboard((GLFWwindow*) w, &fake_ev); } } static bool is_switch_layout_key(xkb_keysym_t xkb_sym) { return xkb_sym == XKB_KEY_ISO_First_Group || xkb_sym == XKB_KEY_ISO_Last_Group || xkb_sym == XKB_KEY_ISO_Next_Group || xkb_sym == XKB_KEY_ISO_Prev_Group || xkb_sym == XKB_KEY_Mode_switch; } void glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t xkb_keycode, int action) { static char key_text[64] = {0}; const xkb_keysym_t *syms, *clean_syms, *default_syms; xkb_keysym_t xkb_sym, shifted_xkb_sym = XKB_KEY_NoSymbol, alternate_xkb_sym = XKB_KEY_NoSymbol; xkb_keycode_t code_for_sym = xkb_keycode, ibus_keycode = xkb_keycode; GLFWkeyevent glfw_ev = {.action = GLFW_PRESS, .native_key_id = xkb_keycode}; #ifdef _GLFW_WAYLAND code_for_sym += 8; #else ibus_keycode -= 8; #endif debug("%s xkb_keycode: 0x%x ", action == GLFW_RELEASE ? "\x1b[32mRelease\x1b[m" : "\x1b[31mPress\x1b[m", xkb_keycode); XKBStateGroup *sg = &xkb->states; int num_syms = xkb_state_key_get_syms(sg->state, code_for_sym, &syms); int num_clean_syms = xkb_state_key_get_syms(sg->clean_state, code_for_sym, &clean_syms); key_text[0] = 0; // According to the documentation of xkb_compose_state_feed it does not // support multi-sym events, so we ignore them if (num_syms != 1 || num_clean_syms != 1) { debug("num_syms: %d num_clean_syms: %d ignoring event\n", num_syms, num_clean_syms); return; } xkb_sym = clean_syms[0]; shifted_xkb_sym = syms[0]; debug("clean_sym: %s ", glfw_xkb_keysym_name(clean_syms[0])); if (action == GLFW_PRESS || action == GLFW_REPEAT) { const char *text_type = "composed_text"; int compose_completed; xkb_sym = compose_symbol(sg->composeState, syms[0], &compose_completed, key_text, sizeof(key_text)); if (xkb_sym == XKB_KEY_NoSymbol && !compose_completed) { debug("compose not complete, ignoring.\n"); return; } debug("composed_sym: %s ", glfw_xkb_keysym_name(xkb_sym)); if (xkb_sym == syms[0]) { // composed sym is the same as non-composed sym // Only use the clean_sym if no mods other than the mods we report // are active (for example if ISO_Shift_Level_* mods are active // they are not reported by GLFW so the key should be the shifted // key). See https://github.com/kovidgoyal/kitty/issues/171#issuecomment-377557053 xkb_mod_mask_t consumed_unknown_mods = xkb_state_key_get_consumed_mods(sg->state, code_for_sym) & sg->activeUnknownModifiers; if (sg->activeUnknownModifiers) debug("%s", format_xkb_mods(xkb, "active_unknown_mods", sg->activeUnknownModifiers)); if (consumed_unknown_mods) { debug("%s", format_xkb_mods(xkb, "consumed_unknown_mods", consumed_unknown_mods)); } else if (!is_switch_layout_key(xkb_sym)) xkb_sym = clean_syms[0]; // xkb returns text even if alt and/or super are pressed if ( ((GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER | GLFW_MOD_HYPER | GLFW_MOD_META) & sg->modifiers) == 0) { xkb_state_key_get_utf8(sg->state, code_for_sym, key_text, sizeof(key_text)); } text_type = "text"; } if ((1 <= key_text[0] && key_text[0] <= 31) || key_text[0] == 127) { key_text[0] = 0; // don't send text for ascii control codes } if (key_text[0]) { debug("%s: %s ", text_type, key_text); } } if (is_switch_layout_key(xkb_sym)) { debug(" is a keyboard layout shift key, ignoring.\n"); return; } if (sg->modifiers & GLFW_MOD_NUM_LOCK && XKB_KEY_KP_Space <= xkb_sym && xkb_sym <= XKB_KEY_KP_9) { xkb_sym = xkb_state_key_get_one_sym(sg->state, code_for_sym); } int num_default_syms = xkb_state_key_get_syms(sg->default_state, code_for_sym, &default_syms); if (num_default_syms > 0) alternate_xkb_sym = default_syms[0]; int glfw_sym = glfw_key_for_sym(xkb_sym); debug( "%s%s: %d (%s) xkb_key: %d (%s)", format_mods(sg->modifiers), "glfw_key", glfw_sym, _glfwGetKeyName(glfw_sym), xkb_sym, glfw_xkb_keysym_name(xkb_sym) ); bool has_shifted_key = shifted_xkb_sym != xkb_sym && shifted_xkb_sym != XKB_KEY_NoSymbol; bool has_alternate_key = alternate_xkb_sym != xkb_sym && alternate_xkb_sym != XKB_KEY_NoSymbol; if (has_shifted_key) { glfw_ev.shifted_key = glfw_key_for_sym(shifted_xkb_sym); if (glfw_ev.shifted_key) debug(" shifted_key: %d (%s)", glfw_ev.shifted_key, _glfwGetKeyName(glfw_ev.shifted_key)) } if (has_alternate_key) { glfw_ev.alternate_key = glfw_key_for_sym(alternate_xkb_sym); if (glfw_ev.alternate_key) debug(" alternate_key: %d (%s)", glfw_ev.alternate_key, _glfwGetKeyName(glfw_ev.alternate_key)) } debug("\n"); // NOTE: On linux, the reported native key identifier is the XKB keysym value. // Do not confuse `native_key` with `xkb_keycode` (the native keycode reported for the // glfw event VS the X internal code for a key). // // We use the XKB keysym instead of the X keycode to be able to go back-and-forth between // the GLFW keysym and the XKB keysym when needed, which is not possible using the X keycode, // because of the lost information when resolving the keycode to the keysym, like consumed // mods. glfw_ev.native_key = xkb_sym; glfw_ev.action = action; glfw_ev.key = glfw_sym; glfw_ev.mods = sg->modifiers; glfw_ev.text = key_text; _GLFWIBUSKeyEvent ibus_ev; ibus_ev.glfw_ev = glfw_ev; ibus_ev.ibus_keycode = ibus_keycode; ibus_ev.window_id = window->id; ibus_ev.ibus_keysym = syms[0]; if (ibus_process_key(&ibus_ev, &xkb->ibus)) { debug("↳ to IBUS: keycode: 0x%x keysym: 0x%x (%s) %s\n", ibus_ev.ibus_keycode, ibus_ev.ibus_keysym, glfw_xkb_keysym_name(ibus_ev.ibus_keysym), format_mods(ibus_ev.glfw_ev.mods)); } else { _glfwInputKeyboard(window, &glfw_ev); } } kitty-0.41.1/glfw/xkb_glfw.h0000664000175000017510000000766014773370543015233 0ustar nileshnilesh//======================================================================== // GLFW 3.4 XKB - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2018 Kovid Goyal // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #pragma once #include #include #ifdef _GLFW_X11 #include #endif #include "ibus_glfw.h" typedef struct { struct xkb_state* state; struct xkb_state* clean_state; struct xkb_state* default_state; struct xkb_compose_state* composeState; xkb_mod_mask_t activeUnknownModifiers; unsigned int modifiers; } XKBStateGroup; typedef struct { struct xkb_context* context; struct xkb_keymap* keymap; struct xkb_keymap* default_keymap; XKBStateGroup states; xkb_mod_index_t controlIdx; xkb_mod_index_t altIdx; xkb_mod_index_t shiftIdx; xkb_mod_index_t superIdx; xkb_mod_index_t hyperIdx; xkb_mod_index_t metaIdx; xkb_mod_index_t capsLockIdx; xkb_mod_index_t numLockIdx; xkb_mod_mask_t controlMask; xkb_mod_mask_t altMask; xkb_mod_mask_t shiftMask; xkb_mod_mask_t superMask; xkb_mod_mask_t hyperMask; xkb_mod_mask_t metaMask; xkb_mod_mask_t capsLockMask; xkb_mod_mask_t numLockMask; xkb_mod_index_t unknownModifiers[256]; _GLFWIBUSData ibus; #ifdef _GLFW_X11 int32_t keyboard_device_id; bool available; bool detectable; int majorOpcode; int eventBase; int errorBase; int major; int minor; #endif } _GLFWXKBData; #ifdef _GLFW_X11 bool glfw_xkb_set_x11_events_mask(void); bool glfw_xkb_update_x11_keyboard_id(_GLFWXKBData *xkb); #endif void glfw_xkb_release(_GLFWXKBData *xkb); bool glfw_xkb_create_context(_GLFWXKBData *xkb); bool glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str); void glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group); bool glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t keycode); const char* glfw_xkb_keysym_name(xkb_keysym_t sym); xkb_keysym_t glfw_xkb_sym_for_key(uint32_t key); void glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t keycode, int action); int glfw_xkb_keysym_from_name(const char *name, bool case_sensitive); void glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, const GLFWIMEUpdateEvent *ev); void glfw_xkb_key_from_ime(_GLFWIBUSKeyEvent *ev, bool handled_by_ime, bool failed); void glfw_xkb_forwarded_key_from_ime(xkb_keysym_t keysym, unsigned int glfw_mods); kitty-0.41.1/go.mod0000664000175000017510000000235314773370543013420 0ustar nileshnileshmodule kitty go 1.23.0 toolchain go1.24.1 require ( github.com/ALTree/bigfloat v0.2.0 github.com/alecthomas/chroma/v2 v2.15.0 github.com/bmatcuk/doublestar/v4 v4.8.1 github.com/dlclark/regexp2 v1.11.5 github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/kovidgoyal/imaging v1.6.4 github.com/seancfoley/ipaddress-go v1.7.1 github.com/shirou/gopsutil/v3 v3.24.5 github.com/zeebo/xxh3 v1.0.2 golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b golang.org/x/image v0.25.0 golang.org/x/sys v0.31.0 howett.net/plist v1.0.1 ) require ( github.com/disintegration/imaging v1.6.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect github.com/seancfoley/bintree v1.3.1 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect ) kitty-0.41.1/go.sum0000664000175000017510000001621114773370543013443 0ustar nileshnileshgithub.com/ALTree/bigfloat v0.2.0 h1:AwNzawrpFuw55/YDVlcPw0F0cmmXrmngBHhVrvdXPvM= github.com/ALTree/bigfloat v0.2.0/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc= github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be h1:FNPYI8/ifKGW7kdBdlogyGGaPXZmOXBbV1uz4Amr3s0= github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be/go.mod h1:G3dK5MziX9e4jUa8PWjowCOPCcyQwxsZ5a0oYA73280= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kovidgoyal/imaging v1.6.4 h1:K0idhRPXnRrJBKnBYcTfI1HTWSNDeAn7hYDvf9I0dCk= github.com/kovidgoyal/imaging v1.6.4/go.mod h1:bEIgsaZmXlvFfkv/CUxr9rJook6AQkJnpB5EPosRfRY= github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik= github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/seancfoley/bintree v1.3.1 h1:cqmmQK7Jm4aw8gna0bP+huu5leVOgHGSJBEpUx3EXGI= github.com/seancfoley/bintree v1.3.1/go.mod h1:hIUabL8OFYyFVTQ6azeajbopogQc2l5C/hiXMcemWNU= github.com/seancfoley/ipaddress-go v1.7.1 h1:fDWryS+L8iaaH5RxIKbY0xB5Z+Zxk8xoXLN4S4eAPdQ= github.com/seancfoley/ipaddress-go v1.7.1/go.mod h1:TQRZgv+9jdvzHmKoPGBMxyiaVmoI0rYpfEk8Q/sL/Iw= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= kitty-0.41.1/key_encoding.json0000664000175000017510000000350614773370543015644 0ustar nileshnilesh{ "0": "G", "1": "H", "2": "I", "3": "J", "4": "K", "5": "L", "6": "M", "7": "N", "8": "O", "9": "P", "A": "S", "APOSTROPHE": "B", "B": "T", "BACKSLASH": "t", "BACKSPACE": "1", "C": "U", "CAPS LOCK": ":", "COMMA": "C", "D": "V", "DELETE": "3", "DOWN": "6", "E": "W", "END": "-", "ENTER": "z", "EQUAL": "R", "ESCAPE": "y", "F": "X", "F1": "/", "F10": "]", "F11": "{", "F12": "}", "F13": "@", "F14": "%", "F15": "$", "F16": "#", "F17": "BA", "F18": "BB", "F19": "BC", "F2": "*", "F20": "BD", "F21": "BE", "F22": "BF", "F23": "BG", "F24": "BH", "F25": "BI", "F3": "?", "F4": "&", "F5": "<", "F6": ">", "F7": "(", "F8": ")", "F9": "[", "G": "Y", "GRAVE ACCENT": "v", "H": "Z", "HOME": ".", "I": "a", "INSERT": "2", "J": "b", "K": "c", "KP 0": "BJ", "KP 1": "BK", "KP 2": "BL", "KP 3": "BM", "KP 4": "BN", "KP 5": "BO", "KP 6": "BP", "KP 7": "BQ", "KP 8": "BR", "KP 9": "BS", "KP ADD": "BX", "KP DECIMAL": "BT", "KP DIVIDE": "BU", "KP ENTER": "BY", "KP EQUAL": "BZ", "KP MULTIPLY": "BV", "KP SUBTRACT": "BW", "L": "d", "LEFT": "5", "LEFT ALT": "Bc", "LEFT BRACKET": "s", "LEFT CONTROL": "Bb", "LEFT SHIFT": "Ba", "LEFT SUPER": "Bd", "M": "e", "MINUS": "D", "N": "f", "NUM LOCK": "=", "O": "g", "P": "h", "PAGE DOWN": "9", "PAGE UP": "8", "PAUSE": "!", "PERIOD": "E", "PRINT SCREEN": "^", "Q": "i", "R": "j", "RIGHT": "4", "RIGHT ALT": "Bg", "RIGHT BRACKET": "u", "RIGHT CONTROL": "Bf", "RIGHT SHIFT": "Be", "RIGHT SUPER": "Bh", "S": "k", "SCROLL LOCK": "+", "SEMICOLON": "Q", "SLASH": "F", "SPACE": "A", "T": "l", "TAB": "0", "U": "m", "UP": "7", "V": "n", "W": "o", "WORLD 1": "w", "WORLD 2": "x", "X": "p", "Y": "q", "Z": "r" }kitty-0.41.1/kittens/0000775000175000017510000000000014773370543013770 5ustar nileshnileshkitty-0.41.1/kittens/__init__.py0000664000175000017510000000000014773370543016067 0ustar nileshnileshkitty-0.41.1/kittens/ask/0000775000175000017510000000000014773370543014546 5ustar nileshnileshkitty-0.41.1/kittens/ask/__init__.py0000664000175000017510000000000014773370543016645 0ustar nileshnileshkitty-0.41.1/kittens/ask/choices.go0000664000175000017510000002752214773370543016522 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package ask import ( "fmt" "io" "kitty/tools/cli/markup" "kitty/tools/tty" "kitty/tools/tui/loop" "kitty/tools/utils" "kitty/tools/utils/style" "kitty/tools/wcswidth" "os" "regexp" "strings" "unicode" ) var _ = fmt.Print type Choice struct { text string idx int color, letter string } func (self Choice) prefix() string { return string([]rune(self.text)[:self.idx]) } func (self Choice) display_letter() string { return string([]rune(self.text)[self.idx]) } func (self Choice) suffix() string { return string([]rune(self.text)[self.idx+1:]) } type Range struct { start, end, y int } func (self *Range) has_point(x, y int) bool { return y == self.y && self.start <= x && x <= self.end } func truncate_at_space(text string, width int) (string, string) { truncated, p := wcswidth.TruncateToVisualLengthWithWidth(text, width) if len(truncated) == len(text) { return text, "" } i := strings.LastIndexByte(truncated, ' ') if i > 0 && p-i < 12 { p = i + 1 } return text[:p], text[p:] } func extra_for(width, screen_width int) int { return max(0, screen_width-width)/2 + 1 } var debugprintln = tty.DebugPrintln var _ = debugprintln func GetChoices(o *Options) (response string, err error) { response = "" lp, err := loop.New() if err != nil { return "", err } lp.MouseTrackingMode(loop.FULL_MOUSE_TRACKING) prefix_style_pat := regexp.MustCompile("^(?:\x1b\\[[^m]*?m)+") choice_order := make([]Choice, 0, len(o.Choices)) clickable_ranges := make(map[string][]Range, 16) allowed := utils.NewSet[string](max(2, len(o.Choices))) response_on_accept := o.Default switch o.Type { case "yesno": allowed.AddItems("y", "n") if !allowed.Has(response_on_accept) { response_on_accept = "y" } case "choices": first_choice := "" for i, x := range o.Choices { letter, text, _ := strings.Cut(x, ":") color := "" if strings.Contains(letter, ";") { letter, color, _ = strings.Cut(letter, ";") } letter = strings.ToLower(letter) idx := strings.Index(strings.ToLower(text), letter) if idx < 0 { return "", fmt.Errorf("The choice letter %#v is not present in the choice text: %#v", letter, text) } idx = len([]rune(strings.ToLower(text)[:idx])) allowed.Add(letter) c := Choice{text: text, idx: idx, color: color, letter: letter} choice_order = append(choice_order, c) if i == 0 { first_choice = letter } } if !allowed.Has(response_on_accept) { response_on_accept = first_choice } } message := o.Message hidden_text_start_pos := -1 hidden_text_end_pos := -1 hidden_text := "" m := markup.New(true) replacement_text := fmt.Sprintf("Press %s or click to show", m.Green(o.UnhideKey)) replacement_range := Range{-1, -1, -1} if message != "" && o.HiddenTextPlaceholder != "" { hidden_text_start_pos = strings.Index(message, o.HiddenTextPlaceholder) if hidden_text_start_pos > -1 { raw, err := io.ReadAll(os.Stdin) if err != nil { return "", fmt.Errorf("Failed to read hidden text from STDIN: %w", err) } hidden_text = strings.TrimRightFunc(utils.UnsafeBytesToString(raw), unicode.IsSpace) hidden_text_end_pos = hidden_text_start_pos + len(replacement_text) suffix := message[hidden_text_start_pos+len(o.HiddenTextPlaceholder):] message = message[:hidden_text_start_pos] + replacement_text + suffix } } draw_long_text := func(screen_width int, text string, msg_lines []string) []string { if screen_width < 3 { return msg_lines } if text == "" { msg_lines = append(msg_lines, "") } else { width := screen_width - 2 prefix := prefix_style_pat.FindString(text) for text != "" { var t string t, text = truncate_at_space(text, width) t = strings.TrimSpace(t) msg_lines = append(msg_lines, strings.Repeat(" ", extra_for(wcswidth.Stringwidth(t), width))+m.Bold(prefix+t)) } } return msg_lines } ctx := style.Context{AllowEscapeCodes: true} draw_choice_boxes := func(y, screen_width, _ int, choices ...Choice) { clickable_ranges = map[string][]Range{} width := screen_width - 2 current_line_length := 0 type Item struct{ letter, text string } type Line = []Item var current_line Line lines := make([]Line, 0, 32) sep := " " sep_sz := len(sep) + 2 // for the borders for _, choice := range choices { clickable_ranges[choice.letter] = make([]Range, 0, 4) text := " " + choice.prefix() color := choice.color if choice.color == "" { color = "green" } text += ctx.SprintFunc("fg=" + color)(choice.display_letter()) text += choice.suffix() + " " sz := wcswidth.Stringwidth(text) if sz+sep_sz+current_line_length > width { lines = append(lines, current_line) current_line = nil current_line_length = 0 } current_line = append(current_line, Item{choice.letter, text}) current_line_length += sz + sep_sz } if len(current_line) > 0 { lines = append(lines, current_line) } highlight := func(text string) string { return m.Yellow(text) } top := func(text string, highlight_frame bool) (ans string) { ans = "╭" + strings.Repeat("─", wcswidth.Stringwidth(text)) + "╮" if highlight_frame { ans = highlight(ans) } return } middle := func(text string, highlight_frame bool) (ans string) { f := "│" if highlight_frame { f = highlight(f) } return f + text + f } bottom := func(text string, highlight_frame bool) (ans string) { ans = "╰" + strings.Repeat("─", wcswidth.Stringwidth(text)) + "╯" if highlight_frame { ans = highlight(ans) } return } print_line := func(add_borders func(string, bool) string, is_last bool, items ...Item) { type Position struct { letter string x, size int } texts := make([]string, 0, 8) positions := make([]Position, 0, 8) x := 0 for _, item := range items { text := item.text positions = append(positions, Position{item.letter, x, wcswidth.Stringwidth(text) + 2}) text = add_borders(text, item.letter == response_on_accept) text += sep x += wcswidth.Stringwidth(text) texts = append(texts, text) } line := strings.TrimRightFunc(strings.Join(texts, ""), unicode.IsSpace) offset := extra_for(wcswidth.Stringwidth(line), width) for _, pos := range positions { x = pos.x x += offset clickable_ranges[pos.letter] = append(clickable_ranges[pos.letter], Range{x, x + pos.size - 1, y}) } end := "\r\n" if is_last { end = "" } lp.QueueWriteString(strings.Repeat(" ", offset) + line + end) y++ } lp.AllowLineWrapping(false) defer func() { lp.AllowLineWrapping(true) }() for i, boxed_line := range lines { print_line(top, false, boxed_line...) print_line(middle, false, boxed_line...) is_last := i == len(lines)-1 print_line(bottom, is_last, boxed_line...) } } draw_yesno := func(y, screen_width, screen_height int) { yes := m.Green("Y") + "es" no := m.BrightRed("N") + "o" if y+3 <= screen_height { draw_choice_boxes(y, screen_width, screen_height, Choice{"Yes", 0, "green", "y"}, Choice{"No", 0, "red", "n"}) } else { sep := strings.Repeat(" ", 3) text := yes + sep + no w := wcswidth.Stringwidth(text) x := extra_for(w, screen_width-2) nx := x + wcswidth.Stringwidth(yes) + len(sep) clickable_ranges = map[string][]Range{ "y": {{x, x + wcswidth.Stringwidth(yes) - 1, y}}, "n": {{nx, nx + wcswidth.Stringwidth(no) - 1, y}}, } lp.QueueWriteString(strings.Repeat(" ", x) + text) } } draw_choice := func(y, screen_width, screen_height int) { if y+3 <= screen_height { draw_choice_boxes(y, screen_width, screen_height, choice_order...) return } clickable_ranges = map[string][]Range{} current_line := "" current_ranges := map[string]int{} width := screen_width - 2 commit_line := func(add_newline bool) { x := extra_for(wcswidth.Stringwidth(current_line), width) text := strings.Repeat(" ", x) + current_line if add_newline { lp.Println(text) } else { lp.QueueWriteString(text) } for letter, sz := range current_ranges { clickable_ranges[letter] = []Range{{x, x + sz - 3, y}} x += sz } current_ranges = map[string]int{} y++ current_line = "" } for _, choice := range choice_order { text := choice.prefix() spec := "" if choice.color != "" { spec = "fg=" + choice.color } else { spec = "fg=green" } if choice.letter == response_on_accept { spec += " u=straight" } text += ctx.SprintFunc(spec)(choice.display_letter()) text += choice.suffix() text += " " sz := wcswidth.Stringwidth(text) if sz+wcswidth.Stringwidth(current_line) >= width { commit_line(true) } current_line += text current_ranges[choice.letter] = sz } if current_line != "" { commit_line(false) } } draw_screen := func() error { lp.StartAtomicUpdate() defer lp.EndAtomicUpdate() lp.ClearScreen() msg_lines := make([]string, 0, 8) sz, err := lp.ScreenSize() if err != nil { return err } if message != "" { scanner := utils.NewLineScanner(message) for scanner.Scan() { msg_lines = draw_long_text(int(sz.WidthCells), scanner.Text(), msg_lines) } } y := int(sz.HeightCells) - len(msg_lines) y = max(0, (y/2)-2) lp.QueueWriteString(strings.Repeat("\r\n", y)) for _, line := range msg_lines { if replacement_text != "" { idx := strings.Index(line, replacement_text) if idx > -1 { x := wcswidth.Stringwidth(line[:idx]) replacement_range = Range{x, x + wcswidth.Stringwidth(replacement_text), y} } } lp.Println(line) y++ } if sz.HeightCells > 2 { lp.Println() y++ } switch o.Type { case "yesno": draw_yesno(y, int(sz.WidthCells), int(sz.HeightCells)) case "choices": draw_choice(y, int(sz.WidthCells), int(sz.HeightCells)) } return nil } unhide := func() { if hidden_text != "" && message != "" { message = message[:hidden_text_start_pos] + hidden_text + message[hidden_text_end_pos:] hidden_text = "" _ = draw_screen() } } lp.OnInitialize = func() (string, error) { lp.SetCursorVisible(false) if o.Title != "" { lp.SetWindowTitle(o.Title) } return "", draw_screen() } lp.OnFinalize = func() string { lp.SetCursorVisible(true) return "" } lp.OnText = func(text string, from_key_event, in_bracketed_paste bool) error { text = strings.ToLower(text) if allowed.Has(text) { response = text lp.Quit(0) } else if hidden_text != "" && text == o.UnhideKey { unhide() } else if o.Type == "yesno" { lp.Quit(1) } return nil } lp.OnKeyEvent = func(ev *loop.KeyEvent) error { if ev.MatchesPressOrRepeat("esc") || ev.MatchesPressOrRepeat("ctrl+c") { ev.Handled = true lp.Quit(1) } else if ev.MatchesPressOrRepeat("enter") || ev.MatchesPressOrRepeat("kp_enter") { ev.Handled = true response = response_on_accept lp.Quit(0) } return nil } lp.OnMouseEvent = func(ev *loop.MouseEvent) error { on_letter := "" for letter, ranges := range clickable_ranges { for _, r := range ranges { if r.has_point(ev.Cell.X, ev.Cell.Y) { on_letter = letter break } } } if on_letter != "" { if s, has_shape := lp.CurrentPointerShape(); !has_shape && s != loop.POINTER_POINTER { lp.PushPointerShape(loop.POINTER_POINTER) } } else { if _, has_shape := lp.CurrentPointerShape(); has_shape { lp.PopPointerShape() } } if ev.Event_type == loop.MOUSE_CLICK { if on_letter != "" { response = on_letter lp.Quit(0) return nil } if hidden_text != "" && replacement_range.has_point(ev.Cell.X, ev.Cell.Y) { unhide() } } return nil } lp.OnResize = func(old, news loop.ScreenSize) error { return draw_screen() } err = lp.Run() if err != nil { return "", err } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() return "", fmt.Errorf("Filled by signal: %s", ds) } return response, nil } kitty-0.41.1/kittens/ask/get_line.go0000664000175000017510000000347714773370543016676 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package ask import ( "fmt" "io" "os" "path/filepath" "time" "kitty/tools/tui/loop" "kitty/tools/tui/readline" "kitty/tools/utils" ) var _ = fmt.Print func get_line(o *Options) (result string, err error) { lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors) if err != nil { return } cwd, _ := os.Getwd() ropts := readline.RlInit{Prompt: o.Prompt} if o.Name != "" { base := filepath.Join(utils.CacheDir(), "ask") ropts.HistoryPath = filepath.Join(base, o.Name+".history.json") os.MkdirAll(base, 0o755) } rl := readline.New(lp, ropts) if o.Default != "" { rl.SetText(o.Default) } lp.OnInitialize = func() (string, error) { rl.Start() return "", nil } lp.OnFinalize = func() string { rl.End(); return "" } lp.OnResumeFromStop = func() error { rl.Start() return nil } lp.OnResize = rl.OnResize lp.OnKeyEvent = func(event *loop.KeyEvent) error { if event.MatchesPressOrRepeat("ctrl+c") { return fmt.Errorf("Canceled by user") } err := rl.OnKeyEvent(event) if err != nil { if err == io.EOF { lp.Quit(0) return nil } if err == readline.ErrAcceptInput { hi := readline.HistoryItem{Timestamp: time.Now(), Cmd: rl.AllText(), ExitCode: 0, Cwd: cwd} rl.AddHistoryItem(hi) result = rl.AllText() lp.Quit(0) return nil } return err } if event.Handled { rl.Redraw() return nil } return nil } lp.OnText = func(text string, from_key_event, in_bracketed_paste bool) error { err := rl.OnText(text, from_key_event, in_bracketed_paste) if err == nil { rl.Redraw() } return err } err = lp.Run() rl.Shutdown() if err != nil { return "", err } ds := lp.DeathSignalName() if ds != "" { return "", fmt.Errorf("Killed by signal: %s", ds) } return } kitty-0.41.1/kittens/ask/main.go0000664000175000017510000000263314773370543016025 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package ask import ( "errors" "fmt" "kitty/tools/cli" "kitty/tools/cli/markup" "kitty/tools/tui" ) var _ = fmt.Print type Response struct { Items []string `json:"items"` Response string `json:"response"` } func show_message(msg string) { if msg != "" { m := markup.New(true) fmt.Println(m.Bold(msg)) } } func main(_ *cli.Command, o *Options, args []string) (rc int, err error) { output := tui.KittenOutputSerializer() result := &Response{Items: args} if len(o.Prompt) > 2 && o.Prompt[0] == o.Prompt[len(o.Prompt)-1] && (o.Prompt[0] == '"' || o.Prompt[0] == '\'') { o.Prompt = o.Prompt[1 : len(o.Prompt)-1] } switch o.Type { case "yesno", "choices": result.Response, err = GetChoices(o) if err != nil { return 1, err } case "password": show_message(o.Message) pw, err := tui.ReadPassword(o.Prompt, false) if err != nil { if errors.Is(err, tui.Canceled) { pw = "" } else { return 1, err } } result.Response = pw case "line": show_message(o.Message) result.Response, err = get_line(o) if err != nil { return 1, err } default: return 1, fmt.Errorf("Unknown type: %s", o.Type) } s, err := output(result) if err != nil { return 1, err } _, err = fmt.Println(s) if err != nil { return 1, err } return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } kitty-0.41.1/kittens/ask/main.py0000664000175000017510000000461214773370543016047 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import sys from kitty.typing import BossType, TypedDict from ..tui.handler import result_handler def option_text() -> str: return '''\ --type -t choices=line,yesno,choices,password default=line Type of input. Defaults to asking for a line of text. --message -m The message to display to the user. If not specified a default message is shown. --name -n The name for this question. Used to store history of previous answers which can be used for completions and via the browse history readline bindings. --title --window-title The title for the window in which the question is displayed. Only implemented for yesno and choices types. --choice -c type=list dest=choices A choice for the choices type. Can be specified multiple times. Every choice has the syntax: ``letter[;color]:text``, where :italic:`text` is the choice text and :italic:`letter` is the selection key. :italic:`letter` is a single letter belonging to :italic:`text`. This letter is highlighted within the choice text. There can be an optional color specification after the letter to indicate what color it should be. For example: :code:`y:Yes` and :code:`n;red:No` --default -d A default choice or text. If unspecified, it is :code:`y` for the type :code:`yesno`, the first choice for :code:`choices` and empty for others types. The default choice is selected when the user presses the :kbd:`Enter` key. --prompt -p default="> " The prompt to use when inputting a line of text or a password. --unhide-key default=u The key to be pressed to unhide hidden text --hidden-text-placeholder The text in the message to be replaced by hidden text. The hidden text is read via STDIN. ''' class Response(TypedDict): items: list[str] response: str | None def main(args: list[str]) -> Response: raise SystemExit('This must be run as kitten ask') @result_handler() def handle_result(args: list[str], data: Response, target_window_id: int, boss: BossType) -> None: if data['response'] is not None: func, *args = data['items'] getattr(boss, func)(data['response'], *args) if __name__ == '__main__': main(sys.argv) elif __name__ == '__doc__': cd = sys.cli_docs # type: ignore cd['usage'] = '' cd['options'] = option_text cd['help_text'] = 'Ask the user for input' cd['short_desc'] = 'Ask the user for input' kitty-0.41.1/kittens/broadcast/0000775000175000017510000000000014773370543015732 5ustar nileshnileshkitty-0.41.1/kittens/broadcast/__init__.py0000664000175000017510000000000014773370543020031 0ustar nileshnileshkitty-0.41.1/kittens/broadcast/main.py0000664000175000017510000001326614773370543017240 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import sys from base64 import standard_b64encode from gettext import gettext as _ from typing import Any from kitty.cli import parse_args from kitty.cli_stub import BroadcastCLIOptions from kitty.key_encoding import encode_key_event from kitty.rc.base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION from kitty.remote_control import create_basic_command, encode_send from kitty.short_uuid import uuid4 from kitty.typing import KeyEventType, ScreenSize from ..tui.handler import Handler from ..tui.line_edit import LineEdit from ..tui.loop import Loop from ..tui.operations import RESTORE_CURSOR, SAVE_CURSOR, styled def session_command(payload: dict[str, Any], start: bool = True) -> bytes: payload = payload.copy() payload['data'] = 'session:' + ('start' if start else 'end') send = create_basic_command('send-text', payload, no_response=True) return encode_send(send) class Broadcast(Handler): def __init__(self, opts: BroadcastCLIOptions, initial_strings: list[str]) -> None: self.opts = opts self.hide_input = False self.initial_strings = initial_strings self.payload = {'exclude_active': True, 'data': '', 'match': opts.match, 'match_tab': opts.match_tab, 'session_id': uuid4()} self.line_edit = LineEdit() self.session_started = False if not opts.match and not opts.match_tab: self.payload['all'] = True def initialize(self) -> None: self.write_broadcast_session() self.print('Type the text to broadcast below, press', styled(self.opts.end_session, fg='yellow'), 'to quit:') for x in self.initial_strings: self.write_broadcast_text(x) self.write(SAVE_CURSOR) def commit_line(self) -> None: self.write(RESTORE_CURSOR + SAVE_CURSOR) self.cmd.clear_to_end_of_screen() self.line_edit.write(self.write, screen_cols=self.screen_size.cols) def on_resize(self, screen_size: ScreenSize) -> None: super().on_resize(screen_size) self.commit_line() def on_text(self, text: str, in_bracketed_paste: bool = False) -> None: self.write_broadcast_text(text) if not self.hide_input: self.line_edit.on_text(text, in_bracketed_paste) self.commit_line() def on_interrupt(self) -> None: self.write_broadcast_text('\x03') self.line_edit.clear() self.commit_line() def on_eot(self) -> None: self.write_broadcast_text('\x04') def on_key(self, key_event: KeyEventType) -> None: if key_event.matches(self.opts.hide_input_toggle): self.hide_input ^= True self.cmd.set_cursor_visible(not self.hide_input) if self.hide_input: self.end_line() self.print('Input hidden, press', styled(self.opts.hide_input_toggle, fg='yellow'), 'to unhide:') self.end_line() return if key_event.matches(self.opts.end_session): self.quit_loop(0) return if not self.hide_input and self.line_edit.on_key(key_event): self.commit_line() if key_event.matches('enter'): self.write_broadcast_text('\r') self.end_line() return ek = encode_key_event(key_event) ek = standard_b64encode(ek.encode('utf-8')).decode('ascii') self.write_broadcast_data('kitty-key:' + ek) def end_line(self) -> None: self.print('') self.line_edit.clear() self.write(SAVE_CURSOR) def write_broadcast_text(self, text: str) -> None: self.write_broadcast_data('base64:' + standard_b64encode(text.encode('utf-8')).decode('ascii')) def write_broadcast_data(self, data: str) -> None: payload = self.payload.copy() payload['data'] = data send = create_basic_command('send-text', payload, no_response=True) self.write(encode_send(send)) def write_broadcast_session(self, start: bool = True) -> None: self.session_started = start self.write(session_command(self.payload, start)) OPTIONS = (''' --hide-input-toggle default=Ctrl+Alt+Esc Key to press that will toggle hiding of the input in the broadcast window itself. Useful while typing a password, prevents the password from being visible on the screen. --end-session default=Ctrl+Esc Key to press to end the broadcast session. ''' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')).format help_text = 'Broadcast typed text to kitty windows. By default text is sent to all windows, unless one of the matching options is specified' usage = '[initial text to send ...]' def parse_broadcast_args(args: list[str]) -> tuple[BroadcastCLIOptions, list[str]]: return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten broadcast', result_class=BroadcastCLIOptions) def main(args: list[str]) -> dict[str, Any] | None: try: opts, items = parse_broadcast_args(args[1:]) except SystemExit as e: if e.code != 0: print(e.args[0], file=sys.stderr) input(_('Press Enter to quit')) return None sys.stdout.flush() loop = Loop() handler = Broadcast(opts, items) try: loop.loop(handler) finally: if handler.session_started: sys.stdout.buffer.write(session_command(handler.payload, False)) sys.stdout.buffer.flush() return None if __name__ == '__main__': main(sys.argv) elif __name__ == '__doc__': cd = sys.cli_docs # type: ignore cd['usage'] = usage cd['options'] = OPTIONS cd['help_text'] = help_text cd['short_desc'] = 'Broadcast typed text to kitty windows' kitty-0.41.1/kittens/choose_fonts/0000775000175000017510000000000014773370543016461 5ustar nileshnileshkitty-0.41.1/kittens/choose_fonts/__init__.py0000664000175000017510000000000014773370543020560 0ustar nileshnileshkitty-0.41.1/kittens/choose_fonts/backend.go0000664000175000017510000000762014773370543020404 0ustar nileshnileshpackage choose_fonts import ( "encoding/json" "fmt" "io" "os" "os/exec" "strings" "sync" "time" "kitty/tools/utils" ) var _ = fmt.Print type kitty_font_backend_type struct { from io.ReadCloser to io.WriteCloser json_decoder *json.Decoder cmd *exec.Cmd stderr strings.Builder lock sync.Mutex r io.ReadCloser w io.WriteCloser wait_for_exit chan error started, exited, failed bool timeout time.Duration } func (k *kitty_font_backend_type) start() (err error) { exe := utils.KittyExe() if exe == "" { exe = utils.Which("kitty") } if exe == "" { return fmt.Errorf("Failed to find the kitty executable, this kitten requires the kitty executable to be present. You can use the environment variable KITTY_PATH_TO_KITTY_EXE to specify the path to the kitty executable") } k.cmd = exec.Command(exe, "+runpy", "from kittens.choose_fonts.backend import main; main()") k.cmd.Stderr = &k.stderr if k.r, k.to, err = os.Pipe(); err != nil { return err } k.cmd.Stdin = k.r if k.from, k.w, err = os.Pipe(); err != nil { return err } k.cmd.Stdout = k.w k.json_decoder = json.NewDecoder(k.from) if err = k.cmd.Start(); err != nil { return err } k.started = true k.timeout = 60 * time.Second k.wait_for_exit = make(chan error) go func() { k.wait_for_exit <- k.cmd.Wait() }() return } var kitty_font_backend kitty_font_backend_type func (k *kitty_font_backend_type) send(v any) error { if k.to == nil { return fmt.Errorf("Trying to send data when to pipe is nil") } data, err := json.Marshal(v) if err != nil { return fmt.Errorf("Could not encode message to kitty with error: %w", err) } c := make(chan error) go func() { if _, err = k.to.Write(data); err != nil { c <- fmt.Errorf("Failed to send message to kitty with I/O error: %w", err) return } if _, err = k.to.Write([]byte{'\n'}); err != nil { c <- fmt.Errorf("Failed to send message to kitty with I/O error: %w", err) return } c <- nil }() select { case err := <-c: return err case <-time.After(k.timeout): return fmt.Errorf("Timed out waiting to write to kitty font backend after %v", k.timeout) case err := <-k.wait_for_exit: k.exited = true if err == nil { err = fmt.Errorf("kitty font backend exited with no error while waiting for a response from it") } else { k.failed = true } return err } } func (k *kitty_font_backend_type) query(action string, cmd map[string]any, result any) error { k.lock.Lock() defer k.lock.Unlock() if cmd == nil { cmd = make(map[string]any) } cmd["action"] = action if err := k.send(cmd); err != nil { return err } c := make(chan error) go func() { if err := k.json_decoder.Decode(result); err != nil { c <- fmt.Errorf("Failed to decode JSON from kitty with error: %w", err) } c <- nil }() select { case err := <-c: return err case <-time.After(k.timeout): return fmt.Errorf("Timed out waiting for response from kitty font backend after %v", k.timeout) case err := <-k.wait_for_exit: k.exited = true if err == nil { err = fmt.Errorf("kitty font backed exited with no error while waiting for a response from it") } else { k.failed = true } return err } } func (k *kitty_font_backend_type) release() (err error) { if k.r != nil { k.r.Close() k.r = nil } if k.to != nil { k.to.Close() k.to = nil } if k.w != nil { k.w.Close() k.w = nil } if k.from != nil { k.from.Close() k.from = nil } if k.started && !k.exited { timeout := 2 * time.Second select { case err = <-k.wait_for_exit: k.exited = true if err != nil { k.failed = true } case <-time.After(timeout): k.failed = true err = fmt.Errorf("Timed out waiting for kitty font backend to exit for %v", timeout) } } os.Stderr.WriteString(k.stderr.String()) return } kitty-0.41.1/kittens/choose_fonts/backend.py0000664000175000017510000002170714773370543020431 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2024, Kovid Goyal import json import os import string import sys import tempfile from typing import TYPE_CHECKING, Any, Literal, Optional, TypedDict from kitty.cli import create_default_opts from kitty.conf.utils import to_color from kitty.constants import kitten_exe from kitty.fonts import Descriptor from kitty.fonts.common import ( face_from_descriptor, get_axis_map, get_font_files, get_named_style, get_variable_data_for_descriptor, get_variable_data_for_face, is_variable, spec_for_face, ) from kitty.fonts.features import Type, known_features from kitty.fonts.list import create_family_groups from kitty.fonts.render import display_bitmap from kitty.options.types import Options from kitty.options.utils import parse_font_spec from kitty.typing import NotRequired from kitty.utils import screen_size_function if TYPE_CHECKING: from kitty.fast_data_types import FeatureData def setup_debug_print() -> bool: if 'KITTY_STDIO_FORWARDED' in os.environ: try: fd = int(os.environ['KITTY_STDIO_FORWARDED']) except Exception: return False try: sys.stdout = open(fd, 'w', closefd=False) return True except OSError: return False return False def send_to_kitten(x: Any) -> None: f = sys.__stdout__ assert f is not None try: f.buffer.write(json.dumps(x).encode()) f.buffer.write(b'\n') f.buffer.flush() except BrokenPipeError: raise SystemExit('Pipe to kitten was broken while sending data to it') class TextStyle(TypedDict): font_size: float dpi_x: float dpi_y: float foreground: str background: str OptNames = Literal['font_family', 'bold_font', 'italic_font', 'bold_italic_font'] FamilyKey = tuple[OptNames, ...] def opts_from_cmd(cmd: dict[str, Any]) -> tuple[Options, FamilyKey, float, float]: opts = Options() ts: TextStyle = cmd['text_style'] opts.font_size = ts['font_size'] opts.foreground = to_color(ts['foreground']) opts.background = to_color(ts['background']) family_key = [] def d(k: OptNames) -> None: if k in cmd: setattr(opts, k, parse_font_spec(cmd[k])) family_key.append(k) d('font_family') d('bold_font') d('italic_font') d('bold_italic_font') return opts, tuple(family_key), ts['dpi_x'], ts['dpi_y'] BaseKey = tuple[str, int, int] FaceKey = tuple[str, BaseKey] RenderedSample = tuple[bytes, dict[str, Any]] RenderedSampleTransmit = dict[str, Any] SAMPLE_TEXT = string.ascii_lowercase + ' ' + string.digits + ' ' + string.ascii_uppercase + ' ' + string.punctuation class FD(TypedDict): is_index: bool name: NotRequired[str] tooltip: NotRequired[str] sample: NotRequired[str] params: NotRequired[tuple[str, ...]] def get_features(features: dict[str, Optional['FeatureData']]) -> dict[str, FD]: ans = {} for tag, data in features.items(): kf = known_features.get(tag) if kf is None or kf.type is Type.hidden: continue fd: FD = {'is_index': kf.type is Type.index} ans[tag] = fd if data is not None: if n := data.get('name'): fd['name'] = n if n := data.get('tooltip'): fd['tooltip'] = n if n := data.get('sample'): fd['sample'] = n if p := data.get('params'): fd['params'] = p return ans def render_face_sample(font: Descriptor, opts: Options, dpi_x: float, dpi_y: float, width: int, height: int, sample_text: str = '') -> RenderedSample: face = face_from_descriptor(font, opts.font_size, dpi_x, dpi_y) face.set_size(opts.font_size, dpi_x, dpi_y) metadata = { 'variable_data': get_variable_data_for_face(face), 'style': font['style'], 'psname': face.postscript_name(), 'features': get_features(face.get_features()), 'applied_features': face.applied_features(), 'spec': spec_for_face(font['family'], face).as_setting, 'cell_width': 0, 'cell_height': 0, 'canvas_height': 0, 'canvas_width': width, } if is_variable(font): ns = get_named_style(face) if ns: metadata['variable_named_style'] = ns metadata['variable_axis_map'] = get_axis_map(face) bitmap, cell_width, cell_height = face.render_sample_text(sample_text or SAMPLE_TEXT, width, height, opts.foreground.rgb) metadata['cell_width'] = cell_width metadata['cell_height'] = cell_height metadata['canvas_height'] = len(bitmap) // (4 *width) return bitmap, metadata def render_family_sample( opts: Options, family_key: FamilyKey, dpi_x: float, dpi_y: float, width: int, height: int, output_dir: str, cache: dict[FaceKey, RenderedSampleTransmit] ) -> dict[str, RenderedSampleTransmit]: base_key: BaseKey = opts.font_family.created_from_string, width, height ans: dict[str, RenderedSampleTransmit] = {} font_files = get_font_files(opts) for x in family_key: key: FaceKey = x + ': ' + str(getattr(opts, x)), base_key if x == 'font_family': desc = font_files['medium'] elif x == 'bold_font': desc = font_files['bold'] elif x == 'italic_font': desc = font_files['italic'] elif x == 'bold_italic_font': desc = font_files['bi'] cached = cache.get(key) if cached is not None: ans[x] = cached else: with tempfile.NamedTemporaryFile(delete=False, suffix='.rgba', dir=output_dir) as tf: bitmap, metadata = render_face_sample(desc, opts, dpi_x, dpi_y, width, height) tf.write(bitmap) metadata['path'] = tf.name cache[key] = ans[x] = metadata return ans ResolvedFace = dict[Literal['family', 'spec', 'setting'], str] def spec_for_descriptor(d: Descriptor, font_size: float) -> str: face = face_from_descriptor(d, font_size, 288, 288) return spec_for_face(d['family'], face).as_setting def resolved_faces(opts: Options) -> dict[OptNames, ResolvedFace]: font_files = get_font_files(opts) ans: dict[OptNames, ResolvedFace] = {} def d(key: Literal['medium', 'bold', 'italic', 'bi'], opt_name: OptNames) -> None: descriptor = font_files[key] ans[opt_name] = { 'family': descriptor['family'], 'spec': spec_for_descriptor(descriptor, opts.font_size), 'setting': getattr(opts, opt_name).created_from_string } d('medium', 'font_family') d('bold', 'bold_font') d('italic', 'italic_font') d('bi', 'bold_italic_font') return ans def main() -> None: setup_debug_print() cache: dict[FaceKey, RenderedSampleTransmit] = {} for line in sys.stdin.buffer: cmd = json.loads(line) action = cmd.get('action', '') if action == 'list_monospaced_fonts': opts = create_default_opts() send_to_kitten({'fonts': create_family_groups(), 'resolved_faces': resolved_faces(opts)}) elif action == 'read_variable_data': ans = [] for descriptor in cmd['descriptors']: ans.append(get_variable_data_for_descriptor(descriptor)) send_to_kitten(ans) elif action == 'render_family_samples': opts, family_key, dpi_x, dpi_y = opts_from_cmd(cmd) send_to_kitten(render_family_sample(opts, family_key, dpi_x, dpi_y, cmd['width'], cmd['height'], cmd['output_dir'], cache)) else: raise SystemExit(f'Unknown action: {action}') def query_kitty() -> dict[str, str]: import subprocess ans = {} for line in subprocess.check_output([kitten_exe(), 'query-terminal']).decode().splitlines(): k, sep, v = line.partition(':') if sep == ':': ans[k] = v.strip() return ans def showcase(family: str = 'family="Fira Code"', sample_text: str = '') -> None: q = query_kitty() opts = Options() opts.foreground = to_color(q['foreground']) opts.background = to_color(q['background']) opts.font_size = float(q['font_size']) opts.font_family = parse_font_spec(family) font_files = get_font_files(opts) desc = font_files['medium'] ss = screen_size_function()() width = ss.cell_width * ss.cols height = 5 * ss.cell_height bitmap, m = render_face_sample(desc, opts, float(q['dpi_x']), float(q['dpi_y']), width, height, sample_text=sample_text) display_bitmap(bitmap, m['canvas_width'], m['canvas_height']) def test_render(spec: str = 'family="Fira Code"', width: int = 1560, height: int = 116, font_size: float = 12, dpi: float = 288) -> None: opts = Options() opts.font_family = parse_font_spec(spec) opts.font_size = font_size opts.foreground = to_color('white') desc = get_font_files(opts)['medium'] bitmap, m = render_face_sample(desc, opts, float(dpi), float(dpi), width, height) display_bitmap(bitmap, m['canvas_width'], m['canvas_height']) kitty-0.41.1/kittens/choose_fonts/face.go0000664000175000017510000003520514773370543017713 0ustar nileshnileshpackage choose_fonts import ( "fmt" "maps" "math" "slices" "strconv" "strings" "sync" "kitty/tools/tui" "kitty/tools/tui/loop" "kitty/tools/utils" "kitty/tools/wcswidth" ) var _ = fmt.Print type face_panel struct { handler *handler family, which string settings faces_settings current_preview *RenderedSampleTransmit current_preview_key faces_preview_key preview_cache map[faces_preview_key]map[string]RenderedSampleTransmit preview_cache_mutex sync.Mutex } // Create a new FontSpec that keeps features and axis values and named styles // same as the current setting. Names are all reset apart from style name. func (self *face_panel) new_font_spec() (*FontSpec, error) { fs, err := NewFontSpec(self.get(), self.current_preview.Features) if err != nil { return nil, err } if fs.system.val == "auto" { if fs, err = NewFontSpec(self.current_preview.Spec, self.current_preview.Features); err != nil { return nil, err } } // reset these selectors as we will be using some style/axis based selector instead fs.family = settable_string{self.family, true} fs.postscript_name = settable_string{} fs.full_name = settable_string{} if len(self.current_preview.Variable_data.Axes) > 0 { fs.variable_name = settable_string{self.current_preview.Variable_data.Variations_postscript_name_prefix, true} } else { fs.variable_name = settable_string{} } return &fs, nil } func (self *face_panel) set_variable_spec(named_style string, axis_overrides map[string]float64) error { fs, err := self.new_font_spec() if err != nil { return err } if axis_overrides != nil { axis_values := self.current_preview.current_axis_values() maps.Copy(axis_values, axis_overrides) fs.axes = axis_values fs.style = settable_string{"", false} } else if named_style != "" { fs.style = settable_string{named_style, true} fs.axes = nil } self.set(fs.String()) return nil } func (self *face_panel) set_style(named_style string) error { fs, err := self.new_font_spec() if err != nil { return err } fs.style = settable_string{named_style, true} self.set(fs.String()) return nil } func (self *face_panel) render_lines(start_y int, lines ...string) (y int) { sz, _ := self.handler.lp.ScreenSize() _, y, str := self.handler.render_lines.InRectangle(lines, 0, start_y, int(sz.WidthCells), int(sz.HeightCells)-y, &self.handler.mouse_state, self.on_click) self.handler.lp.QueueWriteString(str) return } const current_val_style = "fg=cyan bold" const control_name_style = "fg=yellow bright bold" func (self *face_panel) draw_axis(sz loop.ScreenSize, y int, ax VariableAxis, axis_value float64) int { lp := self.handler.lp buf := strings.Builder{} buf.WriteString(fmt.Sprintf("%s: ", lp.SprintStyled(control_name_style, utils.IfElse(ax.Strid != "", ax.Strid, ax.Tag)))) num_of_cells := int(sz.WidthCells) - wcswidth.Stringwidth(buf.String()) if num_of_cells < 5 { return y } frac := (min(axis_value, ax.Maximum) - ax.Minimum) / (ax.Maximum - ax.Minimum) current_cell := int(math.Floor(frac * float64(num_of_cells-1))) for i := 0; i < num_of_cells; i++ { buf.WriteString(utils.IfElse(i == current_cell, lp.SprintStyled(current_val_style, `⬤`), tui.InternalHyperlink("•", fmt.Sprintf("axis:%d/%d:%s", i, num_of_cells-1, ax.Tag)))) } return self.render_lines(y, buf.String()) } func is_current_named_style(style_group_name, style_name string, vd VariableData, ns NamedStyle) bool { for _, dax := range vd.Design_axes { if dax.Name == style_group_name { if val, found := ns.Axis_values[dax.Tag]; found { for _, v := range dax.Values { if v.Value == val { return v.Name == style_name } } } break } } return false } func (self *face_panel) draw_variable_fine_tune(sz loop.ScreenSize, start_y int, preview RenderedSampleTransmit) (y int, err error) { s := styles_for_variable_data(preview.Variable_data) lines := []string{} lp := self.handler.lp for _, sg := range s.style_groups { if len(sg.styles) < 2 { continue } formatted := make([]string, len(sg.styles)) for i, style_name := range sg.styles { if is_current_named_style(sg.name, style_name, preview.Variable_data, preview.Variable_named_style) { formatted[i] = self.handler.lp.SprintStyled(current_val_style, style_name) } else { formatted[i] = tui.InternalHyperlink(style_name, "variable_style:"+style_name) } } line := lp.SprintStyled(control_name_style, sg.name) + ": " + strings.Join(formatted, ", ") lines = append(lines, line) } y = self.render_lines(start_y, lines...) sub_title := "Fine tune the appearance by clicking in the variable axes below:" axis_values := self.current_preview.current_axis_values() for _, ax := range self.current_preview.Variable_data.Axes { if ax.Hidden { continue } if sub_title != "" { y = self.render_lines(y+1, sub_title, "") sub_title = `` } y = self.draw_axis(sz, y, ax, axis_values[ax.Tag]) } return y, nil } func (self *face_panel) draw_family_style_select(_ loop.ScreenSize, start_y int, preview RenderedSampleTransmit) (y int, err error) { lp := self.handler.lp s := styles_in_family(self.family, self.handler.listing.fonts[self.family]) lines := []string{} for _, sg := range s.style_groups { formatted := make([]string, len(sg.styles)) for i, style_name := range sg.styles { if style_name == preview.Style { formatted[i] = lp.SprintStyled(current_val_style, style_name) } else { formatted[i] = tui.InternalHyperlink(style_name, "style:"+style_name) } } line := lp.SprintStyled(control_name_style, sg.name) + ": " + strings.Join(formatted, ", ") lines = append(lines, line) } y = self.render_lines(start_y, lines...) return y, nil } func (self *face_panel) draw_font_features(_ loop.ScreenSize, start_y int, preview RenderedSampleTransmit) (y int, err error) { lp := self.handler.lp y = start_y if len(preview.Features) == 0 { return } formatted := make([]string, 0, len(preview.Features)) sort_keys := make(map[string]string) for feat_tag, data := range preview.Features { var text, sort_key string if preview.Applied_features[feat_tag] != "" { text = preview.Applied_features[feat_tag] sort_key = text if sort_key[0] == '-' || sort_key[1] == '+' { sort_key = sort_key[1:] } text = strings.Replace(text, "+", lp.SprintStyled("fg=green", "+"), 1) text = strings.Replace(text, "-", lp.SprintStyled("fg=red", "-"), 1) text = strings.Replace(text, "=", lp.SprintStyled("fg=cyan", "="), 1) if data.Name != "" { text = data.Name + ": " + text sort_key = data.Name } } else { if data.Name != "" { text = data.Name sort_key = data.Name + ": " + text } else { text = feat_tag sort_key = text } text = lp.SprintStyled("dim", text) } f := tui.InternalHyperlink(text, "feature:"+feat_tag) sort_keys[f] = strings.ToLower(sort_key) formatted = append(formatted, f) } utils.StableSortWithKey(formatted, func(a string) string { return sort_keys[a] }) line := lp.SprintStyled(control_name_style, `Features`) + ": " + strings.Join(formatted, ", ") y = self.render_lines(start_y, ``, line) return } func (self *handler) draw_preview_header(x int) { sz, _ := self.lp.ScreenSize() width := int(sz.WidthCells) - x p := center_string(self.lp.SprintStyled("italic", " preview "), width, "─") self.lp.QueueWriteString(self.lp.SprintStyled("dim", p)) } func (self *face_panel) render_preview(key faces_preview_key) { var r map[string]RenderedSampleTransmit s := key.settings self.handler.set_worker_error(kitty_font_backend.query("render_family_samples", map[string]any{ "text_style": self.handler.text_style, "font_family": s.font_family, "bold_font": s.bold_font, "italic_font": s.italic_font, "bold_italic_font": s.bold_italic_font, "width": key.width, "height": key.height, "output_dir": self.handler.temp_dir, }, &r)) self.preview_cache_mutex.Lock() defer self.preview_cache_mutex.Unlock() self.preview_cache[key] = r } func (self *face_panel) draw_screen() (err error) { lp := self.handler.lp lp.SetCursorVisible(false) sz, _ := lp.ScreenSize() styled := lp.SprintStyled wt := "Regular" switch self.which { case "bold_font": wt = "Bold" case "italic_font": wt = "Italic" case "bold_italic_font": wt = "Bold-Italic font" } lp.QueueWriteString(self.handler.format_title(fmt.Sprintf("%s: %s face", self.family, wt), 0)) lines := []string{ fmt.Sprintf("Press %s to accept any changes or %s to cancel. Click on a style name below to switch to it.", styled("fg=green", "Enter"), styled("fg=red", "Esc")), "", fmt.Sprintf("Current setting: %s", self.get()), "", } y := self.render_lines(2, lines...) num_lines_per_font := (int(sz.HeightCells) - y - 1) - 2 num_lines := max(1, num_lines_per_font) key := faces_preview_key{settings: self.settings, width: int(sz.WidthCells * sz.CellWidth), height: int(sz.CellHeight) * num_lines} self.current_preview_key = key self.preview_cache_mutex.Lock() defer self.preview_cache_mutex.Unlock() previews, found := self.preview_cache[key] if !found { self.preview_cache[key] = make(map[string]RenderedSampleTransmit) go func() { self.render_preview(key) self.handler.lp.WakeupMainThread() }() return } if len(previews) < 4 { return } preview := previews[self.which] self.current_preview = &preview if len(preview.Variable_data.Axes) > 0 { y, err = self.draw_variable_fine_tune(sz, y, preview) } else { y, err = self.draw_family_style_select(sz, y, preview) } if err != nil { return err } if y, err = self.draw_font_features(sz, y, preview); err != nil { return err } num_lines = int(math.Ceil(float64(preview.Canvas_height) / float64(sz.CellHeight))) if int(sz.HeightCells)-y >= num_lines+2 { y++ lp.MoveCursorTo(1, y+1) self.handler.draw_preview_header(0) y++ lp.MoveCursorTo(1, y+1) self.handler.graphics_manager.display_image(0, preview.Path, preview.Canvas_width, preview.Canvas_height) } return } func (self *face_panel) initialize(h *handler) (err error) { self.handler = h self.preview_cache = make(map[faces_preview_key]map[string]RenderedSampleTransmit) return } func (self *face_panel) on_wakeup() error { return self.handler.draw_screen() } func (self *face_panel) get() string { switch self.which { case "font_family": return self.settings.font_family case "bold_font": return self.settings.bold_font case "italic_font": return self.settings.italic_font case "bold_italic_font": return self.settings.bold_italic_font } panic(fmt.Sprintf("Unknown self.which value: %s", self.which)) } func (self *face_panel) set(setting string) { switch self.which { case "font_family": self.settings.font_family = setting case "bold_font": self.settings.bold_font = setting case "italic_font": self.settings.italic_font = setting case "bold_italic_font": self.settings.bold_italic_font = setting } } func (self *face_panel) update_feature_in_setting(pff ParsedFontFeature) error { fs, err := self.new_font_spec() if err != nil { return err } found := false for _, f := range fs.features { if f.tag == pff.tag { f.val = pff.val found = true break } } if !found { fs.features = append(fs.features, &pff) } self.set(fs.String()) return nil } func (self *face_panel) remove_feature_in_setting(tag string) error { fs, err := self.new_font_spec() if err != nil { return err } if len(fs.features) > 0 { fs.features = slices.DeleteFunc(fs.features, func(x *ParsedFontFeature) bool { return x.tag == tag }) } self.set(fs.String()) return nil } func (self *face_panel) change_feature_value(tag string, val uint, remove bool) error { if remove { return self.remove_feature_in_setting(tag) } pff := ParsedFontFeature{tag: tag, val: val} return self.update_feature_in_setting(pff) } func (self *face_panel) handle_click_on_feature(feat_tag string) error { d := self.current_preview.Features[feat_tag] if d.Is_index { var current_val uint for q, serialized := range self.current_preview.Applied_features { if q == feat_tag && serialized != "" { if _, num, found := strings.Cut(serialized, "="); found { if v, err := strconv.ParseUint(num, 10, 0); err == nil { current_val = uint(v) } } else { current_val = utils.IfElse(serialized[0] == '-', uint(0), uint(1)) } return self.handler.if_pane.on_enter(self.family, self.which, self.settings, feat_tag, d, current_val) } } return self.handler.if_pane.on_enter(self.family, self.which, self.settings, feat_tag, d, current_val) } else { for q, serialized := range self.current_preview.Applied_features { if q == feat_tag && serialized != "" { if serialized[0] == '-' { return self.remove_feature_in_setting(feat_tag) } return self.update_feature_in_setting(ParsedFontFeature{tag: feat_tag, is_bool: true, val: 0}) } } return self.update_feature_in_setting(ParsedFontFeature{tag: feat_tag, is_bool: true, val: 1}) } } func (self *face_panel) on_click(id string) (err error) { scheme, val, _ := strings.Cut(id, ":") switch scheme { case "style": if err = self.set_style(val); err != nil { return err } case "variable_style": if err = self.set_variable_spec(val, nil); err != nil { return err } case "feature": if err = self.handle_click_on_feature(val); err != nil { return err } case "axis": p, tag, _ := strings.Cut(val, ":") num, den, _ := strings.Cut(p, "/") n, _ := strconv.Atoi(num) d, _ := strconv.Atoi(den) frac := float64(n) / float64(d) for _, ax := range self.current_preview.Variable_data.Axes { if ax.Tag == tag { axval := ax.Minimum + (ax.Maximum-ax.Minimum)*frac if err = self.set_variable_spec("", map[string]float64{tag: axval}); err != nil { return err } break } } } // Render preview synchronously to void flashing key := self.current_preview_key key.settings = self.settings self.preview_cache_mutex.Lock() previews := self.preview_cache[key] self.preview_cache_mutex.Unlock() if len(previews) < 4 { self.render_preview(key) } return self.handler.draw_screen() } func (self *face_panel) on_key_event(event *loop.KeyEvent) (err error) { if event.MatchesPressOrRepeat("esc") { event.Handled = true self.handler.current_pane = &self.handler.faces return self.handler.draw_screen() } else if event.MatchesPressOrRepeat("enter") { event.Handled = true self.handler.current_pane = &self.handler.faces self.handler.faces.settings = self.settings return self.handler.draw_screen() } return } func (self *face_panel) on_text(text string, from_key_event bool, in_bracketed_paste bool) (err error) { return } func (self *face_panel) on_enter(family, which string, settings faces_settings) error { self.family = family self.settings = settings self.which = which self.handler.current_pane = self return self.handler.draw_screen() } kitty-0.41.1/kittens/choose_fonts/faces.go0000664000175000017510000001165714773370543020103 0ustar nileshnileshpackage choose_fonts import ( "fmt" "math" "sync" "kitty/tools/tui/loop" "kitty/tools/utils" ) var _ = fmt.Print type faces_settings struct { font_family, bold_font, italic_font, bold_italic_font string } type faces_preview_key struct { settings faces_settings width, height int } type faces struct { handler *handler family string settings faces_settings preview_cache map[faces_preview_key]map[string]RenderedSampleTransmit preview_cache_mutex sync.Mutex } const highlight_key_style = "fg=magenta bold" func (self *faces) draw_screen() (err error) { lp := self.handler.lp lp.SetCursorVisible(false) sz, _ := lp.ScreenSize() styled := lp.SprintStyled lp.QueueWriteString(self.handler.format_title(self.family, 0)) lines := []string{ fmt.Sprintf("Press %s to select this font, %s to go back to the font list or any of the %s keys below to fine-tune the appearance of the individual font styles.", styled("fg=green", "Enter"), styled("fg=red", "Esc"), styled(highlight_key_style, "highlighted")), "", } _, y, str := self.handler.render_lines.InRectangle(lines, 0, 2, int(sz.WidthCells), int(sz.HeightCells), &self.handler.mouse_state, self.on_click) lp.QueueWriteString(str) num_lines_per_font := ((int(sz.HeightCells) - y - 1) / 4) - 2 num_lines := max(1, num_lines_per_font) key := faces_preview_key{settings: self.settings, width: int(sz.WidthCells * sz.CellWidth), height: int(sz.CellHeight) * num_lines} self.preview_cache_mutex.Lock() defer self.preview_cache_mutex.Unlock() previews, found := self.preview_cache[key] if !found { self.preview_cache[key] = make(map[string]RenderedSampleTransmit) go func() { var r map[string]RenderedSampleTransmit s := key.settings self.handler.set_worker_error(kitty_font_backend.query("render_family_samples", map[string]any{ "text_style": self.handler.text_style, "font_family": s.font_family, "bold_font": s.bold_font, "italic_font": s.italic_font, "bold_italic_font": s.bold_italic_font, "width": key.width, "height": key.height, "output_dir": self.handler.temp_dir, }, &r)) self.preview_cache_mutex.Lock() defer self.preview_cache_mutex.Unlock() self.preview_cache[key] = r self.handler.lp.WakeupMainThread() }() return } if len(previews) < 4 { return } slot := 0 d := func(setting, title string) { r := previews[setting] num_lines := int(math.Ceil(float64(r.Canvas_height) / float64(sz.CellHeight))) if int(sz.HeightCells)-y < num_lines+1 { return } lp.MoveCursorTo(1, y+1) _, y, str = self.handler.render_lines.InRectangle([]string{title + ": " + previews[setting].Psname}, 0, y, int(sz.WidthCells), int(sz.HeightCells), &self.handler.mouse_state, self.on_click) lp.QueueWriteString(str) if y+num_lines < int(sz.HeightCells) { lp.MoveCursorTo(1, y+1) self.handler.graphics_manager.display_image(slot, r.Path, r.Canvas_width, r.Canvas_height) slot++ y += num_lines + 1 } } d(`font_family`, styled(highlight_key_style, "R")+`egular`) d(`bold_font`, styled(highlight_key_style, "B")+`old`) d(`italic_font`, styled(highlight_key_style, "I")+`talic`) d(`bold_italic_font`, "B"+styled(highlight_key_style, "o")+`ld-Italic`) return } func (self *faces) initialize(h *handler) (err error) { self.handler = h self.preview_cache = make(map[faces_preview_key]map[string]RenderedSampleTransmit) return } func (self *faces) on_wakeup() error { return self.handler.draw_screen() } func (self *faces) on_click(id string) (err error) { return } func (self *faces) on_key_event(event *loop.KeyEvent) (err error) { if event.MatchesPressOrRepeat("esc") { event.Handled = true self.handler.current_pane = &self.handler.listing return self.handler.draw_screen() } if event.MatchesPressOrRepeat("enter") { event.Handled = true return self.handler.final_pane.on_enter(self.family, self.settings) } return } func (self *faces) on_text(text string, from_key_event bool, in_bracketed_paste bool) (err error) { if from_key_event { which := "" switch text { case "r", "R": which = "font_family" case "b", "B": which = "bold_font" case "i", "I": which = "italic_font" case "o", "O": which = "bold_italic_font" } if which != "" { return self.handler.face_pane.on_enter(self.family, which, self.settings) } } return } func (self *faces) on_enter(family string) error { if family != "" { self.family = family r := self.handler.listing.resolved_faces_from_kitty_conf d := func(conf ResolvedFace, setting *string, defval string) { s := utils.IfElse(conf.Setting == "auto", "auto", conf.Spec) *setting = utils.IfElse(family == conf.Family, s, defval) } d(r.Font_family, &self.settings.font_family, fmt.Sprintf(`family="%s"`, family)) d(r.Bold_font, &self.settings.bold_font, "auto") d(r.Italic_font, &self.settings.italic_font, "auto") d(r.Bold_italic_font, &self.settings.bold_italic_font, "auto") } self.handler.current_pane = self return self.handler.draw_screen() } kitty-0.41.1/kittens/choose_fonts/family_list.go0000664000175000017510000000776514773370543021343 0ustar nileshnileshpackage choose_fonts import ( "fmt" "kitty/tools/tui" "kitty/tools/tui/subseq" "kitty/tools/utils" "kitty/tools/wcswidth" ) var _ = fmt.Print type FamilyList struct { families, all_families []string current_search string display_strings []string widths []int max_width, current_idx int } func (self *FamilyList) Len() int { return len(self.families) } func (self *FamilyList) Select(family string) bool { for idx, q := range self.families { if q == family { self.current_idx = idx return true } } return false } func (self *FamilyList) Next(delta int, allow_wrapping bool) bool { l := func() int { return self.Len() } if l() == 0 { return false } idx := self.current_idx + delta if !allow_wrapping && (idx < 0 || idx > l()) { return false } for idx < 0 { idx += l() } self.current_idx = idx % l() return true } func limit_lengths(text string) string { t, _ := wcswidth.TruncateToVisualLengthWithWidth(text, 31) if len(t) >= len(text) { return text } return t + "…" } func match(expression string, items []string) []*subseq.Match { matches := subseq.ScoreItems(expression, items, subseq.Options{Level1: " "}) matches = utils.StableSort(matches, func(a, b *subseq.Match) int { if b.Score < a.Score { return -1 } if b.Score > a.Score { return 1 } return 0 }) return matches } const ( MARK_BEFORE = "\033[33m" MARK_AFTER = "\033[39m" ) func apply_search(families []string, expression string, marks ...string) (matched_families []string, display_strings []string) { mark_before, mark_after := MARK_BEFORE, MARK_AFTER if len(marks) == 2 { mark_before, mark_after = marks[0], marks[1] } results := utils.Filter(match(expression, families), func(x *subseq.Match) bool { return x.Score > 0 }) matched_families = make([]string, 0, len(results)) display_strings = make([]string, 0, len(results)) for _, m := range results { text := m.Text positions := m.Positions for i := len(positions) - 1; i >= 0; i-- { p := positions[i] text = text[:p] + mark_before + text[p:p+1] + mark_after + text[p+1:] } display_strings = append(display_strings, text) matched_families = append(matched_families, m.Text) } return } func make_family_names_clickable(family string) string { id := wcswidth.StripEscapeCodes(family) return tui.InternalHyperlink(family, "family-chosen:"+id) } func (self *FamilyList) UpdateFamilies(families []string) { self.families, self.all_families = families, families if self.current_search != "" { self.families, self.display_strings = apply_search(self.all_families, self.current_search) self.display_strings = utils.Map(limit_lengths, self.display_strings) } else { self.display_strings = utils.Map(limit_lengths, families) } self.display_strings = utils.Map(make_family_names_clickable, self.display_strings) self.widths = utils.Map(wcswidth.Stringwidth, self.display_strings) self.max_width = utils.Max(0, self.widths...) self.current_idx = 0 } func (self *FamilyList) UpdateSearch(query string) bool { if query == self.current_search || len(self.all_families) == 0 { return false } self.current_search = query self.UpdateFamilies(self.all_families) return true } type Line struct { text string width int is_current bool } func (self *FamilyList) Lines(num_rows int) []Line { if num_rows < 1 { return nil } ans := make([]Line, 0, len(self.display_strings)) before_num := utils.Min(self.current_idx, num_rows-1) start := self.current_idx - before_num for i := start; i < utils.Min(start+num_rows, len(self.display_strings)); i++ { ans = append(ans, Line{self.display_strings[i], self.widths[i], i == self.current_idx}) } return ans } func (self *FamilyList) SelectFamily(family string) bool { for i, f := range self.families { if f == family { self.current_idx = i return true } } return false } func (self *FamilyList) CurrentFamily() string { if self.current_idx >= 0 && self.current_idx < len(self.families) { return self.families[self.current_idx] } return "" } kitty-0.41.1/kittens/choose_fonts/final.go0000664000175000017510000000612614773370543020106 0ustar nileshnileshpackage choose_fonts import ( "fmt" "path/filepath" "strings" "kitty/tools/config" "kitty/tools/tui/loop" "kitty/tools/utils" ) var _ = fmt.Print type final_pane struct { handler *handler settings faces_settings family string lp *loop.Loop } func (self *final_pane) render_lines(start_y int, lines ...string) (y int) { sz, _ := self.handler.lp.ScreenSize() _, y, str := self.handler.render_lines.InRectangle(lines, 0, start_y, int(sz.WidthCells), int(sz.HeightCells)-y, &self.handler.mouse_state, self.on_click) self.handler.lp.QueueWriteString(str) return } func (self *final_pane) draw_screen() (err error) { s := self.lp.SprintStyled h := func(x string) string { return s(highlight_key_style, x) } self.render_lines(0, fmt.Sprintf("You have chosen the %s family", s(current_val_style, self.family)), "", "What would you like to do?", "", fmt.Sprintf("%s to modify %s and use the new fonts", h("Enter"), s("italic", self.handler.opts.Config_file_name)), "", fmt.Sprintf("%s to abort and return to font selection", h("Esc")), "", fmt.Sprintf("%s to write the new font settings to %s", h("s"), s("italic", `STDOUT`)), "", fmt.Sprintf("%s to quit", h("Ctrl+c")), ) return } func (self *final_pane) initialize(h *handler) (err error) { self.handler = h self.lp = h.lp return } func (self *final_pane) on_wakeup() error { return self.handler.draw_screen() } func (self *final_pane) on_click(id string) (err error) { return } func (self faces_settings) serialized() string { return strings.Join([]string{ "font_family " + self.font_family, "bold_font " + self.bold_font, "italic_font " + self.italic_font, "bold_italic_font " + self.bold_italic_font, }, "\n") } func (self *final_pane) on_key_event(event *loop.KeyEvent) (err error) { if event.MatchesPressOrRepeat("esc") { event.Handled = true self.handler.current_pane = &self.handler.faces return self.handler.draw_screen() } if event.MatchesPressOrRepeat("enter") { event.Handled = true patcher := config.Patcher{Write_backup: true} path := "" if filepath.IsAbs(self.handler.opts.Config_file_name) { path = self.handler.opts.Config_file_name } else { path = filepath.Join(utils.ConfigDir(), self.handler.opts.Config_file_name) } updated, err := patcher.Patch(path, "KITTY_FONTS", self.settings.serialized(), "font_family", "bold_font", "italic_font", "bold_italic_font") if err != nil { return err } if updated { switch self.handler.opts.Reload_in { case "parent": config.ReloadConfigInKitty(true) case "all": config.ReloadConfigInKitty(false) } } self.lp.Quit(0) return nil } return } func (self *final_pane) on_text(text string, from_key_event bool, in_bracketed_paste bool) (err error) { if from_key_event { switch text { case "s", "S": output_on_exit = self.settings.serialized() + "\n" self.lp.Quit(0) return } } return } func (self *final_pane) on_enter(family string, settings faces_settings) error { self.settings = settings self.family = family self.handler.current_pane = self return self.handler.draw_screen() } kitty-0.41.1/kittens/choose_fonts/graphics.go0000664000175000017510000000547714773370543020625 0ustar nileshnileshpackage choose_fonts import ( "fmt" "strings" "kitty/tools/tui/graphics" "kitty/tools/tui/loop" "kitty/tools/utils" ) var _ = fmt.Print type image struct { id, image_number uint32 current_file string } func (i image) new_graphics_command() *graphics.GraphicsCommand { gc := &graphics.GraphicsCommand{} if i.id > 0 { gc.SetImageId(i.id) } else { gc.SetImageNumber(i.image_number) } return gc } type graphics_manager struct { main, bold, italic, bi, extra image lp *loop.Loop images [5]*image } func (g *graphics_manager) initialize(lp *loop.Loop) { g.images = [5]*image{&g.main, &g.bold, &g.italic, &g.bi, &g.extra} g.lp = lp payload := []byte("123") buf := strings.Builder{} gc := &graphics.GraphicsCommand{} gc.SetImageNumber(7891230).SetTransmission(graphics.GRT_transmission_direct).SetDataWidth(1).SetDataHeight(1).SetFormat( graphics.GRT_format_rgb).SetDataSize(uint64(len(payload))) d := func() uint32 { im := gc.ImageNumber() im++ gc.SetImageNumber(im) _ = gc.WriteWithPayloadTo(&buf, payload) return im } for _, img := range g.images { img.image_number = d() } lp.QueueWriteString(buf.String()) } func (g *graphics_manager) clear_placements() { buf := strings.Builder{} for _, img := range g.images { if img.current_file == "" { continue } gc := img.new_graphics_command() gc.SetAction(graphics.GRT_action_delete) gc.SetDelete(utils.IfElse(img.id > 0, graphics.GRT_delete_by_id, graphics.GRT_delete_by_number)) gc.WriteWithPayloadTo(&buf, nil) } g.lp.QueueWriteString(buf.String()) } func (g *graphics_manager) display_image(slot int, path string, img_width, img_height int) { img := g.images[slot] if img.current_file != path { gc := img.new_graphics_command() gc.SetAction(graphics.GRT_action_transmit).SetDataWidth(uint64(img_width)).SetDataHeight(uint64(img_height)).SetTransmission(graphics.GRT_transmission_file) gc.WriteWithPayloadToLoop(g.lp, []byte(path)) img.current_file = path } gc := img.new_graphics_command() gc.SetAction(graphics.GRT_action_display).SetCursorMovement(graphics.GRT_cursor_static) gc.WriteWithPayloadToLoop(g.lp, nil) } func (g *graphics_manager) on_response(gc *graphics.GraphicsCommand) (err error) { if gc.ResponseMessage() != "OK" { return fmt.Errorf("Failed to load image with error: %s", gc.ResponseMessage()) } for _, img := range g.images { if img.image_number == gc.ImageNumber() { img.id = gc.ImageId() break } } return } func (g *graphics_manager) finalize() { buf := strings.Builder{} for _, img := range g.images { gc := img.new_graphics_command() gc.SetAction(graphics.GRT_action_delete) gc.SetDelete(utils.IfElse(img.id > 0, graphics.GRT_free_by_id, graphics.GRT_free_by_number)) gc.WriteWithPayloadTo(&buf, nil) } g.lp.QueueWriteString(buf.String()) } kitty-0.41.1/kittens/choose_fonts/index_feature.go0000664000175000017510000001037714773370543021642 0ustar nileshnileshpackage choose_fonts import ( "fmt" "strconv" "strings" "kitty/tools/tui" "kitty/tools/tui/loop" "kitty/tools/tui/readline" "kitty/tools/utils" ) var _ = fmt.Print type if_panel struct { handler *handler rl *readline.Readline family, which, feat_tag string settings faces_settings feature_data FeatureData current_val uint } func (self *if_panel) render_lines(start_y int, lines ...string) (y int) { sz, _ := self.handler.lp.ScreenSize() _, y, str := self.handler.render_lines.InRectangle(lines, 0, start_y, int(sz.WidthCells), int(sz.HeightCells)-y, &self.handler.mouse_state, self.on_click) self.handler.lp.QueueWriteString(str) return } func (self *if_panel) draw_screen() (err error) { lp := self.handler.lp feat_name := utils.IfElse(self.feature_data.Name == "", self.feat_tag, self.feature_data.Name) lp.QueueWriteString(self.handler.format_title("Edit "+feat_name, 0)) lines := []string{ fmt.Sprintf("Enter a value for the '%s' feature of the %s font. Values are non-negative integers. Leaving it blank will cause the feature value to be not set, i.e. take its default value.", feat_name, self.family), } if self.feature_data.Tooltip != "" { lines = append(lines, "") lines = append(lines, self.feature_data.Tooltip) } if len(self.feature_data.Params) > 0 { lines = append(lines, "") lines = append(lines, "You can also click on any of the feature names below to choose the corresponding value.") } else { lines = append(lines, "") lines = append(lines, "Consult the documentation for this font to find out what values are valid for this feature.") } lines = append(lines, "") cursor_y := self.render_lines(2, lines...) if len(self.feature_data.Params) > 0 { lp.MoveCursorTo(1, cursor_y+3) num := 1 strings.Join(utils.Map(func(x string) string { ans := tui.InternalHyperlink(x, fmt.Sprintf("fval:%d", num)) num++ return ans }, self.feature_data.Params), ", ") } lp.MoveCursorTo(1, cursor_y+1) lp.ClearToEndOfLine() self.rl.RedrawNonAtomic() lp.SetCursorVisible(true) return } func (self *if_panel) initialize(h *handler) (err error) { self.handler = h self.rl = readline.New(h.lp, readline.RlInit{DontMarkPrompts: true, Prompt: "Value: "}) return } func (self *if_panel) on_wakeup() error { return self.handler.draw_screen() } func (self *if_panel) on_click(id string) (err error) { scheme, val, _ := strings.Cut(id, ":") if scheme != "fval" { return } v, _ := strconv.ParseUint(val, 10, 64) if err = self.handler.face_pane.change_feature_value(self.feat_tag, uint(v), false); err != nil { return err } self.handler.current_pane = &self.handler.face_pane return self.handler.draw_screen() } func (self *if_panel) on_key_event(event *loop.KeyEvent) (err error) { if event.MatchesPressOrRepeat("esc") { event.Handled = true self.handler.current_pane = &self.handler.face_pane return self.handler.draw_screen() } if event.MatchesPressOrRepeat("enter") { event.Handled = true text := strings.TrimSpace(self.rl.AllText()) remove := false var val uint64 if text == "" { remove = true } else { val, err = strconv.ParseUint(text, 10, 0) } if err != nil { self.rl.ResetText() self.handler.lp.Beep() } else { if err = self.handler.face_pane.change_feature_value(self.feat_tag, uint(val), remove); err != nil { return err } self.handler.current_pane = &self.handler.face_pane } return self.handler.draw_screen() } if err = self.rl.OnKeyEvent(event); err != nil { if err == readline.ErrAcceptInput { return nil } return err } return self.draw_screen() } func (self *if_panel) on_text(text string, from_key_event bool, in_bracketed_paste bool) (err error) { if err = self.rl.OnText(text, from_key_event, in_bracketed_paste); err != nil { return err } return self.draw_screen() } func (self *if_panel) on_enter(family, which string, settings faces_settings, feat_tag string, fd FeatureData, current_val uint) error { self.family = family self.feat_tag = feat_tag self.settings = settings self.which = which self.handler.current_pane = self self.feature_data = fd self.current_val = current_val self.rl.ResetText() if self.current_val > 0 { self.rl.SetText(strconv.Itoa(int(self.current_val))) } return self.handler.draw_screen() } kitty-0.41.1/kittens/choose_fonts/list.go0000664000175000017510000002121114773370543017760 0ustar nileshnileshpackage choose_fonts import ( "fmt" "strings" "sync" "kitty/tools/tui/loop" "kitty/tools/tui/readline" "kitty/tools/utils" "kitty/tools/utils/style" "kitty/tools/wcswidth" ) var _ = fmt.Print type preview_cache_key struct { family string width, height int } type preview_cache_value struct { path string width, height int } type FontList struct { rl *readline.Readline family_list FamilyList fonts map[string][]ListedFont family_list_updated bool resolved_faces_from_kitty_conf ResolvedFaces handler *handler variable_data_requested_for *utils.Set[string] preview_cache map[preview_cache_key]preview_cache_value preview_cache_mutex sync.Mutex } func (self *FontList) initialize(h *handler) error { self.handler = h self.preview_cache = make(map[preview_cache_key]preview_cache_value) self.rl = readline.New(h.lp, readline.RlInit{DontMarkPrompts: true, Prompt: "Family: "}) self.variable_data_requested_for = utils.NewSet[string](256) return nil } func (self *FontList) draw_search_bar() { lp := self.handler.lp lp.SetCursorVisible(true) lp.SetCursorShape(loop.BAR_CURSOR, true) sz, err := lp.ScreenSize() if err != nil { return } lp.MoveCursorTo(1, int(sz.HeightCells)) lp.ClearToEndOfLine() self.rl.RedrawNonAtomic() } const SEPARATOR = "║" func center_string(x string, width int, filler ...string) string { space := " " if len(filler) > 0 { space = filler[0] } l := wcswidth.Stringwidth(x) spaces := int(float64(width-l) / 2) space = strings.Repeat(space, utils.Max(0, spaces)) return space + x + space } func (self *handler) format_title(title string, start_x int) string { sz, _ := self.lp.ScreenSize() return self.lp.SprintStyled("fg=green bold", center_string(title, int(sz.WidthCells)-start_x)) } func (self *FontList) draw_family_summary(start_x int, sz loop.ScreenSize) (err error) { lp := self.handler.lp family := self.family_list.CurrentFamily() if family == "" || int(sz.WidthCells) < start_x+2 { return nil } lines := []string{self.handler.format_title(family, start_x), ""} width := int(sz.WidthCells) - start_x - 1 add_line := func(x string) { lines = append(lines, style.WrapTextAsLines(x, width, style.WrapOptions{})...) } fonts := self.fonts[family] if len(fonts) == 0 { return fmt.Errorf("The family: %s has no fonts", family) } if has_variable_data_for_font(fonts[0]) { s := styles_in_family(family, fonts) for _, sg := range s.style_groups { styles := lp.SprintStyled(control_name_style, sg.name) + ": " + strings.Join(sg.styles, ", ") add_line(styles) add_line("") } if s.has_variable_faces { add_line(fmt.Sprintf("This font is %s allowing for finer style control", lp.SprintStyled("fg=magenta", "variable"))) } add_line(fmt.Sprintf("Press the %s key to choose this family", lp.SprintStyled("fg=yellow", "Enter"))) } else { lines = append(lines, "Reading font data, please wait…") key := fonts[0].cache_key() if !self.variable_data_requested_for.Has(key) { self.variable_data_requested_for.Add(key) go func() { self.handler.set_worker_error(ensure_variable_data_for_fonts(fonts...)) lp.WakeupMainThread() }() } } y := 0 for _, line := range lines { if y >= int(sz.HeightCells)-1 { break } lp.MoveCursorTo(start_x+1, y+1) lp.QueueWriteString(line) y++ } if self.handler.text_style.Background != "" { return self.draw_preview(start_x, y, sz) } return } func (self *FontList) draw_preview(x, y int, sz loop.ScreenSize) (err error) { width_cells, height_cells := int(sz.WidthCells)-x, int(sz.HeightCells)-y if height_cells < 3 { return } y++ self.handler.lp.MoveCursorTo(x+1, y+1) self.handler.draw_preview_header(x) y++ height_cells -= 2 self.handler.lp.MoveCursorTo(x+1, y+1) key := preview_cache_key{ family: self.family_list.CurrentFamily(), width: int(sz.CellWidth) * width_cells, height: int(sz.CellHeight) * height_cells, } if key.family == "" { return } self.preview_cache_mutex.Lock() defer self.preview_cache_mutex.Unlock() cc := self.preview_cache[key] switch cc.path { case "": self.preview_cache[key] = preview_cache_value{path: "requested"} go func() { var r map[string]RenderedSampleTransmit self.handler.set_worker_error(kitty_font_backend.query("render_family_samples", map[string]any{ "text_style": self.handler.text_style, "font_family": key.family, "width": key.width, "height": key.height, "output_dir": self.handler.temp_dir, }, &r)) self.preview_cache_mutex.Lock() defer self.preview_cache_mutex.Unlock() self.preview_cache[key] = preview_cache_value{path: r["font_family"].Path, width: r["font_family"].Canvas_width, height: r["font_family"].Canvas_height} self.handler.lp.WakeupMainThread() }() return case "requested": return } self.handler.graphics_manager.display_image(0, cc.path, cc.width, cc.height) return } func (self *FontList) on_wakeup() error { if !self.family_list_updated { self.family_list_updated = true self.family_list.UpdateFamilies(utils.StableSortWithKey(utils.Keys(self.fonts), strings.ToLower)) self.family_list.SelectFamily(self.resolved_faces_from_kitty_conf.Font_family.Family) } return self.handler.draw_screen() } func (self *FontList) draw_screen() (err error) { lp := self.handler.lp sz, err := lp.ScreenSize() if err != nil { return err } num_rows := max(0, int(sz.HeightCells)-1) mw := self.family_list.max_width + 1 green_fg, _, _ := strings.Cut(lp.SprintStyled("fg=green", "|"), "|") lines := make([]string, 0, num_rows) for _, l := range self.family_list.Lines(num_rows) { line := l.text if l.is_current { line = strings.ReplaceAll(line, MARK_AFTER, green_fg) line = lp.SprintStyled("fg=green", ">") + lp.SprintStyled("fg=green bold", line) } else { line = " " + line } lines = append(lines, line) } _, _, str := self.handler.render_lines.InRectangle(lines, 0, 0, 0, num_rows, &self.handler.mouse_state, self.on_click) lp.QueueWriteString(str) seps := strings.Repeat(SEPARATOR, num_rows) seps = strings.TrimSpace(seps) _, _, str = self.handler.render_lines.InRectangle(strings.Split(seps, ""), mw+1, 0, 0, num_rows, &self.handler.mouse_state) lp.QueueWriteString(str) if self.family_list.Len() > 0 { if err = self.draw_family_summary(mw+3, sz); err != nil { return err } } self.draw_search_bar() return } func (self *FontList) on_click(id string) error { which, data, found := strings.Cut(id, ":") if !found { return fmt.Errorf("Not a valid click id: %s", id) } switch which { case "family-chosen": if self.handler.state == LISTING_FAMILIES { if self.family_list.Select(data) { self.handler.draw_screen() } else { self.handler.lp.Beep() } } } return nil } func (self *FontList) update_family_search() { text := self.rl.AllText() if self.family_list.UpdateSearch(text) { self.handler.draw_screen() } else { self.draw_search_bar() } } func (self *FontList) next(delta int, allow_wrapping bool) error { if self.family_list.Next(delta, allow_wrapping) { return self.handler.draw_screen() } self.handler.lp.Beep() return nil } func (self *FontList) on_key_event(event *loop.KeyEvent) (err error) { if event.MatchesPressOrRepeat("enter") { event.Handled = true if family := self.family_list.CurrentFamily(); family != "" { return self.handler.faces.on_enter(family) } self.handler.lp.Beep() return } if event.MatchesPressOrRepeat("esc") { event.Handled = true if self.rl.AllText() != "" { self.rl.ResetText() self.update_family_search() self.handler.draw_screen() } else { return fmt.Errorf("canceled by user") } return } ev := event if ev.MatchesPressOrRepeat("down") { ev.Handled = true return self.next(1, true) } if ev.MatchesPressOrRepeat("up") { ev.Handled = true return self.next(-1, true) } if ev.MatchesPressOrRepeat("page_down") { ev.Handled = true sz, err := self.handler.lp.ScreenSize() if err == nil { err = self.next(int(sz.HeightCells)-3, false) } return err } if ev.MatchesPressOrRepeat("page_up") { ev.Handled = true sz, err := self.handler.lp.ScreenSize() if err == nil { err = self.next(3-int(sz.HeightCells), false) } return err } if err = self.rl.OnKeyEvent(event); err != nil { if err == readline.ErrAcceptInput { return nil } return err } if event.Handled { self.update_family_search() } self.draw_search_bar() return } func (self *FontList) on_text(text string, from_key_event bool, in_bracketed_paste bool) (err error) { if err = self.rl.OnText(text, from_key_event, in_bracketed_paste); err != nil { return err } self.update_family_search() return } kitty-0.41.1/kittens/choose_fonts/main.go0000664000175000017510000000531314773370543017736 0ustar nileshnileshpackage choose_fonts import ( "fmt" "os" "kitty/tools/cli" "kitty/tools/tty" "kitty/tools/tui/loop" ) var _ = fmt.Print var debugprintln = tty.DebugPrintln var output_on_exit string func main(opts *Options) (rc int, err error) { if err = kitty_font_backend.start(); err != nil { return 1, err } defer func() { if werr := kitty_font_backend.release(); werr != nil { if err == nil { err = werr } if rc == 0 { rc = 1 } } }() lp, err := loop.New() if err != nil { return 1, err } lp.MouseTrackingMode(loop.FULL_MOUSE_TRACKING) h := &handler{lp: lp, opts: opts} lp.OnInitialize = func() (string, error) { lp.AllowLineWrapping(false) lp.SetWindowTitle(`Choose a font for kitty`) return "", h.initialize() } lp.OnWakeup = h.on_wakeup lp.OnEscapeCode = h.on_escape_code lp.OnFinalize = func() string { h.finalize() lp.SetCursorVisible(true) return `` } lp.OnMouseEvent = h.on_mouse_event lp.OnResize = func(_, _ loop.ScreenSize) error { return h.draw_screen() } lp.OnKeyEvent = h.on_key_event lp.OnText = h.on_text err = lp.Run() if err != nil { return 1, err } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() return 1, nil } if output_on_exit != "" { os.Stdout.WriteString(output_on_exit) } return lp.ExitCode(), nil } type Options struct { Reload_in string Config_file_name string } func EntryPoint(root *cli.Command) { ans := root.AddSubCommand(&cli.Command{ Name: "choose-fonts", ShortDescription: "Choose the fonts used in kitty", Run: func(cmd *cli.Command, args []string) (rc int, err error) { opts := Options{} if err = cmd.GetOptionValues(&opts); err != nil { return 1, err } return main(&opts) }, }) ans.Add(cli.OptionSpec{ Name: "--reload-in", Dest: "Reload_in", Type: "choices", Choices: "parent, all, none", Default: "parent", Help: `By default, this kitten will signal only the parent kitty instance it is running in to reload its config, after making changes. Use this option to instead either not reload the config at all or in all running kitty instances.`, }) ans.Add(cli.OptionSpec{ Name: "--config-file-name", Dest: "Config_file_name", Type: "str", Default: "kitty.conf", Help: `The name or path to the config file to edit. Relative paths are interpreted with respect to the kitty config directory. By default the kitty config file, kitty.conf is edited. This is most useful if you add include fonts.conf to your kitty.conf and then have the kitten operate only on fonts.conf, allowing kitty.conf to remain unchanged.`, }) clone := root.AddClone(ans.Group, ans) clone.Hidden = true clone.Name = "choose_fonts" } kitty-0.41.1/kittens/choose_fonts/main.py0000664000175000017510000000022514773370543017756 0ustar nileshnileshif __name__ == '__main__': import os import sys from kitty.constants import kitten_exe os.execlp(kitten_exe(), 'kitten', *sys.argv) kitty-0.41.1/kittens/choose_fonts/styles.go0000664000175000017510000000577114773370543020345 0ustar nileshnileshpackage choose_fonts import ( "fmt" "kitty/tools/utils" "slices" "strings" ) var _ = fmt.Print type style_group struct { name string ordering int styles []string style_sort_map map[string]string } type family_style_data struct { style_groups []style_group has_variable_faces bool has_style_attribute_data bool } func styles_with_attribute_data(ans *family_style_data, items ...VariableData) { groups := make(map[string]*style_group) seen_map := make(map[string]map[string]string) get := func(key string, ordering int) *style_group { sg := groups[key] seen := seen_map[key] if sg == nil { ans.style_groups = append(ans.style_groups, style_group{name: key, ordering: ordering, styles: make([]string, 0)}) sg = &ans.style_groups[len(ans.style_groups)-1] groups[key] = sg sg.style_sort_map = make(map[string]string) seen = make(map[string]string) seen_map[key] = seen } return sg } has := func(n string, m map[string]string) bool { _, found := m[n] return found } for _, vd := range items { for _, ax := range vd.Design_axes { if ax.Name == "" { continue } sg := get(ax.Name, ax.Ordering) for _, v := range ax.Values { if v.Name != "" && !has(v.Name, sg.style_sort_map) { sort_key := fmt.Sprintf("%09d:%s", int(v.Value*10000), strings.ToLower(v.Name)) sg.style_sort_map[v.Name] = sort_key sg.styles = append(sg.styles, v.Name) } } } for _, ma := range vd.Multi_axis_styles { sg := get("Styles", 0) if ma.Name != "" && !has(ma.Name, sg.style_sort_map) { sg.style_sort_map[ma.Name] = strings.ToLower(ma.Name) sg.styles = append(sg.styles, ma.Name) } } } ans.style_groups = utils.StableSortWithKey(ans.style_groups, func(sg style_group) int { return sg.ordering }) for _, sg := range ans.style_groups { sg.styles = utils.StableSortWithKey(sg.styles, func(s string) string { return sg.style_sort_map[s] }) } } func styles_for_variable_data(vd VariableData) (ans *family_style_data) { ans = &family_style_data{style_groups: make([]style_group, 0)} styles_with_attribute_data(ans, vd) return } func styles_in_family(family string, fonts []ListedFont) (ans *family_style_data) { _ = family ans = &family_style_data{style_groups: make([]style_group, 0)} vds := make([]VariableData, len(fonts)) for i, f := range fonts { vds[i] = variable_data_for(f) } for _, vd := range vds { if len(vd.Design_axes) > 0 { ans.has_style_attribute_data = true } if len(vd.Axes) > 0 { ans.has_variable_faces = true } } if ans.has_style_attribute_data { styles_with_attribute_data(ans, vds...) } else { ans.style_groups = append(ans.style_groups, style_group{name: "Styles", styles: make([]string, 0)}) sg := &ans.style_groups[0] seen := utils.NewSet[string]() for _, f := range fonts { if f.Style != "" && !seen.Has(f.Style) { seen.Add(f.Style) sg.styles = append(sg.styles, f.Style) } } } for _, sg := range ans.style_groups { slices.Sort(sg.styles) } return } kitty-0.41.1/kittens/choose_fonts/types.go0000664000175000017510000002227014773370543020157 0ustar nileshnileshpackage choose_fonts import ( "fmt" "maps" "strconv" "strings" "sync" "kitty/tools/utils" "kitty/tools/utils/shlex" ) var _ = fmt.Print type VariableAxis struct { Minimum float64 `json:"minimum"` Maximum float64 `json:"maximum"` Default float64 `json:"default"` Hidden bool `json:"hidden"` Tag string `json:"tag"` Strid string `json:"strid"` } type NamedStyle struct { Axis_values map[string]float64 `json:"axis_values"` Name string `json:"name"` Postscript_name string `json:"psname"` } type DesignAxisValue struct { Format int `json:"format"` Flags int `json:"flags"` Name string `json:"name"` Value float64 `json:"value"` Minimum float64 `json:"minimum"` Maximum float64 `json:"maximum"` Linked_value float64 `json:"linked_value"` } type DesignAxis struct { Tag string `json:"tag"` Name string `json:"name"` Ordering int `json:"ordering"` Values []DesignAxisValue `json:"values"` } type AxisValue struct { Design_index int `json:"design_index"` Value float64 `json:"value"` } type MultiAxisStyle struct { Flags int `json:"flags"` Name string `json:"name"` Values []AxisValue `json:"values"` } type ListedFont struct { Family string `json:"family"` Style string `json:"style"` Fullname string `json:"full_name"` Postscript_name string `json:"postscript_name"` Is_monospace bool `json:"is_monospace"` Is_variable bool `json:"is_variable"` Descriptor map[string]any `json:"descriptor"` } type VariableData struct { Axes []VariableAxis `json:"axes"` Named_styles []NamedStyle `json:"named_styles"` Variations_postscript_name_prefix string `json:"variations_postscript_name_prefix"` Elided_fallback_name string `json:"elided_fallback_name"` Design_axes []DesignAxis `json:"design_axes"` Multi_axis_styles []MultiAxisStyle `json:"multi_axis_styles"` } type ResolvedFace struct { Family string `json:"family"` Spec string `json:"spec"` Setting string `json:"setting"` } type ResolvedFaces struct { Font_family ResolvedFace `json:"font_family"` Bold_font ResolvedFace `json:"bold_font"` Italic_font ResolvedFace `json:"italic_font"` Bold_italic_font ResolvedFace `json:"bold_italic_font"` } type ListResult struct { Fonts map[string][]ListedFont `json:"fonts"` Resolved_faces ResolvedFaces `json:"resolved_faces"` } type FeatureData struct { Is_index bool `json:"is_index"` Name string `json:"name"` Tooltip string `json:"tooltip"` Sample string `json:"sample"` Params []string `json:"params"` } type RenderedSampleTransmit struct { Path string `json:"path"` Variable_data VariableData `json:"variable_data"` Style string `json:"style"` Psname string `json:"psname"` Spec string `json:"spec"` Features map[string]FeatureData `json:"features"` Applied_features map[string]string `json:"applied_features"` Variable_named_style NamedStyle `json:"variable_named_style"` Variable_axis_map map[string]float64 `json:"variable_axis_map"` Cell_width int `json:"cell_width"` Cell_height int `json:"cell_height"` Canvas_width int `json:"canvas_width"` Canvas_height int `json:"canvas_height"` } func (self RenderedSampleTransmit) default_axis_values() (ans map[string]float64) { ans = make(map[string]float64) for _, ax := range self.Variable_data.Axes { ans[ax.Tag] = ax.Default } return } func (self RenderedSampleTransmit) current_axis_values() (ans map[string]float64) { ans = make(map[string]float64, len(self.Variable_data.Axes)) for _, ax := range self.Variable_data.Axes { ans[ax.Tag] = ax.Default } if self.Variable_named_style.Name != "" { maps.Copy(ans, self.Variable_named_style.Axis_values) } else { maps.Copy(ans, self.Variable_axis_map) } return } var variable_data_cache map[string]VariableData var variable_data_cache_mutex sync.Mutex func (f ListedFont) cache_key() string { key := f.Postscript_name if key == "" { key = "path:" + f.Descriptor["path"].(string) } else { key = "psname:" + key } return key } func ensure_variable_data_for_fonts(fonts ...ListedFont) error { descriptors := make([]map[string]any, 0, len(fonts)) keys := make([]string, 0, len(fonts)) variable_data_cache_mutex.Lock() for _, f := range fonts { key := f.cache_key() if _, found := variable_data_cache[key]; !found { descriptors = append(descriptors, f.Descriptor) keys = append(keys, key) } } variable_data_cache_mutex.Unlock() var data []VariableData if err := kitty_font_backend.query("read_variable_data", map[string]any{"descriptors": descriptors}, &data); err != nil { return err } variable_data_cache_mutex.Lock() for i, key := range keys { variable_data_cache[key] = data[i] } variable_data_cache_mutex.Unlock() return nil } func initialize_variable_data_cache() { variable_data_cache = make(map[string]VariableData) } func _cached_vd(key string) (ans VariableData, found bool) { variable_data_cache_mutex.Lock() defer variable_data_cache_mutex.Unlock() ans, found = variable_data_cache[key] return } func variable_data_for(f ListedFont) VariableData { key := f.cache_key() ans, found := _cached_vd(key) if found { return ans } if err := ensure_variable_data_for_fonts(f); err != nil { panic(err) } ans, found = _cached_vd(key) return ans } func has_variable_data_for_font(font ListedFont) bool { _, found := _cached_vd(font.cache_key()) return found } type ParsedFontFeature struct { tag string val uint is_bool bool } func (self ParsedFontFeature) String() string { if self.is_bool { return utils.IfElse(self.val == 0, "-", "+") + self.tag } return fmt.Sprintf("%s=%d", self.tag, self.val) } type settable_string struct { val string is_set bool } type FontSpec struct { family, style, postscript_name, full_name, system, variable_name settable_string axes map[string]float64 features []*ParsedFontFeature } func (self FontSpec) String() string { if self.system.val != "" { return self.system.val } ans := strings.Builder{} a := func(k string, v settable_string) { if v.is_set { ans.WriteString(fmt.Sprintf(" %s=%s", k, shlex.Quote(v.val))) } } a(`family`, self.family) a(`style`, self.style) a(`postscript_name`, self.postscript_name) a(`full_name`, self.full_name) a(`variable_name`, self.variable_name) for name, val := range self.axes { a(name, settable_string{strconv.FormatFloat(val, 'f', -1, 64), true}) } if len(self.features) > 0 { buf := strings.Builder{} for _, f := range self.features { buf.WriteString(f.String()) buf.WriteString(" ") } a(`features`, settable_string{strings.TrimSpace(buf.String()), true}) } return strings.TrimSpace(ans.String()) } func NewParsedFontFeature(x string, features map[string]FeatureData) (ans ParsedFontFeature, err error) { if x != "" { if x[0] == '+' || x[0] == '-' { return ParsedFontFeature{x[1:], utils.IfElse(x[0] == '+', uint(1), uint(0)), true}, nil } else { tag, val, found := strings.Cut(x, "=") fd, defn_found := features[tag] if defn_found && !fd.Is_index { return ParsedFontFeature{tag, 1, true}, nil } pff := ParsedFontFeature{tag: tag} if found { v, err := strconv.ParseUint(val, 10, 0) if err != nil { return ans, err } pff.val = uint(v) } return pff, nil } } return } func NewFontSpec(spec string, features map[string]FeatureData) (ans FontSpec, err error) { if spec == "" || spec == "auto" { ans.system = settable_string{"auto", true} return } parts, err := shlex.Split(spec) if err != nil { return } if !strings.Contains(parts[0], "=") { ans.system = settable_string{spec, true} return } for _, item := range parts { k, v, found := strings.Cut(item, "=") if !found { return ans, fmt.Errorf(fmt.Sprintf("The font specification %s is invalid as %s does not contain an =", spec, item)) } switch k { case "family": ans.family = settable_string{v, true} case "style": ans.style = settable_string{v, true} case "full_name": ans.full_name = settable_string{v, true} case "postscript_name": ans.postscript_name = settable_string{v, true} case "variable_name": ans.variable_name = settable_string{v, true} case "features": for _, x := range utils.NewSeparatorScanner(v, " ").Split(v) { pff, err := NewParsedFontFeature(x, features) if err != nil { return ans, err } ans.features = append(ans.features, &pff) } default: if ans.axes == nil { ans.axes = make(map[string]float64) } f, err := strconv.ParseFloat(v, 64) if err != nil { return ans, err } ans.axes[k] = f } } return } kitty-0.41.1/kittens/choose_fonts/ui.go0000664000175000017510000001230314773370543017424 0ustar nileshnileshpackage choose_fonts import ( "fmt" "os" "strconv" "sync" "kitty/tools/tui" "kitty/tools/tui/graphics" "kitty/tools/tui/loop" "kitty/tools/utils" ) var _ = fmt.Print type State int const ( SCANNING_FAMILIES State = iota LISTING_FAMILIES CHOOSING_FACES ) type TextStyle struct { Font_sz float64 `json:"font_size"` Dpi_x float64 `json:"dpi_x"` Dpi_y float64 `json:"dpi_y"` Foreground string `json:"foreground"` Background string `json:"background"` } type pane interface { initialize(*handler) error draw_screen() error on_wakeup() error on_key_event(event *loop.KeyEvent) error on_text(text string, from_key_event bool, in_bracketed_paste bool) error on_click(id string) error } type handler struct { opts *Options lp *loop.Loop state State err_mutex sync.Mutex err_in_worker_thread error mouse_state tui.MouseState render_count uint render_lines tui.RenderLines text_style TextStyle graphics_manager graphics_manager temp_dir string listing FontList faces faces face_pane face_panel if_pane if_panel final_pane final_pane panes []pane current_pane pane } func (h *handler) set_worker_error(err error) { h.err_mutex.Lock() defer h.err_mutex.Unlock() h.err_in_worker_thread = err } func (h *handler) get_worker_error() error { h.err_mutex.Lock() defer h.err_mutex.Unlock() return h.err_in_worker_thread } // Events {{{ func (h *handler) initialize() (err error) { h.lp.SetCursorVisible(false) h.lp.OnQueryResponse = h.on_query_response h.lp.QueryTerminal("font_size", "dpi_x", "dpi_y", "foreground", "background") h.panes = []pane{&h.listing, &h.faces, &h.face_pane, &h.if_pane, &h.final_pane} for _, pane := range h.panes { if err = pane.initialize(h); err != nil { return err } } // dont use /tmp as it may be mounted in RAM, Le Sigh if h.temp_dir, err = os.MkdirTemp(utils.CacheDir(), "kitten-choose-fonts-*"); err != nil { return } initialize_variable_data_cache() h.graphics_manager.initialize(h.lp) go func() { var r ListResult h.set_worker_error(kitty_font_backend.query("list_monospaced_fonts", nil, &r)) h.listing.fonts = r.Fonts h.listing.resolved_faces_from_kitty_conf = r.Resolved_faces h.lp.WakeupMainThread() }() h.draw_screen() return } func (h *handler) finalize() { if h.temp_dir != "" { os.RemoveAll(h.temp_dir) h.temp_dir = "" } h.lp.SetCursorVisible(true) h.lp.SetCursorShape(loop.BLOCK_CURSOR, true) h.graphics_manager.finalize() } func (h *handler) on_query_response(key, val string, valid bool) error { if !valid { return fmt.Errorf("Terminal does not support querying the: %s", key) } set_float := func(k, v string, dest *float64) error { if fs, err := strconv.ParseFloat(v, 64); err == nil { *dest = fs } else { return fmt.Errorf("Invalid response from terminal to %s query: %#v", k, v) } return nil } switch key { case "font_size": if err := set_float(key, val, &h.text_style.Font_sz); err != nil { return err } case "dpi_x": if err := set_float(key, val, &h.text_style.Dpi_x); err != nil { return err } case "dpi_y": if err := set_float(key, val, &h.text_style.Dpi_y); err != nil { return err } case "foreground": h.text_style.Foreground = val case "background": h.text_style.Background = val return h.draw_screen() } return nil } func (h *handler) draw_screen() (err error) { h.render_count++ h.lp.StartAtomicUpdate() defer func() { h.mouse_state.UpdateHoveredIds() h.mouse_state.ApplyHoverStyles(h.lp) h.lp.EndAtomicUpdate() }() h.graphics_manager.clear_placements() h.lp.ClearScreenButNotGraphics() h.lp.AllowLineWrapping(false) h.mouse_state.ClearCellRegions() if h.current_pane == nil { h.lp.Println("Scanning system for fonts, please wait...") } else { return h.current_pane.draw_screen() } return } func (h *handler) on_wakeup() (err error) { if err = h.get_worker_error(); err != nil { return } if h.current_pane == nil { h.current_pane = &h.listing } return h.listing.on_wakeup() } func (h *handler) on_mouse_event(event *loop.MouseEvent) (err error) { rc := h.render_count redraw_needed := false if h.mouse_state.UpdateState(event) { redraw_needed = true } if event.Event_type == loop.MOUSE_CLICK && event.Buttons&loop.LEFT_MOUSE_BUTTON != 0 { if err = h.mouse_state.ClickHoveredRegions(); err != nil { return } } if redraw_needed && rc == h.render_count { err = h.draw_screen() } return } func (h *handler) on_key_event(event *loop.KeyEvent) (err error) { if event.MatchesPressOrRepeat("ctrl+c") { event.Handled = true return fmt.Errorf("canceled by user") } if h.current_pane != nil { err = h.current_pane.on_key_event(event) } return } func (h *handler) on_text(text string, from_key_event bool, in_bracketed_paste bool) (err error) { if h.current_pane != nil { err = h.current_pane.on_text(text, from_key_event, in_bracketed_paste) } return } func (h *handler) on_escape_code(etype loop.EscapeCodeType, payload []byte) error { switch etype { case loop.APC: gc := graphics.GraphicsCommandFromAPC(payload) if gc != nil { return h.graphics_manager.on_response(gc) } } return nil } // }}} kitty-0.41.1/kittens/clipboard/0000775000175000017510000000000014773370543015727 5ustar nileshnileshkitty-0.41.1/kittens/clipboard/__init__.py0000664000175000017510000000000014773370543020026 0ustar nileshnileshkitty-0.41.1/kittens/clipboard/legacy.go0000664000175000017510000001343514773370543017530 0ustar nileshnilesh// License: GPLv3 Copyright: 2022, Kovid Goyal, package clipboard import ( "bytes" "encoding/base64" "errors" "fmt" "io" "os" "strings" "kitty/tools/tty" "kitty/tools/tui/loop" "kitty/tools/utils" ) var _ = fmt.Print var _ = fmt.Print func encode_read_from_clipboard(use_primary bool) string { dest := "c" if use_primary { dest = "p" } return fmt.Sprintf("\x1b]52;%s;?\x1b\\", dest) } type base64_streaming_enc struct { output func(string) loop.IdType last_written_id loop.IdType } func (self *base64_streaming_enc) Write(p []byte) (int, error) { if len(p) > 0 { self.last_written_id = self.output(string(p)) } return len(p), nil } var ErrTooMuchPipedData = errors.New("Too much piped data") func read_all_with_max_size(r io.Reader, max_size int) ([]byte, error) { b := make([]byte, 0, utils.Min(8192, max_size)) for { if len(b) == cap(b) { new_size := utils.Min(2*cap(b), max_size) if new_size <= cap(b) { return b, ErrTooMuchPipedData } b = append(make([]byte, 0, new_size), b...) } n, err := r.Read(b[len(b):cap(b)]) b = b[:len(b)+n] if err != nil { if err == io.EOF { err = nil } return b, err } } } func preread_stdin() (data_src io.Reader, tempfile *os.File, err error) { // we pre-read STDIN because otherwise if the output of a command is being piped in // and that command itself transmits on the tty we will break. For example // kitten @ ls | kitten clipboard var stdin_data []byte stdin_data, err = read_all_with_max_size(os.Stdin, 2*1024*1024) if err == nil { os.Stdin.Close() } else if err != ErrTooMuchPipedData { os.Stdin.Close() err = fmt.Errorf("Failed to read from STDIN pipe with error: %w", err) return } if err == ErrTooMuchPipedData { tempfile, err = utils.CreateAnonymousTemp("") if err != nil { return nil, nil, fmt.Errorf("Failed to create a temporary from STDIN pipe with error: %w", err) } tempfile.Write(stdin_data) _, err = io.Copy(tempfile, os.Stdin) os.Stdin.Close() if err != nil { return nil, nil, fmt.Errorf("Failed to copy data from STDIN pipe to temp file with error: %w", err) } tempfile.Seek(0, io.SeekStart) data_src = tempfile } else if stdin_data != nil { data_src = bytes.NewBuffer(stdin_data) } return } func run_plain_text_loop(opts *Options) (err error) { stdin_is_tty := tty.IsTerminal(os.Stdin.Fd()) var data_src io.Reader var tempfile *os.File if !stdin_is_tty && !opts.GetClipboard { // we dont read STDIN when getting clipboard as it makes it hard to use the kitten in contexts where // the user does not control STDIN such as being execed from other programs. data_src, tempfile, err = preread_stdin() if err != nil { return err } if tempfile != nil { defer tempfile.Close() } } lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking, loop.NoInBandResizeNotifications) if err != nil { return } dest := "c" if opts.UsePrimary { dest = "p" } send_to_loop := func(data string) loop.IdType { return lp.QueueWriteString(data) } enc_writer := base64_streaming_enc{output: send_to_loop} enc := base64.NewEncoder(base64.StdEncoding, &enc_writer) transmitting := true after_read_from_stdin := func() { transmitting = false if opts.GetClipboard { lp.QueueWriteString(encode_read_from_clipboard(opts.UsePrimary)) } else if opts.WaitForCompletion { lp.QueueWriteString("\x1bP+q544e\x1b\\") } else { lp.Quit(0) } } buf := make([]byte, 8192) write_one_chunk := func() error { orig := enc_writer.last_written_id for enc_writer.last_written_id == orig { n, err := data_src.Read(buf[:cap(buf)]) if n > 0 { enc.Write(buf[:n]) } if err == nil { continue } if errors.Is(err, io.EOF) { enc.Close() send_to_loop("\x1b\\") after_read_from_stdin() return nil } send_to_loop("\x1b\\") return err } return nil } lp.OnInitialize = func() (string, error) { if data_src != nil { send_to_loop(fmt.Sprintf("\x1b]52;%s;", dest)) return "", write_one_chunk() } after_read_from_stdin() return "", nil } lp.OnWriteComplete = func(id loop.IdType, has_pending_writes bool) error { if id == enc_writer.last_written_id { return write_one_chunk() } return nil } var clipboard_contents []byte lp.OnEscapeCode = func(etype loop.EscapeCodeType, data []byte) (err error) { switch etype { case loop.DCS: if strings.HasPrefix(utils.UnsafeBytesToString(data), "1+r") { lp.Quit(0) } case loop.OSC: q := utils.UnsafeBytesToString(data) if strings.HasPrefix(q, "52;") { parts := strings.SplitN(q, ";", 3) if len(parts) < 3 { lp.Quit(0) return } data, err := base64.StdEncoding.DecodeString(parts[2]) if err != nil { return fmt.Errorf("Invalid base64 encoded data from terminal with error: %w", err) } clipboard_contents = data lp.Quit(0) } } return } esc_count := 0 lp.OnKeyEvent = func(event *loop.KeyEvent) error { if event.MatchesPressOrRepeat("ctrl+c") || event.MatchesPressOrRepeat("esc") { if transmitting { return nil } event.Handled = true esc_count++ if esc_count < 2 { key := "Esc" if event.MatchesPressOrRepeat("ctrl+c") { key = "Ctrl+C" } lp.QueueWriteString(fmt.Sprintf("Waiting for response from terminal, press %s again to abort. This could cause garbage to be spewed to the screen.\r\n", key)) } else { return fmt.Errorf("Aborted by user!") } } return nil } err = lp.Run() if err != nil { return } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() return } if len(clipboard_contents) > 0 { _, err = os.Stdout.Write(clipboard_contents) if err != nil { err = fmt.Errorf("Failed to write to STDOUT with error: %w", err) } } return } kitty-0.41.1/kittens/clipboard/main.go0000664000175000017510000000115114773370543017200 0ustar nileshnilesh// License: GPLv3 Copyright: 2022, Kovid Goyal, package clipboard import ( "os" "kitty/tools/cli" ) func run_mime_loop(opts *Options, args []string) (err error) { cwd, err = os.Getwd() if err != nil { return err } if opts.GetClipboard { return run_get_loop(opts, args) } return run_set_loop(opts, args) } func clipboard_main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) { if len(args) > 0 { return 0, run_mime_loop(opts, args) } return 0, run_plain_text_loop(opts) } func EntryPoint(parent *cli.Command) { create_cmd(parent, clipboard_main) } kitty-0.41.1/kittens/clipboard/main.py0000664000175000017510000000677614773370543017245 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import sys OPTIONS = r''' --get-clipboard -g type=bool-set Output the current contents of the clipboard to STDOUT. Note that by default kitty will prompt for permission to access the clipboard. Can be controlled by :opt:`clipboard_control`. --use-primary -p type=bool-set Use the primary selection rather than the clipboard on systems that support it, such as Linux. --mime -m type=list The mimetype of the specified file. Useful when the auto-detected mimetype is likely to be incorrect or the filename has no extension and therefore no mimetype can be detected. If more than one file is specified, this option should be specified multiple times, once for each specified file. When copying data from the clipboard, you can use wildcards to match MIME types. For example: :code:`--mime 'text/*'` will match any textual MIME type available on the clipboard, usually the first matching MIME type is copied. The special MIME type :code:`.` will return the list of available MIME types currently on the system clipboard. --alias -a type=list Specify aliases for MIME types. Aliased MIME types are considered equivalent. When copying to clipboard both the original and alias are made available on the clipboard. When copying from clipboard if the original is not found, the alias is used, as a fallback. Can be specified multiple times to create multiple aliases. For example: :code:`--alias text/plain=text/x-rst` makes :code:`text/plain` an alias of :code:`text/rst`. Aliases are not used in filter mode. An alias for :code:`text/plain` is automatically created if :code:`text/plain` is not present in the input data, but some other :code:`text/*` MIME is present. --wait-for-completion type=bool-set Wait till the copy to clipboard is complete before exiting. Useful if running the kitten in a dedicated, ephemeral window. Only needed in filter mode. '''.format help_text = '''\ Read or write to the system clipboard. This kitten operates most simply in :italic:`filter mode`. To set the clipboard text, pipe in the new text on :file:`STDIN`. Use the :option:`--get-clipboard` option to instead output the current clipboard text content to :file:`STDOUT`. Note that copying from the clipboard will cause a permission popup, see :opt:`clipboard_control` for details. For more control, specify filename arguments. Then, different MIME types can be copied to/from the clipboard. Some examples: .. code:: sh # Copy an image to the clipboard: kitten clipboard picture.png # Copy an image and some text to the clipboard: kitten clipboard picture.jpg text.txt # Copy text from STDIN and an image to the clipboard: echo hello | kitten clipboard picture.png /dev/stdin # Copy any raster image available on the clipboard to a PNG file: kitten clipboard -g picture.png # Copy an image to a file and text to STDOUT: kitten clipboard -g picture.png /dev/stdout # List the formats available on the system clipboard kitten clipboard -g -m . /dev/stdout ''' usage = '[files to copy to/from]' if __name__ == '__main__': raise SystemExit('This should be run as kitten clipboard') elif __name__ == '__doc__': from kitty.cli import CompletionSpec cd = sys.cli_docs # type: ignore cd['usage'] = usage cd['options'] = OPTIONS cd['help_text'] = help_text cd['short_desc'] = 'Copy/paste with the system clipboard, even over SSH' cd['args_completion'] = CompletionSpec.from_string('type:file mime:* group:Files') kitty-0.41.1/kittens/clipboard/read.go0000664000175000017510000002631014773370543017173 0ustar nileshnilesh// License: GPLv3 Copyright: 2022, Kovid Goyal, package clipboard import ( "bytes" "encoding/base64" "fmt" "image" "io" "os" "path/filepath" "slices" "strings" "sync" "kitty/tools/tty" "kitty/tools/tui/loop" "kitty/tools/utils" "kitty/tools/utils/images" ) var _ = fmt.Print var cwd string const OSC_NUMBER = "5522" type Output struct { arg string ext string arg_is_stream bool mime_type string remote_mime_type string image_needs_conversion bool is_stream bool dest_is_tty bool dest *os.File err error started bool all_data_received bool } func (self *Output) cleanup() { if self.dest != nil { self.dest.Close() if !self.is_stream { os.Remove(self.dest.Name()) } self.dest = nil } } func (self *Output) add_data(data []byte) { if self.err != nil { return } if self.dest == nil { if !self.image_needs_conversion && self.arg_is_stream { self.is_stream = true self.dest = os.Stdout if self.arg == "/dev/stderr" { self.dest = os.Stderr } self.dest_is_tty = tty.IsTerminal(self.dest.Fd()) } else { d := cwd if strings.ContainsRune(self.arg, os.PathSeparator) && !self.arg_is_stream { d = filepath.Dir(self.arg) } f, err := os.CreateTemp(d, "."+filepath.Base(self.arg)) if err != nil { self.err = err return } self.dest = f } self.started = true } if self.dest_is_tty { data = bytes.ReplaceAll(data, utils.UnsafeStringToBytes("\n"), utils.UnsafeStringToBytes("\r\n")) } _, self.err = self.dest.Write(data) } func (self *Output) write_image(img image.Image) (err error) { var output *os.File if self.arg_is_stream { output = os.Stdout if self.arg == "/dev/stderr" { output = os.Stderr } } else { output, err = os.Create(self.arg) if err != nil { return err } } defer func() { output.Close() if err != nil && !self.arg_is_stream { os.Remove(output.Name()) } }() return images.Encode(output, img, self.mime_type) } func (self *Output) commit() { if self.err != nil { return } if self.image_needs_conversion { self.dest.Seek(0, io.SeekStart) img, _, err := image.Decode(self.dest) self.dest.Close() os.Remove(self.dest.Name()) if err == nil { err = self.write_image(img) } if err != nil { self.err = fmt.Errorf("Failed to encode image data to %s with error: %w", self.mime_type, err) } } else { self.dest.Close() if !self.is_stream { f, err := os.OpenFile(self.arg, os.O_CREATE|os.O_RDONLY, 0666) if err == nil { fi, err := f.Stat() if err == nil { self.dest.Chmod(fi.Mode().Perm()) } f.Close() os.Remove(f.Name()) } self.err = os.Rename(self.dest.Name(), self.arg) if self.err != nil { os.Remove(self.dest.Name()) self.err = fmt.Errorf("Failed to rename temporary file used for downloading to destination: %s with error: %w", self.arg, self.err) } } } self.dest = nil } func (self *Output) assign_mime_type(available_mimes []string, aliases map[string][]string) (err error) { if self.mime_type == "." { self.remote_mime_type = "." return } if slices.Contains(available_mimes, self.mime_type) { self.remote_mime_type = self.mime_type return } if len(aliases[self.mime_type]) > 0 { for _, alias := range aliases[self.mime_type] { if slices.Contains(available_mimes, alias) { self.remote_mime_type = alias return } } } for _, mt := range available_mimes { if matched, _ := filepath.Match(self.mime_type, mt); matched { self.remote_mime_type = mt return } } if images.EncodableImageTypes[self.mime_type] { for _, mt := range available_mimes { if images.DecodableImageTypes[mt] { self.remote_mime_type = mt self.image_needs_conversion = true return } } } if is_textual_mime(self.mime_type) { for _, mt := range available_mimes { if mt == "text/plain" { self.remote_mime_type = mt return } } } return fmt.Errorf("The MIME type %s for %s not available on the clipboard", self.mime_type, self.arg) } func escape_metadata_value(k, x string) (ans string) { if k == "mime" { x = base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(x)) } return x } func unescape_metadata_value(k, x string) (ans string) { if k == "mime" { b, err := base64.StdEncoding.DecodeString(x) if err == nil { x = string(b) } } return x } func encode_bytes(metadata map[string]string, payload []byte) string { ans := strings.Builder{} enc_payload := "" if len(payload) > 0 { enc_payload = base64.StdEncoding.EncodeToString(payload) } ans.Grow(2048 + len(enc_payload)) ans.WriteString("\x1b]") ans.WriteString(OSC_NUMBER) ans.WriteString(";") for k, v := range metadata { if !strings.HasSuffix(ans.String(), ";") { ans.WriteString(":") } ans.WriteString(k) ans.WriteString("=") ans.WriteString(escape_metadata_value(k, v)) } if len(payload) > 0 { ans.WriteString(";") ans.WriteString(enc_payload) } ans.WriteString("\x1b\\") return ans.String() } func encode(metadata map[string]string, payload string) string { return encode_bytes(metadata, utils.UnsafeStringToBytes(payload)) } func error_from_status(status string) error { switch status { case "ENOSYS": return fmt.Errorf("no primary selection available on this system") case "EPERM": return fmt.Errorf("permission denied") case "EBUSY": return fmt.Errorf("a temporary error occurred, try again later.") default: return fmt.Errorf("%s", status) } } func parse_escape_code(etype loop.EscapeCodeType, data []byte) (metadata map[string]string, payload []byte, err error) { if etype != loop.OSC || !bytes.HasPrefix(data, utils.UnsafeStringToBytes(OSC_NUMBER+";")) { return } parts := bytes.SplitN(data, utils.UnsafeStringToBytes(";"), 3) metadata = make(map[string]string) if len(parts) > 2 && len(parts[2]) > 0 { payload, err = base64.StdEncoding.DecodeString(utils.UnsafeBytesToString(parts[2])) if err != nil { err = fmt.Errorf("Received OSC %s packet from terminal with invalid base64 encoded payload", OSC_NUMBER) return } } if len(parts) > 1 { for _, record := range bytes.Split(parts[1], utils.UnsafeStringToBytes(":")) { rp := bytes.SplitN(record, utils.UnsafeStringToBytes("="), 2) v := "" if len(rp) == 2 { v = string(rp[1]) } k := string(rp[0]) metadata[k] = unescape_metadata_value(k, v) } } return } func parse_aliases(raw []string) (map[string][]string, error) { ans := make(map[string][]string, len(raw)) for _, x := range raw { k, v, found := strings.Cut(x, "=") if !found { return nil, fmt.Errorf("%s is not valid MIME alias specification", x) } ans[k] = append(ans[k], v) ans[v] = append(ans[v], k) } return ans, nil } func run_get_loop(opts *Options, args []string) (err error) { lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking, loop.NoInBandResizeNotifications) if err != nil { return err } var available_mimes []string var wg sync.WaitGroup var getting_data_for string requested_mimes := make(map[string]*Output) reading_available_mimes := true outputs := make([]*Output, len(args)) aliases, merr := parse_aliases(opts.Alias) if merr != nil { return merr } for i, arg := range args { outputs[i] = &Output{arg: arg, arg_is_stream: arg == "/dev/stdout" || arg == "/dev/stderr", ext: filepath.Ext(arg)} if len(opts.Mime) > i { outputs[i].mime_type = opts.Mime[i] } else { if outputs[i].arg_is_stream { outputs[i].mime_type = "text/plain" } else { outputs[i].mime_type = utils.GuessMimeType(outputs[i].arg) } } if outputs[i].mime_type == "" { return fmt.Errorf("Could not detect the MIME type for: %s use --mime to specify it manually", arg) } } defer func() { for _, o := range outputs { if o.dest != nil { o.cleanup() } } }() basic_metadata := map[string]string{"type": "read"} if opts.UsePrimary { basic_metadata["loc"] = "primary" } lp.OnInitialize = func() (string, error) { lp.QueueWriteString(encode(basic_metadata, ".")) return "", nil } lp.OnEscapeCode = func(etype loop.EscapeCodeType, data []byte) (err error) { metadata, payload, err := parse_escape_code(etype, data) if err != nil { return err } if metadata == nil { return nil } if reading_available_mimes { switch metadata["status"] { case "DATA": available_mimes = utils.Map(strings.TrimSpace, strings.Split(utils.UnsafeBytesToString(payload), " ")) case "OK": case "DONE": reading_available_mimes = false if len(available_mimes) == 0 { return fmt.Errorf("The clipboard is empty") } for _, o := range outputs { err = o.assign_mime_type(available_mimes, aliases) if err != nil { return err } if o.remote_mime_type == "." { o.started = true o.add_data(utils.UnsafeStringToBytes(strings.Join(available_mimes, "\n"))) o.all_data_received = true } else { requested_mimes[o.remote_mime_type] = o } } if len(requested_mimes) > 0 { lp.QueueWriteString(encode(basic_metadata, strings.Join(utils.Keys(requested_mimes), " "))) } else { lp.Quit(0) } default: return fmt.Errorf("Failed to read list of available data types in the clipboard with error: %w", error_from_status(metadata["status"])) } } else { switch metadata["status"] { case "DATA": current_mime := metadata["mime"] o := requested_mimes[current_mime] if o != nil { if getting_data_for != current_mime { if prev := requested_mimes[getting_data_for]; prev != nil && !prev.all_data_received { prev.all_data_received = true wg.Add(1) go func() { prev.commit() wg.Done() }() } getting_data_for = current_mime } if !o.all_data_received { o.add_data(payload) } } case "OK": case "DONE": if prev := requested_mimes[getting_data_for]; getting_data_for != "" && prev != nil && !prev.all_data_received { prev.all_data_received = true wg.Add(1) go func() { prev.commit() wg.Done() }() getting_data_for = "" } lp.Quit(0) default: return fmt.Errorf("Failed to read data from the clipboard with error: %w", error_from_status(metadata["status"])) } } return } esc_count := 0 lp.OnKeyEvent = func(event *loop.KeyEvent) error { if event.MatchesPressOrRepeat("ctrl+c") || event.MatchesPressOrRepeat("esc") { event.Handled = true esc_count++ if esc_count < 2 { key := "Esc" if event.MatchesPressOrRepeat("ctrl+c") { key = "Ctrl+C" } lp.QueueWriteString(fmt.Sprintf("Waiting for response from terminal, press %s again to abort. This could cause garbage to be spewed to the screen.\r\n", key)) } else { return fmt.Errorf("Aborted by user!") } } return nil } err = lp.Run() wg.Wait() if err != nil { return } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() return } for _, o := range outputs { if o.err != nil { err = fmt.Errorf("Failed to get %s with error: %w", o.arg, o.err) return } if !o.started { err = fmt.Errorf("No data for %s with MIME type: %s", o.arg, o.mime_type) return } } return } kitty-0.41.1/kittens/clipboard/write.go0000664000175000017510000001320214773370543017406 0ustar nileshnilesh// License: GPLv3 Copyright: 2022, Kovid Goyal, package clipboard import ( "errors" "fmt" "io" "os" "path/filepath" "strings" "kitty/tools/tui/loop" "kitty/tools/utils" ) var _ = fmt.Print type Input struct { src io.Reader arg string ext string is_stream bool mime_type string extra_mime_types []string } func is_textual_mime(x string) bool { return strings.HasPrefix(x, "text/") || utils.KnownTextualMimes[x] } func is_text_plain_mime(x string) bool { return x == "text/plain" } func (self *Input) has_mime_matching(predicate func(string) bool) bool { if predicate(self.mime_type) { return true } for _, i := range self.extra_mime_types { if predicate(i) { return true } } return false } func write_loop(inputs []*Input, opts *Options) (err error) { lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking, loop.NoInBandResizeNotifications) if err != nil { return err } var waiting_for_write loop.IdType var buf [4096]byte aliases, aerr := parse_aliases(opts.Alias) if aerr != nil { return aerr } num_text_mimes := 0 has_text_plain := false for _, i := range inputs { i.extra_mime_types = aliases[i.mime_type] if i.has_mime_matching(is_textual_mime) { num_text_mimes++ if !has_text_plain && i.has_mime_matching(is_text_plain_mime) { has_text_plain = true } } } if num_text_mimes > 0 && !has_text_plain { for _, i := range inputs { if i.has_mime_matching(is_textual_mime) { i.extra_mime_types = append(i.extra_mime_types, "text/plain") break } } } make_metadata := func(ptype, mime string) map[string]string { ans := map[string]string{"type": ptype} if opts.UsePrimary { ans["loc"] = "primary" } if mime != "" { ans["mime"] = mime } return ans } lp.OnInitialize = func() (string, error) { waiting_for_write = lp.QueueWriteString(encode(make_metadata("write", ""), "")) return "", nil } write_chunk := func() error { if len(inputs) == 0 { return nil } i := inputs[0] n, err := i.src.Read(buf[:]) if n > 0 { waiting_for_write = lp.QueueWriteString(encode_bytes(make_metadata("wdata", i.mime_type), buf[:n])) } if err != nil { if errors.Is(err, io.EOF) { if len(i.extra_mime_types) > 0 { lp.QueueWriteString(encode(make_metadata("walias", i.mime_type), strings.Join(i.extra_mime_types, " "))) } inputs = inputs[1:] if len(inputs) == 0 { lp.QueueWriteString(encode(make_metadata("wdata", ""), "")) waiting_for_write = 0 } return lp.OnWriteComplete(waiting_for_write, false) } return fmt.Errorf("Failed to read from %s with error: %w", i.arg, err) } return nil } lp.OnWriteComplete = func(msg_id loop.IdType, has_pending_writes bool) error { if waiting_for_write == msg_id { return write_chunk() } return nil } lp.OnEscapeCode = func(etype loop.EscapeCodeType, data []byte) (err error) { metadata, _, err := parse_escape_code(etype, data) if err != nil { return err } if metadata != nil && metadata["type"] == "write" { switch metadata["status"] { case "DONE": lp.Quit(0) case "EIO": return fmt.Errorf("Could not write to clipboard an I/O error occurred while the terminal was processing the data") case "EINVAL": return fmt.Errorf("Could not write to clipboard base64 encoding invalid") case "ENOSYS": return fmt.Errorf("Could not write to primary selection as the system does not support it") case "EPERM": return fmt.Errorf("Could not write to clipboard as permission was denied") case "EBUSY": return fmt.Errorf("Could not write to clipboard, a temporary error occurred, try again later.") default: return fmt.Errorf("Could not write to clipboard unknowns status returned from terminal: %#v", metadata["status"]) } } return } esc_count := 0 lp.OnKeyEvent = func(event *loop.KeyEvent) error { if event.MatchesPressOrRepeat("ctrl+c") || event.MatchesPressOrRepeat("esc") { event.Handled = true esc_count++ if esc_count < 2 { key := "Esc" if event.MatchesPressOrRepeat("ctrl+c") { key = "Ctrl+C" } lp.QueueWriteString(fmt.Sprintf("Waiting for response from terminal, press %s again to abort. This could cause garbage to be spewed to the screen.\r\n", key)) } else { return fmt.Errorf("Aborted by user!") } } return nil } err = lp.Run() if err != nil { return } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() return } return } func run_set_loop(opts *Options, args []string) (err error) { inputs := make([]*Input, len(args)) to_process := make([]*Input, len(args)) defer func() { for _, i := range inputs { if i != nil && i.src != nil { rc, ok := i.src.(io.Closer) if ok { rc.Close() } } } }() for i, arg := range args { if arg == "/dev/stdin" { f, _, err := preread_stdin() if err != nil { return err } inputs[i] = &Input{arg: arg, src: f, is_stream: true} } else { f, err := os.Open(arg) if err != nil { return fmt.Errorf("Failed to open %s with error: %w", arg, err) } inputs[i] = &Input{arg: arg, src: f, ext: filepath.Ext(arg)} } if i < len(opts.Mime) { inputs[i].mime_type = opts.Mime[i] } else if inputs[i].is_stream { inputs[i].mime_type = "text/plain" } else if inputs[i].ext != "" { inputs[i].mime_type = utils.GuessMimeType(inputs[i].arg) } if inputs[i].mime_type == "" { return fmt.Errorf("Could not guess MIME type for %s use the --mime option to specify a MIME type", arg) } to_process[i] = inputs[i] } return write_loop(to_process, opts) } kitty-0.41.1/kittens/diff/0000775000175000017510000000000014773370543014700 5ustar nileshnileshkitty-0.41.1/kittens/diff/__init__.py0000664000175000017510000000023014773370543017004 0ustar nileshnileshdef syntax_aliases(x: str) -> dict[str, str]: ans = {} for x in x.split(): k, _, v = x.partition(':') ans[k] = v return ans kitty-0.41.1/kittens/diff/collect.go0000664000175000017510000002450014773370543016655 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "crypto/md5" "fmt" "io/fs" "os" "path/filepath" "strings" "unicode/utf8" "kitty/tools/utils" ) var _ = fmt.Print var path_name_map, remote_dirs map[string]string var mimetypes_cache, data_cache, hash_cache *utils.LRUCache[string, string] var size_cache *utils.LRUCache[string, int64] var lines_cache *utils.LRUCache[string, []string] var light_highlighted_lines_cache *utils.LRUCache[string, []string] var dark_highlighted_lines_cache *utils.LRUCache[string, []string] var is_text_cache *utils.LRUCache[string, bool] func init_caches() { path_name_map = make(map[string]string, 32) remote_dirs = make(map[string]string, 32) const sz = 4096 size_cache = utils.NewLRUCache[string, int64](sz) mimetypes_cache = utils.NewLRUCache[string, string](sz) data_cache = utils.NewLRUCache[string, string](sz) is_text_cache = utils.NewLRUCache[string, bool](sz) lines_cache = utils.NewLRUCache[string, []string](sz) light_highlighted_lines_cache = utils.NewLRUCache[string, []string](sz) dark_highlighted_lines_cache = utils.NewLRUCache[string, []string](sz) hash_cache = utils.NewLRUCache[string, string](sz) } func add_remote_dir(val string) { x := filepath.Base(val) idx := strings.LastIndex(x, "-") if idx > -1 { x = x[idx+1:] } else { x = "" } remote_dirs[val] = x } func mimetype_for_path(path string) string { return mimetypes_cache.MustGetOrCreate(path, func(path string) string { mt := utils.GuessMimeTypeWithFileSystemAccess(path) if mt == "" { mt = "application/octet-stream" } if utils.KnownTextualMimes[mt] { if _, a, found := strings.Cut(mt, "/"); found { mt = "text/" + a } } return mt }) } func data_for_path(path string) (string, error) { return data_cache.GetOrCreate(path, func(path string) (string, error) { ans, err := os.ReadFile(path) return utils.UnsafeBytesToString(ans), err }) } func size_for_path(path string) (int64, error) { return size_cache.GetOrCreate(path, func(path string) (int64, error) { s, err := os.Stat(path) if err != nil { return 0, err } return s.Size(), nil }) } func is_image(path string) bool { return strings.HasPrefix(mimetype_for_path(path), "image/") } func is_path_text(path string) bool { return is_text_cache.MustGetOrCreate(path, func(path string) bool { if is_image(path) { return false } s1, err := os.Stat(path) if err == nil { s2, err := os.Stat("/dev/null") if err == nil && os.SameFile(s1, s2) { return false } } d, err := data_for_path(path) if err != nil { return false } return utf8.ValidString(d) }) } func hash_for_path(path string) (string, error) { return hash_cache.GetOrCreate(path, func(path string) (string, error) { ans, err := data_for_path(path) if err != nil { return "", err } hash := md5.Sum(utils.UnsafeStringToBytes(ans)) return utils.UnsafeBytesToString(hash[:]), err }) } // Remove all control codes except newlines func sanitize_control_codes(x string) string { pat := utils.MustCompile("[\x00-\x09\x0b-\x1f\x7f\u0080-\u009f]") return pat.ReplaceAllLiteralString(x, "░") } func sanitize_tabs_and_carriage_returns(x string) string { return strings.NewReplacer("\t", conf.Replace_tab_by, "\r", "⏎").Replace(x) } func sanitize(x string) string { return sanitize_control_codes(sanitize_tabs_and_carriage_returns(x)) } func text_to_lines(text string) []string { lines := make([]string, 0, 512) splitlines_like_git(text, false, func(line string) { lines = append(lines, line) }) return lines } func lines_for_path(path string) ([]string, error) { return lines_cache.GetOrCreate(path, func(path string) ([]string, error) { ans, err := data_for_path(path) if err != nil { return nil, err } return text_to_lines(sanitize(ans)), nil }) } func highlighted_lines_for_path(path string) ([]string, error) { plain_lines, err := lines_for_path(path) if err != nil { return nil, err } var ans []string var found bool if use_light_colors { ans, found = light_highlighted_lines_cache.Get(path) } else { ans, found = dark_highlighted_lines_cache.Get(path) } if found && len(ans) == len(plain_lines) { return ans, nil } return plain_lines, nil } type Collection struct { changes, renames, type_map map[string]string adds, removes *utils.Set[string] all_paths []string paths_to_highlight *utils.Set[string] added_count, removed_count int } func (self *Collection) add_change(left, right string) { self.changes[left] = right self.all_paths = append(self.all_paths, left) self.paths_to_highlight.Add(left) self.paths_to_highlight.Add(right) self.type_map[left] = `diff` } func (self *Collection) add_rename(left, right string) { self.renames[left] = right self.all_paths = append(self.all_paths, left) self.type_map[left] = `rename` } func (self *Collection) add_add(right string) { self.adds.Add(right) self.all_paths = append(self.all_paths, right) self.paths_to_highlight.Add(right) self.type_map[right] = `add` if is_path_text(right) { num, _ := lines_for_path(right) self.added_count += len(num) } } func (self *Collection) add_removal(left string) { self.removes.Add(left) self.all_paths = append(self.all_paths, left) self.paths_to_highlight.Add(left) self.type_map[left] = `removal` if is_path_text(left) { num, _ := lines_for_path(left) self.removed_count += len(num) } } func (self *Collection) finalize() { utils.StableSortWithKey(self.all_paths, func(path string) string { return path_name_map[path] }) } func (self *Collection) Len() int { return len(self.all_paths) } func (self *Collection) Items() int { return len(self.all_paths) } func (self *Collection) Apply(f func(path, typ, changed_path string) error) error { for _, path := range self.all_paths { typ := self.type_map[path] changed_path := "" switch typ { case "diff": changed_path = self.changes[path] case "rename": changed_path = self.renames[path] } if err := f(path, typ, changed_path); err != nil { return err } } return nil } func allowed(path string, patterns ...string) bool { name := filepath.Base(path) for _, pat := range patterns { if matched, err := filepath.Match(pat, name); err == nil && matched { return false } } return true } func remote_hostname(path string) (string, string) { for q, val := range remote_dirs { if strings.HasPrefix(path, q) { return q, val } } return "", "" } func resolve_remote_name(path, defval string) string { remote_dir, rh := remote_hostname(path) if remote_dir != "" && rh != "" { r, err := filepath.Rel(remote_dir, path) if err == nil { return rh + ":" + r } } return defval } func walk(base string, patterns []string, names *utils.Set[string], pmap, path_name_map map[string]string) error { base, err := filepath.Abs(base) if err != nil { return err } return filepath.WalkDir(base, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } is_allowed := allowed(path, patterns...) if !is_allowed { if d.IsDir() { return fs.SkipDir } return nil } if d.IsDir() { return nil } path, err = filepath.Abs(path) if err != nil { return err } name, err := filepath.Rel(base, path) if err != nil { return err } if name != "." { path_name_map[path] = name names.Add(name) pmap[name] = path } return nil }) } func (self *Collection) collect_files(left, right string) error { left_names, right_names := utils.NewSet[string](16), utils.NewSet[string](16) left_path_map, right_path_map := make(map[string]string, 16), make(map[string]string, 16) err := walk(left, conf.Ignore_name, left_names, left_path_map, path_name_map) if err != nil { return err } if err = walk(right, conf.Ignore_name, right_names, right_path_map, path_name_map); err != nil { return err } common_names := left_names.Intersect(right_names) changed_names := utils.NewSet[string](common_names.Len()) for n := range common_names.Iterable() { ld, err := data_for_path(left_path_map[n]) var rd string if err == nil { rd, err = data_for_path(right_path_map[n]) } if err != nil { return err } if ld != rd { changed_names.Add(n) self.add_change(left_path_map[n], right_path_map[n]) } else { if lstat, err := os.Stat(left_path_map[n]); err == nil { if rstat, err := os.Stat(right_path_map[n]); err == nil { if lstat.Mode() != rstat.Mode() { // identical files with only a mode change changed_names.Add(n) self.add_change(left_path_map[n], right_path_map[n]) } } } } } removed := left_names.Subtract(common_names) added := right_names.Subtract(common_names) ahash, rhash := make(map[string]string, added.Len()), make(map[string]string, removed.Len()) for a := range added.Iterable() { ahash[a], err = hash_for_path(right_path_map[a]) if err != nil { return err } } for r := range removed.Iterable() { rhash[r], err = hash_for_path(left_path_map[r]) if err != nil { return err } } for name, rh := range rhash { found := false for n, ah := range ahash { if ah == rh { ld, _ := data_for_path(left_path_map[name]) rd, _ := data_for_path(right_path_map[n]) if ld == rd { self.add_rename(left_path_map[name], right_path_map[n]) added.Discard(n) found = true break } } } if !found { self.add_removal(left_path_map[name]) } } for name := range added.Iterable() { self.add_add(right_path_map[name]) } return nil } func create_collection(left, right string) (ans *Collection, err error) { ans = &Collection{ changes: make(map[string]string), renames: make(map[string]string), type_map: make(map[string]string), adds: utils.NewSet[string](32), removes: utils.NewSet[string](32), paths_to_highlight: utils.NewSet[string](32), all_paths: make([]string, 0, 32), } left_stat, err := os.Stat(left) if err != nil { return nil, err } if left_stat.IsDir() { err = ans.collect_files(left, right) if err != nil { return nil, err } } else { pl, err := filepath.Abs(left) if err != nil { return nil, err } pr, err := filepath.Abs(right) if err != nil { return nil, err } path_name_map[pl] = resolve_remote_name(pl, left) path_name_map[pr] = resolve_remote_name(pr, right) ans.add_change(pl, pr) } ans.finalize() return ans, err } kitty-0.41.1/kittens/diff/collect_test.go0000664000175000017510000000256414773370543017722 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "fmt" "os" "path/filepath" "strings" "testing" "kitty/tools/utils" "github.com/google/go-cmp/cmp" ) var _ = fmt.Print func TestDiffCollectWalk(t *testing.T) { tdir := t.TempDir() j := func(x ...string) string { return filepath.Join(append([]string{tdir}, x...)...) } _ = os.MkdirAll(j("a", "b"), 0o700) _ = os.WriteFile(j("a/b/c"), nil, 0o600) _ = os.WriteFile(j("b"), nil, 0o600) _ = os.WriteFile(j("d"), nil, 0o600) _ = os.WriteFile(j("e"), nil, 0o600) _ = os.WriteFile(j("#d#"), nil, 0o600) _ = os.WriteFile(j("e~"), nil, 0o600) _ = os.MkdirAll(j("f"), 0o700) _ = os.WriteFile(j("f/g"), nil, 0o600) _ = os.WriteFile(j("h space"), nil, 0o600) expected_names := utils.NewSetWithItems("d", "e", "f/g", "h space") expected_pmap := map[string]string{ "d": j("d"), "e": j("e"), "f/g": j("f/g"), "h space": j("h space"), } names := utils.NewSet[string](16) pmap := make(map[string]string, 16) if err := walk(tdir, []string{"*~", "#*#", "b"}, names, pmap, map[string]string{}); err != nil { t.Fatal(err) } if diff := cmp.Diff( utils.Sort(expected_names.AsSlice(), strings.Compare), utils.Sort(names.AsSlice(), strings.Compare), ); diff != "" { t.Fatal(diff) } if diff := cmp.Diff(expected_pmap, pmap); diff != "" { t.Fatal(diff) } } kitty-0.41.1/kittens/diff/diff.go0000664000175000017510000001724114773370543016144 0ustar nileshnilesh// Copied from the Go stdlib, with modifications. //https://github.com/golang/go/raw/master/src/internal/diff/diff.go // Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package diff import ( "bytes" "fmt" "sort" "strings" ) // A pair is a pair of values tracked for both the x and y side of a diff. // It is typically a pair of line indexes. type pair struct{ x, y int } // Diff returns an anchored diff of the two texts old and new // in the “unified diff” format. If old and new are identical, // Diff returns a nil slice (no output). // // Unix diff implementations typically look for a diff with // the smallest number of lines inserted and removed, // which can in the worst case take time quadratic in the // number of lines in the texts. As a result, many implementations // either can be made to run for a long time or cut off the search // after a predetermined amount of work. // // In contrast, this implementation looks for a diff with the // smallest number of “unique” lines inserted and removed, // where unique means a line that appears just once in both old and new. // We call this an “anchored diff” because the unique lines anchor // the chosen matching regions. An anchored diff is usually clearer // than a standard diff, because the algorithm does not try to // reuse unrelated blank lines or closing braces. // The algorithm also guarantees to run in O(n log n) time // instead of the standard O(n²) time. // // Some systems call this approach a “patience diff,” named for // the “patience sorting” algorithm, itself named for a solitaire card game. // We avoid that name for two reasons. First, the name has been used // for a few different variants of the algorithm, so it is imprecise. // Second, the name is frequently interpreted as meaning that you have // to wait longer (to be patient) for the diff, meaning that it is a slower algorithm, // when in fact the algorithm is faster than the standard one. func Diff(oldName, old, newName, new string, num_of_context_lines int) []byte { if old == new { return nil } x := lines(old) y := lines(new) // Print diff header. var out bytes.Buffer fmt.Fprintf(&out, "diff %s %s\n", oldName, newName) fmt.Fprintf(&out, "--- %s\n", oldName) fmt.Fprintf(&out, "+++ %s\n", newName) // Loop over matches to consider, // expanding each match to include surrounding lines, // and then printing diff chunks. // To avoid setup/teardown cases outside the loop, // tgs returns a leading {0,0} and trailing {len(x), len(y)} pair // in the sequence of matches. var ( done pair // printed up to x[:done.x] and y[:done.y] chunk pair // start lines of current chunk count pair // number of lines from each side in current chunk ctext []string // lines for current chunk ) for _, m := range tgs(x, y) { if m.x < done.x { // Already handled scanning forward from earlier match. continue } // Expand matching lines as far possible, // establishing that x[start.x:end.x] == y[start.y:end.y]. // Note that on the first (or last) iteration we may (or definitey do) // have an empty match: start.x==end.x and start.y==end.y. start := m for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] { start.x-- start.y-- } end := m for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] { end.x++ end.y++ } // Emit the mismatched lines before start into this chunk. // (No effect on first sentinel iteration, when start = {0,0}.) for _, s := range x[done.x:start.x] { ctext = append(ctext, "-"+s) count.x++ } for _, s := range y[done.y:start.y] { ctext = append(ctext, "+"+s) count.y++ } // If we're not at EOF and have too few common lines, // the chunk includes all the common lines and continues. C := num_of_context_lines // number of context lines if (end.x < len(x) || end.y < len(y)) && (end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) { for _, s := range x[start.x:end.x] { ctext = append(ctext, " "+s) count.x++ count.y++ } done = end continue } // End chunk with common lines for context. if len(ctext) > 0 { n := end.x - start.x if n > C { n = C } for _, s := range x[start.x : start.x+n] { ctext = append(ctext, " "+s) count.x++ count.y++ } done = pair{start.x + n, start.y + n} // Format and emit chunk. // Convert line numbers to 1-indexed. // Special case: empty file shows up as 0,0 not 1,0. if count.x > 0 { chunk.x++ } if count.y > 0 { chunk.y++ } fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y) for _, s := range ctext { out.WriteString(s) } count.x = 0 count.y = 0 ctext = ctext[:0] } // If we reached EOF, we're done. if end.x >= len(x) && end.y >= len(y) { break } // Otherwise start a new chunk. chunk = pair{end.x - C, end.y - C} for _, s := range x[chunk.x:end.x] { ctext = append(ctext, " "+s) count.x++ count.y++ } done = end } return out.Bytes() } // lines returns the lines in the file x, including newlines. // If the file does not end in a newline, one is supplied // along with a warning about the missing newline. func lines(x string) []string { l := strings.SplitAfter(x, "\n") if l[len(l)-1] == "" { l = l[:len(l)-1] } else { // Treat last line as having a message about the missing newline attached, // using the same text as BSD/GNU diff (including the leading backslash). l[len(l)-1] += "\n\\ No newline at end of file\n" } return l } // tgs returns the pairs of indexes of the longest common subsequence // of unique lines in x and y, where a unique line is one that appears // once in x and once in y. // // The longest common subsequence algorithm is as described in // Thomas G. Szymanski, “A Special Case of the Maximal Common // Subsequence Problem,” Princeton TR #170 (January 1975), // available at https://research.swtch.com/tgs170.pdf. func tgs(x, y []string) []pair { // Count the number of times each string appears in a and b. // We only care about 0, 1, many, counted as 0, -1, -2 // for the x side and 0, -4, -8 for the y side. // Using negative numbers now lets us distinguish positive line numbers later. m := make(map[string]int) for _, s := range x { if c := m[s]; c > -2 { m[s] = c - 1 } } for _, s := range y { if c := m[s]; c > -8 { m[s] = c - 4 } } // Now unique strings can be identified by m[s] = -1+-4. // // Gather the indexes of those strings in x and y, building: // xi[i] = increasing indexes of unique strings in x. // yi[i] = increasing indexes of unique strings in y. // inv[i] = index j such that x[xi[i]] = y[yi[j]]. var xi, yi, inv []int for i, s := range y { if m[s] == -1+-4 { m[s] = len(yi) yi = append(yi, i) } } for i, s := range x { if j, ok := m[s]; ok && j >= 0 { xi = append(xi, i) inv = append(inv, j) } } // Apply Algorithm A from Szymanski's paper. // In those terms, A = J = inv and B = [0, n). // We add sentinel pairs {0,0}, and {len(x),len(y)} // to the returned sequence, to help the processing loop. J := inv n := len(xi) T := make([]int, n) L := make([]int, n) for i := range T { T[i] = n + 1 } for i := 0; i < n; i++ { k := sort.Search(n, func(k int) bool { return T[k] >= J[i] }) T[k] = J[i] L[i] = k + 1 } k := 0 for _, v := range L { if k < v { k = v } } seq := make([]pair, 2+k) seq[1+k] = pair{len(x), len(y)} // sentinel at end lastj := n for i := n - 1; i >= 0; i-- { if L[i] == k && J[i] < lastj { seq[k] = pair{xi[i], yi[J[i]]} k-- } } seq[0] = pair{0, 0} // sentinel at start return seq } kitty-0.41.1/kittens/diff/highlight.go0000664000175000017510000001635514773370543017210 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "errors" "fmt" "io" "os" "path/filepath" "strings" "sync" "kitty/tools/utils" "kitty/tools/utils/images" "github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma/v2/lexers" "github.com/alecthomas/chroma/v2/styles" ) var _ = fmt.Print var _ = os.WriteFile var ErrNoLexer = errors.New("No lexer available for this format") var DefaultStyle = sync.OnceValue(func() *chroma.Style { // Default style generated by python style.py default pygments.styles.default.DefaultStyle // with https://raw.githubusercontent.com/alecthomas/chroma/master/_tools/style.py return styles.Register(chroma.MustNewStyle("default", chroma.StyleEntries{ chroma.TextWhitespace: "#bbbbbb", chroma.Comment: "italic #3D7B7B", chroma.CommentPreproc: "noitalic #9C6500", chroma.Keyword: "bold #008000", chroma.KeywordPseudo: "nobold", chroma.KeywordType: "nobold #B00040", chroma.Operator: "#666666", chroma.OperatorWord: "bold #AA22FF", chroma.NameBuiltin: "#008000", chroma.NameFunction: "#0000FF", chroma.NameClass: "bold #0000FF", chroma.NameNamespace: "bold #0000FF", chroma.NameException: "bold #CB3F38", chroma.NameVariable: "#19177C", chroma.NameConstant: "#880000", chroma.NameLabel: "#767600", chroma.NameEntity: "bold #717171", chroma.NameAttribute: "#687822", chroma.NameTag: "bold #008000", chroma.NameDecorator: "#AA22FF", chroma.LiteralString: "#BA2121", chroma.LiteralStringDoc: "italic", chroma.LiteralStringInterpol: "bold #A45A77", chroma.LiteralStringEscape: "bold #AA5D1F", chroma.LiteralStringRegex: "#A45A77", chroma.LiteralStringSymbol: "#19177C", chroma.LiteralStringOther: "#008000", chroma.LiteralNumber: "#666666", chroma.GenericHeading: "bold #000080", chroma.GenericSubheading: "bold #800080", chroma.GenericDeleted: "#A00000", chroma.GenericInserted: "#008400", chroma.GenericError: "#E40000", chroma.GenericEmph: "italic", chroma.GenericStrong: "bold", chroma.GenericPrompt: "bold #000080", chroma.GenericOutput: "#717171", chroma.GenericTraceback: "#04D", chroma.Error: "border:#FF0000", chroma.Background: " bg:#f8f8f8", })) }) // Clear the background colour. func clear_background(style *chroma.Style) *chroma.Style { builder := style.Builder() bg := builder.Get(chroma.Background) bg.Background = 0 bg.NoInherit = true builder.AddEntry(chroma.Background, bg) style, _ = builder.Build() return style } func ansi_formatter(w io.Writer, style *chroma.Style, it chroma.Iterator) (err error) { const SGR_PREFIX = "\033[" const SGR_SUFFIX = "m" style = clear_background(style) before, after := make([]byte, 0, 64), make([]byte, 0, 64) nl := []byte{'\n'} write_sgr := func(which []byte) (err error) { if len(which) > 1 { if _, err = w.Write(utils.UnsafeStringToBytes(SGR_PREFIX)); err != nil { return err } if _, err = w.Write(which[:len(which)-1]); err != nil { return err } if _, err = w.Write(utils.UnsafeStringToBytes(SGR_SUFFIX)); err != nil { return err } } return } write := func(text string) (err error) { if err = write_sgr(before); err != nil { return err } if _, err = w.Write(utils.UnsafeStringToBytes(text)); err != nil { return err } if err = write_sgr(after); err != nil { return err } return } for token := it(); token != chroma.EOF; token = it() { entry := style.Get(token.Type) before, after = before[:0], after[:0] if !entry.IsZero() { if entry.Bold == chroma.Yes { before = append(before, '1', ';') after = append(after, '2', '2', '1', ';') } if entry.Underline == chroma.Yes { before = append(before, '4', ';') after = append(after, '2', '4', ';') } if entry.Italic == chroma.Yes { before = append(before, '3', ';') after = append(after, '2', '3', ';') } if entry.Colour.IsSet() { before = append(before, fmt.Sprintf("38:2:%d:%d:%d;", entry.Colour.Red(), entry.Colour.Green(), entry.Colour.Blue())...) after = append(after, '3', '9', ';') } } // independently format each line in a multiline token, needed for the diff kitten highlighting to work, also // pagers like less reset SGR formatting at line boundaries text := sanitize(token.Value) for text != "" { idx := strings.IndexByte(text, '\n') if idx < 0 { if err = write(text); err != nil { return err } break } if err = write(text[:idx]); err != nil { return err } if _, err = w.Write(nl); err != nil { return err } text = text[idx+1:] } } return nil } func resolved_chroma_style(use_light_colors bool) *chroma.Style { name := utils.IfElse(use_light_colors, conf.Pygments_style, conf.Dark_pygments_style) var style *chroma.Style if name == "default" { style = DefaultStyle() } else { style = styles.Get(name) } if style == nil { if resolved_colors.Background.IsDark() && !resolved_colors.Foreground.IsDark() { style = styles.Get("monokai") if style == nil { style = styles.Get("github-dark") } } else { style = DefaultStyle() } if style == nil { style = styles.Fallback } } return style } var tokens_map map[string][]chroma.Token var mu sync.Mutex func highlight_file(path string, use_light_colors bool) (highlighted string, err error) { defer func() { if r := recover(); r != nil { e, ok := r.(error) if !ok { e = fmt.Errorf("%v", r) } err = e } }() filename_for_detection := filepath.Base(path) ext := filepath.Ext(filename_for_detection) if ext != "" { ext = strings.ToLower(ext[1:]) r := conf.Syntax_aliases[ext] if r != "" { filename_for_detection = "file." + r } } text, err := data_for_path(path) if err != nil { return "", err } mu.Lock() if tokens_map == nil { tokens_map = make(map[string][]chroma.Token) } tokens := tokens_map[path] mu.Unlock() if tokens == nil { lexer := lexers.Match(filename_for_detection) if lexer == nil { lexer = lexers.Analyse(text) } if lexer == nil { return "", fmt.Errorf("Cannot highlight %#v: %w", path, ErrNoLexer) } lexer = chroma.Coalesce(lexer) iterator, err := lexer.Tokenise(nil, text) if err != nil { return "", err } tokens = iterator.Tokens() mu.Lock() tokens_map[path] = tokens mu.Unlock() } formatter := chroma.FormatterFunc(ansi_formatter) w := strings.Builder{} w.Grow(len(text) * 2) err = formatter.Format(&w, resolved_chroma_style(use_light_colors), chroma.Literator(tokens...)) // os.WriteFile(filepath.Base(path+".highlighted"), []byte(w.String()), 0o600) return w.String(), err } func highlight_all(paths []string, light bool) { ctx := images.Context{} ctx.Parallel(0, len(paths), func(nums <-chan int) { for i := range nums { path := paths[i] raw, err := highlight_file(path, light) if err != nil { continue } if light { light_highlighted_lines_cache.Set(path, text_to_lines(raw)) } else { dark_highlighted_lines_cache.Set(path, text_to_lines(raw)) } } }) } kitty-0.41.1/kittens/diff/main.go0000664000175000017510000001127014773370543016154 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "archive/tar" "bytes" "fmt" "io/fs" "os" "os/exec" "path/filepath" "strings" "kitty/kittens/ssh" "kitty/tools/cli" "kitty/tools/config" "kitty/tools/tui/loop" "kitty/tools/utils" ) var _ = fmt.Print func load_config(opts *Options) (ans *Config, err error) { ans = NewConfig() p := config.ConfigParser{LineHandler: ans.Parse} err = p.LoadConfig("diff.conf", opts.Config, opts.Override) if err != nil { return nil, err } ans.KeyboardShortcuts = config.ResolveShortcuts(ans.KeyboardShortcuts) return ans, nil } var conf *Config var opts *Options var lp *loop.Loop func isdir(path string) bool { if s, err := os.Stat(path); err == nil { return s.IsDir() } return false } func exists(path string) bool { _, err := os.Stat(path) return err == nil } func get_ssh_file(hostname, rpath string) (string, error) { tdir, err := os.MkdirTemp("", "*-"+hostname) if err != nil { return "", err } add_remote_dir(tdir) is_abs := strings.HasPrefix(rpath, "/") for strings.HasPrefix(rpath, "/") { rpath = rpath[1:] } cmd := []string{ssh.SSHExe(), hostname, "tar", "--dereference", "--create", "--file", "-"} if is_abs { cmd = append(cmd, "-C", "/") } cmd = append(cmd, rpath) c := exec.Command(cmd[0], cmd[1:]...) c.Stdin, c.Stderr = os.Stdin, os.Stderr stdout, err := c.Output() if err != nil { return "", fmt.Errorf("Failed to ssh into remote host %s to get file %s with error: %w", hostname, rpath, err) } tf := tar.NewReader(bytes.NewReader(stdout)) count, err := utils.ExtractAllFromTar(tf, tdir) if err != nil { return "", fmt.Errorf("Failed to untar data from remote host %s to get file %s with error: %w", hostname, rpath, err) } ans := filepath.Join(tdir, rpath) if count == 1 { if err = filepath.WalkDir(tdir, func(path string, d fs.DirEntry, err error) error { if !d.IsDir() { ans = path return fs.SkipAll } return nil }); err != nil { return "", err } } return ans, nil } func get_remote_file(path string) (string, error) { if strings.HasPrefix(path, "ssh:") { parts := strings.SplitN(path, ":", 3) if len(parts) == 3 { return get_ssh_file(parts[1], parts[2]) } } return path, nil } func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) { opts = opts_ conf, err = load_config(opts) if err != nil { return 1, err } if len(args) != 2 { return 1, fmt.Errorf("You must specify exactly two files/directories to compare") } if err = set_diff_command(conf.Diff_cmd); err != nil { return 1, err } switch conf.Color_scheme { case Color_scheme_light: use_light_colors = true case Color_scheme_dark: use_light_colors = false case Color_scheme_auto: use_light_colors = false } init_caches() defer func() { for tdir := range remote_dirs { os.RemoveAll(tdir) } }() left, err := get_remote_file(args[0]) if err != nil { return 1, err } right, err := get_remote_file(args[1]) if err != nil { return 1, err } if isdir(left) != isdir(right) { return 1, fmt.Errorf("The items to be diffed should both be either directories or files. Comparing a directory to a file is not valid.'") } if !exists(left) { return 1, fmt.Errorf("%s does not exist", left) } if !exists(right) { return 1, fmt.Errorf("%s does not exist", right) } lp, err = loop.New() loop.MouseTrackingMode(lp, loop.BUTTONS_AND_DRAG_MOUSE_TRACKING) if err != nil { return 1, err } lp.ColorSchemeChangeNotifications() h := Handler{left: left, right: right, lp: lp} lp.OnInitialize = func() (string, error) { lp.SetCursorVisible(false) lp.SetCursorShape(loop.BAR_CURSOR, true) lp.AllowLineWrapping(false) lp.SetWindowTitle(fmt.Sprintf("%s vs. %s", left, right)) lp.QueryCapabilities() h.initialize() return "", nil } lp.OnCapabilitiesReceived = func(tc loop.TerminalCapabilities) error { if !tc.KeyboardProtocol { return fmt.Errorf("This terminal does not support the kitty keyboard protocol, or you are running inside a terminal multiplexer that is blocking querying for kitty keyboard protocol support. The diff kitten cannot function without it.") } h.on_capabilities_received(tc) return nil } lp.OnWakeup = h.on_wakeup lp.OnFinalize = func() string { lp.SetCursorVisible(true) lp.SetCursorShape(loop.BLOCK_CURSOR, true) h.finalize() return "" } lp.OnResize = h.on_resize lp.OnKeyEvent = h.on_key_event lp.OnText = h.on_text lp.OnMouseEvent = h.on_mouse_event err = lp.Run() if err != nil { return 1, err } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() return 1, nil } return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } kitty-0.41.1/kittens/diff/main.py0000664000175000017510000002275614773370543016212 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import sys from functools import partial from kitty.cli import CONFIG_HELP, CompletionSpec from kitty.conf.types import Definition from kitty.constants import appname def main(args: list[str]) -> None: raise SystemExit('Must be run as kitten diff') definition = Definition( '!kittens.diff', ) agr = definition.add_group egr = definition.end_group opt = definition.add_option map = definition.add_map mma = definition.add_mouse_map # diff {{{ agr('diff', 'Diffing') opt('syntax_aliases', 'pyj:py pyi:py recipe:py', ctype='strdict_ _:', option_type='syntax_aliases', long_text=''' File extension aliases for syntax highlight. For example, to syntax highlight :file:`file.xyz` as :file:`file.abc` use a setting of :code:`xyz:abc`. Multiple aliases must be separated by spaces. ''' ) opt('num_context_lines', '3', option_type='positive_int', long_text='The number of lines of context to show around each change.' ) opt('diff_cmd', 'auto', long_text=''' The diff command to use. Must contain the placeholder :code:`_CONTEXT_` which will be replaced by the number of lines of context. A few special values are allowed: :code:`auto` will automatically pick an available diff implementation. :code:`builtin` will use the anchored diff algorithm from the Go standard library. :code:`git` will use the git command to do the diffing. :code:`diff` will use the diff command to do the diffing. ''' ) opt('replace_tab_by', '\\x20\\x20\\x20\\x20', option_type='python_string', long_text='The string to replace tabs with. Default is to use four spaces.' ) opt('+ignore_name', '', ctype='string', add_to_default=False, long_text=''' A glob pattern that is matched against only the filename of files and directories. Matching files and directories are ignored when scanning the filesystem to look for files to diff. Can be specified multiple times to use multiple patterns. For example:: ignore_name .git ignore_name *~ ignore_name *.pyc ''', ) egr() # }}} # colors {{{ agr('colors', 'Colors') opt('color_scheme', 'auto', choices=('auto', 'light', 'dark'), long_text=''' Whether to use the light or dark colors. The default of :code:`auto` means to follow the parent terminal color scheme. Note that the actual colors used for dark schemes are set by the :code:`dark_*` settings below and the non-prefixed settings are used for light colors. ''') opt('pygments_style', 'default', long_text=''' The pygments color scheme to use for syntax highlighting. See :link:`pygments builtin styles ` for a list of schemes. Note that this **does not** change the colors used for diffing, only the colors used for syntax highlighting. To change the general colors use the settings below. This sets the colors used for light color schemes, use :opt:`dark_pygments_style` to change the colors for dark color schemes. ''' ) opt('dark_pygments_style', 'github-dark', long_text=''' The pygments color scheme to use for syntax highlighting with dark colors. See :link:`pygments builtin styles ` for a list of schemes. Note that this **does not** change the colors used for diffing, only the colors used for syntax highlighting. To change the general colors use the settings below. This sets the colors used for dark color schemes, use :opt:`pygments_style` to change the colors for light color schemes.''') opt('foreground', 'black', option_type='to_color', long_text='Basic colors') opt('dark_foreground', '#f8f8f2', option_type='to_color') dark_bg = '#212830' opt('background', 'white', option_type='to_color',) opt('dark_background', dark_bg, option_type='to_color',) opt('title_fg', 'black', option_type='to_color', long_text='Title colors') opt('dark_title_fg', 'white', option_type='to_color') opt('title_bg', 'white', option_type='to_color',) opt('dark_title_bg', dark_bg, option_type='to_color',) opt('margin_bg', '#fafbfc', option_type='to_color', long_text='Margin colors') opt('dark_margin_bg', dark_bg, option_type='to_color') opt('margin_fg', '#aaaaaa', option_type='to_color') opt('dark_margin_fg', '#aaaaaa', option_type='to_color') opt('removed_bg', '#ffeef0', option_type='to_color', long_text='Removed text backgrounds') opt('dark_removed_bg', '#352c33', option_type='to_color') opt('highlight_removed_bg', '#fdb8c0', option_type='to_color') opt('dark_highlight_removed_bg', '#5c3539', option_type='to_color') opt('removed_margin_bg', '#ffdce0', option_type='to_color') opt('dark_removed_margin_bg', '#5c3539', option_type='to_color') opt('added_bg', '#e6ffed', option_type='to_color', long_text='Added text backgrounds') opt('dark_added_bg', '#263834', option_type='to_color') opt('highlight_added_bg', '#acf2bd', option_type='to_color') opt('dark_highlight_added_bg', '#31503d', option_type='to_color') opt('added_margin_bg', '#cdffd8', option_type='to_color') opt('dark_added_margin_bg', '#31503d', option_type='to_color') opt('filler_bg', '#fafbfc', option_type='to_color', long_text='Filler (empty) line background') opt('dark_filler_bg', '#262c36', option_type='to_color') opt('margin_filler_bg', 'none', option_type='to_color_or_none', long_text='Filler (empty) line background in margins, defaults to the filler background') opt('dark_margin_filler_bg', 'none', option_type='to_color_or_none') opt('hunk_margin_bg', '#dbedff', option_type='to_color', long_text='Hunk header colors') opt('dark_hunk_margin_bg', '#0c2d6b', option_type='to_color') opt('hunk_bg', '#f1f8ff', option_type='to_color') opt('dark_hunk_bg', '#253142', option_type='to_color') opt('search_bg', '#444', option_type='to_color', long_text='Highlighting') opt('dark_search_bg', '#2c599c', option_type='to_color') opt('search_fg', 'white', option_type='to_color') opt('dark_search_fg', 'white', option_type='to_color') opt('select_bg', '#b4d5fe', option_type='to_color') opt('dark_select_bg', '#2c599c', option_type='to_color') opt('select_fg', 'black', option_type='to_color_or_none') opt('dark_select_fg', 'white', option_type='to_color_or_none') egr() # }}} # shortcuts {{{ agr('shortcuts', 'Keyboard shortcuts') map('Quit', 'quit q quit', ) map('Quit', 'quit esc quit', ) map('Scroll down', 'scroll_down j scroll_by 1', ) map('Scroll down', 'scroll_down down scroll_by 1', ) map('Scroll up', 'scroll_up k scroll_by -1', ) map('Scroll up', 'scroll_up up scroll_by -1', ) map('Scroll to top', 'scroll_top home scroll_to start', ) map('Scroll to bottom', 'scroll_bottom end scroll_to end', ) map('Scroll to next page', 'scroll_page_down page_down scroll_to next-page', ) map('Scroll to next page', 'scroll_page_down space scroll_to next-page', ) map('Scroll to previous page', 'scroll_page_up page_up scroll_to prev-page', ) map('Scroll to next change', 'next_change n scroll_to next-change', ) map('Scroll to previous change', 'prev_change p scroll_to prev-change', ) map('Scroll to next file', 'next_file shift+j scroll_to next-file', ) map('Scroll to previous file', 'prev_file shift+k scroll_to prev-file', ) map('Show all context', 'all_context a change_context all', ) map('Show default context', 'default_context = change_context default', ) map('Increase context', 'increase_context + change_context 5', ) map('Decrease context', 'decrease_context - change_context -5', ) map('Search forward', 'search_forward / start_search regex forward', ) map('Search backward', 'search_backward ? start_search regex backward', ) map('Scroll to next search match', 'next_match . scroll_to next-match', ) map('Scroll to next search match', 'next_match > scroll_to next-match', ) map('Scroll to previous search match', 'prev_match , scroll_to prev-match', ) map('Scroll to previous search match', 'prev_match < scroll_to prev-match', ) map('Search forward (no regex)', 'search_forward_simple f start_search substring forward', ) map('Search backward (no regex)', 'search_backward_simple b start_search substring backward', ) map('Copy selection to clipboard', 'copy_to_clipboard y copy_to_clipboard') map('Copy selection to clipboard or exit if no selection is present', 'copy_to_clipboard_or_exit ctrl+c copy_to_clipboard_or_exit') egr() # }}} OPTIONS = partial('''\ --context type=int default=-1 Number of lines of context to show between changes. Negative values use the number set in :file:`diff.conf`. --config type=list completion=type:file ext:conf group:"Config files" kwds:none,NONE {config_help} --override -o type=list Override individual configuration options, can be specified multiple times. Syntax: :italic:`name=value`. For example: :italic:`-o background=gray` '''.format, config_help=CONFIG_HELP.format(conf_name='diff', appname=appname)) help_text = 'Show a side-by-side diff of the specified files/directories. You can also use :italic:`ssh:hostname:remote-file-path` to diff remote files.' usage = 'file_or_directory_left file_or_directory_right' if __name__ == '__main__': main(sys.argv) elif __name__ == '__doc__': cd = sys.cli_docs # type: ignore cd['usage'] = usage cd['options'] = OPTIONS cd['help_text'] = help_text cd['short_desc'] = 'Pretty, side-by-side diffing of files and images' cd['args_completion'] = CompletionSpec.from_string('type:file mime:text/* mime:image/* group:"Text and image files"') elif __name__ == '__conf__': sys.options_definition = definition # type: ignore kitty-0.41.1/kittens/diff/mouse.go0000664000175000017510000001423414773370543016363 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "fmt" "strconv" "strings" "sync" "kitty" "kitty/tools/config" "kitty/tools/tty" "kitty/tools/tui" "kitty/tools/tui/loop" "kitty/tools/utils" "kitty/tools/wcswidth" ) var _ = fmt.Print type KittyOpts struct { Wheel_scroll_multiplier int Copy_on_select bool } func read_relevant_kitty_opts() KittyOpts { ans := KittyOpts{Wheel_scroll_multiplier: kitty.KittyConfigDefaults.Wheel_scroll_multiplier} handle_line := func(key, val string) error { switch key { case "wheel_scroll_multiplier": v, err := strconv.Atoi(val) if err == nil { ans.Wheel_scroll_multiplier = v } case "copy_on_select": ans.Copy_on_select = strings.ToLower(val) == "clipboard" } return nil } config.ReadKittyConfig(handle_line) return ans } var RelevantKittyOpts = sync.OnceValue(func() KittyOpts { return read_relevant_kitty_opts() }) func (self *Handler) handle_wheel_event(up bool) { amt := RelevantKittyOpts().Wheel_scroll_multiplier if up { amt *= -1 } _ = self.dispatch_action(`scroll_by`, strconv.Itoa(amt)) } type line_pos struct { min_x, max_x int y ScrollPos } func (self *line_pos) MinX() int { return self.min_x } func (self *line_pos) MaxX() int { return self.max_x } func (self *line_pos) Equal(other tui.LinePos) bool { if o, ok := other.(*line_pos); ok { return self.y == o.y } return false } func (self *line_pos) LessThan(other tui.LinePos) bool { if o, ok := other.(*line_pos); ok { return self.y.Less(o.y) } return false } func (self *Handler) line_pos_from_pos(x int, pos ScrollPos) *line_pos { ans := line_pos{min_x: self.logical_lines.margin_size, y: pos} available_cols := self.logical_lines.columns / 2 if x >= available_cols { ans.min_x += available_cols ans.max_x = utils.Max(ans.min_x, ans.min_x+self.logical_lines.ScreenLineAt(pos).right.wcswidth()-1) } else { ans.max_x = utils.Max(ans.min_x, ans.min_x+self.logical_lines.ScreenLineAt(pos).left.wcswidth()-1) } return &ans } func (self *Handler) start_mouse_selection(ev *loop.MouseEvent) { available_cols := self.logical_lines.columns / 2 if ev.Cell.Y >= self.screen_size.num_lines || ev.Cell.X < self.logical_lines.margin_size || (ev.Cell.X >= available_cols && ev.Cell.X < available_cols+self.logical_lines.margin_size) { return } pos := self.scroll_pos self.logical_lines.IncrementScrollPosBy(&pos, ev.Cell.Y) ll := self.logical_lines.At(pos.logical_line) if ll.line_type == EMPTY_LINE || ll.line_type == IMAGE_LINE { return } self.mouse_selection.StartNewSelection(ev, self.line_pos_from_pos(ev.Cell.X, pos), 0, self.screen_size.num_lines-1, self.screen_size.cell_width, self.screen_size.cell_height) } func (self *Handler) drag_scroll_tick(timer_id loop.IdType) error { return self.mouse_selection.DragScrollTick(timer_id, self.lp, self.drag_scroll_tick, func(amt int, ev *loop.MouseEvent) error { if self.scroll_lines(amt) != 0 { self.do_update_mouse_selection(ev) self.draw_screen() } return nil }) } var debugprintln = tty.DebugPrintln func (self *Handler) update_mouse_selection(ev *loop.MouseEvent) { if !self.mouse_selection.IsActive() { return } if self.mouse_selection.OutOfVerticalBounds(ev) { self.mouse_selection.DragScroll(ev, self.lp, self.drag_scroll_tick) return } self.do_update_mouse_selection(ev) } func (self *Handler) do_update_mouse_selection(ev *loop.MouseEvent) { pos := self.scroll_pos y := ev.Cell.Y y = utils.Max(0, utils.Min(y, self.screen_size.num_lines-1)) self.logical_lines.IncrementScrollPosBy(&pos, y) x := self.mouse_selection.StartLine().MinX() self.mouse_selection.Update(ev, self.line_pos_from_pos(x, pos)) self.draw_screen() } func (self *Handler) clear_mouse_selection() { self.mouse_selection.Clear() } func (self *Handler) text_for_current_mouse_selection() string { if self.mouse_selection.IsEmpty() { return "" } text := make([]byte, 0, 2048) start_pos, end_pos := *self.mouse_selection.StartLine().(*line_pos), *self.mouse_selection.EndLine().(*line_pos) // if start is after end, swap them if end_pos.y.Less(start_pos.y) { start_pos, end_pos = end_pos, start_pos } start, end := start_pos.y, end_pos.y is_left := start_pos.min_x == self.logical_lines.margin_size line_for_pos := func(pos ScrollPos) string { if is_left { return self.logical_lines.ScreenLineAt(pos).left.marked_up_text } return self.logical_lines.ScreenLineAt(pos).right.marked_up_text } for pos, prev_ll_idx := start, start.logical_line; pos.Less(end) || pos == end; { ll := self.logical_lines.At(pos.logical_line) var line string switch ll.line_type { case EMPTY_LINE: case IMAGE_LINE: if pos.screen_line < ll.image_lines_offset { line = line_for_pos(pos) } default: line = line_for_pos(pos) } line = wcswidth.StripEscapeCodes(line) s, e := self.mouse_selection.LineBounds(self.line_pos_from_pos(start_pos.min_x, pos)) s -= start_pos.min_x e -= start_pos.min_x line = wcswidth.TruncateToVisualLength(line, e+1) if s > 0 { prefix := wcswidth.TruncateToVisualLength(line, s) line = line[len(prefix):] } // TODO: look at the original line from the source and handle leading tabs as per it if pos.logical_line > prev_ll_idx { line = "\n" + line } prev_ll_idx = pos.logical_line if line != "" { text = append(text, line...) } if self.logical_lines.IncrementScrollPosBy(&pos, 1) == 0 { break } } return utils.UnsafeBytesToString(text) } func (self *Handler) finish_mouse_selection(ev *loop.MouseEvent) { if !self.mouse_selection.IsActive() { return } self.update_mouse_selection(ev) self.mouse_selection.Finish() text := self.text_for_current_mouse_selection() if text != "" { if RelevantKittyOpts().Copy_on_select { self.lp.CopyTextToClipboard(text) } else { self.lp.CopyTextToPrimarySelection(text) } } } func (self *Handler) add_mouse_selection_to_line(line_pos ScrollPos, y int) string { if self.mouse_selection.IsEmpty() { return "" } selection_sgr := format_as_sgr.selection x := self.mouse_selection.StartLine().MinX() return self.mouse_selection.LineFormatSuffix(self.line_pos_from_pos(x, line_pos), selection_sgr[2:len(selection_sgr)-1], y) } kitty-0.41.1/kittens/diff/patch.go0000664000175000017510000002235714773370543016337 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "bytes" "errors" "fmt" "kitty/tools/utils" "kitty/tools/utils/images" "kitty/tools/utils/shlex" "os/exec" "path/filepath" "strconv" "strings" "sync" ) var _ = fmt.Print const GIT_DIFF = `git diff --no-color --no-ext-diff --exit-code -U_CONTEXT_ --no-index --` const DIFF_DIFF = `diff -p -U _CONTEXT_ --` var diff_cmd []string var GitExe = sync.OnceValue(func() string { return utils.FindExe("git") }) var DiffExe = sync.OnceValue(func() string { return utils.FindExe("diff") }) func find_differ() { if GitExe() != "git" && exec.Command(GitExe(), "--help").Run() == nil { diff_cmd, _ = shlex.Split(GIT_DIFF) } else if DiffExe() != "diff" && exec.Command(DiffExe(), "--help").Run() == nil { diff_cmd, _ = shlex.Split(DIFF_DIFF) } else { diff_cmd = []string{} } } func set_diff_command(q string) error { switch q { case "auto": find_differ() case "builtin", "": diff_cmd = []string{} case "diff": diff_cmd, _ = shlex.Split(DIFF_DIFF) case "git": diff_cmd, _ = shlex.Split(GIT_DIFF) default: c, err := shlex.Split(q) if err != nil { return err } diff_cmd = c } return nil } type Center struct{ offset, left_size, right_size int } type Chunk struct { is_context bool left_start, right_start int left_count, right_count int centers []Center } func (self *Chunk) add_line() { self.right_count++ } func (self *Chunk) remove_line() { self.left_count++ } func (self *Chunk) context_line() { self.left_count++ self.right_count++ } func changed_center(left, right string) (ans Center) { if len(left) > 0 && len(right) > 0 { ll, rl := len(left), len(right) ml := utils.Min(ll, rl) for ; ans.offset < ml && left[ans.offset] == right[ans.offset]; ans.offset++ { } suffix_count := 0 for ; suffix_count < ml && left[ll-1-suffix_count] == right[rl-1-suffix_count]; suffix_count++ { } ans.left_size = ll - suffix_count - ans.offset ans.right_size = rl - suffix_count - ans.offset } return } func (self *Chunk) finalize(left_lines, right_lines []string) { if !self.is_context && self.left_count == self.right_count { for i := 0; i < self.left_count; i++ { self.centers = append(self.centers, changed_center(left_lines[self.left_start+i], right_lines[self.right_start+i])) } } } type Hunk struct { left_start, left_count int right_start, right_count int title string added_count, removed_count int chunks []*Chunk current_chunk *Chunk largest_line_number int } func (self *Hunk) new_chunk(is_context bool) *Chunk { left_start, right_start := self.left_start, self.right_start if len(self.chunks) > 0 { c := self.chunks[len(self.chunks)-1] left_start = c.left_start + c.left_count right_start = c.right_start + c.right_count } return &Chunk{is_context: is_context, left_start: left_start, right_start: right_start} } func (self *Hunk) ensure_diff_chunk() { if self.current_chunk == nil || self.current_chunk.is_context { if self.current_chunk != nil { self.chunks = append(self.chunks, self.current_chunk) } self.current_chunk = self.new_chunk(false) } } func (self *Hunk) ensure_context_chunk() { if self.current_chunk == nil || !self.current_chunk.is_context { if self.current_chunk != nil { self.chunks = append(self.chunks, self.current_chunk) } self.current_chunk = self.new_chunk(true) } } func (self *Hunk) add_line() { self.ensure_diff_chunk() self.current_chunk.add_line() self.added_count++ } func (self *Hunk) remove_line() { self.ensure_diff_chunk() self.current_chunk.remove_line() self.removed_count++ } func (self *Hunk) context_line() { self.ensure_context_chunk() self.current_chunk.context_line() } func (self *Hunk) finalize(left_lines, right_lines []string) error { if self.current_chunk != nil { self.chunks = append(self.chunks, self.current_chunk) } // Sanity check c := self.chunks[len(self.chunks)-1] if c.left_start+c.left_count != self.left_start+self.left_count { return fmt.Errorf("Left side line mismatch %d != %d", c.left_start+c.left_count, self.left_start+self.left_count) } if c.right_start+c.right_count != self.right_start+self.right_count { return fmt.Errorf("Right side line mismatch %d != %d", c.right_start+c.right_count, self.right_start+self.right_count) } for _, c := range self.chunks { c.finalize(left_lines, right_lines) } return nil } type Patch struct { all_hunks []*Hunk largest_line_number, added_count, removed_count int } func (self *Patch) Len() int { return len(self.all_hunks) } func splitlines_like_git(raw string, strip_trailing_lines bool, process_line func(string)) { sz := len(raw) if strip_trailing_lines { for sz > 0 && (raw[sz-1] == '\n' || raw[sz-1] == '\r') { sz-- } } start := 0 for i := 0; i < sz; i++ { switch raw[i] { case '\n': process_line(raw[start:i]) start = i + 1 case '\r': process_line(raw[start:i]) start = i + 1 if start < sz && raw[start] == '\n' { i++ start++ } } } if start < sz { process_line(raw[start:sz]) } } func parse_range(x string) (start, count int) { s, c, found := strings.Cut(x, ",") start, _ = strconv.Atoi(s) if start < 0 { start = -start } count = 1 if found { count, _ = strconv.Atoi(c) } return } func parse_hunk_header(line string) *Hunk { parts := strings.SplitN(line, "@@", 3) linespec := strings.TrimSpace(parts[1]) title := "" if len(parts) == 3 { title = strings.TrimSpace(parts[2]) } left, right, _ := strings.Cut(linespec, " ") ls, lc := parse_range(left) rs, rc := parse_range(right) return &Hunk{ title: title, left_start: ls - 1, left_count: lc, right_start: rs - 1, right_count: rc, largest_line_number: utils.Max(ls-1+lc, rs-1+rc), } } func parse_patch(raw string, left_lines, right_lines []string) (ans *Patch, err error) { ans = &Patch{all_hunks: make([]*Hunk, 0, 32)} var current_hunk *Hunk splitlines_like_git(raw, true, func(line string) { if strings.HasPrefix(line, "@@ ") { current_hunk = parse_hunk_header(line) ans.all_hunks = append(ans.all_hunks, current_hunk) } else if current_hunk != nil { var ch byte if len(line) > 0 { ch = line[0] } switch ch { case '+': current_hunk.add_line() case '-': current_hunk.remove_line() case '\\': default: current_hunk.context_line() } } }) for _, h := range ans.all_hunks { err = h.finalize(left_lines, right_lines) if err != nil { return } ans.added_count += h.added_count ans.removed_count += h.removed_count } if len(ans.all_hunks) > 0 { ans.largest_line_number = ans.all_hunks[len(ans.all_hunks)-1].largest_line_number } return } func run_diff(file1, file2 string, num_of_context_lines int) (ok, is_different bool, patch string, err error) { // we resolve symlinks because git diff does not follow symlinks, while diff // does. We want consistent behavior, also for integration with git difftool // we always want symlinks to be followed. path1, err := filepath.EvalSymlinks(file1) if err != nil { return } path2, err := filepath.EvalSymlinks(file2) if err != nil { return } if len(diff_cmd) == 0 { data1, err := data_for_path(path1) if err != nil { return false, false, "", err } data2, err := data_for_path(path2) if err != nil { return false, false, "", err } patchb := Diff(path1, data1, path2, data2, num_of_context_lines) if patchb == nil { return true, false, "", nil } return true, len(patchb) > 0, utils.UnsafeBytesToString(patchb), nil } else { context := strconv.Itoa(num_of_context_lines) cmd := utils.Map(func(x string) string { return strings.ReplaceAll(x, "_CONTEXT_", context) }, diff_cmd) cmd = append(cmd, path1, path2) c := exec.Command(cmd[0], cmd[1:]...) stdout, stderr := bytes.Buffer{}, bytes.Buffer{} c.Stdout, c.Stderr = &stdout, &stderr err = c.Run() if err != nil { var e *exec.ExitError if errors.As(err, &e) && e.ExitCode() == 1 { return true, true, stdout.String(), nil } return false, false, stderr.String(), err } return true, false, stdout.String(), nil } } func do_diff(file1, file2 string, context_count int) (ans *Patch, err error) { ok, _, raw, err := run_diff(file1, file2, context_count) if !ok { return nil, fmt.Errorf("Failed to diff %s vs. %s with errors:\n%s", file1, file2, raw) } if err != nil { return } left_lines, err := lines_for_path(file1) if err != nil { return } right_lines, err := lines_for_path(file2) if err != nil { return } ans, err = parse_patch(raw, left_lines, right_lines) return } type diff_job struct{ file1, file2 string } func diff(jobs []diff_job, context_count int) (ans map[string]*Patch, err error) { ans = make(map[string]*Patch) ctx := images.Context{} type result struct { file1, file2 string err error patch *Patch } results := make(chan result, len(jobs)) ctx.Parallel(0, len(jobs), func(nums <-chan int) { for i := range nums { job := jobs[i] r := result{file1: job.file1, file2: job.file2} r.patch, r.err = do_diff(job.file1, job.file2, context_count) results <- r } }) close(results) for r := range results { if r.err != nil { return nil, r.err } ans[r.file1] = r.patch } return ans, nil } kitty-0.41.1/kittens/diff/render.go0000664000175000017510000006377514773370543016530 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "errors" "fmt" "math" "os" "strconv" "strings" "kitty/tools/tui/graphics" "kitty/tools/tui/loop" "kitty/tools/tui/sgr" "kitty/tools/utils" "kitty/tools/utils/style" "kitty/tools/wcswidth" ) var _ = fmt.Print type LineType int const ( TITLE_LINE LineType = iota CHANGE_LINE CONTEXT_LINE HUNK_TITLE_LINE IMAGE_LINE EMPTY_LINE ) type Reference struct { path string linenum int // 1 based } type HalfScreenLine struct { marked_up_margin_text string marked_up_text string is_filler bool cached_wcswidth int } func (self *HalfScreenLine) wcswidth() int { if self.cached_wcswidth == 0 && self.marked_up_text != "" { self.cached_wcswidth = wcswidth.Stringwidth(self.marked_up_text) } return self.cached_wcswidth } type ScreenLine struct { left, right HalfScreenLine } type LogicalLine struct { line_type LineType screen_lines []*ScreenLine is_full_width bool is_change_start bool left_reference, right_reference Reference left_image, right_image struct { key string count int } image_lines_offset int } func (self *LogicalLine) render_screen_line(n int, lp *loop.Loop, margin_size, columns int) { if n >= len(self.screen_lines) || n < 0 { return } sl := self.screen_lines[n] available_cols := columns/2 - margin_size if self.is_full_width { available_cols = columns - margin_size } left_margin := place_in(sl.left.marked_up_margin_text, margin_size) left_text := place_in(sl.left.marked_up_text, available_cols) if sl.left.is_filler { left_margin = format_as_sgr.margin_filler + left_margin left_text = format_as_sgr.filler + left_text } else { switch self.line_type { case CHANGE_LINE, IMAGE_LINE: left_margin = format_as_sgr.removed_margin + left_margin left_text = format_as_sgr.removed + left_text case HUNK_TITLE_LINE: left_margin = format_as_sgr.hunk_margin + left_margin left_text = format_as_sgr.hunk + left_text case TITLE_LINE: default: left_margin = format_as_sgr.margin + left_margin } } lp.QueueWriteString(left_margin + "\x1b[m") lp.QueueWriteString(left_text) if self.is_full_width { return } right_margin := place_in(sl.right.marked_up_margin_text, margin_size) right_text := place_in(sl.right.marked_up_text, available_cols) if sl.right.is_filler { right_margin = format_as_sgr.margin_filler + right_margin right_text = format_as_sgr.filler + right_text } else { switch self.line_type { case CHANGE_LINE, IMAGE_LINE: right_margin = format_as_sgr.added_margin + right_margin right_text = format_as_sgr.added + right_text case HUNK_TITLE_LINE: right_margin = format_as_sgr.hunk_margin + right_margin right_text = format_as_sgr.hunk + right_text case TITLE_LINE: default: right_margin = format_as_sgr.margin + right_margin } } lp.QueueWriteString("\x1b[m\r") lp.MoveCursorHorizontally(available_cols + margin_size) lp.QueueWriteString(right_margin + "\x1b[m") lp.QueueWriteString(right_text) } func (self *LogicalLine) IncrementScrollPosBy(pos *ScrollPos, amt int) (delta int) { if len(self.screen_lines) > 0 { npos := utils.Max(0, utils.Min(pos.screen_line+amt, len(self.screen_lines)-1)) delta = npos - pos.screen_line pos.screen_line = npos } return } func fit_in(text string, count int) string { truncated := wcswidth.TruncateToVisualLength(text, count) if len(truncated) >= len(text) { return text } if count > 1 { truncated = wcswidth.TruncateToVisualLength(text, count-1) } return truncated + `…` } func fill_in(text string, sz int) string { w := wcswidth.Stringwidth(text) if w < sz { text += strings.Repeat(` `, (sz - w)) } return text } func place_in(text string, sz int) string { return fill_in(fit_in(text, sz), sz) } var format_as_sgr struct { title, margin, added, removed, added_margin, removed_margin, filler, margin_filler, hunk_margin, hunk, selection, search string } var statusline_format, added_count_format, removed_count_format, message_format func(...any) string var use_light_colors bool = false type ResolvedColors struct { Added_bg style.RGBA Added_margin_bg style.RGBA Background style.RGBA Filler_bg style.RGBA Foreground style.RGBA Highlight_added_bg style.RGBA Highlight_removed_bg style.RGBA Hunk_bg style.RGBA Hunk_margin_bg style.RGBA Margin_bg style.RGBA Margin_fg style.RGBA Margin_filler_bg style.NullableColor Removed_bg style.RGBA Removed_margin_bg style.RGBA Search_bg style.RGBA Search_fg style.RGBA Select_bg style.RGBA Select_fg style.NullableColor Title_bg style.RGBA Title_fg style.RGBA } var resolved_colors ResolvedColors func create_formatters() { rc := &resolved_colors if !use_light_colors { rc.Added_bg = conf.Dark_added_bg rc.Added_margin_bg = conf.Dark_added_margin_bg rc.Background = conf.Dark_background rc.Filler_bg = conf.Dark_filler_bg rc.Foreground = conf.Dark_foreground rc.Highlight_added_bg = conf.Dark_highlight_added_bg rc.Highlight_removed_bg = conf.Dark_highlight_removed_bg rc.Hunk_bg = conf.Dark_hunk_bg rc.Hunk_margin_bg = conf.Dark_hunk_margin_bg rc.Margin_bg = conf.Dark_margin_bg rc.Margin_fg = conf.Dark_margin_fg rc.Margin_filler_bg = conf.Dark_margin_filler_bg rc.Removed_bg = conf.Dark_removed_bg rc.Removed_margin_bg = conf.Dark_removed_margin_bg rc.Search_bg = conf.Dark_search_bg rc.Search_fg = conf.Dark_search_fg rc.Select_bg = conf.Dark_select_bg rc.Select_fg = conf.Dark_select_fg rc.Title_bg = conf.Dark_title_bg rc.Title_fg = conf.Dark_title_fg } else { rc.Added_bg = conf.Added_bg rc.Added_margin_bg = conf.Added_margin_bg rc.Background = conf.Background rc.Filler_bg = conf.Filler_bg rc.Foreground = conf.Foreground rc.Highlight_added_bg = conf.Highlight_added_bg rc.Highlight_removed_bg = conf.Highlight_removed_bg rc.Hunk_bg = conf.Hunk_bg rc.Hunk_margin_bg = conf.Hunk_margin_bg rc.Margin_bg = conf.Margin_bg rc.Margin_fg = conf.Margin_fg rc.Margin_filler_bg = conf.Margin_filler_bg rc.Removed_bg = conf.Removed_bg rc.Removed_margin_bg = conf.Removed_margin_bg rc.Search_bg = conf.Search_bg rc.Search_fg = conf.Search_fg rc.Select_bg = conf.Select_bg rc.Select_fg = conf.Select_fg rc.Title_bg = conf.Title_bg rc.Title_fg = conf.Title_fg } ctx := style.Context{AllowEscapeCodes: true} only_open := func(x string) string { ans := ctx.SprintFunc(x)("|") ans, _, _ = strings.Cut(ans, "|") return ans } format_as_sgr.filler = only_open("bg=" + rc.Filler_bg.AsRGBSharp()) if rc.Margin_filler_bg.IsSet { format_as_sgr.margin_filler = only_open("bg=" + rc.Margin_filler_bg.Color.AsRGBSharp()) } else { format_as_sgr.margin_filler = only_open("bg=" + rc.Filler_bg.AsRGBSharp()) } format_as_sgr.added = only_open("bg=" + rc.Added_bg.AsRGBSharp()) format_as_sgr.added_margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Added_margin_bg.AsRGBSharp())) format_as_sgr.removed = only_open("bg=" + rc.Removed_bg.AsRGBSharp()) format_as_sgr.removed_margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Removed_margin_bg.AsRGBSharp())) format_as_sgr.title = only_open(fmt.Sprintf("fg=%s bg=%s bold", rc.Title_fg.AsRGBSharp(), rc.Title_bg.AsRGBSharp())) format_as_sgr.margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Margin_bg.AsRGBSharp())) format_as_sgr.hunk = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Hunk_bg.AsRGBSharp())) format_as_sgr.hunk_margin = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Margin_fg.AsRGBSharp(), rc.Hunk_margin_bg.AsRGBSharp())) format_as_sgr.search = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Search_fg.AsRGBSharp(), rc.Search_bg.AsRGBSharp())) statusline_format = ctx.SprintFunc(fmt.Sprintf("fg=%s", rc.Margin_fg.AsRGBSharp())) added_count_format = ctx.SprintFunc(fmt.Sprintf("fg=%s", rc.Highlight_added_bg.AsRGBSharp())) removed_count_format = ctx.SprintFunc(fmt.Sprintf("fg=%s", rc.Highlight_removed_bg.AsRGBSharp())) message_format = ctx.SprintFunc("bold") if rc.Select_fg.IsSet { format_as_sgr.selection = only_open(fmt.Sprintf("fg=%s bg=%s", rc.Select_fg.Color.AsRGBSharp(), rc.Select_bg.AsRGBSharp())) } else { format_as_sgr.selection = only_open("bg=" + rc.Select_bg.AsRGBSharp()) } } func center_span(ltype string, offset, size int) *sgr.Span { ans := sgr.NewSpan(offset, size) switch ltype { case "add": ans.SetBackground(resolved_colors.Highlight_added_bg).SetClosingBackground(resolved_colors.Added_bg) case "remove": ans.SetBackground(resolved_colors.Highlight_removed_bg).SetClosingBackground(resolved_colors.Removed_bg) } return ans } func title_lines(left_path, right_path string, columns, margin_size int, ans []*LogicalLine) []*LogicalLine { left_name, right_name := path_name_map[left_path], path_name_map[right_path] available_cols := columns/2 - margin_size ll := LogicalLine{ line_type: TITLE_LINE, left_reference: Reference{path: left_path}, right_reference: Reference{path: right_path}, } sl := ScreenLine{} if right_name != "" && right_name != left_name { sl.left.marked_up_text = format_as_sgr.title + fit_in(sanitize(left_name), available_cols) sl.right.marked_up_text = format_as_sgr.title + fit_in(sanitize(right_name), available_cols) } else { sl.left.marked_up_text = format_as_sgr.title + fit_in(sanitize(left_name), columns-margin_size) ll.is_full_width = true } l2 := ll l2.line_type = EMPTY_LINE ll.screen_lines = append(ll.screen_lines, &sl) sl2 := ScreenLine{} sl2.left.marked_up_margin_text = "\x1b[m" + strings.Repeat("━", margin_size) sl2.left.marked_up_text = strings.Repeat("━", columns-margin_size) l2.is_full_width = true l2.screen_lines = append(l2.screen_lines, &sl2) return append(ans, &ll, &l2) } type LogicalLines struct { lines []*LogicalLine margin_size, columns int } func (self *LogicalLines) At(i int) *LogicalLine { return self.lines[i] } func (self *LogicalLines) ScreenLineAt(pos ScrollPos) *ScreenLine { if pos.logical_line < len(self.lines) && pos.logical_line >= 0 { line := self.lines[pos.logical_line] if pos.screen_line < len(line.screen_lines) && pos.screen_line >= 0 { return self.lines[pos.logical_line].screen_lines[pos.screen_line] } } return nil } func (self *LogicalLines) Len() int { return len(self.lines) } func (self *LogicalLines) NumScreenLinesTo(a ScrollPos) (ans int) { return self.Minus(a, ScrollPos{}) } // a - b in terms of number of screen lines between the positions func (self *LogicalLines) Minus(a, b ScrollPos) (delta int) { if a.logical_line == b.logical_line { return a.screen_line - b.screen_line } amt := 1 if a.Less(b) { amt = -1 } else { a, b = b, a } for i := a.logical_line; i < utils.Min(len(self.lines), b.logical_line+1); i++ { line := self.lines[i] switch i { case a.logical_line: delta += utils.Max(0, len(line.screen_lines)-a.screen_line) case b.logical_line: delta += b.screen_line default: delta += len(line.screen_lines) } } return delta * amt } func (self *LogicalLines) IncrementScrollPosBy(pos *ScrollPos, amt int) (delta int) { if pos.logical_line < 0 || pos.logical_line >= len(self.lines) || amt == 0 { return } one := 1 if amt < 0 { one = -1 } for amt != 0 { line := self.lines[pos.logical_line] d := line.IncrementScrollPosBy(pos, amt) if d == 0 { nlp := pos.logical_line + one if nlp < 0 || nlp >= len(self.lines) { break } pos.logical_line = nlp if one > 0 { pos.screen_line = 0 } else { pos.screen_line = len(self.lines[nlp].screen_lines) - 1 } delta += one amt -= one } else { amt -= d delta += d } } return } func human_readable(size int64) string { divisor, suffix := 1, "B" for i, candidate := range []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"} { if size < (1 << ((i + 1) * 10)) { divisor, suffix = (1 << (i * 10)), candidate break } } fs := float64(size) / float64(divisor) s := strconv.FormatFloat(fs, 'f', 2, 64) if idx := strings.Index(s, "."); idx > -1 { s = s[:idx+2] } if strings.HasSuffix(s, ".0") || strings.HasSuffix(s, ".00") { idx := strings.IndexByte(s, '.') s = s[:idx] } return s + " " + suffix } func image_lines(left_path, right_path string, screen_size screen_size, margin_size int, image_size graphics.Size, ans []*LogicalLine) ([]*LogicalLine, error) { columns := screen_size.columns available_cols := columns/2 - margin_size ll, err := first_binary_line(left_path, right_path, columns, margin_size, func(path string) (string, error) { sz, err := size_for_path(path) if err != nil { return "", err } text := fmt.Sprintf("Size: %s", human_readable(sz)) res := image_collection.ResolutionOf(path) if res.Width > -1 { text = fmt.Sprintf("Dimensions: %dx%d %s", res.Width, res.Height, text) } return text, nil }) if err != nil { return nil, err } ll.image_lines_offset = len(ll.screen_lines) do_side := func(path string) []string { if path == "" { return nil } sz, err := image_collection.GetSizeIfAvailable(path, image_size) if err == nil { count := int(math.Ceil(float64(sz.Height) / float64(screen_size.cell_height))) return utils.Repeat("", count) } if errors.Is(err, graphics.ErrNotFound) { return splitlines("Loading image...", available_cols) } return splitlines(fmt.Sprintf("%s", err), available_cols) } left_lines := do_side(left_path) if ll.left_image.count = len(left_lines); ll.left_image.count > 0 { ll.left_image.key = left_path } right_lines := do_side(right_path) if ll.right_image.count = len(right_lines); ll.right_image.count > 0 { ll.right_image.key = right_path } for i := 0; i < utils.Max(len(left_lines), len(right_lines)); i++ { sl := ScreenLine{} if i < len(left_lines) { sl.left.marked_up_text = left_lines[i] } else { sl.left.is_filler = true } if i < len(right_lines) { sl.right.marked_up_text = right_lines[i] } else { sl.right.is_filler = true } ll.screen_lines = append(ll.screen_lines, &sl) } ll.line_type = IMAGE_LINE return append(ans, ll), nil } func first_binary_line(left_path, right_path string, columns, margin_size int, renderer func(path string) (string, error)) (*LogicalLine, error) { available_cols := columns/2 - margin_size ll := LogicalLine{ is_change_start: true, line_type: CHANGE_LINE, left_reference: Reference{path: left_path}, right_reference: Reference{path: right_path}, } if left_path == "" { line, err := renderer(right_path) if err != nil { return nil, err } for _, x := range splitlines(line, available_cols) { sl := ScreenLine{} sl.right.marked_up_text = x sl.left.is_filler = true ll.screen_lines = append(ll.screen_lines, &sl) } } else if right_path == "" { line, err := renderer(left_path) if err != nil { return nil, err } for _, x := range splitlines(line, available_cols) { sl := ScreenLine{} sl.right.is_filler = true sl.left.marked_up_text = x ll.screen_lines = append(ll.screen_lines, &sl) } } else { l, err := renderer(left_path) if err != nil { return nil, err } r, err := renderer(right_path) if err != nil { return nil, err } left_lines, right_lines := splitlines(l, available_cols), splitlines(r, available_cols) for i := 0; i < utils.Max(len(left_lines), len(right_lines)); i++ { sl := ScreenLine{} if i < len(left_lines) { sl.left.marked_up_text = left_lines[i] } if i < len(right_lines) { sl.right.marked_up_text = right_lines[i] } ll.screen_lines = append(ll.screen_lines, &sl) } } return &ll, nil } func binary_lines(left_path, right_path string, columns, margin_size int, ans []*LogicalLine) (ans2 []*LogicalLine, err error) { ll, err := first_binary_line(left_path, right_path, columns, margin_size, func(path string) (string, error) { sz, err := size_for_path(path) if err != nil { return "", err } return fmt.Sprintf("Binary file: %s", human_readable(sz)), nil }) if err != nil { return nil, err } return append(ans, ll), nil } type DiffData struct { left_path, right_path string available_cols, margin_size int left_lines, right_lines []string } func hunk_title(hunk *Hunk) string { return fmt.Sprintf("@@ -%d,%d +%d,%d @@ %s", hunk.left_start+1, hunk.left_count, hunk.right_start+1, hunk.right_count, hunk.title) } func lines_for_context_chunk(data *DiffData, _ int, chunk *Chunk, _ int, ans []*LogicalLine) []*LogicalLine { for i := 0; i < chunk.left_count; i++ { left_line_number := chunk.left_start + i right_line_number := chunk.right_start + i ll := LogicalLine{line_type: CONTEXT_LINE, left_reference: Reference{path: data.left_path, linenum: left_line_number + 1}, right_reference: Reference{path: data.right_path, linenum: right_line_number + 1}, } left_line_number_s := strconv.Itoa(left_line_number + 1) right_line_number_s := strconv.Itoa(right_line_number + 1) for _, text := range splitlines(data.left_lines[left_line_number], data.available_cols) { left_line := HalfScreenLine{marked_up_margin_text: left_line_number_s, marked_up_text: text} right_line := left_line if right_line_number_s != left_line_number_s { right_line = HalfScreenLine{marked_up_margin_text: right_line_number_s, marked_up_text: text} } ll.screen_lines = append(ll.screen_lines, &ScreenLine{left_line, right_line}) left_line_number_s, right_line_number_s = "", "" } ans = append(ans, &ll) } return ans } func splitlines(text string, width int) []string { return style.WrapTextAsLines(text, width, style.WrapOptions{}) } func render_half_line(line_number int, line, ltype string, available_cols int, center Center, ans []HalfScreenLine) []HalfScreenLine { size := center.left_size if ltype != "remove" { size = center.right_size } if size > 0 { span := center_span(ltype, center.offset, size) line = sgr.InsertFormatting(line, span) } lnum := strconv.Itoa(line_number + 1) for _, sc := range splitlines(line, available_cols) { ans = append(ans, HalfScreenLine{marked_up_margin_text: lnum, marked_up_text: sc}) lnum = "" } return ans } func lines_for_diff_chunk(data *DiffData, _ int, chunk *Chunk, _ int, ans []*LogicalLine) []*LogicalLine { common := utils.Min(chunk.left_count, chunk.right_count) ll, rl := make([]HalfScreenLine, 0, 32), make([]HalfScreenLine, 0, 32) for i := 0; i < utils.Max(chunk.left_count, chunk.right_count); i++ { ll, rl = ll[:0], rl[:0] var center Center left_lnum, right_lnum := 0, 0 if i < len(chunk.centers) { center = chunk.centers[i] } if i < chunk.left_count { left_lnum = chunk.left_start + i ll = render_half_line(left_lnum, data.left_lines[left_lnum], "remove", data.available_cols, center, ll) left_lnum++ } if i < chunk.right_count { right_lnum = chunk.right_start + i rl = render_half_line(right_lnum, data.right_lines[right_lnum], "add", data.available_cols, center, rl) right_lnum++ } if i < common { extra := len(ll) - len(rl) if extra < 0 { ll = append(ll, utils.Repeat(HalfScreenLine{}, -extra)...) } else if extra > 0 { rl = append(rl, utils.Repeat(HalfScreenLine{}, extra)...) } } else { if len(ll) > 0 { rl = append(rl, utils.Repeat(HalfScreenLine{is_filler: true}, len(ll))...) } else if len(rl) > 0 { ll = append(ll, utils.Repeat(HalfScreenLine{is_filler: true}, len(rl))...) } } logline := LogicalLine{ line_type: CHANGE_LINE, is_change_start: i == 0, left_reference: Reference{path: data.left_path, linenum: left_lnum}, right_reference: Reference{path: data.left_path, linenum: right_lnum}, } for l := 0; l < len(ll); l++ { logline.screen_lines = append(logline.screen_lines, &ScreenLine{left: ll[l], right: rl[l]}) } ans = append(ans, &logline) } return ans } func lines_for_diff(left_path string, right_path string, patch *Patch, columns, margin_size int, ans []*LogicalLine) (result []*LogicalLine, err error) { ht := LogicalLine{ line_type: HUNK_TITLE_LINE, left_reference: Reference{path: left_path}, right_reference: Reference{path: right_path}, is_full_width: true, } if patch.Len() == 0 { txt := "The files are identical" if lstat, err := os.Stat(left_path); err == nil { if rstat, err := os.Stat(right_path); err == nil { if lstat.Mode() != rstat.Mode() { txt = fmt.Sprintf("Mode changed: %s to %s", lstat.Mode(), rstat.Mode()) } } } for _, line := range splitlines(txt, columns-margin_size) { sl := ScreenLine{} sl.left.marked_up_text = line ht.screen_lines = append(ht.screen_lines, &sl) } ht.line_type = EMPTY_LINE ht.is_full_width = true return append(ans, &ht), nil } available_cols := columns/2 - margin_size data := DiffData{left_path: left_path, right_path: right_path, available_cols: available_cols, margin_size: margin_size} if left_path != "" { data.left_lines, err = highlighted_lines_for_path(left_path) if err != nil { return } } if right_path != "" { data.right_lines, err = highlighted_lines_for_path(right_path) if err != nil { return } } for hunk_num, hunk := range patch.all_hunks { htl := ht htl.left_reference.linenum = hunk.left_start + 1 htl.right_reference.linenum = hunk.right_start + 1 for _, line := range splitlines(hunk_title(hunk), columns-margin_size) { sl := ScreenLine{} sl.left.marked_up_text = line htl.screen_lines = append(htl.screen_lines, &sl) } ans = append(ans, &htl) for cnum, chunk := range hunk.chunks { if chunk.is_context { ans = lines_for_context_chunk(&data, hunk_num, chunk, cnum, ans) } else { ans = lines_for_diff_chunk(&data, hunk_num, chunk, cnum, ans) } } } return ans, nil } func all_lines(path string, columns, margin_size int, is_add bool, ans []*LogicalLine) ([]*LogicalLine, error) { available_cols := columns/2 - margin_size ltype := `add` ll := LogicalLine{line_type: CHANGE_LINE} if !is_add { ltype = `remove` ll.left_reference.path = path } else { ll.right_reference.path = path } lines, err := highlighted_lines_for_path(path) if err != nil { return nil, err } var msg_lines []string if is_add { msg_lines = splitlines(`This file was added`, available_cols) } else { msg_lines = splitlines(`This file was removed`, available_cols) } for line_number, line := range lines { hlines := make([]HalfScreenLine, 0, 8) hlines = render_half_line(line_number, line, ltype, available_cols, Center{}, hlines) l := ll if is_add { l.right_reference.linenum = line_number + 1 } else { l.left_reference.linenum = line_number + 1 } l.is_change_start = line_number == 0 for i, hl := range hlines { sl := ScreenLine{} if is_add { sl.right = hl if len(msg_lines) > 0 { sl.left.marked_up_text = msg_lines[i] sl.left.is_filler = true msg_lines = msg_lines[1:] } else { sl.left.is_filler = true } } else { sl.left = hl if len(msg_lines) > 0 { sl.right.marked_up_text = msg_lines[i] sl.right.is_filler = true msg_lines = msg_lines[1:] } else { sl.right.is_filler = true } } l.screen_lines = append(l.screen_lines, &sl) } ans = append(ans, &l) } return ans, nil } func rename_lines(path, other_path string, columns, margin_size int, ans []*LogicalLine) ([]*LogicalLine, error) { ll := LogicalLine{ left_reference: Reference{path: path}, right_reference: Reference{path: other_path}, line_type: CHANGE_LINE, is_change_start: true, is_full_width: true} for _, line := range splitlines(fmt.Sprintf(`The file %s was renamed to %s`, sanitize(path_name_map[path]), sanitize(path_name_map[other_path])), columns-margin_size) { sl := ScreenLine{} sl.right.marked_up_text = line ll.screen_lines = append(ll.screen_lines, &sl) } return append(ans, &ll), nil } func render(collection *Collection, diff_map map[string]*Patch, screen_size screen_size, largest_line_number int, image_size graphics.Size) (result *LogicalLines, err error) { margin_size := utils.Max(3, len(strconv.Itoa(largest_line_number))+1) ans := make([]*LogicalLine, 0, 1024) columns := screen_size.columns err = collection.Apply(func(path, item_type, changed_path string) error { ans = title_lines(path, changed_path, columns, margin_size, ans) defer func() { ans = append(ans, &LogicalLine{line_type: EMPTY_LINE, screen_lines: []*ScreenLine{{}}}) }() is_binary := !is_path_text(path) if !is_binary && item_type == `diff` && !is_path_text(changed_path) { is_binary = true } is_img := is_binary && is_image(path) || (item_type == `diff` && is_image(changed_path)) _ = is_img switch item_type { case "diff": if is_binary { if is_img { ans, err = image_lines(path, changed_path, screen_size, margin_size, image_size, ans) } else { ans, err = binary_lines(path, changed_path, columns, margin_size, ans) } } else { ans, err = lines_for_diff(path, changed_path, diff_map[path], columns, margin_size, ans) } if err != nil { return err } case "add": if is_binary { if is_img { ans, err = image_lines("", path, screen_size, margin_size, image_size, ans) } else { ans, err = binary_lines("", path, columns, margin_size, ans) } } else { ans, err = all_lines(path, columns, margin_size, true, ans) } if err != nil { return err } case "removal": if is_binary { if is_img { ans, err = image_lines(path, "", screen_size, margin_size, image_size, ans) } else { ans, err = binary_lines(path, "", columns, margin_size, ans) } } else { ans, err = all_lines(path, columns, margin_size, false, ans) } if err != nil { return err } case "rename": ans, err = rename_lines(path, changed_path, columns, margin_size, ans) if err != nil { return err } default: return fmt.Errorf("Unknown change type: %#v", item_type) } return nil }) var ll []*LogicalLine if len(ans) > 1 { ll = ans[:len(ans)-1] } else { // Having am empty list of lines causes panics later on ll = []*LogicalLine{{line_type: EMPTY_LINE, screen_lines: []*ScreenLine{{}}}} } return &LogicalLines{lines: ll, margin_size: margin_size, columns: columns}, err } kitty-0.41.1/kittens/diff/search.go0000664000175000017510000001007014773370543016472 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "fmt" "regexp" "slices" "strings" "sync" "kitty/tools/tui" "kitty/tools/utils" "kitty/tools/utils/images" "kitty/tools/wcswidth" ) var _ = fmt.Print type Search struct { pat *regexp.Regexp matches map[ScrollPos][]Span } func (self *Search) Len() int { return len(self.matches) } func (self *Search) find_matches_in_lines(clean_lines []string, origin int, send_result func(screen_line, offset, size int)) { lengths := utils.Map(func(x string) int { return len(x) }, clean_lines) offsets := make([]int, len(clean_lines)) cell_lengths := utils.Map(wcswidth.Stringwidth, clean_lines) cell_offsets := make([]int, len(clean_lines)) for i := range clean_lines { if i > 0 { offsets[i] = offsets[i-1] + lengths[i-1] cell_offsets[i] = cell_offsets[i-1] + cell_lengths[i-1] } } joined_text := strings.Join(clean_lines, "") matches := self.pat.FindAllStringIndex(joined_text, -1) pos := 0 find_pos := func(start int) int { for i := pos; i < len(clean_lines); i++ { if start < offsets[i]+lengths[i] { pos = i return pos } } return -1 } for _, m := range matches { start, end := m[0], m[1] total_size := end - start if total_size < 1 { continue } start_line := find_pos(start) if start_line > -1 { end_line := find_pos(end) if end_line > -1 { for i := start_line; i <= end_line; i++ { cell_start := 0 if i == start_line { byte_offset := start - offsets[i] cell_start = wcswidth.Stringwidth(clean_lines[i][:byte_offset]) } cell_end := cell_lengths[i] if i == end_line { byte_offset := end - offsets[i] cell_end = wcswidth.Stringwidth(clean_lines[i][:byte_offset]) } send_result(i, origin+cell_start, cell_end-cell_start) } } } } } func (self *Search) find_matches_in_line(line *LogicalLine, margin_size, cols int, send_result func(screen_line, offset, size int)) { half_width := cols / 2 right_offset := half_width + margin_size left_clean_lines, right_clean_lines := make([]string, len(line.screen_lines)), make([]string, len(line.screen_lines)) for i, sl := range line.screen_lines { if line.is_full_width { left_clean_lines[i] = wcswidth.StripEscapeCodes(sl.left.marked_up_text) } else { left_clean_lines[i] = wcswidth.StripEscapeCodes(sl.left.marked_up_text) right_clean_lines[i] = wcswidth.StripEscapeCodes(sl.right.marked_up_text) } } self.find_matches_in_lines(left_clean_lines, margin_size, send_result) self.find_matches_in_lines(right_clean_lines, right_offset, send_result) } func (self *Search) Has(pos ScrollPos) bool { return len(self.matches[pos]) > 0 } type Span struct{ start, end int } func (self *Search) search(logical_lines *LogicalLines) { margin_size := logical_lines.margin_size cols := logical_lines.columns self.matches = make(map[ScrollPos][]Span) ctx := images.Context{} mutex := sync.Mutex{} ctx.Parallel(0, logical_lines.Len(), func(nums <-chan int) { for i := range nums { line := logical_lines.At(i) if line.line_type == EMPTY_LINE || line.line_type == IMAGE_LINE { continue } self.find_matches_in_line(line, margin_size, cols, func(screen_line, offset, size int) { if size > 0 { mutex.Lock() defer mutex.Unlock() pos := ScrollPos{i, screen_line} self.matches[pos] = append(self.matches[pos], Span{offset, offset + size - 1}) } }) } }) for _, spans := range self.matches { slices.SortFunc(spans, func(a, b Span) int { return a.start - b.start }) } } func (self *Search) markup_line(pos ScrollPos, y int) string { spans := self.matches[pos] if spans == nil { return "" } sgr := format_as_sgr.search[2:] sgr = sgr[:len(sgr)-1] ans := make([]byte, 0, 32) for _, span := range spans { ans = append(ans, tui.FormatPartOfLine(sgr, span.start, span.end, y)...) } return utils.UnsafeBytesToString(ans) } func do_search(pat *regexp.Regexp, logical_lines *LogicalLines) *Search { ans := &Search{pat: pat, matches: make(map[ScrollPos][]Span)} ans.search(logical_lines) return ans } kitty-0.41.1/kittens/diff/ui.go0000664000175000017510000005150314773370543015650 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package diff import ( "fmt" "regexp" "strconv" "strings" "kitty/tools/config" "kitty/tools/tui" "kitty/tools/tui/graphics" "kitty/tools/tui/loop" "kitty/tools/tui/readline" "kitty/tools/utils" "kitty/tools/wcswidth" ) var _ = fmt.Print type ResultType int const ( COLLECTION ResultType = iota DIFF HIGHLIGHT IMAGE_LOAD IMAGE_RESIZE ) type ScrollPos struct { logical_line, screen_line int } func (self ScrollPos) Less(other ScrollPos) bool { return self.logical_line < other.logical_line || (self.logical_line == other.logical_line && self.screen_line < other.screen_line) } func (self ScrollPos) Add(other ScrollPos) ScrollPos { return ScrollPos{self.logical_line + other.logical_line, self.screen_line + other.screen_line} } type AsyncResult struct { err error rtype ResultType collection *Collection diff_map map[string]*Patch page_size graphics.Size } var image_collection *graphics.ImageCollection type screen_size struct{ rows, columns, num_lines, cell_width, cell_height int } type Handler struct { async_results chan AsyncResult mouse_selection tui.MouseSelection image_count int shortcut_tracker config.ShortcutTracker left, right string collection *Collection diff_map map[string]*Patch logical_lines *LogicalLines terminal_capabilities_received bool lp *loop.Loop current_context_count, original_context_count int added_count, removed_count int screen_size screen_size scroll_pos, max_scroll_pos ScrollPos restore_position *ScrollPos inputting_command bool statusline_message string rl *readline.Readline current_search *Search current_search_is_regex, current_search_is_backward bool largest_line_number int images_resized_to graphics.Size } func (self *Handler) calculate_statistics() { self.added_count, self.removed_count = self.collection.added_count, self.collection.removed_count self.largest_line_number = 0 for _, patch := range self.diff_map { self.added_count += patch.added_count self.removed_count += patch.removed_count self.largest_line_number = utils.Max(patch.largest_line_number, self.largest_line_number) } } func (self *Handler) update_screen_size(sz loop.ScreenSize) { self.screen_size.rows = int(sz.HeightCells) self.screen_size.columns = int(sz.WidthCells) self.screen_size.num_lines = self.screen_size.rows - 1 self.screen_size.cell_height = int(sz.CellHeight) self.screen_size.cell_width = int(sz.CellWidth) } func (self *Handler) on_escape_code(etype loop.EscapeCodeType, payload []byte) error { switch etype { case loop.APC: gc := graphics.GraphicsCommandFromAPC(payload) if gc != nil { if !image_collection.HandleGraphicsCommand(gc) { self.draw_screen() } } } return nil } func (self *Handler) finalize() { image_collection.Finalize(self.lp) } func set_terminal_colors(lp *loop.Loop) { create_formatters() lp.SetDefaultColor(loop.FOREGROUND, resolved_colors.Foreground) lp.SetDefaultColor(loop.CURSOR, resolved_colors.Foreground) lp.SetDefaultColor(loop.BACKGROUND, resolved_colors.Background) lp.SetDefaultColor(loop.SELECTION_BG, resolved_colors.Select_bg) if resolved_colors.Select_fg.IsSet { lp.SetDefaultColor(loop.SELECTION_FG, resolved_colors.Select_fg.Color) } } func (self *Handler) on_capabilities_received(tc loop.TerminalCapabilities) { var use_dark_colors bool prev := use_light_colors switch conf.Color_scheme { case Color_scheme_auto: use_dark_colors = tc.ColorPreference != loop.LIGHT_COLOR_PREFERENCE case Color_scheme_light: use_dark_colors = false case Color_scheme_dark: use_dark_colors = true } use_light_colors = !use_dark_colors if use_light_colors != prev && (light_highlight_started || dark_highlight_started) { self.highlight_all() } set_terminal_colors(self.lp) self.terminal_capabilities_received = true self.draw_screen() } func (self *Handler) on_color_scheme_change(cp loop.ColorPreference) error { if conf.Color_scheme != Color_scheme_auto { return nil } light := cp == loop.LIGHT_COLOR_PREFERENCE if use_light_colors != light { use_light_colors = light set_terminal_colors(self.lp) self.highlight_all() self.draw_screen() } return nil } func (self *Handler) initialize() { self.rl = readline.New(self.lp, readline.RlInit{DontMarkPrompts: true, Prompt: "/"}) self.lp.OnEscapeCode = self.on_escape_code self.lp.OnColorSchemeChange = self.on_color_scheme_change image_collection = graphics.NewImageCollection() self.current_context_count = opts.Context if self.current_context_count < 0 { self.current_context_count = int(conf.Num_context_lines) } sz, _ := self.lp.ScreenSize() self.update_screen_size(sz) self.original_context_count = self.current_context_count self.async_results = make(chan AsyncResult, 32) go func() { r := AsyncResult{} r.collection, r.err = create_collection(self.left, self.right) self.async_results <- r self.lp.WakeupMainThread() }() self.draw_screen() } func (self *Handler) generate_diff() { self.diff_map = nil jobs := make([]diff_job, 0, 32) _ = self.collection.Apply(func(path, typ, changed_path string) error { if typ == "diff" { if is_path_text(path) && is_path_text(changed_path) { jobs = append(jobs, diff_job{path, changed_path}) } } return nil }) go func() { r := AsyncResult{rtype: DIFF} r.diff_map, r.err = diff(jobs, self.current_context_count) self.async_results <- r self.lp.WakeupMainThread() }() } func (self *Handler) on_wakeup() error { var r AsyncResult for { select { case r = <-self.async_results: if r.err != nil { return r.err } r.err = self.handle_async_result(r) if r.err != nil { return r.err } default: return nil } } } var dark_highlight_started bool var light_highlight_started bool func (self *Handler) highlight_all() { if (use_light_colors && light_highlight_started) || (!use_light_colors && dark_highlight_started) { return } if use_light_colors { light_highlight_started = true } else { dark_highlight_started = true } text_files := utils.Filter(self.collection.paths_to_highlight.AsSlice(), is_path_text) go func() { r := AsyncResult{rtype: HIGHLIGHT} highlight_all(text_files, use_light_colors) self.async_results <- r self.lp.WakeupMainThread() }() } func (self *Handler) load_all_images() { _ = self.collection.Apply(func(path, item_type, changed_path string) error { if path != "" && is_image(path) { image_collection.AddPaths(path) self.image_count++ } if changed_path != "" && is_image(changed_path) { image_collection.AddPaths(changed_path) self.image_count++ } return nil }) if self.image_count > 0 { image_collection.Initialize(self.lp) go func() { r := AsyncResult{rtype: IMAGE_LOAD} image_collection.LoadAll() self.async_results <- r self.lp.WakeupMainThread() }() } } func (self *Handler) resize_all_images_if_needed() { if self.logical_lines == nil { return } margin_size := self.logical_lines.margin_size columns := self.logical_lines.columns available_cols := columns/2 - margin_size sz := graphics.Size{ Width: available_cols * self.screen_size.cell_width, Height: self.screen_size.num_lines * 2 * self.screen_size.cell_height, } if sz != self.images_resized_to && self.image_count > 0 { go func() { image_collection.ResizeForPageSize(sz.Width, sz.Height) r := AsyncResult{rtype: IMAGE_RESIZE, page_size: sz} self.async_results <- r self.lp.WakeupMainThread() }() } } func (self *Handler) rerender_diff() error { if self.diff_map != nil && self.collection != nil { err := self.render_diff() if err != nil { return err } self.draw_screen() } return nil } func (self *Handler) handle_async_result(r AsyncResult) error { switch r.rtype { case COLLECTION: self.collection = r.collection self.generate_diff() self.highlight_all() self.load_all_images() case DIFF: self.diff_map = r.diff_map self.calculate_statistics() self.clear_mouse_selection() err := self.render_diff() if err != nil { return err } self.scroll_pos = ScrollPos{} if self.restore_position != nil { self.scroll_pos = *self.restore_position if self.max_scroll_pos.Less(self.scroll_pos) { self.scroll_pos = self.max_scroll_pos } self.restore_position = nil } self.draw_screen() case IMAGE_RESIZE: self.images_resized_to = r.page_size return self.rerender_diff() case IMAGE_LOAD, HIGHLIGHT: return self.rerender_diff() } return nil } func (self *Handler) on_resize(old_size, new_size loop.ScreenSize) error { self.clear_mouse_selection() self.update_screen_size(new_size) if self.diff_map != nil && self.collection != nil { err := self.render_diff() if err != nil { return err } if self.max_scroll_pos.Less(self.scroll_pos) { self.scroll_pos = self.max_scroll_pos } } self.draw_screen() return nil } func (self *Handler) render_diff() (err error) { if self.screen_size.columns < 8 { return fmt.Errorf("Screen too narrow, need at least 8 columns") } if self.screen_size.rows < 2 { return fmt.Errorf("Screen too short, need at least 2 rows") } self.logical_lines, err = render(self.collection, self.diff_map, self.screen_size, self.largest_line_number, self.images_resized_to) if err != nil { return err } last := self.logical_lines.Len() - 1 self.max_scroll_pos.logical_line = last if last > -1 { self.max_scroll_pos.screen_line = len(self.logical_lines.At(last).screen_lines) - 1 } else { self.max_scroll_pos.screen_line = 0 } self.logical_lines.IncrementScrollPosBy(&self.max_scroll_pos, -self.screen_size.num_lines+1) if self.current_search != nil { self.current_search.search(self.logical_lines) } return nil } func (self *Handler) draw_image(key string, _, starting_row int) { image_collection.PlaceImageSubRect(self.lp, key, self.images_resized_to, 0, self.screen_size.cell_height*starting_row, -1, -1) } func (self *Handler) draw_image_pair(ll *LogicalLine, starting_row int) { if ll.left_image.key == "" && ll.right_image.key == "" { return } defer self.lp.QueueWriteString("\r") if ll.left_image.key != "" { self.lp.QueueWriteString("\r") self.lp.MoveCursorHorizontally(self.logical_lines.margin_size) self.draw_image(ll.left_image.key, ll.left_image.count, starting_row) } if ll.right_image.key != "" { self.lp.QueueWriteString("\r") self.lp.MoveCursorHorizontally(self.logical_lines.margin_size + self.logical_lines.columns/2) self.draw_image(ll.right_image.key, ll.right_image.count, starting_row) } } func (self *Handler) draw_screen() { self.lp.StartAtomicUpdate() defer self.lp.EndAtomicUpdate() if self.image_count > 0 { self.resize_all_images_if_needed() image_collection.DeleteAllVisiblePlacements(self.lp) } lp.MoveCursorTo(1, 1) lp.ClearToEndOfScreen() if self.logical_lines == nil || self.diff_map == nil || self.collection == nil || !self.terminal_capabilities_received { lp.Println(`Calculating diff, please wait...`) return } pos := self.scroll_pos seen_images := utils.NewSet[int]() for num_written := 0; num_written < self.screen_size.num_lines; num_written++ { ll := self.logical_lines.At(pos.logical_line) if ll == nil || self.logical_lines.ScreenLineAt(pos) == nil { num_written-- } else { is_image := ll.line_type == IMAGE_LINE ll.render_screen_line(pos.screen_line, lp, self.logical_lines.margin_size, self.logical_lines.columns) if is_image && !seen_images.Has(pos.logical_line) && pos.screen_line >= ll.image_lines_offset { seen_images.Add(pos.logical_line) self.draw_image_pair(ll, pos.screen_line-ll.image_lines_offset) } if self.current_search != nil { if mkp := self.current_search.markup_line(pos, num_written); mkp != "" { lp.QueueWriteString(mkp) } } if mkp := self.add_mouse_selection_to_line(pos, num_written); mkp != "" { lp.QueueWriteString(mkp) } lp.MoveCursorVertically(1) lp.QueueWriteString("\x1b[m\r") } if self.logical_lines.IncrementScrollPosBy(&pos, 1) == 0 { break } } self.draw_status_line() } func (self *Handler) draw_status_line() { if self.logical_lines == nil || self.diff_map == nil { return } self.lp.MoveCursorTo(1, self.screen_size.rows) self.lp.ClearToEndOfLine() self.lp.SetCursorVisible(self.inputting_command) if self.inputting_command { self.rl.RedrawNonAtomic() } else if self.statusline_message != "" { self.lp.QueueWriteString(message_format(wcswidth.TruncateToVisualLength(sanitize(self.statusline_message), self.screen_size.columns))) } else { num := self.logical_lines.NumScreenLinesTo(self.scroll_pos) den := self.logical_lines.NumScreenLinesTo(self.max_scroll_pos) var frac int if den > 0 { frac = int((float64(num) * 100.0) / float64(den)) } sp := statusline_format(fmt.Sprintf("%d%%", frac)) var counts string if self.current_search == nil { counts = added_count_format(strconv.Itoa(self.added_count)) + statusline_format(`,`) + removed_count_format(strconv.Itoa(self.removed_count)) } else { counts = statusline_format(fmt.Sprintf("%d matches", self.current_search.Len())) } suffix := counts + " " + sp prefix := statusline_format(":") filler := strings.Repeat(" ", utils.Max(0, self.screen_size.columns-wcswidth.Stringwidth(prefix)-wcswidth.Stringwidth(suffix))) self.lp.QueueWriteString(prefix + filler + suffix) } } func (self *Handler) on_text(text string, a, b bool) error { if self.inputting_command { defer self.draw_status_line() return self.rl.OnText(text, a, b) } if self.statusline_message != "" { self.statusline_message = "" self.draw_status_line() return nil } return nil } func (self *Handler) do_search(query string) { self.current_search = nil if len(query) < 2 { return } if !self.current_search_is_regex { query = regexp.QuoteMeta(query) } pat, err := regexp.Compile(`(?i)` + query) if err != nil { self.statusline_message = fmt.Sprintf("Bad regex: %s", err) self.lp.Beep() return } self.current_search = do_search(pat, self.logical_lines) if self.current_search.Len() == 0 { self.current_search = nil self.statusline_message = fmt.Sprintf("No matches for: %#v", query) self.lp.Beep() } else { if self.scroll_to_next_match(false, true) { self.draw_screen() } else { self.lp.Beep() } } } func (self *Handler) on_key_event(ev *loop.KeyEvent) error { if self.inputting_command { defer self.draw_status_line() if ev.MatchesPressOrRepeat("esc") { self.inputting_command = false ev.Handled = true return nil } if ev.MatchesPressOrRepeat("enter") { self.inputting_command = false ev.Handled = true self.do_search(self.rl.AllText()) self.draw_screen() return nil } return self.rl.OnKeyEvent(ev) } if self.statusline_message != "" { if ev.Type != loop.RELEASE { ev.Handled = true self.statusline_message = "" self.draw_status_line() } return nil } if self.current_search != nil && ev.MatchesPressOrRepeat("esc") { self.current_search = nil self.draw_screen() return nil } ac := self.shortcut_tracker.Match(ev, conf.KeyboardShortcuts) if ac != nil { ev.Handled = true return self.dispatch_action(ac.Name, ac.Args) } return nil } func (self *Handler) scroll_lines(amt int) (delta int) { before := self.scroll_pos delta = self.logical_lines.IncrementScrollPosBy(&self.scroll_pos, amt) if delta > 0 && self.max_scroll_pos.Less(self.scroll_pos) { self.scroll_pos = self.max_scroll_pos delta = self.logical_lines.Minus(self.scroll_pos, before) } return } func (self *Handler) scroll_to_next_change(backwards bool) bool { if backwards { for i := self.scroll_pos.logical_line - 1; i >= 0; i-- { line := self.logical_lines.At(i) if line.is_change_start { self.scroll_pos = ScrollPos{i, 0} return true } } } else { for i := self.scroll_pos.logical_line + 1; i < self.logical_lines.Len(); i++ { line := self.logical_lines.At(i) if line.is_change_start { self.scroll_pos = ScrollPos{i, 0} return true } } } return false } func (self *Handler) scroll_to_next_file(backwards bool) bool { if backwards { for i := self.scroll_pos.logical_line - 1; i >= 0; i-- { line := self.logical_lines.At(i) if line.line_type == TITLE_LINE { self.scroll_pos = ScrollPos{i, 0} return true } } } else { for i := self.scroll_pos.logical_line + 1; i < self.logical_lines.Len(); i++ { line := self.logical_lines.At(i) if line.line_type == TITLE_LINE { self.scroll_pos = ScrollPos{i, 0} return true } } } return false } func (self *Handler) scroll_to_next_match(backwards, include_current_match bool) bool { if self.current_search == nil { return false } if self.current_search_is_backward { backwards = !backwards } offset, delta := 1, 1 if include_current_match { offset = 0 } if backwards { offset *= -1 delta *= -1 } pos := self.scroll_pos if offset != 0 && self.logical_lines.IncrementScrollPosBy(&pos, offset) == 0 { return false } for { if self.current_search.Has(pos) { self.scroll_pos = pos self.draw_screen() return true } if self.logical_lines.IncrementScrollPosBy(&pos, delta) == 0 || self.max_scroll_pos.Less(pos) { break } } return false } func (self *Handler) change_context_count(val int) bool { val = utils.Max(0, val) if val == self.current_context_count { return false } self.current_context_count = val p := self.scroll_pos self.restore_position = &p self.clear_mouse_selection() self.generate_diff() self.draw_screen() return true } func (self *Handler) start_search(is_regex, is_backward bool) { if self.inputting_command { self.lp.Beep() return } self.inputting_command = true self.current_search_is_regex = is_regex self.current_search_is_backward = is_backward self.rl.SetText(``) self.draw_status_line() } func (self *Handler) dispatch_action(name, args string) error { switch name { case `quit`: self.lp.Quit(0) case `copy_to_clipboard`: text := self.text_for_current_mouse_selection() if text == "" { self.lp.Beep() } else { self.lp.CopyTextToClipboard(text) } case `copy_to_clipboard_or_exit`: text := self.text_for_current_mouse_selection() if text == "" { self.lp.Quit(0) } else { self.lp.CopyTextToClipboard(text) } case `scroll_by`: if args == "" { args = "1" } amt, err := strconv.Atoi(args) if err == nil { if self.scroll_lines(amt) == 0 { self.lp.Beep() } else { self.draw_screen() } } else { self.lp.Beep() } case `scroll_to`: done := false switch { case strings.Contains(args, "file"): done = self.scroll_to_next_file(strings.Contains(args, `prev`)) case strings.Contains(args, `change`): done = self.scroll_to_next_change(strings.Contains(args, `prev`)) case strings.Contains(args, `match`): done = self.scroll_to_next_match(strings.Contains(args, `prev`), false) case strings.Contains(args, `page`): amt := self.screen_size.num_lines if strings.Contains(args, `prev`) { amt *= -1 } done = self.scroll_lines(amt) != 0 default: npos := ScrollPos{} if strings.Contains(args, `end`) { npos = self.max_scroll_pos } done = npos != self.scroll_pos self.scroll_pos = npos } if done { self.draw_screen() } else { self.lp.Beep() } case `change_context`: new_ctx := self.current_context_count switch args { case `all`: new_ctx = 100000 case `default`: new_ctx = self.original_context_count default: delta, _ := strconv.Atoi(args) new_ctx += delta } if !self.change_context_count(new_ctx) { self.lp.Beep() } case `start_search`: if self.diff_map != nil && self.logical_lines != nil { a, b, _ := strings.Cut(args, " ") self.start_search(config.StringToBool(a), config.StringToBool(b)) } } return nil } func (self *Handler) on_mouse_event(ev *loop.MouseEvent) error { if self.logical_lines == nil { return nil } if ev.Event_type == loop.MOUSE_PRESS && ev.Buttons&(loop.MOUSE_WHEEL_UP|loop.MOUSE_WHEEL_DOWN) != 0 { self.handle_wheel_event(ev.Buttons&(loop.MOUSE_WHEEL_UP) != 0) return nil } if ev.Event_type == loop.MOUSE_PRESS && ev.Buttons&loop.LEFT_MOUSE_BUTTON != 0 { self.start_mouse_selection(ev) return nil } if ev.Event_type == loop.MOUSE_MOVE { self.update_mouse_selection(ev) return nil } if ev.Event_type == loop.MOUSE_RELEASE && ev.Buttons&loop.LEFT_MOUSE_BUTTON != 0 { self.finish_mouse_selection(ev) return nil } return nil } kitty-0.41.1/kittens/hints/0000775000175000017510000000000014773370543015115 5ustar nileshnileshkitty-0.41.1/kittens/hints/__init__.py0000664000175000017510000000000014773370543017214 0ustar nileshnileshkitty-0.41.1/kittens/hints/main.go0000664000175000017510000002165414773370543016400 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package hints import ( "encoding/json" "fmt" "io" "os" "strconv" "strings" "unicode" "kitty/tools/cli" "kitty/tools/tty" "kitty/tools/tui" "kitty/tools/tui/loop" "kitty/tools/utils" "kitty/tools/utils/style" "kitty/tools/wcswidth" ) var _ = fmt.Print func convert_text(text string, cols int) string { lines := make([]string, 0, 64) empty_line := strings.Repeat("\x00", cols) + "\n" s1 := utils.NewLineScanner(text) for s1.Scan() { full_line := s1.Text() if full_line == "" { lines = append(lines, empty_line) continue } if strings.TrimRight(full_line, "\r") == "" { for i := 0; i < len(full_line); i++ { lines = append(lines, empty_line) } continue } appended := false s2 := utils.NewSeparatorScanner(full_line, "\r") for s2.Scan() { line := s2.Text() if line != "" { line_sz := wcswidth.Stringwidth(line) extra := cols - line_sz if extra > 0 { line += strings.Repeat("\x00", extra) } lines = append(lines, line) lines = append(lines, "\r") appended = true } } if appended { lines[len(lines)-1] = "\n" } } ans := strings.Join(lines, "") return strings.TrimRight(ans, "\r\n") } func parse_input(text string) string { cols, err := strconv.Atoi(os.Getenv("OVERLAID_WINDOW_COLS")) if err == nil { return convert_text(text, cols) } term, err := tty.OpenControllingTerm() if err == nil { sz, err := term.GetSize() term.Close() if err == nil { return convert_text(text, int(sz.Col)) } } return convert_text(text, 80) } type Result struct { Match []string `json:"match"` Programs []string `json:"programs"` Multiple_joiner string `json:"multiple_joiner"` Customize_processing string `json:"customize_processing"` Type string `json:"type"` Groupdicts []map[string]any `json:"groupdicts"` Extra_cli_args []string `json:"extra_cli_args"` Linenum_action string `json:"linenum_action"` Cwd string `json:"cwd"` } func encode_hint(num int, alphabet string) (res string) { runes := []rune(alphabet) d := len(runes) for res == "" || num > 0 { res = string(runes[num%d]) + res num /= d } return } func decode_hint(x string, alphabet string) (ans int) { base := len(alphabet) index_map := make(map[rune]int, len(alphabet)) for i, c := range alphabet { index_map[c] = i } for _, char := range x { ans = ans*base + index_map[char] } return } func as_rgb(c uint32) [3]float32 { return [3]float32{float32((c>>16)&255) / 255.0, float32((c>>8)&255) / 255.0, float32(c&255) / 255.0} } func hints_text_color(confval string) (ans string) { ans = confval if ans == "auto" { ans = "bright-gray" if bc, err := tui.ReadBasicColors(); err == nil { bg := as_rgb(bc.Background) c15 := as_rgb(bc.Color15) c8 := as_rgb(bc.Color8) if utils.RGBContrast(bg[0], bg[1], bg[2], c8[0], c8[1], c8[2]) > utils.RGBContrast(bg[0], bg[1], bg[2], c15[0], c15[1], c15[2]) { ans = "bright-black" } } } return } func main(_ *cli.Command, o *Options, args []string) (rc int, err error) { o.HintsTextColor = hints_text_color(o.HintsTextColor) output := tui.KittenOutputSerializer() if tty.IsTerminal(os.Stdin.Fd()) { return 1, fmt.Errorf("You must pass the text to be hinted on STDIN") } stdin, err := io.ReadAll(os.Stdin) if err != nil { return 1, fmt.Errorf("Failed to read from STDIN with error: %w", err) } if len(args) > 0 && o.CustomizeProcessing == "" && o.Type != "linenum" { return 1, fmt.Errorf("Extra command line arguments present: %s", strings.Join(args, " ")) } input_text := parse_input(utils.UnsafeBytesToString(stdin)) text, all_marks, index_map, err := find_marks(input_text, o, os.Args[2:]...) if err != nil { return 1, err } result := Result{ Programs: o.Program, Multiple_joiner: o.MultipleJoiner, Customize_processing: o.CustomizeProcessing, Type: o.Type, Extra_cli_args: args, Linenum_action: o.LinenumAction, } result.Cwd, _ = os.Getwd() alphabet := o.Alphabet if alphabet == "" { alphabet = DEFAULT_HINT_ALPHABET } ignore_mark_indices := utils.NewSet[int](8) window_title := o.WindowTitle if window_title == "" { switch o.Type { case "url": window_title = "Choose URL" default: window_title = "Choose text" } } current_text := "" current_input := "" match_suffix := "" switch o.AddTrailingSpace { case "always": match_suffix = " " case "never": default: if o.Multiple { match_suffix = " " } } chosen := []*Mark{} lp, err := loop.New(loop.NoAlternateScreen) // no alternate screen reduces flicker on exit if err != nil { return } fctx := style.Context{AllowEscapeCodes: true} faint := fctx.SprintFunc("dim") hint_style := fctx.SprintFunc(fmt.Sprintf("fg=%s bg=%s bold", o.HintsForegroundColor, o.HintsBackgroundColor)) text_style := fctx.SprintFunc(fmt.Sprintf("fg=%s bold", o.HintsTextColor)) highlight_mark := func(m *Mark, mark_text string) string { hint := encode_hint(m.Index, alphabet) if current_input != "" && !strings.HasPrefix(hint, current_input) { return faint(mark_text) } hint = hint[len(current_input):] if hint == "" { hint = " " } if len(mark_text) <= len(hint) { mark_text = "" } else { mark_text = mark_text[len(hint):] } ans := hint_style(hint) + text_style(mark_text) return fmt.Sprintf("\x1b]8;;mark:%d\a%s\x1b]8;;\a", m.Index, ans) } render := func() string { ans := text for i := len(all_marks) - 1; i >= 0; i-- { mark := &all_marks[i] if ignore_mark_indices.Has(mark.Index) { continue } mtext := highlight_mark(mark, ans[mark.Start:mark.End]) ans = ans[:mark.Start] + mtext + ans[mark.End:] } ans = strings.ReplaceAll(ans, "\x00", "") return strings.TrimRightFunc(strings.NewReplacer("\r", "\r\n", "\n", "\r\n").Replace(ans), unicode.IsSpace) } draw_screen := func() { lp.StartAtomicUpdate() defer lp.EndAtomicUpdate() if current_text == "" { current_text = render() } lp.ClearScreen() lp.QueueWriteString(current_text) } reset := func() { current_input = "" current_text = "" } lp.OnInitialize = func() (string, error) { lp.SetCursorVisible(false) lp.SetWindowTitle(window_title) lp.AllowLineWrapping(false) draw_screen() lp.SendOverlayReady() return "", nil } lp.OnFinalize = func() string { lp.SetCursorVisible(true) return "" } lp.OnResize = func(old_size, new_size loop.ScreenSize) error { draw_screen() return nil } lp.OnRCResponse = func(data []byte) error { var r struct { Type string Mark int } if err := json.Unmarshal(data, &r); err != nil { return err } if r.Type == "mark_activated" { if m, ok := index_map[r.Mark]; ok { chosen = append(chosen, m) if o.Multiple { ignore_mark_indices.Add(m.Index) reset() } else { lp.Quit(0) return nil } } } return nil } lp.OnText = func(text string, _, _ bool) error { changed := false for _, ch := range text { if strings.ContainsRune(alphabet, ch) { current_input += string(ch) changed = true } } if changed { matches := []*Mark{} for idx, m := range index_map { if eh := encode_hint(idx, alphabet); strings.HasPrefix(eh, current_input) { matches = append(matches, m) } } if len(matches) == 1 { chosen = append(chosen, matches[0]) if o.Multiple { ignore_mark_indices.Add(matches[0].Index) reset() } else { lp.Quit(0) return nil } } current_text = "" draw_screen() } return nil } lp.OnKeyEvent = func(ev *loop.KeyEvent) error { if ev.MatchesPressOrRepeat("backspace") { ev.Handled = true r := []rune(current_input) if len(r) > 0 { r = r[:len(r)-1] current_input = string(r) current_text = "" } draw_screen() } else if ev.MatchesPressOrRepeat("enter") || ev.MatchesPressOrRepeat("space") { ev.Handled = true if current_input != "" { idx := decode_hint(current_input, alphabet) if m := index_map[idx]; m != nil { chosen = append(chosen, m) ignore_mark_indices.Add(idx) if o.Multiple { reset() draw_screen() } else { lp.Quit(0) } } else { current_input = "" current_text = "" draw_screen() } } } else if ev.MatchesPressOrRepeat("esc") { if o.Multiple { lp.Quit(0) } else { lp.Quit(1) } } return nil } err = lp.Run() if err != nil { return 1, err } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() return 1, nil } if lp.ExitCode() != 0 { return lp.ExitCode(), nil } result.Match = make([]string, len(chosen)) result.Groupdicts = make([]map[string]any, len(chosen)) for i, m := range chosen { result.Match[i] = m.Text + match_suffix result.Groupdicts[i] = m.Groupdict } fmt.Println(output(result)) return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } kitty-0.41.1/kittens/hints/main.py0000664000175000017510000003602514773370543016421 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import sys from collections.abc import Sequence from functools import lru_cache from typing import Any from kitty.cli_stub import HintsCLIOptions from kitty.clipboard import set_clipboard_string, set_primary_selection from kitty.constants import website_url from kitty.fast_data_types import get_options from kitty.typing import BossType, WindowType from kitty.utils import get_editor, resolve_custom_file from ..tui.handler import result_handler DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$' def load_custom_processor(customize_processing: str) -> Any: if customize_processing.startswith('::import::'): import importlib m = importlib.import_module(customize_processing[len('::import::'):]) return {k: getattr(m, k) for k in dir(m)} if customize_processing == '::linenum::': return {'handle_result': linenum_handle_result} custom_path = resolve_custom_file(customize_processing) import runpy return runpy.run_path(custom_path, run_name='__main__') class Mark: __slots__ = ('index', 'start', 'end', 'text', 'is_hyperlink', 'group_id', 'groupdict') def __init__( self, index: int, start: int, end: int, text: str, groupdict: Any, is_hyperlink: bool = False, group_id: str | None = None ): self.index, self.start, self.end = index, start, end self.text = text self.groupdict = groupdict self.is_hyperlink = is_hyperlink self.group_id = group_id def as_dict(self) -> dict[str, Any]: return { 'index': self.index, 'start': self.start, 'end': self.end, 'text': self.text, 'groupdict': {str(k):v for k, v in (self.groupdict or {}).items()}, 'group_id': self.group_id or '', 'is_hyperlink': self.is_hyperlink } def parse_hints_args(args: list[str]) -> tuple[HintsCLIOptions, list[str]]: from kitty.cli import parse_args return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten hints', result_class=HintsCLIOptions) def custom_marking() -> None: import json text = sys.stdin.read() sys.stdin.close() opts, extra_cli_args = parse_hints_args(sys.argv[1:]) m = load_custom_processor(opts.customize_processing or '::impossible::') if 'mark' not in m: raise SystemExit(2) all_marks = tuple(x.as_dict() for x in m['mark'](text, opts, Mark, extra_cli_args)) sys.stdout.write(json.dumps(all_marks)) raise SystemExit(0) OPTIONS = r''' --program type=list What program to use to open matched text. Defaults to the default open program for the operating system. Various special values are supported: :code:`-` paste the match into the terminal window. :code:`@` copy the match to the clipboard :code:`*` copy the match to the primary selection (on systems that support primary selections) :code:`@NAME` copy the match to the specified buffer, e.g. :code:`@a` :code:`default` run the default open program. Note that when using the hyperlink :code:`--type` the default is to use the kitty :doc:`hyperlink handling
` facilities. :code:`launch` run :doc:`/launch` to open the program in a new kitty tab, window, overlay, etc. For example:: --program "launch --type=tab vim" Can be specified multiple times to run multiple programs. --type default=url choices=url,regex,path,line,hash,word,linenum,hyperlink,ip The type of text to search for. A value of :code:`linenum` is special, it looks for error messages using the pattern specified with :option:`--regex`, which must have the named groups: :code:`path` and :code:`line`. If not specified, will look for :code:`path:line`. The :option:`--linenum-action` option controls where to display the selected error message, other options are ignored. --regex default={default_regex} The regular expression to use when option :option:`--type` is set to :code:`regex`, in Perl 5 syntax. If you specify a numbered group in the regular expression, only the group will be matched. This allow you to match text ignoring a prefix/suffix, as needed. The default expression matches lines. To match text over multiple lines, things get a little tricky, as line endings are a sequence of zero or more null bytes followed by either a carriage return or a newline character. To have a pattern match over line endings you will need to match the character set ``[\0\r\n]``. The newlines and null bytes are automatically stripped from the returned text. If you specify named groups and a :option:`--program`, then the program will be passed arguments corresponding to each named group of the form :code:`key=value`. --linenum-action default=self type=choice choices=self,window,tab,os_window,background Where to perform the action on matched errors. :code:`self` means the current window, :code:`window` a new kitty window, :code:`tab` a new tab, :code:`os_window` a new OS window and :code:`background` run in the background. The actual action is whatever arguments are provided to the kitten, for example: :code:`kitten hints --type=linenum --linenum-action=tab vim +{line} {path}` will open the matched path at the matched line number in vim in a new kitty tab. Note that in order to use :option:`--program` to copy or paste the provided arguments, you need to use the special value :code:`self`. --url-prefixes default=default Comma separated list of recognized URL prefixes. Defaults to the list of prefixes defined by the :opt:`url_prefixes` option in :file:`kitty.conf`. --url-excluded-characters default=default Characters to exclude when matching URLs. Defaults to the list of characters defined by the :opt:`url_excluded_characters` option in :file:`kitty.conf`. The syntax for this option is the same as for :opt:`url_excluded_characters`. --word-characters Characters to consider as part of a word. In addition, all characters marked as alphanumeric in the Unicode database will be considered as word characters. Defaults to the :opt:`select_by_word_characters` option from :file:`kitty.conf`. --minimum-match-length default=3 type=int The minimum number of characters to consider a match. --multiple type=bool-set Select multiple matches and perform the action on all of them together at the end. In this mode, press :kbd:`Esc` to finish selecting. --multiple-joiner default=auto String for joining multiple selections when copying to the clipboard or inserting into the terminal. The special values are: :code:`space` - a space character, :code:`newline` - a newline, :code:`empty` - an empty joiner, :code:`json` - a JSON serialized list, :code:`auto` - an automatic choice, based on the type of text being selected. In addition, integers are interpreted as zero-based indices into the list of selections. You can use :code:`0` for the first selection and :code:`-1` for the last. --add-trailing-space default=auto choices=auto,always,never Add trailing space after matched text. Defaults to :code:`auto`, which adds the space when used together with :option:`--multiple`. --hints-offset default=1 type=int The offset (from zero) at which to start hint numbering. Note that only numbers greater than or equal to zero are respected. --alphabet The list of characters to use for hints. The default is to use numbers and lowercase English alphabets. Specify your preference as a string of characters. Note that you need to specify the :option:`--hints-offset` as zero to use the first character to highlight the first match, otherwise it will start with the second character by default. --ascending type=bool-set Make the hints increase from top to bottom, instead of decreasing from top to bottom. --hints-foreground-color default=black type=str The foreground color for hints. You can use color names or hex values. For the eight basic named terminal colors you can also use the :code:`bright-` prefix to get the bright variant of the color. --hints-background-color default=green type=str The background color for hints. You can use color names or hex values. For the eight basic named terminal colors you can also use the :code:`bright-` prefix to get the bright variant of the color. --hints-text-color default=auto type=str The foreground color for text pointed to by the hints. You can use color names or hex values. For the eight basic named terminal colors you can also use the :code:`bright-` prefix to get the bright variant of the color. The default is to pick a suitable color automatically. --customize-processing Name of a python file in the kitty config directory which will be imported to provide custom implementations for pattern finding and performing actions on selected matches. You can also specify absolute paths to load the script from elsewhere. See {hints_url} for details. --window-title The title for the hints window, default title is based on the type of text being hinted. '''.format( default_regex=DEFAULT_REGEX, line='{{line}}', path='{{path}}', hints_url=website_url('kittens/hints'), ).format help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.' usage = '' def main(args: list[str]) -> dict[str, Any] | None: raise SystemExit('Should be run as kitten hints') def linenum_process_result(data: dict[str, Any]) -> tuple[str, int]: for match, g in zip(data['match'], data['groupdicts']): path, line = g['path'], g['line'] if path and line: return path, int(line) return '', -1 def linenum_handle_result(args: list[str], data: dict[str, Any], target_window_id: int, boss: BossType, extra_cli_args: Sequence[str], *a: Any) -> None: path, line = linenum_process_result(data) if not path: return if extra_cli_args: cmd = [x.format(path=path, line=line) for x in extra_cli_args] else: cmd = get_editor(path_to_edit=path, line_number=line) w = boss.window_id_map.get(target_window_id) action = data['linenum_action'] if action == 'self': if w is not None: def is_copy_action(s: str) -> bool: return s in ('-', '@', '*') or s.startswith('@') programs = list(filter(is_copy_action, data['programs'] or ())) # keep for backward compatibility, previously option `--program` does not need to be specified to perform copy actions if is_copy_action(cmd[0]): programs.append(cmd.pop(0)) if programs: text = ' '.join(cmd) for program in programs: if program == '-': w.paste_bytes(text) elif program == '@': set_clipboard_string(text) boss.handle_clipboard_loss('clipboard') elif program == '*': set_primary_selection(text) boss.handle_clipboard_loss('primary') elif program.startswith('@'): boss.set_clipboard_buffer(program[1:], text) else: import shlex text = shlex.join(cmd) w.paste_bytes(f'{text}\r') elif action == 'background': import subprocess subprocess.Popen(cmd, cwd=data['cwd']) else: getattr(boss, { 'window': 'new_window_with_cwd', 'tab': 'new_tab_with_cwd', 'os_window': 'new_os_window_with_cwd' }[action])(*cmd) def on_mark_clicked(boss: BossType, window: WindowType, url: str, hyperlink_id: int, cwd: str) -> bool: if url.startswith('mark:'): window.send_cmd_response({'Type': 'mark_activated', 'Mark': int(url[5:])}) return True return False @result_handler(type_of_input='screen-ansi', has_ready_notification=True, open_url_handler=on_mark_clicked) def handle_result(args: list[str], data: dict[str, Any], target_window_id: int, boss: BossType) -> None: cp = data['customize_processing'] if data['type'] == 'linenum': cp = '::linenum::' if cp: m = load_custom_processor(cp) if 'handle_result' in m: m['handle_result'](args, data, target_window_id, boss, data['extra_cli_args']) return None programs = data['programs'] or ('default',) matches: list[str] = [] groupdicts = [] for m, g in zip(data['match'], data['groupdicts']): if m: matches.append(m) groupdicts.append(g) joiner = data['multiple_joiner'] try: is_int: int | None = int(joiner) except Exception: is_int = None text_type = data['type'] @lru_cache def joined_text() -> str: if is_int is not None: try: return matches[is_int] except IndexError: return matches[-1] if joiner == 'json': import json return json.dumps(matches, ensure_ascii=False, indent='\t') if joiner == 'auto': q = '\n\r' if text_type in ('line', 'url') else ' ' else: q = {'newline': '\n\r', 'space': ' '}.get(joiner, '') return q.join(matches) for program in programs: if program == '-': w = boss.window_id_map.get(target_window_id) if w is not None: w.paste_text(joined_text()) elif program == '*': set_primary_selection(joined_text()) elif program.startswith('@'): if program == '@': set_clipboard_string(joined_text()) else: boss.set_clipboard_buffer(program[1:], joined_text()) else: from kitty.conf.utils import to_cmdline cwd = data['cwd'] is_default_program = program == 'default' program = get_options().open_url_with if is_default_program else program if text_type == 'hyperlink' and is_default_program: w = boss.window_id_map.get(target_window_id) for m in matches: if w is not None: w.open_url(m, hyperlink_id=1, cwd=cwd) else: launch_args = [] if isinstance(program, str) and program.startswith('launch '): launch_args = to_cmdline(program) launch_args.insert(1, '--cwd=' + cwd) for m, groupdict in zip(matches, groupdicts): if groupdict: m = [] for k, v in groupdict.items(): m.append('{}={}'.format(k, v or '')) if launch_args: w = boss.window_id_map.get(target_window_id) boss.call_remote_control(self_window=w, args=tuple(launch_args + ([m] if isinstance(m, str) else m))) else: boss.open_url(m, program, cwd=cwd) if __name__ == '__main__': # Run with kitty +kitten hints ans = main(sys.argv) if ans: print(ans) elif __name__ == '__doc__': cd = sys.cli_docs # type: ignore cd['usage'] = usage cd['short_desc'] = 'Select text from screen with keyboard' cd['options'] = OPTIONS cd['help_text'] = help_text # }}} kitty-0.41.1/kittens/hints/marks.go0000664000175000017510000004511314773370543016565 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package hints import ( "bytes" "encoding/json" "errors" "fmt" "os/exec" "regexp" "slices" "strconv" "strings" "sync" "unicode" "unicode/utf8" "github.com/dlclark/regexp2" "github.com/seancfoley/ipaddress-go/ipaddr" "kitty" "kitty/tools/config" "kitty/tools/tty" "kitty/tools/utils" ) var _ = fmt.Print const ( DEFAULT_HINT_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz" FILE_EXTENSION = `\.(?:[a-zA-Z0-9]{2,7}|[ahcmo])(?:\b|[^.])` ) func path_regex() string { return fmt.Sprintf(`(?:\S*?/[\r\S]+)|(?:\S[\r\S]*%s)\b`, FILE_EXTENSION) } func default_linenum_regex() string { return fmt.Sprintf(`(?P%s):(?P\d+)`, path_regex()) } type Mark struct { Index int `json:"index"` Start int `json:"start"` End int `json:"end"` Text string `json:"text"` Group_id string `json:"group_id"` Is_hyperlink bool `json:"is_hyperlink"` Groupdict map[string]any `json:"groupdict"` } func process_escape_codes(text string) (ans string, hyperlinks []Mark) { removed_size, idx := 0, 0 active_hyperlink_url := "" active_hyperlink_id := "" active_hyperlink_start_offset := 0 add_hyperlink := func(end int) { hyperlinks = append(hyperlinks, Mark{ Index: idx, Start: active_hyperlink_start_offset, End: end, Text: active_hyperlink_url, Is_hyperlink: true, Group_id: active_hyperlink_id}) active_hyperlink_url, active_hyperlink_id = "", "" active_hyperlink_start_offset = 0 idx++ } ans = utils.ReplaceAll(utils.MustCompile("\x1b(?:\\[[0-9;:]*?m|\\].*?\x1b\\\\)"), text, func(raw string, groupdict map[string]utils.SubMatch) string { if !strings.HasPrefix(raw, "\x1b]8") { removed_size += len(raw) return "" } start := groupdict[""].Start - removed_size removed_size += len(raw) if active_hyperlink_url != "" { add_hyperlink(start) } raw = raw[4 : len(raw)-2] if metadata, url, found := strings.Cut(raw, ";"); found && url != "" { active_hyperlink_url = url active_hyperlink_start_offset = start if metadata != "" { for _, entry := range strings.Split(metadata, ":") { if strings.HasPrefix(entry, "id=") && len(entry) > 3 { active_hyperlink_id = entry[3:] } } } } return "" }) if active_hyperlink_url != "" { add_hyperlink(len(ans)) } return } type PostProcessorFunc = func(string, int, int) (int, int) type GroupProcessorFunc = func(map[string]string) func is_punctuation(b string) bool { switch b { case ",", ".", "?", "!": return true } return false } func closing_bracket_for(ch string) string { switch ch { case "(": return ")" case "[": return "]" case "{": return "}" case "<": return ">" case "*": return "*" case `"`: return `"` case "'": return "'" case "“": return "”" case "‘": return "’" } return "" } func char_at(s string, i int) string { ans, _ := utf8.DecodeRuneInString(s[i:]) if ans == utf8.RuneError { return "" } return string(ans) } func matching_remover(openers ...string) PostProcessorFunc { return func(text string, s, e int) (int, int) { if s < e && e <= len(text) { before := char_at(text, s) if slices.Index(openers, before) > -1 { q := closing_bracket_for(before) if e > 0 && char_at(text, e-1) == q { s++ e-- } else if char_at(text, e) == q { s++ } } } return s, e } } func linenum_group_processor(gd map[string]string) { pat := utils.MustCompile(`:\d+$`) gd[`path`] = pat.ReplaceAllStringFunc(gd["path"], func(m string) string { gd["line"] = m[1:] return `` }) gd[`path`] = utils.Expanduser(gd[`path`]) } var PostProcessorMap = sync.OnceValue(func() map[string]PostProcessorFunc { return map[string]PostProcessorFunc{ "url": func(text string, s, e int) (int, int) { if s > 4 && text[s-5:s] == "link:" { // asciidoc URLs url := text[s:e] idx := strings.LastIndex(url, "[") if idx > -1 { e -= len(url) - idx } } for e > 1 && is_punctuation(char_at(text, e)) { // remove trailing punctuation e-- } // truncate url at closing bracket/quote if s > 0 && e <= len(text) && closing_bracket_for(char_at(text, s-1)) != "" { q := closing_bracket_for(char_at(text, s-1)) idx := strings.Index(text[s:], q) if idx > 0 { e = s + idx } } // reStructuredText URLs if e > 3 && text[e-2:e] == "`_" { e -= 2 } return s, e }, "brackets": matching_remover("(", "{", "[", "<"), "quotes": matching_remover("'", `"`, "“", "‘"), "ip": func(text string, s, e int) (int, int) { addr := ipaddr.NewHostName(text[s:e]) if !addr.IsAddress() { return -1, -1 } return s, e }, } }) type KittyOpts struct { Url_prefixes *utils.Set[string] Url_excluded_characters string Select_by_word_characters string } func read_relevant_kitty_opts() KittyOpts { ans := KittyOpts{ Select_by_word_characters: kitty.KittyConfigDefaults.Select_by_word_characters, Url_excluded_characters: kitty.KittyConfigDefaults.Url_excluded_characters} handle_line := func(key, val string) error { switch key { case "url_prefixes": ans.Url_prefixes = utils.NewSetWithItems(strings.Split(val, " ")...) case "select_by_word_characters": ans.Select_by_word_characters = strings.TrimSpace(val) case "url_excluded_characters": if s, err := config.StringLiteral(val); err == nil { ans.Url_excluded_characters = s } } return nil } config.ReadKittyConfig(handle_line) if ans.Url_prefixes == nil { ans.Url_prefixes = utils.NewSetWithItems(kitty.KittyConfigDefaults.Url_prefixes...) } return ans } var RelevantKittyOpts = sync.OnceValue(func() KittyOpts { return read_relevant_kitty_opts() }) var debugprintln = tty.DebugPrintln var _ = debugprintln func url_excluded_characters_as_ranges_for_regex(extra_excluded string) string { // See https://url.spec.whatwg.org/#url-code-points ans := strings.Builder{} ans.Grow(4096) type cr struct{ start, end rune } ranges := []cr{} r := func(start rune, end ...rune) { if len(end) == 0 { ranges = append(ranges, cr{start, start}) } else { ranges = append(ranges, cr{start, end[0]}) } } if !strings.Contains(extra_excluded, "\n") { r('\n') } if !strings.Contains(extra_excluded, "\r") { r('\r') } r('!') r('$') r('&') r('#') r('\'') r('/') r(':') r(';') r('@') r('_') r('~') r('(') r(')') r('*') r('+') r(',') r('-') r('.') r('=') r('?') r('%') r('a', 'z') r('A', 'Z') r('0', '9') slices.SortFunc(ranges, func(a, b cr) int { return int(a.start - b.start) }) var prev rune = -1 for _, cr := range ranges { if cr.start-1 > prev+1 { ans.WriteString(regexp.QuoteMeta(string(prev + 1))) ans.WriteRune('-') ans.WriteString(regexp.QuoteMeta(string(cr.start - 1))) } prev = cr.end } ans.WriteString(regexp.QuoteMeta(string(ranges[len(ranges)-1].end + 1))) ans.WriteRune('-') ans.WriteRune(0x9f) ans.WriteString(`\x{d800}-\x{dfff}`) ans.WriteString(`\x{fdd0}-\x{fdef}`) w := func(x rune) { ans.WriteRune(x) } w(0xFFFE) w(0xFFFF) w(0x1FFFE) w(0x1FFFF) w(0x2FFFE) w(0x2FFFF) w(0x3FFFE) w(0x3FFFF) w(0x4FFFE) w(0x4FFFF) w(0x5FFFE) w(0x5FFFF) w(0x6FFFE) w(0x6FFFF) w(0x7FFFE) w(0x7FFFF) w(0x8FFFE) w(0x8FFFF) w(0x9FFFE) w(0x9FFFF) w(0xAFFFE) w(0xAFFFF) w(0xBFFFE) w(0xBFFFF) w(0xCFFFE) w(0xCFFFF) w(0xDFFFE) w(0xDFFFF) w(0xEFFFE) w(0xEFFFF) w(0xFFFFE) w(0xFFFFF) if strings.Contains(extra_excluded, "-") { extra_excluded = strings.ReplaceAll(extra_excluded, "-", "") extra_excluded = regexp.QuoteMeta(extra_excluded) + "-" } else { extra_excluded = regexp.QuoteMeta(extra_excluded) } ans.WriteString(extra_excluded) return ans.String() } func functions_for(opts *Options) (pattern string, post_processors []PostProcessorFunc, group_processors []GroupProcessorFunc, err error) { switch opts.Type { case "url": var url_prefixes *utils.Set[string] if opts.UrlPrefixes == "default" { url_prefixes = RelevantKittyOpts().Url_prefixes } else { url_prefixes = utils.NewSetWithItems(strings.Split(opts.UrlPrefixes, ",")...) } url_excluded_characters := RelevantKittyOpts().Url_excluded_characters if opts.UrlExcludedCharacters != "default" { if url_excluded_characters, err = config.StringLiteral(opts.UrlExcludedCharacters); err != nil { err = fmt.Errorf("Failed to parse --url-excluded-characters value: %#v with error: %w", opts.UrlExcludedCharacters, err) return } } pattern = fmt.Sprintf(`(?:%s)://[^%s]{3,}`, strings.Join(url_prefixes.AsSlice(), "|"), url_excluded_characters_as_ranges_for_regex(url_excluded_characters)) post_processors = append(post_processors, PostProcessorMap()["url"]) case "path": pattern = path_regex() post_processors = append(post_processors, PostProcessorMap()["brackets"], PostProcessorMap()["quotes"]) case "line": pattern = "(?m)^\\s*(.+)[\\s\x00]*$" case "hash": pattern = "[0-9a-f][0-9a-f\r]{6,127}" case "ip": pattern = ( // IPv4 with no validation `((?:\d{1,3}\.){3}\d{1,3}` + "|" + // IPv6 with no validation `(?:[a-fA-F0-9]{0,4}:){2,7}[a-fA-F0-9]{1,4})`) post_processors = append(post_processors, PostProcessorMap()["ip"]) default: pattern = opts.Regex if opts.Type == "linenum" { if pattern == kitty.HintsDefaultRegex { pattern = default_linenum_regex() } post_processors = append(post_processors, PostProcessorMap()["brackets"], PostProcessorMap()["quotes"]) group_processors = append(group_processors, linenum_group_processor) } } return } type Capture struct { Text string Text_as_runes []rune Byte_Offsets struct { Start, End int } Rune_Offsets struct { Start, End int } } func (self Capture) String() string { return fmt.Sprintf("Capture(start=%d, end=%d, %#v)", self.Byte_Offsets.Start, self.Byte_Offsets.End, self.Text) } type Group struct { Name string IsNamed bool Captures []Capture } func (self Group) LastCapture() Capture { if len(self.Captures) == 0 { return Capture{} } return self.Captures[len(self.Captures)-1] } func (self Group) String() string { return fmt.Sprintf("Group(name=%#v, captures=%v)", self.Name, self.Captures) } type Match struct { Groups []Group } func (self Match) HasNamedGroups() bool { for _, g := range self.Groups { if g.IsNamed { return true } } return false } func find_all_matches(re *regexp2.Regexp, text string) (ans []Match, err error) { m, err := re.FindStringMatch(text) if err != nil { return } rune_to_bytes := utils.RuneOffsetsToByteOffsets(text) get_byte_offset_map := func(groups []regexp2.Group) (ans map[int]int, err error) { ans = make(map[int]int, len(groups)*2) rune_offsets := make([]int, 0, len(groups)*2) for _, g := range groups { for _, c := range g.Captures { if _, found := ans[c.Index]; !found { rune_offsets = append(rune_offsets, c.Index) ans[c.Index] = -1 } end := c.Index + c.Length if _, found := ans[end]; !found { rune_offsets = append(rune_offsets, end) ans[end] = -1 } } } slices.Sort(rune_offsets) for _, pos := range rune_offsets { if ans[pos] = rune_to_bytes(pos); ans[pos] < 0 { return nil, fmt.Errorf("Matches are not monotonic cannot map rune offsets to byte offsets") } } return } for m != nil { groups := m.Groups() bom, err := get_byte_offset_map(groups) if err != nil { return nil, err } match := Match{Groups: make([]Group, len(groups))} for i, g := range m.Groups() { match.Groups[i].Name = g.Name match.Groups[i].IsNamed = g.Name != "" && g.Name != strconv.Itoa(i) for _, c := range g.Captures { cn := Capture{Text: c.String(), Text_as_runes: c.Runes()} cn.Rune_Offsets.End = c.Index + c.Length cn.Rune_Offsets.Start = c.Index cn.Byte_Offsets.Start, cn.Byte_Offsets.End = bom[c.Index], bom[cn.Rune_Offsets.End] match.Groups[i].Captures = append(match.Groups[i].Captures, cn) } } ans = append(ans, match) m, _ = re.FindNextMatch(m) } return } func mark(r *regexp2.Regexp, post_processors []PostProcessorFunc, group_processors []GroupProcessorFunc, text string, opts *Options) (ans []Mark) { sanitize_pat := regexp.MustCompile("[\r\n\x00]") all_matches, _ := find_all_matches(r, text) for i, m := range all_matches { full_capture := m.Groups[0].LastCapture() match_start, match_end := full_capture.Byte_Offsets.Start, full_capture.Byte_Offsets.End for match_end > match_start+1 && text[match_end-1] == 0 { match_end-- } full_match := text[match_start:match_end] if len([]rune(full_match)) < opts.MinimumMatchLength { continue } for _, f := range post_processors { match_start, match_end = f(text, match_start, match_end) if match_start < 0 { break } } if match_start < 0 { continue } full_match = sanitize_pat.ReplaceAllLiteralString(text[match_start:match_end], "") gd := make(map[string]string, len(m.Groups)) for idx, g := range m.Groups { if idx > 0 && g.IsNamed { c := g.LastCapture() if s, e := c.Byte_Offsets.Start, c.Byte_Offsets.End; s > -1 && e > -1 { s = max(s, match_start) e = min(e, match_end) gd[g.Name] = sanitize_pat.ReplaceAllLiteralString(text[s:e], "") } } } for _, f := range group_processors { f(gd) } gd2 := make(map[string]any, len(gd)) for k, v := range gd { gd2[k] = v } if opts.Type == "regex" && len(m.Groups) > 1 && !m.HasNamedGroups() { cp := m.Groups[1].LastCapture() ms, me := cp.Byte_Offsets.Start, cp.Byte_Offsets.End match_start = max(match_start, ms) match_end = min(match_end, me) full_match = sanitize_pat.ReplaceAllLiteralString(text[match_start:match_end], "") } if full_match != "" { ans = append(ans, Mark{ Index: i, Start: match_start, End: match_end, Text: full_match, Groupdict: gd2, }) } } return } type ErrNoMatches struct{ Type, Pattern string } func is_word_char(ch rune, current_chars []rune) bool { return unicode.IsLetter(ch) || unicode.IsNumber(ch) || (unicode.IsMark(ch) && len(current_chars) > 0 && unicode.IsLetter(current_chars[len(current_chars)-1])) } func mark_words(text string, opts *Options) (ans []Mark) { left := text var current_run struct { chars []rune start, size int } chars := opts.WordCharacters if chars == "" { chars = RelevantKittyOpts().Select_by_word_characters } allowed_chars := make(map[rune]bool, len(chars)) for _, ch := range chars { allowed_chars[ch] = true } pos := 0 post_processors := []PostProcessorFunc{PostProcessorMap()["brackets"], PostProcessorMap()["quotes"]} commit_run := func() { if len(current_run.chars) >= opts.MinimumMatchLength { match_start, match_end := current_run.start, current_run.start+current_run.size for _, f := range post_processors { match_start, match_end = f(text, match_start, match_end) if match_start < 0 { break } } if match_start > -1 && match_end > match_start { full_match := text[match_start:match_end] if len([]rune(full_match)) >= opts.MinimumMatchLength { ans = append(ans, Mark{ Index: len(ans), Start: match_start, End: match_end, Text: full_match, }) } } } current_run.chars = nil current_run.start = 0 current_run.size = 0 } for { ch, size := utf8.DecodeRuneInString(left) if ch == utf8.RuneError { break } if allowed_chars[ch] || is_word_char(ch, current_run.chars) { if len(current_run.chars) == 0 { current_run.start = pos } current_run.chars = append(current_run.chars, ch) current_run.size += size } else { commit_run() } left = left[size:] pos += size } commit_run() return } func adjust_python_offsets(text string, marks []Mark) error { // python returns rune based offsets (unicode chars not utf-8 bytes) adjust := utils.RuneOffsetsToByteOffsets(text) for i := range marks { mark := &marks[i] if mark.End < mark.Start { return fmt.Errorf("The end of a mark must not be before its start") } s, e := adjust(mark.Start), adjust(mark.End) if s < 0 || e < 0 { return fmt.Errorf("Overlapping marks are not supported") } mark.Start, mark.End = s, e } return nil } func (self *ErrNoMatches) Error() string { none_of := "matches" switch self.Type { case "urls": none_of = "URLs" case "hyperlinks": none_of = "hyperlinks" } if self.Pattern != "" { return fmt.Sprintf("No %s found with pattern: %s", none_of, self.Pattern) } return fmt.Sprintf("No %s found", none_of) } func find_marks(text string, opts *Options, cli_args ...string) (sanitized_text string, ans []Mark, index_map map[int]*Mark, err error) { sanitized_text, hyperlinks := process_escape_codes(text) used_pattern := "" run_basic_matching := func() error { pattern, post_processors, group_processors, err := functions_for(opts) if err != nil { return err } r, err := regexp2.Compile(pattern, regexp2.RE2) if err != nil { return fmt.Errorf("Failed to compile the regex pattern: %#v with error: %w", pattern, err) } ans = mark(r, post_processors, group_processors, sanitized_text, opts) used_pattern = pattern return nil } if opts.CustomizeProcessing != "" { cmd := exec.Command(utils.KittyExe(), append([]string{"+runpy", "from kittens.hints.main import custom_marking; custom_marking()"}, cli_args...)...) cmd.Stdin = strings.NewReader(sanitized_text) stdout, stderr := bytes.Buffer{}, bytes.Buffer{} cmd.Stdout, cmd.Stderr = &stdout, &stderr err = cmd.Run() if err != nil { var e *exec.ExitError if errors.As(err, &e) && e.ExitCode() == 2 { err = run_basic_matching() if err != nil { return } goto process_answer } else { return "", nil, nil, fmt.Errorf("Failed to run custom processor %#v with error: %w\n%s", opts.CustomizeProcessing, err, stderr.String()) } } ans = make([]Mark, 0, 32) err = json.Unmarshal(stdout.Bytes(), &ans) if err != nil { return "", nil, nil, fmt.Errorf("Failed to load output from custom processor %#v with error: %w", opts.CustomizeProcessing, err) } err = adjust_python_offsets(sanitized_text, ans) if err != nil { return "", nil, nil, fmt.Errorf("Custom processor %#v produced invalid mark output with error: %w", opts.CustomizeProcessing, err) } } else if opts.Type == "hyperlink" { ans = hyperlinks } else if opts.Type == "word" { ans = mark_words(sanitized_text, opts) } else { err = run_basic_matching() if err != nil { return } } process_answer: if len(ans) == 0 { return "", nil, nil, &ErrNoMatches{Type: opts.Type, Pattern: used_pattern} } largest_index := ans[len(ans)-1].Index offset := max(0, opts.HintsOffset) index_map = make(map[int]*Mark, len(ans)) for i := range ans { m := &ans[i] if opts.Ascending { m.Index += offset } else { m.Index = largest_index - m.Index + offset } index_map[m.Index] = m } return } kitty-0.41.1/kittens/hints/marks_test.go0000664000175000017510000001001214773370543017612 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package hints import ( "errors" "fmt" "kitty" "kitty/tools/utils" "os" "path/filepath" "strconv" "strings" "testing" "github.com/google/go-cmp/cmp" ) var _ = fmt.Print func TestHintMarking(t *testing.T) { var opts *Options cols := 20 cli_args := []string{} reset := func() { opts = &Options{Type: "url", UrlPrefixes: "default", Regex: kitty.HintsDefaultRegex} cols = 20 cli_args = []string{} } r := func(text string, url ...string) (marks []Mark) { ptext := convert_text(text, cols) ptext, marks, _, err := find_marks(ptext, opts, cli_args...) if err != nil { var e *ErrNoMatches if len(url) != 0 || !errors.As(err, &e) { t.Fatalf("%#v failed with error: %s", text, err) } return } actual := utils.Map(func(m Mark) string { return m.Text }, marks) if diff := cmp.Diff(url, actual); diff != "" { t.Fatalf("%#v failed:\n%s", text, diff) } for _, m := range marks { q := strings.NewReplacer("\n", "", "\r", "", "\x00", "").Replace(ptext[m.Start:m.End]) if diff := cmp.Diff(m.Text, q); diff != "" { t.Fatalf("Mark start (%d) and end (%d) dont point to correct offset in text for %#v\n%s", m.Start, m.End, text, diff) } } return } reset() u := `http://test.me/` r(u, u) r(u+"#fragme", u+"#fragme") r(`"`+u+`"`, u) r("("+u+")", u) cols = len(u) r(u+"\nxxx", u+"xxx") cols = 20 r("link:"+u+"[xxx]", u) r("`xyz <"+u+">`_.", u) r(`moo`, u) r("\x1b[mhttp://test.me/1234\n\x1b[mx", "http://test.me/1234") r("\x1b[mhttp://test.me/12345\r\x1b[m6\n\x1b[mx", "http://test.me/123456") opts.Type = "linenum" m := func(text, path string, line int) { ptext := convert_text(text, cols) _, marks, _, err := find_marks(ptext, opts, cli_args...) if err != nil { t.Fatalf("%#v failed with error: %s", text, err) } gd := map[string]any{"path": path, "line": strconv.Itoa(line)} if diff := cmp.Diff(marks[0].Groupdict, gd); diff != "" { t.Fatalf("%#v failed:\n%s", text, diff) } } m("file.c:23", "file.c", 23) m("file.c:23:32", "file.c", 23) m("file.cpp:23:1", "file.cpp", 23) m("a/file.c:23", "a/file.c", 23) m("a/file.c:23:32", "a/file.c", 23) m("~/file.c:23:32", utils.Expanduser("~/file.c"), 23) reset() opts.Type = "path" r("file.c", "file.c") r("file.c.", "file.c") r("file.epub.", "file.epub") r("(file.epub)", "file.epub") r("some/path", "some/path") reset() cols = 60 opts.Type = "ip" r(`100.64.0.0`, `100.64.0.0`) r(`2001:0db8:0000:0000:0000:ff00:0042:8329`, `2001:0db8:0000:0000:0000:ff00:0042:8329`) r(`2001:db8:0:0:0:ff00:42:8329`, `2001:db8:0:0:0:ff00:42:8329`) r(`2001:db8::ff00:42:8329`, `2001:db8::ff00:42:8329`) r(`2001:DB8::FF00:42:8329`, `2001:DB8::FF00:42:8329`) r(`0000:0000:0000:0000:0000:0000:0000:0001`, `0000:0000:0000:0000:0000:0000:0000:0001`) r(`::1`, `::1`) r(`255.255.255.256`) r(`:1`) reset() opts.Type = "regex" opts.Regex = `(?ms)^[*]?\s(\S+)` r(`* 2b687c2 - test1`, `2b687c2`) opts.Regex = `(?<=got: )sha256.{4}` r(`got: sha256-L8=`, `sha256-L8=`) reset() opts.Type = "word" r(`#one (two) 😍 a-1b `, `#one`, `two`, `a-1b`) r("fōtiz час a\u0310b ", `fōtiz`, `час`, "a\u0310b") reset() tdir := t.TempDir() simple := filepath.Join(tdir, "simple.py") cli_args = []string{"--customize-processing", simple, "extra1"} os.WriteFile(simple, []byte(` def mark(text, args, Mark, extra_cli_args, *a): import re for idx, m in enumerate(re.finditer(r'\w+', text)): start, end = m.span() mark_text = text[start:end].replace('\n', '').replace('\0', '') yield Mark(idx, start, end, mark_text, {"idx": idx, "args": extra_cli_args}) `), 0o600) opts.Type = "regex" opts.CustomizeProcessing = simple marks := r("漢字 b", `漢字`, `b`) if diff := cmp.Diff(marks[0].Groupdict, map[string]any{"idx": float64(0), "args": []any{"extra1"}}); diff != "" { t.Fatalf("Did not get expected groupdict from custom processor:\n%s", diff) } opts.Regex = "b" os.WriteFile(simple, []byte(""), 0o600) r("a b", `b`) } kitty-0.41.1/kittens/hyperlinked_grep/0000775000175000017510000000000014773370543017323 5ustar nileshnileshkitty-0.41.1/kittens/hyperlinked_grep/__init__.py0000664000175000017510000000000014773370543021422 0ustar nileshnileshkitty-0.41.1/kittens/hyperlinked_grep/main.go0000664000175000017510000003061214773370543020600 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package hyperlinked_grep import ( "bytes" "errors" "fmt" "net/url" "os" "os/exec" "path/filepath" "regexp" "strings" "sync" "unicode" "kitty/tools/cli" "kitty/tools/utils" "golang.org/x/sys/unix" ) var _ = fmt.Print var RgExe = sync.OnceValue(func() string { return utils.FindExe("rg") }) func get_options_for_rg() (expecting_args map[string]bool, alias_map map[string]string, err error) { var raw []byte raw, err = exec.Command(RgExe(), "--help").Output() if err != nil { err = fmt.Errorf("Failed to execute rg: %w", err) return } scanner := utils.NewLineScanner(utils.UnsafeBytesToString(raw)) options_started := false expecting_args = make(map[string]bool, 64) alias_map = make(map[string]string, 52) for scanner.Scan() { line := scanner.Text() if options_started { s := strings.TrimLeft(line, " ") indent := len(line) - len(s) if indent < 8 && indent > 0 { expecting_arg := strings.Contains(s, "=") single_letter_aliases := make([]string, 0, 1) long_option_names := make([]string, 0, 1) for _, x := range strings.Split(s, ",") { x = strings.TrimSpace(x) if strings.HasPrefix(x, "--") { lon, _, _ := strings.Cut(x[2:], "=") long_option_names = append(long_option_names, lon) } else if strings.HasPrefix(x, "-") { son, _, _ := strings.Cut(x[1:], " ") single_letter_aliases = append(single_letter_aliases, son) } } if len(long_option_names) == 0 { err = fmt.Errorf("Failed to parse rg help output line: %s", line) return } for _, x := range single_letter_aliases { alias_map[x] = long_option_names[0] } for _, x := range long_option_names[1:] { alias_map[x] = long_option_names[0] } expecting_args[long_option_names[0]] = expecting_arg } } else { if strings.HasSuffix(line, "OPTIONS:") { options_started = true } } } if len(expecting_args) == 0 || len(alias_map) == 0 { err = fmt.Errorf("Failed to parse rg help output, could not find any options") return } return } type kitten_options struct { matching_lines, context_lines, file_headers bool with_filename, heading, line_number bool stats, count, count_matches bool files, files_with_matches, files_without_match bool vimgrep bool } func default_kitten_opts() *kitten_options { return &kitten_options{ matching_lines: true, context_lines: true, file_headers: true, with_filename: true, heading: true, line_number: true, } } func parse_args(args ...string) (delegate_to_rg bool, sanitized_args []string, kitten_opts *kitten_options, err error) { options_that_expect_args, alias_map, err := get_options_for_rg() if err != nil { return } options_that_expect_args["kitten"] = true kitten_opts = default_kitten_opts() sanitized_args = make([]string, 0, len(args)) expecting_option_arg := "" context_separator := "--" field_context_separator := "-" field_match_separator := "-" handle_option_arg := func(key, val string, with_equals bool) error { if key != "kitten" { if with_equals { sanitized_args = append(sanitized_args, "--"+key+"="+val) } else { sanitized_args = append(sanitized_args, "--"+key, val) } } switch key { case "path-separator": if val != string(os.PathSeparator) { delegate_to_rg = true } case "context-separator": context_separator = val case "field-context-separator": field_context_separator = val case "field-match-separator": field_match_separator = val case "kitten": k, v, found := strings.Cut(val, "=") if !found || k != "hyperlink" { return fmt.Errorf("Unknown --kitten option: %s", val) } for _, x := range strings.Split(v, ",") { switch x { case "none": kitten_opts.context_lines = false kitten_opts.file_headers = false kitten_opts.matching_lines = false case "all": kitten_opts.context_lines = true kitten_opts.file_headers = true kitten_opts.matching_lines = true case "matching_lines": kitten_opts.matching_lines = true case "file_headers": kitten_opts.file_headers = true case "context_lines": kitten_opts.context_lines = true default: return fmt.Errorf("hyperlink option invalid: %s", x) } } } return nil } handle_bool_option := func(key string) { switch key { case "no-context-separator": context_separator = "" case "no-filename": kitten_opts.with_filename = false case "with-filename": kitten_opts.with_filename = true case "heading": kitten_opts.heading = true case "no-heading": kitten_opts.heading = false case "line-number": kitten_opts.line_number = true case "no-line-number": kitten_opts.line_number = false case "pretty": kitten_opts.line_number = true kitten_opts.heading = true case "stats": kitten_opts.stats = true case "count": kitten_opts.count = true case "count-matches": kitten_opts.count_matches = true case "files": kitten_opts.files = true case "files-with-matches": kitten_opts.files_with_matches = true case "files-without-match": kitten_opts.files_without_match = true case "vimgrep": kitten_opts.vimgrep = true case "null", "null-data", "type-list", "version", "help": delegate_to_rg = true } } for i, x := range args { if expecting_option_arg != "" { if err = handle_option_arg(expecting_option_arg, x, false); err != nil { return } expecting_option_arg = "" } else { if x == "--" { sanitized_args = append(sanitized_args, args[i:]...) break } if strings.HasPrefix(x, "--") { a, b, found := strings.Cut(x, "=") a = a[2:] q := alias_map[a] if q != "" { a = q } if found { if _, is_known_option := options_that_expect_args[a]; is_known_option { if err = handle_option_arg(a, b, true); err != nil { return } } else { sanitized_args = append(sanitized_args, x) } } else { if options_that_expect_args[a] { expecting_option_arg = a } else { handle_bool_option(a) sanitized_args = append(sanitized_args, x) } } } else if strings.HasPrefix(x, "-") { ok := true chars := make([]string, len(x)-1) for i, ch := range x[1:] { chars[i] = string(ch) _, ok = alias_map[string(ch)] if !ok { sanitized_args = append(sanitized_args, x) break } } if ok { for _, ch := range chars { target := alias_map[ch] if options_that_expect_args[target] { expecting_option_arg = target } else { handle_bool_option(target) sanitized_args = append(sanitized_args, "-"+ch) } } } } else { sanitized_args = append(sanitized_args, x) } } } if !kitten_opts.with_filename || context_separator != "--" || field_context_separator != "-" || field_match_separator != "-" { delegate_to_rg = true } return } type stdout_filter struct { prefix []byte process_line func(string) } func (self *stdout_filter) Write(p []byte) (n int, err error) { n = len(p) for len(p) > 0 { idx := bytes.IndexByte(p, '\n') if idx < 0 { self.prefix = append(self.prefix, p...) break } line := p[:idx] if len(self.prefix) > 0 { self.prefix = append(self.prefix, line...) line = self.prefix } p = p[idx+1:] self.process_line(utils.UnsafeBytesToString(line)) self.prefix = self.prefix[:0] } return } func main(_ *cli.Command, _ *Options, args []string) (rc int, err error) { delegate_to_rg, sanitized_args, kitten_opts, err := parse_args(args...) if err != nil { return 1, err } if delegate_to_rg { sanitized_args = append([]string{"rg"}, sanitized_args...) err = unix.Exec(RgExe(), sanitized_args, os.Environ()) if err != nil { err = fmt.Errorf("Failed to execute rg: %w", err) rc = 1 } return } cmdline := append([]string{"--pretty", "--with-filename"}, sanitized_args...) cmd := exec.Command(RgExe(), cmdline...) cmd.Stdin = os.Stdin cmd.Stderr = os.Stderr buf := stdout_filter{prefix: make([]byte, 0, 8*1024)} cmd.Stdout = &buf sgr_pat := regexp.MustCompile("\x1b\\[.*?m") osc_pat := regexp.MustCompile("\x1b\\].*?\x1b\\\\") num_pat := regexp.MustCompile(`^(\d+)([:-])`) path_with_count_pat := regexp.MustCompile(`^(.*?)(:\d+)`) path_with_linenum_pat := regexp.MustCompile(`^(.*?):(\d+):`) stats_pat := regexp.MustCompile(`^\d+ matches$`) vimgrep_pat := regexp.MustCompile(`^(.*?):(\d+):(\d+):`) in_stats := false in_result := "" hostname := utils.Hostname() get_quoted_url := func(file_path string) string { q, err := filepath.Abs(file_path) if err == nil { file_path = q } file_path = filepath.ToSlash(file_path) file_path = strings.Join(utils.Map(url.PathEscape, strings.Split(file_path, "/")), "/") return "file://" + hostname + file_path } write := func(items ...string) { for _, x := range items { os.Stdout.WriteString(x) } } write_hyperlink := func(url, line, frag string) { write("\033]8;;", url) if frag != "" { write("#", frag) } write("\033\\", line, "\n\033]8;;\033\\") } buf.process_line = func(line string) { line = osc_pat.ReplaceAllLiteralString(line, "") // remove existing hyperlinks clean_line := strings.TrimRightFunc(line, unicode.IsSpace) clean_line = sgr_pat.ReplaceAllLiteralString(clean_line, "") // remove SGR formatting if clean_line == "" { in_result = "" write("\n") } else if in_stats { write(line, "\n") } else if in_result != "" { if kitten_opts.line_number { m := num_pat.FindStringSubmatch(clean_line) if len(m) > 0 { is_match_line := len(m) > 1 && m[2] == ":" if (is_match_line && kitten_opts.matching_lines) || (!is_match_line && kitten_opts.context_lines) { write_hyperlink(in_result, line, m[1]) return } } } write(line, "\n") } else { if strings.TrimSpace(line) != "" { // The option priority should be consistent with ripgrep here. if kitten_opts.stats && !in_stats && stats_pat.MatchString(clean_line) { in_stats = true } else if kitten_opts.count || kitten_opts.count_matches { if m := path_with_count_pat.FindStringSubmatch(clean_line); len(m) > 0 && kitten_opts.file_headers { write_hyperlink(get_quoted_url(m[1]), line, "") return } } else if kitten_opts.files || kitten_opts.files_with_matches || kitten_opts.files_without_match { if kitten_opts.file_headers { write_hyperlink(get_quoted_url(clean_line), line, "") return } } else if kitten_opts.vimgrep || !kitten_opts.heading { var m []string // When the vimgrep option is present, it will take precedence. if kitten_opts.vimgrep { m = vimgrep_pat.FindStringSubmatch(clean_line) } else { m = path_with_linenum_pat.FindStringSubmatch(clean_line) } if len(m) > 0 && (kitten_opts.file_headers || kitten_opts.matching_lines) { write_hyperlink(get_quoted_url(m[1]), line, m[2]) return } } else { in_result = get_quoted_url(clean_line) if kitten_opts.file_headers { write_hyperlink(in_result, line, "") return } } } write(line, "\n") } } err = cmd.Run() var ee *exec.ExitError if err != nil { if errors.As(err, &ee) { return ee.ExitCode(), nil } return 1, fmt.Errorf("Failed to execute rg: %w", err) } return } func specialize_command(hg *cli.Command) { hg.Usage = "arguments for the rg command" hg.ShortDescription = "Add hyperlinks to the output of ripgrep" hg.HelpText = "The hyperlinked_grep kitten is a thin wrapper around the rg command. It automatically adds hyperlinks to the output of rg allowing the user to click on search results to have them open directly in their editor. For details on its usage, see :doc:`/kittens/hyperlinked_grep`." hg.IgnoreAllArgs = true hg.OnlyArgsAllowed = true hg.ArgCompleter = cli.CompletionForWrapper("rg") } type Options struct { } func create_cmd(root *cli.Command, run_func func(*cli.Command, *Options, []string) (int, error)) { ans := root.AddSubCommand(&cli.Command{ Name: "hyperlinked_grep", Run: func(cmd *cli.Command, args []string) (int, error) { opts := Options{} err := cmd.GetOptionValues(&opts) if err != nil { return 1, err } return run_func(cmd, &opts, args) }, Hidden: true, }) specialize_command(ans) clone := root.AddClone(ans.Group, ans) clone.Hidden = false clone.Name = "hyperlinked-grep" } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } kitty-0.41.1/kittens/hyperlinked_grep/main.py0000775000175000017510000000046114773370543020625 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import sys if __name__ == '__main__': raise SystemExit('This should be run as kitten hyperlinked_grep') elif __name__ == '__wrapper_of__': cd = sys.cli_docs # type: ignore cd['wrapper_of'] = 'rg' kitty-0.41.1/kittens/hyperlinked_grep/main_test.go0000664000175000017510000000544614773370543021646 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package hyperlinked_grep import ( "fmt" "kitty/tools/utils/shlex" "testing" "github.com/google/go-cmp/cmp" ) var _ = fmt.Print func TestRgArgParsing(t *testing.T) { if RgExe() == "rg" { t.Skip("Skipping as rg not found in PATH") } check_failure := func(args ...string) { _, _, _, err := parse_args(args...) if err == nil { t.Fatalf("No error when parsing: %#v", args) } } check_failure("--kitten", "xyz") check_failure("--kitten", "xyz=1") check_kitten_opts := func(matching, context, headers bool, args ...string) { _, _, kitten_opts, err := parse_args(args...) if err != nil { t.Fatalf("error when parsing: %#v: %s", args, err) } if matching != kitten_opts.matching_lines { t.Fatalf("Matching lines not correct for: %#v", args) } if context != kitten_opts.context_lines { t.Fatalf("Context lines not correct for: %#v", args) } if headers != kitten_opts.file_headers { t.Fatalf("File headers not correct for: %#v", args) } } check_kitten_opts(true, true, true) check_kitten_opts(false, false, false, "--kitten", "hyperlink=none") check_kitten_opts(false, false, true, "--kitten", "hyperlink=none", "--count", "--kitten=hyperlink=file_headers") check_kitten_opts(false, false, true, "--kitten", "hyperlink=none,file_headers") check_kitten_opts = func(with_filename, heading, line_number bool, args ...string) { _, _, kitten_opts, err := parse_args(args...) if err != nil { t.Fatalf("error when parsing: %#v: %s", args, err) } if with_filename != kitten_opts.with_filename { t.Fatalf("with_filename not correct for: %#v", args) } if heading != kitten_opts.heading { t.Fatalf("heading not correct for: %#v", args) } if line_number != kitten_opts.line_number { t.Fatalf("line_number not correct for: %#v", args) } } check_kitten_opts(true, true, true) check_kitten_opts(true, false, true, "--no-heading") check_kitten_opts(true, true, true, "--no-heading", "--pretty") check_kitten_opts(true, true, true, "--no-heading", "--heading") check_args := func(args, expected string) { a, err := shlex.Split(args) if err != nil { t.Fatal(err) } _, actual, _, err := parse_args(a...) if err != nil { t.Fatalf("error when parsing: %#v: %s", args, err) } ex, err := shlex.Split(expected) if err != nil { t.Fatal(err) } if diff := cmp.Diff(ex, actual); diff != "" { t.Fatalf("args not correct for %s\n%s", args, diff) } } check_args("--count --max-depth 10 --XxX yyy abcd", "--count --max-depth 10 --XxX yyy abcd") check_args("--max-depth=10 --kitten hyperlink=none abcd", "--max-depth=10 abcd") check_args("-m 10 abcd", "--max-count 10 abcd") check_args("-nm 10 abcd", "-n --max-count 10 abcd") check_args("-mn 10 abcd", "-n --max-count 10 abcd") } kitty-0.41.1/kittens/icat/0000775000175000017510000000000014773370543014710 5ustar nileshnileshkitty-0.41.1/kittens/icat/__init__.py0000664000175000017510000000000014773370543017007 0ustar nileshnileshkitty-0.41.1/kittens/icat/detect.go0000664000175000017510000000733114773370543016513 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package icat import ( "errors" "fmt" "os" "time" "kitty/tools/tui/graphics" "kitty/tools/tui/loop" "kitty/tools/utils" "kitty/tools/utils/images" "kitty/tools/utils/shm" ) var _ = fmt.Print func DetectSupport(timeout time.Duration) (memory, files, direct bool, err error) { temp_files_to_delete := make([]string, 0, 8) shm_files_to_delete := make([]shm.MMap, 0, 8) var direct_query_id, file_query_id, memory_query_id uint32 lp, e := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking, loop.NoInBandResizeNotifications) if e != nil { err = e return } print_error := func(format string, args ...any) { lp.Println(fmt.Sprintf(format, args...)) } defer func() { if len(temp_files_to_delete) > 0 && transfer_by_file != supported { for _, name := range temp_files_to_delete { os.Remove(name) } } if len(shm_files_to_delete) > 0 && transfer_by_memory != supported { for _, name := range shm_files_to_delete { _ = name.Unlink() } } }() lp.OnInitialize = func() (string, error) { var iid uint32 _, _ = lp.AddTimer(timeout, false, func(loop.IdType) error { return fmt.Errorf("Timed out waiting for a response from the terminal: %w", os.ErrDeadlineExceeded) }) g := func(t graphics.GRT_t, payload string) uint32 { iid += 1 g1 := &graphics.GraphicsCommand{} g1.SetTransmission(t).SetAction(graphics.GRT_action_query).SetImageId(iid).SetDataWidth(1).SetDataHeight(1).SetFormat( graphics.GRT_format_rgb).SetDataSize(uint64(len(payload))) _ = g1.WriteWithPayloadToLoop(lp, utils.UnsafeStringToBytes(payload)) return iid } direct_query_id = g(graphics.GRT_transmission_direct, "123") tf, err := images.CreateTempInRAM() if err == nil { file_query_id = g(graphics.GRT_transmission_tempfile, tf.Name()) temp_files_to_delete = append(temp_files_to_delete, tf.Name()) if _, err = tf.Write([]byte{1, 2, 3}); err != nil { print_error("Failed to write to temporary file for data transfer, file based transfer is disabled. Error: %v", err) } tf.Close() } else { print_error("Failed to create temporary file for data transfer, file based transfer is disabled. Error: %v", err) } sf, err := shm.CreateTemp("icat-", 3) if err == nil { memory_query_id = g(graphics.GRT_transmission_sharedmem, sf.Name()) shm_files_to_delete = append(shm_files_to_delete, sf) copy(sf.Slice(), []byte{1, 2, 3}) sf.Close() } else { var ens *shm.ErrNotSupported if !errors.As(err, &ens) { print_error("Failed to create SHM for data transfer, memory based transfer is disabled. Error: %v", err) } } lp.QueueWriteString("\x1b[c") return "", nil } lp.OnEscapeCode = func(etype loop.EscapeCodeType, payload []byte) (err error) { switch etype { case loop.CSI: if len(payload) > 3 && payload[0] == '?' && payload[len(payload)-1] == 'c' { lp.Quit(0) return nil } case loop.APC: g := graphics.GraphicsCommandFromAPC(payload) if g != nil { if g.ResponseMessage() == "OK" { switch g.ImageId() { case direct_query_id: direct = true case file_query_id: files = true case memory_query_id: memory = true } } return } } return } lp.OnKeyEvent = func(event *loop.KeyEvent) error { if event.MatchesPressOrRepeat("ctrl+c") { event.Handled = true print_error("Waiting for response from terminal, aborting now could lead to corruption") } if event.MatchesPressOrRepeat("ctrl+z") { event.Handled = true } return nil } err = lp.Run() if err != nil { return } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() return } return } kitty-0.41.1/kittens/icat/magick.go0000664000175000017510000000322114773370543016470 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package icat import ( "fmt" "kitty/tools/tui/graphics" "kitty/tools/utils/images" ) var _ = fmt.Print func Render(path string, ro *images.RenderOptions, frames []images.IdentifyRecord) (ans []*image_frame, err error) { ro.TempfilenameTemplate = shm_template image_frames, filenames, err := images.RenderWithMagick(path, ro, frames) if err == nil { ans = make([]*image_frame, len(image_frames)) for i, x := range image_frames { ans[i] = &image_frame{ filename: filenames[x.Number], filename_is_temporary: true, number: x.Number, width: x.Width, height: x.Height, left: x.Left, top: x.Top, transmission_format: graphics.GRT_format_rgba, delay_ms: int(x.Delay_ms), compose_onto: x.Compose_onto, } if x.Is_opaque { ans[i].transmission_format = graphics.GRT_format_rgb } } } return ans, err } func render_image_with_magick(imgd *image_data, src *opened_input) (err error) { err = src.PutOnFilesystem() if err != nil { return err } frames, err := images.IdentifyWithMagick(src.FileSystemName()) if err != nil { return err } imgd.format_uppercase = frames[0].Fmt_uppercase imgd.canvas_width, imgd.canvas_height = frames[0].Canvas.Width, frames[0].Canvas.Height set_basic_metadata(imgd) if !imgd.needs_conversion { make_output_from_input(imgd, src) return nil } ro := images.RenderOptions{RemoveAlpha: remove_alpha, Flip: flip, Flop: flop} if scale_image(imgd) { ro.ResizeTo.X, ro.ResizeTo.Y = imgd.canvas_width, imgd.canvas_height } imgd.frames, err = Render(src.FileSystemName(), &ro, frames) if err != nil { return err } return nil } kitty-0.41.1/kittens/icat/main.go0000664000175000017510000002005014773370543016160 0ustar nileshnilesh// License: GPLv3 Copyright: 2022, Kovid Goyal, package icat import ( "fmt" "os" "runtime" "strconv" "strings" "sync/atomic" "time" "kitty/tools/cli" "kitty/tools/tty" "kitty/tools/tui" "kitty/tools/tui/graphics" "kitty/tools/utils" "kitty/tools/utils/images" "kitty/tools/utils/style" "golang.org/x/sys/unix" ) var _ = fmt.Print type Place struct { width, height, left, top int } var opts *Options var place *Place var z_index int32 var remove_alpha *images.NRGBColor var flip, flop bool type transfer_mode int const ( unknown transfer_mode = iota unsupported supported ) var transfer_by_file, transfer_by_memory transfer_mode var files_channel chan input_arg var output_channel chan *image_data var num_of_items int var keep_going *atomic.Bool var screen_size *unix.Winsize func send_output(imgd *image_data) { output_channel <- imgd } func parse_mirror() (err error) { flip = opts.Mirror == "both" || opts.Mirror == "vertical" flop = opts.Mirror == "both" || opts.Mirror == "horizontal" return } func parse_background() (err error) { if opts.Background == "" || opts.Background == "none" { return nil } col, err := style.ParseColor(opts.Background) if err != nil { return fmt.Errorf("Invalid value for --background: %w", err) } remove_alpha = &images.NRGBColor{R: col.Red, G: col.Green, B: col.Blue} return } func parse_z_index() (err error) { val := opts.ZIndex var origin int32 if strings.HasPrefix(val, "--") { origin = -1073741824 val = val[1:] } i, err := strconv.ParseInt(val, 10, 32) if err != nil { return fmt.Errorf("Invalid value for --z-index with error: %w", err) } z_index = int32(i) + origin return } func parse_place() (err error) { if opts.Place == "" { return nil } area, pos, found := strings.Cut(opts.Place, "@") if !found { return fmt.Errorf("Invalid --place specification: %s", opts.Place) } w, h, found := strings.Cut(area, "x") if !found { return fmt.Errorf("Invalid --place specification: %s", opts.Place) } l, t, found := strings.Cut(pos, "x") if !found { return fmt.Errorf("Invalid --place specification: %s", opts.Place) } place = &Place{} place.width, err = strconv.Atoi(w) if err != nil { return err } place.height, err = strconv.Atoi(h) if err != nil { return err } place.left, err = strconv.Atoi(l) if err != nil { return err } place.top, err = strconv.Atoi(t) if err != nil { return err } return nil } func print_error(format string, args ...any) { fmt.Fprintf(os.Stderr, format, args...) fmt.Fprintln(os.Stderr) } func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) { opts = o err = parse_place() if err != nil { return 1, err } err = parse_z_index() if err != nil { return 1, err } err = parse_background() if err != nil { return 1, err } err = parse_mirror() if err != nil { return 1, err } if opts.UseWindowSize == "" { if tty.IsTerminal(os.Stdout.Fd()) { screen_size, err = tty.GetSize(int(os.Stdout.Fd())) } else { t, oerr := tty.OpenControllingTerm() if oerr != nil { return 1, fmt.Errorf("Failed to open controlling terminal with error: %w", oerr) } screen_size, err = t.GetSize() } if err != nil { return 1, fmt.Errorf("Failed to query terminal using TIOCGWINSZ with error: %w", err) } } else { parts := strings.SplitN(opts.UseWindowSize, ",", 4) if len(parts) != 4 { return 1, fmt.Errorf("Invalid size specification: " + opts.UseWindowSize) } screen_size = &unix.Winsize{} t := 0 if t, err = strconv.Atoi(parts[0]); err != nil || t < 1 { return 1, fmt.Errorf("Invalid size specification: %s with error: %w", opts.UseWindowSize, err) } screen_size.Col = uint16(t) if t, err = strconv.Atoi(parts[1]); err != nil || t < 1 { return 1, fmt.Errorf("Invalid size specification: %s with error: %w", opts.UseWindowSize, err) } screen_size.Row = uint16(t) if t, err = strconv.Atoi(parts[2]); err != nil || t < 1 { return 1, fmt.Errorf("Invalid size specification: %s with error: %w", opts.UseWindowSize, err) } screen_size.Xpixel = uint16(t) if t, err = strconv.Atoi(parts[3]); err != nil || t < 1 { return 1, fmt.Errorf("Invalid size specification: %s with error: %w", opts.UseWindowSize, err) } screen_size.Ypixel = uint16(t) if screen_size.Xpixel < screen_size.Col { return 1, fmt.Errorf("Invalid size specification: %s with error: The pixel width is smaller than the number of columns", opts.UseWindowSize) } if screen_size.Ypixel < screen_size.Row { return 1, fmt.Errorf("Invalid size specification: %s with error: The pixel height is smaller than the number of rows", opts.UseWindowSize) } } if opts.PrintWindowSize { fmt.Printf("%dx%d", screen_size.Xpixel, screen_size.Ypixel) return 0, nil } if opts.Clear { cc := &graphics.GraphicsCommand{} cc.SetAction(graphics.GRT_action_delete).SetDelete(graphics.GRT_free_visible) if err = cc.WriteWithPayloadTo(os.Stdout, nil); err != nil { return 1, err } } if screen_size.Xpixel == 0 || screen_size.Ypixel == 0 { return 1, fmt.Errorf("Terminal does not support reporting screen sizes in pixels, use a terminal such as kitty, WezTerm, Konsole, etc. that does.") } items, err := process_dirs(args...) if err != nil { return 1, err } if opts.Place != "" && len(items) > 1 { return 1, fmt.Errorf("The --place option can only be used with a single image, not %d", len(items)) } files_channel = make(chan input_arg, len(items)) for _, ia := range items { files_channel <- ia } num_of_items = len(items) output_channel = make(chan *image_data, 1) keep_going = &atomic.Bool{} keep_going.Store(true) if !opts.DetectSupport && num_of_items > 0 { num_workers := utils.Max(1, utils.Min(num_of_items, runtime.NumCPU())) for i := 0; i < num_workers; i++ { go run_worker() } } passthrough_mode := no_passthrough switch opts.Passthrough { case "tmux": passthrough_mode = tmux_passthrough case "detect": if tui.TmuxSocketAddress() != "" { passthrough_mode = tmux_passthrough } } if passthrough_mode == no_passthrough && (opts.TransferMode == "detect" || opts.DetectSupport) { memory, files, direct, err := DetectSupport(time.Duration(opts.DetectionTimeout * float64(time.Second))) if err != nil { return 1, err } if !direct { keep_going.Store(false) return 1, fmt.Errorf("This terminal does not support the graphics protocol use a terminal such as kitty, WezTerm or Konsole that does. If you are running inside a terminal multiplexer such as tmux or screen that might be interfering as well.") } if memory { transfer_by_memory = supported } else { transfer_by_memory = unsupported } if files { transfer_by_file = supported } else { transfer_by_file = unsupported } } if passthrough_mode != no_passthrough { // tmux doesn't allow responses from the terminal so we can't detect if memory or file based transferring is supported transfer_by_memory = unsupported transfer_by_file = unsupported } if opts.DetectSupport { if transfer_by_memory == supported { print_error("memory") } else if transfer_by_file == supported { print_error("files") } else { print_error("stream") } return 0, nil } use_unicode_placeholder := opts.UnicodePlaceholder if passthrough_mode != no_passthrough { use_unicode_placeholder = true } base_id := uint32(opts.ImageId) for num_of_items > 0 { imgd := <-output_channel if base_id != 0 { imgd.image_id = base_id base_id++ if base_id == 0 { base_id++ } } imgd.use_unicode_placeholder = use_unicode_placeholder imgd.passthrough_mode = passthrough_mode num_of_items-- if imgd.err != nil { print_error("Failed to process \x1b[31m%s\x1b[39m: %s\r\n", imgd.source_name, imgd.err) } else { transmit_image(imgd, opts.NoTrailingNewline) if imgd.err != nil { print_error("Failed to transmit \x1b[31m%s\x1b[39m: %s\r\n", imgd.source_name, imgd.err) } } } keep_going.Store(false) if opts.Hold { fmt.Print("\r") if opts.Place != "" { fmt.Println() } tui.HoldTillEnter(false) } return 0, nil } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } kitty-0.41.1/kittens/icat/main.py0000664000175000017510000001445114773370543016213 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2017, Kovid Goyal OPTIONS = '''\ --align type=choices choices=center,left,right default=center Horizontal alignment for the displayed image. --place Choose where on the screen to display the image. The image will be scaled to fit into the specified rectangle. The syntax for specifying rectangles is <:italic:`width`>x<:italic:`height`>@<:italic:`left`>x<:italic:`top`>. All measurements are in cells (i.e. cursor positions) with the origin :italic:`(0, 0)` at the top-left corner of the screen. Note that the :option:`--align` option will horizontally align the image within this rectangle. By default, the image is horizontally centered within the rectangle. Using place will cause the cursor to be positioned at the top left corner of the image, instead of on the line after the image. --scale-up type=bool-set When used in combination with :option:`--place` it will cause images that are smaller than the specified area to be scaled up to use as much of the specified area as possible. --background default=none Specify a background color, this will cause transparent images to be composited on top of the specified color. --mirror default=none type=choices choices=none,horizontal,vertical,both Mirror the image about a horizontal or vertical axis or both. --clear type=bool-set Remove all images currently displayed on the screen. --transfer-mode type=choices choices=detect,file,stream,memory default=detect Which mechanism to use to transfer images to the terminal. The default is to auto-detect. :italic:`file` means to use a temporary file, :italic:`memory` means to use shared memory, :italic:`stream` means to send the data via terminal escape codes. Note that if you use the :italic:`file` or :italic:`memory` transfer modes and you are connecting over a remote session then image display will not work. --detect-support type=bool-set Detect support for image display in the terminal. If not supported, will exit with exit code 1, otherwise will exit with code 0 and print the supported transfer mode to stderr, which can be used with the :option:`--transfer-mode` option. --detection-timeout type=float default=10 The amount of time (in seconds) to wait for a response from the terminal, when detecting image display support. --use-window-size Instead of querying the terminal for the window size, use the specified size, which must be of the format: width_in_cells,height_in_cells,width_in_pixels,height_in_pixels --print-window-size type=bool-set Print out the window size as <:italic:`width`>x<:italic:`height`> (in pixels) and quit. This is a convenience method to query the window size if using :code:`kitten icat` from a scripting language that cannot make termios calls. --stdin type=choices choices=detect,yes,no default=detect Read image data from STDIN. The default is to do it automatically, when STDIN is not a terminal, but you can turn it off or on explicitly, if needed. --silent type=bool-set Not used, present for legacy compatibility. --engine type=choices choices=auto,builtin,magick default=auto The engine used for decoding and processing of images. The default is to use the most appropriate engine. The :code:`builtin` engine uses Go's native imaging libraries. The :code:`magick` engine uses ImageMagick which requires it to be installed on the system. --z-index -z default=0 Z-index of the image. When negative, text will be displayed on top of the image. Use a double minus for values under the threshold for drawing images under cell background colors. For example, :code:`--1` evaluates as -1,073,741,825. --loop -l default=-1 type=int Number of times to loop animations. Negative values loop forever. Zero means only the first frame of the animation is displayed. Otherwise, the animation is looped the specified number of times. --hold type=bool-set Wait for a key press before exiting after displaying the images. --unicode-placeholder type=bool-set Use the Unicode placeholder method to display the images. Useful to display images from within full screen terminal programs that do not understand the kitty graphics protocol such as multiplexers or editors. See :ref:`graphics_unicode_placeholders` for details. Note that when using this method, images placed (with :option:`--place`) that do not fit on the screen, will get wrapped at the screen edge instead of getting truncated. This wrapping is per line and therefore the image will look like it is interleaved with blank lines. --passthrough type=choices choices=detect,tmux,none default=detect Whether to surround graphics commands with escape sequences that allow them to passthrough programs like tmux. The default is to detect when running inside tmux and automatically use the tmux passthrough escape codes. Note that when this option is enabled it implies :option:`--unicode-placeholder` as well. --image-id type=int default=0 The graphics protocol id to use for the created image. Normally, a random id is created if needed. This option allows control of the id. When multiple images are sent, sequential ids starting from the specified id are used. Valid ids are from 1 to 4294967295. Numbers outside this range are automatically wrapped. --no-trailing-newline -n type=bool-set By default, the cursor is moved to the next line after displaying an image. This option, prevents that. Should not be used when catting multiple images. Also has no effect when the :option:`--place` option is used. ''' help_text = ( 'A cat like utility to display images in the terminal.' ' You can specify multiple image files and/or directories.' ' Directories are scanned recursively for image files. If STDIN' ' is not a terminal, image data will be read from it as well.' ' You can also specify HTTP(S) or FTP URLs which will be' ' automatically downloaded and displayed.' ) usage = 'image-file-or-url-or-directory ...' if __name__ == '__main__': raise SystemExit('This should be run as kitten icat') elif __name__ == '__doc__': import sys from kitty.cli import CompletionSpec cd = sys.cli_docs # type: ignore cd['usage'] = usage cd['options'] = lambda: OPTIONS.format() cd['help_text'] = help_text cd['short_desc'] = 'Display images in the terminal' cd['args_completion'] = CompletionSpec.from_string('type:file mime:image/* group:Images') kitty-0.41.1/kittens/icat/native.go0000664000175000017510000001240214773370543016524 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package icat import ( "fmt" "image" "image/gif" "kitty/tools/tty" "kitty/tools/tui/graphics" "kitty/tools/utils" "kitty/tools/utils/images" "kitty/tools/utils/shm" "github.com/edwvee/exiffix" "github.com/kovidgoyal/imaging" ) var _ = fmt.Print func resize_frame(imgd *image_data, img image.Image) (image.Image, image.Rectangle) { b := img.Bounds() left, top, width, height := b.Min.X, b.Min.Y, b.Dx(), b.Dy() new_width := int(imgd.scaled_frac.x * float64(width)) new_height := int(imgd.scaled_frac.y * float64(height)) img = imaging.Resize(img, new_width, new_height, imaging.Lanczos) newleft := int(imgd.scaled_frac.x * float64(left)) newtop := int(imgd.scaled_frac.y * float64(top)) return img, image.Rect(newleft, newtop, newleft+new_width, newtop+new_height) } const shm_template = "kitty-icat-*" func add_frame(ctx *images.Context, imgd *image_data, img image.Image) *image_frame { is_opaque := false if imgd.format_uppercase == "JPEG" { // special cased because EXIF orientation could have already changed this image to an NRGBA making IsOpaque() very slow is_opaque = true } else { is_opaque = images.IsOpaque(img) } b := img.Bounds() if imgd.scaled_frac.x != 0 { img, b = resize_frame(imgd, img) } f := image_frame{width: b.Dx(), height: b.Dy(), number: len(imgd.frames) + 1, left: b.Min.X, top: b.Min.Y} dest_rect := image.Rect(0, 0, f.width, f.height) var final_img image.Image bytes_per_pixel := 4 if is_opaque || remove_alpha != nil { var rgb *images.NRGB bytes_per_pixel = 3 m, err := shm.CreateTemp(shm_template, uint64(f.width*f.height*bytes_per_pixel)) if err != nil { rgb = images.NewNRGB(dest_rect) } else { rgb = &images.NRGB{Pix: m.Slice(), Stride: bytes_per_pixel * f.width, Rect: dest_rect} f.shm = m } f.transmission_format = graphics.GRT_format_rgb f.in_memory_bytes = rgb.Pix final_img = rgb } else { var rgba *image.NRGBA m, err := shm.CreateTemp(shm_template, uint64(f.width*f.height*bytes_per_pixel)) if err != nil { rgba = image.NewNRGBA(dest_rect) } else { rgba = &image.NRGBA{Pix: m.Slice(), Stride: bytes_per_pixel * f.width, Rect: dest_rect} f.shm = m } f.transmission_format = graphics.GRT_format_rgba f.in_memory_bytes = rgba.Pix final_img = rgba } ctx.PasteCenter(final_img, img, remove_alpha) imgd.frames = append(imgd.frames, &f) if flip { ctx.FlipPixelsV(bytes_per_pixel, f.width, f.height, f.in_memory_bytes) if f.height < imgd.canvas_height { f.top = (2*imgd.canvas_height - f.height - f.top) % imgd.canvas_height } } if flop { ctx.FlipPixelsH(bytes_per_pixel, f.width, f.height, f.in_memory_bytes) if f.width < imgd.canvas_width { f.left = (2*imgd.canvas_width - f.width - f.left) % imgd.canvas_width } } return &f } func scale_image(imgd *image_data) bool { if imgd.needs_scaling { width, height := imgd.canvas_width, imgd.canvas_height if imgd.canvas_width < imgd.available_width && opts.ScaleUp && place != nil { r := float64(imgd.available_width) / float64(imgd.canvas_width) imgd.canvas_width, imgd.canvas_height = imgd.available_width, int(r*float64(imgd.canvas_height)) } neww, newh := images.FitImage(imgd.canvas_width, imgd.canvas_height, imgd.available_width, imgd.available_height) imgd.needs_scaling = false imgd.scaled_frac.x = float64(neww) / float64(width) imgd.scaled_frac.y = float64(newh) / float64(height) imgd.canvas_width = int(imgd.scaled_frac.x * float64(width)) imgd.canvas_height = int(imgd.scaled_frac.y * float64(height)) return true } return false } func load_one_frame_image(imgd *image_data, src *opened_input) (img image.Image, err error) { img, _, err = exiffix.Decode(src.file) src.Rewind() if err != nil { return } // reset the sizes as we read EXIF tags here which could have rotated the image imgd.canvas_width = img.Bounds().Dx() imgd.canvas_height = img.Bounds().Dy() set_basic_metadata(imgd) scale_image(imgd) return } var debugprintln = tty.DebugPrintln var _ = debugprintln func (frame *image_frame) set_disposal(anchor_frame int, disposal byte) int { anchor_frame, frame.compose_onto = images.SetGIFFrameDisposal(frame.number, anchor_frame, disposal) return anchor_frame } func (frame *image_frame) set_delay(gap, min_gap int) { frame.delay_ms = utils.Max(min_gap, gap) * 10 if frame.delay_ms == 0 { frame.delay_ms = -1 } } func add_gif_frames(ctx *images.Context, imgd *image_data, gf *gif.GIF) error { min_gap := images.CalcMinimumGIFGap(gf.Delay) scale_image(imgd) anchor_frame := 1 for i, paletted_img := range gf.Image { frame := add_frame(ctx, imgd, paletted_img) frame.set_delay(gf.Delay[i], min_gap) anchor_frame = frame.set_disposal(anchor_frame, gf.Disposal[i]) } return nil } func render_image_with_go(imgd *image_data, src *opened_input) (err error) { ctx := images.Context{} switch { case imgd.format_uppercase == "GIF" && opts.Loop != 0: gif_frames, err := gif.DecodeAll(src.file) src.Rewind() if err != nil { return fmt.Errorf("Failed to decode GIF file with error: %w", err) } err = add_gif_frames(&ctx, imgd, gif_frames) if err != nil { return err } default: img, err := load_one_frame_image(imgd, src) if err != nil { return err } add_frame(&ctx, imgd, img) } return nil } kitty-0.41.1/kittens/icat/process_images.go0000664000175000017510000001771514773370543020255 0ustar nileshnilesh// License: GPLv3 Copyright: 2022, Kovid Goyal, package icat import ( "bytes" "fmt" "image" "image/color" "io" "io/fs" "net/http" "net/url" "os" "path/filepath" "strings" "kitty/tools/tty" "kitty/tools/tui/graphics" "kitty/tools/utils" "kitty/tools/utils/images" "kitty/tools/utils/shm" ) var _ = fmt.Print type BytesBuf struct { data []byte pos int64 } func (self *BytesBuf) Seek(offset int64, whence int) (int64, error) { switch whence { case io.SeekStart: self.pos = offset case io.SeekCurrent: self.pos += offset case io.SeekEnd: self.pos = int64(len(self.data)) + offset default: return self.pos, fmt.Errorf("Unknown value for whence: %#v", whence) } self.pos = utils.Max(0, utils.Min(self.pos, int64(len(self.data)))) return self.pos, nil } func (self *BytesBuf) Read(p []byte) (n int, err error) { nb := utils.Min(int64(len(p)), int64(len(self.data))-self.pos) if nb == 0 { err = io.EOF } else { n = copy(p, self.data[self.pos:self.pos+nb]) self.pos += nb } return } func (self *BytesBuf) Close() error { self.data = nil self.pos = 0 return nil } type input_arg struct { arg string value string is_http_url bool } func is_http_url(arg string) bool { return strings.HasPrefix(arg, "https://") || strings.HasPrefix(arg, "http://") } func process_dirs(args ...string) (results []input_arg, err error) { results = make([]input_arg, 0, 64) if opts.Stdin != "no" && (opts.Stdin == "yes" || !tty.IsTerminal(os.Stdin.Fd())) { results = append(results, input_arg{arg: "/dev/stdin"}) } for _, arg := range args { if arg != "" { if is_http_url(arg) { results = append(results, input_arg{arg: arg, value: arg, is_http_url: true}) } else { if strings.HasPrefix(arg, "file://") { u, err := url.Parse(arg) if err != nil { return nil, &fs.PathError{Op: "Parse", Path: arg, Err: err} } arg = u.Path } s, err := os.Stat(arg) if err != nil { return nil, &fs.PathError{Op: "Stat", Path: arg, Err: err} } if s.IsDir() { if err = filepath.WalkDir(arg, func(path string, d fs.DirEntry, walk_err error) error { if walk_err != nil { if d == nil { err = &fs.PathError{Op: "Stat", Path: arg, Err: walk_err} } return walk_err } if !d.IsDir() { mt := utils.GuessMimeType(path) if strings.HasPrefix(mt, "image/") { results = append(results, input_arg{arg: arg, value: path}) } } return nil }); err != nil { return nil, err } } else { results = append(results, input_arg{arg: arg, value: arg}) } } } } return results, nil } type opened_input struct { file io.ReadSeekCloser name_to_unlink string } func (self *opened_input) Rewind() { if self.file != nil { _, _ = self.file.Seek(0, io.SeekStart) } } func (self *opened_input) Release() { if self.file != nil { self.file.Close() self.file = nil } if self.name_to_unlink != "" { os.Remove(self.name_to_unlink) self.name_to_unlink = "" } } func (self *opened_input) PutOnFilesystem() (err error) { if self.name_to_unlink != "" { return } f, err := images.CreateTempInRAM() if err != nil { return fmt.Errorf("Failed to create a temporary file to store input data with error: %w", err) } self.Rewind() _, err = io.Copy(f, self.file) if err != nil { f.Close() return fmt.Errorf("Failed to copy input data to temporary file with error: %w", err) } self.Release() self.file = f self.name_to_unlink = f.Name() return } func (self *opened_input) FileSystemName() string { return self.name_to_unlink } type image_frame struct { filename string shm shm.MMap in_memory_bytes []byte filename_is_temporary bool width, height, left, top int transmission_format graphics.GRT_f compose_onto int number int disposal_background color.NRGBA delay_ms int } type image_data struct { canvas_width, canvas_height int format_uppercase string available_width, available_height int needs_scaling, needs_conversion bool scaled_frac struct{ x, y float64 } frames []*image_frame image_number uint32 image_id uint32 cell_x_offset int move_x_by int move_to struct{ x, y int } width_cells, height_cells int use_unicode_placeholder bool passthrough_mode passthrough_type // for error reporting err error source_name string } func set_basic_metadata(imgd *image_data) { if imgd.frames == nil { imgd.frames = make([]*image_frame, 0, 32) } imgd.available_width = int(screen_size.Xpixel) imgd.available_height = 10 * imgd.canvas_height if place != nil { imgd.available_width = place.width * int(screen_size.Xpixel) / int(screen_size.Col) imgd.available_height = place.height * int(screen_size.Ypixel) / int(screen_size.Row) } imgd.needs_scaling = imgd.canvas_width > imgd.available_width || imgd.canvas_height > imgd.available_height || opts.ScaleUp imgd.needs_conversion = imgd.needs_scaling || remove_alpha != nil || flip || flop || imgd.format_uppercase != "PNG" } func report_error(source_name, msg string, err error) { imgd := image_data{source_name: source_name, err: fmt.Errorf("%s: %w", msg, err)} send_output(&imgd) } func make_output_from_input(imgd *image_data, f *opened_input) { bb, ok := f.file.(*BytesBuf) frame := image_frame{} imgd.frames = append(imgd.frames, &frame) frame.width = imgd.canvas_width frame.height = imgd.canvas_height if imgd.format_uppercase != "PNG" { panic(fmt.Sprintf("Unknown transmission format: %s", imgd.format_uppercase)) } frame.transmission_format = graphics.GRT_format_png if ok { frame.in_memory_bytes = bb.data } else { frame.filename = f.file.(*os.File).Name() if f.name_to_unlink != "" { frame.filename_is_temporary = true f.name_to_unlink = "" } } } func process_arg(arg input_arg) { var f opened_input if arg.is_http_url { resp, err := http.Get(arg.value) if err != nil { report_error(arg.value, "Could not get", err) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { report_error(arg.value, "Could not get", fmt.Errorf("bad status: %v", resp.Status)) return } dest := bytes.Buffer{} dest.Grow(64 * 1024) _, err = io.Copy(&dest, resp.Body) if err != nil { report_error(arg.value, "Could not download", err) return } f.file = &BytesBuf{data: dest.Bytes()} } else if arg.value == "" { stdin, err := io.ReadAll(os.Stdin) if err != nil { report_error("", "Could not read from", err) return } f.file = &BytesBuf{data: stdin} } else { q, err := os.Open(arg.value) if err != nil { report_error(arg.value, "Could not open", err) return } f.file = q } defer f.Release() can_use_go := false var c image.Config var format string var err error imgd := image_data{source_name: arg.value} if opts.Engine == "auto" || opts.Engine == "native" { c, format, err = image.DecodeConfig(f.file) f.Rewind() can_use_go = err == nil } if !keep_going.Load() { return } if can_use_go { imgd.canvas_width = c.Width imgd.canvas_height = c.Height imgd.format_uppercase = strings.ToUpper(format) set_basic_metadata(&imgd) if !imgd.needs_conversion { make_output_from_input(&imgd, &f) send_output(&imgd) return } err = render_image_with_go(&imgd, &f) if err != nil { report_error(arg.value, "Could not render image to RGB", err) return } } else { err = render_image_with_magick(&imgd, &f) if err != nil { report_error(arg.value, "ImageMagick failed", err) return } } if !keep_going.Load() { return } send_output(&imgd) } func run_worker() { for { select { case arg := <-files_channel: if !keep_going.Load() { return } process_arg(arg) default: return } } } kitty-0.41.1/kittens/icat/transmit.go0000664000175000017510000002704414773370543017107 0ustar nileshnilesh// License: GPLv3 Copyright: 2022, Kovid Goyal, package icat import ( "bytes" "crypto/rand" "encoding/binary" "errors" "fmt" "io" "kitty" "math" not_rand "math/rand/v2" "os" "path/filepath" "strings" "kitty/tools/tui" "kitty/tools/tui/graphics" "kitty/tools/tui/loop" "kitty/tools/utils" "kitty/tools/utils/images" "kitty/tools/utils/shm" ) var _ = fmt.Print type passthrough_type int const ( no_passthrough passthrough_type = iota tmux_passthrough ) func new_graphics_command(imgd *image_data) *graphics.GraphicsCommand { gc := graphics.GraphicsCommand{} switch imgd.passthrough_mode { case tmux_passthrough: gc.WrapPrefix = "\033Ptmux;" gc.WrapSuffix = "\033\\" gc.EncodeSerializedDataFunc = func(x string) string { return strings.ReplaceAll(x, "\033", "\033\033") } } return &gc } func gc_for_image(imgd *image_data, frame_num int, frame *image_frame) *graphics.GraphicsCommand { gc := new_graphics_command(imgd) gc.SetDataWidth(uint64(frame.width)).SetDataHeight(uint64(frame.height)) gc.SetQuiet(graphics.GRT_quiet_silent) gc.SetFormat(frame.transmission_format) if imgd.image_number != 0 { gc.SetImageNumber(imgd.image_number) } if imgd.image_id != 0 { gc.SetImageId(imgd.image_id) } if frame_num == 0 { gc.SetAction(graphics.GRT_action_transmit_and_display) if imgd.use_unicode_placeholder { gc.SetUnicodePlaceholder(graphics.GRT_create_unicode_placeholder) gc.SetColumns(uint64(imgd.width_cells)) gc.SetRows(uint64(imgd.height_cells)) } if imgd.cell_x_offset > 0 { gc.SetXOffset(uint64(imgd.cell_x_offset)) } if z_index != 0 { gc.SetZIndex(z_index) } if place != nil { gc.SetCursorMovement(graphics.GRT_cursor_static) } } else { gc.SetAction(graphics.GRT_action_frame) gc.SetGap(int32(frame.delay_ms)) if frame.compose_onto > 0 { gc.SetOverlaidFrame(uint64(frame.compose_onto)) } else { bg := (uint32(frame.disposal_background.R) << 24) | (uint32(frame.disposal_background.G) << 16) | (uint32(frame.disposal_background.B) << 8) | uint32(frame.disposal_background.A) gc.SetBackgroundColor(bg) } gc.SetLeftEdge(uint64(frame.left)).SetTopEdge(uint64(frame.top)) } return gc } func transmit_shm(imgd *image_data, frame_num int, frame *image_frame) (err error) { var mmap shm.MMap var data_size int64 if frame.in_memory_bytes == nil { f, err := os.Open(frame.filename) if err != nil { return fmt.Errorf("Failed to open image data output file: %s with error: %w", frame.filename, err) } defer f.Close() data_size, _ = f.Seek(0, io.SeekEnd) _, _ = f.Seek(0, io.SeekStart) mmap, err = shm.CreateTemp("icat-*", uint64(data_size)) if err != nil { return fmt.Errorf("Failed to create a SHM file for transmission: %w", err) } dest := mmap.Slice() for len(dest) > 0 { n, err := f.Read(dest) dest = dest[n:] if err != nil { if errors.Is(err, io.EOF) { break } _ = mmap.Unlink() return fmt.Errorf("Failed to read data from image output data file: %w", err) } } } else { if frame.shm == nil { data_size = int64(len(frame.in_memory_bytes)) mmap, err = shm.CreateTemp("icat-*", uint64(data_size)) if err != nil { return fmt.Errorf("Failed to create a SHM file for transmission: %w", err) } copy(mmap.Slice(), frame.in_memory_bytes) } else { mmap = frame.shm frame.shm = nil } } gc := gc_for_image(imgd, frame_num, frame) gc.SetTransmission(graphics.GRT_transmission_sharedmem) gc.SetDataSize(uint64(data_size)) err = gc.WriteWithPayloadTo(os.Stdout, utils.UnsafeStringToBytes(mmap.Name())) mmap.Close() return } func transmit_file(imgd *image_data, frame_num int, frame *image_frame) (err error) { is_temp := false fname := "" var data_size int if frame.in_memory_bytes == nil { is_temp = frame.filename_is_temporary fname, err = filepath.Abs(frame.filename) if err != nil { return fmt.Errorf("Failed to convert image data output file: %s to absolute path with error: %w", frame.filename, err) } frame.filename = "" // so it isn't deleted in cleanup } else { is_temp = true if frame.shm != nil && frame.shm.FileSystemName() != "" { fname = frame.shm.FileSystemName() frame.shm.Close() frame.shm = nil } else { f, err := images.CreateTempInRAM() if err != nil { return fmt.Errorf("Failed to create a temp file for image data transmission: %w", err) } data_size = len(frame.in_memory_bytes) _, err = bytes.NewBuffer(frame.in_memory_bytes).WriteTo(f) f.Close() if err != nil { return fmt.Errorf("Failed to write image data to temp file for transmission: %w", err) } fname = f.Name() } } gc := gc_for_image(imgd, frame_num, frame) if is_temp { gc.SetTransmission(graphics.GRT_transmission_tempfile) } else { gc.SetTransmission(graphics.GRT_transmission_file) } if data_size > 0 { gc.SetDataSize(uint64(data_size)) } return gc.WriteWithPayloadTo(os.Stdout, utils.UnsafeStringToBytes(fname)) } func transmit_stream(imgd *image_data, frame_num int, frame *image_frame) (err error) { data := frame.in_memory_bytes if data == nil { f, err := os.Open(frame.filename) if err != nil { return fmt.Errorf("Failed to open image data output file: %s with error: %w", frame.filename, err) } data, err = io.ReadAll(f) f.Close() if err != nil { return fmt.Errorf("Failed to read data from image output data file: %w", err) } } gc := gc_for_image(imgd, frame_num, frame) return gc.WriteWithPayloadTo(os.Stdout, data) } func calculate_in_cell_x_offset(width, cell_width int) int { extra_pixels := width % cell_width if extra_pixels == 0 { return 0 } switch opts.Align { case "left": return 0 case "right": return cell_width - extra_pixels default: return (cell_width - extra_pixels) / 2 } } func place_cursor(imgd *image_data) { cw := max(int(screen_size.Xpixel)/int(screen_size.Col), 1) ch := max(int(screen_size.Ypixel)/int(screen_size.Row), 1) imgd.cell_x_offset = calculate_in_cell_x_offset(imgd.canvas_width, cw) imgd.width_cells = int(math.Ceil(float64(imgd.canvas_width) / float64(cw))) imgd.height_cells = int(math.Ceil(float64(imgd.canvas_height) / float64(ch))) if place == nil { switch opts.Align { case "center": imgd.move_x_by = (int(screen_size.Col) - imgd.width_cells) / 2 case "right": imgd.move_x_by = (int(screen_size.Col) - imgd.width_cells) } } else { imgd.move_to.x = place.left + 1 imgd.move_to.y = place.top + 1 switch opts.Align { case "center": imgd.move_to.x += (place.width - imgd.width_cells) / 2 case "right": imgd.move_to.x += (place.width - imgd.width_cells) } } } func next_random() (ans uint32) { for ans == 0 { b := make([]byte, 4) _, err := rand.Read(b) if err == nil { ans = binary.LittleEndian.Uint32(b[:]) } else { ans = not_rand.Uint32() } } return ans } func write_unicode_placeholder(imgd *image_data) { prefix := "" foreground := fmt.Sprintf("\033[38:2:%d:%d:%dm", (imgd.image_id>>16)&255, (imgd.image_id>>8)&255, imgd.image_id&255) os.Stdout.WriteString(foreground) restore := "\033[39m" if imgd.move_to.y > 0 { os.Stdout.WriteString(loop.SAVE_CURSOR) restore += loop.RESTORE_CURSOR } else if imgd.move_x_by > 0 { prefix = strings.Repeat(" ", imgd.move_x_by) } defer func() { os.Stdout.WriteString(restore) }() if imgd.move_to.y > 0 { fmt.Printf(loop.MoveCursorToTemplate, imgd.move_to.y, 0) } id_char := string(images.NumberToDiacritic[(imgd.image_id>>24)&255]) for r := 0; r < imgd.height_cells; r++ { if imgd.move_to.x > 0 { fmt.Printf("\x1b[%dC", imgd.move_to.x-1) } else { os.Stdout.WriteString(prefix) } for c := 0; c < imgd.width_cells; c++ { os.Stdout.WriteString(string(kitty.ImagePlaceholderChar) + string(images.NumberToDiacritic[r]) + string(images.NumberToDiacritic[c]) + id_char) } if r < imgd.height_cells-1 { os.Stdout.WriteString("\n\r") } } } var seen_image_ids *utils.Set[uint32] func transmit_image(imgd *image_data, no_trailing_newline bool) { if seen_image_ids == nil { seen_image_ids = utils.NewSet[uint32](32) } defer func() { for _, frame := range imgd.frames { if frame.filename_is_temporary && frame.filename != "" { os.Remove(frame.filename) frame.filename = "" } if frame.shm != nil { _ = frame.shm.Unlink() frame.shm.Close() frame.shm = nil } frame.in_memory_bytes = nil } }() var f func(*image_data, int, *image_frame) error if opts.TransferMode != "detect" { switch opts.TransferMode { case "file": f = transmit_file case "memory": f = transmit_shm case "stream": f = transmit_stream } } if f == nil && transfer_by_memory == supported && imgd.frames[0].in_memory_bytes != nil { f = transmit_shm } if f == nil && transfer_by_file == supported { f = transmit_file } if f == nil { f = transmit_stream } if imgd.image_id == 0 { if imgd.use_unicode_placeholder { for imgd.image_id&0xFF000000 == 0 || imgd.image_id&0x00FFFF00 == 0 || seen_image_ids.Has(imgd.image_id) { // Generate a 32-bit image id using rejection sampling such that the most // significant byte and the two bytes in the middle are non-zero to avoid // collisions with applications that cannot represent non-zero most // significant bytes (which is represented by the third combining character) // or two non-zero bytes in the middle (which requires 24-bit color mode). imgd.image_id = next_random() } seen_image_ids.Add(imgd.image_id) } else { if len(imgd.frames) > 1 { for imgd.image_number == 0 { imgd.image_number = next_random() } } } } place_cursor(imgd) if imgd.use_unicode_placeholder && utils.Max(imgd.width_cells, imgd.height_cells) >= len(images.NumberToDiacritic) { imgd.err = fmt.Errorf("Image too large to be displayed using Unicode placeholders. Maximum size is %dx%d cells", len(images.NumberToDiacritic), len(images.NumberToDiacritic)) return } switch imgd.passthrough_mode { case tmux_passthrough: imgd.err = tui.TmuxAllowPassthrough() if imgd.err != nil { return } } fmt.Print("\r") if !imgd.use_unicode_placeholder { if imgd.move_x_by > 0 { fmt.Printf("\x1b[%dC", imgd.move_x_by) } if imgd.move_to.x > 0 { fmt.Printf(loop.MoveCursorToTemplate, imgd.move_to.y, imgd.move_to.x) } } frame_control_cmd := new_graphics_command(imgd) frame_control_cmd.SetAction(graphics.GRT_action_animate) if imgd.image_id != 0 { frame_control_cmd.SetImageId(imgd.image_id) } else { frame_control_cmd.SetImageNumber(imgd.image_number) } is_animated := len(imgd.frames) > 1 for frame_num, frame := range imgd.frames { err := f(imgd, frame_num, frame) if err != nil { imgd.err = err return } if is_animated { switch frame_num { case 0: // set gap for the first frame and number of loops for the animation c := frame_control_cmd c.SetTargetFrame(uint64(frame.number)) c.SetGap(int32(frame.delay_ms)) switch { case opts.Loop < 0: c.SetNumberOfLoops(1) case opts.Loop > 0: c.SetNumberOfLoops(uint64(opts.Loop) + 1) } if imgd.err = c.WriteWithPayloadTo(os.Stdout, nil); imgd.err != nil { return } case 1: c := frame_control_cmd c.SetAnimationControl(2) // set animation to loading mode if imgd.err = c.WriteWithPayloadTo(os.Stdout, nil); imgd.err != nil { return } } } } if imgd.use_unicode_placeholder { write_unicode_placeholder(imgd) } if is_animated { c := frame_control_cmd c.SetAnimationControl(3) // set animation to normal mode if imgd.err = c.WriteWithPayloadTo(os.Stdout, nil); imgd.err != nil { return } } if imgd.move_to.x == 0 && !no_trailing_newline { fmt.Println() // ensure cursor is on new line } } kitty-0.41.1/kittens/notify/0000775000175000017510000000000014773370543015300 5ustar nileshnileshkitty-0.41.1/kittens/notify/__init__.py0000664000175000017510000000000014773370543017377 0ustar nileshnileshkitty-0.41.1/kittens/notify/main.go0000664000175000017510000002131014773370543016550 0ustar nileshnileshpackage notify import ( "bytes" "encoding/base64" "fmt" "image" "io" "os" "slices" "strconv" "strings" "time" "kitty/tools/cli" "kitty/tools/tty" "kitty/tools/tui/loop" "kitty/tools/utils" ) var _ = fmt.Print const ESC_CODE_PREFIX = "\x1b]99;" const ESC_CODE_SUFFIX = "\x1b\\" const CHUNK_SIZE = 4096 func b64encode(x string) string { return base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(x)) } func check_id_valid(x string) bool { pat := utils.MustCompile(`[^a-zA-Z0-9_+.-]`) return pat.ReplaceAllString(x, "") == x } type parsed_data struct { opts *Options wait_till_closed bool expire_time time.Duration title, body, identifier string image_data []byte initial_msg string } func (p *parsed_data) create_metadata() string { ans := []string{} if p.opts.AppName != "" { ans = append(ans, "f="+b64encode(p.opts.AppName)) } switch p.opts.Urgency { case "low": ans = append(ans, "u=0") case "critical": ans = append(ans, "u=2") } if p.expire_time >= 0 { ans = append(ans, "w="+strconv.FormatInt(p.expire_time.Milliseconds(), 10)) } if p.opts.Type != "" { ans = append(ans, "t="+b64encode(p.opts.Type)) } if p.wait_till_closed { ans = append(ans, "c=1:a=report") } for _, x := range p.opts.Icon { ans = append(ans, "n="+b64encode(x)) } if p.opts.IconCacheId != "" { ans = append(ans, "g="+p.opts.IconCacheId) } if p.opts.SoundName != "system" { ans = append(ans, "s="+b64encode(p.opts.SoundName)) } m := strings.Join(ans, ":") if m != "" { m = ":" + m } return m } var debugprintln = tty.DebugPrintln func (p *parsed_data) generate_chunks(callback func(string)) { prefix := ESC_CODE_PREFIX + "i=" + p.identifier write_chunk := func(middle string) { callback(prefix + middle + ESC_CODE_SUFFIX) } add_payload := func(payload_type, payload string) { if payload == "" { return } p := utils.IfElse(payload_type == "title", "", ":p="+payload_type) payload = b64encode(payload) for len(payload) > 0 { chunk := payload[:min(CHUNK_SIZE, len(payload))] payload = utils.IfElse(len(payload) > len(chunk), payload[len(chunk):], "") write_chunk(":d=0:e=1" + p + ";" + chunk) } } metadata := p.create_metadata() write_chunk(":d=0" + metadata + ";") add_payload("title", p.title) add_payload("body", p.body) if len(p.image_data) > 0 { add_payload("icon", utils.UnsafeBytesToString(p.image_data)) } if len(p.opts.Button) > 0 { add_payload("buttons", strings.Join(p.opts.Button, "\u2028")) } write_chunk(";") } func (p *parsed_data) run_loop() (err error) { lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking, loop.NoInBandResizeNotifications) if err != nil { return err } activated := -1 prefix := ESC_CODE_PREFIX + "i=" + p.identifier poll_for_close := func() { lp.AddTimer(time.Millisecond*50, false, func(_ loop.IdType) error { lp.QueueWriteString(prefix + ":p=alive;" + ESC_CODE_SUFFIX) return nil }) } lp.OnInitialize = func() (string, error) { if p.initial_msg != "" { return p.initial_msg, nil } p.generate_chunks(func(x string) { lp.QueueWriteString(x) }) return "", nil } lp.OnEscapeCode = func(ect loop.EscapeCodeType, data []byte) error { if ect == loop.OSC && bytes.HasPrefix(data, []byte(ESC_CODE_PREFIX[2:])) { raw := utils.UnsafeBytesToString(data[len(ESC_CODE_PREFIX[2:]):]) metadata, payload, _ := strings.Cut(raw, ";") sent_identifier, payload_type := "", "" for _, x := range strings.Split(metadata, ":") { key, val, _ := strings.Cut(x, "=") switch key { case "i": sent_identifier = val case "p": payload_type = val } } if sent_identifier == p.identifier { switch payload_type { case "close": if payload == "untracked" { poll_for_close() } else { lp.Quit(0) } case "alive": live_ids := strings.Split(payload, ",") if slices.Contains(live_ids, p.identifier) { poll_for_close() } else { lp.Quit(0) } case "": if activated, err = strconv.Atoi(utils.IfElse(payload == "", "0", payload)); err != nil { return fmt.Errorf("Got invalid activation response from terminal: %#v", payload) } } } } return nil } close_requested := 0 lp.OnKeyEvent = func(event *loop.KeyEvent) error { if event.MatchesPressOrRepeat("ctrl+c") || event.MatchesPressOrRepeat("esc") { event.Handled = true switch close_requested { case 0: lp.QueueWriteString(prefix + ":p=close;" + ESC_CODE_SUFFIX) lp.Println("Closing notification, please wait...") close_requested++ case 1: key := "Esc" if event.MatchesPressOrRepeat("ctrl+c") { key = "Ctrl+C" } lp.Println(fmt.Sprintf("Waiting for response from terminal, press the %s key again to abort. Note that this might result in garbage being printed to the terminal.", key)) close_requested++ default: return fmt.Errorf("Aborted by user!") } } return nil } err = lp.Run() ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() return } if activated > -1 && err == nil { fmt.Println(activated) } return } func random_ident() (string, error) { return utils.HumanUUID4() } func parse_duration(x string) (ans time.Duration, err error) { switch x { case "never": return 0, nil case "": return -1, nil } trailer := x[len(x)-1] multipler := time.Second switch trailer { case 's': x = x[:len(x)-1] case 'm': x = x[:len(x)-1] multipler = time.Minute case 'h': x = x[:len(x)-1] multipler = time.Hour case 'd': x = x[:len(x)-1] multipler = time.Hour * 24 } val, err := strconv.ParseFloat(x, 64) if err != nil { return ans, err } ans = time.Duration(float64(multipler) * val) return } func (p *parsed_data) load_image_data() (err error) { if p.opts.IconPath == "" { return nil } f, err := os.Open(p.opts.IconPath) if err != nil { return err } defer f.Close() _, imgfmt, err := image.DecodeConfig(f) if _, err = f.Seek(0, io.SeekStart); err != nil { return err } if err == nil && imgfmt != "" && strings.Contains("jpeg jpg gif png", strings.ToLower(imgfmt)) { p.image_data, err = io.ReadAll(f) return } return fmt.Errorf("The icon must be in PNG, JPEG or GIF formats") } func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) { if len(args) == 0 { return 1, fmt.Errorf("Must specify a TITLE for the notification") } var p parsed_data p.opts = opts p.title = args[0] if len(args) > 1 { p.body = strings.Join(args[1:], " ") } ident := opts.Identifier if ident == "" { if ident, err = random_ident(); err != nil { return 1, fmt.Errorf("Failed to generate a random identifier with error: %w", err) } } bad_ident := func(which string) error { return fmt.Errorf("Invalid identifier: %s must be only English letters, numbers, hyphens and underscores.", which) } if !check_id_valid(ident) { return 1, bad_ident(ident) } p.identifier = ident if !check_id_valid(opts.IconCacheId) { return 1, bad_ident(opts.IconCacheId) } if len(p.title) == 0 { if ident == "" { return 1, fmt.Errorf("Must specify a non-empty TITLE for the notification or specify an identifier to close a notification.") } msg := ESC_CODE_PREFIX + "i=" + ident + ":p=close;" + ESC_CODE_SUFFIX if opts.OnlyPrintEscapeCode { _, err = os.Stdout.WriteString(msg) } else if p.wait_till_closed { p.initial_msg = msg err = p.run_loop() } else { var term *tty.Term if term, err = tty.OpenControllingTerm(); err != nil { return 1, fmt.Errorf("Failed to open controlling terminal with error: %w", err) } if _, err = term.WriteString(msg); err != nil { term.RestoreAndClose() return 1, err } term.RestoreAndClose() } } if p.expire_time, err = parse_duration(opts.ExpireAfter); err != nil { return 1, fmt.Errorf("Invalid expire time: %s with error: %w", opts.ExpireAfter, err) } p.wait_till_closed = opts.WaitTillClosed if err = p.load_image_data(); err != nil { return 1, fmt.Errorf("Failed to load image data from %s with error %w", opts.IconPath, err) } if opts.OnlyPrintEscapeCode { p.generate_chunks(func(x string) { if err == nil { _, err = os.Stdout.WriteString(x) } }) } else { if opts.PrintIdentifier { fmt.Println(ident) } if p.wait_till_closed { err = p.run_loop() } else { var term *tty.Term if term, err = tty.OpenControllingTerm(); err != nil { return 1, fmt.Errorf("Failed to open controlling terminal with error: %w", err) } p.generate_chunks(func(x string) { if err == nil { _, err = term.WriteString(x) } }) term.RestoreAndClose() } } if err != nil { rc = 1 } return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } kitty-0.41.1/kittens/notify/main.py0000664000175000017510000001074114773370543016601 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import sys def OPTIONS() -> str: from kitty.constants import standard_icon_names return f''' --icon -n type=list The name of the icon to use for the notification. An icon with this name will be searched for on the computer running the terminal emulator. Can be specified multiple times, the first name that is found will be used. Standard names: {', '.join(sorted(standard_icon_names))} --icon-path -p Path to an image file in PNG/JPEG/GIF formats to use as the icon. If both name and path are specified then first the name will be looked for and if not found then the path will be used. --app-name -a default=kitten-notify The application name for the notification. --button -b type=list Add a button with the specified text to the notification. Can be specified multiple times for multiple buttons. If --wait-till-closed is used then the kitten will print the button number to STDOUT if the user clicks a button. 1 for the first button, 2 for the second button and so on. --urgency -u default=normal choices=normal,low,critical The urgency of the notification. --expire-after -e The duration, for the notification to appear on screen. The default is to use the policy of the OS notification service. A value of :code:`never` means the notification should never expire, however, this may or may not work depending on the policies of the OS notification service. Time is specified in the form NUMBER[SUFFIX] where SUFFIX can be :code:`s` for seconds, :code:`m` for minutes, :code:`h` for hours or :code:`d` for days. Non-integer numbers are allowed. If not specified, seconds is assumed. The notification is guaranteed to be closed automatically after the specified time has elapsed. The notification could be closed before by user action or OS policy. --sound-name -s default=system The name of the sound to play with the notification. :code:`system` means let the notification system use whatever sound it wants. :code:`silent` means prevent any sound from being played. Any other value is passed to the desktop's notification system which may or may not honor it. --type -t The notification type. Can be any string, it is used by users to create filter rules for notifications, so choose something descriptive of the notification's purpose. --identifier -i The identifier of this notification. If a notification with the same identifier is already displayed, it is replaced/updated. --print-identifier -P type=bool-set Print the identifier for the notification to STDOUT. Useful when not specifying your own identifier via the --identifier option. --wait-till-closed --wait-for-completion -w type=bool-set Wait until the notification is closed. If the user activates the notification, "0" is printed to STDOUT before quitting. If a button on the notification is pressed the number corresponding to the button is printed to STDOUT. Press the Esc or Ctrl+C keys to close the notification manually. --only-print-escape-code type=bool-set Only print the escape code to STDOUT. Useful if using this kitten as part of a larger application. If this is specified, the --wait-till-closed option will be used for escape code generation, but no actual waiting will be done. --icon-cache-id -g Identifier to use when caching icons in the terminal emulator. Using an identifier means that icon data needs to be transmitted only once using --icon-path. Subsequent invocations will use the cached icon data, at least until the terminal instance is restarted. This is useful if this kitten is being used inside a larger application, with --only-print-escape-code. ''' help_text = '''\ Send notifications to the user that are displayed to them via the desktop environment's notifications service. Works over SSH as well. To update an existing notification, specify the identifier of the notification with the --identifier option. The value should be the same as the identifier specified for the notification you wish to update. If no title is specified and an identifier is specified using the --identifier option, then instead of creating a new notification, an existing notification with the specified identifier is closed. ''' usage = 'TITLE [BODY ...]' if __name__ == '__main__': raise SystemExit('This should be run as `kitten notify ...`') elif __name__ == '__doc__': cd = sys.cli_docs # type: ignore cd['usage'] = usage cd['options'] = OPTIONS cd['help_text'] = help_text cd['short_desc'] = 'Send notifications to the user' kitty-0.41.1/kittens/pager/0000775000175000017510000000000014773370543015066 5ustar nileshnileshkitty-0.41.1/kittens/pager/__init__.py0000664000175000017510000000000014773370543017165 0ustar nileshnileshkitty-0.41.1/kittens/pager/file_input.go0000664000175000017510000000547714773370543017570 0ustar nileshnilesh// License: GPLv3 Copyright: 2024, Kovid Goyal, package pager import ( "bytes" "errors" "fmt" "io" "os" "strings" "time" "golang.org/x/sys/unix" "kitty/tools/simdstring" ) var _ = fmt.Print func wait_for_file_to_grow(file_name string, limit int64) (err error) { // TODO: Use the fsnotify package to avoid this poll for { time.Sleep(time.Second) s, err := os.Stat(file_name) if err != nil { return err } if s.Size() > limit { break } } return } func read_input(input_file *os.File, input_file_name string, input_channel chan<- input_line_struct, follow_file bool, count_carriage_returns bool) { const buf_capacity = 8192 buf := make([]byte, buf_capacity) output_buf := strings.Builder{} output_buf.Grow(buf_capacity) var err error var n int var total_read int64 var num_carriage_returns int defer func() { _ = input_file.Close() last := input_line_struct{line: output_buf.String(), err: err, num_carriage_returns: num_carriage_returns} if errors.Is(err, io.EOF) { last.err = nil } if len(last.line) > 0 || last.err != nil { input_channel <- last } close(input_channel) }() var process_chunk func([]byte) if count_carriage_returns { process_chunk = func(chunk []byte) { for len(chunk) > 0 { idx := simdstring.IndexByte2(chunk, '\n', '\r') if idx == -1 { _, _ = output_buf.Write(chunk) chunk = nil } switch chunk[idx] { case '\r': num_carriage_returns += 1 default: input_channel <- input_line_struct{line: output_buf.String(), num_carriage_returns: num_carriage_returns, is_a_complete_line: true} num_carriage_returns = 0 output_buf.Reset() output_buf.Grow(buf_capacity) } } } } else { process_chunk = func(chunk []byte) { for len(chunk) > 0 { idx := bytes.IndexByte(chunk, '\n') switch idx { case -1: _, _ = output_buf.Write(chunk) chunk = nil default: _, _ = output_buf.Write(chunk[idx:]) chunk = chunk[idx+1:] input_channel <- input_line_struct{line: output_buf.String(), is_a_complete_line: true} output_buf.Reset() output_buf.Grow(buf_capacity) } } } } for { for err != nil { n, err = input_file.Read(buf) if n > 0 { total_read += int64(n) process_chunk(buf) } if err == unix.EAGAIN || err == unix.EINTR { err = nil } } if !follow_file { break } if errors.Is(err, io.EOF) { input_file.Close() if err = wait_for_file_to_grow(input_file_name, total_read); err != nil { break } if input_file, err = os.Open(input_file_name); err != nil { break } var off int64 if off, err = input_file.Seek(total_read, io.SeekStart); err != nil { break } if off != total_read { err = fmt.Errorf("Failed to seek in %s to: %d", input_file_name, off) break } } } } kitty-0.41.1/kittens/pager/main.go0000664000175000017510000000374714773370543016354 0ustar nileshnilesh// License: GPLv3 Copyright: 2024, Kovid Goyal, package pager // TODO: // Scroll to line when starting // Visual mode elect with copy/paste and copy-on-select // Mouse based wheel scroll, drag to select, drag scroll, double click to select // Hyperlinks: Clicking should delegate to terminal and also allow user to specify action // Keyboard hints mode for clicking hyperlinks // Display images when used as scrollback pager // automatic follow when input is a pipe/tty and on last line like tail -f // syntax highlighting using chroma import ( "fmt" "os" "kitty/tools/cli" "kitty/tools/tty" ) var _ = fmt.Print var debugprintln = tty.DebugPrintln var _ = debugprintln type input_line_struct struct { line string num_carriage_returns int is_a_complete_line bool err error } type global_state_struct struct { input_file_name string opts *Options } var global_state global_state_struct func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) { global_state.opts = opts_ input_channel := make(chan input_line_struct, 4096) var input_file *os.File if len(args) > 1 { return 1, fmt.Errorf("Only a single file can be viewed at a time") } if len(args) == 0 { if tty.IsTerminal(os.Stdin.Fd()) { return 1, fmt.Errorf("STDIN is a terminal and no filename specified. See --help") } input_file = os.Stdin global_state.input_file_name = "/dev/stdin" } else { input_file, err = os.Open(args[0]) if err != nil { return 1, err } if tty.IsTerminal(input_file.Fd()) { return 1, fmt.Errorf("%s is a terminal not paging it", args[0]) } global_state.input_file_name = args[0] } follow := global_state.opts.Follow if follow && global_state.input_file_name == "/dev/stdin" { follow = false } go read_input(input_file, global_state.input_file_name, input_channel, follow, global_state.opts.Role == "scrollback") return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } kitty-0.41.1/kittens/pager/main.py0000664000175000017510000000213014773370543016360 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2024, Kovid Goyal import sys from kitty.cli import CompletionSpec OPTIONS = ''' --role default=pager choices=pager,scrollback The role the pager is used for. The default is a standard less like pager. --follow type=bool-set Follow changes in the specified file, automatically scrolling if currently on the last line. '''.format help_text = '''\ Display text in a pager with various features such as searching, copy/paste, etc. Text can some from the specified file or from STDIN. If no filename is specified and STDIN is not a TTY, it is used. ''' usage = '[filename]' def main(args: list[str]) -> None: raise SystemExit('Must be run as kitten pager') if __name__ == '__main__': main(sys.argv) elif __name__ == '__doc__': cd = sys.cli_docs # type: ignore cd['usage'] = usage cd['options'] = OPTIONS cd['help_text'] = help_text cd['short_desc'] = 'Pretty, side-by-side diffing of files and images' cd['args_completion'] = CompletionSpec.from_string('type:file mime:text/* group:"Text files"') kitty-0.41.1/kittens/panel/0000775000175000017510000000000014773370543015067 5ustar nileshnileshkitty-0.41.1/kittens/panel/__init__.py0000664000175000017510000000000014773370543017166 0ustar nileshnileshkitty-0.41.1/kittens/panel/main.py0000664000175000017510000002570614773370543016377 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import sys from collections.abc import Callable from typing import Any from kitty.cli import parse_args from kitty.cli_stub import PanelCLIOptions from kitty.constants import appname, is_macos, is_wayland from kitty.fast_data_types import ( GLFW_EDGE_BOTTOM, GLFW_EDGE_CENTER, GLFW_EDGE_LEFT, GLFW_EDGE_NONE, GLFW_EDGE_RIGHT, GLFW_EDGE_TOP, GLFW_FOCUS_EXCLUSIVE, GLFW_FOCUS_NOT_ALLOWED, GLFW_FOCUS_ON_DEMAND, GLFW_LAYER_SHELL_BACKGROUND, GLFW_LAYER_SHELL_OVERLAY, GLFW_LAYER_SHELL_PANEL, GLFW_LAYER_SHELL_TOP, glfw_primary_monitor_size, make_x11_window_a_dock_window, ) from kitty.os_window_size import WindowSizeData, edge_spacing from kitty.types import LayerShellConfig from kitty.typing import EdgeLiteral OPTIONS = r''' --lines type=int default=1 The number of lines shown in the panel. Ignored for background, centered, and vertical panels. --columns type=int default=1 The number of columns shown in the panel. Ignored for background, centered, and horizontal panels. --margin-top type=int default=0 Request a given top margin to the compositor. Only works on a Wayland compositor that supports the wlr layer shell protocol. --margin-left type=int default=0 Request a given left margin to the compositor. Only works on a Wayland compositor that supports the wlr layer shell protocol. --margin-bottom type=int default=0 Request a given bottom margin to the compositor. Only works on a Wayland compositor that supports the wlr layer shell protocol. --margin-right type=int default=0 Request a given right margin to the compositor. Only works on a Wayland compositor that supports the wlr layer shell protocol. --edge choices=top,bottom,left,right,background,center,none default=top Which edge of the screen to place the panel on. Note that some window managers (such as i3) do not support placing docked windows on the left and right edges. The value :code:`background` means make the panel the "desktop wallpaper". This is only supported on Wayland, not X11 and note that when using sway if you set a background in your sway config it will cover the background drawn using this kitten. Additionally, there are two Wayland only values: :code:`center` and :code:`none`. The value :code:`center` anchors the panel to all sides and covers the entire display by default. The panel can be shrunk using the margin parameters. The value :code:`none` anchors the panel to the top left corner and should be placed using the margin parameters. --layer choices=background,bottom,top,overlay default=bottom On a Wayland compositor that supports the wlr layer shell protocol, specifies the layer on which the panel should be drawn. This parameter is ignored and set to :code:`background` if :option:`--edge` is set to :code:`background`. --config -c type=list Path to config file to use for kitty when drawing the panel. --override -o type=list Override individual kitty configuration options, can be specified multiple times. Syntax: :italic:`name=value`. For example: :option:`kitty +kitten panel -o` font_size=20 --output-name On Wayland, the panel can only be displayed on a single monitor (output) at a time. This allows you to specify which output is used, by name. If not specified the compositor will choose an output automatically, typically the last output the user interacted with or the primary monitor. --class dest=cls default={appname}-panel condition=not is_macos Set the class part of the :italic:`WM_CLASS` window property. On Wayland, it sets the app id. --name condition=not is_macos Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`) --focus-policy choices=not-allowed,exclusive,on-demand default=not-allowed On a Wayland compositor that supports the wlr layer shell protocol, specify the focus policy for keyboard interactivity with the panel. Please refer to the wlr layer shell protocol documentation for more details. --exclusive-zone type=int default=-1 On a Wayland compositor that supports the wlr layer shell protocol, request a given exclusive zone for the panel. Please refer to the wlr layer shell documentation for more details on the meaning of exclusive and its value. If :option:`--edge` is set to anything other than :code:`center` or :code:`none`, this flag will not have any effect unless the flag :option:`--override-exclusive-zone` is also set. If :option:`--edge` is set to :code:`background`, this option has no effect. --override-exclusive-zone type=bool-set On a Wayland compositor that supports the wlr layer shell protocol, override the default exclusive zone. This has effect only if :option:`--edge` is set to :code:`top`, :code:`left`, :code:`bottom` or :code:`right`. --debug-rendering type=bool-set For internal debugging use. '''.format(appname=appname).format args = PanelCLIOptions() help_text = 'Use a command line program to draw a GPU accelerated panel on your X11 desktop' usage = 'program-to-run' def parse_panel_args(args: list[str]) -> tuple[PanelCLIOptions, list[str]]: return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten panel', result_class=PanelCLIOptions) Strut = tuple[int, int, int, int, int, int, int, int, int, int, int, int] def create_strut( win_id: int, left: int = 0, right: int = 0, top: int = 0, bottom: int = 0, left_start_y: int = 0, left_end_y: int = 0, right_start_y: int = 0, right_end_y: int = 0, top_start_x: int = 0, top_end_x: int = 0, bottom_start_x: int = 0, bottom_end_x: int = 0 ) -> Strut: return left, right, top, bottom, left_start_y, left_end_y, right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x, bottom_end_x def create_top_strut(win_id: int, width: int, height: int) -> Strut: return create_strut(win_id, top=height, top_end_x=width) def create_bottom_strut(win_id: int, width: int, height: int) -> Strut: return create_strut(win_id, bottom=height, bottom_end_x=width) def create_left_strut(win_id: int, width: int, height: int) -> Strut: return create_strut(win_id, left=width, left_end_y=height) def create_right_strut(win_id: int, width: int, height: int) -> Strut: return create_strut(win_id, right=width, right_end_y=height) window_width = window_height = 0 def setup_x11_window(win_id: int) -> None: if is_wayland(): return try: func = globals()[f'create_{args.edge}_strut'] except KeyError: raise SystemExit(f'The value {args.edge} is not support for --edge on X11') strut = func(win_id, window_width, window_height) make_x11_window_a_dock_window(win_id, strut) def initial_window_size_func(opts: WindowSizeData, cached_values: dict[str, Any]) -> Callable[[int, int, float, float, float, float], tuple[int, int]]: def es(which: EdgeLiteral) -> float: return edge_spacing(which, opts) def initial_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y: float, xscale: float, yscale: float) -> tuple[int, int]: if not is_macos and not is_wayland(): # Not sure what the deal with scaling on X11 is xscale = yscale = 1 global window_width, window_height monitor_width, monitor_height = glfw_primary_monitor_size() if args.edge in {'left', 'right'}: spacing = es('left') + es('right') window_width = int(cell_width * args.columns / xscale + (dpi_x / 72) * spacing + 1) window_height = monitor_height elif args.edge in {'top', 'bottom'}: spacing = es('top') + es('bottom') window_height = int(cell_height * args.lines / yscale + (dpi_y / 72) * spacing + 1) window_width = monitor_width elif args.edge in {'background', 'center'}: window_width, window_height = monitor_width, monitor_height else: x_spacing = es('left') + es('right') window_width = int(cell_width * args.columns / xscale + (dpi_x / 72) * x_spacing + 1) y_spacing = es('top') + es('bottom') window_height = int(cell_height * args.lines / yscale + (dpi_y / 72) * y_spacing + 1) return window_width, window_height return initial_window_size def layer_shell_config(opts: PanelCLIOptions) -> LayerShellConfig: ltype = {'background': GLFW_LAYER_SHELL_BACKGROUND, 'bottom': GLFW_LAYER_SHELL_PANEL, 'top': GLFW_LAYER_SHELL_TOP, 'overlay': GLFW_LAYER_SHELL_OVERLAY}.get(opts.layer, GLFW_LAYER_SHELL_PANEL) ltype = GLFW_LAYER_SHELL_BACKGROUND if opts.edge == 'background' else ltype edge = { 'top': GLFW_EDGE_TOP, 'bottom': GLFW_EDGE_BOTTOM, 'left': GLFW_EDGE_LEFT, 'right': GLFW_EDGE_RIGHT, 'center': GLFW_EDGE_CENTER, 'none': GLFW_EDGE_NONE }.get(opts.edge, GLFW_EDGE_TOP) focus_policy = { 'not-allowed': GLFW_FOCUS_NOT_ALLOWED, 'exclusive': GLFW_FOCUS_EXCLUSIVE, 'on-demand': GLFW_FOCUS_ON_DEMAND }.get(opts.focus_policy, GLFW_FOCUS_NOT_ALLOWED) return LayerShellConfig(type=ltype, edge=edge, x_size_in_cells=max(1, opts.columns), y_size_in_cells=max(1, opts.lines), requested_top_margin=max(0, opts.margin_top), requested_left_margin=max(0, opts.margin_left), requested_bottom_margin=max(0, opts.margin_bottom), requested_right_margin=max(0, opts.margin_right), focus_policy=focus_policy, requested_exclusive_zone=opts.exclusive_zone, override_exclusive_zone=opts.override_exclusive_zone, output_name=opts.output_name or '') def main(sys_args: list[str]) -> None: global args if is_macos: raise SystemExit('Currently the panel kitten is not supported on macOS') args, items = parse_panel_args(sys_args[1:]) if not items: raise SystemExit('You must specify the program to run') sys.argv = ['kitty'] if args.debug_rendering: sys.argv.append('--debug-rendering') for config in args.config: sys.argv.extend(('--config', config)) sys.argv.extend(('--class', args.cls)) if args.name: sys.argv.extend(('--name', args.name)) for override in args.override: sys.argv.extend(('--override', override)) sys.argv.append('--override=linux_display_server=auto') sys.argv.extend(items) from kitty.main import main as real_main from kitty.main import run_app run_app.cached_values_name = 'panel' run_app.layer_shell_config = layer_shell_config(args) run_app.first_window_callback = setup_x11_window run_app.initial_window_size_func = initial_window_size_func real_main() if __name__ == '__main__': main(sys.argv) elif __name__ == '__doc__': cd: dict = sys.cli_docs # type: ignore cd['usage'] = usage cd['options'] = OPTIONS cd['help_text'] = help_text cd['short_desc'] = help_text kitty-0.41.1/kittens/query_terminal/0000775000175000017510000000000014773370543017030 5ustar nileshnileshkitty-0.41.1/kittens/query_terminal/__init__.py0000664000175000017510000000000014773370543021127 0ustar nileshnileshkitty-0.41.1/kittens/query_terminal/main.go0000664000175000017510000000337514773370543020313 0ustar nileshnileshpackage query_terminal import ( "bytes" "fmt" "kitty" "os" "slices" "strings" "time" "kitty/tools/cli" "kitty/tools/tui/loop" ) var _ = fmt.Print func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) { queries := kitty.QueryNames if len(args) > 0 && !slices.Contains(args, "all") { queries = make([]string, len(args)) for i, x := range args { if !slices.Contains(kitty.QueryNames, x) { return 1, fmt.Errorf("Unknown query: %s", x) } queries[i] = x } } lp, err := loop.New(loop.NoAlternateScreen, loop.NoKeyboardStateChange, loop.NoMouseTracking, loop.NoRestoreColors, loop.NoInBandResizeNotifications) if err != nil { return 1, err } timed_out := false lp.OnInitialize = func() (string, error) { lp.QueryTerminal(queries...) lp.QueueWriteString("\x1b[c") _, err := lp.AddTimer(time.Duration(opts.WaitFor*float64(time.Second)), false, func(timer_id loop.IdType) error { timed_out = true lp.Quit(1) return nil }) return "", err } buf := strings.Builder{} lp.OnQueryResponse = func(key, val string, found bool) error { if found { fmt.Fprintf(&buf, "%s: %s\n", key, val) } else { fmt.Fprintf(&buf, "%s:\n", key) } return nil } lp.OnEscapeCode = func(typ loop.EscapeCodeType, data []byte) error { if typ == loop.CSI && bytes.HasSuffix(data, []byte{'c'}) { lp.Quit(0) } return nil } err = lp.Run() rc = lp.ExitCode() if err != nil { return 1, err } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() return } os.Stdout.WriteString(buf.String()) if timed_out { return 1, fmt.Errorf("timed out waiting for response from terminal") } return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } kitty-0.41.1/kittens/query_terminal/main.py0000664000175000017510000002131114773370543020324 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import re import sys from binascii import hexlify, unhexlify from contextlib import suppress from typing import get_args from kitty.conf.utils import OSNames, os_name from kitty.constants import appname, str_version from kitty.options.types import Options from kitty.terminfo import names class Query: name: str = '' ans: str = '' help_text: str = '' override_query_name: str = '' @property def query_name(self) -> str: return self.override_query_name or f'kitty-query-{self.name}' def __init__(self) -> None: self.encoded_query_name = hexlify(self.query_name.encode('utf-8')).decode('ascii') self.pat = re.compile(f'\x1bP([01])\\+r{self.encoded_query_name}(.*?)\x1b\\\\'.encode('ascii')) def query_code(self) -> str: return f"\x1bP+q{self.encoded_query_name}\x1b\\" def decode_response(self, res: bytes) -> str: return unhexlify(res).decode('utf-8') def more_needed(self, buffer: bytes) -> bool: m = self.pat.search(buffer) if m is None: return True if m.group(1) == b'1': q = m.group(2) if q.startswith(b'='): with suppress(Exception): self.ans = self.decode_response(memoryview(q)[1:]) return False def output_line(self) -> str: return self.ans @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: raise NotImplementedError() all_queries: dict[str, type[Query]] = {} def query(cls: type[Query]) -> type[Query]: all_queries[cls.name] = cls return cls @query class TerminalName(Query): name: str = 'name' override_query_name: str = 'name' help_text: str = f'Terminal name (e.g. :code:`{names[0]}`)' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: return appname @query class TerminalVersion(Query): name: str = 'version' help_text: str = f'Terminal version (e.g. :code:`{str_version}`)' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: return str_version @query class AllowHyperlinks(Query): name: str = 'allow_hyperlinks' help_text: str = 'The config option :opt:`allow_hyperlinks` in :file:`kitty.conf` for allowing hyperlinks can be :code:`yes`, :code:`no` or :code:`ask`' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: return 'ask' if opts.allow_hyperlinks == 0b11 else ('yes' if opts.allow_hyperlinks else 'no') @query class FontFamily(Query): name: str = 'font_family' help_text: str = 'The current font\'s PostScript name' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: from kitty.fast_data_types import current_fonts cf = current_fonts(os_window_id) return cf['medium'].postscript_name() @query class BoldFont(Query): name: str = 'bold_font' help_text: str = 'The current bold font\'s PostScript name' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: from kitty.fast_data_types import current_fonts cf = current_fonts(os_window_id) return cf['bold'].postscript_name() @query class ItalicFont(Query): name: str = 'italic_font' help_text: str = 'The current italic font\'s PostScript name' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: from kitty.fast_data_types import current_fonts cf = current_fonts(os_window_id) return cf['italic'].postscript_name() @query class BiFont(Query): name: str = 'bold_italic_font' help_text: str = 'The current bold-italic font\'s PostScript name' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: from kitty.fast_data_types import current_fonts cf = current_fonts(os_window_id) return cf['bi'].postscript_name() @query class FontSize(Query): name: str = 'font_size' help_text: str = 'The current font size in pts' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: from kitty.fast_data_types import current_fonts cf = current_fonts(os_window_id) return f'{cf["font_sz_in_pts"]:g}' @query class DpiX(Query): name: str = 'dpi_x' help_text: str = 'The current DPI on the x-axis' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: from kitty.fast_data_types import current_fonts cf = current_fonts(os_window_id) return f'{cf["logical_dpi_x"]:g}' @query class DpiY(Query): name: str = 'dpi_y' help_text: str = 'The current DPI on the y-axis' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: from kitty.fast_data_types import current_fonts cf = current_fonts(os_window_id) return f'{cf["logical_dpi_y"]:g}' @query class Foreground(Query): name: str = 'foreground' help_text: str = 'The current foreground color as a 24-bit # color code' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: from kitty.fast_data_types import get_boss, get_options boss = get_boss() w = boss.window_id_map.get(window_id) if w is None: return opts.foreground.as_sharp return (w.screen.color_profile.default_fg or get_options().foreground).as_sharp @query class Background(Query): name: str = 'background' help_text: str = 'The current background color as a 24-bit # color code' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: from kitty.fast_data_types import get_boss, get_options boss = get_boss() w = boss.window_id_map.get(window_id) if w is None: return opts.background.as_sharp return (w.screen.color_profile.default_bg or get_options().background).as_sharp @query class BackgroundOpacity(Query): name: str = 'background_opacity' help_text: str = 'The current background opacity as a number between 0 and 1' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: from kitty.fast_data_types import background_opacity_of ans = background_opacity_of(os_window_id) if ans is None: ans = 1.0 return f'{ans:g}' @query class ClipboardControl(Query): name: str = 'clipboard_control' help_text: str = 'The config option :opt:`clipboard_control` in :file:`kitty.conf` for allowing reads/writes to/from the clipboard' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> str: return ' '.join(opts.clipboard_control) @query class OSName(Query): name: str = 'os_name' help_text: str = f'The name of the OS the terminal is running on. kitty returns values: {", ".join(sorted(get_args(OSNames)))}' @staticmethod def get_result(opts: Options, window_id: int, os_window_id: int) -> OSNames: return os_name() def get_result(name: str, window_id: int, os_window_id: int) -> str | None: from kitty.fast_data_types import get_options q = all_queries.get(name) if q is None: return None return q.get_result(get_options(), window_id, os_window_id) def options_spec() -> str: return '''\ --wait-for type=float default=10 The amount of time (in seconds) to wait for a response from the terminal, after querying it. ''' help_text = '''\ Query the terminal this kitten is run in for various capabilities. This sends escape codes to the terminal and based on its response prints out data about supported capabilities. Note that this is a blocking operation, since it has to wait for a response from the terminal. You can control the maximum wait time via the :code:`--wait-for` option. The output is lines of the form:: query: data If a particular :italic:`query` is unsupported by the running kitty version, the :italic:`data` will be blank. Note that when calling this from another program, be very careful not to perform any I/O on the terminal device until this kitten exits. Available queries are: {} '''.format('\n'.join( f':code:`{name}`:\n {c.help_text}\n' for name, c in all_queries.items())) usage = '[query1 query2 ...]' if __name__ == '__main__': raise SystemExit('Should be run as kitten hints') elif __name__ == '__doc__': cd = sys.cli_docs # type: ignore cd['usage'] = usage cd['options'] = options_spec cd['help_text'] = help_text cd['short_desc'] = 'Query the terminal for various capabilities' kitty-0.41.1/kittens/remote_file/0000775000175000017510000000000014773370543016262 5ustar nileshnileshkitty-0.41.1/kittens/remote_file/__init__.py0000664000175000017510000000000014773370543020361 0ustar nileshnileshkitty-0.41.1/kittens/remote_file/main.py0000664000175000017510000003324114773370543017563 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import json import os import shlex import shutil import subprocess import sys import tempfile import time from typing import Any, Optional from kitty.cli import parse_args from kitty.cli_stub import RemoteFileCLIOptions from kitty.constants import cache_dir from kitty.typing import BossType from kitty.utils import SSHConnectionData, command_for_open, get_editor, open_cmd from ..tui.handler import result_handler from ..tui.operations import faint, raw_mode, reset_terminal, styled from ..tui.utils import get_key_press is_ssh_kitten_sentinel = '!#*&$#($ssh-kitten)(##$' def key(x: str) -> str: return styled(x, bold=True, fg='green') def option_text() -> str: return '''\ --mode -m choices=ask,edit default=ask Which mode to operate in. --path -p Path to the remote file. --hostname -h Hostname of the remote host. --ssh-connection-data The data used to connect over ssh. ''' def show_error(msg: str) -> None: print(styled(msg, fg='red'), file=sys.stderr) print() print('Press any key to quit', flush=True) with raw_mode(): while True: try: q = sys.stdin.buffer.read(1) if q: break except (KeyboardInterrupt, EOFError): break def ask_action(opts: RemoteFileCLIOptions) -> str: print('What would you like to do with the remote file on {}:'.format(styled(opts.hostname or 'unknown', bold=True, fg='magenta'))) print(styled(opts.path or '', fg='yellow', fg_intense=True)) print() def help_text(x: str) -> str: return faint(x) print('{}dit the file'.format(key('E'))) print(help_text('The file will be downloaded and opened in an editor. Any changes you save will' ' be automatically sent back to the remote machine')) print() print('{}pen the file'.format(key('O'))) print(help_text('The file will be downloaded and opened by the default open program')) print() print('{}ave the file'.format(key('S'))) print(help_text('The file will be downloaded to a destination you select')) print() print('{}ancel'.format(key('C'))) print() sys.stdout.flush() response = get_key_press('ceos', 'c') return {'e': 'edit', 'o': 'open', 's': 'save'}.get(response, 'cancel') def hostname_matches(from_hyperlink: str, actual: str) -> bool: if from_hyperlink == actual: return True if from_hyperlink.partition('.')[0] == actual.partition('.')[0]: return True return False class ControlMaster: def __init__(self, conn_data: SSHConnectionData, remote_path: str, cli_opts: RemoteFileCLIOptions, dest: str = ''): self.conn_data = conn_data self.cli_opts = cli_opts self.remote_path = remote_path self.dest = dest self.tdir = '' self.last_error_log = '' self.cmd_prefix = cmd = [ conn_data.binary, '-o', f'ControlPath=~/.ssh/kitty-rf-{os.getpid()}-%C', '-o', 'TCPKeepAlive=yes', '-o', 'ControlPersist=yes' ] self.is_ssh_kitten = conn_data.binary is is_ssh_kitten_sentinel if self.is_ssh_kitten: del cmd[:] self.batch_cmd_prefix = cmd sk_cmdline = json.loads(conn_data.identity_file) while '-t' in sk_cmdline: sk_cmdline.remove('-t') cmd.extend(sk_cmdline[:-2]) else: if conn_data.port: cmd.extend(['-p', str(conn_data.port)]) if conn_data.identity_file: cmd.extend(['-i', conn_data.identity_file]) self.batch_cmd_prefix = cmd + ['-o', 'BatchMode=yes'] def check_call(self, cmd: list[str]) -> None: p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL) stdout = p.communicate()[0] if p.wait() != 0: out = stdout.decode('utf-8', 'replace') raise Exception(f'The ssh command: {shlex.join(cmd)} failed with exit code {p.returncode} and output: {out}') def __enter__(self) -> 'ControlMaster': if not self.is_ssh_kitten: self.check_call( self.cmd_prefix + ['-o', 'ControlMaster=auto', '-fN', self.conn_data.hostname]) self.check_call( self.batch_cmd_prefix + ['-O', 'check', self.conn_data.hostname]) if not self.dest: self.tdir = tempfile.mkdtemp() self.dest = os.path.join(self.tdir, os.path.basename(self.remote_path)) return self def __exit__(self, *a: Any) -> None: if not self.is_ssh_kitten: subprocess.Popen( self.batch_cmd_prefix + ['-O', 'exit', self.conn_data.hostname], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL ).wait() if self.tdir: shutil.rmtree(self.tdir) @property def is_alive(self) -> bool: if self.is_ssh_kitten: return True return subprocess.Popen( self.batch_cmd_prefix + ['-O', 'check', self.conn_data.hostname], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL ).wait() == 0 def check_hostname_matches(self) -> bool: if self.is_ssh_kitten: return True cp = subprocess.run(self.batch_cmd_prefix + [self.conn_data.hostname, 'hostname', '-f'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) if cp.returncode == 0: q = tuple(filter(None, cp.stdout.decode('utf-8').strip().splitlines()))[-1] if not hostname_matches(self.cli_opts.hostname or '', q): print(reset_terminal(), end='') print(f'The remote hostname {styled(q, fg="green")} does not match the') print(f'hostname in the hyperlink {styled(self.cli_opts.hostname or "", fg="red")}') print('This indicates that kitty has not connected to the correct remote machine.') print('This can happen, for example, when using nested SSH sessions.') print(f'The hostname kitty used to connect was: {styled(self.conn_data.hostname, fg="yellow")}', end='') if self.conn_data.port is not None: print(f' with port: {self.conn_data.port}') print() print() print('Do you want to continue anyway?') print( f'{styled("Y", fg="green")}es', f'{styled("N", fg="red")}o', sep='\t' ) sys.stdout.flush() response = get_key_press('yn', 'n') print(reset_terminal(), end='') return response == 'y' return True def show_error(self, msg: str) -> None: if self.last_error_log: print(self.last_error_log, file=sys.stderr) self.last_error_log = '' show_error(msg) def download(self) -> bool: cmdline = self.batch_cmd_prefix + [self.conn_data.hostname, 'cat', shlex.quote(self.remote_path)] with open(self.dest, 'wb') as f: cp = subprocess.run(cmdline, stdout=f, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL) if cp.returncode != 0: self.last_error_log = f'The command: {shlex.join(cmdline)} failed\n' + cp.stderr.decode() return False return True def upload(self, suppress_output: bool = True) -> bool: cmd_prefix = self.cmd_prefix if suppress_output else self.batch_cmd_prefix cmd = cmd_prefix + [self.conn_data.hostname, 'cat', '>', shlex.quote(self.remote_path)] if not suppress_output: print(shlex.join(cmd)) with open(self.dest, 'rb') as f: if suppress_output: cp = subprocess.run(cmd, stdin=f, capture_output=True) if cp.returncode == 0: return True self.last_error_log = f'The command: {shlex.join(cmd)} failed\n' + cp.stdout.decode() else: return subprocess.run(cmd, stdin=f).returncode == 0 return False Result = Optional[str] def main(args: list[str]) -> Result: msg = 'Ask the user what to do with the remote file. For internal use by kitty, do not run it directly.' try: cli_opts, items = parse_args(args[1:], option_text, '', msg, 'kitty +kitten remote_file', result_class=RemoteFileCLIOptions) except SystemExit as e: if e.code != 0: print(e.args[0]) input('Press Enter to quit') raise SystemExit(e.code) try: action = ask_action(cli_opts) finally: print(reset_terminal(), end='', flush=True) try: return handle_action(action, cli_opts) except Exception: print(reset_terminal(), end='', flush=True) import traceback traceback.print_exc() show_error('Failed with unhandled exception') return None def save_as(conn_data: SSHConnectionData, remote_path: str, cli_opts: RemoteFileCLIOptions) -> None: ddir = cache_dir() os.makedirs(ddir, exist_ok=True) last_used_store_path = os.path.join(ddir, 'remote-file-last-used.txt') try: with open(last_used_store_path) as f: last_used_path = f.read() except FileNotFoundError: last_used_path = tempfile.gettempdir() last_used_file = os.path.join(last_used_path, os.path.basename(remote_path)) print( 'Where do you want to save the file? Leaving it blank will save it as:', styled(last_used_file, fg='yellow') ) print('Relative paths will be resolved from:', styled(os.getcwd(), fg_intense=True, bold=True)) print() from ..tui.path_completer import get_path try: dest = get_path() except (KeyboardInterrupt, EOFError): return if dest: dest = os.path.expandvars(os.path.expanduser(dest)) if os.path.isdir(dest): dest = os.path.join(dest, os.path.basename(remote_path)) with open(last_used_store_path, 'w') as f: f.write(os.path.dirname(os.path.abspath(dest))) else: dest = last_used_file if os.path.exists(dest): print(reset_terminal(), end='') print(f'The file {styled(dest, fg="yellow")} already exists. What would you like to do?') print(f'{key("O")}verwrite {key("A")}bort Auto {key("R")}ename {key("N")}ew name') response = get_key_press('anor', 'a') if response == 'a': return if response == 'n': print(reset_terminal(), end='') return save_as(conn_data, remote_path, cli_opts) if response == 'r': q = dest c = 0 while os.path.exists(q): c += 1 b, ext = os.path.splitext(dest) q = f'{b}-{c}{ext}' dest = q if os.path.dirname(dest): os.makedirs(os.path.dirname(dest), exist_ok=True) with ControlMaster(conn_data, remote_path, cli_opts, dest=dest) as master: if master.check_hostname_matches(): if not master.download(): master.show_error('Failed to copy file from remote machine') def handle_action(action: str, cli_opts: RemoteFileCLIOptions) -> Result: cli_data = json.loads(cli_opts.ssh_connection_data or '') if cli_data and cli_data[0] == is_ssh_kitten_sentinel: conn_data = SSHConnectionData(is_ssh_kitten_sentinel, cli_data[-1], -1, identity_file=json.dumps(cli_data[1:])) else: conn_data = SSHConnectionData(*cli_data) remote_path = cli_opts.path or '' if action == 'open': print('Opening', cli_opts.path, 'from', cli_opts.hostname) dest = os.path.join(tempfile.mkdtemp(), os.path.basename(remote_path)) with ControlMaster(conn_data, remote_path, cli_opts, dest=dest) as master: if master.check_hostname_matches(): if master.download(): return dest master.show_error('Failed to copy file from remote machine') elif action == 'edit': print('Editing', cli_opts.path, 'from', cli_opts.hostname) editor = get_editor() with ControlMaster(conn_data, remote_path, cli_opts) as master: if not master.check_hostname_matches(): return None if not master.download(): master.show_error(f'Failed to download {remote_path}') return None mtime = os.path.getmtime(master.dest) print(reset_terminal(), end='', flush=True) editor_process = subprocess.Popen(editor + [master.dest]) while editor_process.poll() is None: time.sleep(0.1) newmtime = os.path.getmtime(master.dest) if newmtime > mtime: mtime = newmtime if master.is_alive: master.upload() print(reset_terminal(), end='', flush=True) if master.is_alive: if not master.upload(suppress_output=False): master.show_error(f'Failed to upload {remote_path}') else: master.show_error(f'Failed to upload {remote_path}, SSH master process died') elif action == 'save': print('Saving', cli_opts.path, 'from', cli_opts.hostname) save_as(conn_data, remote_path, cli_opts) return None @result_handler() def handle_result(args: list[str], data: Result, target_window_id: int, boss: BossType) -> None: if data: from kitty.fast_data_types import get_options cmd = command_for_open(get_options().open_url_with) open_cmd(cmd, data) if __name__ == '__main__': main(sys.argv) kitty-0.41.1/kittens/resize_window/0000775000175000017510000000000014773370543016660 5ustar nileshnileshkitty-0.41.1/kittens/resize_window/__init__.py0000664000175000017510000000000014773370543020757 0ustar nileshnileshkitty-0.41.1/kittens/resize_window/main.py0000664000175000017510000001140714773370543020161 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import sys from typing import Any from kitty.cli import parse_args from kitty.cli_stub import RCOptions, ResizeCLIOptions from kitty.constants import version from kitty.key_encoding import CTRL, EventType, KeyEvent from kitty.rc.base import command_for_name, parse_subcommand_cli from kitty.remote_control import encode_send, parse_rc_args from kitty.utils import ScreenSize from ..tui.handler import Handler from ..tui.loop import Loop from ..tui.operations import styled global_opts = RCOptions() class Resize(Handler): print_on_fail: str | None = None def __init__(self, opts: ResizeCLIOptions): self.opts = opts def initialize(self) -> None: global global_opts global_opts = parse_rc_args(['kitty', '@resize-window'])[0] self.original_size = self.screen_size self.cmd.set_cursor_visible(False) self.cmd.set_line_wrapping(False) self.draw_screen() def do_window_resize(self, is_decrease: bool = False, is_horizontal: bool = True, reset: bool = False, multiplier: int = 1) -> None: resize_window = command_for_name('resize_window') increment = self.opts.horizontal_increment if is_horizontal else self.opts.vertical_increment increment *= multiplier if is_decrease: increment *= -1 axis = 'reset' if reset else ('horizontal' if is_horizontal else 'vertical') cmdline = [resize_window.name, '--self', f'--increment={increment}', '--axis=' + axis] opts, items = parse_subcommand_cli(resize_window, cmdline) payload = resize_window.message_to_kitty(global_opts, opts, items) send = {'cmd': resize_window.name, 'version': version, 'payload': payload, 'no_response': False} self.write(encode_send(send)) def on_kitty_cmd_response(self, response: dict[str, Any]) -> None: if not response.get('ok'): err = response['error'] if response.get('tb'): err += '\n' + response['tb'] self.print_on_fail = err self.quit_loop(1) return res = response.get('data') if res: self.cmd.bell() def on_text(self, text: str, in_bracketed_paste: bool = False) -> None: text = text.upper() if text in 'WNTSR': self.do_window_resize(is_decrease=text in 'NS', is_horizontal=text in 'WN', reset=text == 'R') elif text == 'Q': self.quit_loop(0) def on_key(self, key_event: KeyEvent) -> None: if key_event.type is EventType.RELEASE: return if key_event.matches('esc'): self.quit_loop(0) return if key_event.key in ('w', 'n', 't', 's') and key_event.mods_without_locks == CTRL: self.do_window_resize(is_decrease=key_event.key in 'ns', is_horizontal=key_event.key in 'wn', multiplier=2) def on_resize(self, new_size: ScreenSize) -> None: self.draw_screen() def draw_screen(self) -> None: self.cmd.clear_screen() print = self.print print(styled('Resize this window', bold=True, fg='gray', fg_intense=True)) print() print('Press one of the following keys:') print(' {}ider'.format(styled('W', fg='green'))) print(' {}arrower'.format(styled('N', fg='green'))) print(' {}aller'.format(styled('T', fg='green'))) print(' {}horter'.format(styled('S', fg='green'))) print(' {}eset'.format(styled('R', fg='red'))) print() print('Press {} to quit resize mode'.format(styled('Esc', italic=True))) print('Hold down {} to double step size'.format(styled('Ctrl', italic=True))) print() print(styled('Sizes', bold=True, fg='white', fg_intense=True)) print(f'Original: {self.original_size.rows} rows {self.original_size.cols} cols') print('Current: {} rows {} cols'.format( styled(str(self.screen_size.rows), fg='magenta'), styled(str(self.screen_size.cols), fg='magenta'))) OPTIONS = r''' --horizontal-increment default=2 type=int The base horizontal increment. --vertical-increment default=2 type=int The base vertical increment. '''.format def main(args: list[str]) -> None: msg = 'Resize the current window' try: cli_opts, items = parse_args(args[1:], OPTIONS, '', msg, 'resize_window', result_class=ResizeCLIOptions) except SystemExit as e: if e.code != 0: print(e.args[0], file=sys.stderr) input('Press Enter to quit') return loop = Loop() handler = Resize(cli_opts) loop.loop(handler) if handler.print_on_fail: print(handler.print_on_fail, file=sys.stderr) input('Press Enter to quit') raise SystemExit(loop.return_code) kitty-0.41.1/kittens/runner.py0000664000175000017510000001577414773370543015671 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import importlib import os import sys from collections.abc import Callable, Generator from contextlib import contextmanager from functools import partial from typing import TYPE_CHECKING, Any, NamedTuple, cast from kitty.constants import list_kitty_resources from kitty.types import run_once from kitty.typing import BossType, WindowType from kitty.utils import resolve_abs_or_config_path aliases = {'url_hints': 'hints'} if TYPE_CHECKING: from kitty.conf.types import Definition else: Definition = object def resolved_kitten(k: str) -> str: ans = aliases.get(k, k) head, tail = os.path.split(ans) tail = tail.replace('-', '_') return os.path.join(head, tail) def path_to_custom_kitten(config_dir: str, kitten: str) -> str: path = resolve_abs_or_config_path(kitten, conf_dir=config_dir) return os.path.abspath(path) @contextmanager def preserve_sys_path() -> Generator[None, None, None]: orig = sys.path[:] try: yield finally: if sys.path != orig: del sys.path[:] sys.path.extend(orig) class CLIOnlyKitten(TypeError): def __init__(self, kitten: str): super().__init__(f'The {kitten} kitten must be run only at the commandline, as: kitten {kitten}') def import_kitten_main_module(config_dir: str, kitten: str) -> dict[str, Any]: if kitten.endswith('.py'): with preserve_sys_path(): path = path_to_custom_kitten(config_dir, kitten) if os.path.dirname(path): sys.path.insert(0, os.path.dirname(path)) with open(path) as f: src = f.read() code = compile(src, path, 'exec') g = {'__name__': 'kitten'} exec(code, g) hr = g.get('handle_result', lambda *a, **kw: None) return {'start': g['main'], 'end': hr} kitten = resolved_kitten(kitten) m = importlib.import_module(f'kittens.{kitten}.main') if not hasattr(m, 'main'): raise CLIOnlyKitten(kitten) return { 'start': getattr(m, 'main'), 'end': getattr(m, 'handle_result', lambda *a, **k: None), } class KittenMetadata(NamedTuple): handle_result: Callable[[Any, int, BossType], None] = lambda *a: None type_of_input: str | None = None no_ui: bool = False has_ready_notification: bool = False open_url_handler: Callable[[BossType, WindowType, str, int, str], bool] | None = None allow_remote_control: bool = False remote_control_password: str | bool = False def create_kitten_handler(kitten: str, orig_args: list[str]) -> KittenMetadata: from kitty.constants import config_dir kitten = resolved_kitten(kitten) m = import_kitten_main_module(config_dir, kitten) main = m['start'] handle_result = m['end'] return KittenMetadata( handle_result=partial(handle_result, [kitten] + orig_args), type_of_input=getattr(handle_result, 'type_of_input', None), no_ui=getattr(handle_result, 'no_ui', False), allow_remote_control=getattr(main, 'allow_remote_control', False), remote_control_password=getattr(main, 'remote_control_password', True), has_ready_notification=getattr(handle_result, 'has_ready_notification', False), open_url_handler=getattr(handle_result, 'open_url_handler', None)) def set_debug(kitten: str) -> None: import builtins from kittens.tui.loop import debug setattr(builtins, 'debug', debug) def launch(args: list[str]) -> None: config_dir, kitten = args[:2] kitten = resolved_kitten(kitten) del args[:2] args = [kitten] + args os.environ['KITTY_CONFIG_DIRECTORY'] = config_dir set_debug(kitten) m = import_kitten_main_module(config_dir, kitten) try: result = m['start'](args) finally: sys.stdin = sys.__stdin__ if result is not None: import base64 import json data = base64.b85encode(json.dumps(result).encode('utf-8')) sys.stdout.buffer.write(b'\x1bP@kitty-kitten-result|') sys.stdout.buffer.write(data) sys.stdout.buffer.write(b'\x1b\\') sys.stderr.flush() sys.stdout.flush() def run_kitten(kitten: str, run_name: str = '__main__') -> None: import runpy original_kitten_name = kitten kitten = resolved_kitten(kitten) set_debug(kitten) if kitten in all_kitten_names(): runpy.run_module(f'kittens.{kitten}.main', run_name=run_name) return # Look for a custom kitten if not kitten.endswith('.py'): kitten += '.py' from kitty.constants import config_dir path = path_to_custom_kitten(config_dir, kitten) if not os.path.exists(path): print('Available builtin kittens:', file=sys.stderr) for kitten in all_kitten_names(): print(kitten, file=sys.stderr) raise SystemExit(f'No kitten named {original_kitten_name}') m = runpy.run_path(path, init_globals={'sys': sys, 'os': os}, run_name='__run_kitten__') from kitty.fast_data_types import set_options try: m['main'](sys.argv) finally: set_options(None) @run_once def all_kitten_names() -> frozenset[str]: ans = [] for name in list_kitty_resources('kittens'): if '__' not in name and '.' not in name and name != 'tui': ans.append(name) return frozenset(ans) def list_kittens() -> None: print('You must specify the name of a kitten to run') print('Choose from:') print() for kitten in all_kitten_names(): print(kitten) def get_kitten_cli_docs(kitten: str) -> Any: setattr(sys, 'cli_docs', {}) run_kitten(kitten, run_name='__doc__') ans = getattr(sys, 'cli_docs') delattr(sys, 'cli_docs') if 'help_text' in ans and 'usage' in ans and 'options' in ans: return ans def get_kitten_wrapper_of(kitten: str) -> str: setattr(sys, 'cli_docs', {}) run_kitten(kitten, run_name='__wrapper_of__') ans = getattr(sys, 'cli_docs') delattr(sys, 'cli_docs') return ans.get('wrapper_of') or '' def get_kitten_completer(kitten: str) -> Any: run_kitten(kitten, run_name='__completer__') ans = getattr(sys, 'kitten_completer', None) if ans is not None: delattr(sys, 'kitten_completer') return ans def get_kitten_conf_docs(kitten: str) -> Definition | None: setattr(sys, 'options_definition', None) run_kitten(kitten, run_name='__conf__') ans = getattr(sys, 'options_definition') delattr(sys, 'options_definition') return cast(Definition, ans) def get_kitten_extra_cli_parsers(kitten: str) -> dict[str,str]: setattr(sys, 'extra_cli_parsers', {}) run_kitten(kitten, run_name='__extra_cli_parsers__') ans = getattr(sys, 'extra_cli_parsers') delattr(sys, 'extra_cli_parsers') return cast(dict[str, str], ans) def main() -> None: try: args = sys.argv[1:] launch(args) except Exception: print('Unhandled exception running kitten:') import traceback traceback.print_exc() input('Press Enter to quit') kitty-0.41.1/kittens/show_key/0000775000175000017510000000000014773370543015620 5ustar nileshnileshkitty-0.41.1/kittens/show_key/__init__.py0000664000175000017510000000000014773370543017717 0ustar nileshnileshkitty-0.41.1/kittens/show_key/kitty.go0000664000175000017510000000352714773370543017322 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package show_key import ( "fmt" "strings" "kitty/tools/cli/markup" "kitty/tools/tui/loop" ) var _ = fmt.Print func csi(csi string) string { return "CSI " + strings.NewReplacer(":", " : ", ";", " ; ").Replace(csi[:len(csi)-1]) + " " + csi[len(csi)-1:] } func run_kitty_loop(_ *Options) (err error) { lp, err := loop.New(loop.FullKeyboardProtocol) if err != nil { return err } ctx := markup.New(true) lp.OnInitialize = func() (string, error) { lp.SetCursorVisible(false) lp.SetWindowTitle("kitty extended keyboard protocol demo") lp.Println("Press any keys - Ctrl+C or Ctrl+D will terminate") return "", nil } lp.OnKeyEvent = func(e *loop.KeyEvent) (err error) { e.Handled = true if e.MatchesPressOrRepeat("ctrl+c") || e.MatchesPressOrRepeat("ctrl+d") { lp.Quit(0) return } mods := e.Mods.String() if mods != "" { mods += "+" } etype := e.Type.String() key := e.Key if key == " " { key = "space" } key = mods + key lp.Printf("%s %s %s\r\n", ctx.Green(key), ctx.Yellow(etype), e.Text) lp.Println(ctx.Cyan(csi(e.CSI))) if e.AlternateKey != "" || e.ShiftedKey != "" { if e.ShiftedKey != "" { lp.QueueWriteString(ctx.Dim("Shifted key: ")) lp.QueueWriteString(e.ShiftedKey + " ") } if e.AlternateKey != "" { lp.QueueWriteString(ctx.Dim("Alternate key: ")) lp.QueueWriteString(e.AlternateKey + " ") } lp.Println() } lp.Println() return } lp.OnText = func(text string, from_key_event bool, in_bracketed_paste bool) error { if from_key_event { return nil } lp.Printf("%s: %s\n\n", ctx.Green("Text"), text) return nil } err = lp.Run() if err != nil { return } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() return } return } kitty-0.41.1/kittens/show_key/legacy.go0000664000175000017510000000314114773370543017412 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package show_key import ( "errors" "fmt" "io" "kitty/tools/cli/markup" "kitty/tools/tty" "os" "golang.org/x/sys/unix" ) var _ = fmt.Print func print_key(buf []byte, ctx *markup.Context) { const ctrl_keys = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" unix := "" send_text := "" for _, ch := range buf { switch { case int(ch) < len(ctrl_keys): unix += "^" + ctrl_keys[ch:ch+1] case ch == 127: unix += "^?" default: unix += string(rune(ch)) } } for _, ch := range string(buf) { q := fmt.Sprintf("%#v", string(ch)) send_text += q[1 : len(q)-1] } os.Stdout.WriteString(unix + "\t\t") os.Stdout.WriteString(ctx.Yellow(send_text) + "\r\n") } func run_legacy_loop(opts *Options) (err error) { term, err := tty.OpenControllingTerm(tty.SetRaw) if err != nil { return err } defer func() { term.RestoreAndClose() }() if opts.KeyMode != "unchanged" { os.Stdout.WriteString("\x1b[?1") switch opts.KeyMode { case "normal": os.Stdout.WriteString("l") default: os.Stdout.WriteString("h") } defer func() { os.Stdout.WriteString("\x1b[?1l") }() } fmt.Print("Press any keys - Ctrl+D will terminate this program\r\n") ctx := markup.New(true) fmt.Print(ctx.Green("UNIX\t\tsend_text\r\n")) buf := make([]byte, 64) for { n, err := term.Read(buf) if err != nil { if errors.Is(err, io.EOF) { break } if !(errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EBUSY)) { return err } } if n > 0 { print_key(buf[:n], ctx) if n == 1 && buf[0] == 4 { break } } } return } kitty-0.41.1/kittens/show_key/main.go0000664000175000017510000000067014773370543017076 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package show_key import ( "fmt" "kitty/tools/cli" ) var _ = fmt.Print func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) { if opts.KeyMode == "kitty" { err = run_kitty_loop(opts) } else { err = run_legacy_loop(opts) } if err != nil { rc = 1 } return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } kitty-0.41.1/kittens/show_key/main.py0000664000175000017510000000152414773370543017120 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import sys OPTIONS = r''' --key-mode -m default=normal type=choices choices=normal,application,kitty,unchanged The keyboard mode to use when showing keys. :code:`normal` mode is with DECCKM reset and :code:`application` mode is with DECCKM set. :code:`kitty` is the full kitty extended keyboard protocol. '''.format help_text = 'Show the codes generated by the terminal for key presses in various keyboard modes' usage = '' def main(args: list[str]) -> None: raise SystemExit('This should be reun as kitten show_key') if __name__ == '__main__': main(sys.argv) elif __name__ == '__doc__': cd = sys.cli_docs # type: ignore cd['usage'] = usage cd['options'] = OPTIONS cd['help_text'] = help_text cd['short_desc'] = help_text kitty-0.41.1/kittens/ssh/0000775000175000017510000000000014773370543014565 5ustar nileshnileshkitty-0.41.1/kittens/ssh/__init__.py0000664000175000017510000000000014773370543016664 0ustar nileshnileshkitty-0.41.1/kittens/ssh/askpass.go0000664000175000017510000000446114773370543016566 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package ssh import ( "encoding/json" "fmt" "os" "strings" "time" "kitty/tools/cli" "kitty/tools/tty" "kitty/tools/utils/shm" ) var _ = fmt.Print func fatal(err error) { cli.ShowError(err) os.Exit(1) } func trigger_ask(name string) { term, err := tty.OpenControllingTerm() if err != nil { fatal(err) } defer term.Close() _, err = term.WriteString("\x1bP@kitty-ask|" + name + "\x1b\\") if err != nil { fatal(err) } } func RunSSHAskpass() { msg := os.Args[len(os.Args)-1] prompt := os.Getenv("SSH_ASKPASS_PROMPT") is_confirm := prompt == "confirm" q_type := "get_line" if is_confirm { q_type = "confirm" } is_fingerprint_check := strings.Contains(msg, "(yes/no/[fingerprint])") q := map[string]any{ "message": msg, "type": q_type, "is_password": !is_fingerprint_check, } data, err := json.Marshal(q) if err != nil { fatal(err) } data_shm, err := shm.CreateTemp("askpass-*", uint64(len(data)+32)) if err != nil { fatal(fmt.Errorf("Failed to create SHM file with error: %w", err)) } defer data_shm.Close() defer func() { _ = data_shm.Unlink() }() data_shm.Slice()[0] = 0 if err = shm.WriteWithSize(data_shm, data, 1); err != nil { fatal(fmt.Errorf("Failed to write to SHM file with error: %w", err)) } if err = data_shm.Flush(); err != nil { fatal(fmt.Errorf("Failed to flush SHM file with error: %w", err)) } trigger_ask(data_shm.Name()) for { time.Sleep(50 * time.Millisecond) if data_shm.Slice()[0] == 1 { break } } data, err = shm.ReadWithSize(data_shm, 1) if err != nil { fatal(fmt.Errorf("Failed to read from SHM file with error: %w", err)) } response := "" if is_confirm { var ok bool err = json.Unmarshal(data, &ok) if err != nil { fatal(fmt.Errorf("Failed to parse response data: %#v with error: %w", string(data), err)) } response = "no" if ok { response = "yes" } } else { err = json.Unmarshal(data, &response) if err != nil { fatal(fmt.Errorf("Failed to parse response data: %#v with error: %w", string(data), err)) } if is_fingerprint_check { response = strings.ToLower(response) if response == "y" { response = "yes" } else if response == "n" { response = "no" } } } if response != "" { fmt.Println(response) } } kitty-0.41.1/kittens/ssh/config.go0000664000175000017510000002462414773370543016371 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package ssh import ( "archive/tar" "encoding/json" "errors" "fmt" "os" "path" "path/filepath" "strings" "time" "kitty/tools/config" "kitty/tools/utils" "kitty/tools/utils/paths" "kitty/tools/utils/shlex" "github.com/bmatcuk/doublestar/v4" "golang.org/x/sys/unix" ) var _ = fmt.Print type EnvInstruction struct { key, val string delete_on_remote, copy_from_local, literal_quote bool } func quote_for_sh(val string, literal_quote bool) string { if literal_quote { return utils.QuoteStringForSH(val) } // See https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html b := strings.Builder{} b.Grow(len(val) + 16) b.WriteRune('"') runes := []rune(val) for i, ch := range runes { if ch == '\\' || ch == '`' || ch == '"' || (ch == '$' && i+1 < len(runes) && runes[i+1] == '(') { // special chars are escaped // $( is escaped to prevent execution b.WriteRune('\\') } b.WriteRune(ch) } b.WriteRune('"') return b.String() } func (self *EnvInstruction) Serialize(for_python bool, get_local_env func(string) (string, bool)) string { var unset func() string var export func(string) string if for_python { dumps := func(x ...any) string { ans, _ := json.Marshal(x) return utils.UnsafeBytesToString(ans) } export = func(val string) string { if val == "" { return fmt.Sprintf("export %s", dumps(self.key)) } return fmt.Sprintf("export %s", dumps(self.key, val, self.literal_quote)) } unset = func() string { return fmt.Sprintf("unset %s", dumps(self.key)) } } else { kq := utils.QuoteStringForSH(self.key) unset = func() string { return fmt.Sprintf("unset %s", kq) } export = func(val string) string { return fmt.Sprintf("export %s=%s", kq, quote_for_sh(val, self.literal_quote)) } } if self.delete_on_remote { return unset() } if self.copy_from_local { val, found := get_local_env(self.key) if !found { return "" } return export(val) } return export(self.val) } func final_env_instructions(for_python bool, get_local_env func(string) (string, bool), env ...*EnvInstruction) string { seen := make(map[string]int, len(env)) ans := make([]string, 0, len(env)) for _, ei := range env { q := ei.Serialize(for_python, get_local_env) if q != "" { if pos, found := seen[ei.key]; found { ans[pos] = q } else { seen[ei.key] = len(ans) ans = append(ans, q) } } } return strings.Join(ans, "\n") } type CopyInstruction struct { local_path, arcname string exclude_patterns []string } func ParseEnvInstruction(spec string) (ans []*EnvInstruction, err error) { const COPY_FROM_LOCAL string = "_kitty_copy_env_var_" ei := &EnvInstruction{} found := false ei.key, ei.val, found = strings.Cut(spec, "=") ei.key = strings.TrimSpace(ei.key) if found { ei.val = strings.TrimSpace(ei.val) if ei.val == COPY_FROM_LOCAL { ei.val = "" ei.copy_from_local = true } } else { ei.delete_on_remote = true } if ei.key == "" { err = fmt.Errorf("The env directive must not be empty") } ans = []*EnvInstruction{ei} return } var paths_ctx *paths.Ctx func resolve_file_spec(spec string, is_glob bool) ([]string, error) { if paths_ctx == nil { paths_ctx = &paths.Ctx{} } ans := os.ExpandEnv(paths_ctx.ExpandHome(spec)) if !filepath.IsAbs(ans) { ans = paths_ctx.AbspathFromHome(ans) } if is_glob { files, err := doublestar.FilepathGlob(ans) if err != nil { return nil, fmt.Errorf("%s is not a valid glob pattern with error: %w", spec, err) } if len(files) == 0 { return nil, fmt.Errorf("%s matches no files", spec) } return files, nil } err := unix.Access(ans, unix.R_OK) if err != nil { if errors.Is(err, os.ErrNotExist) { return nil, fmt.Errorf("%s does not exist", spec) } return nil, fmt.Errorf("Cannot read from: %s with error: %w", spec, err) } return []string{ans}, nil } func get_arcname(loc, dest, home string) (arcname string) { if dest != "" { arcname = dest } else { arcname = filepath.Clean(loc) if strings.HasPrefix(arcname, home) { ra, err := filepath.Rel(home, arcname) if err == nil { arcname = ra } } } prefix := "home/" if strings.HasPrefix(arcname, "/") { prefix = "root" } return prefix + arcname } func ParseCopyInstruction(spec string) (ans []*CopyInstruction, err error) { args, err := shlex.Split("copy " + spec) if err != nil { return nil, err } opts, args, err := parse_copy_args(args) if err != nil { return nil, err } locations := make([]string, 0, len(args)) for _, arg := range args { locs, err := resolve_file_spec(arg, opts.Glob) if err != nil { return nil, err } locations = append(locations, locs...) } if len(locations) == 0 { return nil, fmt.Errorf("No files to copy specified") } if len(locations) > 1 && opts.Dest != "" { return nil, fmt.Errorf("Specifying a remote location with more than one file is not supported") } home := paths_ctx.HomePath() ans = make([]*CopyInstruction, 0, len(locations)) for _, loc := range locations { ci := CopyInstruction{local_path: loc, exclude_patterns: opts.Exclude} if opts.SymlinkStrategy != "preserve" { ci.local_path, err = filepath.EvalSymlinks(loc) if err != nil { return nil, fmt.Errorf("Failed to resolve symlinks in %#v with error: %w", loc, err) } } if opts.SymlinkStrategy == "resolve" { ci.arcname = get_arcname(ci.local_path, opts.Dest, home) } else { ci.arcname = get_arcname(loc, opts.Dest, home) } ans = append(ans, &ci) } return } type file_unique_id struct { dev, inode uint64 } func excluded(pattern, path string) bool { if !strings.ContainsRune(pattern, '/') { path = filepath.Base(path) } if matched, err := doublestar.PathMatch(pattern, path); matched && err == nil { return true } return false } func get_file_data(callback func(h *tar.Header, data []byte) error, seen map[file_unique_id]string, local_path, arcname string, exclude_patterns []string) error { var s unix.Stat_t if err := unix.Lstat(local_path, &s); err != nil { return err } cb := func(h *tar.Header, data []byte, arcname string) error { h.Name = arcname if h.Typeflag == tar.TypeDir { h.Name = strings.TrimRight(h.Name, "/") + "/" } h.Size = int64(len(data)) h.Mode = int64(s.Mode & 0777) // discard the setuid, setgid and sticky bits h.ModTime = time.Unix(s.Mtim.Unix()) h.AccessTime = time.Unix(s.Atim.Unix()) h.ChangeTime = time.Unix(s.Ctim.Unix()) h.Format = tar.FormatPAX return callback(h, data) } // we only copy regular files, directories and symlinks switch s.Mode & unix.S_IFMT { case unix.S_IFBLK, unix.S_IFIFO, unix.S_IFCHR, unix.S_IFSOCK: // ignored case unix.S_IFLNK: // symlink target, err := os.Readlink(local_path) if err != nil { return err } err = cb(&tar.Header{ Typeflag: tar.TypeSymlink, Linkname: target, }, nil, arcname) if err != nil { return err } case unix.S_IFDIR: // directory local_path = filepath.Clean(local_path) type entry struct { path, arcname string } stack := []entry{{local_path, arcname}} for len(stack) > 0 { x := stack[0] stack = stack[1:] entries, err := os.ReadDir(x.path) if err != nil { if x.path == local_path { return err } continue } err = cb(&tar.Header{Typeflag: tar.TypeDir}, nil, x.arcname) if err != nil { return err } for _, e := range entries { entry_path := filepath.Join(x.path, e.Name()) aname := path.Join(x.arcname, e.Name()) ok := true for _, pat := range exclude_patterns { if excluded(pat, entry_path) { ok = false break } } if !ok { continue } if e.IsDir() { stack = append(stack, entry{entry_path, aname}) } else { err = get_file_data(callback, seen, entry_path, aname, exclude_patterns) if err != nil { return err } } } } case unix.S_IFREG: // Regular file fid := file_unique_id{dev: uint64(s.Dev), inode: uint64(s.Ino)} if prev, ok := seen[fid]; ok { // Hard link return cb(&tar.Header{Typeflag: tar.TypeLink, Linkname: prev}, nil, arcname) } seen[fid] = arcname data, err := os.ReadFile(local_path) if err != nil { return err } err = cb(&tar.Header{Typeflag: tar.TypeReg}, data, arcname) if err != nil { return err } } return nil } func (ci *CopyInstruction) get_file_data(callback func(h *tar.Header, data []byte) error, seen map[file_unique_id]string) (err error) { ep := ci.exclude_patterns for _, folder_name := range []string{"__pycache__", ".DS_Store"} { ep = append(ep, "**/"+folder_name, "**/"+folder_name+"/**") } return get_file_data(callback, seen, ci.local_path, ci.arcname, ep) } type ConfigSet struct { all_configs []*Config } func config_for_hostname(hostname_to_match, username_to_match string, cs *ConfigSet) *Config { matcher := func(q *Config) bool { for _, pat := range strings.Split(q.Hostname, " ") { upat := "*" if strings.Contains(pat, "@") { upat, pat, _ = strings.Cut(pat, "@") } var host_matched, user_matched bool if matched, err := filepath.Match(pat, hostname_to_match); matched && err == nil { host_matched = true } if matched, err := filepath.Match(upat, username_to_match); matched && err == nil { user_matched = true } if host_matched && user_matched { return true } } return false } for _, c := range utils.Reversed(cs.all_configs) { if matcher(c) { return c } } return cs.all_configs[0] } func (self *ConfigSet) line_handler(key, val string) error { c := self.all_configs[len(self.all_configs)-1] if key == "hostname" { c = NewConfig() self.all_configs = append(self.all_configs, c) } return c.Parse(key, val) } func load_config(hostname_to_match string, username_to_match string, overrides []string, paths ...string) (*Config, []config.ConfigLine, error) { ans := &ConfigSet{all_configs: []*Config{NewConfig()}} p := config.ConfigParser{LineHandler: ans.line_handler} err := p.LoadConfig("ssh.conf", paths, nil) if err != nil { return nil, nil, err } final_conf := config_for_hostname(hostname_to_match, username_to_match, ans) bad_lines := p.BadLines() if len(overrides) > 0 { h := final_conf.Hostname override_parser := config.ConfigParser{LineHandler: final_conf.Parse} if err = override_parser.ParseOverrides(overrides...); err != nil { return nil, nil, err } bad_lines = append(bad_lines, override_parser.BadLines()...) final_conf.Hostname = h } return final_conf, bad_lines, nil } kitty-0.41.1/kittens/ssh/config_test.go0000664000175000017510000000714414773370543017426 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package ssh import ( "fmt" "kitty/tools/utils" "os" "os/user" "path/filepath" "testing" "github.com/google/go-cmp/cmp" ) var _ = fmt.Print type Pair struct { Input, Uname, Host string } func TestSSHConfigParsing(t *testing.T) { tdir := t.TempDir() hostname := "unmatched" username := "" conf := "" overrides := []string{} for_python := false cf := filepath.Join(tdir, "ssh.conf") rt := func(expected_env ...string) { if err := os.WriteFile(cf, []byte(conf), 0o600); err != nil { t.Fatal(err) } c, bad_lines, err := load_config(hostname, username, overrides, cf) if err != nil { t.Fatal(err) } if len(bad_lines) != 0 { t.Fatalf("Bad config line: %s with error: %s", bad_lines[0].Line, bad_lines[0].Err) } actual := final_env_instructions(for_python, func(key string) (string, bool) { if key == "LOCAL_ENV" { return "LOCAL_VAL", true } return "", false }, c.Env...) if expected_env == nil { expected_env = []string{} } diff := cmp.Diff(expected_env, utils.Splitlines(actual)) if diff != "" { t.Fatalf("Unexpected env for\nhostname: %#v\nusername: %#v\nconf: %s\n%s", hostname, username, conf, diff) } } rt() conf = "env a=b" rt(`export 'a'="b"`) conf = "env a=b" overrides = []string{"env=c=d"} rt(`export 'a'="b"`, `export 'c'="d"`) overrides = nil conf = "env a=\\" rt(`export 'a'="\\"`) conf = `env \ a= \\` conf = "env\n \t \\ a=\n\\\\" rt(`export 'a'="\\"`) conf = ` e \n \v \ a \= \\ \` rt(`export 'a'="\\"`) conf = "env a=b\nhostname 2\nenv a=c\nenv b=b" rt(`export 'a'="b"`) hostname = "2" rt(`export 'a'="c"`, `export 'b'="b"`) conf = "env a=" rt(`export 'a'=""`) conf = "env a" rt(`unset 'a'`) conf = "env a=b\nhostname test@2\nenv a=c\nenv b=b" hostname = "unmatched" rt(`export 'a'="b"`) hostname = "2" rt(`export 'a'="b"`) username = "test" rt(`export 'a'="c"`, `export 'b'="b"`) conf = "env a=b\nhostname 1 2\nenv a=c\nenv b=b" username = "" hostname = "unmatched" rt(`export 'a'="b"`) hostname = "1" rt(`export 'a'="c"`, `export 'b'="b"`) hostname = "2" rt(`export 'a'="c"`, `export 'b'="b"`) for_python = true rt(`export ["a","c",false]`, `export ["b","b",false]`) conf = "env a=" rt(`export ["a"]`) conf = "env a" rt(`unset ["a"]`) conf = "env LOCAL_ENV=_kitty_copy_env_var_" rt(`export ["LOCAL_ENV","LOCAL_VAL",false]`) conf = "env a=b\nhostname 2\ncolor_scheme xyz" hostname = "2" rt() ci, err := ParseCopyInstruction("--exclude moose --dest=target " + cf) if err != nil { t.Fatal(err) } diff := cmp.Diff("home/target", ci[0].arcname) if diff != "" { t.Fatalf("Incorrect arcname:\n%s", diff) } diff = cmp.Diff(cf, ci[0].local_path) if diff != "" { t.Fatalf("Incorrect local_path:\n%s", diff) } diff = cmp.Diff([]string{"moose"}, ci[0].exclude_patterns) if diff != "" { t.Fatalf("Incorrect excludes:\n%s", diff) } ci, err = ParseCopyInstruction("--glob " + filepath.Join(filepath.Dir(cf), "*.conf")) if err != nil { t.Fatal(err) } diff = cmp.Diff(cf, ci[0].local_path) if diff != "" { t.Fatalf("Incorrect local_path:\n%s", diff) } if len(ci) != 1 { t.Fatal(ci) } u, _ := user.Current() un := u.Username for _, x := range []Pair{ {"localhost:12", un, "localhost:12"}, {"@localhost", un, "@localhost"}, {"ssh://@localhost:33", un, "localhost"}, {"me@localhost", "me", "localhost"}, {"ssh://me@localhost:12/something?else=1", "me", "localhost"}, } { ue, uh := get_destination(x.Input) q := Pair{x.Input, ue, uh} if diff := cmp.Diff(x, q); diff != "" { t.Fatalf("Failed: %s", diff) } } } kitty-0.41.1/kittens/ssh/main.go0000664000175000017510000006527214773370543016054 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package ssh import ( "archive/tar" "bytes" "compress/gzip" "encoding/base64" "encoding/json" "errors" "fmt" "io" "io/fs" "kitty" "maps" "net/url" "os" "os/exec" "os/signal" "os/user" "path" "path/filepath" "regexp" "slices" "strconv" "strings" "syscall" "time" "kitty/tools/cli" "kitty/tools/themes" "kitty/tools/tty" "kitty/tools/tui" "kitty/tools/tui/loop" "kitty/tools/tui/shell_integration" "kitty/tools/utils" "kitty/tools/utils/secrets" "kitty/tools/utils/shlex" "kitty/tools/utils/shm" "golang.org/x/sys/unix" ) var _ = fmt.Print func get_destination(hostname string) (username, hostname_for_match string) { u, err := user.Current() if err == nil { username = u.Username } hostname_for_match = hostname parsed := false if strings.HasPrefix(hostname, "ssh://") { p, err := url.Parse(hostname) if err == nil { hostname_for_match = p.Hostname() parsed = true if p.User.Username() != "" { username = p.User.Username() } } } else if strings.Contains(hostname, "@") && hostname[0] != '@' { username, hostname_for_match, _ = strings.Cut(hostname, "@") parsed = true } if !parsed && strings.Contains(hostname, "@") && hostname[0] != '@' { _, hostname_for_match, _ = strings.Cut(hostname, "@") } return } func read_data_from_shared_memory(shm_name string) ([]byte, error) { data, err := shm.ReadWithSizeAndUnlink(shm_name, func(s fs.FileInfo) error { if stat, ok := s.Sys().(syscall.Stat_t); ok { if os.Getuid() != int(stat.Uid) || os.Getgid() != int(stat.Gid) { return fmt.Errorf("Incorrect owner on SHM file") } } if s.Mode().Perm() != 0o600 { return fmt.Errorf("Incorrect permissions on SHM file") } return nil }) return data, err } func add_cloned_env(val string) (ans map[string]string, err error) { data, err := read_data_from_shared_memory(val) if err != nil { return nil, err } err = json.Unmarshal(data, &ans) return ans, err } func parse_kitten_args(found_extra_args []string, username, hostname_for_match string) (overrides []string, literal_env map[string]string, ferr error) { literal_env = make(map[string]string) overrides = make([]string, 0, 4) for i, a := range found_extra_args { if i%2 == 0 { continue } if key, val, found := strings.Cut(a, "="); found { if key == "clone_env" { le, err := add_cloned_env(val) if err != nil { if !errors.Is(err, fs.ErrNotExist) { return nil, nil, ferr } } else if le != nil { literal_env = le } } else if key != "hostname" { overrides = append(overrides, key+"="+val) } } } return } func connection_sharing_args(kitty_pid int) ([]string, error) { rd := utils.RuntimeDir() // Bloody OpenSSH generates a 40 char hash and in creating the socket // appends a 27 char temp suffix to it. Socket max path length is approx // ~104 chars. And on idiotic Apple the path length to the runtime dir // (technically the cache dir since Apple has no runtime dir and thinks it's // a great idea to delete files in /tmp) is ~48 chars. if len(rd) > 35 { idiotic_design := fmt.Sprintf("/tmp/kssh-rdir-%d", os.Geteuid()) if err := utils.AtomicCreateSymlink(rd, idiotic_design); err != nil { return nil, err } rd = idiotic_design } cp := strings.Replace(kitty.SSHControlMasterTemplate, "{kitty_pid}", strconv.Itoa(kitty_pid), 1) cp = strings.Replace(cp, "{ssh_placeholder}", "%C", 1) return []string{ "-o", "ControlMaster=auto", "-o", "ControlPath=" + filepath.Join(rd, cp), "-o", "ControlPersist=yes", "-o", "ServerAliveInterval=60", "-o", "ServerAliveCountMax=5", "-o", "TCPKeepAlive=no", }, nil } func set_askpass() (need_to_request_data bool) { need_to_request_data = true sentinel := filepath.Join(utils.CacheDir(), "openssh-is-new-enough-for-askpass") _, err := os.Stat(sentinel) sentinel_exists := err == nil if sentinel_exists || GetSSHVersion().SupportsAskpassRequire() { if !sentinel_exists { _ = os.WriteFile(sentinel, []byte{0}, 0o644) } need_to_request_data = false } exe, err := os.Executable() if err == nil { os.Setenv("SSH_ASKPASS", exe) os.Setenv("KITTY_KITTEN_RUN_MODULE", "ssh_askpass") if !need_to_request_data { os.Setenv("SSH_ASKPASS_REQUIRE", "force") } } else { need_to_request_data = true } return } type connection_data struct { remote_args []string host_opts *Config hostname_for_match string username string echo_on bool request_data bool literal_env map[string]string listen_on string test_script string dont_create_shm bool shm_name string script_type string rcmd []string replacements map[string]string request_id string bootstrap_script string } func get_effective_ksi_env_var(x string) string { parts := strings.Split(strings.TrimSpace(strings.ToLower(x)), " ") current := utils.NewSetWithItems(parts...) if current.Has("disabled") { return "" } allowed := utils.NewSetWithItems(kitty.AllowedShellIntegrationValues...) if !current.IsSubsetOf(allowed) { return RelevantKittyOpts().Shell_integration } return x } func serialize_env(cd *connection_data, get_local_env func(string) (string, bool)) (string, string) { ksi := "" if cd.host_opts.Shell_integration == "inherited" { ksi = get_effective_ksi_env_var(RelevantKittyOpts().Shell_integration) } else { ksi = get_effective_ksi_env_var(cd.host_opts.Shell_integration) } env := make([]*EnvInstruction, 0, 8) add_env := func(key, val string, fallback ...string) *EnvInstruction { if val == "" && len(fallback) > 0 { val = fallback[0] } if val != "" { env = append(env, &EnvInstruction{key: key, val: val, literal_quote: true}) return env[len(env)-1] } return nil } add_non_literal_env := func(key, val string, fallback ...string) *EnvInstruction { ans := add_env(key, val, fallback...) if ans != nil { ans.literal_quote = false } return ans } for k, v := range cd.literal_env { add_env(k, v) } add_env("TERM", os.Getenv("TERM"), RelevantKittyOpts().Term) add_env("COLORTERM", "truecolor") env = append(env, cd.host_opts.Env...) add_env("KITTY_WINDOW_ID", os.Getenv("KITTY_WINDOW_ID")) add_env("WINDOWID", os.Getenv("WINDOWID")) if ksi != "" { add_env("KITTY_SHELL_INTEGRATION", ksi) } else { env = append(env, &EnvInstruction{key: "KITTY_SHELL_INTEGRATION", delete_on_remote: true}) } add_non_literal_env("KITTY_SSH_KITTEN_DATA_DIR", cd.host_opts.Remote_dir) add_non_literal_env("KITTY_LOGIN_SHELL", cd.host_opts.Login_shell) add_non_literal_env("KITTY_LOGIN_CWD", cd.host_opts.Cwd) if cd.host_opts.Remote_kitty != Remote_kitty_no { add_env("KITTY_REMOTE", cd.host_opts.Remote_kitty.String()) } add_env("KITTY_PUBLIC_KEY", os.Getenv("KITTY_PUBLIC_KEY")) if cd.listen_on != "" { add_env("KITTY_LISTEN_ON", cd.listen_on) } return final_env_instructions(cd.script_type == "py", get_local_env, env...), ksi } func make_tarfile(cd *connection_data, get_local_env func(string) (string, bool)) ([]byte, error) { env_script, ksi := serialize_env(cd, get_local_env) w := bytes.Buffer{} w.Grow(64 * 1024) gw, err := gzip.NewWriterLevel(&w, gzip.BestCompression) if err != nil { return nil, err } tw := tar.NewWriter(gw) rd := strings.TrimRight(cd.host_opts.Remote_dir, "/") seen := make(map[file_unique_id]string, 32) add := func(h *tar.Header, data []byte) (err error) { // some distro's like nix mess with installed file permissions so ensure // files are at least readable and writable by owning user h.Mode |= 0o600 err = tw.WriteHeader(h) if err != nil { return } if data != nil { _, err := tw.Write(data) if err != nil { return err } } return } for _, ci := range cd.host_opts.Copy { err = ci.get_file_data(add, seen) if err != nil { return nil, err } } type fe struct { arcname string data []byte } now := time.Now() add_data := func(items ...fe) error { for _, item := range items { err := add( &tar.Header{ Typeflag: tar.TypeReg, Name: item.arcname, Format: tar.FormatPAX, Size: int64(len(item.data)), Mode: 0o644, ModTime: now, ChangeTime: now, AccessTime: now, }, item.data) if err != nil { return err } } return nil } add_entries := func(prefix string, items ...shell_integration.Entry) error { for _, item := range items { err := add( &tar.Header{ Typeflag: item.Metadata.Typeflag, Name: path.Join(prefix, path.Base(item.Metadata.Name)), Format: tar.FormatPAX, Size: int64(len(item.Data)), Mode: item.Metadata.Mode, ModTime: item.Metadata.ModTime, AccessTime: item.Metadata.AccessTime, ChangeTime: item.Metadata.ChangeTime, }, item.Data) if err != nil { return err } } return nil } if err = add_data(fe{"data.sh", utils.UnsafeStringToBytes(env_script)}); err != nil { return nil, err } if cd.script_type == "sh" { if err = add_data(fe{"bootstrap-utils.sh", shell_integration.Data()[path.Join("shell-integration/ssh/bootstrap-utils.sh")].Data}); err != nil { return nil, err } } if ksi != "" { for _, fname := range shell_integration.Data().FilesMatching( "shell-integration/", "shell-integration/ssh/.+", // bootstrap files are sent as command line args "shell-integration/zsh/kitty.zsh", // backward compat file not needed by ssh kitten ) { arcname := path.Join("home/", rd, "/", path.Dir(fname)) err = add_entries(arcname, shell_integration.Data()[fname]) if err != nil { return nil, err } } } if cd.host_opts.Remote_kitty != Remote_kitty_no { arcname := path.Join("home/", rd, "/kitty") err = add_data(fe{arcname + "/version", utils.UnsafeStringToBytes(kitty.VersionString)}) if err != nil { return nil, err } for _, x := range []string{"kitty", "kitten"} { err = add_entries(path.Join(arcname, "bin"), shell_integration.Data()[path.Join("shell-integration", "ssh", x)]) if err != nil { return nil, err } } } err = add_entries(path.Join("home", ".terminfo"), shell_integration.Data()["terminfo/kitty.terminfo"]) if err == nil { err = add_entries(path.Join("home", ".terminfo", "x"), shell_integration.Data()["terminfo/x/"+kitty.DefaultTermName]) } if err == nil { err = tw.Close() if err == nil { err = gw.Close() } } return w.Bytes(), err } func prepare_home_command(cd *connection_data) string { is_python := cd.script_type == "py" homevar := "" for _, ei := range cd.host_opts.Env { if ei.key == "HOME" && !ei.delete_on_remote { if ei.copy_from_local { homevar = os.Getenv("HOME") } else { homevar = ei.val } } } export_home_cmd := "" if homevar != "" { if is_python { export_home_cmd = base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(homevar)) } else { export_home_cmd = fmt.Sprintf("export HOME=%s; cd \"$HOME\"", utils.QuoteStringForSH(homevar)) } } return export_home_cmd } func prepare_exec_cmd(cd *connection_data) string { // ssh simply concatenates multiple commands using a space see // line 1129 of ssh.c and on the remote side sshd.c runs the // concatenated command as shell -c cmd if cd.script_type == "py" { return base64.RawStdEncoding.EncodeToString(utils.UnsafeStringToBytes(strings.Join(cd.remote_args, " "))) } args := make([]string, len(cd.remote_args)) for i, arg := range cd.remote_args { args[i] = strings.ReplaceAll(arg, "'", "'\"'\"'") } return "unset KITTY_SHELL_INTEGRATION; exec \"$login_shell\" -c '" + strings.Join(args, " ") + "'" } var data_shm shm.MMap func prepare_script(script string, replacements map[string]string) string { if _, found := replacements["EXEC_CMD"]; !found { replacements["EXEC_CMD"] = "" } if _, found := replacements["EXPORT_HOME_CMD"]; !found { replacements["EXPORT_HOME_CMD"] = "" } keys := utils.Keys(replacements) for i, key := range keys { keys[i] = "\\b" + key + "\\b" } pat := regexp.MustCompile(strings.Join(keys, "|")) return pat.ReplaceAllStringFunc(script, func(key string) string { return replacements[key] }) } func bootstrap_script(cd *connection_data) (err error) { if cd.request_id == "" { cd.request_id = os.Getenv("KITTY_PID") + "-" + os.Getenv("KITTY_WINDOW_ID") } export_home_cmd := prepare_home_command(cd) exec_cmd := "" if len(cd.remote_args) > 0 { exec_cmd = prepare_exec_cmd(cd) } pw, err := secrets.TokenHex() if err != nil { return err } tfd, err := make_tarfile(cd, os.LookupEnv) if err != nil { return err } data := map[string]string{ "tarfile": base64.StdEncoding.EncodeToString(tfd), "pw": pw, "hostname": cd.hostname_for_match, "username": cd.username, } encoded_data, err := json.Marshal(data) if err == nil && !cd.dont_create_shm { data_shm, err = shm.CreateTemp(fmt.Sprintf("kssh-%d-", os.Getpid()), uint64(len(encoded_data)+8)) if err == nil { err = shm.WriteWithSize(data_shm, encoded_data, 0) if err == nil { err = data_shm.Flush() } } } if err != nil { return err } if !cd.dont_create_shm { cd.shm_name = data_shm.Name() } sensitive_data := map[string]string{"REQUEST_ID": cd.request_id, "DATA_PASSWORD": pw, "PASSWORD_FILENAME": cd.shm_name} replacements := map[string]string{ "EXPORT_HOME_CMD": export_home_cmd, "EXEC_CMD": exec_cmd, "TEST_SCRIPT": cd.test_script, } add_bool := func(ok bool, key string) { if ok { replacements[key] = "1" } else { replacements[key] = "0" } } add_bool(cd.request_data, "REQUEST_DATA") add_bool(cd.echo_on, "ECHO_ON") sd := maps.Clone(replacements) if cd.request_data { maps.Copy(sd, sensitive_data) } maps.Copy(replacements, sensitive_data) cd.replacements = replacements cd.bootstrap_script = utils.UnsafeBytesToString(shell_integration.Data()["shell-integration/ssh/bootstrap."+cd.script_type].Data) cd.bootstrap_script = prepare_script(cd.bootstrap_script, sd) return err } func wrap_bootstrap_script(cd *connection_data) { // sshd will execute the command we pass it by join all command line // arguments with a space and passing it as a single argument to the users // login shell with -c. If the user has a non POSIX login shell it might // have different escaping semantics and syntax, so the command it should // execute has to be as simple as possible, basically of the form // interpreter -c unwrap_script escaped_bootstrap_script // The unwrap_script is responsible for unescaping the bootstrap script and // executing it. encoded_script := "" unwrap_script := "" if cd.script_type == "py" { encoded_script = base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(cd.bootstrap_script)) unwrap_script = `"import base64, sys; eval(compile(base64.standard_b64decode(sys.argv[-1]), 'bootstrap.py', 'exec'))"` } else { // We can't rely on base64 being available on the remote system, so instead // we quote the bootstrap script by replacing ' and \ with \v and \f // also replacing \n and ! with \r and \b for tcsh // finally surrounding with ' encoded_script = "'" + strings.NewReplacer("'", "\v", "\\", "\f", "\n", "\r", "!", "\b").Replace(cd.bootstrap_script) + "'" unwrap_script = `'eval "$(echo "$0" | tr \\\v\\\f\\\r\\\b \\\047\\\134\\\n\\\041)"' ` } cd.rcmd = []string{"exec", cd.host_opts.Interpreter, "-c", unwrap_script, encoded_script} } func get_remote_command(cd *connection_data) error { interpreter := cd.host_opts.Interpreter q := strings.ToLower(path.Base(interpreter)) is_python := strings.Contains(q, "python") cd.script_type = "sh" if is_python { cd.script_type = "py" } err := bootstrap_script(cd) if err != nil { return err } wrap_bootstrap_script(cd) return nil } var debugprintln = tty.DebugPrintln var _ = debugprintln func drain_potential_tty_garbage(term *tty.Term) { err := term.ApplyOperations(tty.TCSANOW, tty.SetRaw) if err != nil { return } canary, err := secrets.TokenHex() if err != nil { return } dcs, err := tui.DCSToKitty("echo", canary) q := utils.UnsafeStringToBytes(canary) if err != nil { return } err = term.WriteAllString(dcs) if err != nil { return } data := make([]byte, 0) give_up_at := time.Now().Add(2 * time.Second) buf := make([]byte, 0, 8192) for !bytes.Contains(data, q) { buf = buf[:cap(buf)] timeout := time.Until(give_up_at) if timeout < 0 { break } n, err := term.ReadWithTimeout(buf, timeout) if err != nil { break } data = append(data, buf[:n]...) } } func change_colors(color_scheme string) (ans string, err error) { if color_scheme == "" { return } var theme *themes.Theme if !strings.HasSuffix(color_scheme, ".conf") { cs := os.ExpandEnv(color_scheme) tc, closer, err := themes.LoadThemes(-1) if err != nil && errors.Is(err, themes.ErrNoCacheFound) { tc, closer, err = themes.LoadThemes(time.Hour * 24) } if err != nil { return "", err } defer closer.Close() theme = tc.ThemeByName(cs) if theme == nil { return "", fmt.Errorf("No theme named %#v found", cs) } } else { theme, err = themes.ThemeFromFile(utils.ResolveConfPath(color_scheme)) if err != nil { return "", err } } ans, err = theme.AsEscapeCodes() if err == nil { ans = "\033[#P" + ans } return } func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err error) { go shell_integration.Data() go RelevantKittyOpts() defer func() { if data_shm != nil { data_shm.Close() _ = data_shm.Unlink() } }() cmd := append([]string{SSHExe()}, ssh_args...) cd := connection_data{remote_args: server_args[1:]} hostname := server_args[0] if len(cd.remote_args) == 0 { cmd = append(cmd, "-t") } insertion_point := len(cmd) cmd = append(cmd, "--", hostname) uname, hostname_for_match := get_destination(hostname) overrides, literal_env, err := parse_kitten_args(found_extra_args, uname, hostname_for_match) if err != nil { return 1, err } host_opts, bad_lines, err := load_config(hostname_for_match, uname, overrides) if err != nil { return 1, err } if len(bad_lines) > 0 { for _, x := range bad_lines { fmt.Fprintf(os.Stderr, "Ignoring bad config line: %s:%d with error: %s", filepath.Base(x.Src_file), x.Line_number, x.Err) } } if host_opts.Delegate != "" { delegate_cmd, err := shlex.Split(host_opts.Delegate) if err != nil { return 1, fmt.Errorf("Could not parse delegate command: %#v with error: %w", host_opts.Delegate, err) } return 1, unix.Exec(utils.FindExe(delegate_cmd[0]), utils.Concat(delegate_cmd, ssh_args, server_args), os.Environ()) } master_is_alive, master_checked := false, false var control_master_args []string if host_opts.Share_connections { kpid, err := strconv.Atoi(os.Getenv("KITTY_PID")) if err != nil { return 1, fmt.Errorf("Invalid KITTY_PID env var not an integer: %#v", os.Getenv("KITTY_PID")) } control_master_args, err = connection_sharing_args(kpid) if err != nil { return 1, err } cmd = slices.Insert(cmd, insertion_point, control_master_args...) } use_kitty_askpass := host_opts.Askpass == Askpass_native || (host_opts.Askpass == Askpass_unless_set && os.Getenv("SSH_ASKPASS") == "") need_to_request_data := true if use_kitty_askpass { need_to_request_data = set_askpass() } master_is_functional := func() bool { if master_checked { return master_is_alive } master_checked = true check_cmd := slices.Insert(cmd, 1, "-O", "check") master_is_alive = exec.Command(check_cmd[0], check_cmd[1:]...).Run() == nil return master_is_alive } if need_to_request_data && host_opts.Share_connections && master_is_functional() { need_to_request_data = false } run_control_master := func() error { cmcmd := slices.Clone(cmd[:insertion_point]) cmcmd = append(cmcmd, control_master_args...) cmcmd = append(cmcmd, "-N", "-f") cmcmd = append(cmcmd, "--", hostname) c := exec.Command(cmcmd[0], cmcmd[1:]...) c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr err := c.Run() if err != nil { err = fmt.Errorf("Failed to start SSH ControlMaster with cmdline: %s and error: %w", strings.Join(cmcmd, " "), err) } master_checked = false master_is_alive = false return err } if host_opts.Forward_remote_control && os.Getenv("KITTY_LISTEN_ON") != "" { if !host_opts.Share_connections { return 1, fmt.Errorf("Cannot use forward_remote_control=yes without share_connections=yes as it relies on SSH Controlmasters") } if !master_is_functional() { if err = run_control_master(); err != nil { return 1, err } if !master_is_functional() { return 1, fmt.Errorf("SSH ControlMaster not functional after being started explicitly") } } protocol, listen_on, found := strings.Cut(os.Getenv("KITTY_LISTEN_ON"), ":") if !found { return 1, fmt.Errorf("Invalid KITTY_LISTEN_ON: %#v", os.Getenv("KITTY_LISTEN_ON")) } if protocol == "unix" && strings.HasPrefix(listen_on, "@") { return 1, fmt.Errorf("Cannot forward kitty remote control socket when an abstract UNIX socket (%s) is used, due to limitations in OpenSSH. Use either a path based one or a TCP socket", listen_on) } cmcmd := slices.Clone(cmd[:insertion_point]) cmcmd = append(cmcmd, control_master_args...) cmcmd = append(cmcmd, "-R", "0:"+listen_on, "-O", "forward") cmcmd = append(cmcmd, "--", hostname) c := exec.Command(cmcmd[0], cmcmd[1:]...) b := bytes.Buffer{} c.Stdout = &b c.Stderr = os.Stderr if err := c.Run(); err != nil { return 1, fmt.Errorf("%s\nSetup of port forward in SSH ControlMaster failed with error: %w", b.String(), err) } port, err := strconv.Atoi(strings.TrimSpace(b.String())) if err != nil { os.Stderr.Write(b.Bytes()) return 1, fmt.Errorf("Setup of port forward in SSH ControlMaster failed with error: invalid resolved port returned: %s", b.String()) } cd.listen_on = "tcp:localhost:" + strconv.Itoa(port) } term, err := tty.OpenControllingTerm(tty.SetNoEcho) if err != nil { return 1, fmt.Errorf("Failed to open controlling terminal with error: %w", err) } cd.echo_on = term.WasEchoOnOriginally() cd.host_opts, cd.literal_env = host_opts, literal_env cd.request_data = need_to_request_data cd.hostname_for_match, cd.username = hostname_for_match, uname escape_codes_to_set_colors, err := change_colors(cd.host_opts.Color_scheme) if err == nil { err = term.WriteAllString(escape_codes_to_set_colors + loop.SAVE_PRIVATE_MODE_VALUES + loop.HANDLE_TERMIOS_SIGNALS.EscapeCodeToSet()) } if err != nil { return 1, err } restore_escape_codes := loop.RESTORE_PRIVATE_MODE_VALUES + loop.HANDLE_TERMIOS_SIGNALS.EscapeCodeToReset() if escape_codes_to_set_colors != "" { restore_escape_codes += "\x1b[#Q" } sigs := make(chan os.Signal, 8) signal.Notify(sigs, unix.SIGINT, unix.SIGTERM) cleaned_up := false cleanup := func() { if !cleaned_up { _ = term.WriteAllString(restore_escape_codes) term.RestoreAndClose() signal.Reset() cleaned_up = true } } defer cleanup() err = get_remote_command(&cd) if err != nil { return 1, err } cmd = append(cmd, cd.rcmd...) c := exec.Command(cmd[0], cmd[1:]...) c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr err = c.Start() if err != nil { return 1, err } if !cd.request_data { rq := fmt.Sprintf("id=%s:pwfile=%s:pw=%s", cd.replacements["REQUEST_ID"], cd.replacements["PASSWORD_FILENAME"], cd.replacements["DATA_PASSWORD"]) err := term.ApplyOperations(tty.TCSANOW, tty.SetNoEcho) if err == nil { var dcs string dcs, err = tui.DCSToKitty("ssh", rq) if err == nil { err = term.WriteAllString(dcs) } } if err != nil { _ = c.Process.Kill() _ = c.Wait() return 1, err } } go func() { <-sigs // ignore any interrupt and terminate signals as they will usually be sent to the ssh child process as well // and we are waiting on that. }() err = c.Wait() drain_potential_tty_garbage(term) if err != nil { var exit_err *exec.ExitError if errors.As(err, &exit_err) { if state := exit_err.ProcessState.String(); state == "signal: interrupt" { cleanup() _ = unix.Kill(os.Getpid(), unix.SIGINT) // Give the signal time to be delivered time.Sleep(20 * time.Millisecond) } return exit_err.ExitCode(), nil } return 1, err } return 0, nil } func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) { if len(args) > 0 { switch args[0] { case "use-python": args = args[1:] // backwards compat from when we had a python implementation case "-h", "--help": cmd.ShowHelp() return } } ssh_args, server_args, passthrough, found_extra_args, err := ParseSSHArgs(args, "--kitten") if err != nil { var invargs *ErrInvalidSSHArgs switch { case errors.As(err, &invargs): if invargs.Msg != "" { fmt.Fprintln(os.Stderr, invargs.Msg) } return 1, unix.Exec(SSHExe(), []string{"ssh"}, os.Environ()) } return 1, err } if passthrough { return 1, unix.Exec(SSHExe(), utils.Concat([]string{"ssh"}, ssh_args, server_args), os.Environ()) } if os.Getenv("KITTY_WINDOW_ID") == "" || os.Getenv("KITTY_PID") == "" { return 1, fmt.Errorf("The SSH kitten is meant to run inside a kitty window") } if !tty.IsTerminal(os.Stdin.Fd()) { return 1, fmt.Errorf("The SSH kitten is meant for interactive use only, STDIN must be a terminal") } return run_ssh(ssh_args, server_args, found_extra_args) } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } func specialize_command(ssh *cli.Command) { ssh.Usage = "arguments for the ssh command" ssh.ShortDescription = "Truly convenient SSH" ssh.HelpText = "The ssh kitten is a thin wrapper around the ssh command. It automatically enables shell integration on the remote host, re-uses existing connections to reduce latency, makes the kitty terminfo database available, etc. Its invocation is identical to the ssh command. For details on its usage, see :doc:`/kittens/ssh`." ssh.IgnoreAllArgs = true ssh.OnlyArgsAllowed = true ssh.ArgCompleter = cli.CompletionForWrapper("ssh") } func test_integration_with_python(args []string) (rc int, err error) { f, err := os.CreateTemp("", "*.conf") if err != nil { return 1, err } defer func() { f.Close() os.Remove(f.Name()) }() _, err = io.Copy(f, os.Stdin) if err != nil { return 1, err } cd := &connection_data{ request_id: "testing", remote_args: []string{}, username: "testuser", hostname_for_match: "host.test", request_data: true, test_script: args[0], echo_on: true, } opts, bad_lines, err := load_config(cd.hostname_for_match, cd.username, nil, f.Name()) if err == nil { if len(bad_lines) > 0 { return 1, fmt.Errorf("Bad config lines: %s with error: %s", bad_lines[0].Line, bad_lines[0].Err) } cd.host_opts = opts err = get_remote_command(cd) } if err != nil { return 1, err } data, err := json.Marshal(map[string]any{"cmd": cd.rcmd, "shm_name": cd.shm_name}) if err == nil { _, err = os.Stdout.Write(data) os.Stdout.Close() } if err != nil { return 1, err } return } func TestEntryPoint(root *cli.Command) { root.AddSubCommand(&cli.Command{ Name: "ssh", OnlyArgsAllowed: true, Run: func(cmd *cli.Command, args []string) (rc int, err error) { return test_integration_with_python(args) }, }) } kitty-0.41.1/kittens/ssh/main.py0000664000175000017510000002424014773370543016065 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import sys from kitty.conf.types import Definition from kitty.types import run_once copy_message = '''\ Copy files and directories from local to remote hosts. The specified files are assumed to be relative to the HOME directory and copied to the HOME on the remote. Directories are copied recursively. If absolute paths are used, they are copied as is.''' @run_once def option_text() -> str: return ''' --glob type=bool-set Interpret file arguments as glob patterns. Globbing is based on standard wildcards with the addition that ``/**/`` matches any number of directories. See the :link:`detailed syntax `. --dest The destination on the remote host to copy to. Relative paths are resolved relative to HOME on the remote host. When this option is not specified, the local file path is used as the remote destination (with the HOME directory getting automatically replaced by the remote HOME). Note that environment variables and ~ are not expanded. --exclude type=list A glob pattern. Files with names matching this pattern are excluded from being transferred. Only used when copying directories. Can be specified multiple times, if any of the patterns match the file will be excluded. If the pattern includes a :code:`/` then it will match against the full path, not just the filename. In such patterns you can use :code:`/**/` to match zero or more directories. For example, to exclude a directory and everything under it use :code:`**/directory_name`. See the :link:`detailed syntax ` for how wildcards match. --symlink-strategy default=preserve choices=preserve,resolve,keep-path Control what happens if the specified path is a symlink. The default is to preserve the symlink, re-creating it on the remote machine. Setting this to :code:`resolve` will cause the symlink to be followed and its target used as the file/directory to copy. The value of :code:`keep-path` is the same as :code:`resolve` except that the remote file path is derived from the symlink's path instead of the path of the symlink's target. Note that this option does not apply to symlinks encountered while recursively copying directories, those are always preserved. ''' definition = Definition( '!kittens.ssh', ) agr = definition.add_group egr = definition.end_group opt = definition.add_option agr('bootstrap', 'Host bootstrap configuration') # {{{ opt('hostname', '*', long_text=''' The hostname that the following options apply to. A glob pattern to match multiple hosts can be used. Multiple hostnames can also be specified, separated by spaces. The hostname can include an optional username in the form :code:`user@host`. When not specified options apply to all hosts, until the first hostname specification is found. Note that matching of hostname is done against the name you specify on the command line to connect to the remote host. If you wish to include the same basic configuration for many different hosts, you can do so with the :ref:`include ` directive. In version 0.28.0 the behavior of this option was changed slightly, now, when a hostname is encountered all its config values are set to defaults instead of being inherited from a previous matching hostname block. In particular it means hostnames dont inherit configurations, thereby avoiding hard to understand action-at-a-distance. ''') opt('interpreter', 'sh', long_text=''' The interpreter to use on the remote host. Must be either a POSIX complaint shell or a :program:`python` executable. If the default :program:`sh` is not available or broken, using an alternate interpreter can be useful. ''') opt('remote_dir', '.local/share/kitty-ssh-kitten', long_text=''' The location on the remote host where the files needed for this kitten are installed. Relative paths are resolved with respect to :code:`$HOME`. Absolute paths have their leading / removed and so are also resolved with respect to $HOME. ''') opt('+copy', '', add_to_default=False, ctype='CopyInstruction', long_text=f''' {copy_message} For example:: copy .vimrc .zshrc .config/some-dir Use :code:`--dest` to copy a file to some other destination on the remote host:: copy --dest some-other-name some-file Glob patterns can be specified to copy multiple files, with :code:`--glob`:: copy --glob images/*.png Files can be excluded when copying with :code:`--exclude`:: copy --glob --exclude *.jpg --exclude *.bmp images/* Files whose remote name matches the exclude pattern will not be copied. For more details, see :ref:`ssh_copy_command`. ''') egr() # }}} agr('shell', 'Login shell environment') # {{{ opt('shell_integration', 'inherited', long_text=''' Control the shell integration on the remote host. See :ref:`shell_integration` for details on how this setting works. The special value :code:`inherited` means use the setting from :file:`kitty.conf`. This setting is useful for overriding integration on a per-host basis. ''') opt('login_shell', '', long_text=''' The login shell to execute on the remote host. By default, the remote user account's login shell is used. ''') opt('+env', '', add_to_default=False, ctype='EnvInstruction', long_text=''' Specify the environment variables to be set on the remote host. Using the name with an equal sign (e.g. :code:`env VAR=`) will set it to the empty string. Specifying only the name (e.g. :code:`env VAR`) will remove the variable from the remote shell environment. The special value :code:`_kitty_copy_env_var_` will cause the value of the variable to be copied from the local environment. The definitions are processed alphabetically. Note that environment variables are expanded recursively, for example:: env VAR1=a env VAR2=${HOME}/${VAR1}/b The value of :code:`VAR2` will be :code:`/a/b`. ''') opt('cwd', '', long_text=''' The working directory on the remote host to change to. Environment variables in this value are expanded. The default is empty so no changing is done, which usually means the HOME directory is used. ''') opt('color_scheme', '', long_text=''' Specify a color scheme to use when connecting to the remote host. If this option ends with :code:`.conf`, it is assumed to be the name of a config file to load from the kitty config directory, otherwise it is assumed to be the name of a color theme to load via the :doc:`themes kitten
`. Note that only colors applying to the text/background are changed, other config settings in the .conf files/themes are ignored. ''') opt('remote_kitty', 'if-needed', choices=('if-needed', 'no', 'yes'), long_text=''' Make :program:`kitten` available on the remote host. Useful to run kittens such as the :doc:`icat kitten
` to display images or the :doc:`transfer file kitten
` to transfer files. Only works if the remote host has an architecture for which :link:`pre-compiled kitten binaries ` are available. Note that kitten is not actually copied to the remote host, instead a small bootstrap script is copied which will download and run kitten when kitten is first executed on the remote host. A value of :code:`if-needed` means kitten is installed only if not already present in the system-wide PATH. A value of :code:`yes` means that kitten is installed even if already present, and the installed kitten takes precedence. Finally, :code:`no` means no kitten is installed on the remote host. The installed kitten can be updated by running: :code:`kitten update-self` on the remote host. ''') egr() # }}} agr('ssh', 'SSH configuration') # {{{ opt('share_connections', 'yes', option_type='to_bool', long_text=''' Within a single kitty instance, all connections to a particular server can be shared. This reduces startup latency for subsequent connections and means that you have to enter the password only once. Under the hood, it uses SSH ControlMasters and these are automatically cleaned up by kitty when it quits. You can map a shortcut to :ac:`close_shared_ssh_connections` to disconnect all active shared connections. ''') opt('askpass', 'unless-set', choices=('unless-set', 'ssh', 'native'), long_text=''' Control the program SSH uses to ask for passwords or confirmation of host keys etc. The default is to use kitty's native :program:`askpass`, unless the :envvar:`SSH_ASKPASS` environment variable is set. Set this option to :code:`ssh` to not interfere with the normal ssh askpass mechanism at all, which typically means that ssh will prompt at the terminal. Set it to :code:`native` to always use kitty's native, built-in askpass implementation. Note that not using the kitty askpass implementation means that SSH might need to use the terminal before the connection is established, so the kitten cannot use the terminal to send data without an extra roundtrip, adding to initial connection latency. ''') opt('delegate', '', long_text=''' Do not use the SSH kitten for this host. Instead run the command specified as the delegate. For example using :code:`delegate ssh` will run the ssh command with all arguments passed to the kitten, except kitten specific ones. This is useful if some hosts are not capable of supporting the ssh kitten. ''') opt('forward_remote_control', 'no', option_type='to_bool', long_text=''' Forward the kitty remote control socket to the remote host. This allows using the kitty remote control facilities from the remote host. WARNING: This allows any software on the remote host full access to the local computer, so only do it for trusted remote hosts. Note that this does not work with abstract UNIX sockets such as :file:`@mykitty` because of SSH limitations. This option uses SSH socket forwarding to forward the socket pointed to by the :envvar:`KITTY_LISTEN_ON` environment variable. ''') egr() # }}} def main(args: list[str]) -> str | None: raise SystemExit('This should be run as kitten ssh') if __name__ == '__main__': main([]) elif __name__ == '__wrapper_of__': cd = getattr(sys, 'cli_docs') cd['wrapper_of'] = 'ssh' elif __name__ == '__conf__': setattr(sys, 'options_definition', definition) elif __name__ == '__extra_cli_parsers__': setattr(sys, 'extra_cli_parsers', {'copy': option_text()}) kitty-0.41.1/kittens/ssh/main_test.go0000664000175000017510000000704514773370543017105 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package ssh import ( "encoding/binary" "encoding/json" "fmt" "io/fs" "kitty" "kitty/tools/utils/shm" "os" "os/exec" "path" "path/filepath" "strings" "testing" "github.com/google/go-cmp/cmp" "golang.org/x/sys/unix" ) var _ = fmt.Print func TestCloneEnv(t *testing.T) { env := map[string]string{"a": "1", "b": "2"} data, err := json.Marshal(env) if err != nil { t.Fatal(err) } mmap, err := shm.CreateTemp("", 128) if err != nil { t.Fatal(err) } defer mmap.Unlink() copy(mmap.Slice()[4:], data) binary.BigEndian.PutUint32(mmap.Slice(), uint32(len(data))) mmap.Close() x, err := add_cloned_env(mmap.Name()) if err != nil { t.Fatal(err) } diff := cmp.Diff(env, x) if diff != "" { t.Fatalf("Failed to deserialize env\n%s", diff) } } func basic_connection_data(overrides ...string) *connection_data { ans := &connection_data{ script_type: "sh", request_id: "123-123", remote_args: []string{}, username: "testuser", hostname_for_match: "host.test", dont_create_shm: true, } opts, bad_lines, err := load_config(ans.hostname_for_match, ans.username, overrides) if err != nil { panic(err) } if len(bad_lines) != 0 { panic(fmt.Sprintf("Bad config lines: %s with error: %s", bad_lines[0].Line, bad_lines[0].Err)) } ans.host_opts = opts return ans } func TestSSHBootstrapScriptLimit(t *testing.T) { cd := basic_connection_data() err := get_remote_command(cd) if err != nil { t.Fatal(err) } total := 0 for _, x := range cd.rcmd { total += len(x) } if total > 9000 { t.Fatalf("Bootstrap script too large: %d bytes", total) } } func TestSSHTarfile(t *testing.T) { tdir := t.TempDir() cd := basic_connection_data() data, err := make_tarfile(cd, func(key string) (val string, found bool) { return }) if err != nil { t.Fatal(err) } cmd := exec.Command("tar", "xpzf", "-", "-C", tdir) cmd.Stderr = os.Stderr inp, err := cmd.StdinPipe() if err != nil { t.Fatal(err) } err = cmd.Start() if err != nil { t.Fatal(err) } _, err = inp.Write(data) if err != nil { t.Fatal(err) } inp.Close() err = cmd.Wait() if err != nil { t.Fatal(err) } seen := map[string]bool{} err = filepath.WalkDir(tdir, func(name string, d fs.DirEntry, werr error) error { if werr != nil { return werr } rname, werr := filepath.Rel(tdir, name) if werr != nil { return werr } rname = strings.ReplaceAll(rname, "\\", "/") if rname == "." { return nil } fi, werr := d.Info() if werr != nil { return werr } if fi.Mode().Perm()&0o600 == 0 { return fmt.Errorf("%s is not rw for its owner. Actual permissions: %s", rname, fi.Mode().String()) } seen[rname] = true return nil }) if err != nil { t.Fatal(err) } if !seen["data.sh"] { t.Fatalf("data.sh missing") } for _, x := range []string{".terminfo/kitty.terminfo", ".terminfo/x/" + kitty.DefaultTermName} { if !seen["home/"+x] { t.Fatalf("%s missing", x) } } for _, x := range []string{"shell-integration/bash/kitty.bash", "shell-integration/fish/vendor_completions.d/kitty.fish"} { if !seen[path.Join("home", cd.host_opts.Remote_dir, x)] { t.Fatalf("%s missing", x) } } for _, x := range []string{"kitty", "kitten"} { p := filepath.Join(tdir, "home", cd.host_opts.Remote_dir, "kitty", "bin", x) if err = unix.Access(p, unix.X_OK); err != nil { t.Fatalf("Cannot execute %s with error: %s", x, err) } } if seen[path.Join("home", cd.host_opts.Remote_dir, "shell-integration", "ssh", "kitten")] { t.Fatalf("Contents of shell-integration/ssh not excluded") } } kitty-0.41.1/kittens/ssh/utils.go0000664000175000017510000001401614773370543016256 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package ssh import ( "bytes" "fmt" "os/exec" "regexp" "strconv" "strings" "sync" "kitty" "kitty/tools/config" "kitty/tools/utils" ) var _ = fmt.Print var SSHExe = sync.OnceValue(func() string { return utils.FindExe("ssh") }) var SSHOptions = sync.OnceValue(func() (ssh_options map[string]string) { defer func() { if ssh_options == nil { ssh_options = map[string]string{ "4": "", "6": "", "A": "", "a": "", "C": "", "f": "", "G": "", "g": "", "K": "", "k": "", "M": "", "N": "", "n": "", "q": "", "s": "", "T": "", "t": "", "V": "", "v": "", "X": "", "x": "", "Y": "", "y": "", "B": "bind_interface", "b": "bind_address", "c": "cipher_spec", "D": "[bind_address:]port", "E": "log_file", "e": "escape_char", "F": "configfile", "I": "pkcs11", "i": "identity_file", "J": "[user@]host[:port]", "L": "address", "l": "login_name", "m": "mac_spec", "O": "ctl_cmd", "o": "option", "p": "port", "Q": "query_option", "R": "address", "S": "ctl_path", "W": "host:port", "w": "local_tun[:remote_tun]", } } }() cmd := exec.Command(SSHExe()) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr _ = cmd.Run() text := stderr.String() if text == "" || strings.Contains(text, "OpenSSL version mismatch.") { // https://bugzilla.mindrot.org/show_bug.cgi?id=3548 return } ssh_options = make(map[string]string, 32) for { pos := strings.IndexByte(text, '[') if pos < 0 { break } num := 1 epos := pos for num > 0 { epos++ switch text[epos] { case '[': num += 1 case ']': num -= 1 } } q := text[pos+1 : epos] text = text[epos:] if len(q) < 2 || !strings.HasPrefix(q, "-") { continue } opt, desc, found := strings.Cut(q, " ") if found { ssh_options[opt[1:]] = desc } else { for _, ch := range opt[1:] { ssh_options[string(ch)] = "" } } } return }) func GetSSHCLI() (boolean_ssh_args *utils.Set[string], other_ssh_args *utils.Set[string]) { other_ssh_args, boolean_ssh_args = utils.NewSet[string](32), utils.NewSet[string](32) for k, v := range SSHOptions() { k = "-" + k if v == "" { boolean_ssh_args.Add(k) } else { other_ssh_args.Add(k) } } return } func is_extra_arg(arg string, extra_args []string) string { for _, x := range extra_args { if arg == x || strings.HasPrefix(arg, x+"=") { return x } } return "" } type ErrInvalidSSHArgs struct { Msg string } func (self *ErrInvalidSSHArgs) Error() string { return self.Msg } func PassthroughArgs() map[string]bool { return map[string]bool{"-N": true, "-n": true, "-f": true, "-G": true, "-T": true, "-V": true} } func ParseSSHArgs(args []string, extra_args ...string) (ssh_args []string, server_args []string, passthrough bool, found_extra_args []string, err error) { if extra_args == nil { extra_args = []string{} } if len(args) == 0 { passthrough = true return } passthrough_args := PassthroughArgs() boolean_ssh_args, other_ssh_args := GetSSHCLI() ssh_args, server_args, found_extra_args = make([]string, 0, 16), make([]string, 0, 16), make([]string, 0, 16) expecting_option_val := false stop_option_processing := false expecting_extra_val := "" for _, argument := range args { if len(server_args) > 1 || stop_option_processing { server_args = append(server_args, argument) continue } if strings.HasPrefix(argument, "-") && !expecting_option_val { if argument == "--" { stop_option_processing = true continue } if len(extra_args) > 0 { matching_ex := is_extra_arg(argument, extra_args) if matching_ex != "" { _, exval, found := strings.Cut(argument, "=") if found { found_extra_args = append(found_extra_args, matching_ex, exval) } else { expecting_extra_val = matching_ex expecting_option_val = true } continue } } // could be a multi-character option all_args := []rune(argument[1:]) for i, ch := range all_args { arg := "-" + string(ch) if passthrough_args[arg] { passthrough = true } if boolean_ssh_args.Has(arg) { ssh_args = append(ssh_args, arg) continue } if other_ssh_args.Has(arg) { ssh_args = append(ssh_args, arg) if i+1 < len(all_args) { ssh_args = append(ssh_args, string(all_args[i+1:])) } else { expecting_option_val = true } break } err = &ErrInvalidSSHArgs{Msg: "unknown option -- " + arg[1:]} return } continue } if expecting_option_val { if expecting_extra_val != "" { found_extra_args = append(found_extra_args, expecting_extra_val, argument) } else { ssh_args = append(ssh_args, argument) } expecting_option_val = false continue } server_args = append(server_args, argument) } if len(server_args) == 0 && !passthrough { err = &ErrInvalidSSHArgs{Msg: ""} } return } type SSHVersion struct{ Major, Minor int } func (self SSHVersion) SupportsAskpassRequire() bool { return self.Major > 8 || (self.Major == 8 && self.Minor >= 4) } var GetSSHVersion = sync.OnceValue(func() SSHVersion { b, err := exec.Command(SSHExe(), "-V").CombinedOutput() if err != nil { return SSHVersion{} } m := regexp.MustCompile(`OpenSSH_(\d+).(\d+)`).FindSubmatch(b) if len(m) == 3 { maj, _ := strconv.Atoi(utils.UnsafeBytesToString(m[1])) min, _ := strconv.Atoi(utils.UnsafeBytesToString(m[2])) return SSHVersion{Major: maj, Minor: min} } return SSHVersion{} }) type KittyOpts struct { Term, Shell_integration string } func read_relevant_kitty_opts(override_conf_path ...string) KittyOpts { ans := KittyOpts{Term: kitty.KittyConfigDefaults.Term, Shell_integration: kitty.KittyConfigDefaults.Shell_integration} handle_line := func(key, val string) error { switch key { case "term": ans.Term = strings.TrimSpace(val) case "shell_integration": ans.Shell_integration = strings.TrimSpace(val) } return nil } config.ReadKittyConfig(handle_line, override_conf_path...) return ans } var RelevantKittyOpts = sync.OnceValue(func() KittyOpts { return read_relevant_kitty_opts() }) kitty-0.41.1/kittens/ssh/utils.py0000664000175000017510000002752414773370543016311 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2022, Kovid Goyal import os import subprocess import traceback from collections.abc import Iterator, Sequence from contextlib import suppress from typing import Any from kitty.types import run_once from kitty.utils import SSHConnectionData @run_once def ssh_options() -> dict[str, str]: try: p = subprocess.run(['ssh'], stderr=subprocess.PIPE, encoding='utf-8') raw = p.stderr or '' except FileNotFoundError: return { '4': '', '6': '', 'A': '', 'a': '', 'C': '', 'f': '', 'G': '', 'g': '', 'K': '', 'k': '', 'M': '', 'N': '', 'n': '', 'q': '', 's': '', 'T': '', 't': '', 'V': '', 'v': '', 'X': '', 'x': '', 'Y': '', 'y': '', 'B': 'bind_interface', 'b': 'bind_address', 'c': 'cipher_spec', 'D': '[bind_address:]port', 'E': 'log_file', 'e': 'escape_char', 'F': 'configfile', 'I': 'pkcs11', 'i': 'identity_file', 'J': '[user@]host[:port]', 'L': 'address', 'l': 'login_name', 'm': 'mac_spec', 'O': 'ctl_cmd', 'o': 'option', 'p': 'port', 'Q': 'query_option', 'R': 'address', 'S': 'ctl_path', 'W': 'host:port', 'w': 'local_tun[:remote_tun]' } ans: dict[str, str] = {} pos = 0 while True: pos = raw.find('[', pos) if pos < 0: break num = 1 epos = pos while num > 0: epos += 1 if raw[epos] not in '[]': continue num += 1 if raw[epos] == '[' else -1 q = raw[pos+1:epos] pos = epos if len(q) < 2 or q[0] != '-': continue if ' ' in q: opt, desc = q.split(' ', 1) ans[opt[1:]] = desc else: ans.update(dict.fromkeys(q[1:], '')) return ans def is_kitten_cmdline(q: Sequence[str]) -> bool: if not q: return False exe_name = os.path.basename(q[0]).lower() if exe_name == 'kitten' and q[1:2] == ['ssh']: return True if len(q) < 4: return False if exe_name != 'kitty': return False if q[1:3] == ['+kitten', 'ssh'] or q[1:4] == ['+', 'kitten', 'ssh']: return True return q[1:3] == ['+runpy', 'from kittens.runner import main; main()'] and len(q) >= 6 and q[5] == 'ssh' def patch_cmdline(key: str, val: str, argv: list[str]) -> None: for i, arg in enumerate(tuple(argv)): if arg.startswith(f'--kitten={key}='): argv[i] = f'--kitten={key}={val}' return elif i > 0 and argv[i-1] == '--kitten' and (arg.startswith(f'{key}=') or arg.startswith(f'{key} ')): argv[i] = f'{key}={val}' return idx = argv.index('ssh') argv.insert(idx + 1, f'--kitten={key}={val}') def set_cwd_in_cmdline(cwd: str, argv: list[str]) -> None: patch_cmdline('cwd', cwd, argv) def create_shared_memory(data: Any, prefix: str) -> str: import atexit import json from kitty.fast_data_types import get_boss from kitty.shm import SharedMemory db = json.dumps(data).encode('utf-8') with SharedMemory(size=len(db) + SharedMemory.num_bytes_for_size, prefix=prefix) as shm: shm.write_data_with_size(db) shm.flush() atexit.register(shm.close) # keeps shm alive till exit get_boss().atexit.shm_unlink(shm.name) return shm.name def read_data_from_shared_memory(shm_name: str) -> Any: import json import stat from kitty.shm import SharedMemory with SharedMemory(shm_name, readonly=True) as shm: shm.unlink() if shm.stats.st_uid != os.geteuid() or shm.stats.st_gid != os.getegid(): raise ValueError(f'Incorrect owner on pwfile: uid={shm.stats.st_uid} gid={shm.stats.st_gid}') mode = stat.S_IMODE(shm.stats.st_mode) if mode != stat.S_IREAD | stat.S_IWRITE: raise ValueError(f'Incorrect permissions on pwfile: 0o{mode:03o}') return json.loads(shm.read_data_with_size()) def get_ssh_data(msgb: memoryview, request_id: str) -> Iterator[bytes]: from base64 import standard_b64decode yield b'\nKITTY_DATA_START\n' # to discard leading data try: msg = standard_b64decode(msgb).decode('utf-8') md = dict(x.split('=', 1) for x in msg.split(':')) pw = md['pw'] pwfilename = md['pwfile'] rq_id = md['id'] except Exception: traceback.print_exc() yield b'invalid ssh data request message\n' else: try: env_data = read_data_from_shared_memory(pwfilename) if pw != env_data['pw']: raise ValueError('Incorrect password') if rq_id != request_id: raise ValueError(f'Incorrect request id: {rq_id!r} expecting the KITTY_PID-KITTY_WINDOW_ID for the current kitty window') except Exception as e: traceback.print_exc() yield f'{e}\n'.encode() else: yield b'OK\n' encoded_data = memoryview(env_data['tarfile'].encode('ascii')) # macOS has a 255 byte limit on its input queue as per man stty. # Not clear if that applies to canonical mode input as well, but # better to be safe. line_sz = 254 while encoded_data: yield encoded_data[:line_sz] yield b'\n' encoded_data = encoded_data[line_sz:] yield b'KITTY_DATA_END\n' def set_env_in_cmdline(env: dict[str, str], argv: list[str], clone: bool = True) -> None: from kitty.options.utils import DELETE_ENV_VAR if clone: patch_cmdline('clone_env', create_shared_memory(env, 'ksse-'), argv) return idx = argv.index('ssh') for i in range(idx, len(argv)): if argv[i] == '--kitten': idx = i + 1 elif argv[i].startswith('--kitten='): idx = i env_dirs = [] for k, v in env.items(): if v is DELETE_ENV_VAR: x = f'--kitten=env={k}' else: x = f'--kitten=env={k}={v}' env_dirs.append(x) argv[idx+1:idx+1] = env_dirs def get_ssh_cli() -> tuple[set[str], set[str]]: other_ssh_args: set[str] = set() boolean_ssh_args: set[str] = set() for k, v in ssh_options().items(): k = f'-{k}' if v: other_ssh_args.add(k) else: boolean_ssh_args.add(k) return boolean_ssh_args, other_ssh_args def is_extra_arg(arg: str, extra_args: tuple[str, ...]) -> str: for x in extra_args: if arg == x or arg.startswith(f'{x}='): return x return '' passthrough_args = {f'-{x}' for x in 'NnfGT'} def set_server_args_in_cmdline( server_args: list[str], argv: list[str], extra_args: tuple[str, ...] = ('--kitten',), allocate_tty: bool = False ) -> None: boolean_ssh_args, other_ssh_args = get_ssh_cli() ssh_args = [] expecting_option_val = False found_extra_args: list[str] = [] expecting_extra_val = '' ans = list(argv) found_ssh = False for i, argument in enumerate(argv): if not found_ssh: found_ssh = argument == 'ssh' continue if argument.startswith('-') and not expecting_option_val: if argument == '--': del ans[i+2:] if allocate_tty and ans[i-1] != '-t': ans.insert(i, '-t') break if extra_args: matching_ex = is_extra_arg(argument, extra_args) if matching_ex: if '=' in argument: exval = argument.partition('=')[-1] found_extra_args.extend((matching_ex, exval)) else: expecting_extra_val = matching_ex expecting_option_val = True continue # could be a multi-character option all_args = argument[1:] for i, arg in enumerate(all_args): arg = f'-{arg}' if arg in boolean_ssh_args: ssh_args.append(arg) continue if arg in other_ssh_args: ssh_args.append(arg) rest = all_args[i+1:] if rest: ssh_args.append(rest) else: expecting_option_val = True break raise KeyError(f'unknown option -- {arg[1:]}') continue if expecting_option_val: if expecting_extra_val: found_extra_args.extend((expecting_extra_val, argument)) expecting_extra_val = '' else: ssh_args.append(argument) expecting_option_val = False continue del ans[i+1:] if allocate_tty and ans[i] != '-t': ans.insert(i, '-t') break argv[:] = ans + server_args def get_connection_data(args: list[str], cwd: str = '', extra_args: tuple[str, ...] = ()) -> SSHConnectionData | None: boolean_ssh_args, other_ssh_args = get_ssh_cli() port: int | None = None expecting_port = expecting_identity = False expecting_option_val = False expecting_hostname = False expecting_extra_val = '' host_name = identity_file = found_ssh = '' found_extra_args: list[tuple[str, str]] = [] for i, arg in enumerate(args): if not found_ssh: if os.path.basename(arg).lower() in ('ssh', 'ssh.exe'): found_ssh = arg continue if expecting_hostname: host_name = arg continue if arg.startswith('-') and not expecting_option_val: if arg in boolean_ssh_args: continue if arg == '--': expecting_hostname = True if arg.startswith('-p'): if arg[2:].isdigit(): with suppress(Exception): port = int(arg[2:]) continue elif arg == '-p': expecting_port = True elif arg.startswith('-i'): if arg == '-i': expecting_identity = True else: identity_file = arg[2:] continue if arg.startswith('--') and extra_args: matching_ex = is_extra_arg(arg, extra_args) if matching_ex: if '=' in arg: exval = arg.partition('=')[-1] found_extra_args.append((matching_ex, exval)) continue expecting_extra_val = matching_ex expecting_option_val = True continue if expecting_option_val: if expecting_port: with suppress(Exception): port = int(arg) expecting_port = False elif expecting_identity: identity_file = arg elif expecting_extra_val: found_extra_args.append((expecting_extra_val, arg)) expecting_extra_val = '' expecting_option_val = False continue if not host_name: host_name = arg if not host_name: return None if host_name.startswith('ssh://'): from urllib.parse import urlparse purl = urlparse(host_name) if purl.hostname: host_name = purl.hostname if purl.username: host_name = f'{purl.username}@{host_name}' if port is None and purl.port: port = purl.port if identity_file: if not os.path.isabs(identity_file): identity_file = os.path.expanduser(identity_file) if not os.path.isabs(identity_file): identity_file = os.path.normpath(os.path.join(cwd or os.getcwd(), identity_file)) return SSHConnectionData(found_ssh, host_name, port, identity_file, tuple(found_extra_args)) kitty-0.41.1/kittens/ssh/utils_test.go0000664000175000017510000000410714773370543017315 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package ssh import ( "fmt" "os" "os/exec" "path/filepath" "testing" "kitty/tools/utils/shlex" "github.com/google/go-cmp/cmp" ) var _ = fmt.Print func TestGetSSHOptions(t *testing.T) { m := SSHOptions() if m["w"] != "local_tun[:remote_tun]" { cmd := exec.Command(SSHExe()) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Run() t.Fatalf("Unexpected set of SSH options: %#v", m) } } func TestParseSSHArgs(t *testing.T) { split := func(x string) []string { ans, err := shlex.Split(x) if err != nil { t.Fatal(err) } if len(ans) == 0 { ans = []string{} } return ans } p := func(args, expected_ssh_args, expected_server_args, expected_extra_args string, expected_passthrough bool) { ssh_args, server_args, passthrough, extra_args, err := ParseSSHArgs(split(args), "--kitten") if err != nil { t.Fatal(err) } check := func(a, b any) { diff := cmp.Diff(a, b) if diff != "" { t.Fatalf("Unexpected value for args: %#v\n%s", args, diff) } } check(split(expected_ssh_args), ssh_args) check(split(expected_server_args), server_args) check(split(expected_extra_args), extra_args) check(expected_passthrough, passthrough) } p(`localhost`, ``, `localhost`, ``, false) p(`-- localhost`, ``, `localhost`, ``, false) p(`-46p23 localhost sh -c "a b"`, `-4 -6 -p 23`, `localhost sh -c "a b"`, ``, false) p(`-46p23 -S/moose -W x:6 -- localhost sh -c "a b"`, `-4 -6 -p 23 -S /moose -W x:6`, `localhost sh -c "a b"`, ``, false) p(`--kitten=abc -np23 --kitten xyz host`, `-n -p 23`, `host`, `--kitten abc --kitten xyz`, true) } func TestRelevantKittyOpts(t *testing.T) { tdir := t.TempDir() path := filepath.Join(tdir, "kitty.conf") os.WriteFile(path, []byte("term XXX\nshell_integration changed\nterm abcd"), 0o600) rko := read_relevant_kitty_opts(path) if rko.Term != "abcd" { t.Fatalf("Unexpected TERM: %s", RelevantKittyOpts().Term) } if rko.Shell_integration != "changed" { t.Fatalf("Unexpected shell_integration: %s", RelevantKittyOpts().Shell_integration) } } kitty-0.41.1/kittens/themes/0000775000175000017510000000000014773370543015255 5ustar nileshnileshkitty-0.41.1/kittens/themes/__init__.py0000664000175000017510000000000014773370543017354 0ustar nileshnileshkitty-0.41.1/kittens/themes/list.go0000664000175000017510000000443214773370543016562 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package themes import ( "fmt" "kitty/tools/themes" "kitty/tools/utils" "kitty/tools/wcswidth" ) var _ = fmt.Print type ThemesList struct { themes, all_themes *themes.Themes current_search string display_strings []string widths []int max_width, current_idx int } func (self *ThemesList) Len() int { if self.themes == nil { return 0 } return self.themes.Len() } func (self *ThemesList) Next(delta int, allow_wrapping bool) bool { if len(self.display_strings) == 0 { return false } idx := self.current_idx + delta if !allow_wrapping && (idx < 0 || idx > self.Len()) { return false } for idx < 0 { idx += self.Len() } self.current_idx = idx % self.Len() return true } func limit_lengths(text string) string { t, _ := wcswidth.TruncateToVisualLengthWithWidth(text, 31) if len(t) >= len(text) { return text } return t + "…" } func (self *ThemesList) UpdateThemes(themes *themes.Themes) { self.themes, self.all_themes = themes, themes if self.current_search != "" { self.themes = self.all_themes.Copy() self.display_strings = utils.Map(limit_lengths, self.themes.ApplySearch(self.current_search)) } else { self.display_strings = utils.Map(limit_lengths, self.themes.Names()) } self.widths = utils.Map(wcswidth.Stringwidth, self.display_strings) self.max_width = utils.Max(0, self.widths...) self.current_idx = 0 } func (self *ThemesList) UpdateSearch(query string) bool { if query == self.current_search || self.all_themes == nil { return false } self.current_search = query self.UpdateThemes(self.all_themes) return true } type Line struct { text string width int is_current bool } func (self *ThemesList) Lines(num_rows int) []Line { if num_rows < 1 { return nil } ans := make([]Line, 0, len(self.display_strings)) before_num := utils.Min(self.current_idx, num_rows-1) start := self.current_idx - before_num for i := start; i < utils.Min(start+num_rows, len(self.display_strings)); i++ { ans = append(ans, Line{self.display_strings[i], self.widths[i], i == self.current_idx}) } return ans } func (self *ThemesList) CurrentTheme() *themes.Theme { if self.themes == nil { return nil } return self.themes.At(self.current_idx) } kitty-0.41.1/kittens/themes/main.go0000664000175000017510000000612614773370543016535 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package themes import ( "encoding/json" "fmt" "io" "os" "path/filepath" "strings" "time" "kitty/tools/cli" "kitty/tools/themes" "kitty/tools/tui/loop" "kitty/tools/utils" ) var _ = fmt.Print func complete_themes(completions *cli.Completions, word string, arg_num int) { themes.CompleteThemes(completions, word, arg_num) } func non_interactive(opts *Options, theme_name string) (rc int, err error) { themes, closer, err := themes.LoadThemes(time.Duration(opts.CacheAge * float64(time.Hour*24))) if err != nil { return 1, err } defer closer.Close() theme := themes.ThemeByName(theme_name) if theme == nil { theme_name = strings.ReplaceAll(theme_name, `\`, ``) theme = themes.ThemeByName(theme_name) if theme == nil { return 1, fmt.Errorf("No theme named: %s", theme_name) } } if opts.DumpTheme { code, err := theme.Code() if err != nil { return 1, err } fmt.Println(code) } else { err = theme.SaveInConf(utils.ConfigDir(), opts.ReloadIn, opts.ConfigFileName) if err != nil { return 1, err } } return } func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) { if len(args) > 1 { args = []string{strings.Join(args, ` `)} } if len(args) == 1 { return non_interactive(opts, args[0]) } lp, err := loop.New() if err != nil { return 1, err } cv := utils.NewCachedValues("unicode-input", &CachedData{Category: "All"}) h := &handler{lp: lp, opts: opts, cached_data: cv.Load()} defer cv.Save() lp.OnInitialize = func() (string, error) { lp.AllowLineWrapping(false) lp.SetWindowTitle(`Choose a theme for kitty`) h.initialize() return "", nil } lp.OnWakeup = h.on_wakeup lp.OnFinalize = func() string { h.finalize() lp.SetCursorVisible(true) return `` } lp.OnResize = func(_, _ loop.ScreenSize) error { h.draw_screen() return nil } lp.OnKeyEvent = h.on_key_event lp.OnText = h.on_text err = lp.Run() if err != nil { return 1, err } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() return 1, nil } return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } func parse_theme_metadata() error { raw, err := io.ReadAll(os.Stdin) if err != nil { return err } paths := utils.Splitlines(utils.UnsafeBytesToString(raw)) ans := make([]*themes.ThemeMetadata, 0, len(paths)) for _, path := range paths { if path != "" { metadata, _, err := themes.ParseThemeMetadata(path) if err != nil { return err } if metadata.Name == "" { metadata.Name = themes.ThemeNameFromFileName(filepath.Base(path)) } ans = append(ans, metadata) } } raw, err = json.Marshal(ans) if err != nil { return err } _, err = os.Stdout.Write(raw) if err != nil { return err } return nil } func ParseEntryPoint(parent *cli.Command) { parent.AddSubCommand(&cli.Command{ Name: "__parse_theme_metadata__", Hidden: true, Run: func(cmd *cli.Command, args []string) (rc int, err error) { err = parse_theme_metadata() if err != nil { rc = 1 } return }, }) } kitty-0.41.1/kittens/themes/main.py0000664000175000017510000000356514773370543016564 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import sys from kitty.cli import CompletionSpec help_text = ( 'Change the kitty theme. If no theme name is supplied, run interactively, otherwise' ' change the current theme to the specified theme name.' ) usage = '[theme name to switch to]' OPTIONS = ''' --cache-age type=float default=1 Check for new themes only after the specified number of days. A value of zero will always check for new themes. A negative value will never check for new themes, instead raising an error if a local copy of the themes is not available. --reload-in default=parent choices=none,parent,all By default, this kitten will signal only the parent kitty instance it is running in to reload its config, after making changes. Use this option to instead either not reload the config at all or in all running kitty instances. --dump-theme type=bool-set default=false When running non-interactively, dump the specified theme to STDOUT instead of changing kitty.conf. --config-file-name default=kitty.conf The name or path to the config file to edit. Relative paths are interpreted with respect to the kitty config directory. By default the kitty config file, kitty.conf is edited. This is most useful if you add :code:`include themes.conf` to your kitty.conf and then have the kitten operate only on :file:`themes.conf`, allowing :code:`kitty.conf` to remain unchanged. '''.format def main(args: list[str]) -> None: raise SystemExit('This must be run as kitten themes') if __name__ == '__main__': main(sys.argv) elif __name__ == '__doc__': cd = sys.cli_docs # type: ignore cd['usage'] = usage cd['options'] = OPTIONS cd['help_text'] = help_text cd['short_desc'] = 'Manage kitty color schemes easily' cd['args_completion'] = CompletionSpec.from_string('type:special group:complete_themes') kitty-0.41.1/kittens/themes/ui.go0000664000175000017510000003733414773370543016233 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package themes import ( "fmt" "io" "kitty" "maps" "regexp" "slices" "strings" "time" "kitty/tools/config" "kitty/tools/themes" "kitty/tools/tui/loop" "kitty/tools/tui/readline" "kitty/tools/utils" "kitty/tools/wcswidth" ) var _ = fmt.Print type State int const ( FETCHING State = iota BROWSING SEARCHING ACCEPTING ) const SEPARATOR = "║" type CachedData struct { Recent []string `json:"recent"` Category string `json:"category"` } type fetch_data struct { themes *themes.Themes err error closer io.Closer } var category_filters = map[string]func(*themes.Theme) bool{ "all": func(*themes.Theme) bool { return true }, "dark": func(t *themes.Theme) bool { return t.IsDark() }, "light": func(t *themes.Theme) bool { return !t.IsDark() }, "user": func(t *themes.Theme) bool { return t.IsUserDefined() }, } func recent_filter(items []string) func(*themes.Theme) bool { allowed := utils.NewSetWithItems(items...) return func(t *themes.Theme) bool { return allowed.Has(t.Name()) } } type handler struct { lp *loop.Loop opts *Options cached_data *CachedData state State fetch_result chan fetch_data all_themes *themes.Themes themes_closer io.Closer themes_list *ThemesList category_filters map[string]func(*themes.Theme) bool colors_set_once bool tabs []string rl *readline.Readline } // fetching {{{ func (self *handler) fetch_themes() { r := fetch_data{} r.themes, r.closer, r.err = themes.LoadThemes(time.Duration(self.opts.CacheAge * float64(time.Hour*24))) self.lp.WakeupMainThread() self.fetch_result <- r } func (self *handler) on_fetching_key_event(ev *loop.KeyEvent) error { if ev.MatchesPressOrRepeat("esc") { self.lp.Quit(0) ev.Handled = true } return nil } func (self *handler) on_wakeup() error { r := <-self.fetch_result if r.err != nil { return r.err } self.state = BROWSING self.all_themes = r.themes self.themes_closer = r.closer self.redraw_after_category_change() return nil } func (self *handler) draw_fetching_screen() { self.lp.Println("Downloading themes from repository, please wait...") } // }}} func (self *handler) finalize() { t := self.themes_closer if t != nil { t.Close() self.themes_closer = nil } } func (self *handler) initialize() { self.tabs = strings.Split("all dark light recent user", " ") self.rl = readline.New(self.lp, readline.RlInit{DontMarkPrompts: true, Prompt: "/"}) self.themes_list = &ThemesList{} self.fetch_result = make(chan fetch_data) self.category_filters = make(map[string]func(*themes.Theme) bool, len(category_filters)+1) maps.Copy(self.category_filters, category_filters) self.category_filters["recent"] = recent_filter(self.cached_data.Recent) go self.fetch_themes() self.draw_screen() } func (self *handler) enforce_cursor_state() { self.lp.SetCursorVisible(self.state == FETCHING) } func (self *handler) draw_screen() { self.lp.StartAtomicUpdate() defer self.lp.EndAtomicUpdate() self.lp.ClearScreen() self.enforce_cursor_state() switch self.state { case FETCHING: self.draw_fetching_screen() case BROWSING, SEARCHING: self.draw_browsing_screen() case ACCEPTING: self.draw_accepting_screen() } } func (self *handler) current_category() string { ans := self.cached_data.Category if self.category_filters[ans] == nil { ans = "all" } return ans } func (self *handler) set_current_category(category string) { if self.category_filters[category] == nil { category = "all" } self.cached_data.Category = category } func ReadKittyColorSettings() map[string]string { settings := make(map[string]string, 512) handle_line := func(key, val string) error { if themes.AllColorSettingNames[key] { settings[key] = val } return nil } config.ReadKittyConfig(handle_line) return settings } func (self *handler) set_colors_to_current_theme() bool { if self.themes_list == nil && self.colors_set_once { return false } self.colors_set_once = true if self.themes_list != nil { t := self.themes_list.CurrentTheme() if t != nil { raw, err := t.AsEscapeCodes() if err == nil { self.lp.QueueWriteString(raw) return true } } } self.lp.QueueWriteString(themes.ColorSettingsAsEscapeCodes(ReadKittyColorSettings())) return true } func (self *handler) redraw_after_category_change() { self.themes_list.UpdateThemes(self.all_themes.Filtered(self.category_filters[self.current_category()])) self.set_colors_to_current_theme() self.draw_screen() } func (self *handler) on_key_event(ev *loop.KeyEvent) error { switch self.state { case FETCHING: return self.on_fetching_key_event(ev) case BROWSING: return self.on_browsing_key_event(ev) case SEARCHING: return self.on_searching_key_event(ev) case ACCEPTING: return self.on_accepting_key_event(ev) } return nil } // browsing ... {{{ func (self *handler) next_category(delta int) { idx := slices.Index(self.tabs, self.current_category()) + delta + len(self.tabs) self.set_current_category(self.tabs[idx%len(self.tabs)]) self.redraw_after_category_change() } func (self *handler) next(delta int, allow_wrapping bool) { if self.themes_list.Next(delta, allow_wrapping) { self.set_colors_to_current_theme() self.draw_screen() } else { self.lp.Beep() } } func (self *handler) on_browsing_key_event(ev *loop.KeyEvent) error { if ev.MatchesPressOrRepeat("esc") || ev.MatchesCaseInsensitiveTextOrKey("q") { self.lp.Quit(0) ev.Handled = true return nil } for _, cat := range self.tabs { if ev.MatchesPressOrRepeat(cat[0:1]) || ev.MatchesPressOrRepeat("alt+"+cat[0:1]) || ev.MatchesCaseInsensitiveTextOrKey(cat[0:1]) { ev.Handled = true if cat != self.current_category() { self.set_current_category(cat) self.redraw_after_category_change() return nil } } } if ev.MatchesPressOrRepeat("left") || ev.MatchesPressOrRepeat("shift+tab") { self.next_category(-1) ev.Handled = true return nil } if ev.MatchesPressOrRepeat("right") || ev.MatchesPressOrRepeat("tab") { self.next_category(1) ev.Handled = true return nil } if ev.MatchesCaseInsensitiveTextOrKey("j") || ev.MatchesPressOrRepeat("down") { self.next(1, true) ev.Handled = true return nil } if ev.MatchesCaseInsensitiveTextOrKey("k") || ev.MatchesPressOrRepeat("up") { self.next(-1, true) ev.Handled = true return nil } if ev.MatchesPressOrRepeat("page_down") { ev.Handled = true sz, err := self.lp.ScreenSize() if err == nil { self.next(int(sz.HeightCells)-3, false) } return nil } if ev.MatchesPressOrRepeat("page_up") { ev.Handled = true sz, err := self.lp.ScreenSize() if err == nil { self.next(3-int(sz.HeightCells), false) } return nil } if ev.MatchesCaseInsensitiveTextOrKey("s") || ev.MatchesCaseInsensitiveTextOrKey("/") { ev.Handled = true self.start_search() return nil } if ev.MatchesCaseInsensitiveTextOrKey("c") || ev.MatchesPressOrRepeat("enter") { ev.Handled = true if self.themes_list == nil || self.themes_list.Len() == 0 { self.lp.Beep() } else { self.state = ACCEPTING self.draw_screen() } } return nil } func (self *handler) start_search() { self.state = SEARCHING self.rl.SetText(self.themes_list.current_search) self.draw_screen() } func (self *handler) draw_browsing_screen() { self.draw_tab_bar() sz, err := self.lp.ScreenSize() if err != nil { return } num_rows := int(sz.HeightCells) - 2 mw := self.themes_list.max_width + 1 green_fg, _, _ := strings.Cut(self.lp.SprintStyled("fg=green", "|"), "|") for _, l := range self.themes_list.Lines(num_rows) { line := l.text if l.is_current { line = strings.ReplaceAll(line, themes.MARK_AFTER, green_fg) self.lp.PrintStyled("fg=green", ">") self.lp.PrintStyled("fg=green bold", line) } else { self.lp.PrintStyled("fg=green", " ") self.lp.QueueWriteString(line) } self.lp.MoveCursorHorizontally(mw - l.width) self.lp.Println(SEPARATOR) num_rows-- } for ; num_rows > 0; num_rows-- { self.lp.MoveCursorHorizontally(mw + 1) self.lp.Println(SEPARATOR) } if self.themes_list != nil && self.themes_list.Len() > 0 { self.draw_theme_demo() } if self.state == BROWSING { self.draw_bottom_bar() } else { self.draw_search_bar() } } func (self *handler) draw_bottom_bar() { sz, err := self.lp.ScreenSize() if err != nil { return } self.lp.MoveCursorTo(1, int(sz.HeightCells)) self.lp.PrintStyled("reverse", strings.Repeat(" ", int(sz.WidthCells))) self.lp.QueueWriteString("\r") draw_tab := func(t, sc string) { text := self.mark_shortcut(utils.Capitalize(t), sc) self.lp.PrintStyled("reverse", " "+text+" ") } draw_tab("search (/)", "s") draw_tab("accept (⏎)", "c") self.lp.QueueWriteString("\x1b[m") } func (self *handler) draw_search_bar() { sz, err := self.lp.ScreenSize() if err != nil { return } self.lp.MoveCursorTo(1, int(sz.HeightCells)) self.lp.ClearToEndOfLine() self.rl.RedrawNonAtomic() } func (self *handler) mark_shortcut(text, acc string) string { acc_idx := strings.Index(strings.ToLower(text), strings.ToLower(acc)) return text[:acc_idx] + self.lp.SprintStyled("underline bold", text[acc_idx:acc_idx+1]) + text[acc_idx+1:] } func (self *handler) draw_tab_bar() { sz, err := self.lp.ScreenSize() if err != nil { return } self.lp.PrintStyled("reverse", strings.Repeat(` `, int(sz.WidthCells))) self.lp.QueueWriteString("\r") cc := self.current_category() draw_tab := func(text, name, acc string) { is_active := name == cc if is_active { text := self.lp.SprintStyled("italic", fmt.Sprintf("%s #%d", text, self.themes_list.Len())) self.lp.Printf(" %s ", text) } else { text = self.mark_shortcut(text, acc) self.lp.PrintStyled("reverse", " "+text+" ") } } for _, title := range self.tabs { draw_tab(utils.Capitalize(title), title, string([]rune(title)[0])) } self.lp.Println("\x1b[m") } func center_string(x string, width int) string { l := wcswidth.Stringwidth(x) spaces := int(float64(width-l) / 2) return strings.Repeat(" ", utils.Max(0, spaces)) + x + strings.Repeat(" ", utils.Max(0, width-(spaces+l))) } func (self *handler) draw_theme_demo() { ssz, err := self.lp.ScreenSize() if err != nil { return } theme := self.themes_list.CurrentTheme() if theme == nil { return } xstart := self.themes_list.max_width + 3 sz := int(ssz.WidthCells) - xstart if sz < 20 { return } sz-- y := 0 colors := strings.Split(`black red green yellow blue magenta cyan white`, ` `) trunc := sz/8 - 1 pat := regexp.MustCompile(`\s+`) next_line := func() { self.lp.QueueWriteString("\r") y++ self.lp.MoveCursorTo(xstart, y+1) self.lp.QueueWriteString(SEPARATOR + " ") } write_para := func(text string) { text = pat.ReplaceAllLiteralString(text, " ") for text != "" { t, sp := wcswidth.TruncateToVisualLengthWithWidth(text, sz) self.lp.QueueWriteString(t) next_line() text = text[sp:] } } write_colors := func(bg string) { for _, intense := range []bool{false, true} { buf := strings.Builder{} buf.Grow(1024) for _, c := range colors { s := c if intense { s = "bright-" + s } sTrunc := s if len(sTrunc) > trunc { sTrunc = sTrunc[:trunc] } buf.WriteString(self.lp.SprintStyled("fg="+s, sTrunc)) buf.WriteString(" ") } text := strings.TrimSpace(buf.String()) if bg == "" { self.lp.QueueWriteString(text) } else { s := bg if intense { s = "bright-" + s } self.lp.PrintStyled("bg="+s, text) } next_line() } next_line() } self.lp.MoveCursorTo(1, 1) next_line() self.lp.PrintStyled("fg=green bold", center_string(theme.Name(), sz)) next_line() if theme.Author() != "" { self.lp.PrintStyled("italic", center_string(theme.Author(), sz)) next_line() } if theme.Blurb() != "" { next_line() write_para(theme.Blurb()) next_line() } write_colors("") for _, bg := range colors { write_colors(bg) } } // }}} // accepting {{{ func (self *handler) on_accepting_key_event(ev *loop.KeyEvent) error { if ev.MatchesCaseInsensitiveTextOrKey("q") || ev.MatchesPressOrRepeat("esc") || ev.MatchesPressOrRepeat("shift+q") { ev.Handled = true self.lp.Quit(0) return nil } if ev.MatchesCaseInsensitiveTextOrKey("a") || ev.MatchesPressOrRepeat("shift+a") { ev.Handled = true self.state = BROWSING self.draw_screen() return nil } if ev.MatchesCaseInsensitiveTextOrKey("p") || ev.MatchesPressOrRepeat("shift+p") { ev.Handled = true self.themes_list.CurrentTheme().SaveInDir(utils.ConfigDir()) self.update_recent() self.lp.Quit(0) return nil } if ev.MatchesCaseInsensitiveTextOrKey("m") || ev.MatchesPressOrRepeat("shift+m") { ev.Handled = true self.themes_list.CurrentTheme().SaveInConf(utils.ConfigDir(), self.opts.ReloadIn, self.opts.ConfigFileName) self.update_recent() self.lp.Quit(0) return nil } scheme := func(name string) error { ev.Handled = true self.themes_list.CurrentTheme().SaveInFile(utils.ConfigDir(), name) self.update_recent() self.lp.Quit(0) return nil } if ev.MatchesCaseInsensitiveTextOrKey("d") || ev.MatchesPressOrRepeat("shift+d") { return scheme(kitty.DarkThemeFileName) } if ev.MatchesCaseInsensitiveTextOrKey("l") || ev.MatchesPressOrRepeat("shift+l") { return scheme(kitty.LightThemeFileName) } if ev.MatchesCaseInsensitiveTextOrKey("n") || ev.MatchesPressOrRepeat("shift+n") { return scheme(kitty.NoPreferenceThemeFileName) } return nil } func (self *handler) update_recent() { if self.themes_list != nil { recent := slices.Clone(self.cached_data.Recent) name := self.themes_list.CurrentTheme().Name() recent = utils.Remove(recent, name) recent = append([]string{name}, recent...) if len(recent) > 20 { recent = recent[:20] } self.cached_data.Recent = recent } } func (self *handler) draw_accepting_screen() { name := self.themes_list.CurrentTheme().Name() name = self.lp.SprintStyled("fg=green bold", name) kc := self.lp.SprintStyled("italic", self.opts.ConfigFileName) ac := func(x string) string { return self.lp.SprintStyled("fg=red", x) } self.lp.AllowLineWrapping(true) defer self.lp.AllowLineWrapping(false) self.lp.Printf(`You have chosen the %s theme`, name) self.lp.Println() self.lp.Println() self.lp.Println(`What would you like to do?`) self.lp.Println() self.lp.Printf(` %sodify %s to load %s`, ac("M"), kc, name) self.lp.Println() self.lp.Println() self.lp.Printf(` %slace the theme file in %s but do not modify %s`, ac("P"), utils.ConfigDir(), kc) self.lp.Println() self.lp.Println() self.lp.Printf(` Save as colors to use when the OS switches to:`) self.lp.Println() self.lp.Printf(` %sark mode`, ac("D")) self.lp.Println() self.lp.Printf(` %sight mode`, ac("L")) self.lp.Println() self.lp.Printf(` %so preference mode`, ac("N")) self.lp.Println() self.lp.Println() self.lp.Printf(` %sbort and return to list of themes`, ac("A")) self.lp.Println() self.lp.Println() self.lp.Printf(` %suit`, ac("Q")) self.lp.Println() } // }}} // searching {{{ func (self *handler) update_search() { text := self.rl.AllText() if self.themes_list.UpdateSearch(text) { self.set_colors_to_current_theme() self.draw_screen() } else { self.draw_search_bar() } } func (self *handler) on_text(text string, a, b bool) error { if self.state == SEARCHING { err := self.rl.OnText(text, a, b) if err != nil { return err } self.update_search() } return nil } func (self *handler) on_searching_key_event(ev *loop.KeyEvent) error { if ev.MatchesPressOrRepeat("enter") { ev.Handled = true self.state = BROWSING self.draw_bottom_bar() return nil } if ev.MatchesPressOrRepeat("esc") { ev.Handled = true self.state = BROWSING self.themes_list.UpdateSearch("") self.set_colors_to_current_theme() self.draw_screen() return nil } err := self.rl.OnKeyEvent(ev) if err != nil { return err } if ev.Handled { self.update_search() } return nil } // }}} kitty-0.41.1/kittens/transfer/0000775000175000017510000000000014773370543015614 5ustar nileshnileshkitty-0.41.1/kittens/transfer/__init__.py0000664000175000017510000000000014773370543017713 0ustar nileshnileshkitty-0.41.1/kittens/transfer/algorithm.c0000664000175000017510000010470214773370543017752 0ustar nileshnilesh//go:build exclude_me /* * algorithm.c * Copyright (C) 2023 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "data-types.h" #include "binary.h" #include #include static PyObject *RsyncError = NULL; static const size_t default_block_size = 6 * 1024; static const size_t signature_block_size = 20; void log_error(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); } // hashers {{{ typedef void*(*new_hash_t)(void); typedef void(*delete_hash_t)(void*); typedef bool(*reset_hash_t)(void*); typedef bool(*update_hash_t)(void*, const void *input, size_t length); typedef void(*digest_hash_t)(const void*, void *output); typedef uint64_t(*digest_hash64_t)(const void*); typedef uint64_t(*oneshot_hash64_t)(const void*, size_t); typedef struct hasher_t { size_t hash_size, block_size; void *state; new_hash_t new; delete_hash_t delete; reset_hash_t reset; update_hash_t update; digest_hash_t digest; digest_hash64_t digest64; oneshot_hash64_t oneshot64; } hasher_t; static void xxh64_delete(void* s) { XXH3_freeState(s); } static bool xxh64_reset(void* s) { return XXH3_64bits_reset(s) == XXH_OK; } static void* xxh64_create(void) { void *ans = XXH3_createState(); if (ans != NULL) xxh64_reset(ans); return ans; } static bool xxh64_update(void* s, const void *input, size_t length) { return XXH3_64bits_update(s, input, length) == XXH_OK; } static uint64_t xxh64_digest64(const void* s) { return XXH3_64bits_digest(s); } static uint64_t xxh64_oneshot64(const void* s, size_t len) { return XXH3_64bits(s, len); } static void xxh64_digest(const void* s, void *output) { XXH64_hash_t ans = XXH3_64bits_digest(s); XXH64_canonical_t c; XXH64_canonicalFromHash(&c, ans); memcpy(output, c.digest, sizeof(c.digest)); } static hasher_t xxh64_hasher(void) { hasher_t ans = { .hash_size=sizeof(XXH64_hash_t), .block_size = 64, .new=xxh64_create, .delete=xxh64_delete, .reset=xxh64_reset, .update=xxh64_update, .digest=xxh64_digest, .digest64=xxh64_digest64, .oneshot64=xxh64_oneshot64 }; return ans; } static bool xxh128_reset(void* s) { return XXH3_128bits_reset(s) == XXH_OK; } static void* xxh128_create(void) { void *ans = XXH3_createState(); if (ans != NULL) xxh128_reset(ans); return ans; } static bool xxh128_update(void* s, const void *input, size_t length) { return XXH3_128bits_update(s, input, length) == XXH_OK; } static void xxh128_digest(const void* s, void *output) { XXH128_hash_t ans = XXH3_128bits_digest(s); XXH128_canonical_t c; XXH128_canonicalFromHash(&c, ans); memcpy(output, c.digest, sizeof(c.digest)); } static hasher_t xxh128_hasher(void) { hasher_t ans = { .hash_size=sizeof(XXH128_hash_t), .block_size = 64, .new=xxh128_create, .delete=xxh64_delete, .reset=xxh128_reset, .update=xxh128_update, .digest=xxh128_digest, }; return ans; } typedef hasher_t(*hasher_constructor_t)(void); // }}} typedef struct Rsync { size_t block_size; hasher_constructor_t hasher_constructor, checksummer_constructor; hasher_t hasher, checksummer; size_t buffer_cap, buffer_sz; } Rsync; static void free_rsync(Rsync* r) { if (r->hasher.state) { r->hasher.delete(r->hasher.state); r->hasher.state = NULL; } if (r->checksummer.state) { r->checksummer.delete(r->checksummer.state); r->checksummer.state = NULL; } } static const char* init_rsync(Rsync *ans, size_t block_size, int strong_hash_type, int checksum_type) { memset(ans, 0, sizeof(*ans)); ans->block_size = block_size; if (strong_hash_type == 0) ans->hasher_constructor = xxh64_hasher; if (checksum_type == 0) ans->checksummer_constructor = xxh128_hasher; if (ans->hasher_constructor == NULL) { free_rsync(ans); return "Unknown strong hash type"; } if (ans->checksummer_constructor == NULL) { free_rsync(ans); return "Unknown checksum type"; } ans->hasher = ans->hasher_constructor(); ans->checksummer = ans->checksummer_constructor(); ans->hasher.state = ans->hasher.new(); if (ans->hasher.state == NULL) { free_rsync(ans); return "Out of memory"; } ans->checksummer.state = ans->checksummer.new(); if (ans->checksummer.state == NULL) { free_rsync(ans); return "Out of memory"; } return NULL; } typedef struct rolling_checksum { uint32_t alpha, beta, val, l, first_byte_of_previous_window; } rolling_checksum; static const uint32_t _M = (1 << 16); static uint32_t rolling_checksum_full(rolling_checksum *self, uint8_t *data, uint32_t len) { uint32_t alpha = 0, beta = 0; self->l = len; for (uint32_t i = 0; i < len; i++) { alpha += data[i]; beta += (self->l - i) * data[i]; } self->first_byte_of_previous_window = data[0]; self->alpha = alpha % _M; self->beta = beta % _M; self->val = self->alpha + _M*self->beta; return self->val; } inline static void rolling_checksum_add_one_byte(rolling_checksum *self, uint8_t first_byte, uint8_t last_byte) { self->alpha = (self->alpha - self->first_byte_of_previous_window + last_byte) % _M; self->beta = (self->beta - (self->l)*self->first_byte_of_previous_window + self->alpha) % _M; self->val = self->alpha + _M*self->beta; self->first_byte_of_previous_window = first_byte; } // Python interface {{{ typedef struct buffer { uint8_t *data; size_t len, cap; } buffer; static bool ensure_space(buffer *b, size_t amt) { const size_t len = b->len; if (amt > 0 && b->cap < len + amt) { size_t newcap = MAX(b->cap * 2, len + (amt * 2)); b->data = realloc(b->data, newcap); if (b->data == NULL) { PyErr_NoMemory(); return false; } b->cap = newcap; } return true; } static bool write_to_buffer(buffer *b, void *data, size_t len) { if (!ensure_space(b, len)) return false; memcpy(b->data + b->len, data, len); b->len += len; return true; } static void shift_left(buffer *b, size_t amt) { if (amt > b->len) amt = b->len; if (amt > 0) { b->len -= amt; memmove(b->data, b->data + amt, b->len); } } // Patcher {{{ typedef struct { PyObject_HEAD rolling_checksum rc; uint64_t signature_idx; size_t total_data_in_delta; Rsync rsync; buffer buf, block_buf; PyObject *block_buf_view; bool checksum_done; } Patcher; static int Patcher_init(PyObject *s, PyObject *args, PyObject *kwds) { Patcher *self = (Patcher*)s; static char *kwlist[] = {"expected_input_size", NULL}; unsigned long long expected_input_size = 0; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|K", kwlist, &expected_input_size)) return -1; self->rsync.block_size = default_block_size; if (expected_input_size > 0) { self->rsync.block_size = (size_t)round(sqrt((double)expected_input_size)); } const char *err = init_rsync(&self->rsync, self->rsync.block_size, 0, 0); if (err != NULL) { PyErr_SetString(RsyncError, err); return -1; } self->block_buf.cap = self->rsync.block_size; self->block_buf.data = malloc(self->rsync.block_size); if (self->block_buf.data == NULL) { PyErr_NoMemory(); return -1; } if (!(self->block_buf_view = PyMemoryView_FromMemory((char*)self->block_buf.data, self->rsync.block_size, PyBUF_WRITE))) return -1; return 0; } static void Patcher_dealloc(PyObject *self) { Patcher *p = (Patcher*)self; if (p->buf.data) free(p->buf.data); Py_CLEAR(p->block_buf_view); if (p->block_buf.data) free(p->block_buf.data); free_rsync(&p->rsync); Py_TYPE(self)->tp_free(self); } static PyObject* signature_header(Patcher *self, PyObject *a2) { RAII_PY_BUFFER(dest); if (PyObject_GetBuffer(a2, &dest, PyBUF_WRITEABLE) == -1) return NULL; static const ssize_t header_size = 12; if (dest.len < header_size) { PyErr_SetString(RsyncError, "Output buffer is too small"); } uint8_t *o = dest.buf; le16enc(o, 0); // version le16enc(o + 2, 0); // checksum type le16enc(o + 4, 0); // strong hash type le16enc(o + 6, 0); // weak hash type le32enc(o + 8, self->rsync.block_size); // block size return PyLong_FromSsize_t(header_size); } static PyObject* sign_block(Patcher *self, PyObject *args) { PyObject *a1, *a2; if (!PyArg_ParseTuple(args, "OO", &a1, &a2)) return NULL; RAII_PY_BUFFER(src); RAII_PY_BUFFER(dest); if (PyObject_GetBuffer(a1, &src, PyBUF_SIMPLE) == -1) return NULL; if (PyObject_GetBuffer(a2, &dest, PyBUF_WRITEABLE) == -1) return NULL; if (dest.len < (ssize_t)signature_block_size) { PyErr_SetString(RsyncError, "Output buffer is too small"); } self->rsync.hasher.reset(self->rsync.hasher.state); if (!self->rsync.hasher.update(self->rsync.hasher.state, src.buf, src.len)) { PyErr_SetString(PyExc_ValueError, "String hashing failed"); return NULL; } uint64_t strong_hash = self->rsync.hasher.oneshot64(src.buf, src.len); uint32_t weak_hash = rolling_checksum_full(&self->rc, src.buf, src.len); uint8_t *o = dest.buf; le64enc(o, self->signature_idx++); le32enc(o + 8, weak_hash); le64enc(o + 12, strong_hash); return PyLong_FromSize_t(signature_block_size); } typedef enum { OpBlock, OpData, OpHash, OpBlockRange } OpType; typedef struct Operation { OpType type; uint64_t block_index, block_index_end; struct { uint8_t *buf; size_t len; } data; } Operation; static size_t unserialize_op(uint8_t *data, size_t len, Operation *op) { size_t consumed = 0; switch ((OpType)(data[0])) { case OpBlock: consumed = 9; if (len < consumed) return 0; op->block_index = le64dec(data + 1); break; case OpBlockRange: consumed = 13; if (len < consumed) return 0; op->block_index = le64dec(data + 1); op->block_index_end = op->block_index + le32dec(data + 9); break; case OpHash: consumed = 3; if (len < consumed) return 0; op->data.len = le16dec(data + 1); if (len < consumed + op->data.len) return 0; op->data.buf = data + 3; consumed += op->data.len; break; case OpData: consumed = 5; if (len < consumed) return 0; op->data.len = le32dec(data + 1); if (len < consumed + op->data.len) return 0; op->data.buf = data + 5; consumed += op->data.len; break; } if (consumed) op->type = data[0]; return consumed; } static bool write_block(Patcher *self, uint64_t block_index, PyObject *read, PyObject *write) { RAII_PyObject(pos, PyLong_FromUnsignedLongLong((unsigned long long)(self->rsync.block_size * block_index))); if (!pos) return false; RAII_PyObject(ret, PyObject_CallFunctionObjArgs(read, pos, self->block_buf_view, NULL)); if (ret == NULL) return false; if (!PyLong_Check(ret)) { PyErr_SetString(PyExc_TypeError, "read callback function did not return an integer"); return false; } size_t n = PyLong_AsSize_t(ret); self->rsync.checksummer.update(self->rsync.checksummer.state, self->block_buf.data, n); RAII_PyObject(view, PyMemoryView_FromMemory((char*)self->block_buf.data, n, PyBUF_READ)); if (!view) return false; RAII_PyObject(wret, PyObject_CallFunctionObjArgs(write, view, NULL)); if (wret == NULL) return false; return true; } static void bytes_as_hex(const uint8_t *bytes, const size_t len, char *ans) { static const char * hex = "0123456789abcdef"; char *pout = ans; const uint8_t *pin = bytes; for (; pin < bytes + len; pin++) { *pout++ = hex[(*pin>>4) & 0xF]; *pout++ = hex[ *pin & 0xF]; } *pout++ = 0; } static bool apply_op(Patcher *self, Operation op, PyObject *read, PyObject *write) { switch (op.type) { case OpBlock: return write_block(self, op.block_index, read, write); case OpBlockRange: for (size_t i = op.block_index; i <= op.block_index_end; i++) { if (!write_block(self, i, read, write)) return false; } return true; case OpData: { self->total_data_in_delta += op.data.len; self->rsync.checksummer.update(self->rsync.checksummer.state, op.data.buf, op.data.len); RAII_PyObject(view, PyMemoryView_FromMemory((char*)op.data.buf, op.data.len, PyBUF_READ)); if (!view) return false; RAII_PyObject(wret, PyObject_CallFunctionObjArgs(write, view, NULL)); if (!wret) return false; } return true; case OpHash: { uint8_t actual[64]; if (op.data.len != self->rsync.checksummer.hash_size) { PyErr_SetString(RsyncError, "checksum digest not the correct size"); return false; } self->rsync.checksummer.digest(self->rsync.checksummer.state, actual); if (memcmp(actual, op.data.buf, self->rsync.checksummer.hash_size) != 0) { char hexdigest[129]; bytes_as_hex(actual, self->rsync.checksummer.hash_size, hexdigest); RAII_PyObject(h1, PyUnicode_FromStringAndSize(hexdigest, 2*self->rsync.checksummer.hash_size)); bytes_as_hex(op.data.buf, op.data.len, hexdigest); RAII_PyObject(h2, PyUnicode_FromStringAndSize(hexdigest, 2*self->rsync.checksummer.hash_size)); PyErr_Format(RsyncError, "Failed to verify overall file checksum actual: %S != expected: %S, this usually happens because one of the involved files was altered while the operation was in progress.", h1, h2); return false; } self->checksum_done = true; } return true; } PyErr_SetString(RsyncError, "Unknown operation type"); return false; } static PyObject* apply_delta_data(Patcher *self, PyObject *args) { PyObject *read, *write; RAII_PY_BUFFER(data); if (!PyArg_ParseTuple(args, "y*OO", &data, &read, &write)) return NULL; if (!write_to_buffer(&self->buf, data.buf, data.len)) return NULL; size_t pos = 0; Operation op = {0}; while (pos < self->buf.len) { size_t consumed = unserialize_op(self->buf.data + pos, self->buf.len - pos, &op); if (!consumed) { break; } pos += consumed; if (!apply_op(self, op, read, write)) break; } shift_left(&self->buf, pos); if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } static PyObject* finish_delta_data(Patcher *self, PyObject *args UNUSED) { if (self->buf.len > 0) { PyErr_Format(RsyncError, "%zu bytes of unused delta data", self->buf.len); return NULL; } if (!self->checksum_done) { PyErr_SetString(RsyncError, "The checksum was not received at the end of the delta data"); return NULL; } Py_RETURN_NONE; } static PyObject* Patcher_block_size(Patcher* self, void* closure UNUSED) { return PyLong_FromSize_t(self->rsync.block_size); } static PyObject* Patcher_total_data_in_delta(Patcher* self, void* closure UNUSED) { return PyLong_FromSize_t(self->total_data_in_delta); } PyGetSetDef Patcher_getsets[] = { {"block_size", (getter)Patcher_block_size, NULL, NULL, NULL}, {"total_data_in_delta", (getter)Patcher_total_data_in_delta, NULL, NULL, NULL}, {NULL} }; static PyMethodDef Patcher_methods[] = { METHODB(sign_block, METH_VARARGS), METHODB(signature_header, METH_O), METHODB(apply_delta_data, METH_VARARGS), METHODB(finish_delta_data, METH_NOARGS), {NULL} /* Sentinel */ }; PyTypeObject Patcher_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "rsync.Patcher", .tp_basicsize = sizeof(Patcher), .tp_dealloc = Patcher_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "Patcher", .tp_methods = Patcher_methods, .tp_new = PyType_GenericNew, .tp_init = Patcher_init, .tp_getset = Patcher_getsets, }; // }}} Patcher // Differ {{{ typedef struct Signature { uint64_t index, strong_hash; } Signature; typedef struct SignatureVal { Signature sig, *weak_hash_collisions; size_t len, cap; } SignatureVal; #define NAME SignatureMap #define KEY_TY int #define VAL_TY SignatureVal static void free_signature_val(SignatureVal x) { free(x.weak_hash_collisions); } #define VAL_DTOR_FN free_signature_val #include "kitty-verstable.h" typedef struct Differ { PyObject_HEAD rolling_checksum rc; uint64_t signature_idx; Rsync rsync; bool signature_header_parsed; buffer buf; SignatureMap signature_map; PyObject *read, *write; bool written, finished; struct { size_t pos, sz; } window, data; Operation pending_op; bool has_pending; uint8_t checksum[32]; } Differ; static int Differ_init(PyObject *s, PyObject *args, PyObject *kwds) { Differ *self = (Differ*)s; static char *kwlist[] = {NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "", kwlist)) return -1; const char *err = init_rsync(&self->rsync, default_block_size, 0, 0); if (err != NULL) { PyErr_SetString(RsyncError, err); return -1; } vt_init(&self->signature_map); return 0; } static void Differ_dealloc(PyObject *self) { Differ *p = (Differ*)self; if (p->buf.data) free(p->buf.data); free_rsync(&p->rsync); vt_cleanup(&p->signature_map); Py_TYPE(self)->tp_free(self); } static void parse_signature_header(Differ *self) { if (self->buf.len < 12) return; uint8_t *p = self->buf.data; uint32_t x; if ((x = le16dec(p)) != 0) { PyErr_Format(RsyncError, "Invalid version in signature header: %u", x); return; } p += 2; if ((x = le16dec(p)) != 0) { PyErr_Format(RsyncError, "Invalid checksum type in signature header: %u", x); return; } p += 2; if ((x = le16dec(p)) != 0) { PyErr_Format(RsyncError, "Invalid strong hash type in signature header: %u", x); return; } p += 2; if ((x = le16dec(p)) != 0) { PyErr_Format(RsyncError, "Invalid weak hash type in signature header: %u", x); return; } p += 2; const char *err = init_rsync(&self->rsync, le32dec(p), 0, 0); if (err != NULL) { PyErr_SetString(RsyncError, err); return; } p += 4; shift_left(&self->buf, p - self->buf.data); self->signature_header_parsed = true; } static bool add_collision(SignatureVal *sm, Signature s) { if (sm->cap < sm->len + 1) { size_t new_cap = MAX(sm->cap * 2, 8u); sm->weak_hash_collisions = realloc(sm->weak_hash_collisions, new_cap * sizeof(sm->weak_hash_collisions[0])); if (!sm->weak_hash_collisions) { PyErr_NoMemory(); return false; } sm->cap = new_cap; } sm->weak_hash_collisions[sm->len++] = s; return true; } static size_t parse_signature_block(Differ *self, uint8_t *data, size_t len) { if (len < 20) return 0; int weak_hash = le32dec(data + 8); SignatureMap_itr i = vt_get(&self->signature_map, weak_hash); if (vt_is_end(i)) { SignatureVal s = {0}; s.sig.index = le64dec(data); s.sig.strong_hash = le64dec(data+12); vt_insert(&self->signature_map, weak_hash, s); } else { if (!add_collision(&i.data->val, (Signature){.index=le64dec(data), .strong_hash=le64dec(data+12)})) return 0; } return 20; } static PyObject* add_signature_data(Differ *self, PyObject *args) { RAII_PY_BUFFER(data); if (!PyArg_ParseTuple(args, "y*", &data)) return NULL; if (!write_to_buffer(&self->buf, data.buf, data.len)) return NULL; if (!self->signature_header_parsed) { parse_signature_header(self); if (PyErr_Occurred()) return NULL; if (!self->signature_header_parsed) { Py_RETURN_NONE; } } size_t pos = 0; while (pos < self->buf.len) { size_t consumed = parse_signature_block(self, self->buf.data + pos, self->buf.len - pos); if (!consumed) { break; } pos += consumed; } shift_left(&self->buf, pos); if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } static PyObject* finish_signature_data(Differ *self, PyObject *args UNUSED) { if (self->buf.len > 0) { PyErr_Format(RsyncError, "%zu bytes of unused signature data", self->buf.len); return NULL; } self->buf.len = 0; self->buf.cap = 8 * self->rsync.block_size; self->buf.data = realloc(self->buf.data, self->buf.cap); if (!self->buf.data) return PyErr_NoMemory(); Py_RETURN_NONE; } static bool send_op(Differ *self, Operation *op) { uint8_t metadata[32]; size_t len = 0; metadata[0] = op->type; switch (op->type) { case OpBlock: le64enc(metadata + 1, op->block_index); len = 9; break; case OpBlockRange: le64enc(metadata + 1, op->block_index); le32enc(metadata + 9, op->block_index_end - op->block_index); len = 13; break; case OpHash: le16enc(metadata + 1, op->data.len); memcpy(metadata + 3, op->data.buf, op->data.len); len = 3 + op->data.len; break; case OpData: le32enc(metadata + 1, op->data.len); len = 5; break; } RAII_PyObject(mv, PyMemoryView_FromMemory((char*)metadata, len, PyBUF_READ)); RAII_PyObject(ret, PyObject_CallFunctionObjArgs(self->write, mv, NULL)); if (ret == NULL) return false; if (op->type == OpData) { RAII_PyObject(mv, PyMemoryView_FromMemory((char*)op->data.buf, op->data.len, PyBUF_READ)); RAII_PyObject(ret, PyObject_CallFunctionObjArgs(self->write, mv, NULL)); if (ret == NULL) return false; } self->written = true; return true; } static bool send_pending(Differ *self) { bool ret = true; if (self->has_pending) { ret = send_op(self, &self->pending_op); self->has_pending = false; } return ret; } static bool send_data(Differ *self) { if (self->data.sz > 0) { if (!send_pending(self)) return false; Operation op = {.type=OpData}; op.data.buf = self->buf.data + self->data.pos; op.data.len = self->data.sz; self->data.pos += self->data.sz; self->data.sz = 0; return send_op(self, &op); } return true; } static bool ensure_idx_valid(Differ *self, size_t idx) { if (idx < self->buf.len) return true; if (idx >= self->buf.cap) { // need to wrap the buffer, so send off any data present behind the window if (!send_data(self)) return false; // copy the window and any data present after it to the start of the buffer size_t distance_from_window_pos = idx - self->window.pos; size_t amt_to_copy = self->buf.len - self->window.pos; memmove(self->buf.data, self->buf.data + self->window.pos, amt_to_copy); self->buf.len = amt_to_copy; self->window.pos = 0; self->data.pos = 0; return ensure_idx_valid(self, distance_from_window_pos); } RAII_PyObject(mv, PyMemoryView_FromMemory((char*)self->buf.data + self->buf.len, self->buf.cap - self->buf.len, PyBUF_WRITE)); if (!mv) return false; RAII_PyObject(ret, PyObject_CallFunctionObjArgs(self->read, mv, NULL)); if (!ret) return false; if (!PyLong_Check(ret)) { PyErr_SetString(PyExc_TypeError, "read callback did not return an integer"); return false; } size_t n = PyLong_AsSize_t(ret); self->rsync.checksummer.update(self->rsync.checksummer.state, self->buf.data + self->buf.len, n); self->buf.len += n; return self->buf.len > idx; } static bool find_strong_hash(const SignatureVal *sm, uint64_t q, uint64_t *block_index) { if (sm->sig.strong_hash == q) { *block_index = sm->sig.index; return true; } for (size_t i = 0; i < sm->len; i++) { if (sm->weak_hash_collisions[i].strong_hash == q) { *block_index = sm->weak_hash_collisions[i].index; return true; } } return false; } static bool enqueue(Differ *self, Operation op) { switch (op.type) { case OpBlock: if (self->has_pending) { switch (self->pending_op.type) { case OpBlock: if (self->pending_op.block_index+1 == op.block_index) { self->pending_op.type = OpBlockRange; self->pending_op.block_index_end = op.block_index; return true; } break; case OpBlockRange: if (self->pending_op.block_index_end+1 == op.block_index) { self->pending_op.block_index_end = op.block_index; return true; } case OpHash: case OpData: break; } if (!send_pending(self)) return false; } self->pending_op = op; self->has_pending = true; return true; case OpHash: if (!send_pending(self)) return false; return send_op(self, &op); case OpBlockRange: case OpData: PyErr_SetString(RsyncError, "enqueue() must never be called with anything other than OpHash and OpBlock"); return false; } return false; } static bool finish_up(Differ *self) { if (!send_data(self)) return false; self->data.pos = self->window.pos; self->data.sz = self->buf.len - self->window.pos; if (!send_data(self)) return false; self->rsync.checksummer.digest(self->rsync.checksummer.state, self->checksum); Operation op = {.type=OpHash}; op.data.buf = self->checksum; op.data.len = self->rsync.checksummer.hash_size; if (!enqueue(self, op)) return false; self->finished = true; return true; } static bool read_next(Differ *self) { if (self->window.sz > 0) { if (!ensure_idx_valid(self, self->window.pos + self->window.sz)) { if (PyErr_Occurred()) return false; return finish_up(self); } self->window.pos++; self->data.sz++; rolling_checksum_add_one_byte(&self->rc, self->buf.data[self->window.pos], self->buf.data[self->window.pos + self->window.sz - 1]); } else { if (!ensure_idx_valid(self, self->window.pos + self->rsync.block_size - 1)) { if (PyErr_Occurred()) return false; return finish_up(self); } self->window.sz = self->rsync.block_size; rolling_checksum_full(&self->rc, self->buf.data + self->window.pos, self->window.sz); } int weak_hash = self->rc.val; uint64_t block_index = 0; SignatureMap_itr i = vt_get(&self->signature_map, weak_hash); if (!vt_is_end(i) && find_strong_hash(&i.data->val, self->rsync.hasher.oneshot64(self->buf.data + self->window.pos, self->window.sz), &block_index)) { if (!send_data(self)) return false; if (!enqueue(self, (Operation){.type=OpBlock, .block_index=block_index})) return false; self->window.pos += self->window.sz; self->data.pos = self->window.pos; self->window.sz = 0; } return true; } static PyObject* next_op(Differ *self, PyObject *args) { if (!PyArg_ParseTuple(args, "OO", &self->read, &self->write)) return NULL; self->written = false; while (!self->written && !self->finished) { if (!read_next(self)) break; } if (self->finished && !PyErr_Occurred()) { send_pending(self); } self->read = NULL; self->write = NULL; if (PyErr_Occurred()) return NULL; if (self->finished) { Py_RETURN_FALSE; } Py_RETURN_TRUE; } static PyMethodDef Differ_methods[] = { METHODB(add_signature_data, METH_VARARGS), METHODB(finish_signature_data, METH_NOARGS), METHODB(next_op, METH_VARARGS), {NULL} /* Sentinel */ }; PyTypeObject Differ_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "rsync.Differ", .tp_basicsize = sizeof(Differ), .tp_dealloc = Differ_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "Differ", .tp_methods = Differ_methods, .tp_new = PyType_GenericNew, .tp_init = Differ_init, }; // }}} Differ // Hasher {{{ typedef struct { PyObject_HEAD hasher_t h; const char *name; } Hasher; static int Hasher_init(PyObject *s, PyObject *args, PyObject *kwds) { Hasher *self = (Hasher*)s; static char *kwlist[] = {"which", "data", NULL}; const char *which = "xxh3-64"; RAII_PY_BUFFER(data); if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sy*", kwlist, &which, &data)) return -1; if (strcmp(which, "xxh3-64") == 0) { self->h = xxh64_hasher(); self->name = "xxh3-64"; } else if (strcmp(which, "xxh3-128") == 0) { self->h = xxh128_hasher(); self->name = "xxh3-128"; } else { PyErr_Format(PyExc_KeyError, "Unknown hash type: %s", which); return -1; } self->h.state = self->h.new(); if (self->h.state == NULL) { PyErr_NoMemory(); return -1; } if (data.buf && data.len > 0) { self->h.update(self->h.state, data.buf, data.len); } return 0; } static void Hasher_dealloc(PyObject *self) { Hasher *h = (Hasher*)self; if (h->h.state) { h->h.delete(h->h.state); h->h.state = NULL; } Py_TYPE(self)->tp_free(self); } static PyObject* reset(Hasher *self, PyObject *args UNUSED) { if (!self->h.reset(self->h.state)) return PyErr_NoMemory(); Py_RETURN_NONE; } static PyObject* update(Hasher *self, PyObject *o) { RAII_PY_BUFFER(data); if (PyObject_GetBuffer(o, &data, PyBUF_SIMPLE) == -1) return NULL; if (data.buf && data.len > 0) { self->h.update(self->h.state, data.buf, data.len); } Py_RETURN_NONE; } static PyObject* digest(Hasher *self, PyObject *args UNUSED) { PyObject *ans = PyBytes_FromStringAndSize(NULL, self->h.hash_size); if (ans) self->h.digest(self->h.state, PyBytes_AS_STRING(ans)); return ans; } static PyObject* digest64(Hasher *self, PyObject *args UNUSED) { if (self->h.digest64 == NULL) { PyErr_SetString(PyExc_TypeError, "Does not support 64-bit digests"); return NULL; } unsigned long long a = self->h.digest64(self->h.state); return PyLong_FromUnsignedLongLong(a); } static PyObject* hexdigest(Hasher *self, PyObject *args UNUSED) { uint8_t digest[64]; char hexdigest[128]; self->h.digest(self->h.state, digest); bytes_as_hex(digest, self->h.hash_size, hexdigest); return PyUnicode_FromStringAndSize(hexdigest, self->h.hash_size * 2); } static PyObject* Hasher_digest_size(Hasher* self, void* closure UNUSED) { return PyLong_FromSize_t(self->h.hash_size); } static PyObject* Hasher_block_size(Hasher* self, void* closure UNUSED) { return PyLong_FromSize_t(self->h.block_size); } static PyObject* Hasher_name(Hasher* self, void* closure UNUSED) { return PyUnicode_FromString(self->name); } static PyMethodDef Hasher_methods[] = { METHODB(update, METH_O), METHODB(digest, METH_NOARGS), METHODB(digest64, METH_NOARGS), METHODB(hexdigest, METH_NOARGS), METHODB(reset, METH_NOARGS), {NULL} /* Sentinel */ }; PyGetSetDef Hasher_getsets[] = { {"digest_size", (getter)Hasher_digest_size, NULL, NULL, NULL}, {"block_size", (getter)Hasher_block_size, NULL, NULL, NULL}, {"name", (getter)Hasher_name, NULL, NULL, NULL}, {NULL} }; PyTypeObject Hasher_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "rsync.Hasher", .tp_basicsize = sizeof(Hasher), .tp_dealloc = Hasher_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "Hasher", .tp_methods = Hasher_methods, .tp_new = PyType_GenericNew, .tp_init = Hasher_init, .tp_getset = Hasher_getsets, }; // }}} end Hasher static bool call_ftc_callback(PyObject *callback, char *src, Py_ssize_t key_start, Py_ssize_t key_length, Py_ssize_t val_start, Py_ssize_t val_length) { while(src[key_start] == ';' && key_length > 0 ) { key_start++; key_length--; } RAII_PyObject(k, PyMemoryView_FromMemory(src + key_start, key_length, PyBUF_READ)); if (!k) return false; RAII_PyObject(v, PyMemoryView_FromMemory(src + val_start, val_length, PyBUF_READ)); if (!v) return false; RAII_PyObject(ret, PyObject_CallFunctionObjArgs(callback, k, v, NULL)); return ret != NULL; } static PyObject* parse_ftc(PyObject *self UNUSED, PyObject *args) { RAII_PY_BUFFER(buf); PyObject *callback; size_t i = 0, key_start = 0, key_length = 0, val_start = 0, val_length = 0; if (!PyArg_ParseTuple(args, "s*O", &buf, &callback)) return NULL; char *src = buf.buf; size_t sz = buf.len; if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "callback must be callable"); return NULL; } for (i = 0; i < sz; i++) { char ch = src[i]; if (key_length == 0) { if (ch == '=') { key_length = i - key_start; val_start = i + 1; } } else { if (ch == ';') { val_length = i - val_start; if (!call_ftc_callback(callback, src, key_start, key_length, val_start, val_length)) return NULL; key_length = 0; key_start = i + 1; val_start = 0; } } } if (key_length && val_start) { val_length = sz - val_start; if (!call_ftc_callback(callback, src, key_start, key_length, val_start, val_length)) return NULL; } Py_RETURN_NONE; } static PyObject* pyxxh128_hash(PyObject *self UNUSED, PyObject *b) { RAII_PY_BUFFER(data); if (PyObject_GetBuffer(b, &data, PyBUF_SIMPLE) == -1) return NULL; XXH128_canonical_t c; XXH128_canonicalFromHash(&c, XXH3_128bits(data.buf, data.len)); return PyBytes_FromStringAndSize((char*)c.digest, sizeof(c.digest)); } static PyObject* pyxxh128_hash_with_seed(PyObject *self UNUSED, PyObject *args) { RAII_PY_BUFFER(data); unsigned long long seed; if (!PyArg_ParseTuple(args, "y*K", &data, &seed)) return NULL; XXH128_canonical_t c; XXH128_canonicalFromHash(&c, XXH3_128bits_withSeed(data.buf, data.len, seed)); return PyBytes_FromStringAndSize((char*)c.digest, sizeof(c.digest)); } static PyMethodDef module_methods[] = { {"parse_ftc", parse_ftc, METH_VARARGS, ""}, {"xxh128_hash", pyxxh128_hash, METH_O, ""}, {"xxh128_hash_with_seed", pyxxh128_hash_with_seed, METH_VARARGS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ }; static int exec_module(PyObject *m) { RsyncError = PyErr_NewException("rsync.RsyncError", NULL, NULL); if (RsyncError == NULL) return -1; PyModule_AddObject(m, "RsyncError", RsyncError); #define T(which) if (PyType_Ready(& which##_Type) < 0) return -1; Py_INCREF(&which##_Type);\ if (PyModule_AddObject(m, #which, (PyObject *) &which##_Type) < 0) return -1; T(Hasher); T(Patcher); T(Differ); #undef T return 0; } IGNORE_PEDANTIC_WARNINGS static PyModuleDef_Slot slots[] = { {Py_mod_exec, (void*)exec_module}, {0, NULL} }; END_IGNORE_PEDANTIC_WARNINGS static struct PyModuleDef module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "rsync", /* name of module */ .m_doc = NULL, .m_slots = slots, .m_methods = module_methods }; EXPORTED PyMODINIT_FUNC PyInit_rsync(void) { return PyModuleDef_Init(&module); } // }}} kitty-0.41.1/kittens/transfer/ftc.go0000664000175000017510000002025114773370543016717 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package transfer import ( "encoding/base64" "encoding/json" "fmt" "io/fs" "reflect" "regexp" "strconv" "strings" "sync" "time" "kitty" "kitty/tools/utils" ) var _ = fmt.Print type Serializable interface { String() string MarshalJSON() ([]byte, error) } type Unserializable interface { SetString(string) error } type Action int // enum var _ Serializable = Action_cancel var _ Unserializable = (*Action)(nil) const ( Action_invalid Action = iota Action_file Action_data Action_end_data Action_receive Action_send Action_cancel Action_status Action_finish ) type Compression int // enum var _ Serializable = Compression_none var _ Unserializable = (*Compression)(nil) const ( Compression_none Compression = iota Compression_zlib ) type FileType int // enum var _ Serializable = FileType_regular var _ Unserializable = (*FileType)(nil) const ( FileType_regular FileType = iota FileType_symlink FileType_directory FileType_link ) func (self FileType) ShortText() string { switch self { case FileType_regular: return "fil" case FileType_directory: return "dir" case FileType_symlink: return "sym" case FileType_link: return "lnk" } return "und" } func (self FileType) Color() string { switch self { case FileType_regular: return "yellow" case FileType_directory: return "magenta" case FileType_symlink: return "blue" case FileType_link: return "green" } return "" } type TransmissionType int // enum var _ Serializable = TransmissionType_simple var _ Unserializable = (*TransmissionType)(nil) const ( TransmissionType_simple TransmissionType = iota TransmissionType_rsync ) type QuietLevel int // enum var _ Serializable = Quiet_none var _ Unserializable = (*QuietLevel)(nil) const ( Quiet_none QuietLevel = iota // 0 Quiet_acknowledgements // 1 Quiet_errors // 2 ) type FileTransmissionCommand struct { Action Action `json:"ac,omitempty"` Compression Compression `json:"zip,omitempty"` Ftype FileType `json:"ft,omitempty"` Ttype TransmissionType `json:"tt,omitempty"` Quiet QuietLevel `json:"q,omitempty"` Id string `json:"id,omitempty"` File_id string `json:"fid,omitempty"` Bypass string `json:"pw,omitempty" encoding:"base64"` Name string `json:"n,omitempty" encoding:"base64"` Status string `json:"st,omitempty" encoding:"base64"` Parent string `json:"pr,omitempty"` Mtime time.Duration `json:"mod,omitempty"` Permissions fs.FileMode `json:"prm,omitempty"` Size int64 `json:"sz,omitempty" default:"-1"` Data []byte `json:"d,omitempty"` } var ftc_field_map = sync.OnceValue(func() map[string]reflect.StructField { ans := make(map[string]reflect.StructField) self := FileTransmissionCommand{} v := reflect.ValueOf(self) typ := v.Type() fields := reflect.VisibleFields(typ) for _, field := range fields { if name := field.Tag.Get("json"); name != "" && field.IsExported() { name, _, _ = strings.Cut(name, ",") ans[name] = field } } return ans }) var safe_string_pat = sync.OnceValue(func() *regexp.Regexp { return regexp.MustCompile(`[^0-9a-zA-Z_:./@-]`) }) func safe_string(x string) string { return safe_string_pat().ReplaceAllLiteralString(x, ``) } func (self FileTransmissionCommand) Serialize(prefix_with_osc_code ...bool) string { ans := strings.Builder{} v := reflect.ValueOf(self) found := false if len(prefix_with_osc_code) > 0 && prefix_with_osc_code[0] { ans.WriteString(strconv.Itoa(kitty.FileTransferCode)) found = true } for name, field := range ftc_field_map() { val := v.FieldByIndex(field.Index) encoded_val := "" switch val.Kind() { case reflect.String: if sval := val.String(); sval != "" { enc := field.Tag.Get("encoding") switch enc { case "base64": encoded_val = base64.RawStdEncoding.EncodeToString(utils.UnsafeStringToBytes(sval)) default: encoded_val = safe_string(sval) } } case reflect.Slice: switch val.Type().Elem().Kind() { case reflect.Uint8: if bval := val.Bytes(); len(bval) > 0 { encoded_val = base64.RawStdEncoding.EncodeToString(bval) } } case reflect.Int64: if ival := val.Int(); ival != 0 && (ival > 0 || name != "sz") { encoded_val = strconv.FormatInt(ival, 10) } default: if val.CanInterface() { switch field := val.Interface().(type) { case fs.FileMode: if field = field.Perm(); field != 0 { encoded_val = strconv.FormatInt(int64(field), 10) } case Serializable: if !val.Equal(reflect.Zero(val.Type())) { encoded_val = field.String() } } } } if encoded_val != "" { if found { ans.WriteString(";") } else { found = true } ans.WriteString(name) ans.WriteString("=") ans.WriteString(encoded_val) } } return ans.String() } func (self FileTransmissionCommand) String() string { s := self s.Data = nil ans, _ := json.Marshal(s) return utils.UnsafeBytesToString(ans) } func NewFileTransmissionCommand(serialized string) (ans *FileTransmissionCommand, err error) { ans = &FileTransmissionCommand{} v := reflect.Indirect(reflect.ValueOf(ans)) if err = utils.SetStructDefaults(v); err != nil { return } field_map := ftc_field_map() key_length, key_start, val_start := 0, 0, 0 handle_value := func(key, serialized_val string) error { key = strings.TrimLeft(key, `;`) if field, ok := field_map[key]; ok { val := v.FieldByIndex(field.Index) switch val.Kind() { case reflect.String: switch field.Tag.Get("encoding") { case "base64": b, err := base64.RawStdEncoding.DecodeString(serialized_val) if err != nil { return fmt.Errorf("The field %#v has invalid base64 encoded value with error: %w", key, err) } val.SetString(utils.UnsafeBytesToString(b)) default: val.SetString(safe_string(serialized_val)) } case reflect.Slice: switch val.Type().Elem().Kind() { case reflect.Uint8: b, err := base64.RawStdEncoding.DecodeString(serialized_val) if err != nil { return fmt.Errorf("The field %#v has invalid base64 encoded value with error: %w", key, err) } val.SetBytes(b) } case reflect.Int64: b, err := strconv.ParseInt(serialized_val, 10, 64) if err != nil { return fmt.Errorf("The field %#v has invalid integer value with error: %w", key, err) } val.SetInt(b) default: if val.CanAddr() { switch field := val.Addr().Interface().(type) { case Unserializable: err = field.SetString(serialized_val) if err != nil { return fmt.Errorf("The field %#v has invalid enum value with error: %w", key, err) } case *fs.FileMode: b, err := strconv.ParseUint(serialized_val, 10, 32) if err != nil { return fmt.Errorf("The field %#v has invalid file mode value with error: %w", key, err) } *field = fs.FileMode(b).Perm() } } } return nil } else { return fmt.Errorf("The field name %#v is not known", key) } } for i := 0; i < len(serialized); i++ { ch := serialized[i] if key_length == 0 { if ch == '=' { key_length = i - key_start val_start = i + 1 } } else { if ch == ';' { val_length := i - val_start if key_length > 0 && val_start > 0 { err = handle_value(serialized[key_start:key_start+key_length], serialized[val_start:val_start+val_length]) if err != nil { return nil, err } } key_length = 0 key_start = i + 1 val_start = 0 } } } if key_length > 0 && val_start > 0 { err = handle_value(serialized[key_start:key_start+key_length], serialized[val_start:]) if err != nil { return nil, err } } return } func split_for_transfer(data []byte, file_id string, mark_last bool, callback func(*FileTransmissionCommand)) { const chunk_size = 4096 for len(data) > 0 { chunk := data if len(chunk) > chunk_size { chunk = data[:chunk_size] } data = data[len(chunk):] callback(&FileTransmissionCommand{ Action: utils.IfElse(mark_last && len(data) == 0, Action_end_data, Action_data), File_id: file_id, Data: chunk}) } } kitty-0.41.1/kittens/transfer/ftc_test.go0000664000175000017510000000214714773370543017762 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package transfer import ( "fmt" "strings" "testing" "time" "github.com/google/go-cmp/cmp" ) var _ = fmt.Print func TestFTCSerialization(t *testing.T) { ftc := FileTransmissionCommand{} q := func(expected string) { actual := ftc.Serialize() ad := make(map[string]bool) for _, x := range strings.Split(actual, ";") { ad[x] = true } ed := make(map[string]bool) for _, x := range strings.Split(expected, ";") { ed[x] = true } if diff := cmp.Diff(ed, ad); diff != "" { t.Fatalf("Failed to Serialize:\n%s", diff) } } q("") ftc.Action = Action_send q("ac=send") ftc.File_id = "fid" ftc.Name = "moose" ftc.Mtime = time.Second ftc.Permissions = 0o600 ftc.Data = []byte("moose") q("ac=send;fid=fid;n=bW9vc2U;mod=1000000000;prm=384;d=bW9vc2U") n, err := NewFileTransmissionCommand(ftc.Serialize()) if err != nil { t.Fatal(err) } q(n.Serialize()) unsafe := "moo\x1b;;[?*.-se1" if safe_string(unsafe) != "moo.-se1" { t.Fatalf("safe_string() failed for %#v yielding: %#v", unsafe, safe_string(unsafe)) } } kitty-0.41.1/kittens/transfer/main.go0000664000175000017510000000267114773370543017075 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package transfer import ( "fmt" "io" "os" "strconv" "strings" "kitty/tools/cli" "kitty/tools/utils" ) var _ = fmt.Print func read_bypass(loc string) (string, error) { if loc == "" { return "", nil } fdnum, err := strconv.Atoi(loc) if err == nil && fdnum >= 0 && fdnum < 256 && loc[0] >= '0' && loc[0] <= '9' { file := os.NewFile(uintptr(fdnum), loc) defer file.Close() raw, err := io.ReadAll(file) return utils.UnsafeBytesToString(raw), err } if loc == "-" { raw, err := io.ReadAll(os.Stdin) defer os.Stdin.Close() return utils.UnsafeBytesToString(raw), err } switch loc[0] { case '.', '~', '/': if loc[0] == '~' { loc = utils.Expanduser(loc) } raw, err := os.ReadFile(loc) return utils.UnsafeBytesToString(raw), err default: return loc, nil } } func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) { if opts.PermissionsBypass != "" { val, err := read_bypass(opts.PermissionsBypass) if err != nil { return 1, err } opts.PermissionsBypass = strings.TrimSpace(val) } if len(args) == 0 { return 1, fmt.Errorf("Must specify at least one file to transfer") } switch opts.Direction { case "send", "download": err, rc = send_main(opts, args) default: err, rc = receive_main(opts, args) } if err != nil { rc = 1 } return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } kitty-0.41.1/kittens/transfer/main.py0000664000175000017510000001157714773370543017125 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import sys usage = 'source_files_or_directories destination_path' help_text = '''\ Transfer files over the TTY device. Can be used to send files between any two computers provided there is a TTY connection between them, such as over SSH. Supports copying files, directories (recursively), symlinks and hardlinks. Can even use an rsync like protocol to copy only changes between files. When copying multiple files, use the --confirm-paths option to see what exactly will be copied. The easiest way to use this kitten is to first ssh into the remote computer with the ssh kitten: .. code:: $ kitten ssh my-remote-computer Then, on the remote computer run the transfer kitten to do your copying. To copy a file from the remote computer to the local computer, run: .. code:: $ kitten transfer remote-file /path/to/local-file This will copy :file:`remote-file` from the remote computer to :file:`/path/to/local-file` on the local computer. Similarly, to copy a file from the local computer to the remote one, run: .. code:: $ kitten transfer --direction=upload /path/to/local-file remote-file This will copy :file:`/path/to/local-file` from the local computer to :file:`remote-file` on the remote computer. Multiple files can be copied: .. code:: $ kitten transfer file1 file2 /path/to/dir/ This will put :code:`file1` and :code:`file2` into the directory :file:`/path/to/dir/` on the local computer. Directories can also be copied, recursively: .. code:: $ kitten transfer dir1 /path/to/dir/ This will put :file:`dir1` and all its contents into :file:`/path/to/dir/` on the local computer. Note that when copying multiple files or directories, the destination must be an existing directory on the receiving computer. Relative file paths are resolved with respect to the current directory on the computer running the kitten and the home directory on the other computer. It is a good idea to use the :option:`--confirm-paths` command line flag to verify the kitten will copy the files you expect it to. ''' def option_text() -> str: return '''\ --direction -d default=download choices=upload,download,send,receive Whether to send or receive files. :code:`send` or :code:`download` copy files from the computer on which the kitten is running (usually the remote computer) to the local computer. :code:`receive` or :code:`upload` copy files from the local computer to the remote computer. --mode -m default=normal choices=normal,mirror How to interpret command line arguments. In :code:`mirror` mode all arguments are assumed to be files/dirs on the sending computer and they are mirrored onto the receiving computer. Files under the HOME directory are copied to the HOME directory on the receiving computer even if the HOME directory is different. In :code:`normal` mode the last argument is assumed to be a destination path on the receiving computer. The last argument must be an existing directory unless copying a single file. When it is a directory it should end with a trailing slash. --compress default=auto choices=auto,never,always Whether to compress data being sent. By default compression is enabled based on the type of file being sent. For files recognized as being already compressed, compression is turned off as it just wastes CPU cycles. --permissions-bypass -p The password to use to skip the transfer confirmation popup in kitty. Must match the password set for the :opt:`file_transfer_confirmation_bypass` option in :file:`kitty.conf`. Note that leading and trailing whitespace is removed from the password. A password starting with :code:`.`, :code:`/` or :code:`~` characters is assumed to be a file name to read the password from. A value of :code:`-` means read the password from STDIN. A password that is purely a number less than 256 is assumed to be the number of a file descriptor from which to read the actual password. --confirm-paths -c type=bool-set Before actually transferring files, show a mapping of local file names to remote file names and ask for confirmation. --transmit-deltas -x type=bool-set If a file on the receiving side already exists, use the rsync algorithm to update it to match the file on the sending side, potentially saving lots of bandwidth and also automatically resuming partial transfers. Note that this will actually degrade performance on fast links or with small files, so use with care. ''' def main(args: list[str]) -> None: raise SystemExit('This should be run as kitten transfer') if __name__ == '__main__': main(sys.argv) elif __name__ == '__doc__': from kitty.cli import CompletionSpec cd = sys.cli_docs # type: ignore cd['usage'] = usage cd['options'] = option_text cd['help_text'] = help_text cd['short_desc'] = 'Transfer files easily over the TTY device' cd['args_completion'] = CompletionSpec.from_string('type:file group:"Files"') kitty-0.41.1/kittens/transfer/receive.go0000664000175000017510000010127614773370543017574 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package transfer import ( "bytes" "compress/zlib" "errors" "fmt" "io" "io/fs" "os" "path/filepath" "slices" "strconv" "strings" "time" "kitty" "kitty/kittens/unicode_input" "kitty/tools/cli/markup" "kitty/tools/rsync" "kitty/tools/tui" "kitty/tools/tui/loop" "kitty/tools/utils" "kitty/tools/utils/humanize" "kitty/tools/wcswidth" "golang.org/x/sys/unix" ) var _ = fmt.Print type state int const ( state_waiting_for_permission state = iota state_waiting_for_file_metadata state_transferring state_canceled ) type output_file interface { write([]byte) (int, error) close() error tell() (int64, error) } type filesystem_file struct { f *os.File } func (ff *filesystem_file) tell() (int64, error) { return ff.f.Seek(0, io.SeekCurrent) } func (ff *filesystem_file) close() error { return ff.f.Close() } func (ff *filesystem_file) write(data []byte) (int, error) { n, err := ff.f.Write(data) if err == nil && n < len(data) { err = io.ErrShortWrite } return n, err } type patch_file struct { path string src, temp *os.File p *rsync.Patcher } func (pf *patch_file) tell() (int64, error) { if pf.temp == nil { s, err := os.Stat(pf.path) return s.Size(), err } return pf.temp.Seek(0, io.SeekCurrent) } func (pf *patch_file) close() (err error) { if pf.p == nil { return } err = pf.p.FinishDelta() pf.src.Close() pf.temp.Close() if err == nil { err = os.Rename(pf.temp.Name(), pf.src.Name()) } pf.src = nil pf.temp = nil pf.p = nil return } func (pf *patch_file) write(data []byte) (int, error) { if err := pf.p.UpdateDelta(data); err == nil { return len(data), nil } else { return 0, err } } func new_patch_file(path string, p *rsync.Patcher) (ans *patch_file, err error) { ans = &patch_file{p: p, path: path} var f *os.File if f, err = os.Open(path); err != nil { return } else { ans.src = f } if f, err = os.CreateTemp(filepath.Dir(path), ""); err != nil { ans.src.Close() return } else { ans.temp = f } ans.p.StartDelta(ans.temp, ans.src) return } type remote_file struct { expected_size int64 expect_diff bool patcher *rsync.Patcher transmit_started_at, done_at time.Time written_bytes int64 received_bytes int64 sent_bytes int64 ftype FileType mtime time.Duration spec_id int permissions fs.FileMode remote_path string display_name string remote_id, remote_target string parent string expanded_local_path string file_id string decompressor utils.StreamDecompressor compression_type Compression remote_symlink_value string actual_file output_file } func (self *remote_file) close() (err error) { if self.decompressor != nil { err = self.decompressor(nil, true) self.decompressor = nil } if self.actual_file != nil { af := self.actual_file self.actual_file = nil cerr := af.close() if err == nil { err = cerr } } return } func (self *remote_file) Write(data []byte) (n int, err error) { switch self.ftype { default: return 0, fmt.Errorf("Cannot write data to files of type: %s", self.ftype) case FileType_symlink: self.remote_symlink_value += string(data) return len(data), nil case FileType_regular: if self.actual_file == nil { parent := filepath.Dir(self.expanded_local_path) if parent != "" { if err = os.MkdirAll(parent, 0o755); err != nil { return 0, err } } if self.expect_diff { if pf, err := new_patch_file(self.expanded_local_path, self.patcher); err != nil { return 0, err } else { self.actual_file = pf } } else { if ff, err := os.Create(self.expanded_local_path); err != nil { return 0, err } else { f := filesystem_file{f: ff} self.actual_file = &f } } } return self.actual_file.write(data) } } func (self *remote_file) write_data(data []byte, is_last bool) (amt_written int64, err error) { self.received_bytes += int64(len(data)) var base, pos int64 defer func() { if err != nil { err = fmt.Errorf("Failed writing to %s with error: %w", self.expanded_local_path, err) } }() if self.actual_file != nil { base, err = self.actual_file.tell() if err != nil { return 0, err } } err = self.decompressor(data, is_last) if is_last { self.decompressor = nil } if self.actual_file != nil && err == nil { pos, err = self.actual_file.tell() if err != nil { return 0, err } } else { pos = base } amt_written = pos - base if is_last && self.actual_file != nil { cerr := self.actual_file.close() if err == nil { err = cerr } self.actual_file = nil } return } func syscall_mode(i os.FileMode) (o uint32) { o |= uint32(i.Perm()) if i&os.ModeSetuid != 0 { o |= unix.S_ISUID } if i&os.ModeSetgid != 0 { o |= unix.S_ISGID } if i&os.ModeSticky != 0 { o |= unix.S_ISVTX } // No mapping for Go's ModeTemporary (plan9 only). return } func (self *remote_file) apply_metadata() { t := unix.NsecToTimespec(int64(self.mtime)) for { if err := unix.UtimesNanoAt(unix.AT_FDCWD, self.expanded_local_path, []unix.Timespec{t, t}, unix.AT_SYMLINK_NOFOLLOW); err == nil || !(errors.Is(err, unix.EINTR) || errors.Is(err, unix.EAGAIN)) { break } } if self.ftype == FileType_symlink { for { if err := unix.Fchmodat(unix.AT_FDCWD, self.expanded_local_path, syscall_mode(self.permissions), unix.AT_SYMLINK_NOFOLLOW); err == nil || !(errors.Is(err, unix.EINTR) || errors.Is(err, unix.EAGAIN)) { break } } } else { _ = os.Chmod(self.expanded_local_path, self.permissions) } } func new_remote_file(opts *Options, ftc *FileTransmissionCommand, file_id uint64) (*remote_file, error) { spec_id, err := strconv.Atoi(ftc.File_id) if err != nil { return nil, err } ans := &remote_file{ expected_size: ftc.Size, ftype: ftc.Ftype, mtime: ftc.Mtime, spec_id: spec_id, file_id: strconv.FormatUint(file_id, 10), permissions: ftc.Permissions, remote_path: ftc.Name, display_name: wcswidth.StripEscapeCodes(ftc.Name), remote_id: ftc.Status, remote_target: string(ftc.Data), parent: ftc.Parent, } compression_capable := ftc.Ftype == FileType_regular && ftc.Size > 4096 && should_be_compressed(ftc.Name, opts.Compress) if compression_capable { ans.decompressor = utils.NewStreamDecompressor(zlib.NewReader, ans) ans.compression_type = Compression_zlib } else { ans.decompressor = utils.NewStreamDecompressor(nil, ans) ans.compression_type = Compression_none } return ans, nil } type receive_progress_tracker struct { total_size_of_all_files int64 total_bytes_to_transfer int64 total_transferred int64 transfered_stats_amt int64 transfered_stats_interval time.Duration started_at time.Time transfers []Transfer active_file *remote_file done_files []*remote_file } func (self *receive_progress_tracker) change_active_file(nf *remote_file) { now := time.Now() self.active_file = nf nf.transmit_started_at = now } func (self *receive_progress_tracker) start_transfer() { self.started_at = time.Now() self.transfers = append(self.transfers, Transfer{at: time.Now()}) } func (self *receive_progress_tracker) file_written(af *remote_file, amt int64, is_done bool) { if self.active_file != af { self.change_active_file(af) } af.written_bytes += amt self.total_transferred += amt now := time.Now() self.transfers = append(self.transfers, Transfer{amt: amt, at: now}) for len(self.transfers) > 2 && self.transfers[0].is_too_old(now) { utils.ShiftLeft(self.transfers, 1) } self.transfered_stats_interval = now.Sub(self.transfers[0].at) self.transfered_stats_amt = 0 for _, t := range self.transfers { self.transfered_stats_amt += t.amt } if is_done { af.done_at = now self.done_files = append(self.done_files, af) } } type manager struct { request_id string file_id_counter uint64 cli_opts *Options spec []string dest string bypass string use_rsync bool failed_specs map[int]string spec_counts map[int]int remote_home string prefix, suffix string transfer_done bool files []*remote_file files_to_be_transferred map[string]*remote_file state state progress_tracker receive_progress_tracker } type transmit_iterator = func(queue_write func(string) loop.IdType) (loop.IdType, error) type sigwriter struct { wid loop.IdType file_id, prefix, suffix string q func(string) loop.IdType amt int64 b bytes.Buffer } func (self *sigwriter) Write(b []byte) (int, error) { self.b.Write(b) if self.b.Len() > 4000 { self.flush() } return len(b), nil } func (self *sigwriter) flush() { frame := len(self.prefix) + len(self.suffix) split_for_transfer(self.b.Bytes(), self.file_id, false, func(ftc *FileTransmissionCommand) { self.q(self.prefix) data := ftc.Serialize(false) self.q(data) self.wid = self.q(self.suffix) self.amt += int64(frame + len(data)) }) self.b.Reset() } var files_done error = errors.New("files done") func (self *manager) request_files() transmit_iterator { pos := 0 return func(queue_write func(string) loop.IdType) (last_write_id loop.IdType, err error) { var f *remote_file for pos < len(self.files) { f = self.files[pos] pos++ if f.ftype == FileType_directory || (f.ftype == FileType_link && f.remote_target != "") { f = nil } else { break } } if f == nil { return 0, files_done } read_signature := self.use_rsync && f.ftype == FileType_regular if read_signature { if s, err := os.Lstat(f.expanded_local_path); err == nil { read_signature = s.Size() > 4096 } else { read_signature = false } } last_write_id = self.send(FileTransmissionCommand{ Action: Action_file, Name: f.remote_path, File_id: f.file_id, Ttype: utils.IfElse( read_signature, TransmissionType_rsync, TransmissionType_simple), Compression: f.compression_type, }, queue_write) if read_signature { fsf, err := os.Open(f.expanded_local_path) if err != nil { return 0, err } defer fsf.Close() f.expect_diff = true f.patcher = rsync.NewPatcher(f.expected_size) output := sigwriter{q: queue_write, file_id: f.file_id, prefix: self.prefix, suffix: self.suffix} s_it := f.patcher.CreateSignatureIterator(fsf, &output) for { err = s_it() if err == io.EOF { break } else if err != nil { return 0, err } } output.flush() f.sent_bytes += output.amt last_write_id = self.send(FileTransmissionCommand{Action: Action_end_data, File_id: f.file_id}, queue_write) } return } } type handler struct { lp *loop.Loop progress_update_timer loop.IdType spinner *tui.Spinner cli_opts *Options ctx *markup.Context manager manager quit_after_write_code int check_paths_printed bool transmit_started bool progress_drawn bool max_name_length int transmit_iterator transmit_iterator last_data_write_id loop.IdType } func (self *manager) send(c FileTransmissionCommand, send func(string) loop.IdType) loop.IdType { send(self.prefix) send(c.Serialize(false)) return send(self.suffix) } func (self *manager) start_transfer(send func(string) loop.IdType) { self.send(FileTransmissionCommand{Action: Action_receive, Bypass: self.bypass, Size: int64(len(self.spec))}, send) for i, x := range self.spec { self.send(FileTransmissionCommand{Action: Action_file, File_id: strconv.Itoa(i), Name: x}, send) } self.progress_tracker.start_transfer() } func (self *handler) print_err(err error) { self.lp.Println(self.ctx.BrightRed(err.Error())) } func (self *handler) abort_with_error(err error, delay ...time.Duration) { if err != nil { self.print_err(err) } var d time.Duration = 5 * time.Second if len(delay) > 0 { d = delay[0] } self.lp.Println(`Waiting to ensure terminal cancels transfer, will quit in no more than`, d) self.manager.send(FileTransmissionCommand{Action: Action_cancel}, self.lp.QueueWriteString) self.manager.state = state_canceled _, _ = self.lp.AddTimer(d, false, self.do_error_quit) } func (self *handler) do_error_quit(loop.IdType) error { self.lp.Quit(1) return nil } func (self *manager) finalize_transfer() (err error) { self.transfer_done = true rid_map := make(map[string]*remote_file) for _, f := range self.files { rid_map[f.remote_id] = f } for _, f := range self.files { switch f.ftype { case FileType_directory: if err = os.MkdirAll(f.expanded_local_path, 0o755); err != nil { return fmt.Errorf("Failed to create directory with error: %w", err) } case FileType_link: tgt, found := rid_map[f.remote_target] if !found { return fmt.Errorf(`Hard link with remote id: {%s} not found`, f.remote_target) } if err = os.MkdirAll(filepath.Dir(f.expanded_local_path), 0o755); err == nil { os.Remove(f.expanded_local_path) err = os.Link(tgt.expanded_local_path, f.expanded_local_path) } if err != nil { return fmt.Errorf(`Failed to create link with error: %w`, err) } case FileType_symlink: lt := f.remote_symlink_value if f.remote_target != "" { tgt, found := rid_map[f.remote_target] if !found { return fmt.Errorf(`Symbolic link with remote id: {%s} not found`, f.remote_target) } lt = tgt.expanded_local_path if !strings.HasPrefix(f.remote_symlink_value, "/") { if lt, err = filepath.Rel(filepath.Dir(f.expanded_local_path), lt); err != nil { return fmt.Errorf(`Could not make symlink relative with error: %w`, err) } } } if lt == "" { return fmt.Errorf("Symlink %s sent without target", f.expanded_local_path) } os.Remove(f.expanded_local_path) if err = os.MkdirAll(filepath.Dir(f.expanded_local_path), 0o755); err != nil { return fmt.Errorf("Failed to create directory with error: %w", err) } if err = os.Symlink(lt, f.expanded_local_path); err != nil { return fmt.Errorf(`Failed to create symlink with error: %w`, err) } } f.apply_metadata() } return } func (self *manager) on_file_transfer_response(ftc *FileTransmissionCommand) (err error) { switch self.state { case state_waiting_for_permission: if ftc.Action == Action_status { if ftc.Status == `OK` { self.state = state_waiting_for_file_metadata } else { return unicode_input.ErrCanceledByUser } } else { return fmt.Errorf(`Unexpected response from terminal: %s`, ftc.String()) } case state_waiting_for_file_metadata: switch ftc.Action { case Action_status: if ftc.File_id != "" { fid, err := strconv.Atoi(ftc.File_id) if err != nil { return fmt.Errorf(`Unexpected response from terminal (non-integer file_id): %s`, ftc.String()) } if fid < 0 || fid >= len(self.spec) { return fmt.Errorf(`Unexpected response from terminal (out-of-range file_id): %s`, ftc.String()) } self.failed_specs[fid] = ftc.Status } else { if ftc.Status == `OK` { self.state = state_transferring self.remote_home = ftc.Name return } return fmt.Errorf("%s", ftc.Status) } case Action_file: fid, err := strconv.Atoi(ftc.File_id) if err != nil { return fmt.Errorf(`Unexpected response from terminal (non-integer file_id): %s`, ftc.String()) } if fid < 0 || fid >= len(self.spec) { return fmt.Errorf(`Unexpected response from terminal (out-of-range file_id): %s`, ftc.String()) } self.spec_counts[fid] += 1 self.file_id_counter++ if rf, err := new_remote_file(self.cli_opts, ftc, self.file_id_counter); err == nil { self.files = append(self.files, rf) } else { return err } default: return fmt.Errorf(`Unexpected response from terminal (invalid action): %s`, ftc.String()) } case state_transferring: if ftc.Action == Action_data || ftc.Action == Action_end_data { f, found := self.files_to_be_transferred[ftc.File_id] if !found { return fmt.Errorf(`Got data for unknown file id: %s`, ftc.File_id) } is_last := ftc.Action == Action_end_data if amt_written, err := f.write_data(ftc.Data, is_last); err != nil { return err } else { self.progress_tracker.file_written(f, amt_written, is_last) } if is_last { delete(self.files_to_be_transferred, ftc.File_id) if len(self.files_to_be_transferred) == 0 { return self.finalize_transfer() } } } } return } type tree_node struct { entry *remote_file added_files map[string]*tree_node } func (self *tree_node) add_child(f *remote_file) *tree_node { if x, found := self.added_files[f.remote_id]; found { return x } c := tree_node{entry: f, added_files: make(map[string]*tree_node)} f.expanded_local_path = filepath.Join(self.entry.expanded_local_path, filepath.Base(f.remote_path)) self.added_files[f.remote_id] = &c return &c } func walk_tree(root *tree_node, cb func(*tree_node) error) error { for _, c := range root.added_files { if err := cb(c); err != nil { return err } if err := walk_tree(c, cb); err != nil { return err } } return nil } func ensure_parent(f *remote_file, node_map map[string]*tree_node, fid_map map[string]*remote_file) *tree_node { if ans := node_map[f.parent]; ans != nil { return ans } parent := fid_map[f.parent] gp := ensure_parent(parent, node_map, fid_map) node := gp.add_child(parent) node_map[parent.remote_id] = node return node } func make_tree(all_files []*remote_file, local_base string) (root_node *tree_node) { fid_map := make(map[string]*remote_file, len(all_files)) node_map := make(map[string]*tree_node, len(all_files)) for _, f := range all_files { if f.remote_id != "" { fid_map[f.remote_id] = f } } root_node = &tree_node{entry: &remote_file{expanded_local_path: local_base}, added_files: make(map[string]*tree_node)} node_map[""] = root_node for _, f := range all_files { if f.remote_id != "" { p := ensure_parent(f, node_map, fid_map) p.add_child(f) } } return } func isdir(path string) bool { if s, err := os.Stat(path); err == nil { return s.IsDir() } return false } func files_for_receive(opts *Options, dest string, files []*remote_file, remote_home string, specs []string) (ans []*remote_file, err error) { spec_map := make(map[int][]*remote_file) for _, f := range files { spec_map[f.spec_id] = append(spec_map[f.spec_id], f) } spec_paths := make([]string, len(specs)) for i := range specs { // use the shortest path as the path for the spec slices.SortStableFunc(spec_map[i], func(a, b *remote_file) int { return len(a.remote_path) - len(b.remote_path) }) spec_paths[i] = spec_map[i][0].remote_path } if opts.Mode == "mirror" { common_path := utils.Commonpath(spec_paths...) home := strings.TrimRight(remote_home, "/") if strings.HasPrefix(common_path, home+"/") { for i, x := range spec_paths { b, err := filepath.Rel(home, x) if err != nil { return nil, err } spec_paths[i] = filepath.Join("~", b) } } for spec_id, files_for_spec := range spec_map { spec := spec_paths[spec_id] tree := make_tree(files_for_spec, filepath.Dir(expand_home(spec))) if err = walk_tree(tree, func(x *tree_node) error { ans = append(ans, x.entry) return nil }); err != nil { return nil, err } } } else { number_of_source_files := 0 for _, x := range spec_map { number_of_source_files += len(x) } dest_is_dir := strings.HasSuffix(dest, "/") || number_of_source_files > 1 || isdir(dest) for _, files_for_spec := range spec_map { if dest_is_dir { dest_path := filepath.Join(dest, filepath.Base(files_for_spec[0].remote_path)) tree := make_tree(files_for_spec, filepath.Dir(expand_home(dest_path))) if err = walk_tree(tree, func(x *tree_node) error { ans = append(ans, x.entry) return nil }); err != nil { return nil, err } } else { f := files_for_spec[0] f.expanded_local_path = expand_home(dest) ans = append(ans, f) } } } return } func (self *manager) collect_files() (err error) { if self.files, err = files_for_receive(self.cli_opts, self.dest, self.files, self.remote_home, self.spec); err != nil { return err } self.progress_tracker.total_size_of_all_files = 0 for _, f := range self.files { if f.ftype != FileType_directory && f.ftype != FileType_link { self.files_to_be_transferred[f.file_id] = f self.progress_tracker.total_size_of_all_files += utils.Max(0, f.expected_size) } } self.progress_tracker.total_bytes_to_transfer = self.progress_tracker.total_size_of_all_files return nil } func (self *handler) print_continue_msg() { self.lp.Println(`Press`, self.ctx.Green(`y`), `to continue or`, self.ctx.BrightRed(`n`), `to abort`) } func lexists(path string) bool { _, err := os.Lstat(path) return err == nil } func (self *handler) print_check_paths() { if self.check_paths_printed { return } self.check_paths_printed = true self.lp.Println(`The following file transfers will be performed. A red destination means an existing file will be overwritten.`) for _, df := range self.manager.files { self.lp.QueueWriteString(self.ctx.Prettify(fmt.Sprintf(":%s:`%s` ", df.ftype.Color(), df.ftype.ShortText()))) self.lp.QueueWriteString(" ") lpath := df.expanded_local_path if lexists(lpath) { lpath = self.ctx.Prettify(self.ctx.BrightRed(lpath) + " ") } self.lp.Println(df.display_name, "→", lpath) } self.lp.Println(fmt.Sprintf(`Transferring %d file(s) of total size: %s`, len(self.manager.files), humanize.Size(self.manager.progress_tracker.total_size_of_all_files))) self.print_continue_msg() } func (self *handler) confirm_paths() { self.print_check_paths() } func (self *handler) transmit_one() { if self.transmit_iterator == nil { return } wid, err := self.transmit_iterator(self.lp.QueueWriteString) if err != nil { if err == files_done { self.transmit_iterator = nil } else { self.abort_with_error(err) return } } else { self.last_data_write_id = wid } } func (self *handler) start_transfer() { self.transmit_started = true n := len(self.manager.files) msg := `Transmitting signature of` if self.manager.use_rsync { msg = `Queueing transfer of` } msg += ` ` if n == 1 { msg += `one file` } else { msg += fmt.Sprintf(`%d files`, n) } self.lp.Println(msg) self.max_name_length = 0 for _, f := range self.manager.files { self.max_name_length = utils.Max(6, self.max_name_length, wcswidth.Stringwidth(f.display_name)) } self.transmit_iterator = self.manager.request_files() self.transmit_one() } func (self *handler) on_file_transfer_response(ftc *FileTransmissionCommand) (err error) { if ftc.Id != self.manager.request_id { return } if ftc.Action == Action_status && ftc.Status == "CANCELED" { self.lp.Quit(1) return } if self.quit_after_write_code > -1 || self.manager.state == state_canceled { return } transfer_started := self.manager.state == state_transferring if merr := self.manager.on_file_transfer_response(ftc); merr != nil { if merr == unicode_input.ErrCanceledByUser { // terminal will not respond to cancel request return fmt.Errorf("Permission denied by user") } self.abort_with_error(merr) return } if !transfer_started && self.manager.state == state_transferring { if len(self.manager.failed_specs) > 0 { self.print_err(fmt.Errorf(`Failed to process some sources:`)) for spec_id, msg := range self.manager.failed_specs { spec := self.manager.spec[spec_id] if strings.HasPrefix(msg, `ENOENT:`) { msg = `File not found` } self.lp.Println(fmt.Sprintf(` %s: %s`, spec, msg)) } self.abort_with_error(nil) return } zero_specs := make([]string, 0, len(self.manager.spec_counts)) for k, v := range self.manager.spec_counts { if v == 0 { zero_specs = append(zero_specs, self.manager.spec[k]) } } if len(zero_specs) > 0 { self.abort_with_error(fmt.Errorf(`No matches found for: %s`, strings.Join(zero_specs, ", "))) return } if merr := self.manager.collect_files(); merr != nil { self.abort_with_error(merr) return } if self.cli_opts.ConfirmPaths { self.confirm_paths() } else { self.start_transfer() } } if self.manager.transfer_done { self.manager.send(FileTransmissionCommand{Action: Action_finish}, self.lp.QueueWriteString) self.quit_after_write_code = 0 if err = self.refresh_progress(0); err != nil { return err } } else if self.transmit_started { if err = self.refresh_progress(0); err != nil { return err } } return } func (self *handler) on_writing_finished(msg_id loop.IdType, has_pending_writes bool) (err error) { if self.quit_after_write_code > -1 { self.lp.Quit(self.quit_after_write_code) } else if msg_id == self.last_data_write_id { self.transmit_one() } return nil } func (self *handler) on_interrupt() (handled bool, err error) { handled = true if self.quit_after_write_code > -1 { return } if self.manager.state == state_canceled { self.lp.Println(`Waiting for canceled acknowledgement from terminal, will abort in a few seconds if no response received`) return } self.abort_with_error(fmt.Errorf(`Interrupt requested, cancelling transfer, transferred files are in undefined state.`)) return } func (self *handler) on_sigterm() (handled bool, err error) { handled = true if self.quit_after_write_code > -1 { return } self.abort_with_error(fmt.Errorf(`Terminate requested, cancelling transfer, transferred files are in undefined state.`), 2*time.Second) return } func (self *handler) erase_progress() { if self.progress_drawn { self.lp.MoveCursorVertically(-2) self.lp.QueueWriteString("\r") self.lp.ClearToEndOfScreen() self.progress_drawn = false } } func (self *handler) render_progress(name string, p Progress) { if p.is_complete { p.bytes_so_far = p.total_bytes } ss, _ := self.lp.ScreenSize() self.lp.QueueWriteString(render_progress_in_width(name, p, int(ss.WidthCells), self.ctx)) } func (self *handler) draw_progress_for_current_file(af *remote_file, spinner_char string, is_complete bool) { p := &self.manager.progress_tracker now := time.Now() secs := utils.IfElse(af.done_at.IsZero(), now, af.done_at) self.render_progress(af.display_name, Progress{ spinner_char: spinner_char, is_complete: is_complete, bytes_so_far: af.written_bytes, total_bytes: af.expected_size, secs_so_far: secs.Sub(af.transmit_started_at).Seconds(), bytes_per_sec: safe_divide(p.transfered_stats_amt, p.transfered_stats_interval), }) } func (self *handler) draw_files() { tick := self.ctx.Green(`✔`) var sc string for _, df := range self.manager.progress_tracker.done_files { sc = tick if df.ftype == FileType_regular { self.draw_progress_for_current_file(df, sc, true) } else { self.lp.QueueWriteString(fmt.Sprintf("%s %s %s", sc, df.display_name, self.ctx.Italic(self.ctx.Dim(df.ftype.String())))) } self.lp.Println() self.manager.progress_tracker.done_files = nil } is_complete := self.quit_after_write_code > -1 if is_complete { sc = utils.IfElse(self.quit_after_write_code == 0, tick, self.ctx.Red(`✘`)) } else { sc = self.spinner.Tick() } p := &self.manager.progress_tracker ss, _ := self.lp.ScreenSize() if is_complete { tui.RepeatChar(`─`, int(ss.WidthCells)) } else { af := p.active_file if af != nil { self.draw_progress_for_current_file(af, sc, false) } } self.lp.Println() if p.total_transferred > 0 { self.render_progress(`Total`, Progress{ spinner_char: sc, bytes_so_far: p.total_transferred, total_bytes: p.total_bytes_to_transfer, secs_so_far: time.Since(p.started_at).Seconds(), is_complete: is_complete, bytes_per_sec: safe_divide(p.transfered_stats_amt, p.transfered_stats_interval.Abs().Seconds()), }) self.lp.Println() } else { self.lp.Println(`File data transfer has not yet started`) } } func (self *handler) schedule_progress_update(delay time.Duration) { if self.progress_update_timer != 0 { self.lp.RemoveTimer(self.progress_update_timer) self.progress_update_timer = 0 } timer_id, err := self.lp.AddTimer(delay, false, self.refresh_progress) if err == nil { self.progress_update_timer = timer_id } } func (self *handler) draw_progress() { if self.manager.state == state_canceled { return } self.lp.AllowLineWrapping(false) defer self.lp.AllowLineWrapping(true) self.draw_files() self.schedule_progress_update(self.spinner.Interval()) self.progress_drawn = true } func (self *handler) refresh_progress(loop.IdType) error { self.lp.StartAtomicUpdate() defer self.lp.EndAtomicUpdate() self.erase_progress() self.draw_progress() return nil } func (self *handler) on_text(text string, from_key_event, in_bracketed_paste bool) error { if self.quit_after_write_code > -1 { return nil } if self.check_paths_printed && !self.transmit_started { switch strings.ToLower(text) { case "y": self.start_transfer() return nil case "n": self.abort_with_error(fmt.Errorf(`Canceled by user`)) return nil } self.print_continue_msg() } return nil } func (self *handler) on_key_event(ev *loop.KeyEvent) error { if self.quit_after_write_code > -1 { return nil } if ev.MatchesPressOrRepeat("esc") { ev.Handled = true if self.check_paths_printed && !self.transmit_started { self.abort_with_error(fmt.Errorf(`Canceled by user`)) } else { if _, err := self.on_interrupt(); err != nil { return err } } } else if ev.MatchesPressOrRepeat("ctrl+c") { ev.Handled = true if _, err := self.on_interrupt(); err != nil { return err } } return nil } func receive_loop(opts *Options, spec []string, dest string) (err error, rc int) { lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors) if err != nil { return err, 1 } handler := handler{ lp: lp, quit_after_write_code: -1, cli_opts: opts, spinner: tui.NewSpinner("dots"), ctx: markup.New(true), manager: manager{ request_id: random_id(), spec: spec, dest: dest, bypass: opts.PermissionsBypass, use_rsync: opts.TransmitDeltas, failed_specs: make(map[int]string, len(spec)), spec_counts: make(map[int]int, len(spec)), suffix: "\x1b\\", cli_opts: opts, files_to_be_transferred: make(map[string]*remote_file), }, } for i := range spec { handler.manager.spec_counts[i] = 0 } handler.manager.prefix = fmt.Sprintf("\x1b]%d;id=%s;", kitty.FileTransferCode, handler.manager.request_id) if handler.manager.bypass != `` { if handler.manager.bypass, err = encode_bypass(handler.manager.request_id, handler.manager.bypass); err != nil { return err, 1 } } lp.OnInitialize = func() (string, error) { lp.SetCursorVisible(false) lp.Println("Scanning files…") handler.manager.start_transfer(lp.QueueWriteString) return "", nil } lp.OnFinalize = func() string { lp.SetCursorVisible(true) return "" } lp.OnSIGINT = handler.on_interrupt lp.OnSIGTERM = handler.on_sigterm lp.OnWriteComplete = handler.on_writing_finished lp.OnText = handler.on_text lp.OnKeyEvent = handler.on_key_event lp.OnResize = func(old_sz, new_sz loop.ScreenSize) error { if handler.progress_drawn { return handler.refresh_progress(0) } return nil } ftc_code := strconv.Itoa(kitty.FileTransferCode) lp.OnEscapeCode = func(et loop.EscapeCodeType, payload []byte) error { if et == loop.OSC { if idx := bytes.IndexByte(payload, ';'); idx > 0 { if utils.UnsafeBytesToString(payload[:idx]) == ftc_code { ftc, err := NewFileTransmissionCommand(utils.UnsafeBytesToString(payload[idx+1:])) if err != nil { return fmt.Errorf("Received invalid FileTransmissionCommand from terminal with error: %w", err) } return handler.on_file_transfer_response(ftc) } } } return nil } err = lp.Run() defer func() { for _, f := range handler.manager.files { f.close() } }() if err != nil { return err, 1 } if lp.DeathSignalName() != "" { lp.KillIfSignalled() return } if lp.ExitCode() != 0 { rc = lp.ExitCode() } var tsf, dsz, ssz int64 for _, f := range handler.manager.files { if rc == 0 { // no error has yet occurred report errors closing files if cerr := f.close(); cerr != nil { return cerr, 1 } } if f.expect_diff { tsf += f.expected_size dsz += f.received_bytes ssz += f.sent_bytes } } if tsf > 0 && dsz+ssz > 0 && rc == 0 { print_rsync_stats(tsf, dsz, ssz) } return } func receive_main(opts *Options, args []string) (err error, rc int) { spec := args var dest string switch opts.Mode { case "mirror": if len(args) < 1 { return fmt.Errorf("Must specify at least one file to transfer"), 1 } case "normal": if len(args) < 2 { return fmt.Errorf("Must specify at least one source and a destination file to transfer"), 1 } dest = args[len(args)-1] spec = args[:len(args)-1] } return receive_loop(opts, spec, dest) } kitty-0.41.1/kittens/transfer/rsync.pyi0000664000175000017510000000301614773370543017475 0ustar nileshnileshfrom typing import Callable, Union from kitty.typing import ReadableBuffer, WriteableBuffer class RsyncError(Exception): pass class Hasher: def __init__(self, which: str, data: ReadableBuffer = b''): ... def update(self, data: ReadableBuffer) -> None: ... def reset(self) -> None: ... def digest(self) -> bytes: ... def hexdigest(self) -> str: ... @property def digest_size(self) -> int: ... @property def block_size(self) -> int: ... @property def name(self) -> str: ... def xxh128_hash(data: ReadableBuffer) -> bytes: ... def xxh128_hash_with_seed(data: ReadableBuffer, seed: int) -> bytes: ... class Patcher: def __init__(self, expected_input_size: int = 0): ... def signature_header(self, output: WriteableBuffer) -> int: ... def sign_block(self, block: ReadableBuffer, output: WriteableBuffer) -> int: ... def apply_delta_data(self, data: ReadableBuffer, read: Callable[[int, WriteableBuffer], int], write: Callable[[ReadableBuffer], None]) -> None: ... def finish_delta_data(self) -> None: ... @property def block_size(self) -> int: ... @property def total_data_in_delta(self) -> int: ... class Differ: def add_signature_data(self, data: ReadableBuffer) -> None: ... def finish_signature_data(self) -> None: ... def next_op(self, read: Callable[[WriteableBuffer], int], write: Callable[[ReadableBuffer], None]) -> bool: ... def parse_ftc(x: Union[str, ReadableBuffer], callback: Callable[[memoryview, memoryview], None]) -> None: ... kitty-0.41.1/kittens/transfer/send.go0000664000175000017510000011316214773370543017100 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package transfer import ( "bytes" "compress/zlib" "errors" "fmt" "io" "io/fs" "os" "path/filepath" "slices" "strconv" "strings" "syscall" "time" "unicode/utf8" "golang.org/x/exp/constraints" "kitty" "kitty/tools/cli/markup" "kitty/tools/rsync" "kitty/tools/tui" "kitty/tools/tui/loop" "kitty/tools/utils" "kitty/tools/utils/humanize" "kitty/tools/wcswidth" ) var _ = fmt.Print type FileState int const ( WAITING_FOR_START FileState = iota WAITING_FOR_DATA TRANSMITTING FINISHED ACKNOWLEDGED ) type FileHash struct{ dev, inode uint64 } type Compressor interface { Compress(data []byte) []byte Flush() []byte } type IdentityCompressor struct{} func (self *IdentityCompressor) Compress(data []byte) []byte { return data } func (self *IdentityCompressor) Flush() []byte { return nil } type ZlibCompressor struct { b bytes.Buffer w zlib.Writer } func NewZlibCompressor() *ZlibCompressor { ans := ZlibCompressor{} ans.b.Grow(4096) ans.w = *zlib.NewWriter(&ans.b) return &ans } func (self *ZlibCompressor) Compress(data []byte) []byte { _, err := self.w.Write(data) if err != nil { panic(err) } defer self.b.Reset() return utils.UnsafeStringToBytes(self.b.String()) } func (self *ZlibCompressor) Flush() []byte { self.w.Close() return self.b.Bytes() } type File struct { file_hash FileHash ttype TransmissionType compression Compression compressor Compressor file_type FileType file_id, hard_link_target string local_path, symbolic_link_target, expanded_local_path string stat_result fs.FileInfo state FileState display_name string mtime time.Time file_size, bytes_to_transmit int64 permissions fs.FileMode remote_path string rsync_capable, compression_capable bool remote_final_path string remote_initial_size int64 err_msg string actual_file *os.File transmitted_bytes, reported_progress int64 transmit_started_at, transmit_ended_at, done_at time.Time differ *rsync.Differ delta_loader func() error deltabuf *bytes.Buffer } func get_remote_path(local_path string, remote_base string) string { if remote_base == "" { return filepath.ToSlash(local_path) } if strings.HasSuffix(remote_base, "/") { return filepath.Join(remote_base, filepath.Base(local_path)) } return remote_base } func NewFile(opts *Options, local_path, expanded_local_path string, file_id int, stat_result fs.FileInfo, remote_base string, file_type FileType) *File { stat, ok := stat_result.Sys().(*syscall.Stat_t) if !ok { panic("This platform does not support getting file identities from stat results") } ans := File{ local_path: local_path, expanded_local_path: expanded_local_path, file_id: fmt.Sprintf("%x", file_id), stat_result: stat_result, file_type: file_type, display_name: wcswidth.StripEscapeCodes(local_path), file_hash: FileHash{uint64(stat.Dev), stat.Ino}, mtime: stat_result.ModTime(), file_size: stat_result.Size(), bytes_to_transmit: stat_result.Size(), permissions: stat_result.Mode().Perm(), remote_path: filepath.ToSlash(get_remote_path(local_path, remote_base)), rsync_capable: file_type == FileType_regular && stat_result.Size() > 4096, compression_capable: file_type == FileType_regular && stat_result.Size() > 4096 && should_be_compressed(expanded_local_path, opts.Compress), remote_initial_size: -1, } return &ans } func process(opts *Options, paths []string, remote_base string, counter *int) (ans []*File, err error) { for _, x := range paths { expanded := expand_home(x) s, err := os.Lstat(expanded) if err != nil { return ans, fmt.Errorf("Failed to stat %s with error: %w", x, err) } if s.IsDir() { *counter += 1 ans = append(ans, NewFile(opts, x, expanded, *counter, s, remote_base, FileType_directory)) new_remote_base := remote_base if new_remote_base != "" { new_remote_base = strings.TrimRight(new_remote_base, "/") + "/" + filepath.Base(x) + "/" } else { new_remote_base = strings.TrimRight(filepath.ToSlash(x), "/") + "/" } contents, err := os.ReadDir(expanded) if err != nil { return ans, fmt.Errorf("Failed to read the directory %s with error: %w", x, err) } new_paths := make([]string, len(contents)) for i, y := range contents { new_paths[i] = filepath.Join(x, y.Name()) } new_ans, err := process(opts, new_paths, new_remote_base, counter) if err != nil { return ans, err } ans = append(ans, new_ans...) } else if s.Mode()&fs.ModeSymlink == fs.ModeSymlink { *counter += 1 ans = append(ans, NewFile(opts, x, expanded, *counter, s, remote_base, FileType_symlink)) } else if s.Mode().IsRegular() { *counter += 1 ans = append(ans, NewFile(opts, x, expanded, *counter, s, remote_base, FileType_regular)) } } return } func process_mirrored_files(opts *Options, args []string) (ans []*File, err error) { paths := utils.Map(func(x string) string { return abspath(expand_home(x)) }, args) home := strings.TrimRight(home_path(), string(filepath.Separator)) + string(filepath.Separator) paths = utils.Map(func(path string) string { if strings.HasPrefix(path, home) { r, _ := filepath.Rel(home, path) return filepath.Join("~", r) } return path }, paths) counter := 0 return process(opts, paths, "", &counter) } func process_normal_files(opts *Options, args []string) (ans []*File, err error) { if len(args) < 2 { return ans, fmt.Errorf("Must specify at least one local path and one remote path") } args = slices.Clone(args) remote_base := filepath.ToSlash(args[len(args)-1]) args = args[:len(args)-1] if len(args) > 1 && !strings.HasSuffix(remote_base, "/") { remote_base += "/" } paths := utils.Map(func(x string) string { return abspath(expand_home(x)) }, args) counter := 0 return process(opts, paths, remote_base, &counter) } func files_for_send(opts *Options, args []string) (files []*File, err error) { if opts.Mode == "mirror" { files, err = process_mirrored_files(opts, args) } else { files, err = process_normal_files(opts, args) } if err != nil { return files, err } groups := make(map[FileHash][]*File, len(files)) // detect hard links for _, f := range files { groups[f.file_hash] = append(groups[f.file_hash], f) } for _, group := range groups { if len(group) > 1 { for _, lf := range group[1:] { lf.file_type = FileType_link lf.hard_link_target = "fid:" + group[0].file_id } } } remove := make([]int, 0, len(files)) // detect symlinks to other transferred files for i, f := range files { if f.file_type == FileType_symlink { link_dest, err := os.Readlink(f.expanded_local_path) if err != nil { remove = append(remove, i) continue } f.symbolic_link_target = "path:" + link_dest is_abs := filepath.IsAbs(link_dest) q := link_dest if !is_abs { q = filepath.Join(filepath.Dir(f.expanded_local_path), link_dest) } st, err := os.Stat(q) if err == nil { stat, ok := st.Sys().(*syscall.Stat_t) if ok { fh := FileHash{uint64(stat.Dev), stat.Ino} gr, found := groups[fh] if found { g := utils.Filter(gr, func(x *File) bool { return os.SameFile(x.stat_result, st) }) if len(g) > 0 { f.symbolic_link_target = "fid" if is_abs { f.symbolic_link_target = "fid_abs" } f.symbolic_link_target += ":" + g[0].file_id } } } } } } if len(remove) > 0 { for _, idx := range utils.Reverse(remove) { files[idx] = nil files = slices.Delete(files, idx, idx+1) } } return files, nil } type SendState int const ( SEND_WAITING_FOR_PERMISSION SendState = iota SEND_PERMISSION_GRANTED SEND_PERMISSION_DENIED SEND_CANCELED ) type Transfer struct { amt int64 at time.Time } func (self *Transfer) is_too_old(now time.Time) bool { return now.Sub(self.at) > 30*time.Second } type ProgressTracker struct { total_size_of_all_files, total_bytes_to_transfer int64 active_file *File total_transferred int64 transfers []*Transfer transfered_stats_amt int64 transfered_stats_interval time.Duration started_at time.Time signature_bytes int total_reported_progress int64 } func (self *ProgressTracker) change_active_file(nf *File) { now := time.Now() self.active_file = nf nf.transmit_started_at = now } func (self *ProgressTracker) start_transfer() { t := Transfer{at: time.Now()} self.transfers = append(self.transfers, &t) self.started_at = t.at } func (self *ProgressTracker) on_transmit(amt int64, active_file *File) { active_file.transmitted_bytes += amt self.total_transferred += amt now := time.Now() self.transfers = append(self.transfers, &Transfer{amt: amt, at: now}) for len(self.transfers) > 2 && self.transfers[0].is_too_old(now) { self.transfers = self.transfers[1:] } self.transfered_stats_interval = now.Sub(self.transfers[0].at) self.transfered_stats_amt = 0 for _, t := range self.transfers { self.transfered_stats_amt += t.amt } } func (self *ProgressTracker) on_file_progress(af *File, delta int64) { if delta > 0 { self.total_reported_progress += delta } } func (self *ProgressTracker) on_file_done(af *File) { af.done_at = time.Now() } type SendManager struct { request_id string state SendState files []*File bypass string use_rsync bool file_progress func(*File, int) file_done func(*File) error fid_map map[string]*File all_acknowledged, all_started, has_transmitting, has_rsync bool active_idx int prefix, suffix string last_progress_file *File progress_tracker ProgressTracker current_chunk_uncompressed_sz int64 current_chunk_write_id loop.IdType current_chunk_for_file_id string } func (self *SendManager) start_transfer() string { return FileTransmissionCommand{Action: Action_send, Bypass: self.bypass}.Serialize() } func (self *SendManager) initialize() { if self.bypass != "" { q, err := encode_bypass(self.request_id, self.bypass) if err == nil { self.bypass = q } else { fmt.Fprintln(os.Stderr, "Ignoring password because of error:", err) } } self.fid_map = make(map[string]*File, len(self.files)) for _, f := range self.files { self.fid_map[f.file_id] = f } self.active_idx = -1 self.current_chunk_uncompressed_sz = -1 self.current_chunk_for_file_id = "" self.prefix = fmt.Sprintf("\x1b]%d;id=%s;", kitty.FileTransferCode, self.request_id) self.suffix = "\x1b\\" for _, f := range self.files { if f.file_size > 0 { self.progress_tracker.total_size_of_all_files += f.file_size } } self.progress_tracker.total_bytes_to_transfer = self.progress_tracker.total_size_of_all_files } type SendHandler struct { manager *SendManager opts *Options files []*File lp *loop.Loop ctx *markup.Context transmit_started, file_metadata_sent bool quit_after_write_code int finish_cmd_write_id loop.IdType check_paths_printed bool transfer_finish_sent bool max_name_length int progress_drawn bool failed_files, done_files []*File done_file_ids *utils.Set[string] transmit_ok_checked bool progress_update_timer loop.IdType spinner *tui.Spinner } func safe_divide[A constraints.Integer | constraints.Float, B constraints.Integer | constraints.Float](a A, b B) float64 { if b == 0 { return 0 } return float64(a) / float64(b) } type Progress struct { spinner_char string bytes_so_far int64 total_bytes int64 secs_so_far float64 bytes_per_sec float64 is_complete bool max_path_length int } func reduce_to_single_grapheme(text string) string { limit := utf8.RuneCountInString(text) if limit < 2 { return text } for x := 1; x < limit; x++ { tt, w := wcswidth.TruncateToVisualLengthWithWidth(text, x) if w <= x { return tt } } return text } func render_path_in_width(path string, width int) string { path = filepath.ToSlash(path) if wcswidth.Stringwidth(path) <= width { return path } parts := strings.Split(path, string(filepath.Separator)) reduced := strings.Join(utils.Map(reduce_to_single_grapheme, parts[:len(parts)-1]), string(filepath.Separator)) path = filepath.Join(reduced, parts[len(parts)-1]) if wcswidth.Stringwidth(path) <= width { return path } return wcswidth.TruncateToVisualLength(path, width-1) + `…` } func ljust(text string, width int) string { if w := wcswidth.Stringwidth(text); w < width { text += strings.Repeat(` `, (width - w)) } return text } func rjust(text string, width int) string { if w := wcswidth.Stringwidth(text); w < width { text = strings.Repeat(` `, (width-w)) + text } return text } func render_progress_in_width(path string, p Progress, width int, ctx *markup.Context) string { unit_style := ctx.Dim(`|`) sep, trail, _ := strings.Cut(unit_style, "|") var ratio, rate, eta string if p.is_complete || p.bytes_so_far >= p.total_bytes { ratio = humanize.Size(uint64(p.total_bytes), humanize.SizeOptions{Separator: sep}) rate = humanize.Size(uint64(safe_divide(float64(p.total_bytes), p.secs_so_far)), humanize.SizeOptions{Separator: sep}) + `/s` eta = ctx.Green(humanize.ShortDuration(time.Duration(float64(time.Second) * p.secs_so_far))) } else { tb := humanize.Size(p.total_bytes) sval, _, _ := strings.Cut(tb, " ") val, _ := strconv.ParseFloat(sval, 64) ratio = humanize.FormatNumber(val*safe_divide(p.bytes_so_far, p.total_bytes)) + `/` + strings.ReplaceAll(tb, ` `, sep) rate = humanize.Size(p.bytes_per_sec, humanize.SizeOptions{Separator: sep}) + `/s` bytes_left := p.total_bytes - p.bytes_so_far eta_seconds := safe_divide(bytes_left, p.bytes_per_sec) eta = humanize.ShortDuration(time.Duration(float64(time.Second) * eta_seconds)) } lft := p.spinner_char + ` ` max_space_for_path := width/2 - wcswidth.Stringwidth(lft) max_path_length := 80 w := utils.Min(max_path_length, max_space_for_path) prefix := lft + render_path_in_width(path, w) w += wcswidth.Stringwidth(lft) prefix = ljust(prefix, w) q := ratio + trail + ctx.Yellow(" @ ") + rate + trail q = rjust(q, 25) + ` ` eta = ` ` + eta if extra := width - w - wcswidth.Stringwidth(q) - wcswidth.Stringwidth(eta); extra > 4 { q += tui.RenderProgressBar(safe_divide(p.bytes_so_far, p.total_bytes), extra) + eta } else { q += strings.TrimSpace(eta) } return prefix + q } func (self *SendHandler) render_progress(name string, p Progress) { if p.spinner_char == "" { p.spinner_char = " " } if p.is_complete { p.bytes_so_far = p.total_bytes } p.max_path_length = self.max_name_length sz, _ := self.lp.ScreenSize() self.lp.QueueWriteString(render_progress_in_width(name, p, int(sz.WidthCells), self.ctx)) } func (self *SendHandler) draw_progress() { self.lp.AllowLineWrapping(false) defer self.lp.AllowLineWrapping(true) var sc string for _, df := range self.done_files { sc = self.ctx.Green(`✔`) if df.err_msg != "" { sc = self.ctx.Err(`✘`) } if df.file_type == FileType_regular { self.draw_progress_for_current_file(df, sc, true) } else { self.lp.QueueWriteString(sc + ` ` + df.display_name + ` ` + self.ctx.Dim(self.ctx.Italic(df.file_type.String()))) } self.lp.Println() self.done_file_ids.Add(df.file_id) } self.done_files = nil is_complete := self.quit_after_write_code > -1 if is_complete { sc = self.ctx.Green(`✔`) if self.quit_after_write_code != 0 { sc = self.ctx.Err(`✘`) } } else { sc = self.spinner.Tick() } now := time.Now() if is_complete { sz, _ := self.lp.ScreenSize() self.lp.QueueWriteString(tui.RepeatChar(`─`, int(sz.WidthCells))) } else { af := self.manager.last_progress_file if af == nil || self.done_file_ids.Has(af.file_id) { if !self.manager.has_transmitting && self.done_file_ids.Len() == 0 { if self.manager.has_rsync { self.lp.QueueWriteString(sc + ` Transferring rsync signatures...`) } else { self.lp.QueueWriteString(sc + ` Transferring metadata...`) } } } else { self.draw_progress_for_current_file(af, sc, false) } } self.lp.Println() if p := self.manager.progress_tracker; p.total_reported_progress > 0 { self.render_progress(`Total`, Progress{ spinner_char: sc, bytes_so_far: p.total_reported_progress, total_bytes: p.total_bytes_to_transfer, secs_so_far: now.Sub(p.started_at).Seconds(), is_complete: is_complete, bytes_per_sec: safe_divide(p.transfered_stats_amt, p.transfered_stats_interval.Abs().Seconds()), }) } else { self.lp.QueueWriteString(`File data transfer has not yet started`) } self.lp.Println() self.schedule_progress_update(self.spinner.Interval()) self.progress_drawn = true } func (self *SendHandler) draw_progress_for_current_file(af *File, spinner_char string, is_complete bool) { p := self.manager.progress_tracker var secs_so_far time.Duration empty := File{} if af.done_at == empty.done_at { secs_so_far = time.Since(af.transmit_started_at) } else { secs_so_far = af.done_at.Sub(af.transmit_started_at) } self.render_progress(af.display_name, Progress{ spinner_char: spinner_char, is_complete: is_complete, bytes_so_far: af.reported_progress, total_bytes: af.bytes_to_transmit, secs_so_far: secs_so_far.Seconds(), bytes_per_sec: safe_divide(p.transfered_stats_amt, p.transfered_stats_interval.Abs().Seconds()), }) } func (self *SendHandler) erase_progress() { if self.progress_drawn { self.progress_drawn = false self.lp.MoveCursorVertically(-2) self.lp.QueueWriteString("\r") self.lp.ClearToEndOfScreen() } } func (self *SendHandler) refresh_progress(timer_id loop.IdType) (err error) { if !self.transmit_started || self.manager.state == SEND_CANCELED { return nil } if timer_id == self.progress_update_timer { self.progress_update_timer = 0 } if self.manager.active_file() == nil && !self.manager.all_acknowledged && self.done_file_ids.Len() != 0 && self.done_file_ids.Len() < len(self.manager.files) { if err = self.transmit_next_chunk(); err != nil { return err } } self.lp.StartAtomicUpdate() defer self.lp.EndAtomicUpdate() self.erase_progress() self.draw_progress() return nil } func (self *SendHandler) schedule_progress_update(delay time.Duration) { if self.progress_update_timer == 0 { timer_id, err := self.lp.AddTimer(delay, false, self.refresh_progress) if err == nil { self.progress_update_timer = timer_id } } } func (self *SendHandler) on_file_progress(f *File, change int) { self.schedule_progress_update(100 * time.Millisecond) } func (self *SendHandler) on_file_done(f *File) error { self.done_files = append(self.done_files, f) if f.err_msg != "" { self.failed_files = append(self.failed_files, f) } return self.refresh_progress(0) } func (self *SendHandler) send_payload(payload string) loop.IdType { self.lp.QueueWriteString(self.manager.prefix) self.lp.QueueWriteString(payload) return self.lp.QueueWriteString(self.manager.suffix) } func (self *File) metadata_command(use_rsync bool) *FileTransmissionCommand { if use_rsync && self.rsync_capable { self.ttype = TransmissionType_rsync } if self.compression_capable { self.compression = Compression_zlib self.compressor = NewZlibCompressor() } else { self.compressor = &IdentityCompressor{} } return &FileTransmissionCommand{ Action: Action_file, Compression: self.compression, Ftype: self.file_type, Name: self.remote_path, Permissions: self.permissions, Mtime: time.Duration(self.mtime.UnixNano()), File_id: self.file_id, Ttype: self.ttype, } } func (self *SendManager) send_file_metadata(send func(string) loop.IdType) { for _, f := range self.files { ftc := f.metadata_command(self.use_rsync) send(ftc.Serialize()) } } func (self *SendHandler) send_file_metadata() { if !self.file_metadata_sent { self.file_metadata_sent = true self.manager.send_file_metadata(self.send_payload) } } func (self *SendManager) update_collective_statuses() { var found_not_started, found_not_done, has_rsync, has_transmitting bool for _, f := range self.files { if f.state != ACKNOWLEDGED { found_not_done = true } if f.state == WAITING_FOR_START { found_not_started = true } else if f.state == TRANSMITTING { has_transmitting = true } if f.ttype == TransmissionType_rsync { has_rsync = true } } self.all_acknowledged = !found_not_done self.all_started = !found_not_started self.has_rsync = has_rsync self.has_transmitting = has_transmitting } func (self *SendManager) on_file_status_update(ftc *FileTransmissionCommand) error { file := self.fid_map[ftc.File_id] if file == nil { return nil } switch ftc.Status { case `STARTED`: file.remote_final_path = ftc.Name file.remote_initial_size = int64(ftc.Size) if file.file_type == FileType_directory { file.state = FINISHED } else { if ftc.Ttype == TransmissionType_rsync { file.state = WAITING_FOR_DATA } else { file.state = TRANSMITTING } if file.state == WAITING_FOR_DATA { file.differ = rsync.NewDiffer() } self.update_collective_statuses() } case `PROGRESS`: self.last_progress_file = file change := int64(ftc.Size) - file.reported_progress file.reported_progress = int64(ftc.Size) self.progress_tracker.on_file_progress(file, change) self.file_progress(file, int(change)) default: if ftc.Name != "" && file.remote_final_path == "" { file.remote_final_path = ftc.Name } file.state = ACKNOWLEDGED if ftc.Status == `OK` { if ftc.Size > 0 { change := int64(ftc.Size) - file.reported_progress file.reported_progress = int64(ftc.Size) self.progress_tracker.on_file_progress(file, change) self.file_progress(file, int(change)) } } else { file.err_msg = ftc.Status } self.progress_tracker.on_file_done(file) if err := self.file_done(file); err != nil { return err } if self.active_idx > -1 && file == self.files[self.active_idx] { self.active_idx = -1 } self.update_collective_statuses() } return nil } func (self *File) start_delta_calculation() (err error) { self.state = TRANSMITTING if self.actual_file == nil { self.actual_file, err = os.Open(self.expanded_local_path) if err != nil { return } } self.deltabuf = bytes.NewBuffer(make([]byte, 0, 32+rsync.DataSizeMultiple*self.differ.BlockSize())) self.delta_loader = self.differ.CreateDelta(self.actual_file, self.deltabuf) return nil } func (self *SendManager) on_signature_data_received(ftc *FileTransmissionCommand) error { file := self.fid_map[ftc.File_id] if file == nil || file.state != WAITING_FOR_DATA { return nil } if file.differ == nil { file.differ = rsync.NewDiffer() } if err := file.differ.AddSignatureData(ftc.Data); err != nil { return err } self.progress_tracker.signature_bytes += len(ftc.Data) if ftc.Action == Action_end_data { if err := file.differ.FinishSignatureData(); err != nil { return err } return file.start_delta_calculation() } return nil } func (self *SendManager) on_file_transfer_response(ftc *FileTransmissionCommand) error { switch ftc.Action { case Action_status: if ftc.File_id != "" { return self.on_file_status_update(ftc) } if ftc.Status == "OK" { self.state = SEND_PERMISSION_GRANTED } else { self.state = SEND_PERMISSION_DENIED } case Action_data, Action_end_data: if ftc.File_id != "" { return self.on_signature_data_received(ftc) } } return nil } func (self *SendHandler) on_file_transfer_response(ftc *FileTransmissionCommand) error { if ftc.Id != self.manager.request_id { return nil } if ftc.Action == Action_status && ftc.Status == "CANCELED" { self.lp.Quit(1) return nil } if self.quit_after_write_code > -1 || self.manager.state == SEND_CANCELED { return nil } before := self.manager.state err := self.manager.on_file_transfer_response(ftc) if err != nil { return err } if before == SEND_WAITING_FOR_PERMISSION { switch self.manager.state { case SEND_PERMISSION_DENIED: self.lp.Println(self.ctx.Err("Permission denied for this transfer")) self.lp.Quit(1) return nil case SEND_PERMISSION_GRANTED: self.lp.Println(self.ctx.Green("Permission granted for this transfer")) self.send_file_metadata() } } if !self.transmit_started { return self.check_for_transmit_ok() } if self.manager.all_acknowledged { self.transfer_finished() } else if ftc.Action == Action_end_data && ftc.File_id != "" { return self.transmit_next_chunk() } return nil } func (self *SendHandler) check_for_transmit_ok() (err error) { if self.transmit_ok_checked { return self.start_transfer() } if self.manager.state != SEND_PERMISSION_GRANTED { return } if self.opts.ConfirmPaths { if self.manager.all_started { self.print_check_paths() } return } self.transmit_ok_checked = true return self.start_transfer() } func (self *SendHandler) print_check_paths() { if self.check_paths_printed { return } self.check_paths_printed = true self.lp.Println(`The following file transfers will be performed. A red destination means an existing file will be overwritten.`) for _, df := range self.manager.files { fn := df.remote_final_path if df.remote_initial_size > -1 { fn = self.ctx.Red(fn) } self.lp.Println( self.ctx.Prettify(fmt.Sprintf(":%s:`%s` ", df.file_type.Color(), df.file_type.ShortText())), df.display_name, ` → `, fn) } hsize := humanize.Size(self.manager.progress_tracker.total_bytes_to_transfer) if n := len(self.manager.files); n == 1 { self.lp.Println(fmt.Sprintf(`Transferring %d file of total size: %s`, n, hsize)) } else { self.lp.Println(fmt.Sprintf(`Transferring %d files of total size: %s`, n, hsize)) } self.print_continue_msg() } func (self *SendManager) activate_next_ready_file() *File { if self.active_idx > -1 && self.active_idx < len(self.files) { self.files[self.active_idx].transmit_ended_at = time.Now() } for i, f := range self.files { if f.state == TRANSMITTING { self.active_idx = i self.update_collective_statuses() self.progress_tracker.change_active_file(f) return f } } self.active_idx = -1 self.update_collective_statuses() return nil } func (self *SendManager) active_file() *File { if self.active_idx > -1 && self.active_idx < len(self.files) { return self.files[self.active_idx] } return nil } func (self *File) next_chunk() (ans string, asz int, err error) { const sz = 1024 * 1024 switch self.file_type { case FileType_symlink: self.state = FINISHED ans, asz = self.symbolic_link_target, len(self.symbolic_link_target) return case FileType_link: self.state = FINISHED ans, asz = self.hard_link_target, len(self.hard_link_target) return } is_last := false var chunk []byte if self.delta_loader != nil { for !is_last && self.deltabuf.Len() < sz { if err = self.delta_loader(); err != nil { if err == io.EOF { is_last = true } else { return } } } chunk = slices.Clone(self.deltabuf.Bytes()) self.deltabuf.Reset() } else { if self.actual_file == nil { self.actual_file, err = os.Open(self.expanded_local_path) if err != nil { return } } chunk = make([]byte, sz) var n int n, err = self.actual_file.Read(chunk) if err != nil && !errors.Is(err, io.EOF) { return } if n <= 0 { is_last = true } else if pos, _ := self.actual_file.Seek(0, io.SeekCurrent); pos >= self.file_size { is_last = true } chunk = chunk[:n] } uncompressed_sz := len(chunk) cchunk := self.compressor.Compress(chunk) if is_last { trail := self.compressor.Flush() if len(trail) >= 0 { cchunk = append(cchunk, trail...) } self.state = FINISHED if self.actual_file != nil { err = self.actual_file.Close() self.actual_file = nil if err != nil { return } } self.delta_loader = nil self.deltabuf = nil } ans, asz = utils.UnsafeBytesToString(cchunk), uncompressed_sz return } func (self *SendManager) next_chunks(callback func(string) loop.IdType) error { if self.active_file() == nil { self.activate_next_ready_file() } af := self.active_file() if af == nil { return nil } chunk := "" self.current_chunk_uncompressed_sz = 0 for af.state != FINISHED && len(chunk) == 0 { c, usz, err := af.next_chunk() if err != nil { return err } self.current_chunk_uncompressed_sz += int64(usz) self.current_chunk_for_file_id = af.file_id chunk = c } is_last := af.state == FINISHED if len(chunk) > 0 { split_for_transfer(utils.UnsafeStringToBytes(chunk), af.file_id, is_last, func(ftc *FileTransmissionCommand) { self.current_chunk_write_id = callback(ftc.Serialize()) }) } else if is_last { self.current_chunk_write_id = callback(FileTransmissionCommand{Action: Action_end_data, File_id: af.file_id}.Serialize()) } if is_last { self.activate_next_ready_file() if self.active_file() == nil { return nil } } return nil } func (self *SendHandler) transmit_next_chunk() (err error) { found_chunk := false for !found_chunk { if err = self.manager.next_chunks(func(chunk string) loop.IdType { found_chunk = true return self.send_payload(chunk) }); err != nil { return err } if !found_chunk { if self.manager.all_acknowledged { self.transfer_finished() return } self.manager.update_collective_statuses() if !self.manager.has_transmitting { return } } } return } func (self *SendHandler) start_transfer() (err error) { if self.manager.active_file() == nil { self.manager.activate_next_ready_file() } if self.manager.active_file() != nil { self.transmit_started = true self.manager.progress_tracker.start_transfer() if err = self.transmit_next_chunk(); err != nil { return } self.draw_progress() } return } func (self *SendHandler) initialize() error { self.manager.initialize() self.spinner = tui.NewSpinner("dots") self.ctx = markup.New(true) self.send_payload(self.manager.start_transfer()) if self.opts.PermissionsBypass != "" { // dont wait for permission, not needed with a bypass and avoids a roundtrip self.send_file_metadata() } return nil } func (self *SendHandler) transfer_finished() { if self.transfer_finish_sent { return } self.transfer_finish_sent = true self.finish_cmd_write_id = self.send_payload(FileTransmissionCommand{Action: Action_finish}.Serialize()) } func (self *SendHandler) on_text(text string, from_key_event, in_bracketed_paste bool) error { if self.quit_after_write_code > -1 { return nil } if self.check_paths_printed && !self.transmit_started { switch strings.ToLower(text) { case "y": err := self.start_transfer() if err != nil { return err } if self.manager.all_acknowledged { if err = self.refresh_progress(0); err != nil { return err } self.transfer_finished() } return nil case "n": self.failed_files = nil self.abort_transfer() self.lp.Println(`Sending cancel request to terminal`) return nil } self.print_continue_msg() } return nil } func (self *SendHandler) print_continue_msg() { self.lp.Println( `Press`, self.ctx.Green(`y`), `to continue or`, self.ctx.BrightRed(`n`), `to abort`) } func (self *SendHandler) abort_transfer(delay ...time.Duration) { d := 5 * time.Second if len(delay) > 0 { d = delay[0] } self.send_payload(FileTransmissionCommand{Action: Action_cancel}.Serialize()) self.manager.state = SEND_CANCELED _, _ = self.lp.AddTimer(d, false, func(loop.IdType) error { self.lp.Quit(1) return nil }) } func (self *SendHandler) on_resize(old_size, new_size loop.ScreenSize) error { if self.progress_drawn { return self.refresh_progress(0) } return nil } func (self *SendHandler) on_key_event(ev *loop.KeyEvent) error { if self.quit_after_write_code > -1 { return nil } if ev.MatchesPressOrRepeat("esc") { ev.Handled = true if self.check_paths_printed && !self.transmit_started { self.failed_files = nil self.abort_transfer() self.lp.Println(`Sending cancel request to terminal`) return nil } else { self.on_interrupt() } } else if ev.MatchesPressOrRepeat("ctrl+c") { self.on_interrupt() ev.Handled = true } return nil } func (self *SendHandler) on_writing_finished(msg_id loop.IdType, has_pending_writes bool) (err error) { chunk_transmitted := self.manager.current_chunk_uncompressed_sz >= 0 && msg_id == self.manager.current_chunk_write_id if chunk_transmitted { self.manager.progress_tracker.on_transmit(self.manager.current_chunk_uncompressed_sz, self.manager.fid_map[self.manager.current_chunk_for_file_id]) self.manager.current_chunk_uncompressed_sz = -1 self.manager.current_chunk_write_id = 0 self.manager.current_chunk_for_file_id = "" } if self.finish_cmd_write_id > 0 && msg_id == self.finish_cmd_write_id { if len(self.failed_files) > 0 { self.quit_after_write_code = 1 } else { self.quit_after_write_code = 0 } if err = self.refresh_progress(0); err != nil { return err } } if self.quit_after_write_code > -1 && !has_pending_writes { self.lp.Quit(self.quit_after_write_code) return } if self.manager.state == SEND_PERMISSION_GRANTED && !self.transmit_started { return self.check_for_transmit_ok() } if chunk_transmitted { if err = self.refresh_progress(0); err != nil { return err } return self.transmit_next_chunk() } return } func (self *SendHandler) on_interrupt() { if self.quit_after_write_code > -1 { return } if self.manager.state == SEND_CANCELED { self.lp.Println(`Waiting for canceled acknowledgement from terminal, will abort in a few seconds if no response received`) return } self.lp.Println(self.ctx.BrightRed(`Interrupt requested, cancelling transfer, transferred files are in undefined state`)) self.abort_transfer() } func send_loop(opts *Options, files []*File) (err error, rc int) { lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors) if err != nil { return err, 1 } handler := &SendHandler{ opts: opts, files: files, lp: lp, quit_after_write_code: -1, max_name_length: utils.Max(0, utils.Map(func(f *File) int { return wcswidth.Stringwidth(f.display_name) }, files)...), progress_drawn: true, done_file_ids: utils.NewSet[string](), manager: &SendManager{ request_id: random_id(), files: files, bypass: opts.PermissionsBypass, use_rsync: opts.TransmitDeltas, }, } handler.manager.file_progress = handler.on_file_progress handler.manager.file_done = handler.on_file_done lp.OnInitialize = func() (string, error) { lp.SetCursorVisible(false) return "", handler.initialize() } lp.OnFinalize = func() string { lp.SetCursorVisible(true) return "" } ftc_code := strconv.Itoa(kitty.FileTransferCode) lp.OnEscapeCode = func(et loop.EscapeCodeType, payload []byte) error { if et == loop.OSC { if idx := bytes.IndexByte(payload, ';'); idx > 0 { if utils.UnsafeBytesToString(payload[:idx]) == ftc_code { ftc, err := NewFileTransmissionCommand(utils.UnsafeBytesToString(payload[idx+1:])) if err != nil { return fmt.Errorf("Received invalid FileTransmissionCommand from terminal with error: %w", err) } return handler.on_file_transfer_response(ftc) } } } return nil } lp.OnText = handler.on_text lp.OnKeyEvent = handler.on_key_event lp.OnResize = handler.on_resize lp.OnWriteComplete = handler.on_writing_finished err = lp.Run() if err != nil { return err, 1 } if lp.DeathSignalName() != "" { lp.KillIfSignalled() return } p := handler.manager.progress_tracker if handler.manager.has_rsync && p.total_transferred+int64(p.signature_bytes) > 0 && lp.ExitCode() == 0 { var tsf int64 for _, f := range files { if f.ttype == TransmissionType_rsync { tsf += f.file_size } } if tsf > 0 { print_rsync_stats(tsf, p.total_transferred, int64(p.signature_bytes)) } } if len(handler.failed_files) > 0 { fmt.Fprintf(os.Stderr, "Transfer of %d out of %d files failed\n", len(handler.failed_files), len(handler.manager.files)) for _, f := range handler.failed_files { fmt.Println(handler.ctx.BrightRed(f.display_name)) fmt.Println(` `, f.err_msg) } rc = 1 } if lp.ExitCode() != 0 { rc = lp.ExitCode() } return } func send_main(opts *Options, args []string) (err error, rc int) { fmt.Println("Scanning files…") files, err := files_for_send(opts, args) if err != nil { return err, 1 } fmt.Printf("Found %d files and directories, requesting transfer permission…", len(files)) fmt.Println() err, rc = send_loop(opts, files) return } kitty-0.41.1/kittens/transfer/send_test.go0000664000175000017510000000564314773370543020143 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package transfer import ( "fmt" "os" "path/filepath" "strings" "testing" "github.com/google/go-cmp/cmp" ) var _ = fmt.Print func TestPathMappingSend(t *testing.T) { opts := &Options{} tdir := t.TempDir() b := filepath.Join(tdir, "b") os.Mkdir(b, 0o700) os.WriteFile(filepath.Join(b, "r"), nil, 0600) os.Mkdir(filepath.Join(b, "d"), 0o700) os.WriteFile(filepath.Join(b, "d", "r"), nil, 0600) gm := func(args ...string) ([]*File, error) { return files_for_send(opts, args) } mp := func(path string, is_remote bool) string { path = strings.TrimSpace(path) if strings.HasPrefix(path, "~") || filepath.IsAbs(path) { return path } return filepath.Join(tdir, path) } tf := func(expected string, args ...string) { files, err := gm(args...) if err != nil { t.Fatalf("Failed with mode: %s cwd: %s home: %s and args: %#v\n%s", opts.Mode, cwd_path(), home_path(), args, err) } actual := make(map[string]string) for _, f := range files { actual[f.expanded_local_path] = f.remote_path } e := make(map[string]string, len(actual)) for _, rec := range strings.Split(expected, " ") { k, v, _ := strings.Cut(rec, ":") e[mp(k, false)] = mp(v, true) } if diff := cmp.Diff(e, actual); diff != "" { t.Fatalf("Failed with mode: %s cwd: %s home: %s and args: %#v\n%s", opts.Mode, cwd_path(), home_path(), args, diff) } } opts.Mode = "mirror" run_with_paths(b, "/foo/bar", func() { tf("b/r:b/r b/d:b/d b/d/r:b/d/r", "r", "d") tf("b/r:b/r b/d/r:b/d/r", "r", "d/r") }) run_with_paths(b, tdir, func() { tf("b/r:~/b/r b/d:~/b/d b/d/r:~/b/d/r", "r", "d") }) opts.Mode = "normal" run_with_paths("/some/else", "/foo/bar", func() { tf("b/r:/dest/r b/d:/dest/d b/d/r:/dest/d/r", filepath.Join(b, "r"), filepath.Join(b, "d"), "/dest") tf("b/r:~/dest/r b/d:~/dest/d b/d/r:~/dest/d/r", filepath.Join(b, "r"), filepath.Join(b, "d"), "~/dest") }) run_with_paths(b, "/foo/bar", func() { tf("b/r:/dest/r b/d:/dest/d b/d/r:/dest/d/r", "r", "d", "/dest") }) os.Symlink("/foo/b", filepath.Join(b, "e")) os.Symlink("r", filepath.Join(b, "s")) os.Link(filepath.Join(b, "r"), filepath.Join(b, "h")) file_idx := 0 first_file := func(args ...string) *File { files, err := gm(args...) if err != nil { t.Fatal(err) } return files[file_idx] } ae := func(a any, b any) { if diff := cmp.Diff(a, b); diff != "" { t.Fatalf("%s", diff) } } run_with_paths("/some/else", "/foo/bar", func() { f := first_file(filepath.Join(b, "e"), "dest") ae(f.symbolic_link_target, "path:/foo/b") f = first_file(filepath.Join(b, "s"), filepath.Join(b, "r"), "dest") ae(f.symbolic_link_target, "fid:2") f = first_file(filepath.Join(b, "h"), "dest") ae(f.file_type, FileType_regular) file_idx = 1 f = first_file(filepath.Join(b, "h"), filepath.Join(b, "r"), "dest") ae(f.hard_link_target, "fid:1") ae(f.file_type, FileType_link) }) } kitty-0.41.1/kittens/transfer/utils.go0000664000175000017510000000551014773370543017304 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package transfer import ( "crypto/rand" "encoding/hex" "fmt" "os" "path/filepath" "strings" "kitty/tools/crypto" "kitty/tools/utils" "kitty/tools/utils/humanize" ) var _ = fmt.Print var global_cwd, global_home string func cwd_path() string { if global_cwd == "" { ans, _ := os.Getwd() return ans } return global_cwd } func home_path() string { if global_home == "" { return utils.Expanduser("~") } return global_home } func encode_bypass(request_id string, bypass string) (string, error) { q := request_id + ";" + bypass if pkey_encoded := os.Getenv("KITTY_PUBLIC_KEY"); pkey_encoded != "" { encryption_protocol, pubkey, err := crypto.DecodePublicKey(pkey_encoded) if err != nil { return "", err } encrypted, err := crypto.Encrypt_data(utils.UnsafeStringToBytes(q), pubkey, encryption_protocol) if err != nil { return "", err } return fmt.Sprintf("kitty-1:%s", utils.UnsafeBytesToString(encrypted)), nil } return "", fmt.Errorf("KITTY_PUBLIC_KEY env var not set, cannot transmit password securely") } func abspath(path string, use_home ...bool) string { if filepath.IsAbs(path) { return path } var base string if len(use_home) > 0 && use_home[0] { base = home_path() } else { base = cwd_path() } return filepath.Join(base, path) } func expand_home(path string) string { if strings.HasPrefix(path, "~"+string(os.PathSeparator)) { path = strings.TrimLeft(path[2:], string(os.PathSeparator)) path = filepath.Join(home_path(), path) } else if path == "~" { path = home_path() } return path } func random_id() string { bytes := []byte{0, 0} rand.Read(bytes) return fmt.Sprintf("%x%s", os.Getpid(), hex.EncodeToString(bytes)) } func run_with_paths(cwd, home string, f func()) { global_cwd, global_home = cwd, home defer func() { global_cwd, global_home = "", "" }() f() } func should_be_compressed(path, strategy string) bool { if strategy == "always" { return true } if strategy == "never" { return false } ext := strings.ToLower(filepath.Ext(path)) if ext != "" { switch ext[1:] { case "zip", "odt", "odp", "pptx", "docx", "gz", "bz2", "xz", "svgz": return false } } mt := utils.GuessMimeType(path) if strings.HasSuffix(mt, "+zip") || (strings.HasPrefix(mt, "image/") && mt != "image/svg+xml") || strings.HasPrefix(mt, "video/") { return false } return true } func print_rsync_stats(total_bytes, delta_bytes, signature_bytes int64) { fmt.Println("Rsync stats:") fmt.Printf(" Delta size: %s Signature size: %s\n", humanize.Size(delta_bytes), humanize.Size(signature_bytes)) frac := float64(delta_bytes+signature_bytes) / float64(utils.Max(1, total_bytes)) fmt.Printf(" Transmitted: %s of a total of %s (%.1f%%)\n", humanize.Size(delta_bytes+signature_bytes), humanize.Size(total_bytes), frac*100) } kitty-0.41.1/kittens/transfer/utils.py0000664000175000017510000000252714773370543017334 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import os from collections.abc import Generator from contextlib import contextmanager _cwd = _home = '' def abspath(path: str, use_home: bool = False) -> str: base = home_path() if use_home else (_cwd or os.getcwd()) return os.path.normpath(os.path.join(base, path)) def home_path() -> str: return _home or os.path.expanduser('~') def cwd_path() -> str: return _cwd or os.getcwd() def expand_home(path: str) -> str: if path.startswith('~' + os.sep) or (os.altsep and path.startswith('~' + os.altsep)): return os.path.join(home_path(), path[2:].lstrip(os.sep + (os.altsep or ''))) return path @contextmanager def set_paths(cwd: str = '', home: str = '') -> Generator[None, None, None]: global _cwd, _home orig = _cwd, _home try: _cwd, _home = cwd, home yield finally: _cwd, _home = orig class IdentityCompressor: def compress(self, data: bytes) -> bytes: return data def flush(self) -> bytes: return b'' class ZlibCompressor: def __init__(self) -> None: import zlib self.c = zlib.compressobj() def compress(self, data: bytes) -> bytes: return self.c.compress(data) def flush(self) -> bytes: return self.c.flush() kitty-0.41.1/kittens/tui/0000775000175000017510000000000014773370543014571 5ustar nileshnileshkitty-0.41.1/kittens/tui/__init__.py0000664000175000017510000000000014773370543016670 0ustar nileshnileshkitty-0.41.1/kittens/tui/dircolors.py0000664000175000017510000002415714773370543017154 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import os import stat from collections.abc import Generator from contextlib import suppress DEFAULT_DIRCOLORS = r"""# {{{ # Configuration file for dircolors, a utility to help you set the # LS_COLORS environment variable used by GNU ls with the --color option. # Copyright (C) 1996-2019 Free Software Foundation, Inc. # Copying and distribution of this file, with or without modification, # are permitted provided the copyright notice and this notice are preserved. # The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the # slackware version of dircolors) are recognized but ignored. # Below are TERM entries, which can be a glob patterns, to match # against the TERM environment variable to determine if it is colorizable. TERM Eterm TERM ansi TERM *color* TERM con[0-9]*x[0-9]* TERM cons25 TERM console TERM cygwin TERM dtterm TERM gnome TERM hurd TERM jfbterm TERM konsole TERM kterm TERM linux TERM linux-c TERM mlterm TERM putty TERM rxvt* TERM screen* TERM st TERM terminator TERM tmux* TERM vt100 TERM xterm* # Below are the color init strings for the basic file types. # One can use codes for 256 or more colors supported by modern terminals. # The default color codes use the capabilities of an 8 color terminal # with some additional attributes as per the following codes: # Attribute codes: # 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed # Text color codes: # 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white # Background color codes: # 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white #NORMAL 00 # no color code at all #FILE 00 # regular file: use no color at all RESET 0 # reset to "normal" color DIR 01;34 # directory LINK 01;36 # symbolic link. (If you set this to 'target' instead of a # numerical value, the color is as for the file pointed to.) MULTIHARDLINK 00 # regular file with more than one link FIFO 40;33 # pipe SOCK 01;35 # socket DOOR 01;35 # door BLK 40;33;01 # block device driver CHR 40;33;01 # character device driver ORPHAN 40;31;01 # symlink to nonexistent file, or non-stat'able file ... MISSING 00 # ... and the files they point to SETUID 37;41 # file that is setuid (u+s) SETGID 30;43 # file that is setgid (g+s) CAPABILITY 30;41 # file with capability STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w) OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable # This is for files with execute permission: EXEC 01;32 # List any file extensions like '.gz' or '.tar' that you would like ls # to colorize below. Put the extension, a space, and the color init string. # (and any comments you want to add after a '#') # If you use DOS-style suffixes, you may want to uncomment the following: #.cmd 01;32 # executables (bright green) #.exe 01;32 #.com 01;32 #.btm 01;32 #.bat 01;32 # Or if you want to colorize scripts even if they do not have the # executable bit actually set. #.sh 01;32 #.csh 01;32 # archives or compressed (bright red) .tar 01;31 .tgz 01;31 .arc 01;31 .arj 01;31 .taz 01;31 .lha 01;31 .lz4 01;31 .lzh 01;31 .lzma 01;31 .tlz 01;31 .txz 01;31 .tzo 01;31 .t7z 01;31 .zip 01;31 .z 01;31 .dz 01;31 .gz 01;31 .lrz 01;31 .lz 01;31 .lzo 01;31 .xz 01;31 .zst 01;31 .tzst 01;31 .bz2 01;31 .bz 01;31 .tbz 01;31 .tbz2 01;31 .tz 01;31 .deb 01;31 .rpm 01;31 .jar 01;31 .war 01;31 .ear 01;31 .sar 01;31 .rar 01;31 .alz 01;31 .ace 01;31 .zoo 01;31 .cpio 01;31 .7z 01;31 .rz 01;31 .cab 01;31 .wim 01;31 .swm 01;31 .dwm 01;31 .esd 01;31 # image formats .jpg 01;35 .jpeg 01;35 .mjpg 01;35 .mjpeg 01;35 .gif 01;35 .bmp 01;35 .pbm 01;35 .pgm 01;35 .ppm 01;35 .tga 01;35 .xbm 01;35 .xpm 01;35 .tif 01;35 .tiff 01;35 .png 01;35 .svg 01;35 .svgz 01;35 .mng 01;35 .pcx 01;35 .mov 01;35 .mpg 01;35 .mpeg 01;35 .m2v 01;35 .mkv 01;35 .webm 01;35 .ogm 01;35 .mp4 01;35 .m4v 01;35 .mp4v 01;35 .vob 01;35 .qt 01;35 .nuv 01;35 .wmv 01;35 .asf 01;35 .rm 01;35 .rmvb 01;35 .flc 01;35 .avi 01;35 .fli 01;35 .flv 01;35 .gl 01;35 .dl 01;35 .xcf 01;35 .xwd 01;35 .yuv 01;35 .cgm 01;35 .emf 01;35 # https://wiki.xiph.org/MIME_Types_and_File_Extensions .ogv 01;35 .ogx 01;35 # audio formats .aac 00;36 .au 00;36 .flac 00;36 .m4a 00;36 .mid 00;36 .midi 00;36 .mka 00;36 .mp3 00;36 .mpc 00;36 .ogg 00;36 .ra 00;36 .wav 00;36 # https://wiki.xiph.org/MIME_Types_and_File_Extensions .oga 00;36 .opus 00;36 .spx 00;36 .xspf 00;36 """ # }}} # special file? special_types = ( (stat.S_IFLNK, 'ln'), # symlink (stat.S_IFIFO, 'pi'), # pipe (FIFO) (stat.S_IFSOCK, 'so'), # socket (stat.S_IFBLK, 'bd'), # block device (stat.S_IFCHR, 'cd'), # character device (stat.S_ISUID, 'su'), # setuid (stat.S_ISGID, 'sg'), # setgid ) CODE_MAP = { 'RESET': 'rs', 'DIR': 'di', 'LINK': 'ln', 'MULTIHARDLINK': 'mh', 'FIFO': 'pi', 'SOCK': 'so', 'DOOR': 'do', 'BLK': 'bd', 'CHR': 'cd', 'ORPHAN': 'or', 'MISSING': 'mi', 'SETUID': 'su', 'SETGID': 'sg', 'CAPABILITY': 'ca', 'STICKY_OTHER_WRITABLE': 'tw', 'OTHER_WRITABLE': 'ow', 'STICKY': 'st', 'EXEC': 'ex', } def stat_at(file: str, cwd: int | str | None = None, follow_symlinks: bool = False) -> os.stat_result: dirfd: int | None = None need_to_close = False if isinstance(cwd, str): dirfd = os.open(cwd, os.O_RDONLY | getattr(os, 'O_CLOEXEC', 0)) need_to_close = True elif isinstance(cwd, int): dirfd = cwd try: return os.stat(file, dir_fd=dirfd, follow_symlinks=follow_symlinks) finally: if need_to_close and dirfd is not None: os.close(dirfd) class Dircolors: def __init__(self) -> None: self.codes: dict[str, str] = {} self.extensions: dict[str, str] = {} if not self.load_from_environ() and not self.load_from_file(): self.load_defaults() def clear(self) -> None: self.codes.clear() self.extensions.clear() def load_from_file(self) -> bool: for candidate in (os.path.expanduser('~/.dir_colors'), '/etc/DIR_COLORS'): with suppress(Exception): with open(candidate) as f: return self.load_from_dircolors(f.read()) return False def load_from_lscolors(self, lscolors: str) -> bool: self.clear() if not lscolors: return False for item in lscolors.split(':'): try: code, color = item.split('=', 1) except ValueError: continue if code.startswith('*.'): self.extensions[code[1:]] = color else: self.codes[code] = color return bool(self.codes or self.extensions) def load_from_environ(self, envvar: str = 'LS_COLORS') -> bool: return self.load_from_lscolors(os.environ.get(envvar) or '') def load_from_dircolors(self, database: str, strict: bool = False) -> bool: self.clear() for line in database.splitlines(): line = line.split('#')[0].strip() if not line: continue split = line.split() if len(split) != 2: if strict: raise ValueError(f'Warning: unable to parse dircolors line "{line}"') continue key, val = split if key == 'TERM': continue if key in CODE_MAP: self.codes[CODE_MAP[key]] = val elif key.startswith('.'): self.extensions[key] = val elif strict: raise ValueError(f'Warning: unable to parse dircolors line "{line}"') return bool(self.codes or self.extensions) def load_defaults(self) -> bool: self.clear() return self.load_from_dircolors(DEFAULT_DIRCOLORS, True) def generate_lscolors(self) -> str: """ Output the database in the format used by the LS_COLORS environment variable. """ def gen_pairs() -> Generator[tuple[str, str], None, None]: for pair in self.codes.items(): yield pair for pair in self.extensions.items(): # change .xyz to *.xyz yield '*' + pair[0], pair[1] return ':'.join('{}={}'.format(*pair) for pair in gen_pairs()) def _format_code(self, text: str, code: str) -> str: val = self.codes.get(code) return '\033[{}m{}\033[{}m'.format(val, text, self.codes.get('rs', '0')) if val else text def _format_ext(self, text: str, ext: str) -> str: val = self.extensions.get(ext, '0') return '\033[{}m{}\033[{}m'.format(val, text, self.codes.get('rs', '0')) if val else text def format_mode(self, text: str, sr: os.stat_result) -> str: mode = sr.st_mode if stat.S_ISDIR(mode): if (mode & (stat.S_ISVTX | stat.S_IWOTH)) == (stat.S_ISVTX | stat.S_IWOTH): # sticky and world-writable return self._format_code(text, 'tw') if mode & stat.S_ISVTX: # sticky but not world-writable return self._format_code(text, 'st') if mode & stat.S_IWOTH: # world-writable but not sticky return self._format_code(text, 'ow') # normal directory return self._format_code(text, 'di') for mask, code in special_types: if (mode & mask) == mask: return self._format_code(text, code) # executable file? if mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH): return self._format_code(text, 'ex') # regular file, format according to its extension ext = os.path.splitext(text)[1] if ext: return self._format_ext(text, ext) return text def __call__(self, path: str, text: str, cwd: int | str | None = None) -> str: follow_symlinks = self.codes.get('ln') == 'target' try: sr = stat_at(path, cwd, follow_symlinks) except OSError: return text return self.format_mode(text, sr) def develop() -> None: import sys print(Dircolors()(sys.argv[-1], sys.argv[-1])) kitty-0.41.1/kittens/tui/handler.py0000664000175000017510000002670514773370543016572 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import os from collections import deque from collections.abc import Callable, Sequence from contextlib import suppress from types import TracebackType from typing import TYPE_CHECKING, Any, ContextManager, Deque, NamedTuple, Optional, cast from kitty.constants import kitten_exe, running_in_kitty from kitty.fast_data_types import monotonic, safe_pipe from kitty.types import DecoratedFunc, ParsedShortcut from kitty.typing import ( AbstractEventLoop, BossType, Debug, ImageManagerType, KeyActionType, KeyEventType, LoopType, MouseButton, MouseEvent, ScreenSize, TermManagerType, WindowType, ) from .operations import MouseTracking, pending_update if TYPE_CHECKING: from kitty.file_transmission import FileTransmissionCommand OpenUrlHandler = Optional[Callable[[BossType, WindowType, str, int, str], bool]] class ButtonEvent(NamedTuple): mouse_event: MouseEvent timestamp: float def is_click(a: ButtonEvent, b: ButtonEvent) -> bool: from .loop import EventType if a.mouse_event.type is not EventType.PRESS or b.mouse_event.type is not EventType.RELEASE: return False x = a.mouse_event.cell_x - b.mouse_event.cell_x y = a.mouse_event.cell_y - b.mouse_event.cell_y return x*x + y*y <= 4 class KittenUI: allow_remote_control: bool = False remote_control_password: bool | str = False def __init__(self, func: Callable[[list[str]], str], allow_remote_control: bool, remote_control_password: bool | str): self.func = func self.allow_remote_control = allow_remote_control self.remote_control_password = remote_control_password self.password = self.to = '' self.rc_fd = -1 self.initialized = False def initialize(self) -> None: if self.initialized: return self.initialized = True if running_in_kitty(): return if self.allow_remote_control: self.to = os.environ.get('KITTY_LISTEN_ON', '') self.rc_fd = int(self.to.partition(':')[-1]) os.set_inheritable(self.rc_fd, False) if (self.remote_control_password or self.remote_control_password == '') and not self.password: import socket with socket.fromfd(self.rc_fd, socket.AF_UNIX, socket.SOCK_STREAM) as s: data = s.recv(256) if not data.endswith(b'\n'): raise Exception(f'The remote control password was invalid: {data!r}') self.password = data.strip().decode() def __call__(self, args: list[str]) -> str: self.initialize() return self.func(args) def allow_indiscriminate_remote_control(self, enable: bool = True) -> None: if self.rc_fd > -1: if enable: os.set_inheritable(self.rc_fd, True) if self.password: os.environ['KITTY_RC_PASSWORD'] = self.password else: os.set_inheritable(self.rc_fd, False) if self.password: os.environ.pop('KITTY_RC_PASSWORD', None) def remote_control(self, cmd: str | Sequence[str], **kw: Any) -> Any: if not self.allow_remote_control: raise ValueError('Remote control is not enabled, remember to use allow_remote_control=True') prefix = [kitten_exe(), '@'] r = -1 pass_fds = list(kw.get('pass_fds') or ()) try: if self.rc_fd > -1: pass_fds.append(self.rc_fd) if self.password and self.rc_fd > -1: r, w = safe_pipe(False) os.write(w, self.password.encode()) os.close(w) prefix += ['--password-file', f'fd:{r}', '--use-password', 'always'] pass_fds.append(r) if pass_fds: kw['pass_fds'] = tuple(pass_fds) if isinstance(cmd, str): cmd = ' '.join(prefix) else: cmd = prefix + list(cmd) import subprocess if self.rc_fd > -1: is_inheritable = os.get_inheritable(self.rc_fd) if not is_inheritable: os.set_inheritable(self.rc_fd, True) try: return subprocess.run(cmd, **kw) finally: if self.rc_fd > -1 and not is_inheritable: os.set_inheritable(self.rc_fd, False) finally: if r > -1: os.close(r) def kitten_ui( allow_remote_control: bool = KittenUI.allow_remote_control, remote_control_password: bool | str = KittenUI.allow_remote_control, ) -> Callable[[Callable[[list[str]], str]], KittenUI]: def wrapper(impl: Callable[..., Any]) -> KittenUI: return KittenUI(impl, allow_remote_control, remote_control_password) return wrapper class Handler: image_manager_class: type[ImageManagerType] | None = None use_alternate_screen = True mouse_tracking = MouseTracking.none terminal_io_ended = False overlay_ready_report_needed = False def _initialize( self, screen_size: ScreenSize, term_manager: TermManagerType, schedule_write: Callable[[bytes], None], tui_loop: LoopType, debug: Debug, image_manager: ImageManagerType | None = None ) -> None: from .operations import commander self.screen_size = screen_size self._term_manager = term_manager self._tui_loop = tui_loop self._schedule_write = schedule_write self.debug = debug self.cmd = commander(self) self._image_manager = image_manager self._button_events: dict[MouseButton, Deque[ButtonEvent]] = {} @property def image_manager(self) -> ImageManagerType: assert self._image_manager is not None return self._image_manager @property def asyncio_loop(self) -> AbstractEventLoop: return self._tui_loop.asyncio_loop def add_shortcut(self, action: KeyActionType, spec: str | ParsedShortcut) -> None: if not hasattr(self, '_key_shortcuts'): self._key_shortcuts: dict[ParsedShortcut, KeyActionType] = {} if isinstance(spec, str): from kitty.key_encoding import parse_shortcut spec = parse_shortcut(spec) self._key_shortcuts[spec] = action def shortcut_action(self, key_event: KeyEventType) -> KeyActionType | None: for sc, action in self._key_shortcuts.items(): if key_event.matches(sc): return action return None def __enter__(self) -> None: if self._image_manager is not None: self._image_manager.__enter__() self.debug.fobj = self self.initialize() def __exit__(self, etype: type, value: Exception, tb: TracebackType) -> None: del self.debug.fobj with suppress(Exception): self.finalize() if self._image_manager is not None: self._image_manager.__exit__(etype, value, tb) def initialize(self) -> None: pass def finalize(self) -> None: pass def on_resize(self, screen_size: ScreenSize) -> None: self.screen_size = screen_size def quit_loop(self, return_code: int | None = None) -> None: self._tui_loop.quit(return_code) def on_term(self) -> None: self._tui_loop.quit(1) def on_hup(self) -> None: self.terminal_io_ended = True self._tui_loop.quit(1) def on_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None: ' Override this method and perform_default_key_action() to handle all key events ' if key_event.text: self.on_text(key_event.text, in_bracketed_paste) else: self.on_key(key_event) def perform_default_key_action(self, key_event: KeyEventType) -> bool: ' Override in sub-class if you want to handle these key events yourself ' if key_event.matches('ctrl+c'): self.on_interrupt() return True if key_event.matches('ctrl+d'): self.on_eot() return True return False def on_text(self, text: str, in_bracketed_paste: bool = False) -> None: pass def on_key(self, key_event: KeyEventType) -> None: pass def on_mouse_event(self, mouse_event: MouseEvent) -> None: from .loop import EventType if mouse_event.type is EventType.MOVE: self.on_mouse_move(mouse_event) elif mouse_event.type is EventType.PRESS: q = self._button_events.setdefault(mouse_event.buttons, deque()) q.append(ButtonEvent(mouse_event, monotonic())) if len(q) > 5: q.popleft() elif mouse_event.type is EventType.RELEASE: q = self._button_events.setdefault(mouse_event.buttons, deque()) q.append(ButtonEvent(mouse_event, monotonic())) if len(q) > 5: q.popleft() if len(q) > 1 and is_click(q[-2], q[-1]): self.on_click(mouse_event) def on_mouse_move(self, mouse_event: MouseEvent) -> None: pass def on_click(self, mouse_event: MouseEvent) -> None: pass def on_interrupt(self) -> None: pass def on_eot(self) -> None: pass def on_writing_finished(self) -> None: pass def on_kitty_cmd_response(self, response: dict[str, Any]) -> None: pass def on_clipboard_response(self, text: str, from_primary: bool = False) -> None: pass def on_file_transfer_response(self, ftc: 'FileTransmissionCommand') -> None: pass def on_capability_response(self, name: str, val: str) -> None: pass def write(self, data: bytes | str) -> None: if isinstance(data, str): data = data.encode('utf-8') self._schedule_write(data) def flush(self) -> None: pass def print(self, *args: object, sep: str = ' ', end: str = '\r\n') -> None: data = sep.join(map(str, args)) + end self.write(data) def suspend(self) -> ContextManager[TermManagerType]: return self._term_manager.suspend() @classmethod def atomic_update(cls, func: DecoratedFunc) -> DecoratedFunc: from functools import wraps @wraps(func) def f(*a: Any, **kw: Any) -> Any: with pending_update(a[0].write): return func(*a, **kw) return cast(DecoratedFunc, f) class HandleResult: type_of_input: str | None = None no_ui: bool = False def __init__(self, impl: Callable[..., Any], type_of_input: str | None, no_ui: bool, has_ready_notification: bool, open_url_handler: OpenUrlHandler): self.impl = impl self.no_ui = no_ui self.type_of_input = type_of_input self.has_ready_notification = has_ready_notification self.open_url_handler = open_url_handler def __call__(self, args: Sequence[str], data: Any, target_window_id: int, boss: BossType) -> Any: return self.impl(args, data, target_window_id, boss) def result_handler( type_of_input: str | None = None, no_ui: bool = False, has_ready_notification: bool = Handler.overlay_ready_report_needed, open_url_handler: OpenUrlHandler = None, ) -> Callable[[Callable[..., Any]], HandleResult]: def wrapper(impl: Callable[..., Any]) -> HandleResult: return HandleResult(impl, type_of_input, no_ui, has_ready_notification, open_url_handler) return wrapper kitty-0.41.1/kittens/tui/images.py0000664000175000017510000005353514773370543016423 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import codecs import os import sys from base64 import standard_b64encode from collections import defaultdict, deque from collections.abc import Callable, Iterator, Sequence from contextlib import suppress from enum import IntEnum from itertools import count from typing import Any, ClassVar, DefaultDict, Deque, Generic, Optional, TypeVar, Union, cast from kitty.conf.utils import positive_float, positive_int from kitty.fast_data_types import create_canvas from kitty.typing import GRT_C, CompletedProcess, GRT_a, GRT_d, GRT_f, GRT_m, GRT_o, GRT_t, HandlerType from kitty.utils import ScreenSize, fit_image, which from .operations import cursor try: fsenc = sys.getfilesystemencoding() or 'utf-8' codecs.lookup(fsenc) except Exception: fsenc = 'utf-8' class Dispose(IntEnum): undefined = 0 none = 1 background = 2 previous = 3 class Frame: gap: int # milliseconds canvas_width: int canvas_height: int width: int height: int index: int xdpi: float ydpi: float canvas_x: int canvas_y: int mode: str needs_blend: bool dimensions_swapped: bool dispose: Dispose path: str = '' def __init__(self, identify_data: Union['Frame', dict[str, str]]): if isinstance(identify_data, Frame): for k in Frame.__annotations__: setattr(self, k, getattr(identify_data, k)) else: self.gap = max(0, int(identify_data['gap']) * 10) sz, pos = identify_data['canvas'].split('+', 1) self.canvas_width, self.canvas_height = map(positive_int, sz.split('x', 1)) self.canvas_x, self.canvas_y = map(int, pos.split('+', 1)) self.width, self.height = map(positive_int, identify_data['size'].split('x', 1)) self.xdpi, self.ydpi = map(positive_float, identify_data['dpi'].split('x', 1)) self.index = positive_int(identify_data['index']) q = identify_data['transparency'].lower() self.mode = 'rgba' if q in ('blend', 'true') else 'rgb' self.needs_blend = q == 'blend' self.dispose = getattr(Dispose, identify_data['dispose'].lower()) self.dimensions_swapped = identify_data.get('orientation') in ('5', '6', '7', '8') if self.dimensions_swapped: self.canvas_width, self.canvas_height = self.canvas_height, self.canvas_width self.width, self.height = self.height, self.width def __repr__(self) -> str: canvas = f'{self.canvas_width}x{self.canvas_height}:{self.canvas_x}+{self.canvas_y}' geom = f'{self.width}x{self.height}' return f'Frame(index={self.index}, gap={self.gap}, geom={geom}, canvas={canvas}, dispose={self.dispose.name})' class ImageData: def __init__(self, fmt: str, width: int, height: int, mode: str, frames: list[Frame]): self.width, self.height, self.fmt, self.mode = width, height, fmt, mode self.transmit_fmt: GRT_f = (24 if self.mode == 'rgb' else 32) self.frames = frames def __len__(self) -> int: return len(self.frames) def __iter__(self) -> Iterator[Frame]: yield from self.frames def __repr__(self) -> str: frames = '\n '.join(map(repr, self.frames)) return f'Image(fmt={self.fmt}, mode={self.mode},\n {frames}\n)' class OpenFailed(ValueError): def __init__(self, path: str, message: str): ValueError.__init__( self, f'Failed to open image: {path} with error: {message}' ) self.path = path class ConvertFailed(ValueError): def __init__(self, path: str, message: str): ValueError.__init__( self, f'Failed to convert image: {path} with error: {message}' ) self.path = path class NoImageMagick(Exception): pass class OutdatedImageMagick(ValueError): def __init__(self, detailed_error: str): super().__init__('ImageMagick on this system is too old ImageMagick 7+ required which was first released in 2016') self.detailed_error = detailed_error last_imagemagick_cmd: Sequence[str] = () def run_imagemagick(path: str, cmd: Sequence[str], keep_stdout: bool = True) -> 'CompletedProcess[bytes]': global last_imagemagick_cmd import subprocess last_imagemagick_cmd = cmd try: p = subprocess.run(cmd, stdout=subprocess.PIPE if keep_stdout else subprocess.DEVNULL, stderr=subprocess.PIPE) except FileNotFoundError: raise NoImageMagick('ImageMagick is required to process images') if p.returncode != 0: raise OpenFailed(path, p.stderr.decode('utf-8')) return p def identify(path: str) -> ImageData: import json q = ( '{"fmt":"%m","canvas":"%g","transparency":"%A","gap":"%T","index":"%p","size":"%wx%h",' '"dpi":"%xx%y","dispose":"%D","orientation":"%[EXIF:Orientation]"},' ) exe = which('magick') if exe: cmd = [exe, 'identify'] else: cmd = ['identify'] p = run_imagemagick(path, cmd + ['-format', q, '--', path]) raw = p.stdout.rstrip(b',') data = json.loads(b'[' + raw + b']') first = data[0] frames = list(map(Frame, data)) image_fmt = first['fmt'].lower() if image_fmt == 'gif' and not any(f.gap > 0 for f in frames): # Some broken GIF images have all zero gaps, browsers with their usual # idiot ideas render these with a default 100ms gap https://bugzilla.mozilla.org/show_bug.cgi?id=125137 # Browsers actually force a 100ms gap at any zero gap frame, but that # just means it is impossible to deliberately use zero gap frames for # sophisticated blending, so we dont do that. for f in frames: f.gap = 100 mode = 'rgb' for f in frames: if f.mode == 'rgba': mode = 'rgba' break return ImageData(image_fmt, frames[0].canvas_width, frames[0].canvas_height, mode, frames) class RenderedImage(ImageData): def __init__(self, fmt: str, width: int, height: int, mode: str): super().__init__(fmt, width, height, mode, []) def render_image( path: str, output_prefix: str, m: ImageData, available_width: int, available_height: int, scale_up: bool, only_first_frame: bool = False, remove_alpha: str = '', flip: bool = False, flop: bool = False, ) -> RenderedImage: import tempfile has_multiple_frames = len(m) > 1 get_multiple_frames = has_multiple_frames and not only_first_frame exe = which('magick') if exe: cmd = [exe, 'convert'] else: exe = which('convert') if exe is None: raise OSError('Failed to find the ImageMagick convert executable, make sure it is present in PATH') cmd = [exe] if remove_alpha: cmd += ['-background', remove_alpha, '-alpha', 'remove'] else: cmd += ['-background', 'none'] if flip: cmd.append('-flip') if flop: cmd.append('-flop') cmd += ['--', path] if only_first_frame and has_multiple_frames: cmd[-1] += '[0]' cmd.append('-auto-orient') scaled = False width, height = m.width, m.height if scale_up: if width < available_width: r = available_width / width width, height = available_width, int(height * r) scaled = True if scaled or width > available_width or height > available_height: width, height = fit_image(width, height, available_width, available_height) resize_cmd = ['-resize', f'{width}x{height}!'] if get_multiple_frames: # we have to coalesce, resize and de-coalesce all frames resize_cmd = ['-coalesce'] + resize_cmd + ['-deconstruct'] cmd += resize_cmd cmd += ['-depth', '8', '-set', 'filename:f', '%w-%h-%g-%p'] ans = RenderedImage(m.fmt, width, height, m.mode) if only_first_frame: ans.frames = [Frame(m.frames[0])] else: ans.frames = list(map(Frame, m.frames)) bytes_per_pixel = 3 if m.mode == 'rgb' else 4 def check_resize(frame: Frame) -> None: # ImageMagick sometimes generates RGBA images smaller than the specified # size. See https://github.com/kovidgoyal/kitty/issues/276 for examples sz = os.path.getsize(frame.path) expected_size = bytes_per_pixel * frame.width * frame.height if sz < expected_size: missing = expected_size - sz if missing % (bytes_per_pixel * width) != 0: raise ConvertFailed( path, 'ImageMagick failed to convert {} correctly,' ' it generated {} < {} of data (w={}, h={}, bpp={})'.format( path, sz, expected_size, frame.width, frame.height, bytes_per_pixel)) frame.height -= missing // (bytes_per_pixel * frame.width) if frame.index == 0: ans.height = frame.height ans.width = frame.width with tempfile.TemporaryDirectory(dir=os.path.dirname(output_prefix)) as tdir: output_template = os.path.join(tdir, f'im-%[filename:f].{m.mode}') if get_multiple_frames: cmd.append('+adjoin') run_imagemagick(path, cmd + [output_template]) unseen = {x.index for x in m} for x in os.listdir(tdir): try: parts = x.split('.', 1)[0].split('-') index = int(parts[-1]) unseen.discard(index) f = ans.frames[index] f.width, f.height = map(positive_int, parts[1:3]) sz, pos = parts[3].split('+', 1) f.canvas_width, f.canvas_height = map(positive_int, sz.split('x', 1)) f.canvas_x, f.canvas_y = map(int, pos.split('+', 1)) except Exception: raise OutdatedImageMagick(f'Unexpected output filename: {x!r} produced by ImageMagick command: {last_imagemagick_cmd}') f.path = output_prefix + f'-{index}.{m.mode}' os.rename(os.path.join(tdir, x), f.path) check_resize(f) f = ans.frames[0] if f.width != ans.width or f.height != ans.height: with open(f.path, 'r+b') as ff: data = ff.read() ff.seek(0) ff.truncate() cd = create_canvas(data, f.width, f.canvas_x, f.canvas_y, ans.width, ans.height, 3 if ans.mode == 'rgb' else 4) ff.write(cd) if get_multiple_frames: if unseen: raise ConvertFailed(path, f'Failed to render {len(unseen)} out of {len(m)} frames of animation') elif not ans.frames[0].path: raise ConvertFailed(path, 'Failed to render image') return ans def render_as_single_image( path: str, m: ImageData, available_width: int, available_height: int, scale_up: bool, tdir: str | None = None, remove_alpha: str = '', flip: bool = False, flop: bool = False, ) -> tuple[str, int, int]: import tempfile fd, output = tempfile.mkstemp(prefix='tty-graphics-protocol-', suffix=f'.{m.mode}', dir=tdir) os.close(fd) result = render_image( path, output, m, available_width, available_height, scale_up, only_first_frame=True, remove_alpha=remove_alpha, flip=flip, flop=flop) os.rename(result.frames[0].path, output) return output, result.width, result.height def can_display_images() -> bool: ans: bool | None = getattr(can_display_images, 'ans', None) if ans is None: ans = which('convert') is not None setattr(can_display_images, 'ans', ans) return ans ImageKey = tuple[str, int, int] SentImageKey = tuple[int, int, int] T = TypeVar('T') class Alias(Generic[T]): currently_processing: ClassVar[str] = '' def __init__(self, defval: T) -> None: self.name = '' self.defval = defval def __get__(self, instance: Optional['GraphicsCommand'], cls: type['GraphicsCommand'] | None = None) -> T: if instance is None: return self.defval return cast(T, instance._actual_values.get(self.name, self.defval)) def __set__(self, instance: 'GraphicsCommand', val: T) -> None: if val == self.defval: instance._actual_values.pop(self.name, None) else: instance._actual_values[self.name] = val def __set_name__(self, owner: type['GraphicsCommand'], name: str) -> None: if len(name) == 1: Alias.currently_processing = name self.name = Alias.currently_processing class GraphicsCommand: a = action = Alias(cast(GRT_a, 't')) q = quiet = Alias(0) f = format = Alias(32) t = transmission_type = Alias(cast(GRT_t, 'd')) s = data_width = animation_state = Alias(0) v = data_height = loop_count = Alias(0) S = data_size = Alias(0) O = data_offset = Alias(0) # noqa i = image_id = Alias(0) I = image_number = Alias(0) # noqa p = placement_id = Alias(0) o = compression = Alias(cast(Optional[GRT_o], None)) m = more = Alias(cast(GRT_m, 0)) x = left_edge = Alias(0) y = top_edge = Alias(0) w = width = Alias(0) h = height = Alias(0) X = cell_x_offset = blend_mode = Alias(0) Y = cell_y_offset = bgcolor = Alias(0) c = columns = other_frame_number = dest_frame = Alias(0) r = rows = frame_number = source_frame = Alias(0) z = z_index = gap = Alias(0) C = cursor_movement = compose_mode = Alias(cast(GRT_C, 0)) d = delete_action = Alias(cast(GRT_d, 'a')) def __init__(self) -> None: self._actual_values: dict[str, Any] = {} def __repr__(self) -> str: return self.serialize().decode('ascii').replace('\033', '^]') def clone(self) -> 'GraphicsCommand': ans = GraphicsCommand() ans._actual_values = self._actual_values.copy() return ans def serialize(self, payload: bytes | str = b'') -> bytes: items = [] for k, val in self._actual_values.items(): items.append(f'{k}={val}') ans: list[bytes] = [] w = ans.append w(b'\033_G') w(','.join(items).encode('ascii')) if payload: w(b';') if isinstance(payload, str): payload = standard_b64encode(payload.encode('utf-8')) w(payload) w(b'\033\\') return b''.join(ans) def clear(self) -> None: self._actual_values = {} def iter_transmission_chunks(self, data: bytes | None = None, level: int = -1, compression_threshold: int = 1024) -> Iterator[bytes]: if data is None: yield self.serialize() return gc = self.clone() gc.S = len(data) if level and len(data) >= compression_threshold: import zlib compressed = zlib.compress(data, level) if len(compressed) < len(data): gc.o = 'z' data = compressed gc.S = len(data) data = standard_b64encode(data) while data: chunk, data = data[:4096], data[4096:] gc.m = 1 if data else 0 yield gc.serialize(chunk) gc.clear() class Placement: cmd: GraphicsCommand x: int = 0 y: int = 0 def __init__(self, cmd: GraphicsCommand, x: int = 0, y: int = 0): self.cmd = cmd self.x = x self.y = y class ImageManager: def __init__(self, handler: HandlerType): self.image_id_counter = count() self.handler = handler self.filesystem_ok: bool | None = None self.image_data: dict[str, ImageData] = {} self.failed_images: dict[str, Exception] = {} self.converted_images: dict[ImageKey, ImageKey] = {} self.sent_images: dict[ImageKey, int] = {} self.image_id_to_image_data: dict[int, ImageData] = {} self.image_id_to_converted_data: dict[int, ImageKey] = {} self.transmission_status: dict[int, str | int] = {} self.placements_in_flight: DefaultDict[int, Deque[Placement]] = defaultdict(deque) self.update_image_placement_for_resend: Callable[[int, Placement], bool] | None @property def next_image_id(self) -> int: return next(self.image_id_counter) + 2 @property def screen_size(self) -> ScreenSize: return self.handler.screen_size def __enter__(self) -> None: import tempfile self.tdir = tempfile.mkdtemp(prefix='kitten-images-') with tempfile.NamedTemporaryFile(dir=self.tdir, delete=False) as f: f.write(b'abcd') gc = GraphicsCommand() gc.a = 'q' gc.s = gc.v = gc.i = 1 gc.t = 'f' self.handler.cmd.gr_command(gc, standard_b64encode(f.name.encode(fsenc))) def __exit__(self, *a: Any) -> None: import shutil shutil.rmtree(self.tdir, ignore_errors=True) self.handler.cmd.clear_images_on_screen(delete_data=True) self.delete_all_sent_images() del self.handler def delete_all_sent_images(self) -> None: gc = GraphicsCommand() gc.a = 'd' for img_id in self.transmission_status: gc.i = img_id self.handler.cmd.gr_command(gc) self.transmission_status.clear() def handle_response(self, apc: str) -> None: cdata, payload = apc[1:].partition(';')[::2] control = {} for x in cdata.split(','): k, v = x.partition('=')[::2] control[k] = v try: image_id = int(control.get('i', '0')) except Exception: image_id = 0 if image_id == 1: self.filesystem_ok = payload == 'OK' return if not image_id: return if not self.transmission_status.get(image_id): self.transmission_status[image_id] = payload else: in_flight = self.placements_in_flight[image_id] if in_flight: pl = in_flight.popleft() if payload.startswith('ENOENT:'): with suppress(Exception): self.resend_image(image_id, pl) if not in_flight: self.placements_in_flight.pop(image_id, None) def resend_image(self, image_id: int, pl: Placement) -> None: if self.update_image_placement_for_resend is not None and not self.update_image_placement_for_resend(image_id, pl): return image_data = self.image_id_to_image_data[image_id] skey = self.image_id_to_converted_data[image_id] self.transmit_image(image_data, image_id, *skey) with cursor(self.handler.write): self.handler.cmd.set_cursor_position(pl.x, pl.y) self.handler.cmd.gr_command(pl.cmd) def send_image(self, path: str, max_cols: int | None = None, max_rows: int | None = None, scale_up: bool = False) -> SentImageKey: path = os.path.abspath(path) if path in self.failed_images: raise self.failed_images[path] if path not in self.image_data: try: self.image_data[path] = identify(path) except Exception as e: self.failed_images[path] = e raise m = self.image_data[path] ss = self.screen_size if max_cols is None: max_cols = ss.cols if max_rows is None: max_rows = ss.rows available_width = max_cols * ss.cell_width available_height = max_rows * ss.cell_height key = path, available_width, available_height skey = self.converted_images.get(key) if skey is None: try: self.converted_images[key] = skey = self.convert_image(path, available_width, available_height, m, scale_up) except Exception as e: self.failed_images[path] = e raise final_width, final_height = skey[1:] if final_width == 0: return 0, 0, 0 image_id = self.sent_images.get(skey) if image_id is None: image_id = self.next_image_id self.transmit_image(m, image_id, *skey) self.sent_images[skey] = image_id self.image_id_to_converted_data[image_id] = skey self.image_id_to_image_data[image_id] = m return image_id, skey[1], skey[2] def hide_image(self, image_id: int) -> None: gc = GraphicsCommand() gc.a = 'd' gc.i = image_id self.handler.cmd.gr_command(gc) def show_image(self, image_id: int, x: int, y: int, src_rect: tuple[int, int, int, int] | None = None) -> None: gc = GraphicsCommand() gc.a = 'p' gc.i = image_id if src_rect is not None: gc.x, gc.y, gc.w, gc.h = map(int, src_rect) self.placements_in_flight[image_id].append(Placement(gc, x, y)) with cursor(self.handler.write): self.handler.cmd.set_cursor_position(x, y) self.handler.cmd.gr_command(gc) def convert_image(self, path: str, available_width: int, available_height: int, image_data: ImageData, scale_up: bool = False) -> ImageKey: rgba_path, width, height = render_as_single_image(path, image_data, available_width, available_height, scale_up, tdir=self.tdir) return rgba_path, width, height def transmit_image(self, image_data: ImageData, image_id: int, rgba_path: str, width: int, height: int) -> int: self.transmission_status[image_id] = 0 gc = GraphicsCommand() gc.a = 't' gc.f = image_data.transmit_fmt gc.s = width gc.v = height gc.i = image_id if self.filesystem_ok: gc.t = 'f' self.handler.cmd.gr_command( gc, standard_b64encode(rgba_path.encode(fsenc))) else: import zlib with open(rgba_path, 'rb') as f: data = f.read() gc.S = len(data) data = zlib.compress(data) gc.o = 'z' data = standard_b64encode(data) while data: chunk, data = data[:4096], data[4096:] gc.m = 1 if data else 0 self.handler.cmd.gr_command(gc, chunk) gc.clear() return image_id kitty-0.41.1/kittens/tui/line_edit.py0000664000175000017510000001236614773370543017107 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal from collections.abc import Callable from kitty.fast_data_types import truncate_point_for_length, wcswidth from kitty.key_encoding import EventType, KeyEvent from .operations import RESTORE_CURSOR, SAVE_CURSOR, move_cursor_by, set_cursor_shape class LineEdit: def __init__(self, is_password: bool = False) -> None: self.clear() self.is_password = is_password def clear(self) -> None: self.current_input = '' self.cursor_pos = 0 self.pending_bell = False def split_at_cursor(self, delta: int = 0) -> tuple[str, str]: pos = max(0, self.cursor_pos + delta) x = truncate_point_for_length(self.current_input, pos) if pos else 0 before, after = self.current_input[:x], self.current_input[x:] return before, after def write(self, write: Callable[[str], None], prompt: str = '', screen_cols: int = 0) -> None: if self.pending_bell: write('\a') self.pending_bell = False ci = self.current_input if self.is_password: ci = '*' * wcswidth(ci) text = prompt + ci cursor_pos = self.cursor_pos + wcswidth(prompt) if screen_cols: write(SAVE_CURSOR + text + RESTORE_CURSOR) used_lines, last_line_cursor_pos = divmod(cursor_pos, screen_cols) if used_lines == 0: if last_line_cursor_pos: write(move_cursor_by(last_line_cursor_pos, 'right')) else: if used_lines: write(move_cursor_by(used_lines, 'down')) if last_line_cursor_pos: write(move_cursor_by(last_line_cursor_pos, 'right')) else: write(text) write('\r') if cursor_pos: write(move_cursor_by(cursor_pos, 'right')) write(set_cursor_shape('beam')) def add_text(self, text: str) -> None: if self.current_input: x = truncate_point_for_length(self.current_input, self.cursor_pos) if self.cursor_pos else 0 self.current_input = self.current_input[:x] + text + self.current_input[x:] else: self.current_input = text self.cursor_pos += wcswidth(text) def on_text(self, text: str, in_bracketed_paste: bool) -> None: self.add_text(text) def backspace(self, num: int = 1) -> bool: before, after = self.split_at_cursor() nbefore = before[:-num] if nbefore != before: self.current_input = nbefore + after self.cursor_pos = wcswidth(nbefore) return True self.pending_bell = True return False def delete(self, num: int = 1) -> bool: before, after = self.split_at_cursor() nafter = after[num:] if nafter != after: self.current_input = before + nafter self.cursor_pos = wcswidth(before) return True self.pending_bell = True return False def _left(self) -> None: if not self.current_input: self.cursor_pos = 0 return if self.cursor_pos: before, after = self.split_at_cursor(-1) self.cursor_pos = wcswidth(before) def _right(self) -> None: if not self.current_input: self.cursor_pos = 0 return max_pos = wcswidth(self.current_input) if self.cursor_pos >= max_pos: self.cursor_pos = max_pos return before, after = self.split_at_cursor(1) self.cursor_pos += 1 + int(wcswidth(before) == self.cursor_pos) def _move_loop(self, func: Callable[[], None], num: int) -> bool: before = self.cursor_pos changed = False while num > 0: func() changed = self.cursor_pos != before if not changed: break num -= 1 if not changed: self.pending_bell = True return changed def left(self, num: int = 1) -> bool: return self._move_loop(self._left, num) def right(self, num: int = 1) -> bool: return self._move_loop(self._right, num) def home(self) -> bool: if self.cursor_pos: self.cursor_pos = 0 return True return False def end(self) -> bool: orig = self.cursor_pos self.cursor_pos = wcswidth(self.current_input) return self.cursor_pos != orig def on_key(self, key_event: KeyEvent) -> bool: if key_event.type is EventType.RELEASE: return False if key_event.matches('home') or key_event.matches('ctrl+a'): return self.home() if key_event.matches('end') or key_event.matches('ctrl+e'): return self.end() if key_event.matches('backspace'): self.backspace() return True if key_event.matches('delete') or key_event.matches('ctrl+d'): self.delete() return True if key_event.matches('left') or key_event.matches('ctrl+b'): self.left() return True if key_event.matches('right') or key_event.matches('ctrl+f'): self.right() return True return False kitty-0.41.1/kittens/tui/loop.py0000664000175000017510000004112114773370543016113 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import asyncio import codecs import io import os import re import selectors import signal import sys import termios from collections.abc import Callable, Generator from contextlib import contextmanager, suppress from enum import Enum, IntFlag, auto from functools import partial from typing import Any, NamedTuple from kitty.constants import is_macos from kitty.fast_data_types import FILE_TRANSFER_CODE, close_tty, normal_tty, open_tty, parse_input_from_terminal, raw_tty from kitty.key_encoding import ALT, CTRL, SHIFT, backspace_key, decode_key_event, enter_key from kitty.typing import ImageManagerType, KeyEventType, Protocol from kitty.utils import ScreenSize, ScreenSizeGetter, screen_size_function, write_all from .handler import Handler from .operations import MouseTracking, init_state, reset_state class BinaryWrite(Protocol): def write(self, data: bytes) -> None: pass def flush(self) -> None: pass def debug_write(*a: Any, **kw: Any) -> None: from base64 import standard_b64encode fobj = kw.pop('file', sys.stderr.buffer) buf = io.StringIO() kw['file'] = buf print(*a, **kw) stext = buf.getvalue() for i in range(0, len(stext), 256): chunk = stext[i:i + 256] text = b'\x1bP@kitty-print|' + standard_b64encode(chunk.encode('utf-8')) + b'\x1b\\' fobj.write(text) fobj.flush() class Debug: fobj: BinaryWrite | None = None def __call__(self, *a: Any, **kw: Any) -> None: kw['file'] = self.fobj or sys.stdout.buffer debug_write(*a, **kw) debug = Debug() ftc_code = str(FILE_TRANSFER_CODE) class TermManager: def __init__( self, optional_actions: int = termios.TCSANOW, use_alternate_screen: bool = True, mouse_tracking: MouseTracking = MouseTracking.none ) -> None: self.extra_finalize: str | None = None self.optional_actions = optional_actions self.use_alternate_screen = use_alternate_screen self.mouse_tracking = mouse_tracking def set_state_for_loop(self, set_raw: bool = True) -> None: if set_raw: raw_tty(self.tty_fd, self.original_termios) write_all(self.tty_fd, init_state(self.use_alternate_screen, self.mouse_tracking)) def reset_state_to_original(self) -> None: normal_tty(self.tty_fd, self.original_termios) if self.extra_finalize: write_all(self.tty_fd, self.extra_finalize) write_all(self.tty_fd, reset_state(self.use_alternate_screen)) @contextmanager def suspend(self) -> Generator['TermManager', None, None]: self.reset_state_to_original() yield self self.set_state_for_loop() def __enter__(self) -> 'TermManager': self.tty_fd, self.original_termios = open_tty(False, self.optional_actions) self.set_state_for_loop(set_raw=False) return self def __exit__(self, *a: object) -> None: with suppress(Exception): self.reset_state_to_original() close_tty(self.tty_fd, self.original_termios) del self.tty_fd, self.original_termios class MouseButton(IntFlag): NONE, LEFT, MIDDLE, RIGHT, FOURTH, FIFTH, SIXTH, SEVENTH = 0, 1, 2, 4, 8, 16, 32, 64 WHEEL_UP, WHEEL_DOWN, WHEEL_LEFT, WHEEL_RIGHT = -1, -2, -4, -8 bmap = MouseButton.LEFT, MouseButton.MIDDLE, MouseButton.RIGHT ebmap = MouseButton.FOURTH, MouseButton.FIFTH, MouseButton.SIXTH, MouseButton.SEVENTH wbmap = MouseButton.WHEEL_UP, MouseButton.WHEEL_DOWN, MouseButton.WHEEL_LEFT, MouseButton.WHEEL_RIGHT SHIFT_INDICATOR = 1 << 2 ALT_INDICATOR = 1 << 3 CTRL_INDICATOR = 1 << 4 MOTION_INDICATOR = 1 << 5 class EventType(Enum): PRESS = auto() RELEASE = auto() MOVE = auto() class MouseEvent(NamedTuple): cell_x: int cell_y: int pixel_x: int pixel_y: int type: EventType buttons: MouseButton mods: int def pixel_to_cell(px: int, length: int, cell_length: int) -> int: px = max(0, min(px, length - 1)) return px // cell_length def decode_sgr_mouse(text: str, screen_size: ScreenSize) -> MouseEvent: cb_, x_, y_ = text.split(';') m, y_ = y_[-1], y_[:-1] cb, x, y = map(int, (cb_, x_, y_)) typ = EventType.RELEASE if m == 'm' else (EventType.MOVE if cb & MOTION_INDICATOR else EventType.PRESS) buttons: MouseButton = MouseButton.NONE cb3 = cb & 3 if cb >= 128: buttons |= ebmap[cb3] elif cb >= 64: buttons |= wbmap[cb3] elif cb3 < 3: buttons |= bmap[cb3] mods = 0 if cb & SHIFT_INDICATOR: mods |= SHIFT if cb & ALT_INDICATOR: mods |= ALT if cb & CTRL_INDICATOR: mods |= CTRL return MouseEvent( pixel_to_cell(x, screen_size.width, screen_size.cell_width), pixel_to_cell(y, screen_size.height, screen_size.cell_height), x, y, typ, buttons, mods ) class UnhandledException(Handler): def __init__(self, tb: str) -> None: self.tb = tb def initialize(self) -> None: self.cmd.clear_screen() self.cmd.set_scrolling_region() self.cmd.set_cursor_visible(True) self.cmd.set_default_colors() self.write(self.tb.replace('\n', '\r\n')) self.write('\r\n') self.write('Press Enter to quit') def on_key(self, key_event: KeyEventType) -> None: if key_event.key == 'ENTER': self.quit_loop(1) def on_interrupt(self) -> None: self.quit_loop(1) on_eot = on_term = on_interrupt class SignalManager: def __init__( self, loop: asyncio.AbstractEventLoop, on_winch: Callable[[], None], on_interrupt: Callable[[], None], on_term: Callable[[], None], on_hup: Callable[[], None], ) -> None: self.asyncio_loop = loop self.on_winch, self.on_interrupt, self.on_term = on_winch, on_interrupt, on_term self.on_hup = on_hup def __enter__(self) -> None: self.asyncio_loop.add_signal_handler(signal.SIGWINCH, self.on_winch) self.asyncio_loop.add_signal_handler(signal.SIGINT, self.on_interrupt) self.asyncio_loop.add_signal_handler(signal.SIGTERM, self.on_term) self.asyncio_loop.add_signal_handler(signal.SIGHUP, self.on_hup) def __exit__(self, *a: Any) -> None: tuple(map(self.asyncio_loop.remove_signal_handler, ( signal.SIGWINCH, signal.SIGINT, signal.SIGTERM, signal.SIGHUP))) sanitize_bracketed_paste: str = '[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]' class Loop: def __init__( self, sanitize_bracketed_paste: str = sanitize_bracketed_paste, optional_actions: int = termios.TCSADRAIN ): if is_macos: # On macOS PTY devices are not supported by the KqueueSelector and # the PollSelector is broken, causes 100% CPU usage self.asyncio_loop: asyncio.AbstractEventLoop = asyncio.SelectorEventLoop(selectors.SelectSelector()) asyncio.set_event_loop(self.asyncio_loop) else: self.asyncio_loop = asyncio.get_event_loop() self.return_code = 0 self.overlay_ready_reported = False self.optional_actions = optional_actions self.read_buf = '' self.decoder = codecs.getincrementaldecoder('utf-8')('ignore') try: self.iov_limit = max(os.sysconf('SC_IOV_MAX') - 1, 255) except Exception: self.iov_limit = 255 self.parse_input_from_terminal = partial(parse_input_from_terminal, self._on_text, self._on_dcs, self._on_csi, self._on_osc, self._on_pm, self._on_apc) self.ebs_pat = re.compile('([\177\r\x03\x04])') self.in_bracketed_paste = False self.sanitize_bracketed_paste = bool(sanitize_bracketed_paste) if self.sanitize_bracketed_paste: self.sanitize_ibp_pat = re.compile(sanitize_bracketed_paste) def _read_ready(self, handler: Handler, fd: int) -> None: try: bdata = os.read(fd, io.DEFAULT_BUFFER_SIZE) except BlockingIOError: return if not bdata: handler.terminal_io_ended = True self.quit(1) return data = self.decoder.decode(bdata) if self.read_buf: data = self.read_buf + data self.read_buf = data self.handler = handler try: self.read_buf = self.parse_input_from_terminal(self.read_buf, self.in_bracketed_paste) except Exception: self.read_buf = '' raise finally: del self.handler # terminal input callbacks {{{ def _on_text(self, text: str) -> None: if self.in_bracketed_paste and self.sanitize_bracketed_paste: text = self.sanitize_ibp_pat.sub('', text) for chunk in self.ebs_pat.split(text): if len(chunk) == 1: if chunk == '\r': self.handler.on_key(enter_key) elif chunk == '\177': self.handler.on_key(backspace_key) elif chunk == '\x03': self.handler.on_interrupt() elif chunk == '\x04': self.handler.on_eot() else: self.handler.on_text(chunk, self.in_bracketed_paste) elif chunk: self.handler.on_text(chunk, self.in_bracketed_paste) def _on_dcs(self, dcs: str) -> None: if dcs.startswith('@kitty-cmd'): import json self.handler.on_kitty_cmd_response(json.loads(dcs[len('@kitty-cmd'):])) elif dcs.startswith('1+r'): from binascii import unhexlify vals = dcs[3:].split(';') for q in vals: parts = q.split('=', 1) try: name, val = parts[0], unhexlify(parts[1]).decode('utf-8', 'replace') except Exception: continue self.handler.on_capability_response(name, val) def _on_csi(self, csi: str) -> None: q = csi[-1] if q in 'mM': if csi.startswith('<'): # SGR mouse event try: ev = decode_sgr_mouse(csi[1:], self.handler.screen_size) except Exception: pass else: self.handler.on_mouse_event(ev) elif q in 'u~ABCDEHFPQRS': if csi == '200~': self.in_bracketed_paste = True return elif csi == '201~': self.in_bracketed_paste = False return try: k = decode_key_event(csi[:-1], q) except Exception: pass else: if not self.handler.perform_default_key_action(k): self.handler.on_key_event(k) def _on_pm(self, pm: str) -> None: pass def _on_osc(self, osc: str) -> None: idx = osc.find(';') if idx <= 0: return q = osc[:idx] if q == '52': widx = osc.find(';', idx + 1) if widx < idx: from_primary = osc.find('p', idx + 1) > -1 payload = '' else: from base64 import standard_b64decode from_primary = osc.find('p', idx+1, widx) > -1 data = memoryview(osc.encode('ascii')) payload = standard_b64decode(data[widx+1:]).decode('utf-8') self.handler.on_clipboard_response(payload, from_primary) elif q == ftc_code: from kitty.file_transmission import FileTransmissionCommand data = memoryview(osc.encode('ascii')) self.handler.on_file_transfer_response(FileTransmissionCommand.deserialize(data[idx+1:])) def _on_apc(self, apc: str) -> None: if apc.startswith('G'): if self.handler.image_manager is not None: self.handler.image_manager.handle_response(apc) # }}} @property def total_pending_bytes_to_write(self) -> int: return sum(map(len, self.write_buf)) def _write_ready(self, handler: Handler, fd: int) -> None: if len(self.write_buf) > self.iov_limit: self.write_buf[self.iov_limit - 1] = b''.join(self.write_buf[self.iov_limit - 1:]) del self.write_buf[self.iov_limit:] total_size = self.total_pending_bytes_to_write if total_size: try: written = os.writev(fd, self.write_buf) except BlockingIOError: return if not written: handler.terminal_io_ended = True self.quit(1) return else: written = 0 if written >= total_size: self.write_buf: list[bytes] = [] self.asyncio_loop.remove_writer(fd) self.waiting_for_writes = False handler.on_writing_finished() else: consumed = 0 for i, buf in enumerate(self.write_buf): if not written: break if len(buf) <= written: written -= len(buf) consumed += 1 continue self.write_buf[i] = buf[written:] break del self.write_buf[:consumed] def quit(self, return_code: int | None = None) -> None: if return_code is not None: self.return_code = return_code self.asyncio_loop.stop() def loop_impl(self, handler: Handler, term_manager: TermManager, image_manager: ImageManagerType | None = None) -> str | None: self.write_buf = [] tty_fd = term_manager.tty_fd tb = None self.waiting_for_writes = True def schedule_write(data: bytes) -> None: self.write_buf.append(data) if not self.waiting_for_writes: self.asyncio_loop.add_writer(tty_fd, self._write_ready, handler, tty_fd) self.waiting_for_writes = True def handle_exception(loop: asyncio.AbstractEventLoop, context: dict[str, Any]) -> None: nonlocal tb loop.stop() tb = context['message'] exc = context.get('exception') if exc is not None: import traceback tb += '\n' + ''.join(traceback.format_exception(exc.__class__, exc, exc.__traceback__)) self.asyncio_loop.set_exception_handler(handle_exception) handler._initialize(self._get_screen_size(), term_manager, schedule_write, self, debug, image_manager) with handler: if handler.overlay_ready_report_needed: handler.cmd.overlay_ready() self.asyncio_loop.add_reader( tty_fd, self._read_ready, handler, tty_fd) self.asyncio_loop.add_writer( tty_fd, self._write_ready, handler, tty_fd) self.asyncio_loop.run_forever() self.asyncio_loop.remove_reader(tty_fd) if self.waiting_for_writes: self.asyncio_loop.remove_writer(tty_fd) return tb def loop(self, handler: Handler) -> None: tb: str | None = None def _on_sigwinch() -> None: self._get_screen_size.changed = True handler.screen_size = self._get_screen_size() handler.on_resize(handler.screen_size) signal_manager = SignalManager(self.asyncio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term, handler.on_hup) with TermManager(self.optional_actions, handler.use_alternate_screen, handler.mouse_tracking) as term_manager, signal_manager: self._get_screen_size: ScreenSizeGetter = screen_size_function(term_manager.tty_fd) image_manager = None if handler.image_manager_class is not None: image_manager = handler.image_manager_class(handler) try: tb = self.loop_impl(handler, term_manager, image_manager) except Exception: import traceback tb = traceback.format_exc() term_manager.extra_finalize = b''.join(self.write_buf).decode('utf-8') if tb is not None: report_overlay_ready = handler.overlay_ready_report_needed and not self.overlay_ready_reported self.return_code = 1 if not handler.terminal_io_ended: self._report_error_loop(tb, term_manager, report_overlay_ready) def _report_error_loop(self, tb: str, term_manager: TermManager, overlay_ready_report_needed: bool) -> None: handler = UnhandledException(tb) handler.overlay_ready_report_needed = overlay_ready_report_needed self.loop_impl(handler, term_manager) kitty-0.41.1/kittens/tui/operations.py0000664000175000017510000003111314773370543017325 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import os import sys from collections.abc import Callable, Generator from contextlib import contextmanager from enum import Enum, auto from functools import wraps from typing import Any, Optional, TypeVar, Union from kitty.fast_data_types import Color from kitty.rgb import color_as_sharp, to_color from kitty.typing import GraphicsCommandType, HandlerType, ScreenSize, UnderlineLiteral from .operations_stub import CMD GraphicsCommandType, ScreenSize # needed for stub generation SAVE_CURSOR = '\0337' RESTORE_CURSOR = '\0338' SAVE_PRIVATE_MODE_VALUES = '\033[?s' RESTORE_PRIVATE_MODE_VALUES = '\033[?r' SAVE_COLORS = '\033[#P' RESTORE_COLORS = '\033[#Q' F = TypeVar('F') all_cmds: dict[str, Callable[..., Any]] = {} class Mode(Enum): LNM = 20, '' IRM = 4, '' DECKM = 1, '?' DECSCNM = 5, '?' DECOM = 6, '?' DECAWM = 7, '?' DECARM = 8, '?' DECTCEM = 25, '?' MOUSE_BUTTON_TRACKING = 1000, '?' MOUSE_MOTION_TRACKING = 1002, '?' MOUSE_MOVE_TRACKING = 1003, '?' FOCUS_TRACKING = 1004, '?' MOUSE_UTF8_MODE = 1005, '?' MOUSE_SGR_MODE = 1006, '?' MOUSE_URXVT_MODE = 1015, '?' MOUSE_SGR_PIXEL_MODE = 1016, '?' ALTERNATE_SCREEN = 1049, '?' BRACKETED_PASTE = 2004, '?' PENDING_UPDATE = 2026, '?' HANDLE_TERMIOS_SIGNALS = 19997, '?' def cmd(f: F) -> F: all_cmds[f.__name__] = f # type: ignore return f @cmd def set_mode(which: Mode) -> str: num, private = which.value return f'\033[{private}{num}h' @cmd def reset_mode(which: Mode) -> str: num, private = which.value return f'\033[{private}{num}l' @cmd def clear_screen() -> str: return '\033[H\033[2J' @cmd def clear_to_end_of_screen() -> str: return '\033[J' @cmd def clear_to_eol() -> str: return '\033[K' @cmd def reset_terminal() -> str: return '\033]\033\\\033c' @cmd def bell() -> str: return '\a' @cmd def beep() -> str: return '\a' @cmd def set_window_title(value: str) -> str: return '\033]2;' + value.replace('\033', '').replace('\x9c', '') + '\033\\' @cmd def set_line_wrapping(yes_or_no: bool) -> str: return set_mode(Mode.DECAWM) if yes_or_no else reset_mode(Mode.DECAWM) @contextmanager def without_line_wrap(write: Callable[[str], None]) -> Generator[None, None, None]: write(set_line_wrapping(False)) try: yield finally: write(set_line_wrapping(True)) @cmd def repeat(char: str, count: int) -> str: if count > 5: return f'{char}\x1b[{count-1}b' return char * count @cmd def set_cursor_visible(yes_or_no: bool) -> str: return set_mode(Mode.DECTCEM) if yes_or_no else reset_mode(Mode.DECTCEM) @cmd def set_cursor_position(x: int = 0, y: int = 0) -> str: # (0, 0) is top left return f'\033[{y + 1};{x + 1}H' @cmd def move_cursor_by(amt: int, direction: str) -> str: suffix = {'up': 'A', 'down': 'B', 'right': 'C', 'left': 'D'}[direction] return f'\033[{amt}{suffix}' @cmd def set_cursor_shape(shape: str = 'block', blink: bool = True) -> str: val = {'block': 1, 'underline': 3, 'beam': 5}.get(shape, 1) if not blink: val += 1 return f'\033[{val} q' @cmd def set_scrolling_region(screen_size: Optional['ScreenSize'] = None, top: int | None = None, bottom: int | None = None) -> str: if screen_size is None: return '\033[r' if top is None: top = 0 if bottom is None: bottom = screen_size.rows - 1 if bottom < 0: bottom = screen_size.rows - 1 + bottom else: bottom += 1 return f'\033[{top + 1};{bottom + 1}r' @cmd def scroll_screen(amt: int = 1) -> str: return f'\033[{abs(amt)}{"T" if amt < 0 else "S"}' STANDARD_COLORS = {'black': 0, 'red': 1, 'green': 2, 'yellow': 3, 'blue': 4, 'magenta': 5, 'cyan': 6, 'gray': 7, 'white': 7} UNDERLINE_STYLES = {'straight': 1, 'double': 2, 'curly': 3, 'dotted': 4, 'dashed': 5} ColorSpec = Union[int, str, Color] def color_code(color: ColorSpec, intense: bool = False, base: int = 30) -> str: if isinstance(color, str): e = str((base + 60 if intense else base) + STANDARD_COLORS[color]) elif isinstance(color, int): e = f'{base + 8}:5:{max(0, min(color, 255))}' else: e = f'{base + 8}{color.as_sgr}' return e @cmd def sgr(*parts: str) -> str: return '\033[{}m'.format(';'.join(parts)) @cmd def colored( text: str, color: ColorSpec, intense: bool = False, reset_to: ColorSpec | None = None, reset_to_intense: bool = False ) -> str: e = color_code(color, intense) return f'\033[{e}m{text}\033[{39 if reset_to is None else color_code(reset_to, reset_to_intense)}m' @cmd def faint(text: str) -> str: return colored(text, 'black', True) @cmd def styled( text: str, fg: ColorSpec | None = None, bg: ColorSpec | None = None, fg_intense: bool = False, bg_intense: bool = False, italic: bool | None = None, bold: bool | None = None, underline: UnderlineLiteral | None = None, underline_color: ColorSpec | None = None, reverse: bool | None = None, dim: bool | None = None, ) -> str: start, end = [], [] if fg is not None: start.append(color_code(fg, fg_intense)) end.append('39') if bg is not None: start.append(color_code(bg, bg_intense, 40)) end.append('49') if underline_color is not None: if isinstance(underline_color, str): underline_color = STANDARD_COLORS[underline_color] start.append(color_code(underline_color, base=50)) end.append('59') if underline is not None: start.append(f'4:{UNDERLINE_STYLES[underline]}') end.append('4:0') if italic is not None: s, e = (start, end) if italic else (end, start) s.append('3') e.append('23') if bold is not None: s, e = (start, end) if bold else (end, start) s.append('1') e.append('22') if dim is not None: s, e = (start, end) if dim else (end, start) s.append('2') e.append('22') if reverse is not None: s, e = (start, end) if reverse else (end, start) s.append('7') e.append('27') if not start: return text return '\033[{}m{}\033[{}m'.format(';'.join(start), text, ';'.join(end)) def serialize_gr_command(cmd: dict[str, int | str], payload: bytes | None = None) -> bytes: from .images import GraphicsCommand gc = GraphicsCommand() for k, v in cmd.items(): setattr(gc, k, v) return gc.serialize(payload or b'') @cmd def gr_command(cmd: Union[dict[str, int | str], 'GraphicsCommandType'], payload: bytes | None = None) -> str: if isinstance(cmd, dict): raw = serialize_gr_command(cmd, payload) else: raw = cmd.serialize(payload or b'') return raw.decode('ascii') @cmd def clear_images_on_screen(delete_data: bool = False) -> str: from .images import GraphicsCommand gc = GraphicsCommand() gc.a = 'd' gc.d = 'A' if delete_data else 'a' return gc.serialize().decode('ascii') class MouseTracking(Enum): none = auto() buttons_only = auto() buttons_and_drag = auto() full = auto() def init_state(alternate_screen: bool = True, mouse_tracking: MouseTracking = MouseTracking.none, kitty_keyboard_mode: bool = True) -> str: sc = SAVE_CURSOR if alternate_screen else '' ans = ( sc + SAVE_PRIVATE_MODE_VALUES + reset_mode(Mode.LNM) + reset_mode(Mode.IRM) + reset_mode(Mode.DECKM) + reset_mode(Mode.DECSCNM) + set_mode(Mode.DECARM) + set_mode(Mode.DECAWM) + set_mode(Mode.DECTCEM) + reset_mode(Mode.MOUSE_BUTTON_TRACKING) + reset_mode(Mode.MOUSE_MOTION_TRACKING) + reset_mode(Mode.MOUSE_MOVE_TRACKING) + reset_mode(Mode.FOCUS_TRACKING) + reset_mode(Mode.MOUSE_UTF8_MODE) + reset_mode(Mode.MOUSE_SGR_MODE) + set_mode(Mode.BRACKETED_PASTE) + SAVE_COLORS + '\033[*x' # reset DECSACE to default region select ) if alternate_screen: ans += set_mode(Mode.ALTERNATE_SCREEN) + reset_mode(Mode.DECOM) ans += clear_screen() if mouse_tracking is not MouseTracking.none: ans += set_mode(Mode.MOUSE_SGR_PIXEL_MODE) if mouse_tracking is MouseTracking.buttons_only: ans += set_mode(Mode.MOUSE_BUTTON_TRACKING) elif mouse_tracking is MouseTracking.buttons_and_drag: ans += set_mode(Mode.MOUSE_MOTION_TRACKING) elif mouse_tracking is MouseTracking.full: ans += set_mode(Mode.MOUSE_MOVE_TRACKING) if kitty_keyboard_mode: ans += '\033[>31u' # extended keyboard mode else: ans += '\033[>u' # legacy keyboard mode return ans def reset_state(normal_screen: bool = True) -> str: ans = '\033[ Generator[None, None, None]: write(set_mode(Mode.PENDING_UPDATE)) try: yield finally: write(reset_mode(Mode.PENDING_UPDATE)) @contextmanager def cursor(write: Callable[[str], None]) -> Generator[None, None, None]: write(SAVE_CURSOR) try: yield finally: write(RESTORE_CURSOR) @contextmanager def alternate_screen() -> Generator[None, None, None]: with open(os.ctermid(), 'w') as f: print(set_mode(Mode.ALTERNATE_SCREEN), end='', file=f, flush=True) try: yield finally: print(reset_mode(Mode.ALTERNATE_SCREEN), end='', file=f, flush=True) @contextmanager def raw_mode(fd: int | None = None) -> Generator[None, None, None]: import termios import tty if fd is None: fd = sys.stdin.fileno() old = termios.tcgetattr(fd) try: tty.setraw(fd) yield finally: termios.tcsetattr(fd, termios.TCSADRAIN, old) @cmd def set_default_colors( fg: Color | str | None = None, bg: Color | str | None = None, cursor: Color | str | None = None, select_bg: Color | str | None = None, select_fg: Color | str | None = None ) -> str: ans = '' def item(which: Color | str | None, num: int) -> None: nonlocal ans if which is None: ans += f'\x1b]1{num}\x1b\\' else: if isinstance(which, Color): q = color_as_sharp(which) else: x = to_color(which) assert x is not None q = color_as_sharp(x) ans += f'\x1b]{num};{q}\x1b\\' item(fg, 10) item(bg, 11) item(cursor, 12) item(select_bg, 17) item(select_fg, 19) return ans @cmd def save_colors() -> str: return '\x1b[#P' @cmd def restore_colors() -> str: return '\x1b[#Q' @cmd def overlay_ready() -> str: return '\x1bP@kitty-overlay-ready|\x1b\\' @cmd def write_to_clipboard(data: str | bytes, use_primary: bool = False) -> str: from base64 import standard_b64encode fmt = 'p' if use_primary else 'c' if isinstance(data, str): data = data.encode('utf-8') payload = standard_b64encode(data).decode('ascii') return f'\x1b]52;{fmt};{payload}\a' @cmd def request_from_clipboard(use_primary: bool = False) -> str: return '\x1b]52;{};?\a'.format('p' if use_primary else 'c') # Boilerplate to make operations available via Handler.cmd {{{ def writer(handler: HandlerType, func: Callable[..., bytes | str]) -> Callable[..., None]: @wraps(func) def f(*a: Any, **kw: Any) -> None: handler.write(func(*a, **kw)) return f def commander(handler: HandlerType) -> CMD: ans = CMD() for name, func in all_cmds.items(): setattr(ans, name, writer(handler, func)) return ans def func_sig(func: Callable[..., Any]) -> Generator[str, None, None]: import inspect import re s = inspect.signature(func) for val in s.parameters.values(): yield re.sub(r'ForwardRef\([\'"](\w+?)[\'"]\)', r'\1', str(val).replace('NoneType', 'None')) def as_type_stub() -> str: ans = [ 'from typing import * # noqa', 'from kitty.typing import GraphicsCommandType, ScreenSize', 'from kitty.fast_data_types import Color', 'import kitty.rgb', 'import kittens.tui.operations', ] methods = [] for name, func in all_cmds.items(): args = ', '.join(func_sig(func)) if args: args = f', {args}' methods.append(f' def {name}(self{args}) -> str: pass') ans += ['', '', 'class CMD:'] + methods return '\n'.join(ans) + '\n\n\n' # }}} kitty-0.41.1/kittens/tui/operations_stub.py0000664000175000017510000000046414773370543020367 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal class CMD: pass def generate_stub() -> None: from kittens.tui.operations import as_type_stub from kitty.conf.utils import save_type_stub text = as_type_stub() save_type_stub(text, __file__) kitty-0.41.1/kittens/tui/path_completer.py0000664000175000017510000001142014773370543020147 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import os from collections.abc import Callable, Generator, Sequence from typing import Any from kitty.fast_data_types import wcswidth from kitty.utils import ScreenSize, screen_size_function from .operations import styled def directory_completions(path: str, qpath: str, prefix: str = '') -> Generator[str, None, None]: try: entries = os.scandir(qpath) except OSError: return for x in entries: try: is_dir = x.is_dir() except OSError: is_dir = False name = x.name + (os.sep if is_dir else '') if not prefix or name.startswith(prefix): if path: yield os.path.join(path, name) else: yield name def expand_path(path: str) -> str: return os.path.abspath(os.path.expandvars(os.path.expanduser(path))) def find_completions(path: str) -> Generator[str, None, None]: if path and path[0] == '~': if path == '~': yield '~' + os.sep return if os.sep not in path: qpath = os.path.expanduser(path) if qpath != path: yield path + os.sep return qpath = expand_path(path) if not path or path.endswith(os.sep): yield from directory_completions(path, qpath) else: yield from directory_completions(os.path.dirname(path), os.path.dirname(qpath), os.path.basename(qpath)) def print_table(items: Sequence[str], screen_size: ScreenSize, dir_colors: Callable[[str, str], str]) -> None: max_width = 0 item_widths = {} for item in items: item_widths[item] = w = wcswidth(item) max_width = max(w, max_width) col_width = max_width + 2 num_of_cols = max(1, screen_size.cols // col_width) cr = 0 at_start = False for item in items: w = item_widths[item] left = col_width - w print(dir_colors(expand_path(item), item), ' ' * left, sep='', end='') at_start = False cr = (cr + 1) % num_of_cols if not cr: print() at_start = True if not at_start: print() class PathCompleter: def __init__(self, prompt: str = '> '): self.prompt = prompt self.prompt_len = wcswidth(self.prompt) def __enter__(self) -> 'PathCompleter': import readline from .dircolors import Dircolors if 'libedit' in readline.__doc__: readline.parse_and_bind("bind -e") readline.parse_and_bind("bind '\t' rl_complete") else: readline.parse_and_bind('tab: complete') readline.parse_and_bind('set colored-stats on') readline.set_completer_delims(' \t\n`!@#$%^&*()-=+[{]}\\|;:\'",<>?') readline.set_completion_display_matches_hook(self.format_completions) self.original_completer = readline.get_completer() readline.set_completer(self) self.cache: dict[str, tuple[str, ...]] = {} self.dircolors = Dircolors() return self def format_completions(self, substitution: str, matches: Sequence[str], longest_match_length: int) -> None: import readline print() files, dirs = [], [] for m in matches: if m.endswith('/'): if len(m) > 1: m = m[:-1] dirs.append(m) else: files.append(m) ss = screen_size_function()() if dirs: print(styled('Directories', bold=True, fg_intense=True)) print_table(dirs, ss, self.dircolors) if files: print(styled('Files', bold=True, fg_intense=True)) print_table(files, ss, self.dircolors) buf = readline.get_line_buffer() x = readline.get_endidx() buflen = wcswidth(buf) print(self.prompt, buf, sep='', end='') if x < buflen: pos = x + self.prompt_len print(f"\r\033[{pos}C", end='') print(sep='', end='', flush=True) def __call__(self, text: str, state: int) -> str | None: options = self.cache.get(text) if options is None: options = self.cache[text] = tuple(find_completions(text)) if options and state < len(options): return options[state] return None def __exit__(self, *a: Any) -> bool: import readline del self.cache readline.set_completer(self.original_completer) readline.set_completion_display_matches_hook() return True def input(self) -> str: with self: return input(self.prompt) return '' def get_path(prompt: str = '> ') -> str: return PathCompleter(prompt).input() def develop() -> None: PathCompleter().input() kitty-0.41.1/kittens/tui/progress.py0000664000175000017510000000203414773370543017006 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal from .operations import repeat, styled def render_progress_bar(frac: float, width: int = 80) -> str: if frac >= 1: return styled('🬋' * width, fg='green') if frac <= 0: return styled('🬋' * width, dim=True) w = frac * width fl = int(w) overhang = w - fl filled = repeat('🬋', fl) if overhang < 0.2: needs_break = True elif overhang < 0.8: filled += '🬃' fl += 1 needs_break = False else: if fl < width - 1: filled += '🬋' fl += 1 needs_break = True else: filled += '🬃' fl += 1 needs_break = False ans = styled(filled, fg='blue') unfilled = '🬇' if width > fl and needs_break else '' filler = width - fl - len(unfilled) if filler > 0: unfilled += repeat('🬋', filler) if unfilled: ans += styled(unfilled, dim=True) return ans kitty-0.41.1/kittens/tui/spinners.py0000664000175000017510000005445314773370543017017 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal from collections.abc import Sequence from kitty.fast_data_types import monotonic from kitty.typing import TypedDict class SpinnerDef(TypedDict): interval: int frames: Sequence[str] # Spinner definitions are from # https://raw.githubusercontent.com/sindresorhus/cli-spinners/main/spinners.json spinners: dict[str, SpinnerDef] = { # {{{ "dots": { "interval": 80, "frames": ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] }, "dots2": { "interval": 80, "frames": ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"] }, "dots3": { "interval": 80, "frames": ["⠋", "⠙", "⠚", "⠞", "⠖", "⠦", "⠴", "⠲", "⠳", "⠓"] }, "dots4": { "interval": 80, "frames": ["⠄", "⠆", "⠇", "⠋", "⠙", "⠸", "⠰", "⠠", "⠰", "⠸", "⠙", "⠋", "⠇", "⠆"] }, "dots5": { "interval": 80, "frames": [ "⠋", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋" ] }, "dots6": { "interval": 80, "frames": [ "⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠴", "⠲", "⠒", "⠂", "⠂", "⠒", "⠚", "⠙", "⠉", "⠁" ] }, "dots7": { "interval": 80, "frames": [ "⠈", "⠉", "⠋", "⠓", "⠒", "⠐", "⠐", "⠒", "⠖", "⠦", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈" ] }, "dots8": { "interval": 80, "frames": [ "⠁", "⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈", "⠈" ] }, "dots9": { "interval": 80, "frames": ["⢹", "⢺", "⢼", "⣸", "⣇", "⡧", "⡗", "⡏"] }, "dots10": { "interval": 80, "frames": ["⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"] }, "dots11": { "interval": 100, "frames": ["⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"] }, "dots12": { "interval": 80, "frames": [ "⢀⠀", "⡀⠀", "⠄⠀", "⢂⠀", "⡂⠀", "⠅⠀", "⢃⠀", "⡃⠀", "⠍⠀", "⢋⠀", "⡋⠀", "⠍⠁", "⢋⠁", "⡋⠁", "⠍⠉", "⠋⠉", "⠋⠉", "⠉⠙", "⠉⠙", "⠉⠩", "⠈⢙", "⠈⡙", "⢈⠩", "⡀⢙", "⠄⡙", "⢂⠩", "⡂⢘", "⠅⡘", "⢃⠨", "⡃⢐", "⠍⡐", "⢋⠠", "⡋⢀", "⠍⡁", "⢋⠁", "⡋⠁", "⠍⠉", "⠋⠉", "⠋⠉", "⠉⠙", "⠉⠙", "⠉⠩", "⠈⢙", "⠈⡙", "⠈⠩", "⠀⢙", "⠀⡙", "⠀⠩", "⠀⢘", "⠀⡘", "⠀⠨", "⠀⢐", "⠀⡐", "⠀⠠", "⠀⢀", "⠀⡀" ] }, "dots8Bit": { "interval": 80, "frames": [ "⠀", "⠁", "⠂", "⠃", "⠄", "⠅", "⠆", "⠇", "⡀", "⡁", "⡂", "⡃", "⡄", "⡅", "⡆", "⡇", "⠈", "⠉", "⠊", "⠋", "⠌", "⠍", "⠎", "⠏", "⡈", "⡉", "⡊", "⡋", "⡌", "⡍", "⡎", "⡏", "⠐", "⠑", "⠒", "⠓", "⠔", "⠕", "⠖", "⠗", "⡐", "⡑", "⡒", "⡓", "⡔", "⡕", "⡖", "⡗", "⠘", "⠙", "⠚", "⠛", "⠜", "⠝", "⠞", "⠟", "⡘", "⡙", "⡚", "⡛", "⡜", "⡝", "⡞", "⡟", "⠠", "⠡", "⠢", "⠣", "⠤", "⠥", "⠦", "⠧", "⡠", "⡡", "⡢", "⡣", "⡤", "⡥", "⡦", "⡧", "⠨", "⠩", "⠪", "⠫", "⠬", "⠭", "⠮", "⠯", "⡨", "⡩", "⡪", "⡫", "⡬", "⡭", "⡮", "⡯", "⠰", "⠱", "⠲", "⠳", "⠴", "⠵", "⠶", "⠷", "⡰", "⡱", "⡲", "⡳", "⡴", "⡵", "⡶", "⡷", "⠸", "⠹", "⠺", "⠻", "⠼", "⠽", "⠾", "⠿", "⡸", "⡹", "⡺", "⡻", "⡼", "⡽", "⡾", "⡿", "⢀", "⢁", "⢂", "⢃", "⢄", "⢅", "⢆", "⢇", "⣀", "⣁", "⣂", "⣃", "⣄", "⣅", "⣆", "⣇", "⢈", "⢉", "⢊", "⢋", "⢌", "⢍", "⢎", "⢏", "⣈", "⣉", "⣊", "⣋", "⣌", "⣍", "⣎", "⣏", "⢐", "⢑", "⢒", "⢓", "⢔", "⢕", "⢖", "⢗", "⣐", "⣑", "⣒", "⣓", "⣔", "⣕", "⣖", "⣗", "⢘", "⢙", "⢚", "⢛", "⢜", "⢝", "⢞", "⢟", "⣘", "⣙", "⣚", "⣛", "⣜", "⣝", "⣞", "⣟", "⢠", "⢡", "⢢", "⢣", "⢤", "⢥", "⢦", "⢧", "⣠", "⣡", "⣢", "⣣", "⣤", "⣥", "⣦", "⣧", "⢨", "⢩", "⢪", "⢫", "⢬", "⢭", "⢮", "⢯", "⣨", "⣩", "⣪", "⣫", "⣬", "⣭", "⣮", "⣯", "⢰", "⢱", "⢲", "⢳", "⢴", "⢵", "⢶", "⢷", "⣰", "⣱", "⣲", "⣳", "⣴", "⣵", "⣶", "⣷", "⢸", "⢹", "⢺", "⢻", "⢼", "⢽", "⢾", "⢿", "⣸", "⣹", "⣺", "⣻", "⣼", "⣽", "⣾", "⣿" ] }, "line": { "interval": 130, "frames": ["-", "\\", "|", "/"] }, "line2": { "interval": 100, "frames": ["⠂", "-", "–", "—", "–", "-"] }, "pipe": { "interval": 100, "frames": ["┤", "┘", "┴", "└", "├", "┌", "┬", "┐"] }, "simpleDots": { "interval": 400, "frames": [". ", ".. ", "...", " "] }, "simpleDotsScrolling": { "interval": 200, "frames": [". ", ".. ", "...", " ..", " .", " "] }, "star": { "interval": 70, "frames": ["✶", "✸", "✹", "✺", "✹", "✷"] }, "star2": { "interval": 80, "frames": ["+", "x", "*"] }, "flip": { "interval": 70, "frames": ["_", "_", "_", "-", "`", "`", "'", "´", "-", "_", "_", "_"] }, "hamburger": { "interval": 100, "frames": ["☱", "☲", "☴"] }, "growVertical": { "interval": 120, "frames": ["▁", "▃", "▄", "▅", "▆", "▇", "▆", "▅", "▄", "▃"] }, "growHorizontal": { "interval": 120, "frames": ["▏", "▎", "▍", "▌", "▋", "▊", "▉", "▊", "▋", "▌", "▍", "▎"] }, "balloon": { "interval": 140, "frames": [" ", ".", "o", "O", "@", "*", " "] }, "balloon2": { "interval": 120, "frames": [".", "o", "O", "°", "O", "o", "."] }, "noise": { "interval": 100, "frames": ["▓", "▒", "░"] }, "bounce": { "interval": 120, "frames": ["⠁", "⠂", "⠄", "⠂"] }, "boxBounce": { "interval": 120, "frames": ["▖", "▘", "▝", "▗"] }, "boxBounce2": { "interval": 100, "frames": ["▌", "▀", "▐", "▄"] }, "triangle": { "interval": 50, "frames": ["◢", "◣", "◤", "◥"] }, "arc": { "interval": 100, "frames": ["◜", "◠", "◝", "◞", "◡", "◟"] }, "circle": { "interval": 120, "frames": ["◡", "⊙", "◠"] }, "squareCorners": { "interval": 180, "frames": ["◰", "◳", "◲", "◱"] }, "circleQuarters": { "interval": 120, "frames": ["◴", "◷", "◶", "◵"] }, "circleHalves": { "interval": 50, "frames": ["◐", "◓", "◑", "◒"] }, "squish": { "interval": 100, "frames": ["╫", "╪"] }, "toggle": { "interval": 250, "frames": ["⊶", "⊷"] }, "toggle2": { "interval": 80, "frames": ["▫", "▪"] }, "toggle3": { "interval": 120, "frames": ["□", "■"] }, "toggle4": { "interval": 100, "frames": ["■", "□", "▪", "▫"] }, "toggle5": { "interval": 100, "frames": ["▮", "▯"] }, "toggle6": { "interval": 300, "frames": ["ဝ", "၀"] }, "toggle7": { "interval": 80, "frames": ["⦾", "⦿"] }, "toggle8": { "interval": 100, "frames": ["◍", "◌"] }, "toggle9": { "interval": 100, "frames": ["◉", "◎"] }, "toggle10": { "interval": 100, "frames": ["㊂", "㊀", "㊁"] }, "toggle11": { "interval": 50, "frames": ["⧇", "⧆"] }, "toggle12": { "interval": 120, "frames": ["☗", "☖"] }, "toggle13": { "interval": 80, "frames": ["=", "*", "-"] }, "arrow": { "interval": 100, "frames": ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"] }, "arrow2": { "interval": 80, "frames": ["⬆️ ", "↗️ ", "➡️ ", "↘️ ", "⬇️ ", "↙️ ", "⬅️ ", "↖️ "] }, "arrow3": { "interval": 120, "frames": ["▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸"] }, "bouncingBar": { "interval": 80, "frames": [ "[ ]", "[= ]", "[== ]", "[=== ]", "[ ===]", "[ ==]", "[ =]", "[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]" ] }, "bouncingBall": { "interval": 80, "frames": [ "( ● )", "( ● )", "( ● )", "( ● )", "( ●)", "( ● )", "( ● )", "( ● )", "( ● )", "(● )" ] }, "smiley": { "interval": 200, "frames": ["😄 ", "😝 "] }, "monkey": { "interval": 300, "frames": ["🙈 ", "🙈 ", "🙉 ", "🙊 "] }, "hearts": { "interval": 100, "frames": ["💛 ", "💙 ", "💜 ", "💚 ", "❤️ "] }, "clock": { "interval": 100, "frames": [ "🕛 ", "🕐 ", "🕑 ", "🕒 ", "🕓 ", "🕔 ", "🕕 ", "🕖 ", "🕗 ", "🕘 ", "🕙 ", "🕚 " ] }, "earth": { "interval": 180, "frames": ["🌍 ", "🌎 ", "🌏 "] }, "material": { "interval": 17, "frames": [ "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "███████▁▁▁▁▁▁▁▁▁▁▁▁▁", "████████▁▁▁▁▁▁▁▁▁▁▁▁", "█████████▁▁▁▁▁▁▁▁▁▁▁", "█████████▁▁▁▁▁▁▁▁▁▁▁", "██████████▁▁▁▁▁▁▁▁▁▁", "███████████▁▁▁▁▁▁▁▁▁", "█████████████▁▁▁▁▁▁▁", "██████████████▁▁▁▁▁▁", "██████████████▁▁▁▁▁▁", "▁██████████████▁▁▁▁▁", "▁██████████████▁▁▁▁▁", "▁██████████████▁▁▁▁▁", "▁▁██████████████▁▁▁▁", "▁▁▁██████████████▁▁▁", "▁▁▁▁█████████████▁▁▁", "▁▁▁▁██████████████▁▁", "▁▁▁▁██████████████▁▁", "▁▁▁▁▁██████████████▁", "▁▁▁▁▁██████████████▁", "▁▁▁▁▁██████████████▁", "▁▁▁▁▁▁██████████████", "▁▁▁▁▁▁██████████████", "▁▁▁▁▁▁▁█████████████", "▁▁▁▁▁▁▁█████████████", "▁▁▁▁▁▁▁▁████████████", "▁▁▁▁▁▁▁▁████████████", "▁▁▁▁▁▁▁▁▁███████████", "▁▁▁▁▁▁▁▁▁███████████", "▁▁▁▁▁▁▁▁▁▁██████████", "▁▁▁▁▁▁▁▁▁▁██████████", "▁▁▁▁▁▁▁▁▁▁▁▁████████", "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", "██████▁▁▁▁▁▁▁▁▁▁▁▁▁█", "████████▁▁▁▁▁▁▁▁▁▁▁▁", "█████████▁▁▁▁▁▁▁▁▁▁▁", "█████████▁▁▁▁▁▁▁▁▁▁▁", "█████████▁▁▁▁▁▁▁▁▁▁▁", "█████████▁▁▁▁▁▁▁▁▁▁▁", "███████████▁▁▁▁▁▁▁▁▁", "████████████▁▁▁▁▁▁▁▁", "████████████▁▁▁▁▁▁▁▁", "██████████████▁▁▁▁▁▁", "██████████████▁▁▁▁▁▁", "▁██████████████▁▁▁▁▁", "▁██████████████▁▁▁▁▁", "▁▁▁█████████████▁▁▁▁", "▁▁▁▁▁████████████▁▁▁", "▁▁▁▁▁████████████▁▁▁", "▁▁▁▁▁▁███████████▁▁▁", "▁▁▁▁▁▁▁▁█████████▁▁▁", "▁▁▁▁▁▁▁▁█████████▁▁▁", "▁▁▁▁▁▁▁▁▁█████████▁▁", "▁▁▁▁▁▁▁▁▁█████████▁▁", "▁▁▁▁▁▁▁▁▁▁█████████▁", "▁▁▁▁▁▁▁▁▁▁▁████████▁", "▁▁▁▁▁▁▁▁▁▁▁████████▁", "▁▁▁▁▁▁▁▁▁▁▁▁███████▁", "▁▁▁▁▁▁▁▁▁▁▁▁███████▁", "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁" ] }, "moon": { "interval": 80, "frames": ["🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "] }, "runner": { "interval": 140, "frames": ["🚶 ", "🏃 "] }, "pong": { "interval": 80, "frames": [ "▐⠂ ▌", "▐⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂▌", "▐ ⠠▌", "▐ ⡀▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐⠠ ▌" ] }, "shark": { "interval": 120, "frames": [ "▐|\\____________▌", "▐_|\\___________▌", "▐__|\\__________▌", "▐___|\\_________▌", "▐____|\\________▌", "▐_____|\\_______▌", "▐______|\\______▌", "▐_______|\\_____▌", "▐________|\\____▌", "▐_________|\\___▌", "▐__________|\\__▌", "▐___________|\\_▌", "▐____________|\\▌", "▐____________/|▌", "▐___________/|_▌", "▐__________/|__▌", "▐_________/|___▌", "▐________/|____▌", "▐_______/|_____▌", "▐______/|______▌", "▐_____/|_______▌", "▐____/|________▌", "▐___/|_________▌", "▐__/|__________▌", "▐_/|___________▌", "▐/|____________▌" ] }, "dqpb": { "interval": 100, "frames": ["d", "q", "p", "b"] }, "weather": { "interval": 100, "frames": [ "☀️ ", "☀️ ", "☀️ ", "🌤 ", "⛅️ ", "🌥 ", "☁️ ", "🌧 ", "🌨 ", "🌧 ", "🌨 ", "🌧 ", "🌨 ", "⛈ ", "🌨 ", "🌧 ", "🌨 ", "☁️ ", "🌥 ", "⛅️ ", "🌤 ", "☀️ ", "☀️ " ] }, "christmas": { "interval": 400, "frames": ["🌲", "🎄"] }, "grenade": { "interval": 80, "frames": [ "، ", "′ ", " ´ ", " ‾ ", " ⸌", " ⸊", " |", " ⁎", " ⁕", " ෴ ", " ⁓", " ", " ", " " ] }, "point": { "interval": 125, "frames": ["∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"] }, "layer": { "interval": 150, "frames": ["-", "=", "≡"] }, "betaWave": { "interval": 80, "frames": [ "ρββββββ", "βρβββββ", "ββρββββ", "βββρβββ", "ββββρββ", "βββββρβ", "ββββββρ" ] }, "fingerDance": { "interval": 160, "frames": ["🤘 ", "🤟 ", "🖖 ", "✋ ", "🤚 ", "👆 "] }, "fistBump": { "interval": 80, "frames": [ "🤜\u3000\u3000\u3000\u3000🤛 ", "🤜\u3000\u3000\u3000\u3000🤛 ", "🤜\u3000\u3000\u3000\u3000🤛 ", "\u3000🤜\u3000\u3000🤛\u3000 ", "\u3000\u3000🤜🤛\u3000\u3000 ", "\u3000🤜✨🤛\u3000\u3000 ", "🤜\u3000✨\u3000🤛\u3000 " ] }, "soccerHeader": { "interval": 80, "frames": [ " 🧑⚽️ 🧑 ", "🧑 ⚽️ 🧑 ", "🧑 ⚽️ 🧑 ", "🧑 ⚽️ 🧑 ", "🧑 ⚽️ 🧑 ", "🧑 ⚽️ 🧑 ", "🧑 ⚽️🧑 ", "🧑 ⚽️ 🧑 ", "🧑 ⚽️ 🧑 ", "🧑 ⚽️ 🧑 ", "🧑 ⚽️ 🧑 ", "🧑 ⚽️ 🧑 " ] }, "mindblown": { "interval": 160, "frames": [ "😐 ", "😐 ", "😮 ", "😮 ", "😦 ", "😦 ", "😧 ", "😧 ", "🤯 ", "💥 ", "✨ ", "\u3000 ", "\u3000 ", "\u3000 " ] }, "speaker": { "interval": 160, "frames": ["🔈 ", "🔉 ", "🔊 ", "🔉 "] }, "orangePulse": { "interval": 100, "frames": ["🔸 ", "🔶 ", "🟠 ", "🟠 ", "🔶 "] }, "bluePulse": { "interval": 100, "frames": ["🔹 ", "🔷 ", "🔵 ", "🔵 ", "🔷 "] }, "orangeBluePulse": { "interval": 100, "frames": ["🔸 ", "🔶 ", "🟠 ", "🟠 ", "🔶 ", "🔹 ", "🔷 ", "🔵 ", "🔵 ", "🔷 "] }, "timeTravel": { "interval": 100, "frames": [ "🕛 ", "🕚 ", "🕙 ", "🕘 ", "🕗 ", "🕖 ", "🕕 ", "🕔 ", "🕓 ", "🕒 ", "🕑 ", "🕐 " ] }, "aesthetic": { "interval": 80, "frames": [ "▰▱▱▱▱▱▱", "▰▰▱▱▱▱▱", "▰▰▰▱▱▱▱", "▰▰▰▰▱▱▱", "▰▰▰▰▰▱▱", "▰▰▰▰▰▰▱", "▰▰▰▰▰▰▰", "▰▱▱▱▱▱▱" ] } } # }}} class Spinner: def __init__(self, name: str = 'dots'): definition = spinners[name] self.interval = definition['interval'] / 1000 self.frames = definition['frames'] self.current_frame = -1 self.last_change_at = -self.interval def __call__(self) -> str: now = monotonic() if now - self.last_change_at >= self.interval: self.last_change_at = now self.current_frame = (self.current_frame + 1) % len(self.frames) return self.frames[self.current_frame] kitty-0.41.1/kittens/tui/utils.py0000664000175000017510000000746614773370543016320 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import os import sys from collections.abc import Sequence from contextlib import suppress from typing import TYPE_CHECKING, Optional, cast from kitty.types import run_once from .operations import raw_mode, set_cursor_visible if TYPE_CHECKING: from kitty.options.types import Options def get_key_press(allowed: str, default: str) -> str: response = default with raw_mode(): print(set_cursor_visible(False), end='', flush=True) try: while True: q = sys.stdin.buffer.read(1) if q: if q in b'\x1b\x03': break with suppress(Exception): response = q.decode('utf-8').lower() if response in allowed: break except (KeyboardInterrupt, EOFError): pass finally: print(set_cursor_visible(True), end='', flush=True) return response def format_number(val: float, max_num_of_decimals: int = 2) -> str: ans = str(val) pos = ans.find('.') if pos > -1: ans = ans[:pos + max_num_of_decimals + 1] return ans.rstrip('0').rstrip('.') def human_size( size: int, sep: str = ' ', max_num_of_decimals: int = 2, unit_list: tuple[str, ...] = ('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB') ) -> str: """ Convert a size in bytes into a human readable form """ if size < 2: return f'{size}{sep}{unit_list[0]}' from math import log exponent = min(int(log(size, 1024)), len(unit_list) - 1) return format_number(size / 1024**exponent, max_num_of_decimals) + sep + unit_list[exponent] def kitty_opts() -> 'Options': from kitty.fast_data_types import get_options, set_options try: ans = cast(Optional['Options'], get_options()) except RuntimeError: ans = None if ans is None: from kitty.cli import create_default_opts from kitty.utils import suppress_error_logging with suppress_error_logging(): ans = create_default_opts() set_options(ans) return ans def set_kitty_opts(paths: Sequence[str], overrides: Sequence[str] = ()) -> 'Options': from kitty.config import load_config from kitty.fast_data_types import set_options from kitty.utils import suppress_error_logging with suppress_error_logging(): opts = load_config(*paths, overrides=overrides or None) set_options(opts) return opts def report_error(msg: str = '', return_code: int = 1, print_exc: bool = False) -> None: ' Report an error also sending the overlay ready message to ensure kitten is visible ' from .operations import overlay_ready print(end=overlay_ready()) if msg: print(msg, file=sys.stderr) if print_exc: cls, e, tb = sys.exc_info() if e and not isinstance(e, (SystemExit, KeyboardInterrupt)): import traceback traceback.print_exc() with suppress(KeyboardInterrupt, EOFError): input('Press Enter to quit') raise SystemExit(return_code) def report_unhandled_error(msg: str = '') -> None: ' Report an unhandled exception with the overlay ready message ' return report_error(msg, print_exc=True) @run_once def running_in_tmux() -> str: socket = os.environ.get('TMUX') if not socket: return '' parts = socket.split(',') if len(parts) < 2: return '' try: if not os.access(parts[0], os.R_OK | os.W_OK): return '' except OSError: return '' from kitty.child import cmdline_of_pid c = cmdline_of_pid(int(parts[1])) if not c: return '' exe = os.path.basename(c[0]) if exe.lower() == 'tmux': return exe return '' kitty-0.41.1/kittens/unicode_input/0000775000175000017510000000000014773370543016635 5ustar nileshnileshkitty-0.41.1/kittens/unicode_input/__init__.py0000664000175000017510000000000014773370543020734 0ustar nileshnileshkitty-0.41.1/kittens/unicode_input/main.go0000664000175000017510000004131314773370543020112 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package unicode_input import ( "bytes" "errors" "fmt" "os" "os/exec" "path/filepath" "slices" "strconv" "strings" "unicode" "kitty/tools/cli" "kitty/tools/tty" "kitty/tools/tui" "kitty/tools/tui/loop" "kitty/tools/tui/readline" "kitty/tools/unicode_names" "kitty/tools/utils" "kitty/tools/utils/style" "kitty/tools/wcswidth" ) var _ = fmt.Print const INDEX_CHAR string = "." const INDEX_BASE = 36 const InvalidChar rune = unicode.MaxRune + 1 const default_set_of_symbols string = ` ‘’“”‹›«»‚„ 😀😛😇😈😉😍😎😮👍👎 —–§¶†‡©®™ →⇒•·°±−×÷¼½½¾ …µ¢£€¿¡¨´¸ˆ˜ ÀÁÂÃÄÅÆÇÈÉÊË ÌÍÎÏÐÑÒÓÔÕÖØ ŒŠÙÚÛÜÝŸÞßàá âãäåæçèéêëìí îïðñòóôõöøœš ùúûüýÿþªºαΩ∞ ` var DEFAULT_SET []rune var EMOTICONS_SET []rune const DEFAULT_MODE string = "HEX" func build_sets() { DEFAULT_SET = make([]rune, 0, len(default_set_of_symbols)) for _, ch := range default_set_of_symbols { if !unicode.IsSpace(ch) { DEFAULT_SET = append(DEFAULT_SET, ch) } } EMOTICONS_SET = make([]rune, 0, 0x1f64f-0x1f600+1) for i := 0x1f600; i <= 0x1f64f; i++ { EMOTICONS_SET = append(EMOTICONS_SET, rune(i)) } } func codepoint_ok(code rune) bool { return !(code <= 32 || code == 127 || (128 <= code && code <= 159) || (0xd800 <= code && code <= 0xdbff) || (0xDC00 <= code && code <= 0xDFFF) || code > unicode.MaxRune) } func parse_favorites(raw string) (ans []rune) { ans = make([]rune, 0, 128) for _, line := range utils.Splitlines(raw) { line = strings.TrimSpace(line) if len(line) == 0 || strings.HasPrefix(line, "#") { continue } idx := strings.Index(line, "#") if idx > -1 { line = line[:idx] } code_text, _, _ := strings.Cut(line, " ") code, err := strconv.ParseUint(code_text, 16, 32) if err == nil && codepoint_ok(rune(code)) { ans = append(ans, rune(code)) } } return } func serialize_favorites(favs []rune) string { b := strings.Builder{} b.Grow(8192) b.WriteString(`# Favorite characters for unicode input # Enter the hex code for each favorite character on a new line. Blank lines are # ignored and anything after a # is considered a comment. `) for _, ch := range favs { b.WriteString(fmt.Sprintf("%x # %s %s\n", ch, string(ch), unicode_names.NameForCodePoint(ch))) } return b.String() } var loaded_favorites []rune var favorites_loaded_from_user_config bool func favorites_path() string { return filepath.Join(utils.ConfigDir(), "unicode-input-favorites.conf") } func load_favorites(refresh bool) []rune { if refresh || loaded_favorites == nil { raw, err := os.ReadFile(favorites_path()) if err == nil { loaded_favorites = parse_favorites(utils.UnsafeBytesToString(raw)) favorites_loaded_from_user_config = true } else { loaded_favorites = DEFAULT_SET favorites_loaded_from_user_config = false } } return loaded_favorites } type CachedData struct { Recent []rune `json:"recent,omitempty"` Mode string `json:"mode,omitempty"` } var cached_data *CachedData type Mode int const ( HEX Mode = iota NAME EMOTICONS FAVORITES ) type ModeData struct { mode Mode key string title string } var all_modes [4]ModeData type checkpoints_key struct { mode Mode text string codepoints []rune index_word int } func (self *checkpoints_key) clear() { *self = checkpoints_key{} } func (self *checkpoints_key) is_equal(other checkpoints_key) bool { return self.mode == other.mode && self.text == other.text && slices.Equal(self.codepoints, other.codepoints) && self.index_word == other.index_word } type handler struct { mode Mode recent []rune current_char rune err error lp *loop.Loop ctx style.Context rl *readline.Readline choice_line string emoji_variation string checkpoints_key checkpoints_key table table current_tab_formatter, tab_bar_formatter, chosen_formatter, chosen_name_formatter, dim_formatter func(...any) string } func (self *handler) initialize() { self.ctx.AllowEscapeCodes = true self.checkpoints_key.index_word = -1 self.table.initialize(self.emoji_variation, self.ctx) self.lp.SetWindowTitle("Unicode input") self.current_char = InvalidChar self.current_tab_formatter = self.ctx.SprintFunc("reverse=false bold=true") self.tab_bar_formatter = self.ctx.SprintFunc("reverse=true") self.chosen_formatter = self.ctx.SprintFunc("fg=green") self.chosen_name_formatter = self.ctx.SprintFunc("italic=true dim=true") self.dim_formatter = self.ctx.SprintFunc("dim=true") self.rl = readline.New(self.lp, readline.RlInit{Prompt: "> ", DontMarkPrompts: true}) self.rl.Start() self.refresh() } func (self *handler) finalize() string { self.rl.End() self.rl.Shutdown() return "" } func (self *handler) resolved_char() string { if self.current_char == InvalidChar { return "" } return resolved_char(self.current_char, self.emoji_variation) } func is_index(word string) bool { if !strings.HasPrefix(word, INDEX_CHAR) { return false } word = strings.TrimLeft(word, INDEX_CHAR) _, err := strconv.ParseUint(word, INDEX_BASE, 32) return err == nil } func (self *handler) update_codepoints() { var index_word uint64 var q checkpoints_key q.mode = self.mode q.index_word = -1 switch self.mode { case HEX: q.codepoints = self.recent if len(q.codepoints) == 0 { q.codepoints = DEFAULT_SET } case EMOTICONS: q.codepoints = EMOTICONS_SET case FAVORITES: q.codepoints = load_favorites(false) case NAME: q.text = self.rl.AllText() if !q.is_equal(self.checkpoints_key) { words := strings.Split(q.text, " ") words = utils.RemoveAll(words, INDEX_CHAR) if len(words) > 1 { for i, w := range words { if i > 0 && is_index(w) { iw := words[i] words = words[:i] index_word, _ = strconv.ParseUint(strings.TrimLeft(iw, INDEX_CHAR), INDEX_BASE, 32) q.index_word = int(index_word) break } } } query := strings.Join(words, " ") if len(query) > 1 { words = words[1:] q.codepoints = unicode_names.CodePointsForQuery(query) } } } if !q.is_equal(self.checkpoints_key) { self.checkpoints_key = q self.table.set_codepoints(q.codepoints, self.mode, q.index_word) } } var debugprintln = tty.DebugPrintln func (self *handler) update_current_char() { self.update_codepoints() self.current_char = InvalidChar text := self.rl.AllText() switch self.mode { case HEX: if strings.HasPrefix(text, INDEX_CHAR) { if len(text) > 1 { self.current_char = self.table.codepoint_at_hint(text[1:]) } } else if len(text) > 0 { code, err := strconv.ParseUint(text, 16, 32) if err == nil && code <= unicode.MaxRune { self.current_char = rune(code) } } case NAME: cc := self.table.current_codepoint() if cc > 0 && cc <= unicode.MaxRune { self.current_char = rune(cc) } default: if len(text) > 0 { self.current_char = self.table.codepoint_at_hint(strings.TrimLeft(text, INDEX_CHAR)) } } if !codepoint_ok(self.current_char) { self.current_char = InvalidChar } } func (self *handler) update_prompt() { self.update_current_char() ch := "??" color := "red" self.choice_line = "" if self.current_char != InvalidChar { ch, color = self.resolved_char(), "green" self.choice_line = fmt.Sprintf( "Chosen: %s U+%x %s", self.chosen_formatter(ch), self.current_char, self.chosen_name_formatter(title(unicode_names.NameForCodePoint(self.current_char)))) } prompt := fmt.Sprintf("%s> ", self.ctx.SprintFunc("fg="+color)(ch)) self.rl.SetPrompt(prompt) } func (self *handler) draw_title_bar() { self.lp.AllowLineWrapping(false) entries := make([]string, 0, len(all_modes)) for _, md := range all_modes { entry := fmt.Sprintf(" %s (%s) ", md.title, md.key) if md.mode == self.mode { entry = self.current_tab_formatter(entry) } entries = append(entries, entry) } sz, _ := self.lp.ScreenSize() text := fmt.Sprintf("Search by:%s", strings.Join(entries, "")) extra := int(sz.WidthCells) - wcswidth.Stringwidth(text) if extra > 0 { text += strings.Repeat(" ", extra) } self.lp.Println(self.tab_bar_formatter(text)) } func (self *handler) draw_screen() { self.lp.StartAtomicUpdate() defer self.lp.EndAtomicUpdate() self.lp.ClearScreen() self.draw_title_bar() y := 1 writeln := func(text ...any) { self.lp.Println(text...) y += 1 } switch self.mode { case NAME: writeln("Enter words from the name of the character") case HEX: writeln("Enter the hex code for the character") default: writeln("Enter the index for the character you want from the list below") } self.rl.RedrawNonAtomic() self.lp.AllowLineWrapping(false) self.lp.SaveCursorPosition() defer self.lp.RestoreCursorPosition() writeln() writeln(self.choice_line) sz, _ := self.lp.ScreenSize() write_help := func(x string) { lines := style.WrapTextAsLines(x, int(sz.WidthCells)-1, style.WrapOptions{}) for _, line := range lines { if line != "" { writeln(self.dim_formatter(line)) } } } switch self.mode { case HEX: write_help(fmt.Sprintf("Type %s followed by the index for the recent entries below", INDEX_CHAR)) case NAME: write_help(fmt.Sprintf("Use Tab or arrow keys to choose a character. Type space and %s to select by index", INDEX_CHAR)) case FAVORITES: write_help("Press F12 to edit the list of favorites") } q := self.table.layout(int(sz.HeightCells)-y, int(sz.WidthCells)) if q != "" { self.lp.QueueWriteString(q) } } func (self *handler) on_text(text string, from_key_event, in_bracketed_paste bool) error { err := self.rl.OnText(text, from_key_event, in_bracketed_paste) if err != nil { return err } self.refresh() return nil } func (self *handler) switch_mode(mode Mode) { if self.mode != mode { self.mode = mode self.rl.ResetText() self.current_char = InvalidChar self.choice_line = "" } } func (self *handler) handle_hex_key_event(event *loop.KeyEvent) { text := self.rl.AllText() val, err := strconv.ParseUint(text, 16, 32) new_val := -1 if err != nil { return } if event.MatchesPressOrRepeat("tab") { new_val = int(val) + 10 } else if event.MatchesPressOrRepeat("up") { new_val = int(val) + 1 } else if event.MatchesPressOrRepeat("down") { new_val = utils.Max(32, int(val)-1) } if new_val > -1 { event.Handled = true self.rl.SetText(fmt.Sprintf("%x", new_val)) } } func (self *handler) handle_name_key_event(event *loop.KeyEvent) { if event.MatchesPressOrRepeat("shift+tab") || event.MatchesPressOrRepeat("left") { event.Handled = true self.table.move_current(0, -1) } else if event.MatchesPressOrRepeat("tab") || event.MatchesPressOrRepeat("right") { event.Handled = true self.table.move_current(0, 1) } else if event.MatchesPressOrRepeat("up") { event.Handled = true self.table.move_current(-1, 0) } else if event.MatchesPressOrRepeat("down") { event.Handled = true self.table.move_current(1, 0) } } func (self *handler) handle_emoticons_key_event(event *loop.KeyEvent) { } func (self *handler) handle_favorites_key_event(event *loop.KeyEvent) { if event.MatchesPressOrRepeat("f12") { event.Handled = true exe, err := os.Executable() if err != nil { self.err = err self.lp.Quit(1) return } fp := favorites_path() if len(load_favorites(false)) == 0 || !favorites_loaded_from_user_config { raw := serialize_favorites(load_favorites(false)) err = os.MkdirAll(filepath.Dir(fp), 0o755) if err != nil { self.err = fmt.Errorf("Failed to create config directory to store favorites in: %w", err) self.lp.Quit(1) return } err = utils.AtomicUpdateFile(fp, bytes.NewReader(utils.UnsafeStringToBytes(raw)), 0o600) if err != nil { self.err = fmt.Errorf("Failed to write to favorites file %s with error: %w", fp, err) self.lp.Quit(1) return } } err = self.lp.SuspendAndRun(func() error { cmd := exec.Command(exe, "edit-in-kitty", "--type=overlay", fp) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err == nil { load_favorites(true) } else { fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, "Failed to run edit-in-kitty, favorites have not been changed. Press Enter to continue.") var ln string fmt.Scanln(&ln) } return nil }) if err != nil { self.err = err self.lp.Quit(1) return } } } func (self *handler) next_mode(delta int) { for num, md := range all_modes { if md.mode == self.mode { idx := (num + delta + len(all_modes)) % len(all_modes) md = all_modes[idx] self.switch_mode(md.mode) break } } } var ErrCanceledByUser = errors.New("Canceled by user") func (self *handler) on_key_event(event *loop.KeyEvent) (err error) { if event.MatchesPressOrRepeat("esc") || event.MatchesPressOrRepeat("ctrl+c") { return ErrCanceledByUser } if event.MatchesPressOrRepeat("f1") || event.MatchesPressOrRepeat("ctrl+1") { event.Handled = true self.switch_mode(HEX) } else if event.MatchesPressOrRepeat("f2") || event.MatchesPressOrRepeat("ctrl+2") { event.Handled = true self.switch_mode(NAME) } else if event.MatchesPressOrRepeat("f3") || event.MatchesPressOrRepeat("ctrl+3") { event.Handled = true self.switch_mode(EMOTICONS) } else if event.MatchesPressOrRepeat("f4") || event.MatchesPressOrRepeat("ctrl+4") { event.Handled = true self.switch_mode(FAVORITES) } else if event.MatchesPressOrRepeat("ctrl+tab") || event.MatchesPressOrRepeat("ctrl+]") { event.Handled = true self.next_mode(1) } else if event.MatchesPressOrRepeat("ctrl+shift+tab") || event.MatchesPressOrRepeat("ctrl+[") { event.Handled = true self.next_mode(-1) } if !event.Handled { switch self.mode { case HEX: self.handle_hex_key_event(event) case NAME: self.handle_name_key_event(event) case EMOTICONS: self.handle_emoticons_key_event(event) case FAVORITES: self.handle_favorites_key_event(event) } } if !event.Handled { err = self.rl.OnKeyEvent(event) if err != nil { if err == readline.ErrAcceptInput { self.refresh() self.lp.Quit(0) return nil } return err } } if event.Handled { self.refresh() } return } func (self *handler) refresh() { self.update_prompt() self.draw_screen() } func run_loop(opts *Options) (lp *loop.Loop, err error) { output := tui.KittenOutputSerializer() lp, err = loop.New() if err != nil { return } cv := utils.NewCachedValues("unicode-input", &CachedData{Recent: DEFAULT_SET, Mode: DEFAULT_MODE}) cached_data = cv.Load() defer cv.Save() h := handler{recent: cached_data.Recent, lp: lp, emoji_variation: opts.EmojiVariation} switch opts.Tab { case "previous": switch cached_data.Mode { case "HEX": h.mode = HEX case "NAME": h.mode = NAME case "EMOTICONS": h.mode = EMOTICONS case "FAVORITES": h.mode = FAVORITES } case "code": h.mode = HEX case "name": h.mode = NAME case "emoticons": h.mode = EMOTICONS case "favorites": h.mode = FAVORITES } all_modes[0] = ModeData{mode: HEX, title: "Code", key: "F1"} all_modes[1] = ModeData{mode: NAME, title: "Name", key: "F2"} all_modes[2] = ModeData{mode: EMOTICONS, title: "Emoticons", key: "F3"} all_modes[3] = ModeData{mode: FAVORITES, title: "Favorites", key: "F4"} lp.OnInitialize = func() (string, error) { h.initialize() lp.SendOverlayReady() return "", nil } lp.OnResize = func(old_size, new_size loop.ScreenSize) error { h.refresh() return nil } lp.OnResumeFromStop = func() error { h.refresh() return nil } lp.OnText = h.on_text lp.OnFinalize = h.finalize lp.OnKeyEvent = h.on_key_event err = lp.Run() if err != nil { return } if h.err == nil { switch h.mode { case HEX: cached_data.Mode = "HEX" case NAME: cached_data.Mode = "NAME" case EMOTICONS: cached_data.Mode = "EMOTICONS" case FAVORITES: cached_data.Mode = "FAVORITES" } if h.current_char != InvalidChar { cached_data.Recent = h.recent idx := slices.Index(cached_data.Recent, h.current_char) if idx > -1 { cached_data.Recent = slices.Delete(cached_data.Recent, idx, idx+1) } cached_data.Recent = slices.Insert(cached_data.Recent, 0, h.current_char) if len(cached_data.Recent) > len(DEFAULT_SET) { cached_data.Recent = cached_data.Recent[:len(DEFAULT_SET)] } ans := h.resolved_char() o, err := output(ans) if err != nil { return lp, err } fmt.Println(o) } } err = h.err return } func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) { go unicode_names.Initialize() // start parsing name data in the background build_sets() lp, err := run_loop(o) if err != nil { if err == ErrCanceledByUser { err = nil } return 1, err } ds := lp.DeathSignalName() if ds != "" { fmt.Println("Killed by signal: ", ds) lp.KillIfSignalled() return 1, nil } return } func EntryPoint(parent *cli.Command) { create_cmd(parent, main) } kitty-0.41.1/kittens/unicode_input/main.py0000664000175000017510000000240414773370543020133 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal from kitty.typing import BossType from ..tui.handler import result_handler help_text = 'Input a Unicode character' usage = '' OPTIONS = ''' --emoji-variation type=choices default=none choices=none,graphic,text Whether to use the textual or the graphical form for emoji. By default the default form specified in the Unicode standard for the symbol is used. --tab type=choices default=previous choices=previous,code,name,emoticons,favorites The initial tab to display. Defaults to using the tab from the previous kitten invocation. '''.format @result_handler(has_ready_notification=True) def handle_result(args: list[str], current_char: str, target_window_id: int, boss: BossType) -> None: w = boss.window_id_map.get(target_window_id) if w is not None: w.paste_text(current_char) def main(args: list[str]) -> str | None: raise SystemExit('This should be run as kitten unicode_input') if __name__ == '__main__': main([]) elif __name__ == '__doc__': import sys cd = sys.cli_docs # type: ignore cd['usage'] = usage cd['options'] = OPTIONS cd['help_text'] = help_text cd['short_desc'] = 'Browse and select unicode characters by name' kitty-0.41.1/kittens/unicode_input/table.go0000664000175000017510000001403314773370543020254 0ustar nileshnilesh// License: GPLv3 Copyright: 2023, Kovid Goyal, package unicode_input import ( "fmt" "slices" "strconv" "strings" "kitty/tools/unicode_names" "kitty/tools/utils" "kitty/tools/utils/style" "kitty/tools/wcswidth" ) var _ = fmt.Print func resolved_char(ch rune, emoji_variation string) string { ans := string(ch) if wcswidth.IsEmojiPresentationBase(ch) { switch emoji_variation { case "text": ans += "\ufe0e" case "graphic": ans += "\ufe0f" } } return ans } func decode_hint(text string) int { x, err := strconv.ParseUint(text, INDEX_BASE, 32) if err != nil { return -1 } return int(x) } func encode_hint(num int) string { return strconv.FormatUint(uint64(num), INDEX_BASE) } func ljust(s string, sz int) string { x := wcswidth.Stringwidth(s) if x < sz { s += strings.Repeat(" ", sz-x) } return s } type scroll_data struct { num_items_per_page int scroll_rows int } type table struct { emoji_variation string layout_dirty bool last_rows, last_cols int codepoints []rune current_idx int scroll_data scroll_data text string num_cols, num_rows int mode Mode green, reversed, intense_gray func(...any) string } func (self *table) initialize(emoji_variation string, ctx style.Context) { self.emoji_variation = emoji_variation self.layout_dirty = true self.last_cols, self.last_rows = -1, -1 self.green = ctx.SprintFunc("fg=green") self.reversed = ctx.SprintFunc("reverse=true") self.intense_gray = ctx.SprintFunc("fg=intense-gray") } func (self *table) current_codepoint() rune { if len(self.codepoints) > 0 { return self.codepoints[self.current_idx] } return InvalidChar } func (self *table) set_codepoints(codepoints []rune, mode Mode, current_idx int) { delta := len(codepoints) - len(self.codepoints) self.codepoints = codepoints if self.codepoints != nil && mode != FAVORITES && mode != HEX { slices.Sort(self.codepoints) } self.mode = mode self.layout_dirty = true if current_idx > -1 && current_idx < len(self.codepoints) { self.current_idx = current_idx } if self.current_idx >= len(self.codepoints) { self.current_idx = 0 } if delta != 0 { self.scroll_data = scroll_data{} } } func (self *table) codepoint_at_hint(hint string) rune { idx := decode_hint(hint) if idx >= 0 && idx < len(self.codepoints) { return self.codepoints[idx] } return InvalidChar } type cell_data struct { idx, ch, desc string } func title(x string) string { if len(x) > 1 { x = strings.ToUpper(x[:1]) + x[1:] } return x } func (self *table) layout(rows, cols int) string { if !self.layout_dirty && self.last_cols == cols && self.last_rows == rows { return self.text } self.last_cols, self.last_rows = cols, rows self.layout_dirty = false var as_parts func(int, rune) cell_data var cell func(int, cell_data) var idx_size, space_for_desc int output := strings.Builder{} output.Grow(4096) switch self.mode { case NAME: as_parts = func(i int, codepoint rune) cell_data { return cell_data{idx: ljust(encode_hint(i), idx_size), ch: resolved_char(codepoint, self.emoji_variation), desc: title(unicode_names.NameForCodePoint(codepoint))} } cell = func(i int, cd cell_data) { is_current := i == self.current_idx text := self.green(cd.idx) + " " + cd.ch + " " w := wcswidth.Stringwidth(cd.ch) if w < 2 { text += strings.Repeat(" ", (2 - w)) } desc_width := wcswidth.Stringwidth(cd.desc) if desc_width > space_for_desc { text += cd.desc[:space_for_desc-1] + "…" } else { text += cd.desc extra := space_for_desc - desc_width if extra > 0 { text += strings.Repeat(" ", extra) } } if is_current { text = self.reversed(text) } output.WriteString(text) } default: as_parts = func(i int, codepoint rune) cell_data { return cell_data{idx: ljust(encode_hint(i), idx_size), ch: resolved_char(codepoint, self.emoji_variation)} } cell = func(i int, cd cell_data) { output.WriteString(self.green(cd.idx)) output.WriteString(" ") output.WriteString(self.intense_gray(cd.ch)) w := wcswidth.Stringwidth(cd.ch) if w < 2 { output.WriteString(strings.Repeat(" ", (2 - w))) } } } num := len(self.codepoints) if num < 1 { self.text = "" self.num_cols = 0 self.num_rows = 0 return self.text } idx_size = len(encode_hint(num - 1)) parts := make([]cell_data, len(self.codepoints)) for i, ch := range self.codepoints { parts[i] = as_parts(i, ch) } longest := 0 switch self.mode { case NAME: for _, p := range parts { longest = utils.Max(longest, idx_size+2+len(p.desc)+2) } default: longest = idx_size + 3 } col_width := longest + 2 col_width = utils.Min(col_width, 40) self.num_cols = utils.Max(cols/col_width, 1) if self.num_cols == 1 { col_width = cols } space_for_desc = col_width - 2 - idx_size - 4 self.num_rows = rows rows_left := rows if self.scroll_data.num_items_per_page != self.num_cols*self.num_rows { self.update_scroll_data() } skip_scroll := self.scroll_data.scroll_rows * self.num_cols for i, cd := range parts { if skip_scroll > 0 { skip_scroll -= 1 continue } cell(i, cd) output.WriteString(" ") if self.num_cols == 1 || (i > 0 && (i+1)%self.num_cols == 0) { rows_left -= 1 if rows_left == 0 { break } output.WriteString("\r\n") } } self.text = output.String() return self.text } func (self *table) update_scroll_data() { self.scroll_data.num_items_per_page = self.num_rows * self.num_cols page_num := self.current_idx / self.scroll_data.num_items_per_page self.scroll_data.scroll_rows = self.num_rows * page_num } func (self *table) move_current(rows, cols int) { if len(self.codepoints) == 0 { return } if cols != 0 { self.current_idx = (self.current_idx + len(self.codepoints) + cols) % len(self.codepoints) self.layout_dirty = true } if rows != 0 { amt := rows * self.num_cols self.current_idx += amt self.current_idx = utils.Max(0, utils.Min(self.current_idx, len(self.codepoints)-1)) self.layout_dirty = true } self.update_scroll_data() } kitty-0.41.1/kitty/0000775000175000017510000000000014773370543013453 5ustar nileshnileshkitty-0.41.1/kitty/__init__.py0000664000175000017510000000000014773370543015552 0ustar nileshnileshkitty-0.41.1/kitty/actions.py0000664000175000017510000000700414773370543015466 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import inspect from typing import NamedTuple, cast from .boss import Boss from .tabs import Tab from .types import ActionGroup, ActionSpec, run_once from .window import Window class Action(NamedTuple): name: str group: ActionGroup short_help: str long_help: str groups: dict[ActionGroup, str] = { 'cp': 'Copy/paste', 'sc': 'Scrolling', 'win': 'Window management', 'tab': 'Tab management', 'mouse': 'Mouse actions', 'mk': 'Marks', 'lay': 'Layouts', 'misc': 'Miscellaneous', 'debug': 'Debugging', } group_title = groups.__getitem__ @run_once def get_all_actions() -> dict[ActionGroup, list[Action]]: ' test docstring ' ans: dict[ActionGroup, list[Action]] = {} def is_action(x: object) -> bool: return isinstance(getattr(x, 'action_spec', None), ActionSpec) def as_action(x: object) -> Action: spec: ActionSpec = getattr(x, 'action_spec') doc = inspect.cleandoc(spec.doc) lines = doc.splitlines() first = lines.pop(0) short_help = first long_help = '\n'.join(lines).strip() assert spec.group in groups return Action(getattr(x, '__name__'), cast(ActionGroup, spec.group), short_help, long_help) seen = set() for cls in (Window, Tab, Boss): for (name, func) in inspect.getmembers(cls, is_action): ac = as_action(func) if ac.name not in seen: ans.setdefault(ac.group, []).append(ac) seen.add(ac.name) ans['misc'].append(Action('no_op', 'misc', 'Unbind a shortcut', 'Mapping a shortcut to no_op causes kitty to not intercept the key stroke anymore,' ' instead passing it to the program running inside it.')) return ans def dump() -> None: from pprint import pprint pprint(get_all_actions()) def as_rst() -> str: from .conf.types import Mapping from .options.definition import definition allg = get_all_actions() lines: list[str] = [] a = lines.append maps: dict[str, list[Mapping]] = {} for m in definition.iter_all_maps(): if m.documented: func = m.action_def.split()[0] maps.setdefault(func, []).append(m) def key(x: ActionGroup) -> str: return group_title(x).lower() def kitten_link(text: str) -> str: x = text.split() return f':doc:`kittens/{x[2]}`' if len(x) > 2 else '' for group in sorted(allg, key=key): title = group_title(group) a('') a(f'.. _action-group-{group}:') a('') a(title) a('-' * len(title)) a('') for action in allg[group]: a('') a(f'.. action:: {action.name}') a('') a(action.short_help) a('') if action.long_help: a(action.long_help) if action.name in maps: a('') a('Default shortcuts using this action:') if action.name == 'kitten': a('') scs = {(kitten_link(m.parseable_text), m.short_text, f':sc:`kitty.{m.name}`') for m in maps[action.name]} for s in sorted(scs): a(f'- {s[0]} - {s[2]} {s[1]}') else: sscs = {f':sc:`kitty.{m.name}`' for m in maps[action.name]} a(', '.join(sorted(sscs))) return '\n'.join(lines) kitty-0.41.1/kitty/alpha_blend.glsl0000664000175000017510000000172114773370543016570 0ustar nileshnileshvec4 alpha_blend(vec4 over, vec4 under) { // Alpha blend two colors returning the resulting color pre-multiplied by its alpha // and its alpha. // See https://en.wikipedia.org/wiki/Alpha_compositing float alpha = mix(under.a, 1.0f, over.a); vec3 combined_color = mix(under.rgb * under.a, over.rgb, over.a); return vec4(combined_color, alpha); } vec4 alpha_blend_premul(vec4 over, vec4 under) { // Same as alpha_blend() except that it assumes over and under are both premultiplied. float inv_over_alpha = 1.0f - over.a; float alpha = over.a + under.a * inv_over_alpha; return vec4(over.rgb + under.rgb * inv_over_alpha, alpha); } vec4 alpha_blend_premul(vec4 over, vec3 under) { // same as alpha_blend_premul with under_alpha = 1 outputs a blended color // with alpha 1 which is effectively pre-multiplied since alpha is 1 float inv_over_alpha = 1.0f - over.a; return vec4(over.rgb + under.rgb * inv_over_alpha, 1.0); } kitty-0.41.1/kitty/animation.c0000664000175000017510000002357314773370543015610 0ustar nileshnilesh/* * animation.c * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "data-types.h" #define ANIMATION_INTERNAL_API typedef struct LinearParameters { size_t count; double buf[]; } LinearParameters; typedef struct StepsParameters { size_t num_of_buckets; double jump_size, start_value; } StepsParameters; static const double bezier_epsilon = 1e-7; static const unsigned max_newton_iterations = 4; static const unsigned max_bisection_iterations = 16; typedef struct BezierParameters { double ax, bx, cx, ay, by, cy, start_gradient, end_gradient, spline_samples[11]; } BezierParameters; typedef double(*easing_curve)(void*, double, monotonic_t); typedef struct animation_function { void *params; easing_curve curve; double y_at_start, y_size; } animation_function; typedef struct Animation { animation_function *functions; size_t count, capacity; } Animation; #include "animation.h" #include "state.h" Animation* alloc_animation(void) { return calloc(1, sizeof(Animation)); } bool animation_is_valid(const Animation* a) { return a != NULL && a->count > 0; } Animation* free_animation(Animation *a) { if (a) { for (size_t i = 0; i < a->count; i++) free(a->functions[i].params); free(a->functions); free(a); } return NULL; } static double unit_value(double x) { return MAX(0., MIN(x, 1.)); } static double linear_easing_curve(void *p_, double val, monotonic_t duration UNUSED) { LinearParameters *p = p_; double start_pos = 0, stop_pos = 1, start_val = 0, stop_val = 1; double *x = p->buf, *y = p->buf + p->count; for (size_t i = 0; i < p->count; i++) { if (x[i] >= val) { stop_pos = x[i]; stop_val = y[i]; if (i > 0) { start_val = y[i-1]; start_pos = x[i-1]; } break; } } if (stop_pos > start_pos) { double frac = (val - start_pos) / (stop_pos - start_pos); return start_val + frac * (stop_val - start_val); } return stop_val; } // Cubic Bezier {{{ static double sample_curve_x(const BezierParameters *p, double t) { // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule. return ((p->ax * t + p->bx) * t + p->cx) * t; } static double sample_curve_y(const BezierParameters *p, double t) { return ((p->ay * t + p->by) * t + p->cy) * t; } static double sample_derivative_x(const BezierParameters *p, double t) { return (3.0 * p->ax * t + 2.0 * p->bx) * t + p->cx; } static double solve_curve_x(const BezierParameters *p, double x, double epsilon) { // Given an x value, find a parametric value it came from. double t0 = 0.0, t1 = 0.0, t2 = x, x2 = 0.0, d2 = 0.0; // Linear interpolation of spline curve for initial guess. static const size_t num_samples = arraysz(p->spline_samples); double delta = 1.0 / (num_samples - 1); for (size_t i = 1; i < num_samples; i++) { if (x <= p->spline_samples[i]) { t1 = delta * i; t0 = t1 - delta; t2 = t0 + (t1 - t0) * (x - p->spline_samples[i - 1]) / (p->spline_samples[i] - p->spline_samples[i - 1]); break; } } // Perform a few iterations of Newton's method -- normally very fast. // See https://en.wikipedia.org/wiki/Newton%27s_method. double newton_epsilon = MIN(bezier_epsilon, epsilon); for (unsigned i = 0; i < max_newton_iterations; i++) { x2 = sample_curve_x(p, t2) - x; if (fabs(x2) < newton_epsilon) return t2; d2 = sample_derivative_x(p, t2); if (fabs(d2) < bezier_epsilon) break; t2 = t2 - x2 / d2; } if (fabs(x2) < epsilon) return t2; t0 = 0.0, t1 = 0.0, t2 = x, x2 = 0.0; // Fall back to the bisection method for reliability. unsigned iteration = 0; while (t0 < t1 && iteration++ < max_bisection_iterations) { x2 = sample_curve_x(p, t2); if (fabs(x2 - x) < epsilon) return t2; if (x > x2) t0 = t2; else t1 = t2; t2 = (t1 + t0) * .5; } // Failure. return t2; } static double solve_unit_bezier(const BezierParameters *p, double x, double epsilon) { if (x < 0.0) return 0.0 + p->start_gradient * x; if (x > 1.0) return 1.0 + p->end_gradient * (x - 1.0); return sample_curve_y(p, solve_curve_x(p, x, epsilon)); } static double cubic_bezier_easing_curve(void *p_, double t, monotonic_t duration) { BezierParameters *p = p_; // The longer the animation, the more precision we need double epsilon = 1.0 / monotonic_t_to_ms(duration); return fabs(solve_unit_bezier(p, t, epsilon)); } // }}} static double step_easing_curve(void *p_, double t, monotonic_t duration UNUSED) { StepsParameters *p = p_; size_t val_bucket = (size_t)(t * p->num_of_buckets); return p->start_value + val_bucket * p->jump_size; } static double identity_easing_curve(void *p_ UNUSED, double t, monotonic_t duration UNUSED) { return t; } double apply_easing_curve(const Animation *a, double val, monotonic_t duration) { val = unit_value(val); if (!a->count) return val; size_t idx = MIN((size_t)(val * a->count), a->count - 1); animation_function *f = a->functions + idx; double interval_size = 1. / a->count, interval_start = idx * interval_size; double scaled_val = (val - interval_start) / interval_size; double ans = f->curve(&f->params, scaled_val, duration); return f->y_at_start + unit_value(ans) * f->y_size; } static animation_function* init_function(Animation *a, double y_at_start, double y_at_end, easing_curve curve) { ensure_space_for(a, functions, animation_function, a->count + 1, capacity, 4, false); animation_function *f = a->functions + a->count++; zero_at_ptr(f); f->y_at_start = y_at_start; f->y_size = y_at_end - y_at_start; f->curve = curve; return f; } static bool is_bezier_linear(double p1x, double p1y, double p2x, double p2y) { // Is linear if all four points are on the same line. P0 and P4 are fixed at (0, 0) and (1, 1) for us. return p1x == p1y && p2x == p2y; } void add_cubic_bezier_animation(Animation *a, double y_at_start, double y_at_end, double p1x, double p1y, double p2x, double p2y) { p1x = unit_value(p1x); p2x = unit_value(p2x); if (is_bezier_linear(p1x, p1y, p2x, p2y)) { init_function(a, y_at_start, y_at_end, identity_easing_curve); return; } BezierParameters *p = calloc(1, sizeof(BezierParameters)); if (!p) fatal("Out of memory"); // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1). p->cx = 3.0 * p1x; p->bx = 3.0 * (p2x - p1x) - p->cx; p->ax = 1.0 - p->cx - p->bx; p->cy = 3.0 * p1y; p->by = 3.0 * (p2y - p1y) - p->cy; p->ay = 1.0 - p->cy - p->by; // Calculate gradients used for values outside the unit interval if (p1x > 0) p->start_gradient = p1y / p1x; else if (p1y == 0 && p2x > 0) p->start_gradient = p2y / p2x; else if (p1y == 0 && p2y == 0) p->start_gradient = 1; else p->start_gradient = 0; if (p2x < 1) p->end_gradient = (p2y - 1) / (p2x - 1); else if (p2y == 1 && p1x < 1) p->end_gradient = (p1y - 1) / (p1x - 1); else if (p2y == 1 && p1y == 1) p->end_gradient = 1; else p->end_gradient = 0; size_t num_samples = arraysz(p->spline_samples); double delta = 1. / num_samples; for (size_t i = 0; i < num_samples; i++) p->spline_samples[i] = sample_curve_x(p, i * delta); animation_function *f = init_function(a, y_at_start, y_at_end, cubic_bezier_easing_curve); f->params = p; } void add_linear_animation(Animation *a, double y_at_start, double y_at_end, size_t count, const double *x, const double *y) { const size_t sz = count * sizeof(double); LinearParameters *p = calloc(1, sizeof(LinearParameters) + 2 * sz); if (!p) fatal("Out of memory"); p->count = count; double *px = p->buf, *py = px + count; memcpy(px, x, sz); memcpy(py, y, sz); animation_function *f = init_function(a, y_at_start, y_at_end, linear_easing_curve); f->params = p; } void add_steps_animation(Animation *a, double y_at_start, double y_at_end, size_t count, EasingStep step) { double jump_size = 1. / count, start_value = 0.; size_t num_of_buckets = count; switch (step) { case EASING_STEP_START: start_value = jump_size; break; case EASING_STEP_END: break; case EASING_STEP_NONE: jump_size = 1. / (num_of_buckets - 1); break; case EASING_STEP_BOTH: num_of_buckets++; jump_size = 1. / num_of_buckets; start_value = jump_size; break; } StepsParameters *p = malloc(sizeof(StepsParameters)); if (!p) fatal("Out of memory"); p->num_of_buckets = num_of_buckets; p->jump_size = jump_size; p->start_value = start_value; animation_function *f = init_function(a, y_at_start, y_at_end, step_easing_curve); f->params = p; } static PyObject* test_cursor_blink_easing_function(PyObject *self UNUSED, PyObject *args) { Animation *a = OPT(animation.cursor); if (!animation_is_valid(a)) { PyErr_SetString(PyExc_RuntimeError, "must set a cursor blink animation on the global options object first"); return NULL; } double t, duration_s = 0.5; int only_single = 1; if (!PyArg_ParseTuple(args, "d|pd", &t, &only_single, &duration_s)) return NULL; monotonic_t duration = s_double_to_monotonic_t(duration_s); if (only_single) { animation_function f = a->functions[0]; return PyFloat_FromDouble(f.curve(f.params, t, duration)); } return PyFloat_FromDouble(apply_easing_curve(a, t, duration)); } static PyMethodDef module_methods[] = { METHODB(test_cursor_blink_easing_function, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_animations(PyObject *module) { if (PyModule_AddFunctions(module, module_methods) != 0) return false; return true; } kitty-0.41.1/kitty/animation.h0000664000175000017510000000206014773370543015601 0ustar nileshnilesh/* * animation.h * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include #include #include "monotonic.h" typedef enum { EASING_STEP_START, EASING_STEP_END, EASING_STEP_NONE, EASING_STEP_BOTH } EasingStep; #ifndef ANIMATION_INTERNAL_API typedef struct {int x;} *Animation; #endif #define EASE_IN_OUT 0.42, 0, 0.58, 1 #define ANIMATION_SAMPLE_WAIT (50 * MONOTONIC_T_1e6) Animation* alloc_animation(void); double apply_easing_curve(const Animation *a, double t /* must be between 0 and 1*/, monotonic_t duration); bool animation_is_valid(const Animation *a); void add_cubic_bezier_animation(Animation *a, double y_at_start, double y_at_end, double p1_x, double p1_y, double p2_x, double p2_y); void add_linear_animation(Animation *a, double y_at_start, double y_at_end, size_t count, const double *x, const double *y); void add_steps_animation(Animation *a, double y_at_start, double y_at_end, size_t count, EasingStep step); Animation* free_animation(Animation *a); kitty-0.41.1/kitty/arches.h0000664000175000017510000000131714773370543015073 0ustar nileshnilesh/* * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #ifdef __aarch64__ #define KITTY_TARGET_CPU_IS_ARM64 #define KITTY_128BIT_ALLOWED #define KITTY_256BIT_ALLOWED #elif defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) #define KITTY_TARGET_CPU_IS_X86 #define KITTY_128BIT_ALLOWED #elif defined(__amd64__) #define KITTY_TARGET_CPU_IS_AMD64 #define KITTY_128BIT_ALLOWED #define KITTY_256BIT_ALLOWED #endif #if defined(__clang__) && defined(KITTY_128BIT_ALLOWED) #define KITTY_START_128BIT_CODE #elif defined(KITTY_128BIT_ALLOWED) #define KITTY_START_128BIT_CODE #else #define KITTY_START_128BIT_CODE #endif kitty-0.41.1/kitty/arena.h0000664000175000017510000000457014773370543014720 0ustar nileshnilesh/* * arena.h * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "data-types.h" #ifndef MA_NAME #error "Must define MA_NAME" #endif #ifndef MA_BLOCK_SIZE #define MA_BLOCK_SIZE 1u #endif #define MA_CAT_( a, b ) a##b #define MA_CAT( a, b ) MA_CAT_( a, b ) #ifndef MA_ARENA_NUM_BLOCKS #define MA_ARENA_NUM_BLOCKS 4096u #endif #define MA_TYPE_NAME MA_CAT(MA_NAME, MonotonicArena) #define MA_BLOCK_TYPE_NAME MA_CAT(MA_NAME, MonotonicArenaBlock) typedef struct MA_BLOCK_TYPE_NAME { void *buf; size_t used, capacity; } MA_BLOCK_TYPE_NAME; typedef struct MA_TYPE_NAME { MA_BLOCK_TYPE_NAME *blocks; size_t count, capacity; } MA_TYPE_NAME; static inline void MA_CAT(MA_NAME, _free_all)(MA_TYPE_NAME *self) { for (size_t i = 0; i < self->count; i++) free(self->blocks[i].buf); free(self->blocks); zero_at_ptr(self); } static inline void* MA_CAT(MA_NAME, _get)(MA_TYPE_NAME *self, size_t sz) { size_t required_size = (sz / MA_BLOCK_SIZE) * MA_BLOCK_SIZE; if (required_size < sz) required_size += MA_BLOCK_SIZE; if (!self->count || (self->blocks[self->count-1].capacity - self->blocks[self->count-1].used) < required_size) { size_t count = self->count + 1; size_t block_sz = MAX(required_size, MA_ARENA_NUM_BLOCKS * MA_BLOCK_SIZE); void *chunk = NULL; if (MA_BLOCK_SIZE >= sizeof(void*) && MA_BLOCK_SIZE % sizeof(void*) == 0) { if (posix_memalign(&chunk, MA_BLOCK_SIZE, block_sz) != 0) chunk = NULL; memset(chunk, 0, block_sz); } else chunk = calloc(1, block_sz); if (!chunk) { return NULL; } if (count > self->capacity) { size_t capacity = MAX(8u, 2 * self->capacity); MA_BLOCK_TYPE_NAME *blocks = realloc(self->blocks, capacity * sizeof(MA_BLOCK_TYPE_NAME)); if (!blocks) { free(chunk); return NULL; } self->capacity = capacity; self->blocks = blocks; } self->blocks[count - 1] = (MA_BLOCK_TYPE_NAME){.capacity=block_sz, .buf=chunk}; self->count = count; } char *ans = (char*)self->blocks[self->count-1].buf + self->blocks[self->count-1].used; self->blocks[self->count-1].used += required_size; return ans; } #undef MA_NAME #undef MA_BLOCK_SIZE #undef MA_ARENA_NUM_BLOCKS #undef MA_TYPE_NAME #undef MA_BLOCK_TYPE_NAME #undef MA_CAT #undef MA_CAT_ kitty-0.41.1/kitty/backtrace.h0000664000175000017510000000122514773370543015543 0ustar nileshnilesh/* * Copyright (C) 2022 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include #include #include #include #if __has_include() #include static inline void print_stack_trace(void) { void *array[256]; size_t size; // get void*'s for all entries on the stack size = backtrace(array, 256); // print out all the frames to stderr backtrace_symbols_fd(array, size, STDERR_FILENO); } #else static inline void print_stack_trace(void) { fprintf(stderr, "Stack trace functionality not available.\n"); } #endif kitty-0.41.1/kitty/banned.h0000664000175000017510000000167114773370543015060 0ustar nileshnilesh/* * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #define BANNED(func) sorry_##func##_is_a_banned_function #undef strcpy #define strcpy(x,y) BANNED(strcpy) #undef strcat #define strcat(x,y) BANNED(strcat) #undef strncpy #define strncpy(x,y,n) BANNED(strncpy) #undef strncat #define strncat(x,y,n) BANNED(strncat) #undef sprintf #undef vsprintf #ifdef HAVE_VARIADIC_MACROS #define sprintf(...) BANNED(sprintf) #define vsprintf(...) BANNED(vsprintf) #else #define sprintf(buf,fmt,arg) BANNED(sprintf) #define vsprintf(buf,fmt,arg) BANNED(vsprintf) #endif #undef gmtime #define gmtime(t) BANNED(gmtime) #undef localtime #define localtime(t) BANNED(localtime) #undef ctime #define ctime(t) BANNED(ctime) #undef ctime_r #define ctime_r(t, buf) BANNED(ctime_r) #undef asctime #define asctime(t) BANNED(asctime) #undef asctime_r #define asctime_r(t, buf) BANNED(asctime_r) kitty-0.41.1/kitty/base64.h0000664000175000017510000000235114773370543014711 0ustar nileshnilesh/* * Copyright (C) 2023 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include #include #include #include "../3rdparty/base64/include/libbase64.h" static inline size_t required_buffer_size_for_base64_decode(size_t src_sz) { return (src_sz / 4 * 3 + 2); } static inline size_t required_buffer_size_for_base64_encode(size_t src_sz) { return ((src_sz + 2) / 3 * 4); } static inline bool base64_decode8(const uint8_t *src, size_t src_sz, uint8_t *dest, size_t *dest_sz) { if (*dest_sz < required_buffer_size_for_base64_decode(src_sz)) return false; // we ignore the return value of base64_decode as it returns non-zero when it is // waiting for padding bytes base64_decode((const char*)src, src_sz, (char*)dest, dest_sz, 0); return true; } static inline bool base64_encode8(const unsigned char *src, size_t src_len, unsigned char *out, size_t *out_len, bool add_padding) { if (*out_len < required_buffer_size_for_base64_encode(src_len)) return false; base64_encode((const char*)src, src_len, (char*)out, out_len, 0); if (!add_padding) { while(*out_len && out[*out_len - 1] == '=') *out_len -= 1; } return true; } kitty-0.41.1/kitty/bash.py0000664000175000017510000000330714773370543014745 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2022, Kovid Goyal from .utils import shlex_split def decode_ansi_c_quoted_string(text: str) -> str: return next(shlex_split(text, True)) def decode_double_quoted_string(text: str, pos: int) -> tuple[str, int]: escapes = r'"\$`' buf: list[str] = [] a = buf.append while pos < len(text): ch = text[pos] pos += 1 if ch == '\\': if text[pos] in escapes: a(text[pos]) pos += 1 continue a(ch) elif ch == '"': break else: a(ch) return ''.join(buf), pos def parse_modern_bash_env(text: str) -> dict[str, str]: ans = {} for line in text.splitlines(): idx = line.find('=') if idx < 0: break key = line[:idx].rpartition(' ')[2] val = line[idx+1:] if val.startswith('"'): val = decode_double_quoted_string(val, 1)[0] else: val = decode_ansi_c_quoted_string(val) ans[key] = val return ans def parse_bash_env(text: str, bash_version: str) -> dict[str, str]: # See https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html parts = bash_version.split('.') bv = tuple(map(int, parts[:2])) if bv >= (5, 2): return parse_modern_bash_env(text) ans = {} pos = 0 while pos < len(text): idx = text.find('="', pos) if idx < 0: break i = text.rfind(' ', 0, idx) if i < 0: break key = text[i+1:idx] pos = idx + 2 ans[key], pos = decode_double_quoted_string(text, pos) return ans kitty-0.41.1/kitty/bgimage_fragment.glsl0000664000175000017510000000054014773370543017613 0ustar nileshnilesh uniform sampler2D image; uniform float opacity; uniform float premult; in vec2 texcoord; out vec4 color; void main() { color = texture(image, texcoord); float alpha = color.a * opacity; vec4 premult_color = vec4(color.rgb * alpha, alpha); color = vec4(color.rgb, alpha); color = premult * premult_color + (1 - premult) * color; } kitty-0.41.1/kitty/bgimage_vertex.glsl0000664000175000017510000000232614773370543017331 0ustar nileshnilesh#define left 0 #define top 1 #define right 2 #define bottom 3 #define tex_left 0 #define tex_top 0 #define tex_right 1 #define tex_bottom 1 #define x_axis 0 #define y_axis 1 #define window i #define image i + 2 uniform float tiled; uniform vec4 sizes; // [ window_width, window_height, image_width, image_height ] uniform vec4 positions; // [ left, top, right, bottom ] out vec2 texcoord; const vec2 tex_map[] = vec2[4]( vec2(tex_left, tex_top), vec2(tex_left, tex_bottom), vec2(tex_right, tex_bottom), vec2(tex_right, tex_top) ); float scale_factor(float window_size, float image_size) { return window_size / image_size; } float tiling_factor(int i) { return tiled * scale_factor(sizes[window], sizes[image]) + (1 - tiled); } void main() { vec2 pos_map[] = vec2[4]( vec2(positions[left], positions[top]), vec2(positions[left], positions[bottom]), vec2(positions[right], positions[bottom]), vec2(positions[right], positions[top]) ); vec2 tex_coords = tex_map[gl_VertexID]; texcoord = vec2( tex_coords[x_axis] * tiling_factor(x_axis), tex_coords[y_axis] * tiling_factor(y_axis) ); gl_Position = vec4(pos_map[gl_VertexID], 0, 1); } kitty-0.41.1/kitty/binary.h0000664000175000017510000000414114773370543015110 0ustar nileshnilesh/* * Copyright (C) 2023 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include static inline uint16_t be16dec(const void *pp) { uint8_t const *p = (uint8_t const *)pp; return (((unsigned)p[0] << 8) | p[1]); } static inline uint32_t be32dec(const void *pp) { uint8_t const *p = (uint8_t const *)pp; return (((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3]); } static inline uint64_t be64dec(const void *pp) { uint8_t const *p = (uint8_t const *)pp; return (((uint64_t)be32dec(p) << 32) | be32dec(p + 4)); } static inline uint16_t le16dec(const void *pp) { uint8_t const *p = (uint8_t const *)pp; return (((unsigned)p[1] << 8) | p[0]); } static inline uint32_t le32dec(const void *pp) { uint8_t const *p = (uint8_t const *)pp; return (((uint32_t)p[3] << 24) | ((uint32_t)p[2] << 16) | ((uint32_t)p[1] << 8) | p[0]); } static inline uint64_t le64dec(const void *pp) { uint8_t const *p = (uint8_t const *)pp; return (((uint64_t)le32dec(p + 4) << 32) | le32dec(p)); } static inline void be16enc(void *pp, uint16_t u) { uint8_t *p = (uint8_t *)pp; p[0] = (u >> 8) & 0xff; p[1] = u & 0xff; } static inline void be32enc(void *pp, uint32_t u) { uint8_t *p = (uint8_t *)pp; p[0] = (u >> 24) & 0xff; p[1] = (u >> 16) & 0xff; p[2] = (u >> 8) & 0xff; p[3] = u & 0xff; } static inline void be64enc(void *pp, uint64_t u) { uint8_t *p = (uint8_t *)pp; be32enc(p, (uint32_t)(u >> 32)); be32enc(p + 4, (uint32_t)(u & 0xffffffffU)); } static inline void le16enc(void *pp, uint16_t u) { uint8_t *p = (uint8_t *)pp; p[0] = u & 0xff; p[1] = (u >> 8) & 0xff; } static inline void le32enc(void *pp, uint32_t u) { uint8_t *p = (uint8_t *)pp; p[0] = u & 0xff; p[1] = (u >> 8) & 0xff; p[2] = (u >> 16) & 0xff; p[3] = (u >> 24) & 0xff; } static inline void le64enc(void *pp, uint64_t u) { uint8_t *p = (uint8_t *)pp; le32enc(p, (uint32_t)(u & 0xffffffffU)); le32enc(p + 4, (uint32_t)(u >> 32)); } kitty-0.41.1/kitty/border_fragment.glsl0000664000175000017510000000011714773370543017475 0ustar nileshnileshin vec4 color; out vec4 final_color; void main() { final_color = color; } kitty-0.41.1/kitty/border_vertex.glsl0000664000175000017510000000344314773370543017214 0ustar nileshnileshuniform uvec2 viewport; uniform uint colors[9]; uniform float background_opacity; uniform float tint_opacity, tint_premult; uniform float gamma_lut[256]; in vec4 rect; // left, top, right, bottom in uint rect_color; out vec4 color; // indices into the rect vector const int LEFT = 0; const int TOP = 1; const int RIGHT = 2; const int BOTTOM = 3; const uint FF = uint(0xff); const uvec2 pos_map[] = uvec2[4]( uvec2(RIGHT, TOP), uvec2(RIGHT, BOTTOM), uvec2(LEFT, BOTTOM), uvec2(LEFT, TOP) ); float to_color(uint c) { return gamma_lut[c & FF]; } float is_integer_value(uint c, float x) { return 1. - step(0.5, abs(float(c) - x)); } vec3 as_color_vector(uint c, int shift) { return vec3(to_color(c >> shift), to_color(c >> (shift - 8)), to_color(c >> (shift - 16))); } void main() { uvec2 pos = pos_map[gl_VertexID]; gl_Position = vec4(rect[pos.x], rect[pos.y], 0, 1); vec3 window_bg = as_color_vector(rect_color, 24); uint rc = rect_color & FF; vec3 color3 = as_color_vector(colors[rc], 16); float is_window_bg = is_integer_value(rc, 3.); float is_default_bg = is_integer_value(rc, 0.); color3 = is_window_bg * window_bg + (1. - is_window_bg) * color3; // Border must be always drawn opaque float is_border_bg = 1. - step(0.5, abs((float(rc) - 2.) * (float(rc) - 1.) * (float(rc) - 4.))); // 1 if rc in (1, 2, 4) else 0 float final_opacity = is_default_bg * tint_opacity + (1. - is_default_bg) * background_opacity; final_opacity = is_border_bg + (1. - is_border_bg) * final_opacity; float final_premult_opacity = is_default_bg * tint_premult + (1. - is_default_bg) * background_opacity; final_premult_opacity = is_border_bg + (1. - is_border_bg) * final_premult_opacity; color = vec4(color3 * final_premult_opacity, final_opacity); } kitty-0.41.1/kitty/borders.py0000664000175000017510000001132414773370543015466 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal from collections.abc import Iterable, Sequence from enum import IntFlag from typing import NamedTuple from .fast_data_types import BORDERS_PROGRAM, add_borders_rect, get_options, init_borders_program, os_window_has_background_image from .shaders import program_for from .typing import LayoutType from .utils import color_as_int from .window_list import WindowGroup, WindowList class BorderColor(IntFlag): # These are indices into the array of colors in the border vertex shader default_bg, active, inactive, window_bg, bell, tab_bar_bg, tab_bar_margin_color, tab_bar_left_edge_color, tab_bar_right_edge_color = range(9) class Border(NamedTuple): left: int top: int right: int bottom: int color: BorderColor def vertical_edge(os_window_id: int, tab_id: int, color: int, width: int, top: int, bottom: int, left: int) -> None: if width > 0: add_borders_rect(os_window_id, tab_id, left, top, left + width, bottom, color) def horizontal_edge(os_window_id: int, tab_id: int, color: int, height: int, left: int, right: int, top: int) -> None: if height > 0: add_borders_rect(os_window_id, tab_id, left, top, right, top + height, color) def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], wg: WindowGroup, borders: bool = False) -> None: geometry = wg.geometry if geometry is None: return pl, pt = wg.effective_padding('left'), wg.effective_padding('top') pr, pb = wg.effective_padding('right'), wg.effective_padding('bottom') left = geometry.left - pl top = geometry.top - pt lr = geometry.right right = lr + pr bt = geometry.bottom bottom = bt + pb if borders: width = wg.effective_border() bt = bottom lr = right left -= width top -= width right += width bottom += width pl = pr = pb = pt = width horizontal_edge(os_window_id, tab_id, colors[1], pt, left, right, top) horizontal_edge(os_window_id, tab_id, colors[3], pb, left, right, bt) vertical_edge(os_window_id, tab_id, colors[0], pl, top, bottom, left) vertical_edge(os_window_id, tab_id, colors[2], pr, top, bottom, lr) def load_borders_program() -> None: program_for('border').compile(BORDERS_PROGRAM) init_borders_program() class Borders: def __init__(self, os_window_id: int, tab_id: int): self.os_window_id = os_window_id self.tab_id = tab_id def __call__( self, all_windows: WindowList, current_layout: LayoutType, tab_bar_rects: Iterable[Border], draw_window_borders: bool = True, ) -> None: opts = get_options() draw_active_borders = opts.active_border_color is not None draw_minimal_borders = opts.draw_minimal_borders and max(opts.window_margin_width) < 1 add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, BorderColor.default_bg) has_background_image = os_window_has_background_image(self.os_window_id) if not has_background_image or opts.background_tint > 0.0: for br in current_layout.blank_rects: add_borders_rect(self.os_window_id, self.tab_id, *br, BorderColor.default_bg) for tbr in tab_bar_rects: add_borders_rect(self.os_window_id, self.tab_id, *tbr) bw = 0 groups = tuple(all_windows.iter_all_layoutable_groups(only_visible=True)) if groups: bw = groups[0].effective_border() draw_borders = bw > 0 and draw_window_borders active_group = all_windows.active_group for i, wg in enumerate(groups): window_bg = color_as_int(wg.default_bg) window_bg = (window_bg << 8) | BorderColor.window_bg if draw_borders and not draw_minimal_borders: # Draw the border rectangles if wg is active_group and draw_active_borders: color = BorderColor.active else: color = BorderColor.bell if wg.needs_attention else BorderColor.inactive draw_edges(self.os_window_id, self.tab_id, (color, color, color, color), wg, borders=True) if not has_background_image: # Draw the background rectangles over the padding region colors = window_bg, window_bg, window_bg, window_bg draw_edges(self.os_window_id, self.tab_id, colors, wg) if draw_minimal_borders: for border_line in current_layout.get_minimal_borders(all_windows): left, top, right, bottom = border_line.edges add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, border_line.color) kitty-0.41.1/kitty/boss.py0000664000175000017510000040625514773370543015007 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal # Imports {{{ import base64 import json import os import re import socket import subprocess import sys from collections.abc import Callable, Container, Generator, Iterable, Iterator, Sequence from contextlib import contextmanager, suppress from functools import partial from gettext import gettext as _ from gettext import ngettext from time import sleep from typing import ( TYPE_CHECKING, Any, Literal, Optional, Union, ) from weakref import WeakValueDictionary from .child import cached_process_data, default_env, set_default_env from .cli import create_opts, green, parse_args from .cli_stub import CLIOptions from .clipboard import ( Clipboard, ClipboardType, get_clipboard_string, get_primary_selection, set_clipboard_string, set_primary_selection, ) from .colors import ColorSchemes, theme_colors from .conf.utils import BadLine, KeyAction, to_cmdline from .config import common_opts_as_dict, prepare_config_file_for_editing, store_effective_config from .constants import ( RC_ENCRYPTION_PROTOCOL_VERSION, appname, cache_dir, clear_handled_signals, config_dir, handled_signals, is_macos, is_wayland, kitten_exe, kitty_exe, logo_png_file, supports_primary_selection, website_url, ) from .fast_data_types import ( CLOSE_BEING_CONFIRMED, GLFW_MOD_ALT, GLFW_MOD_CONTROL, GLFW_MOD_SHIFT, GLFW_MOD_SUPER, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED, ChildMonitor, Color, EllipticCurveKey, KeyEvent, SingleKey, add_timer, apply_options_update, background_opacity_of, change_background_opacity, cocoa_hide_app, cocoa_hide_other_apps, cocoa_minimize_os_window, cocoa_set_menubar_title, create_os_window, current_application_quit_request, current_focused_os_window_id, current_os_window, destroy_global_data, focus_os_window, get_boss, get_options, get_os_window_size, global_font_size, last_focused_os_window_id, mark_os_window_for_close, monitor_pid, monotonic, os_window_focus_counters, os_window_font_size, redirect_mouse_handling, ring_bell, run_with_activation_token, safe_pipe, send_data_to_peer, set_application_quit_request, set_background_image, set_boss, set_options, set_os_window_chrome, set_os_window_size, set_os_window_title, thread_write, toggle_fullscreen, toggle_maximized, toggle_secure_input, wrapped_kitten_names, ) from .key_encoding import get_name_to_functional_number_map from .keys import Mappings from .layout.base import set_layout_options from .notifications import NotificationManager from .options.types import Options, nullable_colors from .options.utils import MINIMUM_FONT_SIZE, KeyboardMode, KeyDefinition from .os_window_size import initial_window_size_func from .session import Session, create_sessions, get_os_window_sizing_data from .shaders import load_shader_programs from .tabs import SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager from .types import _T, AsyncResponse, SingleInstanceData, WindowSystemMouseEvent, ac from .typing import PopenType, TypedDict from .utils import ( cleanup_ssh_control_masters, func_name, get_editor, get_new_os_window_size, is_ok_to_read_image_file, is_path_in_temp_dir, less_version, log_error, macos_version, open_url, parse_address_spec, parse_os_window_state, parse_uri_list, platform_window_id, safe_print, sanitize_url_for_dispay_to_user, startup_notification_handler, timed_debug_print, which, ) from .window import CommandOutput, CwdRequest, Window if TYPE_CHECKING: from .rc.base import ResponseType # }}} RCResponse = Union[dict[str, Any], None, AsyncResponse] class OSWindowDict(TypedDict): id: int platform_window_id: int | None is_focused: bool is_active: bool last_focused: bool tabs: list[TabDict] wm_class: str wm_name: str background_opacity: float class Atexit: def __init__(self) -> None: self.worker: subprocess.Popen[bytes] | None = None def _write_line(self, line: str) -> None: if '\n' in line: raise ValueError('Newlines not allowed in atexit arguments: {path!r}') w = self.worker if w is None: w = self.worker = subprocess.Popen([kitten_exe(), '__atexit__'], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, close_fds=True) assert w.stdin is not None os.set_inheritable(w.stdin.fileno(), False) assert w.stdin is not None w.stdin.write((line + '\n').encode()) w.stdin.flush() def unlink(self, path: str) -> None: self._write_line(f'unlink {path}') def shm_unlink(self, path: str) -> None: self._write_line(f'shm_unlink {path}') def rmtree(self, path: str) -> None: self._write_line(f'rmtree {path}') def listen_on(spec: str, robust_atexit: Atexit) -> tuple[int, str]: import socket family, address, socket_path = parse_address_spec(spec) s = socket.socket(family) s.bind(address) if family == socket.AF_UNIX and socket_path: robust_atexit.unlink(socket_path) s.listen() if isinstance(address, tuple): # tcp socket h, resolved_port = s.getsockname()[:2] spec = spec.rpartition(':')[0] + f':{resolved_port}' import atexit atexit.register(s.close) # prevents s from being garbage collected return s.fileno(), spec def data_for_at(w: Window | None, arg: str, add_wrap_markers: bool = False) -> str | None: if not w: return None def as_text(**kw: bool) -> str: kw['add_wrap_markers'] = add_wrap_markers return w.as_text(**kw) if w else '' if arg == '@selection': return w.text_for_selection() if arg in ('@ansi', '@ansi_screen_scrollback'): return as_text(as_ansi=True, add_history=True) if arg in ('@text', '@screen_scrollback'): return as_text(add_history=True) if arg == '@screen': return as_text() if arg == '@ansi_screen': return as_text(as_ansi=True) if arg == '@alternate': return as_text(alternate_screen=True) if arg == '@alternate_scrollback': return as_text(alternate_screen=True, add_history=True) if arg == '@ansi_alternate': return as_text(as_ansi=True, alternate_screen=True) if arg == '@ansi_alternate_scrollback': return as_text(as_ansi=True, alternate_screen=True, add_history=True) if arg == '@first_cmd_output_on_screen': return w.cmd_output(CommandOutput.first_on_screen, add_wrap_markers=add_wrap_markers) if arg == '@ansi_first_cmd_output_on_screen': return w.cmd_output(CommandOutput.first_on_screen, as_ansi=True, add_wrap_markers=add_wrap_markers) if arg == '@last_cmd_output': return w.cmd_output(CommandOutput.last_run, add_wrap_markers=add_wrap_markers) if arg == '@ansi_last_cmd_output': return w.cmd_output(CommandOutput.last_run, as_ansi=True, add_wrap_markers=add_wrap_markers) if arg == '@last_visited_cmd_output': return w.cmd_output(CommandOutput.last_visited, add_wrap_markers=add_wrap_markers) if arg == '@ansi_last_visited_cmd_output': return w.cmd_output(CommandOutput.last_visited, as_ansi=True, add_wrap_markers=add_wrap_markers) return None class DumpCommands: # {{{ def __init__(self, args: CLIOptions): self.draw_dump_buf: list[str] = [] if args.dump_bytes: self.dump_bytes_to = open(args.dump_bytes, 'wb') def __call__(self, window_id: int, what: str, *a: Any) -> None: if what == 'draw': self.draw_dump_buf.append(a[0]) elif what == 'bytes': self.dump_bytes_to.write(a[0]) self.dump_bytes_to.flush() elif what == 'error': log_error(*a) else: if self.draw_dump_buf: safe_print('draw', ''.join(self.draw_dump_buf)) self.draw_dump_buf = [] def fmt(x: Any) -> Any: if isinstance(x, (bytes, memoryview)): return str(x, 'utf-8', 'replace') if isinstance(x, dict): return json.dumps(x) return x safe_print(what, *map(fmt, a)) # }}} class VisualSelect: def __init__( self, tab_id: int, os_window_id: int, prev_tab_id: int | None, prev_os_window_id: int | None, title: str, callback: Callable[[Tab | None, Window | None], None], reactivate_prev_tab: bool ) -> None: self.tab_id = tab_id self.os_window_id = os_window_id self.prev_tab_id = prev_tab_id self.prev_os_window_id = prev_os_window_id self.callback = callback self.window_ids: list[int] = [] self.window_used_for_selection_id = 0 self.reactivate_prev_tab = reactivate_prev_tab set_os_window_title(self.os_window_id, title) def cancel(self) -> None: self.clear_global_state() self.activate_prev_tab() self.callback(None, None) def trigger(self, window_id: int) -> None: boss = self.clear_global_state() self.activate_prev_tab() w = boss.window_id_map.get(window_id) if w is None: self.callback(None, None) else: tab = w.tabref() if tab is None: self.callback(None, None) else: self.callback(tab, w) def clear_global_state(self) -> 'Boss': set_os_window_title(self.os_window_id, '') boss = get_boss() redirect_mouse_handling(False) for wid in self.window_ids: w = boss.window_id_map.get(wid) if w is not None: w.screen.set_window_char() if self.window_used_for_selection_id: w = boss.window_id_map.get(self.window_used_for_selection_id) if w is not None: boss.mark_window_for_close(w) return boss def activate_prev_tab(self) -> None: if not self.reactivate_prev_tab or self.prev_tab_id is None: return None boss = get_boss() tm = boss.os_window_map.get(self.os_window_id) if tm is not None: t = tm.tab_for_id(self.prev_tab_id) if t is not tm.active_tab and t is not None: tm.set_active_tab(t) if current_focused_os_window_id() != self.prev_os_window_id and self.prev_os_window_id is not None: focus_os_window(self.prev_os_window_id, True) class Boss: def __init__( self, opts: Options, args: CLIOptions, cached_values: dict[str, Any], global_shortcuts: dict[str, SingleKey], talk_fd: int = -1, ): self.atexit = Atexit() set_layout_options(opts) self.clipboard = Clipboard() self.window_for_dispatch: Window | None = None self.primary_selection = Clipboard(ClipboardType.primary_selection) self.update_check_started = False self.peer_data_map: dict[int, dict[str, Sequence[str]] | None] = {} self.background_process_death_notify_map: dict[int, Callable[[int, Exception | None], None]] = {} self.encryption_key = EllipticCurveKey() self.encryption_public_key = f'{RC_ENCRYPTION_PROTOCOL_VERSION}:{base64.b85encode(self.encryption_key.public).decode("ascii")}' self.clipboard_buffers: dict[str, str] = {} self.update_check_process: Optional['PopenType[bytes]'] = None self.window_id_map: WeakValueDictionary[int, Window] = WeakValueDictionary() self.color_settings_at_startup: dict[str, Color | None] = { k: opts[k] for k in opts if isinstance(opts[k], Color) or k in nullable_colors} self.current_visual_select: VisualSelect | None = None # A list of events received so far that are potentially part of a sequence keybinding. self.cached_values = cached_values self.os_window_map: dict[int, TabManager] = {} self.os_window_death_actions: dict[int, Callable[[], None]] = {} self.cursor_blinking = True self.shutting_down = False self.misc_config_errors: list[str] = [] # we dont allow reloading the config file to change # allow_remote_control self.allow_remote_control = opts.allow_remote_control if self.allow_remote_control in ('y', 'yes', 'true'): self.allow_remote_control = 'y' elif self.allow_remote_control in ('n', 'no', 'false'): self.allow_remote_control = 'n' self.listening_on: str = '' listen_fd = -1 if args.listen_on and self.allow_remote_control in ('y', 'socket', 'socket-only', 'password'): try: listen_fd, self.listening_on = listen_on(args.listen_on, self.atexit) except Exception: self.misc_config_errors.append(f'Invalid listen_on={args.listen_on}, ignoring') log_error(self.misc_config_errors[-1]) self.child_monitor: ChildMonitor = ChildMonitor( self.on_child_death, DumpCommands(args) if args.dump_commands or args.dump_bytes else None, talk_fd, listen_fd, self.listening_on.startswith('unix:') ) self.args: CLIOptions = args self.mouse_handler: Callable[[WindowSystemMouseEvent], None] | None = None set_boss(self) self.mappings: Mappings = Mappings(global_shortcuts, self.refresh_active_tab_bar) self.notification_manager: NotificationManager = NotificationManager(debug=self.args.debug_keyboard or self.args.debug_rendering) self.atexit.unlink(store_effective_config()) def startup_first_child(self, os_window_id: int | None, startup_sessions: Iterable[Session] = ()) -> None: si = startup_sessions or create_sessions(get_options(), self.args, default_session=get_options().startup_session) focused_os_window = wid = 0 token = os.environ.pop('XDG_ACTIVATION_TOKEN', '') with Window.set_ignore_focus_changes_for_new_windows(): for startup_session in si: # The window state from the CLI options will override and apply to every single OS window in startup session wstate = self.args.start_as if self.args.start_as and self.args.start_as != 'normal' else None wid = self.add_os_window(startup_session, window_state=wstate, os_window_id=os_window_id) if startup_session.focus_os_window: focused_os_window = wid os_window_id = None if focused_os_window > 0: focus_os_window(focused_os_window, True, token) elif token and is_wayland() and wid: focus_os_window(wid, True, token) for w in self.all_windows: w.ignore_focus_changes = False def add_os_window( self, startup_session: Session | None = None, os_window_id: int | None = None, wclass: str | None = None, wname: str | None = None, window_state: str | None = None, opts_for_size: Options | None = None, startup_id: str | None = None, override_title: str | None = None, ) -> int: if os_window_id is None: size_data = get_os_window_sizing_data(opts_for_size or get_options(), startup_session) wclass = wclass or getattr(startup_session, 'os_window_class', None) or self.args.cls or appname wname = wname or getattr(startup_session, 'os_window_name', None) or self.args.name or wclass wtitle = override_title or self.args.title window_state = window_state or getattr(startup_session, 'os_window_state', None) wstate = parse_os_window_state(window_state) if window_state is not None else None with startup_notification_handler(do_notify=startup_id is not None, startup_id=startup_id) as pre_show_callback: os_window_id = create_os_window( initial_window_size_func(size_data, self.cached_values), pre_show_callback, wtitle or appname, wname, wclass, wstate, disallow_override_title=bool(wtitle)) else: wname = self.args.name or self.args.cls or appname wclass = self.args.cls or appname tm = TabManager(os_window_id, self.args, wclass, wname, startup_session) self.os_window_map[os_window_id] = tm return os_window_id def list_os_windows( self, self_window: Window | None = None, tab_filter: Callable[[Tab], bool] | None = None, window_filter: Callable[[Window], bool] | None = None ) -> Iterator[OSWindowDict]: with cached_process_data(): active_tab_manager = self.active_tab_manager focused_wid = current_focused_os_window_id() last_focused = last_focused_os_window_id() for os_window_id, tm in self.os_window_map.items(): tabs = list(tm.list_tabs(self_window, tab_filter, window_filter)) if tabs: bo = background_opacity_of(os_window_id) if bo is None: bo = 1 yield { 'id': os_window_id, 'platform_window_id': platform_window_id(os_window_id), 'is_active': tm is active_tab_manager, 'is_focused': focused_wid == os_window_id, 'last_focused': os_window_id == last_focused, 'tabs': tabs, 'wm_class': tm.wm_class, 'wm_name': tm.wm_name, 'background_opacity': bo, } @property def all_tab_managers(self) -> Iterator[TabManager]: yield from self.os_window_map.values() @property def all_tabs(self) -> Iterator[Tab]: for tm in self.all_tab_managers: yield from tm @property def all_windows(self) -> Iterator[Window]: for tab in self.all_tabs: yield from tab def match_windows(self, match: str, self_window: Optional['Window'] = None) -> Iterator[Window]: if match == 'all': yield from self.all_windows return from .search_query_parser import search tab = self.active_tab if current_focused_os_window_id() <= 0: tm = self.os_window_map.get(last_focused_os_window_id()) if tm is not None: tab = tm.active_tab window_id_limit = max(self.window_id_map, default=-1) + 1 def get_matches(location: str, query: str, candidates: set[int]) -> set[int]: if location == 'id' and query.startswith('-'): try: q = int(query) except Exception: return set() if q < 0: query = str(window_id_limit + q) return {wid for wid in candidates if self.window_id_map[wid].matches_query(location, query, tab, self_window)} for wid in search(match, ( 'id', 'title', 'pid', 'cwd', 'cmdline', 'num', 'env', 'var', 'recent', 'state', 'neighbor', ), set(self.window_id_map), get_matches): yield self.window_id_map[wid] def tab_for_window(self, window: Window) -> Tab | None: for tab in self.all_tabs: for w in tab: if w.id == window.id: return tab return None def match_tabs(self, match: str) -> Iterator[Tab]: if match == 'all': yield from self.all_tabs return from .search_query_parser import search tm = self.active_tab_manager if current_focused_os_window_id() <= 0: tm = self.os_window_map.get(last_focused_os_window_id()) or tm tim = {t.id: t for t in self.all_tabs} tab_id_limit = max(tim, default=-1) + 1 window_id_limit = max(self.window_id_map, default=-1) + 1 def get_matches(location: str, query: str, candidates: set[int]) -> set[int]: if location in ('id', 'window_id') and query.startswith('-'): try: q = int(query) except Exception: return set() if q < 0: limit = tab_id_limit if location == 'id' else window_id_limit query = str(limit + q) return {wid for wid in candidates if tim[wid].matches_query(location, query, tm)} found = False for tid in search(match, ( 'id', 'index', 'title', 'window_id', 'window_title', 'pid', 'cwd', 'env', 'var', 'cmdline', 'recent', 'state' ), set(tim), get_matches): found = True yield tim[tid] if not found: tabs = {self.tab_for_window(w) for w in self.match_windows(match)} for q in tabs: if q: yield q def set_active_window( self, window: Window, switch_os_window_if_needed: bool = False, for_keep_focus: bool = False, activation_token: str = '' ) -> int | None: for os_window_id, tm in self.os_window_map.items(): for tab in tm: for w in tab: if w.id == window.id: if tab is not self.active_tab: tm.set_active_tab(tab, for_keep_focus=window.tabref() if for_keep_focus else None) tab.set_active_window(w, for_keep_focus=window if for_keep_focus else None) if activation_token or (switch_os_window_if_needed and current_focused_os_window_id() != os_window_id): focus_os_window(os_window_id, True, activation_token) return os_window_id return None def _new_os_window(self, args: SpecialWindowInstance | Iterable[str], cwd_from: CwdRequest | None = None) -> int: if isinstance(args, SpecialWindowInstance): sw: SpecialWindowInstance | None = args else: sw = self.args_to_special_window(args, cwd_from) if args else None startup_session = next(create_sessions(get_options(), special_window=sw, cwd_from=cwd_from)) return self.add_os_window(startup_session) @ac('win', 'New OS Window') def new_os_window(self, *args: str) -> None: self._new_os_window(args) @property def active_window_for_cwd(self) -> Window | None: t = self.active_tab if t is not None: return t.active_window_for_cwd return None @ac('win', 'New OS Window with the same working directory as the currently active window') def new_os_window_with_cwd(self, *args: str) -> None: w = self.window_for_dispatch or self.active_window_for_cwd self._new_os_window(args, CwdRequest(w)) def new_os_window_with_wd(self, wd: str | list[str], str_is_multiple_paths: bool = False) -> None: if isinstance(wd, str): wd = wd.split(os.pathsep) if str_is_multiple_paths else [wd] for path in wd: special_window = SpecialWindow(None, cwd=path) self._new_os_window(special_window) def add_child(self, window: Window) -> None: assert window.child.pid is not None and window.child.child_fd is not None self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen) self.window_id_map[window.id] = window def _handle_remote_command(self, cmd: memoryview, window: Window | None = None, peer_id: int = 0) -> RCResponse: from .remote_control import is_cmd_allowed, parse_cmd, remote_control_allowed response = None window = window or None from_socket = peer_id > 0 is_fd_peer = from_socket and peer_id in self.peer_data_map window_has_remote_control = bool(window and window.allow_remote_control) if not window_has_remote_control and not is_fd_peer: if self.allow_remote_control == 'n': return {'ok': False, 'error': 'Remote control is disabled'} if self.allow_remote_control == 'socket-only' and not from_socket: return {'ok': False, 'error': 'Remote control is allowed over a socket only'} try: pcmd = parse_cmd(cmd, self.encryption_key) except Exception as e: log_error(f'Failed to parse remote command with error: {e}') return response if not pcmd: return response self_window: Window | None = None if window is not None: self_window = window else: try: swid = int(pcmd.get('kitty_window_id', 0)) except Exception: pass else: if swid > 0: self_window = self.window_id_map.get(swid) extra_data: dict[str, Any] = {} try: allowed_unconditionally = ( self.allow_remote_control == 'y' or (from_socket and not is_fd_peer and self.allow_remote_control in ('socket-only', 'socket')) or (window and window.remote_control_allowed(pcmd, extra_data)) or (is_fd_peer and remote_control_allowed(pcmd, self.peer_data_map.get(peer_id), None, extra_data)) ) except PermissionError: return {'ok': False, 'error': 'Remote control disallowed by window specific password'} if allowed_unconditionally: return self._execute_remote_command(pcmd, window, peer_id, self_window) q = is_cmd_allowed(pcmd, window, from_socket, extra_data) if q is True: return self._execute_remote_command(pcmd, window, peer_id, self_window) if q is None: if self.ask_if_remote_cmd_is_allowed(pcmd, window, peer_id, self_window): return AsyncResponse() response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'} if q is False and pcmd.get('password'): response['error'] = 'The user rejected this password or it is disallowed by remote_control_password in kitty.conf' no_response = pcmd.get('no_response') or False if no_response: return None return response def ask_if_remote_cmd_is_allowed( self, pcmd: dict[str, Any], window: Window | None = None, peer_id: int = 0, self_window: Window | None = None ) -> bool: from kittens.tui.operations import styled in_flight = 0 for w in self.window_id_map.values(): if w.window_custom_type == 'remote_command_permission_dialog': in_flight += 1 if in_flight > 4: log_error('Denying remote command permission as there are too many existing permission requests') return False wid = 0 if window is None else window.id hidden_text = styled(pcmd['password'], fg='yellow') overlay_window = self.choose( _('A program wishes to control kitty.\n' 'Action: {1}\n' 'Password: {0}\n\n' '{2}' ).format( hidden_text, styled(pcmd['cmd'], fg='magenta'), '\x1b[m' + styled(_( 'Note that allowing the password will allow all future actions using the same password, in this kitty instance.' ), dim=True, italic=True)), partial(self.remote_cmd_permission_received, pcmd, wid, peer_id, self_window), 'a;green:Allow request', 'p;yellow:Allow password', 'r;magenta:Deny request', 'd;red:Deny password', window=window, default='a', hidden_text=hidden_text, title=_('Allow remote control?'), ) if overlay_window is None: return False overlay_window.window_custom_type = 'remote_command_permission_dialog' return True def remote_cmd_permission_received(self, pcmd: dict[str, Any], window_id: int, peer_id: int, self_window: Window | None, choice: str) -> None: from .remote_control import encode_response_for_peer, set_user_password_allowed response: RCResponse = None window = self.window_id_map.get(window_id) choice = choice or 'r' if choice in ('r', 'd'): if choice == 'd': set_user_password_allowed(pcmd['password'], False) no_response = pcmd.get('no_response') or False if not no_response: response = {'ok': False, 'error': 'The user rejected this ' + ('request' if choice == 'r' else 'password')} elif choice in ('a', 'p'): if choice == 'p': set_user_password_allowed(pcmd['password'], True) response = self._execute_remote_command(pcmd, window, peer_id, self_window) if window is not None and response is not None and not isinstance(response, AsyncResponse): window.send_cmd_response(response) if peer_id > 0: if response is None: send_data_to_peer(peer_id, b'') elif not isinstance(response, AsyncResponse): send_data_to_peer(peer_id, encode_response_for_peer(response)) def _execute_remote_command( self, pcmd: dict[str, Any], window: Window | None = None, peer_id: int = 0, self_window: Window | None = None ) -> RCResponse: from .remote_control import handle_cmd try: response = handle_cmd(self, window, pcmd, peer_id, self_window) except Exception as err: import traceback response = {'ok': False, 'error': str(err)} if not getattr(err, 'hide_traceback', False): response['tb'] = traceback.format_exc() return response @ac('misc', ''' Run a remote control command without needing to allow remote control For example:: map f1 remote_control set-spacing margin=30 See :ref:`rc_mapping` for details. ''') def remote_control(self, *args: str) -> None: try: self.call_remote_control(self.window_for_dispatch or self.active_window, args) except (Exception, SystemExit) as e: import shlex self.show_error(_('remote_control mapping failed'), shlex.join(args) + '\n' + str(e)) @ac('misc', ''' Run a remote control script without needing to allow remote control For example:: map f1 remote_control_script /path/to/script arg1 arg2 ... See :ref:`rc_mapping` for details. ''') def remote_control_script(self, path: str, *args: str) -> None: path = which(path) or path if not os.access(path, os.X_OK): self.show_error('Remote control script not executable', f'The script {path} is not executable check its permissions') return self.run_background_process([path] + list(args), allow_remote_control=True) def call_remote_control(self, self_window: Window | None, args: tuple[str, ...]) -> 'ResponseType': from .rc.base import PayloadGetter, command_for_name, parse_subcommand_cli from .remote_control import parse_rc_args aa = list(args) silent = False if aa and aa[0].startswith('!'): aa[0] = aa[0][1:] silent = True try: global_opts, items = parse_rc_args(['@'] + aa) if not items: return None cmd = items[0] c = command_for_name(cmd) opts, items = parse_subcommand_cli(c, items) payload = c.message_to_kitty(global_opts, opts, items) except SystemExit as e: raise Exception(str(e)) from e import types try: if isinstance(payload, types.GeneratorType): for x in payload: c.response_from_kitty(self, self_window, PayloadGetter(c, x if isinstance(x, dict) else {})) return None return c.response_from_kitty(self, self_window, PayloadGetter(c, payload if isinstance(payload, dict) else {})) except Exception as e: if silent: log_error(f'Failed to run remote_control mapping: {aa} with error: {e}') return None raise def peer_message_received(self, msg_bytes: bytes, peer_id: int, is_remote_control: bool) -> bytes | bool | None: if peer_id > 0 and msg_bytes == b'peer_death': self.peer_data_map.pop(peer_id, None) return False if is_remote_control: cmd_prefix = b'\x1bP@kitty-cmd' terminator = b'\x1b\\' if msg_bytes.startswith(cmd_prefix) and msg_bytes.endswith(terminator): cmd = memoryview(msg_bytes)[len(cmd_prefix):-len(terminator)] response = self._handle_remote_command(cmd, peer_id=peer_id) if response is None: return None if isinstance(response, AsyncResponse): return True from kitty.remote_control import encode_response_for_peer return encode_response_for_peer(response) log_error('Malformatted remote control message received from peer, ignoring') return None try: data:SingleInstanceData = json.loads(msg_bytes.decode('utf-8')) except Exception: log_error('Malformed command received over single instance socket, ignoring') return None if isinstance(data, dict) and data.get('cmd') == 'new_instance': from .cli_stub import CLIOptions startup_id = data['environ'].get('DESKTOP_STARTUP_ID', '') activation_token = data['environ'].get('XDG_ACTIVATION_TOKEN', '') args, rest = parse_args(list(data['args'][1:]), result_class=CLIOptions) cmdline_args_for_open = data.get('cmdline_args_for_open') if cmdline_args_for_open: self.launch_urls(*cmdline_args_for_open, no_replace_window=True) return None args.args = rest opts = create_opts(args) if data['session_data']: if data['session_data'] == 'none': args.session = 'none' else: from .session import PreReadSession args.session = PreReadSession(data['session_data'], data['environ']) else: args.session = '' if not os.path.isabs(args.directory): args.directory = os.path.join(data['cwd'], args.directory) focused_os_window = os_window_id = 0 for session in create_sessions(opts, args, respect_cwd=True): if not session.has_non_background_processes: # background only do not create and OS Window from .launch import LaunchSpec, launch for tab in session.tabs: for window in tab.windows: if window.is_background_process: assert isinstance(window.launch_spec, LaunchSpec) launch(get_boss(), window.launch_spec.opts, window.launch_spec.args) continue os_window_id = self.add_os_window( session, wclass=args.cls, wname=args.name, opts_for_size=opts, startup_id=startup_id, override_title=args.title or None) if session.focus_os_window: focused_os_window = os_window_id if opts.background_opacity != get_options().background_opacity: self._set_os_window_background_opacity(os_window_id, opts.background_opacity) if n := data.get('notify_on_os_window_death'): self.os_window_death_actions[os_window_id] = partial(self.notify_on_os_window_death, n) if focused_os_window > 0: focus_os_window(focused_os_window, True, activation_token) elif activation_token and is_wayland() and os_window_id: focus_os_window(os_window_id, True, activation_token) else: log_error('Unknown message received over single instance socket, ignoring') return None def handle_remote_cmd(self, cmd: memoryview, window: Window | None = None) -> None: response = self._handle_remote_command(cmd, window) if response is not None and not isinstance(response, AsyncResponse) and window is not None: window.send_cmd_response(response) def mark_os_window_for_close(self, os_window_id: int, request_type: int = IMPERATIVE_CLOSE_REQUESTED) -> None: if self.current_visual_select is not None and self.current_visual_select.os_window_id == os_window_id and request_type == IMPERATIVE_CLOSE_REQUESTED: self.cancel_current_visual_select() mark_os_window_for_close(os_window_id, request_type) def _cleanup_tab_after_window_removal(self, src_tab: Tab) -> None: if len(src_tab) < 1: tm = src_tab.tab_manager_ref() if tm is not None: tm.remove(src_tab) src_tab.destroy() if len(tm) == 0: if not self.shutting_down: self.mark_os_window_for_close(src_tab.os_window_id) @contextmanager def suppress_focus_change_events(self) -> Generator[None, None, None]: changes = {} for w in self.window_id_map.values(): changes[w] = w.ignore_focus_changes w.ignore_focus_changes = True try: yield finally: for w, val in changes.items(): w.ignore_focus_changes = val def on_child_death(self, window_id: int) -> None: prev_active_window = self.active_window window = self.window_id_map.pop(window_id, None) if window is None: return with self.suppress_focus_change_events(): for close_action in window.actions_on_close: try: close_action(window) except Exception: import traceback traceback.print_exc() os_window_id = window.os_window_id window.destroy() tm = self.os_window_map.get(os_window_id) tab = None if tm is not None: for q in tm: if window in q: tab = q break if tab is not None: tab.remove_window(window) self._cleanup_tab_after_window_removal(tab) for removal_action in window.actions_on_removal: try: removal_action(window) except Exception: import traceback traceback.print_exc() del window.actions_on_close[:], window.actions_on_removal[:] window = self.active_window if window is not prev_active_window: if prev_active_window is not None: prev_active_window.focus_changed(False) if window is not None: window.focus_changed(True) def mark_window_for_close(self, q: Window | None | int = None) -> None: if isinstance(q, int): window = self.window_id_map.get(q) if window is None: return else: window = q or self.active_window if window: self.child_monitor.mark_for_close(window.id) @ac('win', 'Close the currently active window') def close_window(self) -> None: self.mark_window_for_close(self.window_for_dispatch) def close_windows_with_confirmation_msg(self, windows: Iterable[Window], active_window: Window | None) -> tuple[str, int]: num_running_programs = 0 num_background_programs = 0 count_background = get_options().confirm_os_window_close[1] running_program = background_program = '' windows = sorted(windows, key=lambda w: 0 if w is active_window else 1) with cached_process_data(): for window in windows: if window.has_running_program: num_running_programs += 1 running_program = running_program or (window.child.foreground_cmdline or [''])[0] elif count_background and (bp := window.child.background_processes): num_background_programs += len(bp) for q in bp: background_program = background_program or (q['cmdline'] or [''])[0] if num := num_running_programs + num_background_programs: if num_running_programs: return ngettext(_('It is running: {0}.'), _('It is running: {0} and {1} other programs.'), num_running_programs).format( green(running_program), num_running_programs - 1), num if num_background_programs: return ngettext(_('It is running: {0} in the background.'), _( 'It is running: {0} in the background and {1} other programs.'), num_background_programs).format(green(background_program), num_background_programs - 1) + ' ' + _( '\n\nBackground programs should be run with the disown command' ' to allow them to continue running when the terminal is closed.'), num return '', 0 @ac('win', ''' Close window with confirmation Asks for confirmation before closing the window. If you don't want the confirmation when the window is sitting at a shell prompt (requires :ref:`shell_integration`), use:: map f1 close_window_with_confirmation ignore-shell ''') def close_window_with_confirmation(self, ignore_shell: bool = False) -> None: window = self.window_for_dispatch or self.active_window if window is None: return msg = self.close_windows_with_confirmation_msg((window,), window)[0] if not msg and not ignore_shell: msg = _('It is running a shell.') if msg: msg = _('Are you sure you want to close this window?') + ' ' + msg self.confirm(msg, self.handle_close_window_confirmation, window.id, window=window, title=_('Close window?')) else: self.mark_window_for_close(window) def handle_close_window_confirmation(self, allowed: bool, window_id: int) -> None: if allowed: self.mark_window_for_close(window_id) @ac('tab', 'Close the current tab') def close_tab(self, tab: Tab | None = None) -> None: if tab is None and self.window_for_dispatch: tab = self.window_for_dispatch.tabref() tab = tab or self.active_tab if tab: self.confirm_tab_close(tab) @property def active_tab_manager_with_dispatch(self) -> TabManager | None: if self.window_for_dispatch: td = self.window_for_dispatch.tabref() tm = td.tab_manager_ref() if td else None else: tm = self.active_tab_manager return tm @ac('tab', 'Close all the tabs in the current OS window other than the currently active tab') def close_other_tabs_in_os_window(self) -> None: tm = self.active_tab_manager_with_dispatch if tm is not None and len(tm.tabs) > 1: active_tab = self.active_tab for tab in tm: if tab is not active_tab: self.close_tab(tab) @ac('win', 'Close all other OS Windows other than the OS Window containing the currently active window') def close_other_os_windows(self) -> None: active = self.active_tab_manager_with_dispatch if active is not None: for x in self.os_window_map.values(): if x is not active: self.mark_os_window_for_close(x.os_window_id) def confirm( self, msg: str, # can contain newlines and ANSI formatting callback: Callable[..., None], # called with True or False and *args *args: Any, # passed to the callback function window: Window | None = None, # the window associated with the confirmation confirm_on_cancel: bool = False, # on closing window confirm_on_accept: bool = True, # on pressing enter title: str = '' # window title ) -> Window: result: bool = False def callback_(res: dict[str, Any], x: int, boss: Boss) -> None: nonlocal result result = res.get('response') == 'y' def on_popup_overlay_removal(wid: int, boss: Boss) -> None: callback(result, *args) cmd = ['--type=yesno', '--message', msg, '--default', 'y' if confirm_on_accept else 'n'] if title: cmd += ['--title', title] w = self.run_kitten_with_metadata( 'ask', cmd, window=window, custom_callback=callback_, action_on_removal=on_popup_overlay_removal, default_data={'response': 'y' if confirm_on_cancel else 'n'}) assert isinstance(w, Window) return w def choose( self, msg: str, # can contain newlines and ANSI formatting callback: Callable[..., None], # called with the choice or empty string when aborted *choices: str, # The choices, see the help for the ask kitten for format of a choice window: Window | None = None, # the window associated with the confirmation default: str = '', # the default choice when the user presses Enter hidden_text: str = '', # text to hide in the message hidden_text_placeholder: str = 'HIDDEN_TEXT_PLACEHOLDER', # placeholder text to insert in to message unhide_key: str = 'u', # key to press to unhide hidden text title: str = '' # window title ) -> Window | None: result: str = '' def callback_(res: dict[str, Any], x: int, boss: Boss) -> None: nonlocal result result = res.get('response') or '' if hidden_text: msg = msg.replace(hidden_text, hidden_text_placeholder) cmd = ['--type=choices', '--message', msg] if default: cmd += ['-d', default] for c in choices: cmd += ['-c', c] if hidden_text: cmd += ['--hidden-text-placeholder', hidden_text_placeholder, '--unhide-key', unhide_key] input_data = hidden_text else: input_data = None if title: cmd += ['--title', title] def on_popup_overlay_removal(wid: int, boss: Boss) -> None: callback(result) ans = self.run_kitten_with_metadata( 'ask', cmd, window=window, custom_callback=callback_, input_data=input_data, default_data={'response': ''}, action_on_removal=on_popup_overlay_removal ) if isinstance(ans, Window): return ans return None def get_line( self, msg: str, # can contain newlines and ANSI formatting callback: Callable[..., None], # called with the answer or empty string when aborted window: Window | None = None, # the window associated with the confirmation prompt: str = '> ', is_password: bool = False, initial_value: str = '' ) -> None: result: str = '' def callback_(res: dict[str, Any], x: int, boss: Boss) -> None: nonlocal result result = res.get('response') or '' def on_popup_overlay_removal(wid: int, boss: Boss) -> None: callback(result) cmd = ['--type', 'password' if is_password else 'line', '--message', msg, '--prompt', prompt] if initial_value: cmd.append('--default=' + initial_value) self.run_kitten_with_metadata( 'ask', cmd, window=window, custom_callback=callback_, default_data={'response': ''}, action_on_removal=on_popup_overlay_removal ) def confirm_tab_close(self, tab: Tab) -> None: msg, num_active_windows = self.close_windows_with_confirmation_msg(tab, tab.active_window) x = get_options().confirm_os_window_close[0] num = num_active_windows if x < 0 else len(tab) needs_confirmation = x != 0 and num >= abs(x) if not needs_confirmation: self.close_tab_no_confirm(tab) return msg = msg or _('It has {} windows?').format(num) if tab is not self.active_tab: tm = tab.tab_manager_ref() if tm is not None: tm.set_active_tab(tab) if tab.confirm_close_window_id and tab.confirm_close_window_id in self.window_id_map: w = self.window_id_map[tab.confirm_close_window_id] if w in tab: tab.set_active_window(w) return msg = _('Are you sure you want to close this tab?') + ' ' + msg w = self.confirm(msg, self.handle_close_tab_confirmation, tab.id, window=tab.active_window, title=_('Close tab?')) tab.confirm_close_window_id = w.id def handle_close_tab_confirmation(self, confirmed: bool, tab_id: int) -> None: for tab in self.all_tabs: if tab.id == tab_id: tab.confirm_close_window_id = 0 break else: return if not confirmed: return self.close_tab_no_confirm(tab) def close_tab_no_confirm(self, tab: Tab) -> None: if self.current_visual_select is not None and self.current_visual_select.tab_id == tab.id: self.cancel_current_visual_select() for window in tab: self.mark_window_for_close(window) @ac('win', 'Toggle the fullscreen status of the active OS Window') def toggle_fullscreen(self, os_window_id: int = 0) -> None: if os_window_id == 0: tm = self.active_tab_manager_with_dispatch if tm: os_window_id = tm.os_window_id toggle_fullscreen(os_window_id) @ac('win', 'Toggle the maximized status of the active OS Window') def toggle_maximized(self, os_window_id: int = 0) -> None: if os_window_id == 0: tm = self.active_tab_manager_with_dispatch if tm: os_window_id = tm.os_window_id toggle_maximized(os_window_id) @ac('misc', 'Toggle macOS secure keyboard entry') def toggle_macos_secure_keyboard_entry(self) -> None: toggle_secure_input() @ac('misc', 'Hide macOS kitty application') def hide_macos_app(self) -> None: cocoa_hide_app() @ac('misc', 'Hide macOS other applications') def hide_macos_other_apps(self) -> None: cocoa_hide_other_apps() @ac('misc', 'Minimize macOS window') def minimize_macos_window(self) -> None: osw_id = None if self.window_for_dispatch: tm = self.active_tab_manager_with_dispatch if tm: osw_id = tm.os_window_id else: osw_id = current_os_window() if osw_id is not None: cocoa_minimize_os_window(osw_id) def start(self, first_os_window_id: int, startup_sessions: Iterable[Session]) -> None: if not getattr(self, 'io_thread_started', False): self.child_monitor.start() self.io_thread_started = True for signum in self.child_monitor.handled_signals(): handled_signals.add(signum) urls: list[str] = getattr(sys, 'cmdline_args_for_open', []) if urls: delattr(sys, 'cmdline_args_for_open') sess = create_sessions(get_options(), self.args, special_window=SpecialWindow([kitty_exe(), '+runpy', 'input()'])) self.startup_first_child(first_os_window_id, startup_sessions=tuple(sess)) self.launch_urls(*urls) else: self.startup_first_child(first_os_window_id, startup_sessions=startup_sessions) if get_options().update_check_interval > 0 and not self.update_check_started and getattr(sys, 'frozen', False): from .update_check import run_update_check run_update_check(get_options().update_check_interval * 60 * 60) self.update_check_started = True def handle_click_on_tab(self, os_window_id: int, x: int, button: int, modifiers: int, action: int) -> None: tm = self.os_window_map.get(os_window_id) if tm is not None: tm.handle_click_on_tab(x, button, modifiers, action) def on_window_resize(self, os_window_id: int, w: int, h: int, dpi_changed: bool) -> None: if dpi_changed: self.on_dpi_change(os_window_id) else: tm = self.os_window_map.get(os_window_id) if tm is not None: tm.resize() @ac('misc', ''' Clear the terminal See :sc:`reset_terminal ` for details. For example:: # Reset the terminal map f1 clear_terminal reset active # Clear the terminal screen by erasing all contents map f1 clear_terminal clear active # Clear the terminal scrollback by erasing it map f1 clear_terminal scrollback active # Scroll the contents of the screen into the scrollback map f1 clear_terminal scroll active # Clear everything on screen up to the line with the cursor or the start of the current prompt (needs shell integration) # Useful for clearing the screen up to the shell prompt and moving the shell prompt to the top of the screen. map f1 clear_terminal to_cursor active # Same as above except cleared lines are moved into scrollback map f1 clear_terminal to_cursor_scroll active ''') def clear_terminal(self, action: str, only_active: bool) -> None: if only_active: windows = [] w = self.window_for_dispatch or self.active_window if w is not None: windows.append(w) else: windows = list(self.all_windows) if action == 'reset': for w in windows: w.clear_screen(reset=True, scrollback=True) elif action == 'scrollback': for w in windows: w.screen.clear_scrollback() elif action == 'clear': for w in windows: w.clear_screen() elif action == 'scroll': for w in windows: w.scroll_prompt_to_top() elif action == 'to_cursor': for w in windows: w.scroll_prompt_to_top(clear_scrollback=True) elif action == 'to_cursor_scroll': for w in windows: w.scroll_prompt_to_top(clear_scrollback=False) else: self.show_error(_('Unknown clear type'), _('The clear type: {} is unknown').format(action)) def increase_font_size(self) -> None: # legacy cfs = global_font_size() self.set_font_size(min(get_options().font_size * 5, cfs + 2.0)) def decrease_font_size(self) -> None: # legacy cfs = global_font_size() self.set_font_size(max(MINIMUM_FONT_SIZE, cfs - 2.0)) def restore_font_size(self) -> None: # legacy self.set_font_size(get_options().font_size) def set_font_size(self, new_size: float) -> None: # legacy self.change_font_size(True, None, new_size) @ac('win', ''' Change the font size for the current or all OS Windows See :ref:`conf-kitty-shortcuts.fonts` for details. ''') def change_font_size(self, all_windows: bool, increment_operation: str | None, amt: float) -> None: def calc_new_size(old_size: float) -> float: new_size = old_size if amt == 0: new_size = get_options().font_size else: if increment_operation: new_size += (1 if increment_operation == '+' else -1) * amt else: new_size = amt new_size = max(MINIMUM_FONT_SIZE, min(new_size, get_options().font_size * 5)) return new_size if all_windows: current_global_size = global_font_size() new_size = calc_new_size(current_global_size) if new_size != current_global_size: global_font_size(new_size) os_windows = list(self.os_window_map.keys()) else: os_windows = [] w = self.window_for_dispatch or self.active_window if w is not None: os_windows.append(w.os_window_id) if os_windows: final_windows = {} for wid in os_windows: current_size = os_window_font_size(wid) if current_size: new_size = calc_new_size(current_size) if new_size != current_size: final_windows[wid] = new_size if final_windows: self._change_font_size(final_windows) def _change_font_size(self, sz_map: dict[int, float]) -> None: for os_window_id, sz in sz_map.items(): tm = self.os_window_map.get(os_window_id) if tm is not None: os_window_font_size(os_window_id, sz) tm.resize() def on_dpi_change(self, os_window_id: int) -> None: tm = self.os_window_map.get(os_window_id) if tm is not None: sz = os_window_font_size(os_window_id) if sz: os_window_font_size(os_window_id, sz, True) for tab in tm: for window in tab: window.on_dpi_change(sz) tm.resize() def _set_os_window_background_opacity(self, os_window_id: int, opacity: float) -> None: change_background_opacity(os_window_id, max(0.0, min(opacity, 1.0))) @ac('win', ''' Set the background opacity for the active OS Window For example:: map f1 set_background_opacity +0.1 map f2 set_background_opacity -0.1 map f3 set_background_opacity 0.5 ''') def set_background_opacity(self, opacity: str) -> None: window = self.window_for_dispatch or self.active_window if window is None or not opacity: return if not get_options().dynamic_background_opacity: self.show_error( _('Cannot change background opacity'), _('You must set the dynamic_background_opacity option in kitty.conf to be able to change background opacity')) return os_window_id = window.os_window_id if opacity[0] in '+-': old_opacity = background_opacity_of(os_window_id) if old_opacity is None: return fin_opacity = old_opacity + float(opacity) elif opacity == 'default': fin_opacity = get_options().background_opacity else: fin_opacity = float(opacity) self._set_os_window_background_opacity(os_window_id, fin_opacity) @property def active_tab_manager(self) -> TabManager | None: os_window_id = current_focused_os_window_id() if os_window_id <= 0: os_window_id = last_focused_os_window_id() if os_window_id <= 0: q = current_os_window() if q is not None: os_window_id = q return self.os_window_map.get(os_window_id) @property def active_tab(self) -> Tab | None: tm = self.active_tab_manager return None if tm is None else tm.active_tab @property def active_window(self) -> Window | None: t = self.active_tab return None if t is None else t.active_window def refresh_active_tab_bar(self) -> bool: tm = self.active_tab_manager if tm: tm.update_tab_bar_data() tm.mark_tab_bar_dirty() return True return False @ac('misc', ''' End the current keyboard mode switching to the previous mode. ''') def pop_keyboard_mode(self) -> bool: return self.mappings.pop_keyboard_mode() @ac('misc', ''' Switch to the specified keyboard mode, pushing it onto the stack of keyboard modes. ''') def push_keyboard_mode(self, new_mode: str) -> None: self.mappings.push_keyboard_mode(new_mode) def dispatch_possible_special_key(self, ev: KeyEvent) -> bool: return self.mappings.dispatch_possible_special_key(ev) def cancel_current_visual_select(self) -> None: if self.current_visual_select: self.current_visual_select.cancel() self.current_visual_select = None def visual_window_select_action( self, tab: Tab, callback: Callable[[Tab | None, Window | None], None], choose_msg: str, only_window_ids: Container[int] = (), reactivate_prev_tab: bool = False ) -> None: import string self.cancel_current_visual_select() initial_tab_id: int | None = None initial_os_window_id = current_os_window() tm = tab.tab_manager_ref() if tm is not None: if tm.active_tab is not None: initial_tab_id = tm.active_tab.id tm.set_active_tab(tab) if initial_os_window_id != tab.os_window_id: focus_os_window(tab.os_window_id, True) self.current_visual_select = VisualSelect(tab.id, tab.os_window_id, initial_tab_id, initial_os_window_id, choose_msg, callback, reactivate_prev_tab) if tab.current_layout.only_active_window_visible: self.select_window_in_tab_using_overlay(tab, choose_msg, only_window_ids) return km = KeyboardMode('__visual_select__') km.on_action = 'end' fmap = get_name_to_functional_number_map() alphanumerics = get_options().visual_window_select_characters for idx, window in tab.windows.iter_windows_with_number(only_visible=True): if only_window_ids and window.id not in only_window_ids: continue ac = KeyDefinition(definition=f'visual_window_select_action_trigger {window.id}') if idx >= len(alphanumerics): break ch = alphanumerics[idx] window.screen.set_window_char(ch) self.current_visual_select.window_ids.append(window.id) for mods in (0, GLFW_MOD_CONTROL, GLFW_MOD_CONTROL | GLFW_MOD_SHIFT, GLFW_MOD_SUPER, GLFW_MOD_ALT, GLFW_MOD_SHIFT): km.keymap[SingleKey(mods=mods, key=ord(ch.lower()))].append(ac) if ch in string.digits: km.keymap[SingleKey(mods=mods, key=fmap[f'KP_{ch}'])].append(ac) if len(self.current_visual_select.window_ids) > 1: self.mappings._push_keyboard_mode(km) redirect_mouse_handling(True) self.mouse_handler = self.visual_window_select_mouse_handler else: self.visual_window_select_action_trigger(self.current_visual_select.window_ids[0] if self.current_visual_select.window_ids else 0) if get_options().enable_audio_bell: ring_bell() def visual_window_select_action_trigger(self, window_id: int = 0) -> None: if self.current_visual_select: self.current_visual_select.trigger(int(window_id)) self.current_visual_select = None def visual_window_select_mouse_handler(self, ev: WindowSystemMouseEvent) -> None: tab = self.active_tab def trigger(window_id: int = 0) -> None: self.visual_window_select_action_trigger(window_id) self.mappings.pop_keyboard_mode_if_is('__visual_select__') if ev.button == GLFW_MOUSE_BUTTON_LEFT and ev.action == GLFW_PRESS and ev.window_id: w = self.window_id_map.get(ev.window_id) if w is not None and tab is not None and w in tab: if self.current_visual_select and self.current_visual_select.tab_id == tab.id: trigger(w.id) else: trigger() return if ev.button > -1 and tab is not None: trigger() def mouse_event( self, in_tab_bar: bool, window_id: int, action: int, modifiers: int, button: int, currently_pressed_button: int, x: float, y: float ) -> None: if self.mouse_handler is not None: ev = WindowSystemMouseEvent(in_tab_bar, window_id, action, modifiers, button, currently_pressed_button, x, y) self.mouse_handler(ev) def select_window_in_tab_using_overlay(self, tab: Tab, msg: str, only_window_ids: Container[int] = ()) -> Window | None: windows: list[tuple[int | None, str]] = [] selectable_windows: list[tuple[int, str]] = [] for i, w in tab.windows.iter_windows_with_number(only_visible=False): if only_window_ids and w.id not in only_window_ids: windows.append((None, f'Current window: {w.title}' if w is self.active_window else w.title)) else: windows.append((w.id, w.title)) selectable_windows.append((w.id, w.title)) if len(selectable_windows) < 2: self.visual_window_select_action_trigger(selectable_windows[0][0] if selectable_windows else 0) if get_options().enable_audio_bell: ring_bell() return None cvs = self.current_visual_select def chosen(ans: None | int | str) -> None: q = self.current_visual_select self.current_visual_select = None if cvs and q is cvs: q.trigger(ans if isinstance(ans, int) else 0) return self.choose_entry(msg, windows, chosen, hints_args=('--hints-offset=0', '--alphabet', get_options().visual_window_select_characters.lower())) @ac('win', ''' Resize the active window interactively See :ref:`window_resizing` for details. ''') def start_resizing_window(self) -> None: w = self.window_for_dispatch or self.active_window if w is None: return overlay_window = self.run_kitten_with_metadata('resize_window', args=[ f'--horizontal-increment={get_options().window_resize_step_cells}', f'--vertical-increment={get_options().window_resize_step_lines}' ]) if overlay_window is not None: overlay_window.allow_remote_control = True def resize_layout_window(self, window: Window, increment: float, is_horizontal: bool, reset: bool = False) -> bool | None | str: tab = window.tabref() if tab is None or not increment: return False if reset: tab.reset_window_sizes() return None return tab.resize_window_by(window.id, increment, is_horizontal) def resize_os_window(self, os_window_id: int, width: int, height: int, unit: str, incremental: bool = False) -> None: if not incremental and (width < 0 or height < 0): return metrics = get_os_window_size(os_window_id) if metrics is None: return has_window_scaling = is_macos or is_wayland() w, h = get_new_os_window_size(metrics, width, height, unit, incremental, has_window_scaling) set_os_window_size(os_window_id, w, h) def tab_for_id(self, tab_id: int) -> Tab | None: for tm in self.os_window_map.values(): tab = tm.tab_for_id(tab_id) if tab is not None: return tab return None def default_bg_changed_for(self, window_id: int, via_escape_code: bool = False) -> None: w = self.window_id_map.get(window_id) if w is not None: w.on_color_scheme_preference_change(via_escape_code=via_escape_code) tm = self.os_window_map.get(w.os_window_id) if tm is not None: tm.update_tab_bar_data() tm.mark_tab_bar_dirty() t = tm.tab_for_id(w.tab_id) if t is not None: t.relayout_borders() set_os_window_chrome(w.os_window_id) def dispatch_action( self, key_action: KeyAction, window_for_dispatch: Window | None = None, dispatch_type: str = 'KeyPress' ) -> bool: def report_match(f: Callable[..., Any]) -> None: if self.args.debug_keyboard: prefix = '\n' if dispatch_type == 'KeyPress' else '' end = ', ' if dispatch_type == 'KeyPress' else '\n' timed_debug_print(f'{prefix}\x1b[35m{dispatch_type}\x1b[m matched action:', func_name(f), end=end) if key_action is not None: f = getattr(self, key_action.func, None) if f is not None: orig, self.window_for_dispatch = self.window_for_dispatch, window_for_dispatch try: report_match(f) passthrough = f(*key_action.args) if passthrough is not True: return True finally: self.window_for_dispatch = orig if window_for_dispatch is None: tab = self.active_tab window = self.active_window else: window = window_for_dispatch tab = window.tabref() if tab is None or window is None: return False if key_action is not None: f = getattr(tab, key_action.func, getattr(window, key_action.func, None)) if f is not None: passthrough = f(*key_action.args) report_match(f) if passthrough is not True: return True return False def user_menu_action(self, defn: str) -> None: ' Callback from user actions in the macOS global menu bar or other menus ' self.combine(defn) @ac('misc', ''' Combine multiple actions and map to a single keypress The syntax is:: map key combine action1 action2 action3 ... For example:: map kitty_mod+e combine : new_window : next_layout map kitty_mod+e combine | new_tab | goto_tab -1 ''') def combine(self, action_definition: str, window_for_dispatch: Window | None = None, dispatch_type: str = 'KeyPress', raise_error: bool = False) -> bool: consumed = False if action_definition: try: actions = get_options().alias_map.resolve_aliases(action_definition, 'map' if dispatch_type == 'KeyPress' else 'mouse_map') except Exception as e: self.show_error('Failed to parse action', f'{action_definition}\n{e}') return True if actions: window_for_dispatch = window_for_dispatch or self.window_for_dispatch try: if self.dispatch_action(actions[0], window_for_dispatch, dispatch_type): consumed = True if len(actions) > 1: self.drain_actions(list(actions[1:]), window_for_dispatch, dispatch_type) except Exception as e: if raise_error: raise self.show_error('Key action failed', f'{actions[0].pretty()}\n{e}') consumed = True return consumed def on_focus(self, os_window_id: int, focused: bool) -> None: tm = self.os_window_map.get(os_window_id) if tm is not None: w = tm.active_window if w is not None: w.focus_changed(focused) if is_macos and focused: cocoa_set_menubar_title(w.title or '') tm.mark_tab_bar_dirty() def on_activity_since_last_focus(self, window: Window) -> None: os_window_id = window.os_window_id tm = self.os_window_map.get(os_window_id) if tm is not None: tm.mark_tab_bar_dirty() def update_tab_bar_data(self, os_window_id: int) -> None: tm = self.os_window_map.get(os_window_id) if tm is not None: tm.update_tab_bar_data() def on_drop(self, os_window_id: int, mime: str, data: bytes) -> None: tm = self.os_window_map.get(os_window_id) if tm is not None: w = tm.active_window if w is not None: text = data.decode('utf-8', 'replace') if mime == 'text/uri-list': urls = parse_uri_list(text) if w.at_prompt: import shlex text = ' '.join(map(shlex.quote, urls)) else: text = '\n'.join(urls) w.paste_text(text) @ac('win', ''' Focus the nth OS window if positive or the previously active OS windows if negative. When the number is larger than the number of OS windows focus the last OS window. A value of zero will refocus the currently focused OS window, this is useful if focus is not on any kitty OS window at all, however, it will only work if the window manager allows applications to grab focus. For example:: # focus the previously active kitty OS window map ctrl+p nth_os_window -1 # focus the current kitty OS window (grab focus) map ctrl+0 nth_os_window 0 # focus the first kitty OS window map ctrl+1 nth_os_window 1 # focus the last kitty OS window map ctrl+1 nth_os_window 999 ''') def nth_os_window(self, num: int = 1) -> None: if not self.os_window_map: return if num == 0: os_window_id = current_focused_os_window_id() or last_focused_os_window_id() focus_os_window(os_window_id, True) elif num > 0: ids = tuple(self.os_window_map.keys()) os_window_id = ids[min(num, len(ids)) - 1] focus_os_window(os_window_id, True) elif num < 0: fc_map = os_window_focus_counters() s = sorted(fc_map.keys(), key=fc_map.__getitem__) if not s: return try: os_window_id = s[num-1] except IndexError: os_window_id = s[0] focus_os_window(os_window_id, True) @ac('win', 'Close the currently active OS Window') def close_os_window(self) -> None: tm = self.active_tab_manager_with_dispatch if tm is not None: self.confirm_os_window_close(tm.os_window_id) def confirm_os_window_close(self, os_window_id: int) -> None: tm = self.os_window_map.get(os_window_id) if tm is None: self.mark_os_window_for_close(os_window_id) return active_window = tm.active_window windows = [] for tab in tm: windows += list(tab) msg, num_active_windows = self.close_windows_with_confirmation_msg(windows, active_window) q = get_options().confirm_os_window_close[0] num = num_active_windows if q < 0 else len(windows) needs_confirmation = tm is not None and q != 0 and num >= abs(q) if not needs_confirmation: self.mark_os_window_for_close(os_window_id) return msg = msg or _('It has {} windows?').format(num) msg = _('Are you sure you want to close this OS Window?') + ' ' + msg w = self.confirm(msg, self.handle_close_os_window_confirmation, os_window_id, window=tm.active_window, title=_('Close OS window')) tm.confirm_close_window_id = w.id def handle_close_os_window_confirmation(self, confirmed: bool, os_window_id: int) -> None: tm = self.os_window_map.get(os_window_id) if tm is not None: tm.confirm_close_window_id = 0 if confirmed: self.mark_os_window_for_close(os_window_id) else: self.mark_os_window_for_close(os_window_id, NO_CLOSE_REQUESTED) def on_os_window_closed(self, os_window_id: int, viewport_width: int, viewport_height: int) -> None: self.cached_values['window-size'] = viewport_width, viewport_height tm = self.os_window_map.pop(os_window_id, None) if tm is not None: tm.destroy() for window_id in tuple(w.id for w in self.window_id_map.values() if getattr(w, 'os_window_id', None) == os_window_id): self.window_id_map.pop(window_id, None) if not self.os_window_map and is_macos: cocoa_set_menubar_title('') action = self.os_window_death_actions.pop(os_window_id, None) if action is not None: action() quit_confirmation_window_id: int = 0 @ac('win', 'Quit, closing all windows') def quit(self, *args: Any) -> None: windows = [] for q in self.os_window_map.values(): for qt in q: windows += list(qt) active_window = self.active_window msg, num_active_windows = self.close_windows_with_confirmation_msg(windows, active_window) x = get_options().confirm_os_window_close[0] num = num_active_windows if x < 0 else len(windows) needs_confirmation = x != 0 and num >= abs(x) if not needs_confirmation: set_application_quit_request(IMPERATIVE_CLOSE_REQUESTED) return if current_application_quit_request() == CLOSE_BEING_CONFIRMED: if self.quit_confirmation_window_id and self.quit_confirmation_window_id in self.window_id_map: w = self.window_id_map[self.quit_confirmation_window_id] tab = w.tabref() if tab is not None: ctm = tab.tab_manager_ref() if ctm is not None and tab in ctm and w in tab: focus_os_window(ctm.os_window_id) ctm.set_active_tab(tab) tab.set_active_window(w) return return msg = msg or _('It has {} windows.').format(num) w = self.confirm(_('Are you sure you want to quit kitty?') + ' ' + msg, self.handle_quit_confirmation, window=active_window, title=_('Quit kitty?')) self.quit_confirmation_window_id = w.id set_application_quit_request(CLOSE_BEING_CONFIRMED) def handle_quit_confirmation(self, confirmed: bool) -> None: self.quit_confirmation_window_id = 0 set_application_quit_request(IMPERATIVE_CLOSE_REQUESTED if confirmed else NO_CLOSE_REQUESTED) def notify_on_os_window_death(self, address: str) -> None: import socket s = socket.socket(family=socket.AF_UNIX) with suppress(Exception): s.connect(address) s.sendall(b'c') with suppress(OSError): s.shutdown(socket.SHUT_RDWR) s.close() def display_scrollback(self, window: Window, data: bytes | str, input_line_number: int = 0, title: str = '', report_cursor: bool = True) -> None: def prepare_arg(x: str) -> str: x = x.replace('INPUT_LINE_NUMBER', str(input_line_number)) x = x.replace('CURSOR_LINE', str(window.screen.cursor.y + 1) if report_cursor else '0') x = x.replace('CURSOR_COLUMN', str(window.screen.cursor.x + 1) if report_cursor else '0') return x cmd = list(map(prepare_arg, get_options().scrollback_pager)) if not os.path.isabs(cmd[0]): resolved_exe = which(cmd[0]) if not resolved_exe: log_error(f'The scrollback_pager {cmd[0]} was not found in PATH, falling back to less') resolved_exe = which('less') or 'less' cmd[0] = resolved_exe if os.path.basename(cmd[0]) == 'less': cmd.append('-+F') # reset --quit-if-one-screen tab = self.active_tab if tab is not None: bdata = data.encode('utf-8') if isinstance(data, str) else data if is_macos and cmd[0] == '/usr/bin/less' and macos_version()[:2] < (12, 3): # the system less before macOS 12.3 barfs up OSC codes, so sanitize them ourselves sentinel = os.path.join(cache_dir(), 'less-is-new-enough') if not os.path.exists(sentinel): if less_version(cmd[0]) >= 581: open(sentinel, 'w').close() else: bdata = re.sub(br'\x1b\].*?\x1b\\', b'', bdata) tab.new_special_window( SpecialWindow(cmd, bdata, title or _('History'), overlay_for=window.id, cwd=window.cwd_of_child), copy_colors_from=self.active_window ) @ac('misc', 'Edit the kitty.conf config file in your favorite text editor') def edit_config_file(self, *a: Any) -> None: confpath = prepare_config_file_for_editing() cmd = [kitty_exe(), '+edit'] + get_editor(get_options()) + [confpath] self.new_os_window(*cmd) def run_kitten_with_metadata( self, kitten: str, args: Iterable[str] = (), input_data: bytes | str | None = None, window: Window | None = None, custom_callback: Callable[[dict[str, Any], int, 'Boss'], None] | None = None, action_on_removal: Callable[[int, 'Boss'], None] | None = None, default_data: dict[str, Any] | None = None ) -> Any: from kittens.runner import CLIOnlyKitten, KittenMetadata, create_kitten_handler is_wrapped = kitten in wrapped_kitten_names() if window is None: w = self.active_window tab = self.active_tab else: w = window tab = w.tabref() if w else None args = list(args) if w is not None and '@selection' in args and (sel := self.data_for_at(which='@selection', window=w)): args = [sel if xa == '@selection' else xa for xa in args] try: end_kitten = create_kitten_handler(kitten, args) except CLIOnlyKitten: is_wrapped = True end_kitten = KittenMetadata() if end_kitten.no_ui: return end_kitten.handle_result(None, w.id if w else 0, self) if w is not None and tab is not None: if not is_wrapped: args[0:0] = [config_dir, kitten] if input_data is None: type_of_input = end_kitten.type_of_input q = type_of_input.split('-') if type_of_input else [] if not q: data: bytes | None = None elif q[0] in ('text', 'history', 'ansi', 'screen'): data = w.as_text(as_ansi='ansi' in q, add_history='history' in q, add_wrap_markers='screen' in q).encode('utf-8') elif type_of_input == 'selection': sel = self.data_for_at(which='@selection', window=w) data = sel.encode('utf-8') if sel else None elif q[0] in ('output', 'first_output', 'last_visited_output'): which = { 'output': CommandOutput.last_run, 'first_output': CommandOutput.first_on_screen, 'last_visited_output': CommandOutput.last_visited}[q[0]] data = w.cmd_output(which, as_ansi='ansi' in q, add_wrap_markers='screen' in q).encode('utf-8') else: raise ValueError(f'Unknown type_of_input: {type_of_input}') else: data = input_data if isinstance(input_data, bytes) else input_data.encode('utf-8') copts = common_opts_as_dict(get_options()) env = { 'KITTY_COMMON_OPTS': json.dumps(copts), 'KITTY_CHILD_PID': str(w.child.pid), 'OVERLAID_WINDOW_LINES': str(w.screen.lines), 'OVERLAID_WINDOW_COLS': str(w.screen.columns), } if is_wrapped: cmd = [kitten_exe(), kitten] env['KITTEN_RUNNING_AS_UI'] = '1' env['KITTY_CONFIG_DIRECTORY'] = config_dir if w is not None: env['KITTY_BASIC_COLORS'] = json.dumps(w.screen.color_profile.basic_colors()) else: cmd = [kitty_exe(), '+runpy', 'from kittens.runner import main; main()'] env['PYTHONWARNINGS'] = 'ignore' remote_control_fd = -1 if end_kitten.allow_remote_control: remote_control_passwords: dict[str, Sequence[str]] | None = None initial_data = b'' if end_kitten.remote_control_password: from secrets import token_hex p = token_hex(16) remote_control_passwords = {p: end_kitten.remote_control_password if isinstance(end_kitten.remote_control_password, str) else ''} initial_data = p.encode() + b'\n' remote = self.add_fd_based_remote_control(remote_control_passwords, initial_data) remote_control_fd = remote.fileno() try: overlay_window = tab.new_special_window( SpecialWindow( cmd + args, stdin=data, env=env, cwd=w.cwd_of_child, overlay_for=w.id, overlay_behind=end_kitten.has_ready_notification, ), copy_colors_from=w, remote_control_fd=remote_control_fd, ) finally: if end_kitten.allow_remote_control: remote.close() wid = w.id overlay_window.actions_on_close.append(partial(self.on_kitten_finish, wid, custom_callback or end_kitten.handle_result, default_data=default_data)) overlay_window.open_url_handler = end_kitten.open_url_handler if action_on_removal is not None: def callback_wrapper(*a: Any) -> None: if action_on_removal is not None: action_on_removal(wid, self) overlay_window.actions_on_removal.append(callback_wrapper) return overlay_window _run_kitten = run_kitten_with_metadata @ac('misc', 'Run the specified kitten. See :doc:`/kittens/custom` for details') def kitten(self, kitten: str, *kargs: str) -> None: self.run_kitten_with_metadata(kitten, kargs, window=self.window_for_dispatch) def run_kitten(self, kitten: str, *args: str) -> None: self.run_kitten_with_metadata(kitten, args) def on_kitten_finish( self, target_window_id: int, end_kitten: Callable[[dict[str, Any], int, 'Boss'], None], source_window: Window, default_data: dict[str, Any] | None = None ) -> None: data, source_window.kitten_result = source_window.kitten_result, None if data is None: data = default_data if data is not None: end_kitten(data, target_window_id, self) @ac('misc', 'Input an arbitrary unicode character. See :doc:`/kittens/unicode_input` for details.') def input_unicode_character(self) -> None: self.run_kitten_with_metadata('unicode_input', window=self.window_for_dispatch) @ac( 'tab', ''' Change the title of the active tab interactively, by typing in the new title. If you specify an argument to this action then that is used as the title instead of asking for it. Use the empty string ("") to reset the title to default. Use a space (" ") to indicate that the prompt should not be pre-filled. For example:: # interactive usage map f1 set_tab_title # set a specific title map f2 set_tab_title some title # reset to default map f3 set_tab_title "" # interactive usage without prefilled prompt map f3 set_tab_title " " ''' ) def set_tab_title(self, title: str | None = None) -> None: tab = self.window_for_dispatch.tabref() if self.window_for_dispatch else self.active_tab if tab: if title is not None and title not in ('" "', "' '"): if title in ('""', "''"): title = '' tab.set_title(title) return prefilled = tab.name or tab.title if title in ('" "', "' '"): prefilled = '' self.get_line( _('Enter the new title for this tab below. An empty title will cause the default title to be used.'), tab.set_title, window=tab.active_window, initial_value=prefilled) def create_special_window_for_show_error(self, title: str, msg: str, overlay_for: int | None = None) -> SpecialWindowInstance: ec = sys.exc_info() tb = '' if ec != (None, None, None): import traceback tb = traceback.format_exc() cmd = [kitten_exe(), '__show_error__', '--title', title] env = {} env['KITTEN_RUNNING_AS_UI'] = '1' env['KITTY_CONFIG_DIRECTORY'] = config_dir return SpecialWindow( cmd, override_title=title, stdin=json.dumps({'msg': msg, 'tb': tb}).encode(), env=env, overlay_for=overlay_for, ) @ac('misc', 'Show an error message with the specified title and text') def show_error(self, title: str, msg: str) -> None: w = self.window_for_dispatch or self.active_window if w: tab = w.tabref() if w is not None and tab is not None: tab.new_special_window(self.create_special_window_for_show_error(title, msg, w.id), copy_colors_from=w) @ac('mk', 'Create a new marker') def create_marker(self) -> None: w = self.window_for_dispatch or self.active_window if w: spec = None def done(data: dict[str, Any], target_window_id: int, self: Boss) -> None: nonlocal spec spec = data['response'] def done2(target_window_id: int, self: Boss) -> None: w = self.window_id_map.get(target_window_id) if w is not None and spec: try: w.set_marker(spec) except Exception as err: self.show_error(_('Invalid marker specification'), str(err)) self.run_kitten_with_metadata('ask', [ '--name=create-marker', '--message', _('Create marker, for example:\ntext 1 ERROR\nSee {}\n').format(website_url('marks')) ], custom_callback=done, action_on_removal=done2) @ac('misc', 'Run the kitty shell to control kitty with commands') def kitty_shell(self, window_type: str = 'window') -> None: kw: dict[str, Any] = {} cmd = [kitty_exe(), '@'] aw = self.window_for_dispatch or self.active_window if aw is not None: env = {'KITTY_SHELL_ACTIVE_WINDOW_ID': str(aw.id)} at = self.active_tab if at is not None: env['KITTY_SHELL_ACTIVE_TAB_ID'] = str(at.id) kw['env'] = env if window_type == 'tab': tab = self._new_tab(SpecialWindow(cmd, **kw)) if tab is not None: for w in tab: window = w elif window_type == 'os_window': os_window_id = self._new_os_window(SpecialWindow(cmd, **kw)) for tab in self.os_window_map[os_window_id]: for w in tab: window = w elif window_type == 'overlay': tab = self.active_tab if aw is not None and tab is not None: kw['overlay_for'] = aw.id window = tab.new_special_window(SpecialWindow(cmd, **kw)) else: tab = self.active_tab if tab is not None: window = tab.new_special_window(SpecialWindow(cmd, **kw)) path, ext = os.path.splitext(logo_png_file) window.set_logo(f'{path}-128{ext}', position='bottom-right', alpha=0.25) window.allow_remote_control = True def switch_focus_to(self, window_id: int) -> None: tab = self.active_tab if tab: tab.set_active_window(window_id) def open_kitty_website(self) -> None: self.open_url(website_url()) @ac('misc', 'Open the specified URL') def open_url(self, url: str, program: str | list[str] | None = None, cwd: str | None = None) -> None: if not url: return if isinstance(program, str): program = to_cmdline(program) found_action = False if program is None: from .open_actions import actions_for_url actions = list(actions_for_url(url)) if actions: found_action = True self.dispatch_action(actions.pop(0)) if actions: self.drain_actions(actions) if not found_action: extra_env = {} if self.listening_on: extra_env['KITTY_LISTEN_ON'] = self.listening_on def doit(activation_token: str = '') -> None: if activation_token: extra_env['XDG_ACTIVATION_TOKEN'] = activation_token open_url(url, program or get_options().open_url_with, cwd=cwd, extra_env=extra_env) if is_wayland(): run_with_activation_token(doit) else: doit() @ac('misc', 'Sleep for the specified time period. Suffix can be s for seconds, m, for minutes, h for hours and d for days. The time can be fractional.') def sleep(self, sleep_time: float = 1.0) -> None: sleep(sleep_time) @ac('misc', 'Click a URL using the keyboard') def open_url_with_hints(self) -> None: self.run_kitten_with_metadata('hints', window=self.window_for_dispatch) def drain_actions(self, actions: list[KeyAction], window_for_dispatch: Window | None = None, dispatch_type: str = 'KeyPress') -> None: def callback(timer_id: int | None) -> None: self.dispatch_action(actions.pop(0), window_for_dispatch, dispatch_type) if actions: self.drain_actions(actions) add_timer(callback, 0, False) def destroy(self) -> None: self.shutting_down = True self.child_monitor.shutdown_monitor() self.set_update_check_process() self.update_check_process = None del self.child_monitor for tm in self.os_window_map.values(): tm.destroy() self.os_window_map = {} destroy_global_data() def paste_to_active_window(self, text: str) -> None: if text: w = self.active_window if w is not None: w.paste_with_actions(text) @ac('cp', 'Paste from the clipboard to the active window') def paste_from_clipboard(self) -> None: text = get_clipboard_string() if text: w = self.window_for_dispatch or self.active_window if w is not None: w.paste_with_actions(text) def current_primary_selection(self) -> str: return get_primary_selection() if supports_primary_selection else '' def current_primary_selection_or_clipboard(self) -> str: return get_primary_selection() if supports_primary_selection else get_clipboard_string() @ac('cp', 'Paste from the primary selection, if present, otherwise the clipboard to the active window') def paste_from_selection(self) -> None: text = self.current_primary_selection_or_clipboard() if text: w = self.window_for_dispatch or self.active_window if w is not None: w.paste_with_actions(text) def set_primary_selection(self) -> None: w = self.active_window if w is not None and not w.destroyed: text = w.text_for_selection() if text: set_primary_selection(text) self.handle_clipboard_loss('primary', w.id) if get_options().copy_on_select: self.copy_to_buffer(get_options().copy_on_select) def get_active_selection(self) -> str | None: w = self.active_window if w is not None and not w.destroyed: return w.text_for_selection() return None def has_active_selection(self) -> bool: w = self.active_window if w is not None and not w.destroyed: return w.has_selection() return False def set_clipboard_buffer(self, buffer_name: str, text: str | None = None) -> None: if buffer_name: if text is not None: self.clipboard_buffers[buffer_name] = text elif buffer_name in self.clipboard_buffers: del self.clipboard_buffers[buffer_name] def get_clipboard_buffer(self, buffer_name: str) -> str | None: return self.clipboard_buffers.get(buffer_name) @ac('cp', ''' Copy the selection from the active window to the specified buffer See :ref:`cpbuf` for details. ''') def copy_to_buffer(self, buffer_name: str) -> None: w = self.window_for_dispatch or self.active_window if w is not None and not w.destroyed: text = w.text_for_selection() if text: if buffer_name == 'clipboard': set_clipboard_string(text) self.handle_clipboard_loss('clipboard', w.id) elif buffer_name == 'primary': set_primary_selection(text) self.handle_clipboard_loss('primary', w.id) else: self.set_clipboard_buffer(buffer_name, text) @ac('cp', ''' Paste from the specified buffer to the active window See :ref:`cpbuf` for details. ''') def paste_from_buffer(self, buffer_name: str) -> None: if buffer_name == 'clipboard': text: str | None = get_clipboard_string() elif buffer_name == 'primary': text = get_primary_selection() else: text = self.get_clipboard_buffer(buffer_name) if text: w = self.window_for_dispatch or self.active_window if w: w.paste_with_actions(text) @ac('tab', ''' Go to the specified tab, by number, starting with 1 Zero and negative numbers go to previously active tabs. Use the :ac:`select_tab` action to interactively select a tab to go to. ''') def goto_tab(self, tab_num: int) -> None: tm = self.active_tab_manager_with_dispatch if tm is not None: tm.goto_tab(tab_num - 1) def set_active_tab(self, tab: Tab) -> bool: tm = self.active_tab_manager if tm is not None: return tm.set_active_tab(tab) return False @ac('tab', 'Make the next tab active') def next_tab(self) -> None: tm = self.active_tab_manager_with_dispatch if tm is not None: tm.next_tab() @ac('tab', 'Make the previous tab active') def previous_tab(self) -> None: tm = self.active_tab_manager_with_dispatch if tm is not None: tm.next_tab(-1) prev_tab = previous_tab def process_stdin_source( self, window: Window | None = None, stdin: str | None = None, copy_pipe_data: dict[str, Any] | None = None ) -> tuple[dict[str, str] | None, bytes | None]: w = window or self.active_window if not w: return None, None env = None input_data = None if stdin: add_wrap_markers = stdin.endswith('_wrap') if add_wrap_markers: stdin = stdin[:-len('_wrap')] stdin = data_for_at(w, stdin, add_wrap_markers=add_wrap_markers) if stdin is not None: pipe_data = w.pipe_data(stdin, has_wrap_markers=add_wrap_markers) if w else None if pipe_data: if copy_pipe_data is not None: copy_pipe_data.update(pipe_data) env = { 'KITTY_PIPE_DATA': '{scrolled_by}:{cursor_x},{cursor_y}:{lines},{columns}'.format(**pipe_data) } input_data = stdin.encode('utf-8') return env, input_data def data_for_at(self, which: str, window: Window | None = None, add_wrap_markers: bool = False) -> str | None: window = window or self.active_window if not window: return None return data_for_at(window, which, add_wrap_markers=add_wrap_markers) def special_window_for_cmd( self, cmd: list[str], window: Window | None = None, stdin: str | None = None, cwd_from: CwdRequest | None = None, as_overlay: bool = False ) -> SpecialWindowInstance: w = window or self.active_window env, input_data = self.process_stdin_source(w, stdin) cmdline = [] for arg in cmd: if arg == '@selection' and w: q = data_for_at(w, arg) if not q: continue arg = q cmdline.append(arg) overlay_for = w.id if w and as_overlay else None return SpecialWindow(cmd, input_data, cwd_from=cwd_from, overlay_for=overlay_for, env=env) def add_fd_based_remote_control(self, remote_control_passwords: dict[str, Sequence[str]] | None = None, initial_data: bytes = b'') -> socket.socket: local, remote = socket.socketpair() os.set_inheritable(remote.fileno(), True) if initial_data: local.send(initial_data) lfd = os.dup(local.fileno()) local.close() try: peer_id = self.child_monitor.inject_peer(lfd) except Exception: os.close(lfd) remote.close() raise self.peer_data_map[peer_id] = remote_control_passwords return remote def run_background_process( self, cmd: list[str], cwd: str | None = None, env: dict[str, str] | None = None, stdin: bytes | None = None, cwd_from: CwdRequest | None = None, allow_remote_control: bool = False, remote_control_passwords: dict[str, Sequence[str]] | None = None, notify_on_death: Callable[[int, Exception | None], None] | None = None, # guaranteed to be called only after event loop tick stdout: int | None = None, stderr: int | None = None, ) -> None: env = env or None if env: env_ = default_env().copy() env_.update(env) env = env_ if cwd_from: with suppress(Exception): cwd = cwd_from.cwd_of_child def add_env(key: str, val: str) -> None: nonlocal env if env is None: env = default_env().copy() env[key] = val def doit(activation_token: str = '') -> None: nonlocal env pass_fds: list[int] = [] fds_to_close_on_launch_failure: list[int] = [] if allow_remote_control: remote = self.add_fd_based_remote_control(remote_control_passwords) pass_fds.append(remote.fileno()) add_env('KITTY_LISTEN_ON', f'fd:{remote.fileno()}') if activation_token: add_env('XDG_ACTIVATION_TOKEN', activation_token) fds_to_close_on_launch_failure = list(pass_fds) if stdout is not None and stdout > -1: pass_fds.append(stdout) if stderr is not None and stderr > -1 and stderr not in pass_fds: pass_fds.append(stderr) def run(stdin: int | None, stdout: int | None, stderr: int | None) -> None: try: p = subprocess.Popen( cmd, env=env, cwd=cwd, preexec_fn=clear_handled_signals, pass_fds=pass_fds, stdin=stdin, stdout=stdout, stderr=stderr) if notify_on_death: self.background_process_death_notify_map[p.pid] = notify_on_death monitor_pid(p.pid) except Exception as err: for fd in fds_to_close_on_launch_failure: with suppress(OSError): os.close(fd) if notify_on_death: def callback(err: Exception, timer_id: int | None) -> None: notify_on_death(-1, err) add_timer(partial(callback, err), 0, False) else: self.show_error(_('Failed to run background process'), _('Failed to run background process with error: {}').format(err)) r = subprocess.DEVNULL if stdin: r, w = safe_pipe(False) fds_to_close_on_launch_failure.append(w) pass_fds.append(r) try: run(r, stdout, stderr) if stdin: thread_write(w, stdin) finally: if stdin: os.close(r) if allow_remote_control: remote.close() if is_wayland(): if not run_with_activation_token(doit): doit() else: doit() def pipe(self, source: str, dest: str, exe: str, *args: str) -> Window | None: cmd = [exe] + list(args) window = self.active_window cwd_from = CwdRequest(window) if window else None def create_window() -> SpecialWindowInstance: return self.special_window_for_cmd( cmd, stdin=source, as_overlay=dest == 'overlay', cwd_from=cwd_from) if dest == 'overlay' or dest == 'window': tab = self.active_tab if tab is not None: return tab.new_special_window(create_window()) elif dest == 'tab': tm = self.active_tab_manager if tm is not None: tm.new_tab(special_window=create_window(), cwd_from=cwd_from) elif dest == 'os_window': self._new_os_window(create_window(), cwd_from=cwd_from) elif dest in ('clipboard', 'primary'): env, stdin = self.process_stdin_source(stdin=source, window=window) if stdin: if dest == 'clipboard': set_clipboard_string(stdin) self.handle_clipboard_loss('clipboard') else: set_primary_selection(stdin) self.handle_clipboard_loss('primary') else: env, stdin = self.process_stdin_source(stdin=source, window=window) self.run_background_process(cmd, cwd_from=cwd_from, stdin=stdin, env=env) return None def args_to_special_window(self, args: Iterable[str], cwd_from: CwdRequest | None = None) -> SpecialWindowInstance: args = list(args) stdin = None w = self.active_window if args[0].startswith('@') and args[0] != '@': q = data_for_at(w, args[0]) or None if q is not None: stdin = q.encode('utf-8') del args[0] cmd = [] for arg in args: if arg == '@selection': q = data_for_at(w, arg) if not q: continue arg = q cmd.append(arg) return SpecialWindow(cmd, stdin, cwd_from=cwd_from) def _new_tab(self, args: SpecialWindowInstance | Iterable[str], cwd_from: CwdRequest | None = None, as_neighbor: bool = False) -> Tab | None: special_window = None if args: if isinstance(args, SpecialWindowInstance): special_window = args else: special_window = self.args_to_special_window(args, cwd_from=cwd_from) if not self.os_window_map: self.add_os_window() tm = self.active_tab_manager if tm is None and not self.os_window_map: os_window_id = self.add_os_window() tm = self.os_window_map.get(os_window_id) if tm is not None: return tm.new_tab(special_window=special_window, cwd_from=cwd_from, as_neighbor=as_neighbor) return None def _create_tab(self, args: list[str], cwd_from: CwdRequest | None = None) -> None: as_neighbor = False if args and args[0].startswith('!'): as_neighbor = 'neighbor' in args[0][1:].split(',') args = args[1:] self._new_tab(args, as_neighbor=as_neighbor, cwd_from=cwd_from) @ac('tab', 'Create a new tab') def new_tab(self, *args: str) -> None: self._create_tab(list(args)) @ac('tab', 'Create a new tab with working directory for the window in it set to the same as the active window') def new_tab_with_cwd(self, *args: str) -> None: self._create_tab(list(args), cwd_from=CwdRequest(self.window_for_dispatch or self.active_window_for_cwd)) def new_tab_with_wd(self, wd: str | list[str], str_is_multiple_paths: bool = False) -> None: if isinstance(wd, str): wd = wd.split(os.pathsep) if str_is_multiple_paths else [wd] for path in wd: special_window = SpecialWindow(None, cwd=path) self._new_tab(special_window) def _new_window(self, args: list[str], cwd_from: CwdRequest | None = None) -> Window | None: if not self.os_window_map: os_window_id = self.add_os_window() tm = self.os_window_map.get(os_window_id) if tm is not None and not tm.active_tab: tm.new_tab(empty_tab=True) tab = self.active_tab if tab is None: return None allow_remote_control = False location = None if args and args[0].startswith('!'): location = args[0][1:].lower() args = args[1:] if args and args[0] == '@': args = args[1:] allow_remote_control = True if args: return tab.new_special_window( self.args_to_special_window(args, cwd_from=cwd_from), location=location, allow_remote_control=allow_remote_control) else: return tab.new_window(cwd_from=cwd_from, location=location, allow_remote_control=allow_remote_control) @ac('win', 'Create a new window') def new_window(self, *args: str) -> None: self._new_window(list(args)) @ac('win', 'Create a new window with working directory same as that of the active window') def new_window_with_cwd(self, *args: str) -> None: w = self.window_for_dispatch or self.active_window_for_cwd if w is None: return self.new_window(*args) self._new_window(list(args), cwd_from=CwdRequest(w)) @ac('misc', ''' Launch the specified program in a new window/tab/etc. See :doc:`launch` for details ''') def launch(self, *args: str) -> None: from kitty.launch import launch, parse_launch_args opts, args_ = parse_launch_args(args) if self.window_for_dispatch: opts.source_window = opts.source_window or f'id:{self.window_for_dispatch.id}' opts.next_to = opts.next_to or f'id:{self.window_for_dispatch.id}' launch(self, opts, args_) @ac('tab', 'Move the active tab forward') def move_tab_forward(self) -> None: tm = self.active_tab_manager if tm is not None: tm.move_tab(1) @ac('tab', 'Move the active tab backward') def move_tab_backward(self) -> None: tm = self.active_tab_manager_with_dispatch if tm is not None: tm.move_tab(-1) @ac('misc', ''' Turn on/off ligatures in the specified window See :opt:`disable_ligatures` for details ''') def disable_ligatures_in(self, where: str | Iterable[Window], strategy: int) -> None: w = self.window_for_dispatch or self.active_window if isinstance(where, str): windows: list[Window] = [] if where == 'active': if w: windows = [w] elif where == 'all': windows = list(self.all_windows) elif where == 'tab': if w: tab = w.tabref() if tab: windows = list(tab) else: windows = list(where) for window in windows: window.screen.disable_ligatures = strategy window.refresh() def apply_new_options(self, opts: Options) -> None: bg_colors_before = {w.id: w.screen.color_profile.default_bg for w in self.all_windows} # Update options storage set_options(opts, is_wayland(), self.args.debug_rendering, self.args.debug_font_fallback) apply_options_update() set_layout_options(opts) set_default_env(opts.env.copy()) # Update font data from .fonts.render import set_font_family set_font_family(opts) for os_window_id, tm in self.os_window_map.items(): if tm is not None: os_window_font_size(os_window_id, opts.font_size, True) tm.resize() # Update key bindings if is_macos: from .fast_data_types import cocoa_clear_global_shortcuts cocoa_clear_global_shortcuts() self.mappings.update_keymap() if is_macos: from .fast_data_types import cocoa_recreate_global_menu cocoa_recreate_global_menu() # Update misc options try: set_background_image(opts.background_image, tuple(self.os_window_map), True, opts.background_image_layout) except Exception as e: log_error(f'Failed to set background image with error: {e}') for tm in self.all_tab_managers: tm.apply_options() # Update colors if theme_colors.applied_theme and theme_colors.refresh(): theme_colors.apply_theme(theme_colors.applied_theme, notify_on_bg_change=False) for w in self.all_windows: if w.screen.color_profile.default_bg != bg_colors_before.get(w.id): self.default_bg_changed_for(w.id) w.refresh(reload_all_gpu_data=True) load_shader_programs.recompile_if_needed() @ac('misc', ''' Reload the config file If mapped without arguments reloads the default config file, otherwise loads the specified config files, in order. Loading a config file *replaces* all config options. For example:: map f5 load_config_file /path/to/some/kitty.conf ''') def load_config_file(self, *paths: str, apply_overrides: bool = True, overrides: Sequence[str] = ()) -> None: from .cli import default_config_paths from .config import load_config old_opts = get_options() prev_paths = old_opts.all_config_paths or default_config_paths(self.args.config) paths = paths or prev_paths bad_lines: list[BadLine] = [] final_overrides = old_opts.config_overrides if apply_overrides else () if overrides: final_overrides += tuple(overrides) opts = load_config(*paths, overrides=final_overrides or None, accumulate_bad_lines=bad_lines) if bad_lines: self.show_bad_config_lines(bad_lines) self.apply_new_options(opts) from .open_actions import clear_caches clear_caches() from .guess_mime_type import clear_mime_cache clear_mime_cache() store_effective_config() def safe_delete_temp_file(self, path: str) -> None: if is_path_in_temp_dir(path): with suppress(FileNotFoundError): os.remove(path) def is_ok_to_read_image_file(self, path: str, fd: int) -> bool: return is_ok_to_read_image_file(path, fd) def set_update_check_process(self, process: Optional['PopenType[bytes]'] = None) -> None: if self.update_check_process is not None: with suppress(Exception): if self.update_check_process.poll() is None: self.update_check_process.kill() self.update_check_process = process def on_monitored_pid_death(self, pid: int, exit_status: int) -> None: callback = self.background_process_death_notify_map.pop(pid, None) if callback is not None: try: callback(exit_status, None) except Exception: import traceback traceback.print_exc() return update_check_process = self.update_check_process if update_check_process is not None and pid == update_check_process.pid: self.update_check_process = None from .update_check import process_current_release try: assert update_check_process.stdout is not None raw = update_check_process.stdout.read().decode('utf-8') except Exception as e: log_error(f'Failed to read data from update check process, with error: {e}') else: try: process_current_release(raw) except Exception as e: log_error(f'Failed to process update check data {raw!r}, with error: {e}') def show_bad_config_lines(self, bad_lines: Iterable[BadLine], misc_errors: Iterable[str] = ()) -> None: def format_bad_line(bad_line: BadLine) -> str: return f'{bad_line.number}:{bad_line.exception} in line: {bad_line.line}\n' groups: dict[str, list[BadLine]] = {} for bl in bad_lines: groups.setdefault(bl.file, []).append(bl) ans: list[str] = [] a = ans.append for file in sorted(groups): if file: a(f'In file {file}:') [a(format_bad_line(x)) for x in groups[file]] if misc_errors: a('In final effective configuration:') for line in misc_errors: a(line) msg = '\n'.join(ans).rstrip() self.show_error(_('Errors parsing configuration'), msg) @ac('misc', ''' Change colors in the specified windows For details, see :ref:`at-set-colors`. For example:: map f5 set_colors --configured /path/to/some/config/file/colors.conf ''') def set_colors(self, *args: str) -> None: from kitty.rc.base import PayloadGetter, command_for_name, parse_subcommand_cli from kitty.remote_control import parse_rc_args c = command_for_name('set_colors') try: opts, items = parse_subcommand_cli(c, ['set-colors'] + list(args)) except (Exception, SystemExit) as err: self.show_error('Invalid set_colors mapping', str(err)) return try: payload = c.message_to_kitty(parse_rc_args([])[0], opts, items) except (Exception, SystemExit) as err: self.show_error('Failed to set colors', str(err)) return c.response_from_kitty(self, self.window_for_dispatch or self.active_window, PayloadGetter(c, payload if isinstance(payload, dict) else {})) def _move_window_to( self, window: Window | None = None, target_tab_id: str | int | None = None, target_os_window_id: str | int | None = None ) -> None: window = window or self.active_window if not window: return src_tab = self.tab_for_window(window) if src_tab is None: return with self.suppress_focus_change_events(): if target_os_window_id == 'new': target_os_window_id = self.add_os_window() tm = self.os_window_map[target_os_window_id] target_tab = tm.new_tab(empty_tab=True) else: target_os_window_id = target_os_window_id or current_os_window() if isinstance(target_tab_id, str): if not isinstance(target_os_window_id, int): q = self.active_tab_manager assert q is not None tm = q else: tm = self.os_window_map[target_os_window_id] if target_tab_id.startswith('new'): # valid values for target_tab_id are 'new', 'new_after' and 'new_before' target_tab = tm.new_tab(empty_tab=True, location=(target_tab_id[4:] or 'last')) else: target_tab = tm.tab_at_location(target_tab_id) or tm.new_tab(empty_tab=True) else: for tab in self.all_tabs: if tab.id == target_tab_id: target_tab = tab target_os_window_id = tab.os_window_id break else: return for detached_window in src_tab.detach_window(window): target_tab.attach_window(detached_window) self._cleanup_tab_after_window_removal(src_tab) target_tab.make_active() def _move_tab_to(self, tab: Tab | None = None, target_os_window_id: int | None = None) -> None: tab = tab or self.active_tab if tab is None: return if target_os_window_id is None: target_os_window_id = self.add_os_window() tm = self.os_window_map[target_os_window_id] target_tab = tm.new_tab(empty_tab=True) target_tab.take_over_from(tab) self._cleanup_tab_after_window_removal(tab) target_tab.make_active() def choose_entry( self, title: str, entries: Iterable[tuple[_T | str | None, str]], callback: Callable[[_T | str | None], None], subtitle: str = '', hints_args: tuple[str, ...] | None = None, ) -> Window | None: lines = [title, subtitle, ' '] if subtitle else [title, ' '] idx_map: list[_T | str | None] = [] ans: str | _T | None = None fmt = ': {1}' for obj, text in entries: idx_map.append(obj) if obj is None: lines.append(text) else: lines.append(fmt.format(len(idx_map), text)) def done(data: dict[str, Any], target_window_id: int, self: Boss) -> None: nonlocal ans ans = idx_map[int(data['groupdicts'][0]['index'])] def done2(target_window_id: int, self: Boss) -> None: callback(ans) q = self.run_kitten_with_metadata( 'hints', args=( '--ascending', '--customize-processing=::import::kitty.choose_entry', '--window-title', title, *(hints_args or ()) ), input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2 ) return q if isinstance(q, Window) else None @ac('tab', 'Interactively select a tab to switch to') def select_tab(self) -> None: def chosen(ans: None | str | int) -> None: if isinstance(ans, int): for tab in self.all_tabs: if tab.id == ans: self.set_active_tab(tab) def format_tab_title(tab: Tab) -> str: w = 'windows' if tab.num_window_groups > 1 else 'window' return f'{tab.name or tab.title} [{tab.num_window_groups} {w}]' w = self.window_for_dispatch or self.active_window ct = w.tabref() if w else None self.choose_entry( 'Choose a tab to switch to', ((None, f'Current tab: {format_tab_title(t)}') if t is ct else (t.id, format_tab_title(t)) for t in self.all_tabs), chosen ) @ac('win', ''' Detach a window, moving it to another tab or OS Window See :ref:`detaching windows ` for details. ''') def detach_window(self, *args: str) -> None: if not args or args[0] == 'new': return self._move_window_to(target_os_window_id='new') if args[0] in ('new-tab', 'tab-prev', 'tab-left', 'tab-right', 'new-tab-left', 'new-tab-right'): if args[0] == 'new-tab': where = 'new' elif args[0] == 'new-tab-right': where = 'new_after' elif args[0] == 'new-tab-left': where = 'new_before' else: where = args[0][4:] return self._move_window_to(target_tab_id=where) w = self.window_for_dispatch or self.active_window ct = w.tabref() if w else None items: list[tuple[str | int, str]] = [(t.id, t.effective_title) for t in self.all_tabs if t is not ct] items.append(('new_tab', 'New tab')) items.append(('new_os_window', 'New OS Window')) target_window = w def chosen(ans: None | str | int) -> None: if ans is not None: if isinstance(ans, str): if ans == 'new_os_window': self._move_window_to(target_os_window_id='new') elif ans == 'new_tab': self._move_window_to(target_tab_id=ans) else: self._move_window_to(target_window, target_tab_id=ans) self.choose_entry('Choose a tab to move the window to', items, chosen) @ac('tab', ''' Detach a tab, moving it to another OS Window See :ref:`detaching windows ` for details. ''') def detach_tab(self, *args: str) -> None: if not args or args[0] == 'new': return self._move_tab_to() items: list[tuple[str | int, str]] = [] ct = self.active_tab_manager_with_dispatch for osw_id, tm in self.os_window_map.items(): if tm is not ct and tm.active_tab: items.append((osw_id, tm.active_tab.title)) items.append(('new', 'New OS Window')) w = self.window_for_dispatch or self.active_window target_tab = w.tabref() if w else None def chosen(ans: None | int | str) -> None: if ans is not None: os_window_id = None if isinstance(ans, str) else ans self._move_tab_to(tab=target_tab, target_os_window_id=os_window_id) self.choose_entry('Choose an OS window to move the tab to', items, chosen) def set_background_image(self, path: str | None, os_windows: tuple[int, ...], configured: bool, layout: str | None, png_data: bytes = b'') -> None: set_background_image(path, os_windows, configured, layout, png_data) for os_window_id in os_windows: self.default_bg_changed_for(os_window_id) # Can be called with kitty -o "map f1 send_test_notification" def send_test_notification(self) -> None: self.notification_manager.send_test_notification() @ac('debug', 'Show the environment variables that the kitty process sees') def show_kitty_env_vars(self) -> None: w = self.window_for_dispatch or self.active_window env = os.environ.copy() if is_macos and env.get('LC_CTYPE') == 'UTF-8' and not getattr(sys, 'kitty_run_data').get('lc_ctype_before_python'): del env['LC_CTYPE'] if w: output = '\n'.join(f'{k}={v}' for k, v in env.items()) self.display_scrollback(w, output, title=_('Current kitty env vars'), report_cursor=False) @ac('debug', ''' Close all shared SSH connections See :opt:`share_connections ` for details. ''') def close_shared_ssh_connections(self) -> None: cleanup_ssh_control_masters() def launch_urls(self, *urls: str, no_replace_window: bool = False) -> None: from .launch import force_window_launch from .open_actions import actions_for_launch actions: list[KeyAction] = [] failures = [] for url in urls: uactions = tuple(actions_for_launch(url)) if uactions: actions.extend(uactions) else: failures.append(url) tab = self.active_tab if tab is not None: w = tab.active_window else: w = None needs_window_replaced = False if not no_replace_window and not get_options().startup_session: if w is not None and w.id == 1 and monotonic() - w.started_at < 2 and len(tuple(self.all_windows)) == 1: # first window, soon after startup replace it needs_window_replaced = True def clear_initial_window() -> None: if needs_window_replaced and tab is not None and w is not None: tab.remove_window(w) if failures: from kittens.tui.operations import styled spec = '\n '.join(styled(u, fg='yellow') for u in failures) special_window = self.create_special_window_for_show_error('Open URL error', f"Unknown URL type, cannot open:\n {spec}") if needs_window_replaced and tab is not None: tab.new_special_window(special_window) else: self._new_os_window(special_window) clear_initial_window() needs_window_replaced = False if actions: with force_window_launch(needs_window_replaced): self.dispatch_action(actions.pop(0)) clear_initial_window() if actions: self.drain_actions(actions) @ac('debug', 'Show the effective configuration kitty is running with') def debug_config(self) -> None: from .debug_config import debug_config w = self.window_for_dispatch or self.active_window if w is not None: output = debug_config(get_options(), self.mappings.global_shortcuts) set_clipboard_string(re.sub(r'\x1b.+?m', '', output)) self.handle_clipboard_loss('clipboard') output += '\n\x1b[35mThis debug output has been copied to the clipboard\x1b[m' # ]]] self.display_scrollback(w, output, title=_('Current kitty options'), report_cursor=False) @ac('misc', 'Discard this event completely ignoring it') def discard_event(self) -> None: pass mouse_discard_event = discard_event def sanitize_url_for_dispay_to_user(self, url: str) -> str: return sanitize_url_for_dispay_to_user(url) def on_system_color_scheme_change(self, appearance: ColorSchemes, is_initial_value: bool) -> None: theme_colors.on_system_color_scheme_change(appearance, is_initial_value) @ac('win', ''' Toggle to the tab matching the specified expression Switches to the matching tab if another tab is current, otherwise switches to the last used tab. Useful to easily switch to and back from a tab using a single shortcut. Note that toggling works only between tabs in the same OS window. See :ref:`search_syntax` for details on the match expression. For example:: map f1 toggle_tab title:mytab ''') def toggle_tab(self, match_expression: str) -> None: tm = self.active_tab_manager_with_dispatch if tm is not None: tm.toggle_tab(match_expression) def update_progress_in_dock(self) -> None: if not is_macos: return has_indeterminate_progress = False num_of_windows_with_progress = total_progress = 0 for tm in self.os_window_map.values(): if tm.num_of_windows_with_progress: total_progress += tm.total_progress num_of_windows_with_progress += tm.num_of_windows_with_progress if tm.has_indeterminate_progress: has_indeterminate_progress = True from .fast_data_types import cocoa_show_progress_bar_on_dock_icon if num_of_windows_with_progress: cocoa_show_progress_bar_on_dock_icon(min(100, total_progress / num_of_windows_with_progress)) elif has_indeterminate_progress: cocoa_show_progress_bar_on_dock_icon(101) else: cocoa_show_progress_bar_on_dock_icon() def on_clipboard_lost(self, which: Literal['clipboard', 'primary']) -> None: self.handle_clipboard_loss(which) def handle_clipboard_loss(self, which: Literal['clipboard', 'primary'], exception: int = 0) -> None: opts = get_options() if opts.clear_selection_on_clipboard_loss and (which == 'primary' or opts.copy_on_select == 'clipboard'): for wid, window in self.window_id_map.items(): if wid == exception: continue window.screen.clear_selection() kitty-0.41.1/kitty/cell_defines.glsl0000664000175000017510000000165214773370543016756 0ustar nileshnilesh#define PHASE_BOTH 1 #define PHASE_BACKGROUND 2 #define PHASE_SPECIAL 3 #define PHASE_FOREGROUND 4 #define PHASE {WHICH_PHASE} #define HAS_TRANSPARENCY {TRANSPARENT} #define DO_FG_OVERRIDE {DO_FG_OVERRIDE} #define FG_OVERRIDE_THRESHOLD {FG_OVERRIDE_THRESHOLD} #define FG_OVERRIDE_ALGO {FG_OVERRIDE_ALGO} #define TEXT_NEW_GAMMA {TEXT_NEW_GAMMA} #define DECORATION_SHIFT {DECORATION_SHIFT} #define REVERSE_SHIFT {REVERSE_SHIFT} #define STRIKE_SHIFT {STRIKE_SHIFT} #define DIM_SHIFT {DIM_SHIFT} #define MARK_SHIFT {MARK_SHIFT} #define MARK_MASK {MARK_MASK} #define USE_SELECTION_FG #define NUM_COLORS 256 #if (PHASE == PHASE_BOTH) || (PHASE == PHASE_BACKGROUND) || (PHASE == PHASE_SPECIAL) #define NEEDS_BACKROUND #endif #if (PHASE == PHASE_BOTH) || (PHASE == PHASE_FOREGROUND) #define NEEDS_FOREGROUND #endif #if (HAS_TRANSPARENCY == 1) #define TRANSPARENT #endif // sRGB luminance values const vec3 Y = vec3(0.2126, 0.7152, 0.0722); kitty-0.41.1/kitty/cell_fragment.glsl0000664000175000017510000002007314773370543017142 0ustar nileshnilesh#pragma kitty_include_shader #pragma kitty_include_shader #pragma kitty_include_shader in vec3 background; in float draw_bg; in float bg_alpha; #ifdef NEEDS_FOREGROUND uniform sampler2DArray sprites; uniform float text_contrast; uniform float text_gamma_adjustment; in float effective_text_alpha; in vec3 sprite_pos; in vec3 underline_pos; in vec3 cursor_pos; in vec3 strike_pos; flat in uint underline_exclusion_pos; in vec3 foreground; in vec4 cursor_color_premult; in vec3 decoration_fg; in float colored_sprite; #endif out vec4 output_color; // Util functions {{{ vec4 vec4_premul(vec3 rgb, float a) { return vec4(rgb * a, a); } vec4 vec4_premul(vec4 rgba) { return vec4(rgba.rgb * rgba.a, rgba.a); } // }}} /* * Explanation of rendering: * There are two types of rendering, single pass and multi-pass. Multi-pass rendering is used when there * are images that are below the foreground. Single pass rendering has PHASE=PHASE_BOTH. Otherwise, there * are three passes, PHASE=PHASE_BACKGROUND, PHASE=PHASE_SPECIAL, PHASE=PHASE_FOREGROUND. * 1) Single pass -- this path is used when there are either no images, or all images are * drawn on top of text. In this case, there is a single pass, * of this shader with cell foreground and background colors blended directly. * Expected output is either opaque colors or pre-multiplied colors. * * 2) Interleaved -- this path is used if background is not opaque and there are images or * if the background is opaque but there are images under text. Rendering happens in * multiple passes drawing the background and foreground separately and blending. * * 2a) Opaque bg with images under text * There are multiple passes, each pass is blended onto the previous using the opaque blend func (alpha, 1- alpha). TRANSPARENT is not * defined in the shaders. * 1) Draw background for all cells * 2) Draw the images that are supposed to be below both the background and text, if any. This happens in the graphics shader * 3) Draw the background of cells that don't have the default background if any images were drawn in 2 above * 4) Draw the images that are supposed to be below text but not background, again in graphics shader. * 5) Draw the special cells (selection/cursor). Output is same as from step 1, with bg_alpha 1 for special cells and 0 otherwise * 6) Draw the foreground -- expected output is color with alpha premultiplied which is blended using the premult blend func * 7) Draw the images that are supposed to be above text again in the graphics shader * * 2b) Transparent bg with images * Same as (2a) except blending is done with PREMULT_BLEND and TRANSPARENT is defined in the shaders. background_opacity * is applied to default colored background cells in step (1). */ // foreground functions {{{ #ifdef NEEDS_FOREGROUND // Scaling factor for the extra text-alpha adjustment for luminance-difference. const float text_gamma_scaling = 0.5; float clamp_to_unit_float(float x) { // Clamp value to suitable output range return clamp(x, 0.0f, 1.0f); } #if TEXT_NEW_GAMMA == 1 vec4 foreground_contrast(vec4 over, vec3 under) { float under_luminance = dot(under, Y); float over_lumininace = dot(over.rgb, Y); // Apply additional gamma-adjustment scaled by the luminance difference, the darker the foreground the more adjustment we apply. // A multiplicative contrast is also available to increase saturation. over.a = clamp_to_unit_float(mix(over.a, pow(over.a, text_gamma_adjustment), (1 - over_lumininace + under_luminance) * text_gamma_scaling) * text_contrast); return over; } #else vec4 foreground_contrast(vec4 over, vec3 under) { // Simulation of gamma-incorrect blending float under_luminance = dot(under, Y); float over_lumininace = dot(over.rgb, Y); // This is the original gamma-incorrect rendering, it is the solution of the following equation: // // linear2srgb(over * overA2 + under * (1 - overA2)) = linear2srgb(over) * over.a + linear2srgb(under) * (1 - over.a) // ^ gamma correct blending with new alpha ^ gamma incorrect blending with old alpha over.a = clamp_to_unit_float((srgb2linear(linear2srgb(over_lumininace) * over.a + linear2srgb(under_luminance) * (1.0f - over.a)) - under_luminance) / (over_lumininace - under_luminance)); return over; } #endif vec4 load_text_foreground_color() { // For colored sprites use the color from the sprite rather than the text foreground // Return non-premultiplied foreground color vec4 text_fg = texture(sprites, sprite_pos); return vec4(mix(foreground, text_fg.rgb, colored_sprite), text_fg.a); } vec4 calculate_premul_foreground_from_sprites(vec4 text_fg) { // Return premul foreground color from decorations (cursor, underline, strikethrough) ivec3 sz = textureSize(sprites, 0); float underline_alpha = texture(sprites, underline_pos).a; float underline_exclusion = texelFetch(sprites, ivec3(int( sprite_pos.x * float(sz.x)), int(underline_exclusion_pos), int(sprite_pos.z)), 0).a; underline_alpha *= 1.0f - underline_exclusion; float strike_alpha = texture(sprites, strike_pos).a; float cursor_alpha = texture(sprites, cursor_pos).a; // Since strike and text are the same color, we simply add the alpha values float combined_alpha = min(text_fg.a + strike_alpha, 1.0f); // Underline color might be different, so alpha blend vec4 ans = alpha_blend(vec4(text_fg.rgb, combined_alpha * effective_text_alpha), vec4(decoration_fg, underline_alpha * effective_text_alpha)); return mix(ans, cursor_color_premult, cursor_alpha * cursor_color_premult.a); } vec4 adjust_foreground_contrast_with_background(vec4 text_fg, vec3 bg) { // When rendering on a background we can adjust the alpha channel contrast // to improve legibility based on the source and destination colors return foreground_contrast(text_fg, bg); } #endif // end foreground functions }}} float adjust_alpha_for_incorrect_blending_by_compositor(float text_fg_alpha, float final_alpha) { // Adjust the transparent alpha-channel to account for incorrect // gamma-blending performed by the compositor (true for at least wlroots, picom) // We have a linear alpha channel apply the sRGB curve to it once again to compensate // for the incorrect blending in the compositor. // We apply the correction only if there was actual text at this pixel, so as to not make // background_opacity non-linear // See https://github.com/kovidgoyal/kitty/issues/6209 for discussion. // ans = text_fg_alpha * linear2srgb(final_alpha) + (1 - text_fg_alpha) * final_alpha return mix(final_alpha, linear2srgb(final_alpha), text_fg_alpha); } void main() { vec4 final_color; #if (PHASE == PHASE_BOTH) vec4 text_fg = load_text_foreground_color(); text_fg = adjust_foreground_contrast_with_background(text_fg, background); vec4 text_fg_premul = calculate_premul_foreground_from_sprites(text_fg); #ifdef TRANSPARENT final_color = alpha_blend_premul(text_fg_premul, vec4_premul(background, bg_alpha)); final_color.a = adjust_alpha_for_incorrect_blending_by_compositor(text_fg_premul.a, final_color.a); #else final_color = alpha_blend_premul(text_fg_premul, background); #endif #endif #if (PHASE == PHASE_SPECIAL) #ifdef TRANSPARENT final_color = vec4_premul(background, bg_alpha); #else final_color = vec4(background, bg_alpha); #endif #endif #if (PHASE == PHASE_BACKGROUND) #ifdef TRANSPARENT final_color = vec4_premul(background, bg_alpha); #else final_color = vec4(background, draw_bg * bg_alpha); #endif #endif #if (PHASE == PHASE_FOREGROUND) vec4 text_fg = load_text_foreground_color(); text_fg = adjust_foreground_contrast_with_background(text_fg, background); vec4 text_fg_premul = calculate_premul_foreground_from_sprites(text_fg); final_color = text_fg_premul; #ifdef TRANSPARENT final_color.a = adjust_alpha_for_incorrect_blending_by_compositor(text_fg_premul.a, final_color.a); #endif #endif output_color = final_color; } kitty-0.41.1/kitty/cell_vertex.glsl0000664000175000017510000003363214773370543016661 0ustar nileshnilesh#extension GL_ARB_explicit_attrib_location : require #pragma kitty_include_shader // Inputs {{{ layout(std140) uniform CellRenderData { float xstart, ystart, dx, dy, use_cell_bg_for_selection_fg, use_cell_fg_for_selection_fg, use_cell_for_selection_bg; uint default_fg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted; uint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx, cell_height; uint cursor_x1, cursor_x2, cursor_y1, cursor_y2; float cursor_opacity; // must have unique entries with 0 being default_bg and unset being UINT32_MAX uint bg_colors0, bg_colors1, bg_colors2, bg_colors3, bg_colors4, bg_colors5, bg_colors6, bg_colors7; float bg_opacities0, bg_opacities1, bg_opacities2, bg_opacities3, bg_opacities4, bg_opacities5, bg_opacities6, bg_opacities7; uint color_table[NUM_COLORS + MARK_MASK + MARK_MASK + 2]; }; uniform float gamma_lut[256]; #ifdef NEEDS_FOREGROUND uniform usampler2D sprite_decorations_map; #endif #if (PHASE == PHASE_BACKGROUND) uniform uint draw_bg_bitfield; #endif // Have to use fixed locations here as all variants of the cell program share the same VAOs layout(location=0) in uvec3 colors; layout(location=1) in uvec2 sprite_idx; layout(location=2) in uint is_selected; const int fg_index_map[] = int[3](0, 1, 0); const uvec2 cell_pos_map[] = uvec2[4]( uvec2(1, 0), // right, top uvec2(1, 1), // right, bottom uvec2(0, 1), // left, bottom uvec2(0, 0) // left, top ); // }}} out vec3 background; out float draw_bg; out float bg_alpha; #ifdef NEEDS_FOREGROUND uniform float inactive_text_alpha; uniform float dim_opacity; out vec3 sprite_pos; out vec3 underline_pos; out vec3 cursor_pos; out vec4 cursor_color_premult; out vec3 strike_pos; flat out uint underline_exclusion_pos; out vec3 foreground; out vec3 decoration_fg; out float colored_sprite; out float effective_text_alpha; #endif // Utility functions {{{ const uint BYTE_MASK = uint(0xFF); const uint SPRITE_INDEX_MASK = uint(0x7fffffff); const uint SPRITE_COLORED_MASK = uint(0x80000000); const uint SPRITE_COLORED_SHIFT = uint(31); const uint ZERO = uint(0); const uint ONE = uint(1); const uint TWO = uint(2); const uint DECORATION_MASK = uint({DECORATION_MASK}); vec3 color_to_vec(uint c) { uint r, g, b; r = (c >> 16) & BYTE_MASK; g = (c >> 8) & BYTE_MASK; b = c & BYTE_MASK; return vec3(gamma_lut[r], gamma_lut[g], gamma_lut[b]); } float one_if_equal_zero_otherwise(int a, int b) { return 1.0f - clamp(abs(float(a) - float(b)), 0.0f, 1.0f); } float one_if_equal_zero_otherwise(uint a, uint b) { return 1.0f - clamp(abs(float(a) - float(b)), 0.0f, 1.0f); } uint resolve_color(uint c, uint defval) { // Convert a cell color to an actual color based on the color table int t = int(c & BYTE_MASK); uint is_one = uint(one_if_equal_zero_otherwise(t, 1)); uint is_two = uint(one_if_equal_zero_otherwise(t, 2)); uint is_neither_one_nor_two = 1u - is_one - is_two; return is_one * color_table[(c >> 8) & BYTE_MASK] + is_two * (c >> 8) + is_neither_one_nor_two * defval; } vec3 to_color(uint c, uint defval) { return color_to_vec(resolve_color(c, defval)); } #ifdef NEEDS_FOREGROUND uvec3 to_sprite_coords(uint idx) { uint sprites_per_page = sprites_xnum * sprites_ynum; uint z = idx / sprites_per_page; uint num_on_last_page = idx - sprites_per_page * z; uint y = num_on_last_page / sprites_xnum; uint x = num_on_last_page - sprites_xnum * y; return uvec3(x, y, z); } vec3 to_sprite_pos(uvec2 pos, uint idx) { uvec3 c = to_sprite_coords(idx); vec2 s_xpos = vec2(c.x, float(c.x) + 1.0f) * (1.0f / float(sprites_xnum)); vec2 s_ypos = vec2(c.y, float(c.y) + 1.0f) * (1.0f / float(sprites_ynum)); uint texture_height_px = (cell_height + 1u) * sprites_ynum; float row_height = 1.0f / float(texture_height_px); s_ypos[1] -= row_height; // skip the decorations_exclude row return vec3(s_xpos[pos.x], s_ypos[pos.y], c.z); } uint to_underline_exclusion_pos() { uvec3 c = to_sprite_coords(sprite_idx[0]); uint cell_top_px = c.y * (cell_height + 1u); return cell_top_px + cell_height; } uint read_sprite_decorations_idx() { int idx = int(sprite_idx[0] & SPRITE_INDEX_MASK); ivec2 sz = textureSize(sprite_decorations_map, 0); int y = idx / sz[0]; int x = idx - y * sz[0]; return texelFetch(sprite_decorations_map, ivec2(x, y), 0).r; } uvec2 get_decorations_indices(uint in_url /* [0, 1] */, uint text_attrs) { uint decorations_idx = read_sprite_decorations_idx(); uint strike_style = ((text_attrs >> STRIKE_SHIFT) & ONE); // 0 or 1 uint strike_idx = decorations_idx * strike_style; uint underline_style = ((text_attrs >> DECORATION_SHIFT) & DECORATION_MASK); underline_style = in_url * url_style + (1u - in_url) * underline_style; // [0, 5] uint has_underline = uint(step(0.5f, float(underline_style))); // [0, 1] return uvec2(strike_idx, has_underline * (decorations_idx + underline_style)); } #endif vec3 choose_color(float q, vec3 a, vec3 b) { return mix(b, a, q); } float choose_alpha(float q, float a, float b) { return mix(b, a, q); } float is_cursor(uint x, uint y) { uint clamped_x = clamp(x, cursor_x1, cursor_x2); uint clamped_y = clamp(y, cursor_y1, cursor_y2); return one_if_equal_zero_otherwise(x, clamped_x) * one_if_equal_zero_otherwise(y, clamped_y); } // }}} struct CellData { float has_cursor, has_block_cursor; uvec2 pos; } cell_data; CellData set_vertex_position() { uint instance_id = uint(gl_InstanceID); /* The current cell being rendered */ uint r = instance_id / xnum; uint c = instance_id - r * xnum; /* The position of this vertex, at a corner of the cell */ float left = xstart + c * dx; float top = ystart - r * dy; vec2 xpos = vec2(left, left + dx); vec2 ypos = vec2(top, top - dy); uvec2 pos = cell_pos_map[gl_VertexID]; gl_Position = vec4(xpos[pos.x], ypos[pos.y], 0, 1); #ifdef NEEDS_FOREGROUND // The character sprite being rendered sprite_pos = to_sprite_pos(pos, sprite_idx[0] & SPRITE_INDEX_MASK); colored_sprite = float((sprite_idx[0] & SPRITE_COLORED_MASK) >> SPRITE_COLORED_SHIFT); #endif float is_block_cursor = step(float(cursor_fg_sprite_idx), 0.5); float has_cursor = is_cursor(c, r); return CellData(has_cursor, has_cursor * is_block_cursor, pos); } float background_opacity_for(uint bg, uint colorval, float opacity_if_matched) { // opacity_if_matched if bg == colorval else 1 float not_matched = step(1.f, abs(float(colorval - bg))); // not_matched = 0 if bg == colorval else 1 return not_matched + opacity_if_matched * (1.f - not_matched); } float calc_background_opacity(uint bg) { return ( background_opacity_for(bg, bg_colors0, bg_opacities0) * background_opacity_for(bg, bg_colors1, bg_opacities1) * background_opacity_for(bg, bg_colors2, bg_opacities2) * background_opacity_for(bg, bg_colors3, bg_opacities3) * background_opacity_for(bg, bg_colors4, bg_opacities4) * background_opacity_for(bg, bg_colors5, bg_opacities5) * background_opacity_for(bg, bg_colors6, bg_opacities6) * background_opacity_for(bg, bg_colors7, bg_opacities7) ); } // Overriding of foreground colors for contrast requirements {{{ #if defined(NEEDS_FOREGROUND) && DO_FG_OVERRIDE == 1 #define OVERRIDE_FG_COLORS #pragma kitty_include_shader #if (FG_OVERRIDE_ALGO == 1) vec3 fg_override(float under_luminance, float over_lumininace, vec3 under, vec3 over) { // If the difference in luminance is too small, // force the foreground color to be black or white. float diff_luminance = abs(under_luminance - over_lumininace); float override_level = (1.f - colored_sprite) * step(diff_luminance, FG_OVERRIDE_THRESHOLD); float original_level = 1.f - override_level; return original_level * over + override_level * vec3(step(under_luminance, 0.5f)); } #else float contrast_ratio(float under_luminance, float over_luminance) { return clamp((max(under_luminance, over_luminance) + 0.05f) / (min(under_luminance, over_luminance) + 0.05f), 1.f, 21.f); } vec3 fg_override(float under_luminance, float over_luminance, vec3 under, vec3 over) { float ratio = contrast_ratio(under_luminance, over_luminance); vec3 diff = abs(under - over); vec3 over_hsluv = rgbToHsluv(over); const float min_contrast_ratio = FG_OVERRIDE_THRESHOLD; float target_lum_a = clamp((under_luminance + 0.05f) * min_contrast_ratio - 0.05f, 0.f, 1.f); float target_lum_b = clamp((under_luminance + 0.05f) / min_contrast_ratio - 0.05f, 0.f, 1.f); vec3 result_a = clamp(hsluvToRgb(vec3(over_hsluv.x, over_hsluv.y, target_lum_a * 100.f)), 0.f, 1.f); vec3 result_b = clamp(hsluvToRgb(vec3(over_hsluv.x, over_hsluv.y, target_lum_b * 100.f)), 0.f, 1.f); float result_a_ratio = contrast_ratio(under_luminance, dot(result_a, Y)); float result_b_ratio = contrast_ratio(under_luminance, dot(result_b, Y)); vec3 result = mix(result_a, result_b, step(result_a_ratio, result_b_ratio)); return mix(result, over, max(step(diff.r + diff.g + diff.g, 0.001f), step(min_contrast_ratio, ratio))); } #endif vec3 override_foreground_color(vec3 over, vec3 under) { float under_luminance = dot(under, Y); float over_lumininace = dot(over.rgb, Y); return fg_override(under_luminance, over_lumininace, under, over); } #endif // }}} void main() { CellData cell_data = set_vertex_position(); // set cell color indices {{{ uvec2 default_colors = uvec2(default_fg, bg_colors0); uint text_attrs = sprite_idx[1]; uint is_reversed = ((text_attrs >> REVERSE_SHIFT) & ONE); uint is_inverted = is_reversed + inverted; int fg_index = fg_index_map[is_inverted]; int bg_index = 1 - fg_index; int mark = int(text_attrs >> MARK_SHIFT) & MARK_MASK; uint has_mark = uint(step(1, float(mark))); uint bg_as_uint = resolve_color(colors[bg_index], default_colors[bg_index]); bg_as_uint = has_mark * color_table[NUM_COLORS + mark - 1] + (ONE - has_mark) * bg_as_uint; vec3 bg = color_to_vec(bg_as_uint); uint fg_as_uint = resolve_color(colors[fg_index], default_colors[fg_index]); // }}} // Foreground {{{ #ifdef NEEDS_FOREGROUND // Foreground fg_as_uint = has_mark * color_table[NUM_COLORS + MARK_MASK + mark] + (ONE - has_mark) * fg_as_uint; foreground = color_to_vec(fg_as_uint); float has_dim = float((text_attrs >> DIM_SHIFT) & ONE); effective_text_alpha = inactive_text_alpha * mix(1.0, dim_opacity, has_dim); float in_url = float((is_selected & TWO) >> 1); decoration_fg = choose_color(in_url, color_to_vec(url_color), to_color(colors[2], fg_as_uint)); // Selection vec3 selection_color = choose_color(use_cell_bg_for_selection_fg, bg, color_to_vec(highlight_fg)); selection_color = choose_color(use_cell_fg_for_selection_fg, foreground, selection_color); foreground = choose_color(float(is_selected & ONE), selection_color, foreground); decoration_fg = choose_color(float(is_selected & ONE), selection_color, decoration_fg); // Underline and strike through (rendered via sprites) uvec2 decs = get_decorations_indices(uint(in_url), text_attrs); strike_pos = to_sprite_pos(cell_data.pos, decs[0]); underline_pos = to_sprite_pos(cell_data.pos, decs[1]); underline_exclusion_pos = to_underline_exclusion_pos(); // Cursor cursor_color_premult = vec4(color_to_vec(cursor_bg) * cursor_opacity, cursor_opacity); vec3 final_cursor_text_color = mix(foreground, color_to_vec(cursor_fg), cursor_opacity); foreground = choose_color(cell_data.has_block_cursor, final_cursor_text_color, foreground); decoration_fg = choose_color(cell_data.has_block_cursor, final_cursor_text_color, decoration_fg); cursor_pos = to_sprite_pos(cell_data.pos, cursor_fg_sprite_idx * uint(cell_data.has_cursor)); #endif // }}} // Background {{{ float orig_bg_alpha = 1; #if PHASE == PHASE_BOTH && !defined(TRANSPARENT) // fast case single pass opaque background bg_alpha = 1; draw_bg = 1; #else bg_alpha = calc_background_opacity(bg_as_uint); orig_bg_alpha = bg_alpha; #if (PHASE == PHASE_BACKGROUND) // draw_bg_bitfield has bit 0 set to draw default bg cells and bit 1 set to draw non-default bg cells float cell_has_non_default_bg = step(1.f, abs(float(bg_as_uint - bg_colors0))); // 0 if has default bg else 1 uint draw_bg_mask = uint(2.f * cell_has_non_default_bg + (1.f - cell_has_non_default_bg)); // 1 if has default bg else 2 draw_bg = step(0.5, float(draw_bg_bitfield & draw_bg_mask)); #else draw_bg = 1; #endif float is_special_cell = cell_data.has_block_cursor + float(is_selected & ONE); #if PHASE == PHASE_SPECIAL // Only special cells must be drawn and they must have bg_alpha 1 bg_alpha = step(0.5, is_special_cell); // bg_alpha = 1 if is_special_cell else 0 #else is_special_cell += float(is_reversed); // bg_alpha should be 1 for reverse video cells as well is_special_cell = step(0.5, is_special_cell); // is_special_cell = 1 if is_special_cell else 0 bg_alpha = bg_alpha * (1. - float(is_special_cell)) + is_special_cell; // bg_alpha = 1 if is_special_cell else bg_alpha #endif bg_alpha *= draw_bg; #endif // ends fast case #if else // Selection and cursor bg = choose_color(float(is_selected & ONE), choose_color(use_cell_for_selection_bg, color_to_vec(fg_as_uint), color_to_vec(highlight_bg)), bg); background = choose_color(cell_data.has_block_cursor, mix(bg, color_to_vec(cursor_bg), cursor_opacity), bg); // we use max so that opacity of the block cursor cell background goes from orig_bg_alpha to 1 float effective_cursor_opacity = max(cursor_opacity, orig_bg_alpha) * draw_bg; bg_alpha = choose_alpha(cell_data.has_block_cursor, effective_cursor_opacity, bg_alpha); // }}} #ifdef OVERRIDE_FG_COLORS decoration_fg = override_foreground_color(decoration_fg, background); foreground = override_foreground_color(foreground, background); #endif } kitty-0.41.1/kitty/char-props-data.h0000664000175000017510000344327714773370543016635 0ustar nileshnilesh// Unicode data, built from the Unicode Standard 16.0.0 // Code generated by wcwidth.py, DO NOT EDIT. #pragma once typedef enum GraphemeBreakProperty { GBP_AtStart, GBP_None, GBP_Prepend, GBP_CR, GBP_LF, GBP_Control, GBP_Extend, GBP_Regional_Indicator, GBP_SpacingMark, GBP_L, GBP_V, GBP_T, GBP_LV, GBP_LVT, GBP_ZWJ, GBP_Private_Expecting_RI, } GraphemeBreakProperty; typedef enum IndicConjunctBreak { ICB_None, ICB_Linker, ICB_Consonant, ICB_Extend, } IndicConjunctBreak; static const char_type CharProps_mask = 255u; static const char_type CharProps_shift = 8u; static const uint8_t CharProps_t1[4352] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 54, 52, 52, 52, 55, 21, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 62, 63, 64, 65, 66, 67, 68, 62, 63, 64, 65, 66, 67, 68, 62, 63, 64, 65, 66, 67, 68, 62, 63, 64, 65, 66, 67, 68, 62, 63, 64, 65, 66, 67, 68, 62, 69, 70, 70, 70, 70, 70, 70, 70, 70, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 52, 72, 73, 21, 74, 75, 76, 77, 78, 79, 80, 81, 82, 21, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 21, 21, 21, 108, 109, 110, 111, 111, 111, 111, 111, 111, 111, 111, 111, 112, 21, 21, 21, 21, 113, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 114, 21, 21, 115, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 116, 111, 111, 111, 111, 111, 111, 21, 21, 117, 118, 111, 119, 120, 121, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 122, 52, 52, 52, 52, 123, 124, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 125, 52, 126, 127, 111, 111, 111, 111, 111, 111, 111, 111, 111, 128, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 129, 40, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 40, 40, 140, 111, 111, 111, 111, 141, 142, 143, 144, 111, 145, 146, 111, 147, 148, 149, 111, 111, 150, 151, 152, 111, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 165, 165, 166, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 167, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 168, 169, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 170, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 171, 52, 52, 172, 173, 173, 173, 173, 173, 173, 173, 173, 173, 52, 52, 174, 173, 173, 173, 173, 175, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 176, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 177, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, 175, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 178, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 178, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 178, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 178, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 178, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 178, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 178, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 178, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 178, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 178, 179, 180, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 181, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, 178, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 182, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 182 }; static const uint8_t CharProps_t2[46848] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 5, 5, 6, 7, 5, 5, 5, 8, 9, 6, 10, 5, 11, 5, 5, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 5, 5, 10, 10, 10, 5, 5, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 8, 5, 9, 14, 15, 14, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 10, 9, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 17, 7, 7, 18, 7, 19, 17, 20, 21, 22, 23, 10, 24, 25, 14, 26, 27, 28, 28, 20, 16, 17, 17, 20, 28, 22, 29, 28, 28, 28, 17, 13, 13, 13, 13, 13, 13, 30, 13, 13, 13, 13, 13, 13, 13, 13, 13, 30, 13, 13, 13, 13, 13, 13, 27, 30, 13, 13, 13, 13, 13, 30, 31, 31, 31, 16, 16, 16, 16, 31, 16, 31, 31, 31, 16, 31, 31, 16, 16, 31, 16, 31, 31, 16, 16, 16, 27, 31, 31, 31, 16, 31, 16, 31, 16, 13, 31, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 31, 13, 31, 13, 16, 13, 16, 13, 16, 13, 31, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 30, 31, 13, 16, 13, 31, 13, 16, 13, 16, 13, 31, 30, 31, 13, 16, 13, 16, 31, 13, 16, 13, 16, 13, 16, 30, 31, 30, 31, 13, 31, 13, 16, 13, 31, 31, 30, 31, 13, 31, 13, 16, 13, 16, 30, 31, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 30, 31, 13, 16, 13, 31, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 13, 16, 13, 16, 13, 16, 16, 16, 13, 13, 16, 13, 16, 13, 13, 16, 13, 13, 13, 16, 16, 13, 13, 13, 13, 16, 13, 13, 16, 13, 13, 13, 16, 16, 16, 13, 13, 16, 13, 13, 16, 13, 16, 13, 16, 13, 13, 16, 13, 16, 16, 13, 16, 13, 13, 16, 13, 13, 13, 16, 13, 16, 13, 13, 16, 16, 32, 13, 16, 16, 16, 32, 32, 32, 32, 13, 33, 16, 13, 33, 16, 13, 33, 16, 13, 31, 13, 31, 13, 31, 13, 31, 13, 31, 13, 31, 13, 31, 13, 31, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 16, 13, 33, 16, 13, 16, 13, 13, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 16, 16, 16, 16, 16, 16, 13, 13, 16, 13, 13, 16, 16, 13, 16, 13, 13, 13, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 16, 31, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 31, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 14, 14, 20, 14, 34, 35, 34, 35, 35, 35, 34, 35, 34, 34, 35, 34, 14, 14, 14, 14, 14, 14, 20, 20, 20, 20, 14, 20, 14, 20, 34, 34, 34, 34, 34, 14, 14, 14, 14, 14, 14, 14, 34, 14, 34, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 37, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 13, 16, 13, 16, 34, 14, 13, 16, 38, 38, 34, 16, 16, 16, 5, 13, 38, 38, 38, 38, 14, 14, 13, 5, 13, 13, 13, 38, 13, 38, 13, 13, 16, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 38, 30, 30, 30, 30, 30, 30, 30, 13, 13, 16, 16, 16, 16, 16, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 16, 31, 31, 31, 31, 31, 31, 31, 16, 16, 16, 16, 16, 13, 16, 16, 13, 13, 13, 16, 16, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 16, 16, 16, 16, 13, 16, 10, 13, 16, 13, 13, 16, 16, 13, 13, 13, 13, 30, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 16, 31, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 19, 36, 36, 36, 36, 36, 39, 39, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 38, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 38, 38, 34, 5, 5, 5, 5, 5, 5, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 5, 11, 38, 38, 19, 19, 7, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 11, 36, 5, 36, 36, 5, 36, 36, 5, 36, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 32, 32, 32, 32, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 40, 40, 40, 40, 40, 40, 10, 10, 10, 5, 5, 7, 5, 5, 19, 19, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 5, 24, 5, 5, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 5, 5, 5, 5, 32, 32, 36, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 5, 32, 36, 36, 36, 36, 36, 36, 36, 40, 19, 36, 36, 36, 36, 36, 36, 34, 34, 36, 36, 19, 36, 36, 36, 36, 32, 32, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 32, 32, 32, 19, 19, 32, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 38, 40, 32, 36, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 36, 36, 36, 34, 34, 19, 5, 5, 5, 34, 38, 38, 36, 7, 7, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 34, 36, 36, 36, 36, 36, 36, 36, 36, 36, 34, 36, 36, 36, 34, 36, 36, 36, 36, 36, 38, 38, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 38, 38, 5, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 14, 32, 32, 32, 32, 32, 32, 38, 40, 40, 38, 38, 38, 38, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 40, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 42, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 36, 42, 36, 32, 42, 42, 42, 36, 36, 36, 36, 36, 36, 36, 36, 42, 42, 42, 42, 44, 42, 42, 32, 36, 36, 36, 36, 36, 36, 36, 43, 43, 43, 43, 43, 43, 43, 43, 32, 32, 36, 36, 5, 5, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 5, 34, 32, 32, 32, 32, 32, 32, 43, 43, 43, 43, 43, 43, 43, 43, 32, 36, 42, 42, 38, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 32, 32, 38, 38, 32, 32, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 38, 43, 43, 43, 43, 43, 43, 43, 38, 43, 38, 38, 38, 43, 43, 43, 43, 38, 38, 36, 32, 45, 42, 42, 36, 36, 36, 36, 38, 38, 42, 42, 38, 38, 42, 42, 44, 32, 38, 38, 38, 38, 38, 38, 38, 38, 45, 38, 38, 38, 38, 43, 43, 38, 43, 32, 32, 36, 36, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 43, 43, 7, 7, 46, 46, 46, 46, 46, 46, 19, 7, 32, 5, 36, 38, 38, 36, 36, 42, 38, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 38, 32, 32, 38, 32, 32, 38, 38, 36, 38, 42, 42, 42, 36, 36, 38, 38, 38, 38, 36, 36, 38, 38, 36, 36, 36, 38, 38, 38, 36, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 38, 32, 38, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 36, 36, 32, 32, 32, 36, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 36, 36, 42, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 38, 32, 32, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 38, 43, 43, 43, 43, 43, 43, 43, 38, 43, 43, 38, 43, 43, 43, 43, 43, 38, 38, 36, 32, 42, 42, 42, 36, 36, 36, 36, 36, 38, 36, 36, 42, 38, 42, 42, 44, 38, 38, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 36, 36, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 5, 7, 38, 38, 38, 38, 38, 38, 38, 43, 36, 36, 36, 36, 36, 36, 38, 36, 42, 42, 38, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 32, 32, 38, 38, 32, 32, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 38, 43, 43, 43, 43, 43, 43, 43, 38, 43, 43, 38, 43, 43, 43, 43, 43, 38, 38, 36, 32, 45, 36, 42, 36, 36, 36, 36, 38, 38, 42, 42, 38, 38, 42, 42, 44, 38, 38, 38, 38, 38, 38, 38, 36, 36, 45, 38, 38, 38, 38, 43, 43, 38, 43, 32, 32, 36, 36, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 19, 43, 46, 46, 46, 46, 46, 46, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 36, 32, 38, 32, 32, 32, 32, 32, 32, 38, 38, 38, 32, 32, 32, 38, 32, 32, 32, 32, 38, 38, 38, 32, 32, 38, 32, 38, 32, 32, 38, 38, 38, 32, 32, 38, 38, 38, 32, 32, 32, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 45, 42, 36, 42, 42, 38, 38, 38, 42, 42, 42, 38, 42, 42, 42, 36, 38, 38, 32, 38, 38, 38, 38, 38, 38, 45, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 46, 46, 46, 19, 19, 19, 19, 19, 19, 7, 19, 38, 38, 38, 38, 38, 36, 42, 42, 42, 36, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 38, 32, 32, 32, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 38, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 38, 38, 36, 32, 36, 36, 36, 42, 42, 42, 42, 38, 36, 36, 36, 38, 36, 36, 36, 44, 38, 38, 38, 38, 38, 38, 38, 36, 36, 38, 43, 43, 43, 38, 38, 32, 38, 38, 32, 32, 36, 36, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 38, 5, 46, 46, 46, 46, 46, 46, 46, 19, 32, 36, 42, 42, 5, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 38, 38, 36, 32, 42, 36, 45, 42, 45, 42, 42, 38, 36, 45, 45, 38, 45, 45, 36, 36, 38, 38, 38, 38, 38, 38, 38, 45, 45, 38, 38, 38, 38, 38, 38, 32, 32, 38, 32, 32, 36, 36, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 32, 32, 42, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 36, 36, 42, 42, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 38, 32, 32, 32, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 36, 36, 32, 45, 42, 42, 36, 36, 36, 36, 38, 42, 42, 42, 38, 42, 42, 42, 44, 47, 19, 38, 38, 38, 38, 32, 32, 32, 45, 46, 46, 46, 46, 46, 46, 46, 32, 32, 32, 36, 36, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 46, 46, 46, 46, 46, 46, 46, 46, 46, 19, 32, 32, 32, 32, 32, 32, 38, 36, 42, 42, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 38, 38, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 36, 38, 38, 38, 38, 45, 42, 42, 36, 36, 36, 38, 36, 38, 42, 42, 42, 42, 42, 42, 42, 45, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 42, 42, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 32, 48, 36, 36, 36, 36, 36, 36, 36, 38, 38, 38, 38, 7, 32, 32, 32, 32, 32, 32, 34, 36, 36, 36, 36, 36, 36, 36, 36, 5, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 38, 32, 38, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 32, 48, 36, 36, 36, 36, 36, 36, 36, 36, 36, 32, 38, 38, 32, 32, 32, 32, 32, 38, 34, 38, 36, 36, 36, 36, 36, 36, 36, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 19, 19, 19, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 19, 5, 19, 19, 19, 36, 36, 19, 19, 19, 19, 19, 19, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 19, 36, 19, 36, 19, 36, 8, 9, 8, 9, 42, 42, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 42, 36, 36, 36, 36, 36, 5, 36, 36, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 38, 19, 19, 19, 19, 19, 19, 19, 19, 36, 19, 19, 19, 19, 19, 19, 38, 19, 19, 5, 5, 5, 5, 5, 19, 19, 19, 19, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, 49, 36, 36, 36, 36, 42, 36, 36, 36, 36, 36, 36, 49, 36, 36, 42, 42, 36, 36, 32, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 5, 5, 5, 5, 5, 5, 32, 32, 32, 32, 32, 32, 42, 42, 36, 36, 32, 32, 32, 32, 36, 36, 36, 32, 49, 49, 49, 32, 32, 49, 49, 49, 49, 49, 49, 49, 32, 32, 32, 36, 36, 36, 36, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 49, 42, 36, 36, 49, 49, 49, 49, 49, 49, 36, 32, 49, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 49, 49, 49, 36, 19, 19, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 38, 13, 38, 38, 38, 38, 38, 13, 38, 38, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 5, 34, 16, 16, 16, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 51, 52, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 38, 32, 32, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 38, 32, 32, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 36, 36, 36, 5, 5, 5, 5, 5, 5, 5, 5, 5, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 38, 38, 38, 38, 38, 38, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 38, 38, 16, 16, 16, 16, 16, 16, 38, 38, 11, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 19, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 4, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 8, 9, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 5, 5, 5, 55, 55, 55, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 45, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 45, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 38, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 37, 37, 42, 36, 36, 36, 36, 36, 36, 36, 42, 42, 42, 42, 42, 42, 42, 42, 36, 42, 42, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 5, 5, 5, 34, 5, 5, 5, 7, 32, 36, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 38, 38, 38, 38, 38, 38, 5, 5, 5, 5, 5, 5, 11, 5, 5, 5, 5, 36, 36, 36, 24, 36, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 36, 36, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 32, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 36, 36, 36, 42, 42, 42, 42, 36, 36, 42, 42, 42, 38, 38, 38, 38, 42, 42, 36, 42, 42, 42, 42, 42, 42, 36, 36, 36, 38, 38, 38, 38, 19, 38, 38, 38, 5, 5, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 46, 38, 38, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 42, 42, 36, 38, 38, 5, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 36, 42, 36, 36, 36, 36, 36, 36, 36, 38, 36, 49, 36, 49, 49, 36, 36, 36, 36, 36, 36, 36, 36, 42, 42, 42, 42, 42, 42, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 38, 38, 36, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 5, 5, 5, 5, 5, 5, 5, 34, 5, 5, 5, 5, 5, 5, 38, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 39, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 36, 36, 36, 36, 42, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 45, 36, 36, 36, 36, 36, 45, 36, 45, 42, 42, 42, 42, 36, 45, 45, 32, 32, 32, 32, 32, 32, 32, 32, 38, 5, 5, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 5, 5, 5, 5, 5, 5, 5, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 36, 36, 36, 36, 36, 36, 36, 36, 36, 19, 19, 19, 19, 19, 19, 19, 19, 19, 5, 5, 5, 36, 36, 42, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 36, 36, 36, 36, 42, 42, 36, 36, 45, 36, 36, 36, 32, 32, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 42, 36, 36, 42, 42, 42, 36, 42, 36, 36, 36, 45, 45, 38, 38, 38, 38, 38, 38, 38, 38, 5, 5, 5, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 42, 42, 42, 42, 42, 42, 42, 36, 36, 36, 36, 36, 36, 36, 36, 42, 42, 36, 36, 38, 38, 38, 5, 5, 5, 5, 5, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 32, 32, 32, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 34, 34, 34, 34, 34, 5, 5, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 16, 38, 38, 38, 38, 38, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 38, 38, 13, 13, 13, 5, 5, 5, 5, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 36, 36, 36, 5, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 42, 36, 36, 36, 36, 36, 36, 36, 32, 32, 32, 32, 36, 32, 32, 32, 32, 32, 32, 36, 32, 32, 42, 36, 36, 32, 38, 38, 38, 38, 38, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 34, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 38, 38, 13, 13, 13, 13, 13, 13, 38, 38, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 38, 38, 13, 13, 13, 13, 13, 13, 38, 38, 16, 16, 16, 16, 16, 16, 16, 16, 38, 13, 38, 13, 38, 13, 38, 13, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 38, 38, 16, 16, 16, 16, 16, 16, 16, 16, 33, 33, 33, 33, 33, 33, 33, 33, 16, 16, 16, 16, 16, 16, 16, 16, 33, 33, 33, 33, 33, 33, 33, 33, 16, 16, 16, 16, 16, 16, 16, 16, 33, 33, 33, 33, 33, 33, 33, 33, 16, 16, 16, 16, 16, 38, 16, 16, 13, 13, 13, 13, 33, 14, 16, 14, 14, 14, 16, 16, 16, 38, 16, 16, 13, 13, 13, 13, 33, 14, 14, 14, 16, 16, 16, 16, 38, 38, 16, 16, 13, 13, 13, 13, 38, 14, 14, 14, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 14, 14, 14, 38, 38, 16, 16, 16, 38, 16, 16, 13, 13, 13, 13, 33, 14, 14, 38, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 24, 56, 57, 24, 24, 58, 11, 11, 58, 58, 58, 17, 5, 59, 60, 8, 23, 59, 60, 8, 23, 17, 17, 17, 5, 17, 17, 17, 17, 61, 62, 24, 24, 24, 24, 24, 4, 17, 5, 17, 17, 5, 17, 5, 5, 5, 23, 29, 17, 63, 5, 17, 15, 15, 5, 5, 5, 10, 8, 9, 5, 5, 63, 5, 5, 5, 5, 5, 5, 5, 5, 10, 5, 15, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 24, 24, 24, 24, 24, 64, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 46, 34, 38, 38, 28, 46, 46, 46, 46, 46, 10, 10, 10, 8, 9, 35, 46, 28, 28, 28, 28, 46, 46, 46, 46, 46, 10, 10, 10, 8, 9, 38, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 38, 38, 38, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 18, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 39, 39, 39, 39, 36, 39, 39, 39, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 19, 19, 13, 26, 19, 26, 19, 13, 19, 26, 16, 13, 13, 13, 16, 16, 13, 13, 13, 31, 19, 13, 26, 19, 10, 13, 13, 13, 13, 13, 19, 19, 19, 26, 25, 19, 13, 19, 30, 19, 13, 19, 13, 30, 13, 13, 19, 16, 13, 13, 13, 13, 16, 32, 32, 32, 32, 65, 19, 19, 16, 16, 13, 13, 10, 10, 10, 10, 10, 13, 16, 16, 16, 16, 19, 10, 19, 19, 16, 19, 46, 46, 46, 28, 28, 46, 46, 46, 46, 46, 46, 28, 28, 28, 28, 46, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 55, 55, 55, 55, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 55, 55, 55, 55, 55, 55, 55, 55, 55, 13, 16, 55, 55, 55, 55, 28, 19, 19, 38, 38, 38, 38, 27, 27, 27, 27, 67, 25, 25, 25, 25, 25, 10, 10, 19, 19, 19, 19, 10, 19, 19, 10, 19, 19, 10, 19, 19, 21, 21, 19, 19, 19, 10, 19, 19, 19, 19, 19, 19, 19, 19, 19, 26, 26, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 10, 10, 19, 19, 27, 19, 27, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 26, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 27, 10, 27, 27, 10, 10, 10, 27, 27, 10, 10, 27, 10, 10, 10, 27, 10, 27, 10, 10, 10, 27, 10, 10, 10, 10, 27, 10, 10, 27, 27, 27, 27, 10, 10, 27, 10, 27, 10, 27, 27, 27, 27, 27, 27, 10, 27, 10, 10, 10, 10, 10, 27, 27, 27, 27, 10, 10, 10, 10, 27, 27, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 27, 10, 10, 10, 27, 10, 10, 10, 10, 10, 27, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 27, 27, 10, 10, 27, 27, 27, 27, 10, 10, 27, 27, 10, 10, 27, 27, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 27, 27, 10, 10, 27, 27, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 27, 10, 10, 10, 27, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 27, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 27, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 19, 19, 19, 19, 19, 19, 19, 19, 8, 9, 8, 9, 19, 19, 19, 19, 19, 19, 26, 19, 19, 19, 19, 19, 19, 19, 68, 68, 19, 19, 19, 19, 10, 10, 19, 19, 19, 19, 19, 19, 21, 69, 70, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 10, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 71, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 10, 10, 10, 10, 10, 10, 19, 19, 19, 19, 19, 19, 19, 68, 68, 68, 68, 21, 21, 21, 68, 21, 21, 68, 19, 19, 19, 19, 21, 21, 21, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 46, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 19, 19, 19, 19, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 19, 19, 26, 26, 26, 26, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 26, 26, 19, 26, 26, 26, 26, 26, 26, 26, 21, 21, 19, 19, 19, 19, 19, 19, 26, 26, 19, 19, 25, 27, 19, 19, 19, 19, 26, 26, 19, 19, 25, 27, 19, 19, 19, 19, 26, 26, 26, 19, 19, 26, 19, 19, 26, 26, 26, 26, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 26, 26, 26, 26, 19, 19, 19, 19, 19, 19, 19, 19, 19, 26, 19, 19, 19, 19, 19, 19, 19, 19, 10, 10, 10, 72, 72, 73, 73, 10, 21, 21, 21, 21, 21, 74, 26, 71, 71, 74, 71, 71, 71, 71, 25, 74, 71, 21, 71, 19, 68, 68, 71, 71, 21, 71, 71, 71, 74, 68, 74, 71, 21, 71, 21, 21, 71, 71, 21, 71, 71, 71, 21, 71, 71, 71, 21, 21, 75, 75, 75, 75, 75, 75, 75, 75, 21, 21, 21, 71, 71, 71, 71, 71, 25, 71, 25, 71, 71, 71, 71, 71, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 21, 25, 74, 71, 25, 74, 25, 21, 74, 25, 74, 74, 71, 74, 74, 71, 76, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 21, 71, 71, 21, 68, 71, 71, 71, 71, 71, 71, 19, 19, 19, 19, 77, 77, 77, 77, 77, 77, 71, 71, 21, 68, 21, 21, 21, 21, 71, 21, 71, 21, 21, 71, 74, 74, 21, 68, 71, 71, 71, 71, 71, 21, 71, 71, 68, 68, 71, 71, 71, 71, 21, 21, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 68, 68, 74, 71, 71, 71, 71, 68, 68, 74, 74, 25, 74, 74, 74, 74, 74, 68, 25, 74, 25, 74, 25, 68, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 71, 74, 71, 71, 71, 71, 74, 25, 68, 74, 74, 74, 74, 74, 25, 25, 68, 68, 25, 68, 74, 25, 25, 68, 68, 74, 74, 68, 74, 74, 71, 71, 21, 71, 71, 68, 19, 19, 21, 21, 68, 68, 68, 68, 71, 21, 71, 71, 21, 19, 21, 19, 21, 19, 19, 19, 19, 19, 19, 21, 19, 19, 19, 21, 19, 19, 19, 19, 19, 19, 68, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 21, 19, 19, 19, 19, 19, 19, 19, 19, 26, 19, 19, 19, 19, 19, 19, 21, 19, 19, 21, 19, 19, 19, 19, 68, 19, 68, 19, 19, 19, 19, 68, 68, 68, 19, 68, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 21, 71, 71, 71, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 19, 68, 68, 68, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 68, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 68, 10, 10, 10, 10, 10, 8, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 72, 72, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 8, 9, 8, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 8, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 19, 19, 19, 19, 19, 21, 21, 21, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 68, 68, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 19, 19, 10, 10, 10, 10, 10, 10, 19, 19, 19, 68, 19, 19, 19, 19, 68, 26, 26, 26, 26, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 38, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 16, 13, 13, 13, 16, 16, 13, 16, 13, 16, 13, 16, 13, 13, 13, 13, 16, 13, 16, 16, 13, 16, 16, 16, 16, 16, 16, 34, 34, 13, 13, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 16, 19, 19, 19, 19, 19, 19, 13, 16, 13, 16, 36, 36, 36, 13, 16, 38, 38, 38, 38, 38, 5, 5, 5, 5, 46, 5, 5, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 38, 16, 38, 38, 38, 38, 38, 16, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 34, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 36, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 5, 5, 23, 29, 23, 29, 5, 5, 5, 23, 29, 5, 23, 29, 5, 5, 5, 5, 5, 5, 5, 5, 5, 11, 5, 5, 11, 5, 23, 29, 5, 5, 23, 29, 8, 9, 8, 9, 8, 9, 8, 9, 5, 5, 5, 5, 5, 34, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 11, 11, 5, 5, 5, 5, 11, 5, 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 19, 19, 5, 5, 5, 8, 9, 8, 9, 8, 9, 8, 9, 11, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 38, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 78, 79, 79, 79, 77, 80, 81, 82, 69, 70, 69, 70, 69, 70, 69, 70, 69, 70, 77, 77, 69, 70, 69, 70, 69, 70, 69, 70, 83, 69, 70, 70, 77, 82, 82, 82, 82, 82, 82, 82, 82, 82, 84, 84, 84, 84, 85, 85, 86, 80, 80, 80, 80, 80, 77, 77, 82, 82, 82, 80, 81, 87, 77, 19, 38, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 38, 38, 84, 84, 88, 88, 80, 80, 81, 83, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 79, 80, 80, 80, 81, 38, 38, 38, 38, 38, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 38, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 89, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 38, 77, 77, 90, 90, 90, 90, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 38, 38, 38, 38, 38, 38, 38, 38, 38, 77, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 38, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 28, 28, 28, 28, 28, 28, 28, 28, 77, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 68, 77, 68, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 80, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 38, 38, 38, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 34, 34, 34, 34, 34, 5, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 5, 5, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 32, 36, 39, 39, 39, 5, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 5, 34, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 34, 34, 36, 36, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 36, 36, 5, 5, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 34, 34, 34, 34, 34, 34, 34, 34, 34, 14, 14, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 16, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 34, 16, 16, 16, 16, 16, 16, 16, 16, 13, 16, 13, 16, 13, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 34, 14, 14, 13, 16, 13, 16, 32, 13, 16, 13, 16, 16, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 13, 13, 13, 13, 16, 13, 13, 13, 13, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 16, 13, 13, 13, 13, 16, 13, 16, 13, 13, 16, 38, 38, 13, 16, 38, 16, 38, 16, 13, 16, 13, 16, 13, 16, 13, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 34, 34, 34, 13, 16, 32, 34, 34, 16, 32, 32, 32, 32, 32, 32, 32, 36, 32, 32, 32, 36, 32, 32, 32, 32, 36, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 42, 36, 36, 42, 19, 19, 19, 19, 36, 38, 38, 38, 46, 46, 46, 46, 46, 46, 19, 19, 7, 19, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 42, 42, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 5, 5, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 32, 32, 32, 32, 32, 32, 5, 5, 5, 32, 5, 32, 32, 36, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 36, 36, 5, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 42, 45, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 5, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 38, 38, 38, 36, 36, 36, 42, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 42, 42, 36, 36, 36, 36, 42, 42, 36, 36, 42, 42, 45, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 38, 34, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 5, 5, 32, 32, 32, 32, 32, 36, 34, 32, 32, 32, 32, 32, 32, 32, 32, 32, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 42, 42, 36, 36, 42, 42, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 36, 32, 32, 32, 32, 32, 32, 32, 32, 36, 42, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 5, 5, 5, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 32, 32, 32, 32, 32, 32, 19, 19, 19, 32, 49, 36, 49, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 32, 36, 36, 36, 32, 32, 36, 36, 32, 32, 32, 32, 32, 36, 36, 32, 36, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 34, 5, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 36, 36, 42, 42, 5, 5, 32, 34, 34, 42, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 38, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 14, 34, 34, 34, 34, 16, 16, 16, 16, 16, 16, 16, 16, 16, 34, 14, 14, 38, 38, 38, 38, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 42, 36, 42, 42, 36, 42, 42, 5, 42, 36, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 91, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 38, 38, 38, 38, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 38, 38, 38, 38, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 95, 95, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 16, 16, 16, 16, 16, 16, 16, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 16, 16, 16, 16, 16, 38, 38, 38, 38, 38, 32, 36, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 38, 32, 38, 32, 32, 38, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 9, 8, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 19, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 7, 19, 19, 19, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 79, 79, 79, 79, 79, 79, 79, 69, 70, 79, 38, 38, 38, 38, 38, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 79, 83, 83, 97, 97, 69, 70, 69, 70, 69, 70, 69, 70, 69, 70, 69, 70, 69, 70, 69, 70, 79, 79, 69, 70, 79, 79, 79, 79, 97, 97, 97, 79, 79, 79, 38, 79, 79, 79, 79, 83, 69, 70, 69, 70, 69, 70, 79, 79, 79, 98, 83, 98, 98, 98, 38, 79, 99, 79, 79, 38, 38, 38, 38, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 24, 38, 79, 79, 79, 99, 79, 79, 79, 69, 70, 79, 98, 79, 83, 79, 79, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 79, 79, 98, 98, 98, 79, 79, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, 69, 79, 70, 88, 97, 88, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 69, 98, 70, 98, 69, 70, 5, 8, 9, 5, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 103, 103, 104, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 32, 32, 32, 32, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 38, 38, 32, 32, 32, 38, 38, 38, 99, 99, 98, 88, 77, 99, 99, 38, 19, 10, 10, 10, 10, 19, 19, 38, 64, 64, 64, 64, 64, 64, 64, 64, 64, 24, 24, 24, 19, 26, 96, 96, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 5, 5, 5, 38, 38, 38, 38, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 38, 38, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 46, 46, 46, 46, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 46, 46, 19, 19, 19, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 38, 38, 38, 19, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 36, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 46, 46, 46, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 55, 32, 32, 32, 32, 32, 32, 32, 32, 55, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 5, 55, 55, 55, 55, 55, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 38, 38, 38, 38, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 5, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 38, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 38, 13, 13, 13, 13, 13, 13, 13, 38, 13, 13, 38, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 38, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 38, 16, 16, 16, 16, 16, 16, 16, 38, 16, 16, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 34, 34, 34, 34, 34, 34, 38, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 38, 34, 34, 34, 34, 34, 34, 34, 34, 34, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 38, 38, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 38, 38, 38, 32, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 5, 46, 46, 46, 46, 46, 46, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 19, 19, 46, 46, 46, 46, 46, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 46, 46, 46, 46, 46, 46, 46, 46, 46, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 38, 38, 38, 38, 38, 46, 46, 46, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 46, 46, 46, 46, 46, 38, 38, 38, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 46, 46, 32, 32, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 38, 38, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 32, 36, 36, 36, 38, 36, 36, 38, 38, 38, 38, 38, 36, 36, 36, 36, 32, 32, 32, 32, 38, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 36, 36, 36, 38, 38, 38, 38, 36, 46, 46, 46, 46, 46, 46, 46, 46, 46, 38, 38, 38, 38, 38, 38, 38, 5, 5, 5, 5, 5, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 46, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 46, 46, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 19, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 38, 38, 38, 38, 46, 46, 46, 46, 46, 5, 5, 5, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 5, 5, 5, 5, 5, 5, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 46, 46, 46, 46, 46, 46, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 46, 46, 46, 46, 46, 46, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 46, 46, 46, 46, 46, 46, 46, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 38, 38, 38, 38, 38, 38, 38, 46, 46, 46, 46, 46, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 32, 32, 32, 32, 34, 32, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 38, 38, 38, 36, 36, 36, 36, 36, 11, 34, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 38, 38, 38, 38, 38, 38, 38, 38, 10, 10, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 36, 36, 11, 38, 38, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 36, 36, 36, 36, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 32, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 46, 46, 46, 46, 5, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 46, 46, 46, 46, 46, 46, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 42, 36, 42, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 5, 5, 5, 5, 5, 5, 5, 38, 38, 38, 38, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 36, 32, 32, 36, 36, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 36, 36, 36, 42, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 42, 42, 36, 36, 36, 36, 42, 42, 36, 36, 5, 5, 40, 5, 5, 5, 5, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 40, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 36, 36, 36, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 42, 36, 36, 36, 36, 36, 36, 36, 36, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 5, 5, 5, 5, 32, 42, 42, 32, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 5, 5, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 36, 36, 42, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 42, 42, 36, 36, 36, 36, 36, 36, 36, 36, 36, 42, 45, 32, 47, 47, 32, 5, 5, 5, 5, 36, 36, 36, 36, 5, 42, 36, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 32, 5, 32, 5, 5, 5, 38, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 42, 42, 36, 36, 36, 42, 42, 36, 45, 36, 36, 5, 5, 5, 5, 5, 5, 36, 32, 32, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 38, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 5, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 42, 42, 42, 36, 36, 36, 36, 36, 36, 36, 36, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 36, 36, 42, 42, 38, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 38, 32, 32, 32, 32, 32, 38, 36, 36, 32, 45, 42, 36, 42, 42, 42, 42, 38, 38, 42, 42, 38, 38, 42, 42, 45, 38, 38, 32, 38, 38, 38, 38, 38, 38, 45, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 42, 42, 38, 38, 36, 36, 36, 36, 36, 36, 36, 38, 38, 38, 36, 36, 36, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 38, 38, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 45, 42, 42, 36, 36, 36, 36, 36, 36, 38, 45, 38, 38, 45, 38, 45, 45, 45, 42, 38, 42, 42, 36, 45, 36, 47, 36, 32, 5, 5, 38, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 42, 42, 36, 36, 36, 36, 36, 36, 36, 36, 42, 42, 36, 36, 36, 42, 36, 32, 32, 32, 32, 5, 5, 5, 5, 5, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 5, 5, 38, 5, 36, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 42, 42, 36, 36, 36, 36, 36, 36, 42, 36, 42, 42, 45, 42, 36, 36, 42, 36, 36, 32, 32, 5, 32, 38, 38, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 42, 42, 36, 36, 36, 36, 38, 38, 42, 42, 42, 42, 36, 36, 42, 36, 36, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 32, 32, 32, 32, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 42, 42, 36, 36, 36, 36, 36, 36, 36, 36, 42, 42, 36, 42, 36, 36, 5, 5, 5, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 42, 36, 42, 42, 36, 36, 36, 36, 36, 36, 45, 36, 32, 5, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 36, 42, 36, 49, 49, 36, 36, 36, 36, 42, 36, 36, 36, 36, 36, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 46, 46, 5, 5, 5, 19, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 42, 42, 36, 36, 36, 36, 36, 36, 36, 36, 36, 42, 36, 36, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 46, 46, 46, 46, 46, 46, 46, 46, 46, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 32, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 42, 42, 42, 42, 42, 38, 42, 42, 38, 38, 36, 36, 45, 36, 47, 42, 47, 42, 36, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 42, 42, 36, 36, 36, 36, 38, 38, 36, 36, 42, 42, 42, 42, 36, 32, 5, 32, 42, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 42, 47, 36, 36, 36, 36, 5, 5, 5, 5, 5, 5, 5, 5, 36, 38, 38, 38, 38, 38, 38, 38, 38, 32, 36, 36, 36, 36, 36, 36, 42, 42, 36, 36, 36, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 47, 47, 47, 47, 47, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 42, 36, 36, 5, 5, 5, 32, 5, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 36, 36, 36, 36, 36, 36, 36, 38, 36, 36, 36, 36, 36, 36, 42, 36, 32, 5, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 38, 38, 38, 5, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 38, 42, 36, 36, 36, 36, 36, 36, 36, 42, 36, 36, 42, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 38, 38, 38, 36, 38, 36, 36, 38, 36, 36, 36, 36, 36, 36, 36, 47, 36, 38, 38, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 38, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 42, 42, 42, 42, 38, 36, 36, 38, 42, 42, 36, 42, 36, 32, 38, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 42, 42, 5, 5, 38, 38, 38, 38, 38, 38, 38, 36, 36, 47, 42, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 42, 36, 36, 36, 36, 36, 38, 38, 38, 42, 42, 36, 45, 36, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 19, 19, 19, 19, 19, 19, 19, 19, 7, 7, 7, 7, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 38, 5, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 36, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 42, 42, 42, 36, 36, 36, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 5, 5, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 36, 36, 36, 36, 36, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 36, 36, 36, 5, 5, 5, 5, 5, 19, 19, 19, 19, 34, 34, 34, 34, 5, 19, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 46, 46, 46, 46, 46, 46, 46, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 34, 34, 34, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 53, 32, 32, 32, 53, 53, 53, 53, 34, 34, 5, 5, 5, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 36, 32, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 38, 38, 38, 38, 38, 38, 38, 36, 36, 36, 36, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 80, 80, 79, 80, 84, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 85, 85, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 38, 38, 38, 38, 38, 38, 38, 38, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 80, 80, 80, 80, 38, 80, 80, 80, 80, 80, 80, 80, 38, 80, 80, 38, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 81, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 81, 81, 81, 38, 38, 81, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 81, 81, 81, 81, 38, 38, 38, 38, 38, 38, 38, 38, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 19, 36, 36, 5, 24, 24, 24, 24, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 38, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 38, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 45, 45, 36, 36, 36, 19, 19, 19, 45, 45, 45, 45, 45, 45, 24, 24, 24, 24, 24, 24, 24, 24, 36, 36, 36, 36, 36, 36, 36, 36, 19, 19, 36, 36, 36, 36, 36, 36, 36, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 36, 36, 36, 36, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 36, 36, 36, 19, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 38, 38, 38, 38, 38, 38, 38, 38, 38, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 46, 46, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 38, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 38, 13, 13, 38, 38, 13, 38, 38, 13, 13, 38, 38, 13, 13, 13, 13, 38, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 38, 16, 38, 16, 16, 16, 16, 16, 16, 16, 38, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 38, 13, 13, 13, 13, 38, 38, 13, 13, 13, 13, 13, 13, 13, 13, 38, 13, 13, 13, 13, 13, 13, 13, 38, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 38, 13, 13, 13, 13, 38, 13, 13, 13, 13, 13, 38, 13, 38, 38, 38, 13, 13, 13, 13, 13, 13, 13, 38, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 38, 38, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 10, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 10, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 10, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 10, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 10, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 10, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 10, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 10, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 10, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 10, 16, 16, 16, 16, 16, 16, 13, 16, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 19, 19, 19, 19, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 19, 19, 19, 19, 19, 19, 19, 19, 36, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 36, 19, 19, 5, 5, 5, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 36, 36, 36, 36, 36, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 38, 38, 38, 38, 38, 38, 16, 16, 16, 16, 16, 16, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 36, 36, 36, 36, 36, 36, 36, 38, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 38, 38, 36, 36, 36, 36, 36, 36, 36, 38, 36, 36, 38, 36, 36, 36, 36, 36, 38, 38, 38, 38, 38, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 36, 36, 36, 36, 36, 36, 36, 34, 34, 34, 34, 34, 34, 34, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 32, 19, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 36, 36, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 7, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 36, 36, 36, 36, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 36, 36, 32, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 38, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 46, 46, 46, 46, 46, 46, 46, 46, 46, 36, 36, 36, 36, 36, 36, 36, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 36, 36, 36, 36, 36, 36, 36, 34, 38, 38, 38, 38, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 5, 5, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 19, 46, 46, 46, 7, 46, 46, 46, 46, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 19, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 38, 32, 38, 38, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 38, 32, 38, 32, 38, 38, 38, 38, 38, 38, 32, 38, 38, 38, 38, 32, 38, 32, 38, 32, 38, 32, 32, 32, 38, 32, 32, 38, 32, 38, 38, 32, 38, 32, 38, 32, 38, 32, 38, 32, 38, 32, 32, 38, 32, 38, 38, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 38, 32, 32, 32, 32, 38, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 32, 32, 32, 38, 32, 32, 32, 32, 32, 38, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 10, 10, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 71, 71, 71, 71, 68, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 105, 105, 105, 105, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 105, 105, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 105, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 68, 105, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 46, 46, 71, 71, 71, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 19, 71, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 19, 19, 71, 71, 71, 71, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 68, 26, 26, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 71, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 77, 68, 68, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 68, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 68, 77, 77, 68, 68, 68, 68, 68, 68, 68, 68, 68, 77, 105, 105, 105, 105, 77, 77, 77, 77, 77, 77, 77, 77, 77, 105, 105, 105, 105, 105, 105, 105, 68, 68, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 75, 75, 75, 75, 75, 75, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 21, 71, 71, 21, 21, 21, 21, 21, 21, 21, 21, 21, 68, 68, 68, 68, 68, 68, 68, 68, 68, 21, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 21, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 71, 71, 21, 21, 71, 21, 21, 21, 71, 71, 21, 21, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 21, 21, 68, 68, 68, 68, 68, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 71, 71, 21, 68, 21, 71, 21, 68, 68, 68, 107, 107, 107, 107, 107, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 21, 68, 21, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 21, 71, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 19, 19, 19, 19, 19, 19, 19, 19, 71, 71, 71, 21, 21, 68, 68, 68, 68, 71, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 71, 71, 71, 71, 71, 71, 71, 21, 21, 71, 71, 21, 68, 68, 21, 21, 21, 21, 68, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 21, 71, 71, 21, 21, 21, 21, 71, 71, 68, 71, 71, 71, 71, 68, 68, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 68, 21, 71, 71, 21, 71, 71, 71, 71, 71, 71, 71, 71, 21, 21, 71, 71, 71, 71, 71, 71, 71, 71, 71, 21, 71, 71, 71, 71, 71, 21, 21, 21, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 21, 21, 21, 71, 71, 71, 71, 71, 71, 71, 71, 21, 21, 21, 71, 71, 21, 71, 21, 71, 71, 71, 71, 21, 71, 71, 71, 71, 71, 71, 21, 71, 71, 71, 21, 71, 71, 71, 71, 71, 71, 21, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 71, 71, 71, 71, 71, 21, 68, 21, 21, 21, 68, 68, 68, 71, 71, 68, 68, 68, 105, 105, 105, 105, 68, 68, 68, 68, 21, 21, 21, 21, 21, 21, 71, 71, 71, 21, 71, 68, 68, 105, 105, 105, 21, 71, 71, 21, 68, 68, 68, 68, 68, 68, 68, 68, 68, 105, 105, 105, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 71, 71, 71, 105, 105, 105, 105, 71, 71, 71, 71, 71, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 71, 71, 71, 71, 71, 105, 105, 105, 105, 105, 105, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 105, 105, 105, 105, 68, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 105, 105, 105, 105, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 105, 105, 105, 105, 105, 105, 105, 105, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 105, 105, 105, 105, 105, 105, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 105, 105, 105, 105, 105, 105, 105, 105, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 105, 105, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 105, 105, 105, 105, 71, 71, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 19, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 19, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 105, 105, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 105, 105, 105, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 105, 105, 105, 105, 105, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 105, 105, 105, 105, 105, 105, 105, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 105, 105, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 105, 105, 105, 105, 105, 105, 68, 68, 68, 68, 68, 68, 68, 68, 68, 105, 105, 105, 105, 105, 105, 105, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 38, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 38, 38, 38, 38, 38, 38, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 96, 96, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 95, 95, 95, 95, 95, 95, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 95, 95, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 96, 96, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 95, 95, 95, 95, 95, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 96, 96, 64, 24, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 96, 96 }; static const CharProps CharProps_t3[109] = { {.shifted_width=4, .is_emoji=0, .category=UC_Cc, .is_emoji_presentation_base=0, .is_invalid=1, .is_non_rendered=1, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Control, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 0 {.shifted_width=3, .is_emoji=0, .category=UC_Cc, .is_emoji_presentation_base=0, .is_invalid=1, .is_non_rendered=1, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Control, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 1 {.shifted_width=3, .is_emoji=0, .category=UC_Cc, .is_emoji_presentation_base=0, .is_invalid=1, .is_non_rendered=1, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_LF, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 2 {.shifted_width=3, .is_emoji=0, .category=UC_Cc, .is_emoji_presentation_base=0, .is_invalid=1, .is_non_rendered=1, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_CR, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 3 {.shifted_width=5, .is_emoji=0, .category=UC_Zs, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 4 {.shifted_width=5, .is_emoji=0, .category=UC_Po, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 5 {.shifted_width=5, .is_emoji=1, .category=UC_Po, .is_emoji_presentation_base=1, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 6 {.shifted_width=5, .is_emoji=0, .category=UC_Sc, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 7 {.shifted_width=5, .is_emoji=0, .category=UC_Ps, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 8 {.shifted_width=5, .is_emoji=0, .category=UC_Pe, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 9 {.shifted_width=5, .is_emoji=0, .category=UC_Sm, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 10 {.shifted_width=5, .is_emoji=0, .category=UC_Pd, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 11 {.shifted_width=5, .is_emoji=1, .category=UC_Nd, .is_emoji_presentation_base=1, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 12 {.shifted_width=5, .is_emoji=0, .category=UC_Lu, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 13 {.shifted_width=5, .is_emoji=0, .category=UC_Sk, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 14 {.shifted_width=5, .is_emoji=0, .category=UC_Pc, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 15 {.shifted_width=5, .is_emoji=0, .category=UC_Ll, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 16 {.shifted_width=2, .is_emoji=0, .category=UC_Po, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 17 {.shifted_width=2, .is_emoji=0, .category=UC_Sc, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 18 {.shifted_width=5, .is_emoji=0, .category=UC_So, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 19 {.shifted_width=2, .is_emoji=0, .category=UC_Sk, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 20 {.shifted_width=5, .is_emoji=1, .category=UC_So, .is_emoji_presentation_base=1, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 21 {.shifted_width=2, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 22 {.shifted_width=5, .is_emoji=0, .category=UC_Pi, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 23 {.shifted_width=4, .is_emoji=0, .category=UC_Cf, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=1, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Control, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 24 {.shifted_width=2, .is_emoji=1, .category=UC_So, .is_emoji_presentation_base=1, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 25 {.shifted_width=2, .is_emoji=0, .category=UC_So, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 26 {.shifted_width=2, .is_emoji=0, .category=UC_Sm, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 27 {.shifted_width=2, .is_emoji=0, .category=UC_No, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 28 {.shifted_width=5, .is_emoji=0, .category=UC_Pf, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 29 {.shifted_width=2, .is_emoji=0, .category=UC_Lu, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 30 {.shifted_width=2, .is_emoji=0, .category=UC_Ll, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 31 {.shifted_width=5, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 32 {.shifted_width=5, .is_emoji=0, .category=UC_Lt, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 33 {.shifted_width=5, .is_emoji=0, .category=UC_Lm, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 34 {.shifted_width=2, .is_emoji=0, .category=UC_Lm, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 35 {.shifted_width=4, .is_emoji=0, .category=UC_Mn, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Extend, .indic_conjunct_break=ICB_Extend, .is_extended_pictographic=0}, // 36 {.shifted_width=4, .is_emoji=0, .category=UC_Mn, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=1, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Extend, .indic_conjunct_break=ICB_Extend, .is_extended_pictographic=0}, // 37 {.shifted_width=0, .is_emoji=0, .category=UC_Cn, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 38 {.shifted_width=4, .is_emoji=0, .category=UC_Me, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Extend, .indic_conjunct_break=ICB_Extend, .is_extended_pictographic=0}, // 39 {.shifted_width=4, .is_emoji=0, .category=UC_Cf, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=1, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Prepend, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 40 {.shifted_width=5, .is_emoji=0, .category=UC_Nd, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 41 {.shifted_width=4, .is_emoji=0, .category=UC_Mc, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_SpacingMark, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 42 {.shifted_width=5, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_Consonant, .is_extended_pictographic=0}, // 43 {.shifted_width=4, .is_emoji=0, .category=UC_Mn, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Extend, .indic_conjunct_break=ICB_Linker, .is_extended_pictographic=0}, // 44 {.shifted_width=4, .is_emoji=0, .category=UC_Mc, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Extend, .indic_conjunct_break=ICB_Extend, .is_extended_pictographic=0}, // 45 {.shifted_width=5, .is_emoji=0, .category=UC_No, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 46 {.shifted_width=5, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_Prepend, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 47 {.shifted_width=5, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_SpacingMark, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 48 {.shifted_width=4, .is_emoji=0, .category=UC_Mc, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 49 {.shifted_width=6, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_L, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 50 {.shifted_width=6, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=1, .is_symbol=0, .is_combining_char=1, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_L, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 51 {.shifted_width=4, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=1, .is_symbol=0, .is_combining_char=1, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_V, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 52 {.shifted_width=5, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_V, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 53 {.shifted_width=5, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_T, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 54 {.shifted_width=5, .is_emoji=0, .category=UC_Nl, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 55 {.shifted_width=4, .is_emoji=0, .category=UC_Cf, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=1, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Extend, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 56 {.shifted_width=4, .is_emoji=0, .category=UC_Cf, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=1, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_ZWJ, .indic_conjunct_break=ICB_Extend, .is_extended_pictographic=0}, // 57 {.shifted_width=2, .is_emoji=0, .category=UC_Pd, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 58 {.shifted_width=2, .is_emoji=0, .category=UC_Pi, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 59 {.shifted_width=2, .is_emoji=0, .category=UC_Pf, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 60 {.shifted_width=5, .is_emoji=0, .category=UC_Zl, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Control, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 61 {.shifted_width=5, .is_emoji=0, .category=UC_Zp, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Control, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 62 {.shifted_width=5, .is_emoji=1, .category=UC_Po, .is_emoji_presentation_base=1, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 63 {.shifted_width=4, .is_emoji=0, .category=UC_Cn, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=1, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Control, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 64 {.shifted_width=5, .is_emoji=1, .category=UC_Ll, .is_emoji_presentation_base=1, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 65 {.shifted_width=2, .is_emoji=0, .category=UC_Nl, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 66 {.shifted_width=2, .is_emoji=1, .category=UC_Sm, .is_emoji_presentation_base=1, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 67 {.shifted_width=6, .is_emoji=1, .category=UC_So, .is_emoji_presentation_base=1, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 68 {.shifted_width=6, .is_emoji=0, .category=UC_Ps, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 69 {.shifted_width=6, .is_emoji=0, .category=UC_Pe, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 70 {.shifted_width=5, .is_emoji=0, .category=UC_So, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 71 {.shifted_width=5, .is_emoji=1, .category=UC_Sm, .is_emoji_presentation_base=1, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 72 {.shifted_width=6, .is_emoji=1, .category=UC_Sm, .is_emoji_presentation_base=1, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 73 {.shifted_width=2, .is_emoji=0, .category=UC_So, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 74 {.shifted_width=6, .is_emoji=0, .category=UC_So, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 75 {.shifted_width=2, .is_emoji=0, .category=UC_Sm, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 76 {.shifted_width=6, .is_emoji=0, .category=UC_So, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 77 {.shifted_width=6, .is_emoji=0, .category=UC_Zs, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 78 {.shifted_width=6, .is_emoji=0, .category=UC_Po, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 79 {.shifted_width=6, .is_emoji=0, .category=UC_Lm, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 80 {.shifted_width=6, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 81 {.shifted_width=6, .is_emoji=0, .category=UC_Nl, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 82 {.shifted_width=6, .is_emoji=0, .category=UC_Pd, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 83 {.shifted_width=6, .is_emoji=0, .category=UC_Mn, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Extend, .indic_conjunct_break=ICB_Extend, .is_extended_pictographic=0}, // 84 {.shifted_width=6, .is_emoji=0, .category=UC_Mc, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Extend, .indic_conjunct_break=ICB_Extend, .is_extended_pictographic=0}, // 85 {.shifted_width=6, .is_emoji=1, .category=UC_Pd, .is_emoji_presentation_base=1, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 86 {.shifted_width=6, .is_emoji=1, .category=UC_Po, .is_emoji_presentation_base=1, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 87 {.shifted_width=6, .is_emoji=0, .category=UC_Sk, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 88 {.shifted_width=6, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=1, .is_symbol=0, .is_combining_char=1, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 89 {.shifted_width=6, .is_emoji=0, .category=UC_No, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 90 {.shifted_width=6, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_LV, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 91 {.shifted_width=6, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_LVT, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 92 {.shifted_width=3, .is_emoji=0, .category=UC_Cs, .is_emoji_presentation_base=0, .is_invalid=1, .is_non_rendered=1, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 93 {.shifted_width=2, .is_emoji=0, .category=UC_Co, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 94 {.shifted_width=6, .is_emoji=0, .category=UC_Cn, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 95 {.shifted_width=3, .is_emoji=0, .category=UC_Cn, .is_emoji_presentation_base=0, .is_invalid=1, .is_non_rendered=1, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 96 {.shifted_width=6, .is_emoji=0, .category=UC_Pc, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=1, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 97 {.shifted_width=6, .is_emoji=0, .category=UC_Sm, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 98 {.shifted_width=6, .is_emoji=0, .category=UC_Sc, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 99 {.shifted_width=6, .is_emoji=0, .category=UC_Nd, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 100 {.shifted_width=6, .is_emoji=0, .category=UC_Lu, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 101 {.shifted_width=6, .is_emoji=0, .category=UC_Ll, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 102 {.shifted_width=5, .is_emoji=0, .category=UC_Lm, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_Extend, .indic_conjunct_break=ICB_Extend, .is_extended_pictographic=0}, // 103 {.shifted_width=4, .is_emoji=0, .category=UC_Lo, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=1, .is_symbol=0, .is_combining_char=1, .is_word_char=1, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 104 {.shifted_width=0, .is_emoji=0, .category=UC_Cn, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=0, .is_symbol=0, .is_combining_char=0, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_None, .indic_conjunct_break=ICB_None, .is_extended_pictographic=1}, // 105 {.shifted_width=6, .is_emoji=1, .category=UC_So, .is_emoji_presentation_base=1, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Regional_Indicator, .indic_conjunct_break=ICB_None, .is_extended_pictographic=0}, // 106 {.shifted_width=6, .is_emoji=1, .category=UC_Sk, .is_emoji_presentation_base=1, .is_invalid=0, .is_non_rendered=0, .is_symbol=1, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Extend, .indic_conjunct_break=ICB_Extend, .is_extended_pictographic=0}, // 107 {.shifted_width=4, .is_emoji=0, .category=UC_Cf, .is_emoji_presentation_base=0, .is_invalid=0, .is_non_rendered=1, .is_symbol=0, .is_combining_char=1, .is_word_char=0, .is_punctuation=0, .grapheme_break=GBP_Extend, .indic_conjunct_break=ICB_Extend, .is_extended_pictographic=0}, // 108 }; static const char_type GraphemeSegmentationResult_mask = 15u; static const char_type GraphemeSegmentationResult_shift = 4u; static const uint8_t GraphemeSegmentationResult_t1[4096] = { 0, 0, 1, 0, 2, 2, 3, 2, 4, 4, 5, 4, 6, 6, 7, 6, 8, 8, 9, 8, 10, 10, 11, 10, 12, 12, 13, 12, 14, 14, 15, 14, 16, 16, 17, 16, 18, 18, 19, 18, 16, 16, 17, 16, 18, 18, 19, 18, 4, 4, 5, 4, 6, 6, 7, 6, 4, 4, 5, 4, 6, 6, 7, 6, 4, 4, 5, 4, 6, 6, 7, 6, 20, 20, 21, 20, 22, 22, 23, 22, 24, 24, 25, 24, 26, 26, 27, 26, 28, 28, 29, 28, 30, 30, 31, 30, 24, 24, 25, 24, 26, 26, 27, 26, 28, 28, 29, 28, 30, 30, 31, 30, 4, 4, 5, 4, 6, 6, 7, 6, 32, 32, 33, 32, 34, 34, 35, 34, 0, 36, 1, 1, 2, 37, 3, 3, 4, 38, 5, 5, 6, 39, 7, 7, 8, 40, 9, 9, 10, 41, 11, 11, 12, 42, 13, 13, 14, 43, 15, 15, 16, 44, 17, 17, 18, 45, 19, 19, 16, 44, 17, 17, 18, 45, 19, 19, 4, 38, 5, 5, 6, 39, 7, 7, 4, 38, 5, 5, 6, 39, 7, 7, 4, 38, 5, 5, 6, 39, 7, 7, 20, 46, 21, 21, 22, 47, 23, 23, 24, 48, 25, 25, 26, 49, 27, 27, 28, 50, 29, 29, 30, 51, 31, 31, 24, 48, 25, 25, 26, 49, 27, 27, 28, 50, 29, 29, 30, 51, 31, 31, 4, 38, 5, 5, 6, 39, 7, 7, 32, 52, 33, 33, 34, 53, 35, 35, 0, 0, 1, 0, 2, 2, 3, 2, 4, 4, 5, 4, 6, 6, 7, 6, 8, 8, 9, 8, 10, 10, 11, 10, 12, 12, 13, 12, 14, 14, 15, 14, 16, 16, 17, 16, 18, 18, 19, 18, 16, 16, 17, 16, 18, 18, 19, 18, 4, 4, 5, 4, 6, 6, 7, 6, 4, 4, 5, 4, 6, 6, 7, 6, 4, 4, 5, 4, 6, 6, 7, 6, 20, 20, 21, 20, 22, 22, 23, 22, 24, 24, 25, 24, 26, 26, 27, 26, 28, 28, 29, 28, 30, 30, 31, 30, 24, 24, 25, 24, 26, 26, 27, 26, 28, 28, 29, 28, 30, 30, 31, 30, 4, 4, 5, 4, 6, 6, 7, 6, 32, 32, 33, 32, 34, 34, 35, 34, 0, 36, 1, 1, 2, 37, 3, 3, 4, 38, 5, 5, 6, 39, 7, 7, 8, 40, 9, 9, 10, 41, 11, 11, 12, 42, 13, 13, 14, 43, 15, 15, 16, 44, 17, 17, 18, 45, 19, 19, 16, 44, 17, 17, 18, 45, 19, 19, 4, 38, 5, 5, 6, 39, 7, 7, 4, 38, 5, 5, 6, 39, 7, 7, 4, 38, 5, 5, 6, 39, 7, 7, 20, 46, 21, 21, 22, 47, 23, 23, 24, 48, 25, 25, 26, 49, 27, 27, 28, 50, 29, 29, 30, 51, 31, 31, 24, 48, 25, 25, 26, 49, 27, 27, 28, 50, 29, 29, 30, 51, 31, 31, 4, 38, 5, 5, 6, 39, 7, 7, 32, 52, 33, 33, 34, 53, 35, 35, 0, 54, 1, 54, 2, 55, 3, 55, 4, 56, 9, 56, 6, 57, 11, 57, 8, 58, 9, 58, 10, 59, 11, 59, 12, 60, 13, 60, 14, 61, 15, 61, 16, 62, 17, 62, 18, 63, 19, 63, 16, 62, 17, 62, 18, 63, 19, 63, 4, 56, 9, 56, 6, 57, 11, 57, 4, 56, 9, 56, 6, 57, 11, 57, 4, 56, 9, 56, 6, 57, 11, 57, 20, 64, 9, 64, 22, 65, 11, 65, 24, 66, 9, 66, 26, 67, 11, 67, 28, 68, 9, 68, 30, 69, 11, 69, 24, 66, 9, 66, 26, 67, 11, 67, 28, 68, 9, 68, 30, 69, 11, 69, 4, 56, 9, 56, 6, 57, 11, 57, 32, 70, 9, 70, 34, 71, 11, 71, 0, 36, 1, 72, 2, 37, 3, 73, 4, 38, 9, 74, 6, 39, 11, 75, 8, 40, 9, 76, 10, 41, 11, 77, 12, 42, 13, 78, 14, 43, 15, 79, 16, 44, 17, 80, 18, 45, 19, 81, 16, 44, 17, 80, 18, 45, 19, 81, 4, 38, 9, 74, 6, 39, 11, 75, 4, 38, 9, 74, 6, 39, 11, 75, 4, 38, 9, 74, 6, 39, 11, 75, 20, 46, 9, 82, 22, 47, 11, 83, 24, 48, 9, 84, 26, 49, 11, 85, 28, 50, 9, 86, 30, 51, 11, 87, 24, 48, 9, 84, 26, 49, 11, 85, 28, 50, 9, 86, 30, 51, 11, 87, 4, 38, 9, 74, 6, 39, 11, 75, 32, 52, 9, 88, 34, 53, 11, 89, 0, 54, 1, 54, 2, 55, 3, 55, 4, 56, 9, 56, 6, 57, 11, 57, 8, 58, 9, 58, 10, 59, 11, 59, 12, 60, 13, 60, 14, 61, 15, 61, 16, 62, 17, 62, 18, 63, 19, 63, 16, 62, 17, 62, 18, 63, 19, 63, 4, 56, 9, 56, 6, 57, 11, 57, 4, 56, 9, 56, 6, 57, 11, 57, 4, 56, 9, 56, 6, 57, 11, 57, 20, 64, 9, 64, 22, 65, 11, 65, 24, 66, 9, 66, 26, 67, 11, 67, 28, 68, 9, 68, 30, 69, 11, 69, 24, 66, 9, 66, 26, 67, 11, 67, 28, 68, 9, 68, 30, 69, 11, 69, 4, 56, 9, 56, 6, 57, 11, 57, 32, 70, 9, 70, 34, 71, 11, 71, 0, 36, 1, 72, 2, 37, 3, 73, 4, 38, 9, 74, 6, 39, 11, 75, 8, 40, 9, 76, 10, 41, 11, 77, 12, 42, 13, 78, 14, 43, 15, 79, 16, 44, 17, 80, 18, 45, 19, 81, 16, 44, 17, 80, 18, 45, 19, 81, 4, 38, 9, 74, 6, 39, 11, 75, 4, 38, 9, 74, 6, 39, 11, 75, 4, 38, 9, 74, 6, 39, 11, 75, 20, 46, 9, 82, 22, 47, 11, 83, 24, 48, 9, 84, 26, 49, 11, 85, 28, 50, 9, 86, 30, 51, 11, 87, 24, 48, 9, 84, 26, 49, 11, 85, 28, 50, 9, 86, 30, 51, 11, 87, 4, 38, 9, 74, 6, 39, 11, 75, 32, 52, 9, 88, 34, 53, 11, 89, 90, 90, 91, 90, 92, 92, 93, 92, 94, 94, 95, 94, 96, 96, 97, 96, 98, 98, 99, 98, 100, 100, 101, 100, 102, 102, 103, 102, 104, 104, 105, 104, 106, 106, 107, 106, 108, 108, 109, 108, 106, 106, 107, 106, 108, 108, 109, 108, 94, 94, 95, 94, 96, 96, 97, 96, 94, 94, 95, 94, 96, 96, 97, 96, 94, 94, 95, 94, 96, 96, 97, 96, 110, 110, 111, 110, 112, 112, 113, 112, 114, 114, 115, 114, 116, 116, 117, 116, 118, 118, 119, 118, 120, 120, 121, 120, 114, 114, 115, 114, 116, 116, 117, 116, 118, 118, 119, 118, 120, 120, 121, 120, 94, 94, 95, 94, 96, 96, 97, 96, 122, 122, 123, 122, 124, 124, 125, 124, 90, 126, 91, 91, 92, 127, 93, 93, 94, 128, 95, 95, 96, 129, 97, 97, 98, 130, 99, 99, 100, 131, 101, 101, 102, 132, 103, 103, 104, 133, 105, 105, 106, 134, 107, 107, 108, 135, 109, 109, 106, 134, 107, 107, 108, 135, 109, 109, 94, 128, 95, 95, 96, 129, 97, 97, 94, 128, 95, 95, 96, 129, 97, 97, 94, 128, 95, 95, 96, 129, 97, 97, 110, 136, 111, 111, 112, 137, 113, 113, 114, 138, 115, 115, 116, 139, 117, 117, 118, 140, 119, 119, 120, 141, 121, 121, 114, 138, 115, 115, 116, 139, 117, 117, 118, 140, 119, 119, 120, 141, 121, 121, 94, 128, 95, 95, 96, 129, 97, 97, 122, 142, 123, 123, 124, 143, 125, 125, 90, 90, 91, 90, 92, 92, 93, 92, 94, 94, 95, 94, 96, 96, 97, 96, 98, 98, 99, 98, 100, 100, 101, 100, 102, 102, 103, 102, 104, 104, 105, 104, 106, 106, 107, 106, 108, 108, 109, 108, 106, 106, 107, 106, 108, 108, 109, 108, 94, 94, 95, 94, 96, 96, 97, 96, 94, 94, 95, 94, 96, 96, 97, 96, 94, 94, 95, 94, 96, 96, 97, 96, 110, 110, 111, 110, 112, 112, 113, 112, 114, 114, 115, 114, 116, 116, 117, 116, 118, 118, 119, 118, 120, 120, 121, 120, 114, 114, 115, 114, 116, 116, 117, 116, 118, 118, 119, 118, 120, 120, 121, 120, 94, 94, 95, 94, 96, 96, 97, 96, 122, 122, 123, 122, 124, 124, 125, 124, 90, 126, 91, 91, 92, 127, 93, 93, 94, 128, 95, 95, 96, 129, 97, 97, 98, 130, 99, 99, 100, 131, 101, 101, 102, 132, 103, 103, 104, 133, 105, 105, 106, 134, 107, 107, 108, 135, 109, 109, 106, 134, 107, 107, 108, 135, 109, 109, 94, 128, 95, 95, 96, 129, 97, 97, 94, 128, 95, 95, 96, 129, 97, 97, 94, 128, 95, 95, 96, 129, 97, 97, 110, 136, 111, 111, 112, 137, 113, 113, 114, 138, 115, 115, 116, 139, 117, 117, 118, 140, 119, 119, 120, 141, 121, 121, 114, 138, 115, 115, 116, 139, 117, 117, 118, 140, 119, 119, 120, 141, 121, 121, 94, 128, 95, 95, 96, 129, 97, 97, 122, 142, 123, 123, 124, 143, 125, 125, 90, 144, 91, 144, 92, 145, 93, 145, 94, 146, 99, 146, 96, 147, 101, 147, 98, 148, 99, 148, 100, 149, 101, 149, 102, 150, 103, 150, 104, 151, 105, 151, 106, 152, 107, 152, 108, 153, 109, 153, 106, 152, 107, 152, 108, 153, 109, 153, 94, 146, 99, 146, 96, 147, 101, 147, 94, 146, 99, 146, 96, 147, 101, 147, 94, 146, 99, 146, 96, 147, 101, 147, 110, 154, 99, 154, 112, 155, 101, 155, 114, 156, 99, 156, 116, 157, 101, 157, 118, 158, 99, 158, 120, 159, 101, 159, 114, 156, 99, 156, 116, 157, 101, 157, 118, 158, 99, 158, 120, 159, 101, 159, 94, 146, 99, 146, 96, 147, 101, 147, 122, 160, 99, 160, 124, 161, 101, 161, 90, 126, 91, 162, 92, 127, 93, 163, 94, 128, 99, 164, 96, 129, 101, 165, 98, 130, 99, 166, 100, 131, 101, 167, 102, 132, 103, 168, 104, 133, 105, 169, 106, 134, 107, 170, 108, 135, 109, 171, 106, 134, 107, 170, 108, 135, 109, 171, 94, 128, 99, 164, 96, 129, 101, 165, 94, 128, 99, 164, 96, 129, 101, 165, 94, 128, 99, 164, 96, 129, 101, 165, 110, 136, 99, 172, 112, 137, 101, 173, 114, 138, 99, 174, 116, 139, 101, 175, 118, 140, 99, 176, 120, 141, 101, 177, 114, 138, 99, 174, 116, 139, 101, 175, 118, 140, 99, 176, 120, 141, 101, 177, 94, 128, 99, 164, 96, 129, 101, 165, 122, 142, 99, 178, 124, 143, 101, 179, 90, 144, 91, 144, 92, 145, 93, 145, 94, 146, 99, 146, 96, 147, 101, 147, 98, 148, 99, 148, 100, 149, 101, 149, 102, 150, 103, 150, 104, 151, 105, 151, 106, 152, 107, 152, 108, 153, 109, 153, 106, 152, 107, 152, 108, 153, 109, 153, 94, 146, 99, 146, 96, 147, 101, 147, 94, 146, 99, 146, 96, 147, 101, 147, 94, 146, 99, 146, 96, 147, 101, 147, 110, 154, 99, 154, 112, 155, 101, 155, 114, 156, 99, 156, 116, 157, 101, 157, 118, 158, 99, 158, 120, 159, 101, 159, 114, 156, 99, 156, 116, 157, 101, 157, 118, 158, 99, 158, 120, 159, 101, 159, 94, 146, 99, 146, 96, 147, 101, 147, 122, 160, 99, 160, 124, 161, 101, 161, 90, 126, 91, 162, 92, 127, 93, 163, 94, 128, 99, 164, 96, 129, 101, 165, 98, 130, 99, 166, 100, 131, 101, 167, 102, 132, 103, 168, 104, 133, 105, 169, 106, 134, 107, 170, 108, 135, 109, 171, 106, 134, 107, 170, 108, 135, 109, 171, 94, 128, 99, 164, 96, 129, 101, 165, 94, 128, 99, 164, 96, 129, 101, 165, 94, 128, 99, 164, 96, 129, 101, 165, 110, 136, 99, 172, 112, 137, 101, 173, 114, 138, 99, 174, 116, 139, 101, 175, 118, 140, 99, 176, 120, 141, 101, 177, 114, 138, 99, 174, 116, 139, 101, 175, 118, 140, 99, 176, 120, 141, 101, 177, 94, 128, 99, 164, 96, 129, 101, 165, 122, 142, 99, 178, 124, 143, 101, 179, 0, 0, 1, 0, 2, 2, 3, 2, 4, 4, 5, 4, 6, 6, 7, 6, 8, 8, 9, 8, 10, 10, 11, 10, 12, 12, 13, 12, 14, 14, 15, 14, 16, 16, 17, 16, 18, 18, 19, 18, 16, 16, 17, 16, 18, 18, 19, 18, 4, 4, 5, 4, 6, 6, 7, 6, 4, 4, 5, 4, 6, 6, 7, 6, 4, 4, 5, 4, 6, 6, 7, 6, 20, 20, 21, 20, 22, 22, 23, 22, 24, 24, 25, 24, 26, 26, 27, 26, 28, 28, 29, 28, 30, 30, 31, 30, 24, 24, 25, 24, 26, 26, 27, 26, 28, 28, 29, 28, 30, 30, 31, 30, 4, 4, 5, 4, 10, 10, 11, 10, 32, 32, 33, 32, 34, 34, 35, 34, 0, 36, 1, 1, 2, 37, 3, 3, 4, 38, 5, 5, 6, 39, 7, 7, 8, 40, 9, 9, 10, 41, 11, 11, 12, 42, 13, 13, 14, 43, 15, 15, 16, 44, 17, 17, 18, 45, 19, 19, 16, 44, 17, 17, 18, 45, 19, 19, 4, 38, 5, 5, 6, 39, 7, 7, 4, 38, 5, 5, 6, 39, 7, 7, 4, 38, 5, 5, 6, 39, 7, 7, 20, 46, 21, 21, 22, 47, 23, 23, 24, 48, 25, 25, 26, 49, 27, 27, 28, 50, 29, 29, 30, 51, 31, 31, 24, 48, 25, 25, 26, 49, 27, 27, 28, 50, 29, 29, 30, 51, 31, 31, 4, 38, 5, 5, 10, 41, 11, 11, 32, 52, 33, 33, 34, 53, 35, 35, 0, 0, 1, 0, 2, 2, 3, 2, 4, 4, 5, 4, 6, 6, 7, 6, 8, 8, 9, 8, 10, 10, 11, 10, 12, 12, 13, 12, 14, 14, 15, 14, 16, 16, 17, 16, 18, 18, 19, 18, 16, 16, 17, 16, 18, 18, 19, 18, 4, 4, 5, 4, 6, 6, 7, 6, 4, 4, 5, 4, 6, 6, 7, 6, 4, 4, 5, 4, 6, 6, 7, 6, 20, 20, 21, 20, 22, 22, 23, 22, 24, 24, 25, 24, 26, 26, 27, 26, 28, 28, 29, 28, 30, 30, 31, 30, 24, 24, 25, 24, 26, 26, 27, 26, 28, 28, 29, 28, 30, 30, 31, 30, 4, 4, 5, 4, 10, 10, 11, 10, 32, 32, 33, 32, 34, 34, 35, 34, 0, 36, 1, 1, 2, 37, 3, 3, 4, 38, 5, 5, 6, 39, 7, 7, 8, 40, 9, 9, 10, 41, 11, 11, 12, 42, 13, 13, 14, 43, 15, 15, 16, 44, 17, 17, 18, 45, 19, 19, 16, 44, 17, 17, 18, 45, 19, 19, 4, 38, 5, 5, 6, 39, 7, 7, 4, 38, 5, 5, 6, 39, 7, 7, 4, 38, 5, 5, 6, 39, 7, 7, 20, 46, 21, 21, 22, 47, 23, 23, 24, 48, 25, 25, 26, 49, 27, 27, 28, 50, 29, 29, 30, 51, 31, 31, 24, 48, 25, 25, 26, 49, 27, 27, 28, 50, 29, 29, 30, 51, 31, 31, 4, 38, 5, 5, 10, 41, 11, 11, 32, 52, 33, 33, 34, 53, 35, 35, 0, 54, 1, 54, 2, 55, 3, 55, 4, 56, 9, 56, 6, 57, 11, 57, 8, 58, 9, 58, 10, 59, 11, 59, 12, 60, 13, 60, 14, 61, 15, 61, 16, 62, 17, 62, 18, 63, 19, 63, 16, 62, 17, 62, 18, 63, 19, 63, 4, 56, 9, 56, 6, 57, 11, 57, 4, 56, 9, 56, 6, 57, 11, 57, 4, 56, 9, 56, 6, 57, 11, 57, 20, 64, 9, 64, 22, 65, 11, 65, 24, 66, 9, 66, 26, 67, 11, 67, 28, 68, 9, 68, 30, 69, 11, 69, 24, 66, 9, 66, 26, 67, 11, 67, 28, 68, 9, 68, 30, 69, 11, 69, 4, 56, 9, 56, 10, 59, 11, 59, 32, 70, 9, 70, 34, 71, 11, 71, 0, 36, 1, 72, 2, 37, 3, 73, 4, 38, 9, 74, 6, 39, 11, 75, 8, 40, 9, 76, 10, 41, 11, 77, 12, 42, 13, 78, 14, 43, 15, 79, 16, 44, 17, 80, 18, 45, 19, 81, 16, 44, 17, 80, 18, 45, 19, 81, 4, 38, 9, 74, 6, 39, 11, 75, 4, 38, 9, 74, 6, 39, 11, 75, 4, 38, 9, 74, 6, 39, 11, 75, 20, 46, 9, 82, 22, 47, 11, 83, 24, 48, 9, 84, 26, 49, 11, 85, 28, 50, 9, 86, 30, 51, 11, 87, 24, 48, 9, 84, 26, 49, 11, 85, 28, 50, 9, 86, 30, 51, 11, 87, 4, 38, 9, 74, 10, 41, 11, 77, 32, 52, 9, 88, 34, 53, 11, 89, 0, 54, 1, 54, 2, 55, 3, 55, 4, 56, 9, 56, 6, 57, 11, 57, 8, 58, 9, 58, 10, 59, 11, 59, 12, 60, 13, 60, 14, 61, 15, 61, 16, 62, 17, 62, 18, 63, 19, 63, 16, 62, 17, 62, 18, 63, 19, 63, 4, 56, 9, 56, 6, 57, 11, 57, 4, 56, 9, 56, 6, 57, 11, 57, 4, 56, 9, 56, 6, 57, 11, 57, 20, 64, 9, 64, 22, 65, 11, 65, 24, 66, 9, 66, 26, 67, 11, 67, 28, 68, 9, 68, 30, 69, 11, 69, 24, 66, 9, 66, 26, 67, 11, 67, 28, 68, 9, 68, 30, 69, 11, 69, 4, 56, 9, 56, 10, 59, 11, 59, 32, 70, 9, 70, 34, 71, 11, 71, 0, 36, 1, 72, 2, 37, 3, 73, 4, 38, 9, 74, 6, 39, 11, 75, 8, 40, 9, 76, 10, 41, 11, 77, 12, 42, 13, 78, 14, 43, 15, 79, 16, 44, 17, 80, 18, 45, 19, 81, 16, 44, 17, 80, 18, 45, 19, 81, 4, 38, 9, 74, 6, 39, 11, 75, 4, 38, 9, 74, 6, 39, 11, 75, 4, 38, 9, 74, 6, 39, 11, 75, 20, 46, 9, 82, 22, 47, 11, 83, 24, 48, 9, 84, 26, 49, 11, 85, 28, 50, 9, 86, 30, 51, 11, 87, 24, 48, 9, 84, 26, 49, 11, 85, 28, 50, 9, 86, 30, 51, 11, 87, 4, 38, 9, 74, 10, 41, 11, 77, 32, 52, 9, 88, 34, 53, 11, 89, 90, 90, 91, 90, 92, 92, 93, 92, 94, 94, 95, 94, 96, 96, 97, 96, 98, 98, 99, 98, 100, 100, 101, 100, 102, 102, 103, 102, 104, 104, 105, 104, 106, 106, 107, 106, 108, 108, 109, 108, 106, 106, 107, 106, 108, 108, 109, 108, 94, 94, 95, 94, 96, 96, 97, 96, 94, 94, 95, 94, 96, 96, 97, 96, 94, 94, 95, 94, 96, 96, 97, 96, 110, 110, 111, 110, 112, 112, 113, 112, 114, 114, 115, 114, 116, 116, 117, 116, 118, 118, 119, 118, 120, 120, 121, 120, 114, 114, 115, 114, 116, 116, 117, 116, 118, 118, 119, 118, 120, 120, 121, 120, 94, 94, 95, 94, 100, 100, 101, 100, 122, 122, 123, 122, 124, 124, 125, 124, 90, 126, 91, 91, 92, 127, 93, 93, 94, 128, 95, 95, 96, 129, 97, 97, 98, 130, 99, 99, 100, 131, 101, 101, 102, 132, 103, 103, 104, 133, 105, 105, 106, 134, 107, 107, 108, 135, 109, 109, 106, 134, 107, 107, 108, 135, 109, 109, 94, 128, 95, 95, 96, 129, 97, 97, 94, 128, 95, 95, 96, 129, 97, 97, 94, 128, 95, 95, 96, 129, 97, 97, 110, 136, 111, 111, 112, 137, 113, 113, 114, 138, 115, 115, 116, 139, 117, 117, 118, 140, 119, 119, 120, 141, 121, 121, 114, 138, 115, 115, 116, 139, 117, 117, 118, 140, 119, 119, 120, 141, 121, 121, 94, 128, 95, 95, 100, 131, 101, 101, 122, 142, 123, 123, 124, 143, 125, 125, 90, 90, 91, 90, 92, 92, 93, 92, 94, 94, 95, 94, 96, 96, 97, 96, 98, 98, 99, 98, 100, 100, 101, 100, 102, 102, 103, 102, 104, 104, 105, 104, 106, 106, 107, 106, 108, 108, 109, 108, 106, 106, 107, 106, 108, 108, 109, 108, 94, 94, 95, 94, 96, 96, 97, 96, 94, 94, 95, 94, 96, 96, 97, 96, 94, 94, 95, 94, 96, 96, 97, 96, 110, 110, 111, 110, 112, 112, 113, 112, 114, 114, 115, 114, 116, 116, 117, 116, 118, 118, 119, 118, 120, 120, 121, 120, 114, 114, 115, 114, 116, 116, 117, 116, 118, 118, 119, 118, 120, 120, 121, 120, 94, 94, 95, 94, 100, 100, 101, 100, 122, 122, 123, 122, 124, 124, 125, 124, 90, 126, 91, 91, 92, 127, 93, 93, 94, 128, 95, 95, 96, 129, 97, 97, 98, 130, 99, 99, 100, 131, 101, 101, 102, 132, 103, 103, 104, 133, 105, 105, 106, 134, 107, 107, 108, 135, 109, 109, 106, 134, 107, 107, 108, 135, 109, 109, 94, 128, 95, 95, 96, 129, 97, 97, 94, 128, 95, 95, 96, 129, 97, 97, 94, 128, 95, 95, 96, 129, 97, 97, 110, 136, 111, 111, 112, 137, 113, 113, 114, 138, 115, 115, 116, 139, 117, 117, 118, 140, 119, 119, 120, 141, 121, 121, 114, 138, 115, 115, 116, 139, 117, 117, 118, 140, 119, 119, 120, 141, 121, 121, 94, 128, 95, 95, 100, 131, 101, 101, 122, 142, 123, 123, 124, 143, 125, 125, 90, 144, 91, 144, 92, 145, 93, 145, 94, 146, 99, 146, 96, 147, 101, 147, 98, 148, 99, 148, 100, 149, 101, 149, 102, 150, 103, 150, 104, 151, 105, 151, 106, 152, 107, 152, 108, 153, 109, 153, 106, 152, 107, 152, 108, 153, 109, 153, 94, 146, 99, 146, 96, 147, 101, 147, 94, 146, 99, 146, 96, 147, 101, 147, 94, 146, 99, 146, 96, 147, 101, 147, 110, 154, 99, 154, 112, 155, 101, 155, 114, 156, 99, 156, 116, 157, 101, 157, 118, 158, 99, 158, 120, 159, 101, 159, 114, 156, 99, 156, 116, 157, 101, 157, 118, 158, 99, 158, 120, 159, 101, 159, 94, 146, 99, 146, 100, 149, 101, 149, 122, 160, 99, 160, 124, 161, 101, 161, 90, 126, 91, 162, 92, 127, 93, 163, 94, 128, 99, 164, 96, 129, 101, 165, 98, 130, 99, 166, 100, 131, 101, 167, 102, 132, 103, 168, 104, 133, 105, 169, 106, 134, 107, 170, 108, 135, 109, 171, 106, 134, 107, 170, 108, 135, 109, 171, 94, 128, 99, 164, 96, 129, 101, 165, 94, 128, 99, 164, 96, 129, 101, 165, 94, 128, 99, 164, 96, 129, 101, 165, 110, 136, 99, 172, 112, 137, 101, 173, 114, 138, 99, 174, 116, 139, 101, 175, 118, 140, 99, 176, 120, 141, 101, 177, 114, 138, 99, 174, 116, 139, 101, 175, 118, 140, 99, 176, 120, 141, 101, 177, 94, 128, 99, 164, 100, 131, 101, 167, 122, 142, 99, 178, 124, 143, 101, 179, 90, 144, 91, 144, 92, 145, 93, 145, 94, 146, 99, 146, 96, 147, 101, 147, 98, 148, 99, 148, 100, 149, 101, 149, 102, 150, 103, 150, 104, 151, 105, 151, 106, 152, 107, 152, 108, 153, 109, 153, 106, 152, 107, 152, 108, 153, 109, 153, 94, 146, 99, 146, 96, 147, 101, 147, 94, 146, 99, 146, 96, 147, 101, 147, 94, 146, 99, 146, 96, 147, 101, 147, 110, 154, 99, 154, 112, 155, 101, 155, 114, 156, 99, 156, 116, 157, 101, 157, 118, 158, 99, 158, 120, 159, 101, 159, 114, 156, 99, 156, 116, 157, 101, 157, 118, 158, 99, 158, 120, 159, 101, 159, 94, 146, 99, 146, 100, 149, 101, 149, 122, 160, 99, 160, 124, 161, 101, 161, 90, 126, 91, 162, 92, 127, 93, 163, 94, 128, 99, 164, 96, 129, 101, 165, 98, 130, 99, 166, 100, 131, 101, 167, 102, 132, 103, 168, 104, 133, 105, 169, 106, 134, 107, 170, 108, 135, 109, 171, 106, 134, 107, 170, 108, 135, 109, 171, 94, 128, 99, 164, 96, 129, 101, 165, 94, 128, 99, 164, 96, 129, 101, 165, 94, 128, 99, 164, 96, 129, 101, 165, 110, 136, 99, 172, 112, 137, 101, 173, 114, 138, 99, 174, 116, 139, 101, 175, 118, 140, 99, 176, 120, 141, 101, 177, 114, 138, 99, 174, 116, 139, 101, 175, 118, 140, 99, 176, 120, 141, 101, 177, 94, 128, 99, 164, 100, 131, 101, 167, 122, 142, 99, 178, 124, 143, 101, 179 }; static const GraphemeSegmentationResult GraphemeSegmentationResult_t2[2880] = { {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=0, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=0, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=1, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=0, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=0, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_AtStart, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_None, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Prepend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_CR, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LF, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Control, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_Extend, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Regional_Indicator, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_SpacingMark, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_L, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_V, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_T, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LV, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_LVT, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, {.grapheme_break=GBP_ZWJ, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=1}, {.grapheme_break=GBP_Private_Expecting_RI, .incb_consonant_extended=1, .incb_consonant_extended_linker=0, .incb_consonant_extended_linker_extended=1, .emoji_modifier_sequence=1, .emoji_modifier_sequence_before_last_char=1, .add_to_current_cell=0}, }; static inline uint16_t GraphemeSegmentationKey(GraphemeSegmentationResult r, CharProps ch){ return (r.state << 7) | ch.grapheme_segmentation_property; } // GraphemeSegmentationStateDeclaration: uses 9 bits {{{ typedef union GraphemeSegmentationState { struct __attribute__((packed)) { #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ uint8_t emoji_modifier_sequence_before_last_char : 1; uint8_t emoji_modifier_sequence : 1; uint8_t incb_consonant_extended_linker_extended : 1; uint8_t incb_consonant_extended_linker : 1; uint8_t incb_consonant_extended : 1; uint8_t grapheme_break : 4; uint8_t : 7; #elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ uint8_t : 7; uint8_t grapheme_break : 4; uint8_t incb_consonant_extended : 1; uint8_t incb_consonant_extended_linker : 1; uint8_t incb_consonant_extended_linker_extended : 1; uint8_t emoji_modifier_sequence : 1; uint8_t emoji_modifier_sequence_before_last_char : 1; #else #error "Unsupported endianness" #endif }; uint16_t val; } GraphemeSegmentationState; static_assert(sizeof(GraphemeSegmentationState) == sizeof(uint16_t), "Fix the ordering of GraphemeSegmentationState"); // EndGraphemeSegmentationStateDeclaration }}} kitty-0.41.1/kitty/char-props.c0000664000175000017510000000256314773370543015703 0ustar nileshnilesh/* * char-props.c * Copyright (C) 2025 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "char-props.h" #include "char-props-data.h" static char_type ensure_char_in_range(const char_type value) { // Branchless: if (value > MAX_UNICODE) value = 0 const int64_t diff = ((int64_t)value) - ((int64_t)(MAX_UNICODE + 1u)); // The right shift gives all ones for negative diff and all zeros for positive diff const char_type mask = diff >> 63; return value & mask; } CharProps char_props_for(char_type ch) { ch = ensure_char_in_range(ch); return CharProps_t3[CharProps_t2[(CharProps_t1[ch >> CharProps_shift] << CharProps_shift) + (ch & CharProps_mask)]]; } void grapheme_segmentation_reset(GraphemeSegmentationResult *s) { s->val = 0; } GraphemeSegmentationResult grapheme_segmentation_step(GraphemeSegmentationResult r, CharProps ch) { unsigned key = GraphemeSegmentationKey(r, ch); unsigned t1 = ((unsigned)GraphemeSegmentationResult_t1[key >> GraphemeSegmentationResult_shift]) << GraphemeSegmentationResult_shift; GraphemeSegmentationResult ans = GraphemeSegmentationResult_t2[t1 + (key & GraphemeSegmentationResult_mask)]; // printf("state: %u gsp: %u -> key: %u t1: %u -> add_to_cell: %u\n", r.state, ch.grapheme_segmentation_property, key, t1, ans.add_to_current_cell); return ans; } kitty-0.41.1/kitty/char-props.h0000664000175000017510000001070714773370543015707 0ustar nileshnilesh/* * char-props.h * Copyright (C) 2025 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" // CharPropsDeclaration: uses 23 bits {{{ typedef union CharProps { struct __attribute__((packed)) { #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ uint8_t is_extended_pictographic : 1; uint8_t indic_conjunct_break : 2; uint8_t grapheme_break : 4; uint8_t is_punctuation : 1; uint8_t is_word_char : 1; uint8_t is_combining_char : 1; uint8_t is_symbol : 1; uint8_t is_non_rendered : 1; uint8_t is_invalid : 1; uint8_t is_emoji_presentation_base : 1; uint8_t category : 5; uint8_t is_emoji : 1; uint8_t shifted_width : 3; uint16_t : 9; #elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ uint16_t : 9; uint8_t shifted_width : 3; uint8_t is_emoji : 1; uint8_t category : 5; uint8_t is_emoji_presentation_base : 1; uint8_t is_invalid : 1; uint8_t is_non_rendered : 1; uint8_t is_symbol : 1; uint8_t is_combining_char : 1; uint8_t is_word_char : 1; uint8_t is_punctuation : 1; uint8_t grapheme_break : 4; uint8_t indic_conjunct_break : 2; uint8_t is_extended_pictographic : 1; #else #error "Unsupported endianness" #endif }; struct __attribute__((packed)) { #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ uint8_t grapheme_segmentation_property : 7; uint32_t : 25; #elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ uint32_t : 25; uint8_t grapheme_segmentation_property : 7; #else #error "Unsupported endianness" #endif }; uint32_t val; } CharProps; static_assert(sizeof(CharProps) == sizeof(uint32_t), "Fix the ordering of CharProps"); // EndCharPropsDeclaration }}} // GraphemeSegmentationResultDeclaration: uses 10 bits {{{ typedef union GraphemeSegmentationResult { struct __attribute__((packed)) { #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ uint8_t emoji_modifier_sequence_before_last_char : 1; uint8_t emoji_modifier_sequence : 1; uint8_t incb_consonant_extended_linker_extended : 1; uint8_t incb_consonant_extended_linker : 1; uint8_t incb_consonant_extended : 1; uint8_t grapheme_break : 4; uint8_t add_to_current_cell : 1; uint8_t : 6; #elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ uint8_t : 6; uint8_t add_to_current_cell : 1; uint8_t grapheme_break : 4; uint8_t incb_consonant_extended : 1; uint8_t incb_consonant_extended_linker : 1; uint8_t incb_consonant_extended_linker_extended : 1; uint8_t emoji_modifier_sequence : 1; uint8_t emoji_modifier_sequence_before_last_char : 1; #else #error "Unsupported endianness" #endif }; struct __attribute__((packed)) { #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ uint16_t state : 9; uint8_t : 7; #elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ uint8_t : 7; uint16_t state : 9; #else #error "Unsupported endianness" #endif }; uint16_t val; } GraphemeSegmentationResult; static_assert(sizeof(GraphemeSegmentationResult) == sizeof(uint16_t), "Fix the ordering of GraphemeSegmentationResult"); // EndGraphemeSegmentationResultDeclaration }}} // UCBDeclaration {{{ #define MAX_UNICODE (1114111u) typedef enum UnicodeCategory { UC_Cn, UC_Cc, UC_Zs, UC_Po, UC_Sc, UC_Ps, UC_Pe, UC_Sm, UC_Pd, UC_Nd, UC_Lu, UC_Sk, UC_Pc, UC_Ll, UC_So, UC_Lo, UC_Pi, UC_Cf, UC_No, UC_Pf, UC_Lt, UC_Lm, UC_Mn, UC_Me, UC_Mc, UC_Nl, UC_Zl, UC_Zp, UC_Cs, UC_Co, } UnicodeCategory; // EndUCBDeclaration }}} CharProps char_props_for(char_type ch); void grapheme_segmentation_reset(GraphemeSegmentationResult *s); GraphemeSegmentationResult grapheme_segmentation_step(GraphemeSegmentationResult r, CharProps ch); static inline int wcwidth_std(CharProps ch) { return (int)ch.shifted_width - 4/*=width_shift*/; } static inline bool is_private_use(CharProps ch) { return ch.category == UC_Co; } static inline const char* char_category(CharProps cp) { #define a(x) case UC_##x: return #x switch((UnicodeCategory)cp.category) { a(Cn); a(Cc); a(Zs); a(Po); a(Sc); a(Ps); a(Pe); a(Sm); a(Pd); a(Nd); a(Lu); a(Sk); a(Pc); a(Ll); a(So); a(Lo); a(Pi); a(Cf); a(No); a(Pf); a(Lt); a(Lm); a(Mn); a(Me); a(Mc); a(Nl); a(Zl); a(Zp); a(Cs); a(Co); } return "Cn"; #undef a } kitty-0.41.1/kitty/charsets.c0000664000175000017510000003055014773370543015436 0ustar nileshnilesh/* * consolemap.c * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ // Taken from consolemap.c in the linux vt driver sourcecode #include "data-types.h" #ifndef NO_SINGLE_BYTE_CHARSETS static uint32_t charset_translations[4][256] = { /* VT100 graphics mapped to Unicode */ { 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x2192, 0x2190, 0x2191, 0x2193, 0x002f, 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x00a0, 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, 0x2591, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x007f, 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff }, /* IBM Codepage 437 mapped to Unicode */ { 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c, 0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8, 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302, 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0 }, // VAX 42 map { 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c, 0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8, 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc, 0x0020, 0x043b, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x0435, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0441, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0435, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x043a, 0x0070, 0x0071, 0x0442, 0x0073, 0x043b, 0x0435, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302, 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0 }, /* UK mapping, same as 8-bit Latin1 except the pound sign replaces # */ { 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x00a3, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff }, }; uint32_t* translation_table(uint32_t which) { switch(which){ default: return NULL; case '0': return charset_translations[0]; case 'U': return charset_translations[1]; case 'V': return charset_translations[2]; case 'A': return charset_translations[3]; } } #endif // UTF-8 decode taken from: https://bjoern.hoehrmann.de/utf-8/decoder/dfa/ static const uint8_t utf8_data[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8 }; #ifndef CHARSETS_STORAGE #define CHARSETS_STORAGE #endif CHARSETS_STORAGE uint32_t decode_utf8(UTF8State* state, uint32_t* codep, uint8_t byte) { uint32_t type = utf8_data[byte]; *codep = (*state != UTF8_ACCEPT) ? (byte & 0x3fu) | (*codep << 6) : (0xff >> type) & (byte); *state = utf8_data[256 + *state*16 + type]; return *state; } CHARSETS_STORAGE size_t decode_utf8_string(const char *src, size_t sz, uint32_t *dest) { // dest must be a zeroed array of size at least sz uint32_t codep = 0; UTF8State state = 0, prev = UTF8_ACCEPT; size_t i, d; for (i = 0, d = 0; i < sz; i++) { switch(decode_utf8(&state, &codep, src[i])) { case UTF8_ACCEPT: dest[d++] = codep; break; case UTF8_REJECT: state = UTF8_ACCEPT; if (prev != UTF8_ACCEPT && i > 0) i--; break; } prev = state; } return d; } CHARSETS_STORAGE unsigned int encode_utf8(uint32_t ch, char* dest) { if (ch < 0x80) { // only lower 7 bits can be 1 dest[0] = (char)ch; // 0xxxxxxx return 1; } if (ch < 0x800) { // only lower 11 bits can be 1 dest[0] = (ch>>6) | 0xC0; // 110xxxxx dest[1] = (ch & 0x3F) | 0x80; // 10xxxxxx return 2; } if (ch < 0x10000) { // only lower 16 bits can be 1 dest[0] = (ch>>12) | 0xE0; // 1110xxxx dest[1] = ((ch>>6) & 0x3F) | 0x80; // 10xxxxxx dest[2] = (ch & 0x3F) | 0x80; // 10xxxxxx return 3; } if (ch < 0x110000) { // only lower 21 bits can be 1 dest[0] = (ch>>18) | 0xF0; // 11110xxx dest[1] = ((ch>>12) & 0x3F) | 0x80; // 10xxxxxx dest[2] = ((ch>>6) & 0x3F) | 0x80; // 10xxxxxx dest[3] = (ch & 0x3F) | 0x80; // 10xxxxxx return 4; } return 0; } kitty-0.41.1/kitty/charsets.h0000664000175000017510000000061714773370543015444 0ustar nileshnilesh/* * Copyright (C) 2019 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include #include uint32_t decode_utf8(uint32_t*, uint32_t*, uint8_t byte); size_t decode_utf8_string(const char *src, size_t sz, uint32_t *dest); unsigned int encode_utf8(uint32_t ch, char* dest); uint32_t* translation_table(uint32_t which); kitty-0.41.1/kitty/child-monitor.c0000664000175000017510000023325114773370543016375 0ustar nileshnilesh/* * child-monitor.c * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "loop-utils.h" #include "safe-wrappers.h" #include "state.h" #include "threading.h" #include "screen.h" #include "fonts.h" #include "monotonic.h" #include #include #include #include #include #include #include #include extern PyTypeObject Screen_Type; #if defined(__APPLE__) || defined(__OpenBSD__) #define NO_SIGQUEUE 1 #endif #ifdef DEBUG_EVENT_LOOP #define EVDBG(...) timed_debug_print(__VA_ARGS__) #else #define EVDBG(...) #endif #define EXTRA_FDS 2 #ifndef MSG_NOSIGNAL // Apple does not implement MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif #define USE_RENDER_FRAMES (global_state.has_render_frames && OPT(sync_to_monitor)) typedef struct { char *data; size_t sz; id_type peer_id; bool is_remote_control_peer; } Message; typedef struct { PyObject_HEAD PyObject *dump_callback, *update_screen, *death_notify; unsigned int count; bool shutting_down; pthread_t io_thread, talk_thread; int talk_fd, listen_fd; Message *messages; size_t messages_capacity, messages_count; LoopData io_loop_data; void (*parse_func)(void*, ParseData*, bool); } ChildMonitor; typedef struct { Screen *screen; bool needs_removal; int fd; unsigned long id; pid_t pid; } Child; static const Child EMPTY_CHILD = {0}; #define screen_mutex(op, which) \ pthread_mutex_##op(&screen->which##_buf_lock); #define children_mutex(op) \ pthread_mutex_##op(&children_lock); #define talk_mutex(op) \ pthread_mutex_##op(&talk_lock); static Child children[MAX_CHILDREN] = {{0}}; static Child scratch[MAX_CHILDREN] = {{0}}; static Child add_queue[MAX_CHILDREN] = {{0}}, remove_queue[MAX_CHILDREN] = {{0}}, remove_notify[MAX_CHILDREN] = {{0}}; static size_t add_queue_count = 0, remove_queue_count = 0; static struct pollfd children_fds[MAX_CHILDREN + EXTRA_FDS] = {{0}}; static pthread_mutex_t children_lock, talk_lock; static bool kill_signal_received = false, reload_config_signal_received = false; static ChildMonitor *the_monitor = NULL; typedef struct { pid_t pid; int status; } ReapedPID; static pid_t monitored_pids[256] = {0}; static size_t monitored_pids_count = 0; static ReapedPID reaped_pids[arraysz(monitored_pids)] = {{0}}; static size_t reaped_pids_count = 0; // Main thread functions {{{ #define FREE_CHILD(x) \ Py_CLEAR((x).screen); x = EMPTY_CHILD; #define XREF_CHILD(x, OP) OP(x.screen); #define INCREF_CHILD(x) XREF_CHILD(x, Py_INCREF) #define DECREF_CHILD(x) XREF_CHILD(x, Py_DECREF) // The max time to wait for events from the window system // before ticking over the main loop. Negative values mean wait forever. static monotonic_t maximum_wait = -1; static void set_maximum_wait(monotonic_t val) { if (val >= 0 && (val < maximum_wait || maximum_wait < 0)) maximum_wait = val; } #define KITTY_HANDLED_SIGNALS SIGINT, SIGHUP, SIGTERM, SIGCHLD, SIGUSR1, SIGUSR2, 0 static void mask_variadic_signals(int sentinel, ...) { sigset_t signals; sigemptyset(&signals); va_list valist; va_start(valist, sentinel); while (true) { int sig = va_arg(valist, int); if (sig == sentinel) break; sigaddset(&signals, sig); } va_end(valist); #ifdef HAS_SIGNAL_FD sigprocmask(SIG_BLOCK, &signals, NULL); #else struct sigaction act = {.sa_handler=SIG_IGN, .sa_flags=SA_RESTART, .sa_mask = signals}; va_start(valist, sentinel); while (true) { int sig = va_arg(valist, int); if (sig == sentinel) break; sigaction(sig, &act, NULL); } va_end(valist); #endif } static PyObject* mask_kitty_signals_process_wide(PyObject *self UNUSED, PyObject *a UNUSED) { mask_variadic_signals(0, KITTY_HANDLED_SIGNALS); Py_RETURN_NONE; } static int verify_peer_uid = false; static PyObject * new_childmonitor_object(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { ChildMonitor *self; PyObject *dump_callback, *death_notify; int talk_fd = -1, listen_fd = -1; int ret; if (the_monitor) { PyErr_SetString(PyExc_RuntimeError, "Can have only a single ChildMonitor instance"); return NULL; } if (!PyArg_ParseTuple(args, "OO|iip", &death_notify, &dump_callback, &talk_fd, &listen_fd, &verify_peer_uid)) return NULL; if ((ret = pthread_mutex_init(&children_lock, NULL)) != 0) { PyErr_Format(PyExc_RuntimeError, "Failed to create children_lock mutex: %s", strerror(ret)); return NULL; } if ((ret = pthread_mutex_init(&talk_lock, NULL)) != 0) { PyErr_Format(PyExc_RuntimeError, "Failed to create talk_lock mutex: %s", strerror(ret)); return NULL; } self = (ChildMonitor *)type->tp_alloc(type, 0); if (!init_loop_data(&self->io_loop_data, KITTY_HANDLED_SIGNALS)) return PyErr_SetFromErrno(PyExc_OSError); self->talk_fd = talk_fd; self->listen_fd = listen_fd; if (self == NULL) return PyErr_NoMemory(); self->death_notify = death_notify; Py_INCREF(death_notify); if (dump_callback != Py_None) { self->dump_callback = dump_callback; Py_INCREF(dump_callback); self->parse_func = parse_worker_dump; } else self->parse_func = parse_worker; self->count = 0; children_fds[0].fd = self->io_loop_data.wakeup_read_fd; children_fds[1].fd = self->io_loop_data.signal_read_fd; children_fds[0].events = POLLIN; children_fds[1].events = POLLIN; children_fds[2].events = POLLIN; the_monitor = self; return (PyObject*) self; } static void dealloc(ChildMonitor* self) { if (self->messages) { for (size_t i = 0; i < self->messages_count; i++) free(self->messages[i].data); free(self->messages); self->messages = NULL; self->messages_count = 0; self->messages_capacity = 0; } pthread_mutex_destroy(&children_lock); pthread_mutex_destroy(&talk_lock); Py_CLEAR(self->dump_callback); Py_CLEAR(self->death_notify); while (remove_queue_count) { remove_queue_count--; FREE_CHILD(remove_queue[remove_queue_count]); } while (add_queue_count) { add_queue_count--; FREE_CHILD(add_queue[add_queue_count]); } free_loop_data(&self->io_loop_data); Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject* handled_signals(ChildMonitor *self, PyObject *args UNUSED) { PyObject *ans = PyTuple_New(self->io_loop_data.num_handled_signals); if (ans) { for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(ans); i++) { PyTuple_SET_ITEM(ans, i, PyLong_FromLong((long)self->io_loop_data.handled_signals[i])); } } return ans; } static void wakeup_io_loop(ChildMonitor *self, bool in_signal_handler) { wakeup_loop(&self->io_loop_data, in_signal_handler, "io_loop"); } static void* io_loop(void *data); static void* talk_loop(void *data); static void send_response_to_peer(id_type peer_id, const char *msg, size_t msg_sz); static void wakeup_talk_loop(bool); static bool add_peer_to_injection_queue(int peer_fd, int pipe_fd); static bool talk_thread_started = false; static bool simple_read_from_pipe(int fd, void *data, size_t sz) { // read a small amount of data to a pipe handling only EINTR while (true) { ssize_t ret = read(fd, data, sz); if (ret == -1 && errno == EINTR) continue; return ret == (ssize_t)sz; } } static PyObject* inject_peer(PyObject *s, PyObject *a) { #define inject_peer_doc "inject_peer(fd) -> Start communication with a peer over the specified file descriptor" ChildMonitor *self = (ChildMonitor*)s; if (!PyLong_Check(a)) { PyErr_SetString(PyExc_TypeError, "peer fd must be an int"); return NULL; } long fd = PyLong_AsLong(a); if (fd < 0) { PyErr_Format(PyExc_ValueError, "Invalid peer fd: %ld", fd); return NULL; } if (!talk_thread_started) { int ret; if ((ret = pthread_create(&self->talk_thread, NULL, talk_loop, self)) != 0) { return PyErr_Format(PyExc_OSError, "Failed to start talk thread with error: %s", strerror(ret)); } talk_thread_started = true; } int fds[2] = {0}; if (!self_pipe(fds, false)) { safe_close(fd, __FILE__, __LINE__); return PyErr_SetFromErrno(PyExc_OSError); } if (!add_peer_to_injection_queue(fd, fds[1])) { safe_close(fd, __FILE__, __LINE__); safe_close(fds[0], __FILE__, __LINE__); safe_close(fds[1], __FILE__, __LINE__); PyErr_SetString(PyExc_RuntimeError, "Too many peers waiting to be injected"); return NULL; } wakeup_talk_loop(false); id_type peer_id = 0; bool ok = simple_read_from_pipe(fds[0], &peer_id, sizeof(peer_id)); safe_close(fds[0], __FILE__, __LINE__); if (!ok) { PyErr_SetString(PyExc_RuntimeError, "Failed to read peer id from self pipe"); return NULL; } return PyLong_FromUnsignedLongLong(peer_id); } static PyObject * start(PyObject *s, PyObject *a UNUSED) { #define start_doc "start() -> Start the I/O thread" ChildMonitor *self = (ChildMonitor*)s; int ret; if (self->talk_fd > -1 || self->listen_fd > -1) { if ((ret = pthread_create(&self->talk_thread, NULL, talk_loop, self)) != 0) { return PyErr_Format(PyExc_OSError, "Failed to start talk thread with error: %s", strerror(ret)); } talk_thread_started = true; } ret = pthread_create(&self->io_thread, NULL, io_loop, self); if (ret != 0) return PyErr_Format(PyExc_OSError, "Failed to start I/O thread with error: %s", strerror(ret)); Py_RETURN_NONE; } static PyObject * wakeup(ChildMonitor *self, PyObject *args UNUSED) { #define wakeup_doc "wakeup() -> wakeup the ChildMonitor I/O thread, forcing it to exit from poll() if it is waiting there." wakeup_io_loop(self, false); Py_RETURN_NONE; } static PyObject * add_child(ChildMonitor *self, PyObject *args) { #define add_child_doc "add_child(id, pid, fd, screen) -> Add a child." children_mutex(lock); if (self->count + add_queue_count >= MAX_CHILDREN) { PyErr_SetString(PyExc_ValueError, "Too many children"); children_mutex(unlock); return NULL; } add_queue[add_queue_count] = EMPTY_CHILD; #define A(attr) &add_queue[add_queue_count].attr if (!PyArg_ParseTuple(args, "kiiO", A(id), A(pid), A(fd), A(screen))) { children_mutex(unlock); return NULL; } #undef A INCREF_CHILD(add_queue[add_queue_count]); add_queue_count++; children_mutex(unlock); wakeup_io_loop(self, false); Py_RETURN_NONE; } #define schedule_write_to_child_generic(id, num, va_start, get_next_arg, va_end) \ ChildMonitor *self = the_monitor; \ bool found = false; \ const char *data; \ size_t szval, sz = 0; \ va_start(ap, num); \ for (unsigned int i = 0; i < num; i++) { \ get_next_arg(ap); \ sz += szval; \ } \ va_end(ap); \ children_mutex(lock); \ for (size_t i = 0; i < self->count; i++) { \ if (children[i].id == id) { \ Screen *screen = children[i].screen; \ screen_mutex(lock, write); \ size_t space_left = screen->write_buf_sz - screen->write_buf_used; \ if (space_left < sz) { \ if (screen->write_buf_used + sz > 100 * 1024 * 1024) { \ log_error("Too much data being sent to child with id: %lu, ignoring it", id); \ screen_mutex(unlock, write); \ break; \ } \ screen->write_buf_sz = screen->write_buf_used + sz; \ screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz); \ if (screen->write_buf == NULL) { fatal("Out of memory."); } \ } \ found = true; \ va_start(ap, num); \ for (unsigned int i = 0; i < num; i++) { \ get_next_arg(ap); \ memcpy(screen->write_buf + screen->write_buf_used, data, szval); \ screen->write_buf_used += szval; \ } \ va_end(ap); \ if (screen->write_buf_sz > BUFSIZ && screen->write_buf_used < BUFSIZ) { \ screen->write_buf_sz = BUFSIZ; \ screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz); \ if (screen->write_buf == NULL) { fatal("Out of memory."); } \ } \ if (screen->write_buf_used) wakeup_io_loop(self, false); \ screen_mutex(unlock, write); \ break; \ } \ } \ children_mutex(unlock); \ return found; bool schedule_write_to_child(unsigned long id, unsigned int num, ...) { va_list ap; #define get_next_arg(ap) data = va_arg(ap, const char*); szval = va_arg(ap, size_t); schedule_write_to_child_generic(id, num, va_start, get_next_arg, va_end); #undef get_next_arg } bool schedule_write_to_child_python(unsigned long id, const char *prefix, PyObject *ap, const char *suffix) { if (!PyTuple_Check(ap)) return false; bool has_prefix = prefix && prefix[0], has_suffix = suffix && suffix[0]; const size_t extra = (has_prefix ? 1 : 0) + (has_suffix ? 1 : 0); size_t num = PyTuple_GET_SIZE(ap) + extra; Py_ssize_t pidx; #define py_start(ap, num) pidx = 0; #define py_end(ap) pidx = 0; #define get_next_arg(ap) { \ size_t pidxf = pidx++; \ if (pidxf == 0 && has_prefix) { data = prefix; szval = strlen(prefix); } \ else { \ if (has_prefix) pidxf--; \ if (has_suffix && pidxf >= (size_t)PyTuple_GET_SIZE(ap)) { data = suffix; szval = strlen(suffix); } \ else { \ PyObject *t = PyTuple_GET_ITEM(ap, pidxf); \ if (PyBytes_Check(t)) { data = PyBytes_AS_STRING(t); szval = PyBytes_GET_SIZE(t); } \ else { \ Py_ssize_t usz; \ data = PyUnicode_AsUTF8AndSize(t, &usz); szval = usz; \ if (!data) fatal("Failed to convert object to bytes in schedule_write_to_child_python"); \ } \ } \ } \ } schedule_write_to_child_generic(id, num, py_start, get_next_arg, py_end); #undef py_start #undef py_end #undef get_next_arg } static PyObject * needs_write(ChildMonitor UNUSED *self, PyObject *args) { #define needs_write_doc "needs_write(id, data) -> Queue data to be written to child." unsigned long id; Py_buffer buf; if (!PyArg_ParseTuple(args, "ky*", &id, &buf)) return NULL; if (schedule_write_to_child(id, 1, buf.buf, (size_t)buf.len)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static PyObject * shutdown_monitor(ChildMonitor *self, PyObject *a UNUSED) { #define shutdown_monitor_doc "shutdown_monitor() -> Shutdown the monitor loop." self->shutting_down = true; wakeup_talk_loop(false); wakeup_io_loop(self, false); int ret = pthread_join(self->io_thread, NULL); if (ret != 0) return PyErr_Format(PyExc_OSError, "Failed to join() I/O thread with error: %s", strerror(ret)); if (talk_thread_started) { ret = pthread_join(self->talk_thread, NULL); if (ret != 0) return PyErr_Format(PyExc_OSError, "Failed to join() talk thread with error: %s", strerror(ret)); } talk_thread_started = false; Py_RETURN_NONE; } static bool do_parse(ChildMonitor *self, Screen *screen, monotonic_t now, bool flush) { ParseData pd = {.dump_callback = self->dump_callback, .now = now}; self->parse_func(screen, &pd, flush); if (pd.input_read) { if (pd.write_space_created) wakeup_io_loop(self, false); if (screen->paused_rendering.expires_at) { set_maximum_wait(MAX(0, screen->paused_rendering.expires_at - now)); } else set_maximum_wait(OPT(input_delay) - pd.time_since_new_input); } else if (pd.has_pending_input) set_maximum_wait(OPT(input_delay) - pd.time_since_new_input); return pd.input_read; } static bool parse_input(ChildMonitor *self) { // Parse all available input that was read in the I/O thread. size_t count = 0, remove_count = 0; bool input_read = false, reload_config_called = false; monotonic_t now = monotonic(); children_mutex(lock); while (remove_queue_count) { remove_queue_count--; remove_notify[remove_count] = remove_queue[remove_queue_count]; INCREF_CHILD(remove_notify[remove_count]); remove_count++; FREE_CHILD(remove_queue[remove_queue_count]); } if (UNLIKELY(kill_signal_received || reload_config_signal_received)) { if (kill_signal_received) { global_state.quit_request = IMPERATIVE_CLOSE_REQUESTED; global_state.has_pending_closes = true; request_tick_callback(); kill_signal_received = false; } else if (reload_config_signal_received) { reload_config_signal_received = false; reload_config_called = true; } } else { count = self->count; for (size_t i = 0; i < count; i++) { scratch[i] = children[i]; INCREF_CHILD(scratch[i]); } } children_mutex(unlock); Message *msgs = NULL; size_t msgs_count = 0; talk_mutex(lock); if (UNLIKELY(self->messages_count)) { msgs = malloc(sizeof(Message) * self->messages_count); if (msgs) { memcpy(msgs, self->messages, sizeof(Message) * self->messages_count); msgs_count = self->messages_count; } memset(self->messages, 0, sizeof(Message) * self->messages_capacity); self->messages_count = 0; } talk_mutex(unlock); if (msgs_count) { for (size_t i = 0; i < msgs_count; i++) { Message *msg = msgs + i; PyObject *resp = NULL; if (msg->data) { resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "y#KO", msg->data, (int)msg->sz, msg->peer_id, msg->is_remote_control_peer ? Py_True : Py_False); free(msg->data); if (!resp) PyErr_Print(); } if (resp) { if (PyBytes_Check(resp)) send_response_to_peer(msg->peer_id, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp)); else if (resp == Py_None) send_response_to_peer(msg->peer_id, NULL, 0); Py_CLEAR(resp); } else send_response_to_peer(msg->peer_id, NULL, 0); } free(msgs); msgs = NULL; } while(remove_count) { // must be done while no locks are held, since the locks are non-recursive and // the python function could call into other functions in this module remove_count--; if (remove_notify[remove_count].screen) do_parse(self, remove_notify[remove_count].screen, now, true); PyObject *t = PyObject_CallFunction(self->death_notify, "k", remove_notify[remove_count].id); if (t == NULL) PyErr_Print(); else Py_DECREF(t); FREE_CHILD(remove_notify[remove_count]); } for (size_t i = 0; i < count; i++) { if (!scratch[i].needs_removal) { if (do_parse(self, scratch[i].screen, now, false)) input_read = true; } DECREF_CHILD(scratch[i]); } if (reload_config_called) { call_boss(load_config_file, NULL); } return input_read; } static bool mark_child_for_close(ChildMonitor *self, id_type window_id) { bool found = false; children_mutex(lock); for (size_t i = 0; i < self->count; i++) { if (children[i].id == window_id) { children[i].needs_removal = true; found = true; break; } } if (!found) { for (size_t i = 0; i < add_queue_count; i++) { if (add_queue[i].id == window_id) { add_queue[i].needs_removal = true; found = true; break; } } } children_mutex(unlock); wakeup_io_loop(self, false); return found; } static PyObject * mark_for_close(ChildMonitor *self, PyObject *args) { #define mark_for_close_doc "Mark a child to be removed from the child monitor" id_type window_id; if (!PyArg_ParseTuple(args, "K", &window_id)) return NULL; if (mark_child_for_close(self, window_id)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static bool pty_resize(int fd, struct winsize *dim) { while(true) { if (ioctl(fd, TIOCSWINSZ, dim) == -1) { if (errno == EINTR) continue; if (errno != EBADF && errno != ENOTTY) { log_error("Failed to resize tty associated with fd: %d with error: %s", fd, strerror(errno)); return false; } } break; } return true; } static PyObject * resize_pty(ChildMonitor *self, PyObject *args) { #define resize_pty_doc "Resize the pty associated with the specified child" unsigned long window_id; struct winsize dim; int fd = -1; if (!PyArg_ParseTuple(args, "kHHHH", &window_id, &dim.ws_row, &dim.ws_col, &dim.ws_xpixel, &dim.ws_ypixel)) return NULL; children_mutex(lock); #define FIND(queue, count) { \ for (size_t i = 0; i < count; i++) { \ if (queue[i].id == window_id) { \ fd = queue[i].fd; \ break; \ } \ }} FIND(children, self->count); if (fd == -1) FIND(add_queue, add_queue_count); if (fd != -1) { if (!pty_resize(fd, &dim)) PyErr_SetFromErrno(PyExc_OSError); } else log_error("Failed to send resize signal to child with id: %lu (children count: %u) (add queue: %zu)", window_id, self->count, add_queue_count); children_mutex(unlock); if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } bool set_iutf8(int UNUSED fd, bool UNUSED on) { #ifdef IUTF8 struct termios attrs; if (tcgetattr(fd, &attrs) != 0) return false; if (on) attrs.c_iflag |= IUTF8; else attrs.c_iflag &= ~IUTF8; if (tcsetattr(fd, TCSANOW, &attrs) != 0) return false; #endif return true; } static PyObject* pyset_iutf8(ChildMonitor *self, PyObject *args) { id_type window_id; int on; PyObject *found = Py_False; if (!PyArg_ParseTuple(args, "Kp", &window_id, &on)) return NULL; children_mutex(lock); for (size_t i = 0; i < self->count; i++) { if (children[i].id == window_id) { found = Py_True; if (!set_iutf8(children_fds[EXTRA_FDS + i].fd, on & 1)) PyErr_SetFromErrno(PyExc_OSError); break; } } children_mutex(unlock); if (PyErr_Occurred()) return NULL; Py_INCREF(found); return found; } #undef FREE_CHILD #undef INCREF_CHILD #undef DECREF_CHILD static bool cursor_needs_render(Window *w) { #define cri w->render_data.screen->cursor_render_info return w->cursor_opacity_at_last_render != cri.opacity || w->render_data.screen->last_rendered.cursor_x != cri.x || w->render_data.screen->last_rendered.cursor_y != cri.y || w->last_cursor_shape != cri.shape; #undef cri } static bool collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow *os_window) { WindowRenderData *rd = &w->render_data; const Cursor *cursor; if (screen_is_overlay_active(rd->screen)) { // Do not force the cursor to be visible here for the sake of some programs that prefer it hidden cursor = &(rd->screen->overlay_line.original_line.cursor); ans->x = rd->screen->overlay_line.cursor_x; ans->y = rd->screen->overlay_line.ynum; } else { cursor = rd->screen->paused_rendering.expires_at ? &rd->screen->paused_rendering.cursor : rd->screen->cursor; ans->x = cursor->x; ans->y = cursor->y; } ans->opacity = 0; if (rd->screen->scrolled_by || !screen_is_cursor_visible(rd->screen)) return cursor_needs_render(w); monotonic_t time_since_start_blink = now - os_window->cursor_blink_zero_time; bool cursor_blinking = OPT(cursor_blink_interval) > 0 && !cursor->non_blinking && os_window->is_focused && (OPT(cursor_stop_blinking_after) == 0 || time_since_start_blink <= OPT(cursor_stop_blinking_after)); ans->opacity = 1; if (cursor_blinking) { if (animation_is_valid(OPT(animation.cursor))) { monotonic_t duration = OPT(cursor_blink_interval) * 2; monotonic_t time_into_cycle = time_since_start_blink % duration; double frac_into_cycle = (double)time_into_cycle / (double)duration; ans->opacity = (float)apply_easing_curve(OPT(animation.cursor), frac_into_cycle, duration); set_maximum_wait(ANIMATION_SAMPLE_WAIT); } else { monotonic_t n = time_since_start_blink / OPT(cursor_blink_interval); ans->opacity = 1 - n % 2; set_maximum_wait((n + 1) * OPT(cursor_blink_interval) - time_since_start_blink); } } ans->shape = cursor->shape ? cursor->shape : OPT(cursor_shape); ans->is_focused = os_window->is_focused; return cursor_needs_render(w); } static void change_menubar_title(PyObject *title UNUSED) { #ifdef __APPLE__ static PyObject *current_title = NULL; if (title != current_title) { current_title = title; if (title && OPT(macos_show_window_title_in) & MENUBAR) update_menu_bar_title(title); } #endif } static bool prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows, bool *all_windows_have_same_bg, bool scan_for_animated_images) { #define TD os_window->tab_bar_render_data bool needs_render = os_window->needs_render; os_window->needs_render = false; if (TD.screen && os_window->num_tabs >= OPT(tab_bar_min_tabs)) { if (!os_window->tab_bar_data_updated) { call_boss(update_tab_bar_data, "K", os_window->id); os_window->tab_bar_data_updated = true; } if (send_cell_data_to_gpu(TD.vao_idx, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window)) needs_render = true; } if (OPT(mouse_hide_wait) > 0 && !is_mouse_hidden(os_window)) { if (now - os_window->last_mouse_activity_at >= OPT(mouse_hide_wait)) hide_mouse(os_window); else set_maximum_wait(OPT(mouse_hide_wait) - now + os_window->last_mouse_activity_at); } Tab *tab = os_window->tabs + os_window->active_tab; *active_window_bg = OPT(background); *all_windows_have_same_bg = true; *num_visible_windows = 0; color_type first_window_bg = 0; for (unsigned int i = 0; i < tab->num_windows; i++) { Window *w = tab->windows + i; #define WD w->render_data if (w->visible && WD.screen) { screen_check_pause_rendering(WD.screen, now); *num_visible_windows += 1; color_type window_bg = colorprofile_to_color(WD.screen->color_profile, WD.screen->color_profile->overridden.default_bg, WD.screen->color_profile->configured.default_bg).rgb; if (*num_visible_windows == 1) first_window_bg = window_bg; if (first_window_bg != window_bg) *all_windows_have_same_bg = false; if (w->last_drag_scroll_at > 0) { if (now - w->last_drag_scroll_at >= ms_to_monotonic_t(20ll)) { if (drag_scroll(w, os_window)) { w->last_drag_scroll_at = now; set_maximum_wait(ms_to_monotonic_t(20ll)); needs_render = true; } else w->last_drag_scroll_at = 0; } else set_maximum_wait(now - w->last_drag_scroll_at); } bool is_active_window = i == tab->active_window; if (is_active_window) { *active_window_id = w->id; if (collect_cursor_info(&WD.screen->cursor_render_info, w, now, os_window)) needs_render = true; WD.screen->cursor_render_info.is_focused = os_window->is_focused; set_os_window_title_from_window(w, os_window); *active_window_bg = window_bg; if (OPT(cursor_trail)) { if (update_cursor_trail(&tab->cursor_trail, w, now, os_window)) { needs_render = true; // A max wait of zero causes key input processing to be // slow so handle the case of OPT(repaint_delay) == 0, see https://github.com/kovidgoyal/kitty/pull/8066 set_maximum_wait(MAX(OPT(repaint_delay), ms_to_monotonic_t(1ll))); } else if (OPT(cursor_trail) > now - WD.screen->cursor->position_changed_by_client_at) { // If update_cursor_trail failed due to time threshold, the trail animation // should be evaluated again shortly. Schedule next update when enough time // has passed since the cursor was last moved. set_maximum_wait(OPT(cursor_trail) - now + WD.screen->cursor->position_changed_by_client_at); } } } else { if (WD.screen->cursor_render_info.render_even_when_unfocused) { if (collect_cursor_info(&WD.screen->cursor_render_info, w, now, os_window)) needs_render = true; WD.screen->cursor_render_info.is_focused = false; } else { WD.screen->cursor_render_info.opacity = 0; } } if (scan_for_animated_images) { monotonic_t min_gap; if (scan_active_animations(WD.screen->grman, now, &min_gap, true)) needs_render = true; if (min_gap < MONOTONIC_T_MAX) { global_state.check_for_active_animated_images = true; set_maximum_wait(min_gap); } } if (send_cell_data_to_gpu(WD.vao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window)) needs_render = true; if (WD.screen->start_visual_bell_at != 0) needs_render = true; } } return needs_render; } static void draw_resizing_text(OSWindow *w) { if (monotonic() - w->created_at > ms_to_monotonic_t(1000) && w->live_resize.num_of_resize_events > 1) { char text[32] = {0}; unsigned int width = w->live_resize.width, height = w->live_resize.height; snprintf(text, sizeof(text), "%u x %u cells", width / w->fonts_data->fcm.cell_width, height / w->fonts_data->fcm.cell_height); StringCanvas rendered = render_simple_text(w->fonts_data, text); if (rendered.canvas) { draw_centered_alpha_mask(w, width, height, rendered.width, rendered.height, rendered.canvas, OPT(background_opacity)); free(rendered.canvas); } } } static void render_prepared_os_window(OSWindow *os_window, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg) { // ensure all pixels are cleared to background color at least once in every buffer if (os_window->clear_count++ < 3) blank_os_window(os_window); Tab *tab = os_window->tabs + os_window->active_tab; BorderRects *br = &tab->border_rects; draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, all_windows_have_same_bg, os_window); br->is_dirty = false; if (TD.screen && os_window->num_tabs >= OPT(tab_bar_min_tabs)) draw_cells(TD.vao_idx, &TD, os_window, true, true, false, NULL); unsigned int num_of_visible_windows = 0; Window *active_window = NULL; for (unsigned int i = 0; i < tab->num_windows; i++) { if (tab->windows[i].visible) num_of_visible_windows++; } for (unsigned int i = 0; i < tab->num_windows; i++) { Window *w = tab->windows + i; if (w->visible && WD.screen) { bool is_active_window = i == tab->active_window; if (is_active_window) active_window = w; draw_cells(WD.vao_idx, &WD, os_window, is_active_window, false, num_of_visible_windows == 1, w); if (WD.screen->start_visual_bell_at != 0) set_maximum_wait(ANIMATION_SAMPLE_WAIT); w->cursor_opacity_at_last_render = WD.screen->cursor_render_info.opacity; w->last_cursor_shape = WD.screen->cursor_render_info.shape; } } if (OPT(cursor_trail) && tab->cursor_trail.needs_render) draw_cursor_trail(&tab->cursor_trail, active_window); if (os_window->live_resize.in_progress) draw_resizing_text(os_window); swap_window_buffers(os_window); os_window->last_active_tab = os_window->active_tab; os_window->last_num_tabs = os_window->num_tabs; os_window->last_active_window_id = active_window_id; os_window->focused_at_last_render = os_window->is_focused; if (os_window->redraw_count) os_window->redraw_count--; if (USE_RENDER_FRAMES) request_frame_render(os_window); #undef WD #undef TD } static bool no_render_frame_received_recently(OSWindow *w, monotonic_t now, monotonic_t max_wait) { bool ans = now - w->last_render_frame_received_at > max_wait; if (ans && global_state.debug_rendering) { if (global_state.is_wayland) { log_error("No render frame received in %.2f seconds", monotonic_t_to_s_double(max_wait)); } else { log_error("No render frame received in %.2f seconds, re-requesting", monotonic_t_to_s_double(max_wait)); } } return ans; } bool render_os_window(OSWindow *w, monotonic_t now, bool ignore_render_frames, bool scan_for_animated_images) { if (!w->num_tabs) return false; if (!should_os_window_be_rendered(w)) { update_os_window_title(w); if (w->is_focused) change_menubar_title(w->window_title); return false; } if (!ignore_render_frames && USE_RENDER_FRAMES && w->render_state != RENDER_FRAME_READY) { if (w->render_state == RENDER_FRAME_NOT_REQUESTED || no_render_frame_received_recently(w, now, ms_to_monotonic_t(250ll))) request_frame_render(w); // dont respect render frames soon after a resize on Wayland as they cause flicker because // we want to fill the newly resized buffer ASAP, not at compositors convenience if (!global_state.is_wayland || (monotonic() - w->viewport_resized_at) > s_double_to_monotonic_t(1)) { return false; } } w->render_calls++; make_os_window_context_current(w); if (w->live_resize.in_progress) blank_os_window(w); bool needs_render = w->redraw_count > 0 || w->live_resize.in_progress; if (w->viewport_size_dirty) { w->clear_count = 0; update_surface_size(w->viewport_width, w->viewport_height, 0); w->viewport_size_dirty = false; needs_render = true; } unsigned int active_window_id = 0, num_visible_windows = 0; bool all_windows_have_same_bg; color_type active_window_bg = 0; if (!w->fonts_data) { log_error("No fonts data found for window id: %llu", w->id); return false; } if (prepare_to_render_os_window(w, now, &active_window_id, &active_window_bg, &num_visible_windows, &all_windows_have_same_bg, scan_for_animated_images)) needs_render = true; if (w->last_active_window_id != active_window_id || w->last_active_tab != w->active_tab || w->focused_at_last_render != w->is_focused) needs_render = true; if (w->render_calls < 3 && w->bgimage && w->bgimage->texture_id) needs_render = true; if (needs_render) render_prepared_os_window(w, active_window_id, active_window_bg, num_visible_windows, all_windows_have_same_bg); if (w->is_focused) change_menubar_title(w->window_title); return needs_render; } static void render(monotonic_t now, bool input_read) { EVDBG("input_read: %d, check_for_active_animated_images: %d", input_read, global_state.check_for_active_animated_images); static monotonic_t last_render_at = MONOTONIC_T_MIN; monotonic_t time_since_last_render = last_render_at == MONOTONIC_T_MIN ? OPT(repaint_delay) : now - last_render_at; if (!input_read && time_since_last_render < OPT(repaint_delay)) { set_maximum_wait(OPT(repaint_delay) - time_since_last_render); return; } const bool scan_for_animated_images = global_state.check_for_active_animated_images; global_state.check_for_active_animated_images = false; for (size_t i = 0; i < global_state.num_os_windows; i++) { OSWindow *w = global_state.os_windows + i; #ifdef __APPLE__ // rendering is done in cocoa_os_window_resized() if (w->live_resize.in_progress) continue; #endif if (!render_os_window(w, now, false, scan_for_animated_images)) { // since we didn't scan the window for animations, force a rescan on next wakeup/render frame if (scan_for_animated_images) global_state.check_for_active_animated_images = true; } } last_render_at = now; #undef TD } typedef struct { int fd; uint8_t *buf; size_t sz; } ThreadWriteData; static ThreadWriteData* alloc_twd(size_t sz) { ThreadWriteData *data = calloc(1, sizeof(ThreadWriteData)); if (data != NULL) { data->sz = sz; data->buf = malloc(sz); if (data->buf == NULL) { free(data); data = NULL; } } return data; } static void free_twd(ThreadWriteData *x) { if (x != NULL) free(x->buf); free(x); } static PyObject* sig_queue(PyObject *self UNUSED, PyObject *args) { int pid, signal, value; if (!PyArg_ParseTuple(args, "iii", &pid, &signal, &value)) return NULL; #ifdef NO_SIGQUEUE if (kill(pid, signal) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } #else union sigval v; v.sival_int = value; if (sigqueue(pid, signal, v) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } #endif Py_RETURN_NONE; } static PyObject* monitor_pid(PyObject *self UNUSED, PyObject *args) { int pid; bool ok = true; if (!PyArg_ParseTuple(args, "i", &pid)) return NULL; children_mutex(lock); if (monitored_pids_count >= arraysz(monitored_pids)) { PyErr_SetString(PyExc_RuntimeError, "Too many monitored pids"); ok = false; } else { monitored_pids[monitored_pids_count++] = pid; } children_mutex(unlock); if (!ok) return NULL; Py_RETURN_NONE; } static void report_reaped_pids(void) { static ReapedPID pids[64]; size_t i = 0; children_mutex(lock); if (reaped_pids_count) { for (; i < reaped_pids_count && i < arraysz(pids); i++) { pids[i] = reaped_pids[i]; } reaped_pids_count = 0; } children_mutex(unlock); for (size_t n = 0; n < i; n++) { call_boss(on_monitored_pid_death, "li", (long)pids[n].pid, pids[n].status); } } static void* thread_write(void *x) { ThreadWriteData *data = (ThreadWriteData*)x; set_thread_name("KittyWriteStdin"); int flags = fcntl(data->fd, F_GETFL, 0); if (flags == -1) { free_twd(data); return 0; } flags &= ~O_NONBLOCK; fcntl(data->fd, F_SETFL, flags); size_t pos = 0; while (pos < data->sz) { errno = 0; ssize_t nbytes = write(data->fd, data->buf + pos, data->sz - pos); if (nbytes < 0) { if (errno == EAGAIN || errno == EINTR) continue; break; } if (nbytes == 0) break; pos += nbytes; } if (pos < data->sz) { log_error("Failed to write all data to STDIN of child process with error: %s", strerror(errno)); } safe_close(data->fd, __FILE__, __LINE__); free_twd(data); return 0; } PyObject* cm_thread_write(PyObject UNUSED *self, PyObject *args) { static pthread_t thread; int fd; Py_ssize_t sz; const char *buf; if (!PyArg_ParseTuple(args, "is#", &fd, &buf, &sz)) return NULL; ThreadWriteData *data = alloc_twd(sz); if (data == NULL) return PyErr_NoMemory(); data->fd = fd; memcpy(data->buf, buf, data->sz); int ret = pthread_create(&thread, NULL, thread_write, data); if (ret != 0) { safe_close(fd, __FILE__, __LINE__); free_twd(data); return PyErr_Format(PyExc_OSError, "Failed to start write thread with error: %s", strerror(ret)); } pthread_detach(thread); Py_RETURN_NONE; } static void python_timer_callback(id_type timer_id, void *data) { PyObject *callback = (PyObject*)data; unsigned long long id = timer_id; PyObject *ret = PyObject_CallFunction(callback, "K", id); if (ret == NULL) PyErr_Print(); else Py_DECREF(ret); } static void python_timer_cleanup(id_type timer_id UNUSED, void *data) { if (data) Py_DECREF((PyObject*)data); } static PyObject* add_python_timer(PyObject *self UNUSED, PyObject *args) { PyObject *callback; double interval; int repeats = 1; if (!PyArg_ParseTuple(args, "Od|p", &callback, &interval, &repeats)) return NULL; unsigned long long timer_id = add_main_loop_timer(s_double_to_monotonic_t(interval), repeats ? true: false, python_timer_callback, callback, python_timer_cleanup); Py_INCREF(callback); return Py_BuildValue("K", timer_id); } static PyObject* remove_python_timer(PyObject *self UNUSED, PyObject *args) { unsigned long long timer_id; if (!PyArg_ParseTuple(args, "K", &timer_id)) return NULL; remove_main_loop_timer(timer_id); Py_RETURN_NONE; } static void process_pending_resizes(monotonic_t now) { global_state.has_pending_resizes = false; for (size_t i = 0; i < global_state.num_os_windows; i++) { OSWindow *w = global_state.os_windows + i; if (w->live_resize.in_progress) { bool update_viewport = false; if (w->live_resize.from_os_notification) { if (w->live_resize.os_says_resize_complete) update_viewport = true; else { // prevent a "hang" if the OS never sends a resize complete event // also reflow the screen when the user pauses resizing so the user can see what the resized // screen will look like. if ((now - w->live_resize.last_resize_event_at) > OPT(resize_debounce_time).on_pause) update_viewport = true; else { global_state.has_pending_resizes = true; set_maximum_wait(s_double_to_monotonic_t(0.05)); } } } else { monotonic_t debounce_time = OPT(resize_debounce_time).on_end; // if more than one resize event has occurred, wait at least 0.2 secs // before repainting, to avoid rapid transitions between the cells banner // and the normal screen if (now - w->live_resize.last_resize_event_at >= debounce_time) update_viewport = true; else { global_state.has_pending_resizes = true; set_maximum_wait(debounce_time - now + w->live_resize.last_resize_event_at); } } if (update_viewport) { update_os_window_viewport(w, true); change_live_resize_state(w, false); zero_at_ptr(&w->live_resize); // because the window size should be hidden even if update_os_window_viewport does nothing // On Wayland some compositors require two redraws after a // resize to actually render correctly (Run kitty -1 --wait-for-os-window-close in sway to reproduce) w->redraw_count = global_state.is_wayland ? 2 : 1; } } } } static void close_os_window(ChildMonitor *self, OSWindow *os_window) { int w = os_window->window_width, h = os_window->window_height; if (os_window->before_fullscreen.is_set && is_os_window_fullscreen(os_window)) { w = os_window->before_fullscreen.w; h = os_window->before_fullscreen.h; } destroy_os_window(os_window); call_boss(on_os_window_closed, "Kii", os_window->id, w, h); for (size_t t=0; t < os_window->num_tabs; t++) { Tab *tab = os_window->tabs + t; for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id); } remove_os_window(os_window->id); } static bool process_pending_closes(ChildMonitor *self) { if (global_state.quit_request == CONFIRMABLE_CLOSE_REQUESTED) { call_boss(quit, ""); } if (global_state.quit_request == IMPERATIVE_CLOSE_REQUESTED) { for (size_t w = 0; w < global_state.num_os_windows; w++) global_state.os_windows[w].close_request = IMPERATIVE_CLOSE_REQUESTED; } bool has_open_windows = false; for (size_t w = global_state.num_os_windows; w > 0; w--) { OSWindow *os_window = global_state.os_windows + w - 1; switch(os_window->close_request) { case NO_CLOSE_REQUESTED: has_open_windows = true; break; case CONFIRMABLE_CLOSE_REQUESTED: os_window->close_request = CLOSE_BEING_CONFIRMED; call_boss(confirm_os_window_close, "K", os_window->id); if (os_window->close_request == IMPERATIVE_CLOSE_REQUESTED) { close_os_window(self, os_window); } else has_open_windows = true; break; case CLOSE_BEING_CONFIRMED: has_open_windows = true; break; case IMPERATIVE_CLOSE_REQUESTED: close_os_window(self, os_window); break; } } global_state.has_pending_closes = false; #ifdef __APPLE__ if (!OPT(macos_quit_when_last_window_closed)) { if (!has_open_windows && global_state.quit_request != IMPERATIVE_CLOSE_REQUESTED) has_open_windows = true; } #endif return !has_open_windows; } #ifdef __APPLE__ // If we create new OS windows during wait_events(), using global menu actions // via the mouse causes a crash because of the way autorelease pools work in // glfw/cocoa. So we use a flag instead. static bool cocoa_pending_actions[NUM_COCOA_PENDING_ACTIONS] = {0}; static bool has_cocoa_pending_actions = false; typedef struct cocoa_list { char **items; size_t count, capacity; } cocoa_list; typedef struct { char* wd; cocoa_list open_urls, untracked_notifications; } CocoaPendingActionsData; static CocoaPendingActionsData cocoa_pending_actions_data = {0}; static void cocoa_append_to_pending_list(cocoa_list *array, const char* item) { ensure_space_for(array, items, char*, array->count + 1, capacity, 8, false); array->items[array->count++] = strdup(item); } static void cocoa_free_pending_list(cocoa_list *array) { for (size_t i = 0; i < array->count; i++) free(array->items[i]); free(array->items); zero_at_ptr(array); } static void cocoa_free_actions_data(void) { if (cocoa_pending_actions_data.wd) { free(cocoa_pending_actions_data.wd); cocoa_pending_actions_data.wd = NULL; } cocoa_free_pending_list(&cocoa_pending_actions_data.open_urls); cocoa_free_pending_list(&cocoa_pending_actions_data.untracked_notifications); } void set_cocoa_pending_action(CocoaPendingAction action, const char *data) { if (data) { switch(action) { case LAUNCH_URLS: cocoa_append_to_pending_list(&cocoa_pending_actions_data.open_urls, data); break; case COCOA_NOTIFICATION_UNTRACKED: cocoa_append_to_pending_list(&cocoa_pending_actions_data.untracked_notifications, data); break; default: if (cocoa_pending_actions_data.wd) free(cocoa_pending_actions_data.wd); cocoa_pending_actions_data.wd = strdup(data); break; } } cocoa_pending_actions[action] = true; has_cocoa_pending_actions = true; // The main loop may be blocking on the event queue, if e.g. unfocused. // Unjam it so the pending action is processed right now. wakeup_main_loop(); } static void process_cocoa_pending_actions(void) { if (cocoa_pending_actions[PREFERENCES_WINDOW]) { call_boss(edit_config_file, NULL); } if (cocoa_pending_actions[NEW_OS_WINDOW]) { call_boss(new_os_window, NULL); } if (cocoa_pending_actions[CLOSE_OS_WINDOW]) { call_boss(close_os_window, NULL); } if (cocoa_pending_actions[CLOSE_TAB]) { call_boss(close_tab, NULL); } if (cocoa_pending_actions[NEW_TAB]) { call_boss(new_tab, NULL); } if (cocoa_pending_actions[NEXT_TAB]) { call_boss(next_tab, NULL); } if (cocoa_pending_actions[PREVIOUS_TAB]) { call_boss(previous_tab, NULL); } if (cocoa_pending_actions[DETACH_TAB]) { call_boss(detach_tab, NULL); } if (cocoa_pending_actions[NEW_WINDOW]) { call_boss(new_window, NULL); } if (cocoa_pending_actions[CLOSE_WINDOW]) { call_boss(close_window, NULL); } if (cocoa_pending_actions[RESET_TERMINAL]) { call_boss(clear_terminal, "sO", "reset", Py_True ); } if (cocoa_pending_actions[CLEAR_TERMINAL_AND_SCROLLBACK]) { call_boss(clear_terminal, "sO", "to_cursor", Py_True ); } if (cocoa_pending_actions[CLEAR_SCROLLBACK]) { call_boss(clear_terminal, "sO", "scrollback", Py_True ); } if (cocoa_pending_actions[CLEAR_SCREEN]) { call_boss(clear_terminal, "sO", "to_cursor_scroll", Py_True ); } if (cocoa_pending_actions[RELOAD_CONFIG]) { call_boss(load_config_file, NULL); } if (cocoa_pending_actions[TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY]) { call_boss(toggle_macos_secure_keyboard_entry, NULL); } if (cocoa_pending_actions[TOGGLE_FULLSCREEN]) { call_boss(toggle_fullscreen, NULL); } if (cocoa_pending_actions[OPEN_KITTY_WEBSITE]) { call_boss(open_kitty_website, NULL); } if (cocoa_pending_actions[HIDE]) { call_boss(hide_macos_app, NULL); } if (cocoa_pending_actions[HIDE_OTHERS]) { call_boss(hide_macos_other_apps, NULL); } if (cocoa_pending_actions[MINIMIZE]) { call_boss(minimize_macos_window, NULL); } if (cocoa_pending_actions[QUIT]) { call_boss(quit, NULL); } if (cocoa_pending_actions_data.wd) { if (cocoa_pending_actions[NEW_OS_WINDOW_WITH_WD]) { call_boss(new_os_window_with_wd, "sO", cocoa_pending_actions_data.wd, Py_True); } if (cocoa_pending_actions[NEW_TAB_WITH_WD]) { call_boss(new_tab_with_wd, "sO", cocoa_pending_actions_data.wd, Py_True); } if (cocoa_pending_actions[USER_MENU_ACTION]) { call_boss(user_menu_action, "s", cocoa_pending_actions_data.wd); } free(cocoa_pending_actions_data.wd); cocoa_pending_actions_data.wd = NULL; } for (unsigned cpa = 0; cpa < cocoa_pending_actions_data.open_urls.count; cpa++) { if (cocoa_pending_actions_data.open_urls.items[cpa]) { call_boss(launch_urls, "s", cocoa_pending_actions_data.open_urls.items[cpa]); free(cocoa_pending_actions_data.open_urls.items[cpa]); cocoa_pending_actions_data.open_urls.items[cpa] = NULL; } } cocoa_pending_actions_data.open_urls.count = 0; for (unsigned cpa = 0; cpa < cocoa_pending_actions_data.untracked_notifications.count; cpa++) { if (cocoa_pending_actions_data.untracked_notifications.items[cpa]) { cocoa_report_live_notifications(cocoa_pending_actions_data.untracked_notifications.items[cpa]); free(cocoa_pending_actions_data.untracked_notifications.items[cpa]); cocoa_pending_actions_data.untracked_notifications.items[cpa] = NULL; } } cocoa_pending_actions_data.untracked_notifications.count = 0; memset(cocoa_pending_actions, 0, sizeof(cocoa_pending_actions)); has_cocoa_pending_actions = false; } #endif static void process_global_state(void *data); static void do_state_check(id_type timer_id UNUSED, void *data) { EVDBG("State check timer fired"); process_global_state(data); } static id_type state_check_timer = 0; static void process_global_state(void *data) { EVDBG("Processing global state"); ChildMonitor *self = data; maximum_wait = -1; bool state_check_timer_enabled = false; bool input_read = false; monotonic_t now = monotonic(); if (global_state.has_pending_resizes) { process_pending_resizes(now); input_read = true; } if (parse_input(self)) input_read = true; render(now, input_read); #ifdef __APPLE__ if (has_cocoa_pending_actions) { process_cocoa_pending_actions(); maximum_wait = 0; // ensure loop ticks again so that the actions side effects are performed immediately } #endif report_reaped_pids(); bool should_quit = false; if (global_state.has_pending_closes) should_quit = process_pending_closes(self); if (should_quit) { stop_main_loop(); } else { if (maximum_wait >= 0) { if (maximum_wait == 0) request_tick_callback(); else state_check_timer_enabled = true; } } update_main_loop_timer(state_check_timer, MAX(0, maximum_wait), state_check_timer_enabled); } static PyObject* main_loop(ChildMonitor *self, PyObject *a UNUSED) { #define main_loop_doc "The main thread loop" state_check_timer = add_main_loop_timer(1000, true, do_state_check, self, NULL); run_main_loop(process_global_state, self); #ifdef __APPLE__ cocoa_free_actions_data(); #endif if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } // }}} // I/O thread functions {{{ static void add_children(ChildMonitor *self) { for (; add_queue_count > 0 && self->count < MAX_CHILDREN;) { add_queue_count--; children[self->count] = add_queue[add_queue_count]; add_queue[add_queue_count] = EMPTY_CHILD; children_fds[EXTRA_FDS + self->count].fd = children[self->count].fd; children_fds[EXTRA_FDS + self->count].events = POLLIN; self->count++; } } static void hangup(pid_t pid) { errno = 0; pid_t pgid = getpgid(pid); if (errno == ESRCH) return; if (errno != 0) { perror("Failed to get process group id for child"); return; } if (killpg(pgid, SIGHUP) != 0) { if (errno != ESRCH) perror("Failed to kill child"); } } static void cleanup_child(ssize_t i) { safe_close(children[i].fd, __FILE__, __LINE__); hangup(children[i].pid); } static void remove_children(ChildMonitor *self) { if (self->count > 0) { size_t count = 0; for (ssize_t i = self->count - 1; i >= 0; i--) { if (children[i].needs_removal) { count++; cleanup_child(i); remove_queue[remove_queue_count] = children[i]; remove_queue_count++; children[i] = EMPTY_CHILD; children_fds[EXTRA_FDS + i].fd = -1; size_t num_to_right = self->count - 1 - i; if (num_to_right > 0) { memmove(children + i, children + i + 1, num_to_right * sizeof(Child)); memmove(children_fds + EXTRA_FDS + i, children_fds + EXTRA_FDS + i + 1, num_to_right * sizeof(struct pollfd)); } } } self->count -= count; } } static bool read_bytes(int fd, Screen *screen) { ssize_t len; size_t available_buffer_space; uint8_t *buf = vt_parser_create_write_buffer(screen->vt_parser, &available_buffer_space); if (!available_buffer_space) return true; while(true) { len = read(fd, buf, available_buffer_space); if (len < 0) { if (errno == EINTR || errno == EAGAIN) continue; if (errno != EIO) perror("Call to read() from child fd failed"); vt_parser_commit_write(screen->vt_parser, 0); return false; } break; } vt_parser_commit_write(screen->vt_parser, len); return len != 0; } typedef struct { bool kill_signal, child_died, reload_config; } SignalSet; static bool handle_signal(const siginfo_t *siginfo, void *data) { SignalSet *ss = data; switch(siginfo->si_signo) { case SIGINT: case SIGTERM: case SIGHUP: ss->kill_signal = true; break; case SIGCHLD: ss->child_died = true; break; case SIGUSR1: ss->reload_config = true; break; case SIGUSR2: log_error("Received SIGUSR2: %d\n", siginfo->si_value.sival_int); break; default: break; } return true; } static void mark_child_for_removal(ChildMonitor *self, pid_t pid) { children_mutex(lock); for (size_t i = 0; i < self->count; i++) { if (children[i].pid == pid) { children[i].needs_removal = true; break; } } children_mutex(unlock); } static void mark_monitored_pids(pid_t pid, int status) { children_mutex(lock); for (ssize_t i = monitored_pids_count - 1; i >= 0; i--) { if (pid == monitored_pids[i]) { if (reaped_pids_count < arraysz(reaped_pids)) { reaped_pids[reaped_pids_count].status = status; reaped_pids[reaped_pids_count++].pid = pid; } remove_i_from_array(monitored_pids, (size_t)i, monitored_pids_count); } } children_mutex(unlock); } static void reap_children(ChildMonitor *self, bool enable_close_on_child_death) { int status; pid_t pid; (void)self; while(true) { pid = waitpid(-1, &status, WNOHANG); if (pid == -1) { if (errno != EINTR) break; } else if (pid > 0) { if (enable_close_on_child_death) mark_child_for_removal(self, pid); mark_monitored_pids(pid, status); } else break; } } #ifdef KITTY_PRINT_BYTES_SENT_TO_CHILD static void print_text(const unsigned char *text, ssize_t sz) { for (ssize_t i = 0; i < sz; i++) { unsigned char ch = text[i]; if (32 <= ch && ch < 127) { if (ch == '\\') fprintf(stderr, "%c", ch); fprintf(stderr, "%c", ch); } else fprintf(stderr, "\\x%02x", ch); } } #endif static void write_to_child(int fd, Screen *screen) { size_t written = 0; ssize_t ret = 0; screen_mutex(lock, write); while (written < screen->write_buf_used) { ret = write(fd, screen->write_buf + written, screen->write_buf_used - written); #ifdef KITTY_PRINT_BYTES_SENT_TO_CHILD fprintf(stderr, "Wrote: %zd bytes: ", ret); #endif if (ret > 0) { #ifdef KITTY_PRINT_BYTES_SENT_TO_CHILD print_text(screen->write_buf + written, ret); #endif written += ret; } else if (ret == 0) { // could mean anything, ignore break; } else { if (errno == EINTR) continue; if (errno == EWOULDBLOCK || errno == EAGAIN) break; perror("Call to write() to child fd failed, discarding data."); written = screen->write_buf_used; } #ifdef KITTY_PRINT_BYTES_SENT_TO_CHILD fprintf(stderr, "\n"); #endif } if (written) { screen->write_buf_used -= written; if (screen->write_buf_used) { memmove(screen->write_buf, screen->write_buf + written, screen->write_buf_used); } } screen_mutex(unlock, write); } static void* io_loop(void *data) { // The I/O thread loop size_t i; int ret; bool has_more, data_received, has_pending_wakeups = false; monotonic_t last_main_loop_wakeup_at = -1, now = -1; Screen *screen; ChildMonitor *self = (ChildMonitor*)data; set_thread_name("KittyChildMon"); while (LIKELY(!self->shutting_down)) { children_mutex(lock); remove_children(self); add_children(self); children_mutex(unlock); data_received = false; for (i = 0; i < self->count + EXTRA_FDS; i++) children_fds[i].revents = 0; for (i = 0; i < self->count; i++) { screen = children[i].screen; /* printf("i:%lu id:%lu fd: %d read_buf_sz: %lu write_buf_used: %lu\n", i, children[i].id, children[i].fd, screen->read_buf_sz, screen->write_buf_used); */ children_fds[EXTRA_FDS + i].events = vt_parser_has_space_for_input(screen->vt_parser) ? POLLIN : 0; screen_mutex(lock, write); children_fds[EXTRA_FDS + i].events |= (screen->write_buf_used ? POLLOUT : 0); screen_mutex(unlock, write); } if (has_pending_wakeups) { now = monotonic(); monotonic_t time_delta = OPT(input_delay) - (now - last_main_loop_wakeup_at); if (time_delta >= 0) ret = poll(children_fds, self->count + EXTRA_FDS, monotonic_t_to_ms(time_delta)); else ret = 0; } else { ret = poll(children_fds, self->count + EXTRA_FDS, -1); } if (ret > 0) { if (children_fds[0].revents && POLLIN) drain_fd(children_fds[0].fd); // wakeup if (children_fds[1].revents && POLLIN) { SignalSet ss = {0}; data_received = true; read_signals(children_fds[1].fd, handle_signal, &ss); if (ss.kill_signal || ss.reload_config) { children_mutex(lock); if (ss.kill_signal) kill_signal_received = true; if (ss.reload_config) reload_config_signal_received = true; children_mutex(unlock); } if (ss.child_died) reap_children(self, OPT(close_on_child_death)); } for (i = 0; i < self->count; i++) { if (children_fds[EXTRA_FDS + i].revents & (POLLIN | POLLHUP)) { data_received = true; has_more = read_bytes(children_fds[EXTRA_FDS + i].fd, children[i].screen); if (!has_more) { // child is dead children_mutex(lock); children[i].needs_removal = true; children_mutex(unlock); } } if (children_fds[EXTRA_FDS + i].revents & POLLOUT) { write_to_child(children[i].fd, children[i].screen); } if (children_fds[EXTRA_FDS + i].revents & POLLNVAL) { // fd was closed children_mutex(lock); children[i].needs_removal = true; children_mutex(unlock); log_error("The child %lu had its fd unexpectedly closed", children[i].id); } } #ifdef DEBUG_POLL_EVENTS for (i = 0; i < self->count + EXTRA_FDS; i++) { #define P(w) if (children_fds[i].revents & w) printf("i:%lu %s\n", i, #w); P(POLLIN); P(POLLPRI); P(POLLOUT); P(POLLERR); P(POLLHUP); P(POLLNVAL); #undef P } #endif } else if (ret < 0) { if (errno != EAGAIN && errno != EINTR) { perror("Call to poll() failed"); } } #define WAKEUP { wakeup_main_loop(); last_main_loop_wakeup_at = now; has_pending_wakeups = false; } // we only wakeup the main loop after input_delay as wakeup is an expensive operation // on some platforms, such as cocoa if (data_received) { if ((now = monotonic()) - last_main_loop_wakeup_at > OPT(input_delay)) WAKEUP else has_pending_wakeups = true; } else { if (has_pending_wakeups && (now = monotonic()) - last_main_loop_wakeup_at > OPT(input_delay)) WAKEUP } } #undef WAKEUP children_mutex(lock); for (i = 0; i < self->count; i++) children[i].needs_removal = true; remove_children(self); children_mutex(unlock); return 0; } // }}} // {{{ Talk thread functions typedef struct { id_type id; size_t num_of_unresponded_messages_sent_to_main_thread, fd_array_idx; bool finished_reading; int fd; struct { char *data; size_t capacity, used, command_end; bool finished; } read; struct { char *data; size_t capacity, used; bool failed; } write; bool is_remote_control_peer; } Peer; static id_type peer_id_counter = 0; typedef struct { size_t num_peers, peers_capacity; Peer *peers; LoopData loop_data; } TalkData; static TalkData talk_data = {0}; typedef struct pollfd PollFD; #define PEER_LIMIT 256 #define nuke_socket(s) { shutdown(s, SHUT_RDWR); safe_close(s, __FILE__, __LINE__); } static id_type add_peer(int peer, bool is_remote_control_peer) { id_type ans = 0; if (talk_data.num_peers < PEER_LIMIT) { ensure_space_for(&talk_data, peers, Peer, talk_data.num_peers + 8, peers_capacity, 8, false); Peer *p = talk_data.peers + talk_data.num_peers++; memset(p, 0, sizeof(Peer)); p->fd = peer; p->id = ++peer_id_counter; if (!p->id) p->id = ++peer_id_counter; ans = p->id; p->is_remote_control_peer = is_remote_control_peer; } else { log_error("Too many peers want to talk, ignoring one."); nuke_socket(peer); } return ans; } static bool getpeerid(int fd, uid_t *euid, gid_t *egid) { #ifdef __linux__ struct ucred cr; socklen_t sz = sizeof(cr); if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &sz) != 0) return false; *euid = cr.uid; *egid = cr.gid; #else if (getpeereid(fd, euid, egid) != 0) return false; #endif return true; } static bool accept_peer(int listen_fd, bool shutting_down, bool is_remote_control_peer) { int peer = accept(listen_fd, NULL, NULL); if (UNLIKELY(peer == -1)) { if (errno == EINTR) return true; if (!shutting_down) perror("accept() on talk socket failed!"); return false; } if (verify_peer_uid) { uid_t peer_uid; gid_t peer_gid; if (!getpeerid(peer, &peer_uid, &peer_gid)) { log_error("Denying access to peer because failed to get uid and gid for peer: %d with error: %s", peer, strerror(errno)); shutdown(peer, SHUT_RDWR); safe_close(peer, __FILE__, __LINE__); return true; } if (peer_uid != geteuid()) { log_error("Denying access to peer because its uid (%d) does not match our uid (%d)", peer_uid, geteuid()); shutdown(peer, SHUT_RDWR); safe_close(peer, __FILE__, __LINE__); return true; } } add_peer(peer, is_remote_control_peer); return true; } static void free_peer(Peer *peer) { free(peer->read.data); peer->read.data = NULL; free(peer->write.data); peer->write.data = NULL; if (peer->fd > -1) { nuke_socket(peer->fd); peer->fd = -1; } } #define KITTY_CMD_PREFIX "\x1bP@kitty-cmd{" static void queue_peer_message(ChildMonitor *self, Peer *peer) { talk_mutex(lock); ensure_space_for(self, messages, Message, self->messages_count + 16, messages_capacity, 16, true); Message *m = self->messages + self->messages_count++; memset(m, 0, sizeof(Message)); if (peer->read.used) { m->data = malloc(peer->read.used); if (m->data) { memcpy(m->data, peer->read.data, peer->read.used); m->sz = peer->read.used; } } m->peer_id = peer->id; m->is_remote_control_peer = peer->is_remote_control_peer; peer->num_of_unresponded_messages_sent_to_main_thread++; talk_mutex(unlock); wakeup_main_loop(); } static void notify_on_peer_removal(ChildMonitor *self, const Peer *p) { ensure_space_for(self, messages, Message, self->messages_count + 16, messages_capacity, 16, true); Message *m = self->messages + self->messages_count++; memset(m, 0, sizeof(Message)); m->data = strdup("peer_death"); if (m->data) m->sz = strlen("peer_death"); m->peer_id = p->id; m->is_remote_control_peer = p->id; } static bool has_complete_peer_command(Peer *peer) { peer->read.command_end = 0; if (peer->read.used > sizeof(KITTY_CMD_PREFIX) && memcmp(peer->read.data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) { for (size_t i = sizeof(KITTY_CMD_PREFIX)-1; i < peer->read.used - 1; i++) { if (peer->read.data[i] == 0x1b && peer->read.data[i+1] == '\\') { peer->read.command_end = i + 2; break; } } } return peer->read.command_end ? true : false; } static void dispatch_peer_command(ChildMonitor *self, Peer *peer) { if (peer->read.command_end) { size_t used = peer->read.used; peer->read.used = peer->read.command_end; queue_peer_message(self, peer); peer->read.used = used; if (peer->read.used > peer->read.command_end) { peer->read.used -= peer->read.command_end; memmove(peer->read.data, peer->read.data + peer->read.command_end, peer->read.used); } else peer->read.used = 0; peer->read.command_end = 0; } } static void read_from_peer(ChildMonitor *self, Peer *peer) { #define failed(msg) { log_error("Reading from peer failed: %s", msg); shutdown(peer->fd, SHUT_RD); peer->read.finished = true; return; } if (peer->read.used >= peer->read.capacity) { if (peer->read.capacity >= 64 * 1024) failed("Ignoring too large message from peer"); peer->read.capacity = MAX(8192u, peer->read.capacity * 2); peer->read.data = realloc(peer->read.data, peer->read.capacity); if (!peer->read.data) failed("Out of memory"); } ssize_t n = recv(peer->fd, peer->read.data + peer->read.used, peer->read.capacity - peer->read.used, 0); if (n == 0) { peer->read.finished = true; shutdown(peer->fd, SHUT_RD); while (has_complete_peer_command(peer)) dispatch_peer_command(self, peer); queue_peer_message(self, peer); free(peer->read.data); peer->read.data = NULL; peer->read.used = 0; peer->read.capacity = 0; } else if (n < 0) { if (errno != EINTR) failed(strerror(errno)); } else { peer->read.used += n; while (has_complete_peer_command(peer)) dispatch_peer_command(self, peer); } #undef failed } static void write_to_peer(Peer *peer) { talk_mutex(lock); ssize_t n = send(peer->fd, peer->write.data, peer->write.used, MSG_NOSIGNAL); if (n == 0) { log_error("send() to peer failed to send any data"); peer->write.used = 0; peer->write.failed = true; } else if (n < 0) { if (errno != EINTR) { log_error("write() to peer socket failed with error: %s", strerror(errno)); peer->write.used = 0; peer->write.failed = true; } } else { if ((size_t)n > peer->write.used) memmove(peer->write.data, peer->write.data + n, peer->write.used - n); peer->write.used -= n; } talk_mutex(unlock); } static void wakeup_talk_loop(bool in_signal_handler) { if (talk_thread_started) wakeup_loop(&talk_data.loop_data, in_signal_handler, "talk_loop"); } static bool prune_peers(ChildMonitor *self) { bool pruned = false; for (size_t idx = talk_data.num_peers; idx-- > 0;) { Peer *p = talk_data.peers + idx; if (p->read.finished && !p->num_of_unresponded_messages_sent_to_main_thread && !p->write.used) { notify_on_peer_removal(self, p); free_peer(p); remove_i_from_array(talk_data.peers, idx, talk_data.num_peers); pruned = true; } } return pruned; } static struct { size_t num; struct { int peer_fd, pipe_fd; } fds[16]; } peers_to_inject = {0}; static bool add_peer_to_injection_queue(int peer_fd, int pipe_fd) { bool added = false; talk_mutex(lock); if (peers_to_inject.num < arraysz(peers_to_inject.fds)) { peers_to_inject.fds[peers_to_inject.num].peer_fd = peer_fd; peers_to_inject.fds[peers_to_inject.num].pipe_fd = pipe_fd; peers_to_inject.num++; added = true; } talk_mutex(unlock); return added; } static void simple_write_to_pipe(int fd, void *data, size_t sz) { // write a small amount of data to a pipe handling only EINTR while (true) { ssize_t ret = write(fd, data, sz); if (ret == -1 && errno == EINTR) continue; break; } } static void* talk_loop(void *data) { // The talk thread loop ChildMonitor *self = (ChildMonitor*)data; set_thread_name("KittyPeerMon"); if (!init_loop_data(&talk_data.loop_data, 0)) { log_error("Failed to create wakeup fd for talk thread with error: %s", strerror(errno)); } PollFD fds[PEER_LIMIT + 8] = {{0}}; size_t num_listen_fds = 0, num_peer_fds = 0; #define add_listener(which) \ if (self->which > -1) { \ fds[num_listen_fds].fd = self->which; fds[num_listen_fds++].events = POLLIN; \ } add_listener(talk_fd); add_listener(listen_fd); #undef add_listener fds[num_listen_fds].fd = talk_data.loop_data.wakeup_read_fd; fds[num_listen_fds++].events = POLLIN; while (LIKELY(!self->shutting_down)) { num_peer_fds = 0; bool need_to_wakup_main_loop = false; talk_mutex(lock); if (peers_to_inject.num) { for (size_t i = 0; i < peers_to_inject.num; i++) { id_type added_peer_id = add_peer(peers_to_inject.fds[i].peer_fd, true); simple_write_to_pipe(peers_to_inject.fds[i].pipe_fd, &added_peer_id, sizeof(id_type)); safe_close(peers_to_inject.fds[i].pipe_fd, __FILE__, __LINE__); } peers_to_inject.num = 0; } if (talk_data.num_peers > 0) { if (prune_peers(self)) need_to_wakup_main_loop = true; for (size_t i = 0; i < talk_data.num_peers; i++) { Peer *p = talk_data.peers + i; if (!p->read.finished || p->write.used) { p->fd_array_idx = num_listen_fds + num_peer_fds++; fds[p->fd_array_idx].fd = p->fd; fds[p->fd_array_idx].revents = 0; int flags = 0; if (!p->read.finished) flags |= POLLIN; if (p->write.used) flags |= POLLOUT; fds[p->fd_array_idx].events = flags; } else p->fd_array_idx = 0; } } talk_mutex(unlock); if (need_to_wakup_main_loop) wakeup_main_loop(); for (size_t i = 0; i < num_listen_fds; i++) fds[i].revents = 0; int ret = poll(fds, num_listen_fds + num_peer_fds, -1); if (ret > 0) { for (size_t i = 0; i < num_listen_fds - 1; i++) { if (fds[i].revents & POLLIN) { if (!accept_peer(fds[i].fd, self->shutting_down, fds[i].fd == self->listen_fd)) goto end; } } if (fds[num_listen_fds - 1].revents & POLLIN) { drain_fd(fds[num_listen_fds - 1].fd); // wakeup } for (size_t k = 0; k < talk_data.num_peers; k++) { Peer *p = talk_data.peers + k; if (p->fd_array_idx) { if (fds[p->fd_array_idx].revents & (POLLIN | POLLHUP)) read_from_peer(self, p); if (fds[p->fd_array_idx].revents & POLLOUT) write_to_peer(p); if (fds[p->fd_array_idx].revents & POLLNVAL) { p->read.finished = true; p->write.failed = true; p->write.used = 0; } break; } } } else if (ret < 0) { if (errno != EAGAIN && errno != EINTR) perror("poll() on talk fds failed"); } } end: free_loop_data(&talk_data.loop_data); for (size_t i = 0; i < talk_data.num_peers; i++) free_peer(talk_data.peers + i); free(talk_data.peers); return 0; } static void send_response_to_peer(id_type peer_id, const char *msg, size_t msg_sz) { bool wakeup = false; talk_mutex(lock); for (size_t i = 0; i < talk_data.num_peers; i++) { Peer *peer = talk_data.peers + i; if (peer->id == peer_id) { if (peer->num_of_unresponded_messages_sent_to_main_thread) peer->num_of_unresponded_messages_sent_to_main_thread--; if (!peer->write.failed) { if (peer->write.capacity - peer->write.used < msg_sz) { void *data = realloc(peer->write.data, peer->write.capacity + msg_sz); if (data) { peer->write.data = data; peer->write.capacity += msg_sz; } else fatal("Out of memory"); } if (msg_sz && msg) { memcpy(peer->write.data + peer->write.used, msg, msg_sz); peer->write.used += msg_sz; } } wakeup = true; break; } } talk_mutex(unlock); if (wakeup) wakeup_talk_loop(false); } // }}} // Boilerplate {{{ static PyMethodDef methods[] = { METHOD(add_child, METH_VARARGS) METHOD(inject_peer, METH_O) METHOD(needs_write, METH_VARARGS) METHOD(start, METH_NOARGS) METHOD(wakeup, METH_NOARGS) METHOD(shutdown_monitor, METH_NOARGS) METHOD(main_loop, METH_NOARGS) METHOD(mark_for_close, METH_VARARGS) METHOD(resize_pty, METH_VARARGS) METHODB(handled_signals, METH_NOARGS), {"set_iutf8_winid", (PyCFunction)pyset_iutf8, METH_VARARGS, ""}, {NULL} /* Sentinel */ }; PyTypeObject ChildMonitor_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.ChildMonitor", .tp_basicsize = sizeof(ChildMonitor), .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "ChildMonitor", .tp_methods = methods, .tp_new = new_childmonitor_object, }; static PyObject* safe_pipe(PyObject *self UNUSED, PyObject *args) { int nonblock = 1; if (!PyArg_ParseTuple(args, "|p", &nonblock)) return NULL; int fds[2] = {0}; if (!self_pipe(fds, nonblock)) return PyErr_SetFromErrno(PyExc_OSError); return Py_BuildValue("ii", fds[0], fds[1]); } static PyObject* cocoa_set_menubar_title(PyObject *self UNUSED, PyObject *args UNUSED) { #ifdef __APPLE__ PyObject *title = NULL; if (!PyArg_ParseTuple(args, "U", &title)) return NULL; change_menubar_title(title); #endif Py_RETURN_NONE; } static PyObject* send_data_to_peer(PyObject *self UNUSED, PyObject *args) { char * msg; Py_ssize_t sz; unsigned long long peer_id; if (!PyArg_ParseTuple(args, "Ks#", &peer_id, &msg, &sz)) return NULL; send_response_to_peer(peer_id, msg, sz); Py_RETURN_NONE; } static PyMethodDef module_methods[] = { METHODB(safe_pipe, METH_VARARGS), {"add_timer", (PyCFunction)add_python_timer, METH_VARARGS, ""}, {"remove_timer", (PyCFunction)remove_python_timer, METH_VARARGS, ""}, METHODB(monitor_pid, METH_VARARGS), METHODB(send_data_to_peer, METH_VARARGS), METHODB(cocoa_set_menubar_title, METH_VARARGS), METHODB(mask_kitty_signals_process_wide, METH_NOARGS), {"sigqueue", (PyCFunction)sig_queue, METH_VARARGS, ""}, {NULL} /* Sentinel */ }; bool init_child_monitor(PyObject *module) { if (PyType_Ready(&ChildMonitor_Type) < 0) return false; if (PyModule_AddObject(module, "ChildMonitor", (PyObject *)&ChildMonitor_Type) != 0) return false; Py_INCREF(&ChildMonitor_Type); if (PyModule_AddFunctions(module, module_methods) != 0) return false; #ifdef NO_SIGQUEUE PyModule_AddIntConstant(module, "has_sigqueue", 0); #else PyModule_AddIntConstant(module, "has_sigqueue", 1); #endif return true; } // }}} kitty-0.41.1/kitty/child.c0000664000175000017510000002045514773370543014710 0ustar nileshnilesh/* * child.c * Copyright (C) 2018 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "data-types.h" #include "safe-wrappers.h" #include #include #include #include #include #include #include #include #define EXTRA_ENV_BUFFER_SIZE 64 static char** serialize_string_tuple(PyObject *src, Py_ssize_t extra) { const Py_ssize_t sz = PyTuple_GET_SIZE(src); size_t required_size = sizeof(char*) * (1 + sz + extra); required_size += extra * EXTRA_ENV_BUFFER_SIZE; void *block = calloc(required_size, 1); if (!block) { PyErr_NoMemory(); return NULL; } char **ans = block; for (Py_ssize_t i = 0; i < sz; i++) { PyObject *x = PyTuple_GET_ITEM(src, i); if (!PyUnicode_Check(x)) { free(block); PyErr_SetString(PyExc_TypeError, "string tuple must have only strings"); return NULL; } ans[i] = (char*)PyUnicode_AsUTF8(x); if (!ans[i]) { free(block); return NULL; } } return ans; } static void write_to_stderr(const char *text) { size_t sz = strlen(text); size_t written = 0; while(written < sz) { ssize_t amt = write(2, text + written, sz - written); if (amt == 0) break; if (amt < 0) { if (errno == EAGAIN || errno == EINTR) continue; break; } written += amt; } } #define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); } static void wait_for_terminal_ready(int fd) { char data; while(1) { int ret = read(fd, &data, 1); if (ret == -1 && (errno == EINTR || errno == EAGAIN)) continue; break; } } static PyObject* spawn(PyObject *self UNUSED, PyObject *args) { PyObject *argv_p, *env_p, *handled_signals_p, *pass_fds; int master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd, forward_stdio; const char *kitten_exe; char *cwd, *exe; if (!PyArg_ParseTuple(args, "ssO!O!iiiiiiO!spO!", &exe, &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd, &ready_read_fd, &ready_write_fd, &PyTuple_Type, &handled_signals_p, &kitten_exe, &forward_stdio, &PyTuple_Type, &pass_fds)) return NULL; char name[2048] = {0}; if (ttyname_r(slave, name, sizeof(name) - 1) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } char **argv = serialize_string_tuple(argv_p, 0); if (!argv) return NULL; char **env = serialize_string_tuple(env_p, 1); if (!env) { free(argv); return NULL; } int handled_signals[16] = {0}, num_handled_signals = MIN((int)arraysz(handled_signals), PyTuple_GET_SIZE(handled_signals_p)); for (Py_ssize_t i = 0; i < num_handled_signals; i++) handled_signals[i] = PyLong_AsLong(PyTuple_GET_ITEM(handled_signals_p, i)); #if PY_VERSION_HEX >= 0x03070000 PyOS_BeforeFork(); #endif pid_t pid = fork(); switch(pid) { case 0: { // child #if PY_VERSION_HEX >= 0x03070000 PyOS_AfterFork_Child(); #endif const struct sigaction act = {.sa_handler=SIG_DFL}; #define SA(which) if (sigaction(which, &act, NULL) != 0) exit_on_err("sigaction() in child process failed"); for (int si = 0; si < num_handled_signals; si++) { SA(handled_signals[si]); } // See _Py_RestoreSignals in signalmodule.c for a list of signals python nukes #ifdef SIGPIPE SA(SIGPIPE) #endif #ifdef SIGXFSZ SA(SIGXFSZ); #endif #ifdef SIGXFZ SA(SIGXFZ); #endif #undef SA sigset_t signals; sigemptyset(&signals); if (sigprocmask(SIG_SETMASK, &signals, NULL) != 0) exit_on_err("sigprocmask() in child process failed"); // Use only signal-safe functions (man 7 signal-safety) if (chdir(cwd) != 0) { if (chdir("/") != 0) {} }; // ignore failure to chdir to / if (setsid() == -1) exit_on_err("setsid() in child process failed"); // Establish the controlling terminal (see man 7 credentials) int tfd = safe_open(name, O_RDWR | O_CLOEXEC, 0); if (tfd == -1) exit_on_err("Failed to open controlling terminal"); // On BSD open() does not establish the controlling terminal if (ioctl(tfd, TIOCSCTTY, 0) == -1) exit_on_err("Failed to set controlling terminal with TIOCSCTTY"); safe_close(tfd, __FILE__, __LINE__); fd_set passed_fds; FD_ZERO(&passed_fds); bool has_preserved_fds = false; if (forward_stdio) { int fd = safe_dup(STDOUT_FILENO); if (fd < 0) exit_on_err("dup() failed for forwarded STDOUT"); FD_SET(fd, &passed_fds); size_t s = PyTuple_GET_SIZE(env_p); env[s] = (char*)(env + (s + 2)); snprintf(env[s], EXTRA_ENV_BUFFER_SIZE, "KITTY_STDIO_FORWARDED=%d", fd); fd = safe_dup(STDERR_FILENO); if (fd < 0) exit_on_err("dup() failed for forwarded STDERR"); FD_SET(fd, &passed_fds); has_preserved_fds = true; } for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(pass_fds); i++) { PyObject *pfd = PyTuple_GET_ITEM(pass_fds, i); if (!PyLong_Check(pfd)) exit_on_err("pass_fds must contain only integers"); int fd = PyLong_AsLong(pfd); if (fd > -1 && fd < FD_SETSIZE) { FD_SET(fd, &passed_fds); has_preserved_fds = true; } } // Redirect stdin/stdout/stderr to the pty if (safe_dup2(slave, STDOUT_FILENO) == -1) exit_on_err("dup2() failed for fd number 1"); if (safe_dup2(slave, STDERR_FILENO) == -1) exit_on_err("dup2() failed for fd number 2"); if (stdin_read_fd > -1) { if (safe_dup2(stdin_read_fd, STDIN_FILENO) == -1) exit_on_err("dup2() failed for fd number 0"); safe_close(stdin_read_fd, __FILE__, __LINE__); safe_close(stdin_write_fd, __FILE__, __LINE__); } else { if (safe_dup2(slave, STDIN_FILENO) == -1) exit_on_err("dup2() failed for fd number 0"); } safe_close(slave, __FILE__, __LINE__); safe_close(master, __FILE__, __LINE__); // Wait for READY_SIGNAL which indicates kitty has setup the screen object safe_close(ready_write_fd, __FILE__, __LINE__); wait_for_terminal_ready(ready_read_fd); safe_close(ready_read_fd, __FILE__, __LINE__); // Close any extra fds inherited from parent if (has_preserved_fds) { for (int c = 3; c < 256; c++) { if (!FD_ISSET(c, &passed_fds)) safe_close(c, __FILE__, __LINE__); } } else for (int c = 3; c < 256; c++) { safe_close(c, __FILE__, __LINE__); } extern char **environ; environ = env; execvp(exe, argv); // Report the failure and exec kitten instead, so that we are not left // with a forked but not exec'ed process write_to_stderr("Failed to launch child: "); write_to_stderr(exe); write_to_stderr("\nWith error: "); write_to_stderr(strerror(errno)); write_to_stderr("\n"); execlp(kitten_exe, "kitten", "__hold_till_enter__", NULL); exit(EXIT_FAILURE); break; } case -1: { #if PY_VERSION_HEX >= 0x03070000 int saved_errno = errno; PyOS_AfterFork_Parent(); errno = saved_errno; #endif PyErr_SetFromErrno(PyExc_OSError); break; } default: #if PY_VERSION_HEX >= 0x03070000 PyOS_AfterFork_Parent(); #endif break; } #undef exit_on_err free(argv); free(env); if (PyErr_Occurred()) return NULL; return PyLong_FromLong(pid); } static PyMethodDef module_methods[] = { METHODB(spawn, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_child(PyObject *module) { PyModule_AddIntMacro(module, CLD_KILLED); PyModule_AddIntMacro(module, CLD_STOPPED); PyModule_AddIntMacro(module, CLD_EXITED); PyModule_AddIntMacro(module, CLD_CONTINUED); if (PyModule_AddFunctions(module, module_methods) != 0) return false; return true; } kitty-0.41.1/kitty/child.py0000664000175000017510000004650714773370543015124 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal import os import sys from collections import defaultdict from collections.abc import Generator, Sequence from contextlib import contextmanager, suppress from itertools import count from typing import TYPE_CHECKING, DefaultDict, Iterable, Optional, TypedDict import kitty.fast_data_types as fast_data_types from .constants import handled_signals, is_freebsd, is_macos, kitten_exe, kitty_base_dir, shell_path, terminfo_dir from .types import run_once from .utils import cmdline_for_hold, log_error, which if TYPE_CHECKING: from .window import CwdRequest if is_macos: from kitty.fast_data_types import cmdline_of_process as cmdline_ from kitty.fast_data_types import cwd_of_process as _cwd from kitty.fast_data_types import environ_of_process as _environ_of_process from kitty.fast_data_types import process_group_map as _process_group_map def cwd_of_process(pid: int) -> str: # The underlying code on macos returns a path with symlinks resolved # anyway but we use realpath for extra safety. return os.path.realpath(_cwd(pid)) def process_group_map() -> DefaultDict[int, list[int]]: ans: DefaultDict[int, list[int]] = defaultdict(list) for pid, pgid in _process_group_map(): ans[pgid].append(pid) return ans def cmdline_of_pid(pid: int) -> list[str]: return cmdline_(pid) else: def cmdline_of_pid(pid: int) -> list[str]: with open(f'/proc/{pid}/cmdline', 'rb') as f: return list(filter(None, f.read().decode('utf-8').split('\0'))) if is_freebsd: def cwd_of_process(pid: int) -> str: import subprocess cp = subprocess.run(['pwdx', str(pid)], capture_output=True) if cp.returncode != 0: raise ValueError(f'Failed to find cwd of process with pid: {pid}') ans = cp.stdout.decode('utf-8', 'replace').split()[1] return os.path.realpath(ans) else: def cwd_of_process(pid: int) -> str: # We use realpath instead of readlink to match macOS behavior where # the underlying OS API returns real paths. ans = f'/proc/{pid}/cwd' return os.path.realpath(ans) def _environ_of_process(pid: int) -> str: with open(f'/proc/{pid}/environ', 'rb') as f: return f.read().decode('utf-8') def process_group_map() -> DefaultDict[int, list[int]]: ans: DefaultDict[int, list[int]] = defaultdict(list) for x in os.listdir('/proc'): try: pid = int(x) except Exception: continue try: with open(f'/proc/{x}/stat', 'rb') as f: raw = f.read().decode('utf-8') except OSError: continue try: q = int(raw.split(' ', 5)[4]) except Exception: continue ans[q].append(pid) return ans @run_once def checked_terminfo_dir() -> str | None: return terminfo_dir if os.path.isdir(terminfo_dir) else None def processes_in_group(grp: int) -> list[int]: gmap: DefaultDict[int, list[int]] | None = getattr(process_group_map, 'cached_map', None) if gmap is None: try: gmap = process_group_map() except Exception: gmap = defaultdict(list) return gmap.get(grp, []) @contextmanager def cached_process_data() -> Generator[None, None, None]: try: cm = process_group_map() except Exception: cm = defaultdict(list) setattr(process_group_map, 'cached_map', cm) try: yield finally: delattr(process_group_map, 'cached_map') def session_id(pids: Iterable[int]) -> int: for pid in pids: with suppress(OSError): if (sid := os.getsid(pid)) > -1: return sid return -1 def parse_environ_block(data: str) -> dict[str, str]: """Parse a C environ block of environment variables into a dictionary.""" # The block is usually raw data from the target process. It might contain # trailing garbage and lines that do not look like assignments. ret: dict[str, str] = {} pos = 0 while True: next_pos = data.find("\0", pos) # nul byte at the beginning or double nul byte means finish if next_pos <= pos: break # there might not be an equals sign equal_pos = data.find("=", pos, next_pos) if equal_pos > pos: key = data[pos:equal_pos] value = data[equal_pos + 1:next_pos] ret[key] = value pos = next_pos + 1 return ret def environ_of_process(pid: int) -> dict[str, str]: return parse_environ_block(_environ_of_process(pid)) def process_env() -> dict[str, str]: ans = dict(os.environ) ssl_env_var = getattr(sys, 'kitty_ssl_env_var', None) if ssl_env_var is not None: ans.pop(ssl_env_var, None) ans.pop('XDG_ACTIVATION_TOKEN', None) ans.pop('VTE_VERSION', None) # Used by the stupid VTE shell integration script that is installed system wide, sigh return ans def default_env() -> dict[str, str]: ans: dict[str, str] | None = getattr(default_env, 'env', None) if ans is None: return process_env() return ans def set_default_env(val: dict[str, str] | None = None) -> None: env = process_env().copy() has_lctype = False if val: has_lctype = 'LC_CTYPE' in val env.update(val) setattr(default_env, 'env', env) setattr(default_env, 'lc_ctype_set_by_user', has_lctype) def set_LANG_in_default_env(val: str) -> None: default_env().setdefault('LANG', val) def openpty() -> tuple[int, int]: master, slave = os.openpty() # Note that master and slave are in blocking mode os.set_inheritable(slave, True) os.set_inheritable(master, False) fast_data_types.set_iutf8_fd(master, True) return master, slave @run_once def getpid() -> str: return str(os.getpid()) @run_once def base64_terminfo_data() -> str: return (b'b64:' + fast_data_types.base64_encode(fast_data_types.terminfo_data(), True)).decode('ascii') class ProcessDesc(TypedDict): cwd: str | None pid: int cmdline: Sequence[str] | None child_counter = count() class Child: child_fd: int | None = None pid: int | None = None forked = False def __init__( self, argv: Sequence[str], cwd: str, stdin: bytes | None = None, env: dict[str, str] | None = None, cwd_from: Optional['CwdRequest'] = None, is_clone_launch: str = '', add_listen_on_env_var: bool = True, hold: bool = False, pass_fds: tuple[int, ...] = (), remote_control_fd: int = -1, ): self.is_clone_launch = is_clone_launch self.id = next(child_counter) self.add_listen_on_env_var = add_listen_on_env_var self.argv = list(argv) self.pass_fds = pass_fds self.remote_control_fd = remote_control_fd if cwd_from: try: cwd = cwd_from.modify_argv_for_launch_with_cwd(self.argv, env) or cwd except Exception as err: log_error(f'Failed to read cwd of {cwd_from} with error: {err}') else: cwd = os.path.expandvars(os.path.expanduser(cwd or os.getcwd())) self.cwd = os.path.abspath(cwd) self.stdin = stdin self.env = env or {} self.final_env:dict[str, str] = {} self.is_default_shell = bool(self.argv and self.argv[0] == shell_path) self.should_run_via_run_shell_kitten = is_macos and self.is_default_shell self.hold = hold def get_final_env(self) -> dict[str, str]: from kitty.options.utils import DELETE_ENV_VAR env = default_env().copy() opts = fast_data_types.get_options() boss = fast_data_types.get_boss() if is_macos and env.get('LC_CTYPE') == 'UTF-8' and not getattr(sys, 'kitty_run_data').get( 'lc_ctype_before_python') and not getattr(default_env, 'lc_ctype_set_by_user', False): del env['LC_CTYPE'] env.update(self.env) env['TERM'] = opts.term env['COLORTERM'] = 'truecolor' env['KITTY_PID'] = getpid() env['KITTY_PUBLIC_KEY'] = boss.encryption_public_key if self.remote_control_fd > -1: env['KITTY_LISTEN_ON'] = f'fd:{self.remote_control_fd}' elif self.add_listen_on_env_var and boss.listening_on: env['KITTY_LISTEN_ON'] = boss.listening_on else: env.pop('KITTY_LISTEN_ON', None) env.pop('KITTY_STDIO_FORWARDED', None) if self.cwd: # needed in case cwd is a symlink, in which case shells # can use it to display the current directory name rather # than the resolved path env['PWD'] = self.cwd if opts.terminfo_type == 'path': tdir = checked_terminfo_dir() if tdir: env['TERMINFO'] = tdir elif opts.terminfo_type == 'direct': env['TERMINFO'] = base64_terminfo_data() env['KITTY_INSTALLATION_DIR'] = kitty_base_dir self.unmodified_argv = list(self.argv) if not self.should_run_via_run_shell_kitten and 'disabled' not in opts.shell_integration: from .shell_integration import modify_shell_environ modify_shell_environ(opts, env, self.argv) env = {k: v for k, v in env.items() if v is not DELETE_ENV_VAR} if self.is_clone_launch: env['KITTY_IS_CLONE_LAUNCH'] = self.is_clone_launch self.is_clone_launch = '1' # free memory else: env.pop('KITTY_IS_CLONE_LAUNCH', None) return env def fork(self) -> int | None: if self.forked: return None opts = fast_data_types.get_options() self.forked = True master, slave = openpty() stdin, self.stdin = self.stdin, None ready_read_fd, ready_write_fd = os.pipe() os.set_inheritable(ready_write_fd, False) os.set_inheritable(ready_read_fd, True) if stdin is not None: stdin_read_fd, stdin_write_fd = os.pipe() os.set_inheritable(stdin_write_fd, False) os.set_inheritable(stdin_read_fd, True) else: stdin_read_fd = stdin_write_fd = -1 self.final_env = self.get_final_env() argv = list(self.argv) cwd = self.cwd pass_fds = self.pass_fds if self.remote_control_fd > -1: pass_fds += self.remote_control_fd, if self.should_run_via_run_shell_kitten: # bash will only source ~/.bash_profile if it detects it is a login # shell (see the invocation section of the bash man page), which it # does if argv[0] is prefixed by a hyphen see # https://github.com/kovidgoyal/kitty/issues/247 # it is apparently common to use ~/.bash_profile instead of the # more correct ~/.bashrc on macOS to setup env vars, so if # the default shell is used prefix argv[0] by '-' # # it is arguable whether graphical terminals should start shells # in login mode in general, there are at least a few Linux users # that also make this incorrect assumption, see for example # https://github.com/kovidgoyal/kitty/issues/1870 # xterm, urxvt, konsole and gnome-terminal do not do it in my # testing. import shlex ksi = ' '.join(opts.shell_integration) if ksi == 'invalid': ksi = 'enabled' argv = [kitten_exe(), 'run-shell', '--shell', shlex.join(argv), '--shell-integration', ksi] if is_macos and not pass_fds and not opts.forward_stdio: # In addition for getlogin() to work we need to run the shell # via the /usr/bin/login wrapper, sigh. # And login on macOS looks for .hushlogin in CWD instead of # HOME, bloody idiotic so we cant cwd when running it. # https://github.com/kovidgoyal/kitty/issues/6511 # login closes inherited file descriptors so dont use it when # forward_stdio or pass_fds are used. import pwd user = pwd.getpwuid(os.geteuid()).pw_name if cwd: argv.append('--cwd=' + cwd) cwd = os.path.expanduser('~') argv = ['/usr/bin/login', '-f', '-l', '-p', user] + argv self.final_exe = final_exe = which(argv[0]) or argv[0] self.final_argv0 = argv[0] if self.hold: argv = cmdline_for_hold(argv) final_exe = argv[0] env = tuple(f'{k}={v}' for k, v in self.final_env.items()) pid = fast_data_types.spawn( final_exe, cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd, tuple(handled_signals), kitten_exe(), opts.forward_stdio, pass_fds) os.close(slave) self.pid = pid self.child_fd = master if stdin is not None: os.close(stdin_read_fd) fast_data_types.thread_write(stdin_write_fd, stdin) os.close(ready_read_fd) self.terminal_ready_fd = ready_write_fd if self.child_fd is not None: os.set_blocking(self.child_fd, False) if not is_macos: ppid = getpid() try: fast_data_types.systemd_move_pid_into_new_scope(pid, f'kitty-{ppid}-{self.id}.scope', f'kitty child process: {pid} launched by: {ppid}') except NotImplementedError: pass except OSError as err: log_error("Could not move child process into a systemd scope: " + str(err)) return pid def __del__(self) -> None: fd = getattr(self, 'terminal_ready_fd', -1) if fd > -1: os.close(fd) self.terminal_ready_fd = -1 def mark_terminal_ready(self) -> None: os.close(self.terminal_ready_fd) self.terminal_ready_fd = -1 def cmdline_of_pid(self, pid: int) -> list[str]: try: ans = cmdline_of_pid(pid) except Exception: ans = [] if pid == self.pid and (not ans): ans = list(self.argv) return ans def process_desc(self, pid: int) -> ProcessDesc: ans: ProcessDesc = {'pid': pid, 'cmdline': None, 'cwd': None} with suppress(Exception): ans['cmdline'] = self.cmdline_of_pid(pid) with suppress(Exception): ans['cwd'] = cwd_of_process(pid) or None return ans @property def foreground_processes(self) -> list[ProcessDesc]: if self.child_fd is None: return [] try: pgrp = os.tcgetpgrp(self.child_fd) foreground_processes = processes_in_group(pgrp) if pgrp >= 0 else [] return [self.process_desc(x) for x in foreground_processes] except Exception: return [] @property def background_processes(self) -> list[ProcessDesc]: if self.child_fd is None: return [] try: foreground_process_group_id = os.tcgetpgrp(self.child_fd) if foreground_process_group_id < 0: return [] gmap = process_group_map() sid = session_id(gmap.get(foreground_process_group_id, ())) if sid < 0: return [] ans: list[ProcessDesc] = [] for grp_id, pids in gmap.items(): if grp_id != foreground_process_group_id and session_id(pids) == sid: ans.extend(map(self.process_desc, pids)) return ans except Exception: return [] @property def cmdline(self) -> list[str]: try: assert self.pid is not None return self.cmdline_of_pid(self.pid) or list(self.argv) except Exception: return list(self.argv) @property def foreground_cmdline(self) -> list[str]: try: assert self.pid_for_cwd is not None return self.cmdline_of_pid(self.pid_for_cwd) or self.cmdline except Exception: return self.cmdline @property def environ(self) -> dict[str, str]: try: assert self.pid is not None return environ_of_process(self.pid) or self.final_env.copy() except Exception: return self.final_env.copy() @property def current_cwd(self) -> str | None: with suppress(Exception): assert self.pid is not None return cwd_of_process(self.pid) return None def get_pid_for_cwd(self, oldest: bool = False) -> int | None: with suppress(Exception): assert self.child_fd is not None pgrp = os.tcgetpgrp(self.child_fd) foreground_processes = processes_in_group(pgrp) if pgrp >= 0 else [] if foreground_processes: # there is no easy way that I know of to know which process is the # foreground process in this group from the users perspective, # so we assume the one with the highest PID is as that is most # likely to be the newest process. This situation can happen # for example with a shell script such as: # #!/bin/bash # cd /tmp # vim # With this script , the foreground process group will contain # both the bash instance running the script and vim. return min(foreground_processes) if oldest else max(foreground_processes) return self.pid @property def pid_for_cwd(self) -> int | None: return self.get_pid_for_cwd() def get_foreground_cwd(self, oldest: bool = False) -> str | None: with suppress(Exception): pid = self.get_pid_for_cwd(oldest) if pid is not None: return cwd_of_process(pid) or None return None def get_foreground_exe(self, oldest: bool = False) -> str | None: with suppress(Exception): pid = self.get_pid_for_cwd(oldest) if pid is not None: c = cmdline_of_pid(pid) if c: return c[0] return None @property def foreground_cwd(self) -> str | None: return self.get_foreground_cwd() @property def foreground_environ(self) -> dict[str, str]: pid = self.pid_for_cwd if pid is not None: with suppress(Exception): return environ_of_process(pid) pid = self.pid if pid is not None: with suppress(Exception): return environ_of_process(pid) return {} def send_signal_for_key(self, key_num: bytes) -> bool: import signal import termios if self.child_fd is None: return False t = termios.tcgetattr(self.child_fd) if not t[3] & termios.ISIG: return False cc = t[-1] if key_num == cc[termios.VINTR]: s: signal.Signals = signal.SIGINT elif key_num == cc[termios.VSUSP]: s = signal.SIGTSTP elif key_num == cc[termios.VQUIT]: s = signal.SIGQUIT else: return False pgrp = os.tcgetpgrp(self.child_fd) os.killpg(pgrp, s) return True kitty-0.41.1/kitty/choose_entry.py0000664000175000017510000000153214773370543016527 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2019, Kovid Goyal import re from collections.abc import Generator from typing import Any from .cli_stub import HintsCLIOptions from .typing import MarkType def mark(text: str, args: HintsCLIOptions, Mark: type[MarkType], extra_cli_args: list[str], *a: Any) -> Generator[MarkType, None, None]: idx = 0 found_start_line = False for m in re.finditer(r'(?m)^.+$', text): start, end = m.span() line = text[start:end].replace('\0', '').replace('\n', '') if line == ' ': found_start_line = True continue if line.startswith(': '): yield Mark(idx, start, end, line, {'index': idx}) idx += 1 elif found_start_line: # skip this line incrementing the index idx += 1 kitty-0.41.1/kitty/cleanup.c0000664000175000017510000000110314773370543015241 0ustar nileshnilesh/* * cleanup.c * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "cleanup.h" #include kitty_cleanup_at_exit_func exit_funcs[NUM_CLEANUP_FUNCS] = {0}; void register_at_exit_cleanup_func(AtExitCleanupFunc which, kitty_cleanup_at_exit_func func) { if (which < NUM_CLEANUP_FUNCS) exit_funcs[which] = func; } void run_at_exit_cleanup_functions(void) { for (unsigned i = 0; i < NUM_CLEANUP_FUNCS; i++) { if (exit_funcs[i]) exit_funcs[i](); exit_funcs[i] = NULL; } } kitty-0.41.1/kitty/cleanup.h0000664000175000017510000000120214773370543015246 0ustar nileshnilesh/* * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once typedef void (*kitty_cleanup_at_exit_func)(void); typedef enum { STATE_CLEANUP_FUNC, GLFW_CLEANUP_FUNC, DESKTOP_CLEANUP_FUNC, CORE_TEXT_CLEANUP_FUNC, COCOA_CLEANUP_FUNC, PNG_READER_CLEANUP_FUNC, FONTCONFIG_CLEANUP_FUNC, FREETYPE_CLEANUP_FUNC, SYSTEMD_CLEANUP_FUNC, SHADERS_CLEANUP_FUNC, NUM_CLEANUP_FUNCS } AtExitCleanupFunc; void register_at_exit_cleanup_func(AtExitCleanupFunc which, kitty_cleanup_at_exit_func func); void run_at_exit_cleanup_functions(void); kitty-0.41.1/kitty/cli.py0000664000175000017510000010615514773370543014604 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2017, Kovid Goyal import os import re import sys from collections import deque from collections.abc import Callable, Iterator, Sequence from dataclasses import dataclass from enum import Enum, auto from re import Match from typing import Any, TypeVar, Union, cast from .cli_stub import CLIOptions from .conf.utils import resolve_config from .constants import appname, clear_handled_signals, config_dir, default_pager_for_help, defconf, is_macos, str_version, website_url from .fast_data_types import wcswidth from .options.types import Options as KittyOpts from .types import run_once from .typing import BadLineType, TypedDict from .utils import shlex_split class CompletionType(Enum): file = auto() directory = auto() keyword = auto() special = auto() none = auto() class CompletionRelativeTo(Enum): cwd = auto() config_dir = auto() @dataclass class CompletionSpec: type: CompletionType = CompletionType.none kwds: tuple[str,...] = () extensions: tuple[str,...] = () mime_patterns: tuple[str,...] = () group: str = '' relative_to: CompletionRelativeTo = CompletionRelativeTo.cwd @staticmethod def from_string(raw: str) -> 'CompletionSpec': self = CompletionSpec() for x in shlex_split(raw): ck, vv = x.split(':', 1) if ck == 'type': self.type = getattr(CompletionType, vv) elif ck == 'kwds': self.kwds += tuple(vv.split(',')) elif ck == 'ext': self.extensions += tuple(vv.split(',')) elif ck == 'group': self.group = vv elif ck == 'mime': self.mime_patterns += tuple(vv.split(',')) elif ck == 'relative': if vv == 'conf': self.relative_to = CompletionRelativeTo.config_dir else: raise ValueError(f'Unknown completion relative to value: {vv}') else: raise KeyError(f'Unknown completion property: {ck}') return self def as_go_code(self, go_name: str, sep: str = ': ') -> Iterator[str]: completers = [] if self.kwds: kwds = (f'"{serialize_as_go_string(x)}"' for x in self.kwds) g = (self.group if self.type is CompletionType.keyword else '') or "Keywords" completers.append(f'cli.NamesCompleter("{serialize_as_go_string(g)}", ' + ', '.join(kwds) + ')') relative_to = 'CONFIG' if self.relative_to is CompletionRelativeTo.config_dir else 'CWD' if self.type is CompletionType.file: g = serialize_as_go_string(self.group or 'Files') added = False if self.extensions: added = True pats = (f'"*.{ext}"' for ext in self.extensions) completers.append(f'cli.FnmatchCompleter("{g}", cli.{relative_to}, ' + ', '.join(pats) + ')') if self.mime_patterns: added = True completers.append(f'cli.MimepatCompleter("{g}", cli.{relative_to}, ' + ', '.join(f'"{p}"' for p in self.mime_patterns) + ')') if not added: completers.append(f'cli.FnmatchCompleter("{g}", cli.{relative_to}, "*")') if self.type is CompletionType.directory: g = serialize_as_go_string(self.group or 'Directories') completers.append(f'cli.DirectoryCompleter("{g}", cli.{relative_to})') if self.type is CompletionType.special: completers.append(self.group) if len(completers) > 1: yield f'{go_name}{sep}cli.ChainCompleters(' + ', '.join(completers) + ')' elif completers: yield f'{go_name}{sep}{completers[0]}' class OptionDict(TypedDict): dest: str name: str aliases: frozenset[str] help: str choices: frozenset[str] type: str default: str | None condition: bool completion: CompletionSpec def serialize_as_go_string(x: str) -> str: return x.replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"') go_type_map = { 'bool-set': 'bool', 'bool-reset': 'bool', 'int': 'int', 'float': 'float64', '': 'string', 'list': '[]string', 'choices': 'string', 'str': 'string'} class GoOption: def __init__(self, x: OptionDict) -> None: flags = sorted(x['aliases'], key=len) short = '' self.aliases = [] if len(flags) > 1 and not flags[0].startswith("--"): short = flags[0][1:] self.short, self.long = short, x['name'].replace('_', '-') for f in flags: q = f[2:] if f.startswith('--') else f[1:] self.aliases.append(q) self.type = x['type'] if x['choices']: self.type = 'choices' self.default = x['default'] self.obj_dict = x self.go_type = go_type_map[self.type] if x['dest']: self.go_var_name = ''.join(x.capitalize() for x in x['dest'].replace('-', '_').split('_')) else: self.go_var_name = ''.join(x.capitalize() for x in self.long.replace('-', '_').split('_')) self.help_text = serialize_as_go_string(self.obj_dict['help'].strip()) def struct_declaration(self) -> str: return f'{self.go_var_name} {self.go_type}' def as_option(self, cmd_name: str = 'cmd', depth: int = 0, group: str = '') -> str: add = f'AddToGroup("{serialize_as_go_string(group)}", ' if group else 'Add(' aliases = ' '.join(sorted(self.obj_dict['aliases'])) ans = f'''{cmd_name}.{add}cli.OptionSpec{{ Name: "{serialize_as_go_string(aliases)}", Type: "{self.type}", Dest: "{serialize_as_go_string(self.go_var_name)}", Help: "{self.help_text}", ''' if self.type in ('choice', 'choices'): c = ', '.join(self.sorted_choices) cx = ', '.join(f'"{serialize_as_go_string(x)}"' for x in self.sorted_choices) ans += f'\nChoices: "{serialize_as_go_string(c)}",\n' ans += f'\nCompleter: cli.NamesCompleter("Choices for {self.long}", {cx}),' elif self.obj_dict['completion'].type is not CompletionType.none: ans += ''.join(self.obj_dict['completion'].as_go_code('Completer', ': ')) + ',' if depth > 0: ans += f'\nDepth: {depth},\n' if self.default: ans += f'\nDefault: "{serialize_as_go_string(self.default)}",\n' return ans + '})' @property def sorted_choices(self) -> list[str]: choices = sorted(self.obj_dict['choices']) choices.remove(self.default or '') choices.insert(0, self.default or '') return choices def go_options_for_seq(seq: 'OptionSpecSeq') -> Iterator[GoOption]: for x in seq: if not isinstance(x, str): yield GoOption(x) CONFIG_HELP = '''\ Specify a path to the configuration file(s) to use. All configuration files are merged onto the builtin :file:`{conf_name}.conf`, overriding the builtin values. This option can be specified multiple times to read multiple configuration files in sequence, which are merged. Use the special value :code:`NONE` to not load any config file. If this option is not specified, config files are searched for in the order: :file:`$XDG_CONFIG_HOME/{appname}/{conf_name}.conf`, :file:`~/.config/{appname}/{conf_name}.conf`,{macos_confpath} :file:`$XDG_CONFIG_DIRS/{appname}/{conf_name}.conf`. The first one that exists is used as the config file. If the environment variable :envvar:`KITTY_CONFIG_DIRECTORY` is specified, that directory is always used and the above searching does not happen. If :file:`/etc/xdg/{appname}/{conf_name}.conf` exists, it is merged before (i.e. with lower priority) than any user config files. It can be used to specify system-wide defaults for all users. You can use either :code:`-` or :file:`/dev/stdin` to read the config from STDIN. '''.replace( '{macos_confpath}', (' :file:`~/Library/Preferences/{appname}/{conf_name}.conf`,' if is_macos else ''), 1 ) def surround(x: str, start: int, end: int) -> str: if sys.stdout.isatty(): x = f'\033[{start}m{x}\033[{end}m' return x role_map: dict[str, Callable[[str], str]] = {} def role(func: Callable[[str], str]) -> Callable[[str], str]: role_map[func.__name__] = func return func @role def emph(x: str) -> str: return surround(x, 91, 39) @role def cyan(x: str) -> str: return surround(x, 96, 39) @role def green(x: str) -> str: return surround(x, 32, 39) @role def blue(x: str) -> str: return surround(x, 34, 39) @role def yellow(x: str) -> str: return surround(x, 93, 39) @role def italic(x: str) -> str: return surround(x, 3, 23) @role def bold(x: str) -> str: return surround(x, 1, 22) @role def title(x: str) -> str: return blue(bold(x)) @role def opt(text: str) -> str: return bold(text) @role def option(x: str) -> str: idx = x.rfind('--') if idx < 0: idx = x.find('-') if idx > -1: x = x[idx:] return bold(x.rstrip('>')) @role def code(x: str) -> str: return cyan(x) def text_and_target(x: str) -> tuple[str, str]: parts = x.split('<', 1) return parts[0].strip(), parts[-1].rstrip('>') @role def term(x: str) -> str: return ref_hyperlink(x, 'term-') @role def kbd(x: str) -> str: return x @role def env(x: str) -> str: return ref_hyperlink(x, 'envvar-') role_map['envvar'] = role_map['env'] @run_once def hostname() -> str: from .utils import get_hostname return get_hostname(fallback='localhost') def hyperlink_for_url(url: str, text: str) -> str: if sys.stdout.isatty(): return f'\x1b]8;;{url}\x1b\\\x1b[4:3;58:5:4m{text}\x1b[4:0;59m\x1b]8;;\x1b\\' return text def hyperlink_for_path(path: str, text: str) -> str: path = os.path.abspath(path).replace(os.sep, "/") if os.path.isdir(path): path += path.rstrip("/") + "/" return hyperlink_for_url(f'file://{hostname()}{path}', text) @role def file(x: str) -> str: if x == 'kitty.conf': x = hyperlink_for_path(os.path.join(config_dir, x), x) return italic(x) @role def doc(x: str) -> str: t, q = text_and_target(x) if t == q: from .conf.types import ref_map m = ref_map()['doc'] q = q.strip('/') if q in m: x = f'{m[q]} <{t}>' return ref_hyperlink(x, 'doc-') def ref_hyperlink(x: str, prefix: str = '') -> str: t, q = text_and_target(x) url = f'kitty+doc://{hostname()}/#ref={prefix}{q}' t = re.sub(r':([a-z]+):`([^`]+)`', r'\2', t) return hyperlink_for_url(url, t) @role def ref(x: str) -> str: return ref_hyperlink(x) @role def ac(x: str) -> str: return ref_hyperlink(x, 'action-') @role def iss(x: str) -> str: return ref_hyperlink(x, 'issues-') @role def pull(x: str) -> str: return ref_hyperlink(x, 'pull-') @role def disc(x: str) -> str: return ref_hyperlink(x, 'discussions-') OptionSpecSeq = list[Union[str, OptionDict]] def parse_option_spec(spec: str | None = None) -> tuple[OptionSpecSeq, OptionSpecSeq]: if spec is None: spec = options_spec() NORMAL, METADATA, HELP = 'NORMAL', 'METADATA', 'HELP' state = NORMAL lines = spec.splitlines() prev_line = '' prev_indent = 0 seq: OptionSpecSeq = [] disabled: OptionSpecSeq = [] mpat = re.compile('([a-z]+)=(.+)') current_cmd: OptionDict = { 'dest': '', 'aliases': frozenset(), 'help': '', 'choices': frozenset(), 'type': '', 'condition': False, 'default': None, 'completion': CompletionSpec(), 'name': '' } empty_cmd = current_cmd def indent_of_line(x: str) -> int: return len(x) - len(x.lstrip()) for line in lines: line = line.rstrip() if state is NORMAL: if not line: continue if line.startswith('# '): seq.append(line[2:]) continue if line.startswith('--'): parts = line.split(' ') defdest = parts[0][2:].replace('-', '_') current_cmd = { 'dest': defdest, 'aliases': frozenset(parts), 'help': '', 'choices': frozenset(), 'type': '', 'name': defdest, 'default': None, 'condition': True, 'completion': CompletionSpec(), } state = METADATA continue raise ValueError(f'Invalid option spec, unexpected line: {line}') elif state is METADATA: m = mpat.match(line) if m is None: state = HELP current_cmd['help'] += line else: k, v = m.group(1), m.group(2) if k == 'choices': vals = tuple(x.strip() for x in v.split(',')) current_cmd['choices'] = frozenset(vals) if current_cmd['default'] is None: current_cmd['default'] = vals[0] else: if k == 'default': current_cmd['default'] = v elif k == 'type': if v == 'choice': v = 'choices' current_cmd['type'] = v elif k == 'dest': current_cmd['dest'] = v elif k == 'condition': current_cmd['condition'] = bool(eval(v)) elif k == 'completion': current_cmd['completion'] = CompletionSpec.from_string(v) elif state is HELP: if line: current_indent = indent_of_line(line) if current_indent > 1: if prev_indent == 0: current_cmd['help'] += '\n' else: line = line.strip() prev_indent = current_indent spc = '' if current_cmd['help'].endswith('\n') else ' ' current_cmd['help'] += spc + line else: prev_indent = 0 if prev_line: current_cmd['help'] += '\n' if current_cmd['help'].endswith('::') else '\n\n' else: state = NORMAL (seq if current_cmd.get('condition', True) else disabled).append(current_cmd) current_cmd = empty_cmd prev_line = line if current_cmd is not empty_cmd: (seq if current_cmd.get('condition', True) else disabled).append(current_cmd) return seq, disabled def prettify(text: str) -> str: def identity(x: str) -> str: return x def sub(m: 'Match[str]') -> str: role, text = m.group(1, 2) return role_map.get(role, identity)(text) text = re.sub(r':([a-z]+):`([^`]+)`', sub, text) return text def prettify_rst(text: str) -> str: return re.sub(r':([a-z]+):`([^`]+)`(=[^\s.]+)', r':\1:`\2`:code:`\3`', text) def version(add_rev: bool = False) -> str: rev = '' from . import fast_data_types if add_rev: if getattr(fast_data_types, 'KITTY_VCS_REV', ''): rev = f' ({fast_data_types.KITTY_VCS_REV[:10]})' return '{} {}{} created by {}'.format(italic(appname), green(str_version), rev, title('Kovid Goyal')) def wrap(text: str, limit: int = 80) -> Iterator[str]: if not text.strip(): yield '' return in_escape = 0 current_line: list[str] = [] escapes: list[str] = [] current_word: list[str] = [] current_line_length = 0 def print_word(ch: str = '') -> Iterator[str]: nonlocal current_word, current_line, escapes, current_line_length cw = ''.join(current_word) w = wcswidth(cw) if current_line_length + w > limit: yield ''.join(current_line) current_line = [] current_line_length = 0 cw = cw.strip() current_word = [cw] if escapes: current_line.append(''.join(escapes)) escapes = [] if current_word: current_line.append(cw) current_line_length += w current_word = [] if ch: current_word.append(ch) for i, ch in enumerate(text): if in_escape > 0: if in_escape == 1 and ch in '[]': in_escape = 2 if ch == '[' else 3 if (in_escape == 2 and ch == 'm') or (in_escape == 3 and ch == '\\' and text[i-1] == '\x1b'): in_escape = 0 escapes.append(ch) continue if ch == '\x1b': in_escape = 1 if current_word: yield from print_word() escapes.append(ch) continue if current_word and ch.isspace() and ch != '\xa0': yield from print_word(ch) else: current_word.append(ch) yield from print_word() if current_line: yield ''.join(current_line) def get_defaults_from_seq(seq: OptionSpecSeq) -> dict[str, Any]: ans: dict[str, Any] = {} for opt in seq: if not isinstance(opt, str): ans[opt['dest']] = defval_for_opt(opt) return ans default_msg = ('''\ Run the :italic:`{appname}` terminal emulator. You can also specify the :italic:`program` to run inside :italic:`{appname}` as normal arguments following the :italic:`options`. For example: {appname} --hold sh -c "echo hello, world" For comprehensive documentation for kitty, please see: {url}''').format( appname=appname, url=website_url()) class PrintHelpForSeq: allow_pager = True def __call__(self, seq: OptionSpecSeq, usage: str | None, message: str | None, appname: str) -> None: from kitty.utils import screen_size_function screen_size = screen_size_function() try: linesz = min(screen_size().cols, 76) except OSError: linesz = 76 blocks: list[str] = [] a = blocks.append def wa(text: str, indent: int = 0, leading_indent: int | None = None) -> None: if leading_indent is None: leading_indent = indent j = '\n' + (' ' * indent) lines: list[str] = [] for ln in text.splitlines(): lines.extend(wrap(ln, limit=linesz - indent)) a((' ' * leading_indent) + j.join(lines)) usage = '[program-to-run ...]' if usage is None else usage optstring = '[options] ' if seq else '' a('{}: {} {}{}'.format(title('Usage'), bold(yellow(appname)), optstring, usage)) a('') message = message or default_msg # replace rst literal code block syntax message = message.replace('::\n\n', ':\n\n') wa(prettify(message)) a('') if seq: a('{}:'.format(title('Options'))) for opt in seq: if isinstance(opt, str): a(f'{title(opt)}:') continue help_text = opt['help'] if help_text == '!': continue # hidden option a(' ' + ', '.join(map(green, sorted(opt['aliases'], reverse=True)))) defval = opt.get('default') if not opt.get('type', '').startswith('bool-'): if defval: dt = f'=[{italic(defval)}]' blocks[-1] += dt if opt.get('help'): t = help_text.replace('%default', str(defval)).strip() # replace rst literal code block syntax t = t.replace('::\n\n', ':\n\n') t = t.replace('#placeholder_for_formatting#', '') wa(prettify(t), indent=4) if opt.get('choices'): wa('Choices: {}'.format(', '.join(opt['choices'])), indent=4) a('') text = '\n'.join(blocks) + '\n\n' + version() if print_help_for_seq.allow_pager and sys.stdout.isatty(): import subprocess try: p = subprocess.Popen(default_pager_for_help, stdin=subprocess.PIPE, preexec_fn=clear_handled_signals) except FileNotFoundError: print(text) else: try: p.communicate(text.encode('utf-8')) except KeyboardInterrupt: raise SystemExit(1) raise SystemExit(p.wait()) else: print(text) print_help_for_seq = PrintHelpForSeq() def seq_as_rst( seq: OptionSpecSeq, usage: str | None, message: str | None, appname: str | None, heading_char: str = '-' ) -> str: import textwrap blocks: list[str] = [] a = blocks.append usage = '[program-to-run ...]' if usage is None else usage optstring = '[options] ' if seq else '' a('.. highlight:: sh') a('.. code-block:: sh') a('') a(f' {appname} {optstring}{usage}') a('') message = message or default_msg a(prettify_rst(message)) a('') if seq: a('Options') a(heading_char * 30) for opt in seq: if isinstance(opt, str): a(opt) a('~' * (len(opt) + 10)) continue help_text = opt['help'] if help_text == '!': continue # hidden option defn = '.. option:: ' if not opt.get('type', '').startswith('bool-'): val_name = ' <{}>'.format(opt['dest'].upper()) else: val_name = '' a(defn + ', '.join(o + val_name for o in sorted(opt['aliases']))) if opt.get('help'): defval = opt.get('default') t = help_text.replace('%default', str(defval)).strip() t = t.replace('#placeholder_for_formatting#', '') a('') a(textwrap.indent(prettify_rst(t), ' ' * 4)) if defval is not None: a(textwrap.indent(f'Default: :code:`{defval}`', ' ' * 4)) if opt.get('choices'): a(textwrap.indent('Choices: {}'.format(', '.join(f':code:`{c}`' for c in sorted(opt['choices']))), ' ' * 4)) a('') text = '\n'.join(blocks) return text def as_type_stub(seq: OptionSpecSeq, disabled: OptionSpecSeq, class_name: str, extra_fields: Sequence[str] = ()) -> str: from itertools import chain ans: list[str] = [f'class {class_name}:'] for opt in chain(seq, disabled): if isinstance(opt, str): continue name = opt['dest'] otype = opt['type'] or 'str' if otype in ('str', 'int', 'float'): t = otype if t == 'str' and defval_for_opt(opt) is None: t = 'typing.Optional[str]' elif otype == 'list': t = 'typing.Sequence[str]' elif otype in ('choice', 'choices'): if opt['choices']: t = 'typing.Literal[{}]'.format(','.join(f'{x!r}' for x in opt['choices'])) else: t = 'str' elif otype.startswith('bool-'): t = 'bool' else: raise ValueError(f'Unknown CLI option type: {otype}') ans.append(f' {name}: {t}') for x in extra_fields: ans.append(f' {x}') return '\n'.join(ans) + '\n\n\n' def defval_for_opt(opt: OptionDict) -> Any: dv: Any = opt.get('default') typ = opt.get('type', '') if typ.startswith('bool-'): if dv is None: dv = False if typ == 'bool-set' else True else: dv = dv.lower() in ('true', 'yes', 'y') elif typ == 'list': dv = [] elif typ in ('int', 'float'): dv = (int if typ == 'int' else float)(dv or 0) return dv class Options: def __init__(self, seq: OptionSpecSeq, usage: str | None, message: str | None, appname: str | None): self.alias_map = {} self.seq = seq self.names_map: dict[str, OptionDict] = {} self.values_map: dict[str, Any] = {} self.usage, self.message, self.appname = usage, message, appname for opt in seq: if isinstance(opt, str): continue for alias in opt['aliases']: self.alias_map[alias] = opt name = opt['dest'] self.names_map[name] = opt self.values_map[name] = defval_for_opt(opt) def opt_for_alias(self, alias: str) -> OptionDict: opt = self.alias_map.get(alias) if opt is None: raise SystemExit(f'Unknown option: {emph(alias)}') return opt def needs_arg(self, alias: str) -> bool: if alias in ('-h', '--help'): print_help_for_seq(self.seq, self.usage, self.message, self.appname or appname) raise SystemExit(0) opt = self.opt_for_alias(alias) if opt['dest'] == 'version': print(version()) raise SystemExit(0) typ = opt.get('type', '') return not typ.startswith('bool-') def process_arg(self, alias: str, val: Any = None) -> None: opt = self.opt_for_alias(alias) typ = opt.get('type', '') name = opt['dest'] nmap = {'float': float, 'int': int} if typ == 'bool-set': self.values_map[name] = True elif typ == 'bool-reset': self.values_map[name] = False elif typ == 'list': self.values_map.setdefault(name, []) self.values_map[name].append(val) elif typ == 'choices': choices = opt['choices'] if val not in choices: raise SystemExit('{} is not a valid value for the {} option. Valid values are: {}'.format( val, emph(alias), ', '.join(choices))) self.values_map[name] = val elif typ in nmap: f = nmap[typ] try: self.values_map[name] = f(val) except Exception: raise SystemExit('{} is not a valid value for the {} option, a number is required.'.format( val, emph(alias))) else: self.values_map[name] = val def parse_cmdline(oc: Options, disabled: OptionSpecSeq, ans: Any, args: list[str] | None = None) -> list[str]: NORMAL, EXPECTING_ARG = 'NORMAL', 'EXPECTING_ARG' state = NORMAL dargs = deque(sys.argv[1:] if args is None else args) leftover_args: list[str] = [] current_option = None while dargs: arg = dargs.popleft() if state is NORMAL: if arg.startswith('-'): if arg == '--': leftover_args = list(dargs) break parts = arg.split('=', 1) needs_arg = oc.needs_arg(parts[0]) if not needs_arg: if len(parts) != 1: raise SystemExit(f'The {emph(parts[0])} option does not accept arguments') oc.process_arg(parts[0]) continue if len(parts) == 1: current_option = parts[0] state = EXPECTING_ARG continue oc.process_arg(parts[0], parts[1]) else: leftover_args = [arg] + list(dargs) break elif current_option is not None: oc.process_arg(current_option, arg) current_option, state = None, NORMAL if state is EXPECTING_ARG: raise SystemExit(f'An argument is required for the option: {emph(arg)}') for key, val in oc.values_map.items(): setattr(ans, key, val) for opt in disabled: if not isinstance(opt, str): setattr(ans, opt['dest'], defval_for_opt(opt)) return leftover_args def options_spec() -> str: if not hasattr(options_spec, 'ans'): OPTIONS = ''' --class --app-id dest=cls default={appname} condition=not is_macos Set the class part of the :italic:`WM_CLASS` window property. On Wayland, it sets the app id. --name condition=not is_macos Set the name part of the :italic:`WM_CLASS` property. Defaults to using the value from :option:`{appname} --class`. --title -T Set the OS window title. This will override any title set by the program running inside kitty, permanently fixing the OS window's title. So only use this if you are running a program that does not set titles. --config -c type=list completion=type:file ext:conf group:"Config files" kwds:none,NONE {config_help} --override -o type=list completion=type:special group:complete_kitty_override Override individual configuration options, can be specified multiple times. Syntax: :italic:`name=value`. For example: :option:`{appname} -o` font_size=20 --directory --working-directory -d default=. completion=type:directory Change to the specified directory when launching. --detach type=bool-set condition=not is_macos Detach from the controlling terminal, if any. Not available on macOS. On macOS use :code:`open -a kitty.app -n` instead. --session completion=type:file ext:session relative:conf group:"Session files" Path to a file containing the startup :italic:`session` (tabs, windows, layout, programs). Use - to read from STDIN. See :ref:`sessions` for details and an example. Environment variables in the file name are expanded, relative paths are resolved relative to the kitty configuration directory. The special value :code:`none` means no session will be used, even if the :opt:`startup_session` option has been specified in kitty.conf. Note that using this option means the command line arguments to kitty specifying a program to run are ignored. --hold type=bool-set Remain open, at a shell prompt, after child process exits. Note that this only affects the first window. You can quit by either using the close window shortcut or running the exit command. --single-instance -1 type=bool-set If specified only a single instance of :italic:`{appname}` will run. New invocations will instead create a new top-level window in the existing :italic:`{appname}` instance. This allows :italic:`{appname}` to share a single sprite cache on the GPU and also reduces startup time. You can also have separate groups of :italic:`{appname}` instances by using the :option:`{appname} --instance-group` option. --instance-group Used in combination with the :option:`{appname} --single-instance` option. All :italic:`{appname}` invocations with the same :option:`{appname} --instance-group` will result in new windows being created in the first :italic:`{appname}` instance within that group. --wait-for-single-instance-window-close type=bool-set Normally, when using :option:`{appname} --single-instance`, :italic:`{appname}` will open a new window in an existing instance and quit immediately. With this option, it will not quit till the newly opened window is closed. Note that if no previous instance is found, then :italic:`{appname}` will wait anyway, regardless of this option. --listen-on completion=type:special group:complete_kitty_listen_on Listen on the specified socket address for control messages. For example, :option:`{appname} --listen-on`=unix:/tmp/mykitty or :option:`{appname} --listen-on`=tcp:localhost:12345. On Linux systems, you can also use abstract UNIX sockets, not associated with a file, like this: :option:`{appname} --listen-on`=unix:@mykitty. Environment variables are expanded and relative paths are resolved with respect to the temporary directory. To control kitty, you can send commands to it with :italic:`kitten @` using the :option:`kitten @ --to` option to specify this address. Note that if you run :italic:`kitten @` within a kitty window, there is no need to specify the :option:`kitten @ --to` option as it will automatically read from the environment. Note that this will be ignored unless :opt:`allow_remote_control` is set to either: :code:`yes`, :code:`socket` or :code:`socket-only`. This can also be specified in :file:`kitty.conf`. --start-as type=choices default=normal choices=normal,fullscreen,maximized,minimized Control how the initial kitty window is created. # Debugging options --version -v type=bool-set The current {appname} version. --dump-commands type=bool-set Output commands received from child process to STDOUT. --replay-commands Replay previously dumped commands. Specify the path to a dump file previously created by :option:`{appname} --dump-commands`. You can open a new kitty window to replay the commands with:: {appname} sh -c "{appname} --replay-commands /path/to/dump/file; read" --dump-bytes Path to file in which to store the raw bytes received from the child process. --debug-rendering --debug-gl type=bool-set Debug rendering commands. This will cause all OpenGL calls to check for errors instead of ignoring them. Also prints out miscellaneous debug information. Useful when debugging rendering problems. --debug-input --debug-keyboard dest=debug_keyboard type=bool-set Print out key and mouse events as they are received. --debug-font-fallback type=bool-set Print out information about the selection of fallback fonts for characters not present in the main font. --watcher completion=type:file ext:py relative:conf group:"Watcher files" This option is deprecated in favor of the :opt:`watcher` option in :file:`{conf_name}.conf` and should not be used. --execute -e type=bool-set ! ''' setattr(options_spec, 'ans', OPTIONS.format( appname=appname, conf_name=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname), )) ans: str = getattr(options_spec, 'ans') return ans def options_for_completion() -> OptionSpecSeq: raw = '--help -h\ntype=bool-set\nShow help for {appname} command line options\n\n{raw}'.format( appname=appname, raw=options_spec()) return parse_option_spec(raw)[0] def option_spec_as_rst( ospec: Callable[[], str] = options_spec, usage: str | None = None, message: str | None = None, appname: str | None = None, heading_char: str = '-' ) -> str: options = parse_option_spec(ospec()) seq, disabled = options oc = Options(seq, usage, message, appname) return seq_as_rst(oc.seq, oc.usage, oc.message, oc.appname, heading_char=heading_char) T = TypeVar('T') def parse_args( args: list[str] | None = None, ospec: Callable[[], str] = options_spec, usage: str | None = None, message: str | None = None, appname: str | None = None, result_class: type[T] | None = None, ) -> tuple[T, list[str]]: options = parse_option_spec(ospec()) seq, disabled = options oc = Options(seq, usage, message, appname) if result_class is not None: ans = result_class() else: ans = cast(T, CLIOptions()) return ans, parse_cmdline(oc, disabled, ans, args=args) SYSTEM_CONF = f'/etc/xdg/{appname}/{appname}.conf' def default_config_paths(conf_paths: Sequence[str]) -> tuple[str, ...]: return tuple(resolve_config(SYSTEM_CONF, defconf, conf_paths)) @run_once def override_pat() -> 're.Pattern[str]': return re.compile(r'^([a-zA-Z0-9_]+)[ \t]*=') def parse_override(x: str) -> str: # Does not cover the case where `name =` when `=` is the value. return override_pat().sub(r'\1 ', x.lstrip()) def create_opts(args: CLIOptions, accumulate_bad_lines: list[BadLineType] | None = None) -> KittyOpts: from .config import load_config config = default_config_paths(args.config) overrides = map(parse_override, args.override or ()) opts = load_config(*config, overrides=overrides, accumulate_bad_lines=accumulate_bad_lines) return opts def create_default_opts() -> KittyOpts: from .config import load_config config = default_config_paths(()) opts = load_config(*config) return opts kitty-0.41.1/kitty/cli_stub.py0000664000175000017510000000531214773370543015632 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Sequence class CLIOptions: pass LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions BroadcastCLIOptions = ShowKeyCLIOptions = CLIOptions ThemesCLIOptions = TransferCLIOptions = LoadConfigRCOptions = ActionRCOptions = CLIOptions def generate_stub() -> None: from .cli import as_type_stub, parse_option_spec from .conf.utils import save_type_stub text = 'import typing\n\n\n' def do(otext: str | None = None, cls: str = 'CLIOptions', extra_fields: Sequence[str] = ()) -> None: nonlocal text text += as_type_stub(*parse_option_spec(otext), class_name=cls, extra_fields=extra_fields) do(extra_fields=('args: typing.List[str]',)) from .launch import options_spec do(options_spec(), 'LaunchCLIOptions') from .remote_control import global_options_spec do(global_options_spec(), 'RCOptions') from kittens.ask.main import option_text do(option_text(), 'AskCLIOptions') from kittens.remote_file.main import option_text do(option_text(), 'RemoteFileCLIOptions') from kittens.clipboard.main import OPTIONS do(OPTIONS(), 'ClipboardCLIOptions') from kittens.show_key.main import OPTIONS do(OPTIONS(), 'ShowKeyCLIOptions') from kittens.diff.main import OPTIONS do(OPTIONS(), 'DiffCLIOptions') from kittens.hints.main import OPTIONS do(OPTIONS(), 'HintsCLIOptions') from kittens.broadcast.main import OPTIONS do(OPTIONS(), 'BroadcastCLIOptions') from kittens.icat.main import OPTIONS as OS do(OS, 'IcatCLIOptions') from kittens.panel.main import OPTIONS do(OPTIONS(), 'PanelCLIOptions') from kittens.resize_window.main import OPTIONS do(OPTIONS(), 'ResizeCLIOptions') from kittens.unicode_input.main import OPTIONS do(OPTIONS(), 'UnicodeCLIOptions') from kittens.themes.main import OPTIONS do(OPTIONS(), 'ThemesCLIOptions') from kittens.transfer.main import option_text do(option_text(), 'TransferCLIOptions') from kitty.rc.base import all_command_names, command_for_name for cmd_name in all_command_names(): cmd = command_for_name(cmd_name) if cmd.options_spec: do(cmd.options_spec, f'{cmd.__class__.__name__}RCOptions') save_type_stub(text, __file__) if __name__ == '__main__': import subprocess subprocess.Popen([ 'kitty', '+runpy', 'from kitty.cli_stub import generate_stub; generate_stub()' ]) kitty-0.41.1/kitty/client.py0000664000175000017510000001563514773370543015315 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal # Replay the log from --dump-commands. To use first run # kitty --dump-commands > file.txt # then run # kitty --replay-commands file.txt # will replay the commands and pause at the end waiting for user to press enter import json import sys from contextlib import suppress from typing import Any from .fast_data_types import TEXT_SIZE_CODE CSI = '\x1b[' OSC = '\x1b]' def write(x: str) -> None: sys.stdout.write(x) sys.stdout.flush() def set_title(*args: Any) -> None: pass def set_icon(*args: Any) -> None: pass def screen_bell() -> None: pass def screen_normal_keypad_mode() -> None: write('\x1b>') def screen_alternate_keypad_mode() -> None: write('\x1b=') def screen_cursor_position(y: int, x: int) -> None: write(f'{CSI}{y};{x}H') def screen_cursor_forward(amt: int) -> None: write(f'{CSI}{amt}C') def screen_save_cursor() -> None: write('\x1b7') def screen_restore_cursor() -> None: write('\x1b8') def screen_cursor_back1(amt: int) -> None: write(f'{CSI}{amt}D') def screen_save_modes() -> None: write(f'{CSI}?s') def screen_restore_modes() -> None: write(f'{CSI}?r') def screen_designate_charset(which: int, to: int) -> None: w = '()'[int(which)] t = chr(int(to)) write(f'\x1b{w}{t}') def select_graphic_rendition(payload: str) -> None: write(f'{CSI}{payload}m') def deccara(*a: int) -> None: write(f'{CSI}{";".join(map(str, a))}$r') def screen_cursor_to_column(c: int) -> None: write(f'{CSI}{c}G') def screen_cursor_to_line(ln: int) -> None: write(f'{CSI}{ln}d') def screen_set_mode(x: int, private: bool) -> None: write(f'{CSI}{"?" if private else ""}{x}h') def screen_save_mode(x: int, private: bool) -> None: write(f'{CSI}{"?" if private else ""}{x}s') def screen_reset_mode(x: int, private: bool) -> None: write(f'{CSI}{"?" if private else ""}{x}l') def screen_restore_mode(x: int, private: bool) -> None: write(f'{CSI}{"?" if private else ""}{x}r') def screen_set_margins(t: int, b: int) -> None: write(f'{CSI}{t};{b}r') def screen_indexn(n: int) -> None: write(f'{CSI}{n}S') def screen_delete_characters(count: int) -> None: write(f'{CSI}{count}P') def screen_push_colors(which: int) -> None: write(f'{CSI}{which}#P') def screen_pop_colors(which: int) -> None: write(f'{CSI}{which}#Q') def screen_report_colors() -> None: write(f'{CSI}#R') def screen_repeat_character(num: int) -> None: write(f'{CSI}{num}b') def screen_insert_characters(count: int) -> None: write(f'{CSI}{count}@') def screen_scroll(count: int) -> None: write(f'{CSI}{count}S') def screen_erase_in_display(how: int, private: bool) -> None: write(f'{CSI}{"?" if private else ""}{how}J') def screen_erase_in_line(how: int, private: bool) -> None: write(f'{CSI}{"?" if private else ""}{how}K') def screen_erase_characters(num: int) -> None: write(f'{CSI}{num}X') def screen_delete_lines(num: int) -> None: write(f'{CSI}{num}M') def screen_cursor_up2(count: int) -> None: write(f'{CSI}{count}A') def screen_cursor_down(count: int) -> None: write(f'{CSI}{count}B') def screen_cursor_down1(count: int) -> None: write(f'{CSI}{count}E') def screen_report_key_encoding_flags() -> None: write(f'{CSI}?u') def screen_set_key_encoding_flags(flags: int, how: int) -> None: write(f'{CSI}={flags};{how}u') def screen_push_key_encoding_flags(flags: int) -> None: write(f'{CSI}>{flags}u') def screen_pop_key_encoding_flags(flags: int) -> None: write(f'{CSI}<{flags}u') def screen_carriage_return() -> None: write('\r') def screen_linefeed() -> None: write('\n') def screen_tab() -> None: write('\t') def screen_backspace() -> None: write('\x08') def screen_set_cursor(mode: int, secondary: int) -> None: write(f'{CSI}{secondary} q') def screen_insert_lines(num: int) -> None: write(f'{CSI}{num}L') def draw(*a: str) -> None: write(' '.join(a)) def screen_manipulate_title_stack(op: int, which: int) -> None: write(f'{CSI}{op};{which}t') def report_device_attributes(mode: int, char: int) -> None: x = CSI if char: x += chr(char) if mode: x += str(mode) write(f'{x}c') def report_device_status(x: int, private: bool) -> None: write(f'{CSI}{"?" if private else ""}{x}n') def screen_decsace(mode: int) -> None: write(f'{CSI}{mode}*x') def screen_set_8bit_controls(mode: int) -> None: write(f'\x1b {"G" if mode else "F"}') def write_osc(code: int, string: str = '') -> None: if string: write(f'{OSC}{code};{string}\x07') else: write(f'{OSC}{code}\x07') set_color_table_color = process_cwd_notification = write_osc clipboard_control_pending: str = '' def set_dynamic_color(payload: str) -> None: code, data = payload.partition(' ')[::2] write_osc(int(code), data) def shell_prompt_marking(payload: str) -> None: write_osc(133, payload) def clipboard_control(payload: str) -> None: global clipboard_control_pending code, data = payload.split(';', 1) if code == '-52': if clipboard_control_pending: clipboard_control_pending += data.lstrip(';') else: clipboard_control_pending = payload return if clipboard_control_pending: clipboard_control_pending += data.lstrip(';') payload = clipboard_control_pending clipboard_control_pending = '' write(f'{OSC}{payload}\x07') def multicell_command(payload: str) -> None: c = json.loads(payload) text = c.pop('', '') m = '' if (w := c.pop('width', None)) is not None and w > 0: m += f'w={w}:' if (s := c.pop('scale', None)) is not None and s > 1: m += f's={s}:' if (n := c.pop('subscale_n', None)) is not None and n > 0: m += f'n={n}:' if (d := c.pop('subscale_d', None)) is not None and d > 0: m += f'd={d}:' if (v := c.pop('vertical_align', None)) is not None and v > 0: m += f'v={v}:' if c: raise Exception('Unknown keys in multicell_command: ' + ', '.join(c)) write(f'{OSC}{TEXT_SIZE_CODE};{m.rstrip(":")};{text}\a') def replay(raw: str) -> None: specials = frozenset({ 'draw', 'set_title', 'set_icon', 'set_dynamic_color', 'set_color_table_color', 'select_graphic_rendition', 'process_cwd_notification', 'clipboard_control', 'shell_prompt_marking', 'multicell_command', }) for line in raw.splitlines(): if line.strip() and not line.startswith('#'): cmd, rest = line.partition(' ')[::2] if cmd in specials: globals()[cmd](rest) else: r = map(int, rest.split()) if rest else () globals()[cmd](*r) def main(path: str) -> None: with open(path) as f: raw = f.read() replay(raw) with suppress(EOFError, KeyboardInterrupt): input() kitty-0.41.1/kitty/clipboard.py0000664000175000017510000005013214773370543015765 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2022, Kovid Goyal import io import os from collections.abc import Callable, Mapping from enum import Enum, IntEnum from gettext import gettext as _ from tempfile import TemporaryFile from typing import IO, NamedTuple, Union from .conf.utils import uniq from .constants import supports_primary_selection from .fast_data_types import ( ESC_OSC, GLFW_CLIPBOARD, GLFW_PRIMARY_SELECTION, StreamingBase64Decoder, find_in_memoryview, get_boss, get_clipboard_mime, get_options, set_clipboard_data_types, ) from .utils import log_error READ_RESPONSE_CHUNK_SIZE = 4096 class Tempfile: def __init__(self, max_size: int) -> None: self.file: io.BytesIO | IO[bytes] = io.BytesIO() self.max_size = max_size def rollover_if_needed(self, sz: int) -> None: if isinstance(self.file, io.BytesIO) and self.file.tell() + sz > self.max_size: before = self.file.getvalue() self.file = TemporaryFile() self.file.write(before) def write(self, data: bytes) -> None: self.rollover_if_needed(len(data)) self.file.write(data) def tell(self) -> int: return self.file.tell() def seek(self, pos: int) -> None: self.file.seek(pos, os.SEEK_SET) def read(self, offset: int, size: int) -> bytes: self.file.seek(offset) return self.file.read(size) def create_chunker(self, offset: int, size: int) -> Callable[[], Callable[[], bytes]]: def chunk_creator() -> Callable[[], bytes]: pos = offset limit = offset + size def chunker() -> bytes: nonlocal pos, limit if pos >= limit: return b'' ans = self.read(pos, min(io.DEFAULT_BUFFER_SIZE, limit - pos)) pos = self.file.tell() return ans return chunker return chunk_creator DataType = Union[bytes, Callable[[], Callable[[], bytes]]] TARGETS_MIME = '.' class ClipboardType(IntEnum): clipboard = GLFW_CLIPBOARD primary_selection = GLFW_PRIMARY_SELECTION unknown = -311 @staticmethod def from_osc52_where_field(where: str) -> 'ClipboardType': if where in ('c', 's'): return ClipboardType.clipboard if where == 'p': return ClipboardType.primary_selection return ClipboardType.unknown class Clipboard: def __init__(self, clipboard_type: ClipboardType = ClipboardType.clipboard) -> None: self.data: dict[str, DataType] = {} self.clipboard_type = clipboard_type self.enabled = self.clipboard_type is ClipboardType.clipboard or supports_primary_selection def set_text(self, x: str | bytes) -> None: if isinstance(x, str): x = x.encode('utf-8') self.set_mime({'text/plain': x}) def set_mime(self, data: Mapping[str, DataType]) -> None: if self.enabled and isinstance(data, dict): self.data = data set_clipboard_data_types(self.clipboard_type, tuple(self.data)) def get_text(self) -> str: parts: list[bytes] = [] self.get_mime("text/plain", parts.append) return b''.join(parts).decode('utf-8', 'replace') def get_mime(self, mime: str, output: Callable[[bytes], None]) -> None: if self.enabled: try: get_clipboard_mime(self.clipboard_type, mime, output) except RuntimeError as err: if str(err) != 'is_self_offer': raise data = self.data.get(mime, b'') if isinstance(data, bytes): output(data) else: chunker = data() q = b' ' while q: q = chunker() output(q) def get_mime_data(self, mime: str) -> bytes: ans: list[bytes] = [] self.get_mime(mime, ans.append) return b''.join(ans) def get_available_mime_types_for_paste(self) -> tuple[str, ...]: if self.enabled: parts: list[bytes] = [] try: get_clipboard_mime(self.clipboard_type, None, parts.append) except RuntimeError as err: if str(err) != 'is_self_offer': raise return tuple(self.data) return tuple(x.decode('utf-8', 'replace') for x in uniq(parts)) return () def __call__(self, mime: str) -> Callable[[], bytes]: data = self.data.get(mime, b'') if isinstance(data, str): data = data.encode('utf-8') # type: ignore if isinstance(data, bytes): def chunker() -> bytes: nonlocal data assert isinstance(data, bytes) ans = data data = b'' return ans return chunker return data() def set_clipboard_string(x: str | bytes) -> None: get_boss().clipboard.set_text(x) def get_clipboard_string() -> str: return get_boss().clipboard.get_text() def set_primary_selection(x: str | bytes) -> None: get_boss().primary_selection.set_text(x) def get_primary_selection() -> str: return get_boss().primary_selection.get_text() def develop() -> tuple[Clipboard, Clipboard]: from .constants import detect_if_wayland_ok, is_macos from .fast_data_types import set_boss from .main import init_glfw_module glfw_module = 'cocoa' if is_macos else ('wayland' if detect_if_wayland_ok() else 'x11') class Boss: clipboard = Clipboard() primary_selection = Clipboard(ClipboardType.primary_selection) init_glfw_module(glfw_module) set_boss(Boss()) # type: ignore return Boss.clipboard, Boss.primary_selection class ProtocolType(Enum): osc_52 = 52 osc_5522 = 5522 def encode_mime(x: str) -> str: import base64 return base64.standard_b64encode(x.encode('utf-8')).decode('ascii') def decode_metadata_value(k: str, x: str) -> str: if k == 'mime': import base64 x = base64.standard_b64decode(x).decode('utf-8') return x class ReadRequest(NamedTuple): is_primary_selection: bool = False mime_types: tuple[str, ...] = ('text/plain',) id: str = '' protocol_type: ProtocolType = ProtocolType.osc_52 def encode_response(self, status: str = 'DATA', mime: str = '', payload: bytes = b'') -> bytes: ans = f'{self.protocol_type.value};type=read:status={status}' if status == 'OK' and self.is_primary_selection: ans += ':loc=primary' if self.id: ans += f':id={self.id}' if mime: ans += f':mime={encode_mime(mime)}' a = ans.encode('ascii') if payload: import base64 a += b';' + base64.standard_b64encode(payload) return a def encode_osc52(loc: str, response: str) -> str: from base64 import standard_b64encode return '52;{};{}'.format( loc, standard_b64encode(response.encode('utf-8')).decode('ascii')) class MimePos(NamedTuple): start: int size: int class WriteRequest: def __init__( self, is_primary_selection: bool = False, protocol_type: ProtocolType = ProtocolType.osc_52, id: str = '', rollover_size: int = 16 * 1024 * 1024, max_size: int = -1, ) -> None: self.decoder = StreamingBase64Decoder() self.id = id self.is_primary_selection = is_primary_selection self.protocol_type = protocol_type self.max_size_exceeded = False self.tempfile = Tempfile(max_size=rollover_size) self.mime_map: dict[str, MimePos] = {} self.currently_writing_mime = '' self.max_size = (get_options().clipboard_max_size * 1024 * 1024) if max_size < 0 else max_size self.aliases: dict[str, str] = {} self.committed = False def encode_response(self, status: str = 'OK') -> bytes: ans = f'{self.protocol_type.value};type=write:status={status}' if self.id: ans += f':id={self.id}' a = ans.encode('ascii') return a def commit(self) -> None: if self.committed: return self.committed = True cp = get_boss().primary_selection if self.is_primary_selection else get_boss().clipboard if cp.enabled: for alias, src in self.aliases.items(): pos = self.mime_map.get(src) if pos is not None: self.mime_map[alias] = pos x = {mime: self.tempfile.create_chunker(pos.start, pos.size) for mime, pos in self.mime_map.items()} cp.set_mime(x) def add_base64_data(self, data: str | bytes, mime: str = 'text/plain') -> None: if isinstance(data, str): data = data.encode('ascii') if self.currently_writing_mime and self.currently_writing_mime != mime: self.flush_base64_data() if not self.currently_writing_mime: self.mime_map[mime] = MimePos(self.tempfile.tell(), -1) self.currently_writing_mime = mime self.write_base64_data(data) def flush_base64_data(self) -> None: if self.currently_writing_mime: if self.decoder.needs_more_data(): log_error('Received incomplete data for clipboard') self.decoder.reset() start = self.mime_map[self.currently_writing_mime][0] self.mime_map[self.currently_writing_mime] = MimePos(start, self.tempfile.tell() - start) self.currently_writing_mime = '' def write_base64_data(self, b: bytes) -> None: if not self.max_size_exceeded: try: decoded = self.decoder.decode(b) except ValueError as e: log_error(f'Clipboard write request has invalid data, ignoring this chunk of data. Error: {e}') self.decoder.reset() decoded = b'' if decoded: self.tempfile.write(decoded) if self.max_size > 0 and self.tempfile.tell() > (self.max_size * 1024 * 1024): log_error(f'Clipboard write request has more data than allowed by clipboard_max_size ({self.max_size}), truncating') self.max_size_exceeded = True def data_for(self, mime: str = 'text/plain', offset: int = 0, size: int = -1) -> bytes: start, full_size = self.mime_map[mime] if size == -1: size = full_size return self.tempfile.read(start+offset, size) class ClipboardRequestManager: def __init__(self, window_id: int) -> None: self.window_id = window_id self.currently_asking_permission_for: ReadRequest | None = None self.in_flight_write_request: WriteRequest | None = None self.osc52_in_flight_write_requests: dict[ClipboardType, WriteRequest] = {} def parse_osc_5522(self, data: memoryview) -> None: import base64 from .notifications import sanitize_id idx = find_in_memoryview(data, ord(b';')) if idx > -1: metadata = str(data[:idx], "utf-8", "replace") epayload = data[idx+1:] else: metadata = str(data, "utf-8", "replace") epayload = data[len(data):] m: dict[str, str] = {} for record in metadata.split(':'): try: k, v = record.split('=', 1) except Exception: log_error('Malformed OSC 5522: metadata is not key=value pairs') return m[k] = decode_metadata_value(k, v) typ = m.get('type', '') if typ == 'read': payload = base64.standard_b64decode(epayload) rr = ReadRequest( is_primary_selection=m.get('loc', '') == 'primary', mime_types=tuple(payload.decode('utf-8').split()), protocol_type=ProtocolType.osc_5522, id=sanitize_id(m.get('id', '')) ) self.handle_read_request(rr) elif typ == 'write': self.in_flight_write_request = WriteRequest( is_primary_selection=m.get('loc', '') == 'primary', protocol_type=ProtocolType.osc_5522, id=sanitize_id(m.get('id', '')) ) self.handle_write_request(self.in_flight_write_request) elif typ == 'walias': wr = self.in_flight_write_request mime = m.get('mime', '') if mime and wr is not None: aliases = base64.standard_b64decode(epayload).decode('utf-8').split() for alias in aliases: wr.aliases[alias] = mime elif typ == 'wdata': wr = self.in_flight_write_request w = get_boss().window_id_map.get(self.window_id) if wr is None: return mime = m.get('mime', '') if mime: try: wr.add_base64_data(epayload, mime) except OSError: if w is not None: w.screen.send_escape_code_to_child(ESC_OSC, wr.encode_response(status='EIO')) self.in_flight_write_request = None raise except Exception: if w is not None: w.screen.send_escape_code_to_child(ESC_OSC, wr.encode_response(status='EINVAL')) self.in_flight_write_request = None raise else: wr.flush_base64_data() wr.commit() self.in_flight_write_request = None if w is not None: w.screen.send_escape_code_to_child(ESC_OSC, wr.encode_response(status='DONE')) def parse_osc_52(self, data: memoryview, is_partial: bool = False) -> None: idx = find_in_memoryview(data, ord(b';')) if idx > -1: where = str(data[:idx], "utf-8", 'replace') data = data[idx+1:] else: where = str(data, "utf-8", 'replace') data = data[len(data):] destinations = {ClipboardType.from_osc52_where_field(where) for where in where or 's0'} destinations.discard(ClipboardType.unknown) if len(data) == 1 and data.tobytes() == b'?': for d in destinations: rr = ReadRequest(is_primary_selection=d is ClipboardType.primary_selection) self.handle_read_request(rr) else: for d in destinations: wr = self.osc52_in_flight_write_requests.get(d) if wr is None: wr = self.osc52_in_flight_write_requests[d] = WriteRequest(d is ClipboardType.primary_selection) wr.add_base64_data(data) if is_partial: return self.osc52_in_flight_write_requests.pop(d, None) self.handle_write_request(wr) def handle_write_request(self, wr: WriteRequest) -> None: wr.flush_base64_data() q = 'write-primary' if wr.is_primary_selection else 'write-clipboard' allowed = q in get_options().clipboard_control self.fulfill_write_request(wr, allowed) def fulfill_write_request(self, wr: WriteRequest, allowed: bool = True) -> None: if wr.protocol_type is ProtocolType.osc_52: self.fulfill_legacy_write_request(wr, allowed) return w = get_boss().window_id_map.get(self.window_id) cp = get_boss().primary_selection if wr.is_primary_selection else get_boss().clipboard if not allowed or not cp.enabled: self.in_flight_write_request = None if w is not None: w.screen.send_escape_code_to_child(ESC_OSC, wr.encode_response(status='EPERM' if not allowed else 'ENOSYS')) def fulfill_legacy_write_request(self, wr: WriteRequest, allowed: bool = True) -> None: cp = get_boss().primary_selection if wr.is_primary_selection else get_boss().clipboard w = get_boss().window_id_map.get(self.window_id) if w is not None and cp.enabled and allowed: wr.commit() def handle_read_request(self, rr: ReadRequest) -> None: cc = get_options().clipboard_control if rr.is_primary_selection: ask_for_permission = 'read-primary-ask' in cc allowed = 'read-primary' in cc else: ask_for_permission = 'read-clipboard-ask' in cc allowed = 'read-clipboard' in cc if ask_for_permission: self.ask_to_read_clipboard(rr) else: self.fulfill_read_request(rr, allowed=allowed) def fulfill_read_request(self, rr: ReadRequest, allowed: bool = True) -> None: if rr.protocol_type is ProtocolType.osc_52: return self.fulfill_legacy_read_request(rr, allowed) w = get_boss().window_id_map.get(self.window_id) if w is None: return cp = get_boss().primary_selection if rr.is_primary_selection else get_boss().clipboard if not cp.enabled: w.screen.send_escape_code_to_child(ESC_OSC, rr.encode_response(status='ENOSYS')) return if not allowed: w.screen.send_escape_code_to_child(ESC_OSC, rr.encode_response(status='EPERM')) return w.screen.send_escape_code_to_child(ESC_OSC, rr.encode_response(status='OK')) current_mime = '' def write_chunks(data: bytes) -> None: assert w is not None mv = memoryview(data) while mv: w.screen.send_escape_code_to_child(ESC_OSC, rr.encode_response(payload=mv[:READ_RESPONSE_CHUNK_SIZE], mime=current_mime)) mv = mv[READ_RESPONSE_CHUNK_SIZE:] for mime in rr.mime_types: current_mime = mime if mime == TARGETS_MIME: payload = ' '.join(cp.get_available_mime_types_for_paste()).encode('utf-8') if payload: payload += b'\n' w.screen.send_escape_code_to_child(ESC_OSC, rr.encode_response(payload=payload, mime=current_mime)) continue try: cp.get_mime(mime, write_chunks) except Exception as e: log_error(f'Failed to read requested mime type {mime} with error: {e}') w.screen.send_escape_code_to_child(ESC_OSC, rr.encode_response(status='DONE')) def reject_read_request(self, rr: ReadRequest) -> None: if rr.protocol_type is ProtocolType.osc_52: return self.fulfill_legacy_read_request(rr, False) w = get_boss().window_id_map.get(self.window_id) if w is not None: w.screen.send_escape_code_to_child(ESC_OSC, rr.encode_response(status='EPERM')) def fulfill_legacy_read_request(self, rr: ReadRequest, allowed: bool = True) -> None: cp = get_boss().primary_selection if rr.is_primary_selection else get_boss().clipboard w = get_boss().window_id_map.get(self.window_id) if w is not None: text = '' if cp.enabled and allowed: text = cp.get_text() loc = 'p' if rr.is_primary_selection else 'c' w.screen.send_escape_code_to_child(ESC_OSC, encode_osc52(loc, text)) def ask_to_read_clipboard(self, rr: ReadRequest) -> None: if rr.mime_types == (TARGETS_MIME,): self.fulfill_read_request(rr, True) return if self.currently_asking_permission_for is not None: self.reject_read_request(rr) return w = get_boss().window_id_map.get(self.window_id) if w is not None: self.currently_asking_permission_for = rr get_boss().confirm(_( 'A program running in this window wants to read from the system clipboard.' ' Allow it to do so, once?'), self.handle_clipboard_confirmation, window=w, ) def handle_clipboard_confirmation(self, confirmed: bool) -> None: rr = self.currently_asking_permission_for self.currently_asking_permission_for = None if rr is not None: self.fulfill_read_request(rr, confirmed) def close(self) -> None: if self.in_flight_write_request is not None: self.in_flight_write_request = None self.osc52_in_flight_write_requests.clear() kitty-0.41.1/kitty/cocoa_window.h0000664000175000017510000000361014773370543016277 0ustar nileshnilesh/* * cocoa_window.h * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" typedef enum { PREFERENCES_WINDOW, NEW_OS_WINDOW, NEW_OS_WINDOW_WITH_WD, NEW_TAB_WITH_WD, CLOSE_OS_WINDOW, CLOSE_TAB, NEW_TAB, NEXT_TAB, PREVIOUS_TAB, DETACH_TAB, LAUNCH_URLS, NEW_WINDOW, CLOSE_WINDOW, RESET_TERMINAL, CLEAR_TERMINAL_AND_SCROLLBACK, CLEAR_SCROLLBACK, CLEAR_SCREEN, RELOAD_CONFIG, TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY, TOGGLE_FULLSCREEN, OPEN_KITTY_WEBSITE, HIDE, HIDE_OTHERS, MINIMIZE, QUIT, USER_MENU_ACTION, COCOA_NOTIFICATION_UNTRACKED, NUM_COCOA_PENDING_ACTIONS } CocoaPendingAction; void cocoa_focus_window(void *w); long cocoa_window_number(void *w); void cocoa_create_global_menu(void); void cocoa_recreate_global_menu(void); void cocoa_system_beep(const char*); void cocoa_set_activation_policy(bool); bool cocoa_alt_option_key_pressed(unsigned long); void cocoa_toggle_secure_keyboard_entry(void); void cocoa_hide(void); void cocoa_clear_global_shortcuts(void); void cocoa_hide_others(void); void cocoa_minimize(void *w); void cocoa_set_uncaught_exception_handler(void); void cocoa_update_menu_bar_title(PyObject*); size_t cocoa_get_workspace_ids(void *w, size_t *workspace_ids, size_t array_sz); monotonic_t cocoa_cursor_blink_interval(void); bool cocoa_render_line_of_text(const char *text, const color_type fg, const color_type bg, uint8_t *rgba_output, const size_t width, const size_t height); extern uint8_t* render_single_ascii_char_as_mask(const char ch, size_t *result_width, size_t *result_height); void get_cocoa_key_equivalent(uint32_t, int, char *key, size_t key_sz, int*); void set_cocoa_pending_action(CocoaPendingAction action, const char*); void cocoa_report_live_notifications(const char* ident); kitty-0.41.1/kitty/cocoa_window.m0000664000175000017510000014764014773370543016320 0ustar nileshnilesh/* * cocoa_window.m * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "state.h" #include "cleanup.h" #include "cocoa_window.h" #include #include #include #include #import #include // Needed for _NSGetProgname #include #include static inline void cleanup_cfrelease(void *__p) { CFTypeRef *tp = (CFTypeRef *)__p; CFTypeRef cf = *tp; if (cf) { CFRelease(cf); } } #define RAII_CoreFoundation(type, name, initializer) __attribute__((cleanup(cleanup_cfrelease))) type name = initializer #if (MAC_OS_X_VERSION_MAX_ALLOWED < 101300) #define NSControlStateValueOn NSOnState #define NSControlStateValueOff NSOffState #define NSControlStateValueMixed NSMixedState #endif #if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200) #define NSWindowStyleMaskResizable NSResizableWindowMask #define NSEventModifierFlagOption NSAlternateKeyMask #define NSEventModifierFlagCommand NSCommandKeyMask #define NSEventModifierFlagControl NSControlKeyMask #endif #if (MAC_OS_X_VERSION_MAX_ALLOWED < 110000) #define UNNotificationPresentationOptionList (1 << 3) #define UNNotificationPresentationOptionBanner (1 << 4) #endif typedef int CGSConnectionID; typedef int CGSWindowID; typedef int CGSWorkspaceID; typedef enum _CGSSpaceSelector { kCGSSpaceCurrent = 5, kCGSSpaceAll = 7 } CGSSpaceSelector; extern CGSConnectionID _CGSDefaultConnection(void); CFArrayRef CGSCopySpacesForWindows(CGSConnectionID Connection, CGSSpaceSelector Type, CFArrayRef Windows); static NSMenuItem* title_menu = NULL; static NSString* find_app_name(void) { size_t i; NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary]; // Keys to search for as potential application names NSString* name_keys[] = { @"CFBundleDisplayName", @"CFBundleName", @"CFBundleExecutable", }; for (i = 0; i < sizeof(name_keys) / sizeof(name_keys[0]); i++) { id name = infoDictionary[name_keys[i]]; if (name && [name isKindOfClass:[NSString class]] && ![name isEqualToString:@""]) { return name; } } char** progname = _NSGetProgname(); if (progname && *progname) return @(*progname); // Really shouldn't get here return @"kitty"; } #define debug_key(...) if (OPT(debug_keyboard)) { fprintf(stderr, __VA_ARGS__); fflush(stderr); } // SecureKeyboardEntryController {{{ @interface SecureKeyboardEntryController : NSObject @property (nonatomic, readonly) BOOL isDesired; @property (nonatomic, readonly, getter=isEnabled) BOOL enabled; + (instancetype)sharedInstance; - (void)toggle; - (void)update; @end @implementation SecureKeyboardEntryController { int _count; BOOL _desired; } + (instancetype)sharedInstance { static id instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; } - (instancetype)init { self = [super init]; if (self) { _desired = false; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidResignActive:) name:NSApplicationDidResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:NSApplicationDidBecomeActiveNotification object:nil]; if ([NSApp isActive]) { [self update]; } } return self; } #pragma mark - API - (void)toggle { // Set _desired to the opposite of the current state. _desired = !_desired; debug_key("SecureKeyboardEntry: toggle called. Setting desired to %d ", _desired); // Try to set the system's state of secure input to the desired state. [self update]; } - (BOOL)isEnabled { return !!IsSecureEventInputEnabled(); } - (BOOL)isDesired { return _desired; } #pragma mark - Notifications - (void)applicationDidResignActive:(NSNotification *)notification { (void)notification; if (_count > 0) { debug_key("SecureKeyboardEntry: Application resigning active."); [self update]; } } - (void)applicationDidBecomeActive:(NSNotification *)notification { (void)notification; if (self.isDesired) { debug_key("SecureKeyboardEntry: Application became active."); [self update]; } } #pragma mark - Private - (BOOL)allowed { return [NSApp isActive]; } - (void)update { debug_key("Update secure keyboard entry. desired=%d active=%d\n", (int)self.isDesired, (int)[NSApp isActive]); const BOOL secure = self.isDesired && [self allowed]; if (secure && _count > 0) { debug_key("Want to turn on secure input but it's already on\n"); return; } if (!secure && _count == 0) { debug_key("Want to turn off secure input but it's already off\n"); return; } debug_key("Before: IsSecureEventInputEnabled returns %d ", (int)self.isEnabled); if (secure) { OSErr err = EnableSecureEventInput(); debug_key("EnableSecureEventInput err=%d ", (int)err); if (err) { debug_key("EnableSecureEventInput failed with error %d ", (int)err); } else { _count += 1; } } else { OSErr err = DisableSecureEventInput(); debug_key("DisableSecureEventInput err=%d ", (int)err); if (err) { debug_key("DisableSecureEventInput failed with error %d ", (int)err); } else { _count -= 1; } } debug_key("After: IsSecureEventInputEnabled returns %d\n", (int)self.isEnabled); } @end // }}} @interface UserMenuItem : NSMenuItem @property (nonatomic) size_t action_index; @end @implementation UserMenuItem { } @end @interface GlobalMenuTarget : NSObject + (GlobalMenuTarget *) shared_instance; @end #define PENDING(selector, which) - (void)selector:(id)sender { (void)sender; set_cocoa_pending_action(which, NULL); } @implementation GlobalMenuTarget - (void)user_menu_action:(id)sender { UserMenuItem *m = sender; if (m.action_index < OPT(global_menu).count && OPT(global_menu.entries)) { set_cocoa_pending_action(USER_MENU_ACTION, OPT(global_menu).entries[m.action_index].definition); } } PENDING(edit_config_file, PREFERENCES_WINDOW) PENDING(new_os_window, NEW_OS_WINDOW) PENDING(detach_tab, DETACH_TAB) PENDING(close_os_window, CLOSE_OS_WINDOW) PENDING(close_tab, CLOSE_TAB) PENDING(new_tab, NEW_TAB) PENDING(next_tab, NEXT_TAB) PENDING(previous_tab, PREVIOUS_TAB) PENDING(new_window, NEW_WINDOW) PENDING(close_window, CLOSE_WINDOW) PENDING(reset_terminal, RESET_TERMINAL) PENDING(clear_terminal_and_scrollback, CLEAR_TERMINAL_AND_SCROLLBACK) PENDING(clear_scrollback, CLEAR_SCROLLBACK) PENDING(clear_screen, CLEAR_SCREEN) PENDING(reload_config, RELOAD_CONFIG) PENDING(toggle_macos_secure_keyboard_entry, TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY) PENDING(toggle_fullscreen, TOGGLE_FULLSCREEN) PENDING(open_kitty_website, OPEN_KITTY_WEBSITE) PENDING(hide_macos_app, HIDE) PENDING(hide_macos_other_apps, HIDE_OTHERS) PENDING(minimize_macos_window, MINIMIZE) PENDING(quit, QUIT) - (BOOL)validateMenuItem:(NSMenuItem *)item { if (item.action == @selector(toggle_macos_secure_keyboard_entry:)) { item.state = [SecureKeyboardEntryController sharedInstance].isDesired ? NSControlStateValueOn : NSControlStateValueOff; } else if (item.action == @selector(toggle_fullscreen:)) { item.title = ([NSApp currentSystemPresentationOptions] & NSApplicationPresentationFullScreen) ? @"Exit Full Screen" : @"Enter Full Screen"; if (![NSApp keyWindow]) return NO; } else if (item.action == @selector(minimize_macos_window:)) { NSWindow *window = [NSApp keyWindow]; if (!window || window.miniaturized || [NSApp currentSystemPresentationOptions] & NSApplicationPresentationFullScreen) return NO; } else if (item.action == @selector(close_os_window:) || item.action == @selector(close_tab:) || item.action == @selector(close_window:) || item.action == @selector(reset_terminal:) || item.action == @selector(clear_terminal_and_scrollback:) || item.action == @selector(clear_scrollback:) || item.action == @selector(clear_screen:) || item.action == @selector(previous_tab:) || item.action == @selector(next_tab:) || item.action == @selector(detach_tab:)) { if (![NSApp keyWindow]) return NO; } return YES; } #undef PENDING + (GlobalMenuTarget *) shared_instance { static GlobalMenuTarget *sharedGlobalMenuTarget = nil; @synchronized(self) { if (!sharedGlobalMenuTarget) { sharedGlobalMenuTarget = [[GlobalMenuTarget alloc] init]; SecureKeyboardEntryController *k = [SecureKeyboardEntryController sharedInstance]; if (!k.isDesired && [[NSUserDefaults standardUserDefaults] boolForKey:@"SecureKeyboardEntry"]) [k toggle]; } return sharedGlobalMenuTarget; } } @end typedef struct { char key[32]; NSEventModifierFlags mods; } GlobalShortcut; typedef struct { GlobalShortcut new_os_window, close_os_window, close_tab, edit_config_file, reload_config; GlobalShortcut previous_tab, next_tab, new_tab, new_window, close_window, reset_terminal; GlobalShortcut clear_terminal_and_scrollback, clear_screen, clear_scrollback; GlobalShortcut toggle_macos_secure_keyboard_entry, toggle_fullscreen, open_kitty_website; GlobalShortcut hide_macos_app, hide_macos_other_apps, minimize_macos_window, quit; } GlobalShortcuts; static GlobalShortcuts global_shortcuts; static PyObject* cocoa_set_global_shortcut(PyObject *self UNUSED, PyObject *args) { int mods; unsigned int key; const char *name; if (!PyArg_ParseTuple(args, "siI", &name, &mods, &key)) return NULL; GlobalShortcut *gs = NULL; #define Q(x) if (strcmp(name, #x) == 0) gs = &global_shortcuts.x Q(new_os_window); else Q(close_os_window); else Q(close_tab); else Q(edit_config_file); else Q(new_tab); else Q(next_tab); else Q(previous_tab); else Q(new_window); else Q(close_window); else Q(reset_terminal); else Q(clear_terminal_and_scrollback); else Q(clear_scrollback); else Q(clear_screen); else Q(reload_config); else Q(toggle_macos_secure_keyboard_entry); else Q(toggle_fullscreen); else Q(open_kitty_website); else Q(hide_macos_app); else Q(hide_macos_other_apps); else Q(minimize_macos_window); else Q(quit); #undef Q if (gs == NULL) { PyErr_SetString(PyExc_KeyError, "Unknown shortcut name"); return NULL; } int cocoa_mods; get_cocoa_key_equivalent(key, mods, gs->key, 32, &cocoa_mods); gs->mods = cocoa_mods; if (gs->key[0]) Py_RETURN_TRUE; Py_RETURN_FALSE; } // Implementation of applicationDockMenu: for the app delegate static NSMenu *dockMenu = nil; static NSMenu * get_dock_menu(id self UNUSED, SEL _cmd UNUSED, NSApplication *sender UNUSED) { if (!dockMenu) { GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance]; dockMenu = [[NSMenu alloc] init]; [[dockMenu addItemWithTitle:@"New OS Window" action:@selector(new_os_window:) keyEquivalent:@""] setTarget:global_menu_target]; } return dockMenu; } static PyObject *notification_activated_callback = NULL; static PyObject* set_notification_activated_callback(PyObject *self UNUSED, PyObject *callback) { Py_CLEAR(notification_activated_callback); if (callback != Py_None) notification_activated_callback = Py_NewRef(callback); Py_RETURN_NONE; } static void do_notification_callback(const char *identifier, const char *event, const char *action_identifer) { if (notification_activated_callback) { PyObject *ret = PyObject_CallFunction(notification_activated_callback, "sss", event, identifier ? identifier : "", action_identifer ? action_identifer : ""); if (ret) Py_DECREF(ret); else PyErr_Print(); } } @interface NotificationDelegate : NSObject @end @implementation NotificationDelegate - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { (void)(center); (void)notification; UNNotificationPresentationOptions options = UNNotificationPresentationOptionSound; if (@available(macOS 11.0, *)) options |= UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner; else options |= (1 << 2); // UNNotificationPresentationOptionAlert avoid deprecated warning completionHandler(options); } - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { (void)(center); char *identifier = strdup(response.notification.request.identifier.UTF8String); char *action_identifier = strdup(response.actionIdentifier.UTF8String); const char *event = "button"; if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) { event = "activated"; } else if ([response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier]) { // Crapple never actually sends this event on macOS event = "closed"; } dispatch_async(dispatch_get_main_queue(), ^{ do_notification_callback(identifier, event, action_identifier); free(identifier); free(action_identifier); }); completionHandler(); } @end static UNUserNotificationCenter* get_notification_center_safely(void) { NSBundle *b = [NSBundle mainBundle]; // when bundleIdentifier is nil currentNotificationCenter crashes instead // of returning nil. Apple...purveyor of shiny TOYS if (!b || !b.bundleIdentifier) return nil; UNUserNotificationCenter *center = nil; @try { center = [UNUserNotificationCenter currentNotificationCenter]; } @catch (NSException *e) { log_error("Failed to get current UNUserNotificationCenter object with error: %s (%s)", [[e name] UTF8String], [[e reason] UTF8String]); } return center; } static bool ident_in_list_of_notifications(NSString *ident, NSArray *list) { for (UNNotification *n in list) { if ([[[n request] identifier] isEqualToString:ident]) return true; } return false; } void cocoa_report_live_notifications(const char* ident) { do_notification_callback(ident, "live", ident ? ident : ""); } static bool remove_delivered_notification(const char *identifier) { UNUserNotificationCenter *center = get_notification_center_safely(); if (!center) return false; char *ident = strdup(identifier); [center getDeliveredNotificationsWithCompletionHandler:^(NSArray * notifications) { if (ident_in_list_of_notifications(@(ident), notifications)) { [center removeDeliveredNotificationsWithIdentifiers:@[ @(ident) ]]; } free(ident); }]; return true; } static bool live_delivered_notifications(void) { UNUserNotificationCenter *center = get_notification_center_safely(); if (!center) return false; [center getDeliveredNotificationsWithCompletionHandler:^(NSArray * notifications) { @autoreleasepool { NSMutableString *buffer = [NSMutableString stringWithCapacity:1024]; // autoreleased for (UNNotification *n in notifications) [buffer appendFormat:@"%@,", [[n request] identifier]]; const char *val = [buffer UTF8String]; set_cocoa_pending_action(COCOA_NOTIFICATION_UNTRACKED, val ? val : ""); } }]; return true; } static void schedule_notification(const char *appname, const char *identifier, const char *title, const char *body, const char *image_path, int urgency, const char *category_id, bool muted) {@autoreleasepool { UNUserNotificationCenter *center = get_notification_center_safely(); if (!center) return; // Configure the notification's payload. UNMutableNotificationContent *content = [[[UNMutableNotificationContent alloc] init] autorelease]; if (title) content.title = @(title); if (body) content.body = @(body); if (appname) content.threadIdentifier = @(appname); if (category_id) content.categoryIdentifier = @(category_id); if (!muted) content.sound = [UNNotificationSound defaultSound]; #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 120000 switch (urgency) { case 0: content.interruptionLevel = UNNotificationInterruptionLevelPassive; case 2: content.interruptionLevel = UNNotificationInterruptionLevelCritical; default: content.interruptionLevel = UNNotificationInterruptionLevelActive; } #else if ([content respondsToSelector:@selector(interruptionLevel)]) { NSUInteger level = 1; if (urgency == 0) level = 0; else if (urgency == 2) level = 3; [content setValue:@(level) forKey:@"interruptionLevel"]; } #endif if (image_path) { @try { NSError *error; NSURL *image_url = [NSURL fileURLWithFileSystemRepresentation:image_path isDirectory:NO relativeToURL:nil]; // autoreleased UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"image" URL:image_url options:nil error:&error]; // autoreleased if (attachment) { content.attachments = @[ attachment ]; } else NSLog(@"Error attaching image %@ to notification: %@", @(image_path), error.localizedDescription); } @catch(NSException *exc) { NSLog(@"Creating image attachment %@ for notification failed with error: %@", @(image_path), exc.reason); } } // Deliver the notification static unsigned long counter = 1; UNNotificationRequest* request = [ UNNotificationRequest requestWithIdentifier:(identifier ? @(identifier) : [NSString stringWithFormat:@"Id_%lu", counter++]) content:content trigger:nil]; char *duped_ident = strdup(identifier ? identifier : ""); [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { if (error != nil) log_error("Failed to show notification: %s", [[error localizedDescription] UTF8String]); bool ok = error == nil; dispatch_async(dispatch_get_main_queue(), ^{ do_notification_callback(duped_ident, ok ? "created" : "creation_failed", ""); free(duped_ident); }); }]; }} typedef struct { char *identifier, *title, *body, *appname, *image_path, *category_id; int urgency; bool muted; } QueuedNotification; typedef struct { QueuedNotification *notifications; size_t count, capacity; } NotificationQueue; static NotificationQueue notification_queue = {0}; static void queue_notification(const char *appname, const char *identifier, const char *title, const char* body, const char *image_path, int urgency, const char *category_id, bool muted) { ensure_space_for((¬ification_queue), notifications, QueuedNotification, notification_queue.count + 16, capacity, 16, true); QueuedNotification *n = notification_queue.notifications + notification_queue.count++; #define d(x) n->x = (x && x[0]) ? strdup(x) : NULL; d(appname); d(identifier); d(title); d(body); d(image_path); d(category_id); #undef d n->urgency = urgency; n->muted = muted; } static void drain_pending_notifications(BOOL granted) { if (granted) { for (size_t i = 0; i < notification_queue.count; i++) { QueuedNotification *n = notification_queue.notifications + i; schedule_notification(n->appname, n->identifier, n->title, n->body, n->image_path, n->urgency, n->category_id, n->muted); } } while(notification_queue.count) { QueuedNotification *n = notification_queue.notifications + --notification_queue.count; if (!granted) do_notification_callback(n->identifier, "creation_failed", ""); free(n->identifier); free(n->title); free(n->body); free(n->appname); free(n->image_path); free(n->category_id); memset(n, 0, sizeof(QueuedNotification)); } } static PyObject* cocoa_remove_delivered_notification(PyObject *self UNUSED, PyObject *x) { if (!PyUnicode_Check(x)) { PyErr_SetString(PyExc_TypeError, "identifier must be a string"); return NULL; } if (remove_delivered_notification(PyUnicode_AsUTF8(x))) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static PyObject* cocoa_live_delivered_notifications(PyObject *self UNUSED, PyObject *x UNUSED) { if (live_delivered_notifications()) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static UNNotificationCategory* category_from_python(PyObject *p) { RAII_PyObject(button_ids, PyObject_GetAttrString(p, "button_ids")); RAII_PyObject(buttons, PyObject_GetAttrString(p, "buttons")); RAII_PyObject(id, PyObject_GetAttrString(p, "id")); NSMutableArray *actions = [NSMutableArray arrayWithCapacity:PyTuple_GET_SIZE(buttons)]; for (int i = 0; i < PyTuple_GET_SIZE(buttons); i++) [actions addObject: [UNNotificationAction actionWithIdentifier:@(PyUnicode_AsUTF8(PyTuple_GET_ITEM(button_ids, i))) title:@(PyUnicode_AsUTF8(PyTuple_GET_ITEM(buttons, i))) options:UNNotificationActionOptionNone]]; return [UNNotificationCategory categoryWithIdentifier:@(PyUnicode_AsUTF8(id)) actions:actions intentIdentifiers:@[] options:0]; } static bool set_notification_categories(UNUserNotificationCenter *center, PyObject *categories) { NSMutableArray *ans = [NSMutableArray arrayWithCapacity:PyTuple_GET_SIZE(categories)]; for (int i = 0; i < PyTuple_GET_SIZE(categories); i++) { UNNotificationCategory *c = category_from_python(PyTuple_GET_ITEM(categories, i)); if (!c) return false; [ans addObject:c]; } [center setNotificationCategories:[NSSet setWithArray:ans]]; return true; } static PyObject* cocoa_send_notification(PyObject *self UNUSED, PyObject *args, PyObject *kw) { const char *identifier = "", *title = "", *body = "", *appname = "", *image_path = ""; int urgency = 1; PyObject *category, *categories; int muted = 0; static const char* kwlist[] = {"appname", "identifier", "title", "body", "category", "categories", "image_path", "urgency", "muted", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kw, "ssssOO!|sip", (char**)kwlist, &appname, &identifier, &title, &body, &category, &PyTuple_Type, &categories, &image_path, &urgency, &muted)) return NULL; UNUserNotificationCenter *center = get_notification_center_safely(); if (!center) Py_RETURN_NONE; if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init]; if (PyObject_IsTrue(categories)) if (!set_notification_categories(center, categories)) return NULL; RAII_PyObject(category_id, PyObject_GetAttrString(category, "id")); queue_notification(appname, identifier, title, body, image_path, urgency, PyUnicode_AsUTF8(category_id), muted); // The badge permission needs to be requested as well, even though it is not used, // otherwise macOS refuses to show the preference checkbox for enable/disable notification sound. [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) { if (!granted && error != nil) { log_error("Failed to request permission for showing notification: %s", [[error localizedDescription] UTF8String]); } dispatch_async(dispatch_get_main_queue(), ^{ drain_pending_notifications(granted); }); } ]; Py_RETURN_NONE; } @interface ServiceProvider : NSObject @end @implementation ServiceProvider - (BOOL)openTab:(NSPasteboard*)pasteboard userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error { return [self openDirsFromPasteboard:pasteboard type:NEW_TAB_WITH_WD]; } - (BOOL)openOSWindow:(NSPasteboard*)pasteboard userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error { return [self openDirsFromPasteboard:pasteboard type:NEW_OS_WINDOW_WITH_WD]; } - (BOOL)openDirsFromPasteboard:(NSPasteboard *)pasteboard type:(int)type { NSDictionary *options = @{ NSPasteboardURLReadingFileURLsOnlyKey: @YES }; NSArray *filePathArray = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:options]; NSMutableArray *dirPathArray = [NSMutableArray arrayWithCapacity:[filePathArray count]]; for (NSURL *url in filePathArray) { NSString *path = [url path]; BOOL isDirectory = NO; if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]) { if (!isDirectory) path = [path stringByDeletingLastPathComponent]; if (![dirPathArray containsObject:path]) [dirPathArray addObject:path]; } } if ([dirPathArray count] > 0) { // Colons are not valid in paths under macOS. set_cocoa_pending_action(type, [[dirPathArray componentsJoinedByString:@":"] UTF8String]); } return YES; } - (BOOL)openFileURLs:(NSPasteboard*)pasteboard userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error { NSDictionary *options = @{ NSPasteboardURLReadingFileURLsOnlyKey: @YES }; NSArray *urlArray = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:options]; for (NSURL *url in urlArray) { NSString *path = [url path]; if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { set_cocoa_pending_action(LAUNCH_URLS, [[[NSURL fileURLWithPath:path] absoluteString] UTF8String]); } } return YES; } @end // global menu {{{ static void add_user_global_menu_entry(struct MenuItem *e, NSMenu *bar, size_t action_index) { NSMenu *parent = bar; UserMenuItem *final_item = nil; GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance]; for (size_t i = 0; i < e->location_count; i++) { NSMenuItem *item = [parent itemWithTitle:@(e->location[i])]; if (!item) { final_item = [[UserMenuItem alloc] initWithTitle:@(e->location[i]) action:@selector(user_menu_action:) keyEquivalent:@""]; final_item.target = global_menu_target; [parent addItem:final_item]; item = final_item; [final_item release]; } if (i + 1 < e->location_count) { if (![item hasSubmenu]) { NSMenu* sub_menu = [[NSMenu alloc] initWithTitle:item.title]; [item setSubmenu:sub_menu]; [sub_menu release]; } parent = [item submenu]; if (!parent) return; } } if (final_item != nil) { final_item.action_index = action_index; } } void cocoa_create_global_menu(void) { NSString* app_name = find_app_name(); NSMenu* bar = [[NSMenu alloc] init]; GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance]; [NSApp setMainMenu:bar]; #define MENU_ITEM(menu, title, name) { \ NSMenuItem *__mi = [menu addItemWithTitle:title action:@selector(name:) keyEquivalent:@(global_shortcuts.name.key)]; \ [__mi setKeyEquivalentModifierMask:global_shortcuts.name.mods]; \ [__mi setTarget:global_menu_target]; \ } NSMenuItem* appMenuItem = [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""]; NSMenu* appMenu = [[NSMenu alloc] init]; [appMenuItem setSubmenu:appMenu]; [appMenu addItemWithTitle:[NSString stringWithFormat:@"About %@", app_name] action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; [appMenu addItem:[NSMenuItem separatorItem]]; MENU_ITEM(appMenu, @"Preferences…", edit_config_file); MENU_ITEM(appMenu, @"Reload Preferences", reload_config); [appMenu addItem:[NSMenuItem separatorItem]]; NSMenu* servicesMenu = [[NSMenu alloc] init]; [NSApp setServicesMenu:servicesMenu]; [[appMenu addItemWithTitle:@"Services" action:NULL keyEquivalent:@""] setSubmenu:servicesMenu]; [servicesMenu release]; [appMenu addItem:[NSMenuItem separatorItem]]; MENU_ITEM(appMenu, ([NSString stringWithFormat:@"Hide %@", app_name]), hide_macos_app); MENU_ITEM(appMenu, @"Hide Others", hide_macos_other_apps); [appMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; [appMenu addItem:[NSMenuItem separatorItem]]; MENU_ITEM(appMenu, @"Secure Keyboard Entry", toggle_macos_secure_keyboard_entry); [appMenu addItem:[NSMenuItem separatorItem]]; MENU_ITEM(appMenu, ([NSString stringWithFormat:@"Quit %@", app_name]), quit); [appMenu release]; NSMenuItem* shellMenuItem = [bar addItemWithTitle:@"Shell" action:NULL keyEquivalent:@""]; NSMenu* shellMenu = [[NSMenu alloc] initWithTitle:@"Shell"]; [shellMenuItem setSubmenu:shellMenu]; MENU_ITEM(shellMenu, @"New OS Window", new_os_window); MENU_ITEM(shellMenu, @"New Tab", new_tab); MENU_ITEM(shellMenu, @"New Window", new_window); [shellMenu addItem:[NSMenuItem separatorItem]]; MENU_ITEM(shellMenu, @"Close OS Window", close_os_window); MENU_ITEM(shellMenu, @"Close Tab", close_tab); MENU_ITEM(shellMenu, @"Close Window", close_window); [shellMenu addItem:[NSMenuItem separatorItem]]; MENU_ITEM(shellMenu, @"Reset", reset_terminal); [shellMenu release]; NSMenuItem* editMenuItem = [bar addItemWithTitle:@"Edit" action:NULL keyEquivalent:@""]; NSMenu* editMenu = [[NSMenu alloc] initWithTitle:@"Edit"]; [editMenuItem setSubmenu:editMenu]; MENU_ITEM(editMenu, @"Clear to Start", clear_terminal_and_scrollback); MENU_ITEM(editMenu, @"Clear Scrollback", clear_scrollback); MENU_ITEM(editMenu, @"Clear Screen", clear_screen); [editMenu release]; NSMenuItem* windowMenuItem = [bar addItemWithTitle:@"Window" action:NULL keyEquivalent:@""]; NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; [windowMenuItem setSubmenu:windowMenu]; MENU_ITEM(windowMenu, @"Minimize", minimize_macos_window); [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""]; [windowMenu addItem:[NSMenuItem separatorItem]]; [windowMenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""]; [windowMenu addItem:[NSMenuItem separatorItem]]; MENU_ITEM(windowMenu, @"Show Previous Tab", previous_tab); MENU_ITEM(windowMenu, @"Show Next Tab", next_tab); [[windowMenu addItemWithTitle:@"Move Tab to New Window" action:@selector(detach_tab:) keyEquivalent:@""] setTarget:global_menu_target]; [windowMenu addItem:[NSMenuItem separatorItem]]; MENU_ITEM(windowMenu, @"Enter Full Screen", toggle_fullscreen); [NSApp setWindowsMenu:windowMenu]; [windowMenu release]; NSMenuItem* helpMenuItem = [bar addItemWithTitle:@"Help" action:NULL keyEquivalent:@""]; NSMenu* helpMenu = [[NSMenu alloc] initWithTitle:@"Help"]; [helpMenuItem setSubmenu:helpMenu]; MENU_ITEM(helpMenu, @"Visit kitty Website", open_kitty_website); [NSApp setHelpMenu:helpMenu]; [helpMenu release]; if (OPT(global_menu.entries)) { for (size_t i = 0; i < OPT(global_menu.count); i++) { struct MenuItem *e = OPT(global_menu.entries) + i; if (e->definition && e->location && e->location_count > 1) { add_user_global_menu_entry(e, bar, i); } } } [bar release]; class_addMethod( object_getClass([NSApp delegate]), @selector(applicationDockMenu:), (IMP)get_dock_menu, "@@:@"); [NSApp setServicesProvider:[[[ServiceProvider alloc] init] autorelease]]; #undef MENU_ITEM } void cocoa_update_menu_bar_title(PyObject *pytitle) { if (!pytitle) return; NSString *title = nil; if (OPT(macos_menubar_title_max_length) > 0 && PyUnicode_GetLength(pytitle) > OPT(macos_menubar_title_max_length)) { static char fmt[64]; snprintf(fmt, sizeof(fmt), "%%%ld.%ldU%%s", OPT(macos_menubar_title_max_length), OPT(macos_menubar_title_max_length)); RAII_PyObject(st, PyUnicode_FromFormat(fmt, pytitle, "…")); if (st) title = @(PyUnicode_AsUTF8(st)); else PyErr_Print(); } else { title = @(PyUnicode_AsUTF8(pytitle)); } if (!title) return; NSMenu *bar = [NSApp mainMenu]; if (title_menu != NULL) { [bar removeItem:title_menu]; } title_menu = [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""]; NSMenu *m = [[NSMenu alloc] initWithTitle:[NSString stringWithFormat:@" :: %@", title]]; [title_menu setSubmenu:m]; [m release]; } void cocoa_clear_global_shortcuts(void) { memset(&global_shortcuts, 0, sizeof(global_shortcuts)); } void cocoa_recreate_global_menu(void) { if (title_menu != NULL) { NSMenu *bar = [NSApp mainMenu]; [bar removeItem:title_menu]; } title_menu = NULL; cocoa_create_global_menu(); } // }}} #define NSLeftAlternateKeyMask (0x000020 | NSEventModifierFlagOption) #define NSRightAlternateKeyMask (0x000040 | NSEventModifierFlagOption) bool cocoa_alt_option_key_pressed(NSUInteger flags) { NSUInteger q = (OPT(macos_option_as_alt) == 1) ? NSRightAlternateKeyMask : NSLeftAlternateKeyMask; return (q & flags) == q; } void cocoa_toggle_secure_keyboard_entry(void) { SecureKeyboardEntryController *k = [SecureKeyboardEntryController sharedInstance]; [k toggle]; [[NSUserDefaults standardUserDefaults] setBool:k.isDesired forKey:@"SecureKeyboardEntry"]; } void cocoa_hide(void) { [[NSApplication sharedApplication] performSelectorOnMainThread:@selector(hide:) withObject:nil waitUntilDone:NO]; } void cocoa_hide_others(void) { [[NSApplication sharedApplication] performSelectorOnMainThread:@selector(hideOtherApplications:) withObject:nil waitUntilDone:NO]; } void cocoa_minimize(void *w) { NSWindow *window = (NSWindow*)w; if (window && !window.miniaturized) [window performSelectorOnMainThread:@selector(performMiniaturize:) withObject:nil waitUntilDone:NO]; } void cocoa_focus_window(void *w) { NSWindow *window = (NSWindow*)w; [window makeKeyWindow]; } long cocoa_window_number(void *w) { NSWindow *window = (NSWindow*)w; return [window windowNumber]; } size_t cocoa_get_workspace_ids(void *w, size_t *workspace_ids, size_t array_sz) { NSWindow *window = (NSWindow*)w; if (!window) return 0; NSArray *window_array = @[ @([window windowNumber]) ]; CFArrayRef spaces = CGSCopySpacesForWindows(_CGSDefaultConnection(), kCGSSpaceAll, (__bridge CFArrayRef)window_array); CFIndex ans = CFArrayGetCount(spaces); if (ans > 0) { for (CFIndex i = 0; i < MIN(ans, (CFIndex)array_sz); i++) { NSNumber *s = (NSNumber*)CFArrayGetValueAtIndex(spaces, i); workspace_ids[i] = [s intValue]; } } else ans = 0; CFRelease(spaces); return ans; } static PyObject* cocoa_get_lang(PyObject UNUSED *self, PyObject *args UNUSED) { @autoreleasepool { NSString* lang_code = [[NSLocale currentLocale] languageCode]; NSString* country_code = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode]; NSString* identifier = [[NSLocale currentLocale] localeIdentifier]; return Py_BuildValue("sss", lang_code ? [lang_code UTF8String]:"", country_code ? [country_code UTF8String] : "", identifier ? [identifier UTF8String]: ""); } // autoreleasepool } monotonic_t cocoa_cursor_blink_interval(void) { @autoreleasepool { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; double on_period_ms = [defaults doubleForKey:@"NSTextInsertionPointBlinkPeriodOn"]; double off_period_ms = [defaults doubleForKey:@"NSTextInsertionPointBlinkPeriodOff"]; double period_ms = [defaults doubleForKey:@"NSTextInsertionPointBlinkPeriod"]; double max_value = 60 * 1000.0, ans = -1.0; if (on_period_ms != 0. || off_period_ms != 0.) { ans = on_period_ms + off_period_ms; } else if (period_ms != 0.) { ans = period_ms; } return ans > max_value ? 0ll : ms_double_to_monotonic_t(ans); } // autoreleasepool } void cocoa_set_activation_policy(bool hide_from_tasks) { [NSApp setActivationPolicy:(hide_from_tasks ? NSApplicationActivationPolicyAccessory : NSApplicationActivationPolicyRegular)]; } static PyObject* cocoa_set_url_handler(PyObject UNUSED *self, PyObject *args) { @autoreleasepool { const char *url_scheme = NULL, *bundle_id = NULL; if (!PyArg_ParseTuple(args, "s|z", &url_scheme, &bundle_id)) return NULL; if (!url_scheme || url_scheme[0] == '\0') { PyErr_SetString(PyExc_TypeError, "Empty url scheme"); return NULL; } NSString *scheme = [NSString stringWithUTF8String:url_scheme]; NSString *identifier = @""; if (!bundle_id) { identifier = [[NSBundle mainBundle] bundleIdentifier]; if (!identifier || identifier.length == 0) identifier = @"net.kovidgoyal.kitty"; } else if (bundle_id[0] != '\0') { identifier = [NSString stringWithUTF8String:bundle_id]; } // This API has been marked as deprecated. It will need to be replaced when a new approach is available. OSStatus err = LSSetDefaultHandlerForURLScheme((CFStringRef)scheme, (CFStringRef)identifier); if (err == noErr) Py_RETURN_NONE; PyErr_Format(PyExc_OSError, "Failed to set default handler with error code: %d", err); return NULL; } // autoreleasepool } static PyObject* cocoa_set_app_icon(PyObject UNUSED *self, PyObject *args) { @autoreleasepool { const char *icon_path = NULL, *app_path = NULL; if (!PyArg_ParseTuple(args, "s|z", &icon_path, &app_path)) return NULL; if (!icon_path || icon_path[0] == '\0') { PyErr_SetString(PyExc_TypeError, "Empty icon file path"); return NULL; } NSString *custom_icon_path = [NSString stringWithUTF8String:icon_path]; if (![[NSFileManager defaultManager] fileExistsAtPath:custom_icon_path]) { PyErr_Format(PyExc_FileNotFoundError, "Icon file not found: %s", [custom_icon_path UTF8String]); return NULL; } NSString *bundle_path = @""; if (!app_path) { bundle_path = [[NSBundle mainBundle] bundlePath]; if (!bundle_path || bundle_path.length == 0) bundle_path = @"/Applications/kitty.app"; // When compiled from source and run from the launcher folder the bundle path should be `kitty.app` in it if (![bundle_path hasSuffix:@".app"]) { NSString *launcher_app_path = [bundle_path stringByAppendingPathComponent:@"kitty.app"]; bundle_path = @""; BOOL is_dir; if ([[NSFileManager defaultManager] fileExistsAtPath:launcher_app_path isDirectory:&is_dir] && is_dir && [[NSWorkspace sharedWorkspace] isFilePackageAtPath:launcher_app_path]) { bundle_path = launcher_app_path; } } } else if (app_path[0] != '\0') { bundle_path = [NSString stringWithUTF8String:app_path]; } if (!bundle_path || bundle_path.length == 0 || ![[NSFileManager defaultManager] fileExistsAtPath:bundle_path]) { PyErr_Format(PyExc_FileNotFoundError, "Application bundle not found: %s", [bundle_path UTF8String]); return NULL; } NSImage *icon_image = [[NSImage alloc] initWithContentsOfFile:custom_icon_path]; BOOL result = [[NSWorkspace sharedWorkspace] setIcon:icon_image forFile:bundle_path options:NSExcludeQuickDrawElementsIconCreationOption]; [icon_image release]; if (result) Py_RETURN_NONE; PyErr_Format(PyExc_OSError, "Failed to set custom icon %s for %s", [custom_icon_path UTF8String], [bundle_path UTF8String]); return NULL; } // autoreleasepool } static PyObject* cocoa_set_dock_icon(PyObject UNUSED *self, PyObject *args) { @autoreleasepool { const char *icon_path = NULL; if (!PyArg_ParseTuple(args, "s", &icon_path)) return NULL; if (!icon_path || icon_path[0] == '\0') { PyErr_SetString(PyExc_TypeError, "Empty icon file path"); return NULL; } NSString *custom_icon_path = [NSString stringWithUTF8String:icon_path]; if ([[NSFileManager defaultManager] fileExistsAtPath:custom_icon_path]) { NSImage *icon_image = [[[NSImage alloc] initWithContentsOfFile:custom_icon_path] autorelease]; [NSApplication sharedApplication].applicationIconImage = icon_image; Py_RETURN_NONE; } return NULL; } // autoreleasepool } static NSSound *beep_sound = nil; static void cleanup(void) { @autoreleasepool { if (dockMenu) [dockMenu release]; dockMenu = nil; if (beep_sound) [beep_sound release]; beep_sound = nil; drain_pending_notifications(NO); free(notification_queue.notifications); notification_queue.notifications = NULL; notification_queue.capacity = 0; } // autoreleasepool } void cocoa_system_beep(const char *path) { if (!path) { NSBeep(); return; } static const char *beep_path = NULL; if (beep_path != path) { if (beep_sound) [beep_sound release]; beep_sound = [[NSSound alloc] initWithContentsOfFile:@(path) byReference:YES]; } if (beep_sound) [beep_sound play]; else NSBeep(); } static void uncaughtExceptionHandler(NSException *exception) { log_error("Unhandled exception in Cocoa: %s", [[exception description] UTF8String]); log_error("Stack trace:\n%s", [[exception.callStackSymbols description] UTF8String]); } void cocoa_set_uncaught_exception_handler(void) { NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler); } static PyObject* convert_imagerep_to_png(NSBitmapImageRep *rep, const char *output_path) { NSData *png = [rep representationUsingType:NSBitmapImageFileTypePNG properties:@{NSImageCompressionFactor: @1.0}]; // autoreleased if (output_path) { if (![png writeToFile:@(output_path) atomically:YES]) { PyErr_Format(PyExc_OSError, "Failed to write PNG data to %s", output_path); return NULL; } return PyBytes_FromStringAndSize(NULL, 0); } return PyBytes_FromStringAndSize(png.bytes, png.length); } static PyObject* convert_image_to_png(NSImage *icon, unsigned image_size, const char *output_path) { NSRect r = NSMakeRect(0, 0, image_size, image_size); RAII_CoreFoundation(CGColorSpaceRef, colorSpace, CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); RAII_CoreFoundation(CGContextRef, cgContext, CGBitmapContextCreate(NULL, image_size, image_size, 8, 4*image_size, colorSpace, kCGBitmapByteOrderDefault|kCGImageAlphaPremultipliedLast)); NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithCGContext:cgContext flipped:NO]; // autoreleased CGImageRef cg = [icon CGImageForProposedRect:&r context:context hints:nil]; NSBitmapImageRep *rep = [[[NSBitmapImageRep alloc] initWithCGImage:cg] autorelease]; return convert_imagerep_to_png(rep, output_path); } static PyObject* render_emoji(NSString *text, unsigned image_size, const char *output_path) { NSFont *font = [NSFont fontWithName:@"AppleColorEmoji" size:12]; CTFontRef ctfont = (__bridge CTFontRef)(font); CGFloat line_height = MAX(1, floor(CTFontGetAscent(ctfont) + CTFontGetDescent(ctfont) + MAX(0, CTFontGetLeading(ctfont)) + 0.5)); CGFloat pts_per_px = CTFontGetSize(ctfont) / line_height; CGFloat desired_size = image_size * pts_per_px; NSFont *final_font = [NSFont fontWithName:@"AppleColorEmoji" size:desired_size]; NSAttributedString *attr_string = [[[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: final_font}] autorelease]; NSBitmapImageRep *bmp = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil pixelsWide:image_size pixelsHigh:image_size bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace bytesPerRow:0 bitsPerPixel:0] autorelease]; [NSGraphicsContext saveGraphicsState]; NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:bmp]; [NSGraphicsContext setCurrentContext:context]; [attr_string drawInRect:NSMakeRect(0, 0, image_size, image_size)]; [NSGraphicsContext restoreGraphicsState]; return convert_imagerep_to_png(bmp, output_path); } static PyObject* bundle_image_as_png(PyObject *self UNUSED, PyObject *args, PyObject *kw) {@autoreleasepool { const char *b, *output_path = NULL; int image_type = 1; unsigned image_size = 256; static const char* kwlist[] = {"path_or_identifier", "output_path", "image_size", "image_type", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kw, "s|sIi", (char**)kwlist, &b, &output_path, &image_size, &image_type)) return NULL; NSImage *icon = nil; switch (image_type) { case 0: case 1: { NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; // autoreleased if (image_type == 1) { NSURL *url = [workspace URLForApplicationWithBundleIdentifier:@(b)]; // autoreleased if (!url) { PyErr_Format(PyExc_KeyError, "Failed to find bundle path for identifier: %s", b); return NULL; } icon = [workspace iconForFile:@(url.fileSystemRepresentation)]; } else icon = [workspace iconForFile:@(b)]; } break; case 2: return render_emoji(@(b), image_size, output_path); default: if (@available(macOS 11.0, *)) { icon = [NSImage imageWithSystemSymbolName:@(b) accessibilityDescription:@""]; // autoreleased } else { PyErr_SetString(PyExc_ValueError, "Your version of macOS is too old to use symbol images, need >= 11.0"); return NULL; } break; } if (!icon) { PyErr_Format(PyExc_ValueError, "Failed to load icon for bundle: %s", b); return NULL; } return convert_image_to_png(icon, image_size, output_path); }} static PyObject* play_system_sound_by_id_async(PyObject *self UNUSED, PyObject *which) { if (!PyLong_Check(which)) { PyErr_SetString(PyExc_TypeError, "system sound id must be an integer"); return NULL; } AudioServicesPlaySystemSound(PyLong_AsUnsignedLong(which)); Py_RETURN_NONE; } // Dock Progress bar {{{ @interface RoundedRectangleView : NSView { unsigned intermediate_step; CGFloat fill_fraction; BOOL is_indeterminate; } - (void) animate; - (BOOL) isIndeterminate; - (void) setIndeterminate:(BOOL)val; - (void) setFraction:(CGFloat) fraction; @end @implementation RoundedRectangleView - (void) animate { intermediate_step++; } - (BOOL) isIndeterminate { return is_indeterminate; } - (void) setIndeterminate:(BOOL)val { if (val != is_indeterminate) { is_indeterminate = val; intermediate_step = 0; } } - (void) setFraction:(CGFloat)fraction { fill_fraction = fraction; } - (void)drawRect:(NSRect)dirtyRect { [super drawRect:dirtyRect]; NSRect bar = NSInsetRect(self.bounds, 4, 4); CGFloat cornerRadius = self.bounds.size.height / 4.0; #define fill(bar) [[NSBezierPath bezierPathWithRoundedRect:bar xRadius:cornerRadius yRadius:cornerRadius] fill] // Create the border [[[NSColor whiteColor] colorWithAlphaComponent:0.8] setFill]; fill(bar); // Create the background [[[NSColor blackColor] colorWithAlphaComponent:0.8] setFill]; fill(NSInsetRect(bar, 0.5, 0.5)); // Create the progress NSRect bar_progress = NSInsetRect(bar, 1, 1); if (intermediate_step) { unsigned num_of_steps = 80; intermediate_step = intermediate_step % num_of_steps; bar_progress.size.width = self.bounds.size.width / 8; float frac = intermediate_step / (float)num_of_steps; bar_progress.origin.x += (self.bounds.size.width - bar_progress.size.width) * frac; } else bar_progress.size.width *= fill_fraction; [[NSColor whiteColor] setFill]; fill(bar_progress); #undef fill } @end static NSView *dock_content_view = nil; static NSImageView *dock_image_view = nil; static RoundedRectangleView *dock_pbar = nil; static void animate_dock_progress_bar(id_type timer_id UNUSED, void *data UNUSED); static void tick_dock_pbar(void) { add_main_loop_timer(ms_to_monotonic_t(20), false, animate_dock_progress_bar, NULL, NULL); } static void animate_dock_progress_bar(id_type timer_id UNUSED, void *data UNUSED) { if (dock_pbar != nil && [dock_pbar isIndeterminate]) { [dock_pbar animate]; NSDockTile *dockTile = [NSApp dockTile]; [dockTile display]; tick_dock_pbar(); } } static PyObject* cocoa_show_progress_bar_on_dock_icon(PyObject *self UNUSED, PyObject *args) { float percent = -100; if (!PyArg_ParseTuple(args, "|f", &percent)) return NULL; NSDockTile *dockTile = [NSApp dockTile]; if (!dock_content_view) { dock_content_view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, dockTile.size.width, dockTile.size.height)]; dock_image_view = [NSImageView.alloc initWithFrame:dock_content_view.frame]; dock_image_view.imageScaling = NSImageScaleProportionallyDown; dock_image_view.image = NSApp.applicationIconImage; [dock_content_view addSubview:dock_image_view]; dock_pbar = [[RoundedRectangleView alloc] initWithFrame:NSMakeRect(0, 0, dockTile.size.width, dockTile.size.height / 4)]; [dock_content_view addSubview:dock_pbar]; } [dock_content_view setFrameSize:dockTile.size]; [dock_image_view setFrameSize:dockTile.size]; if (percent >= 0 && percent <= 100) { [dock_pbar setFraction:percent/100.]; [dock_pbar setIndeterminate:NO]; } else if (percent > 100) { [dock_pbar setIndeterminate:YES]; tick_dock_pbar(); } [dock_pbar setFrameSize:NSMakeSize(dockTile.size.width - 20, 20)]; [dock_pbar setFrameOrigin:NSMakePoint(10, -2)]; [dockTile setContentView:percent < 0 ? nil : dock_content_view]; [dockTile display]; Py_RETURN_NONE; } // }}} static PyMethodDef module_methods[] = { {"cocoa_play_system_sound_by_id_async", play_system_sound_by_id_async, METH_O, ""}, {"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""}, {"cocoa_set_global_shortcut", (PyCFunction)cocoa_set_global_shortcut, METH_VARARGS, ""}, {"cocoa_send_notification", (PyCFunction)(void(*)(void))cocoa_send_notification, METH_VARARGS | METH_KEYWORDS, ""}, {"cocoa_remove_delivered_notification", (PyCFunction)cocoa_remove_delivered_notification, METH_O, ""}, {"cocoa_live_delivered_notifications", (PyCFunction)cocoa_live_delivered_notifications, METH_NOARGS, ""}, {"cocoa_set_notification_activated_callback", (PyCFunction)set_notification_activated_callback, METH_O, ""}, {"cocoa_set_url_handler", (PyCFunction)cocoa_set_url_handler, METH_VARARGS, ""}, {"cocoa_set_app_icon", (PyCFunction)cocoa_set_app_icon, METH_VARARGS, ""}, {"cocoa_set_dock_icon", (PyCFunction)cocoa_set_dock_icon, METH_VARARGS, ""}, {"cocoa_show_progress_bar_on_dock_icon", (PyCFunction)cocoa_show_progress_bar_on_dock_icon, METH_VARARGS, ""}, {"cocoa_bundle_image_as_png", (PyCFunction)(void(*)(void))bundle_image_as_png, METH_VARARGS | METH_KEYWORDS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_cocoa(PyObject *module) { cocoa_clear_global_shortcuts(); if (PyModule_AddFunctions(module, module_methods) != 0) return false; register_at_exit_cleanup_func(COCOA_CLEANUP_FUNC, cleanup); return true; } kitty-0.41.1/kitty/colors.c0000664000175000017510000007566114773370543015137 0ustar nileshnilesh/* * colors.c * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "state.h" #include #include "colors.h" static uint32_t FG_BG_256[256] = { 0x000000, // 0 0xcd0000, // 1 0x00cd00, // 2 0xcdcd00, // 3 0x0000ee, // 4 0xcd00cd, // 5 0x00cdcd, // 6 0xe5e5e5, // 7 0x7f7f7f, // 8 0xff0000, // 9 0x00ff00, // 10 0xffff00, // 11 0x5c5cff, // 12 0xff00ff, // 13 0x00ffff, // 14 0xffffff, // 15 }; static void init_FG_BG_table(void) { if (UNLIKELY(FG_BG_256[255] == 0)) { // colors 16..232: the 6x6x6 color cube const uint8_t valuerange[6] = {0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff}; uint8_t i, j=16; for(i = 0; i < 216; i++, j++) { uint8_t r = valuerange[(i / 36) % 6], g = valuerange[(i / 6) % 6], b = valuerange[i % 6]; FG_BG_256[j] = (r << 16) | (g << 8) | b; } // colors 232..255: grayscale for(i = 0; i < 24; i++, j++) { uint8_t v = 8 + i * 10; FG_BG_256[j] = (v << 16) | (v << 8) | v; } } } static PyObject* create_256_color_table(void) { init_FG_BG_table(); PyObject *ans = PyTuple_New(arraysz(FG_BG_256)); if (ans == NULL) return PyErr_NoMemory(); for (size_t i=0; i < arraysz(FG_BG_256); i++) { PyObject *temp = PyLong_FromUnsignedLong(FG_BG_256[i]); if (temp == NULL) { Py_CLEAR(ans); return NULL; } PyTuple_SET_ITEM(ans, i, temp); } return ans; } static void set_transparent_background_colors(TransparentDynamicColor *dest, PyObject *src) { memset(dest, 0, sizeof(((ColorProfile*)0)->configured_transparent_colors)); for (Py_ssize_t i = 0; i < MIN(PyTuple_GET_SIZE(src), (Py_ssize_t)arraysz(((ColorProfile*)0)->configured_transparent_colors)); i++) { PyObject *e = PyTuple_GET_ITEM(src, i); dest[i].color = ((Color*)(PyTuple_GET_ITEM(e, 0)))->color.val & 0xffffff; dest[i].opacity = (float)PyFloat_AsDouble(PyTuple_GET_ITEM(e, 1)); dest[i].is_set = true; } } static bool set_configured_colors(ColorProfile *self, PyObject *opts) { #define n(which, attr) { \ RAII_PyObject(t, PyObject_GetAttrString(opts, #attr)); \ if (t == NULL) return false; \ if (t == Py_None) { self->configured.which.rgb = 0; self->configured.which.type = COLOR_IS_SPECIAL; } \ else if (PyLong_Check(t)) { \ unsigned int x = PyLong_AsUnsignedLong(t); \ self->configured.which.rgb = x & 0xffffff; \ self->configured.which.type = COLOR_IS_RGB; \ } else if (PyObject_TypeCheck(t, &Color_Type)) { \ Color *c = (Color*)t; \ self->configured.which.rgb = c->color.rgb; \ self->configured.which.type = COLOR_IS_RGB; \ } else { PyErr_SetString(PyExc_TypeError, "colors must be integers or Color objects"); return false; } \ } n(default_fg, foreground); n(default_bg, background); n(cursor_color, cursor); n(cursor_text_color, cursor_text_color); n(highlight_fg, selection_foreground); n(highlight_bg, selection_background); n(visual_bell_color, visual_bell_color); #undef n RAII_PyObject(src, PyObject_GetAttrString(opts, "transparent_background_colors")); if (!src) { PyErr_SetString(PyExc_TypeError, "No transparent_background_colors on opts object"); return false; } set_transparent_background_colors(self->configured_transparent_colors, src); return PyErr_Occurred() ? false : true; } static bool set_mark_colors(ColorProfile *self, PyObject *opts) { char fgattr[] = "mark?_foreground", bgattr[] = "mark?_background"; #define n(i, attr, which) { \ attr[4] = '1' + i; \ RAII_PyObject(t, PyObject_GetAttrString(opts, attr)); \ if (t == NULL) return false; \ if (!PyObject_TypeCheck(t, &Color_Type)) { PyErr_SetString(PyExc_TypeError, "mark color is not Color object"); return false; } \ Color *c = (Color*)t; self->which[i] = c->color.rgb; \ } #define m(i) n(i, fgattr, mark_foregrounds); n(i, bgattr, mark_backgrounds); m(0); m(1); m(2); #undef m #undef n return true; } static bool set_colortable(ColorProfile *self, PyObject *opts) { RAII_PyObject(ct, PyObject_GetAttrString(opts, "color_table")); if (!ct) return false; RAII_PyObject(ret, PyObject_CallMethod(ct, "buffer_info", NULL)); if (!ret) return false; unsigned long *color_table = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0)); size_t count = PyLong_AsSize_t(PyTuple_GET_ITEM(ret, 1)); if (!color_table || count != arraysz(FG_BG_256)) { PyErr_SetString(PyExc_TypeError, "color_table has incorrect length"); return false; } RAII_PyObject(r2, PyObject_GetAttrString(ct, "itemsize")); if (!r2) return false; size_t itemsize = PyLong_AsSize_t(r2); if (itemsize != sizeof(unsigned long)) { PyErr_Format(PyExc_TypeError, "color_table has incorrect itemsize: %zu", itemsize); return false; } for (size_t i = 0; i < arraysz(FG_BG_256); i++) self->color_table[i] = color_table[i]; memcpy(self->orig_color_table, self->color_table, arraysz(self->color_table) * sizeof(self->color_table[0])); return true; } static PyObject* new_cp(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *opts = global_state.options_object; ColorProfile *self; static const char* kw[] = {"opts", NULL}; if (args && !PyArg_ParseTupleAndKeywords(args, kwds, "|O", (char**)kw, &opts)) return NULL; self = (ColorProfile *)type->tp_alloc(type, 0); RAII_PyObject(ans, (PyObject*)self); if (self != NULL) { init_FG_BG_table(); if (opts) { if (!set_configured_colors(self, opts)) return NULL; if (!set_mark_colors(self, opts)) return NULL; if (!set_colortable(self, opts)) return NULL; } else { memcpy(self->color_table, FG_BG_256, sizeof(FG_BG_256)); memcpy(self->orig_color_table, FG_BG_256, sizeof(FG_BG_256)); } self->dirty = true; Py_INCREF(ans); } return ans; } static void dealloc_cp(ColorProfile* self) { if (self->color_stack) free(self->color_stack); Py_TYPE(self)->tp_free((PyObject*)self); } ColorProfile* alloc_color_profile(void) { return (ColorProfile*)new_cp(&ColorProfile_Type, NULL, NULL); } void copy_color_profile(ColorProfile *dest, ColorProfile *src) { memcpy(dest->color_table, src->color_table, sizeof(dest->color_table)); memcpy(dest->orig_color_table, src->orig_color_table, sizeof(dest->color_table)); memcpy(&dest->configured, &src->configured, sizeof(dest->configured)); memcpy(&dest->overridden, &src->overridden, sizeof(dest->overridden)); memcpy(dest->overriden_transparent_colors, src->overriden_transparent_colors, sizeof(dest->overriden_transparent_colors)); memcpy(dest->configured_transparent_colors, src->configured_transparent_colors, sizeof(dest->configured_transparent_colors)); dest->dirty = true; } static void patch_color_table(const char *key, PyObject *profiles, PyObject *spec, size_t which, int change_configured) { PyObject *v = PyDict_GetItemString(spec, key); if (v && PyLong_Check(v)) { color_type color = PyLong_AsUnsignedLong(v); for (Py_ssize_t j = 0; j < PyTuple_GET_SIZE(profiles); j++) { ColorProfile *self = (ColorProfile*)PyTuple_GET_ITEM(profiles, j); self->color_table[which] = color; if (change_configured) self->orig_color_table[which] = color; self->dirty = true; } } } #define patch_mark_color(key, profiles, spec, array, i) { \ PyObject *v = PyDict_GetItemString(spec, key); \ if (v && PyLong_Check(v)) { \ color_type color = PyLong_AsUnsignedLong(v); \ for (Py_ssize_t j = 0; j < PyTuple_GET_SIZE(profiles); j++) { \ ColorProfile *self = (ColorProfile*)PyTuple_GET_ITEM(profiles, j); \ self->array[i] = color; \ self->dirty = true; \ } } } static PyObject* patch_color_profiles(PyObject *module UNUSED, PyObject *args) { PyObject *spec, *transparent_background_colors, *profiles, *v; ColorProfile *self; int change_configured; if (!PyArg_ParseTuple(args, "O!O!O!p", &PyDict_Type, &spec, &PyTuple_Type, &transparent_background_colors, &PyTuple_Type, &profiles, &change_configured)) return NULL; char key[32] = {0}; for (size_t i = 0; i < arraysz(FG_BG_256); i++) { snprintf(key, sizeof(key) - 1, "color%zu", i); patch_color_table(key, profiles, spec, i, change_configured); } for (size_t i = 1; i <= MARK_MASK; i++) { #define S(which, i) snprintf(key, sizeof(key) - 1, "mark%zu_" #which, i); patch_mark_color(key, profiles, spec, mark_##which##s, i) S(background, i); S(foreground, i); #undef S } #define SI(profile_name) \ DynamicColor color; \ if (PyLong_Check(v)) { \ color.rgb = PyLong_AsUnsignedLong(v); color.type = COLOR_IS_RGB; \ } else { color.rgb = 0; color.type = COLOR_IS_SPECIAL; }\ self->overridden.profile_name = color; \ if (change_configured) self->configured.profile_name = color; \ self->dirty = true; #define S(config_name, profile_name) { \ v = PyDict_GetItemString(spec, #config_name); \ if (v) { \ for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) { \ self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i); \ SI(profile_name); \ } \ } \ } S(foreground, default_fg); S(background, default_bg); S(cursor, cursor_color); S(selection_foreground, highlight_fg); S(selection_background, highlight_bg); S(cursor_text_color, cursor_text_color); S(visual_bell_color, visual_bell_color); #undef SI #undef S for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) { self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i); set_transparent_background_colors(self->overriden_transparent_colors, transparent_background_colors); if (change_configured) set_transparent_background_colors(self->configured_transparent_colors, transparent_background_colors); } if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } bool colorprofile_to_transparent_color(const ColorProfile *self, unsigned index, color_type *color, float *opacity) { *color = UINT32_MAX; *opacity = 1.0; if (index < arraysz(self->configured_transparent_colors)) { if (self->overriden_transparent_colors[index].is_set) { *color = self->overriden_transparent_colors[index].color; *opacity = self->overriden_transparent_colors[index].opacity; if (*opacity < 0) *opacity = OPT(background_opacity); return true; } if (self->configured_transparent_colors[index].is_set) { *color = self->configured_transparent_colors[index].color; *opacity = self->configured_transparent_colors[index].opacity; if (*opacity < 0) *opacity = OPT(background_opacity); return true; } } return false; } DynamicColor colorprofile_to_color(const ColorProfile *self, DynamicColor entry, DynamicColor defval) { switch(entry.type) { case COLOR_NOT_SET: return defval; case COLOR_IS_INDEX: { DynamicColor ans; ans.rgb = self->color_table[entry.rgb & 0xff] & 0xffffff; ans.type = COLOR_IS_RGB; return ans; } case COLOR_IS_RGB: case COLOR_IS_SPECIAL: return entry; } return entry; } color_type colorprofile_to_color_with_fallback(ColorProfile *self, DynamicColor entry, DynamicColor defval, DynamicColor fallback, DynamicColor fallback_defval) { switch(entry.type) { case COLOR_NOT_SET: case COLOR_IS_SPECIAL: if (defval.type == COLOR_IS_SPECIAL) return colorprofile_to_color(self, fallback, fallback_defval).rgb; return defval.rgb; case COLOR_IS_RGB: return entry.rgb; case COLOR_IS_INDEX: return self->color_table[entry.rgb & 0xff] & 0xffffff; } return entry.rgb; } static Color* alloc_color(unsigned char r, unsigned char g, unsigned char b, unsigned a); static bool colortable_colors_into_dict(ColorProfile *self, unsigned start, unsigned limit, PyObject *ans) { static char buf[32] = {'c', 'o', 'l', 'o', 'r', 0}; for (unsigned i = start; i < limit; i++) { snprintf(buf + 5, sizeof(buf) - 6, "%u", i); PyObject *val = PyLong_FromUnsignedLong(self->color_table[i]); if (!val) return false; int ret = PyDict_SetItemString(ans, buf, val); Py_DECREF(val); if (ret != 0) return false; } return true; } static PyObject* basic_colors(ColorProfile *self, PyObject *args UNUSED) { #define basic_colors_doc "Return the basic colors as a dictionary of color_name to integer or None (names are the same as used in kitty.conf)" RAII_PyObject(ans, PyDict_New()); if (ans == NULL) return NULL; if (!colortable_colors_into_dict(self, 0, 16, ans)) return NULL; #define D(attr, name) { \ unsigned long c = colorprofile_to_color(self, self->overridden.attr, self->configured.attr).rgb; \ PyObject *val = PyLong_FromUnsignedLong(c); if (!val) return NULL; \ int ret = PyDict_SetItemString(ans, #name, val); Py_DECREF(val); \ if (ret != 0) return NULL; \ } D(default_fg, foreground); D(default_bg, background); #undef D return Py_NewRef(ans); } static PyObject* as_dict(ColorProfile *self, PyObject *args UNUSED) { #define as_dict_doc "Return all colors as a dictionary of color_name to integer or None (names are the same as used in kitty.conf)" RAII_PyObject(ans, PyDict_New()); if (ans == NULL) return NULL; if (!colortable_colors_into_dict(self, 0, arraysz(self->color_table), ans)) return NULL; #define D(attr, name) { \ if (self->overridden.attr.type != COLOR_NOT_SET) { \ int ret; PyObject *val; \ if (self->overridden.attr.type == COLOR_IS_SPECIAL) { \ val = Py_NewRef(Py_None); \ } else { \ unsigned long c = colorprofile_to_color(self, self->overridden.attr, self->configured.attr).rgb; \ val = PyLong_FromUnsignedLong(c); \ } \ if (!val) { return NULL; } \ ret = PyDict_SetItemString(ans, #name, val); \ Py_DECREF(val); \ if (ret != 0) { return NULL; } \ }} D(default_fg, foreground); D(default_bg, background); D(cursor_color, cursor); D(cursor_text_color, cursor_text); D(highlight_fg, selection_foreground); D(highlight_bg, selection_background); D(visual_bell_color, visual_bell_color); RAII_PyObject(transparent_background_colors, PyList_New(0)); if (!transparent_background_colors) return NULL; for (size_t i = 0; i < arraysz(self->overriden_transparent_colors); i++) { TransparentDynamicColor *c = NULL; if (self->overriden_transparent_colors[i].is_set) c = self->overriden_transparent_colors + i; else if (self->configured_transparent_colors[i].is_set) c = self->configured_transparent_colors + i; if (c) { RAII_PyObject(t, Py_BuildValue("Nf", alloc_color((c->color >> 16) & 0xff, (c->color >> 8) & 0xff, c->color & 0xff, 0), c->opacity)); if (!t) return NULL; if (PyList_Append(transparent_background_colors, t) != 0) return NULL; } } if (PyList_GET_SIZE(transparent_background_colors)) { RAII_PyObject(t, PyList_AsTuple(transparent_background_colors)); if (!t) return NULL; if (PyDict_SetItemString(ans, "transparent_background_colors", t) != 0) return NULL; } #undef D return Py_NewRef(ans); } static PyObject* as_color(ColorProfile *self, PyObject *val) { #define as_color_doc "Convert the specified terminal color into an (r, g, b) tuple based on the current profile values" if (!PyLong_Check(val)) { PyErr_SetString(PyExc_TypeError, "val must be an int"); return NULL; } unsigned long entry = PyLong_AsUnsignedLong(val); unsigned int t = entry & 0xFF; uint8_t r; uint32_t col = 0; switch(t) { case 1: r = (entry >> 8) & 0xff; col = self->color_table[r]; break; case 2: col = entry >> 8; break; default: Py_RETURN_NONE; } Color *ans = PyObject_New(Color, &Color_Type); if (ans) { ans->color.val = 0; ans->color.rgb = col; } return (PyObject*)ans; } static PyObject* reset_color_table(ColorProfile *self, PyObject *a UNUSED) { #define reset_color_table_doc "Reset all customized colors back to defaults" memcpy(self->color_table, self->orig_color_table, sizeof(FG_BG_256)); self->dirty = true; Py_RETURN_NONE; } static PyObject* reset_color(ColorProfile *self, PyObject *val) { #define reset_color_doc "Reset the specified color" uint8_t i = PyLong_AsUnsignedLong(val) & 0xff; self->color_table[i] = self->orig_color_table[i]; self->dirty = true; Py_RETURN_NONE; } static PyObject* set_color(ColorProfile *self, PyObject *args) { #define set_color_doc "Set the specified color" unsigned char i; unsigned long val; if (!PyArg_ParseTuple(args, "Bk", &i, &val)) return NULL; self->color_table[i] = val; self->dirty = true; Py_RETURN_NONE; } void copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size_t stride) { size_t i; stride = MAX(1u, stride); for (i = 0, buf = buf + offset; i < arraysz(self->color_table); i++, buf += stride) *buf = self->color_table[i]; // Copy the mark colors for (i = 0; i < arraysz(self->mark_backgrounds); i++) { *buf = self->mark_backgrounds[i]; buf += stride; } for (i = 0; i < arraysz(self->mark_foregrounds); i++) { *buf = self->mark_foregrounds[i]; buf += stride; } self->dirty = false; } static void push_onto_color_stack_at(ColorProfile *self, unsigned int i) { self->color_stack[i].dynamic_colors = self->overridden; memcpy(self->color_stack[i].transparent_colors, self->overriden_transparent_colors, sizeof(self->overriden_transparent_colors)); self->color_stack[i].dynamic_colors = self->overridden; memcpy(self->color_stack[i].color_table, self->color_table, sizeof(self->color_stack->color_table)); } static void copy_from_color_stack_at(ColorProfile *self, unsigned int i) { self->overridden = self->color_stack[i].dynamic_colors; memcpy(self->color_table, self->color_stack[i].color_table, sizeof(self->color_table)); memcpy(self->overriden_transparent_colors, self->color_stack[i].transparent_colors, sizeof(self->overriden_transparent_colors)); } bool colorprofile_push_colors(ColorProfile *self, unsigned int idx) { if (idx > 10) return false; size_t sz = idx ? idx : self->color_stack_idx + 1; sz = MIN(10u, sz); if (self->color_stack_sz < sz) { self->color_stack = realloc(self->color_stack, sz * sizeof(self->color_stack[0])); if (self->color_stack == NULL) fatal("Out of memory while ensuring space for %zu elements in color stack", sz); memset(self->color_stack + self->color_stack_sz, 0, (sz - self->color_stack_sz) * sizeof(self->color_stack[0])); self->color_stack_sz = sz; } if (idx == 0) { if (self->color_stack_idx >= self->color_stack_sz) { memmove(self->color_stack, self->color_stack + 1, (self->color_stack_sz - 1) * sizeof(self->color_stack[0])); idx = self->color_stack_sz - 1; } else idx = self->color_stack_idx++; push_onto_color_stack_at(self, idx); return true; } idx -= 1; if (idx < self->color_stack_sz) { push_onto_color_stack_at(self, idx); return true; } return false; } bool colorprofile_pop_colors(ColorProfile *self, unsigned int idx) { if (idx == 0) { if (!self->color_stack_idx) return false; copy_from_color_stack_at(self, --self->color_stack_idx); memset(self->color_stack + self->color_stack_idx, 0, sizeof(self->color_stack[0])); return true; } idx -= 1; if (idx < self->color_stack_sz) { copy_from_color_stack_at(self, idx); return true; } return false; } void colorprofile_report_stack(ColorProfile *self, unsigned int *idx, unsigned int *count) { *count = self->color_stack_idx; *idx = self->color_stack_idx ? self->color_stack_idx - 1 : 0; } static PyObject* color_table_address(ColorProfile *self, PyObject *a UNUSED) { #define color_table_address_doc "Pointer address to start of color table" return PyLong_FromVoidPtr((void*)self->color_table); } static PyObject* default_color_table(PyObject *self UNUSED, PyObject *args UNUSED) { return create_256_color_table(); } // Boilerplate {{{ #define CGETSET(name, nullable) \ static PyObject* name##_get(ColorProfile *self, void UNUSED *closure) { \ DynamicColor ans = colorprofile_to_color(self, self->overridden.name, self->configured.name); \ if (ans.type == COLOR_IS_SPECIAL) { \ if (nullable) Py_RETURN_NONE; \ return (PyObject*)alloc_color(0, 0, 0, 0); \ } \ return (PyObject*)alloc_color((ans.rgb >> 16) & 0xff, (ans.rgb >> 8) & 0xff, ans.rgb & 0xff, 0); \ } \ static int name##_set(ColorProfile *self, PyObject *v, void UNUSED *closure) { \ if (v == NULL) { self->overridden.name.val = 0; return 0; } \ if (PyLong_Check(v)) { \ unsigned long val = PyLong_AsUnsignedLong(v); \ self->overridden.name.rgb = val & 0xffffff; \ self->overridden.name.type = COLOR_IS_RGB; \ } else if (PyObject_TypeCheck(v, &Color_Type)) { \ Color *c = (Color*)v; self->overridden.name.rgb = c->color.rgb; self->overridden.name.type = COLOR_IS_RGB; \ } else if (v == Py_None) { \ if (!nullable) { PyErr_SetString(PyExc_TypeError, #name " cannot be set to None"); return -1; } \ self->overridden.name.type = COLOR_IS_SPECIAL; self->overridden.name.rgb = 0; \ } \ self->dirty = true; return 0; \ } CGETSET(default_fg, false) CGETSET(default_bg, false) CGETSET(cursor_color, true) CGETSET(cursor_text_color, true) CGETSET(highlight_fg, true) CGETSET(highlight_bg, true) CGETSET(visual_bell_color, true) #undef CGETSET static PyGetSetDef cp_getsetters[] = { GETSET(default_fg) GETSET(default_bg) GETSET(cursor_color) GETSET(cursor_text_color) GETSET(highlight_fg) GETSET(highlight_bg) GETSET(visual_bell_color) {NULL} /* Sentinel */ }; static PyMemberDef cp_members[] = { {NULL} }; static PyObject* reload_from_opts(ColorProfile *self, PyObject *args UNUSED) { PyObject *opts = global_state.options_object; if (!PyArg_ParseTuple(args, "|O", &opts)) return NULL; self->dirty = true; if (!set_configured_colors(self, opts)) return NULL; if (!set_mark_colors(self, opts)) return NULL; if (!set_colortable(self, opts)) return NULL; Py_RETURN_NONE; } static PyObject* get_transparent_background_color(ColorProfile *self, PyObject *index) { if (!PyLong_Check(index)) { PyErr_SetString(PyExc_TypeError, "index must be an int"); return NULL; } unsigned long idx = PyLong_AsUnsignedLong(index); if (PyErr_Occurred()) return NULL; if (idx >= arraysz(self->configured_transparent_colors)) Py_RETURN_NONE; TransparentDynamicColor *c = self->overriden_transparent_colors[idx].is_set ? self->overriden_transparent_colors + idx : self->configured_transparent_colors + idx; if (!c->is_set) Py_RETURN_NONE; float opacity = c->opacity >= 0 ? c->opacity : OPT(background_opacity); return (PyObject*)alloc_color((c->color >> 16) & 0xff, (c->color >> 8) & 0xff, c->color & 0xff, (unsigned)(255.f * opacity)); } static PyObject* set_transparent_background_color(ColorProfile *self, PyObject *const *args, Py_ssize_t nargs) { if (nargs < 1) { PyErr_SetString(PyExc_TypeError, "must specify index"); return NULL; } if (!PyLong_Check(args[0])) { PyErr_SetString(PyExc_TypeError, "index must be an int"); return NULL; } unsigned long idx = PyLong_AsUnsignedLong(args[0]); if (PyErr_Occurred()) return NULL; if (idx >= arraysz(self->configured_transparent_colors)) Py_RETURN_NONE; if (nargs < 2) { self->overriden_transparent_colors[idx].is_set = false; Py_RETURN_NONE; } if (!PyObject_TypeCheck(args[1], &Color_Type)) { PyErr_SetString(PyExc_TypeError, "color must be Color object"); return NULL; } Color *c = (Color*)args[1]; float opacity = (float)(c->color.alpha) / 255.f; if (nargs > 2 && PyFloat_Check(args[2])) opacity = (float)PyFloat_AsDouble(args[2]); self->overriden_transparent_colors[idx].is_set = true; self->overriden_transparent_colors[idx].color = c->color.rgb; self->overriden_transparent_colors[idx].opacity = MAX(-1.f, MIN(opacity, 1.f)); Py_RETURN_NONE; } static PyMethodDef cp_methods[] = { METHOD(reset_color_table, METH_NOARGS) METHOD(as_dict, METH_NOARGS) METHOD(basic_colors, METH_NOARGS) METHOD(color_table_address, METH_NOARGS) METHOD(as_color, METH_O) METHOD(reset_color, METH_O) METHOD(set_color, METH_VARARGS) METHODB(get_transparent_background_color, METH_O), METHODB(reload_from_opts, METH_VARARGS), {"set_transparent_background_color", (PyCFunction)(void(*)(void))set_transparent_background_color, METH_FASTCALL, ""}, {NULL} /* Sentinel */ }; PyTypeObject ColorProfile_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.ColorProfile", .tp_basicsize = sizeof(ColorProfile), .tp_dealloc = (destructor)dealloc_cp, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "ColorProfile", .tp_members = cp_members, .tp_methods = cp_methods, .tp_getset = cp_getsetters, .tp_new = new_cp, }; // }}} static Color* alloc_color(unsigned char r, unsigned char g, unsigned char b, unsigned a) { Color *self = (Color *)(&Color_Type)->tp_alloc(&Color_Type, 0); if (self != NULL) { self->color.r = r; self->color.g = g; self->color.b = b; self->color.a = a; } return self; } static PyObject * new_color(PyTypeObject *type UNUSED, PyObject *args, PyObject *kwds) { static const char* kwlist[] = {"red", "green", "blue", "alpha", NULL}; unsigned char r = 0, g = 0, b = 0, a = 0; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|BBBB", (char**)kwlist, &r, &g, &b, &a)) return NULL; return (PyObject*) alloc_color(r, g, b, a); } static PyObject* Color_as_int(Color *self) { return PyLong_FromUnsignedLong(self->color.val); } static PyObject* color_truediv(Color *self, PyObject *divisor) { RAII_PyObject(o, PyNumber_Float(divisor)); if (o == NULL) return NULL; double r = self->color.r, g = self->color.g, b = self->color.b, a = self->color.a; double d = PyFloat_AS_DOUBLE(o) * 255.; return Py_BuildValue("dddd", r/d, g/d, b/d, a/d); } static PyNumberMethods color_number_methods = { .nb_int = (unaryfunc)Color_as_int, .nb_true_divide = (binaryfunc)color_truediv, }; #define CGETSET(name) \ static PyObject* name##_get(Color *self, void UNUSED *closure) { return PyLong_FromUnsignedLong(self->color.name); } CGETSET(red) CGETSET(green) CGETSET(blue) CGETSET(alpha) #undef CGETSET static PyObject* rgb_get(Color *self, void *closure UNUSED) { return PyLong_FromUnsignedLong(self->color.rgb); } static PyObject* luminance_get(Color *self, void *closure UNUSED) { return PyFloat_FromDouble(rgb_luminance(self->color) / 255.0); } static PyObject* is_dark_get(Color *self, void *closure UNUSED) { if (rgb_luminance(self->color) / 255.0 < 0.5) Py_RETURN_TRUE; Py_RETURN_FALSE; } static PyObject* sgr_get(Color* self, void *closure UNUSED) { char buf[32]; int sz = snprintf(buf, sizeof(buf), ":2:%u:%u:%u", self->color.r, self->color.g, self->color.b); return PyUnicode_FromStringAndSize(buf, sz); } static PyObject* sharp_get(Color* self, void *closure UNUSED) { char buf[32]; int sz; if (self->color.alpha) sz = snprintf(buf, sizeof(buf), "#%02x%02x%02x%02x", self->color.a, self->color.r, self->color.g, self->color.b); else sz = snprintf(buf, sizeof(buf), "#%02x%02x%02x", self->color.r, self->color.g, self->color.b); return PyUnicode_FromStringAndSize(buf, sz); } static PyObject* color_cmp(PyObject *self, PyObject *other, int op) { if (op != Py_EQ && op != Py_NE) return Py_NotImplemented; if (!PyObject_TypeCheck(other, &Color_Type)) { if (op == Py_EQ) Py_RETURN_FALSE; Py_RETURN_TRUE; } Color *a = (Color*)self, *b = (Color*)other; switch (op) { case Py_EQ: { if (a->color.val == b->color.val) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } case Py_NE: { if (a->color.val != b->color.val) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } default: return Py_NotImplemented; } } static PyGetSetDef color_getsetters[] = { {"rgb", (getter) rgb_get, NULL, "rgb", NULL}, {"red", (getter) red_get, NULL, "red", NULL}, {"green", (getter) green_get, NULL, "green", NULL}, {"blue", (getter) blue_get, NULL, "blue", NULL}, {"alpha", (getter) alpha_get, NULL, "alpha", NULL}, {"r", (getter) red_get, NULL, "red", NULL}, {"g", (getter) green_get, NULL, "green", NULL}, {"b", (getter) blue_get, NULL, "blue", NULL}, {"a", (getter) alpha_get, NULL, "alpha", NULL}, {"luminance", (getter) luminance_get, NULL, "luminance", NULL}, {"as_sgr", (getter) sgr_get, NULL, "as_sgr", NULL}, {"as_sharp", (getter) sharp_get, NULL, "as_sharp", NULL}, {"is_dark", (getter) is_dark_get, NULL, "is_dark", NULL}, {NULL} /* Sentinel */ }; static PyObject* contrast(Color* self, PyObject *o) { if (!PyObject_TypeCheck(o, &Color_Type)) { PyErr_SetString(PyExc_TypeError, "Not a Color"); return NULL; } Color *other = (Color*) o; return PyFloat_FromDouble(rgb_contrast(self->color, other->color)); } static PyMethodDef color_methods[] = { METHODB(contrast, METH_O), {NULL} /* Sentinel */ }; static PyObject * repr(Color *self) { if (self->color.alpha) return PyUnicode_FromFormat("Color(red=%u, green=%u, blue=%u, alpha=%u)", self->color.r, self->color.g, self->color.b, self->color.a); return PyUnicode_FromFormat("Color(%u, %u, %u)", self->color.r, self->color.g, self->color.b); } static Py_hash_t color_hash(PyObject *x) { return ((Color*)x)->color.val; } PyTypeObject Color_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kitty.fast_data_types.Color", .tp_basicsize = sizeof(Color), .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "Color", .tp_new = new_color, .tp_getset = color_getsetters, .tp_as_number = &color_number_methods, .tp_methods = color_methods, .tp_repr = (reprfunc)repr, .tp_hash = color_hash, .tp_richcompare = color_cmp, }; static PyMethodDef module_methods[] = { METHODB(default_color_table, METH_NOARGS), METHODB(patch_color_profiles, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; int init_ColorProfile(PyObject *module) {\ if (PyType_Ready(&ColorProfile_Type) < 0) return 0; if (PyModule_AddObject(module, "ColorProfile", (PyObject *)&ColorProfile_Type) != 0) return 0; Py_INCREF(&ColorProfile_Type); if (PyType_Ready(&Color_Type) < 0) return 0; if (PyModule_AddObject(module, "Color", (PyObject *)&Color_Type) != 0) return 0; Py_INCREF(&Color_Type); if (PyModule_AddFunctions(module, module_methods) != 0) return false; return 1; } // }}} kitty-0.41.1/kitty/colors.h0000664000175000017510000000320314773370543015123 0ustar nileshnilesh/* * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" typedef union ARGB32 { color_type val; struct { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ uint8_t b: 8; uint8_t g: 8; uint8_t r: 8; uint8_t a: 8; #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ uint8_t a: 8; uint8_t r: 8; uint8_t g: 8; uint8_t b: 8; #else #error "Unsupported endianness" #endif }; struct { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ uint8_t blue: 8; uint8_t green: 8; uint8_t red: 8; uint8_t alpha: 8; #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ uint8_t alpha: 8; uint8_t red: 8; uint8_t green: 8; uint8_t blue: 8; #else #error "Unsupported endianness" #endif }; struct { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ color_type rgb: 24; uint8_t _ignore_me: 8; #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ uint8_t _ignore_me: 8; color_type rgb: 24; #else #error "Unsupported endianness" #endif }; } ARGB32; typedef struct { PyObject_HEAD ARGB32 color; } Color; extern PyTypeObject ColorProfile_Type; extern PyTypeObject Color_Type; static inline double rgb_luminance(ARGB32 c) { // From ITU BT 601 https://www.itu.int/rec/R-REC-BT.601 return 0.299 * c.red + 0.587 * c.green + 0.114 * c.blue; } static inline double rgb_contrast(ARGB32 a, ARGB32 b) { double al = rgb_luminance(a), bl = rgb_luminance(b); if (al < bl) SWAP(al, bl); return (al + 0.05) / (bl + 0.05); } kitty-0.41.1/kitty/colors.py0000664000175000017510000002131014773370543015323 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2024, Kovid Goyal import os from collections.abc import Iterable, Sequence from contextlib import suppress from enum import Enum from typing import Literal, Optional from .config import parse_config from .constants import config_dir from .fast_data_types import Color, get_boss, get_options, glfw_get_system_color_theme, patch_color_profiles, patch_global_colors, set_os_window_chrome from .options.types import Options, nullable_colors from .rgb import color_from_int from .typing import WindowType ColorsSpec = dict[str, Optional[int]] TransparentBackgroundColors = tuple[tuple[Color, float], ...] ColorSchemes = Literal['light', 'dark', 'no_preference'] Colors = tuple[ColorsSpec, TransparentBackgroundColors] class ThemeFile(Enum): dark = 'dark-theme.auto.conf' light = 'light-theme.auto.conf' no_preference = 'no-preference-theme.auto.conf' class ThemeColors: dark_mtime: int = -1 light_mtime: int = -1 no_preference_mtime: int = -1 applied_theme: Literal['light', 'dark', 'no_preference', ''] = '' default_colors: ColorsSpec | None = None def get_default_colors(self) -> ColorsSpec: if self.default_colors is None: from kitty.options.types import defaults, option_names ans: ColorsSpec = dict.fromkeys(nullable_colors) for name in option_names: defval = getattr(defaults, name) if isinstance(defval, Color): ans[name] = int(defval) self.default_colors = ans return self.default_colors def parse_colors(self, f: Iterable[str]) -> Colors: # When parsing the theme file we first apply the default theme so that # all colors are reset to default values first. This is needed for themes # that don't specify all colors. spec, tbc = parse_colors((f,)) dc_spec = self.get_default_colors() ans = dc_spec.copy() ans.update(spec) return ans, tbc def refresh(self) -> bool: found = False with suppress(FileNotFoundError): for x in os.scandir(config_dir): if x.name == ThemeFile.dark.value: mtime = x.stat().st_mtime_ns if mtime > self.dark_mtime: with open(x.path) as f: self.dark_spec, self.dark_tbc = self.parse_colors(f) self.dark_mtime = mtime found = True elif x.name == ThemeFile.light.value: mtime = x.stat().st_mtime_ns if mtime > self.light_mtime: with open(x.path) as f: self.light_spec, self.light_tbc = self.parse_colors(f) self.light_mtime = mtime found = True elif x.name == ThemeFile.no_preference.value: mtime = x.stat().st_mtime_ns if mtime > self.no_preference_mtime: with open(x.path) as f: self.no_preference_spec, self.no_preference_tbc = self.parse_colors(f) self.no_preference_mtime = mtime found = True return found @property def has_dark_theme(self) -> bool: return self.dark_mtime > -1 @property def has_light_theme(self) -> bool: return self.light_mtime > -1 @property def has_no_preference_theme(self) -> bool: return self.no_preference_mtime > -1 def patch_opts(self, opts: Options, debug_rendering: bool = False) -> None: from .utils import log_error if debug_rendering: log_error('Querying system for current color scheme') which = glfw_get_system_color_theme() if debug_rendering: log_error('Current system color scheme:', which) cols: Colors | None = None if which == 'dark' and self.has_dark_theme: cols = self.dark_spec, self.dark_tbc elif which == 'light' and self.has_light_theme: cols = self.light_spec, self.light_tbc elif which == 'no_preference' and self.has_no_preference_theme: cols = self.no_preference_spec, self.no_preference_tbc if cols is not None: patch_options_with_color_spec(opts, *cols) patch_global_colors(cols[0], True) self.applied_theme = which if debug_rendering: log_error(f'Applied {self.applied_theme} color theme') def on_system_color_scheme_change(self, new_value: ColorSchemes, is_initial_value: bool = False) -> bool: if is_initial_value: return False self.refresh() return self.apply_theme(new_value) def apply_theme(self, new_value: ColorSchemes, notify_on_bg_change: bool = True) -> bool: from .utils import log_error boss = get_boss() if new_value == 'dark' and self.has_dark_theme: patch_colors(self.dark_spec, self.dark_tbc, True, notify_on_bg_change=notify_on_bg_change) self.applied_theme = new_value if boss.args.debug_rendering: log_error(f'Applied color theme {new_value}') return True if new_value == 'light' and self.has_light_theme: patch_colors(self.light_spec, self.light_tbc, True, notify_on_bg_change=notify_on_bg_change) self.applied_theme = new_value if boss.args.debug_rendering: log_error(f'Applied color theme {new_value}') return True if new_value == 'no_preference' and self.has_no_preference_theme: patch_colors(self.no_preference_spec, self.no_preference_tbc, True, notify_on_bg_change=notify_on_bg_change) self.applied_theme = new_value if boss.args.debug_rendering: log_error(f'Applied color theme {new_value}') return True return False theme_colors = ThemeColors() def parse_colors(args: Iterable[str | Iterable[str]]) -> Colors: colors: dict[str, Color | None] = {} nullable_color_map: dict[str, int | None] = {} transparent_background_colors = () for spec in args: if isinstance(spec, str): if '=' in spec: conf = parse_config((spec.replace('=', ' '),)) else: with open(os.path.expanduser(spec), encoding='utf-8', errors='replace') as f: conf = parse_config(f) else: conf = parse_config(spec) transparent_background_colors = conf.pop('transparent_background_colors', ()) colors.update(conf) for k in nullable_colors: q = colors.pop(k, False) if q is not False: val = int(q) if isinstance(q, Color) else None nullable_color_map[k] = val ans: dict[str, int | None] = {k: int(v) for k, v in colors.items() if isinstance(v, Color)} ans.update(nullable_color_map) return ans, transparent_background_colors def patch_options_with_color_spec(opts: Options, spec: ColorsSpec, transparent_background_colors: TransparentBackgroundColors) -> None: for k, v in spec.items(): if hasattr(opts, k): if v is None: if k in nullable_colors: setattr(opts, k, None) else: setattr(opts, k, color_from_int(v)) opts.transparent_background_colors = transparent_background_colors def patch_colors( spec: ColorsSpec, transparent_background_colors: TransparentBackgroundColors, configured: bool = False, windows: Sequence[WindowType] | None = None, notify_on_bg_change: bool = True, ) -> None: boss = get_boss() if windows is None: windows = tuple(boss.all_windows) bg_colors_before = {w.id: w.screen.color_profile.default_bg for w in windows} profiles = tuple(w.screen.color_profile for w in windows if w) patch_color_profiles(spec, transparent_background_colors, profiles, configured) opts = get_options() if configured: patch_options_with_color_spec(opts, spec, transparent_background_colors) for tm in get_boss().all_tab_managers: tm.tab_bar.patch_colors(spec) tm.tab_bar.layout() tm.mark_tab_bar_dirty() t = tm.active_tab if t is not None: t.relayout_borders() set_os_window_chrome(tm.os_window_id) patch_global_colors(spec, configured) default_bg_changed = 'background' in spec notify_bg = notify_on_bg_change and default_bg_changed boss = get_boss() for w in windows: if w: if notify_bg and w.screen.color_profile.default_bg != bg_colors_before.get(w.id): boss.default_bg_changed_for(w.id) w.refresh() kitty-0.41.1/kitty/conf/0000775000175000017510000000000014773370543014400 5ustar nileshnileshkitty-0.41.1/kitty/conf/__init__.py0000664000175000017510000000000014773370543016477 0ustar nileshnileshkitty-0.41.1/kitty/conf/generate.py0000664000175000017510000006327514773370543016561 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import inspect import os import re import textwrap from collections.abc import Callable, Iterator from typing import Any, get_type_hints from kitty.conf.types import Definition, MultiOption, Option, ParserFuncType, unset from kitty.types import _T def chunks(lst: list[_T], n: int) -> Iterator[list[_T]]: for i in range(0, len(lst), n): yield lst[i:i + n] def atoi(text: str) -> str: return f'{int(text):08d}' if text.isdigit() else text def natural_keys(text: str) -> tuple[str, ...]: return tuple(atoi(c) for c in re.split(r'(\d+)', text)) def generate_class(defn: Definition, loc: str) -> tuple[str, str]: class_lines: list[str] = [] tc_lines: list[str] = [] a = class_lines.append t = tc_lines.append a('class Options:') t('class Parser:') choices = {} imports: set[tuple[str, str]] = set() tc_imports: set[tuple[str, str]] = set() ki_imports: 're.Pattern[str]' = re.compile(r'\b((?:kittens|kitty).+?)[,\]]') def option_type_as_str(x: Any) -> str: needs_import = False if type(x) is type: ans = x.__name__ needs_import = True else: ans = repr(x) ans = ans.replace('NoneType', 'None') if needs_import and getattr(x, '__module__', None) and x.__module__ not in ('builtins', 'typing'): imports.add((x.__module__, x.__name__)) return ans def option_type_data(option: Option | MultiOption) -> tuple[Callable[[Any], Any], str]: func = option.parser_func if func.__module__ == 'builtins': return func, func.__name__ th = get_type_hints(func) rettype = th['return'] typ = option_type_as_str(rettype) if isinstance(option, MultiOption): typ = typ[typ.index('[') + 1:-1] typ = typ.replace('tuple', 'dict', 1) kq = ki_imports.search(typ) if kq is not None: kqi = kq.group(1) kqim, kqii = kqi.rsplit('.', 1) imports.add((kqim, '')) return func, typ is_mutiple_vars = {} option_names = set() color_table = list(map(str, range(256))) choice_dedup: dict[str, str] = {} choice_parser_dedup: dict[str, str] = {} def parser_function_declaration(option_name: str) -> None: t('') t(f' def {option_name}(self, val: str, ans: dict[str, typing.Any]) -> None:') for option in sorted(defn.iter_all_options(), key=lambda a: natural_keys(a.name)): option_names.add(option.name) parser_function_declaration(option.name) if isinstance(option, MultiOption): mval: dict[str, dict[str, Any]] = {'macos': {}, 'linux': {}, '': {}} func, typ = option_type_data(option) for val in option: if val.add_to_default: gr = mval[val.only] for k, v in func(val.defval_as_str): gr[k] = v is_mutiple_vars[option.name] = typ, mval sig = inspect.signature(func) tc_imports.add((func.__module__, func.__name__)) if len(sig.parameters) == 1: t(f' for k, v in {func.__name__}(val):') t(f' ans["{option.name}"][k] = v') else: t(f' for k, v in {func.__name__}(val, ans["{option.name}"]):') t(f' ans["{option.name}"][k] = v') continue if option.choices: typ = 'typing.Literal[{}]'.format(', '.join(repr(x) for x in option.choices)) ename = f'choices_for_{option.name}' if typ in choice_dedup: typ = choice_dedup[typ] else: choice_dedup[typ] = ename choices[ename] = typ typ = ename func = str elif defn.has_color_table and option.is_color_table_color: func, typ = option_type_data(option) t(f' ans[{option.name!r}] = {func.__name__}(val)') tc_imports.add((func.__module__, func.__name__)) cnum = int(option.name[5:]) color_table[cnum] = f'0x{func(option.defval_as_string).__int__():06x}' continue else: func, typ = option_type_data(option) try: params = dict(inspect.signature(func).parameters) except Exception: params = {} if 'dict_with_parse_results' in params: t(f' {func.__name__}(val, ans)') else: t(f' ans[{option.name!r}] = {func.__name__}(val)') if func.__module__ != 'builtins': tc_imports.add((func.__module__, func.__name__)) defval_as_obj = func(option.defval_as_string) if isinstance(defval_as_obj, frozenset): defval = 'frozenset({' + ', '.join(repr(x) for x in sorted(defval_as_obj)) + '})' else: defval = repr(defval_as_obj) if option.macos_defval is not unset: md = repr(func(option.macos_defval)) defval = f'{md} if is_macos else {defval}' imports.add(('kitty.constants', 'is_macos')) a(f' {option.name}: {typ} = {defval}') if option.choices: ecname = f'choices_for_{option.name}' crepr = f'frozenset({option.choices!r})' if crepr in choice_parser_dedup: crepr = choice_parser_dedup[crepr] else: choice_parser_dedup[crepr] = ecname t(' val = val.lower()') t(f' if val not in self.choices_for_{option.name}:') t(f' raise ValueError(f"The value {{val}} is not a valid choice for {option.name}")') t(f' ans["{option.name}"] = val') t('') t(f' {ecname} = {crepr}') for option_name, (typ, mval) in is_mutiple_vars.items(): a(f' {option_name}: {typ} = ' '{}') for parser, aliases in defn.deprecations.items(): for alias in aliases: parser_function_declaration(alias) tc_imports.add((parser.__module__, parser.__name__)) t(f' {parser.__name__}({alias!r}, val, ans)') action_parsers = {} def resolve_import(ftype: str) -> str: if '.' in ftype: fmod, ftype = ftype.rpartition('.')[::2] else: fmod = f'{loc}.options.utils' imports.add((fmod, ftype)) return ftype for aname, action in defn.actions.items(): option_names.add(aname) action_parsers[aname] = func = action.parser_func th = get_type_hints(func) rettype = th['return'] typ = option_type_as_str(rettype) typ = typ[typ.index('[') + 1:-1] a(f' {aname}: list[{typ}] = []') for imp in action.imports: resolve_import(imp) for fname, ftype in action.fields.items(): ftype = resolve_import(ftype) fval = f'{ftype}()' if ftype == 'AliasMap' else '{}' a(f' {fname}: {ftype} = {fval}') parser_function_declaration(aname) t(f' for k in {func.__name__}(val):') t(f' ans[{aname!r}].append(k)') tc_imports.add((func.__module__, func.__name__)) if defn.has_color_table: imports.add(('array', 'array')) a(' color_table: "array[int]" = array("L", (') for grp in chunks(color_table, 8): a(' ' + ', '.join(grp) + ',') a(' ))') a(' config_paths: tuple[str, ...] = ()') a(' all_config_paths: tuple[str, ...] = ()') a(' config_overrides: tuple[str, ...] = ()') a('') a(' def __init__(self, options_dict: dict[str, typing.Any] | None = None) -> None:') if defn.has_color_table: a(' self.color_table = array(self.color_table.typecode, self.color_table)') a(' if options_dict is not None:') a(' null = object()') a(' for key in option_names:') a(' val = options_dict.get(key, null)') a(' if val is not null:') a(' setattr(self, key, val)') a('') a(' @property') a(' def _fields(self) -> tuple[str, ...]:') a(' return option_names') a('') a(' def __iter__(self) -> typing.Iterator[str]:') a(' return iter(self._fields)') a('') a(' def __len__(self) -> int:') a(' return len(self._fields)') a('') a(' def _copy_of_val(self, name: str) -> typing.Any:') a(' ans = getattr(self, name)') a(' if isinstance(ans, dict):\n ans = ans.copy()') a(' elif isinstance(ans, list):\n ans = ans[:]') a(' return ans') a('') a(' def _asdict(self) -> dict[str, typing.Any]:') a(' return {k: self._copy_of_val(k) for k in self}') a('') a(' def _replace(self, **kw: typing.Any) -> "Options":') a(' ans = Options()') a(' for name in self:') a(' setattr(ans, name, self._copy_of_val(name))') a(' for name, val in kw.items():') a(' setattr(ans, name, val)') a(' return ans') a('') a(' def __getitem__(self, key: int | str) -> typing.Any:') a(' k = option_names[key] if isinstance(key, int) else key') a(' try:') a(' return getattr(self, k)') a(' except AttributeError:') a(' pass') a(' raise KeyError(f"No option named: {k}")') if defn.has_color_table: a('') a(' def __getattr__(self, key: str) -> typing.Any:') a(' if key.startswith("color"):') a(' q = key[5:]') a(' if q.isdigit():') a(' k = int(q)') a(' if 0 <= k <= 255:') a(' x = self.color_table[k]') a(' return Color((x >> 16) & 255, (x >> 8) & 255, x & 255)') a(' raise AttributeError(key)') a('') a(' def __setattr__(self, key: str, val: typing.Any) -> typing.Any:') a(' if key.startswith("color"):') a(' q = key[5:]') a(' if q.isdigit():') a(' k = int(q)') a(' if 0 <= k <= 255:') a(' self.color_table[k] = int(val)') a(' return') a(' object.__setattr__(self, key, val)') a('') a('') a('defaults = Options()') a('') for option_name, (typ, mval) in is_mutiple_vars.items(): a(f'defaults.{option_name} = {mval[""]!r}') if mval['macos']: imports.add(('kitty.constants', 'is_macos')) a('if is_macos:') a(f' defaults.{option_name}.update({mval["macos"]!r}') if mval['macos']: imports.add(('kitty.constants', 'is_macos')) a('if not is_macos:') a(f' defaults.{option_name}.update({mval["linux"]!r}') a('') for aname, func in action_parsers.items(): a(f'defaults.{aname} = [') only: dict[str, list[tuple[str, Callable[..., Any]]]] = {} for sc in defn.iter_all_maps(aname): if not sc.add_to_default: continue text = sc.parseable_text if sc.only: only.setdefault(sc.only, []).append((text, func)) else: for val in func(text): a(f' # {sc.name}') a(f' {val!r},') a(']') a('') if only: imports.add(('kitty.constants', 'is_macos')) for cond, items in only.items(): cond = 'is_macos' if cond == 'macos' else 'not is_macos' a(f'if {cond}:') for (text, parser_func) in items: for val in parser_func(text): a(f' defaults.{aname}.append({val!r})') a('') t('') t('') t('def create_result_dict() -> dict[str, typing.Any]:') t(' return {') for oname in is_mutiple_vars: t(f' {oname!r}: {{}},') for aname in defn.actions: t(f' {aname!r}: [],') t(' }') t('') t('') t(f'actions: frozenset[str] = frozenset({tuple(defn.actions)!r})') t('') t('') t('def merge_result_dicts(defaults: dict[str, typing.Any], vals: dict[str, typing.Any]) -> dict[str, typing.Any]:') t(' ans = {}') t(' for k, v in defaults.items():') t(' if isinstance(v, dict):') t(' ans[k] = merge_dicts(v, vals.get(k, {}))') t(' elif k in actions:') t(' ans[k] = v + vals.get(k, [])') t(' else:') t(' ans[k] = vals.get(k, v)') t(' return ans') tc_imports.add(('kitty.conf.utils', 'merge_dicts')) t('') t('') t('parser = Parser()') t('') t('') t('def parse_conf_item(key: str, val: str, ans: dict[str, typing.Any]) -> bool:') t(' func = getattr(parser, key, None)') t(' if func is not None:') t(' func(val, ans)') t(' return True') t(' return False') preamble = ['# generated by gen-config.py DO NOT edit', ''] a = preamble.append def output_imports(imports: set[tuple[str, str]], add_module_imports: bool = True) -> None: a('# isort: skip_file') a('import typing') a('import collections.abc # noqa: F401, RUF100') seen_mods = {'typing'} mmap: dict[str, list[str]] = {} for mod, name in imports: mmap.setdefault(mod, []).append(name) for mod in sorted(mmap): names = list(filter(None, sorted(mmap[mod]))) if names: lines = textwrap.wrap(', '.join(names), 100) if len(lines) == 1: s = lines[0] else: s = '\n '.join(lines) s = f'(\n {s}\n)' a(f'from {mod} import {s}') else: s = '' if add_module_imports and mod not in seen_mods and mod != s: a(f'import {mod}') seen_mods.add(mod) output_imports(imports) a('') if choices: for name, cdefn in choices.items(): a(f'{name} = {cdefn}') a('') a('option_names = (') for option_name in sorted(option_names, key=natural_keys): a(f' {option_name!r},') a(')') class_def = '\n'.join(preamble + ['', ''] + class_lines) preamble = ['# generated by gen-config.py DO NOT edit', ''] a = preamble.append output_imports(tc_imports, False) return class_def, '\n'.join(preamble + ['', ''] + tc_lines) def generate_c_conversion(loc: str, ctypes: list[Option | MultiOption]) -> str: lines: list[str] = [] basic_converters = { 'int': 'PyLong_AsLong', 'uint': 'PyLong_AsUnsignedLong', 'bool': 'PyObject_IsTrue', 'float': 'PyFloat_AsFloat', 'double': 'PyFloat_AsDouble', 'percent': 'percent', 'time': 'parse_s_double_to_monotonic_t', 'time-ms': 'parse_ms_long_to_monotonic_t' } for opt in ctypes: lines.append('') lines.append(f'static void\nconvert_from_python_{opt.name}(PyObject *val, Options *opts) ''{') is_special = opt.ctype.startswith('!') if is_special: func = opt.ctype[1:] lines.append(f' {func}(val, opts);') else: func = basic_converters.get(opt.ctype, opt.ctype) lines.append(f' opts->{opt.name} = {func}(val);') lines.append('}') lines.append('') lines.append(f'static void\nconvert_from_opts_{opt.name}(PyObject *py_opts, Options *opts) ''{') lines.append(f' PyObject *ret = PyObject_GetAttrString(py_opts, "{opt.name}");') lines.append(' if (ret == NULL) return;') lines.append(f' convert_from_python_{opt.name}(ret, opts);') lines.append(' Py_DECREF(ret);') lines.append('}') lines.append('') lines.append('static bool\nconvert_opts_from_python_opts(PyObject *py_opts, Options *opts) ''{') for opt in ctypes: lines.append(f' convert_from_opts_{opt.name}(py_opts, opts);') lines.append(' if (PyErr_Occurred()) return false;') lines.append(' return true;') lines.append('}') preamble = ['// generated by gen-config.py DO NOT edit', '// vim:fileencoding=utf-8', '#pragma once', '#include "to-c.h"'] return '\n'.join(preamble + ['', ''] + lines) def write_output(loc: str, defn: Definition, extra_after_type_defn: str = '') -> None: cls, tc = generate_class(defn, loc) ctypes = [] has_secret = [] for opt in defn.root_group.iter_all_non_groups(): if isinstance(opt, (Option, MultiOption)) and opt.ctype: ctypes.append(opt) if getattr(opt, 'has_secret', False): has_secret.append(opt.name) with open(os.path.join(*loc.split('.'), 'options', 'types.py'), 'w') as f: f.write(f'{cls}\n') f.write(extra_after_type_defn) if has_secret: f.write('\n\nsecret_options = ' + repr(tuple(has_secret))) with open(os.path.join(*loc.split('.'), 'options', 'parse.py'), 'w') as f: f.write(f'{tc}\n') if ctypes: c = generate_c_conversion(loc, ctypes) with open(os.path.join(*loc.split('.'), 'options', 'to-c-generated.h'), 'w') as f: f.write(f'{c}\n') def go_type_data(parser_func: ParserFuncType, ctype: str, is_multiple: bool = False) -> tuple[str, str]: if ctype: if ctype == 'string': if is_multiple: return 'string', '[]string{val}, nil' return 'string', 'val, nil' if ctype.startswith('strdict_'): _, rsep, fsep = ctype.split('_', 2) return 'map[string]string', f'config.ParseStrDict(val, `{rsep}`, `{fsep}`)' return f'*{ctype}', f'Parse{ctype}(val)' p = parser_func.__name__ if p == 'int': return 'int64', 'strconv.ParseInt(val, 10, 64)' if p == 'str': return 'string', 'val, nil' if p == 'float': return 'float64', 'strconv.ParseFloat(val, 10, 64)' if p == 'to_bool': return 'bool', 'config.StringToBool(val), nil' if p == 'to_color': return 'style.RGBA', 'style.ParseColor(val)' if p == 'to_color_or_none': return 'style.NullableColor', 'style.ParseColorOrNone(val)' if p == 'positive_int': return 'uint64', 'strconv.ParseUint(val, 10, 64)' if p == 'positive_float': return 'float64', 'config.PositiveFloat(val, 10, 64)' if p == 'unit_float': return 'float64', 'config.UnitFloat(val, 10, 64)' if p == 'python_string': return 'string', 'config.StringLiteral(val)' th = get_type_hints(parser_func) rettype = th['return'] return {int: 'int64', str: 'string', float: 'float64'}[rettype], f'{p}(val)' mod_map = { "shift": "shift", "⇧": "shift", "alt": "alt", "option": "alt", "opt": "alt", "⌥": "alt", "super": "super", "command": "super", "cmd": "super", "⌘": "super", "control": "ctrl", "ctrl": "ctrl", "⌃": "ctrl", "hyper": "hyper", "meta": "meta", "num_lock": "num_lock", "caps_lock": "caps_lock", } def normalize_shortcut(spec: str) -> str: if spec.endswith('+'): spec = spec[:-1] + 'plus' parts = spec.lower().split('+') key = parts[-1] if len(parts) == 1: return key mods = parts[:-1] return '+'.join(mod_map.get(x, x) for x in mods) + '+' + key def normalize_shortcuts(spec: str) -> Iterator[str]: spec = spec.replace('++', '+plus') spec = re.sub(r'([^+])>', '\\1\0', spec) for x in spec.split('\0'): yield normalize_shortcut(x) def gen_go_code(defn: Definition) -> str: lines = ['import "fmt"', 'import "strconv"', 'import "kitty/tools/config"', 'import "kitty/tools/utils/style"', 'var _ = fmt.Println', 'var _ = config.StringToBool', 'var _ = strconv.Atoi', 'var _ = style.ParseColor'] a = lines.append keyboard_shortcuts = tuple(defn.iter_all_maps()) choices = {} go_types = {} go_parsers = {} defaults = {} multiopts = {''} for option in sorted(defn.iter_all_options(), key=lambda a: natural_keys(a.name)): name = option.name.capitalize() if isinstance(option, MultiOption): go_types[name], go_parsers[name] = go_type_data(option.parser_func, option.ctype, True) multiopts.add(name) else: defaults[name] = option.parser_func(option.defval_as_string) if option.choices: choices[name] = option.choices go_types[name] = f'{name}_Choice_Type' go_parsers[name] = f'Parse_{name}(val)' continue go_types[name], go_parsers[name] = go_type_data(option.parser_func, option.ctype) for oname in choices: a(f'type {go_types[oname]} int') a('type Config struct {') for name, gotype in go_types.items(): if name in multiopts: a(f'{name} []{gotype}') else: a(f'{name} {gotype}') if keyboard_shortcuts: a('KeyboardShortcuts []*config.KeyAction') a('}') def cval(x: str) -> str: return x.replace('-', '_') a('func NewConfig() *Config {') a('return &Config{') from kitty.cli import serialize_as_go_string from kitty.fast_data_types import Color for name, pname in go_parsers.items(): if name in multiopts: continue d = defaults[name] if not d: continue if isinstance(d, str): dval = f'{name}_{cval(d)}' if name in choices else f'`{d}`' elif isinstance(d, bool): dval = repr(d).lower() elif isinstance(d, dict): dval = 'map[string]string{' for k, v in d.items(): dval += f'"{serialize_as_go_string(k)}": "{serialize_as_go_string(v)}",' dval += '}' elif isinstance(d, Color): dval = f'style.RGBA{{Red:{d.red}, Green: {d.green}, Blue: {d.blue}}}' if 'NullableColor' in go_types[name]: dval = f'style.NullableColor{{IsSet: true, Color:{dval}}}' else: dval = repr(d) a(f'{name}: {dval},') if keyboard_shortcuts: a('KeyboardShortcuts: []*config.KeyAction{') for sc in keyboard_shortcuts: aname, aargs = map(serialize_as_go_string, sc.action_def.partition(' ')[::2]) a('{'f'Name: "{aname}", Args: "{aargs}", Normalized_keys: []string''{') ns = normalize_shortcuts(sc.key_text) a(', '.join(f'"{serialize_as_go_string(x)}"' for x in ns) + ',') a('}''},') a('},') a('}''}') for oname, choice_vals in choices.items(): a('const (') for i, c in enumerate(choice_vals): c = cval(c) if i == 0: a(f'{oname}_{c} {oname}_Choice_Type = iota') else: a(f'{oname}_{c}') a(')') a(f'func (x {oname}_Choice_Type) String() string'' {') a('switch x {') a('default: return ""') for c in choice_vals: a(f'case {oname}_{cval(c)}: return "{c}"') a('}''}') a(f'func {go_parsers[oname].split("(")[0]}(val string) (ans {go_types[oname]}, err error) ''{') a('switch val {') for c in choice_vals: a(f'case "{c}": return {oname}_{cval(c)}, nil') vals = ', '.join(choice_vals) a(f'default: return ans, fmt.Errorf("%#v is not a valid value for %s. Valid values are: %s", val, "{c}", "{vals}")') a('}''}') a('func (c *Config) Parse(key, val string) (err error) {') a('switch key {') a('default: return fmt.Errorf("Unknown configuration key: %#v", key)') for oname, pname in go_parsers.items(): ol = oname.lower() is_multiple = oname in multiopts a(f'case "{ol}":') if is_multiple: a(f'var temp_val []{go_types[oname]}') else: a(f'var temp_val {go_types[oname]}') a(f'temp_val, err = {pname}') a(f'if err != nil {{ return fmt.Errorf("Failed to parse {ol} = %#v with error: %w", val, err) }}') if is_multiple: a(f'c.{oname} = append(c.{oname}, temp_val...)') else: a(f'c.{oname} = temp_val') if keyboard_shortcuts: a('case "map":') a('tempsc, err := config.ParseMap(val)') a('if err != nil { return fmt.Errorf("Failed to parse map = %#v with error: %w", val, err) }') a('c.KeyboardShortcuts = append(c.KeyboardShortcuts, tempsc)') a('}') a('return}') return '\n'.join(lines) def main() -> None: # To use run it as: # kitty +runpy 'from kitty.conf.generate import main; main()' /path/to/kitten/file.py import importlib import sys from kittens.runner import path_to_custom_kitten, resolved_kitten from kitty.constants import config_dir kitten = sys.argv[-1] if not kitten.endswith('.py'): kitten += '.py' kitten = resolved_kitten(kitten) path = os.path.realpath(path_to_custom_kitten(config_dir, kitten)) if not os.path.dirname(path): raise SystemExit(f'No custom kitten named {kitten} found') sys.path.insert(0, os.path.dirname(path)) package_name = os.path.basename(os.path.dirname(path)) m = importlib.import_module('kitten_options_definition') defn = getattr(m, 'definition') loc = package_name cls, tc = generate_class(defn, loc) with open(os.path.join(os.path.dirname(path), 'kitten_options_types.py'), 'w') as f: f.write(f'{cls}\n') with open(os.path.join(os.path.dirname(path), 'kitten_options_parse.py'), 'w') as f: f.write(f'{tc}\n') kitty-0.41.1/kitty/conf/types.py0000664000175000017510000006555214773370543016133 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import builtins import re import textwrap import typing from collections.abc import Callable, Iterable, Iterator from functools import lru_cache from importlib import import_module from re import Match from typing import Any, Optional, Union, cast import kitty.conf.utils as generic_parsers from kitty.constants import website_url from kitty.types import run_once if typing.TYPE_CHECKING: Only = typing.Literal['macos', 'linux', ''] else: Only = str class Unset: def __bool__(self) -> bool: return False unset = Unset() ParserFuncType = Callable[[str], Any] def expand_opt_references(conf_name: str, text: str) -> str: conf_name += '.' def expand(m: 'Match[str]') -> str: ref = m.group(1) if '<' not in ref and '.' not in ref: # full ref return f':opt:`{ref} <{conf_name}{ref}>`' return str(m.group()) return re.sub(r':opt:`(.+?)`', expand, text) @run_once def ref_map() -> dict[str, dict[str, str]]: import json from ..fast_data_types import get_docs_ref_map ans: dict[str, dict[str, str]] = json.loads(get_docs_ref_map()) return ans def resolve_ref(ref: str, website_url: Callable[[str], str] = website_url) -> str: m = ref_map() href = m['ref'].get(ref, '') prefix, rest = ref.partition('-')[::2] if href: pass elif ref.startswith('conf-kitty-'): href = f'conf#{ref}' elif ref.startswith('conf-kitten-'): parts = ref.split('-') href = "kittens/" + parts[2] + f'/#{ref}' elif ref.startswith('at_'): base = ref.split('_', 1)[1] href = "remote-control/#at-" + base.replace('_', '-') elif ref.startswith('at-'): base = ref.split('-', 1)[1] href = "remote-control/#at-" + base.replace('_', '-') elif ref.startswith('action-group-'): href = f'actions/#{ref}' elif prefix == 'action': href = f'actions/#{rest.replace("_", "-")}' elif prefix in ('term', 'envvar'): href = 'glossary/#' + ref elif prefix == 'doc': href = rest.lstrip('/') elif prefix in ('issues', 'pull', 'discussions'): t, num = ref.partition(':')[::2] href = f'https://github.com/kovidgoyal/kitty/{prefix}/{rest}' if not (href.startswith('https://') or href.startswith('http://')): href = website_url(href) return href def remove_markup(text: str) -> str: imap = {'iss': 'issues-', 'pull': 'pull-', 'disc': 'discussions-'} def extract(m: 'Match[str]') -> tuple[str, str]: parts = m.group(2).split('<') t = parts[0].strip() q = parts[-1].rstrip('>') return t, q def sub(m: 'Match[str]') -> str: key = m.group(1) if key in ('ref', 'iss', 'pull', 'disc'): t, q = extract(m) q = imap.get(key, '') + q url = resolve_ref(q) if not url: raise KeyError(f'Failed to resolve :{m.group(1)}: {q}') return f'{t} <{url}>' if key == 'doc': t, q = extract(m) return f'{t} <{website_url(q)}>' if key in ('term', 'option'): t, _ = extract(m) return t if key in ('ac', 'opt'): t, q = extract(m) return f'{t} {q}' if q and q != t else t if key == 'code': return m.group(2).replace('\\\\', '\\') return str(m.group(2)) return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL) def strip_inline_literal(text: str) -> str: return re.sub(r'``([^`]+)``', r'`\1`', text, flags=re.DOTALL) def iter_blocks(lines: Iterable[str]) -> Iterator[tuple[list[str], int]]: current_block: list[str] = [] prev_indent = 0 for line in lines: indent_size = len(line) - len(line.lstrip()) if indent_size != prev_indent or not line: if current_block: yield current_block, prev_indent current_block = [] prev_indent = indent_size if not line: yield [''], 100 else: current_block.append(line) if current_block: yield current_block, indent_size @lru_cache(maxsize=8) def block_wrapper(comment_symbol: str) -> textwrap.TextWrapper: return textwrap.TextWrapper( initial_indent=comment_symbol, subsequent_indent=comment_symbol, width=70, break_long_words=False ) def wrapped_block(lines: Iterable[str], comment_symbol: str = '#: ') -> Iterator[str]: wrapper = block_wrapper(comment_symbol) for block, indent_size in iter_blocks(lines): if indent_size > 0: for line in block: if not line: yield line else: yield comment_symbol + line else: for line in wrapper.wrap('\n'.join(block)): yield line def render_block(text: str, comment_symbol: str = '#: ') -> str: text = remove_markup(text) text = strip_inline_literal(text) lines = text.splitlines() return '\n'.join(wrapped_block(lines, comment_symbol)) class CoalescedIteratorData: option_groups: dict[int, list['Option']] = {} action_groups: dict[str, list['Mapping']] = {} coalesced: set[int] = set() initialized: bool = False kitty_mod: str = 'kitty_mod' def initialize(self, root: 'Group') -> None: if self.initialized: return self.root = root option_groups = self.option_groups = {} current_group: list[Option] = [] action_groups: dict[str, list[Mapping]] = {} self.action_groups = action_groups coalesced = self.coalesced = set() self.kitty_mod = 'kitty_mod' for item in root.iter_all_non_groups(): if isinstance(item, Option): if item.name == 'kitty_mod': self.kitty_mod = item.defval_as_string if current_group: if item.needs_coalescing: current_group.append(item) coalesced.add(id(item)) continue option_groups[id(current_group[0])] = current_group[1:] current_group = [item] else: current_group.append(item) elif isinstance(item, Mapping): if item.name in action_groups: coalesced.add(id(item)) action_groups[item.name].append(item) else: action_groups[item.name] = [] if current_group: option_groups[id(current_group[0])] = current_group[1:] def option_group_for_option(self, opt: 'Option') -> list['Option']: return self.option_groups.get(id(opt), []) def action_group_for_action(self, ac: 'Mapping') -> list['Mapping']: return self.action_groups.get(ac.name, []) class Option: def __init__( self, name: str, defval: str, macos_default: Unset | str, parser_func: ParserFuncType, long_text: str, documented: bool, group: 'Group', choices: tuple[str, ...], ctype: str, has_secret: bool = False, ): self.name = name self.ctype = ctype self.defval_as_string = defval self.macos_defval = macos_default self.long_text = long_text self.documented = documented self.group = group self.parser_func = parser_func self.choices = choices self.has_secret = has_secret @property def needs_coalescing(self) -> bool: return self.documented and not self.long_text @property def is_color_table_color(self) -> bool: return self.name.startswith('color') and self.name[5:].isdigit() def as_conf(self, commented: bool = False, level: int = 0, option_group: list['Option'] = []) -> list[str]: ans: list[str] = [] a = ans.append if not self.documented: return ans if option_group: sz = max(len(self.name), max(len(o.name) for o in option_group)) a(f'{self.name.ljust(sz)} {self.defval_as_string}'.rstrip()) for o in option_group: a(f'{o.name.ljust(sz)} {o.defval_as_string}'.rstrip()) else: a(f'{self.name} {self.defval_as_string}'.rstrip()) if self.long_text: a('') a(render_block(self.long_text)) a('') return ans def as_rst( self, conf_name: str, shortcut_slugs: dict[str, tuple[str, str]], kitty_mod: str, level: int = 0, option_group: list['Option'] = [] ) -> list[str]: ans: list[str] = [] a = ans.append if not self.documented: return ans mopts = [self] + option_group a('.. opt:: ' + ', '.join(f'{conf_name}.{mo.name}' for mo in mopts)) if any(mo.defval_as_string for mo in mopts): a('.. code-block:: conf') a('') sz = max(len(x.name) for x in mopts) for mo in mopts: a((' {:%ds} {}' % sz).format(mo.name, mo.defval_as_string)) a('') if self.long_text: a(expand_opt_references(conf_name, self.long_text)) a('') return ans class MultiVal: def __init__(self, val_as_str: str, add_to_default: bool, documented: bool, only: Only) -> None: self.defval_as_str = val_as_str self.documented = documented self.only = only self.add_to_default = add_to_default class MultiOption: def __init__(self, name: str, parser_func: ParserFuncType, long_text: str, group: 'Group', ctype: str, has_secret: bool = False): self.name = name self.ctype = ctype self.parser_func = parser_func self.long_text = long_text self.group = group self.has_secret = has_secret self.items: list[MultiVal] = [] def add_value(self, val_as_str: str, add_to_default: bool, documented: bool, only: Only) -> None: self.items.append(MultiVal(val_as_str, add_to_default, documented, only)) def __iter__(self) -> Iterator[MultiVal]: yield from self.items def as_conf(self, commented: bool = False, level: int = 0) -> list[str]: ans: list[str] = [] a = ans.append documented = False for k in self.items: if k.documented: documented = True if k.add_to_default: a(f'{self.name} {k.defval_as_str}'.rstrip()) else: # Comment out multi-options that have no default values a(f'# {self.name}'.rstrip()) if not k.add_to_default and k.defval_as_str: a('') a(f'#: E.g. {self.name} {k.defval_as_str}'.rstrip()) if self.long_text and documented: a('') a(render_block(self.long_text)) a('') return ans def as_rst(self, conf_name: str, shortcut_slugs: dict[str, tuple[str, str]], kitty_mod: str, level: int = 0) -> list[str]: ans: list[str] = [] a = ans.append a(f'.. opt:: {conf_name}.{self.name}') documented = tuple(x for x in self.items if x.documented) if any(k.defval_as_str for k in documented): defaults = tuple(x for x in documented if x.add_to_default) if defaults: a('.. code-block:: conf') a('') for k in defaults: a(f' {self.name:s} {k.defval_as_str}'.rstrip()) else: a('') a('Has no default values. Example values are shown below:') a('') a('.. code-block:: conf') a('') for k in self.items: a(f' {self.name:s} {k.defval_as_str}'.rstrip()) a('') if self.long_text: a(expand_opt_references(conf_name, self.long_text)) a('') return ans class Mapping: add_to_default: bool short_text: str long_text: str documented: bool setting_name: str name: str only: Only @property def parseable_text(self) -> str: return '' @property def key_text(self) -> str: return '' def as_conf(self, commented: bool = False, level: int = 0, action_group: list['Mapping'] = []) -> list[str]: ans: list[str] = [] if not self.documented: return ans a = ans.append if self.short_text: a(render_block(self.short_text.strip())), a('') for sc in [self] + action_group: if sc.documented: prefix = '' if sc.add_to_default else '#:: E.g. ' a(f'{prefix}{sc.setting_name} {sc.parseable_text}') if self.long_text: a(''), a(render_block(self.long_text.strip(), '#:: ')) a('') return ans def as_rst( self, conf_name: str, shortcut_slugs: dict[str, tuple[str, str]], kitty_mod: str, level: int = 0, action_group: list['Mapping'] = [] ) -> list[str]: ans: list[str] = [] a = ans.append if not self.documented: return ans if not self.short_text: raise ValueError(f'The shortcut for {self.name} has no short_text') sc_text = f'{conf_name}.{self.short_text}' shortcut_slugs[f'{conf_name}.{self.name}'] = (sc_text, self.key_text.replace('kitty_mod', kitty_mod)) a(f'.. shortcut:: {sc_text}') block_started = False for sc in [self] + action_group: if sc.add_to_default and sc.documented: if not block_started: a('.. code-block:: conf') a('') block_started = True suffix = '' if sc.only == 'macos': suffix = ' 🍎' elif sc.only == 'linux': suffix = ' 🐧' a(f' {sc.setting_name} {sc.parseable_text.replace("kitty_mod", kitty_mod)}{suffix}') a('') if self.long_text: a('') a(expand_opt_references(conf_name, self.long_text)) a('') return ans class ShortcutMapping(Mapping): setting_name: str = 'map' def __init__( self, name: str, key: str, action_def: str, short_text: str, long_text: str, add_to_default: bool, documented: bool, group: 'Group', only: Only ): self.name = name self.only = only self.key = key self.action_def = action_def self.short_text = short_text self.long_text = long_text self.documented = documented self.add_to_default = add_to_default self.group = group @property def parseable_text(self) -> str: return f'{self.key} {self.action_def}' @property def key_text(self) -> str: return self.key class MouseMapping(Mapping): setting_name: str = 'mouse_map' def __init__( self, name: str, button: str, event: str, modes: str, action_def: str, short_text: str, long_text: str, add_to_default: bool, documented: bool, group: 'Group', only: Only ): self.name = name self.only = only self.button = button self.event = event self.modes = modes self.action_def = action_def self.short_text = short_text self.long_text = long_text self.documented = documented self.add_to_default = add_to_default self.group = group @property def parseable_text(self) -> str: return f'{self.button} {self.event} {self.modes} {self.action_def}' @property def key_text(self) -> str: return self.button NonGroups = Union[Option, MultiOption, ShortcutMapping, MouseMapping] GroupItem = Union[NonGroups, 'Group'] class Group: def __init__(self, name: str, title: str, coalesced_iterator_data: CoalescedIteratorData, start_text: str = '', parent: Optional['Group'] = None): self.name = name self.coalesced_iterator_data = coalesced_iterator_data self.title = title self.start_text = start_text self.end_text = '' self.items: list[GroupItem] = [] self.parent = parent def append(self, item: GroupItem) -> None: self.items.append(item) def __iter__(self) -> Iterator[GroupItem]: return iter(self.items) def __len__(self) -> int: return len(self.items) def iter_with_coalesced_options(self) -> Iterator[GroupItem]: for item in self: if id(item) not in self.coalesced_iterator_data.coalesced: yield item def iter_all(self) -> Iterator[GroupItem]: for x in self: yield x if isinstance(x, Group): yield from x.iter_all() def iter_all_non_groups(self) -> Iterator[NonGroups]: for x in self: if isinstance(x, Group): yield from x.iter_all_non_groups() else: yield x def as_rst(self, conf_name: str, shortcut_slugs: dict[str, tuple[str, str]], kitty_mod: str = 'kitty_mod', level: int = 0) -> list[str]: ans: list[str] = [] a = ans.append if level: a('') a(f'.. _conf-{conf_name}-{self.name}:') a('') a(self.title) heading_level = '+' if level > 1 else '-' a(heading_level * (len(self.title) + 20)) a('') if self.start_text: a(self.start_text) a('') else: ans.extend(('.. default-domain:: conf', '')) kitty_mod = self.coalesced_iterator_data.kitty_mod for item in self.iter_with_coalesced_options(): if isinstance(item, Option): lines = item.as_rst(conf_name, shortcut_slugs, kitty_mod, option_group=self.coalesced_iterator_data.option_group_for_option(item)) elif isinstance(item, Mapping): lines = item.as_rst(conf_name, shortcut_slugs, kitty_mod, level + 1, action_group=self.coalesced_iterator_data.action_group_for_action(item)) else: lines = item.as_rst(conf_name, shortcut_slugs, kitty_mod, level + 1) ans.extend(lines) if level: if self.end_text: a('') a(self.end_text) return ans def as_conf(self, commented: bool = False, level: int = 0) -> list[str]: ans: list[str] = [] a = ans.append if level: a('#: ' + self.title + ' {{''{') a('') if self.start_text: a(render_block(self.start_text)) a('') else: ans.extend(('# vim:fileencoding=utf-8:foldmethod=marker', '')) for item in self.iter_with_coalesced_options(): if isinstance(item, Option): lines = item.as_conf(option_group=self.coalesced_iterator_data.option_group_for_option(item)) elif isinstance(item, Mapping): lines = item.as_conf(commented, level + 1, action_group=self.coalesced_iterator_data.action_group_for_action(item)) else: lines = item.as_conf(commented, level + 1) ans.extend(lines) if level: if self.end_text: a('') a(render_block(self.end_text)) a('#: }}''}') a('') else: map_groups = [] start: int | None = None count: int | None = None for i, line in enumerate(ans): if line.startswith('map ') or line.startswith('mouse_map '): if start is None: start = i count = 1 else: if count is not None: count += 1 else: if start is not None and count is not None: map_groups.append((start, count)) start = count = None for start, count in map_groups: r = range(start, start + count) sz = max(len(ans[i].split(' ', 3)[1]) for i in r) for i in r: line = ans[i] parts = line.split(' ', 3) parts[1] = parts[1].ljust(sz) ans[i] = ' '.join(parts) if commented: ans = [x if x.startswith('#') or not x.strip() else (f'# {x}') for x in ans] else: # Comment out any invalid options that have no value ans = [f'# {x}' if not x.startswith('#') and len(x.strip().split()) == 1 else x for x in ans] return ans def resolve_import(name: str, module: Any = None) -> ParserFuncType: ans = None if name.count('.') > 1: m = import_module(name.rpartition('.')[0]) ans = getattr(m, name.rpartition('.')[2]) else: ans = getattr(builtins, name, None) if not callable(ans): ans = getattr(generic_parsers, name, None) if not callable(ans): ans = getattr(module, name) if not callable(ans): raise TypeError(f'{name} is not a function') return cast(ParserFuncType, ans) class Action: def __init__(self, name: str, option_type: str, fields: dict[str, str], imports: Iterable[str]): self.name = name self._parser_func = option_type self.fields = fields self.imports = frozenset(imports) def resolve_imports(self, module: Any) -> 'Action': self.parser_func = resolve_import(self._parser_func, module) return self class Definition: def __init__(self, package: str, *actions: Action, has_color_table: bool = False) -> None: if package.startswith('!'): self.module_for_parsers = import_module(package[1:]) else: self.module_for_parsers = import_module(f'{package}.options.utils') self.has_color_table = has_color_table self.coalesced_iterator_data = CoalescedIteratorData() self.root_group = Group('', '', self.coalesced_iterator_data) self.current_group = self.root_group self.option_map: dict[str, Option] = {} self.multi_option_map: dict[str, MultiOption] = {} self.shortcut_map: dict[str, list[ShortcutMapping]] = {} self.mouse_map: dict[str, list[MouseMapping]] = {} self.actions = {a.name: a.resolve_imports(self.module_for_parsers) for a in actions} self.deprecations: dict[ParserFuncType, tuple[str, ...]] = {} def iter_all_non_groups(self) -> Iterator[NonGroups]: yield from self.root_group.iter_all_non_groups() def iter_all_options(self) -> Iterator[Option | MultiOption]: for x in self.iter_all_non_groups(): if isinstance(x, (Option, MultiOption)): yield x def iter_all_maps(self, which: str = 'map') -> Iterator[ShortcutMapping | MouseMapping]: for x in self.iter_all_non_groups(): if isinstance(x, ShortcutMapping) and which in ('map', '*'): yield x elif isinstance(x, MouseMapping) and which in ('mouse_map', '*'): yield x def parser_func(self, name: str) -> ParserFuncType: ans = getattr(builtins, name, None) if callable(ans): return cast(ParserFuncType, ans) ans = getattr(generic_parsers, name, None) if callable(ans): return cast(ParserFuncType, ans) ans = getattr(self.module_for_parsers, name) if not callable(ans): raise TypeError(f'{name} is not a function') return cast(ParserFuncType, ans) def add_group(self, name: str, title: str = '', start_text: str = '') -> None: self.current_group = Group(name, title or name, self.coalesced_iterator_data, start_text.strip(), self.current_group) if self.current_group.parent is not None: self.current_group.parent.append(self.current_group) def end_group(self, end_text: str = '') -> None: self.current_group.end_text = end_text.strip() if self.current_group.parent is not None: self.current_group = self.current_group.parent def add_option( self, name: str, defval: str | float | int | bool, option_type: str = 'str', long_text: str = '', documented: bool = True, add_to_default: bool = False, only: Only = '', macos_default: Unset | str = unset, choices: tuple[str, ...] = (), ctype: str = '', has_secret: bool = False, ) -> None: if isinstance(defval, bool): defval = 'yes' if defval else 'no' else: defval = str(defval) is_multiple = name.startswith('+') long_text = long_text.strip() if is_multiple: name = name[1:] if macos_default is not unset: raise TypeError(f'Cannot specify macos_default for is_multiple option: {name} use only instead') is_new = name not in self.multi_option_map if is_new: self.multi_option_map[name] = MultiOption(name, self.parser_func(option_type), long_text, self.current_group, ctype, has_secret) mopt = self.multi_option_map[name] if is_new: self.current_group.append(mopt) mopt.add_value(defval, add_to_default, documented, only) return opt = Option(name, defval, macos_default, self.parser_func(option_type), long_text, documented, self.current_group, choices, ctype, has_secret) self.current_group.append(opt) self.option_map[name] = opt def add_map( self, short_text: str, defn: str, long_text: str = '', add_to_default: bool = True, documented: bool = True, only: Only = '' ) -> None: name, key, action_def = defn.split(maxsplit=2) sc = ShortcutMapping(name, key, action_def, short_text, long_text.strip(), add_to_default, documented, self.current_group, only) self.current_group.append(sc) self.shortcut_map.setdefault(name, []).append(sc) def add_mouse_map( self, short_text: str, defn: str, long_text: str = '', add_to_default: bool = True, documented: bool = True, only: Only = '' ) -> None: name, button, event, modes, action_def = defn.split(maxsplit=4) mm = MouseMapping(name, button, event, modes, action_def, short_text, long_text.strip(), add_to_default, documented, self.current_group, only) self.current_group.append(mm) self.mouse_map.setdefault(name, []).append(mm) def add_deprecation(self, parser_name: str, *aliases: str) -> None: self.deprecations[self.parser_func(parser_name)] = aliases def as_conf(self, commented: bool = False) -> list[str]: self.coalesced_iterator_data.initialize(self.root_group) return self.root_group.as_conf(commented) def as_rst(self, conf_name: str, shortcut_slugs: dict[str, tuple[str, str]]) -> list[str]: self.coalesced_iterator_data.initialize(self.root_group) return self.root_group.as_rst(conf_name, shortcut_slugs) kitty-0.41.1/kitty/conf/utils.py0000664000175000017510000004147114773370543016121 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import os import re import sys from collections.abc import Callable, Generator, Iterable, Iterator, Sequence from contextlib import contextmanager from typing import ( Any, Generic, Literal, NamedTuple, TypeVar, ) from ..constants import _plat, is_macos from ..fast_data_types import Color from ..rgb import to_color as as_color from ..types import ConvertibleToNumbers, ParsedShortcut, run_once from ..typing import Protocol from ..utils import expandvars, log_error, shlex_split key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$') number_unit_pat = re.compile(r'\s*([-+]?\d+\.?\d*)\s*([^\d\s]*)?') ItemParser = Callable[[str, str, dict[str, Any]], bool] T = TypeVar('T') class OptionsProtocol(Protocol): def _asdict(self) -> dict[str, Any]: pass class BadLine(NamedTuple): number: int line: str exception: Exception file: str def positive_int(x: ConvertibleToNumbers) -> int: return max(0, int(x)) def positive_float(x: ConvertibleToNumbers) -> float: return max(0, float(x)) def percent(x: str) -> float: return float(x.rstrip('%')) / 100. def to_color(x: str) -> Color: ans = as_color(x, validate=True) if ans is None: # this is only for type-checking ans = Color(0, 0, 0) return ans def to_color_or_none(x: str) -> Color | None: return None if x.lower() == 'none' else to_color(x) def unit_float(x: ConvertibleToNumbers) -> float: return max(0, min(float(x), 1)) def number_with_unit(x: str, default_unit: str, *extra_units: str) -> tuple[float, str]: if (mat := number_unit_pat.match(x)) is not None: try: value = float(mat.group(1)) except Exception as e: raise ValueError(f'Not a number: {x} with error: {e}') unit = mat.group(2) or default_unit if unit != default_unit and unit not in extra_units: raise ValueError(f'Not a valid unit: {x}. Allowed units are: {default_unit}, {", ".join(extra_units)}') return value, unit raise ValueError(f'Invalid number with unit: {x}') def to_bool(x: str) -> bool: return x.lower() in ('y', 'yes', 'true') class ToCmdline: def __init__(self) -> None: self.override_env: dict[str, str] | None = None def __enter__(self) -> 'ToCmdline': return self def __exit__(self, *a: Any) -> None: self.override_env = None def filter_env_vars(self, *a: str, **override: str) -> 'ToCmdline': remove = frozenset(a) self.override_env = {k: v for k, v in os.environ.items() if k not in remove} self.override_env.update(override) return self def __call__(self, x: str, expand: bool = True) -> list[str]: if expand: ans = list( map( lambda y: expandvars( os.path.expanduser(y), os.environ if self.override_env is None else self.override_env, fallback_to_os_env=False ), shlex_split(x) ) ) else: ans = list(shlex_split(x)) return ans to_cmdline_implementation = ToCmdline() def to_cmdline(x: str, expand: bool = True) -> list[str]: return to_cmdline_implementation(x, expand) def python_string(text: str) -> str: from ast import literal_eval ans: str = literal_eval("'''" + text.replace("'''", "'\\''") + "'''") return ans class Choice: def __init__(self, choices: Sequence[str]): self.defval = choices[0] self.all_choices = frozenset(choices) def __call__(self, x: str) -> str: x = x.lower() if x not in self.all_choices: raise ValueError(f'The value {x} is not a known choice') return x def choices(*choices: str) -> Choice: return Choice(choices) class CurrentlyParsing: __slots__ = 'line', 'number', 'file' def __init__(self, line: str = '', number: int = -1, file: str = ''): self.line = line self.number = number self.file = file def __copy__(self) -> 'CurrentlyParsing': return CurrentlyParsing(self.line, self.number, self.file) @contextmanager def set_line(self, line: str, number: int) -> Iterator['CurrentlyParsing']: orig = self.line, self.number self.line = line self.number = number try: yield self finally: self.line, self.number = orig @contextmanager def set_file(self, file: str) -> Iterator['CurrentlyParsing']: orig = self.file self.file = file try: yield self finally: self.file = orig currently_parsing = CurrentlyParsing() OSNames = Literal['macos', 'bsd', 'linux', 'unknown'] @run_once def os_name() -> OSNames: if is_macos: return 'macos' if 'bsd' in _plat: return 'bsd' if 'linux' in _plat: return 'linux' return 'unknown' class NamedLineIterator: def __init__(self, name: str, lines: Iterator[str]): self.lines = lines self.name = name def __iter__(self) -> Iterator[str]: return self.lines class GenincludeError(Exception): ... def pygeninclude(path: str) -> list[str]: import io import runpy before = sys.stdout buf = sys.stdout = io.StringIO() try: runpy.run_path(path, run_name='__main__') except FileNotFoundError: raise except Exception: import traceback tb = traceback.format_exc() raise GenincludeError(f'Running the geninclude program: {path} failed with the error:\n{tb}') finally: sys.stdout = before return buf.getvalue().splitlines() def geninclude(path: str) -> list[str]: old = os.environ.get('KITTY_OS') os.environ['KITTY_OS'] = os_name() try: if path.endswith('.py'): return pygeninclude(path) import subprocess cp = subprocess.run([path], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) if cp.returncode != 0: raise GenincludeError(f'Running the geninclude program: {path} failed with exit code: {cp.returncode} and STDERR:\n{cp.stderr}') return cp.stdout.splitlines() finally: if old is None: os.environ.pop('KITTY_OS', None) else: os.environ['KITTY_OS'] = old include_keys = 'include', 'globinclude', 'envinclude', 'geninclude' class RecursiveInclude(Exception): pass class Memory: def __init__(self, accumulate_bad_lines: list[BadLine] | None) -> None: self.s: set[str] = set() if accumulate_bad_lines is None: accumulate_bad_lines = [] self.accumulate_bad_lines = accumulate_bad_lines def seen(self, path: str) -> bool: key = os.path.normpath(path) if key in self.s: self.accumulate_bad_lines.append(BadLine(currently_parsing.number, currently_parsing.line.rstrip(), RecursiveInclude( f'The file {path} has already been included, ignoring'), currently_parsing.file)) return True self.s.add(key) return False def parse_line( line: str, parse_conf_item: ItemParser, ans: dict[str, Any], base_path_for_includes: str, effective_config_lines: Callable[[str, str], None], memory: Memory, accumulate_bad_lines: list[BadLine] | None = None, ) -> None: line = line.strip() if not line or line.startswith('#'): return m = key_pat.match(line) if m is None: log_error(f'Ignoring invalid config line: {line!r}') return key, val = m.groups() if key.endswith('include') and key in include_keys: val = expandvars(os.path.expanduser(val.strip()), {'KITTY_OS': os_name()}) if key == 'globinclude': from pathlib import Path vals = tuple(map(lambda x: str(os.fspath(x)), sorted(Path(base_path_for_includes).glob(val)))) elif key == 'envinclude': from fnmatch import fnmatchcase for x in os.environ: if fnmatchcase(x, val): with currently_parsing.set_file(f''): _parse( NamedLineIterator(os.path.join(base_path_for_includes, ''), iter(os.environ[x].splitlines())), parse_conf_item, ans, memory, accumulate_bad_lines, effective_config_lines ) return elif key == 'geninclude': if not os.path.isabs(val): val = os.path.join(base_path_for_includes, val) if not memory.seen(val): try: lines = geninclude(val) except FileNotFoundError as e: if e.filename == val: log_error(f'Could not find the geninclude file: {val}, ignoring') else: raise else: with currently_parsing.set_file(f''): _parse( NamedLineIterator(os.path.join(base_path_for_includes, ''), iter(lines)), parse_conf_item, ans, memory, accumulate_bad_lines, effective_config_lines ) return else: if not os.path.isabs(val): val = os.path.join(base_path_for_includes, val) vals = (val,) for val in vals: if memory.seen(val): continue try: with open(val, encoding='utf-8', errors='replace') as include: with currently_parsing.set_file(val): _parse(include, parse_conf_item, ans, memory, accumulate_bad_lines, effective_config_lines) except FileNotFoundError: log_error(f'Could not find included config file: {val}, ignoring') except OSError: log_error( 'Could not read from included config file: {}, ignoring'. format(val) ) return if parse_conf_item(key, val, ans): effective_config_lines(key, line) else: log_error(f'Ignoring unknown config key: {key}') def _parse( lines: Iterable[str], parse_conf_item: ItemParser, ans: dict[str, Any], memory: Memory, accumulate_bad_lines: list[BadLine] | None = None, effective_config_lines: Callable[[str, str], None] | None = None, ) -> None: name = getattr(lines, 'name', None) effective_config_lines = effective_config_lines or (lambda a, b: None) if name: base_path_for_includes = os.path.abspath(name) if name.endswith(os.path.sep) else os.path.dirname(os.path.abspath(name)) else: from ..constants import config_dir base_path_for_includes = config_dir it = iter(lines) line = '' next_line: str = '' next_line_num = 0 while True: try: if next_line: line = next_line else: line = next(it).lstrip() next_line_num += 1 line_num = next_line_num try: next_line = next(it).lstrip() next_line_num += 1 while next_line.startswith('\\'): line = line.rstrip('\n') + next_line[1:] try: next_line = next(it).lstrip() next_line_num += 1 except StopIteration: next_line = '' break except StopIteration: next_line = '' try: with currently_parsing.set_line(line, line_num): parse_line(line, parse_conf_item, ans, base_path_for_includes, effective_config_lines, memory, accumulate_bad_lines) except Exception as e: if accumulate_bad_lines is None: raise accumulate_bad_lines.append(BadLine(line_num, line.rstrip(), e, currently_parsing.file)) except StopIteration: break def parse_config_base( lines: Iterable[str], parse_conf_item: ItemParser, ans: dict[str, Any], accumulate_bad_lines: list[BadLine] | None = None, effective_config_lines: Callable[[str, str], None] | None = None, ) -> None: _parse(lines, parse_conf_item, ans, Memory(accumulate_bad_lines), accumulate_bad_lines, effective_config_lines) def merge_dicts(defaults: dict[str, Any], newvals: dict[str, Any]) -> dict[str, Any]: ans = defaults.copy() ans.update(newvals) return ans def resolve_config(SYSTEM_CONF: str, defconf: str, config_files_on_cmd_line: Sequence[str] = ()) -> Generator[str, None, None]: if config_files_on_cmd_line: if 'NONE' not in config_files_on_cmd_line: yield SYSTEM_CONF yield from config_files_on_cmd_line else: yield SYSTEM_CONF yield defconf def load_config( defaults: OptionsProtocol, parse_config: Callable[[Iterable[str]], dict[str, Any]], merge_configs: Callable[[dict[str, Any], dict[str, Any]], dict[str, Any]], *paths: str, overrides: Iterable[str] | None = None, initialize_defaults: Callable[[dict[str, Any]], dict[str, Any]] = lambda x: x, ) -> tuple[dict[str, Any], tuple[str, ...]]: ans = initialize_defaults(defaults._asdict()) found_paths = [] for path in paths: if not path: continue if path == '-': path = '/dev/stdin' with currently_parsing.set_file(path): vals = parse_config(sys.stdin) else: try: with open(path, encoding='utf-8', errors='replace') as f: with currently_parsing.set_file(path): vals = parse_config(f) except (FileNotFoundError, PermissionError): continue found_paths.append(path) ans = merge_configs(ans, vals) if overrides is not None: with currently_parsing.set_file(''): vals = parse_config(overrides) ans = merge_configs(ans, vals) return ans, tuple(found_paths) ReturnType = TypeVar('ReturnType') KeyFunc = Callable[[str, str], ReturnType] class KeyFuncWrapper(Generic[ReturnType]): def __init__(self) -> None: self.args_funcs: dict[str, KeyFunc[ReturnType]] = {} def __call__(self, *names: str) -> Callable[[KeyFunc[ReturnType]], KeyFunc[ReturnType]]: def w(f: KeyFunc[ReturnType]) -> KeyFunc[ReturnType]: for name in names: if self.args_funcs.setdefault(name, f) is not f: raise ValueError(f'the args_func {name} is being redefined') return f return w def get(self, name: str) -> KeyFunc[ReturnType] | None: return self.args_funcs.get(name) class KeyAction(NamedTuple): func: str args: tuple[str | float | bool | int | None, ...] = () def __repr__(self) -> str: if self.args: return f'KeyAction({self.func!r}, {self.args!r})' return f'KeyAction({self.func!r})' def pretty(self) -> str: ans = self.func for x in self.args: ans += f' {x}' return ans def parse_kittens_func_args(action: str, args_funcs: dict[str, KeyFunc[tuple[str, Any]]]) -> KeyAction: parts = action.strip().split(' ', 1) func = parts[0] if len(parts) == 1: return KeyAction(func, ()) rest = parts[1] try: parser = args_funcs[func] except KeyError as e: raise KeyError( f'Unknown action: {func}. Check if map action: {action} is valid' ) from e try: func, args = parser(func, rest) except Exception: raise ValueError(f'Unknown key action: {action}') if not isinstance(args, (list, tuple)): args = (args, ) return KeyAction(func, tuple(args)) KittensKeyDefinition = tuple[ParsedShortcut, KeyAction] KittensKeyMap = dict[ParsedShortcut, KeyAction] def parse_kittens_key( val: str, funcs_with_args: dict[str, KeyFunc[tuple[str, Any]]] ) -> KittensKeyDefinition | None: from ..key_encoding import parse_shortcut sc, action = val.partition(' ')[::2] if not sc or not action: return None ans = parse_kittens_func_args(action, funcs_with_args) return parse_shortcut(sc), ans def uniq(vals: Iterable[T]) -> list[T]: seen: set[T] = set() seen_add = seen.add return [x for x in vals if x not in seen and not seen_add(x)] def save_type_stub(text: str, fpath: str) -> None: fpath += 'i' preamble = '# Update this file by running: ./test.py mypy\n\n' try: with open(fpath) as fs: existing = fs.read() except FileNotFoundError: existing = '' current = preamble + text if existing != current: with open(fpath, 'w') as f: f.write(current) kitty-0.41.1/kitty/config.py0000664000175000017510000001774314773370543015306 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal import json import os from collections.abc import Callable, Generator, Iterable from contextlib import contextmanager, suppress from functools import partial from typing import Any from .conf.utils import BadLine, parse_config_base from .conf.utils import load_config as _load_config from .constants import cache_dir, defconf from .options.types import Options, defaults, option_names from .options.utils import KeyboardMode, KeyboardModeMap, KeyDefinition, MouseMap, MouseMapping, build_action_aliases from .typing import TypedDict from .utils import log_error def option_names_for_completion() -> tuple[str, ...]: return option_names def atomic_save(data: bytes, path: str) -> None: import shutil import tempfile path = os.path.realpath(path) fd, p = tempfile.mkstemp(dir=os.path.dirname(path), suffix='.tmp') try: with os.fdopen(fd, 'wb') as f: f.write(data) with suppress(FileNotFoundError): shutil.copystat(path, p) os.utime(p) os.replace(p, path) finally: try: os.remove(p) except FileNotFoundError: pass except Exception as err: log_error(f'Failed to delete temp file {p} for atomic save with error: {err}') @contextmanager def cached_values_for(name: str) -> Generator[dict[str, Any], None, None]: cached_path = os.path.join(cache_dir(), f'{name}.json') cached_values: dict[str, Any] = {} try: with open(cached_path, 'rb') as f: cached_values.update(json.loads(f.read().decode('utf-8'))) except FileNotFoundError: pass except Exception as err: log_error(f'Failed to load cached in {name} values with error: {err}') yield cached_values try: data = json.dumps(cached_values).encode('utf-8') atomic_save(data, cached_path) except Exception as err: log_error(f'Failed to save cached values with error: {err}') def commented_out_default_config() -> str: from .options.definition import definition return '\n'.join(definition.as_conf(commented=True)) def prepare_config_file_for_editing() -> str: if not os.path.exists(defconf): d = os.path.dirname(defconf) with suppress(FileExistsError): os.makedirs(d) with open(defconf, 'w', encoding='utf-8') as f: f.write(commented_out_default_config()) return defconf def finalize_keys(opts: Options, accumulate_bad_lines: list[BadLine] | None = None) -> None: defns: list[KeyDefinition] = [] for d in opts.map: if d is None: # clear_all_shortcuts defns = [] # type: ignore else: try: defns.append(d.resolve_and_copy(opts.kitty_mod)) except Exception as err: if accumulate_bad_lines is None: log_error(f'Ignoring map with invalid action: {d.definition}. Error: {err}') else: accumulate_bad_lines.append(BadLine(d.definition_location.number, d.definition_location.line, err, d.definition_location.file)) modes: KeyboardModeMap = {'': KeyboardMode()} for defn in defns: if defn.options.new_mode: modes[defn.options.new_mode] = nm = KeyboardMode(defn.options.new_mode) nm.on_unknown = defn.options.on_unknown nm.on_action = defn.options.on_action defn.definition = f'push_keyboard_mode {defn.options.new_mode}' try: m = modes[defn.options.mode] except KeyError: kerr = f'The keyboard mode {defn.options.mode} is unknown, ignoring the mapping' if accumulate_bad_lines is None: log_error(kerr) else: dl = defn.definition_location accumulate_bad_lines.append(BadLine(dl.number, dl.line, KeyError(kerr), dl.file)) continue items = m.keymap[defn.trigger] if defn.is_sequence: items = m.keymap[defn.trigger] = [kd for kd in items if defn.rest != kd.rest or defn.options.when_focus_on != kd.options.when_focus_on] items.append(defn) opts.keyboard_modes = modes def finalize_mouse_mappings(opts: Options, accumulate_bad_lines: list[BadLine] | None = None) -> None: defns: list[MouseMapping] = [] for d in opts.mouse_map: if d is None: # clear_all_mouse_actions defns = [] # type: ignore else: try: defns.append(d.resolve_and_copy(opts.kitty_mod)) except Exception as err: if accumulate_bad_lines is None: log_error(f'Ignoring mouse_map with invalid action: {d.definition}. Error: {err}') else: accumulate_bad_lines.append(BadLine(d.definition_location.number, d.definition_location.line, err, d.definition_location.file)) mousemap: MouseMap = {} for defn in defns: if defn.definition: mousemap[defn.trigger] = defn.definition else: mousemap.pop(defn.trigger, None) opts.mousemap = mousemap def parse_config( lines: Iterable[str], accumulate_bad_lines: list[BadLine] | None = None, effective_config_lines: Callable[[str, str], None] | None = None ) -> dict[str, Any]: from .options.parse import create_result_dict, parse_conf_item ans: dict[str, Any] = create_result_dict() parse_config_base( lines, parse_conf_item, ans, accumulate_bad_lines=accumulate_bad_lines, effective_config_lines=effective_config_lines, ) return ans effective_config_lines: list[str] = [] def load_config(*paths: str, overrides: Iterable[str] | None = None, accumulate_bad_lines: list[BadLine] | None = None) -> Options: from .options.parse import merge_result_dicts from .options.types import secret_options del effective_config_lines[:] def add_effective_config_line(key: str, line: str) -> None: if key not in secret_options: effective_config_lines.append(line) overrides = tuple(overrides) if overrides is not None else () opts_dict, found_paths = _load_config( defaults, partial(parse_config, accumulate_bad_lines=accumulate_bad_lines, effective_config_lines=add_effective_config_line), merge_result_dicts, *paths, overrides=overrides) opts = Options(opts_dict) opts.alias_map = build_action_aliases(opts.kitten_alias, 'kitten') opts.alias_map.update(build_action_aliases(opts.action_alias)) finalize_keys(opts, accumulate_bad_lines) finalize_mouse_mappings(opts, accumulate_bad_lines) # delete no longer needed definitions, replacing with empty placeholders opts.kitten_alias = {} opts.action_alias = {} opts.mouse_map = [] opts.map = [] if opts.background_opacity < 1.0 and opts.macos_titlebar_color > 0: log_error('Cannot use both macos_titlebar_color and background_opacity') opts.macos_titlebar_color = 0 opts.config_paths = found_paths opts.all_config_paths = paths opts.config_overrides = overrides return opts def store_effective_config() -> str: import os import stat import tempfile dest = os.path.join(cache_dir(), 'effective-config') os.makedirs(dest, exist_ok=True) raw = '\n'.join(effective_config_lines) with suppress(FileNotFoundError), tempfile.NamedTemporaryFile('w', dir=dest) as tf: os.chmod(tf.name, stat.S_IRUSR | stat.S_IWUSR) print(raw, file=tf) path = os.path.join(dest, f'{os.getpid()}') os.replace(tf.name, path) return path class KittyCommonOpts(TypedDict): select_by_word_characters: str open_url_with: list[str] url_prefixes: tuple[str, ...] def common_opts_as_dict(opts: Options | None = None) -> KittyCommonOpts: if opts is None: opts = defaults return { 'select_by_word_characters': opts.select_by_word_characters, 'open_url_with': opts.open_url_with, 'url_prefixes': opts.url_prefixes, } kitty-0.41.1/kitty/constants.py0000664000175000017510000002623214773370543016046 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal import errno import os import pwd import sys from collections.abc import Iterator from contextlib import suppress from typing import TYPE_CHECKING, Any, NamedTuple, Optional from .types import run_once if TYPE_CHECKING: from .options.types import Options class Version(NamedTuple): major: int minor: int patch: int appname: str = 'kitty' kitty_face = '🐱' version: Version = Version(0, 41, 1) str_version: str = '.'.join(map(str, version)) _plat = sys.platform.lower() is_macos: bool = 'darwin' in _plat is_freebsd: bool = 'freebsd' in _plat is_running_from_develop: bool = False RC_ENCRYPTION_PROTOCOL_VERSION = '1' website_base_url = 'https://sw.kovidgoyal.net/kitty/' default_pager_for_help = ('less', '-iRXF') if getattr(sys, 'frozen', False): extensions_dir: str = getattr(sys, 'kitty_run_data')['extensions_dir'] def get_frozen_base() -> str: global is_running_from_develop try: from bypy_importer import running_in_develop_mode # type: ignore except ImportError: pass else: is_running_from_develop = running_in_develop_mode() if is_running_from_develop: q = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) try: if os.path.isdir(q): return q except OSError: pass ans = os.path.dirname(extensions_dir) if is_macos: ans = os.path.dirname(os.path.dirname(ans)) ans = os.path.join(ans, 'kitty') return ans kitty_base_dir = get_frozen_base() del get_frozen_base else: kitty_base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) extensions_dir = os.path.join(kitty_base_dir, 'kitty') @run_once def kitty_exe() -> str: rpath = getattr(sys, 'kitty_run_data').get('bundle_exe_dir') if not rpath: items = os.environ.get('PATH', '').split(os.pathsep) + [os.path.join(kitty_base_dir, 'kitty', 'launcher')] seen: set[str] = set() for candidate in filter(None, items): if candidate not in seen: seen.add(candidate) if os.access(os.path.join(candidate, 'kitty'), os.X_OK): rpath = candidate break else: raise RuntimeError('kitty binary not found') return os.path.join(rpath, 'kitty') @run_once def kitten_exe() -> str: return os.path.join(os.path.dirname(kitty_exe()), 'kitten') def _get_config_dir() -> str: if 'KITTY_CONFIG_DIRECTORY' in os.environ: return os.path.abspath(os.path.expanduser(os.environ['KITTY_CONFIG_DIRECTORY'])) locations = [] if 'XDG_CONFIG_HOME' in os.environ: locations.append(os.path.abspath(os.path.expanduser(os.environ['XDG_CONFIG_HOME']))) locations.append(os.path.expanduser('~/.config')) if is_macos: locations.append(os.path.expanduser('~/Library/Preferences')) for loc in filter(None, os.environ.get('XDG_CONFIG_DIRS', '').split(os.pathsep)): locations.append(os.path.abspath(os.path.expanduser(loc))) for loc in locations: if loc: q = os.path.join(loc, appname) if os.access(q, os.W_OK) and os.path.exists(os.path.join(q, 'kitty.conf')): return q def make_tmp_conf() -> None: import atexit import tempfile ans = tempfile.mkdtemp(prefix='kitty-conf-') def cleanup() -> None: import shutil with suppress(Exception): shutil.rmtree(ans) atexit.register(cleanup) candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or '~/.config')) ans = os.path.join(candidate, appname) try: os.makedirs(os.path.realpath(ans), exist_ok=True) except FileExistsError: raise SystemExit(f'A file {ans} already exists. It must be a directory, not a file.') except PermissionError: make_tmp_conf() except OSError as err: if err.errno != errno.EROFS: # Error other than read-only file system raise make_tmp_conf() return ans config_dir = _get_config_dir() del _get_config_dir defconf = os.path.join(config_dir, 'kitty.conf') @run_once def cache_dir() -> str: if 'KITTY_CACHE_DIRECTORY' in os.environ: candidate = os.path.abspath(os.environ['KITTY_CACHE_DIRECTORY']) elif is_macos: candidate = os.path.join(os.path.expanduser('~/Library/Caches'), appname) else: candidate = os.environ.get('XDG_CACHE_HOME', '~/.cache') candidate = os.path.join(os.path.expanduser(candidate), appname) os.makedirs(candidate, exist_ok=True) return candidate @run_once def runtime_dir() -> str: if 'KITTY_RUNTIME_DIRECTORY' in os.environ: candidate = os.path.abspath(os.environ['KITTY_RUNTIME_DIRECTORY']) elif is_macos: from .fast_data_types import user_cache_dir candidate = user_cache_dir() elif 'XDG_RUNTIME_DIR' in os.environ: candidate = os.path.abspath(os.environ['XDG_RUNTIME_DIR']) else: candidate = f'/run/user/{os.geteuid()}' if not os.path.isdir(candidate) or not os.access(candidate, os.X_OK | os.W_OK | os.R_OK): candidate = os.path.join(cache_dir(), 'run') os.makedirs(candidate, exist_ok=True) import stat if stat.S_IMODE(os.stat(candidate).st_mode) != 0o700: os.chmod(candidate, 0o700) return candidate def wakeup_io_loop() -> None: from .fast_data_types import get_boss b = get_boss() if b is not None: b.child_monitor.wakeup() terminfo_dir = os.path.join(kitty_base_dir, 'terminfo') logo_png_file = os.path.join(kitty_base_dir, 'logo', 'kitty.png') beam_cursor_data_file = os.path.join(kitty_base_dir, 'logo', 'beam-cursor.png') shell_integration_dir = os.path.join(kitty_base_dir, 'shell-integration') fonts_dir = os.path.join(kitty_base_dir, 'fonts') try: shell_path = os.environ.get('SHELL') or pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh' except KeyError: with suppress(Exception): print('Failed to read login shell via getpwuid() for current user, falling back to /bin/sh', file=sys.stderr) shell_path = '/bin/sh' # Keep this short as it is limited to 103 bytes on macOS # https://github.com/ansible/ansible/issues/11536#issuecomment-153030743 ssh_control_master_template = 'kssh-{kitty_pid}-{ssh_placeholder}' # See https://specifications.freedesktop.org/icon-naming-spec/latest/ar01s04.html # Update the spec in docs/desktop-notifications.rst if you change this. standard_icon_names = { 'error': ('dialog-error', '☠'), 'warning': ('dialog-warning','⚠'), 'warn': ('dialog-warning', '⚠'), 'info': ('dialog-information', 'ℹ'), 'question': ('dialog-question', '❔'), 'help': ('system-help', '📖'), 'file-manager': ('system-file-manager', '🗄'), 'system-monitor': ('utilities-system-monitor', '🎛'), 'text-editor': ('utilities-text-editor', '📄'), } # See https://github.com/TUNER88/iOSSystemSoundsLibrary for Apple's system # sound ids not all of which are available on macOS. standard_sound_names = { 'error': ('dialog-error', 1), 'info': ('dialog-information', 2), 'warning': ('dialog-warning', 3), 'warn': ('dialog-warning', 3), 'question': ('dialog-question', 4), } def glfw_path(module: str) -> str: prefix = 'kitty.' if getattr(sys, 'frozen', False) else '' return os.path.join(extensions_dir, f'{prefix}glfw-{module}.so') def detect_if_wayland_ok() -> bool: if 'WAYLAND_DISPLAY' not in os.environ and 'WAYLAND_SOCKET' not in os.environ: return False if 'KITTY_DISABLE_WAYLAND' in os.environ: return False wayland = glfw_path('wayland') if not os.path.exists(wayland): return False return True def is_wayland(opts: Optional['Options'] = None) -> bool: if is_macos: return False if opts is None: return bool(getattr(is_wayland, 'ans')) if opts.linux_display_server == 'auto': ans = detect_if_wayland_ok() else: ans = opts.linux_display_server == 'wayland' setattr(is_wayland, 'ans', ans) return ans supports_primary_selection = not is_macos def running_in_kitty(set_val: bool | None = None) -> bool: if set_val is not None: setattr(running_in_kitty, 'ans', set_val) return bool(getattr(running_in_kitty, 'ans', False)) def list_kitty_resources(package: str = 'kitty') -> Iterator[str]: try: if sys.version_info[:2] < (3, 10): raise ImportError("importlib.resources.files() doesn't work with frozen builds on python 3.9") from importlib.resources import files except ImportError: from importlib.resources import contents return iter(contents(package)) else: return (path.name for path in files(package).iterdir()) def read_kitty_resource(name: str, package_name: str = 'kitty') -> bytes: try: if sys.version_info[:2] < (3, 10): raise ImportError("importlib.resources.files() doesn't work with frozen builds on python 3.9") from importlib.resources import files except ImportError: from importlib.resources import read_binary return read_binary(package_name, name) else: return (files(package_name) / name).read_bytes() def website_url(doc_name: str = '', website: str = website_base_url) -> str: if doc_name: base, _, frag = doc_name.partition('#') base = base.rstrip('/') if base: base += '/' doc_name = base + (f'#{frag}' if frag else '') return website + doc_name.lstrip('/') handled_signals: set[int] = set() def clear_handled_signals(*a: Any) -> None: if not handled_signals: return import signal if hasattr(signal, 'pthread_sigmask'): signal.pthread_sigmask(signal.SIG_UNBLOCK, handled_signals) for s in handled_signals: signal.signal(s, signal.SIG_DFL) @run_once def local_docs() -> str: d = os.path.dirname base = d(d(kitty_exe())) from_source = getattr(sys, 'kitty_run_data').get('from_source') if is_macos and from_source and '/kitty.app/Contents/' in kitty_exe(): base = d(d(d(base))) subdir = os.path.join('doc', 'kitty', 'html') linux_ans = os.path.join(base, 'share', subdir) if getattr(sys, 'frozen', False): if is_macos: return os.path.join(d(d(d(extensions_dir))), subdir) return linux_ans if os.path.isdir(linux_ans): return linux_ans if from_source: sq = os.path.join(d(base), 'docs', '_build', 'html') if os.path.isdir(sq): return sq for candidate in ('/usr', '/usr/local', '/opt/homebrew'): q = os.path.join(candidate, 'share', subdir) if os.path.isdir(q): return q return '' @run_once def wrapped_kitten_names() -> frozenset[str]: import kitty.fast_data_types as f return frozenset(f.wrapped_kitten_names()) _supports_window_occlusion = False def supports_window_occlusion(set: bool | None = None) -> bool: global _supports_window_occlusion if set is not None: _supports_window_occlusion = set return _supports_window_occlusion kitty-0.41.1/kitty/control-codes.h0000664000175000017510000001313714773370543016404 0ustar nileshnilesh/* * control_codes.h * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once // Space #define SP ' ' // *Null*: Does nothing. #define NUL 0 // *Bell*: Beeps. #define BEL 0x07 // *Backspace*: Backspace one column, but not past the beginning of the // line. #define BS 0x08 // *Horizontal tab*: Move cursor to the next tab stop, or to the end // of the line if there is no earlier tab stop. #define HT 0x09 // *Linefeed*: Give a line feed, and, if LNM (new // line mode) is set also a carriage return. #define LF 10 // *Vertical tab*: Same as :data:`LF`. #define VT 0x0b // *Form feed*: Same as :data:`LF`. #define FF 0x0c // *Carriage return*: Move cursor to left margin on current line. #define CR 13 // *Shift out*: Activate G1 character set. #define SO 0x0e // *Shift in*: Activate G0 character set. #define SI 0x0f // *Cancel*: Interrupt escape sequence. If received during an escape or // control sequence, cancels the sequence and displays substitution // character. #define CAN 0x18 // *Substitute*: Same as :data:`CAN`. #define SUB 0x1a // *Escape*: Starts an escape sequence. #define ESC 0x1b // *Delete*: Is ignored. #define DEL 0x7f // Sharp control codes // ------------------- // Align display #define DECALN '8' // Esc control codes // ------------------ #define ESC_DCS 'P' #define ESC_OSC ']' #define ESC_CSI '[' #define ESC_ST '\\' #define ESC_PM '^' #define ESC_APC '_' #define ESC_SOS 'X' // *Reset*. #define ESC_RIS 'c' // *Index*: Move cursor down one line in same column. If the cursor is // at the bottom margin, the screen performs a scroll-up. #define ESC_IND 'D' // *Next line*: Same as LF. #define ESC_NEL 'E' // Tabulation set: Set a horizontal tab stop at cursor position. #define ESC_HTS 'H' // *Reverse index*: Move cursor up one line in same column. If the // cursor is at the top margin, the screen performs a scroll-down. #define ESC_RI 'M' // Save cursor: Save cursor position, character attribute (graphic // rendition), character set, and origin mode selection (see // :data:`DECRC`). #define ESC_DECSC '7' // *Restore cursor*: Restore previously saved cursor position, character // attribute (graphic rendition), character set, and origin mode // selection. If none were saved, move cursor to home position. #define ESC_DECRC '8' // Set normal keypad mode #define ESC_DECKPNM '>' // Set alternate keypad mode #define ESC_DECKPAM '=' // ECMA-48 CSI sequences. // --------------------- // *Insert character*: Insert the indicated # of blank characters. #define ICH '@' // *Cursor up*: Move cursor up the indicated # of lines in same column. // Cursor stops at top margin. #define CUU 'A' // *Cursor down*: Move cursor down the indicated # of lines in same // column. Cursor stops at bottom margin. #define CUD 'B' // *Cursor forward*: Move cursor right the indicated # of columns. // Cursor stops at right margin. #define CUF 'C' // *Cursor back*: Move cursor left the indicated # of columns. Cursor // stops at left margin. #define CUB 'D' // *Cursor next line*: Move cursor down the indicated # of lines to // column 1. #define CNL 'E' // *Cursor previous line*: Move cursor up the indicated # of lines to // column 1. #define CPL 'F' // *Cursor horizontal align*: Move cursor to the indicated column in // current line. #define CHA 'G' // *Cursor position*: Move cursor to the indicated line, column (origin // at ``1, 1``). #define CUP 'H' // *Erase data* (default: from cursor to end of line). #define ED 'J' // *Erase in line* (default: from cursor to end of line). #define EL 'K' // *Insert line*: Insert the indicated # of blank lines, starting from // the current line. Lines displayed below cursor move down. Lines moved // past the bottom margin are lost. #define IL 'L' // *Delete line*: Delete the indicated # of lines, starting from the // current line. As lines are deleted, lines displayed below cursor // move up. Lines added to bottom of screen have spaces with same // character attributes as last line move up. #define DL 'M' // *Delete character*: Delete the indicated # of characters on the // current line. When character is deleted, all characters to the right // of cursor move left. #define DCH 'P' // Scroll up by the specified number of lines #define SU 'S' // Scroll down by the specified number of lines #define SD 'T' // *Erase character*: Erase the indicated # of characters on the // current line. #define ECH 'X' // *Horizontal position relative*: Same as :data:`CUF`. #define HPR 'a' // Repeat the preceding graphic character Ps times. #define REP 'b' // *Device Attributes*. #define DA 'c' // *Vertical position adjust*: Move cursor to the indicated line, // current column. #define VPA 'd' // *Vertical position relative*: Same as :data:`CUD`. #define VPR 'e' // *Horizontal / Vertical position*: Same as :data:`CUP`. #define HVP 'f' // *Tabulation clear*: Clears a horizontal tab stop at cursor position. #define TBC 'g' // *Set mode*. #define SM 'h' // *Reset mode*. #define RM 'l' // *Select graphics rendition*: The terminal can display the following // character attributes that change the character display without // changing the character #define SGR 'm' // *Device status report*. #define DSR 'n' // Soft reset #define DECSTR 'p' // *Horizontal position adjust*: Same as :data:`CHA`. #define HPA '`' // Back tab #define CBT 'Z' // Forward tab #define CHT 'I' // Misc sequences // ---------------- // Change cursor shape/blink #define DECSCUSR 'q' // File transfer OSC number #define FILE_TRANSFER_CODE 5113 // Pending mode CSI code #define PENDING_MODE 2026 // Text size OSC number #define TEXT_SIZE_CODE 66 kitty-0.41.1/kitty/core_text.m0000664000175000017510000016263214773370543015637 0ustar nileshnilesh/* * core_text.c * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "state.h" #include "cleanup.h" #include "fonts.h" #include "unicode-data.h" #include #include #include #include #include #import #import #include #include #import #import #define debug debug_fonts static inline void cleanup_cfrelease(void *__p) { CFTypeRef *tp = (CFTypeRef *)__p; CFTypeRef cf = *tp; if (cf) { CFRelease(cf); } } #define RAII_CoreFoundation(type, name, initializer) __attribute__((cleanup(cleanup_cfrelease))) type name = initializer typedef struct { PyObject_HEAD unsigned int units_per_em; float ascent, descent, leading, underline_position, underline_thickness, point_sz, scaled_point_sz; CTFontRef ct_font; hb_font_t *hb_font; PyObject *family_name, *full_name, *postscript_name, *path, *name_lookup_table; FontFeatures font_features; } CTFace; PyTypeObject CTFace_Type; static CTFontRef window_title_font = nil; static BOOL CTFontSupportsColorGlyphs(CTFontRef font) { CFTypeRef symbolicTraits = CTFontCopyAttribute(font, kCTFontSymbolicTrait); if (symbolicTraits) { if ([(NSNumber *)symbolicTraits unsignedLongValue] & kCTFontColorGlyphsTrait) { CFRelease(symbolicTraits); return YES; } CFRelease(symbolicTraits); } return NO; } static PyObject* convert_cfstring(CFStringRef src, int free_src) { RAII_CoreFoundation(CFStringRef, releaseme, free_src ? src : nil); (void)releaseme; if (!src) return PyUnicode_FromString(""); const char *fast = CFStringGetCStringPtr(src, kCFStringEncodingUTF8); if (fast) return PyUnicode_FromString(fast); #define SZ 4096 char buf[SZ]; if(!CFStringGetCString(src, buf, SZ, kCFStringEncodingUTF8)) { PyErr_SetString(PyExc_ValueError, "Failed to convert CFString"); return NULL; } return PyUnicode_FromString(buf); #undef SZ } static void init_face(CTFace *self, CTFontRef font) { if (self->hb_font) hb_font_destroy(self->hb_font); self->hb_font = NULL; if (self->ct_font) CFRelease(self->ct_font); self->ct_font = font; CFRetain(font); self->units_per_em = CTFontGetUnitsPerEm(self->ct_font); self->ascent = CTFontGetAscent(self->ct_font); self->descent = CTFontGetDescent(self->ct_font); self->leading = CTFontGetLeading(self->ct_font); self->underline_position = CTFontGetUnderlinePosition(self->ct_font); self->underline_thickness = CTFontGetUnderlineThickness(self->ct_font); self->scaled_point_sz = CTFontGetSize(self->ct_font); } static PyObject* convert_url_to_filesystem_path(CFURLRef url) { uint8_t buf[4096]; if (url && CFURLGetFileSystemRepresentation(url, true, buf, sizeof(buf))) return PyUnicode_FromString((const char*)buf); return PyUnicode_FromString(""); } static PyObject* get_path_for_font(CTFontRef font) { RAII_CoreFoundation(CFURLRef, url, CTFontCopyAttribute(font, kCTFontURLAttribute)); return convert_url_to_filesystem_path(url); } static PyObject* get_path_for_font_descriptor(CTFontDescriptorRef font) { RAII_CoreFoundation(CFURLRef, url, CTFontDescriptorCopyAttribute(font, kCTFontURLAttribute)); return convert_url_to_filesystem_path(url); } static CTFace* ct_face(CTFontRef font, PyObject *features) { CTFace *self = (CTFace *)CTFace_Type.tp_alloc(&CTFace_Type, 0); if (self) { init_face(self, font); self->family_name = convert_cfstring(CTFontCopyFamilyName(self->ct_font), true); self->full_name = convert_cfstring(CTFontCopyFullName(self->ct_font), true); self->postscript_name = convert_cfstring(CTFontCopyPostScriptName(self->ct_font), true); self->path = get_path_for_font(self->ct_font); if (self->family_name == NULL || self->full_name == NULL || self->postscript_name == NULL || self->path == NULL) { Py_CLEAR(self); } else { if (!create_features_for_face(postscript_name_for_face((PyObject*)self), features, &self->font_features)) { Py_CLEAR(self); } } } return self; } static void dealloc(CTFace* self) { if (self->hb_font) hb_font_destroy(self->hb_font); if (self->ct_font) CFRelease(self->ct_font); self->hb_font = NULL; self->ct_font = NULL; free(self->font_features.features); Py_CLEAR(self->family_name); Py_CLEAR(self->full_name); Py_CLEAR(self->postscript_name); Py_CLEAR(self->path); Py_CLEAR(self->name_lookup_table); Py_TYPE(self)->tp_free((PyObject*)self); } static const char* tag_to_string(uint32_t tag, uint8_t bytes[5]) { bytes[0] = (tag >> 24) & 0xff; bytes[1] = (tag >> 16) & 0xff; bytes[2] = (tag >> 8) & 0xff; bytes[3] = (tag) & 0xff; bytes[4] = 0; return (const char*)bytes; } static uint32_t string_to_tag(const uint8_t *bytes) { return (((uint32_t)bytes[0]) << 24) | (((uint32_t)bytes[1]) << 16) | (((uint32_t)bytes[2]) << 8) | bytes[3]; } FontFeatures* features_for_face(PyObject *s) { return &((CTFace*)s)->font_features; } static void add_variation_pair(const void *key_, const void *value_, void *ctx) { PyObject *ans = ctx; CFNumberRef key = key_, value = value_; uint32_t tag; double val; if (!CFNumberGetValue(key, kCFNumberSInt32Type, &tag)) return; if (!CFNumberGetValue(value, kCFNumberDoubleType, &val)) return; uint8_t tag_string[5]; tag_to_string(tag, tag_string); RAII_PyObject(pyval, PyFloat_FromDouble(val)); if (pyval) PyDict_SetItemString(ans, (const char*)tag_string, pyval); } static PyObject* variation_to_python(CFDictionaryRef v) { if (!v) { Py_RETURN_NONE; } RAII_PyObject(ans, PyDict_New()); if (!ans) return NULL; CFDictionaryApplyFunction(v, add_variation_pair, ans); if (PyErr_Occurred()) return NULL; Py_INCREF(ans); return ans; } static PyObject* font_descriptor_to_python(CTFontDescriptorRef descriptor) { RAII_PyObject(path, get_path_for_font_descriptor(descriptor)); RAII_PyObject(ps_name, convert_cfstring(CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute), true)); RAII_PyObject(family, convert_cfstring(CTFontDescriptorCopyAttribute(descriptor, kCTFontFamilyNameAttribute), true)); RAII_PyObject(style, convert_cfstring(CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute), true)); RAII_PyObject(display_name, convert_cfstring(CTFontDescriptorCopyAttribute(descriptor, kCTFontDisplayNameAttribute), true)); RAII_CoreFoundation(CFDictionaryRef, traits, CTFontDescriptorCopyAttribute(descriptor, kCTFontTraitsAttribute)); unsigned long symbolic_traits = 0; float weight = 0, width = 0, slant = 0; #define get_number(d, key, output, type_) { \ CFNumberRef value = (CFNumberRef)CFDictionaryGetValue(d, key); \ if (value) CFNumberGetValue(value, type_, &output); } get_number(traits, kCTFontSymbolicTrait, symbolic_traits, kCFNumberLongType); get_number(traits, kCTFontWeightTrait, weight, kCFNumberFloatType); get_number(traits, kCTFontWidthTrait, width, kCFNumberFloatType); get_number(traits, kCTFontSlantTrait, slant, kCFNumberFloatType); RAII_CoreFoundation(CFDictionaryRef, cf_variation, CTFontDescriptorCopyAttribute(descriptor, kCTFontVariationAttribute)); RAII_PyObject(variation, variation_to_python(cf_variation)); if (!variation) return NULL; #undef get_number PyObject *ans = Py_BuildValue("{ss sOsOsOsOsO sOsOsOsOsOsOsO sfsfsfsk}", "descriptor_type", "core_text", "path", path, "postscript_name", ps_name, "family", family, "style", style, "display_name", display_name, "bold", (symbolic_traits & kCTFontBoldTrait) != 0 ? Py_True : Py_False, "italic", (symbolic_traits & kCTFontItalicTrait) != 0 ? Py_True : Py_False, "monospace", (symbolic_traits & kCTFontTraitMonoSpace) != 0 ? Py_True : Py_False, "expanded", (symbolic_traits & kCTFontExpandedTrait) != 0 ? Py_True : Py_False, "condensed", (symbolic_traits & kCTFontCondensedTrait) != 0 ? Py_True : Py_False, "color_glyphs", (symbolic_traits & kCTFontColorGlyphsTrait) != 0 ? Py_True : Py_False, "variation", variation, "weight", weight, "width", width, "slant", slant, "traits", symbolic_traits ); return ans; } static CTFontDescriptorRef font_descriptor_from_python(PyObject *src) { CTFontSymbolicTraits symbolic_traits = 0; RAII_CoreFoundation(CFMutableDictionaryRef, ans, CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); PyObject *t = PyDict_GetItemString(src, "traits"); if (t == NULL) { symbolic_traits = ( (PyDict_GetItemString(src, "bold") == Py_True ? kCTFontBoldTrait : 0) | (PyDict_GetItemString(src, "italic") == Py_True ? kCTFontItalicTrait : 0) | (PyDict_GetItemString(src, "monospace") == Py_True ? kCTFontMonoSpaceTrait : 0)); } else { symbolic_traits = PyLong_AsUnsignedLong(t); } RAII_CoreFoundation(CFNumberRef, cf_symbolic_traits, CFNumberCreate(NULL, kCFNumberSInt32Type, &symbolic_traits)); CFTypeRef keys[] = { kCTFontSymbolicTrait }; CFTypeRef values[] = { cf_symbolic_traits }; RAII_CoreFoundation(CFDictionaryRef, traits, CFDictionaryCreate(NULL, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); CFDictionaryAddValue(ans, kCTFontTraitsAttribute, traits); #define SET(x, attr) if ((t = PyDict_GetItemString(src, #x))) { \ RAII_CoreFoundation(CFStringRef, cs, CFStringCreateWithCString(NULL, PyUnicode_AsUTF8(t), kCFStringEncodingUTF8)); \ CFDictionaryAddValue(ans, attr, cs); } SET(family, kCTFontFamilyNameAttribute); SET(style, kCTFontStyleNameAttribute); SET(postscript_name, kCTFontNameAttribute); #undef SET if ((t = PyDict_GetItemString(src, "axis_map"))) { RAII_CoreFoundation(CFMutableDictionaryRef, axis_map, CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); PyObject *key, *value; Py_ssize_t pos = 0; while (PyDict_Next(t, &pos, &key, &value)) { double val = PyFloat_AS_DOUBLE(value); uint32_t tag = string_to_tag((const uint8_t*)PyUnicode_AsUTF8(key)); RAII_CoreFoundation(CFNumberRef, cf_tag, CFNumberCreate(NULL, kCFNumberSInt32Type, &tag)); RAII_CoreFoundation(CFNumberRef, cf_val, CFNumberCreate(NULL, kCFNumberDoubleType, &val)); CFDictionaryAddValue(axis_map, cf_tag, cf_val); } CFDictionaryAddValue(ans, kCTFontVariationAttribute, axis_map); } return CTFontDescriptorCreateWithAttributes(ans); } static CTFontCollectionRef all_fonts_collection_data = NULL; static CTFontCollectionRef all_fonts_collection(void) { if (all_fonts_collection_data == NULL) all_fonts_collection_data = CTFontCollectionCreateFromAvailableFonts(NULL); return all_fonts_collection_data; } static PyObject* coretext_all_fonts(PyObject UNUSED *_self, PyObject *monospaced_only_) { int monospaced_only = PyObject_IsTrue(monospaced_only_); RAII_CoreFoundation(CFArrayRef, matches, CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection())); const CFIndex count = CFArrayGetCount(matches); RAII_PyObject(ans, PyTuple_New(count)); if (ans == NULL) return NULL; PyObject *temp; Py_ssize_t num = 0; for (CFIndex i = 0; i < count; i++) { CTFontDescriptorRef desc = (CTFontDescriptorRef) CFArrayGetValueAtIndex(matches, i); if (monospaced_only) { RAII_CoreFoundation(CFDictionaryRef, traits, CTFontDescriptorCopyAttribute(desc, kCTFontTraitsAttribute)); if (traits) { unsigned long symbolic_traits; CFNumberRef value = (CFNumberRef)CFDictionaryGetValue(traits, kCTFontSymbolicTrait); if (value) { CFNumberGetValue(value, kCFNumberLongType, &symbolic_traits); if (!(symbolic_traits & kCTFontTraitMonoSpace)) continue; } } } temp = font_descriptor_to_python(desc); if (temp == NULL) return NULL; PyTuple_SET_ITEM(ans, num++, temp); temp = NULL; } if (_PyTuple_Resize(&ans, num) == -1) return NULL; Py_INCREF(ans); return ans; } static unsigned int glyph_id_for_codepoint_ctfont(CTFontRef ct_font, char_type ch) { unichar chars[2] = {0}; CGGlyph glyphs[2] = {0}; int count = CFStringGetSurrogatePairForLongCharacter(ch, chars) ? 2 : 1; CTFontGetGlyphsForCharacters(ct_font, chars, glyphs, count); return glyphs[0]; } static bool cf_string_equals(CFStringRef a, CFStringRef b) { return CFStringCompare(a, b, 0) == kCFCompareEqualTo; } #define LAST_RESORT_FONT_NAME "LastResort" static bool is_last_resort_font(CTFontRef new_font) { CFStringRef name = CTFontCopyPostScriptName(new_font); bool ans = cf_string_equals(name, CFSTR(LAST_RESORT_FONT_NAME)); CFRelease(name); return ans; } static CTFontDescriptorRef _nerd_font_descriptor = NULL, builtin_nerd_font_descriptor = NULL; static CTFontRef nerd_font(CGFloat sz) { static bool searched = false; if (!searched) { searched = true; CFArrayRef fonts = CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection()); const CFIndex count = CFArrayGetCount(fonts); for (CFIndex i = 0; i < count; i++) { CTFontDescriptorRef descriptor = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fonts, i); CFStringRef name = CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute); bool is_nerd_font = cf_string_equals(name, CFSTR("SymbolsNFM")); CFRelease(name); if (is_nerd_font) { _nerd_font_descriptor = CTFontDescriptorCreateCopyWithAttributes(descriptor, CTFontDescriptorCopyAttributes(descriptor)); break; } } CFRelease(fonts); } if (_nerd_font_descriptor) return CTFontCreateWithFontDescriptor(_nerd_font_descriptor, sz, NULL); if (builtin_nerd_font_descriptor) return CTFontCreateWithFontDescriptor(builtin_nerd_font_descriptor, sz, NULL); return NULL; } static bool ctfont_has_codepoint(const void *ctfont, char_type cp) { return glyph_id_for_codepoint_ctfont(ctfont, cp) > 0; } static bool font_can_render_cell(CTFontRef font, const ListOfChars *lc) { return has_cell_text(ctfont_has_codepoint, font, false, lc); } static CTFontRef manually_search_fallback_fonts(CTFontRef current_font, const ListOfChars *lc) { char_type ch = lc->chars[0] ? lc->chars[0] : ' '; const bool in_first_pua = 0xe000 <= ch && ch <= 0xf8ff; // preferentially load from NERD fonts if (in_first_pua) { CTFontRef nf = nerd_font(CTFontGetSize(current_font)); if (nf) { if (font_can_render_cell(nf, lc)) return nf; CFRelease(nf); } } CFArrayRef fonts = CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection()); CTFontRef ans = NULL; const CFIndex count = CFArrayGetCount(fonts); for (CFIndex i = 0; i < count; i++) { CTFontDescriptorRef descriptor = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fonts, i); CTFontRef new_font = CTFontCreateWithFontDescriptor(descriptor, CTFontGetSize(current_font), NULL); if (!is_last_resort_font(new_font) && font_can_render_cell(new_font, lc)) { ans = new_font; break; } CFRelease(new_font); } CFRelease(fonts); if (!ans) { CTFontRef nf = nerd_font(CTFontGetSize(current_font)); if (nf) { if (font_can_render_cell(nf, lc)) ans = nf; else CFRelease(nf); } } return ans; } static CTFontRef find_substitute_face(CFStringRef str, CTFontRef old_font, const ListOfChars *lc) { // CoreText's fallback system has various problems: // 1) CTFontCreateForString returns the original font when there are combining // diacritics in the text and the base character is in the original font. // 2) Fallback does not work for PUA characters CTFontRef new_font = CTFontCreateForString(old_font, str, CFRangeMake(0, CFStringGetLength(str))); if (!new_font || is_last_resort_font(new_font) || !font_can_render_cell(new_font, lc)) { if (new_font) CFRelease(new_font); // CoreText's fallback font mechanism does not work for private use characters, it also fails // in specific circumstances, such as: return manually_search_fallback_fonts(old_font, lc); } return new_font; } static CTFontRef apply_styles_to_fallback_font(CTFontRef original_fallback_font, bool bold, bool italic, const ListOfChars *lc) { if (!original_fallback_font || (!bold && !italic) || is_last_resort_font(original_fallback_font)) return original_fallback_font; RAII_CoreFoundation(CTFontDescriptorRef, original_descriptor, CTFontCopyFontDescriptor(original_fallback_font)); if (!original_descriptor) return original_fallback_font; // We cannot set kCTFontTraitMonoSpace in traits as if the original // fallback font is Zapf Dingbats we get .AppleSystemUIFontMonospaced as // the new fallback CTFontSymbolicTraits traits = 0; if (bold) traits |= kCTFontTraitBold; if (italic) traits |= kCTFontTraitItalic; RAII_CoreFoundation(CTFontDescriptorRef, descriptor, CTFontDescriptorCreateCopyWithSymbolicTraits(original_descriptor, traits, traits)); if (!descriptor) return original_fallback_font; CTFontRef ans = CTFontCreateWithFontDescriptor(descriptor, CTFontGetSize(original_fallback_font), NULL); if (!ans) return original_fallback_font; RAII_CoreFoundation(CFStringRef, new_name, CTFontCopyFamilyName(ans)); RAII_CoreFoundation(CFStringRef, old_name, CTFontCopyFamilyName(original_fallback_font)); bool same_family = cf_string_equals(new_name, old_name); /* NSLog(@"old: %@ new: %@", old_name, new_name); */ if (same_family && font_can_render_cell(ans, lc)) { CFRelease(original_fallback_font); return ans; } CFRelease(ans); return original_fallback_font; } static bool face_has_codepoint(const void *face, char_type ch) { return glyph_id_for_codepoint(face, ch) > 0; } static struct { char *buf; size_t capacity; } ft_buffer; static CFStringRef lc_as_fallback(const ListOfChars *lc) { ensure_space_for((&ft_buffer), buf, ft_buffer.buf[0], lc->count * 4 + 128, capacity, 256, false); cell_as_utf8_for_fallback(lc, ft_buffer.buf, ft_buffer.capacity); return CFStringCreateWithCString(NULL, ft_buffer.buf, kCFStringEncodingUTF8); } PyObject* create_fallback_face(PyObject *base_face, const ListOfChars *lc, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg) { CTFace *self = (CTFace*)base_face; RAII_CoreFoundation(CTFontRef, new_font, NULL); RAII_CoreFoundation(CFStringRef, str, lc_as_fallback(lc)); if (str == NULL) return PyErr_NoMemory(); if (emoji_presentation) { new_font = CTFontCreateWithName((CFStringRef)@"AppleColorEmoji", self->scaled_point_sz, NULL); if (!new_font || !glyph_id_for_codepoint_ctfont(new_font, lc->chars[0])) { if (new_font) CFRelease(new_font); new_font = find_substitute_face(str, self->ct_font, lc); } } else { new_font = find_substitute_face(str, self->ct_font, lc); new_font = apply_styles_to_fallback_font(new_font, bold, italic, lc); } if (new_font == NULL) Py_RETURN_NONE; RAII_PyObject(postscript_name, convert_cfstring(CTFontCopyPostScriptName(new_font), true)); if (!postscript_name) return NULL; ssize_t idx = -1; PyObject *q, *ans = NULL; while ((q = iter_fallback_faces(fg, &idx))) { CTFace *qf = (CTFace*)q; if (PyObject_RichCompareBool(postscript_name, qf->postscript_name, Py_EQ) == 1) { ans = PyLong_FromSsize_t(idx); break; } } if (!ans) { ans = (PyObject*)ct_face(new_font, NULL); if (ans && !has_cell_text(face_has_codepoint, ans, global_state.debug_font_fallback, lc)) { Py_CLEAR(ans); Py_RETURN_NONE; } } return ans; } unsigned int glyph_id_for_codepoint(const PyObject *s, char_type ch) { const CTFace *self = (CTFace*)s; return glyph_id_for_codepoint_ctfont(self->ct_font, ch); } bool is_glyph_empty(PyObject *s, glyph_index g) { CTFace *self = (CTFace*)s; CGGlyph gg = g; CGRect bounds; CTFontGetBoundingRectsForGlyphs(self->ct_font, kCTFontOrientationHorizontal, &gg, &bounds, 1); return bounds.size.width <= 0; } int get_glyph_width(PyObject *s, glyph_index g) { CTFace *self = (CTFace*)s; CGGlyph gg = g; CGRect bounds; CTFontGetBoundingRectsForGlyphs(self->ct_font, kCTFontOrientationHorizontal, &gg, &bounds, 1); return (int)ceil(bounds.size.width); } static float _scaled_point_sz(double font_sz_in_pts, double dpi_x, double dpi_y) { return ((dpi_x + dpi_y) / 144.0) * font_sz_in_pts; } static float scaled_point_sz(FONTS_DATA_HANDLE fg) { return _scaled_point_sz(fg->font_sz_in_pts, fg->logical_dpi_x, fg->logical_dpi_y); } static bool _set_size_for_face(CTFace *self, bool force, double font_sz_in_pts, double dpi_x, double dpi_y) { float sz = _scaled_point_sz(font_sz_in_pts, dpi_x, dpi_y); if (!force && self->scaled_point_sz == sz) return true; RAII_CoreFoundation(CTFontRef, new_font, CTFontCreateCopyWithAttributes(self->ct_font, sz, NULL, NULL)); if (new_font == NULL) fatal("Out of memory"); init_face(self, new_font); return true; } bool set_size_for_face(PyObject *s, unsigned int UNUSED desired_height, bool force, FONTS_DATA_HANDLE fg) { CTFace *self = (CTFace*)s; return _set_size_for_face(self, force, fg->font_sz_in_pts, fg->logical_dpi_x, fg->logical_dpi_y); } bool face_apply_scaling(PyObject *f, const FONTS_DATA_HANDLE fg) { return set_size_for_face(f, 0, false, fg); } static PyObject* set_size(CTFace *self, PyObject *args) { double font_sz_in_pts, dpi_x, dpi_y; if (!PyArg_ParseTuple(args, "ddd", &font_sz_in_pts, &dpi_x, &dpi_y)) return NULL; if (!_set_size_for_face(self, false, font_sz_in_pts, dpi_x, dpi_y)) return NULL; Py_RETURN_NONE; } // CoreText delegates U+2010 to U+00AD if the font is missing U+2010. Example // of such a font is Fira Code. So we specialize HarfBuzz glyph lookup to take // this into account. static hb_bool_t get_nominal_glyph(hb_font_t *font, void *font_data, hb_codepoint_t unicode, hb_codepoint_t *glyph, void *user_data) { hb_font_t *parent_font = font_data; (void)user_data; (void)font; hb_bool_t ans = hb_font_get_nominal_glyph(parent_font, unicode, glyph); if (!ans && unicode == 0x2010) { CTFontRef ct_font = hb_coretext_font_get_ct_font(parent_font); unsigned int gid = glyph_id_for_codepoint_ctfont(ct_font, unicode); if (gid > 0) { ans = true; *glyph = gid; } } return ans; } static hb_bool_t get_variation_glyph(hb_font_t *font, void *font_data, hb_codepoint_t unicode, hb_codepoint_t variation, hb_codepoint_t *glyph, void *user_data) { hb_font_t *parent_font = font_data; (void)user_data; (void)font; hb_bool_t ans = hb_font_get_variation_glyph(parent_font, unicode, variation, glyph); if (!ans && unicode == 0x2010) { CTFontRef ct_font = hb_coretext_font_get_ct_font(parent_font); unsigned int gid = glyph_id_for_codepoint_ctfont(ct_font, unicode); if (gid > 0) { ans = true; *glyph = gid; } } return ans; } hb_font_t* harfbuzz_font_for_face(PyObject* s) { CTFace *self = (CTFace*)s; if (!self->hb_font) { hb_font_t *hb = hb_coretext_font_create(self->ct_font); if (!hb) fatal("Failed to create hb_font_t"); // dunno if we need this, harfbuzz docs say it is used by CoreText // for optical sizing which changes the look of glyphs at small and large sizes hb_font_set_ptem(hb, self->scaled_point_sz); // Setup CoreText compatible glyph lookup functions self->hb_font = hb_font_create_sub_font(hb); if (!self->hb_font) fatal("Failed to create sub hb_font_t"); hb_font_funcs_t *ffunctions = hb_font_funcs_create(); hb_font_set_funcs(self->hb_font, ffunctions, hb, NULL); hb_font_funcs_set_nominal_glyph_func(ffunctions, get_nominal_glyph, NULL, NULL); hb_font_funcs_set_variation_glyph_func(ffunctions, get_variation_glyph, NULL, NULL); hb_font_funcs_destroy(ffunctions); // sub font retains a reference to this hb_font_destroy(hb); // the sub font retains a reference to the parent font } return self->hb_font; } FontCellMetrics cell_metrics(PyObject *s) { // See https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/TypoFeatures/TextSystemFeatures.html CTFace *self = (CTFace*)s; FontCellMetrics fcm = {0}; #define count (128 - 32) unichar chars[count+1] = {0}; CGGlyph glyphs[count+1] = {0}; unsigned int width = 0, w, i; for (i = 0; i < count; i++) chars[i] = 32 + i; CTFontGetGlyphsForCharacters(self->ct_font, chars, glyphs, count); for (i = 0; i < count; i++) { if (glyphs[i]) { w = (unsigned int)(ceilf( CTFontGetAdvancesForGlyphs(self->ct_font, kCTFontOrientationHorizontal, glyphs+i, NULL, 1))); if (w > width) width = w; } } fcm.cell_width = MAX(1u, width); fcm.underline_thickness = (unsigned int)ceil(MAX(0.1, self->underline_thickness)); fcm.strikethrough_thickness = fcm.underline_thickness; // float line_height = MAX(1, floor(self->ascent + self->descent + MAX(0, self->leading) + 0.5)); // Let CoreText's layout engine calculate the line height. Slower, but hopefully more accurate. #define W "AQWMH_gyl " CFStringRef ts = CFSTR(W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W); #undef W CFMutableAttributedStringRef test_string = CFAttributedStringCreateMutable(kCFAllocatorDefault, CFStringGetLength(ts)); CFAttributedStringReplaceString(test_string, CFRangeMake(0, 0), ts); CFAttributedStringSetAttribute(test_string, CFRangeMake(0, CFStringGetLength(ts)), kCTFontAttributeName, self->ct_font); CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, NULL, CGRectMake(10, 10, 200, 8000)); CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(test_string); CFRelease(test_string); CTFrameRef test_frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); CGPoint origin1, origin2; CTFrameGetLineOrigins(test_frame, CFRangeMake(0, 1), &origin1); CTFrameGetLineOrigins(test_frame, CFRangeMake(1, 1), &origin2); CGFloat line_height = origin1.y - origin2.y; CFArrayRef lines = CTFrameGetLines(test_frame); if (!CFArrayGetCount(lines)) fatal("Failed to typeset test line to calculate cell metrics"); CTLineRef line = CFArrayGetValueAtIndex(lines, 0); CGRect bounds = CTLineGetBoundsWithOptions(line, 0); CGRect bounds_without_leading = CTLineGetBoundsWithOptions(line, kCTLineBoundsExcludeTypographicLeading); CGFloat typographic_ascent, typographic_descent, typographic_leading; CTLineGetTypographicBounds(line, &typographic_ascent, &typographic_descent, &typographic_leading); fcm.cell_height = MAX(4u, (unsigned int)ceilf(line_height)); CGFloat bounds_ascent = bounds_without_leading.size.height + bounds_without_leading.origin.y; fcm.baseline = (unsigned int)floor(bounds_ascent + 0.5); // Not sure if we should add this to bounds ascent and then round it or add // it to already rounded baseline and round again. fcm.underline_position = (unsigned int)floor(bounds_ascent - self->underline_position + 0.5); fcm.strikethrough_position = (unsigned int)floor(fcm.baseline * 0.65); debug("Cell height calculation:\n"); debug("\tline height from line origins: %f\n", line_height); debug("\tline bounds: origin-y: %f height: %f\n", bounds.origin.y, bounds.size.height); debug("\tline bounds-no-leading: origin-y: %f height: %f\n", bounds.origin.y, bounds.size.height); debug("\tbounds metrics: ascent: %f\n", bounds_ascent); debug("\tline metrics: ascent: %f descent: %f leading: %f\n", typographic_ascent, typographic_descent, typographic_leading); debug("\tfont metrics: ascent: %f descent: %f leading: %f underline_position: %f\n", self->ascent, self->descent, self->leading, self->underline_position); debug("\tcell_height: %u baseline: %u underline_position: %u strikethrough_position: %u\n", fcm.cell_height, fcm.baseline, fcm.underline_position, fcm.strikethrough_position); CFRelease(test_frame); CFRelease(path); CFRelease(framesetter); return fcm; #undef count } PyObject* face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) { RAII_CoreFoundation(CTFontDescriptorRef, desc, NULL); if (builtin_nerd_font_descriptor) { PyObject *psname = PyDict_GetItemString(descriptor, "postscript_name"); if (psname && PyUnicode_CompareWithASCIIString(psname, "SymbolsNFM") == 0) { RAII_PyObject(path, get_path_for_font_descriptor(builtin_nerd_font_descriptor)); PyObject *dpath = PyDict_GetItemString(descriptor, "path"); if (dpath && PyUnicode_Compare(path, dpath) == 0) { desc = builtin_nerd_font_descriptor; CFRetain(desc); } } } if (!desc) desc = font_descriptor_from_python(descriptor); if (!desc) return NULL; RAII_CoreFoundation(CTFontRef, font, CTFontCreateWithFontDescriptor(desc, fg ? scaled_point_sz(fg) : 12, NULL)); if (!font) { PyErr_SetString(PyExc_ValueError, "Failed to create CTFont object"); return NULL; } return (PyObject*) ct_face(font, PyDict_GetItemString(descriptor, "features")); } PyObject* face_from_path(const char *path, int UNUSED index, FONTS_DATA_HANDLE fg UNUSED) { RAII_CoreFoundation(CFStringRef, s, CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8)); RAII_CoreFoundation(CFURLRef, url, CFURLCreateWithFileSystemPath(kCFAllocatorDefault, s, kCFURLPOSIXPathStyle, false)); RAII_CoreFoundation(CGDataProviderRef, dp, CGDataProviderCreateWithURL(url)); RAII_CoreFoundation(CGFontRef, cg_font, CGFontCreateWithDataProvider(dp)); RAII_CoreFoundation(CTFontRef, ct_font, CTFontCreateWithGraphicsFont(cg_font, 0.0, NULL, NULL)); return (PyObject*) ct_face(ct_font, NULL); } static PyObject* new(PyTypeObject *type UNUSED, PyObject *args, PyObject *kw) { const char *path = NULL; PyObject *descriptor = NULL; static char *kwds[] = {"descriptor", "path", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kw, "|Os", kwds, &descriptor, &path)) return NULL; if (descriptor) return face_from_descriptor(descriptor, NULL); if (path) return face_from_path(path, 0, NULL); PyErr_SetString(PyExc_TypeError, "Must specify either path or descriptor"); return NULL; } PyObject* specialize_font_descriptor(PyObject *base_descriptor, double font_sz_in_pts UNUSED, double dpi_x UNUSED, double dpi_y UNUSED) { return PyDict_Copy(base_descriptor); } struct RenderBuffers { uint8_t *render_buf; size_t render_buf_sz, sz; CGGlyph *glyphs; CGRect *boxes; CGPoint *positions; }; static struct RenderBuffers buffers = {0}; static void finalize(void) { free(ft_buffer.buf); ft_buffer.buf = NULL; ft_buffer.capacity = 0; free(buffers.render_buf); free(buffers.glyphs); free(buffers.boxes); free(buffers.positions); memset(&buffers, 0, sizeof(struct RenderBuffers)); if (all_fonts_collection_data) CFRelease(all_fonts_collection_data); if (window_title_font) CFRelease(window_title_font); window_title_font = nil; if (_nerd_font_descriptor) CFRelease(_nerd_font_descriptor); if (builtin_nerd_font_descriptor) CFRelease(builtin_nerd_font_descriptor); _nerd_font_descriptor = NULL; builtin_nerd_font_descriptor = NULL; } static void render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned int width, unsigned int height, unsigned int baseline) { CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); if (color_space == NULL) fatal("Out of memory"); CGContextRef ctx = CGBitmapContextCreate(buf, width, height, 8, 4 * width, color_space, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault); if (ctx == NULL) fatal("Out of memory"); CGContextSetShouldAntialias(ctx, true); CGContextSetShouldSmoothFonts(ctx, true); // sub-pixel antialias CGContextSetRGBFillColor(ctx, 1, 1, 1, 1); CGAffineTransform transform = CGAffineTransformIdentity; CGContextSetTextDrawingMode(ctx, kCGTextFill); CGGlyph glyph = glyph_id; CGContextSetTextMatrix(ctx, transform); CGContextSetTextPosition(ctx, -buffers.boxes[0].origin.x, MAX(2, height - baseline)); CGPoint p = CGPointMake(0, 0); CTFontDrawGlyphs(font, &glyph, &p, 1, ctx); CGContextRelease(ctx); CGColorSpaceRelease(color_space); for (size_t r = 0; r < width; r++) { for (size_t c = 0; c < height; c++, buf += 4) { uint32_t px = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; *((pixel*)buf) = px; } } } static void ensure_render_space(size_t width, size_t height, size_t num_glyphs) { if (buffers.render_buf_sz < width * height) { free(buffers.render_buf); buffers.render_buf = NULL; buffers.render_buf_sz = width * height; buffers.render_buf = malloc(buffers.render_buf_sz); if (buffers.render_buf == NULL) fatal("Out of memory"); } if (buffers.sz < num_glyphs) { buffers.sz = MAX(128, num_glyphs * 2); free(buffers.boxes); free(buffers.glyphs); free(buffers.positions); buffers.boxes = calloc(sizeof(buffers.boxes[0]), buffers.sz); buffers.glyphs = calloc(sizeof(buffers.glyphs[0]), buffers.sz); buffers.positions = calloc(sizeof(buffers.positions[0]), buffers.sz); if (!buffers.boxes || !buffers.glyphs || !buffers.positions) fatal("Out of memory"); } } static void setup_ctx_for_alpha_mask(CGContextRef render_ctx) { CGContextSetShouldAntialias(render_ctx, true); CGContextSetShouldSmoothFonts(render_ctx, true); CGContextSetGrayFillColor(render_ctx, 1, 1); // white glyphs CGContextSetGrayStrokeColor(render_ctx, 1, 1); CGContextSetLineWidth(render_ctx, OPT(macos_thicken_font)); CGContextSetTextDrawingMode(render_ctx, kCGTextFillStroke); CGContextSetTextMatrix(render_ctx, CGAffineTransformIdentity); } static void render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned int baseline, unsigned int num_glyphs) { memset(buffers.render_buf, 0, width * height); CGColorSpaceRef gray_color_space = CGColorSpaceCreateDeviceGray(); if (gray_color_space == NULL) fatal("Out of memory"); CGContextRef render_ctx = CGBitmapContextCreate(buffers.render_buf, width, height, 8, width, gray_color_space, (kCGBitmapAlphaInfoMask & kCGImageAlphaNone)); CGColorSpaceRelease(gray_color_space); if (render_ctx == NULL) fatal("Out of memory"); setup_ctx_for_alpha_mask(render_ctx); CGContextSetTextPosition(render_ctx, 0, height - baseline); CTFontDrawGlyphs(font, buffers.glyphs, buffers.positions, num_glyphs, render_ctx); CGContextRelease(render_ctx); } StringCanvas render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline) { CTFace *self = (CTFace*)s; CTFontRef font = self->ct_font; size_t num_chars = strnlen(text, 32); unichar chars[num_chars]; CGSize local_advances[num_chars]; for (size_t i = 0; i < num_chars; i++) chars[i] = text[i]; ensure_render_space(0, 0, num_chars); CTFontGetGlyphsForCharacters(font, chars, buffers.glyphs, num_chars); CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, buffers.glyphs, local_advances, num_chars); CGRect bounding_box = CTFontGetBoundingRectsForGlyphs(font, kCTFontOrientationDefault, buffers.glyphs, buffers.boxes, num_chars); CGFloat x = 0, y = 0; for (size_t i = 0; i < num_chars; i++) { buffers.positions[i] = CGPointMake(x, y); x += local_advances[i].width; y += local_advances[i].height; } StringCanvas ans = { .width = (size_t)ceil(x), .height = (size_t)(2 * bounding_box.size.height) }; ensure_render_space(ans.width, ans.height, num_chars); render_glyphs(font, ans.width, ans.height, baseline, num_chars); ans.canvas = malloc(ans.width * ans.height); if (ans.canvas) memcpy(ans.canvas, buffers.render_buf, ans.width * ans.height); return ans; } static void destroy_hb_buffer(hb_buffer_t **x) { if (*x) hb_buffer_destroy(*x); } static PyObject* render_codepoint(CTFace *self, PyObject *args) { unsigned long cp, fg = 0xffffff; if (!PyArg_ParseTuple(args, "k|k", &cp, &fg)) return NULL; const int num_chars = 1; ensure_render_space(0, 0, num_chars); buffers.glyphs[0] = glyph_id_for_codepoint_ctfont(self->ct_font, cp); CGSize local_advances[num_chars]; CTFontGetAdvancesForGlyphs(self->ct_font, kCTFontOrientationDefault, buffers.glyphs, local_advances, num_chars); CGRect bounding_box = CTFontGetBoundingRectsForGlyphs(self->ct_font, kCTFontOrientationDefault, buffers.glyphs, buffers.boxes, num_chars); StringCanvas ans = { .width = (size_t)(bounding_box.size.width + 1), .height = (size_t)(1 + bounding_box.size.height) }; size_t baseline = ans.height; ensure_render_space(ans.width, ans.height, num_chars); PyObject *pbuf = PyBytes_FromStringAndSize(NULL, ans.width * ans.height * sizeof(pixel)); if (!pbuf) return NULL; memset(PyBytes_AS_STRING(pbuf), 0, PyBytes_GET_SIZE(pbuf)); const unsigned long canvas_width = ans.width, canvas_height = ans.height; if (CTFontSupportsColorGlyphs(self->ct_font)) { render_color_glyph(self->ct_font, (uint8_t*)PyBytes_AS_STRING(pbuf), buffers.glyphs[0], ans.width, ans.height, baseline); } else { render_glyphs(self->ct_font, ans.width, ans.height, baseline, num_chars); uint8_t r = (fg >> 16) & 0xff, g = (fg >> 8) & 0xff, b = fg & 0xff; const uint8_t *last_pixel = (uint8_t*)PyBytes_AS_STRING(pbuf) + PyBytes_GET_SIZE(pbuf) - sizeof(pixel); const uint8_t *s_limit = buffers.render_buf + canvas_width * canvas_height; for ( uint8_t *p = (uint8_t*)PyBytes_AS_STRING(pbuf), *s = buffers.render_buf; p <= last_pixel && s < s_limit; p += sizeof(pixel), s++ ) { p[0] = r; p[1] = g; p[2] = b; p[3] = s[0]; } } return Py_BuildValue("Nkk", pbuf, canvas_width, canvas_height); } static PyObject* render_sample_text(CTFace *self, PyObject *args) { unsigned long canvas_width, canvas_height; unsigned long fg = 0xffffff; CTFontRef font = self->ct_font; PyObject *ptext; if (!PyArg_ParseTuple(args, "Ukk|k", &ptext, &canvas_width, &canvas_height, &fg)) return NULL; FontCellMetrics fcm = cell_metrics((PyObject*)self); if (!fcm.cell_width || !fcm.cell_height) return Py_BuildValue("yII", "", fcm.cell_width, fcm.cell_height); size_t num_chars = PyUnicode_GET_LENGTH(ptext); int num_chars_per_line = canvas_width / fcm.cell_width, num_of_lines = (int)ceil((float)num_chars / (float)num_chars_per_line); canvas_height = MIN(canvas_height, num_of_lines * fcm.cell_height); RAII_PyObject(pbuf, PyBytes_FromStringAndSize(NULL, sizeof(pixel) * canvas_width * canvas_height)); if (!pbuf) return NULL; memset(PyBytes_AS_STRING(pbuf), 0, PyBytes_GET_SIZE(pbuf)); __attribute__((cleanup(destroy_hb_buffer))) hb_buffer_t *hb_buffer = hb_buffer_create(); if (!hb_buffer_pre_allocate(hb_buffer, 4*num_chars)) { PyErr_NoMemory(); return NULL; } for (size_t n = 0; n < num_chars; n++) { Py_UCS4 codep = PyUnicode_READ_CHAR(ptext, n); hb_buffer_add_utf32(hb_buffer, &codep, 1, 0, 1); } hb_buffer_guess_segment_properties(hb_buffer); if (!HB_DIRECTION_IS_HORIZONTAL(hb_buffer_get_direction(hb_buffer))) goto end; hb_shape(harfbuzz_font_for_face((PyObject*)self), hb_buffer, self->font_features.features, self->font_features.count); unsigned int len = hb_buffer_get_length(hb_buffer); hb_glyph_info_t *info = hb_buffer_get_glyph_infos(hb_buffer, NULL); hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(hb_buffer, NULL); memset(PyBytes_AS_STRING(pbuf), 0, PyBytes_GET_SIZE(pbuf)); if (fcm.cell_width > canvas_width) goto end; ensure_render_space(canvas_width, canvas_height, len); float pen_x = 0, pen_y = 0; unsigned num_glyphs = 0; CGFloat scale = CTFontGetSize(self->ct_font) / CTFontGetUnitsPerEm(self->ct_font); for (unsigned int i = 0; i < len; i++) { float advance = (float)positions[i].x_advance * scale; if (pen_x + advance > canvas_width) { pen_y += fcm.cell_height; pen_x = 0; if (pen_y >= canvas_height) break; } double x = pen_x + (double)positions[i].x_offset * scale; double y = pen_y + (double)positions[i].y_offset * scale; pen_x += advance; buffers.positions[i] = CGPointMake(x, -y); buffers.glyphs[i] = info[i].codepoint; num_glyphs++; } render_glyphs(font, canvas_width, canvas_height, fcm.baseline, num_glyphs); uint8_t r = (fg >> 16) & 0xff, g = (fg >> 8) & 0xff, b = fg & 0xff; const uint8_t *last_pixel = (uint8_t*)PyBytes_AS_STRING(pbuf) + PyBytes_GET_SIZE(pbuf) - sizeof(pixel); const uint8_t *s_limit = buffers.render_buf + canvas_width * canvas_height; for ( uint8_t *p = (uint8_t*)PyBytes_AS_STRING(pbuf), *s = buffers.render_buf; p <= last_pixel && s < s_limit; p += sizeof(pixel), s++ ) { p[0] = r; p[1] = g; p[2] = b; p[3] = s[0]; } end: return Py_BuildValue("OII", pbuf, fcm.cell_width, fcm.cell_height); } static bool ensure_ui_font(size_t in_height) { static size_t for_height = 0; if (window_title_font) { if (for_height == in_height) return true; CFRelease(window_title_font); } window_title_font = CTFontCreateUIFontForLanguage(kCTFontUIFontWindowTitle, 0.f, NULL); if (!window_title_font) return false; CGFloat line_height = MAX(1, floor(CTFontGetAscent(window_title_font) + CTFontGetDescent(window_title_font) + MAX(0, CTFontGetLeading(window_title_font)) + 0.5)); CGFloat pts_per_px = CTFontGetSize(window_title_font) / line_height; CGFloat desired_size = in_height * pts_per_px; if (desired_size != CTFontGetSize(window_title_font)) { CTFontRef sized = CTFontCreateCopyWithAttributes(window_title_font, desired_size, NULL, NULL); CFRelease(window_title_font); window_title_font = sized; if (!window_title_font) return false; } for_height = in_height; return true; } bool cocoa_render_line_of_text(const char *text, const color_type fg, const color_type bg, uint8_t *rgba_output, const size_t width, const size_t height) { CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); if (color_space == NULL) return false; CGContextRef ctx = CGBitmapContextCreate(rgba_output, width, height, 8, 4 * width, color_space, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault); CGColorSpaceRelease(color_space); if (ctx == NULL) return false; if (!ensure_ui_font(height)) return false; CGContextSetShouldAntialias(ctx, true); CGContextSetShouldSmoothFonts(ctx, true); // sub-pixel antialias CGContextSetRGBFillColor(ctx, ((bg >> 16) & 0xff) / 255.f, ((bg >> 8) & 0xff) / 255.f, (bg & 0xff) / 255.f, 1.f); CGContextFillRect(ctx, CGRectMake(0.0, 0.0, width, height)); CGContextSetTextDrawingMode(ctx, kCGTextFill); CGContextSetTextMatrix(ctx, CGAffineTransformIdentity); CGContextSetRGBFillColor(ctx, ((fg >> 16) & 0xff) / 255.f, ((fg >> 8) & 0xff) / 255.f, (fg & 0xff) / 255.f, 1.f); CGContextSetRGBStrokeColor(ctx, ((fg >> 16) & 0xff) / 255.f, ((fg >> 8) & 0xff) / 255.f, (fg & 0xff) / 255.f, 1.f); NSAttributedString *str = [[NSAttributedString alloc] initWithString:@(text) attributes:@{(NSString *)kCTFontAttributeName: (__bridge id)window_title_font}]; if (!str) { CGContextRelease(ctx); return false; } CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)str); [str release]; if (!line) { CGContextRelease(ctx); return false; } CGFloat ascent, descent, leading; CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGContextSetTextPosition(ctx, 0, descent); CTLineDraw(line, ctx); CFRelease(line); CGContextRelease(ctx); return true; } uint8_t* render_single_ascii_char_as_mask(const char ch, size_t *result_width, size_t *result_height) { if (!ensure_ui_font(*result_height)) { PyErr_SetString(PyExc_RuntimeError, "failed to create UI font"); return NULL; } unichar chars = ch; CGSize local_advances[1]; CTFontGetGlyphsForCharacters(window_title_font, &chars, buffers.glyphs, 1); CTFontGetAdvancesForGlyphs(window_title_font, kCTFontOrientationDefault, buffers.glyphs, local_advances, 1); CGRect bounding_box = CTFontGetBoundingRectsForGlyphs(window_title_font, kCTFontOrientationDefault, buffers.glyphs, buffers.boxes, 1); size_t width = (size_t)ceilf(bounding_box.size.width); size_t height = (size_t)ceilf(bounding_box.size.height); uint8_t *canvas = calloc(width, height); if (!canvas) { PyErr_NoMemory(); return NULL; } CGColorSpaceRef gray_color_space = CGColorSpaceCreateDeviceGray(); if (gray_color_space == NULL) { PyErr_NoMemory(); free(canvas); return NULL; } CGContextRef render_ctx = CGBitmapContextCreate(canvas, width, height, 8, width, gray_color_space, (kCGBitmapAlphaInfoMask & kCGImageAlphaNone)); CGColorSpaceRelease(gray_color_space); if (render_ctx == NULL) { PyErr_NoMemory(); free(canvas); return NULL; } setup_ctx_for_alpha_mask(render_ctx); /* printf("origin.y: %f descent: %f ascent: %f height: %zu size.height: %f\n", bounding_box.origin.y, CTFontGetDescent(window_title_font), CTFontGetAscent(window_title_font), height, bounding_box.size.height); */ CGContextSetTextPosition(render_ctx, -bounding_box.origin.x, -bounding_box.origin.y); CTFontDrawGlyphs(window_title_font, buffers.glyphs, buffers.positions, 1, render_ctx); CGContextRelease(render_ctx); *result_width = width; *result_height = height; return canvas; } static bool do_render(CTFontRef ct_font, unsigned int units_per_em, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg, GlyphRenderInfo *ri) { unsigned int canvas_width = cell_width * num_cells; ensure_render_space(canvas_width, cell_height, num_glyphs); CGRect br = CTFontGetBoundingRectsForGlyphs(ct_font, kCTFontOrientationHorizontal, buffers.glyphs, buffers.boxes, num_glyphs); const bool debug_rendering = false; if (allow_resize) { // Resize glyphs that would bleed into neighboring cells, by scaling the font size float right = 0; for (unsigned i=0; i < num_glyphs; i++) right = MAX(right, buffers.boxes[i].origin.x + buffers.boxes[i].size.width); if (!bold && !italic && right > canvas_width + 1) { if (debug_rendering) printf("resizing glyphs, right: %f canvas_width: %u\n", right, canvas_width); CGFloat sz = CTFontGetSize(ct_font); sz *= canvas_width / right; CTFontRef new_font = CTFontCreateCopyWithAttributes(ct_font, sz, NULL, NULL); bool ret = do_render(new_font, CTFontGetUnitsPerEm(new_font), bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg, ri); CFRelease(new_font); return ret; } } CGFloat x = 0, y = 0; CGFloat scale = CTFontGetSize(ct_font) / units_per_em; for (unsigned i=0; i < num_glyphs; i++) { buffers.positions[i].x = x + hb_positions[i].x_offset * scale; buffers.positions[i].y = y + hb_positions[i].y_offset * scale; if (debug_rendering) printf("x=%f y=%f origin=%f width=%f x_advance=%f x_offset=%f y_advance=%f y_offset=%f\n", buffers.positions[i].x, buffers.positions[i].y, buffers.boxes[i].origin.x, buffers.boxes[i].size.width, hb_positions[i].x_advance * scale, hb_positions[i].x_offset * scale, hb_positions[i].y_advance * scale, hb_positions[i].y_offset * scale); x += hb_positions[i].x_advance * scale; y += hb_positions[i].y_advance * scale; } if (*was_colored) { render_color_glyph(ct_font, (uint8_t*)canvas, info[0].codepoint, cell_width * num_cells, cell_height, baseline); } else { render_glyphs(ct_font, canvas_width, cell_height, baseline, num_glyphs); Region src = {.bottom=cell_height, .right=canvas_width}, dest = {.bottom=cell_height, .right=canvas_width}; render_alpha_mask(buffers.render_buf, canvas, &src, &dest, canvas_width, canvas_width, 0xffffff); } ri->canvas_width = canvas_width; ri->rendered_width = (unsigned)ceil(br.size.width); ri->x = 0; // FiraCode ligatures result in negative origins if (br.origin.x > 0) ri->x = (int)br.origin.x; return true; } bool render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, GlyphRenderInfo *ri) { CTFace *self = (CTFace*)s; ensure_render_space(128, 128, num_glyphs); for (unsigned i=0; i < num_glyphs; i++) buffers.glyphs[i] = info[i].codepoint; return do_render(self->ct_font, self->units_per_em, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, ri); } // Font tables {{{ static bool ensure_name_table(CTFace *self) { if (self->name_lookup_table) return true; RAII_CoreFoundation(CFDataRef, cftable, CTFontCopyTable(self->ct_font, kCTFontTableName, kCTFontTableOptionNoOptions)); const uint8_t *table = cftable ? CFDataGetBytePtr(cftable) : NULL; size_t table_len = cftable ? CFDataGetLength(cftable) : 0; self->name_lookup_table = read_name_font_table(table, table_len); return !!self->name_lookup_table; } static PyObject* get_best_name(CTFace *self, PyObject *nameid) { if (!ensure_name_table(self)) return NULL; return get_best_name_from_name_table(self->name_lookup_table, nameid); } static PyObject* get_variation(CTFace *self, PyObject *args UNUSED) { RAII_CoreFoundation(CFDictionaryRef, src, CTFontCopyVariation(self->ct_font)); return variation_to_python(src); } static PyObject* applied_features(CTFace *self, PyObject *a UNUSED) { return font_features_as_dict(&self->font_features); } static PyObject* get_features(CTFace *self, PyObject *a UNUSED) { if (!ensure_name_table(self)) return NULL; RAII_PyObject(output, PyDict_New()); if (!output) return NULL; RAII_CoreFoundation(CFDataRef, cftable, CTFontCopyTable(self->ct_font, kCTFontTableGSUB, kCTFontTableOptionNoOptions)); const uint8_t *table = cftable ? CFDataGetBytePtr(cftable) : NULL; size_t table_len = cftable ? CFDataGetLength(cftable) : 0; if (!read_features_from_font_table(table, table_len, self->name_lookup_table, output)) return NULL; RAII_CoreFoundation(CFDataRef, cfpostable, CTFontCopyTable(self->ct_font, kCTFontTableGPOS, kCTFontTableOptionNoOptions)); table = cfpostable ? CFDataGetBytePtr(cfpostable) : NULL; table_len = cfpostable ? CFDataGetLength(cfpostable) : 0; if (!read_features_from_font_table(table, table_len, self->name_lookup_table, output)) return NULL; Py_INCREF(output); return output; } static PyObject* get_variable_data(CTFace *self, PyObject *args UNUSED) { if (!ensure_name_table(self)) return NULL; RAII_PyObject(output, PyDict_New()); if (!output) return NULL; RAII_CoreFoundation(CFDataRef, cftable, CTFontCopyTable(self->ct_font, kCTFontTableFvar, kCTFontTableOptionNoOptions)); const uint8_t *table = cftable ? CFDataGetBytePtr(cftable) : NULL; size_t table_len = cftable ? CFDataGetLength(cftable) : 0; if (!read_fvar_font_table(table, table_len, self->name_lookup_table, output)) return NULL; RAII_CoreFoundation(CFDataRef, stable, CTFontCopyTable(self->ct_font, kCTFontTableSTAT, kCTFontTableOptionNoOptions)); table = stable ? CFDataGetBytePtr(stable) : NULL; table_len = stable ? CFDataGetLength(stable) : 0; if (!read_STAT_font_table(table, table_len, self->name_lookup_table, output)) return NULL; Py_INCREF(output); return output; } static PyObject* identify_for_debug(CTFace *self, PyObject *args UNUSED) { RAII_PyObject(features, PyTuple_New(self->font_features.count)); if (!features) return NULL; char buf[128]; for (unsigned i = 0; i < self->font_features.count; i++) { hb_feature_to_string(self->font_features.features + i, buf, sizeof(buf)); PyObject *f = PyUnicode_FromString(buf); if (!f) return NULL; PyTuple_SET_ITEM(features, i, f); } return PyUnicode_FromFormat("%V: %V\nFeatures: %S", self->postscript_name, "[psname]", self->path, "[path]", features); } // }}} // Boilerplate {{{ static PyObject* display_name(CTFace *self, PyObject *args UNUSED) { CFStringRef dn = CTFontCopyDisplayName(self->ct_font); return convert_cfstring(dn, true); } static PyObject* postscript_name(CTFace *self, PyObject *args UNUSED) { return self->postscript_name ? Py_BuildValue("O", self->postscript_name) : PyUnicode_FromString(""); } static PyMethodDef methods[] = { METHODB(display_name, METH_NOARGS), METHODB(postscript_name, METH_NOARGS), METHODB(get_variable_data, METH_NOARGS), METHODB(applied_features, METH_NOARGS), METHODB(get_features, METH_NOARGS), METHODB(get_variation, METH_NOARGS), METHODB(identify_for_debug, METH_NOARGS), METHODB(set_size, METH_VARARGS), METHODB(render_sample_text, METH_VARARGS), METHODB(render_codepoint, METH_VARARGS), METHODB(get_best_name, METH_O), {NULL} /* Sentinel */ }; const char* postscript_name_for_face(const PyObject *face_) { const CTFace *self = (const CTFace*)face_; if (self->postscript_name) return PyUnicode_AsUTF8(self->postscript_name); return ""; } static PyObject * repr(CTFace *self) { char buf[1024] = {0}; snprintf(buf, sizeof(buf)/sizeof(buf[0]), "ascent=%.1f, descent=%.1f, leading=%.1f, scaled_point_sz=%.1f, underline_position=%.1f underline_thickness=%.1f", (self->ascent), (self->descent), (self->leading), (self->scaled_point_sz), (self->underline_position), (self->underline_thickness)); return PyUnicode_FromFormat( "Face(family=%U, full_name=%U, postscript_name=%U, path=%U, units_per_em=%u, %s)", self->family_name, self->full_name, self->postscript_name, self->path, self->units_per_em, buf ); } static PyObject* add_font_file(PyObject UNUSED *_self, PyObject *args) { const unsigned char *path = NULL; Py_ssize_t sz; if (!PyArg_ParseTuple(args, "s#", &path, &sz)) return NULL; RAII_CoreFoundation(CFURLRef, url, CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, path, sz, false)); if (CTFontManagerRegisterFontsForURL(url, kCTFontManagerScopeProcess, NULL)) Py_RETURN_TRUE; Py_RETURN_FALSE; } static PyObject* set_builtin_nerd_font(PyObject UNUSED *self, PyObject *pypath) { if (!PyUnicode_Check(pypath)) { PyErr_SetString(PyExc_TypeError, "path must be a string"); return NULL; } const char *path = NULL; Py_ssize_t sz; path = PyUnicode_AsUTF8AndSize(pypath, &sz); RAII_CoreFoundation(CFURLRef, url, CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const unsigned char*)path, sz, false)); RAII_CoreFoundation(CFArrayRef, descriptors, CTFontManagerCreateFontDescriptorsFromURL(url)); if (!descriptors || CFArrayGetCount(descriptors) == 0) { PyErr_SetString(PyExc_OSError, "Failed to create descriptor from nerd font path"); return NULL; } if (builtin_nerd_font_descriptor) CFRelease(builtin_nerd_font_descriptor); builtin_nerd_font_descriptor = CFArrayGetValueAtIndex(descriptors, 0); CFRetain(builtin_nerd_font_descriptor); return font_descriptor_to_python(builtin_nerd_font_descriptor); } static PyMethodDef module_methods[] = { METHODB(coretext_all_fonts, METH_O), METHODB(add_font_file, METH_VARARGS), METHODB(set_builtin_nerd_font, METH_O), {NULL, NULL, 0, NULL} /* Sentinel */ }; static PyMemberDef members[] = { #define MEM(name, type) {#name, type, offsetof(CTFace, name), READONLY, #name} MEM(units_per_em, T_UINT), MEM(scaled_point_sz, T_FLOAT), MEM(ascent, T_FLOAT), MEM(descent, T_FLOAT), MEM(leading, T_FLOAT), MEM(underline_position, T_FLOAT), MEM(underline_thickness, T_FLOAT), MEM(family_name, T_OBJECT), MEM(path, T_OBJECT), MEM(full_name, T_OBJECT), {NULL} /* Sentinel */ }; PyTypeObject CTFace_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.CTFace", .tp_new = new, .tp_basicsize = sizeof(CTFace), .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "CoreText Font face", .tp_methods = methods, .tp_members = members, .tp_repr = (reprfunc)repr, }; int init_CoreText(PyObject *module) { if (PyType_Ready(&CTFace_Type) < 0) return 0; if (PyModule_AddObject(module, "CTFace", (PyObject *)&CTFace_Type) != 0) return 0; if (PyModule_AddFunctions(module, module_methods) != 0) return 0; register_at_exit_cleanup_func(CORE_TEXT_CLEANUP_FUNC, finalize); return 1; } // }}} kitty-0.41.1/kitty/cross-platform-random.h0000664000175000017510000000250114773370543020053 0ustar nileshnilesh/* * Copyright (C) 2020 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include #include #if __linux__ #include #if __has_include() #include static inline bool secure_random_bytes(void *buf, size_t nbytes) { unsigned char* p = buf; ssize_t left = nbytes; while(1) { ssize_t n = getrandom(p, left, 0); if (n >= left) return true; if (n < 0) { if (errno != EINTR) return false; // should never happen but if it does, we fail without any feedback continue; } left -= n; p += n; } } #else #include "safe-wrappers.h" static inline bool secure_random_bytes(void *buf, size_t nbytes) { int fd = safe_open("/dev/urandom", O_RDONLY, 0); if (fd < 0) return false; size_t bytes_read = 0; while (bytes_read < nbytes) { ssize_t n = read(fd, (uint8_t*)buf + bytes_read, nbytes - bytes_read); if (n < 0) { if (errno == EINTR) continue; break; } bytes_read += n; } safe_close(fd, __FILE__, __LINE__); return bytes_read == nbytes; } #endif #else static inline bool secure_random_bytes(void *buf, size_t nbytes) { arc4random_buf(buf, nbytes); return true; } #endif kitty-0.41.1/kitty/crypto.c0000664000175000017510000004656614773370543015160 0ustar nileshnilesh/* * crypto.c * Copyright (C) 2022 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "data-types.h" #include "cross-platform-random.h" #include #include #include #include #include #include #include #include #ifdef LIBRESSL_VERSION_NUMBER /* from: https://github.com/libressl/portable/blob/master/include/compat/string.h#L63 */ #define explicit_bzero libressl_explicit_bzero void explicit_bzero(void *, size_t); /* from: https://github.com/libressl/portable/blob/master/crypto/compat/freezero.c */ void freezero(void *ptr, size_t sz) { if (ptr == NULL) return; explicit_bzero(ptr, sz); free(ptr); } #define OPENSSL_clear_free freezero #endif #define SHA1_DIGEST_LENGTH SHA_DIGEST_LENGTH typedef enum HASH_ALGORITHM { SHA1_HASH, SHA224_HASH, SHA256_HASH, SHA384_HASH, SHA512_HASH } HASH_ALGORITHM; static PyObject* Crypto_Exception = NULL; static PyObject* set_error_from_openssl(const char *prefix) { BIO *bio = BIO_new(BIO_s_mem()); ERR_print_errors(bio); char *buf = NULL; size_t len = BIO_get_mem_data(bio, &buf); PyObject *msg = PyUnicode_FromStringAndSize(buf, len); if (msg) PyErr_Format(Crypto_Exception, "%s: %U", prefix, msg); BIO_free(bio); Py_CLEAR(msg); return NULL; } // Secret {{{ typedef struct { PyObject_HEAD void *secret; size_t secret_len; } Secret; static PyObject * new_secret(PyTypeObject *type UNUSED, PyObject *args UNUSED, PyObject *kwds UNUSED) { PyErr_SetString(PyExc_TypeError, "Cannot create Secret objects directly"); return NULL; } static Secret* alloc_secret(size_t len); static void dealloc_secret(Secret *self) { if (self->secret) OPENSSL_clear_free(self->secret, self->secret_len); Py_TYPE(self)->tp_free((PyObject*)self); } static int __eq__(Secret *a, Secret *b) { const size_t l = a->secret_len < b->secret_len ? a->secret_len : b->secret_len; return memcmp(a->secret, b->secret, l) == 0; } static Py_ssize_t __len__(PyObject *self) { return (Py_ssize_t)(((Secret*)self)->secret_len); } static PySequenceMethods sequence_methods = { .sq_length = __len__, }; static PyObject * richcmp(PyObject *obj1, PyObject *obj2, int op); static PyTypeObject Secret_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.Secret", .tp_basicsize = sizeof(Secret), .tp_dealloc = (destructor)dealloc_secret, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "Secure storage for secrets", .tp_new = new_secret, .tp_richcompare = richcmp, .tp_as_sequence = &sequence_methods, }; RICHCMP(Secret) static Secret* alloc_secret(size_t len) { Secret *self = (Secret*)Secret_Type.tp_alloc(&Secret_Type, 0); if (self) { self->secret_len = len; if (NULL == (self->secret = OPENSSL_malloc(len))) { Py_CLEAR(self); return (Secret*)set_error_from_openssl("Failed to malloc"); } if (0 != mlock(self->secret, self->secret_len)) { Py_CLEAR(self); return (Secret*)PyErr_SetFromErrno(PyExc_OSError); } } return self; } // }}} // EllipticCurveKey {{{ typedef struct { PyObject_HEAD EVP_PKEY *key; int algorithm, nid; } EllipticCurveKey; static PyObject * new_ec_key(PyTypeObject *type, PyObject *args, PyObject *kwds) { EllipticCurveKey *self; static const char* kwlist[] = {"algorithm", NULL}; int algorithm = EVP_PKEY_X25519, nid = NID_X25519; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i", (char**)kwlist, &algorithm)) return NULL; switch(algorithm) { case EVP_PKEY_X25519: break; default: PyErr_SetString(PyExc_KeyError, "Unknown algorithm"); return NULL; } EVP_PKEY *key = NULL; EVP_PKEY_CTX *pctx = NULL; #define cleanup() { if (key) EVP_PKEY_free(key); key = NULL; if (pctx) EVP_PKEY_CTX_free(pctx); pctx = NULL; } #define ssl_error(text) { cleanup(); return set_error_from_openssl(text); } if (NULL == (pctx = EVP_PKEY_CTX_new_id(nid, NULL))) ssl_error("Failed to create context for key generation"); if(1 != EVP_PKEY_keygen_init(pctx)) ssl_error("Failed to initialize keygen context"); if (1 != EVP_PKEY_keygen(pctx, &key)) ssl_error("Failed to generate key"); self = (EllipticCurveKey *)type->tp_alloc(type, 0); if (self) { self->key = key; key = NULL; self->nid = nid; self->algorithm = algorithm; } cleanup(); return (PyObject*) self; #undef cleanup #undef ssl_error } static void dealloc_ec_key(EllipticCurveKey* self) { if (self->key) EVP_PKEY_free(self->key); Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject* hash_data_to_secret(const unsigned char *data, size_t len, int hash_algorithm) { size_t hash_size; #define H(which) case which##_HASH: hash_size = which##_DIGEST_LENGTH; break; switch (hash_algorithm) { H(SHA1) H(SHA224) H(SHA256) H(SHA384) H(SHA512) default: PyErr_Format(PyExc_KeyError, "Unknown hash algorithm: %d", hash_algorithm); return NULL; } #undef H Secret *ans = alloc_secret(hash_size); if (!ans) return NULL; #define H(which) case which##_HASH: if (which(data, len, ans->secret) == NULL) { Py_CLEAR(ans); return set_error_from_openssl("Failed to " #which); } break; switch ((HASH_ALGORITHM)hash_algorithm) { H(SHA1) H(SHA224) H(SHA256) H(SHA384) H(SHA512) } #undef H return (PyObject*)ans; } static PyObject* derive_secret(EllipticCurveKey *self, PyObject *args) { const char *pubkey_raw; int hash_algorithm = SHA256_HASH; Py_ssize_t pubkey_len; if (!PyArg_ParseTuple(args, "y#|i", &pubkey_raw, &pubkey_len, &hash_algorithm)) return NULL; EVP_PKEY_CTX *ctx = NULL; unsigned char *secret = NULL; size_t secret_len = 0; EVP_PKEY *public_key = EVP_PKEY_new_raw_public_key(self->algorithm, NULL, (const unsigned char*)pubkey_raw, pubkey_len); #define cleanup() { if (public_key) EVP_PKEY_free(public_key); public_key = NULL; if (ctx) EVP_PKEY_CTX_free(ctx); ctx = NULL; if (secret) OPENSSL_clear_free(secret, secret_len); secret = NULL; } #define ssl_error(text) { cleanup(); return set_error_from_openssl(text); } if (!public_key) ssl_error("Failed to create public key"); if (NULL == (ctx = EVP_PKEY_CTX_new(self->key, NULL))) ssl_error("Failed to create context for shared secret derivation"); if (1 != EVP_PKEY_derive_init(ctx)) ssl_error("Failed to initialize derivation"); if (1 != EVP_PKEY_derive_set_peer(ctx, public_key)) ssl_error("Failed to add public key"); if (1 != EVP_PKEY_derive(ctx, NULL, &secret_len)) ssl_error("Failed to get length for secret"); if (NULL == (secret = OPENSSL_malloc(secret_len))) ssl_error("Failed to allocate secret key"); if (mlock(secret, secret_len) != 0) { cleanup(); return PyErr_SetFromErrno(PyExc_OSError); } if (1 != (EVP_PKEY_derive(ctx, secret, &secret_len))) ssl_error("Failed to derive the secret"); PyObject *ans = hash_data_to_secret(secret, secret_len, hash_algorithm); cleanup(); return ans; #undef cleanup #undef ssl_error } static PyObject* elliptic_curve_key_get_public(EllipticCurveKey *self, void UNUSED *closure) { /* PEM_write_PUBKEY(stdout, pkey); */ size_t len = 0; if (1 != EVP_PKEY_get_raw_public_key(self->key, NULL, &len)) return set_error_from_openssl("Could not get public key from EVP_PKEY"); PyObject *ans = PyBytes_FromStringAndSize(NULL, len); if (!ans) return NULL; if (1 != EVP_PKEY_get_raw_public_key(self->key, (unsigned char*)PyBytes_AS_STRING(ans), &len)) { Py_CLEAR(ans); return set_error_from_openssl("Could not get public key from EVP_PKEY"); } return ans; } static PyObject* elliptic_curve_key_get_private(EllipticCurveKey *self, void UNUSED *closure) { size_t len = 0; if (1 != EVP_PKEY_get_raw_private_key(self->key, NULL, &len)) return set_error_from_openssl("Could not get public key from EVP_PKEY"); Secret *ans = alloc_secret(len); if (!ans) return NULL; if (mlock(PyBytes_AS_STRING(ans), len) != 0) { Py_CLEAR(ans); return PyErr_SetFromErrno(PyExc_OSError); } if (1 != EVP_PKEY_get_raw_private_key(self->key, (unsigned char*)ans->secret, &len)) { Py_CLEAR(ans); return set_error_from_openssl("Could not get public key from EVP_PKEY"); } return (PyObject*)ans; } static PyGetSetDef getsetters[] = { {"public", (getter)elliptic_curve_key_get_public, NULL, "Get the public key as raw bytes", NULL}, {"private", (getter)elliptic_curve_key_get_private, NULL, "Get the private key as raw bytes", NULL}, {NULL} /* Sentinel */ }; static PyMethodDef methods[] = { METHODB(derive_secret, METH_VARARGS), {NULL} /* Sentinel */ }; static PyTypeObject EllipticCurveKey_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.EllipticCurveKey", .tp_basicsize = sizeof(EllipticCurveKey), .tp_dealloc = (destructor)dealloc_ec_key, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "Keys for use with Elliptic Curve crypto", .tp_new = new_ec_key, .tp_methods = methods, .tp_getset = getsetters, }; // }}} // AES256GCMEncrypt {{{ typedef struct { PyObject_HEAD EVP_CIPHER_CTX *ctx; PyObject *iv, *tag; int state; } AES256GCMEncrypt; static PyObject * new_aes256gcmencrypt(PyTypeObject *type, PyObject *args, PyObject *kwds UNUSED) { Secret *key; if (!PyArg_ParseTuple(args, "O!", &Secret_Type, &key)) return NULL; const EVP_CIPHER *cipher = EVP_get_cipherbynid(NID_aes_256_gcm); if (key->secret_len != (size_t)EVP_CIPHER_key_length(cipher)) { PyErr_Format(PyExc_ValueError, "The key for AES 256 GCM must be %d bytes long", EVP_CIPHER_key_length(cipher)); return NULL; } AES256GCMEncrypt *self = (AES256GCMEncrypt *)type->tp_alloc(type, 0); if (!self) return NULL; if (!(self->ctx = EVP_CIPHER_CTX_new())) { Py_CLEAR(self); return set_error_from_openssl("Failed to allocate encryption context"); } if (!(self->iv = PyBytes_FromStringAndSize(NULL, EVP_CIPHER_iv_length(cipher)))) { Py_CLEAR(self); return NULL; } if (!secure_random_bytes((unsigned char*)PyBytes_AS_STRING(self->iv), PyBytes_GET_SIZE(self->iv))) { Py_CLEAR(self); return NULL; } if (!(self->tag = PyBytes_FromStringAndSize(NULL, 0))) { Py_CLEAR(self); return NULL; } if (1 != EVP_EncryptInit_ex(self->ctx, cipher, NULL, key->secret, (const unsigned char*)PyBytes_AS_STRING(self->iv))) { Py_CLEAR(self); return set_error_from_openssl("Failed to initialize encryption context"); } return (PyObject*)self; } static void dealloc_aes256gcmencrypt(AES256GCMEncrypt *self) { Py_CLEAR(self->iv); Py_CLEAR(self->tag); if (self->ctx) EVP_CIPHER_CTX_free(self->ctx); Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject* add_authenticated_but_unencrypted_data(AES256GCMEncrypt *self, PyObject *args) { if (self->state > 0) { PyErr_SetString(Crypto_Exception, "Cannot add data once encryption has started"); return NULL; } const char *aad; Py_ssize_t aad_len; if (!PyArg_ParseTuple(args, "y#", &aad, &aad_len)) return NULL; int len; if (aad_len > 0 && 1 != EVP_EncryptUpdate(self->ctx, NULL, &len, (const unsigned char*)aad, aad_len)) return set_error_from_openssl("Failed to add AAD data"); Py_RETURN_NONE; } static int cipher_ctx_tag_length(const EVP_CIPHER_CTX *ctx) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L return EVP_CIPHER_CTX_tag_length(ctx); #else (void)ctx; return 16; #endif } static PyObject* add_data_to_be_encrypted(AES256GCMEncrypt *self, PyObject *args) { if (self->state > 1) { PyErr_SetString(Crypto_Exception, "Encryption has been finished"); return NULL; } const char *plaintext; Py_ssize_t plaintext_len; int finish_encryption = 0; if (!PyArg_ParseTuple(args, "y#|p", &plaintext, &plaintext_len, &finish_encryption)) return NULL; PyObject *ciphertext = PyBytes_FromStringAndSize(NULL, plaintext_len + 2 * EVP_CIPHER_CTX_block_size(self->ctx)); if (!ciphertext) return NULL; self->state = 1; int offset = 0; if (plaintext_len) { int len = PyBytes_GET_SIZE(ciphertext); if (1 != EVP_EncryptUpdate(self->ctx, (unsigned char*)PyBytes_AS_STRING(ciphertext), &len, (const unsigned char*)plaintext, plaintext_len) ) { Py_CLEAR(ciphertext); return set_error_from_openssl("Failed to encrypt"); } offset = len; } if (finish_encryption) { int len = PyBytes_GET_SIZE(ciphertext) - offset; if (1 != EVP_EncryptFinal_ex(self->ctx, (unsigned char*)PyBytes_AS_STRING(ciphertext) + offset, &len)) { Py_CLEAR(ciphertext); return set_error_from_openssl("Failed to finish encryption"); } offset += len; self->state = 2; PyObject *tag = PyBytes_FromStringAndSize(NULL, cipher_ctx_tag_length(self->ctx)); if (!tag) { Py_CLEAR(ciphertext); return NULL; } Py_CLEAR(self->tag); self->tag = tag; if (1 != EVP_CIPHER_CTX_ctrl(self->ctx, EVP_CTRL_AEAD_GET_TAG, PyBytes_GET_SIZE(self->tag), PyBytes_AS_STRING(tag))) { Py_CLEAR(ciphertext); return NULL; } } if (offset != PyBytes_GET_SIZE(ciphertext)) { _PyBytes_Resize(&ciphertext, offset); if (!ciphertext) return NULL; } return ciphertext; } static PyMethodDef aes256gcmencrypt_methods[] = { METHODB(add_authenticated_but_unencrypted_data, METH_VARARGS), METHODB(add_data_to_be_encrypted, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; static PyMemberDef aes256gcmencrypt_members[] = { {"iv", T_OBJECT_EX, offsetof(AES256GCMEncrypt, iv), READONLY, "IV"}, {"tag", T_OBJECT_EX, offsetof(AES256GCMEncrypt, tag), READONLY, "The tag for authentication"}, {NULL} }; static PyTypeObject AES256GCMEncrypt_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.AES256GCMEncrypt", .tp_basicsize = sizeof(AES256GCMEncrypt), .tp_dealloc = (destructor)dealloc_aes256gcmencrypt, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "Encrypt using AES 256 GCM with authentication", .tp_new = new_aes256gcmencrypt, .tp_methods = aes256gcmencrypt_methods, .tp_members = aes256gcmencrypt_members, }; // }}} // AES256GCMDecrypt {{{ typedef struct { PyObject_HEAD EVP_CIPHER_CTX *ctx; int state; } AES256GCMDecrypt; static PyObject * new_aes256gcmdecrypt(PyTypeObject *type, PyObject *args, PyObject *kwds UNUSED) { Secret *key; unsigned char *iv, *tag; Py_ssize_t iv_len, tag_len; if (!PyArg_ParseTuple(args, "O!y#y#", &Secret_Type, &key, &iv, &iv_len, &tag, &tag_len)) return NULL; const EVP_CIPHER *cipher = EVP_get_cipherbynid(NID_aes_256_gcm); if (key->secret_len != (size_t)EVP_CIPHER_key_length(cipher)) { PyErr_Format(PyExc_ValueError, "The key for AES 256 GCM must be %d bytes long", EVP_CIPHER_key_length(cipher)); return NULL; } if (iv_len < EVP_CIPHER_iv_length(cipher)) { PyErr_Format(PyExc_ValueError, "The iv for AES 256 GCM must be at least %d bytes long", EVP_CIPHER_iv_length(cipher)); return NULL; } AES256GCMDecrypt *self = (AES256GCMDecrypt *)type->tp_alloc(type, 0); if (!self) return NULL; if (!(self->ctx = EVP_CIPHER_CTX_new())) { Py_CLEAR(self); return set_error_from_openssl("Failed to allocate decryption context"); } if (iv_len > EVP_CIPHER_iv_length(cipher)) { if (!EVP_CIPHER_CTX_ctrl(self->ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL)) { Py_CLEAR(self); return set_error_from_openssl("Failed to set the IV length"); } } if (1 != EVP_DecryptInit_ex(self->ctx, cipher, NULL, key->secret, iv)) { Py_CLEAR(self); return set_error_from_openssl("Failed to initialize encryption context"); } // Ensure tag length is 16 because the OpenSSL verification routines will happily pass even if you set a truncated tag. if (tag_len < cipher_ctx_tag_length(self->ctx)) { PyErr_Format(PyExc_ValueError, "Tag length for AES 256 GCM must be at least %d", cipher_ctx_tag_length(self->ctx)); return NULL; } if (!EVP_CIPHER_CTX_ctrl(self->ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, tag)) { Py_CLEAR(self); return set_error_from_openssl("Failed to set the tag"); } return (PyObject*)self; } static void dealloc_aes256gcmdecrypt(AES256GCMDecrypt *self) { if (self->ctx) EVP_CIPHER_CTX_free(self->ctx); Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject* add_data_to_be_authenticated_but_not_decrypted(AES256GCMDecrypt *self, PyObject *args) { if (self->state > 0) { PyErr_SetString(Crypto_Exception, "Cannot add data once decryption has started"); return NULL; } const char *aad; Py_ssize_t aad_len; if (!PyArg_ParseTuple(args, "y#", &aad, &aad_len)) return NULL; int len; if (aad_len > 0 && 1 != EVP_DecryptUpdate(self->ctx, NULL, &len, (const unsigned char*)aad, aad_len)) return set_error_from_openssl("Failed to add AAD data"); Py_RETURN_NONE; } static PyObject* add_data_to_be_decrypted(AES256GCMDecrypt *self, PyObject *args) { if (self->state > 1) { PyErr_SetString(Crypto_Exception, "Decryption has been finished"); return NULL; } const char *ciphertext; Py_ssize_t ciphertext_len; int finish_decryption = 0; if (!PyArg_ParseTuple(args, "y#|p", &ciphertext, &ciphertext_len, &finish_decryption)) return NULL; PyObject *plaintext = PyBytes_FromStringAndSize(NULL, ciphertext_len + 2 * EVP_CIPHER_CTX_block_size(self->ctx)); if (!plaintext) return NULL; self->state = 1; int offset = 0; if (ciphertext_len) { int len = PyBytes_GET_SIZE(plaintext); if (1 != EVP_DecryptUpdate(self->ctx, (unsigned char*)PyBytes_AS_STRING(plaintext), &len, (const unsigned char*)ciphertext, ciphertext_len) ) { Py_CLEAR(plaintext); return set_error_from_openssl("Failed to decrypt"); } offset = len; } if (finish_decryption) { int len = PyBytes_GET_SIZE(plaintext) - offset; int ret = EVP_DecryptFinal_ex(self->ctx, (unsigned char*)PyBytes_AS_STRING(plaintext) + offset, &len); self->state = 2; if (ret <= 0) { Py_CLEAR(plaintext); PyErr_SetString(Crypto_Exception, "Failed to finish decrypt"); return NULL; } offset += len; } if (offset != PyBytes_GET_SIZE(plaintext)) { _PyBytes_Resize(&plaintext, offset); if (!plaintext) return NULL; } return plaintext; } static PyMethodDef aes256gcmdecrypt_methods[] = { METHODB(add_data_to_be_authenticated_but_not_decrypted, METH_VARARGS), METHODB(add_data_to_be_decrypted, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; static PyTypeObject AES256GCMDecrypt_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.AES256GCMDecrypt", .tp_basicsize = sizeof(AES256GCMDecrypt), .tp_dealloc = (destructor)dealloc_aes256gcmdecrypt, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "Decrypt using AES 256 GCM with authentication", .tp_new = new_aes256gcmdecrypt, .tp_methods = aes256gcmdecrypt_methods, }; // }}} static PyMethodDef module_methods[] = { {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_crypto_library(PyObject *module) { Crypto_Exception = PyErr_NewException("fast_data_types.CryptoError", NULL, NULL); if (Crypto_Exception == NULL) return false; if (PyModule_AddObject(module, "CryptoError", Crypto_Exception) != 0) return false; if (PyModule_AddFunctions(module, module_methods) != 0) return false; ADD_TYPE(Secret); ADD_TYPE(EllipticCurveKey); ADD_TYPE(AES256GCMEncrypt); ADD_TYPE(AES256GCMDecrypt); if (PyModule_AddIntConstant(module, "X25519", EVP_PKEY_X25519) != 0) return false; #define AI(which) if (PyModule_AddIntMacro(module, which) != 0) return false; AI(SHA1_HASH); AI(SHA224_HASH); AI(SHA256_HASH); AI(SHA384_HASH); AI(SHA512_HASH); #undef AI return true; } kitty-0.41.1/kitty/cursor.c0000664000175000017510000002521514773370543015141 0ustar nileshnilesh/* * cursor.c * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "line.h" #include static PyObject * new_cursor_object(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) { Cursor *self; self = (Cursor *)type->tp_alloc(type, 0); return (PyObject*) self; } static void dealloc(Cursor* self) { Py_TYPE(self)->tp_free((PyObject*)self); } #define EQ(x) (a->x == b->x) static int __eq__(Cursor *a, Cursor *b) { return EQ(bold) && EQ(italic) && EQ(strikethrough) && EQ(dim) && EQ(reverse) && EQ(decoration) && EQ(fg) && EQ(bg) && EQ(decoration_fg) && EQ(x) && EQ(y) && EQ(shape) && EQ(non_blinking); } static const char* cursor_names[NUM_OF_CURSOR_SHAPES] = { "NO_SHAPE", "BLOCK", "BEAM", "UNDERLINE", "HOLLOW" }; #define BOOL(x) ((x) ? Py_True : Py_False) static PyObject * repr(Cursor *self) { return PyUnicode_FromFormat( "Cursor(x=%u, y=%u, shape=%s, blink=%R, fg=#%08x, bg=#%08x, bold=%R, italic=%R, reverse=%R, strikethrough=%R, dim=%R, decoration=%d, decoration_fg=#%08x)", self->x, self->y, (self->shape < NUM_OF_CURSOR_SHAPES ? cursor_names[self->shape] : "INVALID"), BOOL(!self->non_blinking), self->fg, self->bg, BOOL(self->bold), BOOL(self->italic), BOOL(self->reverse), BOOL(self->strikethrough), BOOL(self->dim), self->decoration, self->decoration_fg ); } void cursor_reset_display_attrs(Cursor *self) { self->bg = 0; self->fg = 0; self->decoration_fg = 0; self->decoration = 0; self->bold = false; self->italic = false; self->reverse = false; self->strikethrough = false; self->dim = false; } static void parse_color(int *params, unsigned int *i, unsigned int count, uint32_t *result) { unsigned int attr; uint8_t r, g, b; if (*i < count) { attr = params[(*i)++]; switch(attr) { case 5: if (*i < count) *result = (params[(*i)++] & 0xFF) << 8 | 1; break; case 2: \ if (*i + 2 < count) { /* Ignore the first parameter in a four parameter RGB */ /* sequence (unused color space id), see https://github.com/kovidgoyal/kitty/issues/227 */ if (*i +3 < count) (*i)++; r = params[(*i)++] & 0xFF; g = params[(*i)++] & 0xFF; b = params[(*i)++] & 0xFF; *result = r << 24 | g << 16 | b << 8 | 2; } break; } } } void cursor_from_sgr(Cursor *self, int *params, unsigned int count, bool is_group) { #define SET_COLOR(which) { parse_color(params, &i, count, &self->which); } break; START_ALLOW_CASE_RANGE unsigned int i = 0, attr; if (!count) { params[0] = 0; count = 1; } while (i < count) { attr = params[i++]; switch(attr) { case 0: cursor_reset_display_attrs(self); break; case 1: self->bold = true; break; case 2: self->dim = true; break; case 3: self->italic = true; break; case 4: if (is_group && i < count) { self->decoration = MIN(5, params[i]); i++; } else self->decoration = 1; break; case 7: self->reverse = true; break; case 9: self->strikethrough = true; break; case 21: self->decoration = 2; break; case 221: self->bold = false; break; case 222: self->dim = false; break; case 22: self->bold = false; self->dim = false; break; case 23: self->italic = false; break; case 24: self->decoration = 0; break; case 27: self->reverse = false; break; case 29: self->strikethrough = false; break; case 30 ... 37: self->fg = ((attr - 30) << 8) | 1; break; case 38: SET_COLOR(fg); case 39: self->fg = 0; break; case 40 ... 47: self->bg = ((attr - 40) << 8) | 1; break; case 48: SET_COLOR(bg); case 49: self->bg = 0; break; case 90 ... 97: self->fg = ((attr - 90 + 8) << 8) | 1; break; case 100 ... 107: self->bg = ((attr - 100 + 8) << 8) | 1; break; case DECORATION_FG_CODE: SET_COLOR(decoration_fg); case DECORATION_FG_CODE + 1: self->decoration_fg = 0; break; } if (is_group) break; } #undef SET_COLOR END_ALLOW_CASE_RANGE } void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, unsigned int count, bool is_group) { #define RANGE for(unsigned c = 0; c < cell_count; c++, cell++) #define SET_COLOR(which) { color_type color = 0; parse_color(params, &i, count, &color); if (color) { RANGE { cell->which = color; }} } break; #define SIMPLE(which, val) RANGE { cell->which = (val); } break; #define S(which, val) RANGE { cell->attrs.which = (val); } break; unsigned int i = 0, attr; if (!count) { params[0] = 0; count = 1; } while (i < count) { GPUCell *cell = first_cell; attr = params[i++]; switch(attr) { case 0: { const CellAttrs remove_sgr_mask = {.val=~SGR_MASK}; RANGE { cell->attrs.val &= remove_sgr_mask.val; cell->fg = 0; cell->bg = 0; cell->decoration_fg = 0; } } break; case 1: S(bold, true); case 2: S(dim, true); case 3: S(italic, true); case 4: { uint8_t val = 1; if (is_group && i < count) { val = MIN(5, params[i]); i++; } S(decoration, val); } case 7: S(reverse, true); case 9: S(strike, true); case 21: S(decoration, 2); case 221: S(bold, false); case 222: S(dim, false); case 22: RANGE { cell->attrs.bold = false; cell->attrs.dim = false; } break; case 23: S(italic, false); case 24: S(decoration, 0); case 27: S(reverse, false); case 29: S(strike, false); START_ALLOW_CASE_RANGE case 30 ... 37: SIMPLE(fg, ((attr - 30) << 8) | 1); case 38: SET_COLOR(fg); case 39: SIMPLE(fg, 0); case 40 ... 47: SIMPLE(bg, ((attr - 40) << 8) | 1); case 48: SET_COLOR(bg); case 49: SIMPLE(bg, 0); case 90 ... 97: SIMPLE(fg, ((attr - 90 + 8) << 8) | 1); case 100 ... 107: SIMPLE(bg, ((attr - 100 + 8) << 8) | 1); END_ALLOW_CASE_RANGE case DECORATION_FG_CODE: SET_COLOR(decoration_fg); case DECORATION_FG_CODE + 1: SIMPLE(decoration_fg, 0); } if (is_group) break; } #undef SET_COLOR #undef RANGE #undef SIMPLE #undef S } const char* cursor_as_sgr(const Cursor *self) { GPUCell blank_cell = { 0 }, cursor_cell = { .attrs = cursor_to_attrs(self), .fg = self->fg & COL_MASK, .bg = self->bg & COL_MASK, .decoration_fg = self->decoration_fg & COL_MASK, }; return cell_as_sgr(&cursor_cell, &blank_cell); } static PyObject * reset_display_attrs(Cursor *self, PyObject *a UNUSED) { #define reset_display_attrs_doc "Reset all display attributes to unset" cursor_reset_display_attrs(self); Py_RETURN_NONE; } void cursor_reset(Cursor *self) { cursor_reset_display_attrs(self); self->x = 0; self->y = 0; self->shape = NO_CURSOR_SHAPE; self->non_blinking = false; } void cursor_copy_to(Cursor *src, Cursor *dest) { #define CCY(x) dest->x = src->x; CCY(x); CCY(y); CCY(shape); CCY(non_blinking); CCY(bold); CCY(italic); CCY(strikethrough); CCY(dim); CCY(reverse); CCY(decoration); CCY(fg); CCY(bg); CCY(decoration_fg); } static PyObject* copy(Cursor *self, PyObject*); #define copy_doc "Create a clone of this cursor" // Boilerplate {{{ BOOL_GETSET(Cursor, bold) BOOL_GETSET(Cursor, italic) BOOL_GETSET(Cursor, reverse) BOOL_GETSET(Cursor, strikethrough) BOOL_GETSET(Cursor, dim) static PyObject* blink_get(Cursor *self, void UNUSED *closure) { PyObject *ans = !self->non_blinking ? Py_True : Py_False; Py_INCREF(ans); return ans; } static int blink_set(Cursor *self, PyObject *value, void UNUSED *closure) { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } self->non_blinking = PyObject_IsTrue(value) ? false : true; return 0; } static PyMemberDef members[] = { {"x", T_UINT, offsetof(Cursor, x), 0, "x"}, {"y", T_UINT, offsetof(Cursor, y), 0, "y"}, {"shape", T_INT, offsetof(Cursor, shape), 0, "shape"}, {"decoration", T_UBYTE, offsetof(Cursor, decoration), 0, "decoration"}, {"fg", T_UINT, offsetof(Cursor, fg), 0, "fg"}, {"bg", T_UINT, offsetof(Cursor, bg), 0, "bg"}, {"decoration_fg", T_UINT, offsetof(Cursor, decoration_fg), 0, "decoration_fg"}, {NULL} /* Sentinel */ }; static PyGetSetDef getseters[] = { GETSET(bold) GETSET(italic) GETSET(reverse) GETSET(strikethrough) GETSET(dim) GETSET(blink) {NULL} /* Sentinel */ }; static PyMethodDef methods[] = { METHOD(copy, METH_NOARGS) METHOD(reset_display_attrs, METH_NOARGS) {NULL} /* Sentinel */ }; static PyObject * richcmp(PyObject *obj1, PyObject *obj2, int op); PyTypeObject Cursor_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.Cursor", .tp_basicsize = sizeof(Cursor), .tp_dealloc = (destructor)dealloc, .tp_repr = (reprfunc)repr, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "Cursors", .tp_richcompare = richcmp, .tp_methods = methods, .tp_members = members, .tp_getset = getseters, .tp_new = new_cursor_object, }; RICHCMP(Cursor) // }}} Cursor* cursor_copy(Cursor *self) { Cursor* ans; ans = alloc_cursor(); if (ans == NULL) { PyErr_NoMemory(); return NULL; } cursor_copy_to(self, ans); return ans; } static PyObject* copy(Cursor *self, PyObject *a UNUSED) { return (PyObject*)cursor_copy(self); } Cursor *alloc_cursor(void) { return (Cursor*)new_cursor_object(&Cursor_Type, NULL, NULL); } INIT_TYPE(Cursor) kitty-0.41.1/kitty/cursor_trail.c0000664000175000017510000001464714773370543016343 0ustar nileshnilesh#include #include "state.h" inline static float norm(float x, float y) { return sqrtf(x * x + y * y); } static void update_cursor_trail_target(CursorTrail *ct, Window *w) { #define EDGE(axis, index) ct->cursor_edge_##axis[index] #define WD w->render_data float left = FLT_MAX, right = FLT_MAX, top = FLT_MAX, bottom = FLT_MAX; switch (WD.screen->cursor_render_info.shape) { case CURSOR_BLOCK: case CURSOR_HOLLOW: case CURSOR_BEAM: case CURSOR_UNDERLINE: left = WD.xstart + WD.screen->cursor_render_info.x * WD.dx; bottom = WD.ystart - (WD.screen->cursor_render_info.y + 1) * WD.dy; default: break; } switch (WD.screen->cursor_render_info.shape) { case CURSOR_BLOCK: case CURSOR_HOLLOW: right = left + WD.dx; top = bottom + WD.dy; break; case CURSOR_BEAM: right = left + WD.dx / WD.screen->cell_size.width * OPT(cursor_beam_thickness); top = bottom + WD.dy; break; case CURSOR_UNDERLINE: right = left + WD.dx; top = bottom + WD.dy / WD.screen->cell_size.height * OPT(cursor_underline_thickness); break; default: break; } if (left != FLT_MAX) { EDGE(x, 0) = left; EDGE(x, 1) = right; EDGE(y, 0) = top; EDGE(y, 1) = bottom; } } static bool should_skip_cursor_trail_update(CursorTrail *ct, Window *w, OSWindow *os_window) { if (os_window->live_resize.in_progress) { return true; } if (OPT(cursor_trail_start_threshold) > 0 && !ct->needs_render) { int dx = (int)round((ct->corner_x[0] - EDGE(x, 1)) / WD.dx); int dy = (int)round((ct->corner_y[0] - EDGE(y, 0)) / WD.dy); if (abs(dx) + abs(dy) <= OPT(cursor_trail_start_threshold)) { return true; } } return false; } static void update_cursor_trail_corners(CursorTrail *ct, Window *w, monotonic_t now, OSWindow *os_window) { // the trail corners move towards the cursor corner at a speed proportional to their distance from the cursor corner. // equivalent to exponential ease out animation. static const int corner_index[2][4] = {{1, 1, 0, 0}, {0, 1, 1, 0}}; // the decay time for the trail to reach 1/1024 of its distance from the cursor corner float decay_fast = OPT(cursor_trail_decay_fast); float decay_slow = OPT(cursor_trail_decay_slow); if (should_skip_cursor_trail_update(ct, w, os_window)) { for (int i = 0; i < 4; ++i) { ct->corner_x[i] = EDGE(x, corner_index[0][i]); ct->corner_y[i] = EDGE(y, corner_index[1][i]); } } else if (ct->updated_at < now) { float cursor_center_x = (EDGE(x, 0) + EDGE(x, 1)) * 0.5f; float cursor_center_y = (EDGE(y, 0) + EDGE(y, 1)) * 0.5f; float cursor_diag_2 = norm(EDGE(x, 1) - EDGE(x, 0), EDGE(y, 1) - EDGE(y, 0)) * 0.5f; float dt = (float)monotonic_t_to_s_double(now - ct->updated_at); // dot product here is used to dynamically adjust the decay speed of // each corner. The closer the corner is to the cursor, the faster it // moves. float dx[4], dy[4]; float dot[4]; // dot product of "direction vector" and "cursor center to corner vector" for (int i = 0; i < 4; ++i) { dx[i] = EDGE(x, corner_index[0][i]) - ct->corner_x[i]; dy[i] = EDGE(y, corner_index[1][i]) - ct->corner_y[i]; if (fabsf(dx[i]) < 1e-6 && fabsf(dy[i]) < 1e-6) { dx[i] = dy[i] = 0.0f; dot[i] = 0.0f; continue; } dot[i] = (dx[i] * (EDGE(x, corner_index[0][i]) - cursor_center_x) + dy[i] * (EDGE(y, corner_index[1][i]) - cursor_center_y)) / cursor_diag_2 / norm(dx[i], dy[i]); } float min_dot = FLT_MAX, max_dot = -FLT_MAX; for (int i = 0; i < 4; ++i) { min_dot = fminf(min_dot, dot[i]); max_dot = fmaxf(max_dot, dot[i]); } for (int i = 0; i < 4; ++i) { if ((dx[i] == 0 && dy[i] == 0) || min_dot == FLT_MAX) { continue; } float decay = (min_dot == max_dot) ? decay_slow : decay_slow + (decay_fast - decay_slow) * (dot[i] - min_dot) / (max_dot - min_dot); float step = 1.0f - exp2f(-10.0f * dt / decay); ct->corner_x[i] += dx[i] * step; ct->corner_y[i] += dy[i] * step; } } } static void update_cursor_trail_opacity(CursorTrail *ct, Window *w, monotonic_t now) { const bool cursor_trail_always_visible = false; if (cursor_trail_always_visible) { ct->opacity = 1.0f; } else if (WD.screen->modes.mDECTCEM) { ct->opacity += (float)monotonic_t_to_s_double(now - ct->updated_at) / OPT(cursor_trail_decay_slow); ct->opacity = fminf(ct->opacity, 1.0f); } else { ct->opacity -= (float)monotonic_t_to_s_double(now - ct->updated_at) / OPT(cursor_trail_decay_slow); ct->opacity = fmaxf(ct->opacity, 0.0f); } } static void update_cursor_trail_needs_render(CursorTrail *ct, Window *w) { static const int corner_index[2][4] = {{1, 1, 0, 0}, {0, 1, 1, 0}}; ct->needs_render = false; // check if any corner is still far from the cursor corner, so it should be rendered const float dx_threshold = WD.dx / WD.screen->cell_size.width * 0.5f; const float dy_threshold = WD.dy / WD.screen->cell_size.height * 0.5f; for (int i = 0; i < 4; ++i) { float dx = fabsf(EDGE(x, corner_index[0][i]) - ct->corner_x[i]); float dy = fabsf(EDGE(y, corner_index[1][i]) - ct->corner_y[i]); if (dx_threshold <= dx || dy_threshold <= dy) { ct->needs_render = true; break; } } } bool update_cursor_trail(CursorTrail *ct, Window *w, monotonic_t now, OSWindow *os_window) { if (!WD.screen->paused_rendering.expires_at && OPT(cursor_trail) <= now - WD.screen->cursor->position_changed_by_client_at) { update_cursor_trail_target(ct, w); } update_cursor_trail_corners(ct, w, now, os_window); update_cursor_trail_opacity(ct, w, now); bool needs_render_prev = ct->needs_render; update_cursor_trail_needs_render(ct, w); ct->updated_at = now; // returning true here will cause the cells to be drawn return ct->needs_render || needs_render_prev; } #undef WD #undef EDGE kitty-0.41.1/kitty/data-types.c0000664000175000017510000010056414773370543015700 0ustar nileshnilesh/* * data-types.c * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #ifdef __APPLE__ // Needed for _CS_DARWIN_USER_CACHE_DIR #define _DARWIN_C_SOURCE #include #undef _DARWIN_C_SOURCE #endif #include "char-props.h" #include "line.h" #include "charsets.h" #include "base64.h" #include #include #include #include "cleanup.h" #include "safe-wrappers.h" #include "control-codes.h" #include "wcswidth.h" #include "modes.h" #include #include #include #include #include #ifdef WITH_PROFILER #include #endif #include "monotonic.h" #ifdef __APPLE__ #include #include static PyObject* user_cache_dir(PyObject *self UNUSED, PyObject *args UNUSED) { static char buf[1024]; if (!confstr(_CS_DARWIN_USER_CACHE_DIR, buf, sizeof(buf) - 1)) return PyErr_SetFromErrno(PyExc_OSError); return PyUnicode_FromString(buf); } static PyObject* process_group_map(PyObject *self UNUSED, PyObject *args UNUSED) { int num_of_processes = proc_listallpids(NULL, 0); size_t bufsize = sizeof(pid_t) * (num_of_processes + 1024); RAII_ALLOC(pid_t, buf, malloc(bufsize)); if (!buf) return PyErr_NoMemory(); num_of_processes = proc_listallpids(buf, (int)bufsize); PyObject *ans = PyTuple_New(num_of_processes); if (ans == NULL) { return PyErr_NoMemory(); } for (int i = 0; i < num_of_processes; i++) { long pid = buf[i], pgid = getpgid(buf[i]); PyObject *t = Py_BuildValue("ll", pid, pgid); if (t == NULL) { Py_DECREF(ans); return NULL; } PyTuple_SET_ITEM(ans, i, t); } return ans; } #endif static PyObject* redirect_std_streams(PyObject UNUSED *self, PyObject *args) { char *devnull = NULL; if (!PyArg_ParseTuple(args, "s", &devnull)) return NULL; if (freopen(devnull, "r", stdin) == NULL) return PyErr_SetFromErrno(PyExc_OSError); if (freopen(devnull, "w", stdout) == NULL) return PyErr_SetFromErrno(PyExc_OSError); if (freopen(devnull, "w", stderr) == NULL) return PyErr_SetFromErrno(PyExc_OSError); Py_RETURN_NONE; } static PyObject* pybase64_encode(PyObject UNUSED *self, PyObject *const *args, Py_ssize_t nargs) { int add_padding = 0; if (nargs < 1 || nargs > 2) { PyErr_SetString(PyExc_TypeError, "must supply one or two arguments"); return NULL; } RAII_PY_BUFFER(view); if (PyUnicode_Check(args[0])) view.buf = (void*)PyUnicode_AsUTF8AndSize(args[0], &view.len); else if (PyObject_GetBuffer(args[0], &view, PyBUF_SIMPLE) != 0) return NULL; if (nargs == 2) add_padding = PyObject_IsTrue(args[1]); size_t sz = required_buffer_size_for_base64_encode(view.len); PyObject *ans = PyBytes_FromStringAndSize(NULL, sz); if (!ans) return NULL; base64_encode8(view.buf, view.len, (unsigned char*)PyBytes_AS_STRING(ans), &sz, add_padding); if (_PyBytes_Resize(&ans, sz) != 0) return NULL; return ans; } static PyObject* base64_encode_into(PyObject UNUSED *self, PyObject *args) { int add_padding = 0; RAII_PY_BUFFER(view); RAII_PY_BUFFER(output); if (!PyArg_ParseTuple(args, "s*w*|i", &view, &output, &add_padding)) return NULL; size_t sz = required_buffer_size_for_base64_encode(view.len); if (output.len < (ssize_t)sz) { PyErr_SetString(PyExc_TypeError, "output buffer too small"); return NULL; } base64_encode8(view.buf, view.len, output.buf, &sz, add_padding); return PyLong_FromSize_t(sz); } static PyObject* pybase64_decode(PyObject UNUSED *self, PyObject *input_data) { RAII_PY_BUFFER(view); if (PyUnicode_Check(input_data)) view.buf = (void*)PyUnicode_AsUTF8AndSize(input_data, &view.len); else if (PyObject_GetBuffer(input_data, &view, PyBUF_SIMPLE) != 0) return NULL; size_t sz = required_buffer_size_for_base64_decode(view.len); PyObject *ans = PyBytes_FromStringAndSize(NULL, sz); if (!ans) return NULL; if (!base64_decode8(view.buf, view.len, (unsigned char*)PyBytes_AS_STRING(ans), &sz)) { Py_DECREF(ans); PyErr_SetString(PyExc_ValueError, "Invalid base64 input data"); return NULL; } if (_PyBytes_Resize(&ans, sz) != 0) return NULL; return ans; } static PyObject* base64_decode_into(PyObject UNUSED *self, PyObject *args) { RAII_PY_BUFFER(view); RAII_PY_BUFFER(output); if (!PyArg_ParseTuple(args, "s*w*", &view, &output)) return NULL; size_t sz = required_buffer_size_for_base64_decode(view.len); if (output.len < (ssize_t)sz) { PyErr_SetString(PyExc_TypeError, "output buffer too small"); return NULL; } if (!base64_decode8(view.buf, view.len, output.buf, &sz)) { PyErr_SetString(PyExc_ValueError, "Invalid base64 input data"); return NULL; } return PyLong_FromSize_t(sz); } static PyObject* split_into_graphemes(PyObject UNUSED *self, PyObject *src) { if (!PyUnicode_Check(src)) { PyErr_SetString(PyExc_TypeError, "must provide a unicode string"); return NULL; } int kind = PyUnicode_KIND(src); char *data = PyUnicode_DATA(src); RAII_PyObject(ans, PyList_New(0)); if (!ans) return NULL; GraphemeSegmentationResult s; grapheme_segmentation_reset(&s); Py_ssize_t pos = 0; for (Py_ssize_t i = 0; i < PyUnicode_GET_LENGTH(src); i++) { char_type ch = PyUnicode_READ(kind, data, i); if (!(s = grapheme_segmentation_step(s, char_props_for(ch))).add_to_current_cell) { RAII_PyObject(u, PyUnicode_FromKindAndData(kind, data + kind * pos, i - pos)); if (!u || PyList_Append(ans, u) != 0) return NULL; pos = i; } } if (pos < PyUnicode_GET_LENGTH(src)) { RAII_PyObject(u, PyUnicode_FromKindAndData(kind, data + kind * pos, PyUnicode_GET_LENGTH(src) - pos)); if (!u || PyList_Append(ans, u) != 0) return NULL; } return Py_NewRef(ans); } typedef struct StreamingBase64Decoder { PyObject_HEAD struct base64_state state; bool add_trailing_bytes, needs_more_data; } StreamingBase64Decoder; static void StreamingBase64Decoder_reset_(StreamingBase64Decoder *self) { base64_stream_decode_init(&self->state, 0); self->needs_more_data = false; } static int StreamingBase64Decoder_init(PyObject *s, PyObject *args, PyObject *kwds UNUSED) { if (PyTuple_GET_SIZE(args)) { PyErr_SetString(PyExc_TypeError, "constructor takes no arguments"); return -1; } StreamingBase64Decoder *self = (StreamingBase64Decoder*)s; base64_stream_decode_init(&self->state, 0); return 0; } static PyObject* StreamingBase64Decoder_decode(StreamingBase64Decoder *self, PyObject *a) { RAII_PY_BUFFER(data); if (PyObject_GetBuffer(a, &data, PyBUF_SIMPLE) != 0) return NULL; if (!data.buf || !data.len) return PyBytes_FromStringAndSize(NULL, 0); size_t sz = required_buffer_size_for_base64_decode(data.len); RAII_PyObject(ans, PyBytes_FromStringAndSize(NULL, sz)); if (!ans) return NULL; int ret; Py_BEGIN_ALLOW_THREADS ret = base64_stream_decode(&self->state, data.buf, data.len, PyBytes_AS_STRING(ans), &sz); Py_END_ALLOW_THREADS; if (!ret) { StreamingBase64Decoder_reset_(self); PyErr_SetString(PyExc_ValueError, "Invalid base64 input data"); return NULL; } if (self->state.eof) StreamingBase64Decoder_reset_(self); else self->needs_more_data = self->state.carry != 0 || self->state.bytes != 0; if (_PyBytes_Resize(&ans, sz) != 0) return NULL; return Py_NewRef(ans); } static PyObject* StreamingBase64Decoder_decode_into(StreamingBase64Decoder *self, PyObject *const *args, Py_ssize_t nargs) { if (nargs != 2) { PyErr_SetString(PyExc_TypeError, "constructor takes exactly two arguments"); return NULL; } RAII_PY_BUFFER(data); if (PyObject_GetBuffer(args[0], &data, PyBUF_WRITE) != 0) return NULL; if (!data.buf || !data.len) return PyLong_FromLong(0); RAII_PY_BUFFER(src); if (PyObject_GetBuffer(args[1], &src, PyBUF_SIMPLE) != 0) return NULL; if (!src.buf || !src.len) return PyLong_FromLong(0); size_t sz = required_buffer_size_for_base64_decode(src.len); if ((Py_ssize_t)sz > data.len) { PyErr_SetString(PyExc_BufferError, "output buffer too small"); return NULL; } int ret; Py_BEGIN_ALLOW_THREADS ret = base64_stream_decode(&self->state, src.buf, src.len, data.buf, &sz); Py_END_ALLOW_THREADS if (!ret) { StreamingBase64Decoder_reset_(self); PyErr_SetString(PyExc_ValueError, "Invalid base64 input data"); return NULL; } if (self->state.eof) StreamingBase64Decoder_reset_(self); else self->needs_more_data = true; return PyLong_FromSize_t(sz); } static PyObject* StreamingBase64Decoder_reset(StreamingBase64Decoder *self, PyObject *args UNUSED) { StreamingBase64Decoder_reset_(self); Py_RETURN_NONE; } static PyObject* StreamingBase64Decoder_needs_more_data(StreamingBase64Decoder *self, PyObject *args UNUSED) { return Py_NewRef(self->needs_more_data ? Py_True : Py_False); } static PyTypeObject StreamingBase64Decoder_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kitty.fast_data_types.StreamingBase64Decoder", .tp_basicsize = sizeof(StreamingBase64Decoder), .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "StreamingBase64Decoder", .tp_methods = (PyMethodDef[]){ {"decode", (PyCFunction)StreamingBase64Decoder_decode, METH_O, ""}, {"decode_into", (PyCFunction)(void(*)(void))StreamingBase64Decoder_decode_into, METH_FASTCALL, ""}, {"reset", (PyCFunction)StreamingBase64Decoder_reset, METH_NOARGS, ""}, {"needs_more_data", (PyCFunction)StreamingBase64Decoder_needs_more_data, METH_NOARGS, ""}, {NULL, NULL, 0, NULL}, }, .tp_new = PyType_GenericNew, .tp_init = StreamingBase64Decoder_init, }; static int StreamingBase64Encoder_init(PyObject *s, PyObject *args, PyObject *kwds UNUSED) { StreamingBase64Decoder *self = (StreamingBase64Decoder*)s; self->add_trailing_bytes = true; switch (PyTuple_GET_SIZE(args)) { case 0: break; case 1: self->add_trailing_bytes = PyObject_IsTrue(PyTuple_GET_ITEM(args, 0)); break; default: PyErr_SetString(PyExc_TypeError, "constructor takes no more than one argument"); return -1; } base64_stream_encode_init(&self->state, 0); return 0; } static PyObject* StreamingBase64Encoder_encode(StreamingBase64Decoder *self, PyObject *a) { RAII_PY_BUFFER(data); if (PyObject_GetBuffer(a, &data, PyBUF_SIMPLE) != 0) return NULL; if (!data.buf || !data.len) return PyBytes_FromStringAndSize(NULL, 0); size_t sz = required_buffer_size_for_base64_encode(data.len); RAII_PyObject(ans, PyBytes_FromStringAndSize(NULL, sz)); if (!ans) return NULL; Py_BEGIN_ALLOW_THREADS base64_stream_encode(&self->state, data.buf, data.len, PyBytes_AS_STRING(ans), &sz); Py_END_ALLOW_THREADS if (_PyBytes_Resize(&ans, sz) != 0) return NULL; return Py_NewRef(ans); } static PyObject* StreamingBase64Encoder_encode_into(StreamingBase64Decoder *self, PyObject *const *args, Py_ssize_t nargs) { if (nargs != 2) { PyErr_SetString(PyExc_TypeError, "constructor takes exactly two arguments"); return NULL; } RAII_PY_BUFFER(data); if (PyObject_GetBuffer(args[0], &data, PyBUF_WRITE) != 0) return NULL; if (!data.buf || !data.len) return PyLong_FromLong(0); RAII_PY_BUFFER(src); if (PyObject_GetBuffer(args[1], &src, PyBUF_SIMPLE) != 0) return NULL; if (!src.buf || !src.len) return PyLong_FromLong(0); size_t sz = required_buffer_size_for_base64_encode(src.len); if ((Py_ssize_t)sz > data.len) { PyErr_SetString(PyExc_BufferError, "output buffer too small"); return NULL; } Py_BEGIN_ALLOW_THREADS base64_stream_encode(&self->state, src.buf, src.len, data.buf, &sz); Py_END_ALLOW_THREADS return PyLong_FromSize_t(sz); } static PyObject* StreamingBase64Encoder_reset(StreamingBase64Decoder *self, PyObject *args UNUSED) { char trailer[4]; size_t sz; base64_stream_encode_final(&self->state, trailer, &sz); base64_stream_encode_init(&self->state, 0); if (!self->add_trailing_bytes) { while(sz && trailer[sz-1] == '=') sz--; } return PyBytes_FromStringAndSize(trailer, sz); } static PyTypeObject StreamingBase64Encoder_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kitty.fast_data_types.StreamingBase64Encoder", .tp_basicsize = sizeof(StreamingBase64Decoder), .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "StreamingBase64Encoder", .tp_methods = (PyMethodDef[]){ {"encode", (PyCFunction)StreamingBase64Encoder_encode, METH_O, ""}, {"encode_into", (PyCFunction)(void(*)(void))StreamingBase64Encoder_encode_into, METH_FASTCALL, ""}, {"reset", (PyCFunction)StreamingBase64Encoder_reset, METH_NOARGS, ""}, {NULL, NULL, 0, NULL}, }, .tp_new = PyType_GenericNew, .tp_init = StreamingBase64Encoder_init, }; static PyObject* pyset_iutf8(PyObject UNUSED *self, PyObject *args) { int fd, on; if (!PyArg_ParseTuple(args, "ip", &fd, &on)) return NULL; if (!set_iutf8(fd, on & 1)) return PyErr_SetFromErrno(PyExc_OSError); Py_RETURN_NONE; } #ifdef WITH_PROFILER static PyObject* start_profiler(PyObject UNUSED *self, PyObject *args) { char *path; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; ProfilerStart(path); Py_RETURN_NONE; } static PyObject* stop_profiler(PyObject UNUSED *self, PyObject *args UNUSED) { ProfilerStop(); Py_RETURN_NONE; } #endif static bool put_tty_in_raw_mode(int fd, const struct termios* termios_p, bool read_with_timeout, int optional_actions) { struct termios raw_termios = *termios_p; cfmakeraw(&raw_termios); if (read_with_timeout) { raw_termios.c_cc[VMIN] = 0; raw_termios.c_cc[VTIME] = 1; } else { raw_termios.c_cc[VMIN] = 1; raw_termios.c_cc[VTIME] = 0; } if (tcsetattr(fd, optional_actions, &raw_termios) != 0) { PyErr_SetFromErrno(PyExc_OSError); return false; } return true; } static PyObject* open_tty(PyObject *self UNUSED, PyObject *args) { int read_with_timeout = 0, optional_actions = TCSAFLUSH; if (!PyArg_ParseTuple(args, "|pi", &read_with_timeout, &optional_actions)) return NULL; int flags = O_RDWR | O_CLOEXEC | O_NOCTTY; if (!read_with_timeout) flags |= O_NONBLOCK; static char ctty[L_ctermid+1]; int fd = safe_open(ctermid(ctty), flags, 0); if (fd == -1) { PyErr_Format(PyExc_OSError, "Failed to open controlling terminal: %s (identified with ctermid()) with error: %s", ctty, strerror(errno)); return NULL; } struct termios *termios_p = calloc(1, sizeof(struct termios)); if (!termios_p) return PyErr_NoMemory(); if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; } if (!put_tty_in_raw_mode(fd, termios_p, read_with_timeout != 0, optional_actions)) { free(termios_p); return NULL; } return Py_BuildValue("iN", fd, PyLong_FromVoidPtr(termios_p)); } #define TTY_ARGS \ PyObject *tp; int fd; int optional_actions = TCSAFLUSH; \ if (!PyArg_ParseTuple(args, "iO!|i", &fd, &PyLong_Type, &tp, &optional_actions)) return NULL; \ struct termios *termios_p = PyLong_AsVoidPtr(tp); static PyObject* normal_tty(PyObject *self UNUSED, PyObject *args) { TTY_ARGS if (tcsetattr(fd, optional_actions, termios_p) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } Py_RETURN_NONE; } static PyObject* raw_tty(PyObject *self UNUSED, PyObject *args) { TTY_ARGS if (!put_tty_in_raw_mode(fd, termios_p, false, optional_actions)) return NULL; Py_RETURN_NONE; } static PyObject* close_tty(PyObject *self UNUSED, PyObject *args) { TTY_ARGS tcsetattr(fd, optional_actions, termios_p); // deliberately ignore failure free(termios_p); safe_close(fd, __FILE__, __LINE__); Py_RETURN_NONE; } #undef TTY_ARGS static PyObject* py_shm_open(PyObject UNUSED *self, PyObject *args) { char *name; int flags, mode = 0600; if (!PyArg_ParseTuple(args, "si|i", &name, &flags, &mode)) return NULL; long fd = safe_shm_open(name, flags, mode); if (fd < 0) return PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, PyTuple_GET_ITEM(args, 0)); return PyLong_FromLong(fd); } static PyObject* py_shm_unlink(PyObject UNUSED *self, PyObject *args) { char *name; if (!PyArg_ParseTuple(args, "s", &name)) return NULL; if (shm_unlink(name) != 0) return PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, PyTuple_GET_ITEM(args, 0)); Py_RETURN_NONE; } static PyObject* wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) { return PyLong_FromLong(wcwidth_std(char_props_for(PyLong_AsLong(chr)))); } static PyObject* locale_is_valid(PyObject *self UNUSED, PyObject *args) { char *name; if (!PyArg_ParseTuple(args, "s", &name)) return NULL; locale_t test_locale = newlocale(LC_ALL_MASK, name, NULL); if (!test_locale) { Py_RETURN_FALSE; } freelocale(test_locale); Py_RETURN_TRUE; } #include "docs_ref_map_generated.h" static PyObject* get_docs_ref_map(PyObject *self UNUSED, PyObject *args UNUSED) { return PyBytes_FromStringAndSize(docs_ref_map, sizeof(docs_ref_map)); } static PyObject* wrapped_kittens(PyObject *self UNUSED, PyObject *args UNUSED) { static const char *wrapped_kitten_names = WRAPPED_KITTENS; PyObject *ans = PyUnicode_FromString(wrapped_kitten_names); if (ans == NULL) return NULL; PyObject *s = PyUnicode_Split(ans, NULL, -1); Py_DECREF(ans); return s; } static PyObject* expand_ansi_c_escapes(PyObject *self UNUSED, PyObject *src) { enum { NORMAL, PREV_ESC, HEX_DIGIT, OCT_DIGIT, CONTROL_CHAR } state = NORMAL; if (PyUnicode_READY(src) != 0) return NULL; int max_num_hex_digits = 0, hex_digit_idx = 0; char hex_digits[16]; Py_ssize_t idx = 0, dest_idx = 0; PyObject *dest = PyUnicode_New(PyUnicode_GET_LENGTH(src)*2, 1114111); if (dest == NULL) return NULL; const int kind = PyUnicode_KIND(src), dest_kind = PyUnicode_KIND(dest); const void *data = PyUnicode_DATA(src), *dest_data = PyUnicode_DATA(dest); #define w(ch) { PyUnicode_WRITE(dest_kind, dest_data, dest_idx, ch); dest_idx++; } #define write_digits(base) { hex_digits[hex_digit_idx] = 0; if (hex_digit_idx > 0) w(strtol(hex_digits, NULL, base)); hex_digit_idx = 0; state = NORMAL; } #define add_digit(base) { hex_digits[hex_digit_idx++] = ch; if (idx >= PyUnicode_GET_LENGTH(src)) write_digits(base); } START_ALLOW_CASE_RANGE while (idx < PyUnicode_GET_LENGTH(src)) { Py_UCS4 ch = PyUnicode_READ(kind, data, idx); idx++; switch(state) { case NORMAL: { if (ch == '\\' && idx < PyUnicode_GET_LENGTH(src)) { state = PREV_ESC; continue; } w(ch); } break; case CONTROL_CHAR: w(ch & 0x1f); state = NORMAL; break; case HEX_DIGIT: { if (hex_digit_idx < max_num_hex_digits && (('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F'))) add_digit(16) else { write_digits(16); idx--; } }; break; case OCT_DIGIT: { if ('0' <= ch && ch <= '7' && hex_digit_idx < max_num_hex_digits) add_digit(16) else { write_digits(8); idx--; } }; break; case PREV_ESC: { state = NORMAL; switch(ch) { default: w('\\'); w(ch); break; case 'a': w(7); break; case 'b': w(8); break; case 'c': if (idx < PyUnicode_GET_LENGTH(src)) {state = CONTROL_CHAR;} else {w('\\'); w(ch);}; break; case 'e': case 'E': w(27); break; case 'f': w(12); break; case 'n': w(10); break; case 'r': w(13); break; case 't': w(9); break; case 'v': w(11); break; case 'x': max_num_hex_digits = 2; hex_digit_idx = 0; state = HEX_DIGIT; break; case 'u': max_num_hex_digits = 4; hex_digit_idx = 0; state = HEX_DIGIT; break; case 'U': max_num_hex_digits = 8; hex_digit_idx = 0; state = HEX_DIGIT; break; case '0' ... '7': max_num_hex_digits = 3; hex_digits[0] = ch; hex_digit_idx = 1; state = OCT_DIGIT; break; case '\\': w('\\'); break; case '?': w('?'); break; case '"': w('"'); break; case '\'': w('\''); break; } } break; } } #undef add_digit #undef write_digits #undef w END_ALLOW_CASE_RANGE PyObject *ans = PyUnicode_FromKindAndData(dest_kind, dest_data, dest_idx); Py_DECREF(dest); return ans; } START_ALLOW_CASE_RANGE static PyObject* c0_replace_bytes(const char *input_data, Py_ssize_t input_sz) { RAII_PyObject(ans, PyBytes_FromStringAndSize(NULL, input_sz * 3)); if (!ans) return NULL; char *output = PyBytes_AS_STRING(ans); char buf[4]; Py_ssize_t j = 0; for (Py_ssize_t i = 0; i < input_sz; i++) { const char x = input_data[i]; switch (x) { case C0_EXCEPT_NL_SPACE_TAB: { const uint32_t ch = 0x2400 + x; const unsigned sz = encode_utf8(ch, buf); for (unsigned c = 0; c < sz; c++, j++) output[j] = buf[c]; } break; default: output[j++] = x; break; } } if (_PyBytes_Resize(&ans, j) != 0) return NULL; Py_INCREF(ans); return ans; } static PyObject* c0_replace_unicode(PyObject *input) { RAII_PyObject(ans, PyUnicode_New(PyUnicode_GET_LENGTH(input), 1114111)); if (!ans) return NULL; void *input_data = PyUnicode_DATA(input); int input_kind = PyUnicode_KIND(input); void *output_data = PyUnicode_DATA(ans); int output_kind = PyUnicode_KIND(ans); Py_UCS4 maxchar = 0; bool changed = false; for (Py_ssize_t i = 0; i < PyUnicode_GET_LENGTH(input); i++) { Py_UCS4 ch = PyUnicode_READ(input_kind, input_data, i); switch(ch) { case C0_EXCEPT_NL_SPACE_TAB: ch += 0x2400; changed = true; } if (ch > maxchar) maxchar = ch; PyUnicode_WRITE(output_kind, output_data, i, ch); } if (!changed) { Py_INCREF(input); return input; } if (maxchar > 65535) { Py_INCREF(ans); return ans; } RAII_PyObject(ans2, PyUnicode_New(PyUnicode_GET_LENGTH(ans), maxchar)); if (!ans2) return NULL; if (PyUnicode_CopyCharacters(ans2, 0, ans, 0, PyUnicode_GET_LENGTH(ans)) == -1) return NULL; Py_INCREF(ans2); return ans2; } END_ALLOW_CASE_RANGE static PyObject* replace_c0_codes_except_nl_space_tab(PyObject *self UNUSED, PyObject *obj) { if (PyUnicode_Check(obj)) { return c0_replace_unicode(obj); } else if (PyBytes_Check(obj)) { return c0_replace_bytes(PyBytes_AS_STRING(obj), PyBytes_GET_SIZE(obj)); } else if (PyMemoryView_Check(obj)) { Py_buffer *buf = PyMemoryView_GET_BUFFER(obj); return c0_replace_bytes(buf->buf, buf->len); } else if (PyByteArray_Check(obj)) { return c0_replace_bytes(PyByteArray_AS_STRING(obj), PyByteArray_GET_SIZE(obj)); } else { PyErr_SetString(PyExc_TypeError, "Input must be bytes, memoryview, bytearray or unicode"); return NULL; } } static PyObject* find_in_memoryview(PyObject *self UNUSED, PyObject *args) { unsigned char q; RAII_PY_BUFFER(view); if (!PyArg_ParseTuple(args, "y*b", &view, &q)) return NULL; const char *buf = view.buf, *p = memchr(buf, q, view.len); Py_ssize_t ans = -1; if (p) ans = p - buf; return PyLong_FromSsize_t(ans); } #include "terminfo.h" static PyObject* py_terminfo_data(PyObject *self UNUSED, PyObject *args UNUSED) { return PyBytes_FromStringAndSize((const char*)terminfo_data, arraysz(terminfo_data)); } static PyObject* py_monotonic(PyObject *self UNUSED, PyObject *args UNUSED) { return PyFloat_FromDouble(monotonic_t_to_s_double(monotonic())); } static PyObject* py_timed_debug_print(PyObject *self UNUSED, PyObject *args) { const char *payload; Py_ssize_t sz; if (!PyArg_ParseTuple(args, "s#", &payload, &sz)) return NULL; const char *fmt = "%.*s"; if (sz && payload[sz-1] == '\n') { fmt = "%.*s\n"; sz--; } timed_debug_print(fmt, sz, payload); Py_RETURN_NONE; } static PyObject* py_run_atexit_cleanup_functions(PyObject *self UNUSED, PyObject *args UNUSED) { run_at_exit_cleanup_functions(); Py_RETURN_NONE; } static PyObject* py_char_props_for(PyObject *self UNUSED, PyObject *ch) { if (!PyUnicode_Check(ch) || PyUnicode_GET_LENGTH(ch) != 1) { PyErr_SetString(PyExc_TypeError, "must suply a single character"); return NULL; } char_type c = PyUnicode_READ_CHAR(ch, 0); CharProps cp = char_props_for(c); #define B(x) #x, cp.x ? Py_True : Py_False return Py_BuildValue("{si sO sB sB ss sO sO}", "width", wcwidth_std(cp), B(is_extended_pictographic), "grapheme_break", cp.grapheme_break, "indic_conjunct_break", cp.indic_conjunct_break, "category", char_category(cp), B(is_emoji), B(is_emoji_presentation_base) ); #undef B } static PyMethodDef module_methods[] = { METHODB(replace_c0_codes_except_nl_space_tab, METH_O), {"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""}, {"expand_ansi_c_escapes", (PyCFunction)expand_ansi_c_escapes, METH_O, ""}, {"get_docs_ref_map", (PyCFunction)get_docs_ref_map, METH_NOARGS, ""}, {"wcswidth", (PyCFunction)wcswidth_std, METH_O, ""}, {"open_tty", open_tty, METH_VARARGS, ""}, {"normal_tty", normal_tty, METH_VARARGS, ""}, {"raw_tty", raw_tty, METH_VARARGS, ""}, {"close_tty", close_tty, METH_VARARGS, ""}, {"set_iutf8_fd", (PyCFunction)pyset_iutf8, METH_VARARGS, ""}, {"base64_encode", (PyCFunction)(void (*) (void))(pybase64_encode), METH_FASTCALL, ""}, {"base64_encode_into", (PyCFunction)base64_encode_into, METH_VARARGS, ""}, {"base64_decode", (PyCFunction)(void (*) (void))(pybase64_decode), METH_O, ""}, {"base64_decode_into", (PyCFunction)base64_decode_into, METH_VARARGS, ""}, {"char_props_for", py_char_props_for, METH_O, ""}, {"split_into_graphemes", (PyCFunction)split_into_graphemes, METH_O, ""}, {"thread_write", (PyCFunction)cm_thread_write, METH_VARARGS, ""}, {"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""}, {"locale_is_valid", (PyCFunction)locale_is_valid, METH_VARARGS, ""}, {"shm_open", (PyCFunction)py_shm_open, METH_VARARGS, ""}, {"shm_unlink", (PyCFunction)py_shm_unlink, METH_VARARGS, ""}, {"wrapped_kitten_names", (PyCFunction)wrapped_kittens, METH_NOARGS, ""}, {"terminfo_data", (PyCFunction)py_terminfo_data, METH_NOARGS, ""}, {"monotonic", (PyCFunction)py_monotonic, METH_NOARGS, ""}, {"timed_debug_print", (PyCFunction)py_timed_debug_print, METH_VARARGS, ""}, {"find_in_memoryview", (PyCFunction)find_in_memoryview, METH_VARARGS, ""}, {"run_at_exit_cleanup_functions", (PyCFunction)py_run_atexit_cleanup_functions, METH_NOARGS, ""}, #ifdef __APPLE__ METHODB(user_cache_dir, METH_NOARGS), METHODB(process_group_map, METH_NOARGS), #endif #ifdef WITH_PROFILER {"start_profiler", (PyCFunction)start_profiler, METH_VARARGS, ""}, {"stop_profiler", (PyCFunction)stop_profiler, METH_NOARGS, ""}, #endif {NULL, NULL, 0, NULL} /* Sentinel */ }; static void free_fast_data_types_module(void *m UNUSED) { run_at_exit_cleanup_functions(); } static struct PyModuleDef module = { .m_base = PyModuleDef_HEAD_INIT, .m_name = "fast_data_types", /* name of module */ .m_doc = NULL, .m_size = -1, .m_methods = module_methods, .m_free = free_fast_data_types_module, }; extern int init_LineBuf(PyObject *); extern int init_HistoryBuf(PyObject *); extern int init_Cursor(PyObject *); extern int init_Shlex(PyObject *); extern int init_Parser(PyObject *); extern int init_DiskCache(PyObject *); extern bool init_child_monitor(PyObject *); extern int init_Line(PyObject *); extern int init_ColorProfile(PyObject *); extern int init_Screen(PyObject *); extern bool init_animations(PyObject*); extern bool init_fontconfig_library(PyObject*); extern bool init_crypto_library(PyObject*); extern bool init_desktop(PyObject*); extern bool init_fonts(PyObject*); extern bool init_glfw(PyObject *m); extern bool init_child(PyObject *m); extern bool init_state(PyObject *module); extern bool init_keys(PyObject *module); extern bool init_graphics(PyObject *module); extern bool init_shaders(PyObject *module); extern bool init_mouse(PyObject *module); extern bool init_kittens(PyObject *module); extern bool init_logging(PyObject *module); extern bool init_png_reader(PyObject *module); extern bool init_utmp(PyObject *module); extern bool init_loop_utils(PyObject *module); extern bool init_systemd_module(PyObject *module); #ifdef __APPLE__ extern int init_CoreText(PyObject *); extern bool init_cocoa(PyObject *module); extern bool init_macos_process_info(PyObject *module); #else extern bool init_freetype_library(PyObject*); extern bool init_freetype_render_ui_text(PyObject*); #endif static unsigned shift_to_first_set_bit(CellAttrs x) { unsigned num_of_bits = 8 * sizeof(x.val); unsigned ans = 0; while (num_of_bits--) { if (x.val & 1) return ans; x.val >>= 1; ans++; } return ans; } EXPORTED PyMODINIT_FUNC PyInit_fast_data_types(void) { PyObject *m; m = PyModule_Create(&module); if (m == NULL) return NULL; init_monotonic(); if (!init_logging(m)) return NULL; if (!init_LineBuf(m)) return NULL; if (!init_HistoryBuf(m)) return NULL; if (!init_Line(m)) return NULL; if (!init_Cursor(m)) return NULL; if (!init_Shlex(m)) return NULL; if (!init_Parser(m)) return NULL; if (!init_DiskCache(m)) return NULL; if (!init_child_monitor(m)) return NULL; if (!init_ColorProfile(m)) return NULL; if (!init_Screen(m)) return NULL; if (!init_glfw(m)) return NULL; if (!init_child(m)) return NULL; if (!init_state(m)) return NULL; if (!init_keys(m)) return NULL; if (!init_graphics(m)) return NULL; if (!init_shaders(m)) return NULL; if (!init_mouse(m)) return NULL; if (!init_kittens(m)) return NULL; if (!init_png_reader(m)) return NULL; #ifdef __APPLE__ if (!init_macos_process_info(m)) return NULL; if (!init_CoreText(m)) return NULL; if (!init_cocoa(m)) return NULL; #else if (!init_freetype_library(m)) return NULL; if (!init_fontconfig_library(m)) return NULL; if (!init_desktop(m)) return NULL; if (!init_freetype_render_ui_text(m)) return NULL; #endif if (!init_fonts(m)) return NULL; if (!init_utmp(m)) return NULL; if (!init_loop_utils(m)) return NULL; if (!init_crypto_library(m)) return NULL; if (!init_systemd_module(m)) return NULL; if (!init_animations(m)) return NULL; CellAttrs a; #define s(name, attr) { a.val = 0; a.attr = 1; PyModule_AddIntConstant(m, #name, shift_to_first_set_bit(a)); } s(BOLD, bold); s(ITALIC, italic); s(REVERSE, reverse); s(MARK, mark); s(STRIKETHROUGH, strike); s(DIM, dim); s(DECORATION, decoration); #undef s PyModule_AddIntConstant(m, "MARK_MASK", MARK_MASK); PyModule_AddIntConstant(m, "DECORATION_MASK", DECORATION_MASK); PyModule_AddStringMacro(m, ERROR_PREFIX); #ifdef KITTY_VCS_REV PyModule_AddStringMacro(m, KITTY_VCS_REV); #endif PyModule_AddIntMacro(m, CURSOR_BLOCK); PyModule_AddIntMacro(m, CURSOR_BEAM); PyModule_AddIntMacro(m, CURSOR_UNDERLINE); PyModule_AddIntMacro(m, CURSOR_HOLLOW); PyModule_AddIntMacro(m, NO_CURSOR_SHAPE); PyModule_AddIntMacro(m, DECAWM); PyModule_AddIntMacro(m, DECCOLM); PyModule_AddIntMacro(m, DECOM); PyModule_AddIntMacro(m, IRM); PyModule_AddIntMacro(m, FILE_TRANSFER_CODE); PyModule_AddIntMacro(m, ESC_CSI); PyModule_AddIntMacro(m, ESC_OSC); PyModule_AddIntMacro(m, ESC_APC); PyModule_AddIntMacro(m, ESC_DCS); PyModule_AddIntMacro(m, ESC_PM); PyModule_AddIntMacro(m, TEXT_SIZE_CODE); #ifdef __APPLE__ // Apple says its SHM_NAME_MAX but SHM_NAME_MAX is not actually declared in typical CrApple style. // This value is based on experimentation and from qsharedmemory.cpp in Qt PyModule_AddIntConstant(m, "SHM_NAME_MAX", 30); #else // FreeBSD's man page says this is 1023. Linux says its PATH_MAX. PyModule_AddIntConstant(m, "SHM_NAME_MAX", MIN(1023, PATH_MAX)); #endif if (PyType_Ready(&StreamingBase64Decoder_Type) < 0) return NULL; if (PyModule_AddObject(m, "StreamingBase64Decoder", (PyObject *) &StreamingBase64Decoder_Type) < 0) return NULL; if (PyType_Ready(&StreamingBase64Encoder_Type) < 0) return NULL; if (PyModule_AddObject(m, "StreamingBase64Encoder", (PyObject *) &StreamingBase64Encoder_Type) < 0) return NULL; return m; } kitty-0.41.1/kitty/data-types.h0000664000175000017510000003171114773370543015702 0ustar nileshnilesh/* * data-types.h * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #ifdef _POSIX_C_SOURCE #error "Must include \"data-types.h\" before any system headers" #endif #define PY_SSIZE_T_CLEAN #include #include #include #include #include #include #include "glfw-wrapper.h" #include "banned.h" // Required minimum OpenGL version #define OPENGL_REQUIRED_VERSION_MAJOR 3 #ifdef __APPLE__ #define OPENGL_REQUIRED_VERSION_MINOR 3 #else #define OPENGL_REQUIRED_VERSION_MINOR 1 #endif #define GLSL_VERSION 140 #define GLFW_MOD_KITTY (GLFW_MOD_LAST * 2) #define UNUSED __attribute__ ((unused)) #define PYNOARG PyObject *__a1 UNUSED, PyObject *__a2 UNUSED #define EXPORTED __attribute__ ((visibility ("default"))) #define LIKELY(x) __builtin_expect (!!(x), 1) #define UNLIKELY(x) __builtin_expect (!!(x), 0) #define MAX(x, y) __extension__ ({ \ const __typeof__ (x) __a__ = (x); const __typeof__ (y) __b__ = (y); \ __a__ > __b__ ? __a__ : __b__;}) #define MIN(x, y) __extension__ ({ \ const __typeof__ (x) __a__ = (x); const __typeof__ (y) __b__ = (y); \ __a__ < __b__ ? __a__ : __b__;}) #define SWAP(x, y) do { __typeof__(x) _sw_ = y; y = x; x = _sw_; } while(0) #define xstr(s) str(s) #define str(s) #s #define arraysz(x) (sizeof(x)/sizeof(x[0])) #define zero_at_i(array, idx) memset((array) + (idx), 0, sizeof((array)[0])) #define zero_at_ptr(p) memset((p), 0, sizeof((p)[0])) #define literal_strlen(x) (sizeof(x)-1) #define zero_at_ptr_count(p, count) memset((p), 0, (count) * sizeof((p)[0])) #define C0_EXCEPT_NL_SPACE_TAB 0x0 ... 0x8: case 0xb ... 0x1f: case 0x7f void log_error(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); #define fatal(...) { log_error(__VA_ARGS__); exit(EXIT_FAILURE); } static inline void cleanup_free(void *p) { free(*(void**)p); } #define RAII_ALLOC(type, name, initializer) __attribute__((cleanup(cleanup_free))) type *name = initializer static inline void cleanup_decref(PyObject **p) { Py_CLEAR(*p); } #define RAII_PyObject(name, initializer) __attribute__((cleanup(cleanup_decref))) PyObject *name = initializer #define RAII_PY_BUFFER(name) __attribute__((cleanup(PyBuffer_Release))) Py_buffer name = {0} typedef unsigned long long id_type; typedef uint32_t char_type; static_assert(sizeof(Py_UCS4) == sizeof(char_type), "PyUCS4 and char_type dont match"); #define MAX_CHAR_TYPE_VALUE UINT32_MAX typedef uint32_t color_type; typedef uint16_t hyperlink_id_type; typedef int key_type; #define HYPERLINK_MAX_NUMBER UINT16_MAX typedef uint16_t combining_type; typedef uint16_t glyph_index; typedef uint32_t pixel; typedef unsigned int index_type; typedef uint32_t sprite_index; typedef enum CursorShapes { NO_CURSOR_SHAPE, CURSOR_BLOCK, CURSOR_BEAM, CURSOR_UNDERLINE, CURSOR_HOLLOW, NUM_OF_CURSOR_SHAPES } CursorShape; typedef enum { DISABLE_LIGATURES_NEVER, DISABLE_LIGATURES_CURSOR, DISABLE_LIGATURES_ALWAYS } DisableLigature; #define ERROR_PREFIX "[PARSE ERROR]" typedef enum MouseTrackingModes { NO_TRACKING, BUTTON_MODE, MOTION_MODE, ANY_MODE } MouseTrackingMode; typedef enum MouseTrackingProtocols { NORMAL_PROTOCOL, UTF8_PROTOCOL, SGR_PROTOCOL, URXVT_PROTOCOL, SGR_PIXEL_PROTOCOL} MouseTrackingProtocol; typedef enum MouseShapes { INVALID_POINTER, /* start mouse shapes (auto generated by gen-key-constants.py do not edit) */ DEFAULT_POINTER, TEXT_POINTER, POINTER_POINTER, HELP_POINTER, WAIT_POINTER, PROGRESS_POINTER, CROSSHAIR_POINTER, CELL_POINTER, VERTICAL_TEXT_POINTER, MOVE_POINTER, E_RESIZE_POINTER, NE_RESIZE_POINTER, NW_RESIZE_POINTER, N_RESIZE_POINTER, SE_RESIZE_POINTER, SW_RESIZE_POINTER, S_RESIZE_POINTER, W_RESIZE_POINTER, EW_RESIZE_POINTER, NS_RESIZE_POINTER, NESW_RESIZE_POINTER, NWSE_RESIZE_POINTER, ZOOM_IN_POINTER, ZOOM_OUT_POINTER, ALIAS_POINTER, COPY_POINTER, NOT_ALLOWED_POINTER, NO_DROP_POINTER, GRAB_POINTER, GRABBING_POINTER, /* end mouse shapes */ } MouseShape; typedef enum { NONE, MENUBAR, WINDOW, ALL } WindowTitleIn; typedef enum { TILING, SCALED, MIRRORED, CLAMPED, CENTER_CLAMPED, CENTER_SCALED } BackgroundImageLayout; typedef struct ImageAnchorPosition { float canvas_x, canvas_y, image_x, image_y; } ImageAnchorPosition; #define MAX_CHILDREN 512 #define BLANK_CHAR 0 #define COL_MASK 0xFFFFFFFF #define DECORATION_FG_CODE 58 // PUA character used as an image placeholder. #define IMAGE_PLACEHOLDER_CHAR 0x10EEEE #define FG 1 #define BG 2 #define COPY_CELL(src, s, dest, d) \ (dest)->cpu_cells[d] = (src)->cpu_cells[s]; (dest)->gpu_cells[d] = (src)->gpu_cells[s]; #define COPY_SELF_CELL(s, d) COPY_CELL(self, s, self, d) #define METHOD(name, arg_type) {#name, (PyCFunction)name, arg_type, name##_doc}, #define METHODB(name, arg_type) {#name, (PyCFunction)name, arg_type, ""} #define BOOL_GETSET(type, x) \ static PyObject* x##_get(type *self, void UNUSED *closure) { PyObject *ans = self->x ? Py_True : Py_False; Py_INCREF(ans); return ans; } \ static int x##_set(type *self, PyObject *value, void UNUSED *closure) { if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } self->x = PyObject_IsTrue(value) ? true : false; return 0; } #define GETSET(x) \ {#x, (getter) x##_get, (setter) x##_set, #x, NULL}, #ifndef EXTRA_INIT #define EXTRA_INIT #endif #define INIT_TYPE(type) \ int init_##type(PyObject *module) {\ if (PyType_Ready(&type##_Type) < 0) return 0; \ if (PyModule_AddObject(module, #type, (PyObject *)&type##_Type) != 0) return 0; \ Py_INCREF(&type##_Type); \ EXTRA_INIT; \ return 1; \ } #define RICHCMP(type) \ static PyObject * richcmp(PyObject *obj1, PyObject *obj2, int op) { \ PyObject *result = NULL; \ int eq; \ if (op != Py_EQ && op != Py_NE) { Py_RETURN_NOTIMPLEMENTED; } \ if (!PyObject_TypeCheck(obj1, &type##_Type)) { Py_RETURN_FALSE; } \ if (!PyObject_TypeCheck(obj2, &type##_Type)) { Py_RETURN_FALSE; } \ eq = __eq__((type*)obj1, (type*)obj2); \ if (op == Py_NE) result = eq ? Py_False : Py_True; \ else result = eq ? Py_True : Py_False; \ Py_INCREF(result); \ return result; \ } #ifdef __clang__ #define START_IGNORE_DIAGNOSTIC(diag) _Pragma(xstr(clang diagnostic push)) _Pragma(xstr(clang diagnostic ignored diag)) #define END_IGNORE_DIAGNOSTIC _Pragma("clang diagnostic pop") #else #define START_IGNORE_DIAGNOSTIC(diag) _Pragma(xstr(GCC diagnostic push)) _Pragma(xstr(GCC diagnostic ignored diag)) #define END_IGNORE_DIAGNOSTIC _Pragma("GCC diagnostic pop") #endif #define IGNORE_PEDANTIC_WARNINGS START_IGNORE_DIAGNOSTIC("-Wpedantic") #define END_IGNORE_PEDANTIC_WARNINGS END_IGNORE_DIAGNOSTIC #define ALLOW_UNUSED_RESULT START_IGNORE_DIAGNOSTIC("-Wunused-result") #define END_ALLOW_UNUSED_RESULT END_IGNORE_DIAGNOSTIC #define START_ALLOW_CASE_RANGE IGNORE_PEDANTIC_WARNINGS #define END_ALLOW_CASE_RANGE END_IGNORE_PEDANTIC_WARNINGS #define BIT_MASK(__TYPE__, __ONE_COUNT__) \ (((__TYPE__) (-((__ONE_COUNT__) != 0))) \ & (((__TYPE__) -1) >> ((sizeof(__TYPE__) * CHAR_BIT) - (__ONE_COUNT__)))) #define ADD_TYPE(which) \ if (PyType_Ready(&which##_Type) < 0) return false; \ if (PyModule_AddObject(module, #which, (PyObject *)&which##_Type) != 0) return false; \ Py_INCREF(&which##_Type); typedef enum UTF8State { UTF8_ACCEPT = 0, UTF8_REJECT = 1} UTF8State; typedef struct { uint32_t left, top, right, bottom; } Region; typedef enum { UNKNOWN_PROMPT_KIND = 0, PROMPT_START = 1, SECONDARY_PROMPT = 2, OUTPUT_START = 3 } PromptKind; typedef struct {int x;} *HYPERLINK_POOL_HANDLE; typedef struct { Py_UCS4 *buf; size_t len, capacity; HYPERLINK_POOL_HANDLE hyperlink_pool; hyperlink_id_type active_hyperlink_id; } ANSIBuf; typedef struct { PyObject_HEAD bool bold, italic, reverse, strikethrough, dim, non_blinking; monotonic_t position_changed_by_client_at; unsigned int x, y; uint8_t decoration; CursorShape shape; color_type fg, bg, decoration_fg; } Cursor; typedef struct { bool is_focused, render_even_when_unfocused; CursorShape shape; unsigned int x, y; float opacity; } CursorRenderInfo; typedef enum DynamicColorType { COLOR_NOT_SET, COLOR_IS_SPECIAL, COLOR_IS_INDEX, COLOR_IS_RGB } DynamicColorType; typedef union DynamicColor { struct { color_type rgb: 24; DynamicColorType type: 8; }; color_type val; } DynamicColor; typedef struct { DynamicColor default_fg, default_bg, cursor_color, cursor_text_color, highlight_fg, highlight_bg, visual_bell_color; } DynamicColors; typedef struct TransparentDynamicColor { color_type color; float opacity; bool is_set; } TransparentDynamicColor; #define MARK_MASK (3u) typedef struct { PyObject_HEAD bool dirty; uint32_t color_table[256], orig_color_table[256]; TransparentDynamicColor configured_transparent_colors[8], overriden_transparent_colors[8]; struct { DynamicColors dynamic_colors; uint32_t color_table[256]; TransparentDynamicColor transparent_colors[8]; } *color_stack; unsigned int color_stack_idx, color_stack_sz; DynamicColors configured, overridden; color_type mark_foregrounds[MARK_MASK+1], mark_backgrounds[MARK_MASK+1]; } ColorProfile; typedef struct { unsigned int width, height; } CellPixelSize; typedef struct {int x;} *SPRITE_MAP_HANDLE; typedef struct FontCellMetrics { unsigned int cell_width, cell_height, baseline, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness; } FontCellMetrics; #define FONTS_DATA_HEAD SPRITE_MAP_HANDLE sprite_map; double logical_dpi_x, logical_dpi_y, font_sz_in_pts; FontCellMetrics fcm; typedef struct {FONTS_DATA_HEAD} *FONTS_DATA_HANDLE; #define clear_sprite_position(cell) (cell).sprite_idx = 0; #define ensure_space_for(base, array, type, num, capacity, initial_cap, zero_mem) \ if ((base)->capacity < num) { \ size_t _newcap = MAX((size_t)initial_cap, MAX(2 * (base)->capacity, (size_t)num)); \ (base)->array = realloc((base)->array, sizeof(type) * _newcap); \ if ((base)->array == NULL) fatal("Out of memory while ensuring space for %zu elements in array of %s", (size_t)num, #type); \ if (zero_mem) memset((base)->array + (base)->capacity, 0, sizeof(type) * (_newcap - (base)->capacity)); \ (base)->capacity = _newcap; \ } #define remove_i_from_array(array, i, count) { \ (count)--; \ if ((i) < (count)) { \ memmove((array) + (i), (array) + (i) + 1, sizeof((array)[0]) * ((count) - (i))); \ }} // Global functions Cursor* alloc_cursor(void); ColorProfile* alloc_color_profile(void); void copy_color_profile(ColorProfile*, ColorProfile*); PyObject* parse_bytes_dump(PyObject UNUSED *, PyObject *); PyObject* parse_bytes(PyObject UNUSED *, PyObject *); void cursor_reset(Cursor*); Cursor* cursor_copy(Cursor*); void cursor_copy_to(Cursor *src, Cursor *dest); void cursor_reset_display_attrs(Cursor*); void cursor_from_sgr(Cursor *self, int *params, unsigned int count, bool is_group); const char* cursor_as_sgr(const Cursor *); PyObject* cm_thread_write(PyObject *self, PyObject *args); bool schedule_write_to_child(unsigned long id, unsigned int num, ...); bool schedule_write_to_child_python(unsigned long id, const char *prefix, PyObject* tuple_of_str_or_bytes, const char *suffix); bool set_iutf8(int, bool); DynamicColor colorprofile_to_color(const ColorProfile *self, DynamicColor entry, DynamicColor defval); bool colorprofile_to_transparent_color(const ColorProfile *self, unsigned index, color_type *color, float *opacity); color_type colorprofile_to_color_with_fallback(ColorProfile *self, DynamicColor entry, DynamicColor defval, DynamicColor fallback, DynamicColor falback_defval); void copy_color_table_to_buffer(ColorProfile *self, color_type *address, int offset, size_t stride); bool colorprofile_push_colors(ColorProfile*, unsigned int); bool colorprofile_pop_colors(ColorProfile*, unsigned int); void colorprofile_report_stack(ColorProfile*, unsigned int*, unsigned int*); void set_mouse_cursor(MouseShape); void enter_event(int modifiers); void mouse_event(const int, int, int); void focus_in_event(void); void scroll_event(double, double, int, int); void on_key_input(const GLFWkeyevent *ev); void request_window_attention(id_type, bool); #ifndef __APPLE__ void play_canberra_sound(const char *which_sound, const char *event_id, bool is_path, const char *role, const char *theme_name); #endif SPRITE_MAP_HANDLE alloc_sprite_map(void); void free_sprite_data(FONTS_DATA_HANDLE); const char* get_hyperlink_for_id(const HYPERLINK_POOL_HANDLE, hyperlink_id_type id, bool only_url); #define memset_array(array, val, count) if ((count) > 0) { \ (array)[0] = (val); \ size_t __copied__ = 1; \ while (__copied__ < (count)) { \ const size_t __num__ = MIN(__copied__, (count) - __copied__); \ memcpy((array) + __copied__, (array), __num__ * sizeof((val))); \ __copied__ += __num__; \ } \ } kitty-0.41.1/kitty/debug_config.py0000664000175000017510000002710014773370543016440 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import os import re import socket import sys import termios import time from collections.abc import Callable, Iterator, Sequence from contextlib import suppress from functools import partial from pprint import pformat from typing import IO, TypeVar from kittens.tui.operations import colored, styled from .child import cmdline_of_pid from .cli import version from .colors import theme_colors from .constants import extensions_dir, is_macos, is_wayland, kitty_base_dir, kitty_exe, shell_path from .fast_data_types import Color, SingleKey, current_fonts, glfw_get_system_color_theme, num_users, opengl_version_string, wayland_compositor_data from .options.types import Options as KittyOpts from .options.types import defaults from .options.utils import KeyboardMode, KeyDefinition from .rgb import color_as_sharp, color_from_int from .types import MouseEvent, Shortcut, mod_to_names AnyEvent = TypeVar('AnyEvent', MouseEvent, Shortcut) Print = Callable[..., None] ShortcutMap = dict[Shortcut, str] def green(x: str) -> str: return colored(x, 'green') def yellow(x: str) -> str: return colored(x, 'yellow') def title(x: str) -> str: return colored(x, 'blue', intense=True) def print_event(ev: str, defn: str, print: Print) -> None: print(f'\t{ev} → {defn}') def print_mapping_changes(defns: dict[str, str], changes: set[str], text: str, print: Print) -> None: if changes: print(title(text)) for k in sorted(changes): print_event(k, defns[k], print) def compare_maps( final: dict[AnyEvent, str], final_kitty_mod: int, initial: dict[AnyEvent, str], initial_kitty_mod: int, print: Print, mode_name: str = '' ) -> None: ei = {k.human_repr(initial_kitty_mod): v for k, v in initial.items()} ef = {k.human_repr(final_kitty_mod): v for k, v in final.items()} added = set(ef) - set(ei) removed = set(ei) - set(ef) changed = {k for k in set(ef) & set(ei) if ef[k] != ei[k]} which = 'shortcuts' if isinstance(next(iter(initial or final)), Shortcut) else 'mouse actions' if mode_name and (added or removed or changed): print(f'{title("Changes in keyboard mode: " + mode_name)}') print_mapping_changes(ef, added, f'Added {which}:', print) print_mapping_changes(ei, removed, f'Removed {which}:', print) print_mapping_changes(ef, changed, f'Changed {which}:', print) def compare_opts(opts: KittyOpts, global_shortcuts: dict[str, SingleKey] | None, print: Print) -> None: from .config import load_config print() print('Config options different from defaults:') default_opts = load_config() ignored = ('keymap', 'sequence_map', 'mousemap', 'map', 'mouse_map') changed_opts = [ f for f in sorted(defaults._fields) if f not in ignored and getattr(opts, f) != getattr(defaults, f) ] field_len = max(map(len, changed_opts)) if changed_opts else 20 fmt = f'{{:{field_len:d}s}}' colors = [] for f in changed_opts: val = getattr(opts, f) if isinstance(val, dict): print(title(f'{f}:')) if f == 'symbol_map': for k in sorted(val): print(f'\tU+{k[0]:04x} - U+{k[1]:04x} → {val[k]}') elif f == 'modify_font': for k in sorted(val): print(' ', val[k]) else: print(pformat(val)) else: val = getattr(opts, f) if isinstance(val, Color): colors.append(fmt.format(f) + ' ' + color_as_sharp(val) + ' ' + styled(' ', bg=val)) else: if f == 'kitty_mod': print(fmt.format(f), '+'.join(mod_to_names(getattr(opts, f)))) elif f in ('wayland_titlebar_color', 'macos_titlebar_color'): if val == 0: cval = 'system' elif val == 1: cval = 'background' else: col = color_from_int(val >> 8) cval = color_as_sharp(col) + ' ' + styled(' ', bg=col) colors.append(fmt.format(f) + ' ' + cval) else: print(fmt.format(f), str(getattr(opts, f))) compare_maps(opts.mousemap, opts.kitty_mod, default_opts.mousemap, default_opts.kitty_mod, print) def as_sc(k: SingleKey, v: KeyDefinition) -> Shortcut: if v.is_sequence: return Shortcut((v.trigger,) + v.rest) return Shortcut((k,)) def as_str(defns: Sequence[KeyDefinition]) -> str: seen = set() uniq = [] for d in reversed(defns): key = d.unique_identity_within_keymap if key not in seen: seen.add(key) uniq.append(d) return ', '.join(d.human_repr() for d in uniq) for kmn, initial_ in default_opts.keyboard_modes.items(): initial = {as_sc(k, v[0]): as_str(v) for k, v in initial_.keymap.items()} final_ = opts.keyboard_modes.get(kmn, KeyboardMode(kmn)) final = {as_sc(k, v[0]): as_str(v) for k, v in final_.keymap.items()} if not kmn and global_shortcuts: for action, sk in global_shortcuts.items(): sc = Shortcut((sk,)) if sc not in final: final[sc] = action compare_maps(final, opts.kitty_mod, initial, default_opts.kitty_mod, print, mode_name=kmn) new_keyboard_modes = set(opts.keyboard_modes) - set(default_opts.keyboard_modes) for kmn in new_keyboard_modes: initial_ = KeyboardMode(kmn) initial = {as_sc(k, v[0]): as_str(v) for k, v in initial_.keymap.items()} final_ = opts.keyboard_modes[kmn] final = {as_sc(k, v[0]): as_str(v) for k, v in final_.keymap.items()} compare_maps(final, opts.kitty_mod, initial, default_opts.kitty_mod, print, mode_name=kmn) if colors: print(f'{title("Colors")}:', end='\n\t') print('\n\t'.join(sorted(colors))) class IssueData: def __init__(self) -> None: self.uname = os.uname() self.s, self.n, self.r, self.v, self.m = self.uname try: self.hostname = self.o = socket.gethostname() except Exception: self.hostname = self.o = 'localhost' _time = time.localtime() self.formatted_time = self.d = time.strftime('%a %b %d %Y', _time) self.formatted_date = self.t = time.strftime('%H:%M:%S', _time) try: self.tty_name = format_tty_name(os.ctermid()) except OSError: self.tty_name = '(none)' self.l = self.tty_name self.baud_rate = 0 if sys.stdin.isatty(): with suppress(OSError): self.baud_rate = termios.tcgetattr(sys.stdin.fileno())[5] self.b = str(self.baud_rate) try: self.num_users = num_users() except RuntimeError: self.num_users = -1 self.u = str(self.num_users) self.U = self.u + ' user' + ('' if self.num_users == 1 else 's') def translate_issue_char(self, char: str) -> str: try: return str(getattr(self, char)) if len(char) == 1 else char except AttributeError: return char def parse_issue_file(self, issue_file: IO[str]) -> Iterator[str]: last_char: str | None = None while True: this_char = issue_file.read(1) if not this_char: break if last_char == '\\': yield self.translate_issue_char(this_char) elif last_char is not None: yield last_char # `\\\a` should not match the last two slashes, # so make it look like it was `\?\a` where `?` # is some character other than `\`. last_char = None if last_char == '\\' else this_char if last_char is not None: yield last_char def format_tty_name(raw: str) -> str: return re.sub(r'^/dev/([^/]+)/([^/]+)$', r'\1\2', raw) def compositor_name() -> str: ans = 'X11' if is_wayland(): ans = 'Wayland' with suppress(Exception): pid, missing_capabilities = wayland_compositor_data() if pid > -1: cmdline = cmdline_of_pid(pid) exe = cmdline[0] with suppress(Exception): import subprocess if exe.lower() == 'hyprland': raw = subprocess.check_output(['hyprctl', 'version']).decode().strip() m = re.search(r'^Tag:\s*(\S+)', raw, flags=re.M) if m is not None: exe = f'{exe} {m.group(1)}' else: exe = raw.splitlines()[0] exe = subprocess.check_output([exe, '--version']).decode().strip().splitlines()[0] ans += f' ({exe})' if missing_capabilities: ans += f' missing: {missing_capabilities}' return ans def debug_config(opts: KittyOpts, global_shortcuts: dict[str, SingleKey] | None = None) -> str: from io import StringIO out = StringIO() p = partial(print, file=out) p(version(add_rev=True)) p(' '.join(os.uname())) if is_macos: import subprocess p(' '.join(subprocess.check_output(['sw_vers']).decode('utf-8').splitlines()).strip()) if os.path.exists('/etc/issue'): try: idata = IssueData() except Exception: pass else: with open('/etc/issue', encoding='utf-8', errors='replace') as f: try: datums = idata.parse_issue_file(f) except Exception: pass else: p(end=''.join(datums)) if os.path.exists('/etc/lsb-release'): with open('/etc/lsb-release', encoding='utf-8', errors='replace') as f: p(f.read().strip()) if not is_macos: p('Running under:', green(compositor_name())) p(green('OpenGL:'), opengl_version_string()) p(green('Frozen:'), 'True' if getattr(sys, 'frozen', False) else 'False') p(green('Fonts:')) for k, font in current_fonts().items(): if hasattr(font, 'identify_for_debug'): flines = font.identify_for_debug().splitlines() p(yellow(f'{k.rjust(8)}:'), flines[0]) for fl in flines[1:]: p(' ' * 9, fl) p(green('Paths:')) p(yellow(' kitty:'), os.path.realpath(kitty_exe())) p(yellow(' base dir:'), kitty_base_dir) p(yellow(' extensions dir:'), extensions_dir) p(yellow(' system shell:'), shell_path) p(f'System color scheme: {green(glfw_get_system_color_theme())}. Applied color theme type: {yellow(theme_colors.applied_theme or "none")}') if opts.config_paths: p(green('Loaded config files:')) p(' ', '\n '.join(opts.config_paths)) if opts.config_overrides: p(green('Loaded config overrides:')) p(' ', '\n '.join(opts.config_overrides)) compare_opts(opts, global_shortcuts, p) p() p(green('Important environment variables seen by the kitty process:')) def penv(k: str) -> None: v = os.environ.get(k) if v is not None: p('\t' + k.ljust(35), styled(v, dim=True)) for k in ( 'PATH LANG KITTY_CONFIG_DIRECTORY KITTY_CACHE_DIRECTORY VISUAL EDITOR SHELL' ' GLFW_IM_MODULE KITTY_WAYLAND_DETECT_MODIFIERS DISPLAY WAYLAND_DISPLAY USER XCURSOR_SIZE' ).split(): penv(k) for k in os.environ: if k.startswith('LC_') or k.startswith('XDG_'): penv(k) return out.getvalue() kitty-0.41.1/kitty/decorations.c0000664000175000017510000021741214773370543016140 0ustar nileshnilesh/* * decorations.c * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "decorations.h" #include "state.h" typedef uint32_t uint; static uint max(uint a, uint b) { return a > b ? a : b; } static uint min(uint a, uint b) { return a < b ? a : b; } // Decorations {{{ #define STRAIGHT_UNDERLINE_LOOP \ unsigned half = fcm.underline_thickness / 2; \ DecorationGeometry ans = {.top = half > fcm.underline_position ? 0 : fcm.underline_position - half}; \ for (unsigned y = ans.top; fcm.underline_thickness > 0 && y < fcm.cell_height; fcm.underline_thickness--, y++, ans.height++) DecorationGeometry add_straight_underline(uint8_t *buf, FontCellMetrics fcm) { STRAIGHT_UNDERLINE_LOOP { memset(buf + fcm.cell_width * y, 0xff, fcm.cell_width * sizeof(buf[0])); } return ans; } DecorationGeometry add_strikethrough(uint8_t *buf, FontCellMetrics fcm) { unsigned half = fcm.strikethrough_thickness / 2; DecorationGeometry ans = {.top = half > fcm.strikethrough_position ? 0 : fcm.strikethrough_position - half}; for (unsigned y = ans.top; fcm.strikethrough_thickness > 0 && y < fcm.cell_height; fcm.strikethrough_thickness--, y++, ans.height++) { memset(buf + fcm.cell_width * y, 0xff, fcm.cell_width * sizeof(buf[0])); } return ans; } DecorationGeometry add_missing_glyph(uint8_t *buf, FontCellMetrics fcm) { DecorationGeometry ans = {.height=fcm.cell_height}; unsigned thickness = min(fcm.underline_thickness, fcm.strikethrough_thickness); thickness = min(thickness, fcm.cell_width); for (unsigned y = 0; y < ans.height; y++) { uint8_t *line = buf + fcm.cell_width * y; if (y < thickness || y >= ans.height - thickness) memset(line, 0xff, fcm.cell_width); else { memset(line, 0xff, thickness); memset(line + fcm.cell_width - thickness, 0xff, thickness); } } return ans; } DecorationGeometry add_double_underline(uint8_t *buf, FontCellMetrics fcm) { unsigned a = fcm.underline_position > fcm.underline_thickness ? fcm.underline_position - fcm.underline_thickness : 0; a = min(a, fcm.cell_height - 1); unsigned b = min(fcm.underline_position, fcm.cell_height - 1); unsigned top = min(a, b), bottom = max(a, b); int deficit = 2 - (bottom - top); if (deficit > 0) { if (bottom + deficit < fcm.cell_height) bottom += deficit; else if (bottom < fcm.cell_height - 1) { bottom += 1; if (deficit > 1) top -= deficit - 1; } else top -= deficit; } top = max(0u, min(top, fcm.cell_height - 1u)); bottom = max(0u, min(bottom, fcm.cell_height - 1u)); memset(buf + fcm.cell_width * top, 0xff, fcm.cell_width); memset(buf + fcm.cell_width * bottom, 0xff, fcm.cell_width); DecorationGeometry ans = {.top=top, .height = bottom + 1 - top}; return ans; } static unsigned distribute_dots(unsigned available_space, unsigned num_of_dots, unsigned *summed_gaps, unsigned *gaps) { unsigned dot_size = max(1u, available_space / (2u * num_of_dots)); unsigned extra = 2 * num_of_dots * dot_size; extra = available_space > extra ? available_space - extra : 0; for (unsigned i = 0; i < num_of_dots; i++) gaps[i] = dot_size; if (extra > 0) { unsigned idx = 0; while (extra > 0) { gaps[idx] += 1; idx = (idx + 1) % num_of_dots; extra--; } } gaps[0] /= 2; for (unsigned i = 0; i < num_of_dots; i++) { summed_gaps[i] = 0; for (unsigned g = 0; g <= i; g++) summed_gaps[i] += gaps[g]; } return dot_size; } DecorationGeometry add_dotted_underline(uint8_t *buf, FontCellMetrics fcm) { unsigned num_of_dots = MAX(1u, fcm.cell_width / (2 * MAX(1u, fcm.underline_thickness))); RAII_ALLOC(unsigned, spacing, malloc(num_of_dots * 2 * sizeof(unsigned))); if (!spacing) fatal("Out of memory"); unsigned size = distribute_dots(fcm.cell_width, num_of_dots, spacing, spacing + num_of_dots); STRAIGHT_UNDERLINE_LOOP { uint8_t *offset = buf + fcm.cell_width * y; for (unsigned j = 0; j < num_of_dots; j++) { unsigned s = spacing[j]; memset(offset + j * size + s, 0xff, size); } } return ans; } DecorationGeometry add_dashed_underline(uint8_t *buf, FontCellMetrics fcm) { unsigned quarter_width = fcm.cell_width / 4; unsigned dash_width = fcm.cell_width - 3 * quarter_width; unsigned second_dash_start = 3 * quarter_width; STRAIGHT_UNDERLINE_LOOP { uint8_t *offset = buf + fcm.cell_width * y; memset(offset, 0xff, dash_width); memset(offset + second_dash_start, 0xff, dash_width); } return ans; } static unsigned add_intensity(uint8_t *buf, unsigned x, int y, uint8_t val, unsigned max_y, unsigned position, unsigned cell_width) { y += position; y = min(MAX(0, y), max_y); unsigned idx = cell_width * y + x; buf[idx] = min(255, buf[idx] + val); return y; } static uint minus(uint a, uint b) { // saturating subtraction (a > b ? a - b : 0) uint res = a - b; res &= -(res <= a); return res; } DecorationGeometry add_curl_underline(uint8_t *buf, FontCellMetrics fcm) { unsigned max_x = fcm.cell_width - 1, max_y = fcm.cell_height - 1; double xfactor = ((OPT(undercurl_style) & 1) ? 4.0 : 2.0) * M_PI / max_x; div_t d = div(fcm.underline_thickness, 2); /*printf("cell_width: %u cell_height: %u underline_position: %u underline_thickness: %u\n",*/ /* fcm.cell_width, fcm.cell_height, fcm.underline_position, fcm.underline_thickness);*/ unsigned position = min(fcm.underline_position, minus(fcm.cell_height, d.quot + d.rem)); unsigned thickness = max(1u, min(fcm.underline_thickness, minus(fcm.cell_height, position + 1))); unsigned max_height = fcm.cell_height - minus(position, thickness / 2); // descender from the font unsigned half_height = max(1u, max_height / 4u); // 4 so as to be not too large if (OPT(undercurl_style) & 2) thickness = max(half_height, thickness); else thickness = max(1u, thickness) - (thickness < 3u ? 1u : 2u); position += half_height * 2; if (position + half_height > max_y) position = max_y - half_height; /*printf("position: %u half_height: %u thickness: %u\n", position, half_height, thickness);*/ unsigned miny = fcm.cell_height, maxy = 0; // Use the Wu antialias algorithm to draw the curve // cosine waves always have slope <= 1 so are never steep for (unsigned x = 0; x < fcm.cell_width; x++) { double y = half_height * cos(x * xfactor); int y1 = (int)(floor(y - thickness)), y2 = (int)(ceil(y)); unsigned intensity = (unsigned)((255. * fabs(y - floor(y)))); unsigned i1 = 255 - intensity, i2 = intensity; unsigned yc = add_intensity(buf, x, y1, i1, max_y, position, fcm.cell_width); // upper bound if (i1) { if (yc < miny) miny = yc; if (yc > maxy) maxy = yc; } yc = add_intensity(buf, x, y2, i2, max_y, position, fcm.cell_width); // lower bound if (i2) { if (yc < miny) miny = yc; if (yc > maxy) maxy = yc; } // fill between upper and lower bound for (unsigned t = 1; t <= thickness; t++) add_intensity(buf, x, y1 + t, 255, max_y, position, fcm.cell_width); } DecorationGeometry ans = {.top=miny, .height=maxy-miny + 1}; return ans; } static void vert(uint8_t *ans, bool is_left_edge, double width_pt, double dpi_x, FontCellMetrics fcm) { unsigned width = max(1u, min((unsigned)(round(width_pt * dpi_x / 72.0)), fcm.cell_width)); const unsigned left = is_left_edge ? 0 : (fcm.cell_width > width ? fcm.cell_width - width : 0); for (unsigned y = 0; y < fcm.cell_height; y++) { const unsigned offset = y * fcm.cell_width + left; for (unsigned x = offset; x < offset + width; x++) ans[x] = 0xff; } } static unsigned horz(uint8_t *ans, bool is_top_edge, double height_pt, double dpi_y, FontCellMetrics fcm) { unsigned height = max(1u, min((unsigned)(round(height_pt * dpi_y / 72.0)), fcm.cell_height)); const unsigned top = is_top_edge ? 0 : (fcm.cell_height > height ? fcm.cell_height - height : 0); for (unsigned y = top; y < top + height; y++) { const unsigned offset = y * fcm.cell_width; for (unsigned x = 0; x < fcm.cell_width; x++) ans[offset + x] = 0xff; } return top; } DecorationGeometry add_beam_cursor(uint8_t *buf, FontCellMetrics fcm, double dpi_x) { vert(buf, true, OPT(cursor_beam_thickness), dpi_x, fcm); DecorationGeometry ans = {.height=fcm.cell_height}; return ans; } DecorationGeometry add_underline_cursor(uint8_t *buf, FontCellMetrics fcm, double dpi_y) { DecorationGeometry ans = {0}; ans.top = horz(buf, false, OPT(cursor_underline_thickness), dpi_y, fcm); ans.height = fcm.cell_height - ans.top; return ans; } DecorationGeometry add_hollow_cursor(uint8_t *buf, FontCellMetrics fcm, double dpi_x, double dpi_y) { vert(buf, true, 1.0, dpi_x, fcm); vert(buf, false, 1.0, dpi_x, fcm); horz(buf, true, 1.0, dpi_y, fcm); horz(buf, false, 1.0, dpi_y, fcm); DecorationGeometry ans = {.height=fcm.cell_height}; return ans; } // }}} typedef struct Range { uint start, end; } Range; typedef struct Limit { double upper, lower; } Limit; typedef struct Canvas { uint8_t *mask; uint width, height, supersample_factor; struct { double x, y; } dpi; double scale; // used to scale line thickness with font size for multicell rendering Range *holes; uint holes_count, holes_capacity; Limit *y_limits; uint y_limits_count, y_limits_capacity; } Canvas; static void fill_canvas(Canvas *self, int byte) { memset(self->mask, byte, sizeof(self->mask[0]) * self->width * self->height); } static void append_hole(Canvas *self, Range hole) { ensure_space_for(self, holes, self->holes[0], self->holes_count + 1, holes_capacity, self->width, false); self->holes[self->holes_count++] = hole; } static void append_limit(Canvas *self, double upper, double lower) { ensure_space_for(self, y_limits, self->y_limits[0], self->y_limits_count + 1, y_limits_capacity, self->width, false); self->y_limits[self->y_limits_count].upper = upper; self->y_limits[self->y_limits_count++].lower = lower; } static uint thickness(Canvas *self, uint level, bool horizontal) { level = min(level, arraysz(OPT(box_drawing_scale))); double pts = OPT(box_drawing_scale)[level]; double dpi = horizontal ? self->dpi.x : self->dpi.y; return (uint)ceil(self->supersample_factor * self->scale * pts * dpi / 72.0); } static const uint hole_factor = 8; static void get_holes(Canvas *self, uint sz, uint hole_sz, uint num) { uint all_holes_use = (num + 1) * hole_sz; uint individual_block_size = max(1u, minus(sz, all_holes_use) / (num + 1)); uint half_hole_sz = hole_sz / 2; int pos = - half_hole_sz; while (pos < (int)sz) { uint left = pos > 0 ? pos : 0; uint right = min(sz, pos + hole_sz); if (right > left) append_hole(self, (Range){left, right}); pos = right + individual_block_size; } } static void add_hholes(Canvas *self, uint level, uint num) { uint line_sz = thickness(self, level, true); uint hole_sz = self->width / hole_factor; uint start = minus(self->height / 2, line_sz / 2); get_holes(self, self->width, hole_sz, num); for (uint y = 0; y < start + line_sz; y++) { uint offset = y * self->width; for (uint i = 0; i < self->holes_count; i++) memset(self->mask + offset + self->holes[i].start, 0, self->holes[i].end - self->holes[i].start); } } static void add_vholes(Canvas *self, uint level, uint num) { uint line_sz = thickness(self, level, false); uint hole_sz = self->height / hole_factor; uint start = minus(self->width / 2, line_sz / 2); get_holes(self, self->height, hole_sz, num); for (uint i = 0; i < self->holes_count; i++) { for (uint y = self->holes[i].start; y < self->holes[i].end; y++) { uint offset = y * self->width; memset(self->mask + offset + start, 0, line_sz); } } } static void draw_hline(Canvas *self, uint x1, uint x2, uint y, uint level) { // Draw a horizontal line between [x1, x2) centered at y with the thickness given by level and self->supersample_factor uint sz = thickness(self, level, false); uint start = minus(y, sz / 2); for (uint y = start; y < min(start + sz, self->height); y++) { uint8_t *py = self->mask + y * self->width; memset(py + x1, 255, minus(min(x2, self->width), x1)); } } static void draw_vline(Canvas *self, uint y1, uint y2, uint x, uint level) { // Draw a vertical line between [y1, y2) centered at x with the thickness given by level and self->supersample_factor uint sz = thickness(self, level, true); uint start = minus(x, sz / 2), end = min(start + sz, self->width), xsz = minus(end, start); for (uint y = y1; y < min(y2, self->height); y++) { uint8_t *py = self->mask + y * self->width; memset(py + start, 255, xsz); } } static uint half_width(Canvas *self) { // align with non-supersampled co-ords return self->supersample_factor * (self->width / 2 / self->supersample_factor); } static uint half_height(Canvas *self) { // align with non-supersampled co-ords return self->supersample_factor * (self->height / 2 / self->supersample_factor); } static void half_hline(Canvas *self, uint level, bool right_half, uint extend_by) { uint x1, x2; if (right_half) { x1 = minus(half_width(self), extend_by); x2 = self->width; } else { x1 = 0; x2 = half_width(self) + extend_by; } draw_hline(self, x1, x2, half_height(self), level); } typedef union Point { struct { int32_t x: 32, y: 32; }; int64_t val; } Point; static Point half_dhline(Canvas *self, uint level, bool right_half, Edge which) { uint x1 = 0, x2 = 0; if (right_half) { x1 = self->width / 2; x2 = self->width; } else x2 = self->width / 2; uint gap = thickness(self, level + 1, false); Point ans = {.x=self->height / 2 - gap, .y=self->height / 2 + gap}; if (which & TOP_EDGE) draw_hline(self, x1, x2, ans.x, level); if (which & BOTTOM_EDGE) draw_hline(self, x1, x2, ans.y, level); return ans; } static Point half_dvline(Canvas *self, uint level, bool bottom_half, Edge which) { uint y1 = 0, y2 = 0; if (bottom_half) { y1 = self->height / 2; y2 = self->height; } else y2 = self->height / 2; uint gap = thickness(self, level + 1, true); Point ans = {.x=self->width / 2 - gap, .y=self->width / 2 + gap}; if (which & LEFT_EDGE) draw_vline(self, y1, y2, ans.x, level); if (which & RIGHT_EDGE) draw_vline(self, y1, y2, ans.y, level); return ans; } static Point dhline(Canvas *self, uint level, Edge which) { half_dhline(self, level, false, which); return half_dhline(self, level, true, which); } static Point dvline(Canvas *self, uint level, Edge which) { half_dvline(self, level, false, which); return half_dvline(self, level, true, which); } static void half_vline(Canvas *self, uint level, bool bottom_half, uint extend_by) { uint y1, y2; if (bottom_half) { y1 = minus(half_height(self), extend_by); y2 = self->height; } else { y1 = 0; y2 = half_height(self) + extend_by; } draw_vline(self, y1, y2, half_width(self), level); } static void hline(Canvas *self, uint level) { half_hline(self, level, false, 0); half_hline(self, level, true, 0); } static void vline(Canvas *self, uint level) { half_vline(self, level, false, 0); half_vline(self, level, true, 0); } static void hholes(Canvas *self, uint level, uint num) { hline(self, level); add_hholes(self, level, num); } static void vholes(Canvas *self, uint level, uint num) { vline(self, level); add_vholes(self, level, num); } static uint8_t plus(uint8_t a, uint8_t b) { uint8_t res = a + b; res |= -(res < a); return res; } static uint8_t average_intensity(const Canvas *src, uint dest_x, uint dest_y) { uint src_x = dest_x * src->supersample_factor, src_y = dest_y * src->supersample_factor; uint total = 0; for (uint y = src_y; y < src_y + src->supersample_factor; y++) { uint offset = src->width * y; for (uint x = src_x; x < src_x + src->supersample_factor; x++) total += src->mask[offset + x]; } return (total / (src->supersample_factor * src->supersample_factor)) & 0xff; } static void downsample(const Canvas *src, Canvas *dest) { for (uint y = 0; y < dest->height; y++) { uint offset = dest->width * y; for (uint x = 0; x < dest->width; x++) { dest->mask[offset + x] = plus(dest->mask[offset + x], average_intensity(src, x, y)); } } } typedef struct StraightLine { double m, c; } StraightLine; static StraightLine line_from_points(double x1, double y1, double x2, double y2) { StraightLine ans = {.m = (y2 - y1) / (x2 - x1)}; ans.c = y1 - ans.m * x1; return ans; } static double line_y(StraightLine l, int x) { return l.m * x + l.c; } #define calc_limits(self, lower_y, upper_y) { \ if (!self->y_limits) { \ self->y_limits_count = self->width; self->y_limits = malloc(sizeof(self->y_limits[0]) * self->y_limits_count); \ if (!self->y_limits) fatal("Out of memory"); \ } \ for (uint x = 0; x < self->width; x++) { self->y_limits[x].lower = lower_y; self->y_limits[x].upper = upper_y; } \ } static void fill_region(Canvas *self, bool inverted) { uint8_t full = 0, empty = 0; if (inverted) empty = 255; else full = 255; for (uint y = 0; y < self->height; y++) { uint offset = y * self->width; for (uint x = 0; x < self->width && x < self->y_limits_count; x++) { self->mask[offset + x] = self->y_limits[x].lower <= y && y <= self->y_limits[x].upper ? full : empty; } } } static void triangle(Canvas *self, bool left, bool inverted) { int ay1 = 0, by1 = self->height - 1, y2 = self->height / 2, x1 = 0, x2 = 0; if (left) x2 = self->width - 1; else x1 = self->width - 1; StraightLine uppery = line_from_points(x1, ay1, x2, y2); StraightLine lowery = line_from_points(x1, by1, x2, y2); calc_limits(self, line_y(uppery, x), line_y(lowery, x)); fill_region(self, inverted); } typedef enum Corner { TOP_LEFT = LEFT_EDGE | TOP_EDGE, TOP_RIGHT = TOP_EDGE | RIGHT_EDGE, BOTTOM_LEFT = BOTTOM_EDGE | LEFT_EDGE, BOTTOM_RIGHT = BOTTOM_EDGE | RIGHT_EDGE, } Corner; static void thick_line(Canvas *self, uint thickness_in_pixels, Point p1, Point p2) { if (p1.x > p2.x) SWAP(p1, p2); StraightLine l = line_from_points(p1.x, p1.y, p2.x, p2.y); div_t d = div(thickness_in_pixels, 2); int delta = d.quot, extra = d.rem; for (int x = p1.x > 0 ? p1.x : 0; x < (int)self->width && x < p2.x + 1; x++) { int y_p = (int)line_y(l, x); for (int y = MAX(0, y_p - delta); y < MIN(y_p + delta + extra, (int)self->height); y++) { self->mask[x + y * self->width] = 255; } } } static void frame(Canvas *self, uint level, Edge edges) { uint h = thickness(self, level, true), v = thickness(self, level, false); #define line(x1, x2, y1, y2) { \ for (uint y=y1; y < min(y2, self->height); y++) memset(self->mask + y * self->width + x1, 255, minus(min(x2, self->width), x1)); } #define hline(y1, y2) line(0, self->width, y1, y2) #define vline(x1, x2) line(x1, x2, 0, self->height) if (edges & TOP_EDGE) hline(0, h + 1); if (edges & BOTTOM_EDGE) hline(self->height - h - 1, self->height); if (edges & LEFT_EDGE) vline(0, v + 1); if (edges & RIGHT_EDGE) vline(self->width - v - 1, self->width); #undef hline #undef vline #undef line } typedef enum Segment { LEFT, MIDDLE, RIGHT } Segment; static void progress_bar(Canvas *self, Segment which, bool filled) { const Edge edges = TOP_EDGE | BOTTOM_EDGE; switch(which) { case LEFT: frame(self, 1, LEFT_EDGE | edges); break; case MIDDLE: frame(self, 1, edges); break; case RIGHT: frame(self, 1, RIGHT_EDGE | edges); break; } if (!filled) return; uint h = thickness(self, 1, true), v = thickness(self, 1, false); static const uint gap_factor = 3; uint y1 = gap_factor * h, y2 = minus(self->height, gap_factor*h), x1 = 0, x2 = 0; switch(which) { case LEFT: x1 = gap_factor * v; x2 = self->width; break; case MIDDLE: x2 = self->width; break; case RIGHT: x2 = minus(self->width, gap_factor * v); break; } for (uint y = y1; y < y2; y++) memset(self->mask + y * self->width + x1, 255, minus(min(x2, self->width), x1)); } static void half_cross_line(Canvas *self, uint level, Corner corner) { uint my = minus(self->height, 1) / 2; Point p1 = {0}, p2 = {0}; switch (corner) { case TOP_LEFT: p2.x = minus(self->width, 1); p2.y = my; break; case BOTTOM_LEFT: p1.x = minus(self->width, 1); p1.y = my; p2.y = self->height -1; break; case TOP_RIGHT: p1.x = minus(self->width, 1); p2.y = my; break; case BOTTOM_RIGHT: p2.x = minus(self->width, 1), p2.y = minus(self->height, 1); p1.y = my; break; } thick_line(self, thickness(self, level, true), p1, p2); } static void cross_line(Canvas *self, uint level, bool left) { uint w = minus(self->width, 1), h = minus(self->height, 1); Point p1 = {0}, p2 = {0}; if (left) p2 = (Point){.x=w, .y=h}; else { p1.x = w; p2.y = h; } thick_line(self, thickness(self, level, true), p1, p2); } typedef struct CubicBezier { Point start, c1, c2, end; } CubicBezier; #define bezier_eq(which) { \ double tm1 = 1 - t; \ double tm1_3 = tm1 * tm1 * tm1; \ double t_3 = t * t * t; \ return tm1_3 * cb.start.which + 3 * t * tm1 * (tm1 * cb.c1.which + t * cb.c2.which) + t_3 * cb.end.which; \ } static double bezier_x(CubicBezier cb, double t) { bezier_eq(x); } static double bezier_y(CubicBezier cb, double t) { bezier_eq(y); } #undef bezier_eq static int find_bezier_for_D(int width, int height) { int cx = width - 1, last_cx = cx; CubicBezier cb = {.end={.x=0, .y=height - 1}, .c2={.x=0, .y=height - 1}}; while (true) { cb.c1.x = cx; cb.c2.x = cx; if (bezier_x(cb, 0.5) > width - 1) return last_cx; last_cx = cx++; } } static double find_t_for_x(CubicBezier cb, int x, double start_t) { if (fabs(bezier_x(cb, start_t) - x) < 0.1) return start_t; static const double t_limit = 0.5; double increment = t_limit - start_t; if (increment <= 0) return start_t; while (true) { double q = bezier_x(cb, start_t + increment); if (fabs(q - x) < 0.1) return start_t + increment; if (q > x) { increment /= 2.0; if (increment < 1e-6) { log_error("Failed to find cubic bezier t for x=%d\n", x); return start_t; } } else { start_t += increment; increment = t_limit - start_t; if (increment <= 0) return start_t; } } } static void get_bezier_limits(Canvas *self, CubicBezier cb) { int start_x = (int)bezier_x(cb, 0), max_x = (int)bezier_x(cb, 0.5); double last_t = 0.; for (int x = start_x; x < max_x + 1; x++) { if (x > start_x) last_t = find_t_for_x(cb, x, last_t); double upper = bezier_y(cb, last_t), lower = bezier_y(cb, 1.0 - last_t); if (fabs(upper - lower) <= 2.0) break; // avoid pip on end of D append_limit(self, lower, upper); } } #define mirror_horizontally(expr) { \ RAII_ALLOC(uint8_t, mbuf, calloc(self->width, self->height)); \ if (!mbuf) fatal("Out of memory"); \ uint8_t *buf = self->mask; \ self->mask = mbuf; \ expr; \ self->mask = buf; \ for (uint y = 0; y < self->height; y++) { \ uint offset = y * self->width; \ for (uint src_x = 0; src_x < self->width; src_x++) { \ uint dest_x = self->width - 1 - src_x; \ buf[offset + dest_x] = mbuf[offset + src_x]; \ } \ } \ } static void filled_D(Canvas *self, bool left) { int c1x = find_bezier_for_D(self->width, self->height); CubicBezier cb = {.end={.y=self->height-1}, .c1 = {.x=c1x}, .c2 = {.x=c1x, .y=self->height - 1}}; get_bezier_limits(self, cb); if (left) fill_region(self, false); else mirror_horizontally(fill_region(self, false)); } #define NAME position_set #define KEY_TY Point #define HASH_FN hash_point #define CMPR_FN cmpr_point static uint64_t hash_point(Point p); static bool cmpr_point(Point, Point); #include "kitty-verstable.h" static uint64_t hash_point(Point p) { return vt_hash_integer(p.val); } static bool cmpr_point(Point a, Point b) { return a.val == b.val; } #define draw_parametrized_curve(self, level, xfunc, yfunc) { \ div_t d = div(thickness(self, level, true), 2u); \ int delta = d.quot, extra = d.rem; \ uint num_samples = self->height * 8; \ position_set seen; vt_init(&seen); \ for (uint i = 0; i < num_samples + 1; i++) { \ double t = i / (double)num_samples; \ Point p = {.x=(int32_t)xfunc, .y=(int32_t)yfunc}; \ position_set_itr q = vt_get(&seen, p); \ if (!vt_is_end(q)) continue; \ if (vt_is_end(vt_insert(&seen, p))) fatal("Out of memory"); \ for (int y = MAX(0, p.y - delta); y < MIN(p.y + delta + extra, (int)self->height); y++) { \ uint offset = y * self->width, start = MAX(0, p.x - delta); \ memset(self->mask + offset + start, 255, minus((uint)MIN(p.x + delta + extra, (int)self->width), start)); \ } \ } \ vt_cleanup(&seen); \ } static void rounded_separator(Canvas *self, uint level, bool left) { uint gap = thickness(self, level, true); int c1x = find_bezier_for_D(minus(self->width, gap), self->height); CubicBezier cb = {.end={.y=self->height - 1}, .c1={.x=c1x}, .c2={.x=c1x, .y=self->height - 1}}; if (left) { draw_parametrized_curve(self, level, bezier_x(cb, t), bezier_y(cb, t)); } else { mirror_horizontally(draw_parametrized_curve(self, level, bezier_x(cb, t), bezier_y(cb, t))); } } static void corner_triangle(Canvas *self, const Corner corner) { StraightLine diag; const uint w = minus(self->width, 1), h = minus(self->height, 1); bool top = corner == TOP_RIGHT || corner == TOP_LEFT; if (corner == TOP_RIGHT || corner == BOTTOM_LEFT) diag = line_from_points(0, 0, w, h); else diag = line_from_points(w, 0, 0, h); for (uint x = 0; x < self->width; x++) { if (top) append_limit(self, line_y(diag, x), 0); else append_limit(self, h, line_y(diag, x)); } fill_region(self, false); } typedef struct Circle { Point origin; double radius; double start, end, amt; } Circle; static Circle circle(Point origin, double radius, double start_at, double end_at) { double conv = M_PI / 180.; Circle ans = {.origin=origin, .radius=radius, .start=start_at*conv, .end=end_at*conv}; ans.amt = ans.end - ans.start; return ans; } static double circle_x(Circle c, double t) { return c.origin.x + c.radius * cos(c.start + c.amt * t); } static double circle_y(Circle c, double t) { return c.origin.y + c.radius * sin(c.start + c.amt * t); } static void spinner(Canvas *self, uint level, double start_degrees, double end_degrees) { uint w = self->width / 2, h = self->height / 2; uint radius = minus(min(w, h), thickness(self, level, true) / 2); Circle c = circle((Point){.x=w, .y=h}, radius, start_degrees, end_degrees); draw_parametrized_curve(self, level, circle_x(c, t), circle_y(c, t)); } static void draw_circle(Canvas *self, double scale, double gap, bool invert) { const uint w = self->width / 2, h = self->height / 2; const double radius = (int)(scale * min(w, h) - gap / 2); const uint8_t fill = invert ? 0 : 255; const double limit = radius * radius; for (uint y = 0; y < self->height; y++) { for (uint x = 0; x < self->width; x++) { double xw = (double)x - w, yh = (double)y - h; if (xw * xw + yh * yh <= limit) self->mask[y * self->width + x] = fill; } } } static void draw_fish_eye(Canvas *self, uint level) { uint w = self->width / 2, h = self->height / 2; uint line_width = thickness(self, level, true) / 2; uint radius = minus(min(w, h), line_width); Circle c = circle((Point){.x=w, .y=h}, radius, 0, 360); draw_parametrized_curve(self, level, circle_x(c, t), circle_y(c, t)); uint gap = minus(radius, radius / 10); draw_circle(self, 1.0, gap, false); } static void inner_corner(Canvas *self, uint level, Corner corner) { uint hgap = thickness(self, level + 1, true), vgap = thickness(self, level + 1, false); uint vthick = thickness(self, level, true) / 2; uint x1 = 0, x2 = self->width, y1 = 0, y2 = self->height; int xd = 1, yd = 1; if (corner & LEFT_EDGE) { x2 = minus(self->width / 2 + vthick + 1, hgap); xd = -1; } else x1 = minus(self->width / 2 + hgap, vthick); if (corner & TOP_EDGE) { y2 = minus(self->height / 2, vgap); yd = -1; } else y1 = self->height / 2 + vgap; draw_hline(self, x1, x2, self->height / 2 + (yd * vgap), level); draw_vline(self, y1, y2, self->width / 2 + (xd * hgap), level); } static Range fourth_range(uint size, uint which) { uint thickness = max(1, size / 4); uint block = thickness * 4; if (block == size) return (Range){.start=thickness * which, .end=thickness * (which + 1)}; if (block > size) { uint start = min(which * thickness, minus(size, thickness)); return (Range){.start=start, .end=start + thickness}; } uint extra = minus(size, block); uint thicknesses[4] = {thickness, thickness, thickness, thickness}; uint pos = 0; if (extra) { #define d(i) thicknesses[i]++; if (!--extra) goto done; // ensures the thickness of first and last are least likely to be changed d(1); d(2); d(3); d(0); #undef d } done: for (uint i = 0; i < which; i++) pos += thicknesses[i]; return (Range){.start=pos, .end=pos + thicknesses[which]}; } static Range eight_range(uint size, uint which) { uint thickness = max(1, size / 8); uint block = thickness * 8; if (block == size) return (Range){.start=thickness * which, .end=thickness * (which + 1)}; if (block > size) { uint start = min(which * thickness, minus(size, thickness)); return (Range){.start=start, .end=start + thickness}; } uint extra = minus(size, block); uint thicknesses[8] = {thickness, thickness, thickness, thickness, thickness, thickness, thickness, thickness}; uint pos = 0; if (extra) { #define d(i) thicknesses[i]++; if (!--extra) goto done; // ensures the thickness of first and last are least likely to be changed d(3); d(4); d(2); d(5); d(6); d(1); d(7); d(0); #undef d } done: for (uint i = 0; i < which; i++) pos += thicknesses[i]; return (Range){.start=pos, .end=pos + thicknesses[which]}; } static void eight_bar(Canvas *self, uint which, bool horizontal) { Range x_range, y_range; if (horizontal) { x_range = (Range){0, self->width}; y_range = eight_range(self->height, which); } else { y_range = (Range){0, self->height}; x_range = eight_range(self->width, which); } for (uint y = y_range.start; y < y_range.end; y++) { uint offset = y * self->width; memset(self->mask + offset + x_range.start, 255, minus(x_range.end, x_range.start)); } } static void octant_segment(Canvas *self, uint8_t which, bool left) { Range x_range = left ? (Range){0, self->width / 2} : (Range){self->width/2, self->width}; Range y_range = fourth_range(self->height, which); for (uint y = y_range.start; y < y_range.end; y++) { uint offset = y * self->width; memset(self->mask + offset + x_range.start, 255, minus(x_range.end, x_range.start)); } } static void octant(Canvas *self, uint8_t which) { enum flags { a = 1, b = 2, c = 4, d = 8, m = 16, n = 32, o = 64, p = 128 }; static const enum flags mapping[232] = { // 00 - 0f b, b|m, a|b|m, n, a|n, a|m|n, b|n, a|b|n, b|m|n, c, a|c, c|m, a|c|m, a|b|c, b|c|m, a|b|c|m, // 10 - 1f c|n, a|c|n, c|m|n, a|c|m|n, b|c|n, a|b|c|n, b|c|m|n, a|b|c|m|n, o, a|o, m|o, a|m|o, b|o, a|b|o, b|m|o, a|b|m|o, // 20 - 2f a|n|o, m|n|o, a|m|n|o, b|n|o, a|b|n|o, b|m|n|o, a|b|m|n|o, c|o, a|c|o, c|m|o, a|c|m|o, b|c|o, a|b|c|o, b|c|m|o, a|b|c|m|o, c|n|o, // 30 - 3f a|c|n|o, c|m|n|o, a|c|m|n|o, b|c|n|o, a|b|c|n|o, b|c|m|n|o, a|d, d|m, a|d|m, b|d, a|b|d, b|d|m, a|b|d|m, d|n, a|d|n, d|m|n, // 40 - 4f a|d|m|n, b|d|n, a|b|d|n, b|d|m|n, a|b|d|m|n, a|c|d, c|d|m, a|c|d|m, b|c|d, b|c|d|m, a|b|c|d|m, c|d|n, a|c|d|n, a|c|d|m|n, b|c|d|n, a|b|c|d|n, // 50 - 5f b|c|d|m|n, d|o, a|d|o, d|m|o, a|d|m|o, b|d|o, a|b|d|o, b|d|m|o, a|b|d|m|o, d|n|o, a|d|n|o, d|m|n|o, a|d|m|n|o, b|d|n|o, a|b|d|n|o, b|d|m|n|o, // 60 - 6f ~(c|p), c|d|o, a|c|d|o, c|d|m|o, a|c|d|m|o, b|c|d|o, ~(m|n|p), b|c|d|m|o, ~(n|p), c|d|n|o, a|c|d|n|o, c|d|m|n|o, ~(b|p), b|c|d|n|o, ~(m|p), ~(a|p), // 70 - 7f ~p, a|p, m|p, a|m|p, b|p, a|b|p, b|m|p, a|b|m|p, n|p, a|n|p, m|n|p, a|m|n|p, b|n|p, a|b|n|p, b|m|n|p, ~(c|d|o), // 80 - 8f c|p, a|c|p, c|m|p, a|c|m|p, b|c|p, a|b|c|p, b|c|m|p, ~(d|n|o), c|n|p, a|c|n|p, c|m|n|p, ~(b|d|o), b|c|n|p, ~(d|m|o), ~(a|d|o), ~(d|o), // 90 - 9f a|o|p, m|o|p, a|m|o|p, b|o|p, b|m|o|p, a|b|m|o|p, n|o|p, a|n|o|p, a|m|n|o|p, b|n|o|p, a|b|n|o|p, b|m|n|o|p, c|o|p, a|c|o|p, c|m|o|p, a|c|m|o|p, // a0 - af b|c|o|p, a|b|c|o|p, b|c|m|o|p, ~(n|d), c|n|o|p, a|c|n|o|p, c|m|n|o|p, ~(b|d), b|c|n|o|p, ~(d|m), ~(a|d), ~d, a|d|p, d|m|p, a|d|m|p, b|d|p, // b0 - bf a|b|d|p, b|d|m|p, a|b|d|m|p, d|n|p, a|d|n|p, d|m|n|p, a|d|m|n|p, b|d|n|p, a|b|d|n|p, b|d|m|n|p, ~(c|o), c|d|p, a|c|d|p, c|d|m|p, a|c|d|m|p, b|c|d|p, // c0 -cf a|b|c|d|p, b|c|d|m|p, ~(n|o), c|d|n|p, a|c|d|n|p, c|d|m|n|p, ~(b|o), b|c|d|n|p, ~(m|o), ~(a|o), ~o, d|o|p, a|d|o|p, d|m|o|p, a|d|m|o|p, b|d|o|p, // d0 - df a|b|d|o|p, b|d|m|o|p, ~(c|n), d|n|o|p, a|d|n|o|p, d|m|n|o|p, ~(b|c), b|d|n|o|p, ~(c|m), ~(a|c), ~c, a|c|d|o|p, c|d|m|o|p, ~(b|n), b|c|d|o|p, ~(a|n), // e0 - e7 ~n, c|d|n|o|p, ~(b|m), ~b, ~m, ~a, b|c, n|o, }; which = mapping[which]; if (which & a) octant_segment(self, 0, true); if (which & b) octant_segment(self, 1, true); if (which & c) octant_segment(self, 2, true); if (which & d) octant_segment(self, 3, true); if (which & m) octant_segment(self, 0, false); if (which & n) octant_segment(self, 1, false); if (which & o) octant_segment(self, 2, false); if (which & p) octant_segment(self, 3, false); } static void eight_block(Canvas *self, int horizontal, ...) { va_list args; va_start(args, horizontal); int which; while ((which = va_arg(args, int)) >= 0) eight_bar(self, which, horizontal); va_end(args); } typedef struct Shade { bool light, invert, fill_blank; Edge which_half; uint xnum, ynum; } Shade; #define is_odd(x) ((x) & 1u) static void shade(Canvas *self, Shade s) { const uint square_width = max(1, self->width / s.xnum); const uint square_height = max(1, s.ynum ? (self->height / s.ynum) : square_width); uint number_of_rows = self->height / square_height; uint number_of_cols = self->width / square_width; // Make sure the parity is correct // (except when that would cause division by zero) if (number_of_cols > 1 && is_odd(number_of_cols) != is_odd(s.xnum)) number_of_cols--; if (number_of_rows > 1 && is_odd(number_of_rows) != is_odd(s.ynum)) number_of_rows--; // Calculate how much space remains unused, and how frequently // to insert an extra column/row to fill all of it uint excess_cols = minus(self->width, square_width * number_of_cols); double square_width_extension = (double)excess_cols / number_of_cols; uint excess_rows = minus(self->height, square_height * number_of_rows); double square_height_extension = (double)excess_rows / number_of_rows; Range rows = {.end=number_of_rows}, cols = {.end=number_of_cols}; switch(s.which_half) { // this is to remove gaps between half-filled characters case TOP_EDGE: rows.end /= 2; square_height_extension *= 2; break; case BOTTOM_EDGE: rows.start = number_of_rows / 2; square_height_extension *= 2; break; case LEFT_EDGE: cols.end /= 2; square_width_extension *= 2; break; case RIGHT_EDGE: cols.start = number_of_cols / 2; square_width_extension *= 2; break; } bool extra_row = false; uint ey = 0, old_ey = 0, drawn_rows = 0; for (uint r = rows.start; r < rows.end; r++) { // Keep track of how much extra height has accumulated, and add an extra row at every passed integer, including 0 old_ey = ey; ey = (uint)ceil(drawn_rows * square_height_extension); extra_row = ey != old_ey; drawn_rows += 1; bool extra_col = false; uint ex = 0, old_ex = 0, drawn_cols = 0; for (uint c = cols.start; c < cols.end; c++) { old_ex = ex; ex = (uint)ceil(drawn_cols * square_width_extension); extra_col = ex != old_ex; drawn_cols += 1; // Fill extra rows with semi-transparent pixels that match the pattern if (extra_row) { uint y = r * square_height + old_ey; uint offset = self->width * y; for (uint xc = 0; xc < square_width; xc++) { uint x = c * square_width + xc + ex; if (s.light) { if (s.invert) self->mask[offset + x] = is_odd(c) ? 255 : 70; else self->mask[offset + x] = is_odd(c) ? 0 : 70; } else self->mask[offset + x] = is_odd(c) == s.invert ? 120 : 30; } } // Do the same for the extra columns if (extra_col) { uint x = c * square_width + old_ex; for (uint yr = 0; yr < square_height; yr++) { uint y = r * square_height + yr + ey; uint offset = self->width * y; if (s.light) { if (s.invert) self->mask[offset + x] = is_odd(r) ? 255 : 70; else self->mask[offset + x] = is_odd(r) ? 0 : 70; } else self->mask[offset + x] = is_odd(r) == s.invert ? 120 : 30; } } // And in case they intersect, set the corner pixel too if (extra_row && extra_col) { uint x = c * square_width + old_ex; uint y = r * square_height + old_ey; uint offset = self->width * y; self->mask[offset + x] = 50; } const bool is_blank = s.invert ^ (is_odd(r) != is_odd(c) || (s.light && is_odd(r))); if (!is_blank) { // Fill the square for (uint yr = 0; yr < square_height; yr++) { uint y = r * square_height + yr + ey; uint offset = self->width * y; for (uint xc = 0; xc < square_width; xc++) { uint x = c * square_width + xc + ex; self->mask[offset + x] = 255; } } } } } if (!s.fill_blank) return; cols = (Range){.end=self->width}; rows = (Range){.end=self->height}; switch(s.which_half) { case BOTTOM_EDGE: rows.end = self->height / 2; break; case TOP_EDGE: rows.start = minus(self->height / 2, 1); break; case RIGHT_EDGE: cols.end = self->width / 2; break; case LEFT_EDGE: cols.start = minus(self->width / 2, 1); break; } for (uint r = rows.start; r < rows.end; r++) memset(self->mask + r * self->width + cols.start, 255, cols.end - cols.start); } static void apply_mask(Canvas *self, uint8_t *mask) { for (uint y = 0; y < self->height; y++) { uint offset = y * self->width; for (uint x = 0; x < self->width; x++) { uint p = offset + x; self->mask[p] = (uint8_t)round((mask[p] / 255.0) * self->mask[p]); } } } static void cross_shade(Canvas *self, bool rotate) { static const uint num_of_lines = 7; uint line_thickness = max(self->supersample_factor, self->width / num_of_lines); uint delta = 2 * line_thickness; uint y1 = 0, y2 = self->height; if (rotate) SWAP(y1, y2); for (uint x = 0; x < self->width; x += delta) { thick_line(self, line_thickness, (Point){.x=0 + x, .y=y1}, (Point){.x=self->width + x, .y=y2}); thick_line(self, line_thickness, (Point){.x=0 - x, .y=y1}, (Point){.x=self->width - x, .y=y2}); } } static void quad(Canvas *self, Corner which) { uint x = which & LEFT_EDGE ? 0 : 1, y = which & TOP_EDGE ? 0 : 1; uint num_cols = self->width / 2; uint left = x * num_cols; uint right = x ? self->width : num_cols; uint num_rows = self->height / 2; uint top = y * num_rows; uint bottom = y ? self->height : num_rows; for (uint r = top; r < bottom; r++) { uint off = r * self->width; memset(self->mask + off + left, 255, right - left); } } static void quads(Canvas *self, ...) { va_list args; va_start(args, self); int which; while ((which = va_arg(args, int))) quad(self, which); va_end(args); } static void smooth_mosaic(Canvas *self, bool lower, double ax, double ay, double bx, double by) { StraightLine l = line_from_points( ax * minus(self->width, 1), ay * minus(self->height, 1), bx * minus(self->width, 1), by * minus(self->height, 1)); for (uint y = 0; y < self->height; y++) { uint offset = y * self->width; for (uint x = 0; x < self->width; x++) { double edge = line_y(l, x); if ((lower && y >= edge) || (!lower && y <= edge)) self->mask[offset + x] = 255; } } } static void half_triangle(Canvas *self, Edge which, bool inverted) { uint mid_x = self->width / 2, mid_y = self->height / 2; StraightLine u, l; append_limit(self, 0, 0); // ensure space for limits #define set_limits(startx, endx, a, b) for (uint x = startx; x < endx; x++) self->y_limits[x] = (Limit){.upper=b, .lower=a}; switch (which) { case LEFT_EDGE: u = line_from_points(0, 0, mid_x, mid_y); l = line_from_points(0, minus(self->height, 1), mid_x, mid_y); set_limits(0, self->width, line_y(u, x), line_y(l, x)); break; case TOP_EDGE: l = line_from_points(0, 0, mid_x, mid_y); set_limits(0, mid_x, 0, line_y(l, x)); l = line_from_points(mid_x, mid_y, minus(self->width, 1), 0); set_limits(mid_x, self->width, 0, line_y(l, x)); break; case RIGHT_EDGE: u = line_from_points(mid_x, mid_y, minus(self->width, 1), 0); l = line_from_points(mid_x, mid_y, minus(self->width, 1), minus(self->height, 1)); set_limits(0, self->width, line_y(u, x), line_y(l, x)); break; case BOTTOM_EDGE: l = line_from_points(0, minus(self->height, 1), mid_x, mid_y); set_limits(0, mid_x, line_y(l, x), minus(self->height, 1)); l = line_from_points(mid_x, mid_y, minus(self->width, 1), minus(self->height, 1)); set_limits(mid_x, self->width, line_y(l, x), minus(self->height, 1)); break; } self->y_limits_count = self->width; fill_region(self, inverted); #undef set_limits } static void mid_lines(Canvas *self, uint level, ...) { uint mid_x = self->width / 2, mid_y = self->height / 2; const uint th = thickness(self, level, true); const Point l = {.x=0, .y=mid_y}, t={.x=mid_x, .y=0}, r={.x=minus(self->width, 1), .y=mid_y}, b={.x=mid_x, .y=minus(self->height, 1)}; va_list args; va_start(args, level); Corner which; while ((which = va_arg(args, int)) > 0) { Point p1, p2; switch(which) { case TOP_LEFT: p1 = l; p2 = t; break; case TOP_RIGHT: p1 = r; p2 = t; break; case BOTTOM_LEFT: p1 = l; p2 = b; break; case BOTTOM_RIGHT: p1 = r; p2 = b; break; } thick_line(self, th, p1, p2); } va_end(args); } static Point* get_fading_lines(uint total_length, uint num, Edge fade) { uint step = total_length / num, d1 = 0; int dir = 1; if (fade == LEFT_EDGE || fade == TOP_EDGE) { dir = -1; d1 = total_length; } Point *ans = malloc(num * sizeof(Point)); if (!ans) fatal("Out of memory"); for (uint i = 0; i < num; i++) { uint sz = step * (num - i) / (num + 1); if (step > 2 && sz >= step - 1) sz = step - 2; int d2 = d1 + dir * sz; if (d2 < 0) d2 = 0; if (d1 <= (uint)d2) { ans[i].x = d1; ans[i].y = d2; } else { ans[i].x = d2; ans[i].y = d1; } d1 += step * dir; } return ans; } static void fading_hline(Canvas *self, uint level, uint num, Edge fade) { uint y = self->height / 2; RAII_ALLOC(Point, pts, get_fading_lines(self->width, num, fade)); for (uint i = 0; i < num; i++) { uint x1 = pts[i].x, x2 = pts[i].y; draw_hline(self, x1, x2, y, level); } } static void fading_vline(Canvas *self, uint level, uint num, Edge fade) { uint x = self->width / 2; RAII_ALLOC(Point, pts, get_fading_lines(self->height, num, fade)); for (uint i = 0; i < num; i++) { uint y1 = pts[i].x, y2 = pts[i].y; draw_vline(self, y1, y2, x, level); } } typedef struct Rectircle Rectircle; typedef double (*Rectircle_equation)(Rectircle r, double t); typedef struct Rectircle { uint a, b; double yexp, xexp, adjust_x; uint cell_width; Rectircle_equation x, y; } Rectircle; static double rectircle_lower_quadrant_y(Rectircle r, double t) { return r.b * t; // 0 -> top of cell, 1 -> middle of cell } static double rectircle_upper_quadrant_y(Rectircle r, double t) { return r.b * (2. - t); // 0 -> bottom of cell, 1 -> middle of cell } // x(t). To get this we first need |y(t)|/b. This is just t since as t goes // from 0 to 1 y goes from either 0 to b or 0 to -b static double rectircle_left_quadrant_x(Rectircle r, double t) { double xterm = 1 - pow(t, r.yexp); return floor(r.cell_width - fabs(r.a * pow(xterm, r.xexp)) - r.adjust_x); } static double rectircle_right_quadrant_x(Rectircle r, double t) { double xterm = 1 - pow(t, r.yexp); return ceil(fabs(r.a * pow(xterm, r.xexp))); } static Rectircle rectcircle(Canvas *self, Corner which) { /* Return two functions, x(t) and y(t) that map the parameter t which must be in the range [0, 1] to x and y coordinates in the cell. The rectircle equation we use is: (|x| / a) ^ (2a / r) + (|y| / a) ^ (2b / r) = 1 where 2a = width, 2b = height and r is radius The entire rectircle fits in four cells, each cell being one quadrant of the full rectircle and the origin being the center of the rectircle. The functions we return do the mapping for the specified cell. ╭╮ ╭─╮ ╰╯ │ │ ╰─╯ See https://math.stackexchange.com/questions/1649714 */ double radius = self->width / 2.; uint cell_width_is_odd = (self->width / self->supersample_factor) & 1; Rectircle ans = { .a = half_width(self), .b = half_height(self), .yexp = self->height / radius, .xexp = radius / self->width, .cell_width = self->width, .adjust_x = cell_width_is_odd * self->supersample_factor, .x = which & LEFT_EDGE ? rectircle_left_quadrant_x : rectircle_right_quadrant_x, .y = which & TOP_EDGE ? rectircle_upper_quadrant_y : rectircle_lower_quadrant_y, }; return ans; } static void rounded_corner(Canvas *self, uint level, Corner which) { Rectircle r = rectcircle(self, which); draw_parametrized_curve(self, level, r.x(r, t), r.y(r, t)); } static void commit(Canvas *self, Edge lines, bool solid) { static const uint level = 1; static const double scale = 0.9; uint hw = half_width(self), hh = half_height(self); if (lines & RIGHT_EDGE) draw_hline(self, hw, self->width, hh, level); if (lines & LEFT_EDGE) draw_hline(self, 0, hw, hh, level); if (lines & TOP_EDGE) draw_vline(self, 0, hh, hw, level); if (lines & BOTTOM_EDGE) draw_vline(self, hh, self->height, hw, level); draw_circle(self, scale, 0, false); if (!solid) draw_circle(self, scale, thickness(self, level, true), true); } // thin and fat line levels #define t 1u #define f 3u static void corner(Canvas *self, uint hlevel, uint vlevel, Corner which) { half_hline(self, hlevel, which & RIGHT_EDGE, thickness(self, vlevel, true) / 2); half_vline(self, vlevel, which & BOTTOM_EDGE, 0); } static void cross(Canvas *self, uint which) { static const uint level_map[16][4] = { {t, t, t, t}, {f, t, t, t}, {t, f, t, t}, {f, f, t, t}, {t, t, f, t}, {t, t, t, f}, {t, t, f, f}, {f, t, f, t}, {t, f, f, t}, {f, t, t, f}, {t, f, t, f}, {f, f, f, t}, {f, f, t, f}, {f, t, f, f}, {t, f, f, f}, {f, f, f, f} }; const uint *m = level_map[which]; half_hline(self, m[0], false, 0); half_hline(self, m[1], true, 0); half_vline(self, m[2], false, 0); half_vline(self, m[3], true, 0); } static void vert_t(Canvas *self, uint base_char, uint variation) { static const uint level_map[8][3] = { {t, t, t}, {t, f, t}, {f, t, t}, {t, t, f}, {f, t, f}, {f, f, t}, {t, f, f}, {f, f, f} }; const uint *m = level_map[variation]; half_vline(self, m[0], false, 0); half_hline(self, m[1], base_char != L'┤', 0); half_vline(self, m[2], true, 0); } static void horz_t(Canvas *self, uint base_char, uint variation) { static const uint level_map[8][3] = { {t, t, t}, {f, t, t}, {t, f, t}, {f, f, t}, {t, t, f}, {f, t, f}, {t, f, f}, {f, f, f} }; const uint *m = level_map[variation]; half_hline(self, m[0], false, 0); half_hline(self, m[1], true, 0); half_vline(self, m[2], base_char != L'┴', 0); } static void dvcorner(Canvas *self, uint level, Corner which) { half_dhline(self, level, which & LEFT_EDGE, TOP_EDGE | BOTTOM_EDGE); uint gap = thickness(self, level + 1, false); half_vline(self, level, which & TOP_EDGE, gap / 2 + thickness(self, level, false)); } static void dhcorner(Canvas *self, uint level, Corner which) { half_dvline(self, level, which & TOP_EDGE, LEFT_EDGE | RIGHT_EDGE); uint gap = thickness(self, level + 1, true); half_hline(self, level, which & LEFT_EDGE, gap / 2 + thickness(self, level, true)); } static void dcorner(Canvas *self, uint level, Corner which) { uint hgap = thickness(self, level + 1, false); uint vgap = thickness(self, level + 1, true); uint x1 = self->width / 2, x2 = self->width / 2; if (which & RIGHT_EDGE) x1 = 0; else x2 = self->width; uint ypos = self->height / 2; int ydelta = which & BOTTOM_EDGE ? hgap : -hgap; if (which & RIGHT_EDGE) x2 += vgap; else x1 = minus(x1, vgap); draw_hline(self, x1, x2, ypos + ydelta, level); if (which & RIGHT_EDGE) x2 = minus(x2, 2 * vgap); else x1 += 2 * vgap; draw_hline(self, x1, x2, ypos - ydelta, level); uint y1 = self->height / 2, y2 = self->height / 2; if (which & BOTTOM_EDGE) y1 = 0; else y2 = self->height; uint xpos = self->width / 2; int xdelta = (which & LEFT_EDGE) ? vgap : -vgap; uint yd = thickness(self, level, true) / 2; if (which & BOTTOM_EDGE) y2 += hgap + yd; else y1 -= hgap + yd; draw_vline(self, y1, y2, xpos - xdelta, level); if (which & BOTTOM_EDGE) y2 -= 2 * hgap; else y1 += 2 * hgap; draw_vline(self, y1, y2, xpos + xdelta, level); } static void dpip(Canvas *self, uint level, Edge which) { uint x1, x2, y1, y2; if (which & (LEFT_EDGE | RIGHT_EDGE)) { Point p = dvline(self, level, LEFT_EDGE | RIGHT_EDGE); if (which & LEFT_EDGE) { x1 = 0; x2 = p.x; } else { x1 = p.y; x2 =self->width; } draw_hline(self, x1, x2, self->height / 2, level); } else { Point p = dhline(self, level, TOP_EDGE | BOTTOM_EDGE); if (which & TOP_EDGE) { y1 = 0; y2 = p.x; } else { y1 = p.y; y2 = self->height; } draw_vline(self, y1, y2, self->width / 2, level); } } static void braille_dot(Canvas *self, uint col, uint row) { static const uint num_x_dots = 2, num_y_dots = 4; unsigned x_gaps[num_x_dots * 2], y_gaps[num_y_dots * 2]; unsigned dot_width = distribute_dots(self->width, num_x_dots, x_gaps, x_gaps + num_x_dots); unsigned dot_height = distribute_dots(self->height, num_y_dots, y_gaps, y_gaps + num_y_dots); uint x_start = x_gaps[col] + col * dot_width; uint y_start = y_gaps[row] + row * dot_height; if (y_start < self->height && x_start < self->width) { for (uint y = y_start; y < min(self->height, y_start + dot_height); y++) { uint offset = y * self->width; memset(self->mask + offset + x_start, 255, minus(min(self->width, x_start + dot_width), x_start)); } } } static void braille(Canvas *self, uint8_t which) { if (!which) return; for (uint8_t i = 0, mask = 1; i < 8; i++, mask <<= 1) { if (which & mask) { uint q = i + 1, col, row; switch(q) { case 1: case 2: case 3: case 7: col = 0; break; default: col = 1; break; } switch(q) { case 1: case 4: row = 0; break; case 2: case 5: row = 1; break; case 3: case 6: row = 2; break; default: row = 3; } braille_dot(self, col, row); } } } static void draw_sextant(Canvas *self, uint row, uint col) { Point start = {0}, end = {.x=self->width, .y = self->height}; switch(row) { case 0: end.y = self->height / 3; break; case 1: start.y = self->height / 3; end.y = 2 * self->height / 3; break; case 2: start.y = 2 * self->height / 3; break; } switch(col) { case 0: end.x = self->width / 2; break; default: start.x = self->width / 2; break; } for (int r = start.y; r < end.y; r++) { uint off = r * self->width; memset(self->mask + off + start.x, 255, end.x - start.x); } } static void sextant(Canvas *self, uint which) { #define add_row(q, r) if (q & 1) { draw_sextant(self, r, 0); } if (q & 2) { draw_sextant(self, r, 1); } add_row(which % 4, 0) add_row(which / 4, 1) add_row(which / 16, 2) #undef add_row } void render_box_char(char_type ch, uint8_t *buf, unsigned width, unsigned height, double dpi_x, double dpi_y, double scale) { Canvas canvas = {.mask=buf, .width = width, .height = height, .dpi={.x=dpi_x, .y=dpi_y}, .supersample_factor=1u, .scale=scale}, ss = canvas; ss.mask = buf + width*height; ss.supersample_factor = SUPERSAMPLE_FACTOR; ss.width *= SUPERSAMPLE_FACTOR; ss.height *= SUPERSAMPLE_FACTOR; fill_canvas(&canvas, 0); Canvas *c = &canvas; #define SB(ch, ...) case ch: fill_canvas(&ss, 0); c = &ss, __VA_ARGS__; downsample(&ss, &canvas); #define CC(ch, ...) case ch: __VA_ARGS__; break #define SS(ch, ...) SB(ch, __VA_ARGS__); break #define C(ch, func, ...) CC(ch, func(c, __VA_ARGS__)) #define S(ch, func, ...) SS(ch, func(c, __VA_ARGS__)) START_ALLOW_CASE_RANGE switch(ch) { default: log_error("Unknown box drawing character: U+%x rendered as blank", ch); break; case L'█': fill_canvas(c, 255); break; C(L'─', hline, 1); C(L'━', hline, 3); C(L'│', vline, 1); C(L'┃', vline, 3); C(L'╌', hholes, 1, 1); C(L'╍', hholes, 3, 1); C(L'┄', hholes, 1, 2); C(L'┅', hholes, 3, 2); C(L'┈', hholes, 1, 3); C(L'┉', hholes, 3, 3); C(L'╎', vholes, 1, 1); C(L'╏', vholes, 3, 1); C(L'┆', vholes, 1, 2); C(L'┇', vholes, 3, 2); C(L'┊', vholes, 1, 3); C(L'┋', vholes, 3, 3); C(L'╴', half_hline, 1, false, 0); C(L'╵', half_vline, 1, false, 0); C(L'╶', half_hline, 1, true, 0); C(L'╷', half_vline, 1, true, 0); C(L'╸', half_hline, 3, false, 0); C(L'╹', half_vline, 3, false, 0); C(L'╺', half_hline, 3, true, 0); C(L'╻', half_vline, 3, true, 0); CC(L'╾', half_hline(c, 3, false, 0); half_hline(c, 1, true, 0)); CC(L'╼', half_hline(c, 1, false, 0); half_hline(c, 3, true, 0)); CC(L'╿', half_vline(c, 3, false, 0); half_vline(c, 1, true, 0)); CC(L'╽', half_vline(c, 1, false, 0); half_vline(c, 3, true, 0)); S(L'', triangle, true, false); S(L'', triangle, true, true); SS(L'', half_cross_line(c, 1, TOP_LEFT); half_cross_line(c, 1, BOTTOM_LEFT)); S(L'', triangle, false, false); S(L'', triangle, false, true); SS(L'', half_cross_line(c, 1, TOP_RIGHT); half_cross_line(c, 1, BOTTOM_RIGHT)); S(L'', filled_D, true); S(L'◗', filled_D, true); S(L'', filled_D, false); S(L'◖', filled_D, false); S(L'', rounded_separator, 1, true); S(L'', rounded_separator, 1, false); S(L'', cross_line, 1, true); S(L'', cross_line, 1, true); S(L'╲', cross_line, 1, true); S(L'', cross_line, 1, false); S(L'', cross_line, 1, false); S(L'╱', cross_line, 1, false); SS(L'╳', cross_line(c, 1, false); cross_line(c, 1, true)); S(L'', corner_triangle, BOTTOM_LEFT); S(L'◣', corner_triangle, BOTTOM_LEFT); S(L'', corner_triangle, BOTTOM_RIGHT); S(L'◢', corner_triangle, BOTTOM_RIGHT); S(L'', corner_triangle, TOP_LEFT); S(L'◤', corner_triangle, TOP_LEFT); S(L'', corner_triangle, TOP_RIGHT); S(L'◥', corner_triangle, TOP_RIGHT); C(L'', progress_bar, LEFT, false); C(L'', progress_bar, MIDDLE, false); C(L'', progress_bar, RIGHT, false); C(L'', progress_bar, LEFT, true); C(L'', progress_bar, MIDDLE, true); C(L'', progress_bar, RIGHT, true); S(L'', spinner, 1, 235, 305); S(L'', spinner, 1, 270, 390); S(L'', spinner, 1, 315, 470); S(L'', spinner, 1, 360, 540); S(L'', spinner, 1, 80, 220); S(L'', spinner, 1, 170, 270); S(L'○', spinner, 0, 0, 360); S(L'◜', spinner, 1, 180, 270); S(L'◝', spinner, 1, 270, 360); S(L'◞', spinner, 1, 360, 450); S(L'◟', spinner, 1, 450, 540); S(L'◠', spinner, 1, 180, 360); S(L'◡', spinner, 1, 0, 180); S(L'●', draw_circle, 1.0, 0, false); S(L'◉', draw_fish_eye, 0); C(L'═', dhline, 1, TOP_EDGE | BOTTOM_EDGE); C(L'║', dvline, 1, LEFT_EDGE | RIGHT_EDGE); CC(L'╞', vline(c, 1); half_dhline(c, 1, true, TOP_EDGE | BOTTOM_EDGE)); CC(L'╡', vline(c, 1); half_dhline(c, 1, false, TOP_EDGE | BOTTOM_EDGE)); CC(L'╥', hline(c, 1); half_dvline(c, 1, true, LEFT_EDGE | RIGHT_EDGE)); CC(L'╨', hline(c, 1); half_dvline(c, 1, false, LEFT_EDGE | RIGHT_EDGE)); CC(L'╪', vline(c, 1); dhline(c, 1, TOP_EDGE | BOTTOM_EDGE)); CC(L'╫', hline(c, 1), dvline(c, 1, LEFT_EDGE | RIGHT_EDGE)); CC(L'╬', inner_corner(c, 1, TOP_LEFT); inner_corner(c, 1, TOP_RIGHT); inner_corner(c, 1, BOTTOM_LEFT); inner_corner(c, 1, BOTTOM_RIGHT)); CC(L'╠', inner_corner(c, 1, TOP_RIGHT); inner_corner(c, 1, BOTTOM_RIGHT); dvline(c, 1, LEFT_EDGE)); CC(L'╣', inner_corner(c, 1, TOP_LEFT); inner_corner(c, 1, BOTTOM_LEFT); dvline(c, 1, RIGHT_EDGE)); CC(L'╦', inner_corner(c, 1, BOTTOM_LEFT); inner_corner(c, 1, BOTTOM_RIGHT); dhline(c, 1, TOP_EDGE)); CC(L'╩', inner_corner(c, 1, TOP_LEFT); inner_corner(c, 1, TOP_RIGHT); dhline(c, 1, BOTTOM_EDGE)); #define EH(ch, ...) C(ch, eight_block, true, __VA_ARGS__, -1); EH(L'▔', 0); EH(L'▀', 0, 1, 2, 3); EH(L'▁', 7); EH(L'▂', 6, 7); EH(L'▃', 5, 6, 7); EH(L'▄', 4, 5, 6, 7); EH(L'▅', 3, 4, 5, 6, 7); EH(L'▆', 2, 3, 4, 5, 6, 7); EH(L'▇', 1, 2, 3, 4, 5, 6, 7); #undef EH #define EV(ch, ...) C(ch, eight_block, false, __VA_ARGS__, -1); EV(L'▉', 0, 1, 2, 3, 4, 5, 6); EV(L'▊', 0, 1, 2, 3, 4, 5); EV(L'▋', 0, 1, 2, 3, 4); EV(L'▌', 0, 1, 2, 3); EV(L'▍', 0, 1, 2); EV(L'▎', 0, 1); EV(L'▏', 0); EV(L'▕', 7); EV(L'▐', 4, 5, 6, 7); #undef EV #define SH(ch, ...) C(ch, shade, (Shade){ __VA_ARGS__ }); SH(L'░', .xnum=12, .light=true); SH(L'▒', .xnum=12); SH(L'▓', .xnum=12, .light=true, .invert=true); SH(L'🮌', .xnum=12, .which_half=LEFT_EDGE); SH(L'🮍', .xnum=12, .which_half=RIGHT_EDGE); SH(L'🮎', .xnum=12, .which_half=TOP_EDGE); SH(L'🮏', .xnum=12, .which_half=BOTTOM_EDGE); SH(L'🮐', .xnum=12, .invert=true); SH(L'🮑', .xnum=12, .invert=true, .fill_blank=true, .which_half=BOTTOM_EDGE); SH(L'🮒', .xnum=12, .invert=true, .fill_blank=true, .which_half=TOP_EDGE); SH(L'🮓', .xnum=12, .invert=true, .fill_blank=true, .which_half=RIGHT_EDGE); SH(L'🮔', .xnum=12, .invert=true, .fill_blank=true, .which_half=LEFT_EDGE); SH(L'🮕', .xnum=4, .ynum=4); SH(L'🮖', .xnum=4, .ynum=4, .invert=true); SH(L'🮗', .xnum=1, .ynum=4, .invert=true); #define M(ch, corner) SB(ch, corner_triangle(c, corner)); \ memcpy(ss.mask, canvas.mask, sizeof(canvas.mask[0]) * canvas.width * canvas.height); \ fill_canvas(&canvas, 0); shade(&canvas, (Shade){.xnum=12}); \ apply_mask(&canvas, ss.mask); break; M(L'🮜', TOP_LEFT); M(L'🮝', TOP_RIGHT); M(L'🮞', BOTTOM_RIGHT); M(L'🮟', BOTTOM_LEFT); #undef M #undef SH S(L'🮘', cross_shade, false); S(L'🮙', cross_shade, true); C(L'▖', quad, BOTTOM_LEFT); C(L'▗', quad, BOTTOM_RIGHT); C(L'▘', quad, TOP_LEFT); C(L'▝', quad, TOP_RIGHT); C(L'▙', quads, TOP_LEFT, BOTTOM_LEFT, BOTTOM_RIGHT, 0); C(L'▚', quads, TOP_LEFT, BOTTOM_RIGHT, 0); C(L'▛', quads, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, 0); C(L'▜', quads, TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, 0); C(L'▞', quads, TOP_RIGHT, BOTTOM_LEFT, 0); C(L'▟', quads, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, 0); S(L'🬼', smooth_mosaic, true, 0, 2. / 3, 0.5, 1); S(L'🬽', smooth_mosaic, true, 0, 2. / 3, 1, 1); S(L'🬾', smooth_mosaic, true, 0, 1. / 3, 0.5, 1); S(L'🬿', smooth_mosaic, true, 0, 1. / 3, 1, 1); S(L'🭀', smooth_mosaic, true, 0, 0, 0.5, 1); S(L'🭁', smooth_mosaic, true, 0, 1. / 3, 0.5, 0); S(L'🭂', smooth_mosaic, true, 0, 1. / 3, 1, 0); S(L'🭃', smooth_mosaic, true, 0, 2. / 3, 0.5, 0); S(L'🭄', smooth_mosaic, true, 0, 2. / 3, 1, 0); S(L'🭅', smooth_mosaic, true, 0, 1, 0.5, 0); S(L'🭆', smooth_mosaic, true, 0, 2. / 3, 1, 1. / 3); S(L'🭇', smooth_mosaic, true, 0.5, 1, 1, 2. / 3); S(L'🭈', smooth_mosaic, true, 0, 1, 1, 2. / 3); S(L'🭉', smooth_mosaic, true, 0.5, 1, 1, 1. / 3); S(L'🭊', smooth_mosaic, true, 0, 1, 1, 1. / 3); S(L'🭋', smooth_mosaic, true, 0.5, 1, 1, 0); S(L'🭌', smooth_mosaic, true, 0.5, 0, 1, 1. / 3); S(L'🭍', smooth_mosaic, true, 0, 0, 1, 1. / 3); S(L'🭎', smooth_mosaic, true, 0.5, 0, 1, 2. / 3); S(L'🭏', smooth_mosaic, true, 0, 0, 1, 2. / 3); S(L'🭐', smooth_mosaic, true, 0.5, 0, 1, 1); S(L'🭑', smooth_mosaic, true, 0, 1. / 3, 1, 2. / 3); S(L'🭒', smooth_mosaic, false, 0, 2. / 3, 0.5, 1); S(L'🭓', smooth_mosaic, false, 0, 2. / 3, 1, 1); S(L'🭔', smooth_mosaic, false, 0, 1. / 3, 0.5, 1); S(L'🭕', smooth_mosaic, false, 0, 1. / 3, 1, 1); S(L'🭖', smooth_mosaic, false, 0, 0, 0.5, 1); S(L'🭗', smooth_mosaic, false, 0, 1. / 3, 0.5, 0); S(L'🭘', smooth_mosaic, false, 0, 1. / 3, 1, 0); S(L'🭙', smooth_mosaic, false, 0, 2. / 3, 0.5, 0); S(L'🭚', smooth_mosaic, false, 0, 2. / 3, 1, 0); S(L'🭛', smooth_mosaic, false, 0, 1, 0.5, 0); S(L'🭜', smooth_mosaic, false, 0, 2. / 3, 1, 1. / 3); S(L'🭝', smooth_mosaic, false, 0.5, 1, 1, 2. / 3); S(L'🭞', smooth_mosaic, false, 0, 1, 1, 2. / 3); S(L'🭟', smooth_mosaic, false, 0.5, 1, 1, 1. / 3); S(L'🭠', smooth_mosaic, false, 0, 1, 1, 1. / 3); S(L'🭡', smooth_mosaic, false, 0.5, 1, 1, 0); S(L'🭢', smooth_mosaic, false, 0.5, 0, 1, 1. / 3); S(L'🭣', smooth_mosaic, false, 0, 0, 1, 1. / 3); S(L'🭤', smooth_mosaic, false, 0.5, 0, 1, 2. / 3); S(L'🭥', smooth_mosaic, false, 0, 0, 1, 2. / 3); S(L'🭦', smooth_mosaic, false, 0.5, 0, 1, 1); S(L'🭧', smooth_mosaic, false, 0, 1. / 3, 1, 2. / 3); S(L'🭨', half_triangle, LEFT_EDGE, true); S(L'🭩', half_triangle, TOP_EDGE, true); S(L'🭪', half_triangle, RIGHT_EDGE, true); S(L'🭫', half_triangle, BOTTOM_EDGE, true); S(L'🭬', half_triangle, LEFT_EDGE, false); SS(L'🮛', half_triangle(c, LEFT_EDGE, false), half_triangle(c, RIGHT_EDGE, false)); S(L'🭭', half_triangle, TOP_EDGE, false); S(L'🭮', half_triangle, RIGHT_EDGE, false); S(L'🭯', half_triangle, BOTTOM_EDGE, false); SS(L'🮚', half_triangle(c, BOTTOM_EDGE, false), half_triangle(c, TOP_EDGE, false)); CC(L'🭼', eight_bar(c, 0, false); eight_bar(c, 7, true)); CC(L'🭽', eight_bar(c, 0, false); eight_bar(c, 0, true)); CC(L'🭾', eight_bar(c, 7, false); eight_bar(c, 0, true)); CC(L'🭿', eight_bar(c, 7, false); eight_bar(c, 7, true)); CC(L'🮀', eight_bar(c, 0, true); eight_bar(c, 7, true)); CC(L'🮁', eight_bar(c, 0, true); eight_bar(c, 2, true); eight_bar(c, 4, true); eight_bar(c, 7, true)); C(L'🮂', eight_block, true, 0, 1, -1); C(L'🮃', eight_block, true, 0, 1, 2, -1); C(L'🮄', eight_block, true, 0, 1, 2, 3, 4, -1); C(L'🮅', eight_block, true, 0, 1, 2, 3, 4, 5, -1); C(L'🮆', eight_block, true, 0, 1, 2, 3, 4, 5, 6, -1); C(L'🮇', eight_block, false, 6, 7, -1); C(L'🮈', eight_block, false, 5, 6, 7, -1); C(L'🮉', eight_block, false, 3, 4, 5, 6, 7, -1); C(L'🮊', eight_block, false, 2, 3, 4, 5, 6, 7, -1); C(L'🮋', eight_block, false, 1, 2, 3, 4, 5, 6, 7, -1); S(L'🮠', mid_lines, 1, TOP_LEFT, 0); S(L'🮡', mid_lines, 1, TOP_RIGHT, 0); S(L'🮢', mid_lines, 1, BOTTOM_LEFT, 0); S(L'🮣', mid_lines, 1, BOTTOM_RIGHT, 0); S(L'🮤', mid_lines, 1, TOP_LEFT, BOTTOM_LEFT, 0); S(L'🮥', mid_lines, 1, TOP_RIGHT, BOTTOM_RIGHT, 0); S(L'🮦', mid_lines, 1, BOTTOM_RIGHT, BOTTOM_LEFT, 0); S(L'🮧', mid_lines, 1, TOP_RIGHT, TOP_LEFT, 0); S(L'🮨', mid_lines, 1, BOTTOM_RIGHT, TOP_LEFT, 0); S(L'🮩', mid_lines, 1, BOTTOM_LEFT, TOP_RIGHT, 0); S(L'🮪', mid_lines, 1, BOTTOM_LEFT, TOP_RIGHT, BOTTOM_RIGHT, 0); S(L'🮫', mid_lines, 1, BOTTOM_LEFT, TOP_LEFT, BOTTOM_RIGHT, 0); S(L'🮬', mid_lines, 1, TOP_RIGHT, TOP_LEFT, BOTTOM_RIGHT, 0); S(L'🮭', mid_lines, 1, TOP_RIGHT, TOP_LEFT, BOTTOM_LEFT, 0); S(L'🮮', mid_lines, 1, TOP_RIGHT, BOTTOM_RIGHT, TOP_LEFT, BOTTOM_LEFT, 0); C(L'', hline, 1); C(L'', vline, 1); C(L'', fading_hline, 1, 4, RIGHT_EDGE); C(L'', fading_hline, 1, 4, LEFT_EDGE); C(L'', fading_vline, 1, 5, BOTTOM_EDGE); C(L'', fading_vline, 1, 5, TOP_EDGE); S(L'', rounded_corner, 1, TOP_LEFT); S(L'', rounded_corner, 1, TOP_RIGHT); S(L'', rounded_corner, 1, BOTTOM_LEFT); S(L'', rounded_corner, 1, BOTTOM_RIGHT); SS(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT)); SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT)); SS(L'', rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, TOP_LEFT)); SS(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_RIGHT)); SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_RIGHT)); SS(L'', rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_RIGHT)); SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_RIGHT)); SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_LEFT)); SS(L'', rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, TOP_RIGHT)); SS(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_RIGHT)); SS(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT)); SS(L'', rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT)); SS(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT)); SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, TOP_RIGHT)); SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_RIGHT)); SS(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, TOP_LEFT)); SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT)); SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_LEFT)); SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT)); SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_LEFT)); #define P(ch, lines) S(ch, commit, lines, true); S(ch+1, commit, lines, false); P(L'', 0); P(L'', RIGHT_EDGE); P(L'', LEFT_EDGE); P(L'', LEFT_EDGE | RIGHT_EDGE); P(L'', BOTTOM_EDGE); P(L'', TOP_EDGE); P(L'', BOTTOM_EDGE | TOP_EDGE); P(L'', RIGHT_EDGE | BOTTOM_EDGE); P(L'', LEFT_EDGE | BOTTOM_EDGE); P(L'', RIGHT_EDGE | TOP_EDGE); P(L'', LEFT_EDGE | TOP_EDGE); P(L'', TOP_EDGE | BOTTOM_EDGE | RIGHT_EDGE); P(L'', TOP_EDGE | BOTTOM_EDGE | LEFT_EDGE); P(L'', LEFT_EDGE | RIGHT_EDGE | BOTTOM_EDGE); P(L'', LEFT_EDGE | RIGHT_EDGE | TOP_EDGE); P(L'', LEFT_EDGE | RIGHT_EDGE | TOP_EDGE | BOTTOM_EDGE); #undef P #define Q(ch, which) C(ch, corner, t, t, which); C(ch + 1, corner, f, t, which); C(ch + 2, corner, t, f, which); C(ch + 3, corner, f, f, which); Q(L'┌', BOTTOM_RIGHT); Q(L'┐', BOTTOM_LEFT); Q(L'└', TOP_RIGHT); Q(L'┘', TOP_LEFT); #undef Q S(L'╭', rounded_corner, 1, TOP_LEFT); S(L'╮', rounded_corner, 1, TOP_RIGHT); S(L'╰', rounded_corner, 1, BOTTOM_LEFT); S(L'╯', rounded_corner, 1, BOTTOM_RIGHT); case L'┼' ... L'┼' + 15: cross(c, ch - L'┼'); break; #define T(q, func) case q ... q + 7: func(c, q, ch - q); break; T(L'├', vert_t); T(L'┤', vert_t); T(L'┬', horz_t); T(L'┴', horz_t); #undef T C(L'╒', dvcorner, 1, TOP_LEFT); C(L'╕', dvcorner, 1, TOP_RIGHT); C(L'╘', dvcorner, 1, BOTTOM_LEFT); C(L'╛', dvcorner, 1, BOTTOM_RIGHT); C(L'╓', dhcorner, 1, TOP_LEFT); C(L'╖', dhcorner, 1, TOP_RIGHT); C(L'╙', dhcorner, 1, BOTTOM_LEFT); C(L'╜', dhcorner, 1, BOTTOM_RIGHT); C(L'╔', dcorner, 1, TOP_LEFT); C(L'╗', dcorner, 1, TOP_RIGHT); C(L'╚', dcorner, 1, BOTTOM_LEFT); C(L'╝', dcorner, 1, BOTTOM_RIGHT); C(L'╟', dpip, 1, RIGHT_EDGE); C(L'╢', dpip, 1, LEFT_EDGE); C(L'╤', dpip, 1, BOTTOM_EDGE); C(L'╧', dpip, 1, TOP_EDGE); case 0x2800 ... 0x2800 + 255: braille(c, ch - 0x2800); break; case 0x1fb00 ... 0x1fb00 + 19: sextant(c, ch - 0x1fb00 + 1); break; case 0x1fb14 ... 0x1fb14 + 19: sextant(c, ch - 0x1fb00 + 2); break; case 0x1fb28 ... 0x1fb28 + 19: sextant(c, ch - 0x1fb00 + 3); break; case 0x1fb70 ... 0x1fb70 + 5: eight_bar(c, ch - 0x1fb6f, false); break; case 0x1fb76 ... 0x1fb76 + 5: eight_bar(c, ch - 0x1fb75, true); break; case 0x1fbe6: octant(c, 0xe6); break; case 0x1fbe7: octant(c, 0xe7); break; case 0x1cd00 ... 0x1cde5: octant(c, ch - 0x1cd00); break; } free(canvas.holes); free(canvas.y_limits); free(ss.holes); free(ss.y_limits); END_ALLOW_CASE_RANGE #undef CC #undef SS #undef C #undef S #undef SB #undef t #undef f } kitty-0.41.1/kitty/decorations.h0000664000175000017510000000230114773370543016132 0ustar nileshnilesh/* * decorations.h * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" typedef struct DecorationGeometry { uint32_t top, height; } DecorationGeometry; DecorationGeometry add_straight_underline(uint8_t *buf, FontCellMetrics fcm); DecorationGeometry add_double_underline(uint8_t *buf, FontCellMetrics fcm); DecorationGeometry add_dotted_underline(uint8_t *buf, FontCellMetrics fcm); DecorationGeometry add_dashed_underline(uint8_t *buf, FontCellMetrics fcm); DecorationGeometry add_curl_underline(uint8_t *buf, FontCellMetrics fcm); DecorationGeometry add_strikethrough(uint8_t *buf, FontCellMetrics fcm); DecorationGeometry add_missing_glyph(uint8_t *buf, FontCellMetrics fcm); DecorationGeometry add_beam_cursor(uint8_t *buf, FontCellMetrics fcm, double dpi_x); DecorationGeometry add_underline_cursor(uint8_t *buf, FontCellMetrics fcm, double dpi_y); DecorationGeometry add_hollow_cursor(uint8_t *buf, FontCellMetrics fcm, double dpi_x, double dpi_y); void render_box_char(char_type ch, uint8_t *buf, unsigned width, unsigned height, double dpi_x, double dpi_y, double scale); #define SUPERSAMPLE_FACTOR 4u kitty-0.41.1/kitty/desktop.c0000664000175000017510000002452014773370543015273 0ustar nileshnilesh/* * desktop.c * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "state.h" #include "safe-wrappers.h" #include "cleanup.h" #include "loop-utils.h" #include "threading.h" #include #define FUNC(name, restype, ...) typedef restype (*name##_func)(__VA_ARGS__); static name##_func name = NULL #define LOAD_FUNC(handle, name) {\ *(void **) (&name) = dlsym(handle, #name); \ if (!name) { \ const char* error = dlerror(); \ if (error != NULL) { \ PyErr_Format(PyExc_OSError, "Failed to load the function %s with error: %s", #name, error); dlclose(handle); handle = NULL; return NULL; \ } \ } \ } FUNC(sn_display_new, void*, void*, void*, void*); FUNC(sn_launchee_context_new_from_environment, void*, void*, int); FUNC(sn_launchee_context_new, void*, void*, int, const char*); FUNC(sn_display_unref, void, void*); FUNC(sn_launchee_context_setup_window, void, void*, int32_t); FUNC(sn_launchee_context_complete, void, void*); FUNC(sn_launchee_context_unref, void, void*); static void* libsn_handle = NULL; static PyObject* init_x11_startup_notification(PyObject UNUSED *self, PyObject *args) { static bool done = false; if (!done) { done = true; const char* libnames[] = { #if defined(_KITTY_STARTUP_NOTIFICATION_LIBRARY) _KITTY_STARTUP_NOTIFICATION_LIBRARY, #else "libstartup-notification-1.so", // some installs are missing the .so symlink, so try the full name "libstartup-notification-1.so.0", "libstartup-notification-1.so.0.0.0", #endif NULL }; for (int i = 0; libnames[i]; i++) { libsn_handle = dlopen(libnames[i], RTLD_LAZY); if (libsn_handle) break; } if (libsn_handle == NULL) { PyErr_Format(PyExc_OSError, "Failed to load %s with error: %s", libnames[0], dlerror()); return NULL; } dlerror(); /* Clear any existing error */ #define F(name) LOAD_FUNC(libsn_handle, name) F(sn_display_new); F(sn_launchee_context_new_from_environment); F(sn_launchee_context_new); F(sn_display_unref); F(sn_launchee_context_setup_window); F(sn_launchee_context_complete); F(sn_launchee_context_unref); #undef F } int window_id; PyObject *dp; char *startup_id = NULL; if (!PyArg_ParseTuple(args, "O!i|z", &PyLong_Type, &dp, &window_id, &startup_id)) return NULL; void* display = PyLong_AsVoidPtr(dp); void* sn_display = sn_display_new(display, NULL, NULL); if (!sn_display) { PyErr_SetString(PyExc_OSError, "Failed to create SnDisplay"); return NULL; } void *ctx = startup_id ? sn_launchee_context_new(sn_display, 0, startup_id) : sn_launchee_context_new_from_environment(sn_display, 0); sn_display_unref(sn_display); if (!ctx) { PyErr_SetString(PyExc_OSError, "Failed to create startup-notification context"); return NULL; } sn_launchee_context_setup_window(ctx, window_id); return PyLong_FromVoidPtr(ctx); } static PyObject* end_x11_startup_notification(PyObject UNUSED *self, PyObject *args) { if (!libsn_handle) Py_RETURN_NONE; PyObject *dp; if (!PyArg_ParseTuple(args, "O!", &PyLong_Type, &dp)) return NULL; void *ctx = PyLong_AsVoidPtr(dp); sn_launchee_context_complete(ctx); sn_launchee_context_unref(ctx); Py_RETURN_NONE; } static void* libcanberra_handle = NULL; static void *canberra_ctx = NULL; FUNC(ca_context_create, int, void**); FUNC(ca_context_destroy, int, void*); FUNC(ca_context_play_full, int, void*, uint32_t, void*, void(*)(void), void*); typedef int (*ca_context_play_func)(void*, uint32_t, ...); static ca_context_play_func ca_context_play = NULL; typedef int (*ca_context_change_props_func)(void*, ...); static ca_context_change_props_func ca_context_change_props = NULL; static PyObject* load_libcanberra_functions(void) { LOAD_FUNC(libcanberra_handle, ca_context_create); LOAD_FUNC(libcanberra_handle, ca_context_play); LOAD_FUNC(libcanberra_handle, ca_context_play_full); LOAD_FUNC(libcanberra_handle, ca_context_destroy); LOAD_FUNC(libcanberra_handle, ca_context_change_props); return NULL; } static void load_libcanberra(void) { static bool done = false; if (done) return; done = true; const char* libnames[] = { #if defined(_KITTY_CANBERRA_LIBRARY) _KITTY_CANBERRA_LIBRARY, #else "libcanberra.so", // some installs are missing the .so symlink, so try the full name "libcanberra.so.0", "libcanberra.so.0.2.5", #endif NULL }; for (int i = 0; libnames[i]; i++) { libcanberra_handle = dlopen(libnames[i], RTLD_LAZY); if (libcanberra_handle) break; } if (libcanberra_handle == NULL) { fprintf(stderr, "Failed to load %s, cannot play beep sound, with error: %s\n", libnames[0], dlerror()); return; } load_libcanberra_functions(); if (PyErr_Occurred()) { PyErr_Print(); dlclose(libcanberra_handle); libcanberra_handle = NULL; return; } if (ca_context_create(&canberra_ctx) != 0) { fprintf(stderr, "Failed to create libcanberra context, cannot play beep sound\n"); canberra_ctx = NULL; dlclose(libcanberra_handle); libcanberra_handle = NULL; } else { if (ca_context_change_props(canberra_ctx, "application.name", "kitty Terminal", "application.id", "kitty", NULL) != 0) { fprintf(stderr, "Failed to set basic properties on libcanberra context, cannot play beep sound\n"); } } } typedef struct { char *which_sound, *event_id, *media_role, *theme_name; bool is_path; } CanberraEvent; static pthread_t canberra_thread; static int canberra_pipe_r = -1, canberra_pipe_w = -1; static pthread_mutex_t canberra_lock; static CanberraEvent current_sound = {0}; static void free_canberra_event_fields(CanberraEvent *e) { free(e->which_sound); e->which_sound = NULL; free(e->event_id); e->event_id = NULL; free(e->media_role); e->media_role = NULL; free(e->theme_name); e->theme_name = NULL; } static void play_current_sound(void) { CanberraEvent e; pthread_mutex_lock(&canberra_lock); e = current_sound; current_sound = (const CanberraEvent){ 0 }; pthread_mutex_unlock(&canberra_lock); if (e.which_sound && e.event_id && e.media_role) { const char *which_type = e.is_path ? "media.filename" : "event.id"; ca_context_play( canberra_ctx, 0, which_type, e.which_sound, "event.description", e.event_id, "media.role", e.media_role, "canberra.xdg-theme.name", e.theme_name, NULL ); free_canberra_event_fields(&e); } } static void queue_canberra_sound(const char *which_sound, const char *event_id, bool is_path, const char *media_role, const char *theme_name) { pthread_mutex_lock(&canberra_lock); current_sound.which_sound = strdup(which_sound); current_sound.event_id = strdup(event_id); current_sound.media_role = strdup(media_role); current_sound.is_path = is_path; current_sound.theme_name = theme_name ? strdup(theme_name) : NULL; pthread_mutex_unlock(&canberra_lock); while (true) { ssize_t ret = write(canberra_pipe_w, "w", 1); if (ret < 0) { if (errno == EINTR) continue; log_error("Failed to write to canberra wakeup fd with error: %s", strerror(errno)); } break; } } static void* canberra_play_loop(void *x UNUSED) { // canberra hangs on misconfigured systems. We dont want kitty to hang so use a thread. // For example: https://github.com/kovidgoyal/kitty/issues/5646 static char buf[16]; set_thread_name("LinuxAudioSucks"); while (true) { int ret = read(canberra_pipe_r, buf, sizeof(buf)); if (ret < 0) { if (errno == EINTR || errno == EAGAIN) continue; break; } play_current_sound(); } safe_close(canberra_pipe_r, __FILE__, __LINE__); return NULL; } void play_canberra_sound(const char *which_sound, const char *event_id, bool is_path, const char *media_role, const char *theme_name) { load_libcanberra(); if (libcanberra_handle == NULL || canberra_ctx == NULL) return; int ret; if (canberra_pipe_r == -1) { int fds[2]; if ((ret = pthread_mutex_init(&canberra_lock, NULL)) != 0) return; if (!self_pipe(fds, false)) return; canberra_pipe_r = fds[0]; canberra_pipe_w = fds[1]; int flags = fcntl(canberra_pipe_w, F_GETFL); fcntl(canberra_pipe_w, F_SETFL, flags | O_NONBLOCK); if ((ret = pthread_create(&canberra_thread, NULL, canberra_play_loop, NULL)) != 0) return; } queue_canberra_sound(which_sound, event_id, is_path, media_role, theme_name); } static PyObject* play_desktop_sound_async(PyObject *self UNUSED, PyObject *args, PyObject *kw) { const char *which, *event_id = "test sound"; const char *theme_name = OPT(bell_theme); if (!theme_name || !theme_name[0]) theme_name = "__custom"; int is_path = 0; static const char* kwlist[] = {"sound_name", "event_id", "is_path", "theme_name", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kw, "s|sps", (char**)kwlist, &which, &event_id, &is_path, &theme_name)) return NULL; play_canberra_sound(which, event_id, is_path, "event", theme_name); Py_RETURN_NONE; } static void finalize(void) { if (libsn_handle) dlclose(libsn_handle); libsn_handle = NULL; if (canberra_pipe_w > -1) { pthread_mutex_lock(&canberra_lock); free_canberra_event_fields(¤t_sound); pthread_mutex_unlock(&canberra_lock); safe_close(canberra_pipe_w, __FILE__, __LINE__); } if (canberra_ctx) ca_context_destroy(canberra_ctx); canberra_ctx = NULL; if (libcanberra_handle) dlclose(libcanberra_handle); } static PyMethodDef module_methods[] = { METHODB(init_x11_startup_notification, METH_VARARGS), METHODB(end_x11_startup_notification, METH_VARARGS), {"play_desktop_sound_async", (PyCFunction)(void(*)(void))play_desktop_sound_async, METH_VARARGS | METH_KEYWORDS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_desktop(PyObject *m) { if (PyModule_AddFunctions(m, module_methods) != 0) return false; register_at_exit_cleanup_func(DESKTOP_CLEANUP_FUNC, finalize); return true; } kitty-0.41.1/kitty/disk-cache.c0000664000175000017510000007556414773370543015633 0ustar nileshnilesh/* * disk-cache.c * Copyright (C) 2020 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #define MAX_KEY_SIZE 16u #include "disk-cache.h" #include "safe-wrappers.h" #include "simd-string.h" #include "loop-utils.h" #include "fast-file-copy.h" #include "threading.h" #include "cross-platform-random.h" #include #include #include #include #include typedef struct CacheKey { void *hash_key; unsigned short hash_keylen; } CacheKey; typedef struct { uint8_t *data; size_t data_sz; bool written_to_disk; off_t pos_in_cache_file; uint8_t encryption_key[64]; } CacheValue; #define NAME cache_map #define KEY_TY CacheKey #define VAL_TY CacheValue* static uint64_t key_hash(KEY_TY k); #define HASH_FN key_hash static bool keys_are_equal(CacheKey a, CacheKey b) { return a.hash_keylen == b.hash_keylen && memcmp(a.hash_key, b.hash_key, a.hash_keylen) == 0; } #define CMPR_FN keys_are_equal static void free_cache_value(CacheValue *cv) { free(cv->data); cv->data = NULL; free(cv); } static void free_cache_key(CacheKey cv) { free(cv.hash_key); cv.hash_key = NULL; } #define KEY_DTOR_FN free_cache_key #define VAL_DTOR_FN free_cache_value #include "kitty-verstable.h" static uint64_t key_hash(CacheKey k) { return vt_hash_bytes(k.hash_key, k.hash_keylen); } #define cache_map_for_loop(i) vt_create_for_loop(cache_map_itr, i, &self->map) typedef struct Hole { off_t pos, size; } Hole; #define NAME hole_pos_map #define KEY_TY off_t #define VAL_TY off_t #include "kitty-verstable.h" #define hole_pos_map_for_loop(i) vt_create_for_loop(hole_pos_map_itr, i, &self->holes.pos_map) typedef struct PosList { size_t count, capacity; off_t *positions; } PosList; #define NAME hole_size_map #define KEY_TY off_t #define VAL_TY PosList static void free_pos_list(PosList p) { free(p.positions); } #define VAL_DTOR_FN free_pos_list #include "kitty-verstable.h" #define hole_size_map_for_loop(i) vt_create_for_loop(hole_size_map_itr, i, &holes->size_map) typedef struct Holes { hole_pos_map pos_map, end_pos_map; hole_size_map size_map; off_t largest_hole_size; } Holes; typedef struct { PyObject_HEAD char *cache_dir; int cache_file_fd; Py_ssize_t small_hole_threshold; unsigned int defrag_factor; pthread_mutex_t lock; pthread_t write_thread; bool thread_started, lock_inited, loop_data_inited, shutting_down, fully_initialized; LoopData loop_data; struct { CacheValue val; CacheKey key; } currently_writing; cache_map map; Holes holes; unsigned long long total_size; } DiskCache; #define mutex(op) pthread_mutex_##op(&self->lock) static PyObject* new_diskcache_object(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) { DiskCache *self; self = (DiskCache*)type->tp_alloc(type, 0); if (self) { self->cache_file_fd = -1; self->small_hole_threshold = 512; self->defrag_factor = 2; } return (PyObject*) self; } static int open_cache_file_without_tmpfile(const char *cache_path) { int fd = -1; static const char template[] = "%s/disk-cache-XXXXXXXXXXXX"; const size_t sz = strlen(cache_path) + sizeof(template) + 4; RAII_ALLOC(char, buf, calloc(1, sz)); if (!buf) { errno = ENOMEM; return -1; } snprintf(buf, sz - 1, template, cache_path); while (fd < 0) { fd = mkostemp(buf, O_CLOEXEC); if (fd > -1 || errno != EINTR) break; } if (fd > -1) unlink(buf); return fd; } static int open_cache_file(const char *cache_path) { int fd = -1; #ifdef O_TMPFILE while (fd < 0) { fd = safe_open(cache_path, O_TMPFILE | O_CLOEXEC | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); if (fd > -1 || errno != EINTR) break; } if (fd == -1) fd = open_cache_file_without_tmpfile(cache_path); #else fd = open_cache_file_without_tmpfile(cache_path); #endif return fd; } // Write loop {{{ static off_t size_of_cache_file(DiskCache *self) { off_t pos = lseek(self->cache_file_fd, 0, SEEK_CUR); off_t ans = lseek(self->cache_file_fd, 0, SEEK_END); lseek(self->cache_file_fd, pos, SEEK_SET); return ans; } size_t disk_cache_size_on_disk(PyObject *self) { if (((DiskCache*)self)->cache_file_fd > -1) { off_t ans = size_of_cache_file((DiskCache*)self); return MAX(0, ans); } return 0; } typedef struct { CacheKey key; off_t old_offset, new_offset; size_t data_sz; } DefragEntry; static CacheKey keydup(CacheKey k) { CacheKey ans = {.hash_key=malloc(k.hash_keylen), .hash_keylen=k.hash_keylen}; if (ans.hash_key) memcpy(ans.hash_key, k.hash_key, k.hash_keylen); return ans; } static void cleanup_holes(Holes *holes) { vt_cleanup(&holes->size_map); vt_cleanup(&holes->pos_map); vt_cleanup(&holes->end_pos_map); holes->largest_hole_size = 0; } static void defrag(DiskCache *self) { int new_cache_file = -1; RAII_ALLOC(DefragEntry, defrag_entries, NULL); RAII_FreeFastFileCopyBuffer(fcb); bool lock_released = false, ok = false; off_t size_on_disk = size_of_cache_file(self); if (size_on_disk <= 0) goto cleanup; size_t num_entries = vt_size(&self->map); if (!num_entries) goto cleanup; new_cache_file = open_cache_file(self->cache_dir); if (new_cache_file < 0) { perror("Failed to open second file for defrag of disk cache"); goto cleanup; } defrag_entries = calloc(num_entries, sizeof(DefragEntry)); if (!defrag_entries) goto cleanup; size_t total_data_size = 0, num_entries_to_defrag = 0; cache_map_for_loop(i) { CacheValue *s = i.data->val; if (s->pos_in_cache_file > -1 && s->data_sz) { total_data_size += s->data_sz; DefragEntry *e = defrag_entries + num_entries_to_defrag++; e->old_offset = s->pos_in_cache_file; e->data_sz = s->data_sz; e->key = keydup(i.data->key); // have to dup the key as we release the mutex and another thread might free the underlying key. if (!e->key.hash_key) { fprintf(stderr, "Failed to allocate space for keydup in defrag\n"); goto cleanup; } } } if (ftruncate(new_cache_file, total_data_size) != 0) { perror("Failed to allocate space for new disk cache file during defrag"); goto cleanup; } lseek(new_cache_file, 0, SEEK_SET); mutex(unlock); lock_released = true; off_t current_pos = 0; for (size_t i = 0; i < num_entries_to_defrag; i++) { DefragEntry *e = defrag_entries + i; if (!copy_between_files(self->cache_file_fd, new_cache_file, e->old_offset, e->data_sz, &fcb)) { perror("Failed to copy data to new disk cache file during defrag"); goto cleanup; } e->new_offset = current_pos; current_pos += e->data_sz; } ok = true; cleanup: if (lock_released) mutex(lock); if (ok) { cleanup_holes(&self->holes); safe_close(self->cache_file_fd, __FILE__, __LINE__); self->cache_file_fd = new_cache_file; new_cache_file = -1; for (size_t i = 0; i < num_entries_to_defrag; i++) { DefragEntry *e = defrag_entries + i; cache_map_itr i = vt_get(&self->map, e->key); if (!vt_is_end(i)) i.data->val->pos_in_cache_file = e->new_offset; free(e->key.hash_key); } } if (new_cache_file > -1) safe_close(new_cache_file, __FILE__, __LINE__); } static void append_position(PosList *p, off_t pos) { ensure_space_for(p, positions, off_t, p->count + 1, capacity, 8, false); p->positions[p->count++] = pos; } static void add_hole_to_maps(Holes *holes, Hole h) { if (vt_is_end(vt_insert(&holes->pos_map, h.pos, h.size))) fatal("Out of memory"); if (vt_is_end(vt_insert(&holes->end_pos_map, h.pos + h.size, h.size))) fatal("Out of memory"); hole_size_map_itr i = vt_get_or_insert(&holes->size_map, h.size, (PosList){0}); if (vt_is_end(i)) fatal("Out of memory"); append_position(&(i.data->val), h.pos); holes->largest_hole_size = MAX(h.size, holes->largest_hole_size); } static void update_largest_hole_size(Holes *holes) { holes->largest_hole_size = 0; hole_size_map_for_loop(i) { if (i.data->key > holes->largest_hole_size) holes->largest_hole_size = i.data->key; } } static void remove_hole_from_maps_itr(Holes *holes, Hole h, hole_size_map_itr i, size_t pos_in_sizes_array) { vt_erase(&holes->pos_map, h.pos); vt_erase(&holes->end_pos_map, h.pos + h.size); if (i.data->val.count <= 1) { vt_erase_itr(&holes->size_map, i); if (h.size > holes->largest_hole_size) update_largest_hole_size(holes); } else remove_i_from_array(i.data->val.positions, pos_in_sizes_array, i.data->val.count); } static void remove_hole_from_maps(Holes *holes, Hole h) { hole_size_map_itr i = vt_get(&holes->size_map, h.size); for (size_t x = 0; x < i.data->val.count; x++) { if (i.data->val.positions[x] == h.pos) { remove_hole_from_maps_itr(holes, h, vt_get(&holes->size_map, h.size), x); return; } } } static bool find_hole_to_use(DiskCache *self, const off_t required_sz) { if (self->holes.largest_hole_size < required_sz) return false; hole_size_map_itr i = vt_get(&self->holes.size_map, required_sz); if (vt_is_end(i)) { for (i = vt_first(&self->holes.size_map); !vt_is_end(i); i = vt_next(i)) { if (i.data->key >= required_sz) break; } } if (vt_is_end(i)) return false; Hole h = {.pos=i.data->val.positions[i.data->val.count-1], .size=i.data->key}; remove_hole_from_maps_itr(&self->holes, h, i, i.data->val.count-1); self->currently_writing.val.pos_in_cache_file = h.pos; if (required_sz < h.size) { h.pos += required_sz; h.size -= required_sz; if (h.size > self->small_hole_threshold) add_hole_to_maps(&self->holes, h); } return true; } static inline bool needs_defrag(DiskCache *self) { off_t size_on_disk = size_of_cache_file(self); return self->total_size && size_on_disk > 0 && (size_t)size_on_disk > self->total_size * self->defrag_factor; } static void add_hole(DiskCache *self, const off_t pos, const off_t size) { if (size <= self->small_hole_threshold) return; if (vt_size(&self->holes.pos_map)) { // See if we can find a neighboring hole to merge this hole into // First look for a hole after us off_t end_pos = pos + size; hole_pos_map_itr i = vt_get(&self->holes.pos_map, end_pos); Hole original_hole, new_hole; bool found = false; if (vt_is_end(i)) { // Now look for a hole before us i = vt_get(&self->holes.end_pos_map, pos); if (!vt_is_end(i)) { original_hole.pos = i.data->key - i.data->val; original_hole.size = i.data->val; new_hole.pos = original_hole.pos; new_hole.size = original_hole.size + size; found = true; } } else { original_hole.pos = i.data->key; original_hole.size = i.data->val; new_hole.pos = pos; new_hole.size = original_hole.size + size; found = true; // there could be a hole before us as well i = vt_get(&self->holes.end_pos_map, pos); if (!vt_is_end(i)) { self->holes.largest_hole_size = MAX(self->holes.largest_hole_size, new_hole.size); remove_hole_from_maps(&self->holes, original_hole); original_hole.pos = i.data->key - i.data->val; original_hole.size = i.data->val; new_hole.pos = original_hole.pos; new_hole.size += original_hole.size; } } if (found) { // prevent remove_hole_from_maps updating largest hole size self->holes.largest_hole_size = MAX(self->holes.largest_hole_size, new_hole.size); remove_hole_from_maps(&self->holes, original_hole); add_hole_to_maps(&self->holes, new_hole); return; } } Hole h = {.pos=pos, .size=size }; add_hole_to_maps(&self->holes, h); } static void remove_from_disk(DiskCache *self, CacheValue *s) { if (s->written_to_disk) { s->written_to_disk = false; if (s->data_sz && s->pos_in_cache_file > -1) { add_hole(self, s->pos_in_cache_file, s->data_sz); s->pos_in_cache_file = -1; } } } static bool find_cache_entry_to_write(DiskCache *self) { if (needs_defrag(self)) defrag(self); cache_map_for_loop(i) { CacheValue *s = i.data->val; if (!s->written_to_disk) { if (s->data) { if (self->currently_writing.val.data) free(self->currently_writing.val.data); self->currently_writing.val.data = s->data; s->data = NULL; self->currently_writing.val.data_sz = s->data_sz; self->currently_writing.val.pos_in_cache_file = -1; xor_data64(s->encryption_key, self->currently_writing.val.data, s->data_sz); self->currently_writing.key.hash_keylen = MIN(i.data->key.hash_keylen, MAX_KEY_SIZE); memcpy(self->currently_writing.key.hash_key, i.data->key.hash_key, self->currently_writing.key.hash_keylen); find_hole_to_use(self, self->currently_writing.val.data_sz); return true; } s->written_to_disk = true; s->pos_in_cache_file = 0; s->data_sz = 0; } } return false; } static bool write_dirty_entry(DiskCache *self) { size_t left = self->currently_writing.val.data_sz; uint8_t *p = self->currently_writing.val.data; if (self->currently_writing.val.pos_in_cache_file < 0) { self->currently_writing.val.pos_in_cache_file = size_of_cache_file(self); if (self->currently_writing.val.pos_in_cache_file < 0) { perror("Failed to seek in disk cache file"); return false; } } off_t offset = self->currently_writing.val.pos_in_cache_file; while (left > 0) { ssize_t n = pwrite(self->cache_file_fd, p, left, offset); if (n < 0) { if (errno == EINTR || errno == EAGAIN) continue; perror("Failed to write to disk-cache file"); self->currently_writing.val.pos_in_cache_file = -1; return false; } if (n == 0) { fprintf(stderr, "Failed to write to disk-cache file with zero return\n"); self->currently_writing.val.pos_in_cache_file = -1; return false; } left -= n; p += n; offset += n; } return true; } static void retire_currently_writing(DiskCache *self) { cache_map_itr i = vt_get(&self->map, self->currently_writing.key); if (!vt_is_end(i)) { i.data->val->written_to_disk = true; i.data->val->pos_in_cache_file = self->currently_writing.val.pos_in_cache_file; } free(self->currently_writing.val.data); self->currently_writing.val.data = NULL; self->currently_writing.val.data_sz = 0; } static void* write_loop(void *data) { DiskCache *self = (DiskCache*)data; set_thread_name("DiskCacheWrite"); struct pollfd fds[1] = {0}; fds[0].fd = self->loop_data.wakeup_read_fd; fds[0].events = POLLIN; bool found_dirty_entry = false; while (!self->shutting_down) { mutex(lock); found_dirty_entry = find_cache_entry_to_write(self); size_t count = vt_size(&self->map); mutex(unlock); if (found_dirty_entry) { write_dirty_entry(self); mutex(lock); retire_currently_writing(self); mutex(unlock); continue; } else if (!count) { mutex(lock); count = vt_size(&self->map); if (!count && self->cache_file_fd > -1) { if (ftruncate(self->cache_file_fd, 0) == 0) lseek(self->cache_file_fd, 0, SEEK_END); } mutex(unlock); } if (poll(fds, 1, -1) > 0 && fds[0].revents & POLLIN) { drain_fd(fds[0].fd); // wakeup } } return 0; } // }}} static bool ensure_state(DiskCache *self) { int ret; if (self->fully_initialized) return true; if (!self->loop_data_inited) { if (!init_loop_data(&self->loop_data, 0)) { PyErr_SetFromErrno(PyExc_OSError); return false; } self->loop_data_inited = true; } if (!self->currently_writing.key.hash_key) { self->currently_writing.key.hash_key = malloc(MAX_KEY_SIZE); if (!self->currently_writing.key.hash_key) { PyErr_NoMemory(); return false; } } if (!self->lock_inited) { if ((ret = pthread_mutex_init(&self->lock, NULL)) != 0) { PyErr_Format(PyExc_OSError, "Failed to create disk cache lock mutex: %s", strerror(ret)); return false; } self->lock_inited = true; } if (!self->thread_started) { if ((ret = pthread_create(&self->write_thread, NULL, write_loop, self)) != 0) { PyErr_Format(PyExc_OSError, "Failed to start disk cache write thread with error: %s", strerror(ret)); return false; } self->thread_started = true; } if (!self->cache_dir) { PyObject *kc = NULL, *cache_dir = NULL; kc = PyImport_ImportModule("kitty.constants"); if (kc) { cache_dir = PyObject_CallMethod(kc, "cache_dir", NULL); if (cache_dir) { if (PyUnicode_Check(cache_dir)) { self->cache_dir = strdup(PyUnicode_AsUTF8(cache_dir)); if (!self->cache_dir) PyErr_NoMemory(); } else PyErr_SetString(PyExc_TypeError, "cache_dir() did not return a string"); } } Py_CLEAR(kc); Py_CLEAR(cache_dir); if (PyErr_Occurred()) return false; } if (self->cache_file_fd < 0) { self->cache_file_fd = open_cache_file(self->cache_dir); if (self->cache_file_fd < 0) { PyErr_SetFromErrnoWithFilename(PyExc_OSError, self->cache_dir); return false; } } vt_init(&self->map); vt_init(&self->holes.pos_map); vt_init(&self->holes.size_map); vt_init(&self->holes.end_pos_map); self->fully_initialized = true; return true; } static void wakeup_write_loop(DiskCache *self) { if (self->thread_started) wakeup_loop(&self->loop_data, false, "disk_cache_write_loop"); } static void dealloc(DiskCache* self) { self->shutting_down = true; if (self->thread_started) { wakeup_write_loop(self); pthread_join(self->write_thread, NULL); self->thread_started = false; } if (self->currently_writing.key.hash_key) { free(self->currently_writing.key.hash_key); self->currently_writing.key.hash_key = NULL; } if (self->lock_inited) { pthread_mutex_destroy(&self->lock); self->lock_inited = false; } if (self->loop_data_inited) { free_loop_data(&self->loop_data); self->loop_data_inited = false; } vt_cleanup(&self->map); cleanup_holes(&self->holes); if (self->cache_file_fd > -1) { safe_close(self->cache_file_fd, __FILE__, __LINE__); self->cache_file_fd = -1; } if (self->currently_writing.val.data) free(self->currently_writing.val.data); free(self->cache_dir); self->cache_dir = NULL; Py_TYPE(self)->tp_free((PyObject*)self); } static CacheValue* create_cache_entry(void) { CacheValue *s = calloc(1, sizeof(CacheValue)); if (!s) return (CacheValue*)PyErr_NoMemory(); if (!secure_random_bytes(s->encryption_key, sizeof(s->encryption_key))) { free(s); PyErr_SetFromErrno(PyExc_OSError); return NULL; } s->pos_in_cache_file = -2; return s; } bool add_to_disk_cache(PyObject *self_, const void *key, size_t key_sz, const void *data, size_t data_sz) { DiskCache *self = (DiskCache*)self_; if (!ensure_state(self)) return false; if (key_sz > MAX_KEY_SIZE) { PyErr_SetString(PyExc_KeyError, "cache key is too long"); return false; } RAII_ALLOC(uint8_t, copied_data, malloc(data_sz)); if (!copied_data) { PyErr_NoMemory(); return false; } memcpy(copied_data, data, data_sz); CacheKey k = {.hash_key=(void*)key, .hash_keylen=key_sz}; mutex(lock); cache_map_itr i = vt_get(&self->map, k); CacheValue *s; if (vt_is_end(i)) { k.hash_key = malloc(key_sz); if (!k.hash_key) { PyErr_NoMemory(); goto end; } memcpy(k.hash_key, key, key_sz); if (!(s = create_cache_entry())) goto end; if (vt_is_end(vt_insert(&self->map, k, s))) { PyErr_NoMemory(); goto end; } } else { s = i.data->val; remove_from_disk(self, s); self->total_size -= MIN(self->total_size, s->data_sz); if (s->data) free(s->data); } s->data = copied_data; s->data_sz = data_sz; copied_data = NULL; self->total_size += s->data_sz; end: mutex(unlock); if (PyErr_Occurred()) return false; wakeup_write_loop(self); return true; } bool remove_from_disk_cache(PyObject *self_, const void *key, size_t key_sz) { DiskCache *self = (DiskCache*)self_; if (!ensure_state(self)) return false; if (key_sz > MAX_KEY_SIZE) { PyErr_SetString(PyExc_KeyError, "cache key is too long"); return false; } CacheValue *s = NULL; CacheKey k = {.hash_key=(void*)key, .hash_keylen=key_sz}; bool removed = false; mutex(lock); cache_map_itr i = vt_get(&self->map, k); if (!vt_is_end(i)) { removed = true; s = i.data->val; remove_from_disk(self, s); self->total_size = (self->total_size > s->data_sz) ? self->total_size - s->data_sz : 0; vt_erase_itr(&self->map, i); } mutex(unlock); wakeup_write_loop(self); return removed; } void clear_disk_cache(PyObject *self_) { DiskCache *self = (DiskCache*)self_; if (!ensure_state(self)) return; mutex(lock); vt_cleanup(&self->map); cleanup_holes(&self->holes); self->total_size = 0; if (self->cache_file_fd > -1) add_hole(self, 0, size_of_cache_file(self)); mutex(unlock); wakeup_write_loop(self); } static void read_from_cache_file(const DiskCache *self, off_t pos, size_t sz, void *dest) { uint8_t *p = dest; while (sz) { ssize_t n = pread(self->cache_file_fd, p, sz, pos); if (n > 0) { sz -= n; p += n; pos += n; continue; } if (n < 0) { if (errno == EINTR || errno == EAGAIN) continue; PyErr_SetFromErrnoWithFilename(PyExc_OSError, self->cache_dir); break; } if (n == 0) { PyErr_SetString(PyExc_OSError, "Disk cache file truncated"); break; } } } static void read_from_cache_entry(const DiskCache *self, const CacheValue *s, void *dest) { size_t sz = s->data_sz; off_t pos = s->pos_in_cache_file; if (pos < 0) { PyErr_SetString(PyExc_OSError, "Cache entry was not written, could not read from it"); return; } read_from_cache_file(self, pos, sz, dest); } void* read_from_disk_cache(PyObject *self_, const void *key, size_t key_sz, void*(allocator)(void*, size_t), void* allocator_data, bool store_in_ram) { DiskCache *self = (DiskCache*)self_; void *data = NULL; if (!ensure_state(self)) return data; if (key_sz > MAX_KEY_SIZE) { PyErr_SetString(PyExc_KeyError, "cache key is too long"); return data; } CacheKey k = {.hash_key=(void*)key, .hash_keylen=key_sz}; mutex(lock); cache_map_itr i = vt_get(&self->map, k); if (vt_is_end(i)) { PyErr_SetString(PyExc_KeyError, "No cached entry with specified key found"); goto end; } CacheValue *s = i.data->val; data = allocator(allocator_data, s->data_sz); if (!data) { PyErr_NoMemory(); goto end; } if (s->data) { memcpy(data, s->data, s->data_sz); } else if (self->currently_writing.val.data && self->currently_writing.key.hash_key && keys_are_equal(self->currently_writing.key, k)) { memcpy(data, self->currently_writing.val.data, s->data_sz); xor_data64(s->encryption_key, data, s->data_sz); } else { read_from_cache_entry(self, s, data); xor_data64(s->encryption_key, data, s->data_sz); } if (store_in_ram && !s->data && s->data_sz) { void *copy = malloc(s->data_sz); if (copy) { memcpy(copy, data, s->data_sz); s->data = copy; } } end: mutex(unlock); return data; } size_t disk_cache_clear_from_ram(PyObject *self_, bool(matches)(void*, void *key, unsigned keysz), void *data) { DiskCache *self = (DiskCache*)self_; size_t ans = 0; if (!ensure_state(self)) return ans; mutex(lock); cache_map_for_loop(i) { CacheValue *s = i.data->val; if (s->written_to_disk && s->data && matches(data, i.data->key.hash_key, i.data->key.hash_keylen)) { free(s->data); s->data = NULL; ans++; } } mutex(unlock); return ans; } bool disk_cache_wait_for_write(PyObject *self_, monotonic_t timeout) { DiskCache *self = (DiskCache*)self_; if (!ensure_state(self)) return false; monotonic_t end_at = monotonic() + timeout; while (!timeout || monotonic() <= end_at) { bool pending = false; mutex(lock); cache_map_for_loop(i) { if (!i.data->val->written_to_disk) { pending = true; break; } } mutex(unlock); if (!pending) return true; wakeup_write_loop(self); struct timespec a = { .tv_nsec = 10L * MONOTONIC_T_1e6 }, b; // 10ms sleep nanosleep(&a, &b); } return false; } size_t disk_cache_total_size(PyObject *self) { return ((DiskCache*)self)->total_size; } size_t disk_cache_num_cached_in_ram(PyObject *self_) { DiskCache *self = (DiskCache*)self_; unsigned long ans = 0; if (ensure_state(self)) { mutex(lock); cache_map_for_loop(i) { if (i.data->val->written_to_disk && i.data->val->data) ans++; } mutex(unlock); } return ans; } #define PYWRAP(name) static PyObject* py##name(DiskCache *self, PyObject *args) #define PA(fmt, ...) if (!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL; PYWRAP(ensure_state) { (void)args; ensure_state(self); Py_RETURN_NONE; } PYWRAP(read_from_cache_file) { Py_ssize_t pos = 0, sz = -1; PA("|nn", &pos, &sz); mutex(lock); if (sz < 0) sz = size_of_cache_file(self); mutex(unlock); PyObject *ans = PyBytes_FromStringAndSize(NULL, sz); if (ans) { read_from_cache_file(self, pos, sz, PyBytes_AS_STRING(ans)); } return ans; } static PyObject* wait_for_write(PyObject *self, PyObject *args) { double timeout = 0; PA("|d", &timeout); if (disk_cache_wait_for_write(self, s_double_to_monotonic_t(timeout))) Py_RETURN_TRUE; Py_RETURN_FALSE; } static PyObject* size_on_disk(PyObject *self_, PyObject *args UNUSED) { DiskCache *self = (DiskCache*)self_; mutex(lock); unsigned long long ans = disk_cache_size_on_disk(self_); mutex(unlock); return PyLong_FromUnsignedLongLong(ans); } static PyObject* clear(PyObject *self, PyObject *args UNUSED) { clear_disk_cache(self); Py_RETURN_NONE; } static PyObject* holes(PyObject *self_, PyObject *args UNUSED) { DiskCache *self = (DiskCache*)self_; mutex(lock); RAII_PyObject(ans, PyFrozenSet_New(NULL)); if (ans) { hole_pos_map_for_loop(i) { RAII_PyObject(t, Py_BuildValue("LL", (long long)i.data->key, (long long)i.data->val)); if (!t || PySet_Add(ans, t) != 0) break; } } mutex(unlock); if (PyErr_Occurred()) return NULL; Py_INCREF(ans); return ans; } static PyObject* add(PyObject *self, PyObject *args) { const char *key, *data; Py_ssize_t keylen, datalen; PA("y#y#", &key, &keylen, &data, &datalen); if (!add_to_disk_cache(self, key, keylen, data, datalen)) return NULL; Py_RETURN_NONE; } static PyObject* pyremove(PyObject *self, PyObject *args) { const char *key; Py_ssize_t keylen; PA("y#", &key, &keylen); bool removed = remove_from_disk_cache(self, key, keylen); if (PyErr_Occurred()) return NULL; if (removed) Py_RETURN_TRUE; Py_RETURN_FALSE; } typedef struct { PyObject *bytes; } BytesWrapper; static void* bytes_alloc(void *x, size_t sz) { BytesWrapper *w = x; w->bytes = PyBytes_FromStringAndSize(NULL, sz); if (!w->bytes) return NULL; return PyBytes_AS_STRING(w->bytes); } PyObject* read_from_disk_cache_python(PyObject *self, const void *key, size_t keysz, bool store_in_ram) { BytesWrapper w = {0}; read_from_disk_cache(self, key, keysz, bytes_alloc, &w, store_in_ram); if (PyErr_Occurred()) { Py_CLEAR(w.bytes); return NULL; } return w.bytes; } static PyObject* get(PyObject *self, PyObject *args) { const char *key; Py_ssize_t keylen; int store_in_ram = 0; PA("y#|p", &key, &keylen, &store_in_ram); return read_from_disk_cache_python(self, key, keylen, store_in_ram); } static bool python_clear_predicate(void *data, void *key, unsigned keysz) { PyObject *ret = PyObject_CallFunction(data, "y#", key, keysz); if (ret == NULL) { PyErr_Print(); return false; } bool ans = PyObject_IsTrue(ret); Py_DECREF(ret); return ans; } static PyObject* remove_from_ram(PyObject *self, PyObject *callable) { if (!PyCallable_Check(callable)) { PyErr_SetString(PyExc_TypeError, "not a callable"); return NULL; } return PyLong_FromUnsignedLong(disk_cache_clear_from_ram(self, python_clear_predicate, callable)); } static PyObject* num_cached_in_ram(PyObject *self, PyObject *args UNUSED) { return PyLong_FromUnsignedLong(disk_cache_num_cached_in_ram(self)); } #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} static PyMethodDef methods[] = { MW(ensure_state, METH_NOARGS), MW(read_from_cache_file, METH_VARARGS), {"add", add, METH_VARARGS, NULL}, {"remove", pyremove, METH_VARARGS, NULL}, {"remove_from_ram", remove_from_ram, METH_O, NULL}, {"num_cached_in_ram", num_cached_in_ram, METH_NOARGS, NULL}, {"get", get, METH_VARARGS, NULL}, {"wait_for_write", wait_for_write, METH_VARARGS, NULL}, {"size_on_disk", size_on_disk, METH_NOARGS, NULL}, {"clear", clear, METH_NOARGS, NULL}, {"holes", holes, METH_NOARGS, NULL}, {NULL} /* Sentinel */ }; static PyMemberDef members[] = { {"total_size", T_ULONGLONG, offsetof(DiskCache, total_size), READONLY, "total_size"}, {"small_hole_threshold", T_PYSSIZET, offsetof(DiskCache, small_hole_threshold), 0, "small_hole_threshold"}, {"defrag_factor", T_UINT, offsetof(DiskCache, defrag_factor), 0, "defrag_factor"}, {NULL}, }; PyTypeObject DiskCache_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.DiskCache", .tp_basicsize = sizeof(DiskCache), .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "A disk based secure cache", .tp_methods = methods, .tp_members = members, .tp_new = new_diskcache_object, }; INIT_TYPE(DiskCache) PyObject* create_disk_cache(void) { return new_diskcache_object(&DiskCache_Type, NULL, NULL); } kitty-0.41.1/kitty/disk-cache.h0000664000175000017510000000253714773370543015626 0ustar nileshnilesh/* * Copyright (C) 2020 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" PyObject* create_disk_cache(void); bool add_to_disk_cache(PyObject *self, const void *key, size_t key_sz, const void *data, size_t data_sz); bool remove_from_disk_cache(PyObject *self_, const void *key, size_t key_sz); void* read_from_disk_cache(PyObject *self_, const void *key, size_t key_sz, void*(allocator)(void*, size_t), void*, bool); PyObject* read_from_disk_cache_python(PyObject *self_, const void *key, size_t key_sz, bool); bool disk_cache_wait_for_write(PyObject *self, monotonic_t timeout); size_t disk_cache_total_size(PyObject *self); size_t disk_cache_size_on_disk(PyObject *self); void clear_disk_cache(PyObject *self); size_t disk_cache_clear_from_ram(PyObject *self_, bool(matches)(void* data, void *key, unsigned keysz), void*); size_t disk_cache_num_cached_in_ram(PyObject *self_); static inline void* disk_cache_malloc_allocator(void *x, size_t sz) { *((size_t*)x) = sz; return malloc(sz); } static inline bool read_from_disk_cache_simple(PyObject *self_, const void *key, size_t key_sz, void **data, size_t *data_sz, bool store_in_ram) { *data = read_from_disk_cache(self_, key, key_sz, disk_cache_malloc_allocator, data_sz, store_in_ram); return PyErr_Occurred() == NULL; } kitty-0.41.1/kitty/entry_points.py0000664000175000017510000001314114773370543016562 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2022, Kovid Goyal import os import sys def icat(args: list[str]) -> None: from kitty.constants import kitten_exe os.execl(kitten_exe(), "kitten", *args) def list_fonts(args: list[str]) -> None: from kitty.fonts.list import main as list_main list_main(args) def runpy(args: list[str]) -> None: if len(args) < 2: raise SystemExit('Usage: kitty +runpy "some python code"') sys.argv = ['kitty'] + args[2:] exec(args[1]) def hold(args: list[str]) -> None: from kitty.constants import kitten_exe args = ['kitten', '__hold_till_enter__'] + args[1:] os.execvp(kitten_exe(), args) def complete(args: list[str]) -> None: # Delegate to kitten to maintain backward compatibility if len(args) < 2 or args[1] not in ('setup', 'zsh', 'fish2', 'bash'): raise SystemExit(1) if args[1] == 'fish2': args[1:1] = ['fish', '_legacy_completion=fish2'] elif len(args) >= 3 and args [1:3] == ['setup', 'fish2']: args[2] = 'fish' from kitty.constants import kitten_exe args = ['kitten', '__complete__'] + args[1:] os.execvp(kitten_exe(), args) def open_urls(args: list[str]) -> None: setattr(sys, 'cmdline_args_for_open', True) sys.argv = ['kitty'] + args[1:] from kitty.main import main as kitty_main kitty_main() def launch(args: list[str]) -> None: import runpy sys.argv = args[1:] try: exe = args[1] except IndexError: raise SystemExit( 'usage: kitty +launch script.py [arguments to be passed to script.py ...]\n\n' 'script.py will be run with full access to kitty code. If script.py is ' 'prefixed with a : it will be searched for in PATH. If script.py is a directory ' 'the __main__.py file inside it is run just as with the normal Python interpreter.' ) if exe.startswith(':'): import shutil q = shutil.which(exe[1:]) if not q: raise SystemExit(f'{exe[1:]} not found in PATH') exe = q if not os.path.exists(exe): raise SystemExit(f'{exe} does not exist') runpy.run_path(exe, run_name='__main__') def edit(args: list[str]) -> None: import shutil from .constants import is_macos if is_macos: # On macOS vim fails to handle SIGWINCH if it occurs early, so add a small delay. import time time.sleep(0.05) exe = args[1] if not os.path.isabs(exe): exe = shutil.which(exe) or '' if not exe or not os.access(exe, os.X_OK): print('Cannot find an editor on your system. Set the \x1b[33meditor\x1b[39m value in kitty.conf' ' to the absolute path of your editor of choice.', file=sys.stderr) from kitty.utils import hold_till_enter hold_till_enter() raise SystemExit(1) os.execv(exe, args[1:]) def shebang(args: list[str]) -> None: from kitty.constants import kitten_exe os.execvp(kitten_exe(), ['kitten', '__shebang__', 'confirm-if-needed'] + args[1:]) def run_kitten(args: list[str]) -> None: try: kitten = args[1] except IndexError: from kittens.runner import list_kittens list_kittens() raise SystemExit(1) sys.argv = args[1:] from kittens.runner import run_kitten as rk rk(kitten) def edit_config_file(args: list[str]) -> None: from kitty.cli import create_default_opts from kitty.fast_data_types import set_options from kitty.utils import edit_config_file as f set_options(create_default_opts()) f() def namespaced(args: list[str]) -> None: try: func = namespaced_entry_points[args[1]] except IndexError: raise SystemExit('The kitty command line is incomplete') except KeyError: pass else: func(args[1:]) return raise SystemExit(f'{args[1]} is not a known entry point. Choices are: ' + ', '.join(namespaced_entry_points)) entry_points = { # These two are here for backwards compat 'icat': icat, 'list-fonts': list_fonts, '+': namespaced, } namespaced_entry_points = {k: v for k, v in entry_points.items() if k[0] not in '+@'} namespaced_entry_points['hold'] = hold namespaced_entry_points['complete'] = complete namespaced_entry_points['runpy'] = runpy namespaced_entry_points['launch'] = launch namespaced_entry_points['open'] = open_urls namespaced_entry_points['kitten'] = run_kitten namespaced_entry_points['edit-config'] = edit_config_file namespaced_entry_points['shebang'] = shebang namespaced_entry_points['edit'] = edit def setup_openssl_environment(ext_dir: str) -> None: # Use our bundled CA certificates instead of the system ones, since # many systems come with no certificates in a usable form or have various # locations for the certificates. d = os.path.dirname if 'darwin' in sys.platform.lower(): cert_file = os.path.join(d(d(d(ext_dir))), 'cacert.pem') else: cert_file = os.path.join(d(ext_dir), 'cacert.pem') os.environ['SSL_CERT_FILE'] = cert_file setattr(sys, 'kitty_ssl_env_var', 'SSL_CERT_FILE') def main() -> None: if getattr(sys, 'frozen', False): ext_dir: str = getattr(sys, 'kitty_run_data').get('extensions_dir') if ext_dir: setup_openssl_environment(ext_dir) first_arg = '' if len(sys.argv) < 2 else sys.argv[1] func = entry_points.get(first_arg) if func is None: if first_arg.startswith('+'): namespaced(['+', first_arg[1:]] + sys.argv[2:]) else: from kitty.main import main as kitty_main kitty_main() else: func(sys.argv[1:]) kitty-0.41.1/kitty/fast-file-copy.c0000664000175000017510000000722414773370543016446 0ustar nileshnilesh/* * fast-file-copy.c * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #if __linux__ #define _GNU_SOURCE 1 #endif #include "fast-file-copy.h" #if __linux__ #define HAS_SENDFILE #include #include #endif static bool copy_with_buffer(int infd, int outfd, off_t in_pos, size_t len, FastFileCopyBuffer *fcb) { if (!fcb->buf) { fcb->sz = 32 * 1024; fcb->buf = malloc(fcb->sz); if (!fcb->buf) return false; } while (len) { ssize_t amt_read = pread(infd, fcb->buf, MIN(len, fcb->sz), in_pos); if (amt_read < 0) { if (errno == EINTR || errno == EAGAIN) continue; return false; } if (amt_read == 0) { errno = EIO; return false; } len -= amt_read; in_pos += amt_read; uint8_t *p = fcb->buf; while(amt_read) { ssize_t amt_written = write(outfd, p, amt_read); if (amt_written < 0) { if (errno == EINTR || errno == EAGAIN) continue; return false; } if (amt_written == 0) { errno = EIO; return false; } amt_read -= amt_written; p += amt_written; } } return true; } #ifdef HAS_SENDFILE static bool copy_with_sendfile(int infd, int outfd, off_t in_pos, size_t len, FastFileCopyBuffer *fcb) { unsigned num_of_consecutive_zero_returns = 128; while (len) { off_t r = in_pos; ssize_t n = sendfile(outfd, infd, &r, len); if (n < 0) { if (errno == EAGAIN) continue; if (errno == ENOSYS || // No kernel support errno == EPERM || errno == EINVAL) // ZFS for some reason return copy_with_buffer(infd, outfd, in_pos, len, fcb); return false; } if (n == 0) { // happens if input file is truncated if (!--num_of_consecutive_zero_returns) return false; continue; }; num_of_consecutive_zero_returns = 128; in_pos += n; len -= n; } return true; } static bool copy_with_file_range(int infd, int outfd, off_t in_pos, size_t len, FastFileCopyBuffer *fcb) { #ifdef HAS_COPY_FILE_RANGE unsigned num_of_consecutive_zero_returns = 128; while (len) { int64_t r = in_pos; ssize_t n = copy_file_range(infd, &r, outfd, NULL, len, 0); if (n < 0) { if (errno == EAGAIN) continue; if (errno == ENOSYS || // Linux < 4.5 errno == EPERM || // Possibly Docker errno == EINVAL || // ZFS for some reason errno == EIO || // CIFS errno == EOPNOTSUPP || // NFS errno == EXDEV) // Prior to Linux 5.3, it was not possible to copy_file_range across file systems return copy_with_sendfile(infd, outfd, in_pos, len, fcb); return false; } if (n == 0) { // happens if input file is truncated if (!--num_of_consecutive_zero_returns) return false; continue; }; num_of_consecutive_zero_returns = 128; in_pos += n; len -= n; } return true; #else return copy_with_sendfile(infd, outfd, in_pos, len, fcb); #endif } #endif bool copy_between_files(int infd, int outfd, off_t in_pos, size_t len, FastFileCopyBuffer *fcb) { #ifdef HAS_SENDFILE return copy_with_file_range(infd, outfd, in_pos, len, fcb); #else return copy_with_buffer(infd, outfd, in_pos, len, fcb); #endif return true; } kitty-0.41.1/kitty/fast-file-copy.h0000664000175000017510000000111314773370543016442 0ustar nileshnilesh/* * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" typedef struct FastFileCopyBuffer { uint8_t *buf; size_t sz; } FastFileCopyBuffer; static inline void free_fast_file_copy_buffer(FastFileCopyBuffer *fcb) { free(fcb->buf); fcb->buf = NULL; } #define RAII_FreeFastFileCopyBuffer(name) __attribute__ ((__cleanup__(free_fast_file_copy_buffer))) FastFileCopyBuffer name = {0} bool copy_between_files(int infd, int outfd, off_t in_pos, size_t len, FastFileCopyBuffer *fcb); kitty-0.41.1/kitty/fast_data_types.pyi0000664000175000017510000012314414773370543017355 0ustar nileshnileshimport termios from typing import Any, Callable, Dict, Iterator, List, Literal, NewType, Optional, Tuple, TypedDict, Union, overload from kitty.boss import Boss from kitty.fonts import VariableData from kitty.fonts.render import FontObject from kitty.marks import MarkerFunc from kitty.notifications import MacOSNotificationCategory from kitty.options.types import Options from kitty.types import LayerShellConfig, SignalInfo from kitty.typing import EdgeLiteral, NotRequired, ReadableBuffer, WriteableBuffer # Constants {{{ SCALE_BITS: int WIDTH_BITS: int SUBSCALE_BITS: int GLFW_LAYER_SHELL_NONE: int GLFW_LAYER_SHELL_PANEL: int GLFW_LAYER_SHELL_TOP: int GLFW_LAYER_SHELL_OVERLAY: int GLFW_LAYER_SHELL_BACKGROUND: int GLFW_EDGE_TOP: int GLFW_EDGE_BOTTOM: int GLFW_EDGE_LEFT: int GLFW_EDGE_RIGHT: int GLFW_EDGE_CENTER: int GLFW_EDGE_NONE: int GLFW_FOCUS_NOT_ALLOWED: int GLFW_FOCUS_EXCLUSIVE: int GLFW_FOCUS_ON_DEMAND: int IMAGE_PLACEHOLDER_CHAR: int GLFW_PRIMARY_SELECTION: int GLFW_CLIPBOARD: int CLD_KILLED: int CLD_STOPPED: int CLD_CONTINUED: int CLD_EXITED: int SHM_NAME_MAX: int MOUSE_SELECTION_LINE: int MOUSE_SELECTION_EXTEND: int MOUSE_SELECTION_NORMAL: int MOUSE_SELECTION_WORD: int MOUSE_SELECTION_RECTANGLE: int MOUSE_SELECTION_LINE_FROM_POINT: int MOUSE_SELECTION_WORD_AND_LINE_FROM_POINT: int MOUSE_SELECTION_MOVE_END: int KITTY_VCS_REV: str NO_CLOSE_REQUESTED: int IMPERATIVE_CLOSE_REQUESTED: int CLOSE_BEING_CONFIRMED: int ERROR_PREFIX: str GLSL_VERSION: int # start glfw functional keys (auto generated by gen-key-constants.py do not edit) GLFW_FKEY_ESCAPE: int GLFW_FKEY_ENTER: int GLFW_FKEY_TAB: int GLFW_FKEY_BACKSPACE: int GLFW_FKEY_INSERT: int GLFW_FKEY_DELETE: int GLFW_FKEY_LEFT: int GLFW_FKEY_RIGHT: int GLFW_FKEY_UP: int GLFW_FKEY_DOWN: int GLFW_FKEY_PAGE_UP: int GLFW_FKEY_PAGE_DOWN: int GLFW_FKEY_HOME: int GLFW_FKEY_END: int GLFW_FKEY_CAPS_LOCK: int GLFW_FKEY_SCROLL_LOCK: int GLFW_FKEY_NUM_LOCK: int GLFW_FKEY_PRINT_SCREEN: int GLFW_FKEY_PAUSE: int GLFW_FKEY_MENU: int GLFW_FKEY_F1: int GLFW_FKEY_F2: int GLFW_FKEY_F3: int GLFW_FKEY_F4: int GLFW_FKEY_F5: int GLFW_FKEY_F6: int GLFW_FKEY_F7: int GLFW_FKEY_F8: int GLFW_FKEY_F9: int GLFW_FKEY_F10: int GLFW_FKEY_F11: int GLFW_FKEY_F12: int GLFW_FKEY_F13: int GLFW_FKEY_F14: int GLFW_FKEY_F15: int GLFW_FKEY_F16: int GLFW_FKEY_F17: int GLFW_FKEY_F18: int GLFW_FKEY_F19: int GLFW_FKEY_F20: int GLFW_FKEY_F21: int GLFW_FKEY_F22: int GLFW_FKEY_F23: int GLFW_FKEY_F24: int GLFW_FKEY_F25: int GLFW_FKEY_F26: int GLFW_FKEY_F27: int GLFW_FKEY_F28: int GLFW_FKEY_F29: int GLFW_FKEY_F30: int GLFW_FKEY_F31: int GLFW_FKEY_F32: int GLFW_FKEY_F33: int GLFW_FKEY_F34: int GLFW_FKEY_F35: int GLFW_FKEY_KP_0: int GLFW_FKEY_KP_1: int GLFW_FKEY_KP_2: int GLFW_FKEY_KP_3: int GLFW_FKEY_KP_4: int GLFW_FKEY_KP_5: int GLFW_FKEY_KP_6: int GLFW_FKEY_KP_7: int GLFW_FKEY_KP_8: int GLFW_FKEY_KP_9: int GLFW_FKEY_KP_DECIMAL: int GLFW_FKEY_KP_DIVIDE: int GLFW_FKEY_KP_MULTIPLY: int GLFW_FKEY_KP_SUBTRACT: int GLFW_FKEY_KP_ADD: int GLFW_FKEY_KP_ENTER: int GLFW_FKEY_KP_EQUAL: int GLFW_FKEY_KP_SEPARATOR: int GLFW_FKEY_KP_LEFT: int GLFW_FKEY_KP_RIGHT: int GLFW_FKEY_KP_UP: int GLFW_FKEY_KP_DOWN: int GLFW_FKEY_KP_PAGE_UP: int GLFW_FKEY_KP_PAGE_DOWN: int GLFW_FKEY_KP_HOME: int GLFW_FKEY_KP_END: int GLFW_FKEY_KP_INSERT: int GLFW_FKEY_KP_DELETE: int GLFW_FKEY_KP_BEGIN: int GLFW_FKEY_MEDIA_PLAY: int GLFW_FKEY_MEDIA_PAUSE: int GLFW_FKEY_MEDIA_PLAY_PAUSE: int GLFW_FKEY_MEDIA_REVERSE: int GLFW_FKEY_MEDIA_STOP: int GLFW_FKEY_MEDIA_FAST_FORWARD: int GLFW_FKEY_MEDIA_REWIND: int GLFW_FKEY_MEDIA_TRACK_NEXT: int GLFW_FKEY_MEDIA_TRACK_PREVIOUS: int GLFW_FKEY_MEDIA_RECORD: int GLFW_FKEY_LOWER_VOLUME: int GLFW_FKEY_RAISE_VOLUME: int GLFW_FKEY_MUTE_VOLUME: int GLFW_FKEY_LEFT_SHIFT: int GLFW_FKEY_LEFT_CONTROL: int GLFW_FKEY_LEFT_ALT: int GLFW_FKEY_LEFT_SUPER: int GLFW_FKEY_LEFT_HYPER: int GLFW_FKEY_LEFT_META: int GLFW_FKEY_RIGHT_SHIFT: int GLFW_FKEY_RIGHT_CONTROL: int GLFW_FKEY_RIGHT_ALT: int GLFW_FKEY_RIGHT_SUPER: int GLFW_FKEY_RIGHT_HYPER: int GLFW_FKEY_RIGHT_META: int GLFW_FKEY_ISO_LEVEL3_SHIFT: int GLFW_FKEY_ISO_LEVEL5_SHIFT: int # end glfw functional keys GLFW_MOD_SHIFT: int GLFW_MOD_CONTROL: int GLFW_MOD_ALT: int GLFW_MOD_SUPER: int GLFW_MOD_HYPER: int GLFW_MOD_META: int GLFW_MOD_CAPS_LOCK: int GLFW_MOD_NUM_LOCK: int GLFW_MOD_KITTY: int GLFW_MOUSE_BUTTON_1: int GLFW_MOUSE_BUTTON_2: int GLFW_MOUSE_BUTTON_3: int GLFW_MOUSE_BUTTON_4: int GLFW_MOUSE_BUTTON_5: int GLFW_MOUSE_BUTTON_6: int GLFW_MOUSE_BUTTON_7: int GLFW_MOUSE_BUTTON_8: int GLFW_MOUSE_BUTTON_LAST: int GLFW_MOUSE_BUTTON_LEFT: int GLFW_MOUSE_BUTTON_RIGHT: int GLFW_MOUSE_BUTTON_MIDDLE: int GLFW_JOYSTICK_1: int GLFW_JOYSTICK_2: int GLFW_JOYSTICK_3: int GLFW_JOYSTICK_4: int GLFW_JOYSTICK_5: int GLFW_JOYSTICK_6: int GLFW_JOYSTICK_7: int GLFW_JOYSTICK_8: int GLFW_JOYSTICK_9: int GLFW_JOYSTICK_10: int GLFW_JOYSTICK_11: int GLFW_JOYSTICK_12: int GLFW_JOYSTICK_13: int GLFW_JOYSTICK_14: int GLFW_JOYSTICK_15: int GLFW_JOYSTICK_16: int GLFW_JOYSTICK_LAST: int GLFW_NOT_INITIALIZED: int GLFW_NO_CURRENT_CONTEXT: int GLFW_INVALID_ENUM: int GLFW_INVALID_VALUE: int GLFW_OUT_OF_MEMORY: int GLFW_API_UNAVAILABLE: int GLFW_VERSION_UNAVAILABLE: int GLFW_PLATFORM_ERROR: int GLFW_FORMAT_UNAVAILABLE: int GLFW_FOCUSED: int GLFW_ICONIFIED: int GLFW_RESIZABLE: int GLFW_VISIBLE: int GLFW_DECORATED: int GLFW_AUTO_ICONIFY: int GLFW_FLOATING: int GLFW_RED_BITS: int GLFW_GREEN_BITS: int GLFW_BLUE_BITS: int GLFW_ALPHA_BITS: int GLFW_DEPTH_BITS: int GLFW_STENCIL_BITS: int GLFW_ACCUM_RED_BITS: int GLFW_ACCUM_GREEN_BITS: int GLFW_ACCUM_BLUE_BITS: int GLFW_ACCUM_ALPHA_BITS: int GLFW_AUX_BUFFERS: int GLFW_STEREO: int GLFW_SAMPLES: int GLFW_SRGB_CAPABLE: int GLFW_REFRESH_RATE: int GLFW_DOUBLEBUFFER: int GLFW_CLIENT_API: int GLFW_CONTEXT_VERSION_MAJOR: int GLFW_CONTEXT_VERSION_MINOR: int GLFW_CONTEXT_REVISION: int GLFW_CONTEXT_ROBUSTNESS: int GLFW_OPENGL_FORWARD_COMPAT: int GLFW_CONTEXT_DEBUG: int GLFW_OPENGL_PROFILE: int GLFW_OPENGL_API: int GLFW_OPENGL_ES_API: int GLFW_NO_ROBUSTNESS: int GLFW_NO_RESET_NOTIFICATION: int GLFW_LOSE_CONTEXT_ON_RESET: int GLFW_OPENGL_ANY_PROFILE: int GLFW_OPENGL_CORE_PROFILE: int GLFW_OPENGL_COMPAT_PROFILE: int GLFW_CURSOR: int GLFW_STICKY_KEYS: int GLFW_STICKY_MOUSE_BUTTONS: int GLFW_CURSOR_NORMAL: int GLFW_CURSOR_HIDDEN: int GLFW_CURSOR_DISABLED: int GLFW_CONNECTED: int GLFW_DISCONNECTED: int GLFW_PRESS: int GLFW_RELEASE: int GLFW_REPEAT: int CURSOR_BEAM: int CURSOR_BLOCK: int CURSOR_HOLLOW: int NO_CURSOR_SHAPE: int CURSOR_UNDERLINE: int DECAWM: int BGIMAGE_PROGRAM: int CELL_BG_PROGRAM: int CELL_FG_PROGRAM: int CELL_PROGRAM: int CELL_SPECIAL_PROGRAM: int DECORATION: int DIM: int GRAPHICS_ALPHA_MASK_PROGRAM: int GRAPHICS_PREMULT_PROGRAM: int GRAPHICS_PROGRAM: int MARK: int MARK_MASK: int DECORATION_MASK: int FILE_TRANSFER_CODE: int ESC_CSI: int ESC_OSC: int ESC_DCS: int ESC_APC: int ESC_PM: int REVERSE: int SCROLL_FULL: int SCROLL_LINE: int SCROLL_PAGE: int STRIKETHROUGH: int TINT_PROGRAM: int FC_MONO: int = 100 FC_DUAL: int FC_WEIGHT_REGULAR: int FC_WEIGHT_BOLD: int FC_WEIGHT_SEMIBOLD: int FC_WEIGHT_MEDIUM: int FC_WIDTH_NORMAL: int FC_SLANT_ROMAN: int FC_SLANT_ITALIC: int BORDERS_PROGRAM: int TRAIL_PROGRAM: int PRESS: int RELEASE: int DRAG: int MOVE: int WINDOW_NORMAL: int = 0 WINDOW_FULLSCREEN: int WINDOW_MAXIMIZED: int WINDOW_MINIMIZED: int TEXT_SIZE_CODE: int TOP_EDGE: int BOTTOM_EDGE: int # }}} def encode_key_for_tty( key: int = 0, shifted_key: int = 0, alternate_key: int = 0, mods: int = 0, action: int = 1, key_encoding_flags: int = 0, text: str = "", cursor_key_mode: bool = False ) -> str: pass def log_error_string(s: str) -> None: pass def redirect_std_streams(devnull: str) -> None: pass def glfw_get_key_name(key: int, native_key: int) -> Optional[str]: pass StartupCtx = NewType('StartupCtx', int) Display = NewType('Display', int) def init_x11_startup_notification( display: Display, window_id: int, startup_id: Optional[str] = None ) -> StartupCtx: pass def end_x11_startup_notification(ctx: StartupCtx) -> None: pass def x11_display() -> Optional[Display]: pass def user_cache_dir() -> str: pass def process_group_map() -> Tuple[Tuple[int, int], ...]: pass def environ_of_process(pid: int) -> str: pass def cmdline_of_process(pid: int) -> List[str]: pass def cwd_of_process(pid: int) -> str: pass def default_color_table() -> Tuple[int, ...]: pass class FontConfigPattern(TypedDict): descriptor_type: Literal['fontconfig'] path: str index: int family: str full_name: str postscript_name: str style: str spacing: str fontfeatures: List[str] weight: int width: int slant: int hint_style: int subpixel: int lcdfilter: int hinting: bool scalable: bool outline: bool color: bool variable: bool named_instance: bool # The following two are used by C code to get a face from the pattern named_style: NotRequired[int] axes: NotRequired[Tuple[float, ...]] features: NotRequired[Tuple[ParsedFontFeature, ...]] def fc_list(spacing: int = -1, allow_bitmapped_fonts: bool = False, only_variable: bool = False) -> Tuple[FontConfigPattern, ...]: pass def fc_match( family: Optional[str] = None, bold: bool = False, italic: bool = False, spacing: int = FC_MONO, allow_bitmapped_fonts: bool = False, size_in_pts: float = 0., dpi: float = 0. ) -> FontConfigPattern: pass def fc_match_postscript_name( postscript_name: str ) -> FontConfigPattern: pass def add_font_file(path: str) -> bool: ... def set_builtin_nerd_font(path: str) -> Union[CoreTextFont, FontConfigPattern]: ... class FeatureData(TypedDict): name: NotRequired[str] tooltip: NotRequired[str] sample: NotRequired[str] params: NotRequired[Tuple[str, ...]] class Face: path: Optional[str] def __init__(self, descriptor: Optional[FontConfigPattern] = None, path: str = '', index: int = 0): ... def get_variable_data(self) -> VariableData: ... def identify_for_debug(self) -> str: ... def postscript_name(self) -> str: ... def set_size(self, sz_in_pts: float, dpi_x: float, dpi_y: float) -> None: ... def render_sample_text( self, text: str, width: int, height: int, fg_color: int = 0xffffff ) -> tuple[bytes, int, int]: ... def render_codepoint(self, cp: int, fg_color: int = 0xffffff) -> tuple[bytes, int, int]: ... def get_variation(self) -> Optional[Dict[str, float]]: ... def get_features(self) -> Dict[str, Optional[FeatureData]]: ... def applied_features(self) -> Dict[str, str]: ... class CoreTextFont(TypedDict): descriptor_type: Literal['core_text'] path: str postscript_name: str display_name: str family: str style: str bold: bool italic: bool expanded: bool condensed: bool color_glyphs: bool monospace: bool variation: Optional[Dict[str, float]] weight: float width: float slant: float traits: int # The following is used by C code to get a face from the pattern axis_map: NotRequired[Dict[str, float]] features: NotRequired[Tuple[ParsedFontFeature, ...]] class CTFace: path: Optional[str] def __init__(self, descriptor: Optional[CoreTextFont] = None, path: str = ''): ... def get_variable_data(self) -> VariableData: ... def identify_for_debug(self) -> str: ... def postscript_name(self) -> str: ... def set_size(self, sz_in_pts: float, dpi_x: float, dpi_y: float) -> None: ... def render_sample_text( self, text: str, width: int, height: int, fg_color: int = 0xffffff, ) -> tuple[bytes, int, int]: ... def render_codepoint(self, cp: int, fg_color: int = 0xffffff) -> tuple[bytes, int, int]: ... def get_variation(self) -> Optional[Dict[str, float]]: ... def get_features(self) -> Dict[str, Optional[FeatureData]]: ... def applied_features(self) -> Dict[str, str]: ... def coretext_all_fonts(monospaced_only: bool) -> Tuple[CoreTextFont, ...]: pass class ParsedFontFeature: def __init__(self, s: str): ... def add_timer( callback: Callable[[Optional[int]], None], interval: float, repeats: bool = True ) -> int: pass def remove_timer(timer_id: int) -> None: pass def monitor_pid(pid: int) -> None: pass def add_window(os_window_id: int, tab_id: int, title: str) -> int: pass def compile_program( which: int, vertex_shaders: Tuple[str, ...], fragment_shaders: Tuple[str, ...], allow_recompile: bool = False ) -> int: pass def init_cell_program() -> None: pass def set_os_window_chrome(os_window_id: int) -> bool: pass def add_borders_rect( os_window_id: int, tab_id: int, left: int, top: int, right: int, bottom: int, color: int ) -> None: pass def init_borders_program() -> None: pass def init_trail_program() -> None: pass def os_window_has_background_image(os_window_id: int) -> bool: pass def dbus_set_notification_callback(c: Optional[Callable[[str, int, Union[str, int]], None]]) -> None: ... def dbus_send_notification( app_name: str, app_icon: str, title: str, body: str, actions: dict[str, str], timeout: int = -1, urgency: int = 1, replaces: int = 0, category: str = '', muted: bool = False, ) -> int: pass def dbus_close_notification(dbus_notification_id: int) -> bool: ... def cocoa_send_notification( appname: str, identifier: str, title: str, body: str, category: MacOSNotificationCategory, categories: tuple[MacOSNotificationCategory, ...], image_path: str = '', urgency: int = 1, muted: bool = False, ) -> None: pass def cocoa_bundle_image_as_png(path_or_identifier: str, output_path: str = '', image_size: int = 256, image_type: int = 1) -> bytes: ... def cocoa_remove_delivered_notification(identifier: str) -> bool: ... def cocoa_live_delivered_notifications() -> bool: ... def create_os_window( get_window_size: Callable[[int, int, int, int, float, float], Tuple[int, int]], pre_show_callback: Callable[[int], None], title: str, wm_class_name: str, wm_class_class: str, window_state: Optional[int] = WINDOW_NORMAL, load_programs: Optional[Callable[[bool], None]] = None, x: Optional[int] = None, y: Optional[int] = None, disallow_override_title: bool = False, layer_shell_config: Optional[LayerShellConfig] = None, ) -> int: pass def update_window_title( os_window_id: int, tab_id: int, window_id: int, title: str ) -> None: pass def update_window_visibility( os_window_id: int, tab_id: int, window_id: int, visible: bool ) -> None: pass def sync_os_window_title(os_window_id: int) -> None: pass def set_options( opts: Optional[Options], is_wayland: bool = False, debug_rendering: bool = False, debug_font_fallback: bool = False ) -> None: pass def get_options() -> Options: pass def glfw_primary_monitor_size() -> Tuple[int, int]: pass def set_default_window_icon(path: str) -> None: pass def set_os_window_icon(os_window_id: int, path: str | None | bytes = None) -> None: ... def set_custom_cursor( cursor_shape: str, images: Tuple[Tuple[bytes, int, int], ...], x: int = 0, y: int = 0 ) -> None: pass def is_css_pointer_name_valid(name: str) -> bool: ... def pointer_name_to_css_name(name: str) -> str: ... def load_png_data(data: bytes) -> Tuple[bytes, int, int]: pass def glfw_terminate() -> None: pass def glfw_init( path: str, edge_spacing_func: Callable[[EdgeLiteral], float], debug_keyboard: bool = False, debug_rendering: bool = False, wayland_enable_ime: bool = True ) -> tuple[bool, bool]: pass def free_font_data() -> None: pass def toggle_maximized(os_window_id: int = 0) -> bool: pass def toggle_fullscreen(os_window_id: int = 0) -> bool: pass def thread_write(fd: int, data: bytes) -> None: pass def set_ignore_os_keyboard_processing(yes: bool) -> None: pass def set_background_image( path: Optional[str], os_window_ids: Tuple[int, ...], configured: bool = True, layout_name: Optional[str] = None, png_data: bytes = b'' ) -> None: pass def set_boss(boss: Boss) -> None: pass def get_boss() -> Boss: # this can return None but we ignore that for convenience pass def safe_pipe(nonblock: bool = True) -> Tuple[int, int]: pass def patch_global_colors(spec: Dict[str, Optional[int]], configured: bool) -> None: pass class Color: @property def rgb(self) -> int: pass @property def red(self) -> int: pass r = red @property def green(self) -> int: pass g = green @property def blue(self) -> int: pass b = blue @property def alpha(self) -> int: pass a = alpha @property def luminance(self) -> float: pass @property def is_dark(self) -> bool: pass @property def as_sgr(self) -> str: pass @property def as_sharp(self) -> str: pass def __init__(self, red: int = 0, green: int = 0, blue: int = 0, alpha: int = 0) -> None: pass def __truediv__(self, divisor: float) -> Tuple[float, float, float, float]: # (r, g, b, a) pass def __int__(self) -> int: pass def __hash__(self) -> int: pass def __eq__(self, other: Any) -> bool: pass def __ne__(self, other: Any) -> bool: pass def contrast(self, other: 'Color') -> float: pass class ColorProfile: # The dynamic color properties return the current color value. Use delattr # to reset them to configured value. @property def default_fg(self) -> Color: ... @default_fg.setter def default_fg(self, val: Union[int|Color]) -> None: ... @property def default_bg(self) -> Color: ... @default_bg.setter def default_bg(self, val: Union[int|Color]) -> None: ... @property def cursor_color(self) -> Optional[Color]: ... @cursor_color.setter def cursor_color(self, val: Union[None|int|Color]) -> None: ... @property def cursor_text_color(self) -> Optional[Color]: ... @cursor_text_color.setter def cursor_text_color(self, val: Union[None|int|Color]) -> None: ... @property def highlight_fg(self) -> Optional[Color]: ... @highlight_fg.setter def highlight_fg(self, val: Union[None|int|Color]) -> None: ... @property def highlight_bg(self) -> Optional[Color]: ... @highlight_bg.setter def highlight_bg(self, val: Union[None|int|Color]) -> None: ... @property def visual_bell_color(self) -> Optional[Color]: ... @visual_bell_color.setter def visual_bell_color(self, val: Union[None|int|Color]) -> None: ... def __init__(self, opts: Optional[Options] = None): ... def as_dict(self) -> Dict[str, int | None | tuple[tuple[Color, float], ...]]: ... def basic_colors(self) -> Dict[str, int | None | tuple[tuple[Color, float], ...]]: ... def as_color(self, val: int) -> Optional[Color]: pass def set_color(self, num: int, val: int) -> None: pass def reset_color_table(self) -> None: pass def reset_color(self, num: int) -> None: pass def reload_from_opts(self, opts: Optional[Options] = None) -> None: ... def get_transparent_background_color(self, index: int) -> Color | None: ... def set_transparent_background_color(self, index: int, color: Color | None = None, opacity: float | None = None) -> None: ... def patch_color_profiles( spec: Dict[str, Optional[int]], transparent_background_colors: tuple[tuple[Color, float], ...], profiles: Tuple[ColorProfile, ...], change_configured: bool ) -> None: pass def create_canvas(d: bytes, w: int, x: int, y: int, cw: int, ch: int, bpp: int) -> bytes: ... def os_window_font_size( os_window_id: int, new_sz: float = -1., force: bool = False ) -> float: pass def cocoa_set_notification_activated_callback(identifier: Optional[Callable[[str, str, str], None]]) -> None: pass def cocoa_set_global_shortcut(name: str, mods: int, key: int) -> bool: pass def cocoa_get_lang() -> Tuple[str, str, str]: pass def cocoa_set_url_handler(url_scheme: str, bundle_id: Optional[str] = None) -> None: pass def cocoa_set_app_icon(icon_path: str, app_path: Optional[str] = None) -> None: pass def cocoa_set_dock_icon(icon_path: str) -> None: pass def cocoa_show_progress_bar_on_dock_icon(progress: float = -100) -> None: pass def cocoa_hide_app() -> None: pass def cocoa_hide_other_apps() -> None: pass def cocoa_minimize_os_window(os_window_id: Optional[int] = None) -> None: pass def locale_is_valid(name: str) -> bool: pass def mark_os_window_for_close(os_window_id: int, cr_type: int = 2) -> bool: pass def set_application_quit_request(cr_type: int = 2) -> None: pass def current_application_quit_request() -> int: pass def global_font_size(val: float = -1.) -> float: pass def focus_os_window(os_window_id: int, also_raise: bool = True, activation_token: Optional[str] = None) -> bool: pass def toggle_secure_input() -> None: pass def start_profiler(path: str) -> None: pass def stop_profiler() -> None: pass def destroy_global_data() -> None: pass def current_os_window() -> Optional[int]: pass def last_focused_os_window_id() -> int: pass def current_focused_os_window_id() -> int: pass def cocoa_set_menubar_title(title: str) -> None: pass def change_os_window_state(state: int, os_window_id: Optional[int] = 0) -> None: pass def change_background_opacity(os_window_id: int, opacity: float) -> bool: pass def background_opacity_of(os_window_id: int) -> Optional[float]: pass def read_command_response(fd: int, timeout: float, list: List[bytes]) -> None: pass def wcswidth(string: str) -> int: pass def is_emoji_presentation_base(code: int) -> bool: pass def x11_window_id(os_window_id: int) -> int: pass def cocoa_window_id(os_window_id: int) -> int: pass def swap_tabs(os_window_id: int, a: int, b: int) -> None: pass def set_active_tab(os_window_id: int, a: int) -> None: pass def set_active_window(os_window_id: int, tab_id: int, window_id: int) -> None: pass def ring_bell() -> None: pass def concat_cells(cell_width: int, cell_height: int, is_32_bit: bool, cells: Tuple[bytes, ...], bgcolor: int = 0) -> bytes: pass FontFace = Union[Face, CTFace] class CurrentFonts(TypedDict): medium: FontFace bold: FontFace italic: FontFace bi: FontFace symbol: Tuple[FontFace, ...] fallback: Tuple[FontFace, ...] font_sz_in_pts: float logical_dpi_x: float logical_dpi_y: float def current_fonts(os_window_id: int = 0) -> CurrentFonts: ... def remove_window(os_window_id: int, tab_id: int, window_id: int) -> None: pass def remove_tab(os_window_id: int, tab_id: int) -> None: pass def pt_to_px(pt: float, os_window_id: int = 0) -> int: pass def next_window_id() -> int: pass def mark_tab_bar_dirty(os_window_id: int) -> None: pass def detach_window(os_window_id: int, tab_id: int, window_id: int) -> None: pass def attach_window(os_window_id: int, tab_id: int, window_id: int) -> None: pass def add_tab(os_window_id: int) -> int: pass def cell_size_for_window(os_window_id: int) -> Tuple[int, int]: pass def wakeup_main_loop() -> None: pass class Region: left: int top: int right: int bottom: int width: int height: int def __init__(self, x: Tuple[int, int, int, int, int, int]): pass def viewport_for_window( os_window_id: int ) -> Tuple[Region, Region, int, int, int, int]: pass TermiosPtr = NewType('TermiosPtr', int) def raw_tty(fd: int, termios_ptr: TermiosPtr, optional_actions: int = termios.TCSAFLUSH) -> None: pass def close_tty(fd: int, termios_ptr: TermiosPtr, optional_actions: int = termios.TCSAFLUSH) -> None: pass def normal_tty(fd: int, termios_ptr: TermiosPtr, optional_actions: int = termios.TCSAFLUSH) -> None: pass def open_tty(read_with_timeout: bool = False, optional_actions: int = termios.TCSAFLUSH) -> Tuple[int, TermiosPtr]: pass def parse_input_from_terminal( text_callback: Callable[[str], None], dcs_callback: Callable[[str], None], csi_callback: Callable[[str], None], osc_callback: Callable[[str], None], pm_callback: Callable[[str], None], apc_callback: Callable[[str], None], data: str, in_bracketed_paste: bool ) -> str: pass class Line: def sprite_at(self, cell: int) -> int: ... def test_shape(line: Line, path: Optional[str] = None, index: int = 0) -> List[Tuple[int, int, int, Tuple[int, ...]]]: pass def test_render_line(line: Line) -> None: pass def sprite_map_set_limits(w: int, h: int) -> None: pass def set_send_sprite_to_gpu( func: Optional[Callable[[int, int, int, bytes], None]] ) -> None: pass def set_font_data( descriptor_for_idx: Callable[[int], Tuple[Union[FontObject|str], bool, bool]], bold: int, italic: int, bold_italic: int, num_symbol_fonts: int, symbol_maps: Tuple[Tuple[int, int, int], ...], font_sz_in_pts: float, narrow_symbols: Tuple[Tuple[int, int, int], ...], ) -> None: pass def get_fallback_font(text: str, bold: bool, italic: bool) -> Any: pass def create_test_font_group(sz: float, dpix: float, dpiy: float) -> tuple[int, int, int]: ... class HistoryBuf: def pagerhist_as_text(self, upto_output_start: bool = False) -> str: pass def pagerhist_as_bytes(self) -> bytes: pass class LineBuf: def is_continued(self, idx: int) -> bool: pass def line(self, num: int) -> Line: pass def as_ansi(self, callback: Callable[[str], None]) -> None: ... class Cursor: x: int y: int bg: int fg: int bold: bool italic: bool blink: bool shape: int class Screen: color_profile: ColorProfile columns: int lines: int focus_tracking_enabled: bool historybuf: HistoryBuf linebuf: LineBuf in_bracketed_paste_mode: bool in_band_resize_notification: bool color_preference_notification: bool cursor_visible: bool scrolled_by: int cursor: Cursor disable_ligatures: int cursor_key_mode: bool auto_repeat_enabled: bool render_unfocused_cursor: bool last_reported_cwd: Optional[bytes] def __init__( self, callbacks: Any = None, lines: int = 80, columns: int = 24, scrollback: int = 0, cell_width: int = 10, cell_height: int = 20, window_id: int = 0, test_child: Any = None ): pass def test_create_write_buffer(self) -> memoryview: ... def test_commit_write_buffer(self, inp: memoryview, output: memoryview) -> int: ... def test_parse_written_data(self, dump_callback: None = None) -> None: ... def hyperlink_for_id(self, hyperlink_id: int) -> str: ... def cursor_at_prompt(self) -> bool: pass def ignore_bells_for(self, duration: float = 1) -> None: pass def set_window_char(self, ch: str = "") -> None: pass def current_key_encoding_flags(self) -> int: pass def line(self, num: int) -> Line: pass def visual_line(self, num: int) -> Line: pass def draw(self, text: str) -> None: pass def dump_lines_with_attrs(self, acc: Callable[[str], None], which_screen: int = -1) -> None: pass def apply_sgr(self, text: str) -> None: pass def copy_colors_from(self, other: 'Screen') -> None: pass def mark_as_dirty(self) -> None: pass def reload_all_gpu_data(self) -> None: pass def resize(self, width: int, height: int) -> None: pass def send_escape_code_to_child(self, code: int, text: Union[str, bytes, Tuple[Union[str, bytes], ...]]) -> bool: pass def reset_callbacks(self) -> None: pass def has_selection(self) -> bool: pass def text_for_selection(self, ansi: bool, strip_trailing_spaces: bool) -> Tuple[str, ...]: pass def is_rectangle_select(self) -> bool: pass def is_using_alternate_linebuf(self) -> bool: pass def is_main_linebuf(self) -> bool: pass def erase_in_line(self, mode: int = 0, private: bool = False) -> None: pass def scroll(self, amt: int, upwards: bool) -> bool: pass def scroll_to_next_mark(self, mark: int = 0, backwards: bool = True) -> bool: pass def scroll_to_prompt(self, num_of_prompts: int = -1) -> bool: pass def set_last_visited_prompt(self, visual_y: int = 0) -> bool: pass def reverse_scroll(self, amt: int, fill_from_scrollback: bool = False) -> bool: pass def scroll_prompt_to_bottom(self) -> None: pass def clear_selection(self) -> None: pass def reset_mode(self, mode: int, private: bool = False) -> None: pass def refresh_sprite_positions(self) -> None: pass def set_marker(self, marker: Optional[MarkerFunc] = None) -> None: pass def paste_bytes(self, data: bytes) -> None: pass paste = paste_bytes def as_text(self, callback: Callable[[str], None], as_ansi: bool, insert_wrap_markers: bool) -> None: pass as_text_non_visual = as_text as_text_alternate = as_text as_text_for_history_buf = as_text def cmd_output(self, which: int, callback: Callable[[str], None], as_ansi: bool, insert_wrap_markers: bool) -> bool: pass def scroll_until_cursor_prompt(self, add_to_scrollback: bool = True) -> None: pass def reset(self) -> None: pass def erase_in_display(self, how: int = 0, private: bool = False) -> None: pass def clear_scrollback(self) -> None: pass def focus_changed(self, focused: bool) -> bool: pass def has_focus(self) -> bool: pass def has_activity_since_last_focus(self) -> bool: pass def insert_characters(self, num: int) -> None: pass def line_edge_colors(self) -> Tuple[int, int]: pass def current_pointer_shape(self) -> str: ... def change_pointer_shape(self, op: str, name: str) -> None: ... def bell(self) -> None: ... def pause_rendering(self, pause: bool = True, for_how_long_in_ms: int = 100) -> bool: ... def set_tab_bar_render_data( os_window_id: int, screen: Screen, left: int, top: int, right: int, bottom: int ) -> None: pass def set_window_render_data( os_window_id: int, tab_id: int, window_id: int, screen: Screen, left: int, top: int, right: int, bottom: int ) -> None: pass def truncate_point_for_length( text: str, num_cells: int, start_pos: int = 0 ) -> int: pass class ChildMonitor: def __init__( self, death_notify: Callable[[int], None], dump_callback: Optional[Callable[[int, str, Any], None]], talk_fd: int = -1, listen_fd: int = -1, verify_peer_uid: bool = False, ): pass def wakeup(self) -> None: pass def handled_signals(self) -> Tuple[int, ...]: pass def main_loop(self) -> None: pass def resize_pty(self, window_id: int, rows: int, cols: int, x_pixels: int, y_pixels: int) -> None: pass def needs_write(self, child_id: int, data: bytes) -> bool: pass def set_iutf8_winid(self, win_id: int, on: bool) -> bool: pass def add_child(self, id: int, pid: int, fd: int, screen: Screen) -> None: pass def mark_for_close(self, window_id: int) -> bool: pass def start(self) -> None: pass def shutdown_monitor(self) -> None: pass def inject_peer(self, fd: int) -> int: ... class KeyEvent: def __init__( self, key: int, shifted_key: int = 0, alternate_key: int = 0, mods: int = 0, action: int = 1, native_key: int = 1, ime_state: int = 0, text: str = '' ): pass @property def key(self) -> int: pass @property def shifted_key(self) -> int: pass @property def alternate_key(self) -> int: pass @property def mods(self) -> int: pass @property def action(self) -> int: pass @property def native_key(self) -> int: pass @property def ime_state(self) -> int: pass @property def text(self) -> str: pass def set_iutf8_fd(fd: int, on: bool) -> bool: pass def spawn( exe: str, cwd: str, argv: Tuple[str, ...], env: Tuple[str, ...], master: int, slave: int, stdin_read_fd: int, stdin_write_fd: int, ready_read_fd: int, ready_write_fd: int, handled_signals: Tuple[int, ...], kitten_exe: str, forward_stdio: bool, pass_fds: tuple[int, ...], ) -> int: pass def set_window_padding(os_window_id: int, tab_id: int, window_id: int, left: int, top: int, right: int, bottom: int) -> None: pass def click_mouse_url(os_window_id: int, tab_id: int, window_id: int) -> bool: pass def click_mouse_cmd_output(os_window_id: int, tab_id: int, window_id: int, select_cmd_output: bool) -> bool: pass def move_cursor_to_mouse_if_in_prompt(os_window_id: int, tab_id: int, window_id: int) -> bool: pass def mouse_selection(os_window_id: int, tab_id: int, window_id: int, code: int, button: int) -> None: pass def send_mouse_event( screen: Screen, cell_x: int, cell_y: int, button: int, action: int, mods: int, pixel_x: int = 0, pixel_y: int = 0, in_left_half_of_cell: bool = False ) -> bool: ... def set_window_logo(os_window_id: int, tab_id: int, window_id: int, path: str, position: str, alpha: float, png_data: bytes = b'') -> None: pass def apply_options_update() -> None: pass def set_os_window_size(os_window_id: int, x: int, y: int) -> bool: pass class OSWindowSize(TypedDict): width: int height: int framebuffer_width: int framebuffer_height: int xscale: float yscale: float xdpi: float ydpi: float cell_width: int cell_height: int def mark_os_window_dirty(os_window_id: int) -> None: pass def get_os_window_size(os_window_id: int) -> Optional[OSWindowSize]: pass def get_os_window_pos(os_window_id: int) -> Tuple[int, int]: pass def set_os_window_pos(os_window_id: int, x: int, y: int) -> None: pass def get_all_processes() -> Tuple[int, ...]: pass def num_users() -> int: pass def redirect_mouse_handling(yes: bool) -> None: pass def get_click_interval() -> float: pass def send_data_to_peer(peer_id: int, data: Union[str, bytes]) -> None: pass def set_os_window_title(os_window_id: int, title: str) -> None: pass def get_os_window_title(os_window_id: int) -> Optional[str]: pass def update_ime_position_for_window(window_id: int, force: bool = False, update_focus: int = 0) -> bool: pass def shm_open(name: str, flags: int, mode: int = 0o600) -> int: pass def shm_unlink(name: str) -> None: pass def sigqueue(pid: int, signal: int, value: int) -> None: pass def read_signals(fd: int, callback: Callable[[SignalInfo], None]) -> None: pass def install_signal_handlers(*signals: int) -> Tuple[int, int]: pass def remove_signal_handlers() -> None: pass X25519: int SHA1_HASH: int SHA224_HASH: int SHA256_HASH: int SHA384_HASH: int SHA512_HASH: int class Secret: pass class EllipticCurveKey: def __init__( self, algorithm: int = 0 # X25519 ): pass def derive_secret( self, pubkey: bytes, hash_algorithm: int = 0 # SHA256_HASH ) -> Secret: pass @property def public(self) -> bytes: ... @property def private(self) -> Secret: ... class AES256GCMEncrypt: def __init__(self, key: Secret): ... def add_authenticated_but_unencrypted_data(self, data: bytes) -> None: ... def add_data_to_be_encrypted(self, data: bytes, finished: bool = False) -> bytes: ... @property def iv(self) -> bytes: ... @property def tag(self) -> bytes: ... class AES256GCMDecrypt: def __init__(self, key: Secret, iv: bytes, tag: bytes): ... def add_data_to_be_authenticated_but_not_decrypted(self, data: bytes) -> None: ... def add_data_to_be_decrypted(self, data: bytes, finished: bool = False) -> bytes: ... class Shlex: def __init__(self, src: str, allow_ansi_quoted_strings: bool = False): ... def next_word(self) -> Tuple[int, str]: ... class SingleKey: __slots__ = () def __init__(self, mods: int = 0, is_native: object = False, key: int = -1): ... def __hash__(self) -> int: ... def __len__(self) -> int: ... def __getitem__(self, x: int) -> int: ... @property def mods(self) -> int: ... @property def is_native(self) -> bool: ... @property def key(self) -> int: ... @property def defined_with_kitty_mod(self) -> bool: ... def __iter__(self) -> Iterator[int]: ... def _replace(self, mods: int = 0, is_native: object = False, key: int = -1) -> 'SingleKey': ... def resolve_kitty_mod(self, mod: int) -> 'SingleKey': ... def set_use_os_log(yes: bool) -> None: ... def get_docs_ref_map() -> bytes: ... def set_clipboard_data_types(ct: int, mime_types: Tuple[str, ...]) -> None: ... def get_clipboard_mime(ct: int, mime: Optional[str], callback: Callable[[bytes], None]) -> None: ... def run_with_activation_token(func: Callable[[str], None]) -> bool: ... def make_x11_window_a_dock_window(x11_window_id: int, strut: Tuple[int, int, int, int, int, int, int, int, int, int, int, int]) -> None: ... def wrapped_kitten_names() -> List[str]: ... def expand_ansi_c_escapes(test: str) -> str: ... def update_tab_bar_edge_colors(os_window_id: int) -> bool: ... def mask_kitty_signals_process_wide() -> None: ... def is_modifier_key(key: int) -> bool: ... def base64_encode(src: Union[str, ReadableBuffer], add_padding: bool = False) -> bytes: ... def base64_encode_into(src: Union[str, ReadableBuffer], output: WriteableBuffer, add_padding: bool = False) -> int: ... def base64_decode(src: Union[str, ReadableBuffer]) -> bytes: ... def base64_decode_into(src: Union[str, ReadableBuffer], output: WriteableBuffer) -> int: ... def cocoa_recreate_global_menu() -> None: ... def cocoa_clear_global_shortcuts() -> None: ... def update_pointer_shape(os_window_id: int) -> None: ... def os_window_focus_counters() -> Dict[int, int]: ... def find_in_memoryview(buf: Union[bytes, memoryview, bytearray], chr: int) -> int: ... @overload def replace_c0_codes_except_nl_space_tab(text: str) -> str:... @overload def replace_c0_codes_except_nl_space_tab(text: Union[bytes, memoryview, bytearray]) -> bytes:... def terminfo_data() -> bytes:... def wayland_compositor_data() -> Tuple[int, Optional[str]]:... def monotonic() -> float: ... def timed_debug_print(x: str) -> None: ... def opengl_version_string() -> str: ... def systemd_move_pid_into_new_scope(pid: int, scope_name: str, description: str) -> str: ... def play_desktop_sound_async(name: str, event_id: str = 'test sound', is_path: bool = False, theme_name: str = '') -> str: ... def cocoa_play_system_sound_by_id_async(sound_id: int) -> None: ... def glfw_get_system_color_theme(query_if_unintialized: bool = True) -> Literal['light', 'dark', 'no_preference']: ... def set_redirect_keys_to_overlay(os_window_id: int, tab_id: int, window_id: int, overlay_window_id: int) -> None: ... def buffer_keys_in_window(os_window_id: int, tab_id: int, window_id: int, enabled: bool = True) -> bool: ... def sprite_idx_to_pos(idx: int, xnum: int, ynum: int) -> tuple[int, int, int]: ... def render_box_char(ch: int, width: int, height: int, scale: float = 1.0, dpi_x: float = 96.0, dpi_y: float = 96.0) -> bytes: ... def run_at_exit_cleanup_functions() -> None: ... DecorationTypes = Literal[ 'curl', 'dashed', 'dotted', 'double', 'straight', 'strikethrough', 'beam_cursor', 'underline_cursor', 'hollow_cursor', 'missing'] def render_decoration( which: DecorationTypes, cell_width: int, cell_height: int, underline_position: int, underline_thickness: int, dpi: float = 96.0 ) -> bytes: ... def os_window_is_invisible(os_window_id: int) -> bool: ... class MousePosition(TypedDict): cell_x: int cell_y: int in_left_half_of_cell: bool def get_mouse_data_for_window(os_window_id: int, tab_id: int, window_id: int) -> Optional[MousePosition]: ... class StreamingBase64Decoder: # reset the state to empty to start decoding a new stream def reset(self) -> None: ... # decode the specified data def decode(self, data: ReadableBuffer) -> bytes: ... # decode the specified data, return number of bytes written dest should be as large as src (technically 3/4 src + 2) def decode_into(self, dest: WriteableBuffer, src: ReadableBuffer) -> int: ... # whether the data stream decoded so far is complete or not def needs_more_data(self) -> bool: ... class StreamingBase64Encodeer: def __init__(self, add_trailing_bytes: bool = True) -> None: ... # encode the specified data def encode(self, data: ReadableBuffer) -> bytes: ... # reset the state to empty to start encoding a new stream, return any trailing bytes from the previous encode call def reset(self) -> bytes: ... # encode the specified data, return number of bytes written dest should be at least 4/3 *src + 2 bytes in size def encode_into(self, dest: WriteableBuffer, src: ReadableBuffer) -> int: ... class DiskCache: small_hole_threshold: int defrag_factor: int @property def total_size(self) -> int: ... def add(self, key: bytes, data: bytes) -> None: ... def remove(self, key: bytes) -> bool: ... def remove_from_ram(self, predicate: Callable[[bytes], bool]) -> int: ... def num_cached_in_ram(self) -> int: ... def get(self, key: bytes, store_in_ram: bool = False) -> bytes: ... # raises KeyError if not found def size_on_disk(self) -> int: ... def clear(self) -> None: ... kitty-0.41.1/kitty/file_transmission.py0000664000175000017510000014211414773370543017560 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import errno import inspect import io import json import os import re import stat import tempfile from base64 import b85decode from collections import defaultdict, deque from collections.abc import Callable, Iterable, Iterator from contextlib import suppress from dataclasses import Field, dataclass, field, fields from enum import Enum, auto from functools import partial from gettext import gettext as _ from itertools import count from time import time_ns from typing import IO, Any, DefaultDict, Deque, Union from kittens.transfer.utils import IdentityCompressor, ZlibCompressor, abspath, expand_home, home_path from kitty.fast_data_types import ESC_OSC, FILE_TRANSFER_CODE, AES256GCMDecrypt, add_timer, base64_decode, base64_encode, get_boss, get_options, monotonic from kitty.types import run_once from kitty.typing import ReadableBuffer, WriteableBuffer from .utils import log_error EXPIRE_TIME = 10 # minutes MAX_ACTIVE_RECEIVES = MAX_ACTIVE_SENDS = 10 ftc_prefix = str(FILE_TRANSFER_CODE) @run_once def safe_string_pat() -> 're.Pattern[str]': return re.compile(r'[^0-9a-zA-Z_:./@-]') def safe_string(x: str) -> str: return safe_string_pat().sub('', x) def as_unicode(x: str | bytes) -> str: if isinstance(x, bytes): x = x.decode('ascii') return x def encode_bypass(request_id: str, bypass: str) -> str: import hashlib q = request_id + ';' + bypass return 'sha256:' + hashlib.sha256(q.encode('utf-8', 'replace')).hexdigest() def split_for_transfer( data: bytes | bytearray | memoryview, session_id: str = '', file_id: str = '', mark_last: bool = False, chunk_size: int = 4096 ) -> Iterator['FileTransmissionCommand']: if isinstance(data, (bytes, bytearray)): data = memoryview(data) while len(data): ac = Action.data if mark_last and len(data) <= chunk_size: ac = Action.end_data yield FileTransmissionCommand(action=ac, id=session_id, file_id=file_id, data=data[:chunk_size]) data = data[chunk_size:] def iter_file_metadata(file_specs: Iterable[tuple[str, str]]) -> Iterator[Union['FileTransmissionCommand', 'TransmissionError']]: file_map: DefaultDict[tuple[int, int], list[FileTransmissionCommand]] = defaultdict(list) counter = count() def skey(sr: os.stat_result) -> tuple[int, int]: return sr.st_dev, sr.st_ino def make_ftc(path: str, spec_id: str, sr: os.stat_result | None = None, parent: str = '') -> FileTransmissionCommand: if sr is None: sr = os.stat(path, follow_symlinks=False) if stat.S_ISLNK(sr.st_mode): ftype = FileType.symlink elif stat.S_ISDIR(sr.st_mode): ftype = FileType.directory elif stat.S_ISREG(sr.st_mode): ftype = FileType.regular else: raise ValueError('Not an appropriate file type') ans = FileTransmissionCommand( action=Action.file, file_id=spec_id, mtime=sr.st_mtime_ns, permissions=stat.S_IMODE(sr.st_mode), name=path, status=str(next(counter)), size=sr.st_size, ftype=ftype, parent=parent ) file_map[skey(sr)].append(ans) return ans def add_dir(ftc: FileTransmissionCommand) -> None: try: lr = os.listdir(ftc.name) except OSError: return for entry in lr: try: child_ftc = make_ftc(os.path.join(ftc.name, entry), spec_id, parent=ftc.status) except (ValueError, OSError): continue if child_ftc.ftype is FileType.directory: add_dir(child_ftc) for spec_id, spec in file_specs: path = spec if not os.path.isabs(path): path = expand_home(path) if not os.path.isabs(path): path = abspath(path, use_home=True) try: sr = os.stat(path, follow_symlinks=False) read_ok = os.access(path, os.R_OK, follow_symlinks=False) except OSError as err: errname = errno.errorcode.get(err.errno, 'EFAIL') if err.errno is not None else 'EFAIL' yield TransmissionError(file_id=spec_id, code=errname, msg='Failed to read spec') continue if not read_ok: yield TransmissionError(file_id=spec_id, code='EPERM', msg='No permission to read spec') continue try: ftc = make_ftc(path, spec_id, sr) except ValueError: yield TransmissionError(file_id=spec_id, code='EINVAL', msg='Not a valid filetype') continue if ftc.ftype is FileType.directory: add_dir(ftc) def resolve_symlink(ftc: FileTransmissionCommand) -> FileTransmissionCommand: if ftc.ftype is FileType.symlink: try: dest = os.path.realpath(ftc.name) except OSError: pass else: try: s = os.stat(dest, follow_symlinks=False) except OSError: pass else: tgt = file_map.get(skey(s)) if tgt is not None: ftc.data = tgt[0].status.encode('utf-8') return ftc for fkey, cmds in file_map.items(): base = cmds[0] yield resolve_symlink(base) if len(cmds) > 1 and base.ftype is FileType.regular: for q in cmds: if q is not base and q.ftype is FileType.regular: q.ftype = FileType.link q.data = base.status.encode('utf-8', 'replace') yield q class NameReprEnum(Enum): def __repr__(self) -> str: return f'<{self.__class__.__name__}.{self.name}>' class Action(NameReprEnum): send = auto() file = auto() data = auto() end_data = auto() receive = auto() invalid = auto() cancel = auto() status = auto() finish = auto() class Compression(NameReprEnum): zlib = auto() none = auto() class FileType(NameReprEnum): regular = auto() directory = auto() symlink = auto() link = auto() @property def short_text(self) -> str: return {FileType.regular: 'fil', FileType.directory: 'dir', FileType.symlink: 'sym', FileType.link: 'lnk'}[self] @property def color(self) -> str: return {FileType.regular: 'yellow', FileType.directory: 'magenta', FileType.symlink: 'blue', FileType.link: 'green'}[self] class TransmissionType(NameReprEnum): simple = auto() rsync = auto() ErrorCode = Enum('ErrorCode', 'OK STARTED CANCELED PROGRESS EINVAL EPERM EISDIR ENOENT') class TransmissionError(Exception): def __init__( self, code: ErrorCode | str = ErrorCode.EINVAL, msg: str = 'Generic error', transmit: bool = True, file_id: str = '', name: str = '', size: int = -1, ttype: TransmissionType = TransmissionType.simple, ) -> None: super().__init__(msg) self.transmit = transmit self.file_id = file_id self.human_msg = msg self.code = code self.name = name self.size = size self.ttype = ttype def as_ftc(self, request_id: str) -> 'FileTransmissionCommand': name = self.code if isinstance(self.code, str) else self.code.name if self.human_msg: name += ':' + self.human_msg return FileTransmissionCommand( action=Action.status, id=request_id, file_id=self.file_id, status=name, name=self.name, size=self.size, ttype=self.ttype ) @run_once def name_to_serialized_map() -> dict[str, str]: ans: dict[str, str] = {} for k in fields(FileTransmissionCommand): ans[k.name] = k.metadata.get('sname', k.name) return ans @run_once def serialized_to_field_map() -> dict[bytes, 'Field[Any]']: ans: dict[bytes, 'Field[Any]'] = {} for k in fields(FileTransmissionCommand): ans[k.metadata.get('sname', k.name).encode('ascii')] = k return ans @dataclass class FileTransmissionCommand: action: Action = field(default=Action.invalid, metadata={'sname': 'ac'}) compression: Compression = field(default=Compression.none, metadata={'sname': 'zip'}) ftype: FileType = field(default=FileType.regular, metadata={'sname': 'ft'}) ttype: TransmissionType = field(default=TransmissionType.simple, metadata={'sname': 'tt'}) id: str = '' file_id: str = field(default='', metadata={'sname': 'fid'}) bypass: str = field(default='', metadata={'base64': True, 'sname': 'pw'}) quiet: int = field(default=0, metadata={'sname': 'q'}) mtime: int = field(default=-1, metadata={'sname': 'mod'}) permissions: int = field(default=-1, metadata={'sname': 'prm'}) size: int = field(default=-1, metadata={'sname': 'sz'}) name: str = field(default='', metadata={'base64': True, 'sname': 'n'}) status: str = field(default='', metadata={'base64': True, 'sname': 'st'}) parent: str = field(default='', metadata={'sname': 'pr'}) data: bytes = field(default=b'', repr=False, metadata={'sname': 'd'}) def __repr__(self) -> str: ans = [] for k in fields(self): if not k.repr: continue val = getattr(self, k.name) if val != k.default: ans.append(f'{k.name}={val!r}') if self.data: ans.append(f'data={len(self.data)} bytes') return 'FTC(' + ', '.join(ans) + ')' def asdict(self, keep_defaults: bool = False) -> dict[str, str | int | bytes]: ans = {} for k in fields(self): val = getattr(self, k.name) if not keep_defaults and val == k.default: continue if inspect.isclass(k.type) and issubclass(k.type, Enum): val = val.name ans[k.name] = val return ans def get_serialized_fields(self, prefix_with_osc_code: bool = False) -> Iterator[str | bytes]: nts = name_to_serialized_map() found = False if prefix_with_osc_code: yield ftc_prefix found = True for k in fields(self): name = k.name val = getattr(self, name) if val == k.default: continue if found: yield ';' else: found = True yield nts[name] yield '=' if inspect.isclass(k.type) and issubclass(k.type, Enum): yield val.name elif k.type is bytes: yield base64_encode(val) elif k.type is str: if k.metadata.get('base64'): yield base64_encode(val.encode('utf-8')) else: yield safe_string(val) elif k.type is int: yield str(val) else: raise KeyError(f'Field of unknown type: {k.name}') def serialize(self, prefix_with_osc_code: bool = False) -> str: return ''.join(map(as_unicode, self.get_serialized_fields(prefix_with_osc_code))) @classmethod def deserialize(cls, data: str | bytes | memoryview) -> 'FileTransmissionCommand': ans = FileTransmissionCommand() fmap = serialized_to_field_map() from kittens.transfer.rsync import parse_ftc def handle_item(key: memoryview, val: memoryview) -> None: field = fmap.get(key) if field is None: return if inspect.isclass(field.type) and issubclass(field.type, Enum): setattr(ans, field.name, field.type[str(val, "utf-8")]) elif field.type is bytes: setattr(ans, field.name, base64_decode(val)) elif field.type is int: setattr(ans, field.name, int(val)) elif field.type is str: if field.metadata.get('base64'): sval = base64_decode(val).decode('utf-8') else: sval = safe_string(str(val, "utf-8")) setattr(ans, field.name, sval) parse_ftc(data, handle_item) if ans.action is Action.invalid: raise ValueError('No valid action specified in file transmission command') return ans class IdentityDecompressor: def __call__(self, data: bytes, is_last: bool = False) -> bytes: return data class ZlibDecompressor: def __init__(self) -> None: import zlib self.d = zlib.decompressobj(wbits=0) def __call__(self, data: bytes, is_last: bool = False) -> bytes: ans = self.d.decompress(data) if is_last: ans += self.d.flush() return ans class PatchFile: def __init__(self, path: str, expected_size: int): from kittens.transfer.rsync import Patcher self.patcher = Patcher(expected_size) self.block_buffer = memoryview(bytearray(self.patcher.block_size)) self.path = path self.signature_done = False self.src_file: io.BufferedReader | None = None self._dest_file: IO[bytes] | None = None self.closed = False @property def dest_file(self) -> IO[bytes]: if self._dest_file is None: self._dest_file = tempfile.NamedTemporaryFile(mode='wb', dir=os.path.dirname(os.path.abspath(os.path.realpath(self.path))), delete=False) return self._dest_file def close(self) -> None: if self.closed: return self.closed = True p = self.patcher del self.block_buffer, self.patcher if self._dest_file is not None and not self._dest_file.closed: self._dest_file.close() p.finish_delta_data() if self.src_file is not None: os.replace(self.dest_file.name, self.src_file.name) if self.src_file is not None and not self.src_file.closed: self.src_file.close() def tell(self) -> int: df = self.dest_file if df.closed: return os.path.getsize(self.path) return df.tell() def read_from_src(self, pos: int, b: WriteableBuffer) -> int: assert self.src_file is not None self.src_file.seek(pos, os.SEEK_SET) return self.src_file.readinto(b) def write_to_dest(self, b: ReadableBuffer) -> None: self.dest_file.write(b) def write(self, b: bytes) -> None: self.patcher.apply_delta_data(b, self.read_from_src, self.write_to_dest) def next_signature_block(self, buf: memoryview) -> int: if self.signature_done: return 0 if self.src_file is None: self.src_file = open(self.path, 'rb') return self.patcher.signature_header(buf) n = self.src_file.readinto(self.block_buffer) if n > 0: n = self.patcher.sign_block(self.block_buffer[:n], buf) else: self.src_file.seek(0, os.SEEK_SET) self.signature_done = True return n class DestFile: def __init__(self, ftc: FileTransmissionCommand) -> None: self.name = ftc.name if not os.path.isabs(self.name): self.name = expand_home(self.name) if not os.path.isabs(self.name): self.name = abspath(self.name, use_home=True) try: self.existing_stat: os.stat_result | None = os.stat(self.name, follow_symlinks=False) except OSError: self.existing_stat = None self.needs_unlink = self.existing_stat is not None and (self.existing_stat.st_nlink > 1 or stat.S_ISLNK(self.existing_stat.st_mode)) self.mtime = ftc.mtime self.file_id = ftc.file_id self.permissions = ftc.permissions if self.permissions != FileTransmissionCommand.permissions: self.permissions = stat.S_IMODE(self.permissions) self.ftype = ftc.ftype self.ttype = ftc.ttype self.link_target = b'' self.needs_data_sent = self.ttype is not TransmissionType.simple self.decompressor: ZlibDecompressor | IdentityDecompressor = ZlibDecompressor() if ftc.compression is Compression.zlib else IdentityDecompressor() self.closed = self.ftype is FileType.directory self.actual_file: PatchFile | IO[bytes] | None = None self.failed = False self.bytes_written = 0 def signature_iterator(self) -> PatchFile: self.actual_file = PatchFile(self.name, self.existing_stat.st_size if self.existing_stat is not None else 0) return self.actual_file def __repr__(self) -> str: return f'DestFile(name={self.name}, file_id={self.file_id}, actual_file={self.actual_file})' def close(self) -> None: if not self.closed: self.closed = True if self.actual_file is not None: self.actual_file.close() self.actual_file = None def make_parent_dirs(self) -> str: d = os.path.dirname(self.name) if d: os.makedirs(d, exist_ok=True) return d def apply_metadata(self, is_symlink: bool = False) -> None: if self.permissions != FileTransmissionCommand.permissions: if is_symlink: with suppress(NotImplementedError): os.chmod(self.name, self.permissions, follow_symlinks=False) else: os.chmod(self.name, self.permissions) if self.mtime != FileTransmissionCommand.mtime: if is_symlink: with suppress(NotImplementedError): os.utime(self.name, ns=(self.mtime, self.mtime), follow_symlinks=False) else: os.utime(self.name, ns=(self.mtime, self.mtime)) def unlink_existing_if_needed(self, force: bool = False) -> None: if force or self.needs_unlink: with suppress(FileNotFoundError): os.unlink(self.name) self.existing_stat = None self.needs_unlink = False def write_data(self, all_files: dict[str, 'DestFile'], data: bytes, is_last: bool) -> None: if self.ftype is FileType.directory: raise TransmissionError(code=ErrorCode.EISDIR, file_id=self.file_id, msg='Cannot write data to a directory entry') if self.closed: raise TransmissionError(file_id=self.file_id, msg='Cannot write to a closed file') if self.ftype in (FileType.symlink, FileType.link): self.link_target += data self.bytes_written += len(data) if is_last: lt = self.link_target.decode('utf-8', 'replace') base = self.make_parent_dirs() self.unlink_existing_if_needed(force=True) if lt.startswith('fid:'): lt = all_files[lt[4:]].name if self.ftype is FileType.symlink: lt = os.path.relpath(lt, os.path.dirname(self.name)) elif lt.startswith('fid_abs:'): lt = all_files[lt[8:]].name elif lt.startswith('path:'): lt = lt[5:] if not os.path.isabs(lt) and self.ftype is FileType.link: lt = os.path.join(base, lt) lt = lt.replace('/', os.sep) else: raise TransmissionError(msg='Unknown link target type', file_id=self.file_id) if self.ftype is FileType.symlink: os.symlink(lt, self.name) else: os.link(lt, self.name) self.close() self.apply_metadata(is_symlink=True) elif self.ftype is FileType.regular: decompressed = self.decompressor(data, is_last=is_last) if self.actual_file is None: self.make_parent_dirs() self.unlink_existing_if_needed() flags = os.O_RDWR | os.O_CREAT | os.O_TRUNC | getattr(os, 'O_CLOEXEC', 0) | getattr(os, 'O_BINARY', 0) self.actual_file = open(os.open(self.name, flags, self.permissions), mode='r+b', closefd=True) af = self.actual_file if decompressed or is_last: af.write(decompressed) self.bytes_written = af.tell() if is_last: self.close() self.apply_metadata() def check_bypass(password: str, request_id: str, bypass_data: str) -> bool: protocol, sep, bypass_data = bypass_data.partition(':') if protocol == 'kitty-1': try: pcmd = json.loads(bypass_data) pubkey = pcmd.get('pubkey', '') if not pubkey: return False ekey = get_boss().encryption_key d = AES256GCMDecrypt(ekey.derive_secret(b85decode(pubkey)), b85decode(pcmd['iv']), b85decode(pcmd['tag'])) data = d.add_data_to_be_decrypted(b85decode(pcmd['encrypted']), True) timestamp, sep, payload = data.decode('utf-8').partition(':') delta = time_ns() - int(timestamp) if abs(delta) > 5 * 60 * 1e9: return False return payload == f'{request_id};{password}' except Exception as err: log_error(f'Invalid file transmission bypass data received: {err}') return False elif protocol == 'sha256': return (encode_bypass(request_id, password) == bypass_data) if password else False else: log_error(f'Invalid file transmission bypass data received with protocol: {protocol}') return False class ActiveReceive: id: str files: dict[str, DestFile] accepted: bool = False def __init__(self, request_id: str, quiet: int, bypass: str) -> None: self.id = request_id self.bypass_ok: bool | None = None if bypass: byp = get_options().file_transfer_confirmation_bypass self.bypass_ok = check_bypass(byp, request_id, bypass) self.files = {} self.last_activity_at = monotonic() self.send_acknowledgements = quiet < 1 self.send_errors = quiet < 2 self.pending_files_to_transmit_signature_of: Deque[tuple[PatchFile, str]] = deque() self.signature_pending_chunks: Deque[FileTransmissionCommand] = deque() @property def is_expired(self) -> bool: return monotonic() - self.last_activity_at > (60 * EXPIRE_TIME) def close(self) -> None: for x in self.files.values(): x.close() self.files = {} def cancel(self) -> None: self.close() def start_file(self, ftc: FileTransmissionCommand) -> DestFile: self.last_activity_at = monotonic() if ftc.file_id in self.files: raise TransmissionError( msg=f'The file_id {ftc.file_id} already exists', file_id=ftc.file_id, ) self.files[ftc.file_id] = df = DestFile(ftc) return df def add_data(self, ftc: FileTransmissionCommand) -> DestFile: self.last_activity_at = monotonic() df = self.files.get(ftc.file_id) if df is None: raise TransmissionError(file_id=ftc.file_id, msg='Cannot write to a file without first starting it') if df.failed: return df try: df.write_data(self.files, ftc.data, ftc.action is Action.end_data) except Exception: df.failed = True with suppress(Exception): df.close() raise return df def commit(self, send_os_error: Callable[[OSError, str, 'ActiveReceive', str], None]) -> None: directories = sorted((df for df in self.files.values() if df.ftype is FileType.directory), key=lambda x: len(x.name), reverse=True) for df in directories: with suppress(OSError): # we ignore failures to apply directory metadata as we have already sent an OK for the dir df.apply_metadata() class SourceFile: def __init__(self, ftc: FileTransmissionCommand): self.file_id = ftc.file_id self.path = ftc.name self.ttype = ftc.ttype self.waiting_for_signature = True if self.ttype is TransmissionType.rsync else False self.transmitted = False self.stat = os.stat(self.path, follow_symlinks=False) if stat.S_ISDIR(self.stat.st_mode): raise TransmissionError(ErrorCode.EINVAL, msg='Cannot send a directory', file_id=self.file_id) self.compressor: ZlibCompressor | IdentityCompressor = IdentityCompressor() self.target = b'' self.open_file: io.BufferedReader | None = None if stat.S_ISLNK(self.stat.st_mode): self.target = os.readlink(self.path).encode('utf-8') else: self.open_file = open(self.path, 'rb') if ftc.compression is Compression.zlib: self.compressor = ZlibCompressor() from kittens.transfer import rsync self.differ = rsync.Differ() if self.waiting_for_signature else None self.buf = bytearray() self.write_pos = 0 def write(self, b: ReadableBuffer) -> None: self.buf[self.write_pos:self.write_pos+len(b)] = b self.write_pos += len(b) @property def ready_to_transmit(self) -> bool: return not self.transmitted and not self.waiting_for_signature def close(self) -> None: if self.open_file is not None: self.open_file.close() self.open_file = None self.differ = None def next_chunk(self, sz: int = 1024 * 1024) -> tuple[bytes, int]: if self.target: self.transmitted = True data = self.target else: if self.open_file is None: self.transmitted = True data = b'' else: if self.differ is None: data = self.open_file.read(sz) if not data or self.open_file.tell() >= self.stat.st_size: self.transmitted = True else: self.write_pos = 0 has_more = self.differ.next_op(self.open_file.readinto, self.write) data = memoryview(self.buf)[:self.write_pos] if not has_more: self.transmitted = True uncompressed_sz = len(data) cchunk = self.compressor.compress(data) if self.transmitted and not isinstance(self.compressor, IdentityCompressor): cchunk += self.compressor.flush() if self.transmitted: self.close() return cchunk, uncompressed_sz class ActiveSend: def __init__(self, request_id: str, quiet: int, bypass: str, num_of_args: int) -> None: self.id = request_id self.expected_num_of_args = num_of_args self.bypass_ok: bool | None = None if bypass: byp = get_options().file_transfer_confirmation_bypass self.bypass_ok = check_bypass(byp, request_id, bypass) self.accepted = False self.last_activity_at = monotonic() self.send_acknowledgements = quiet < 1 self.send_errors = quiet < 2 self.last_activity_at = monotonic() self.file_specs: list[tuple[str, str]] = [] self.queued_files_map: dict[str, SourceFile] = {} self.active_file: SourceFile | None = None self.pending_chunks: Deque[FileTransmissionCommand] = deque() self.metadata_sent = False @property def spec_complete(self) -> bool: return self.expected_num_of_args <= len(self.file_specs) def add_file_spec(self, cmd: FileTransmissionCommand) -> None: self.last_activity_at = monotonic() if len(self.file_specs) > 8192 or self.spec_complete: raise TransmissionError(ErrorCode.EINVAL, 'Too many file specs') self.file_specs.append((cmd.file_id, cmd.name)) def add_send_file(self, cmd: FileTransmissionCommand) -> None: self.last_activity_at = monotonic() if len(self.queued_files_map) > 32768: raise TransmissionError(ErrorCode.EINVAL, 'Too many queued files') self.queued_files_map[cmd.file_id] = SourceFile(cmd) def add_signature_data(self, cmd: FileTransmissionCommand) -> None: self.last_activity_at = monotonic() af = self.queued_files_map.get(cmd.file_id) if af is None: raise TransmissionError(ErrorCode.EINVAL, f'Signature data for unknown file_id: {cmd.file_id}') sl = af.differ if sl is None: raise TransmissionError(ErrorCode.EINVAL, f'Signature data for file that is not using rsync: {cmd.file_id}') sl.add_signature_data(cmd.data) if cmd.action is Action.end_data: sl.finish_signature_data() af.waiting_for_signature = False @property def is_expired(self) -> bool: return monotonic() - self.last_activity_at > (60 * EXPIRE_TIME) def close(self) -> None: if self.active_file is not None: self.active_file.close() self.active_file = None def next_chunk(self) -> FileTransmissionCommand | None: self.last_activity_at = monotonic() if self.pending_chunks: return self.pending_chunks.popleft() af = self.active_file if af is None: for f in self.queued_files_map.values(): if f.ready_to_transmit: self.active_file = af = f break if af is None: return None self.queued_files_map.pop(af.file_id, None) while True: chunk, uncompressed_sz = af.next_chunk() if af.transmitted: self.active_file = None break if chunk: break if chunk: self.pending_chunks.extend(split_for_transfer(chunk, file_id=af.file_id, mark_last=af.transmitted)) return self.pending_chunks.popleft() elif af.transmitted: return FileTransmissionCommand(action=Action.end_data, file_id=af.file_id) return None def return_chunk(self, ftc: FileTransmissionCommand) -> None: self.pending_chunks.insert(0, ftc) class FileTransmission: def __init__(self, window_id: int): self.window_id = window_id self.active_receives: dict[str, ActiveReceive] = {} self.active_sends: dict[str, ActiveSend] = {} self.pending_receive_responses: Deque[FileTransmissionCommand] = deque() self.pending_timer: int | None = None def callback_after(self, callback: Callable[[int | None], None], timeout: float = 0) -> int | None: return add_timer(callback, timeout, False) def start_pending_timer(self) -> None: if self.pending_timer is None: self.pending_timer = self.callback_after(self.try_pending, 0.2) def try_pending(self, timer_id: int | None) -> None: self.pending_timer = None while self.pending_receive_responses: payload = self.pending_receive_responses.popleft() ar = self.active_receives.get(payload.id) if ar is None: continue if not self.write_ftc_to_child(payload, appendleft=True): break ar.last_activity_at = monotonic() self.prune_expired() def __del__(self) -> None: for ar in self.active_receives.values(): ar.close() self.active_receives = {} for a in self.active_sends.values(): a.close() self.active_receives = {} self.active_sends = {} def drop_receive(self, receive_id: str) -> None: ar = self.active_receives.pop(receive_id, None) if ar is not None: ar.close() def drop_send(self, send_id: str) -> None: a = self.active_sends.pop(send_id, None) if a is not None: a.close() def prune_expired(self) -> None: for k in tuple(self.active_receives): if self.active_receives[k].is_expired: self.drop_receive(k) for a in tuple(self.active_sends): if self.active_sends[a].is_expired: self.drop_send(a) def handle_serialized_command(self, data: memoryview) -> None: try: cmd = FileTransmissionCommand.deserialize(data) except Exception as e: log_error(f'Failed to parse file transmission command with error: {e}') return # print('from kitten:', cmd) if not cmd.id: log_error('File transmission command without id received, ignoring') return if cmd.action is Action.cancel: if cmd.id in self.active_receives: self.handle_receive_cmd(cmd) return if cmd.id in self.active_sends: self.handle_send_cmd(cmd) return self.prune_expired() if cmd.id in self.active_receives or cmd.action is Action.send: self.handle_receive_cmd(cmd) if cmd.id in self.active_sends or cmd.action is Action.receive: self.handle_send_cmd(cmd) def handle_send_cmd(self, cmd: FileTransmissionCommand) -> None: if cmd.id in self.active_sends: asd = self.active_sends[cmd.id] if cmd.action is Action.receive: log_error('File transmission receive received for already active id, aborting') self.drop_send(cmd.id) return if cmd.action is Action.file: try: asd.add_send_file(cmd) if asd.metadata_sent else asd.add_file_spec(cmd) except OSError as err: self.send_fail_on_os_error(err, 'Failed to add send file', asd, cmd.file_id) self.drop_send(asd.id) return except TransmissionError as err: self.drop_send(asd.id) if asd.send_errors: self.send_transmission_error(asd.id, err) return if asd.metadata_sent: self.pump_send_chunks(asd) else: if asd.spec_complete and asd.accepted: self.send_metadata_for_send_transfer(asd) return if cmd.action in (Action.data, Action.end_data): try: asd.add_signature_data(cmd) except TransmissionError as err: self.drop_send(asd.id) if asd.send_errors: self.send_transmission_error(asd.id, err) else: self.pump_send_chunks(asd) elif cmd.action in (Action.status, Action.finish): self.drop_send(asd.id) return if not asd.accepted: log_error(f'File transmission command {cmd.action} received for pending id: {cmd.id}, aborting') self.drop_send(cmd.id) return asd.last_activity_at = monotonic() else: if cmd.action is not Action.receive: log_error(f'File transmission command {cmd.action} received for unknown or rejected id: {cmd.id}, ignoring') return if len(self.active_sends) >= MAX_ACTIVE_SENDS: log_error('New File transmission send with too many active receives, ignoring') return asd = self.active_sends[cmd.id] = ActiveSend(cmd.id, cmd.quiet, cmd.bypass, cmd.size) self.start_send(asd.id) return if cmd.action is Action.cancel: self.drop_send(asd.id) if asd.send_acknowledgements: self.send_status_response(ErrorCode.CANCELED, request_id=asd.id) def send_metadata_for_send_transfer(self, asd: ActiveSend) -> None: sent = False for ftc in iter_file_metadata(asd.file_specs): if isinstance(ftc, TransmissionError): sent = True if asd.send_errors: self.send_transmission_error(asd.id, ftc) else: ftc.id = asd.id self.write_ftc_to_child(ftc) sent = True if sent: self.send_status_response(code=ErrorCode.OK, request_id=asd.id, name=home_path()) asd.metadata_sent = True else: self.send_status_response(code=ErrorCode.ENOENT, request_id=asd.id, msg='No files found') self.drop_send(asd.id) def pump_send_chunks(self, asd: ActiveSend) -> None: while True: try: ftc = asd.next_chunk() except OSError as err: fid = asd.active_file.file_id if asd.active_file else '' self.send_fail_on_os_error(err, 'Failed to read data from file', asd, file_id=fid) self.drop_send(asd.id) break if ftc is None: break ftc.id = asd.id if not self.write_ftc_to_child(ftc, use_pending=False): asd.return_chunk(ftc) self.callback_after(self.pump_sends, 0.05) break def pump_sends(self, timer_id: int | None) -> None: for asd in self.active_sends.values(): if asd.metadata_sent: self.pump_send_chunks(asd) def handle_receive_cmd(self, cmd: FileTransmissionCommand) -> None: if cmd.id in self.active_receives: ar = self.active_receives[cmd.id] if cmd.action is Action.send: log_error('File transmission send received for already active id, aborting') self.drop_receive(cmd.id) return if not ar.accepted: log_error(f'File transmission command {cmd.action} received for pending id: {cmd.id}, aborting') self.drop_receive(cmd.id) return ar.last_activity_at = monotonic() else: if cmd.action is not Action.send: log_error(f'File transmission command {cmd.action} received for unknown or rejected id: {cmd.id}, ignoring') return if len(self.active_receives) >= MAX_ACTIVE_RECEIVES: log_error('New File transmission send with too many active receives, ignoring') return ar = self.active_receives[cmd.id] = ActiveReceive(cmd.id, cmd.quiet, cmd.bypass) self.start_receive(ar.id) return if cmd.action is Action.cancel: self.drop_receive(ar.id) if ar.send_acknowledgements: self.send_status_response(ErrorCode.CANCELED, request_id=ar.id) elif cmd.action is Action.file: try: df = ar.start_file(cmd) except TransmissionError as err: if ar.send_errors: self.send_transmission_error(ar.id, err) except Exception as err: log_error(f'Transmission protocol failed to start file with error: {err}') if ar.send_errors: te = TransmissionError(file_id=cmd.file_id, msg=str(err)) self.send_transmission_error(ar.id, te) else: if df.ftype is FileType.directory: try: os.makedirs(df.name, exist_ok=True) except OSError as err: self.send_fail_on_os_error(err, 'Failed to create directory', ar, df.file_id) else: self.send_status_response(ErrorCode.OK, ar.id, df.file_id, name=df.name) else: if ar.send_acknowledgements: sz = df.existing_stat.st_size if df.existing_stat is not None else -1 ttype = TransmissionType.rsync \ if sz > -1 and df.ttype is TransmissionType.rsync and df.ftype is FileType.regular else TransmissionType.simple self.send_status_response(code=ErrorCode.STARTED, request_id=ar.id, file_id=df.file_id, name=df.name, size=sz, ttype=ttype) df.ttype = ttype if ttype is TransmissionType.rsync: try: fs = df.signature_iterator() except OSError as err: self.send_fail_on_os_error(err, 'Failed to open file to read signature', ar, df.file_id) else: ar.pending_files_to_transmit_signature_of.append((fs, df.file_id)) self.callback_after(partial(self.transmit_rsync_signature, ar.id)) elif cmd.action in (Action.data, Action.end_data): try: before = 0 bf = ar.files.get(cmd.file_id) if bf is not None: before = bf.bytes_written df = ar.add_data(cmd) if df.failed: return if ar.send_acknowledgements: if df.closed: self.send_status_response( code=ErrorCode.OK, request_id=ar.id, file_id=df.file_id, name=df.name, size=df.bytes_written) elif df.bytes_written > before: self.send_status_response( code=ErrorCode.PROGRESS, request_id=ar.id, file_id=df.file_id, size=df.bytes_written) except TransmissionError as err: if ar.send_errors: self.send_transmission_error(ar.id, err) except Exception as err: import traceback st = traceback.format_exc() log_error(f'Transmission protocol failed to write data to file with error: {st}') if ar.send_errors: te = TransmissionError(file_id=cmd.file_id, msg=str(err)) self.send_transmission_error(ar.id, te) elif cmd.action is Action.finish: try: ar.commit(self.send_fail_on_os_error) except TransmissionError as err: if ar.send_errors: self.send_transmission_error(ar.id, err) except Exception as err: log_error(f'Transmission protocol failed to commit receive with error: {err}') if ar.send_errors: te = TransmissionError(msg=str(err)) self.send_transmission_error(ar.id, te) finally: self.drop_receive(ar.id) else: log_error(f'Transmission receive command with unknown action: {cmd.action}, ignoring') def transmit_rsync_signature(self, receive_id: str, timer_id: int | None = None) -> None: q = self.active_receives.get(receive_id) if q is None: return ar = q # for mypy while ar.signature_pending_chunks: if self.write_ftc_to_child(ar.signature_pending_chunks[0], use_pending=False): ar.signature_pending_chunks.popleft() else: self.callback_after(partial(self.transmit_rsync_signature, receive_id), timeout=0.1) return if not ar.pending_files_to_transmit_signature_of: return fs, file_id = ar.pending_files_to_transmit_signature_of[0] pos = 0 buf = memoryview(bytearray(4096)) is_finished = False while len(buf) >= pos + 32: try: n = fs.next_signature_block(buf[pos:]) except OSError as err: if ar.send_errors: self.send_fail_on_os_error(err, 'Failed to read signature', ar, file_id) return if not n: is_finished = True ar.pending_files_to_transmit_signature_of.popleft() break pos += n chunk = buf[:pos] has_capacity = True def write_ftc(data: FileTransmissionCommand) -> None: nonlocal has_capacity if has_capacity: if not self.write_ftc_to_child(data, use_pending=False): has_capacity = False ar.signature_pending_chunks.append(data) else: ar.signature_pending_chunks.append(data) if len(chunk): for data in split_for_transfer(chunk, session_id=receive_id, file_id=file_id): write_ftc(data) if is_finished: endftc = FileTransmissionCommand(id=receive_id, action=Action.end_data, file_id=file_id) write_ftc(endftc) self.callback_after(partial(self.transmit_rsync_signature, receive_id)) def send_status_response( self, code: ErrorCode | str = ErrorCode.EINVAL, request_id: str = '', file_id: str = '', msg: str = '', name: str = '', size: int = -1, ttype: TransmissionType = TransmissionType.simple, ) -> bool: err = TransmissionError(code=code, msg=msg, file_id=file_id, name=name, size=size, ttype=ttype) return self.write_ftc_to_child(err.as_ftc(request_id)) def send_transmission_error(self, request_id: str, err: TransmissionError) -> bool: if err.transmit: return self.write_ftc_to_child(err.as_ftc(request_id)) return True def write_ftc_to_child(self, payload: FileTransmissionCommand, appendleft: bool = False, use_pending: bool = True) -> bool: boss = get_boss() window = boss.window_id_map.get(self.window_id) if window is not None: data = tuple(payload.get_serialized_fields(prefix_with_osc_code=True)) queued = window.screen.send_escape_code_to_child(ESC_OSC, data) if not queued: if use_pending: if appendleft: self.pending_receive_responses.appendleft(payload) else: self.pending_receive_responses.append(payload) self.start_pending_timer() return queued return False def start_send(self, asd_id: str) -> None: asd = self.active_sends[asd_id] if asd.bypass_ok is not None: self.handle_receive_confirmation(asd.bypass_ok, asd_id) return boss = get_boss() window = boss.window_id_map.get(self.window_id) if window is not None: boss.confirm(_( 'The remote machine wants to read some files from this computer. Do you want to allow the transfer?'), self.handle_receive_confirmation, asd_id, window=window, ) def handle_receive_confirmation(self, confirmed: bool, cmd_id: str) -> None: asd = self.active_sends.get(cmd_id) if asd is None: return if confirmed: asd.accepted = True else: self.drop_send(asd.id) if asd.accepted: if asd.send_acknowledgements: self.send_status_response(code=ErrorCode.OK, request_id=asd.id) if asd.spec_complete: self.send_metadata_for_send_transfer(asd) else: if asd.send_errors: self.send_status_response(code=ErrorCode.EPERM, request_id=asd.id, msg='User refused the transfer') def start_receive(self, ar_id: str) -> None: ar = self.active_receives[ar_id] if ar.bypass_ok is not None: self.handle_send_confirmation(ar.bypass_ok, ar_id) return boss = get_boss() window = boss.window_id_map.get(self.window_id) if window is not None: boss.confirm(_( 'The remote machine wants to send some files to this computer. Do you want to allow the transfer?'), self.handle_send_confirmation, ar_id, window=window, ) def handle_send_confirmation(self, confirmed: bool, cmd_id: str) -> None: ar = self.active_receives.get(cmd_id) if ar is None: return if confirmed: ar.accepted = True else: self.drop_receive(ar.id) if ar.accepted: if ar.send_acknowledgements: self.send_status_response(code=ErrorCode.OK, request_id=ar.id) else: if ar.send_errors: self.send_status_response(code=ErrorCode.EPERM, request_id=ar.id, msg='User refused the transfer') def send_fail_on_os_error(self, err: OSError, msg: str, ar: ActiveSend | ActiveReceive, file_id: str = '') -> None: if not ar.send_errors: return errname = errno.errorcode.get(err.errno, 'EFAIL') if err.errno is not None else 'EFAIL' self.send_status_response(code=errname, msg=msg, request_id=ar.id, file_id=file_id) def active_file(self, rid: str = '', file_id: str = '') -> DestFile: return self.active_receives[rid].files[file_id] class TestFileTransmission(FileTransmission): def __init__(self, allow: bool = True) -> None: super().__init__(0) self.test_responses: list[dict[str, str | int | bytes]] = [] self.allow = allow def write_ftc_to_child(self, payload: FileTransmissionCommand, appendleft: bool = False, use_pending: bool = True) -> bool: self.test_responses.append(payload.asdict()) return True def start_receive(self, aid: str) -> None: self.handle_send_confirmation(self.allow, aid) def start_send(self, aid: str) -> None: self.handle_receive_confirmation(self.allow, aid) def callback_after(self, callback: Callable[[int | None], None], timeout: float = 0) -> int | None: callback(None) return None kitty-0.41.1/kitty/font-names.c0000664000175000017510000004254414773370543015677 0ustar nileshnilesh/* * font-names.c * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "fonts.h" static PyObject* decode_name_record(PyObject *namerec) { #define d(x) PyLong_AsUnsignedLong(PyTuple_GET_ITEM(namerec, x)) unsigned long platform_id = d(0), encoding_id = d(1), language_id = d(2); #undef d const char *encoding = "unicode_escape"; if ((platform_id == 3 && encoding_id == 1) || platform_id == 0) encoding = "utf-16-be"; else if (platform_id == 1 && encoding_id == 0 && language_id == 0) encoding = "mac-roman"; PyObject *b = PyTuple_GET_ITEM(namerec, 3); return PyUnicode_Decode(PyBytes_AS_STRING(b), PyBytes_GET_SIZE(b), encoding, "replace"); } static bool namerec_matches(PyObject *namerec, unsigned platform_id, unsigned encoding_id, unsigned language_id) { #define d(x) PyLong_AsUnsignedLong(PyTuple_GET_ITEM(namerec, x)) return d(0) == platform_id && d(1) == encoding_id && d(2) == language_id; #undef d } static PyObject* find_matching_namerec(PyObject *namerecs, unsigned platform_id, unsigned encoding_id, unsigned language_id) { for (Py_ssize_t i = 0; i < PyList_GET_SIZE(namerecs); i++) { PyObject *namerec = PyList_GET_ITEM(namerecs, i); if (namerec_matches(namerec, platform_id, encoding_id, language_id)) return decode_name_record(namerec); } return NULL; } bool add_font_name_record(PyObject *table, uint16_t platform_id, uint16_t encoding_id, uint16_t language_id, uint16_t name_id, const char *string, uint16_t string_len) { RAII_PyObject(key, PyLong_FromUnsignedLong((unsigned long)name_id)); if (!key) return false; RAII_PyObject(list, PyDict_GetItem(table, key)); if (list == NULL) { list = PyList_New(0); if (!list) return false; if (PyDict_SetItem(table, key, list) != 0) return false; } else Py_INCREF(list); RAII_PyObject(value, Py_BuildValue("(H H H y#)", platform_id, encoding_id, language_id, string, (Py_ssize_t)string_len)); if (!value) return false; if (PyList_Append(list, value) != 0) return false; return true; } PyObject* get_best_name_from_name_table(PyObject *table, PyObject *name_id) { PyObject *namerecs = PyDict_GetItem(table, name_id); if (namerecs == NULL) return PyUnicode_FromString(""); if (PyList_GET_SIZE(namerecs) == 1) return decode_name_record(PyList_GET_ITEM(namerecs, 0)); #define d(...) { PyObject *ans = find_matching_namerec(namerecs, __VA_ARGS__); if (ans != NULL || PyErr_Occurred()) return ans; } d(3, 1, 1033); // Microsoft/Windows/US English d(1, 0, 0); // Mac/Roman/English d(0, 6, 0); // Unicode/SMP/* d(0, 4, 0); // Unicode/SMP/* d(0, 3, 0); // Unicode/BMP/* d(0, 2, 0); // Unicode/10646-BMP/* d(0, 1, 0); // Unicode/1.1/* #undef d return PyUnicode_FromString(""); } static PyObject* get_best_name(PyObject *table, unsigned long name_id) { RAII_PyObject(id, PyLong_FromUnsignedLong(name_id)); return get_best_name_from_name_table(table, id); } // OpenType tables are big-endian for god knows what reason so need to byteswap static uint16_t byteswap(const uint16_t *p) { const uint8_t *b = (const uint8_t*)p; return (((uint16_t)b[0]) << 8) | b[1]; } static uint32_t byteswap32(const uint32_t *val) { const uint8_t *p = (const uint8_t*)val; return (((uint32_t)p[0]) << 24) | (((uint32_t)p[1]) << 16) | (((uint32_t)p[2]) << 8) | p[3]; } static double load_fixed(const uint32_t *p_) { uint32_t p = byteswap32(p_); static const double denom = 1 << 16; return ((int32_t)p) / denom; } #define next byteswap(p++) #define next32 load_fixed(p32++) PyObject* read_name_font_table(const uint8_t *table, size_t table_len) { if (!table || table_len < 9 * sizeof(uint16_t)) return PyDict_New(); uint16_t *p = (uint16_t*)table; p++; uint16_t num_of_name_records = next, storage_offset = next; const uint8_t *storage = table + storage_offset, *slimit = table + table_len; if (storage >= slimit) return PyDict_New(); RAII_PyObject(ans, PyDict_New()); for (; num_of_name_records > 0 && p + 6 <= (uint16_t*)slimit; num_of_name_records--) { uint16_t platform_id = next, encoding_id = next, language_id = next, name_id = next, length = next, offset = next; const uint8_t *s = storage + offset; if (s + length <= slimit && !add_font_name_record( ans, platform_id, encoding_id, language_id, name_id, (const char*)(s), length)) return NULL; } Py_INCREF(ans); return ans; } static bool is_digit(char x) { return '0' <= x && x <= '9'; } static PyObject* read_cv_feature_table(const uint8_t *table, const uint8_t *limit, PyObject *name_lookup_table) { RAII_PyObject(ans, PyDict_New()); if (!ans) return NULL; if (limit - table >= 12) { uint16_t *p = (uint16_t*)(table + 2); uint16_t name_id = next, tooltip_id = next, sample_id = next, num_params = next, first_value_id = next; if (name_id) { RAII_PyObject(name, get_best_name(name_lookup_table, name_id)); if (!name) return NULL; if (PyDict_SetItemString(ans, "name", name) != 0) return NULL; } if (tooltip_id) { RAII_PyObject(tooltip, get_best_name(name_lookup_table, tooltip_id)); if (!tooltip) return NULL; if (PyDict_SetItemString(ans, "tooltip", tooltip) != 0) return NULL; } if (sample_id) { RAII_PyObject(sample, get_best_name(name_lookup_table, sample_id)); if (!sample) return NULL; if (PyDict_SetItemString(ans, "sample", sample) != 0) return NULL; } if (num_params && first_value_id) { RAII_PyObject(params, PyTuple_New(num_params)); if (!params) return NULL; for (uint16_t i = 0; i < num_params; i++) { PyObject *pval = get_best_name(name_lookup_table, first_value_id + i); if (!pval) return NULL; PyTuple_SET_ITEM(params, i, pval); } if (PyDict_SetItemString(ans, "params", params) != 0) return NULL; } } Py_INCREF(ans); return ans; } static PyObject* read_ss_feature_table(const uint8_t *table, const uint8_t *limit, PyObject *name_lookup_table) { RAII_PyObject(ans, PyDict_New()); if (!ans) return NULL; if (limit - table < 4) { Py_INCREF(ans); return ans; } uint16_t *p = (uint16_t*)(table + 2); uint16_t name_id = next; if (name_id) { RAII_PyObject(name, get_best_name(name_lookup_table, name_id)); if (!name) return NULL; if (PyDict_SetItemString(ans, "name", name) != 0) return NULL; } Py_INCREF(ans); return ans; } bool read_features_from_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output) { if (table_len < 20) return true; const uint16_t *p = (uint16_t*)table; const uint8_t *limit = table + table_len; uint16_t major_version = next, minor_version = next, script_list_offset = next, feature_list_offset = next; (void)major_version; (void)minor_version; (void)script_list_offset; const uint8_t *feature_list_table = table + feature_list_offset; char tag_buf[5] = {0}; if (feature_list_table + 2 >= limit) return true; p = (uint16_t*)feature_list_table; uint16_t feature_count = next; const uint8_t *pos = (uint8_t*)p; for (uint16_t i = 0; i < feature_count && pos + 6 <= limit; pos += 6, i++) { memcpy(tag_buf, pos, 4); RAII_PyObject(tag, PyUnicode_FromString(tag_buf)); if (!tag) return false; if (PyDict_Contains(output, tag) == 1) continue; if (PyDict_SetItem(output, tag, Py_None) != 0) return false; p = (uint16_t*)(pos + 4); uint16_t offset_to_feature_table = next; const uint8_t *feature_table = feature_list_table + offset_to_feature_table; if (feature_table + 2 > limit) continue; p = (uint16_t*)(feature_table); uint16_t offset_to_feature_params_table = next; if (tag_buf[0] == 'c' && tag_buf[1] == 'v' && is_digit(tag_buf[2]) && is_digit(tag_buf[3])) { if (offset_to_feature_params_table) { RAII_PyObject(cv, read_cv_feature_table(feature_table + offset_to_feature_params_table, limit, name_lookup_table)); if (!cv) return false; if (PyDict_SetItem(output, tag, cv) != 0) return false; } } else if (tag_buf[0] == 's' && tag_buf[1] == 's' && '0' <= tag_buf[2] && tag_buf[2] <= '2' && is_digit(tag_buf[3])) { if (offset_to_feature_params_table) { RAII_PyObject(ss, read_ss_feature_table(feature_table + offset_to_feature_params_table, limit, name_lookup_table)); if (!ss) return false; if (PyDict_SetItem(output, tag, ss) != 0) return false; } } } return true; } bool read_STAT_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output) { RAII_PyObject(design_axes, PyTuple_New(0)); RAII_PyObject(multi_axis_styles, PyTuple_New(0)); if (!design_axes || !multi_axis_styles) return false; if (table_len < 20) goto ok; const uint16_t *p = (uint16_t*)table; uint16_t major_version = next, minor_version = next, size_of_design_axis_entry = next, count_of_design_axis_entries = next; const uint32_t *p32 = (uint32_t*)p; uint32_t offset_to_start_of_design_axes_entries = byteswap32(p32++); p = (uint16_t*)p32; uint16_t count_of_axis_value_entries = next; p32 = (uint32_t*)p; uint32_t offset_to_start_of_axis_value_entries = byteswap32(p32++); p = (uint16_t*)p32; uint16_t elided_fallback_name_id = next; if (major_version == 1 && minor_version < 1) elided_fallback_name_id = 0; if (PyDict_SetItemString(output, "elided_fallback_name", elided_fallback_name_id ? get_best_name(name_lookup_table, elided_fallback_name_id) : PyUnicode_FromString("")) != 0) return false; const uint8_t *table_limit = table + table_len; size_t count = 0; if (_PyTuple_Resize(&design_axes, count_of_design_axis_entries) == -1) return false; for ( const uint8_t *pos = table + offset_to_start_of_design_axes_entries; pos + size_of_design_axis_entry <= table_limit && count < count_of_design_axis_entries; pos += size_of_design_axis_entry, count++ ) { p = (uint16_t*)(pos + 4); uint16_t name_id = next, ordering = next; PyObject *rec = Py_BuildValue("{ss# sN sH sN}", "tag", (char*)pos, 4, "name", get_best_name(name_lookup_table, name_id), "ordering", ordering, "values", PyList_New(0)); if (!rec) return false; PyTuple_SET_ITEM(design_axes, count, rec); } if (_PyTuple_Resize(&design_axes, count) == -1) return false; count = 0; const uint8_t *start_of_axis_values_offsets_array = table + offset_to_start_of_axis_value_entries; Py_ssize_t i = 0; if (_PyTuple_Resize(&multi_axis_styles, count_of_axis_value_entries) == -1) return false; for ( const uint8_t *pos = start_of_axis_values_offsets_array; pos + 2 <= table_limit && count < count_of_axis_value_entries; pos += 2, count++ ) { p = (uint16_t*)pos; uint16_t offset = next; const uint8_t *start_of_axis_values_table = start_of_axis_values_offsets_array + offset; if (start_of_axis_values_table + 12 > table_limit) continue; p = (uint16_t*)(start_of_axis_values_table); uint16_t format = next, axis_index = next, flags = next, value_name_id = next; p32 = (uint32_t*)p; #define app(fmt, ...) { \ RAII_PyObject(v, Py_BuildValue("{sH sH sN " fmt "}", \ "format", format, "flags", flags, "name", get_best_name(name_lookup_table, value_name_id), __VA_ARGS__)); \ if (!v) return false; \ PyObject *l = PyDict_GetItemString(PyTuple_GET_ITEM(design_axes, axis_index), "values"); \ if (l && PyList_Append(l, v) != 0) return false; \ } switch(format) { case 1: if (p32 + 1 <= (uint32_t*)table_limit && axis_index < PyTuple_GET_SIZE(design_axes)) { const double value = next32; app("sd", "value", value); } break; case 2: if (p32 + 3 <= (uint32_t*)table_limit && axis_index < PyTuple_GET_SIZE(design_axes)) { const double value = next32, minimum = next32, maximum = next32; app("sd sd sd", "value", value, "minimum", minimum, "maximum", maximum); } break; case 3: if (p32 + 2 <= (uint32_t*)table_limit && axis_index < PyTuple_GET_SIZE(design_axes)) { const double value = next32, linked_value = next32; app("sd sd", "value", value, "linked_value", linked_value); } break; case 4: if ((uint8_t*)(p) + 6 * axis_index <= table_limit) { RAII_PyObject(values, PyTuple_New(axis_index)); if (!values) return false; for (uint16_t n = 0; n < axis_index; n++, p += 3) { uint16_t actual_axis_index = next; p32 = (uint32_t*)p; double value = next32; PyObject *e = Py_BuildValue("{sH sd}", "design_index", actual_axis_index, "value", value); if (!e) return false; PyTuple_SET_ITEM(values, n, e); } PyObject *e = Py_BuildValue("{sH sN sO}", "flags", flags, "name", get_best_name(name_lookup_table, value_name_id), "values", values); if (!e) return false; PyTuple_SET_ITEM(multi_axis_styles, i++, e); } break; } } if (_PyTuple_Resize(&multi_axis_styles, i) == -1) return false; ok: if (PyDict_SetItemString(output, "design_axes", design_axes) != 0) return false; if (PyDict_SetItemString(output, "multi_axis_styles", multi_axis_styles) != 0) return false; return true; } bool read_fvar_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output) { RAII_PyObject(named_styles, PyTuple_New(0)); if (!named_styles) return false; RAII_PyObject(axes, PyTuple_New(0)); if (!axes) return false; if (!table || table_len < 14 * sizeof(uint16_t)) goto ok; const uint16_t *p = (uint16_t*)table; p += 2; const uint16_t offset_to_start_of_axis_array = next; next; const uint16_t num_of_axis_records = next, size_of_axis_record = next, num_of_name_records = next, size_of_name_record = next; const uint16_t size_of_coordinates = num_of_axis_records * sizeof(int32_t); if (size_of_name_record < size_of_coordinates + 4) { PyErr_Format(PyExc_ValueError, "size of name record: %u too small", size_of_name_record); return NULL; } const bool has_postscript_name = size_of_name_record >= 3 * sizeof(uint16_t) + size_of_coordinates; uint16_t i = 0; if (size_of_axis_record < 20) { PyErr_Format(PyExc_ValueError, "size of axis record: %u too small", size_of_axis_record); return NULL; } if (_PyTuple_Resize(&axes, num_of_axis_records) == -1) return NULL; for ( const uint8_t *pos = table + offset_to_start_of_axis_array; pos + size_of_axis_record <= table + table_len && i < num_of_axis_records; i++, pos += size_of_axis_record ) { uint32_t *p32 = (uint32_t*)(pos + 4); const double minimum = next32, def = next32, maximum = next32; p = (uint16_t*)(pos + 16); int32_t flags = next, strid = next; PyObject *axis = Py_BuildValue("{sd sd sd sN sO sN}", "minimum", minimum, "maximum", maximum, "default", def, "tag", PyUnicode_FromStringAndSize((const char*)pos, 4), "hidden", (flags & 1) ? Py_True : Py_False, "strid", get_best_name(name_lookup_table, strid) ); if (!axis) return NULL; PyTuple_SET_ITEM(axes, i, axis); } if (_PyTuple_Resize(&axes, i) == -1) return NULL; char tag_buf[5] = {0}; i = 0; if (_PyTuple_Resize(&named_styles, num_of_name_records) == -1) return NULL; for ( const uint8_t *pos = table + offset_to_start_of_axis_array + num_of_axis_records * size_of_axis_record; pos + size_of_name_record <= table + table_len && i < num_of_name_records; i++, pos += size_of_name_record ) { p = (uint16_t*)pos; uint16_t name_id = next, psname_id = 0xffff; next; const uint32_t *p32 = (uint32_t*)p; RAII_PyObject(axis_values, PyDict_New()); if (!axis_values) return NULL; for (uint16_t i = 0; i < num_of_axis_records; i++) { const uint8_t *t = table + offset_to_start_of_axis_array + i * size_of_axis_record; memcpy(tag_buf, t, 4); RAII_PyObject(pval, PyFloat_FromDouble(next32)); if (!pval || PyDict_SetItemString(axis_values, tag_buf, pval) != 0) return NULL; } if (has_postscript_name) { p = (uint16_t*)p32; psname_id = next; } PyObject *ns = Py_BuildValue("{sO sN sN}", "axis_values", axis_values, "name", get_best_name(name_lookup_table, name_id), "psname", (psname_id != 0xffff && psname_id ? get_best_name(name_lookup_table, psname_id) : PyUnicode_FromString(""))); if (!ns) return NULL; PyTuple_SET_ITEM(named_styles, i, ns); } if (_PyTuple_Resize(&named_styles, i) == -1) return NULL; ok: if (PyDict_SetItemString(output, "variations_postscript_name_prefix", get_best_name(name_lookup_table, 25)) != 0) return false; if (PyDict_SetItemString(output, "axes", axes) != 0) return false; if (PyDict_SetItemString(output, "named_styles", named_styles) != 0) return false; return true; } #undef next32 #undef next kitty-0.41.1/kitty/fontconfig.c0000664000175000017510000006043014773370543015756 0ustar nileshnilesh/* * fontconfig.c * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "cleanup.h" #include "lineops.h" #include "fonts.h" #include #include #include "freetype_render_ui_text.h" #ifndef FC_COLOR #define FC_COLOR "color" #endif static bool initialized = false; static void* libfontconfig_handle = NULL; static struct {PyObject *face, *descriptor;} builtin_nerd_font = {0}; #define FcInit dynamically_loaded_fc_symbol.Init #define FcFini dynamically_loaded_fc_symbol.Fini #define FcCharSetAddChar dynamically_loaded_fc_symbol.CharSetAddChar #define FcPatternDestroy dynamically_loaded_fc_symbol.PatternDestroy #define FcObjectSetDestroy dynamically_loaded_fc_symbol.ObjectSetDestroy #define FcPatternAddDouble dynamically_loaded_fc_symbol.PatternAddDouble #define FcPatternAddString dynamically_loaded_fc_symbol.PatternAddString #define FcFontMatch dynamically_loaded_fc_symbol.FontMatch #define FcCharSetCreate dynamically_loaded_fc_symbol.CharSetCreate #define FcPatternGetString dynamically_loaded_fc_symbol.PatternGetString #define FcFontSetDestroy dynamically_loaded_fc_symbol.FontSetDestroy #define FcPatternGetInteger dynamically_loaded_fc_symbol.PatternGetInteger #define FcPatternAddBool dynamically_loaded_fc_symbol.PatternAddBool #define FcFontList dynamically_loaded_fc_symbol.FontList #define FcObjectSetBuild dynamically_loaded_fc_symbol.ObjectSetBuild #define FcCharSetDestroy dynamically_loaded_fc_symbol.CharSetDestroy #define FcConfigSubstitute dynamically_loaded_fc_symbol.ConfigSubstitute #define FcDefaultSubstitute dynamically_loaded_fc_symbol.DefaultSubstitute #define FcPatternAddInteger dynamically_loaded_fc_symbol.PatternAddInteger #define FcPatternCreate dynamically_loaded_fc_symbol.PatternCreate #define FcPatternGetBool dynamically_loaded_fc_symbol.PatternGetBool #define FcPatternAddCharSet dynamically_loaded_fc_symbol.PatternAddCharSet #define FcConfigAppFontAddFile dynamically_loaded_fc_symbol.ConfigAppFontAddFile static struct { FcBool(*Init)(void); void(*Fini)(void); FcBool (*CharSetAddChar) (FcCharSet *fcs, FcChar32 ucs4); void (*PatternDestroy) (FcPattern *p); void (*ObjectSetDestroy) (FcObjectSet *os); FcBool (*PatternAddDouble) (FcPattern *p, const char *object, double d); FcBool (*PatternAddString) (FcPattern *p, const char *object, const FcChar8 *s); FcPattern * (*FontMatch) (FcConfig *config, FcPattern *p, FcResult *result); FcCharSet* (*CharSetCreate) (void); FcResult (*PatternGetString) (const FcPattern *p, const char *object, int n, FcChar8 ** s); void (*FontSetDestroy) (FcFontSet *s); FcResult (*PatternGetInteger) (const FcPattern *p, const char *object, int n, int *i); FcBool (*PatternAddBool) (FcPattern *p, const char *object, FcBool b); FcFontSet * (*FontList) (FcConfig *config, FcPattern *p, FcObjectSet *os); FcObjectSet * (*ObjectSetBuild) (const char *first, ...); void (*CharSetDestroy) (FcCharSet *fcs); FcBool (*ConfigSubstitute) (FcConfig *config, FcPattern *p, FcMatchKind kind); void (*DefaultSubstitute) (FcPattern *pattern); FcBool (*PatternAddInteger) (FcPattern *p, const char *object, int i); FcPattern * (*PatternCreate) (void); FcResult (*PatternGetBool) (const FcPattern *p, const char *object, int n, FcBool *b); FcBool (*PatternAddCharSet) (FcPattern *p, const char *object, const FcCharSet *c); FcBool (*ConfigAppFontAddFile) (FcConfig *config, const FcChar8 *file); } dynamically_loaded_fc_symbol = {0}; #define LOAD_FUNC(name) {\ *(void **) (&dynamically_loaded_fc_symbol.name) = dlsym(libfontconfig_handle, "Fc" #name); \ if (!dynamically_loaded_fc_symbol.name) { \ const char* error = dlerror(); \ fatal("Failed to load the function Fc" #name " with error: %s", error ? error : ""); \ } \ } static void load_fontconfig_lib(void) { const char* libnames[] = { #if defined(_KITTY_FONTCONFIG_LIBRARY) _KITTY_FONTCONFIG_LIBRARY, #else "libfontconfig.so", // some installs are missing the .so symlink, so try the full name "libfontconfig.so.1", #endif NULL }; for (int i = 0; libnames[i]; i++) { libfontconfig_handle = dlopen(libnames[i], RTLD_LAZY); if (libfontconfig_handle) break; } if (libfontconfig_handle == NULL) { fatal("Failed to find and load fontconfig"); } dlerror(); /* Clear any existing error */ LOAD_FUNC(Init); LOAD_FUNC(Fini); LOAD_FUNC(CharSetAddChar); LOAD_FUNC(PatternDestroy); LOAD_FUNC(ObjectSetDestroy); LOAD_FUNC(PatternAddDouble); LOAD_FUNC(PatternAddString); LOAD_FUNC(FontMatch); LOAD_FUNC(CharSetCreate); LOAD_FUNC(PatternGetString); LOAD_FUNC(FontSetDestroy); LOAD_FUNC(PatternGetInteger); LOAD_FUNC(PatternAddBool); LOAD_FUNC(FontList); LOAD_FUNC(ObjectSetBuild); LOAD_FUNC(CharSetDestroy); LOAD_FUNC(ConfigSubstitute); LOAD_FUNC(DefaultSubstitute); LOAD_FUNC(PatternAddInteger); LOAD_FUNC(PatternCreate); LOAD_FUNC(PatternGetBool); LOAD_FUNC(PatternAddCharSet); LOAD_FUNC(ConfigAppFontAddFile); } #undef LOAD_FUNC static void ensure_initialized(void) { if (!initialized) { load_fontconfig_lib(); if (!FcInit()) fatal("Failed to initialize fontconfig library"); initialized = true; } } static void finalize(void) { if (initialized) { Py_CLEAR(builtin_nerd_font.face); Py_CLEAR(builtin_nerd_font.descriptor); FcFini(); dlclose(libfontconfig_handle); libfontconfig_handle = NULL; initialized = false; } } static PyObject* pybool(FcBool x) { PyObject *ans = x ? Py_True: Py_False; Py_INCREF(ans); return ans; } static PyObject* pyspacing(int val) { #define S(x) case FC_##x: return PyUnicode_FromString(#x) switch(val) { S(PROPORTIONAL); S(DUAL); S(MONO); S(CHARCELL); default: return PyUnicode_FromString("UNKNOWN"); } #undef S } static PyObject* increment_and_return(PyObject *x) { if (x) Py_INCREF(x); return x; } static PyObject* pattern_as_dict(FcPattern *pat) { RAII_PyObject(ans, Py_BuildValue("{ss}", "descriptor_type", "fontconfig")); if (ans == NULL) return NULL; #define PS(x) PyUnicode_Decode((const char*)x, strlen((const char*)x), "UTF-8", "replace") #define G(type, get, which, conv, name, default) { \ type out; \ if (get(pat, which, 0, &out) == FcResultMatch) { \ RAII_PyObject(p, conv(out)); \ if (!p || PyDict_SetItemString(ans, #name, p) != 0) return NULL; \ } else { RAII_PyObject(d, default); if (!d || PyDict_SetItemString(ans, #name, d) != 0) return NULL; } \ } #define L(type, get, which, conv, name) { \ type out; int n = 0; \ RAII_PyObject(list, PyList_New(0)); \ if (!list) return NULL; \ while (get(pat, which, n++, &out) == FcResultMatch) { \ RAII_PyObject(p, conv(out)); \ if (!p || PyList_Append(list, p) != 0) return NULL; \ } \ if (PyDict_SetItemString(ans, #name, list) != 0) return NULL; \ } #define S(which, key) G(FcChar8*, FcPatternGetString, which, PS, key, PyUnicode_FromString("")) #define LS(which, key) L(FcChar8*, FcPatternGetString, which, PS, key) #define I(which, key) G(int, FcPatternGetInteger, which, PyLong_FromLong, key, PyLong_FromUnsignedLong(0)) #define B(which, key) G(FcBool, FcPatternGetBool, which, pybool, key, increment_and_return(Py_False)) #define E(which, key, conv) G(int, FcPatternGetInteger, which, conv, key, PyLong_FromUnsignedLong(0)) S(FC_FILE, path); S(FC_FAMILY, family); S(FC_STYLE, style); S(FC_FULLNAME, full_name); S(FC_POSTSCRIPT_NAME, postscript_name); LS(FC_FONT_FEATURES, fontfeatures); B(FC_VARIABLE, variable); #ifdef FC_NAMED_INSTANCE B(FC_NAMED_INSTANCE, named_instance); #else PyDict_SetItemString(ans, "named_instance", Py_False); #endif I(FC_WEIGHT, weight); I(FC_WIDTH, width) I(FC_SLANT, slant); I(FC_HINT_STYLE, hint_style); I(FC_INDEX, index); I(FC_RGBA, subpixel); I(FC_LCD_FILTER, lcdfilter); B(FC_HINTING, hinting); B(FC_SCALABLE, scalable); B(FC_OUTLINE, outline); B(FC_COLOR, color); E(FC_SPACING, spacing, pyspacing); Py_INCREF(ans); return ans; #undef PS #undef S #undef I #undef B #undef E #undef G #undef L #undef LS } static PyObject* font_set(FcFontSet *fs) { PyObject *ans = PyTuple_New(fs->nfont); if (ans == NULL) return NULL; for (int i = 0; i < fs->nfont; i++) { PyObject *d = pattern_as_dict(fs->fonts[i]); if (d == NULL) { Py_CLEAR(ans); break; } PyTuple_SET_ITEM(ans, i, d); } return ans; } #define AP(func, which, in, desc) if (!func(pat, which, in)) { PyErr_Format(PyExc_ValueError, "Failed to add %s to fontconfig pattern", desc, NULL); goto end; } static PyObject* fc_list(PyObject UNUSED *self, PyObject *args, PyObject *kw) { ensure_initialized(); int allow_bitmapped_fonts = 0, spacing = -1, only_variable = 0; PyObject *ans = NULL; FcObjectSet *os = NULL; FcPattern *pat = NULL; FcFontSet *fs = NULL; static char *kwds[] = {"spacing", "allow_bitmapped_fonts", "only_variable", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kw, "|ipp", kwds, &spacing, &allow_bitmapped_fonts, &only_variable)) return NULL; pat = FcPatternCreate(); if (pat == NULL) return PyErr_NoMemory(); if (!allow_bitmapped_fonts) { AP(FcPatternAddBool, FC_OUTLINE, FcTrue, "outline"); AP(FcPatternAddBool, FC_SCALABLE, FcTrue, "scalable"); } if (spacing > -1) AP(FcPatternAddInteger, FC_SPACING, spacing, "spacing"); if (only_variable) AP(FcPatternAddBool, FC_VARIABLE, FcTrue, "variable"); os = FcObjectSetBuild(FC_FILE, FC_POSTSCRIPT_NAME, FC_FAMILY, FC_STYLE, FC_FULLNAME, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_HINT_STYLE, FC_INDEX, FC_HINTING, FC_SCALABLE, FC_OUTLINE, FC_COLOR, FC_SPACING, FC_VARIABLE, #ifdef FC_NAMED_INSTANCE FC_NAMED_INSTANCE, #endif NULL); if (!os) { PyErr_SetString(PyExc_ValueError, "Failed to create fontconfig object set"); goto end; } fs = FcFontList(NULL, pat, os); if (!fs) { PyErr_SetString(PyExc_ValueError, "Failed to create fontconfig font set"); goto end; } ans = font_set(fs); end: if (pat != NULL) FcPatternDestroy(pat); if (os != NULL) FcObjectSetDestroy(os); if (fs != NULL) FcFontSetDestroy(fs); return ans; } static PyObject* _fc_match(FcPattern *pat) { FcPattern *match = NULL; PyObject *ans = NULL; FcResult result; FcConfigSubstitute(NULL, pat, FcMatchPattern); FcDefaultSubstitute(pat); /* printf("fc_match = %s\n", FcNameUnparse(pat)); */ match = FcFontMatch(NULL, pat, &result); if (match == NULL) { PyErr_SetString(PyExc_KeyError, "FcFontMatch() failed"); goto end; } ans = pattern_as_dict(match); end: if (match) FcPatternDestroy(match); return ans; } static char_type char_buf[1024]; static void add_charset(FcPattern *pat, size_t num) { FcCharSet *charset = NULL; if (num) { charset = FcCharSetCreate(); if (charset == NULL) { PyErr_NoMemory(); goto end; } for (size_t i = 0; i < num; i++) { if (!FcCharSetAddChar(charset, char_buf[i])) { PyErr_SetString(PyExc_RuntimeError, "Failed to add character to fontconfig charset"); goto end; } } AP(FcPatternAddCharSet, FC_CHARSET, charset, "charset"); } end: if (charset != NULL) FcCharSetDestroy(charset); } static bool _native_fc_match(FcPattern *pat, FontConfigFace *ans) { bool ok = false; FcPattern *match = NULL; FcResult result; FcConfigSubstitute(NULL, pat, FcMatchPattern); FcDefaultSubstitute(pat); /* printf("fc_match = %s\n", FcNameUnparse(pat)); */ match = FcFontMatch(NULL, pat, &result); if (match == NULL) { PyErr_SetString(PyExc_KeyError, "FcFontMatch() failed"); goto end; } FcChar8 *out; #define g(func, prop, output) if (func(match, prop, 0, &output) != FcResultMatch) { PyErr_SetString(PyExc_ValueError, "No " #prop " found in fontconfig match result"); goto end; } g(FcPatternGetString, FC_FILE, out); g(FcPatternGetInteger, FC_INDEX, ans->index); g(FcPatternGetInteger, FC_HINT_STYLE, ans->hintstyle); g(FcPatternGetBool, FC_HINTING, ans->hinting); #undef g ans->path = strdup((char*)out); if (!ans->path) { PyErr_NoMemory(); goto end; } ok = true; end: if (match != NULL) FcPatternDestroy(match); return ok; } bool information_for_font_family(const char *family, bool bold, bool italic, FontConfigFace *ans) { ensure_initialized(); memset(ans, 0, sizeof(FontConfigFace)); FcPattern *pat = FcPatternCreate(); bool ok = false; if (pat == NULL) { PyErr_NoMemory(); return ok; } if (family && strlen(family) > 0) AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)family, "family"); if (bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); } if (italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); } ok = _native_fc_match(pat, ans); end: if (pat != NULL) FcPatternDestroy(pat); return ok; } static PyObject* fc_match(PyObject UNUSED *self, PyObject *args) { ensure_initialized(); char *family = NULL; int bold = 0, italic = 0, allow_bitmapped_fonts = 0, spacing = FC_MONO; double size_in_pts = 0, dpi = 0; FcPattern *pat = NULL; PyObject *ans = NULL; if (!PyArg_ParseTuple(args, "|zppipdd", &family, &bold, &italic, &spacing, &allow_bitmapped_fonts, &size_in_pts, &dpi)) return NULL; pat = FcPatternCreate(); if (pat == NULL) return PyErr_NoMemory(); if (family && strlen(family) > 0) AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)family, "family"); if (spacing >= FC_DUAL) { // pass the family,monospace as the family parameter to fc-match, // which will fallback to using monospace if the family does not match. AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)"monospace", "family"); AP(FcPatternAddInteger, FC_SPACING, spacing, "spacing"); } if (!allow_bitmapped_fonts) { AP(FcPatternAddBool, FC_OUTLINE, true, "outline"); AP(FcPatternAddBool, FC_SCALABLE, true, "scalable"); } if (size_in_pts > 0) { AP(FcPatternAddDouble, FC_SIZE, size_in_pts, "size"); } if (dpi > 0) { AP(FcPatternAddDouble, FC_DPI, dpi, "dpi"); } if (bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); } if (italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); } ans = _fc_match(pat); end: if (pat != NULL) FcPatternDestroy(pat); return ans; } static PyObject* fc_match_postscript_name(PyObject UNUSED *self, PyObject *args) { ensure_initialized(); const char *postscript_name = NULL; FcPattern *pat = NULL; PyObject *ans = NULL; if (!PyArg_ParseTuple(args, "s", &postscript_name)) return NULL; if (!postscript_name || !postscript_name[0]) { PyErr_SetString(PyExc_KeyError, "postscript_name must not be empty"); return NULL; } pat = FcPatternCreate(); if (pat == NULL) return PyErr_NoMemory(); AP(FcPatternAddString, FC_POSTSCRIPT_NAME, (const FcChar8*)postscript_name, "postscript_name"); ans = _fc_match(pat); end: if (pat != NULL) FcPatternDestroy(pat); return ans; } PyObject* specialize_font_descriptor(PyObject *base_descriptor, double font_sz_in_pts, double dpi_x, double dpi_y) { ensure_initialized(); PyObject *p = PyDict_GetItemString(base_descriptor, "path"); PyObject *idx = PyDict_GetItemString(base_descriptor, "index"); if (p == NULL) { PyErr_SetString(PyExc_ValueError, "Base descriptor has no path"); return NULL; } if (idx == NULL) { PyErr_SetString(PyExc_ValueError, "Base descriptor has no index"); return NULL; } unsigned long face_idx = PyLong_AsUnsignedLong(idx); if (PyErr_Occurred()) return NULL; FcPattern *pat = FcPatternCreate(); if (pat == NULL) return PyErr_NoMemory(); RAII_PyObject(features, PyList_New(0)); if (!features) return NULL; RAII_PyObject(final_features, NULL); RAII_PyObject(ans, NULL); AP(FcPatternAddString, FC_FILE, (const FcChar8*)PyUnicode_AsUTF8(p), "path"); AP(FcPatternAddInteger, FC_INDEX, face_idx, "index"); AP(FcPatternAddDouble, FC_SIZE, font_sz_in_pts, "size"); AP(FcPatternAddDouble, FC_DPI, (dpi_x + dpi_y) / 2.0, "dpi"); ans = _fc_match(pat); FcPatternDestroy(pat); pat = NULL; if (!ans) return NULL; // fontconfig returns a completely random font if the base descriptor // points to a font that fontconfig hasnt indexed, for example the builting // NERD font PyObject *new_path = PyDict_GetItemString(ans, "path"); if (!new_path || PyObject_RichCompareBool(p, new_path, Py_EQ) != 1) { Py_CLEAR(ans); ans = PyDict_Copy(base_descriptor); if (!ans) return NULL; } if (face_idx > 0) { // For some reason FcFontMatch sets the index to zero, so manually restore it. if (PyDict_SetItemString(ans, "index", idx) != 0) return NULL; } PyObject *named_style = PyDict_GetItemString(base_descriptor, "named_style"); if (named_style) { if (PyDict_SetItemString(ans, "named_style", named_style) != 0) return NULL; } PyObject *axes = PyDict_GetItemString(base_descriptor, "axes"); if (axes) { if (PyDict_SetItemString(ans, "axes", axes) != 0) return NULL; } PyObject *ff = PyDict_GetItemString(ans, "fontfeatures"); if (ff && PyList_GET_SIZE(ff)) { for (Py_ssize_t i = 0; i < PyList_GET_SIZE(ff); i++) { RAII_PyObject(pff, (PyObject*)parse_font_feature(PyUnicode_AsUTF8(PyList_GET_ITEM(ff, i)))); if (pff == NULL) { PyErr_Print(); fprintf(stderr, "\n"); } else if (PyList_Append(features, pff) != 0) return NULL; } } PyObject *base_features = PyDict_GetItemString(base_descriptor, "features"); final_features = PyTuple_New(PyList_GET_SIZE(features) + (base_features ? PyTuple_GET_SIZE(base_features) : 0)); if (!final_features) return NULL; for (Py_ssize_t i = 0; i < PyList_GET_SIZE(features); i++) { PyTuple_SET_ITEM(final_features, i, Py_NewRef(PyList_GET_ITEM(features, i))); } if (base_features) { for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(base_features); i++) { PyTuple_SET_ITEM(final_features, i + PyList_GET_SIZE(features), Py_NewRef(PyTuple_GET_ITEM(base_features, i))); } } if (PyDict_SetItemString(ans, "features", final_features) != 0) return NULL; Py_INCREF(ans); return ans; end: if (pat) FcPatternDestroy(pat); return NULL; } bool fallback_font(char_type ch, const char *family, bool bold, bool italic, bool prefer_color, FontConfigFace *ans) { ensure_initialized(); memset(ans, 0, sizeof(FontConfigFace)); bool ok = false; FcPattern *pat = FcPatternCreate(); if (pat == NULL) { PyErr_NoMemory(); return ok; } if (family) AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)family, "family"); if (bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); } if (italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); } if (prefer_color) { AP(FcPatternAddBool, FC_COLOR, true, "color"); } char_buf[0] = ch; add_charset(pat, 1); ok = _native_fc_match(pat, ans); end: if (pat != NULL) FcPatternDestroy(pat); return ok; } static bool face_has_codepoint(const void *face, char_type cp) { return glyph_id_for_codepoint(face, cp) > 0; } PyObject* create_fallback_face(PyObject UNUSED *base_face, const ListOfChars *lc, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg) { ensure_initialized(); PyObject *ans = NULL; RAII_PyObject(d, NULL); FcPattern *pat = FcPatternCreate(); if (pat == NULL) return PyErr_NoMemory(); bool glyph_found = false; AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)(emoji_presentation ? "emoji" : "monospace"), "family"); if (!emoji_presentation && bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); } if (!emoji_presentation && italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); } if (emoji_presentation) { AP(FcPatternAddBool, FC_COLOR, true, "color"); } size_t num = cell_as_unicode_for_fallback(lc, char_buf, arraysz(char_buf)); add_charset(pat, num); d = _fc_match(pat); face_from_descriptor: if (d) { ssize_t idx = -1; PyObject *q; while ((q = iter_fallback_faces(fg, &idx))) { if (face_equals_descriptor(q, d)) { ans = PyLong_FromSsize_t(idx); if (!glyph_found) glyph_found = has_cell_text(face_has_codepoint, q, false, lc); goto end; } } ans = face_from_descriptor(d, fg); if (!glyph_found && ans) glyph_found = has_cell_text(face_has_codepoint, ans, false, lc); } end: Py_CLEAR(d); if (pat != NULL) { FcPatternDestroy(pat); pat = NULL; } if (!glyph_found && !PyErr_Occurred()) { if (builtin_nerd_font.face && has_cell_text(face_has_codepoint, builtin_nerd_font.face, false, lc)) { Py_CLEAR(ans); d = builtin_nerd_font.descriptor; Py_INCREF(d); glyph_found = true; goto face_from_descriptor; } else { if (global_state.debug_font_fallback && ans) has_cell_text(face_has_codepoint, ans, true, lc); Py_CLEAR(ans); ans = Py_None; Py_INCREF(ans); } } return ans; } static PyObject* set_builtin_nerd_font(PyObject UNUSED *self, PyObject *pypath) { if (!PyUnicode_Check(pypath)) { PyErr_SetString(PyExc_TypeError, "path must be a string"); return NULL; } ensure_initialized(); const char *path = PyUnicode_AsUTF8(pypath); FcPattern *pat = FcPatternCreate(); if (pat == NULL) return PyErr_NoMemory(); Py_CLEAR(builtin_nerd_font.face); Py_CLEAR(builtin_nerd_font.descriptor); builtin_nerd_font.face = face_from_path(path, 0, NULL); if (builtin_nerd_font.face) { // Copy whatever hinting settings fontconfig returns for the nerd font postscript name AP(FcPatternAddString, FC_POSTSCRIPT_NAME, (const unsigned char*)postscript_name_for_face(builtin_nerd_font.face), "postscript_name"); RAII_PyObject(d, _fc_match(pat)); if (!d) goto end; builtin_nerd_font.descriptor = PyDict_New(); if (!builtin_nerd_font.descriptor) goto end; #define copy(key) { PyObject *t = PyDict_GetItemString(d, #key); if (t) { if (PyDict_SetItemString(builtin_nerd_font.descriptor, #key, t) != 0)goto end; } } copy(hinting); copy(hint_style); #undef copy if (PyDict_SetItemString(builtin_nerd_font.descriptor, "path", pypath) != 0) goto end; if (PyDict_SetItemString(builtin_nerd_font.descriptor, "index", PyLong_FromLong(0)) != 0) goto end; } end: if (pat) FcPatternDestroy(pat); if (PyErr_Occurred()) { Py_CLEAR(builtin_nerd_font.face); Py_CLEAR(builtin_nerd_font.descriptor); return NULL; } Py_INCREF(builtin_nerd_font.descriptor); return builtin_nerd_font.descriptor; } static PyObject* add_font_file(PyObject UNUSED *self, PyObject *args) { ensure_initialized(); const char *path = NULL; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; if (FcConfigAppFontAddFile(NULL, (const unsigned char*)path)) Py_RETURN_TRUE; Py_RETURN_FALSE; } #undef AP static PyMethodDef module_methods[] = { {"fc_list", (PyCFunction)(void (*) (void))(fc_list), METH_VARARGS | METH_KEYWORDS, NULL}, METHODB(fc_match, METH_VARARGS), METHODB(fc_match_postscript_name, METH_VARARGS), METHODB(add_font_file, METH_VARARGS), METHODB(set_builtin_nerd_font, METH_O), {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_fontconfig_library(PyObject *module) { register_at_exit_cleanup_func(FONTCONFIG_CLEANUP_FUNC, finalize); if (PyModule_AddFunctions(module, module_methods) != 0) return false; PyModule_AddIntMacro(module, FC_WEIGHT_REGULAR); PyModule_AddIntMacro(module, FC_WEIGHT_MEDIUM); PyModule_AddIntMacro(module, FC_WEIGHT_SEMIBOLD); PyModule_AddIntMacro(module, FC_WEIGHT_BOLD); PyModule_AddIntMacro(module, FC_SLANT_ITALIC); PyModule_AddIntMacro(module, FC_SLANT_ROMAN); PyModule_AddIntMacro(module, FC_PROPORTIONAL); PyModule_AddIntMacro(module, FC_DUAL); PyModule_AddIntMacro(module, FC_MONO); PyModule_AddIntMacro(module, FC_CHARCELL); PyModule_AddIntMacro(module, FC_WIDTH_NORMAL); return true; } kitty-0.41.1/kitty/fonts.c0000664000175000017510000032536114773370543014762 0ustar nileshnilesh/* * vim:fileencoding=utf-8 * fonts.c * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "fonts.h" #include "pyport.h" #include "charsets.h" #include "state.h" #include "char-props.h" #include "decorations.h" #include "glyph-cache.h" #define MISSING_GLYPH 1 #define MAX_NUM_EXTRA_GLYPHS_PUA 4u #define debug debug_fonts static PyObject *python_send_to_gpu_impl = NULL; extern PyTypeObject Line_Type; enum {NO_FONT=-3, MISSING_FONT=-2, BLANK_FONT=-1, BOX_FONT=0}; typedef enum { LIGATURE_UNKNOWN, INFINITE_LIGATURE_START, INFINITE_LIGATURE_MIDDLE, INFINITE_LIGATURE_END } LigatureType; typedef struct { unsigned x, y, z, xnum, ynum, max_y; } GPUSpriteTracker; typedef struct RunFont { unsigned scale, subscale_n, subscale_d, multicell_y; union { struct { uint8_t vertical: 4; uint8_t horizontal: 4; }; uint8_t val; } align; ssize_t font_idx; } RunFont; static hb_buffer_t *harfbuzz_buffer = NULL; static hb_feature_t hb_features[3] = {{0}}; static struct { char_type *codepoints; size_t capacity; } shape_buffer = {0}; static size_t max_texture_size = 1024, max_array_len = 1024; typedef enum { LIGA_FEATURE, DLIG_FEATURE, CALT_FEATURE } HBFeature; typedef struct { char_type left, right; size_t font_idx; } SymbolMap; static SymbolMap *symbol_maps = NULL, *narrow_symbols = NULL; static size_t num_symbol_maps = 0, num_narrow_symbols = 0; typedef enum { SPACER_STRATEGY_UNKNOWN, SPACERS_BEFORE, SPACERS_AFTER, SPACERS_IOSEVKA } SpacerStrategy; typedef struct { PyObject *face; // Map glyphs to sprite map co-ords SPRITE_POSITION_MAP_HANDLE sprite_position_hash_table; hb_feature_t* ffs_hb_features; size_t num_ffs_hb_features; GLYPH_PROPERTIES_MAP_HANDLE glyph_properties_hash_table; bool bold, italic, emoji_presentation; SpacerStrategy spacer_strategy; } Font; typedef struct Canvas { pixel *buf; uint8_t *alpha_mask; unsigned current_cells, alloced_cells, alloced_scale, current_scale; size_t size_in_bytes, alpha_mask_sz_in_bytes; } Canvas; #define NAME fallback_font_map_t #define KEY_TY const char* #define VAL_TY size_t static void free_const(const void* x) { free((void*)x); } #define KEY_DTOR_FN free_const #include "kitty-verstable.h" typedef struct ScaledFontData { FontCellMetrics fcm; double font_sz_in_pts; } ScaledFontData; #define NAME scaled_font_map_t #define KEY_TY float #define VAL_TY ScaledFontData #define HASH_FN vt_hash_float #define CMPR_FN vt_cmpr_float #include "kitty-verstable.h" typedef union DecorationsKey { struct { uint8_t scale : 8, subscale_n : 8, subscale_d : 8, align : 8, multicell_y : 8, u1 : 8, u2 : 8, u3 : 8; }; uint64_t val; } DecorationsKey; static_assert(sizeof(DecorationsKey) == sizeof(uint64_t), "Fix the ordering of DecorationsKey"); typedef struct DecorationMetadata { sprite_index start_idx; DecorationGeometry underline_region; } DecorationMetadata; static uint64_t hash_decorations_key(DecorationsKey k) { return vt_hash_integer(k.val); } static bool cmpr_decorations_key(DecorationsKey a, DecorationsKey b) { return a.val == b.val; } #define NAME decorations_index_map_t #define KEY_TY DecorationsKey #define VAL_TY DecorationMetadata #define HASH_FN hash_decorations_key #define CMPR_FN cmpr_decorations_key #include "kitty-verstable.h" typedef struct { FONTS_DATA_HEAD id_type id; size_t fonts_capacity, fonts_count, fallback_fonts_count; ssize_t medium_font_idx, bold_font_idx, italic_font_idx, bi_font_idx, first_symbol_font_idx, first_fallback_font_idx; Font *fonts; Canvas canvas; GPUSpriteTracker sprite_tracker; fallback_font_map_t fallback_font_map; scaled_font_map_t scaled_font_map; decorations_index_map_t decorations_index_map; } FontGroup; static FontGroup* font_groups = NULL; static size_t font_groups_capacity = 0; static size_t num_font_groups = 0; static id_type font_group_id_counter = 0; static void initialize_font_group(FontGroup *fg); static void display_rgba_data(const pixel *b, unsigned width, unsigned height) { RAII_PyObject(m, PyImport_ImportModule("kitty.fonts.render")); RAII_PyObject(f, PyObject_GetAttrString(m, "show")); RAII_PyObject(data, PyMemoryView_FromMemory((char*)b, (Py_ssize_t)width * height * sizeof(b[0]), PyBUF_READ)); RAII_PyObject(ret, PyObject_CallFunction(f, "OII", data, width, height)); if (ret == NULL) PyErr_Print(); } static void dump_sprite(pixel *b, unsigned width, unsigned height) { for (unsigned y = 0; y < height; y++) { pixel *p = b + y * width; for (unsigned x = 0; x < width; x++) printf("%d ", p[x] != 0); printf("\n"); } } static void python_send_to_gpu(FontGroup *fg, sprite_index idx, pixel *buf) { if (0) dump_sprite(buf, fg->fcm.cell_width, fg->fcm.cell_height); unsigned int x, y, z; sprite_index_to_pos(idx, fg->sprite_tracker.xnum, fg->sprite_tracker.ynum, &x, &y, &z); const size_t sprite_size = (size_t)fg->fcm.cell_width * fg->fcm.cell_height; PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIy#", x, y, z, buf, sprite_size * sizeof(buf[0])); if (ret == NULL) PyErr_Print(); else Py_DECREF(ret); } static void ensure_canvas_can_fit(FontGroup *fg, unsigned cells, unsigned scale) { #define cs(cells, scale) (sizeof(fg->canvas.buf[0]) * 3u * cells * fg->fcm.cell_width * (fg->fcm.cell_height + 1) * scale * scale) size_t size_in_bytes = cs(cells, scale); if (size_in_bytes > fg->canvas.size_in_bytes) { free(fg->canvas.buf); fg->canvas.alloced_cells = MAX(8u, cells + 4u); fg->canvas.alloced_scale = MAX(scale, 4u); fg->canvas.size_in_bytes = cs(fg->canvas.alloced_cells, fg->canvas.alloced_scale); fg->canvas.buf = malloc(fg->canvas.size_in_bytes); if (!fg->canvas.buf) fatal("Out of memory allocating canvas"); } fg->canvas.current_cells = cells; fg->canvas.current_scale = scale; if (fg->canvas.buf) memset(fg->canvas.buf, 0, cs(cells, scale)); #undef cs size_in_bytes = (sizeof(fg->canvas.alpha_mask[0]) * SUPERSAMPLE_FACTOR * SUPERSAMPLE_FACTOR * 2 * fg->fcm.cell_width * fg->fcm.cell_height * scale * scale); if (size_in_bytes > fg->canvas.alpha_mask_sz_in_bytes) { fg->canvas.alpha_mask_sz_in_bytes = size_in_bytes; fg->canvas.alpha_mask = malloc(fg->canvas.alpha_mask_sz_in_bytes); if (!fg->canvas.alpha_mask) fatal("Out of memory allocating canvas"); } } static void save_window_font_groups(void) { for (size_t o = 0; o < global_state.num_os_windows; o++) { OSWindow *w = global_state.os_windows + o; w->temp_font_group_id = w->fonts_data ? ((FontGroup*)(w->fonts_data))->id : 0; } } static void restore_window_font_groups(void) { for (size_t o = 0; o < global_state.num_os_windows; o++) { OSWindow *w = global_state.os_windows + o; w->fonts_data = NULL; for (size_t i = 0; i < num_font_groups; i++) { if (font_groups[i].id == w->temp_font_group_id) { w->fonts_data = (FONTS_DATA_HANDLE)(font_groups + i); break; } } } } static bool font_group_is_unused(FontGroup *fg) { for (size_t o = 0; o < global_state.num_os_windows; o++) { OSWindow *w = global_state.os_windows + o; if (w->temp_font_group_id == fg->id) return false; } return true; } void free_maps(Font *font) { free_sprite_position_hash_table(&font->sprite_position_hash_table); free_glyph_properties_hash_table(&font->glyph_properties_hash_table); } static void del_font(Font *f) { Py_CLEAR(f->face); free(f->ffs_hb_features); f->ffs_hb_features = NULL; free_maps(f); f->bold = false; f->italic = false; } static void del_font_group(FontGroup *fg) { free(fg->canvas.buf); free(fg->canvas.alpha_mask); fg->canvas = (Canvas){0}; free_sprite_data((FONTS_DATA_HANDLE)fg); vt_cleanup(&fg->fallback_font_map); vt_cleanup(&fg->scaled_font_map); vt_cleanup(&fg->decorations_index_map); for (size_t i = 0; i < fg->fonts_count; i++) del_font(fg->fonts + i); free(fg->fonts); fg->fonts = NULL; fg->fonts_count = 0; } static void trim_unused_font_groups(void) { save_window_font_groups(); size_t i = 0; while (i < num_font_groups) { if (font_group_is_unused(font_groups + i)) { del_font_group(font_groups + i); size_t num_to_right = (--num_font_groups) - i; if (!num_to_right) break; memmove(font_groups + i, font_groups + 1 + i, num_to_right * sizeof(FontGroup)); } else i++; } restore_window_font_groups(); } static void add_font_group(void) { if (num_font_groups) trim_unused_font_groups(); if (num_font_groups >= font_groups_capacity) { save_window_font_groups(); font_groups_capacity += 5; font_groups = realloc(font_groups, sizeof(FontGroup) * font_groups_capacity); if (font_groups == NULL) fatal("Out of memory creating a new font group"); restore_window_font_groups(); } num_font_groups++; } static FontGroup* font_group_for(double font_sz_in_pts, double logical_dpi_x, double logical_dpi_y) { for (size_t i = 0; i < num_font_groups; i++) { FontGroup *fg = font_groups + i; if (fg->font_sz_in_pts == font_sz_in_pts && fg->logical_dpi_x == logical_dpi_x && fg->logical_dpi_y == logical_dpi_y) return fg; } add_font_group(); FontGroup *fg = font_groups + num_font_groups - 1; zero_at_ptr(fg); fg->font_sz_in_pts = font_sz_in_pts; fg->logical_dpi_x = logical_dpi_x; fg->logical_dpi_y = logical_dpi_y; fg->id = ++font_group_id_counter; initialize_font_group(fg); return fg; } // Sprites {{{ void sprite_tracker_set_limits(size_t max_texture_size_, size_t max_array_len_) { max_texture_size = max_texture_size_; max_array_len = MIN(0xfffu, max_array_len_); } static bool do_increment(FontGroup *fg) { fg->sprite_tracker.x++; if (fg->sprite_tracker.x >= fg->sprite_tracker.xnum) { fg->sprite_tracker.x = 0; fg->sprite_tracker.y++; fg->sprite_tracker.ynum = MIN(MAX(fg->sprite_tracker.ynum, fg->sprite_tracker.y + 1), fg->sprite_tracker.max_y); if (fg->sprite_tracker.y >= fg->sprite_tracker.max_y) { fg->sprite_tracker.y = 0; fg->sprite_tracker.z++; if (fg->sprite_tracker.z >= MIN((size_t)UINT16_MAX, max_array_len)) { PyErr_SetString(PyExc_RuntimeError, "Out of texture space for sprites"); return false; } } } return true; } static uint32_t current_sprite_index(const GPUSpriteTracker *sprite_tracker) { return sprite_tracker->z * (sprite_tracker->xnum * sprite_tracker->ynum) + sprite_tracker->y * sprite_tracker->xnum + sprite_tracker->x; } static SpritePosition* sprite_position_for(FontGroup *fg, RunFont rf, glyph_index *glyphs, unsigned glyph_count, uint8_t ligature_index, unsigned cell_count) { bool created; Font *font = fg->fonts + rf.font_idx; uint8_t subscale = ((rf.subscale_n & 0xf) << 4) | (rf.subscale_d & 0xf); SpritePosition *s = find_or_create_sprite_position( font->sprite_position_hash_table, glyphs, glyph_count, ligature_index, cell_count, rf.scale, subscale, rf.multicell_y, rf.align.val, &created); if (!s) { PyErr_NoMemory(); return NULL; } return s; } void sprite_tracker_current_layout(FONTS_DATA_HANDLE data, unsigned int *x, unsigned int *y, unsigned int *z) { FontGroup *fg = (FontGroup*)data; *x = fg->sprite_tracker.xnum; *y = fg->sprite_tracker.ynum; *z = fg->sprite_tracker.z; } static void sprite_tracker_set_layout(GPUSpriteTracker *sprite_tracker, unsigned int cell_width, unsigned int cell_height) { sprite_tracker->xnum = MIN(MAX(1u, max_texture_size / cell_width), (size_t)UINT16_MAX); sprite_tracker->max_y = MIN(MAX(1u, max_texture_size / cell_height), (size_t)UINT16_MAX); sprite_tracker->ynum = 1; sprite_tracker->x = 0; sprite_tracker->y = 0; sprite_tracker->z = 0; } static void calculate_underline_exclusion_zones(pixel *buf, const FontGroup *fg, DecorationGeometry dg, FontCellMetrics scaled_metrics) { pixel *ans = buf + fg->fcm.cell_height * fg->fcm.cell_width; const unsigned bottom = MIN(dg.top + dg.height, fg->fcm.cell_height); unsigned thickness = scaled_metrics.underline_thickness; switch(OPT(underline_exclusion.unit)) { case 2: thickness = ((long)round((OPT(underline_exclusion).thickness * (fg->logical_dpi_x / 72.0)))); break; case 1: thickness = (unsigned)OPT(underline_exclusion).thickness; break; default: thickness = (unsigned)(OPT(underline_exclusion).thickness * thickness); break; } thickness = MAX(1u, thickness); if (0) printf("dg: %u %u cell_height: %u scaled_cell_height: %u\n", dg.top, dg.height, fg->fcm.cell_height, scaled_metrics.cell_height); if (0) { display_rgba_data(buf, fg->fcm.cell_width, fg->fcm.cell_height); printf("\n"); } unsigned max_overlap = 0; #define is_rendered(x, y) ((buf[(y) * fg->fcm.cell_width + (x)] & 0x000000ff) > 0) for (unsigned x = 0; x < fg->fcm.cell_width; x++) { for (unsigned y = dg.top; y < bottom && !ans[x]; y++) { if (is_rendered(x, y)) { while (y + 1 < bottom && is_rendered(x, y + 1)) y++; max_overlap = MAX(max_overlap, y - dg.top + 1); unsigned start_x = x > thickness ? x - thickness : 0; for (unsigned dx = start_x; dx < MIN(x + thickness, fg->fcm.cell_width); dx++) ans[dx] = 0xffffffff; break; } } } #undef is_rendered if (dg.height > 1 && max_overlap <= dg.height / 2) { // ignore half thickness overlap as this is likely a false positive not an actual descender memset(ans, 0, fg->fcm.cell_width * sizeof(ans[0])); } if (0) dump_sprite(ans, fg->fcm.cell_width, 1); } static sprite_index current_send_sprite_to_gpu(FontGroup *fg, pixel *buf, DecorationMetadata dec, FontCellMetrics scaled_metrics) { sprite_index ans = current_sprite_index(&fg->sprite_tracker); if (!do_increment(fg)) return 0; if (python_send_to_gpu_impl) { python_send_to_gpu(fg, ans, buf); return ans; } if (dec.underline_region.height && OPT(underline_exclusion).thickness > 0) calculate_underline_exclusion_zones( buf, fg, dec.underline_region, scaled_metrics); send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, ans, buf, dec.start_idx); if (0) { printf("Sprite: %u dec_idx: %u\n", ans, dec.start_idx); display_rgba_data(buf, fg->fcm.cell_width, fg->fcm.cell_height); printf("\n"); } return ans; } // }}} static PyObject* desc_to_face(PyObject *desc, FONTS_DATA_HANDLE fg) { PyObject *d = specialize_font_descriptor(desc, fg->font_sz_in_pts, fg->logical_dpi_x, fg->logical_dpi_y); if (d == NULL) return NULL; PyObject *ans = face_from_descriptor(d, fg); Py_DECREF(d); return ans; } static void add_feature(FontFeatures *output, const hb_feature_t *feature) { for (size_t i = 0; i < output->count; i++) { if (output->features[i].tag == feature->tag) { output->features[i] = *feature; return; } } output->features[output->count++] = *feature; } static const char* tag_to_string(uint32_t tag, uint8_t bytes[5]) { bytes[0] = (tag >> 24) & 0xff; bytes[1] = (tag >> 16) & 0xff; bytes[2] = (tag >> 8) & 0xff; bytes[3] = (tag) & 0xff; bytes[4] = 0; return (const char*)bytes; } PyObject* font_features_as_dict(const FontFeatures *font_features) { RAII_PyObject(ans, PyDict_New()); if (!ans) return NULL; char buf[256]; char tag[5] = {0}; for (size_t i = 0; i < font_features->count; i++) { tag_to_string(font_features->features[i].tag, (unsigned char*)tag); hb_feature_to_string(&font_features->features[i], buf, arraysz(buf)); PyObject *t = PyUnicode_FromString(buf); if (!t) return NULL; if (PyDict_SetItemString(ans, tag, t) != 0) return NULL; } Py_INCREF(ans); return ans; } bool create_features_for_face(const char *psname, PyObject *features, FontFeatures *output) { size_t count_from_descriptor = features ? PyTuple_GET_SIZE(features): 0; __typeof__(OPT(font_features).entries) from_opts = NULL; if (psname) { for (size_t i = 0; i < OPT(font_features).num && !from_opts; i++) { __typeof__(OPT(font_features).entries) e = OPT(font_features).entries + i; if (strcmp(e->psname, psname) == 0) from_opts = e; } } size_t count_from_opts = from_opts ? from_opts->num : 0; output->features = calloc(MAX(2u, count_from_opts + count_from_descriptor), sizeof(output->features[0])); if (!output->features) { PyErr_NoMemory(); return false; } for (size_t i = 0; i < count_from_opts; i++) { add_feature(output, &from_opts->features[i]); } for (size_t i = 0; i < count_from_descriptor; i++) { ParsedFontFeature *f = (ParsedFontFeature*)PyTuple_GET_ITEM(features, i); add_feature(output, &f->feature); } if (!output->count) { if (strstr(psname, "NimbusMonoPS-") == psname) { add_feature(output, &hb_features[LIGA_FEATURE]); add_feature(output, &hb_features[DLIG_FEATURE]); } } return true; } static bool init_hash_tables(Font *f) { f->sprite_position_hash_table = create_sprite_position_hash_table(); if (!f->sprite_position_hash_table) { PyErr_NoMemory(); return false; } f->glyph_properties_hash_table = create_glyph_properties_hash_table(); if (!f->glyph_properties_hash_table) { PyErr_NoMemory(); return false; } return true; } static bool init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentation) { f->face = face; Py_INCREF(f->face); f->bold = bold; f->italic = italic; f->emoji_presentation = emoji_presentation; if (!init_hash_tables(f)) return false; const FontFeatures *features = features_for_face(face); f->ffs_hb_features = calloc(1 + features->count, sizeof(hb_feature_t)); if (!f->ffs_hb_features) { PyErr_NoMemory(); return false; } f->num_ffs_hb_features = features->count; if (features->count) memcpy(f->ffs_hb_features, features->features, sizeof(hb_feature_t) * features->count); memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[CALT_FEATURE], sizeof(hb_feature_t)); return true; } static void free_font_groups(void) { if (font_groups) { for (size_t i = 0; i < num_font_groups; i++) del_font_group(font_groups + i); free(font_groups); font_groups = NULL; font_groups_capacity = 0; num_font_groups = 0; } } static void adjust_metric(unsigned int *metric, float adj, AdjustmentUnit unit, double dpi) { if (adj == 0.f) return; int a = 0; switch (unit) { case POINT: a = ((long)round((adj * (dpi / 72.0)))); break; case PERCENT: *metric = (int)roundf((fabsf(adj) * (float)*metric) / 100.f); return; case PIXEL: a = (int)roundf(adj); break; } *metric = (a < 0 && -a > (int)*metric) ? 0 : *metric + a; } static unsigned int adjust_ypos(unsigned int pos, unsigned int cell_height, int adjustment) { if (adjustment >= 0) adjustment = MIN(adjustment, (int)pos - 1); else adjustment = MAX(adjustment, (int)pos - (int)cell_height + 1); return pos - adjustment; } static void calc_cell_metrics(FontGroup *fg, PyObject *face) { fg->fcm = cell_metrics(face); if (!fg->fcm.cell_width) fatal("Failed to calculate cell width for the specified font"); unsigned int before_cell_height = fg->fcm.cell_height; unsigned int cw = fg->fcm.cell_width, ch = fg->fcm.cell_height; adjust_metric(&cw, OPT(cell_width).val, OPT(cell_width).unit, fg->logical_dpi_x); adjust_metric(&ch, OPT(cell_height).val, OPT(cell_height).unit, fg->logical_dpi_y); #define MAX_DIM 1000 #define MIN_WIDTH 2 #define MIN_HEIGHT 4 if (cw >= MIN_WIDTH && cw <= MAX_DIM) fg->fcm.cell_width = cw; else log_error("Cell width invalid after adjustment, ignoring modify_font cell_width"); if (ch >= MIN_HEIGHT && ch <= MAX_DIM) fg->fcm.cell_height = ch; else log_error("Cell height invalid after adjustment, ignoring modify_font cell_height"); int line_height_adjustment = fg->fcm.cell_height - before_cell_height; if (fg->fcm.cell_height < MIN_HEIGHT) fatal("Line height too small: %u", fg->fcm.cell_height); if (fg->fcm.cell_height > MAX_DIM) fatal("Line height too large: %u", fg->fcm.cell_height); if (fg->fcm.cell_width < MIN_WIDTH) fatal("Cell width too small: %u", fg->fcm.cell_width); if (fg->fcm.cell_width > MAX_DIM) fatal("Cell width too large: %u", fg->fcm.cell_width); #undef MIN_WIDTH #undef MIN_HEIGHT #undef MAX_DIM unsigned int baseline_before = fg->fcm.baseline; #define A(which, dpi) adjust_metric(&fg->fcm.which, OPT(which).val, OPT(which).unit, fg->logical_dpi_##dpi); A(underline_thickness, y); A(underline_position, y); A(strikethrough_thickness, y); A(strikethrough_position, y); A(baseline, y); #undef A if (baseline_before != fg->fcm.baseline) { int adjustment = fg->fcm.baseline - baseline_before; fg->fcm.baseline = adjust_ypos(baseline_before, fg->fcm.cell_height, adjustment); fg->fcm.underline_position = adjust_ypos(fg->fcm.underline_position, fg->fcm.cell_height, adjustment); fg->fcm.strikethrough_position = adjust_ypos(fg->fcm.strikethrough_position, fg->fcm.cell_height, adjustment); } fg->fcm.underline_position = MIN(fg->fcm.cell_height - 1, fg->fcm.underline_position); // ensure there is at least a couple of pixels available to render styled underlines // there should be at least one pixel on either side of the underline_position if (fg->fcm.underline_position > fg->fcm.baseline + 1 && fg->fcm.underline_position > fg->fcm.cell_height - 1) fg->fcm.underline_position = MAX(fg->fcm.baseline + 1, fg->fcm.cell_height - 1); if (line_height_adjustment > 1) { fg->fcm.baseline += MIN(fg->fcm.cell_height - 1, (unsigned)line_height_adjustment / 2); fg->fcm.underline_position += MIN(fg->fcm.cell_height - 1, (unsigned)line_height_adjustment / 2); } } static bool face_has_codepoint(const void* face, char_type cp) { return glyph_id_for_codepoint(face, cp) > 0; } static bool has_emoji_presentation(const CPUCell *c, const ListOfChars *lc) { bool is_text_presentation; CharProps cp; return c->is_multicell && lc->count && (cp = char_props_for(lc->chars[0])).is_emoji && ( ( (is_text_presentation = wcwidth_std(cp) < 2) && lc->count > 1 && lc->chars[1] == VS16 ) || ( !is_text_presentation && (lc->count == 1 || lc->chars[1] != VS15) ) ); } bool has_cell_text(bool(*has_codepoint)(const void*, char_type ch), const void* face, bool do_debug, const ListOfChars *lc) { RAII_ListOfChars(llc); if (!has_codepoint(face, lc->chars[0])) goto not_found; for (unsigned i = 1; i < lc->count; i++) { if (!char_props_for(lc->chars[i]).is_non_rendered) { ensure_space_for_chars(&llc, llc.count+1); llc.chars[llc.count++] = lc->chars[i]; } } if (llc.count == 0) return true; if (llc.count == 1) { if (has_codepoint(face, llc.chars[0])) return true; char_type ch = 0; if (hb_unicode_compose(hb_unicode_funcs_get_default(), lc->chars[0], llc.chars[0], &ch) && face_has_codepoint(face, ch)) return true; goto not_found; } for (unsigned i = 0; i < llc.count; i++) { if (!has_codepoint(face, llc.chars[i])) goto not_found; } return true; not_found: if (do_debug) { debug("The font chosen by the OS for the text: "); debug("U+%x ", lc->chars[0]); for (unsigned i = 1; i < lc->count; i++) { if (lc->chars[i]) debug("U+%x ", lc->chars[i]); } debug("is "); PyObject_Print((PyObject*)face, stderr, 0); debug(" but it does not actually contain glyphs for that text\n"); } return false; } static void output_cell_fallback_data(const ListOfChars *lc, bool bold, bool italic, bool emoji_presentation, PyObject *face) { debug("U+%x ", lc->chars[0]); for (unsigned i = 1; i < lc->count; i++) debug("U+%x ", lc->chars[i]); if (bold) debug("bold "); if (italic) debug("italic "); if (emoji_presentation) debug("emoji_presentation "); if (PyLong_Check(face)) debug("using previous fallback font at index: "); PyObject_Print(face, stderr, 0); debug("\n"); } PyObject* iter_fallback_faces(FONTS_DATA_HANDLE fgh, ssize_t *idx) { FontGroup *fg = (FontGroup*)fgh; if (*idx + 1 < (ssize_t)fg->fallback_fonts_count) { *idx += 1; return fg->fonts[fg->first_fallback_font_idx + *idx].face; } return NULL; } static ssize_t load_fallback_font(FontGroup *fg, const ListOfChars *lc, bool bold, bool italic, bool emoji_presentation) { if (fg->fallback_fonts_count > 100) { log_error("Too many fallback fonts"); return MISSING_FONT; } ssize_t f; if (bold) f = italic ? fg->bi_font_idx : fg->bold_font_idx; else f = italic ? fg->italic_font_idx : fg->medium_font_idx; if (f < 0) f = fg->medium_font_idx; PyObject *face = create_fallback_face(fg->fonts[f].face, lc, bold, italic, emoji_presentation, (FONTS_DATA_HANDLE)fg); if (face == NULL) { PyErr_Print(); return MISSING_FONT; } if (face == Py_None) { Py_DECREF(face); return MISSING_FONT; } if (global_state.debug_font_fallback) output_cell_fallback_data(lc, bold, italic, emoji_presentation, face); if (PyLong_Check(face)) { ssize_t ans = fg->first_fallback_font_idx + PyLong_AsSsize_t(face); Py_DECREF(face); return ans; } set_size_for_face(face, fg->fcm.cell_height, true, (FONTS_DATA_HANDLE)fg); ensure_space_for(fg, fonts, Font, fg->fonts_count + 1, fonts_capacity, 5, true); ssize_t ans = fg->first_fallback_font_idx + fg->fallback_fonts_count; Font *af = &fg->fonts[ans]; if (!init_font(af, face, bold, italic, emoji_presentation)) fatal("Out of memory"); Py_DECREF(face); fg->fallback_fonts_count++; fg->fonts_count++; return ans; } static size_t chars_as_utf8(const ListOfChars *lc, char *buf, size_t bufsz, char_type zero_char) { size_t n; if (lc->count == 1) n = encode_utf8(lc->chars[0] ? lc->chars[0] : zero_char, buf); else { n = encode_utf8(lc->chars[0], buf); if (lc->chars[0] != '\t') for (unsigned i = 1; i < lc->count && n < bufsz - 4; i++) n += encode_utf8(lc->chars[i], buf + n); } buf[n] = 0; return n; } static ssize_t fallback_font(FontGroup *fg, const CPUCell *cpu_cell, const GPUCell *gpu_cell, const ListOfChars *lc) { bool bold = gpu_cell->attrs.bold; bool italic = gpu_cell->attrs.italic; bool emoji_presentation = has_emoji_presentation(cpu_cell, lc); char style = emoji_presentation ? 'a' : 'A'; if (bold) style += italic ? 3 : 2; else style += italic ? 1 : 0; char cell_text[4u * (MAX_NUM_CODEPOINTS_PER_CELL + 8u)] = {style}; const size_t cell_text_len = 1 + chars_as_utf8(lc, cell_text + 1, arraysz(cell_text) - 1, ' '); fallback_font_map_t_itr fi = vt_get(&fg->fallback_font_map, cell_text); if (!vt_is_end(fi)) return fi.data->val; ssize_t idx = load_fallback_font(fg, lc, bold, italic, emoji_presentation); const char *alloced_key = strndup(cell_text, cell_text_len); if (alloced_key) vt_insert(&fg->fallback_font_map, alloced_key, idx); return idx; } static ssize_t in_symbol_maps(FontGroup *fg, char_type ch) { for (size_t i = 0; i < num_symbol_maps; i++) { if (symbol_maps[i].left <= ch && ch <= symbol_maps[i].right) return fg->first_symbol_font_idx + symbol_maps[i].font_idx; } return NO_FONT; } static bool allow_use_of_box_fonts = true; // Decides which 'font' to use for a given cell. // // Possible results: // - NO_FONT // - MISSING_FONT // - BLANK_FONT // - BOX_FONT // - an index in the fonts list static ssize_t font_for_cell(FontGroup *fg, const CPUCell *cpu_cell, const GPUCell *gpu_cell, bool *is_main_font, bool *is_emoji_presentation, TextCache *tc, ListOfChars *lc) { *is_main_font = false; *is_emoji_presentation = false; text_in_cell(cpu_cell, tc, lc); START_ALLOW_CASE_RANGE ssize_t ans; switch(lc->chars[0]) { case 0: case '\t': case IMAGE_PLACEHOLDER_CHAR: return BLANK_FONT; case 0x2500 ... 0x2573: case 0x2574 ... 0x259f: case 0x25d6 ... 0x25d7: case 0x25cb: case 0x25c9: case 0x25cf: case 0x25dc ... 0x25e5: case 0x2800 ... 0x28ff: case 0xe0b0 ... 0xe0bf: case 0xe0d6 ... 0xe0d7: // powerline box drawing case 0xee00 ... 0xee0b: // fira code progress bar/spinner case 0x1fb00 ... 0x1fbae: // symbols for legacy computing case 0x1cd00 ... 0x1cde5: case 0x1fbe6: case 0x1fbe7: // octants case 0xf5d0 ... 0xf60d: // branch drawing characters if (allow_use_of_box_fonts) return BOX_FONT; /* fallthrough */ default: if (lc->count == 1 && (lc->chars[0] == ' ' || lc->chars[0] == 0x2002 /* en-space */)) return BLANK_FONT; *is_emoji_presentation = has_emoji_presentation(cpu_cell, lc); ans = in_symbol_maps(fg, lc->chars[0]); if (ans > -1) return ans; switch(gpu_cell->attrs.bold | (gpu_cell->attrs.italic << 1)) { case 0: ans = fg->medium_font_idx; break; case 1: ans = fg->bold_font_idx ; break; case 2: ans = fg->italic_font_idx; break; case 3: ans = fg->bi_font_idx; break; } if (ans < 0) ans = fg->medium_font_idx; if (!*is_emoji_presentation && has_cell_text((bool(*)(const void*, char_type))face_has_codepoint, (fg->fonts + ans)->face, false, lc)) { *is_main_font = true; return ans; } return fallback_font(fg, cpu_cell, gpu_cell, lc); } END_ALLOW_CASE_RANGE } // Gives a unique (arbitrary) id to a box glyph static glyph_index box_glyph_id(char_type ch) { START_ALLOW_CASE_RANGE switch(ch) { case 0x2500 ... 0x25ff: return ch - 0x2500; // IDs from 0x00 to 0xff case 0xe0b0 ... 0xee0b: return 0x100 + ch - 0xe0b0; // IDs from 0x100 to 0xe5b case 0x2800 ... 0x28ff: return 0xf00 + ch - 0x2800; // IDs from 0xf00 to 0xfff case 0x1fb00 ... 0x1fbae: return 0x1000 + ch - 0x1fb00; // IDs from 0x1000 to 0x10ae case 0x1cd00 ... 0x1cde5: return 0x1100 + ch - 0x1cd00; // IDs from 0x1100 to 0x11e5 case 0x1fbe6: case 0x1fbe7: return 0x11e6 + ch - 0x1fbe6; case 0xf5d0 ... 0xf60d: return 0x2000 + ch - 0xf5d0; // IDs from 0x2000 to 0x203d default: return 0xffff; } END_ALLOW_CASE_RANGE } static PyObject *descriptor_for_idx = NULL; void render_alpha_mask(const uint8_t *alpha_mask, pixel* dest, const Region *src_rect, const Region *dest_rect, size_t src_stride, size_t dest_stride, pixel color_rgb) { pixel col = color_rgb << 8; for (size_t sr = src_rect->top, dr = dest_rect->top; sr < src_rect->bottom && dr < dest_rect->bottom; sr++, dr++) { pixel *d = dest + dest_stride * dr; const uint8_t *s = alpha_mask + src_stride * sr; for(size_t sc = src_rect->left, dc = dest_rect->left; sc < src_rect->right && dc < dest_rect->right; sc++, dc++) { uint8_t src_alpha = d[dc] & 0xff; uint8_t alpha = s[sc]; d[dc] = col | MAX(alpha, src_alpha); } } } typedef struct GlyphRenderScratch { SpritePosition* *sprite_positions; glyph_index *glyphs; size_t sz; ListOfChars *lc; } GlyphRenderScratch; static GlyphRenderScratch global_glyph_render_scratch = {0}; static void ensure_glyph_render_scratch_space(size_t sz) { #define a global_glyph_render_scratch sz += 16; if (a.sz < sz) { free(a.glyphs); a.glyphs = malloc(sz * sizeof(a.glyphs[0])); if (!a.glyphs) fatal("Out of memory"); free(a.sprite_positions); a.sprite_positions = malloc(sz * sizeof(SpritePosition*)); if (!a.sprite_positions) fatal("Out of memory"); a.sz = sz; if (!a.lc) { a.lc = alloc_list_of_chars(); if (!a.lc) fatal("Out of memory"); } } #undef a } static float effective_scale(RunFont rf) { float ans = MAX(1u, rf.scale); if (rf.subscale_n && rf.subscale_d && rf.subscale_n < rf.subscale_d) { ans *= ((float)rf.subscale_n) / rf.subscale_d; } return ans; } static float scaled_cell_dimensions(RunFont rf, unsigned *width, unsigned *height) { float frac = MAX(effective_scale(rf), MIN(4.f, (float)*width) / *width); *width = (unsigned)ceilf(frac * *width); *height = (unsigned)ceilf(frac * *height); return frac; } static float apply_scale_to_font_group(FontGroup *fg, RunFont *rf) { unsigned int scaled_cell_width = fg->fcm.cell_width, scaled_cell_height = fg->fcm.cell_height; float scale = rf ? scaled_cell_dimensions(*rf, &scaled_cell_width, &scaled_cell_height) : 1.f; scaled_font_map_t_itr i = vt_get(&fg->scaled_font_map, scale); ScaledFontData sfd; #define apply_scaling(which_fg) if (!face_apply_scaling(medium_font->face, (FONTS_DATA_HANDLE)(which_fg))) { \ if (PyErr_Occurred()) PyErr_Print(); \ fatal("Could not apply scale of %f to font group at size: %f", scale, (which_fg)->font_sz_in_pts); \ } if (vt_is_end(i)) { Font *medium_font = &fg->fonts[fg->medium_font_idx]; FontGroup copy = {.fcm=fg->fcm, .logical_dpi_x=fg->logical_dpi_x, .logical_dpi_y=fg->logical_dpi_y}; copy.fcm.cell_width = scaled_cell_width; copy.fcm.cell_height = scaled_cell_height; copy.font_sz_in_pts = scale * fg->font_sz_in_pts; apply_scaling(©); calc_cell_metrics(©, medium_font->face); if (copy.fcm.cell_width > scaled_cell_width || copy.fcm.cell_height > scaled_cell_height) { float wfrac = (float)copy.fcm.cell_width / scaled_cell_width, hfrac = (float)copy.fcm.cell_height / scaled_cell_height; float frac = MIN(wfrac, hfrac); copy.font_sz_in_pts *= frac; while (true) { apply_scaling(©); calc_cell_metrics(©, medium_font->face); if (copy.fcm.cell_width <= scaled_cell_width && copy.fcm.cell_height <= scaled_cell_height) break; if (copy.font_sz_in_pts <= 1) break; copy.font_sz_in_pts -= 0.1; } } sfd.fcm = copy.fcm; sfd.font_sz_in_pts = copy.font_sz_in_pts; sfd.fcm.cell_width = scaled_cell_width; sfd.fcm.cell_height = scaled_cell_height; if (vt_is_end(vt_insert(&fg->scaled_font_map, scale, sfd))) fatal("Out of memory inserting scaled font data into map"); apply_scaling(fg); } else sfd = i.data->val; fg->font_sz_in_pts = sfd.font_sz_in_pts; fg->fcm = sfd.fcm; return scale; #undef apply_scaling } static pixel* pointer_to_space_for_last_sprite(Canvas *canvas, FontCellMetrics fcm, unsigned *sz) { *sz = fcm.cell_width * (fcm.cell_height + 1); return canvas->buf + (canvas->size_in_bytes / sizeof(canvas->buf[0]) - *sz); } static pixel* extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells) { unsigned sz; pixel *ans = pointer_to_space_for_last_sprite(&fg->canvas, fg->fcm, &sz); pixel *dest = ans, *src = fg->canvas.buf + (i * fg->fcm.cell_width); unsigned int stride = fg->fcm.cell_width * num_cells; for (unsigned int r = 0; r < fg->fcm.cell_height; r++, dest += fg->fcm.cell_width, src += stride) memcpy(dest, src, fg->fcm.cell_width * sizeof(fg->canvas.buf[0])); memset(ans + sz - fg->fcm.cell_width, 0, fg->fcm.cell_width * sizeof(ans[0])); // underline_exclusion return ans; } static void calculate_regions_for_line(RunFont rf, unsigned cell_height, Region *src, Region *dest) { unsigned src_height = src->bottom; Region src_in_full_coords = *src; unsigned full_dest_height = cell_height * rf.scale; if (rf.subscale_n && rf.subscale_d) { switch(rf.align.vertical) { case 0: break; // top aligned no change case 1: // bottom aligned src_in_full_coords.top = full_dest_height - src_height; src_in_full_coords.bottom = full_dest_height; break; case 2: // centered src_in_full_coords.top = (full_dest_height - src_height) / 2; src_in_full_coords.bottom = src_in_full_coords.top + src_height; break; } } Region dest_in_full_coords = {.top = rf.multicell_y * cell_height, .bottom = (rf.multicell_y + 1) * cell_height}; unsigned intersection_top = MAX(src_in_full_coords.top, dest_in_full_coords.top); unsigned intersection_bottom = MIN(src_in_full_coords.bottom, dest_in_full_coords.bottom); unsigned src_top_delta = intersection_top - src_in_full_coords.top, src_bottom_delta = src_in_full_coords.bottom - intersection_bottom; src->top += src_top_delta; src->bottom = src->bottom > src_bottom_delta ? src->bottom - src_bottom_delta : 0; unsigned dest_top_delta = intersection_top - dest_in_full_coords.top, dest_bottom_delta = dest_in_full_coords.bottom - intersection_bottom; dest->top = dest_top_delta; dest->bottom = cell_height > dest_bottom_delta ? cell_height - dest_bottom_delta : 0; } static pixel* extract_cell_region(Canvas *canvas, unsigned i, Region *src, const Region *dest, unsigned src_width, FontCellMetrics unscaled_metrics) { src->left = i * unscaled_metrics.cell_width; src->right = MIN(src_width, src->left + unscaled_metrics.cell_width); unsigned sz; pixel *ans = pointer_to_space_for_last_sprite(canvas, unscaled_metrics, &sz); memset(ans, 0, sz * sizeof(ans[0])); unsigned width = MIN(src->right - src->left, unscaled_metrics.cell_width); for (unsigned srcy = src->top, desty = dest->top; srcy < src->bottom && desty < dest->bottom; srcy++, desty++) { pixel *srcp = canvas->buf + srcy * src_width, *destp = ans + desty * unscaled_metrics.cell_width; memcpy(destp, srcp + src->left, width * sizeof(destp[0])); } return ans; } static void set_cell_sprite(GPUCell *cell, const SpritePosition *sp) { cell->sprite_idx = sp->idx & 0x7fffffff; if (sp->colored) cell->sprite_idx |= 0x80000000; } static Region map_scaled_decoration_geometry(DecorationGeometry sdg, Region src, Region dest) { unsigned scaled_top = MAX(sdg.top, src.top), scaled_bottom = MIN(sdg.top + sdg.height, src.bottom); unsigned unscaled_top = dest.top + (scaled_top - src.top); unsigned unscaled_bottom = unscaled_top + (scaled_bottom > scaled_top ? scaled_bottom - scaled_top : 0); unscaled_bottom = MIN(unscaled_bottom, dest.bottom); /*printf("111111 src: (%u, %u) dest: (%u, %u) sdg: (%u, %u) scaled: (%u, %u) unscaled: (%u, %u)\n",*/ /* src.top, src.bottom, dest.top, dest.bottom, sdg.top, sdg.top + sdg.height, scaled_top, scaled_bottom, unscaled_top, unscaled_bottom);*/ return (Region){.top=unscaled_top, .bottom=MAX(unscaled_top, unscaled_bottom)}; } static void render_scaled_decoration(FontCellMetrics unscaled_metrics, FontCellMetrics scaled_metrics, uint8_t *alpha_mask, pixel *output, Region src, Region dest) { memset(output, 0, sizeof(output[0]) * unscaled_metrics.cell_width * (unscaled_metrics.cell_height + 1)); unsigned src_limit = MIN(scaled_metrics.cell_height, src.bottom), dest_limit = MIN(unscaled_metrics.cell_height, dest.bottom); unsigned cell_width = MIN(scaled_metrics.cell_width, unscaled_metrics.cell_width); for (unsigned srcy = src.top, desty=dest.top; srcy < src_limit && desty < dest_limit; srcy++, desty++) { uint8_t *srcp = alpha_mask + cell_width * srcy; pixel *destp = output + cell_width * desty; for (unsigned x = 0; x < cell_width; x++) destp[x] = 0xffffff00 | srcp[x]; } } static sprite_index render_decorations(FontGroup *fg, Region src, Region dest, FontCellMetrics scaled_metrics, DecorationGeometry *underline_region) { *underline_region = (DecorationGeometry){0}; if ((src.bottom == src.top) || (dest.bottom == dest.top)) return 0; // no overlap const FontCellMetrics unscaled_metrics = fg->fcm; scaled_metrics.cell_width = unscaled_metrics.cell_width; RAII_ALLOC(uint8_t, alpha_mask, malloc((size_t)scaled_metrics.cell_height * scaled_metrics.cell_width)); RAII_ALLOC(pixel, buf, malloc(sizeof(pixel) * unscaled_metrics.cell_width * (unscaled_metrics.cell_height + 1))); if (!alpha_mask || !buf) fatal("Out of memory"); sprite_index ans = 0; bool is_underline = false; uint32_t underline_top = unscaled_metrics.cell_height, underline_bottom = 0; #define do_one(call) { \ memset(alpha_mask, 0, sizeof(alpha_mask[0]) * scaled_metrics.cell_width * scaled_metrics.cell_height); \ DecorationGeometry sdg = call; \ render_scaled_decoration(unscaled_metrics, scaled_metrics, alpha_mask, buf, src, dest); \ sprite_index q = current_send_sprite_to_gpu(fg, buf, (DecorationMetadata){0}, scaled_metrics); \ if (!ans) ans = q; \ if (is_underline) { \ Region r = map_scaled_decoration_geometry(sdg, src, dest); \ if (r.top < underline_top) underline_top = r.top; \ if (r.bottom > underline_bottom) underline_bottom = r.bottom; \ }; \ } do_one(add_strikethrough(alpha_mask, scaled_metrics)); is_underline = true; do_one(add_straight_underline(alpha_mask, scaled_metrics)); do_one(add_double_underline(alpha_mask, scaled_metrics)); do_one(add_curl_underline(alpha_mask, scaled_metrics)); do_one(add_dotted_underline(alpha_mask, scaled_metrics)); do_one(add_dashed_underline(alpha_mask, scaled_metrics)); underline_bottom = MIN(underline_bottom, unscaled_metrics.cell_height); if (underline_top < underline_bottom) { underline_region->top = underline_top; underline_region->height = underline_bottom - underline_top; } return ans; #undef do_one } static DecorationMetadata index_for_decorations(FontGroup *fg, RunFont rf, Region src, Region dest, FontCellMetrics scaled_metrics) { const DecorationsKey key = {.scale=rf.scale, .subscale_n = rf.subscale_n, .subscale_d = rf.subscale_d, .align = rf.align.val, .multicell_y = rf.multicell_y, .u1 = 0, .u2 = 0, .u3 = 0 }; decorations_index_map_t_itr i = vt_get(&fg->decorations_index_map, key); if (!vt_is_end(i)) return i.data->val; DecorationMetadata val; val.start_idx = render_decorations(fg, src, dest, scaled_metrics, &val.underline_region); if (vt_is_end(vt_insert(&fg->decorations_index_map, key, val))) fatal("Out of memory"); return val; } static void render_box_cell(FontGroup *fg, RunFont rf, CPUCell *cpu_cell, GPUCell *gpu_cell, const TextCache *tc) { ensure_glyph_render_scratch_space(64); text_in_cell(cpu_cell, tc, global_glyph_render_scratch.lc); ensure_glyph_render_scratch_space(rf.scale * global_glyph_render_scratch.lc->count); unsigned num_glyphs = 0, num_cells = rf.scale; for (unsigned i = 0; i < global_glyph_render_scratch.lc->count; i++) { glyph_index glyph = box_glyph_id(global_glyph_render_scratch.lc->chars[i]); if (glyph != 0xffff) global_glyph_render_scratch.glyphs[num_glyphs++] = glyph; else global_glyph_render_scratch.lc->chars[i] = 0; } #define failed {\ if (PyErr_Occurred()) PyErr_Print(); \ for (unsigned i = 0; i < num_cells; i++) gpu_cell[i].sprite_idx = 0; \ return; \ } if (!num_glyphs) failed; bool all_rendered = true; #define sp global_glyph_render_scratch.sprite_positions for (unsigned ligature_index = 0; ligature_index < num_cells; ligature_index++) { sp[ligature_index] = sprite_position_for(fg, rf, global_glyph_render_scratch.glyphs, num_glyphs, ligature_index, num_cells); if (sp[ligature_index] == NULL) failed; sp[ligature_index]->colored = false; if (!sp[ligature_index]->rendered) all_rendered = false; } if (all_rendered) { for (unsigned i = 0; i < num_cells; i++) set_cell_sprite(gpu_cell + i, sp[i]); return; } FontCellMetrics unscaled_metrics = fg->fcm; float scale = apply_scale_to_font_group(fg, &rf); ensure_canvas_can_fit(fg, num_glyphs + 1, rf.scale); FontCellMetrics scaled_metrics = fg->fcm; if (scale != 1) apply_scale_to_font_group(fg, NULL); ensure_canvas_can_fit(fg, num_glyphs + 1, rf.scale); // in case unscaled size is larger is than scaled size unsigned mask_stride = scaled_metrics.cell_width * num_glyphs, right_shift = 0; if (rf.subscale_n && rf.subscale_d && rf.align.horizontal && scaled_metrics.cell_width <= unscaled_metrics.cell_width) { int delta = unscaled_metrics.cell_width * num_cells - mask_stride; if (rf.align.horizontal == 2) delta /= 2; if (delta > 0) { right_shift = delta; mask_stride += delta; } } Region src = {.right = scaled_metrics.cell_width, .bottom = scaled_metrics.cell_height }, dest = src; for (unsigned i = 0, cnum = 0; i < num_glyphs; i++) { unsigned int ch = global_glyph_render_scratch.lc->chars[cnum++]; while (!ch) ch = global_glyph_render_scratch.lc->chars[cnum++]; render_box_char(ch, fg->canvas.alpha_mask, src.right, src.bottom, fg->logical_dpi_x, fg->logical_dpi_y, scale); dest.left = i * scaled_metrics.cell_width + right_shift; dest.right = dest.left + scaled_metrics.cell_width; render_alpha_mask(fg->canvas.alpha_mask, fg->canvas.buf, &src, &dest, src.right, mask_stride, 0xffffff); } src.right = mask_stride; dest = src; dest.right = unscaled_metrics.cell_width * num_cells; /*printf("Rendered char sz: (%u, %u)\n", src.right, src.bottom); dump_sprite(fg->canvas.buf, src.right, src.bottom);*/ calculate_regions_for_line(rf, unscaled_metrics.cell_height, &src, &dest); DecorationMetadata dm = index_for_decorations(fg, rf, src, dest, scaled_metrics); /*printf("width: %u height: %u unscaled_cell_width: %u unscaled_cell_height: %u src.top: %u src.bottom: %u num_cells: %u\n", width, height, fg->fcm.cell_width, fg->fcm.cell_height, src.top, src.bottom, num_cells);*/ for (unsigned i = 0; i < num_cells; i++) { if (!sp[i]->rendered) { pixel *b = extract_cell_region(&fg->canvas, i, &src, &dest, mask_stride, unscaled_metrics); /*printf("cell %u src -> dest: (%u %u) -> (%u %u)\n", i, src.left, src.right, dest.left, dest.right);*/ sp[i]->idx = current_send_sprite_to_gpu(fg, b, dm, scaled_metrics); if (!sp[i]->idx) failed; /*dump_sprite(b, unscaled_metrics.cell_width, unscaled_metrics.cell_height);*/ sp[i]->rendered = true; sp[i]->colored = false; } set_cell_sprite(gpu_cell + i, sp[i]); /*printf("Sprite %u: pos: %u sz: (%u, %u)\n", i, sp[i]->idx, fg->fcm.cell_width, fg->fcm.cell_height); dump_sprite(b, fg->fcm.cell_width, fg->fcm.cell_height);*/ } #undef sp #undef failed } static void load_hb_buffer(CPUCell *first_cpu_cell, index_type num_cells, const TextCache *tc, ListOfChars *lc) { size_t num = 0; hb_buffer_clear_contents(harfbuzz_buffer); // Although hb_buffer_add_codepoints is supposedly an append, we have to // add all text in one call otherwise it breaks shaping, presumably because // of context?? for (; num_cells; first_cpu_cell++, num_cells--) { if (first_cpu_cell->is_multicell && first_cpu_cell->x) continue; text_in_cell(first_cpu_cell, tc, lc); ensure_space_for((&shape_buffer), codepoints, shape_buffer.codepoints[0], lc->count + num, capacity, 512, false); memcpy(shape_buffer.codepoints + num, lc->chars, lc->count * sizeof(shape_buffer.codepoints[0])); num += lc->count; } hb_buffer_add_codepoints(harfbuzz_buffer, shape_buffer.codepoints, num, 0, num); hb_buffer_guess_segment_properties(harfbuzz_buffer); if (OPT(force_ltr)) hb_buffer_set_direction(harfbuzz_buffer, HB_DIRECTION_LTR); } static void render_filled_sprite(pixel *buf, unsigned num_glyphs, FontCellMetrics scaled_metrics, unsigned num_scaled_cells) { if (num_scaled_cells > num_glyphs) { memset(buf, 0xff, sizeof(buf[0]) * num_glyphs * scaled_metrics.cell_width); memset(buf + num_glyphs * scaled_metrics.cell_width, 0, sizeof(buf[0]) * (num_scaled_cells - num_glyphs) * scaled_metrics.cell_width); for (unsigned y = 1; y < scaled_metrics.cell_height; y++) memcpy( buf + scaled_metrics.cell_width * num_scaled_cells * y, buf, sizeof(buf[0]) * scaled_metrics.cell_width * num_scaled_cells ); } else memset(buf, 0xff, sizeof(buf[0]) * num_glyphs * scaled_metrics.cell_height * scaled_metrics.cell_width ); } static void apply_horizontal_alignment(pixel *canvas, RunFont rf, bool center_glyph, GlyphRenderInfo ri, unsigned canvas_height, unsigned num_cells, unsigned num_glyphs, bool was_colored) { int delta = 0; (void)was_colored; #ifdef __APPLE__ if (num_cells == 2 && was_colored) center_glyph = true; #endif if (rf.subscale_n && rf.subscale_d && rf.align.horizontal) { delta = ri.canvas_width - ri.rendered_width; if (rf.align.horizontal == 2) delta /= 2; } else if (center_glyph && num_glyphs && num_cells > 1 && ri.rendered_width < ri.canvas_width) { unsigned half = (ri.canvas_width - ri.rendered_width) / 2; if (half > 1) delta = half; } delta -= ri.x; if (delta > 0) right_shift_canvas(canvas, ri.canvas_width, canvas_height, delta); } static void render_group( FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, RunFont rf, glyph_index *glyphs, unsigned glyph_count, bool center_glyph, const TextCache *tc, float scale, FontCellMetrics unscaled_metrics ) { #define sp global_glyph_render_scratch.sprite_positions const FontCellMetrics scaled_metrics = fg->fcm; bool all_rendered = true; unsigned num_scaled_cells = (unsigned)ceil(num_cells / scale); if (!num_scaled_cells) num_scaled_cells = 1u; Font *font = fg->fonts + rf.font_idx; #define failed { \ if (PyErr_Occurred()) PyErr_Print(); \ for (unsigned i = 0; i < num_cells; i++) gpu_cells[i].sprite_idx = 0; \ return; \ } // One can have infinite ligatures with repeated groups of sprites when scaled size is an exact multiple or // divisor of unscaled size but I cant be bothered to implement that. bool is_infinite_ligature = num_cells == num_scaled_cells && num_cells > 9 && num_glyphs == num_cells; for (unsigned i = 0, ligature_index = 0; i < num_cells; i++) { bool is_repeat_sprite = is_infinite_ligature && i > 1 && i + 1 < num_glyphs && glyphs[i] == glyphs[i-1] && glyphs[i] == glyphs[i-2] && glyphs[i] == glyphs[i+1]; sp[i] = is_repeat_sprite ? sp[i-1] : sprite_position_for(fg, rf, glyphs, glyph_count, ligature_index++, num_cells); if (!sp[i]) failed; if (!sp[i]->rendered) all_rendered = false; } if (all_rendered) { for (unsigned i = 0; i < num_cells; i++) set_cell_sprite(gpu_cells + i, sp[i]); return; } ensure_canvas_can_fit(fg, MAX(num_cells, num_scaled_cells) + 1, rf.scale); text_in_cell(cpu_cells, tc, global_glyph_render_scratch.lc); bool is_only_filled_boxes = false; bool was_colored = has_emoji_presentation(cpu_cells, global_glyph_render_scratch.lc); if (global_glyph_render_scratch.lc->chars[0] == 0x2588) { glyph_index box_glyph_id = global_glyph_render_scratch.glyphs[0]; is_only_filled_boxes = true; for (unsigned i = 1; i < num_glyphs && is_only_filled_boxes; i++) if (global_glyph_render_scratch.glyphs[i] != box_glyph_id) is_only_filled_boxes = false; } /*printf("num_cells: %u num_scaled_cells: %u num_glyphs: %u scale: %f unscaled: %ux%u scaled: %ux%u\n", num_cells, num_scaled_cells, num_glyphs, scale, unscaled_metrics.cell_width, unscaled_metrics.cell_height, scaled_metrics.cell_width, scaled_metrics.cell_height);*/ GlyphRenderInfo ri = {0}; if (is_only_filled_boxes) { // special case rendering of █ for tests render_filled_sprite(fg->canvas.buf, num_glyphs, scaled_metrics, num_scaled_cells); was_colored = false; ri.canvas_width = num_cells * unscaled_metrics.cell_width; ri.rendered_width = num_glyphs * scaled_metrics.cell_width; /*dump_sprite(fg->canvas.buf, scaled_metrics.cell_width * num_scaled_cells, scaled_metrics.cell_height);*/ } else { render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas.buf, scaled_metrics.cell_width, scaled_metrics.cell_height, num_scaled_cells, scaled_metrics.baseline, &was_colored, (FONTS_DATA_HANDLE)fg, &ri); } apply_horizontal_alignment(fg->canvas.buf, rf, center_glyph, ri, scaled_metrics.cell_height, num_scaled_cells, num_glyphs, was_colored); if (PyErr_Occurred()) PyErr_Print(); fg->fcm = unscaled_metrics; // needed for current_send_sprite_to_gpu() if (num_cells == num_scaled_cells && rf.scale == 1.f) { Region src = {.bottom=unscaled_metrics.cell_height, .right=unscaled_metrics.cell_width}, dest = src; DecorationMetadata dm = index_for_decorations(fg, rf, src, dest, scaled_metrics); for (unsigned i = 0; i < num_cells; i++) { if (!sp[i]->rendered) { bool is_repeat_sprite = is_infinite_ligature && i > 0 && sp[i]->idx == sp[i-1]->idx; if (!is_repeat_sprite) { pixel *b = num_cells == 1 ? fg->canvas.buf : extract_cell_from_canvas(fg, i, num_cells); sp[i]->idx = current_send_sprite_to_gpu(fg, b, dm, scaled_metrics); if (!sp[i]->idx) failed; } else sp[i]->idx = sp[i-1]->idx; sp[i]->rendered = true; sp[i]->colored = was_colored; } set_cell_sprite(gpu_cells + i, sp[i]); } } else { Region src={.bottom=scaled_metrics.cell_height, .right=scaled_metrics.cell_width * num_scaled_cells}, dest={.right=unscaled_metrics.cell_width}; calculate_regions_for_line(rf, unscaled_metrics.cell_height, &src, &dest); DecorationMetadata dm = index_for_decorations(fg, rf, src, dest, scaled_metrics); /*printf("line: %u src -> dest: (%u %u) -> (%u %u)\n", rf.multicell_y, src.top, src.bottom, dest.top, dest.bottom);*/ for (unsigned i = 0; i < num_cells; i++) { if (!sp[i]->rendered) { pixel *b = extract_cell_region(&fg->canvas, i, &src, &dest, scaled_metrics.cell_width * num_scaled_cells, unscaled_metrics); /*printf("cell %u src -> dest: (%u %u) -> (%u %u)\n", i, src.left, src.right, dest.left, dest.right);*/ sp[i]->idx = current_send_sprite_to_gpu(fg, b, dm, scaled_metrics); if (!sp[i]->idx) failed; /*dump_sprite(b, unscaled_metrics.cell_width, unscaled_metrics.cell_height);*/ sp[i]->rendered = true; sp[i]->colored = was_colored; } set_cell_sprite(gpu_cells + i, sp[i]); } } fg->fcm = scaled_metrics; #undef sp #undef failed } typedef struct { CPUCell *cpu_cell; GPUCell *gpu_cell; unsigned int num_codepoints; unsigned int codepoints_consumed; char_type current_codepoint; } CellData; typedef struct { unsigned int first_glyph_idx, first_cell_idx, num_glyphs, num_cells; bool has_special_glyph, started_with_infinite_ligature; } Group; typedef struct { uint32_t previous_cluster; bool prev_was_special, prev_was_empty; CellData current_cell_data; Group *groups; size_t groups_capacity, group_idx, glyph_idx, cell_idx, num_cells, num_glyphs; CPUCell *first_cpu_cell, *last_cpu_cell; GPUCell *first_gpu_cell, *last_gpu_cell; hb_glyph_info_t *info; hb_glyph_position_t *positions; } GroupState; static GroupState group_state = {0}; static void shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb_font_t *font, Font *fobj, bool disable_ligature, const TextCache *tc) { if (group_state.groups_capacity <= 2 * num_cells) { group_state.groups_capacity = MAX(128u, 2 * num_cells); // avoid unnecessary reallocs group_state.groups = realloc(group_state.groups, sizeof(Group) * group_state.groups_capacity); if (!group_state.groups) fatal("Out of memory"); } RAII_ListOfChars(lc); text_in_cell(first_cpu_cell, tc, &lc); group_state.previous_cluster = UINT32_MAX; group_state.prev_was_special = false; group_state.prev_was_empty = false; group_state.current_cell_data.cpu_cell = first_cpu_cell; group_state.current_cell_data.gpu_cell = first_gpu_cell; group_state.current_cell_data.num_codepoints = MAX(1u, lc.count); group_state.current_cell_data.codepoints_consumed = 0; group_state.current_cell_data.current_codepoint = lc.chars[0]; zero_at_ptr_count(group_state.groups, group_state.groups_capacity); group_state.group_idx = 0; group_state.glyph_idx = 0; group_state.cell_idx = 0; group_state.num_cells = num_cells; group_state.first_cpu_cell = first_cpu_cell; group_state.first_gpu_cell = first_gpu_cell; group_state.last_cpu_cell = first_cpu_cell + (num_cells ? num_cells - 1 : 0); group_state.last_gpu_cell = first_gpu_cell + (num_cells ? num_cells - 1 : 0); load_hb_buffer(first_cpu_cell, num_cells, tc, &lc); size_t num_features = fobj->num_ffs_hb_features; if (num_features && !disable_ligature) num_features--; // the last feature is always -calt hb_shape(font, harfbuzz_buffer, fobj->ffs_hb_features, num_features); unsigned int info_length, positions_length; group_state.info = hb_buffer_get_glyph_infos(harfbuzz_buffer, &info_length); group_state.positions = hb_buffer_get_glyph_positions(harfbuzz_buffer, &positions_length); if (!group_state.info || !group_state.positions) group_state.num_glyphs = 0; else group_state.num_glyphs = MIN(info_length, positions_length); } static bool is_special_glyph(glyph_index glyph_id, Font *font, CellData* cell_data) { // A glyph is special if the codepoint it corresponds to matches a // different glyph in the font GlyphProperties s = find_glyph_properties(font->glyph_properties_hash_table, glyph_id); if (!s.special_set) { bool is_special = cell_data->current_codepoint ? ( glyph_id != glyph_id_for_codepoint(font->face, cell_data->current_codepoint) ? true : false) : false; s.special_set = 1; s.special_val = is_special; set_glyph_properties(font->glyph_properties_hash_table, glyph_id, s); } return s.special_val; } static bool is_empty_glyph(glyph_index glyph_id, Font *font) { // A glyph is empty if its metrics have a width of zero GlyphProperties s = find_glyph_properties(font->glyph_properties_hash_table, glyph_id); if (!s.empty_set) { s.empty_val = is_glyph_empty(font->face, glyph_id) ? 1 : 0; s.empty_set = 1; set_glyph_properties(font->glyph_properties_hash_table, glyph_id, s); } return s.empty_val; } static unsigned int check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell, const TextCache *tc, ListOfChars *lc) { cell_data->codepoints_consumed++; if (cell_data->codepoints_consumed >= cell_data->num_codepoints) { uint16_t width = 1; if (cell_data->cpu_cell->is_multicell) width = cell_data->cpu_cell->width * cell_data->cpu_cell->scale; cell_data->cpu_cell += width; cell_data->gpu_cell += width; cell_data->codepoints_consumed = 0; if (cell_data->cpu_cell <= last_cpu_cell) { text_in_cell(cell_data->cpu_cell, tc, lc); cell_data->num_codepoints = lc->count; cell_data->current_codepoint = lc->chars[0]; } else cell_data->current_codepoint = 0; return width; } text_in_cell(cell_data->cpu_cell, tc, lc); char_type cc = lc->chars[cell_data->codepoints_consumed]; // VS15/16 cause rendering to break, as they get marked as // special glyphs, so map to 0, to avoid that cell_data->current_codepoint = (cc == VS15 || cc == VS16) ? 0 : cc; return 0; } static LigatureType ligature_type_from_glyph_name(const char *glyph_name, SpacerStrategy strategy) { const char *p, *m, *s, *e; if (strategy == SPACERS_IOSEVKA) { p = strrchr(glyph_name, '.'); m = ".join-m"; s = ".join-l"; e = ".join-r"; } else { p = strrchr(glyph_name, '_'); m = "_middle.seq"; s = "_start.seq"; e = "_end.seq"; } if (p) { if (strcmp(p, m) == 0) return INFINITE_LIGATURE_MIDDLE; if (strcmp(p, s) == 0) return INFINITE_LIGATURE_START; if (strcmp(p, e) == 0) return INFINITE_LIGATURE_END; } return LIGATURE_UNKNOWN; } #define G(x) (group_state.x) static void detect_spacer_strategy(hb_font_t *hbf, Font *font, const TextCache *tc) { CPUCell cpu_cells[3] = {0}; for (unsigned i = 0; i < arraysz(cpu_cells); i++) cell_set_char(&cpu_cells[i], '='); const CellAttrs w1 = {0}; GPUCell gpu_cells[3] = {{.attrs = w1}, {.attrs = w1}, {.attrs = w1}}; shape(cpu_cells, gpu_cells, arraysz(cpu_cells), hbf, font, false, tc); font->spacer_strategy = SPACERS_BEFORE; if (G(num_glyphs) > 1) { glyph_index glyph_id = G(info)[G(num_glyphs) - 1].codepoint; bool is_special = is_special_glyph(glyph_id, font, &G(current_cell_data)); bool is_empty = is_special && is_empty_glyph(glyph_id, font); if (is_empty) font->spacer_strategy = SPACERS_AFTER; } shape(cpu_cells, gpu_cells, 2, hbf, font, false, tc); if (G(num_glyphs)) { char glyph_name[128]; glyph_name[arraysz(glyph_name)-1] = 0; for (unsigned i = 0; i < G(num_glyphs); i++) { glyph_index glyph_id = G(info)[i].codepoint; hb_font_glyph_to_string(hbf, glyph_id, glyph_name, arraysz(glyph_name)-1); char *dot = strrchr(glyph_name, '.'); if (dot && (!strcmp(dot, ".join-l") || !strcmp(dot, ".join-r") || !strcmp(dot, ".join-m"))) { font->spacer_strategy = SPACERS_IOSEVKA; break; } } } // If spacer_strategy is still default, check ### glyph to confirm strategy // https://github.com/kovidgoyal/kitty/issues/4721 if (font->spacer_strategy == SPACERS_BEFORE) { for (unsigned i = 0; i < arraysz(cpu_cells); i++) cell_set_char(&cpu_cells[i], '#'); shape(cpu_cells, gpu_cells, arraysz(cpu_cells), hbf, font, false, tc); if (G(num_glyphs) > 1) { glyph_index glyph_id = G(info)[G(num_glyphs) - 1].codepoint; bool is_special = is_special_glyph(glyph_id, font, &G(current_cell_data)); bool is_empty = is_special && is_empty_glyph(glyph_id, font); if (is_empty) font->spacer_strategy = SPACERS_AFTER; } } } static LigatureType ligature_type_for_glyph(hb_font_t *hbf, glyph_index glyph_id, SpacerStrategy strategy) { static char glyph_name[128]; glyph_name[arraysz(glyph_name)-1] = 0; hb_font_glyph_to_string(hbf, glyph_id, glyph_name, arraysz(glyph_name)-1); return ligature_type_from_glyph_name(glyph_name, strategy); } #define L INFINITE_LIGATURE_START #define M INFINITE_LIGATURE_MIDDLE #define R INFINITE_LIGATURE_END #define I LIGATURE_UNKNOWN static bool is_iosevka_lig_starter(LigatureType before, LigatureType current, LigatureType after) { return (current == R || (current == I && (after == L || after == M))) \ && \ !(before == R || before == M); } static bool is_iosevka_lig_ender(LigatureType before, LigatureType current, LigatureType after) { return (current == L || (current == I && (before == R || before == M))) \ && \ !(after == L || after == M); } #undef L #undef M #undef R #undef I static LigatureType *ligature_types = NULL; static size_t ligature_types_sz = 0; static void group_iosevka(Font *font, hb_font_t *hbf, const TextCache *tc, ListOfChars *lc) { // Group as per algorithm discussed in: https://github.com/be5invis/Iosevka/issues/1007 if (ligature_types_sz <= G(num_glyphs)) { ligature_types_sz = G(num_glyphs) + 16; ligature_types = realloc(ligature_types, ligature_types_sz * sizeof(ligature_types[0])); if (!ligature_types) fatal("Out of memory allocating ligature types array"); } for (size_t i = G(glyph_idx); i < G(num_glyphs); i++) { ligature_types[i] = ligature_type_for_glyph(hbf, G(info)[i].codepoint, font->spacer_strategy); } uint32_t cluster, next_cluster; while (G(glyph_idx) < G(num_glyphs) && G(cell_idx) < G(num_cells)) { cluster = G(info)[G(glyph_idx)].cluster; uint32_t num_codepoints_used_by_glyph = 0; bool is_last_glyph = G(glyph_idx) == G(num_glyphs) - 1; Group *current_group = G(groups) + G(group_idx); if (is_last_glyph) { num_codepoints_used_by_glyph = UINT32_MAX; next_cluster = 0; } else { next_cluster = G(info)[G(glyph_idx) + 1].cluster; // RTL languages like Arabic have decreasing cluster numbers if (next_cluster != cluster) num_codepoints_used_by_glyph = cluster > next_cluster ? cluster - next_cluster : next_cluster - cluster; } const LigatureType before = G(glyph_idx) ? ligature_types[G(glyph_idx - 1)] : LIGATURE_UNKNOWN; const LigatureType current = ligature_types[G(glyph_idx)]; const LigatureType after = is_last_glyph ? LIGATURE_UNKNOWN : ligature_types[G(glyph_idx + 1)]; bool end_current_group = false; if (current_group->num_glyphs) { if (is_iosevka_lig_ender(before, current, after)) end_current_group = true; else { if (!current_group->num_cells && !current_group->has_special_glyph) { if (is_iosevka_lig_starter(before, current, after)) current_group->has_special_glyph = true; else end_current_group = true; } } } if (!current_group->num_glyphs++) { if (is_iosevka_lig_starter(before, current, after)) current_group->has_special_glyph = true; else end_current_group = true; current_group->first_glyph_idx = G(glyph_idx); current_group->first_cell_idx = G(cell_idx); } if (is_last_glyph) { // soak up all remaining cells if (G(cell_idx) < G(num_cells)) { unsigned int num_left = G(num_cells) - G(cell_idx); current_group->num_cells += num_left; G(cell_idx) += num_left; } } else { unsigned int num_cells_consumed = 0; while (num_codepoints_used_by_glyph && G(cell_idx) < G(num_cells)) { unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cpu_cell), tc, lc); G(cell_idx) += w; num_cells_consumed += w; num_codepoints_used_by_glyph--; } current_group->num_cells += num_cells_consumed; } if (end_current_group && current_group->num_cells) G(group_idx)++; G(glyph_idx)++; } } static void group_normal(Font *font, hb_font_t *hbf, const TextCache *tc, ListOfChars *lc) { /* Now distribute the glyphs into groups of cells * Considerations to keep in mind: * Group sizes should be as small as possible for best performance * Combining chars can result in multiple glyphs rendered into a single cell * Emoji and East Asian wide chars can cause a single glyph to be rendered over multiple cells * Ligature fonts, take two common approaches: * 1. ABC becomes EMPTY, EMPTY, WIDE GLYPH this means we have to render N glyphs in N cells (example Fira Code) * 2. ABC becomes WIDE GLYPH this means we have to render one glyph in N cells (example Operator Mono Lig) * 3. ABC becomes WIDE GLYPH, EMPTY, EMPTY this means we have to render N glyphs in N cells (example Cascadia Code) * 4. Variable length ligatures are identified by a glyph naming convention of _start.seq, _middle.seq and _end.seq * with EMPTY glyphs in the middle or after (both Fira Code and Cascadia Code) * * We rely on the cluster numbers from harfbuzz to tell us how many unicode codepoints a glyph corresponds to. * Then we check if the glyph is a ligature glyph (is_special_glyph) and if it is an empty glyph. * We detect if the font uses EMPTY glyphs before or after ligature glyphs (1. or 3. above) by checking what it does for === and ###. * Finally we look at the glyph name. These five datapoints give us enough information to satisfy the constraint above, * for a wide variety of fonts. */ uint32_t cluster, next_cluster; bool add_to_current_group; bool prev_glyph_was_inifinte_ligature_end = false; while (G(glyph_idx) < G(num_glyphs) && G(cell_idx) < G(num_cells)) { glyph_index glyph_id = G(info)[G(glyph_idx)].codepoint; LigatureType ligature_type = ligature_type_for_glyph(hbf, glyph_id, font->spacer_strategy); cluster = G(info)[G(glyph_idx)].cluster; bool is_special = is_special_glyph(glyph_id, font, &G(current_cell_data)); bool is_empty = is_special && is_empty_glyph(glyph_id, font); uint32_t num_codepoints_used_by_glyph = 0; bool is_last_glyph = G(glyph_idx) == G(num_glyphs) - 1; Group *current_group = G(groups) + G(group_idx); if (is_last_glyph) { num_codepoints_used_by_glyph = UINT32_MAX; next_cluster = 0; } else { next_cluster = G(info)[G(glyph_idx) + 1].cluster; // RTL languages like Arabic have decreasing cluster numbers if (next_cluster != cluster) num_codepoints_used_by_glyph = cluster > next_cluster ? cluster - next_cluster : next_cluster - cluster; } if (!current_group->num_glyphs) { add_to_current_group = true; } else if (current_group->started_with_infinite_ligature) { if (prev_glyph_was_inifinte_ligature_end) add_to_current_group = is_empty && font->spacer_strategy == SPACERS_AFTER; else add_to_current_group = ligature_type == INFINITE_LIGATURE_MIDDLE || ligature_type == INFINITE_LIGATURE_END || is_empty; } else { if (is_special) { if (!current_group->num_cells) add_to_current_group = true; else if (font->spacer_strategy == SPACERS_BEFORE) add_to_current_group = G(prev_was_empty); else add_to_current_group = is_empty; } else { add_to_current_group = !G(prev_was_special) || !current_group->num_cells; } } #if 0 char ch[8] = {0}; encode_utf8(G(current_cell_data).current_codepoint, ch); printf("\x1b[32m→ %s\x1b[m glyph_idx: %zu glyph_id: %u group_idx: %zu cluster: %u -> %u is_special: %d\n" " num_codepoints_used_by_glyph: %u current_group: (%u cells, %u glyphs) add_to_current_group: %d\n", ch, G(glyph_idx), glyph_id, G(group_idx), cluster, next_cluster, is_special, num_codepoints_used_by_glyph, current_group->num_cells, current_group->num_glyphs, add_to_current_group); #endif if (!add_to_current_group) { current_group = G(groups) + ++G(group_idx); } if (!current_group->num_glyphs++) { if (ligature_type == INFINITE_LIGATURE_START || ligature_type == INFINITE_LIGATURE_MIDDLE) current_group->started_with_infinite_ligature = true; current_group->first_glyph_idx = G(glyph_idx); current_group->first_cell_idx = G(cell_idx); } if (is_special) current_group->has_special_glyph = true; if (is_last_glyph) { // soak up all remaining cells if (G(cell_idx) < G(num_cells)) { unsigned int num_left = G(num_cells) - G(cell_idx); current_group->num_cells += num_left; G(cell_idx) += num_left; } } else { unsigned int num_cells_consumed = 0; while (num_codepoints_used_by_glyph && G(cell_idx) < G(num_cells)) { unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cpu_cell), tc, lc); G(cell_idx) += w; num_cells_consumed += w; num_codepoints_used_by_glyph--; } if (num_cells_consumed) { current_group->num_cells += num_cells_consumed; if (!is_special) { // not a ligature, end the group G(group_idx)++; current_group = G(groups) + G(group_idx); } } } G(prev_was_special) = is_special; G(prev_was_empty) = is_empty; G(previous_cluster) = cluster; prev_glyph_was_inifinte_ligature_end = ligature_type == INFINITE_LIGATURE_END; G(glyph_idx)++; } } static float shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, Font *font, RunFont rf, FontGroup *fg, bool disable_ligature, const TextCache *tc, ListOfChars *lc) { float scale = apply_scale_to_font_group(fg, &rf); if (scale != 1.f) if (!face_apply_scaling(font->face, (FONTS_DATA_HANDLE)fg) && PyErr_Occurred()) PyErr_Print(); hb_font_t *hbf = harfbuzz_font_for_face(font->face); if (font->spacer_strategy == SPACER_STRATEGY_UNKNOWN) detect_spacer_strategy(hbf, font, tc); shape(first_cpu_cell, first_gpu_cell, num_cells, hbf, font, disable_ligature, tc); if (font->spacer_strategy == SPACERS_IOSEVKA) group_iosevka(font, hbf, tc, lc); else group_normal(font, hbf, tc, lc); #if 0 static char dbuf[1024]; // You can also generate this easily using hb-shape --show-extents --cluster-level=1 --shapers=ot /path/to/font/file text hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, group_state.num_glyphs, dbuf, sizeof(dbuf), NULL, harfbuzz_font_for_face(font->face), HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS); printf("\n%s\n", dbuf); #endif if (scale != 1.f) { apply_scale_to_font_group(fg, NULL); if (!face_apply_scaling(font->face, (FONTS_DATA_HANDLE)fg) && PyErr_Occurred()) PyErr_Print(); } return scale; } static void collapse_pua_space_ligature(index_type num_cells) { Group *g = G(groups); G(group_idx) = 0; g->num_cells = num_cells; // We dont want to render the spaces in a space ligature because // there exist stupid fonts like Powerline that have no space glyph, // so special case it: https://github.com/kovidgoyal/kitty/issues/1225 g->num_glyphs = 1; } static bool group_has_more_than_one_scaled_cell(const Group *group, float scale) { return group->num_cells / scale > 1.0f; } static void split_run_at_offset(index_type cursor_offset, index_type *left, index_type *right, float scale) { *left = 0; *right = 0; for (unsigned idx = 0; idx < G(group_idx) + 1; idx++) { Group *group = G(groups) + idx; if (group->first_cell_idx <= cursor_offset && cursor_offset < group->first_cell_idx + group->num_cells) { if (group->has_special_glyph && group_has_more_than_one_scaled_cell(group, scale)) { // likely a calt ligature *left = group->first_cell_idx; *right = group->first_cell_idx + group->num_cells; } break; } } } static void render_groups(FontGroup *fg, RunFont rf, bool center_glyph, const TextCache *tc) { unsigned idx = 0; const FontCellMetrics unscaled_metrics = fg->fcm; float scale = apply_scale_to_font_group(fg, &rf); if (scale != 1.f) if (!face_apply_scaling(fg->fonts[rf.font_idx].face, (FONTS_DATA_HANDLE)fg) && PyErr_Occurred()) PyErr_Print(); while (idx <= G(group_idx)) { Group *group = G(groups) + idx; if (!group->num_cells) break; /* printf("Group: idx: %u num_cells: %u num_glyphs: %u first_glyph_idx: %u first_cell_idx: %u total_num_glyphs: %zu\n", */ /* idx, group->num_cells, group->num_glyphs, group->first_glyph_idx, group->first_cell_idx, group_state.num_glyphs); */ if (group->num_glyphs) { ensure_glyph_render_scratch_space(MAX(group->num_glyphs, group->num_cells)); for (unsigned i = 0; i < group->num_glyphs; i++) global_glyph_render_scratch.glyphs[i] = G(info)[group->first_glyph_idx + i].codepoint; render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, rf, global_glyph_render_scratch.glyphs, group->num_glyphs, center_glyph, tc, scale, unscaled_metrics); } idx++; } if (scale != 1.f) { apply_scale_to_font_group(fg, NULL); if (!face_apply_scaling(fg->fonts[rf.font_idx].face, (FONTS_DATA_HANDLE)fg) && PyErr_Occurred()) PyErr_Print(); } } static PyObject* test_shape(PyObject UNUSED *self, PyObject *args) { Line *line; char *path = NULL; int index = 0; if(!PyArg_ParseTuple(args, "O!|zi", &Line_Type, &line, &path, &index)) return NULL; index_type num = 0; const CPUCell *c; while(num < line->xnum && cell_has_text(line->cpu_cells + num)) { index_type width = 1; if ((c = line->cpu_cells + num)->is_multicell) { width = c->width * c->scale; } num += width; } PyObject *face = NULL; Font *font; if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create at least one font group first"); return NULL; } FontGroup *fg = font_groups; if (path) { face = face_from_path(path, index, (FONTS_DATA_HANDLE)font_groups); if (face == NULL) return NULL; font = calloc(1, sizeof(Font)); font->face = face; if (!init_hash_tables(font)) return NULL; } else { font = fg->fonts + fg->medium_font_idx; } RunFont rf = {0}; RAII_ListOfChars(lc); shape_run(line->cpu_cells, line->gpu_cells, num, font, rf, fg, false, line->text_cache, &lc); PyObject *ans = PyList_New(0); unsigned int idx = 0; glyph_index first_glyph; while (idx <= G(group_idx)) { Group *group = G(groups) + idx; if (!group->num_cells) break; first_glyph = group->num_glyphs ? G(info)[group->first_glyph_idx].codepoint : 0; PyObject *eg = PyTuple_New(group->num_glyphs); for (size_t g = 0; g < group->num_glyphs; g++) PyTuple_SET_ITEM(eg, g, Py_BuildValue("H", G(info)[group->first_glyph_idx + g].codepoint)); PyList_Append(ans, Py_BuildValue("IIHN", group->num_cells, group->num_glyphs, first_glyph, eg)); idx++; } if (face) { Py_CLEAR(face); free_maps(font); free(font); } return ans; } #undef G static void render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, RunFont rf, bool pua_space_ligature, bool center_glyph, int cursor_offset, DisableLigature disable_ligature_strategy, const TextCache *tc, ListOfChars *lc) { float scale; switch(rf.font_idx) { default: scale = shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[rf.font_idx], rf, fg, disable_ligature_strategy == DISABLE_LIGATURES_ALWAYS, tc, lc); if (pua_space_ligature) collapse_pua_space_ligature(num_cells); else if (cursor_offset > -1) { // false if DISABLE_LIGATURES_NEVER index_type left, right; split_run_at_offset(cursor_offset, &left, &right, scale); if (right > left) { if (left) { shape_run(first_cpu_cell, first_gpu_cell, left, &fg->fonts[rf.font_idx], rf, fg, false, tc, lc); render_groups(fg, rf, center_glyph, tc); } shape_run(first_cpu_cell + left, first_gpu_cell + left, right - left, &fg->fonts[rf.font_idx], rf, fg, true, tc, lc); render_groups(fg, rf, center_glyph, tc); if (right < num_cells) { shape_run(first_cpu_cell + right, first_gpu_cell + right, num_cells - right, &fg->fonts[rf.font_idx], rf, fg, false, tc, lc); render_groups(fg, rf, center_glyph, tc); } break; } } render_groups(fg, rf, center_glyph, tc); break; case BLANK_FONT: while(num_cells--) { first_gpu_cell->sprite_idx = 0; first_cpu_cell++; first_gpu_cell++; } break; case BOX_FONT: while(num_cells) { render_box_cell(fg, rf, first_cpu_cell, first_gpu_cell, tc); num_cells -= rf.scale; first_cpu_cell += rf.scale; first_gpu_cell += rf.scale; } break; case MISSING_FONT: while(num_cells--) { first_gpu_cell->sprite_idx = MISSING_GLYPH; first_cpu_cell++; first_gpu_cell++; } break; } } static bool is_non_emoji_dingbat(char_type ch, CharProps cp) { switch(ch) { START_ALLOW_CASE_RANGE case 0x2700 ... 0x27bf: case 0x1f100 ... 0x1f1ff: return !cp.is_emoji; END_ALLOW_CASE_RANGE } return false; } static unsigned int cell_cap_for_codepoint(const char_type cp) { unsigned int ans = UINT_MAX; for (size_t i = 0; i < num_narrow_symbols; i++) { SymbolMap *sm = narrow_symbols + i; if (sm->left <= cp && cp <= sm->right) ans = sm->font_idx; } return ans; } static bool run_fonts_are_equal(const RunFont *a, const RunFont *b) { return a->font_idx == b->font_idx && a->scale == b->scale && a->subscale_n == b->subscale_n && a->subscale_d == b->subscale_d && a->align.val == b->align.val && a->multicell_y == b->multicell_y; } static bool multicell_intersects_cursor(const Line *line, index_type lnum, const Cursor *cursor) { const CPUCell *c = line->cpu_cells + cursor->x; if (c->is_multicell) { index_type min_y = lnum > c->y ? lnum - c->y : 0; index_type max_y = lnum + (c->scale - c->y - 1); return min_y <= cursor->y && cursor->y <= max_y; } else return lnum == cursor->y; } void render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, DisableLigature disable_ligature_strategy, ListOfChars *lc) { #define RENDER if (run_font.font_idx != NO_FONT && i > first_cell_in_run) { \ int cursor_offset = -1; \ if (disable_ligature_at_cursor && first_cell_in_run <= cursor->x && cursor->x <= i && cursor->x < line->xnum && \ multicell_intersects_cursor(line, lnum, cursor)) cursor_offset = cursor->x - first_cell_in_run; \ render_run(fg, line->cpu_cells + first_cell_in_run, line->gpu_cells + first_cell_in_run, i - first_cell_in_run, run_font, false, center_glyph, cursor_offset, disable_ligature_strategy, line->text_cache, lc); \ } FontGroup *fg = (FontGroup*)fg_; RunFont basic_font = {.scale=1, .font_idx = NO_FONT}, run_font = basic_font, cell_font = basic_font; bool center_glyph = false; bool disable_ligature_at_cursor = cursor != NULL && disable_ligature_strategy == DISABLE_LIGATURES_CURSOR; index_type first_cell_in_run, i; for (i=0, first_cell_in_run=0; i < line->xnum; i++) { cell_font = basic_font; CPUCell *cpu_cell = line->cpu_cells + i; if (cpu_cell->is_multicell) { if (cpu_cell->x) { i += mcd_x_limit(cpu_cell) - cpu_cell->x - 1; continue; } cell_font.scale = cpu_cell->scale; cell_font.subscale_n = cpu_cell->subscale_n; cell_font.subscale_d = cpu_cell->subscale_d; cell_font.align.vertical = cpu_cell->valign; cell_font.align.horizontal = cpu_cell->halign; cell_font.multicell_y = cpu_cell->y; } text_in_cell(cpu_cell, line->text_cache, lc); bool is_main_font, is_emoji_presentation; GPUCell *gpu_cell = line->gpu_cells + i; const char_type first_ch = lc->chars[0]; cell_font.font_idx = font_for_cell(fg, cpu_cell, gpu_cell, &is_main_font, &is_emoji_presentation, line->text_cache, lc); CharProps cp = char_props_for(first_ch); if ( cell_font.font_idx != MISSING_FONT && ((!is_main_font && !is_emoji_presentation && cp.is_symbol) || (cell_font.font_idx != BOX_FONT && (is_private_use(cp))) || is_non_emoji_dingbat(first_ch, cp)) ) { unsigned int desired_cells = 1; if (cell_font.font_idx > 0) { Font *font = (fg->fonts + cell_font.font_idx); glyph_index glyph_id = glyph_id_for_codepoint(font->face, first_ch); int width = get_glyph_width(font->face, glyph_id); desired_cells = (unsigned int)ceilf((float)width / fg->fcm.cell_width); } desired_cells = MIN(desired_cells, cell_cap_for_codepoint(first_ch)); unsigned int num_spaces = 0; while ( i + num_spaces + 1 < line->xnum && (cell_is_char(line->cpu_cells + i + num_spaces + 1, ' ') || cell_is_char(line->cpu_cells + i + num_spaces + 1, 0x2002)) // space or en-space && num_spaces < MAX_NUM_EXTRA_GLYPHS_PUA && num_spaces + 1 < desired_cells ) { num_spaces++; // We have a private use char followed by space(s), render it as a multi-cell ligature. GPUCell *space_cell = line->gpu_cells + i + num_spaces; // Ensure the space cell uses the foreground color from the PUA cell. // This is needed because there are applications like // Powerline that use PUA+space with different foreground colors // for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467 space_cell->fg = gpu_cell->fg; space_cell->decoration_fg = gpu_cell->decoration_fg; } if (num_spaces) { center_glyph = true; RENDER center_glyph = false; render_run(fg, line->cpu_cells + i, line->gpu_cells + i, num_spaces + 1, cell_font, true, center_glyph, -1, disable_ligature_strategy, line->text_cache, lc); run_font = basic_font; first_cell_in_run = i + num_spaces + 1; i += num_spaces; continue; } } if (run_font.font_idx == NO_FONT) run_font = cell_font; if (run_fonts_are_equal(&run_font, &cell_font)) continue; RENDER run_font = cell_font; first_cell_in_run = i; } RENDER #undef RENDER } StringCanvas render_simple_text(FONTS_DATA_HANDLE fg_, const char *text) { FontGroup *fg = (FontGroup*)fg_; if (fg->fonts_count && fg->medium_font_idx) return render_simple_text_impl(fg->fonts[fg->medium_font_idx].face, text, fg->fcm.baseline); StringCanvas ans = {0}; return ans; } static void clear_symbol_maps(void) { if (symbol_maps) { free(symbol_maps); symbol_maps = NULL; num_symbol_maps = 0; } if (narrow_symbols) { free(narrow_symbols); narrow_symbols = NULL; num_narrow_symbols = 0; } } typedef struct { unsigned int main, bold, italic, bi, num_symbol_fonts; } DescriptorIndices; DescriptorIndices descriptor_indices = {0}; static bool set_symbol_maps(SymbolMap **maps, size_t *num, const PyObject *sm) { *num = PyTuple_GET_SIZE(sm); *maps = calloc(*num, sizeof(SymbolMap)); if (*maps == NULL) { PyErr_NoMemory(); return false; } for (size_t s = 0; s < *num; s++) { unsigned int left, right, font_idx; SymbolMap *x = *maps + s; if (!PyArg_ParseTuple(PyTuple_GET_ITEM(sm, s), "III", &left, &right, &font_idx)) return NULL; x->left = left; x->right = right; x->font_idx = font_idx; } return true; } static PyObject* set_font_data(PyObject UNUSED *m, PyObject *args) { PyObject *sm, *ns; Py_CLEAR(descriptor_for_idx); if (!PyArg_ParseTuple(args, "OIIIIO!dO!", &descriptor_for_idx, &descriptor_indices.bold, &descriptor_indices.italic, &descriptor_indices.bi, &descriptor_indices.num_symbol_fonts, &PyTuple_Type, &sm, &OPT(font_size), &PyTuple_Type, &ns)) return NULL; Py_INCREF(descriptor_for_idx); free_font_groups(); clear_symbol_maps(); set_symbol_maps(&symbol_maps, &num_symbol_maps, sm); set_symbol_maps(&narrow_symbols, &num_narrow_symbols, ns); Py_RETURN_NONE; } static void send_prerendered_sprites(FontGroup *fg) { // blank cell ensure_canvas_can_fit(fg, 1, 1); DecorationMetadata dm = {.start_idx=5}; current_send_sprite_to_gpu(fg, fg->canvas.buf, dm, fg->fcm); const unsigned cell_area = fg->fcm.cell_height * fg->fcm.cell_width; RAII_ALLOC(uint8_t, alpha_mask, malloc(cell_area)); if (!alpha_mask) fatal("Out of memory"); Region r = { .right = fg->fcm.cell_width, .bottom = fg->fcm.cell_height }; #define do_one(call) \ memset(alpha_mask, 0, cell_area); \ call; \ ensure_canvas_can_fit(fg, 1, 1); /* clear canvas */ \ render_alpha_mask(alpha_mask, fg->canvas.buf, &r, &r, fg->fcm.cell_width, fg->fcm.cell_width, 0xffffff); \ current_send_sprite_to_gpu(fg, fg->canvas.buf, dm, fg->fcm); // If you change the mapping of these cells you will need to change // BEAM_IDX in shader.c and STRIKE_SPRITE_INDEX in // shaders.py and MISSING_GLYPH in font.c and dec_idx above do_one(add_missing_glyph(alpha_mask, fg->fcm)); do_one(add_beam_cursor(alpha_mask, fg->fcm, fg->logical_dpi_x)); do_one(add_underline_cursor(alpha_mask, fg->fcm, fg->logical_dpi_y)); do_one(add_hollow_cursor(alpha_mask, fg->fcm, fg->logical_dpi_x, fg->logical_dpi_y)); RunFont rf = {.scale=1}; Region rg = {.bottom = fg->fcm.cell_height, .right = fg->fcm.cell_width}; sprite_index actual_dec_idx = index_for_decorations(fg, rf, rg, rg, fg->fcm).start_idx; if (actual_dec_idx != dm.start_idx) fatal("dec_idx: %u != actual_dec_idx: %u", dm.start_idx, actual_dec_idx); #undef do_one } static size_t initialize_font(FontGroup *fg, unsigned int desc_idx, const char *ftype) { PyObject *d = PyObject_CallFunction(descriptor_for_idx, "I", desc_idx); if (d == NULL) { PyErr_Print(); fatal("Failed for %s font", ftype); } bool bold = PyObject_IsTrue(PyTuple_GET_ITEM(d, 1)); bool italic = PyObject_IsTrue(PyTuple_GET_ITEM(d, 2)); PyObject *x = PyTuple_GET_ITEM(d, 0); PyObject *face = PyUnicode_Check(x) ? face_from_path(PyUnicode_AsUTF8(x), 0, (FONTS_DATA_HANDLE)fg) : desc_to_face(x, (FONTS_DATA_HANDLE)fg); Py_CLEAR(d); if (face == NULL) { PyErr_Print(); fatal("Failed to convert descriptor to face for %s font", ftype); } size_t idx = fg->fonts_count++; bool ok = init_font(fg->fonts + idx, face, bold, italic, false); Py_CLEAR(face); if (!ok) { if (PyErr_Occurred()) { PyErr_Print(); } fatal("Failed to initialize %s font: %zu", ftype, idx); } return idx; } static void initialize_font_group(FontGroup *fg) { fg->fonts_capacity = 10 + descriptor_indices.num_symbol_fonts; fg->fonts = calloc(fg->fonts_capacity, sizeof(Font)); if (fg->fonts == NULL) fatal("Out of memory allocating fonts array"); fg->fonts_count = 1; // the 0 index font is the box font if (!init_hash_tables(fg->fonts)) fatal("Out of memory"); vt_init(&fg->fallback_font_map); vt_init(&fg->scaled_font_map); vt_init(&fg->decorations_index_map); #define I(attr) if (descriptor_indices.attr) fg->attr##_font_idx = initialize_font(fg, descriptor_indices.attr, #attr); else fg->attr##_font_idx = -1; fg->medium_font_idx = initialize_font(fg, 0, "medium"); I(bold); I(italic); I(bi); #undef I fg->first_symbol_font_idx = fg->fonts_count; fg->first_fallback_font_idx = fg->fonts_count; fg->fallback_fonts_count = 0; for (size_t i = 0; i < descriptor_indices.num_symbol_fonts; i++) { initialize_font(fg, descriptor_indices.bi + 1 + i, "symbol_map"); fg->first_fallback_font_idx++; } #undef I calc_cell_metrics(fg, fg->fonts[fg->medium_font_idx].face); ensure_canvas_can_fit(fg, 8, 1); sprite_tracker_set_layout(&fg->sprite_tracker, fg->fcm.cell_width, fg->fcm.cell_height); // rescale the symbol_map faces for the desired cell height, this is how fallback fonts are sized as well for (size_t i = 0; i < descriptor_indices.num_symbol_fonts; i++) { Font *font = fg->fonts + i + fg->first_symbol_font_idx; set_size_for_face(font->face, fg->fcm.cell_height, true, (FONTS_DATA_HANDLE)fg); } ScaledFontData sfd = {.fcm=fg->fcm, .font_sz_in_pts=fg->font_sz_in_pts}; vt_insert(&fg->scaled_font_map, 1.f, sfd); } void send_prerendered_sprites_for_window(OSWindow *w) { FontGroup *fg = (FontGroup*)w->fonts_data; if (!fg->sprite_map) { fg->sprite_map = alloc_sprite_map(); send_prerendered_sprites(fg); } } FONTS_DATA_HANDLE load_fonts_data(double font_sz_in_pts, double dpi_x, double dpi_y) { FontGroup *fg = font_group_for(font_sz_in_pts, dpi_x, dpi_y); return (FONTS_DATA_HANDLE)fg; } static void finalize(void) { Py_CLEAR(python_send_to_gpu_impl); clear_symbol_maps(); Py_CLEAR(descriptor_for_idx); free_font_groups(); free(ligature_types); if (harfbuzz_buffer) { hb_buffer_destroy(harfbuzz_buffer); harfbuzz_buffer = NULL; } free(group_state.groups); group_state.groups = NULL; group_state.groups_capacity = 0; free(global_glyph_render_scratch.glyphs); free(global_glyph_render_scratch.sprite_positions); if (global_glyph_render_scratch.lc) { cleanup_list_of_chars(global_glyph_render_scratch.lc); free(global_glyph_render_scratch.lc); } global_glyph_render_scratch = (GlyphRenderScratch){0}; free(shape_buffer.codepoints); zero_at_ptr(&shape_buffer); } static PyObject* sprite_map_set_layout(PyObject UNUSED *self, PyObject *args) { unsigned int w, h; if(!PyArg_ParseTuple(args, "II", &w, &h)) return NULL; if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; } sprite_tracker_set_layout(&font_groups->sprite_tracker, w, h); Py_RETURN_NONE; } static PyObject* test_sprite_position_increment(PyObject UNUSED *self, PyObject *args UNUSED) { if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; } FontGroup *fg = font_groups; unsigned int x, y, z; sprite_index_to_pos(current_sprite_index(&fg->sprite_tracker), fg->sprite_tracker.xnum, fg->sprite_tracker.ynum, &x, &y, &z); if (!do_increment(fg)) return NULL; return Py_BuildValue("III", x, y, z); } static PyObject* set_send_sprite_to_gpu(PyObject UNUSED *self, PyObject *func) { Py_CLEAR(python_send_to_gpu_impl); if (func != Py_None) { python_send_to_gpu_impl = func; Py_INCREF(python_send_to_gpu_impl); } Py_RETURN_NONE; } static PyObject* test_render_line(PyObject UNUSED *self, PyObject *args) { PyObject *line; if (!PyArg_ParseTuple(args, "O!", &Line_Type, &line)) return NULL; if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; } RAII_ListOfChars(lc); render_line((FONTS_DATA_HANDLE)font_groups, (Line*)line, 0, NULL, DISABLE_LIGATURES_NEVER, &lc); Py_RETURN_NONE; } static uint32_t alpha_blend(uint32_t fg, uint32_t bg) { uint32_t r1 = (fg >> 16) & 0xFF, g1 = (fg >> 8) & 0xFF, b1 = fg & 0xFF, a = (fg >> 24) & 0xff; uint32_t r2 = (bg >> 16) & 0xFF, g2 = (bg >> 8) & 0xFF, b2 = bg & 0xFF; float alpha = a / 255.f; #define mix(x) uint32_t x = ((uint32_t)(alpha * x##1 + (1.0f - alpha) * x##2)) & 0xff; mix(r); mix(g); mix(b); #undef mix // Combine components into result color return (0xff000000) | (r << 16) | (g << 8) | b; } static PyObject* render_decoration(PyObject *self UNUSED, PyObject *args) { const char *which; FontCellMetrics fcm = {0}; double dpi = 96.0; if (!PyArg_ParseTuple(args, "sIIII|d", &which, &fcm.cell_width, &fcm.cell_height, &fcm.underline_position, &fcm.underline_thickness, &dpi)) return NULL; PyObject *ans = PyBytes_FromStringAndSize(NULL, (Py_ssize_t)fcm.cell_width * fcm.cell_height); if (!ans) return NULL; memset(PyBytes_AS_STRING(ans), 0, PyBytes_GET_SIZE(ans)); #define u(x) if (strcmp(which, #x) == 0) add_ ## x ## _underline((uint8_t*)PyBytes_AS_STRING(ans), fcm) u(curl); u(dashed); u(dotted); u(double); u(straight); else if (strcmp(which, "strikethrough") == 0) add_strikethrough((uint8_t*)PyBytes_AS_STRING(ans), fcm); else if (strcmp(which, "missing") == 0) add_missing_glyph((uint8_t*)PyBytes_AS_STRING(ans), fcm); else if (strcmp(which, "beam_cursor") == 0) add_beam_cursor((uint8_t*)PyBytes_AS_STRING(ans), fcm, dpi); else if (strcmp(which, "underline_cursor") == 0) add_underline_cursor((uint8_t*)PyBytes_AS_STRING(ans), fcm, dpi); else if (strcmp(which, "hollow_cursor") == 0) add_hollow_cursor((uint8_t*)PyBytes_AS_STRING(ans), fcm, dpi, dpi); else { Py_CLEAR(ans); PyErr_Format(PyExc_KeyError, "Unknown decoration type: %s", which); } return ans; #undef u } static PyObject* concat_cells(PyObject UNUSED *self, PyObject *args) { // Concatenate cells returning RGBA data unsigned int cell_width, cell_height; int is_32_bit; PyObject *cells; unsigned long bgcolor = 0; if (!PyArg_ParseTuple(args, "IIpO!|k", &cell_width, &cell_height, &is_32_bit, &PyTuple_Type, &cells, &bgcolor)) return NULL; size_t num_cells = PyTuple_GET_SIZE(cells), r, c, i; PyObject *ans = PyBytes_FromStringAndSize(NULL, (size_t)4 * cell_width * cell_height * num_cells); if (ans == NULL) return PyErr_NoMemory(); pixel *dest = (pixel*)PyBytes_AS_STRING(ans); for (r = 0; r < cell_height; r++) { for (c = 0; c < num_cells; c++) { void *s = ((uint8_t*)PyBytes_AS_STRING(PyTuple_GET_ITEM(cells, c))); if (is_32_bit) { pixel *src = (pixel*)s + cell_width * r; for (i = 0; i < cell_width; i++, dest++) dest[0] = alpha_blend(src[0], bgcolor); } else { uint8_t *src = (uint8_t*)s + cell_width * r; for (i = 0; i < cell_width; i++, dest++) dest[0] = alpha_blend(0x00ffffff | ((src[i] & 0xff) << 24), bgcolor); } } } return ans; } static PyObject* current_fonts(PyObject *self UNUSED, PyObject *args) { unsigned long long os_window_id = 0; if (!PyArg_ParseTuple(args, "|K", &os_window_id)) return NULL; if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; } FontGroup *fg = font_groups; if (os_window_id) { OSWindow *os_window = os_window_for_id(os_window_id); if (!os_window) { PyErr_SetString(PyExc_KeyError, "no oswindow with the specified id exists"); return NULL; } fg = (FontGroup*)os_window->fonts_data; } RAII_PyObject(ans, PyDict_New()); if (!ans) return NULL; #define SET(key, val) {if (PyDict_SetItemString(ans, #key, fg->fonts[val].face) != 0) { return NULL; }} SET(medium, fg->medium_font_idx); if (fg->bold_font_idx > 0) SET(bold, fg->bold_font_idx); if (fg->italic_font_idx > 0) SET(italic, fg->italic_font_idx); if (fg->bi_font_idx > 0) SET(bi, fg->bi_font_idx); unsigned num_symbol_fonts = fg->first_fallback_font_idx - fg->first_symbol_font_idx; RAII_PyObject(ss, PyTuple_New(num_symbol_fonts)); if (!ss) return NULL; for (size_t i = 0; i < num_symbol_fonts; i++) { Py_INCREF(fg->fonts[fg->first_symbol_font_idx + i].face); PyTuple_SET_ITEM(ss, i, fg->fonts[fg->first_symbol_font_idx + i].face); } if (PyDict_SetItemString(ans, "symbol", ss) != 0) return NULL; RAII_PyObject(ff, PyTuple_New(fg->fallback_fonts_count)); if (!ff) return NULL; for (size_t i = 0; i < fg->fallback_fonts_count; i++) { Py_INCREF(fg->fonts[fg->first_fallback_font_idx + i].face); PyTuple_SET_ITEM(ff, i, fg->fonts[fg->first_fallback_font_idx + i].face); } if (PyDict_SetItemString(ans, "fallback", ff) != 0) return NULL; #define p(x) { RAII_PyObject(t, PyFloat_FromDouble(fg->x)); if (!t) return NULL; if (PyDict_SetItemString(ans, #x, t) != 0) return NULL; } p(font_sz_in_pts); p(logical_dpi_x); p(logical_dpi_y); #undef p Py_INCREF(ans); return ans; #undef SET } static PyObject* get_fallback_font(PyObject UNUSED *self, PyObject *args) { if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; } PyObject *text; int bold, italic; if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL; GPUCell gpu_cell = {0}; CPUCell cpu_cell = {0}; RAII_ListOfChars(lc); lc.count = PyUnicode_GET_LENGTH(text); ensure_space_for_chars(&lc, lc.count); if (!PyUnicode_AsUCS4(text, lc.chars, lc.capacity, 1)) return NULL; if (bold) gpu_cell.attrs.bold = true; if (italic) gpu_cell.attrs.italic = true; FontGroup *fg = font_groups; ssize_t ans = fallback_font(fg, &cpu_cell, &gpu_cell, &lc); if (ans == MISSING_FONT) { PyErr_SetString(PyExc_ValueError, "No fallback font found"); return NULL; } if (ans < 0) { PyErr_SetString(PyExc_ValueError, "Too many fallback fonts"); return NULL; } return fg->fonts[ans].face; } static PyObject* create_test_font_group(PyObject *self UNUSED, PyObject *args) { double sz, dpix, dpiy; if (!PyArg_ParseTuple(args, "ddd", &sz, &dpix, &dpiy)) return NULL; FontGroup *fg = font_group_for(sz, dpix, dpiy); if (!fg->sprite_map) send_prerendered_sprites(fg); return Py_BuildValue("III", fg->fcm.cell_width, fg->fcm.cell_height, fg->fcm.baseline); } static PyObject* free_font_data(PyObject *self UNUSED, PyObject *args UNUSED) { finalize(); Py_RETURN_NONE; } PyTypeObject ParsedFontFeature_Type; ParsedFontFeature* parse_font_feature(const char *spec) { ParsedFontFeature *self = (ParsedFontFeature*)ParsedFontFeature_Type.tp_alloc(&ParsedFontFeature_Type, 0); if (self != NULL) { if (!hb_feature_from_string(spec, -1, &self->feature)) { PyErr_Format(PyExc_ValueError, "%s is not a valid font feature", self); Py_CLEAR(self); } } return self; } static PyObject * parsed_font_feature_new(PyTypeObject *type UNUSED, PyObject *args, PyObject *kwds UNUSED) { const char *s; if (!PyArg_ParseTuple(args, "s", &s)) return NULL; return (PyObject*)parse_font_feature(s); } static PyObject* parsed_font_feature_str(PyObject *self_) { char buf[128]; hb_feature_to_string(&((ParsedFontFeature*)self_)->feature, buf, arraysz(buf)); return PyUnicode_FromString(buf); } static PyObject* parsed_font_feature_repr(PyObject *self_) { RAII_PyObject(s, parsed_font_feature_str(self_)); return s ? PyObject_Repr(s) : NULL; } static PyObject* parsed_font_feature_cmp(PyObject *self, PyObject *other, int op) { if (op != Py_EQ && op != Py_NE) return Py_NotImplemented; if (!PyObject_TypeCheck(other, &ParsedFontFeature_Type)) { if (op == Py_EQ) Py_RETURN_FALSE; Py_RETURN_TRUE; } ParsedFontFeature *a = (ParsedFontFeature*)self, *b = (ParsedFontFeature*)other; PyObject *ret = Py_True; if (memcmp(&a->feature, &b->feature, sizeof(hb_feature_t)) == 0) { if (op == Py_NE) ret = Py_False; } else { if (op == Py_EQ) ret = Py_False; } Py_INCREF(ret); return ret; } static Py_hash_t parsed_font_feature_hash(PyObject *s) { ParsedFontFeature *self = (ParsedFontFeature*)s; if (!self->hash_computed) { self->hash_computed = true; self->hashval = vt_hash_bytes(&self->feature, sizeof(hb_feature_t)); } return self->hashval; } static PyObject* parsed_font_feature_call(PyObject *s, PyObject *args, PyObject *kwargs UNUSED) { ParsedFontFeature *self = (ParsedFontFeature*)s; void *dest = PyLong_AsVoidPtr(args); memcpy(dest, &self->feature, sizeof(hb_feature_t)); Py_RETURN_NONE; } PyTypeObject ParsedFontFeature_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kitty.fast_data_types.ParsedFontFeature", .tp_basicsize = sizeof(ParsedFontFeature), .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "FontFeature", .tp_new = parsed_font_feature_new, .tp_str = parsed_font_feature_str, .tp_repr = parsed_font_feature_repr, .tp_richcompare = parsed_font_feature_cmp, .tp_hash = parsed_font_feature_hash, .tp_call = parsed_font_feature_call, }; static PyObject* pyspecialize_font_descriptor(PyObject *self UNUSED, PyObject *args) { PyObject *desc; double font_sz, dpi_x, dpi_y; if (!PyArg_ParseTuple(args, "Offf", &desc, &font_sz, &dpi_x, &dpi_y)) return NULL; return specialize_font_descriptor(desc, font_sz, dpi_x, dpi_y); } static PyObject* set_allow_use_of_box_fonts(PyObject *self UNUSED, PyObject *val) { allow_use_of_box_fonts = PyObject_IsTrue(val); Py_RETURN_NONE; } static PyObject* sprite_idx_to_pos(PyObject *self UNUSED, PyObject *args) { unsigned x, y, z, idx, xnum, ynum; if (!PyArg_ParseTuple(args, "III", &idx, &xnum, &ynum)) return NULL; sprite_index_to_pos(idx, xnum, ynum, &x, &y, &z); return Py_BuildValue("III", x, y, z); } static PyObject* pyrender_box_char(PyObject *self UNUSED, PyObject *args) { unsigned int ch; unsigned long width, height; double dpi_x = 96., dpi_y = 96., scale = 1.; if (!PyArg_ParseTuple(args, "Ikk|ddd", &ch, &width, &height, &scale, &dpi_x, &dpi_y)) return NULL; RAII_PyObject(ans, PyBytes_FromStringAndSize(NULL, width*16 * height*16)); if (!ans) return NULL; render_box_char(ch, (uint8_t*)PyBytes_AS_STRING(ans), width, height, dpi_x, dpi_y, scale); if (_PyBytes_Resize(&ans, width * height) != 0) return NULL; return Py_NewRef(ans); } static PyMethodDef module_methods[] = { METHODB(set_font_data, METH_VARARGS), METHODB(sprite_idx_to_pos, METH_VARARGS), METHODB(free_font_data, METH_NOARGS), METHODB(create_test_font_group, METH_VARARGS), METHODB(sprite_map_set_layout, METH_VARARGS), METHODB(test_sprite_position_increment, METH_NOARGS), METHODB(concat_cells, METH_VARARGS), METHODB(render_decoration, METH_VARARGS), METHODB(set_send_sprite_to_gpu, METH_O), METHODB(set_allow_use_of_box_fonts, METH_O), METHODB(test_shape, METH_VARARGS), METHODB(current_fonts, METH_VARARGS), METHODB(test_render_line, METH_VARARGS), METHODB(get_fallback_font, METH_VARARGS), {"specialize_font_descriptor", (PyCFunction)pyspecialize_font_descriptor, METH_VARARGS, ""}, {"render_box_char", (PyCFunction)pyrender_box_char, METH_VARARGS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_fonts(PyObject *module) { harfbuzz_buffer = hb_buffer_create(); if (harfbuzz_buffer == NULL || !hb_buffer_allocation_successful(harfbuzz_buffer) || !hb_buffer_pre_allocate(harfbuzz_buffer, 2048)) { PyErr_NoMemory(); return false; } hb_buffer_set_cluster_level(harfbuzz_buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); #define create_feature(feature, where) {\ if (!hb_feature_from_string(feature, sizeof(feature) - 1, &hb_features[where])) { \ PyErr_SetString(PyExc_RuntimeError, "Failed to create " feature " harfbuzz feature"); \ return false; \ }} create_feature("-liga", LIGA_FEATURE); create_feature("-dlig", DLIG_FEATURE); create_feature("-calt", CALT_FEATURE); #undef create_feature if (PyModule_AddFunctions(module, module_methods) != 0) return false; if (PyType_Ready(&ParsedFontFeature_Type) < 0) return 0; if (PyModule_AddObject(module, "ParsedFontFeature", (PyObject *)&ParsedFontFeature_Type) != 0) return 0; Py_INCREF(&ParsedFontFeature_Type); return true; } kitty-0.41.1/kitty/fonts.h0000664000175000017510000000774614773370543014773 0ustar nileshnilesh/* * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "lineops.h" #include "state.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" #include #pragma GCC diagnostic pop typedef struct { uint8_t *canvas; size_t width, height; } StringCanvas; typedef struct FontFeatures { size_t count; hb_feature_t *features; } FontFeatures; typedef struct ParsedFontFeature { PyObject_HEAD hb_feature_t feature; Py_hash_t hashval; bool hash_computed; } ParsedFontFeature; typedef struct GlyphRenderInfo { unsigned canvas_width, rendered_width; int x; } GlyphRenderInfo; ParsedFontFeature* parse_font_feature(const char *spec); // API that font backends need to implement unsigned int glyph_id_for_codepoint(const PyObject *, char_type); int get_glyph_width(PyObject *, glyph_index); bool is_glyph_empty(PyObject *, glyph_index); hb_font_t* harfbuzz_font_for_face(PyObject*); bool set_size_for_face(PyObject*, unsigned int, bool, FONTS_DATA_HANDLE); FontCellMetrics cell_metrics(PyObject*); bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE, GlyphRenderInfo*); PyObject* create_fallback_face(PyObject *base_face, const ListOfChars *lc, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg); PyObject* specialize_font_descriptor(PyObject *base_descriptor, double, double, double); PyObject* face_from_path(const char *path, int index, FONTS_DATA_HANDLE); PyObject* face_from_descriptor(PyObject*, FONTS_DATA_HANDLE); PyObject* iter_fallback_faces(FONTS_DATA_HANDLE fgh, ssize_t *idx); bool face_equals_descriptor(PyObject *face_, PyObject *descriptor); const char* postscript_name_for_face(const PyObject*); void sprite_tracker_current_layout(FONTS_DATA_HANDLE data, unsigned int *x, unsigned int *y, unsigned int *z); void render_alpha_mask(const uint8_t *alpha_mask, pixel* dest, const Region *src_rect, const Region *dest_rect, size_t src_stride, size_t dest_stride, pixel color_rgb); void render_line(FONTS_DATA_HANDLE, Line *line, index_type lnum, Cursor *cursor, DisableLigature, ListOfChars*); void sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len); typedef void (*free_extra_data_func)(void*); StringCanvas render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline); StringCanvas render_simple_text(FONTS_DATA_HANDLE fg_, const char *text); bool face_apply_scaling(PyObject*face, const FONTS_DATA_HANDLE fg); bool add_font_name_record(PyObject *table, uint16_t platform_id, uint16_t encoding_id, uint16_t language_id, uint16_t name_id, const char *string, uint16_t string_len); PyObject* get_best_name_from_name_table(PyObject *table, PyObject *name_id); PyObject* read_name_font_table(const uint8_t *table, size_t table_len); bool read_fvar_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output); bool read_STAT_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output); bool read_features_from_font_table(const uint8_t *table, size_t table_len, PyObject *name_lookup_table, PyObject *output); FontFeatures* features_for_face(PyObject *); bool create_features_for_face(const char* psname, PyObject *features, FontFeatures* output); PyObject* font_features_as_dict(const FontFeatures *font_features); bool has_cell_text(bool(*has_codepoint)(const void*, char_type ch), const void* face, bool do_debug, const ListOfChars *lc); static inline void right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) { pixel *src; size_t r; for (r = 0, src = canvas; r < height; r++, src += width) { memmove(src + amt, src, sizeof(pixel) * (width - amt)); zero_at_ptr_count(src, amt); } } kitty-0.41.1/kitty/fonts/0000775000175000017510000000000014773370543014604 5ustar nileshnileshkitty-0.41.1/kitty/fonts/__init__.py0000664000175000017510000001463314773370543016724 0ustar nileshnileshfrom collections.abc import Sequence from enum import Enum, IntEnum, auto from typing import TYPE_CHECKING, Literal, NamedTuple, TypedDict, TypeVar, Union from kitty.fast_data_types import ParsedFontFeature from kitty.types import run_once from kitty.typing import CoreTextFont, FontConfigPattern from kitty.utils import shlex_split if TYPE_CHECKING: import re class ListedFont(TypedDict): family: str style: str full_name: str postscript_name: str is_monospace: bool is_variable: bool descriptor: FontConfigPattern | CoreTextFont class VariableAxis(TypedDict): minimum: float maximum: float default: float hidden: bool tag: str strid: str # Can be empty string when not present class NamedStyle(TypedDict): axis_values: dict[str, float] name: str psname: str # can be empty string when not present class DesignValue1(TypedDict): format: Literal[1] flags: int name: str value: float class DesignValue2(TypedDict): format: Literal[2] flags: int name: str value: float minimum: float maximum: float class DesignValue3(TypedDict): format: Literal[3] flags: int name: str value: float linked_value: float DesignValue = Union[DesignValue1, DesignValue2, DesignValue3] class DesignAxis(TypedDict): name: str ordering: int tag: str values: list[DesignValue] class AxisValue(TypedDict): design_index: int value: float class MultiAxisStyle(TypedDict): flags: int name: str values: tuple[AxisValue, ...] class VariableData(TypedDict): axes: tuple[VariableAxis, ...] named_styles: tuple[NamedStyle, ...] variations_postscript_name_prefix: str elided_fallback_name: str design_axes: tuple[DesignAxis, ...] multi_axis_styles: tuple[MultiAxisStyle, ...] class ModificationType(Enum): underline_position = auto() underline_thickness = auto() strikethrough_position = auto() strikethrough_thickness = auto() cell_width = auto() cell_height = auto() baseline = auto() size = auto() class ModificationUnit(IntEnum): pt = 0 percent = 1 pixel = 2 class ModificationValue(NamedTuple): val: float unit: ModificationUnit def __repr__(self) -> str: u = '%' if self.unit is ModificationUnit.percent else '' return f'{self.val:g}{u}' class FontModification(NamedTuple): mod_type: ModificationType mod_value: ModificationValue font_name: str = '' def __repr__(self) -> str: fn = f' {self.font_name}' if self.font_name else '' return f'{self.mod_type.name}{fn} {self.mod_value}' class FontSpec(NamedTuple): family: str | None = None style: str | None = None postscript_name: str | None = None full_name: str | None = None system: str | None = None axes: tuple[tuple[str, float], ...] = () variable_name: str | None = None features: tuple[ParsedFontFeature, ...] = () created_from_string: str = '' @classmethod def from_setting(cls, spec: str) -> 'FontSpec': if spec == 'auto': return FontSpec(system='auto', created_from_string=spec) items = tuple(shlex_split(spec)) if '=' not in items[0]: return FontSpec(system=spec, created_from_string=spec) axes = {} defined = {} features: tuple[ParsedFontFeature, ...] = () for item in items: k, sep, v = item.partition('=') if sep != '=': raise ValueError(f'The font specification: {spec} is not valid as {item} does not contain an =') if k in ('family', 'style', 'full_name', 'postscript_name', 'variable_name'): defined[k] = v elif k == 'features': features += tuple(ParsedFontFeature(x) for x in v.split()) else: try: axes[k] = float(v) except Exception: raise ValueError(f'The font specification: {spec} is not valid as {v} is not a number') return FontSpec(axes=tuple(axes.items()), created_from_string=spec, features=features, **defined) @property def is_system(self) -> bool: return bool(self.system) @property def is_auto(self) -> bool: return self.system == 'auto' @property def as_setting(self) -> str: if self.created_from_string: return self.created_from_string if self.system: return self.system ans = [] from shlex import quote def a(key: str, val: str) -> None: ans.append(f'{key}={quote(val)}') if self.family is not None: a('family', self.family) if self.postscript_name is not None: a('postscript_name', self.postscript_name) if self.full_name is not None: a('full_name', self.full_name) if self.variable_name is not None: a('variable_name', self.variable_name) if self.style is not None: a('style', self.style) if self.features: a('features', ' '.join(str(f) for f in self.features)) if self.axes: for (key, val) in self.axes: a(key, f'{val:g}') return ' '.join(ans) def __str__(self) -> str: return self.as_setting # Cannot change __repr__ as it will break config generation Descriptor = Union[FontConfigPattern, CoreTextFont] DescriptorVar = TypeVar('DescriptorVar', FontConfigPattern, CoreTextFont, Descriptor) class Score(NamedTuple): variable_score: int style_score: float monospace_score: int width_score: int class Scorer: def __init__(self, bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> None: self.bold = bold self.italic = italic self.monospaced = monospaced self.prefer_variable = prefer_variable def sorted_candidates(self, candidates: Sequence[DescriptorVar], dump: bool = False) -> list[DescriptorVar]: raise NotImplementedError() def __repr__(self) -> str: return f'{self.__class__.__name__}(bold={self.bold}, italic={self.italic}, monospaced={self.monospaced}, prefer_variable={self.prefer_variable})' __str__ = __repr__ @run_once def fnname_pat() -> 're.Pattern[str]': import re return re.compile(r'\s+') def family_name_to_key(family: str) -> str: return fnname_pat().sub(' ', family).strip().lower() kitty-0.41.1/kitty/fonts/common.py0000664000175000017510000005110314773370543016446 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2024, Kovid Goyal from typing import TYPE_CHECKING, Any, Literal, TypedDict, Union from kitty.constants import is_macos from kitty.fast_data_types import ParsedFontFeature from kitty.fonts import Descriptor, DescriptorVar, DesignAxis, FontSpec, NamedStyle, Scorer, VariableAxis, VariableData, family_name_to_key from kitty.options.types import Options if TYPE_CHECKING: from kitty.fast_data_types import CTFace from kitty.fast_data_types import Face as FT_Face FontCollectionMapType = Literal['family_map', 'ps_map', 'full_map', 'variable_map'] FontMap = dict[FontCollectionMapType, dict[str, list[Descriptor]]] Face = Union[FT_Face, CTFace] def all_fonts_map(monospaced: bool) -> FontMap: ... def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> Scorer: ... def find_best_match( family: str, bold: bool = False, italic: bool = False, monospaced: bool = True, ignore_face: Descriptor | None = None, prefer_variable: bool = False, ) -> Descriptor: ... def find_last_resort_text_font(bold: bool = False, italic: bool = False, monospaced: bool = True) -> Descriptor: ... def face_from_descriptor(descriptor: Descriptor, font_sz_in_pts: float | None = None, dpi_x: float | None = None, dpi_y: float | None = None ) -> Face: ... def is_monospace(descriptor: Descriptor) -> bool: ... def is_variable(descriptor: Descriptor) -> bool: ... def set_named_style(name: str, font: Descriptor, vd: VariableData) -> bool: ... def set_axis_values(tag_map: dict[str, float], font: Descriptor, vd: VariableData) -> bool: ... def get_axis_values(font: Descriptor, vd: VariableData) -> dict[str, float]: ... else: FontCollectionMapType = FontMap = None from kitty.fast_data_types import specialize_font_descriptor if is_macos: from kitty.fast_data_types import CTFace as Face from kitty.fonts.core_text import ( all_fonts_map, create_scorer, find_best_match, find_last_resort_text_font, get_axis_values, is_monospace, is_variable, set_axis_values, set_named_style, ) else: from kitty.fast_data_types import Face from kitty.fonts.fontconfig import ( all_fonts_map, create_scorer, find_best_match, find_last_resort_text_font, get_axis_values, is_monospace, is_variable, set_axis_values, set_named_style, ) def face_from_descriptor(descriptor, font_sz_in_pts = None, dpi_x = None, dpi_y = None): if font_sz_in_pts is not None: descriptor = specialize_font_descriptor(descriptor, font_sz_in_pts, dpi_x, dpi_y) return Face(descriptor=descriptor) cache_for_variable_data_by_path: dict[str, VariableData] = {} attr_map = {(False, False): 'font_family', (True, False): 'bold_font', (False, True): 'italic_font', (True, True): 'bold_italic_font'} class Event: is_set: bool = False class FamilyAxisValues: regular_weight: float | None = None regular_slant: float | None = None regular_ital: float | None = None regular_width: float | None = None bold_weight: float | None = None italic_slant: float | None = None italic_ital: float | None = None def get_wght(self, bold: bool, italic: bool) -> float | None: return self.bold_weight if bold else self.regular_weight def get_ital(self, bold: bool, italic: bool) -> float | None: return self.italic_ital if italic else self.regular_ital def get_slnt(self, bold: bool, italic: bool) -> float | None: return self.italic_slant if italic else self.regular_slant def get_wdth(self, bold: bool, italic: bool) -> float | None: return self.regular_width def get(self, tag: str, bold: bool, italic: bool) -> float | None: f = getattr(self, f'get_{tag}', None) return None if f is None else f(bold, italic) def set_regular_values(self, axis_values: dict[str, float]) -> None: self.regular_weight = axis_values.get('wght') self.regular_width = axis_values.get('wdth') self.regular_ital = axis_values.get('ital') self.regular_slant = axis_values.get('slnt') def set_bold_values(self, axis_values: dict[str, float]) -> None: self.bold_weight = axis_values.get('wght') def set_italic_values(self, axis_values: dict[str, float]) -> None: self.italic_ital = axis_values.get('ital') self.italic_slant = axis_values.get('slnt') def get_variable_data_for_descriptor(d: Descriptor) -> VariableData: if not d['path']: return face_from_descriptor(d).get_variable_data() ans = cache_for_variable_data_by_path.get(d['path']) if ans is None: ans = cache_for_variable_data_by_path[d['path']] = face_from_descriptor(d).get_variable_data() return ans def get_variable_data_for_face(d: Face) -> VariableData: path = d.path if not path: return d.get_variable_data() ans = cache_for_variable_data_by_path.get(path) if ans is None: ans = cache_for_variable_data_by_path[path] = d.get_variable_data() return ans def find_best_match_in_candidates( candidates: list[DescriptorVar], scorer: Scorer, is_medium_face: bool, ignore_face: DescriptorVar | None = None ) -> DescriptorVar | None: if candidates: for x in scorer.sorted_candidates(candidates): if ignore_face is None or x != ignore_face: return x return None def pprint(*a: Any, **kw: Any) -> None: from pprint import pprint pprint(*a, **kw) def find_medium_variant(font: DescriptorVar) -> DescriptorVar: font = font.copy() vd = get_variable_data_for_descriptor(font) for i, ns in enumerate(vd['named_styles']): if ns['name'] == 'Regular': set_named_style(ns['psname'] or ns['name'], font, vd) return font axis_values = {} for i, ax in enumerate(vd['axes']): tag = ax['tag'] for dax in vd['design_axes']: if dax['tag'] == tag: for x in dax['values']: if x['format'] in (1, 2): if x['name'] == 'Regular': axis_values[tag] = x['value'] break if axis_values: set_axis_values(axis_values, font, vd) return font def get_bold_design_weight(dax: DesignAxis, ax: VariableAxis, regular_weight: float) -> float: ans = regular_weight candidates = [] for x in dax['values']: if x['format'] in (1, 2): if x['value'] > regular_weight: candidates.append(x['value']) if candidates: ans = min(candidates) return ans def get_design_value_for(dax: DesignAxis, ax: VariableAxis, bold: bool, italic: bool, family_axis_values: FamilyAxisValues) -> float: family_val = family_axis_values.get(ax['tag'], bold, italic) if family_val is not None and ax['minimum'] <= family_val <= ax['maximum']: return family_val default = ax['default'] if dax['tag'] == 'wght': keys = ('semibold', 'bold', 'heavy', 'black') if bold else ('regular', 'medium') elif dax['tag'] in ('ital', 'slnt'): keys = ('italic', 'oblique', 'slanted', 'slant') if italic else ('regular', 'normal', 'medium', 'upright') else: return default priorities = {} for x in dax['values']: if x['format'] in (1, 2): q = x['name'].lower() try: idx = keys.index(q) except ValueError: continue priorities[x['value']] = idx ans = default if priorities: ans = sorted(priorities, key=priorities.__getitem__)[0] if bold and ax['tag'] == 'wght' and family_axis_values.regular_weight is not None and ans <= family_axis_values.regular_weight: ans = get_bold_design_weight(dax, ax, family_axis_values.regular_weight) return ans def find_bold_italic_variant(medium: Descriptor, bold: bool, italic: bool, family_axis_values: FamilyAxisValues) -> Descriptor: # we first pick the best font file for bold/italic if there are more than # one. For example SourceCodeVF has Italic and Upright faces with variable # weights in each, so we rely on the OS font matcher to give us the best # font file. monospaced = is_monospace(medium) unsorted = all_fonts_map(monospaced)['variable_map'][family_name_to_key(medium['family'])] fonts = create_scorer(bold, italic, monospaced).sorted_candidates(unsorted) vd = get_variable_data_for_descriptor(fonts[0]) ans = fonts[0].copy() # now we need to specialise all axes in ans axis_values = {} dax_map = {dax['tag']: dax for dax in vd['design_axes']} for ax in vd['axes']: tag = ax['tag'] dax = dax_map.get(tag) if dax is not None: axis_values[tag] = get_design_value_for(dax, ax, bold, italic, family_axis_values) if axis_values: set_axis_values(axis_values, ans, vd) return ans def find_best_variable_face(spec: FontSpec, bold: bool, italic: bool, monospaced: bool, candidates: list[Descriptor]) -> Descriptor: if spec.variable_name is not None: q = spec.variable_name.lower() for font in candidates: vd = get_variable_data_for_descriptor(font) if vd['variations_postscript_name_prefix'].lower() == q: return font if spec.style: q = spec.style.lower() for font in candidates: vd = get_variable_data_for_descriptor(font) for x in vd['named_styles']: if x['psname'].lower() == q: return font for x in vd['named_styles']: if x['name'].lower() == q: return font return create_scorer(bold, italic, monospaced).sorted_candidates(candidates)[0] def get_fine_grained_font( spec: FontSpec, bold: bool = False, italic: bool = False, family_axis_values: FamilyAxisValues = FamilyAxisValues(), resolved_medium_font: Descriptor | None = None, monospaced: bool = True, match_is_more_specific_than_family: Event = Event() ) -> Descriptor: font_map = all_fonts_map(monospaced) is_medium_face = resolved_medium_font is None scorer = create_scorer(bold, italic, monospaced) if spec.postscript_name: q = find_best_match_in_candidates(font_map['ps_map'].get(family_name_to_key(spec.postscript_name), []), scorer, is_medium_face) if q: match_is_more_specific_than_family.is_set = True return q if spec.full_name: q = find_best_match_in_candidates(font_map['full_map'].get(family_name_to_key(spec.full_name), []), scorer, is_medium_face) if q: match_is_more_specific_than_family.is_set = True return q if spec.family: key = family_name_to_key(spec.family) # First look for a variable font candidates = font_map['variable_map'].get(key, []) if candidates: q = candidates[0] if len(candidates) == 1 else find_best_variable_face(spec, bold, italic, monospaced, candidates) q, applied = apply_variation_to_pattern(q, spec) if applied: match_is_more_specific_than_family.is_set = True return q return find_medium_variant(q) if resolved_medium_font is None else find_bold_italic_variant(resolved_medium_font, bold, italic, family_axis_values) # Now look for any font candidates = font_map['family_map'].get(key, []) if candidates: if spec.style: qs = spec.style.lower() candidates = [x for x in candidates if x['style'].lower() == qs] q = find_best_match_in_candidates(candidates, scorer, is_medium_face) if q: return q return find_last_resort_text_font(bold, italic, monospaced) def apply_variation_to_pattern(pat: Descriptor, spec: FontSpec) -> tuple[Descriptor, bool]: vd = face_from_descriptor(pat).get_variable_data() pat = pat.copy() if spec.style: if set_named_style(spec.style, pat, vd): return pat, True tag_map, name_map = {}, {} for i, ax in enumerate(vd['axes']): tag_map[ax['tag']] = i if ax['strid']: name_map[ax['strid'].lower()] = ax['tag'] axis_values = {} for axspec in spec.axes: qname = axspec[0] if qname in tag_map: axis_values[qname] = axspec[1] continue tag = name_map.get(qname.lower()) if tag: axis_values[tag] = axspec[1] return pat, set_axis_values(axis_values, pat, vd) def get_font_from_spec( spec: FontSpec, bold: bool = False, italic: bool = False, family_axis_values: FamilyAxisValues = FamilyAxisValues(), resolved_medium_font: Descriptor | None = None, match_is_more_specific_than_family: Event = Event() ) -> Descriptor: if not spec.is_system: ans = get_fine_grained_font(spec, bold, italic, resolved_medium_font=resolved_medium_font, family_axis_values=family_axis_values, match_is_more_specific_than_family=match_is_more_specific_than_family) if spec.features: ans = ans.copy() ans['features'] = spec.features return ans family = spec.system or '' if family == 'auto': if bold or italic: assert resolved_medium_font is not None family = resolved_medium_font['family'] if is_variable(resolved_medium_font) or is_actually_variable_despite_fontconfigs_lies(resolved_medium_font): v = find_bold_italic_variant(resolved_medium_font, bold, italic, family_axis_values=family_axis_values) if v is not None: return v else: family = 'monospace' return find_best_match(family, bold, italic, ignore_face=resolved_medium_font) class FontFiles(TypedDict): medium: Descriptor bold: Descriptor italic: Descriptor bi: Descriptor actually_variable_cache: dict[str, bool] = {} def is_actually_variable_despite_fontconfigs_lies(d: Descriptor) -> bool: if d['descriptor_type'] != 'fontconfig': return False path = d['path'] ans = actually_variable_cache.get(path) if ans is not None: return ans m = all_fonts_map(is_monospace(d))['variable_map'] for x in m.get(family_name_to_key(d['family']), ()): if x['path'] == path: actually_variable_cache[path] = True return True actually_variable_cache[path] = False return False def get_font_files(opts: Options) -> FontFiles: ans: dict[str, Descriptor] = {} match_is_more_specific_than_family = Event() medium_font = get_font_from_spec(opts.font_family, match_is_more_specific_than_family=match_is_more_specific_than_family) medium_font_is_variable = is_variable(medium_font) or is_actually_variable_despite_fontconfigs_lies(medium_font) if not match_is_more_specific_than_family.is_set and medium_font_is_variable: medium_font = find_medium_variant(medium_font) family_axis_values = FamilyAxisValues() if medium_font_is_variable: family_axis_values.set_regular_values(get_axis_values(medium_font, get_variable_data_for_descriptor(medium_font))) kd = {(False, False): 'medium', (True, False): 'bold', (False, True): 'italic', (True, True): 'bi'} for (bold, italic), attr in attr_map.items(): if bold or italic: spec: FontSpec = getattr(opts, attr) font = get_font_from_spec(spec, bold, italic, resolved_medium_font=medium_font, family_axis_values=family_axis_values) # Set family axis values based on the values in font if not (bold and italic) and (is_variable(medium_font) or is_actually_variable_despite_fontconfigs_lies(medium_font)): av = get_axis_values(font, get_variable_data_for_descriptor(font)) (family_axis_values.set_italic_values if italic else family_axis_values.set_bold_values)(av) if spec.is_auto and not font.get('features') and medium_font.get('features'): # Set font features based on medium face features font = font.copy() font['features'] = medium_font['features'] else: font = medium_font key = kd[(bold, italic)] ans[key] = font return {'medium': ans['medium'], 'bold': ans['bold'], 'italic': ans['italic'], 'bi': ans['bi']} def axis_values_are_equal(defaults: dict[str, float], a: dict[str, float], b: dict[str, float]) -> bool: ad, bd = defaults.copy(), defaults.copy() ad.update(a) bd.update(b) return ad == bd def _get_named_style(axis_map: dict[str, float], vd: VariableData) -> NamedStyle | None: defaults = {ax['tag']: ax['default'] for ax in vd['axes']} for ns in vd['named_styles']: if axis_values_are_equal(defaults, ns['axis_values'], axis_map): return ns return None def get_named_style(face_or_descriptor: Face | Descriptor) -> NamedStyle | None: if isinstance(face_or_descriptor, dict): d: Descriptor = face_or_descriptor vd = get_variable_data_for_descriptor(d) if d['descriptor_type'] == 'fontconfig': ns = d.get('named_style', -1) if ns > -1 and ns < len(vd['named_styles']): return vd['named_styles'][ns] axis_map = {} axes = vd['axes'] for i, val in enumerate(d.get('axes', ())): if i < len(axes): axis_map[axes[i]['tag']] = val else: axis_map = d.get('axis_map', {}).copy() else: face: Face = face_or_descriptor vd = get_variable_data_for_face(face) q = face.get_variation() if q is None: return None axis_map = q return _get_named_style(axis_map, vd) def get_axis_map(face_or_descriptor: Face | Descriptor) -> dict[str, float]: base_axis_map = {} axis_map: dict[str, float] = {} if isinstance(face_or_descriptor, dict): d: Descriptor = face_or_descriptor vd = get_variable_data_for_descriptor(d) if d['descriptor_type'] == 'fontconfig': ns = d.get('named_style', -1) if ns > -1 and ns < len(vd['named_styles']): base_axis_map = vd['named_styles'][ns]['axis_values'].copy() axis_map = {} axes = vd['axes'] for i, val in enumerate(d.get('axes', ())): if i < len(axes): axis_map[axes[i]['tag']] = val else: axis_map = d.get('axis_map', {}).copy() else: face: Face = face_or_descriptor q = face.get_variation() if q is not None: axis_map = q base_axis_map.update(axis_map) return base_axis_map def spec_for_face(family: str, face: Face) -> FontSpec: v = face.get_variation() features = tuple(map(ParsedFontFeature, face.applied_features().values())) if v is None: return FontSpec(family=family, postscript_name=face.postscript_name(), features=features) vd = face.get_variable_data() varname = vd['variations_postscript_name_prefix'] ns = get_named_style(face) if ns is None: axes = [] for key, val in get_axis_map(face).items(): axes.append((key, val)) return FontSpec(family=family, variable_name=varname, axes=tuple(axes), features=features) return FontSpec(family=family, variable_name=varname, style=ns['psname'] or ns['name'], features=features) def develop(family: str = '') -> None: import sys family = family or sys.argv[-1] from kitty.options.utils import parse_font_spec opts = Options() opts.font_family = parse_font_spec(family) ff = get_font_files(opts) def s(name: str, d: Descriptor) -> None: f = face_from_descriptor(d) print(name, str(f)) features = f.get_features() print(' Features :', features) s('Medium :', ff['medium']) print() s('Bold :', ff['bold']) print() s('Italic :', ff['italic']) print() s('Bold-Italic:', ff['bi']) def list_fonts(monospaced: bool = True) -> dict[str, list[dict[str, str]]]: ans: dict[str, list[dict[str, str]]] = {} for key, descriptors in all_fonts_map(monospaced)['family_map'].items(): entries = ans.setdefault(key, []) for d in descriptors: entries.append({'family': d['family'], 'psname': d['postscript_name'], 'path': d['path'], 'style': d['style']}) return ans if __name__ == '__main__': develop() kitty-0.41.1/kitty/fonts/core_text.py0000664000175000017510000002365214773370543017162 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2017, Kovid Goyal import itertools import operator from collections import defaultdict from collections.abc import Generator, Iterable, Sequence from functools import lru_cache from typing import NamedTuple from kitty.fast_data_types import CTFace, coretext_all_fonts from kitty.typing import CoreTextFont from kitty.utils import log_error from . import Descriptor, DescriptorVar, ListedFont, Score, Scorer, VariableData, family_name_to_key attr_map = {(False, False): 'font_family', (True, False): 'bold_font', (False, True): 'italic_font', (True, True): 'bold_italic_font'} FontMap = dict[str, dict[str, list[CoreTextFont]]] def create_font_map(all_fonts: Iterable[CoreTextFont]) -> FontMap: ans: FontMap = {'family_map': {}, 'ps_map': {}, 'full_map': {}, 'variable_map': {}} vmap: dict[str, list[CoreTextFont]] = defaultdict(list) for x in all_fonts: f = family_name_to_key(x['family']) s = family_name_to_key(x['style']) ps = family_name_to_key(x['postscript_name']) ans['family_map'].setdefault(f, []).append(x) ans['ps_map'].setdefault(ps, []).append(x) ans['full_map'].setdefault(f'{f} {s}', []).append(x) if x['variation'] is not None: vmap[f].append(x) # CoreText makes a separate descriptor for each named style in each # variable font file. Keep only the default style descriptor, which has an # empty variation dictionary. If no default exists, pick the one with the # smallest variation dictionary size. keyfunc = operator.itemgetter('path') for k, v in vmap.items(): v.sort(key=keyfunc) uniq_per_path = [] for _, g in itertools.groupby(v, keyfunc): uniq_per_path.append(sorted(g, key=lambda x: len(x['variation'] or ()))[0]) ans['variable_map'][k] = uniq_per_path return ans @lru_cache(maxsize=2) def all_fonts_map(monospaced: bool = True) -> FontMap: return create_font_map(coretext_all_fonts(monospaced)) def is_monospace(descriptor: CoreTextFont) -> bool: return descriptor['monospace'] def is_variable(descriptor: CoreTextFont) -> bool: return descriptor['variation'] is not None def list_fonts() -> Generator[ListedFont, None, None]: for fd in coretext_all_fonts(False): f = fd['family'] if f: fn = fd['display_name'] if not fn: fn = f'{f} {fd["style"]}'.strip() yield {'family': f, 'full_name': fn, 'postscript_name': fd['postscript_name'] or '', 'is_monospace': fd['monospace'], 'is_variable': is_variable(fd), 'descriptor': fd, 'style': fd['style']} class WeightRange(NamedTuple): minimum: float = 99999 maximum: float = -99999 medium: float = -99999 bold: float = -99999 @property def is_valid(self) -> bool: return self.minimum != wr.minimum and self.maximum != wr.maximum and self.medium != wr.medium and self.bold != wr.bold wr = WeightRange() @lru_cache def weight_range_for_family(family: str) -> WeightRange: faces = all_fonts_map(True)['family_map'].get(family_name_to_key(family), ()) mini, maxi, medium, bold = wr.minimum, wr.maximum, wr.medium, wr.bold for face in faces: w = face['weight'] mini, maxi = min(w, mini), max(w, maxi) s = face['style'].lower() if not s: continue s = s.split()[0] if s == 'semibold': bold = w elif s == 'bold' and bold == wr.bold: bold = w elif s == 'regular': medium = w elif s == 'medium' and medium == wr.medium: medium = w return WeightRange(mini, maxi, medium, bold) class CTScorer(Scorer): weight_range: WeightRange | None = None def score(self, candidate: Descriptor) -> Score: assert candidate['descriptor_type'] == 'core_text' variable_score = 0 if self.prefer_variable and candidate['variation'] is not None else 1 bold_score = candidate['weight'] # -1 to 1 with 0 being normal if self.weight_range is None: if bold_score < 0: # thinner than normal, reject bold_score = 2.0 else: if self.bold: # prefer semibold=0.3 to full bold = 0.4 bold_score = abs(bold_score - 0.3) else: anchor = self.weight_range.bold if self.bold else self.weight_range.medium bold_score = abs(bold_score - anchor) italic_score = candidate['slant'] # -1 to 1 with 0 being upright < 0 being backward slant, abs(slant) == 1 implies 30 deg rotation if self.italic: if italic_score < 0: italic_score = 2.0 else: italic_score = abs(1 - italic_score) monospace_match = 0 if candidate['monospace'] else 1 is_regular_width = not candidate['expanded'] and not candidate['condensed'] return Score(variable_score, bold_score + italic_score, monospace_match, 0 if is_regular_width else 1) def sorted_candidates(self, candidates: Sequence[DescriptorVar], dump: bool = False) -> list[DescriptorVar]: self.weight_range = None families = {x['family'] for x in candidates} if len(families) == 1: wr = weight_range_for_family(next(iter(families))) if wr.is_valid and wr.medium < 0: # Operator Mono is an example of this craziness self.weight_range = wr candidates = sorted(candidates, key=self.score) if dump: print(self) if self.weight_range: print(self.weight_range) for x in candidates: assert x['descriptor_type'] == 'core_text' print(CTFace(descriptor=x).postscript_name(), f'bold={x["bold"]}', f'italic={x["italic"]}', f'weight={x["weight"]:.2f}', f'slant={x["slant"]:.2f}') print(' ', self.score(x)) print() return candidates def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> Scorer: return CTScorer(bold, italic, monospaced, prefer_variable) def find_last_resort_text_font(bold: bool = False, italic: bool = False, monospaced: bool = True) -> CoreTextFont: font_map = all_fonts_map(monospaced) candidates = font_map['family_map']['menlo'] return create_scorer(bold, italic, monospaced).sorted_candidates(candidates)[0] def find_best_match( family: str, bold: bool = False, italic: bool = False, monospaced: bool = True, ignore_face: CoreTextFont | None = None, prefer_variable: bool = False ) -> CoreTextFont: q = family_name_to_key(family) font_map = all_fonts_map(monospaced) scorer = create_scorer(bold, italic, monospaced, prefer_variable=prefer_variable) # First look for an exact match for selector in ('ps_map', 'full_map'): candidates = font_map[selector].get(q) if candidates: candidates = scorer.sorted_candidates(candidates) possible = candidates[0] if possible != ignore_face: return possible # See if we have a variable font if not bold and not italic and font_map['variable_map'].get(q): candidates = font_map['variable_map'][q] candidates = scorer.sorted_candidates(candidates) possible = candidates[0] if possible != ignore_face: from .common import find_medium_variant return find_medium_variant(possible) # Let CoreText choose the font if the family exists, otherwise # fallback to Menlo if q not in font_map['family_map']: if family.lower() not in ('monospace', 'symbols nerd font mono'): log_error(f'The font {family} was not found, falling back to Menlo') q = 'menlo' candidates = scorer.sorted_candidates(font_map['family_map'][q]) return candidates[0] def font_for_family(family: str) -> tuple[CoreTextFont, bool, bool]: ans = find_best_match(family, monospaced=False) return ans, ans['bold'], ans['italic'] def descriptor(f: ListedFont) -> CoreTextFont: d = f['descriptor'] assert d['descriptor_type'] == 'core_text' return d def prune_family_group(g: list[ListedFont]) -> list[ListedFont]: # CoreText returns a separate font for every style in the variable font, so # merge them. variable_paths = {descriptor(f)['path']: False for f in g if f['is_variable']} if not variable_paths: return g def is_ok(d: CoreTextFont) -> bool: if d['path'] not in variable_paths: return True if not variable_paths[d['path']]: variable_paths[d['path']] = True return True return False return [x for x in g if is_ok(descriptor(x))] def set_axis_values(tag_map: dict[str, float], font: CoreTextFont, vd: VariableData) -> bool: known_axes = {ax['tag'] for ax in vd['axes']} previous = font.get('axis_map', {}) new = previous.copy() for tag in known_axes: val = tag_map.get(tag) if val is not None: new[tag] = val font['axis_map'] = new return new != previous def set_named_style(name: str, font: CoreTextFont, vd: VariableData) -> bool: q = name.lower() for i, ns in enumerate(vd['named_styles']): if ns['psname'].lower() == q: return set_axis_values(ns['axis_values'], font, vd) for i, ns in enumerate(vd['named_styles']): if ns['name'].lower() == q: return set_axis_values(ns['axis_values'], font, vd) if vd['elided_fallback_name']: for i, ns in enumerate(vd['named_styles']): eq = ' '.join(ns['name'].replace(vd['elided_fallback_name'], '').strip().split()).lower() if q == eq: return set_axis_values(ns['axis_values'], font, vd) return False def get_axis_values(font: CoreTextFont, vd: VariableData) -> dict[str, float]: return font.get('axis_map', {}) kitty-0.41.1/kitty/fonts/features.py0000664000175000017510000002114614773370543017000 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2024, Kovid Goyal from enum import IntEnum from typing import NamedTuple class Type(IntEnum): boolean = 1 index = 2 hidden = 3 class FeatureDefinition(NamedTuple): name: str type: Type # From: https://learn.microsoft.com/en-ca/typography/opentype/spec/featurelist known_features: dict[str, FeatureDefinition] = { # {{{ 'aalt': FeatureDefinition('Access All Alternates', Type.index), 'abvf': FeatureDefinition('Above-base Forms', Type.hidden), 'abvm': FeatureDefinition('Above-base Mark Positioning', Type.hidden), 'abvs': FeatureDefinition('Above-base Substitutions', Type.hidden), 'afrc': FeatureDefinition('Alternative Fractions', Type.boolean), 'akhn': FeatureDefinition('Akhand', Type.hidden), 'blwf': FeatureDefinition('Below-base Forms', Type.hidden), 'blwm': FeatureDefinition('Below-base Mark Positioning', Type.hidden), 'blws': FeatureDefinition('Below-base Substitutions', Type.hidden), 'calt': FeatureDefinition('Contextual Alternates', Type.boolean), 'case': FeatureDefinition('Case-Sensitive Forms', Type.hidden), 'ccmp': FeatureDefinition('Glyph Composition / Decomposition', Type.hidden), 'cfar': FeatureDefinition('Conjunct Form After Ro', Type.hidden), 'chws': FeatureDefinition('Contextual Half-width Spacing', Type.boolean), 'cjct': FeatureDefinition('Conjunct Forms', Type.hidden), 'clig': FeatureDefinition('Contextual Ligatures', Type.boolean), 'cpct': FeatureDefinition('Centered CJK Punctuation', Type.boolean), 'cpsp': FeatureDefinition('Capital Spacing', Type.boolean), 'cswh': FeatureDefinition('Contextual Swash', Type.boolean), 'curs': FeatureDefinition('Cursive Positioning', Type.hidden), 'c2pc': FeatureDefinition('Petite Capitals From Capitals', Type.boolean), 'c2sc': FeatureDefinition('Small Capitals From Capitals', Type.boolean), 'dist': FeatureDefinition('Distances', Type.hidden), 'dlig': FeatureDefinition('Discretionary Ligatures', Type.boolean), 'dnom': FeatureDefinition('Denominators', Type.hidden), 'dtls': FeatureDefinition('Dotless Forms', Type.hidden), 'expt': FeatureDefinition('Expert Forms', Type.boolean), 'falt': FeatureDefinition('Final Glyph on Line Alternates', Type.boolean), 'fin2': FeatureDefinition('Terminal Forms #2', Type.hidden), 'fin3': FeatureDefinition('Terminal Forms #3', Type.hidden), 'fina': FeatureDefinition('Terminal Forms', Type.hidden), 'flac': FeatureDefinition('Flattened accent forms', Type.hidden), 'frac': FeatureDefinition('Fractions', Type.boolean), 'fwid': FeatureDefinition('Full Widths', Type.boolean), 'half': FeatureDefinition('Half Forms', Type.hidden), 'haln': FeatureDefinition('Halant Forms', Type.hidden), 'halt': FeatureDefinition('Alternate Half Widths', Type.boolean), 'hist': FeatureDefinition('Historical Forms', Type.boolean), 'hkna': FeatureDefinition('Horizontal Kana Alternates', Type.boolean), 'hlig': FeatureDefinition('Historical Ligatures', Type.boolean), 'hngl': FeatureDefinition('Hangul', Type.boolean), 'hojo': FeatureDefinition('Hojo Kanji Forms (JIS X 0212-1990 Kanji Forms)', Type.boolean), 'hwid': FeatureDefinition('Half Widths', Type.boolean), 'init': FeatureDefinition('Initial Forms', Type.hidden), 'isol': FeatureDefinition('Isolated Forms', Type.hidden), 'ital': FeatureDefinition('Italics', Type.boolean), 'jalt': FeatureDefinition('Justification Alternates', Type.boolean), 'jp78': FeatureDefinition('JIS78 Forms', Type.boolean), 'jp83': FeatureDefinition('JIS83 Forms', Type.boolean), 'jp90': FeatureDefinition('JIS90 Forms', Type.boolean), 'jp04': FeatureDefinition('JIS2004 Forms', Type.boolean), 'kern': FeatureDefinition('Kerning', Type.boolean), 'lfbd': FeatureDefinition('Left Bounds', Type.boolean), 'liga': FeatureDefinition('Standard Ligatures', Type.boolean), 'ljmo': FeatureDefinition('Leading Jamo Forms', Type.hidden), 'lnum': FeatureDefinition('Lining Figures', Type.boolean), 'locl': FeatureDefinition('Localized Forms', Type.hidden), 'ltra': FeatureDefinition('Left-to-right alternates', Type.hidden), 'ltrm': FeatureDefinition('Left-to-right mirrored forms', Type.hidden), 'mark': FeatureDefinition('Mark Positioning', Type.hidden), 'med2': FeatureDefinition('Medial Forms #2', Type.hidden), 'medi': FeatureDefinition('Medial Forms', Type.hidden), 'mgrk': FeatureDefinition('Mathematical Greek', Type.boolean), 'mkmk': FeatureDefinition('Mark to Mark Positioning', Type.hidden), 'mset': FeatureDefinition('Mark Positioning via Substitution', Type.hidden), 'nalt': FeatureDefinition('Alternate Annotation Forms', Type.index), 'nlck': FeatureDefinition('NLC Kanji Forms', Type.boolean), 'nukt': FeatureDefinition('Nukta Forms', Type.hidden), 'numr': FeatureDefinition('Numerators', Type.hidden), 'onum': FeatureDefinition('Oldstyle Figures', Type.boolean), 'opbd': FeatureDefinition('Optical Bounds', Type.boolean), 'ordn': FeatureDefinition('Ordinals', Type.boolean), 'ornm': FeatureDefinition('Ornaments', Type.index), 'palt': FeatureDefinition('Proportional Alternate Widths', Type.boolean), 'pcap': FeatureDefinition('Petite Capitals', Type.boolean), 'pkna': FeatureDefinition('Proportional Kana', Type.boolean), 'pnum': FeatureDefinition('Proportional Figures', Type.boolean), 'pref': FeatureDefinition('Pre-Base Forms', Type.hidden), 'pres': FeatureDefinition('Pre-base Substitutions', Type.hidden), 'pstf': FeatureDefinition('Post-base Forms', Type.hidden), 'psts': FeatureDefinition('Post-base Substitutions', Type.hidden), 'pwid': FeatureDefinition('Proportional Widths', Type.boolean), 'qwid': FeatureDefinition('Quarter Widths', Type.boolean), 'rand': FeatureDefinition('Randomize', Type.boolean), 'rclt': FeatureDefinition('Required Contextual Alternates', Type.hidden), 'rkrf': FeatureDefinition('Rakar Forms', Type.hidden), 'rlig': FeatureDefinition('Required Ligatures', Type.hidden), 'rphf': FeatureDefinition('Reph Forms', Type.hidden), 'rtbd': FeatureDefinition('Right Bounds', Type.boolean), 'rtla': FeatureDefinition('Right-to-left alternates', Type.hidden), 'rtlm': FeatureDefinition('Right-to-left mirrored forms', Type.hidden), 'ruby': FeatureDefinition('Ruby Notation Forms', Type.boolean), 'rvrn': FeatureDefinition('Required Variation Alternates', Type.hidden), 'salt': FeatureDefinition('Stylistic Alternates', Type.index), 'sinf': FeatureDefinition('Scientific Inferiors', Type.boolean), 'size': FeatureDefinition('Optical size', Type.hidden), 'smcp': FeatureDefinition('Small Capitals', Type.boolean), 'smpl': FeatureDefinition('Simplified Forms', Type.boolean), 'ssty': FeatureDefinition('Math script style alternates', Type.hidden), 'stch': FeatureDefinition('Stretching Glyph Decomposition', Type.hidden), 'subs': FeatureDefinition('Subscript', Type.boolean), 'sups': FeatureDefinition('Superscript', Type.boolean), 'swsh': FeatureDefinition('Swash', Type.index), 'titl': FeatureDefinition('Titling', Type.boolean), 'tjmo': FeatureDefinition('Trailing Jamo Forms', Type.hidden), 'tnam': FeatureDefinition('Traditional Name Forms', Type.boolean), 'tnum': FeatureDefinition('Tabular Figures', Type.boolean), 'trad': FeatureDefinition('Traditional Forms', Type.index), 'twid': FeatureDefinition('Third Widths', Type.boolean), 'unic': FeatureDefinition('Unicase', Type.boolean), 'valt': FeatureDefinition('Alternate Vertical Metrics', Type.boolean), 'vatu': FeatureDefinition('Vattu Variants', Type.hidden), 'vchw': FeatureDefinition('Vertical Contextual Half-width Spacing', Type.hidden), 'vert': FeatureDefinition('Vertical Writing', Type.boolean), 'vhal': FeatureDefinition('Alternate Vertical Half Metrics', Type.boolean), 'vjmo': FeatureDefinition('Vowel Jamo Forms', Type.hidden), 'vkna': FeatureDefinition('Vertical Kana Alternates', Type.boolean), 'vkrn': FeatureDefinition('Vertical Kerning', Type.boolean), 'vpal': FeatureDefinition('Proportional Alternate Vertical Metrics', Type.boolean), 'vrt2': FeatureDefinition('Vertical Alternates and Rotation', Type.boolean), 'vrtr': FeatureDefinition('Vertical Alternates for Rotation', Type.boolean), 'zero': FeatureDefinition('Slashed Zero', Type.boolean), } for i in range(1, 100): known_features[f'cv{i:02d}'] = FeatureDefinition(f'Character Variant {i}', Type.index) for i in range(1, 20): known_features[f'ss{i:02d}'] = FeatureDefinition(f'Stylistic Set {i}', Type.boolean) # }}} kitty-0.41.1/kitty/fonts/fontconfig.py0000664000175000017510000002722314773370543017320 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal import sys from collections.abc import Generator, Sequence from functools import lru_cache from typing import Literal, NamedTuple, Optional, cast from kitty.fast_data_types import ( FC_DUAL, FC_MONO, FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_REGULAR, FC_WIDTH_NORMAL, Face, fc_list, ) from kitty.fast_data_types import ( FC_WEIGHT_SEMIBOLD as FC_WEIGHT_BOLD, ) from kitty.fast_data_types import fc_match as fc_match_impl from kitty.typing import FontConfigPattern from . import Descriptor, DescriptorVar, ListedFont, Score, Scorer, VariableData, family_name_to_key FontCollectionMapType = Literal['family_map', 'ps_map', 'full_map', 'variable_map'] FontMap = dict[FontCollectionMapType, dict[str, list[FontConfigPattern]]] def create_font_map(all_fonts: tuple[FontConfigPattern, ...]) -> FontMap: ans: FontMap = {'family_map': {}, 'ps_map': {}, 'full_map': {}, 'variable_map': {}} for x in all_fonts: if not x.get('path'): continue f = family_name_to_key(x['family']) full = family_name_to_key(x['full_name']) ps = family_name_to_key(x['postscript_name']) ans['family_map'].setdefault(f, []).append(x) ans['ps_map'].setdefault(ps, []).append(x) ans['full_map'].setdefault(full, []).append(x) if x['variable']: ans['variable_map'].setdefault(f, []).append(x) return ans @lru_cache(maxsize=2) def all_fonts_map(monospaced: bool = True) -> FontMap: if monospaced: ans = fc_list(spacing=FC_DUAL) + fc_list(spacing=FC_MONO) else: # allow non-monospaced and bitmapped fonts as these are used for # symbol_map ans = fc_list(allow_bitmapped_fonts=True) return create_font_map(ans) def is_monospace(descriptor: FontConfigPattern) -> bool: return descriptor['spacing'] in ('MONO', 'DUAL') def is_variable(descriptor: FontConfigPattern) -> bool: return descriptor['variable'] def list_fonts(only_variable: bool = False) -> Generator[ListedFont, None, None]: for fd in fc_list(only_variable=only_variable): f = fd.get('family') if f and isinstance(f, str): fn_ = fd.get('full_name') if fn_: fn = str(fn_) else: fn = f'{f} {fd.get("style", "")}'.strip() yield { 'family': f, 'full_name': fn, 'postscript_name': str(fd.get('postscript_name', '')), 'is_monospace': is_monospace(fd), 'descriptor': fd, 'is_variable': is_variable(fd), 'style': fd['style'], } @lru_cache def fc_match(family: str, bold: bool, italic: bool, spacing: int = FC_MONO) -> FontConfigPattern: return fc_match_impl(family, bold, italic, spacing) class WeightRange(NamedTuple): minimum: int = sys.maxsize maximum: int = -1 medium: int = -1 bold: int = -1 @property def is_valid(self) -> bool: return self.minimum != wr.minimum and self.maximum != wr.maximum and self.medium != wr.medium and self.bold != wr.bold wr = WeightRange() @lru_cache def weight_range_for_family(family: str) -> WeightRange: faces = all_fonts_map(True)['family_map'].get(family_name_to_key(family), ()) mini, maxi, medium, bold = wr.minimum, wr.maximum, wr.medium, wr.bold seen_weights = set() for face in faces: w = face['weight'] mini, maxi = min(w, mini), max(w, maxi) seen_weights.add(w) s = face['style'].lower() if not s: continue s = s.split()[0] if s == 'semibold': bold = w elif s == 'bold' and bold == wr.bold: bold = w elif s == 'regular': medium = w elif s == 'medium' and medium == wr.medium: medium = w if len(seen_weights) < 2: return wr return WeightRange(mini, maxi, medium, bold) class FCScorer(Scorer): weight_range: WeightRange | None = None def score(self, candidate: Descriptor) -> Score: assert candidate['descriptor_type'] == 'fontconfig' variable_score = 0 if self.prefer_variable and candidate['variable'] else 1 if self.weight_range is None: bold_score = abs((FC_WEIGHT_BOLD if self.bold else FC_WEIGHT_REGULAR) - candidate['weight']) else: bold_score = abs((self.weight_range.bold if self.bold else self.weight_range.medium) - candidate['weight']) italic_score = abs((FC_SLANT_ITALIC if self.italic else FC_SLANT_ROMAN) - candidate['slant']) monospace_match = 0 if self.monospaced: monospace_match = 0 if candidate.get('spacing') == 'MONO' else 1 width_score = abs(candidate['width'] - FC_WIDTH_NORMAL) return Score(variable_score, bold_score / 1000 + italic_score / 110, monospace_match, width_score) def sorted_candidates(self, candidates: Sequence[DescriptorVar], dump: bool = False) -> list[DescriptorVar]: self.weight_range = None families = {x['family'] for x in candidates} if len(families) == 1: wr = weight_range_for_family(next(iter(families))) if wr.is_valid and wr.medium < 100: # Operator Mono and Cascadia Code are examples self.weight_range = wr candidates = sorted(candidates, key=self.score) if dump: print(self) if self.weight_range: print(self.weight_range) for x in candidates: assert x['descriptor_type'] == 'fontconfig' print(Face(descriptor=x).postscript_name(), f'weight={x["weight"]}', f'slant={x["slant"]}') print(' ', self.score(x)) print() return candidates def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> Scorer: return FCScorer(bold, italic, monospaced, prefer_variable) def find_last_resort_text_font(bold: bool = False, italic: bool = False, monospaced: bool = True) -> FontConfigPattern: # Use fc-match with a generic family family = 'monospace' if monospaced else 'sans-serif' return fc_match(family, bold, italic) def find_best_match( family: str, bold: bool = False, italic: bool = False, monospaced: bool = True, ignore_face: FontConfigPattern | None = None, prefer_variable: bool = False, ) -> FontConfigPattern: from .common import find_best_match_in_candidates q = family_name_to_key(family) font_map = all_fonts_map(monospaced) scorer = create_scorer(bold, italic, monospaced, prefer_variable=prefer_variable) is_medium_face = not bold and not italic # First look for an exact match groups: tuple[FontCollectionMapType, ...] = ('ps_map', 'full_map', 'family_map') for which in groups: m = font_map[which] cq = m.get(q, []) if cq: if which == 'full_map' and cq[0]['family'] == cq[0]['full_name']: continue # IBM Plex Mono has fullname of regular face == family_name under fontconfig exact_match = find_best_match_in_candidates(cq, scorer, is_medium_face, ignore_face=ignore_face) if exact_match: return exact_match # Use fc-match to see if we can find a monospaced font that matches family # When aliases are defined, spacing can cause the incorrect font to be # returned, so check with and without spacing and use the one that matches. mono_possibility = fc_match(family, False, False, FC_MONO) dual_possibility = fc_match(family, False, False, FC_DUAL) any_possibility = fc_match(family, False, False, 0) tries = (dual_possibility, mono_possibility) if any_possibility == dual_possibility else (mono_possibility, dual_possibility) for possibility in tries: for key, map_key in (('postscript_name', 'ps_map'), ('full_name', 'full_map'), ('family', 'family_map')): map_key = cast(FontCollectionMapType, map_key) val: str | None = cast(Optional[str], possibility.get(key)) if val: candidates = font_map[map_key].get(family_name_to_key(val)) if candidates: if len(candidates) == 1: # happens if the family name is an alias, so we search with # the actual family name to see if we can find all the # fonts in the family. family_name_candidates = font_map['family_map'].get(family_name_to_key(candidates[0]['family'])) if family_name_candidates and len(family_name_candidates) > 1: candidates = family_name_candidates return scorer.sorted_candidates(candidates)[0] return find_last_resort_text_font(bold, italic, monospaced) def font_for_family(family: str) -> tuple[FontConfigPattern, bool, bool]: ans = find_best_match(family, monospaced=False) return ans, ans.get('weight', 0) >= FC_WEIGHT_BOLD, ans.get('slant', FC_SLANT_ROMAN) != FC_SLANT_ROMAN def descriptor(f: ListedFont) -> FontConfigPattern: d = f['descriptor'] assert d['descriptor_type'] == 'fontconfig' return d def prune_family_group(g: list[ListedFont]) -> list[ListedFont]: # fontconfig creates dummy entries for named styles in variable fonts, prune them variable_paths = {descriptor(f)['path'] for f in g if f['is_variable']} if not variable_paths: return g def is_ok(d: FontConfigPattern) -> bool: return d['variable'] or d['path'] not in variable_paths return [x for x in g if is_ok(descriptor(x))] def set_named_style(name: str, font: FontConfigPattern, vd: VariableData) -> bool: q = name.lower() for i, ns in enumerate(vd['named_styles']): if ns['psname'].lower() == q: font['named_style'] = i return True for i, ns in enumerate(vd['named_styles']): if ns['name'].lower() == q: font['named_style'] = i return True if vd['elided_fallback_name']: for i, ns in enumerate(vd['named_styles']): eq = ' '.join(ns['name'].replace(vd['elided_fallback_name'], '').strip().split()).lower() if q == eq: font['named_style'] = i return True return False def lift_axes_to_named_style_if_possible(font: FontConfigPattern, vd: VariableData) -> bool: axes = font.get('axes', tuple(ax['default'] for ax in vd['axes'])) q = {vd['axes'][i]['tag']: val for i, val in enumerate(axes)} for i, ns in enumerate(vd['named_styles']): if ns['axis_values'] == q: font.pop('axes', None) font['named_style'] = i return True return False def set_axis_values(tag_map: dict[str, float], font: FontConfigPattern, vd: VariableData) -> bool: axes = list(font.get('axes', ())) or [ax['default'] for ax in vd['axes']] changed = False for i, ax in enumerate(vd['axes']): val = tag_map.get(ax['tag']) if val is not None: changed = True axes[i] = val if changed: font['axes'] = tuple(axes) lift_axes_to_named_style_if_possible(font, vd) return changed def get_axis_values(font: FontConfigPattern, vd: VariableData) -> dict[str, float]: ans: dict[str, float] = {} ns = font.get('named_style') if ns is not None: if ns > -1 and ns < len(vd['named_styles']): ans = vd['named_styles'][ns]['axis_values'] axis_values = font.get('axes', ()) for i, ax in enumerate(vd['axes']): tag = ax['tag'] if i < len(axis_values): ans[tag] = axis_values[i] else: if tag not in ans: ans[tag] = ax['default'] return ans kitty-0.41.1/kitty/fonts/list.py0000664000175000017510000000242014773370543016127 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2017, Kovid Goyal from collections.abc import Sequence from kitty.constants import is_macos from . import ListedFont from .common import get_variable_data_for_descriptor if is_macos: from .core_text import list_fonts, prune_family_group else: from .fontconfig import list_fonts, prune_family_group def create_family_groups(monospaced: bool = True) -> dict[str, list[ListedFont]]: g: dict[str, list[ListedFont]] = {} for f in list_fonts(): if not monospaced or f['is_monospace']: g.setdefault(f['family'], []).append(f) return {k: prune_family_group(v) for k, v in g.items()} def as_json(indent: int | None = None) -> str: import json groups = create_family_groups() for v in groups.values(): for f in v: f['variable_data'] = get_variable_data_for_descriptor(f['descriptor']) # type: ignore return json.dumps(groups, indent=indent) def main(argv: Sequence[str]) -> None: import os from kitty.constants import kitten_exe, kitty_exe argv = list(argv) if '--psnames' in argv: argv.remove('--psnames') os.environ['KITTY_PATH_TO_KITTY_EXE'] = kitty_exe() os.execlp(kitten_exe(), 'kitten', 'choose-fonts') kitty-0.41.1/kitty/fonts/render.py0000664000175000017510000003232314773370543016440 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal import ctypes import os import sys from collections.abc import Callable, Generator from typing import TYPE_CHECKING, Any, Literal, Union from kitty.constants import fonts_dir, is_macos from kitty.fast_data_types import ( Screen, concat_cells, create_test_font_group, current_fonts, get_fallback_font, render_decoration, set_builtin_nerd_font, set_font_data, set_options, set_send_sprite_to_gpu, sprite_idx_to_pos, sprite_map_set_limits, test_render_line, test_shape, ) from kitty.options.types import Options, defaults from kitty.options.utils import parse_font_spec from kitty.types import _T from kitty.typing import CoreTextFont, FontConfigPattern from kitty.utils import log_error from . import family_name_to_key from .common import get_font_files if is_macos: from .core_text import font_for_family as font_for_family_macos else: from .fontconfig import font_for_family as font_for_family_fontconfig if TYPE_CHECKING: from kitty.fast_data_types import CTFace, DecorationTypes, Face else: DecorationTypes = str FontObject = Union[CoreTextFont, FontConfigPattern] current_faces: list[tuple[FontObject, bool, bool]] = [] builtin_nerd_font_descriptor: FontObject | None = None def font_for_family(family: str) -> tuple[FontObject, bool, bool]: if is_macos: return font_for_family_macos(family) return font_for_family_fontconfig(family) def merge_ranges( a: tuple[tuple[int, int], _T], b: tuple[tuple[int, int], _T], priority_map: dict[tuple[int, int], int] ) -> Generator[tuple[tuple[int, int], _T], None, None]: a_start, a_end = a[0] b_start, b_end = b[0] a_val, b_val = a[1], b[1] a_prio, b_prio = priority_map[a[0]], priority_map[b[0]] if b_start > a_end: if b_start == a_end + 1 and a_val == b_val: # ranges can be coalesced r = ((a_start, b_end), a_val) priority_map[r[0]] = max(a_prio, b_prio) yield r return # disjoint ranges yield a yield b return if a_val == b_val: # mergeable ranges r = ((a_start, max(a_end, b_end)), a_val) priority_map[r[0]] = max(a_prio, b_prio) yield r return before_range = mid_range = after_range = None before_range_prio = mid_range_prio = after_range_prio = 0 if b_start > a_start: before_range = ((a_start, b_start - 1), a_val) before_range_prio = a_prio mid_end = min(a_end, b_end) if mid_end >= b_start: # overlap range mid_range = ((b_start, mid_end), a_val if priority_map[a[0]] >= priority_map[b[0]] else b_val) mid_range_prio = max(a_prio, b_prio) # after range if mid_end is a_end: if b_end > a_end: after_range = ((a_end + 1, b_end), b_val) after_range_prio = b_prio else: if a_end > b_end: after_range = ((b_end + 1, a_end), a_val) after_range_prio = a_prio # check if the before, mid and after ranges can be coalesced ranges: list[tuple[tuple[int, int], _T]] = [] priorities: list[int] = [] for rq, prio in ((before_range, before_range_prio), (mid_range, mid_range_prio), (after_range, after_range_prio)): if rq is None: continue r = rq if ranges: x = ranges[-1] if x[0][1] + 1 == r[0][0] and x[1] == r[1]: ranges[-1] = ((x[0][0], r[0][1]), x[1]) priorities[-1] = max(priorities[-1], prio) else: ranges.append(r) priorities.append(prio) else: ranges.append(r) priorities.append(prio) for r, p in zip(ranges, priorities): priority_map[r[0]] = p yield from ranges def coalesce_symbol_maps(maps: dict[tuple[int, int], _T]) -> dict[tuple[int, int], _T]: if not maps: return maps priority_map = {r: i for i, r in enumerate(maps.keys())} ranges = tuple((r, maps[r]) for r in sorted(maps)) ans = [ranges[0]] for i in range(1, len(ranges)): r = ranges[i] new_ranges = merge_ranges(ans[-1], r, priority_map) if ans: del ans[-1] if not ans: ans = list(new_ranges) else: for r in new_ranges: prev = ans[-1] if prev[0][1] + 1 == r[0][0] and prev[1] == r[1]: ans[-1] = (prev[0][0], r[0][1]), prev[1] else: ans.append(r) return dict(ans) def create_symbol_map(opts: Options) -> tuple[tuple[int, int, int], ...]: val = coalesce_symbol_maps(opts.symbol_map) family_map: dict[str, int] = {} count = 0 for family in val.values(): if family not in family_map: font, bold, italic = font_for_family(family) fkey = family_name_to_key(family) if fkey in ('symbolsnfm', 'symbols nerd font mono') and font['postscript_name'] != 'SymbolsNFM' and builtin_nerd_font_descriptor: font = builtin_nerd_font_descriptor bold = italic = False family_map[family] = count count += 1 current_faces.append((font, bold, italic)) sm = tuple((a, b, family_map[f]) for (a, b), f in val.items()) return sm def create_narrow_symbols(opts: Options) -> tuple[tuple[int, int, int], ...]: return tuple((a, b, v) for (a, b), v in coalesce_symbol_maps(opts.narrow_symbols).items()) descriptor_overrides: dict[int, tuple[str, bool, bool]] = {} def descriptor_for_idx(idx: int) -> tuple[FontObject | str, bool, bool]: ans = descriptor_overrides.get(idx) if ans is None: return current_faces[idx] return ans def dump_font_debug() -> None: cf = current_fonts() log_error('Text fonts:') for key, text in {'medium': 'Normal', 'bold': 'Bold', 'italic': 'Italic', 'bi': 'Bold-Italic'}.items(): log_error(f' {text}:', cf[key].identify_for_debug()) # type: ignore ss = cf['symbol'] if ss: log_error('Symbol map fonts:') for s in ss: log_error(' ' + s.identify_for_debug()) def set_font_family(opts: Options | None = None, override_font_size: float | None = None, add_builtin_nerd_font: bool = False) -> None: global current_faces, builtin_nerd_font_descriptor opts = opts or defaults sz = override_font_size or opts.font_size font_map = get_font_files(opts) current_faces = [(font_map['medium'], False, False)] ftypes: list[Literal['bold', 'italic', 'bi']] = ['bold', 'italic', 'bi'] indices = {k: 0 for k in ftypes} for k in ftypes: if k in font_map: indices[k] = len(current_faces) current_faces.append((font_map[k], 'b' in k, 'i' in k)) before = len(current_faces) if add_builtin_nerd_font: builtin_nerd_font_path = os.path.join(fonts_dir, 'SymbolsNerdFontMono-Regular.ttf') if os.path.exists(builtin_nerd_font_path): builtin_nerd_font_descriptor = set_builtin_nerd_font(builtin_nerd_font_path) else: log_error(f'No builtin NERD font found in {fonts_dir}') sm = create_symbol_map(opts) ns = create_narrow_symbols(opts) num_symbol_fonts = len(current_faces) - before set_font_data( descriptor_for_idx, indices['bold'], indices['italic'], indices['bi'], num_symbol_fonts, sm, sz, ns ) if TYPE_CHECKING: CBufType = ctypes.Array[ctypes.c_ubyte] else: CBufType = None UnderlineCallback = Callable[[CBufType, int, int, int, int], None] class setup_for_testing: xnum = 100000 ynum = 100 baseline = 0 def __init__(self, family: str = 'monospace', size: float = 11.0, dpi: float = 96.0, main_face_path: str = ''): self.family, self.size, self.dpi = family, size, dpi self.main_face_path = main_face_path def __enter__(self) -> tuple[dict[tuple[int, int, int], bytes], int, int]: global descriptor_overrides opts = defaults._replace(font_family=parse_font_spec(self.family), font_size=self.size) set_options(opts) sprites = {} def send_to_gpu(x: int, y: int, z: int, data: bytes) -> None: sprites[(x, y, z)] = data sprite_map_set_limits(self.xnum, self.ynum) set_send_sprite_to_gpu(send_to_gpu) self.orig_desc_overrides = descriptor_overrides descriptor_overrides = {} if self.main_face_path: descriptor_overrides[0] = self.main_face_path, False, False try: set_font_family(opts) cell_width, cell_height, self.baseline = create_test_font_group(self.size, self.dpi, self.dpi) return sprites, cell_width, cell_height except Exception: set_send_sprite_to_gpu(None) raise def __exit__(self, *args: Any) -> None: global descriptor_overrides descriptor_overrides = self.orig_desc_overrides set_send_sprite_to_gpu(None) def render_string(text: str, family: str = 'monospace', size: float = 11.0, dpi: float = 96.0) -> tuple[int, int, list[bytes]]: with setup_for_testing(family, size, dpi) as (sprites, cell_width, cell_height): s = Screen(None, 1, len(text)*2) line = s.line(0) s.draw(text) test_render_line(line) cells = [] found_content = False for i in reversed(range(s.columns)): sp = line.sprite_at(i) sp &= 0x7fffffff if not sp and not found_content: continue found_content = True cells.append(sprites[sprite_idx_to_pos(sp, setup_for_testing.xnum, setup_for_testing.ynum)]) return cell_width, cell_height, list(reversed(cells)) def shape_string( text: str = "abcd", family: str = 'monospace', size: float = 11.0, dpi: float = 96.0, path: str | None = None ) -> list[tuple[int, int, int, tuple[int, ...]]]: with setup_for_testing(family, size, dpi) as (sprites, cell_width, cell_height): s = Screen(None, 1, len(text)*2) line = s.line(0) s.draw(text) return test_shape(line, path) def show(rgba_data: bytes | memoryview, width: int, height: int, fmt: int = 32) -> None: from base64 import standard_b64encode from kittens.tui.images import GraphicsCommand data = memoryview(standard_b64encode(rgba_data)) cmd = GraphicsCommand() cmd.a = 'T' cmd.f = fmt cmd.s = width cmd.v = height sys.stdout.flush() while data: chunk, data = data[:4096], data[4096:] cmd.m = 1 if data else 0 sys.stdout.buffer.write(cmd.serialize(chunk)) cmd.clear() sys.stdout.buffer.flush() def display_bitmap(rgb_data: bytes, width: int, height: int) -> None: assert len(rgb_data) == 4 * width * height show(rgb_data, width, height) def test_render_string( text: str = 'Hello, world!', family: str = 'monospace', size: float = 64.0, dpi: float = 96.0 ) -> None: cell_width, cell_height, cells = render_string(text, family, size, dpi) rgb_data = concat_cells(cell_width, cell_height, True, tuple(cells)) cf = current_fonts() fonts = [cf['medium'].postscript_name()] fonts.extend(f.postscript_name() for f in cf['fallback']) msg = 'Rendered string {} below, with fonts: {}\n'.format(text, ', '.join(fonts)) try: print(msg) except UnicodeEncodeError: sys.stdout.buffer.write(msg.encode('utf-8') + b'\n') display_bitmap(rgb_data, cell_width * len(cells), cell_height) print('\n') def test_fallback_font(qtext: str | None = None, bold: bool = False, italic: bool = False) -> None: with setup_for_testing(): if qtext: trials = [qtext] else: trials = ['你', 'He\u0347\u0305', '\U0001F929'] for text in trials: f = get_fallback_font(text, bold, italic) try: print(text, f) except UnicodeEncodeError: sys.stdout.buffer.write(f'{text} {f}\n'.encode()) def showcase() -> None: f = 'monospace' if is_macos else 'Liberation Mono' test_render_string('He\u0347\u0305llo\u0337, w\u0302or\u0306l\u0354d!', family=f) test_render_string('你好,世界', family=f) test_render_string('│😁│🙏│😺│', family=f) test_render_string('A=>>B!=C', family='Fira Code') def create_face(path: str) -> 'Union[CTFace, Face]': if is_macos: from kitty.fast_data_types import CTFace return CTFace(path=path) from kitty.fast_data_types import Face return Face(path=path) def test_render_codepoint(chars: str = '😺', path: str = '/t/Noto-COLRv1.ttf', font_size: float = 160.0) -> None: f = create_face(path=path) f.set_size(font_size, 96, 96) for char in chars: bitmap, w, h = f.render_codepoint(ord(char)) print('Rendered:', char) display_bitmap(bitmap, w, h) print('\n') def test_render_decoration(which: DecorationTypes, cell_width: int, cell_height: int, underline_position: int, underline_thickness: int) -> None: buf = render_decoration(which, cell_width, cell_height, underline_position, underline_thickness) cells = buf, buf, buf, buf, buf rgb_data = concat_cells(cell_width, cell_height, False, cells) display_bitmap(rgb_data, cell_width * len(cells), cell_height) kitty-0.41.1/kitty/freetype.c0000664000175000017510000016357414773370543015462 0ustar nileshnilesh/* * freetype.c * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "fonts.h" #include "colors.h" #include "cleanup.h" #include "state.h" #include #include #include #include #include #if FREETYPE_MAJOR == 2 && FREETYPE_MINOR < 7 #define FT_Bitmap_Init FT_Bitmap_New #endif #include FT_BITMAP_H #include FT_TRUETYPE_TABLES_H #include FT_MULTIPLE_MASTERS_H #include FT_SFNT_NAMES_H #include FT_TYPES_H typedef union FaceIndex { struct { FT_Long ttc_index : 16; FT_Long variation_index : 16; }; FT_Long val; } FaceIndex; typedef struct FaceMetrics { float size_in_pts; unsigned int units_per_EM; // The following are in font units use font_units_to_pixels_x/y to convert to pixels int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness; } FaceMetrics; typedef struct { PyObject_HEAD FT_Face face, face_for_cairo; FaceMetrics metrics; int hinting, hintstyle; bool is_scalable, has_color, is_variable, has_svg; FT_F26Dot6 char_width, char_height; double xdpi, ydpi; PyObject *path; long index; hb_font_t *harfbuzz_font; struct { cairo_font_face_t *font; void *buf; cairo_surface_t *surface; cairo_t *cr; size_t width, height, stride; unsigned size_in_px; } cairo; hb_codepoint_t space_glyph_id; void *extra_data; free_extra_data_func free_extra_data; PyObject *name_lookup_table; FontFeatures font_features; unsigned short dark_palette_index, light_palette_index, palettes_scanned; } Face; PyTypeObject Face_Type; static PyObject* FreeType_Exception = NULL; void set_freetype_error(const char* prefix, int err_code) { int i = 0; #undef FTERRORS_H_ #undef __FTERRORS_H__ #define FT_ERRORDEF( e, v, s ) { e, s }, #define FT_ERROR_START_LIST { #define FT_ERROR_END_LIST { 0, NULL } }; static const struct { int err_code; const char* err_msg; } ft_errors[] = #ifdef FT_ERRORS_H #include FT_ERRORS_H #else FT_ERROR_START_LIST FT_ERROR_END_LIST #endif while(ft_errors[i].err_msg != NULL) { if (ft_errors[i].err_code == err_code) { PyErr_Format(FreeType_Exception, "%s %s", prefix, ft_errors[i].err_msg); return; } i++; } PyErr_Format(FreeType_Exception, "%s (error code: %d)", prefix, err_code); } static FT_Library library; FT_Library freetype_library(void) { return library; } static int font_units_to_pixels_y(Face *self, int x) { return (int)ceil((double)FT_MulFix(x, self->face->size->metrics.y_scale) / 64.0); } static int font_units_to_pixels_x(Face *self, int x) { return (int)ceil((double)FT_MulFix(x, self->face->size->metrics.x_scale) / 64.0); } static int get_load_flags(int hinting, int hintstyle, int base) { int flags = base; if (hinting) { if (hintstyle >= 3) flags |= FT_LOAD_TARGET_NORMAL; else if (0 < hintstyle) flags |= FT_LOAD_TARGET_LIGHT; } else flags |= FT_LOAD_NO_HINTING; return flags; } static bool load_glyph(Face *self, int glyph_index, int load_type) { int flags = get_load_flags(self->hinting, self->hintstyle, load_type); int error = FT_Load_Glyph(self->face, glyph_index, flags); if (error) { char buf[256]; snprintf(buf, sizeof(buf) - 1, "Failed to load glyph_index=%d load_type=%d, with error:", glyph_index, load_type); set_freetype_error(buf, error); return false; } return true; } static unsigned int get_height_for_char(Face *self, char ch) { unsigned int ans = 0; int glyph_index = FT_Get_Char_Index(self->face, ch); if (load_glyph(self, glyph_index, FT_LOAD_DEFAULT)) { unsigned int baseline = font_units_to_pixels_y(self, self->metrics.ascender); FT_GlyphSlotRec *glyph = self->face->glyph; FT_Bitmap *bm = &glyph->bitmap; if (glyph->bitmap_top <= 0 || (glyph->bitmap_top > 0 && (unsigned int)glyph->bitmap_top < baseline)) { ans = baseline - glyph->bitmap_top + bm->rows; } } return ans; } static unsigned int calc_cell_height(Face *self, bool for_metrics) { unsigned int ans = font_units_to_pixels_y(self, self->metrics.height); if (for_metrics) { unsigned int underscore_height = get_height_for_char(self, '_'); if (underscore_height > ans) { if (global_state.debug_font_fallback) printf( "Increasing cell height by %u pixels to work around buggy font that renders underscore outside the bounding box\n", underscore_height - ans); return underscore_height; } } return ans; } static bool set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, double xdpi_, double ydpi_, unsigned int desired_height, unsigned int cell_height) { FT_UInt xdpi = (FT_UInt)xdpi_, ydpi = (FT_UInt)ydpi_; int error = FT_Set_Char_Size(self->face, 0, char_height, xdpi, ydpi); if (!error) { self->char_width = char_width; self->char_height = char_height; self->xdpi = xdpi_; self->ydpi = ydpi_; } else { if (!self->is_scalable && self->face->num_fixed_sizes > 0) { int32_t min_diff = INT32_MAX; if (desired_height == 0) desired_height = cell_height; if (desired_height == 0) { desired_height = (unsigned int)ceil(((double)char_height / 64.) * (double)ydpi / 72.); desired_height += (unsigned int)ceil(0.2 * desired_height); } FT_Int strike_index = -1; for (FT_Int i = 0; i < self->face->num_fixed_sizes; i++) { int h = self->face->available_sizes[i].height; int32_t diff = h < (int32_t)desired_height ? (int32_t)desired_height - h : h - (int32_t)desired_height; if (diff < min_diff) { min_diff = diff; strike_index = i; } } if (strike_index > -1) { error = FT_Select_Size(self->face, strike_index); if (error) { set_freetype_error("Failed to set char size for non-scalable font, with error:", error); return false; } self->xdpi = xdpi_; self->ydpi = ydpi_; return true; } } set_freetype_error("Failed to set char size, with error:", error); return false; } if (self->harfbuzz_font != NULL) hb_ft_font_changed(self->harfbuzz_font); return !error; } bool set_size_for_face(PyObject *s, unsigned int desired_height, bool force, FONTS_DATA_HANDLE fg) { Face *self = (Face*)s; FT_F26Dot6 w = (FT_F26Dot6)(ceil(fg->font_sz_in_pts * 64.0)); FT_UInt xdpi = (FT_UInt)fg->logical_dpi_x, ydpi = (FT_UInt)fg->logical_dpi_y; if (!force && (self->char_width == w && self->char_height == w && self->xdpi == xdpi && self->ydpi == ydpi)) return true; self->metrics.size_in_pts = (float)fg->font_sz_in_pts; return set_font_size(self, w, w, fg->logical_dpi_x, fg->logical_dpi_y, desired_height, fg->fcm.cell_height); } static PyObject* set_size(Face *self, PyObject *args) { double font_sz_in_pts, dpi_x, dpi_y; if (!PyArg_ParseTuple(args, "ddd", &font_sz_in_pts, &dpi_x, &dpi_y)) return NULL; FT_F26Dot6 w = (FT_F26Dot6)(ceil(font_sz_in_pts * 64.0)); if (self->char_width == w && self->char_height == w && self->xdpi == dpi_x && self->ydpi == dpi_y) { Py_RETURN_NONE; } self->metrics.size_in_pts = (float)font_sz_in_pts; if (!set_font_size(self, w, w, dpi_x, dpi_y, 0, 0)) return NULL; Py_RETURN_NONE; } static void copy_face_metrics(Face *self) { #define CPY(n) self->metrics.n = self->face->n; CPY(units_per_EM); CPY(ascender); CPY(descender); CPY(height); CPY(max_advance_width); CPY(max_advance_height); CPY(underline_position); CPY(underline_thickness); #undef CPY } bool face_apply_scaling(PyObject *f, const FONTS_DATA_HANDLE fg) { Face *self = (Face*)f; if (set_size_for_face(f, 0, false, fg)) { if (self->harfbuzz_font) hb_ft_font_changed(self->harfbuzz_font); copy_face_metrics(self); return true; } return false; } static bool init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, long index, FONTS_DATA_HANDLE fg) { copy_face_metrics(self); self->index = index; self->is_scalable = FT_IS_SCALABLE(self->face); self->has_color = FT_HAS_COLOR(self->face); self->is_variable = FT_HAS_MULTIPLE_MASTERS(self->face); #ifdef FT_HAS_SVG self->has_svg = FT_HAS_SVG(self->face); #else self->has_svg = false; #endif self->hinting = hinting; self->hintstyle = hintstyle; if (fg && !set_size_for_face((PyObject*)self, 0, false, fg)) return false; self->harfbuzz_font = hb_ft_font_create(self->face, NULL); if (self->harfbuzz_font == NULL) { PyErr_NoMemory(); return false; } hb_ft_font_set_load_flags(self->harfbuzz_font, get_load_flags(self->hinting, self->hintstyle, FT_LOAD_DEFAULT)); FT_Reference_Face(self->face); TT_OS2 *os2 = (TT_OS2*)FT_Get_Sfnt_Table(self->face, FT_SFNT_OS2); if (os2 != NULL) { self->metrics.strikethrough_position = os2->yStrikeoutPosition; self->metrics.strikethrough_thickness = os2->yStrikeoutSize; } self->path = path; Py_INCREF(self->path); self->space_glyph_id = glyph_id_for_codepoint((PyObject*)self, ' '); return true; } static void* set_load_error(const char *path, int error) { char buf[2048]; snprintf(buf, sizeof(buf), "Failed to load face from path: %s with error:", path); set_freetype_error(buf, error); return NULL; } bool face_equals_descriptor(PyObject *face_, PyObject *descriptor) { Face *face = (Face*)face_; PyObject *t = PyDict_GetItemString(descriptor, "path"); if (!t) return false; if (PyObject_RichCompareBool(face->path, t, Py_EQ) != 1) return false; t = PyDict_GetItemString(descriptor, "index"); if (t && PyLong_AsLong(t) != face->face->face_index) return false; return true; } static char* get_variation_as_string(Face *self); PyObject* face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) { #define D(key, conv, missing_ok) { \ PyObject *t = PyDict_GetItemString(descriptor, #key); \ if (t == NULL) { \ if (!missing_ok) { PyErr_SetString(PyExc_KeyError, "font descriptor is missing the key: " #key); return NULL; } \ } else key = conv(t); \ } const char *path = NULL; long index = 0; bool hinting = false; long hint_style = 0; D(path, PyUnicode_AsUTF8, false); D(index, PyLong_AsLong, true); D(hinting, PyObject_IsTrue, true); D(hint_style, PyLong_AsLong, true); #undef D RAII_PyObject(retval, Face_Type.tp_alloc(&Face_Type, 0)); Face *self = (Face *)retval; if (retval != NULL) { int error; if ((error = FT_New_Face(library, path, index, &(self->face)))) { self->face = NULL; return set_load_error(path, error); } if (!init_ft_face(self, PyDict_GetItemString(descriptor, "path"), hinting, hint_style, index, fg)) { Py_CLEAR(retval); return NULL; } PyObject *ns = PyDict_GetItemString(descriptor, "named_style"); if (ns) { unsigned long index = PyLong_AsUnsignedLong(ns); if (PyErr_Occurred()) return NULL; if ((error = FT_Set_Named_Instance(self->face, index + 1))) return set_load_error(path, error); } PyObject *axes = PyDict_GetItemString(descriptor, "axes"); Py_ssize_t sz; if (axes && (sz = PyTuple_GET_SIZE(axes))) { RAII_ALLOC(FT_Fixed, coords, malloc(sizeof(FT_Fixed) * sz)); for (Py_ssize_t i = 0; i < sz; i++) { PyObject *t = PyTuple_GET_ITEM(axes, i); double val = PyFloat_AsDouble(t); if (PyErr_Occurred()) return NULL; coords[i] = (FT_Fixed)(val * 65536.0); } if ((error = FT_Set_Var_Design_Coordinates(self->face, sz, coords))) return set_load_error(path, error); } if (!create_features_for_face(postscript_name_for_face((PyObject*)self), PyDict_GetItemString(descriptor, "features"), &self->font_features)) return NULL; } Py_XINCREF(retval); return retval; } FontFeatures* features_for_face(PyObject *s) { return &((Face*)s)->font_features; } static PyObject* new(PyTypeObject *type UNUSED, PyObject *args, PyObject *kw) { const char *path = NULL; long index = 0; PyObject *descriptor = NULL; static char *kwds[] = {"descriptor", "path", "index", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kw, "|Osi", kwds, &descriptor, &path, &index)) return NULL; if (descriptor) { return face_from_descriptor(descriptor, NULL); } if (path) return face_from_path(path, index, NULL); PyErr_SetString(PyExc_TypeError, "Must specify either path or descriptor"); return NULL; } FT_Face native_face_from_path(const char *path, int index) { int error; FT_Face ans; error = FT_New_Face(library, path, index, &ans); if (error) return set_load_error(path, error); return ans; } PyObject* face_from_path(const char *path, int index, FONTS_DATA_HANDLE fg) { Face *ans = (Face*)Face_Type.tp_alloc(&Face_Type, 0); if (ans == NULL) return NULL; int error; error = FT_New_Face(library, path, index, &ans->face); if (error) { ans->face = NULL; return set_load_error(path, error); } RAII_PyObject(pypath, PyUnicode_FromString(path)); if (!pypath) return NULL; if (!init_ft_face(ans, pypath, true, 3, index, fg)) { Py_CLEAR(ans); return NULL; } return (PyObject*)ans; } static inline void cleanup_ftmm(FT_MM_Var **p) { if (*p) FT_Done_MM_Var(library, *p); *p = NULL; } static const char* tag_to_string(uint32_t tag, uint8_t bytes[5]) { bytes[0] = (tag >> 24) & 0xff; bytes[1] = (tag >> 16) & 0xff; bytes[2] = (tag >> 8) & 0xff; bytes[3] = (tag) & 0xff; bytes[4] = 0; return (const char*)bytes; } #define RAII_FTMMVar(name) __attribute__((cleanup(cleanup_ftmm))) FT_MM_Var *name = NULL static void free_cairo(Face *self); static void dealloc(Face* self) { if (self->harfbuzz_font) hb_font_destroy(self->harfbuzz_font); FT_Done_Face(self->face); free_cairo(self); if (self->extra_data && self->free_extra_data) self->free_extra_data(self->extra_data); free(self->font_features.features); Py_CLEAR(self->path); Py_CLEAR(self->name_lookup_table); Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject * repr(Face *self) { const char *ps_name = FT_Get_Postscript_Name(self->face); #define B(x) ((x) ? Py_True : Py_False) FaceIndex instance; instance.val = self->face->face_index; return PyUnicode_FromFormat( "Face(family=%s style=%s ps_name=%s path=%S ttc_index=%d variant=%S named_instance=%S scalable=%S color=%S)", self->face->family_name ? self->face->family_name : "", self->face->style_name ? self->face->style_name : "", ps_name ? ps_name: "", self->path, instance.ttc_index, B(FT_IS_VARIATION(self->face)), B(FT_IS_NAMED_INSTANCE(self->face)), B(self->is_scalable), B(self->has_color) ); #undef B } const char* postscript_name_for_face(const PyObject *face_) { const Face *self = (const Face*)face_; const char *ps_name = FT_Get_Postscript_Name(self->face); return ps_name ? ps_name : ""; } static unsigned int calc_cell_width(Face *self) { unsigned int ans = 0; for (char_type i = 32; i < 128; i++) { int glyph_index = FT_Get_Char_Index(self->face, i); if (load_glyph(self, glyph_index, FT_LOAD_DEFAULT)) { ans = MAX(ans, (unsigned int)ceilf((float)self->face->glyph->metrics.horiAdvance / 64.f)); } } if (!ans) ans = MAX(1u, (unsigned int)ceilf(self->face->size->metrics.max_advance / 64.f)); return ans; } FontCellMetrics cell_metrics(PyObject *s) { Face *self = (Face*)s; FontCellMetrics ans = {0}; ans.cell_width = calc_cell_width(self); ans.cell_height = calc_cell_height(self, true); ans.baseline = font_units_to_pixels_y(self, self->metrics.ascender); ans.underline_position = MIN(ans.cell_height - 1, (unsigned int)font_units_to_pixels_y(self, MAX(0, self->metrics.ascender - self->metrics.underline_position))); ans.underline_thickness = MAX(1, font_units_to_pixels_y(self, self->metrics.underline_thickness)); if (self->metrics.strikethrough_position != 0) { ans.strikethrough_position = MIN(ans.cell_height - 1, (unsigned int)font_units_to_pixels_y(self, MAX(0, self->metrics.ascender - self->metrics.strikethrough_position))); } else { ans.strikethrough_position = (unsigned int)floor(ans.baseline * 0.65); } if (self->metrics.strikethrough_thickness > 0) { ans.strikethrough_thickness = MAX(1, font_units_to_pixels_y(self, self->metrics.strikethrough_thickness)); } else { ans.strikethrough_thickness = ans.underline_thickness; } return ans; } unsigned int glyph_id_for_codepoint(const PyObject *s, char_type cp) { return FT_Get_Char_Index(((Face*)s)->face, cp); } typedef enum { NOT_COLORED, CBDT_COLORED, COLR_V0_COLORED, COLR_V1_COLORED } GlyphColorType; static bool is_colrv0_glyph (Face *self, int glyph_id) { FT_LayerIterator iterator = {0}; FT_UInt layer_glyph_index = 0, layer_color_index = 0; return FT_Get_Color_Glyph_Layer(self->face, glyph_id, &layer_glyph_index, &layer_color_index, &iterator); } static bool is_colrv1_glyph(Face *self, int glyph_id) { FT_OpaquePaint paint = {0}; return FT_Get_Color_Glyph_Paint(self->face, glyph_id, FT_COLOR_INCLUDE_ROOT_TRANSFORM, &paint); } static bool is_colored_cbdt_glyph(Face *self, int glyph_id) { FT_Error err = FT_Load_Glyph(self->face, glyph_id, get_load_flags(self->hinting, self->hintstyle, FT_LOAD_DEFAULT | FT_LOAD_COLOR)); if (err) return false; return self->face->glyph->format == FT_GLYPH_FORMAT_BITMAP && self->face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA; } static GlyphColorType glyph_color_type(Face *self, int glyph_id) { if (is_colrv1_glyph(self, glyph_id)) return COLR_V1_COLORED; if (is_colrv0_glyph(self, glyph_id)) return COLR_V0_COLORED; if (is_colored_cbdt_glyph(self, glyph_id)) return CBDT_COLORED; return NOT_COLORED; } bool is_glyph_empty(PyObject *s, glyph_index g) { Face *self = (Face*)s; if (!load_glyph(self, g, FT_LOAD_DEFAULT)) { PyErr_Print(); return false; } #define M self->face->glyph->metrics /* printf("glyph: %u horiBearingX: %ld horiBearingY: %ld width: %ld height: %ld\n", g, M.horiBearingX, M.horiBearingY, M.width, M.height); */ return M.width == 0; #undef M } int get_glyph_width(PyObject *s, glyph_index g) { Face *self = (Face*)s; if (!load_glyph(self, g, FT_LOAD_DEFAULT)) { PyErr_Print(); return 0; } #define M self->face->glyph->metrics #define B self->face->glyph->bitmap /* printf("glyph: %u bitmap.width: %d bitmap.rows: %d horiAdvance: %ld horiBearingX: %ld horiBearingY: %ld vertBearingX: %ld vertBearingY: %ld vertAdvance: %ld width: %ld height: %ld\n", */ /* g, B.width, B.rows, M.horiAdvance, M.horiBearingX, M.horiBearingY, M.vertBearingX, M.vertBearingY, M.vertAdvance, M.width, M.height); */ return B.width ? (int)B.width : (int)(M.width / 64); #undef M #undef B } hb_font_t* harfbuzz_font_for_face(PyObject *self) { return ((Face*)self)->harfbuzz_font; } typedef struct { unsigned char* buf; size_t start_x, width, stride; size_t rows; FT_Pixel_Mode pixel_mode; bool needs_free; unsigned int factor, right_edge; int bitmap_left, bitmap_top; } ProcessedBitmap; static void free_processed_bitmap(ProcessedBitmap *bm) { if (bm->needs_free) { bm->needs_free = false; free(bm->buf); bm->buf = NULL; } } static void trim_borders(ProcessedBitmap *ans, size_t extra) { bool column_has_text = false; // Trim empty columns from the right side of the bitmap for (ssize_t x = ans->width - 1; !column_has_text && x > -1 && extra > 0; x--) { for (size_t y = 0; y < ans->rows && !column_has_text; y++) { if (ans->buf[x + y * ans->stride] > 200) column_has_text = true; } if (!column_has_text) { ans->width--; extra--; } } // Remove any remaining extra columns from the left edge of the bitmap ans->start_x = extra; ans->width -= extra; } static void populate_processed_bitmap(FT_GlyphSlotRec *slot, FT_Bitmap *bitmap, ProcessedBitmap *ans, bool copy_buf) { ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch; ans->rows = bitmap->rows; if (copy_buf) { ans->buf = malloc(ans->rows * ans->stride); if (!ans->buf) fatal("Out of memory"); ans->needs_free = true; memcpy(ans->buf, bitmap->buffer, ans->rows * ans->stride); } else ans->buf = bitmap->buffer; ans->start_x = 0; ans->width = bitmap->width; ans->pixel_mode = bitmap->pixel_mode; ans->bitmap_top = slot->bitmap_top; ans->bitmap_left = slot->bitmap_left; } bool freetype_convert_mono_bitmap(FT_Bitmap *src, FT_Bitmap *dest) { FT_Bitmap_Init(dest); // This also sets pixel_mode to FT_PIXEL_MODE_GRAY so we don't have to int error = FT_Bitmap_Convert(library, src, dest, 1); if (error) { set_freetype_error("Failed to convert bitmap, with error:", error); return false; } // Normalize gray levels to the range [0..255] dest->num_grays = 256; unsigned int stride = dest->pitch < 0 ? -dest->pitch : dest->pitch; for (unsigned i = 0; i < (unsigned)dest->rows; ++i) { // We only have 2 levels for (unsigned j = 0; j < (unsigned)dest->width; ++j) dest->buffer[i * stride + j] *= 255; } return true; } static bool render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, bool bold, bool italic, bool rescale, FONTS_DATA_HANDLE fg) { if (!load_glyph(self, glyph_id, FT_LOAD_RENDER)) return false; unsigned int max_width = cell_width * num_cells; // Embedded bitmap glyph? if (self->face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { FT_Bitmap bitmap; freetype_convert_mono_bitmap(&self->face->glyph->bitmap, &bitmap); populate_processed_bitmap(self->face->glyph, &bitmap, ans, true); FT_Bitmap_Done(library, &bitmap); } else { populate_processed_bitmap(self->face->glyph, &self->face->glyph->bitmap, ans, false); } if (ans->width > max_width) { size_t extra = ans->width - max_width; if (italic && extra < cell_width / 2) { trim_borders(ans, extra); } else if (extra == 2 && num_cells == 1) { // there exist fonts that have bitmaps just a couple of pixels // wider than their advances, rather than rescale, which looks // bad, we just crop the bitmap on the right. See https://github.com/kovidgoyal/kitty/issues/352 } else if (rescale && self->is_scalable && extra > 1) { FT_F26Dot6 char_width = self->char_width, char_height = self->char_height; float ar = (float)max_width / (float)ans->width; if (set_font_size(self, (FT_F26Dot6)((float)self->char_width * ar), (FT_F26Dot6)((float)self->char_height * ar), self->xdpi, self->ydpi, 0, fg->fcm.cell_height)) { free_processed_bitmap(ans); if (!render_bitmap(self, glyph_id, ans, cell_width, cell_height, num_cells, bold, italic, false, fg)) return false; if (!set_font_size(self, char_width, char_height, self->xdpi, self->ydpi, 0, fg->fcm.cell_height)) return false; } else return false; } } return true; } int downsample_32bit_image(uint8_t *src, unsigned src_width, unsigned src_height, unsigned src_stride, uint8_t *dest, unsigned dest_width, unsigned dest_height) { // Downsample using a simple area averaging algorithm. Could probably do // better with bi-cubic or lanczos, but at these small sizes I don't think // it matters float ratio = MAX((float)src_width / dest_width, (float)src_height / dest_height); int factor = (int)ceilf(ratio); uint8_t *d = dest; for (unsigned int i = 0, sr = 0; i < dest_height; i++, sr += factor) { for (unsigned int j = 0, sc = 0; j < dest_width; j++, sc += factor, d += 4) { // calculate area average unsigned int r=0, g=0, b=0, a=0, count=0; for (unsigned int y=sr; y < MIN(sr + factor, src_height); y++) { uint8_t *p = src + (y * src_stride) + sc * 4; for (unsigned int x=sc; x < MIN(sc + factor, src_width); x++, count++) { b += *(p++); g += *(p++); r += *(p++); a += *(p++); } } if (count) { d[0] = b / count; d[1] = g / count; d[2] = r / count; d[3] = a / count; } } } return factor; } static bool set_cairo_exception(const char *msg, cairo_status_t s) { PyErr_Format(FreeType_Exception, "cairo error: %s: %s", msg, cairo_status_to_string(s)); return false; } static void free_cairo_surface_data(Face *self) { if (self->cairo.cr) cairo_destroy(self->cairo.cr); if (self->cairo.surface) cairo_surface_destroy(self->cairo.surface); if (self->cairo.buf) free(self->cairo.buf); } static void free_cairo(Face *self) { free_cairo_surface_data(self); if (self->cairo.font) cairo_font_face_destroy(self->cairo.font); zero_at_ptr(&self->cairo); } static void cairo_done_ft_face(void* x) { if (x) FT_Done_Face(x); } static bool is_color_dark(color_type c) { ARGB32 bg = {.val=c}; return rgb_luminance(bg) / 255.0 < 0.5; } static unsigned short get_preferred_palette_index(Face *self) { if (!self->palettes_scanned) { self->palettes_scanned = 1; self->dark_palette_index = CAIRO_COLOR_PALETTE_DEFAULT; self->light_palette_index = CAIRO_COLOR_PALETTE_DEFAULT; FT_Palette_Data palette_data; FT_Error error = FT_Palette_Data_Get(self->face, &palette_data); if (error) log_error("Could not retrieve palette data for font from FreeType"); else if (palette_data.palette_flags) { for (FT_UShort i = 0; i < palette_data.num_palettes; i++) { FT_UShort flags = palette_data.palette_flags[i]; if (flags & FT_PALETTE_FOR_DARK_BACKGROUND) self->dark_palette_index = i; else if (flags & FT_PALETTE_FOR_DARK_BACKGROUND) self->light_palette_index = i; } } } return is_color_dark(OPT(background)) ? self->dark_palette_index : self->light_palette_index; } static char* get_variation_as_string(Face *self) { RAII_FTMMVar(mm); FT_Error err; if ((err = FT_Get_MM_Var(self->face, &mm))) return NULL; RAII_ALLOC(FT_Fixed, coords, malloc(mm->num_axis * sizeof(FT_Fixed))); if (!coords) return NULL; if ((err = FT_Get_Var_Design_Coordinates(self->face, mm->num_axis, coords))) return NULL; RAII_ALLOC(char, buf, NULL); uint8_t tag[5]; size_t pos = 0, sz = 0, n, bufsz; for (FT_UInt i = 0; i < mm->num_axis; i++) { double val = coords[i] / 65536.0; tag_to_string(mm->axis[i].tag, tag); if (sz - pos < 32) { sz += 4096; buf = realloc(buf, sz); if (!buf) return NULL; } bufsz = sz - pos - 1; if ((long)val == val) n = snprintf(buf + pos, bufsz, "%s=%ld,", tag, (long)val); else n = snprintf(buf + pos, bufsz, "%s=%.4f,", tag, val); if (n < bufsz) pos += n; } char *ans = NULL; if (buf) { buf[pos] = 0; if (pos && buf[pos-1] == ',') buf[pos-1] = 0; ans = buf; buf = NULL; } return ans; } static void set_variation_for_cairo(Face *self, cairo_font_options_t *opts) { RAII_ALLOC(char, buf, get_variation_as_string(self)); cairo_font_options_set_variations(opts, buf ? buf : ""); } static bool ensure_cairo_resources(Face *self, size_t width, size_t height) { if (!self->cairo.font) { const char *path = PyUnicode_AsUTF8(self->path); int error; if ((error = FT_New_Face(library, path, self->index, &self->face_for_cairo))) { self->face_for_cairo = NULL; return set_load_error(path, error); } self->cairo.font = cairo_ft_font_face_create_for_ft_face(self->face_for_cairo, 0); if (!self->cairo.font) { FT_Done_Face(self->face_for_cairo); self->face_for_cairo = NULL; PyErr_NoMemory(); return false; } // Sadly cairo does not use FT_Reference_Face https://lists.cairographics.org/archives/cairo/2015-March/026023.html // so we have to let cairo manage lifetime of the FT_Face static const cairo_user_data_key_t key; cairo_status_t status = cairo_font_face_set_user_data(self->cairo.font, &key, self->face_for_cairo, cairo_done_ft_face); if (status) { FT_Done_Face(self->face_for_cairo); self->face_for_cairo = NULL; PyErr_Format(PyExc_RuntimeError, "Failed to set cairo font destructor with error: %s", cairo_status_to_string(status)); return false; } self->cairo.size_in_px = 0; } size_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); if (stride * height > self->cairo.stride * self->cairo.height) { free_cairo_surface_data(self); self->cairo.width = 0; self->cairo.height = 0; self->cairo.stride = stride; int ret = posix_memalign(&self->cairo.buf, 64, self->cairo.stride * height); switch (ret) { case 0: break; case ENOMEM: PyErr_NoMemory(); return false; case EINVAL: PyErr_SetString(FreeType_Exception, "Invalid alignment for cairo surface buffer: 64"); return false; default: PyErr_SetString(FreeType_Exception, "Unknown error when calling posix_memalign to create ciro surface buffer"); return false; } self->cairo.surface = cairo_image_surface_create_for_data(self->cairo.buf, CAIRO_FORMAT_ARGB32, width, height, self->cairo.stride); if (!self->cairo.surface) { PyErr_NoMemory(); return false; } self->cairo.cr = cairo_create(self->cairo.surface); if (!self->cairo.cr) { PyErr_NoMemory(); return false; } cairo_set_font_face(self->cairo.cr, self->cairo.font); self->cairo.width = width; self->cairo.height = height; self->cairo.size_in_px = 0; cairo_font_options_t *opts = cairo_font_options_create(); cairo_status_t s; #define check(msg) \ if ((s = cairo_font_options_status(opts)) != CAIRO_STATUS_SUCCESS) { \ cairo_font_options_destroy(opts); \ return set_cairo_exception(msg, s); \ } check("Failed to create cairo font options"); cairo_hint_style_t h = CAIRO_HINT_STYLE_NONE; if (self->hinting) { switch(self->hintstyle) { case 0: break; case 1: h = CAIRO_HINT_STYLE_SLIGHT; break; case 2: h = CAIRO_HINT_STYLE_MEDIUM; break; case 3: h = CAIRO_HINT_STYLE_FULL; break; default: h = CAIRO_HINT_STYLE_MEDIUM; break; } } cairo_font_options_set_hint_style(opts, h); check("Failed to set cairo hintstyle"); cairo_font_options_set_color_palette(opts, get_preferred_palette_index(self)); check("Failed to set cairo palette index"); set_variation_for_cairo(self, opts); check("Failed to set cairo font variations"); cairo_set_font_options(self->cairo.cr, opts); cairo_font_options_destroy(opts); #undef check } return true; } static long pt_to_px(double pt, double dpi) { return ((long)round((pt * (dpi / 72.0)))); } static void set_cairo_font_size(Face *self, double size_in_pts) { unsigned sz_px = pt_to_px(size_in_pts, (self->xdpi + self->ydpi) / 2.0); if (self->cairo.size_in_px == sz_px) return; cairo_set_font_size(self->cairo.cr, sz_px); self->cairo.size_in_px = sz_px; } static cairo_scaled_font_t* fit_cairo_glyph(Face *self, cairo_glyph_t *g, cairo_text_extents_t *bb, cairo_scaled_font_t *sf, unsigned width, unsigned height) { while (self->cairo.size_in_px > 2 && (bb->width > width || bb->height > height)) { double ratio = MIN(width / bb->width, height / bb->height); unsigned sz = (unsigned)(ratio * self->cairo.size_in_px); if (sz >= self->cairo.size_in_px) sz = self->cairo.size_in_px - 2; cairo_set_font_size(self->cairo.cr, sz); sf = cairo_get_scaled_font(self->cairo.cr); cairo_scaled_font_glyph_extents(sf, g, 1, bb); self->cairo.size_in_px = sz; } return sf; } static bool render_glyph_with_cairo(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned width, unsigned height, ARGB32 fg, unsigned cell_baseline) { cairo_glyph_t g = {.index=glyph_id}; cairo_text_extents_t bb = {0}; if (!ensure_cairo_resources(self, MAX(width, 256u), MAX(height, 256u))) return false; set_cairo_font_size(self, self->metrics.size_in_pts); cairo_scaled_font_t *sf = cairo_get_scaled_font(self->cairo.cr); cairo_scaled_font_glyph_extents(sf, &g, 1, &bb); cairo_font_extents_t fm; if (!width || !height) { cairo_scaled_font_extents(sf, &fm); width = (unsigned)ceil(fm.max_x_advance); height = (unsigned)ceil(fm.height); return render_glyph_with_cairo(self, glyph_id, ans, width, height, fg, cell_baseline); } sf = fit_cairo_glyph(self, &g, &bb, sf, width, height); cairo_scaled_font_extents(sf, &fm); g.y = fm.ascent; memset(self->cairo.buf, 0, self->cairo.stride * self->cairo.height); cairo_set_source_rgba(self->cairo.cr, fg.r / 255., fg.g / 255., fg.b / 255., fg.a / 255.); cairo_show_glyphs(self->cairo.cr, &g, 1); cairo_surface_flush(self->cairo.surface); #if 0 printf("canvas: %ux%u glyph: %.1fx%.1f x_bearing: %.1f y_bearing: %.1f cell_baseline: %u font_baseline: %.1f\n", width, height, bb.width, bb.height, bb.x_bearing, bb.y_bearing, cell_baseline, fm.ascent); cairo_status_t s = cairo_surface_write_to_png(cairo_get_target(self->cairo.cr), "/tmp/glyph.png"); if (s) fprintf(stderr, "Failed to write to PNG with error: %s", cairo_status_to_string(s)); #endif ans->pixel_mode = FT_PIXEL_MODE_MAX; // place_bitmap_in_canvas() takes this to mean ARGB ans->buf = self->cairo.buf; ans->needs_free = false; ans->start_x = 0; ans->width = width; ans->stride = self->cairo.stride; ans->rows = height; ans->bitmap_left = (int)bb.x_bearing; ans->bitmap_top = -(int)bb.y_bearing; return true; } static bool render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline) { const unsigned int width_to_render_in = num_cells * cell_width; uint8_t v = is_color_dark(OPT(background)) ? 255 : 0; ARGB32 fg = {.red = v, .green = v, .blue = v, .alpha=255}; return render_glyph_with_cairo(self, glyph_id, ans, width_to_render_in, cell_height, fg, baseline); } static void copy_color_bitmap_bgra(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride) { for (size_t sr = src_rect->top, dr = dest_rect->top; sr < src_rect->bottom && dr < dest_rect->bottom; sr++, dr++) { pixel *d = dest + dest_stride * dr; uint8_t *s = src + src_stride * sr; for(size_t sc = src_rect->left, dc = dest_rect->left; sc < src_rect->right && dc < dest_rect->right; sc++, dc++) { uint8_t *bgra = s + 4 * sc; const float inv_alpha = 255.f / bgra[3]; #define C(idx, shift) ( (uint8_t)(bgra[idx] * inv_alpha) << shift) d[dc] = C(2, 24) | C(1, 16) | C(0, 8) | bgra[3]; #undef C } } } static void copy_color_bitmap_argb(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride) { for (size_t sr = src_rect->top, dr = dest_rect->top; sr < src_rect->bottom && dr < dest_rect->bottom; sr++, dr++) { pixel *d = dest + dest_stride * dr; pixel *s = (pixel*)(src + src_stride * sr); for (size_t sc = src_rect->left, dc = dest_rect->left; sc < src_rect->right && dc < dest_rect->right; sc++, dc++) { pixel argb = s[sc]; pixel alpha = (argb >> 24) & 0xff; const float inv_alpha = 255.f / alpha; #define C(src_shift, dest_shift) ( (pixel)(((argb >> src_shift) & 0xff) * inv_alpha) << dest_shift ) d[dc] = C(16, 24) | C(8, 16) | C(0, 8) | alpha; #undef C } } } static const bool debug_placement = false; static void place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, size_t baseline, unsigned int glyph_num, pixel fg_rgb, size_t x_in_canvas, size_t y_in_canvas) { // We want the glyph to be positioned inside the cell based on the bearingX // and bearingY values, making sure that it does not overflow the cell. Region src = { .left = bm->start_x, .bottom = bm->rows, .right = bm->width + bm->start_x }, dest = { .bottom = cell_height, .right = cell_width }; // Calculate column bounds int32_t xoff = (int32_t)(x_offset + bm->bitmap_left); if (debug_placement) printf(" bitmap_left: %d xoff: %d", bm->bitmap_left, xoff); if (xoff < 0) src.left += -xoff; else dest.left = xoff; // Move the dest start column back if the width overflows because of it, but only if we are not in a very long/infinite ligature if (glyph_num < 4 && dest.left > 0 && dest.left + bm->width > cell_width) { uint32_t extra = dest.left + bm->width - cell_width; dest.left = extra > dest.left ? 0 : dest.left - extra; } dest.left += x_in_canvas; // Calculate row bounds int32_t yoff = (ssize_t)(y_offset + bm->bitmap_top); if ((yoff > 0 && (size_t)yoff > baseline)) { dest.top = 0; } else { dest.top = baseline - yoff; } dest.top += y_in_canvas; // printf("x_offset: %d y_offset: %d src_start_row: %u src_start_column: %u dest_start_row: %u dest_start_column: %u bm_width: %lu bitmap_rows: %lu\n", xoff, yoff, src.top, src.left, dest.top, dest.left, bm->width, bm->rows); switch (bm->pixel_mode) { case FT_PIXEL_MODE_BGRA: copy_color_bitmap_bgra(bm->buf, cell, &src, &dest, bm->stride, cell_width); break; case FT_PIXEL_MODE_MAX: copy_color_bitmap_argb(bm->buf, cell, &src, &dest, bm->stride, cell_width); break; default: render_alpha_mask(bm->buf, cell, &src, &dest, bm->stride, cell_width, fg_rgb); break; } } static const ProcessedBitmap EMPTY_PBM = {.factor = 1}; bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, GlyphRenderInfo *ri) { Face *self = (Face*)f; bool is_emoji = *was_colored; *was_colored = is_emoji && self->has_color; float x = 0.f, y = 0.f, x_offset = 0.f; ProcessedBitmap bm; unsigned int canvas_width = cell_width * num_cells; GlyphColorType colored; for (unsigned int i = 0; i < num_glyphs; i++) { bm = EMPTY_PBM; // dont load the space glyph since loading it fails for some fonts/sizes and it is anyway to be rendered as a blank if (info[i].codepoint != self->space_glyph_id) { if (*was_colored && (colored = glyph_color_type(self, info[i].codepoint)) != NOT_COLORED) { if (!render_color_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, baseline)) { if (PyErr_Occurred()) PyErr_Print(); if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic, true, fg)) { free_processed_bitmap(&bm); return false; } *was_colored = false; } } else { if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic, true, fg)) { free_processed_bitmap(&bm); return false; } } } x_offset = x + (float)positions[i].x_offset / 64.0f; y = (float)positions[i].y_offset / 64.0f; if (debug_placement) printf("%d: x=%f canvas: %u", i, x_offset, canvas_width); if ((*was_colored || self->face->glyph->metrics.width > 0) && bm.width > 0) { place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, baseline, i, 0xffffff, 0, 0); } if (debug_placement) printf(" adv: %f\n", (float)positions[i].x_advance / 64.0f); // the roundf() below is needed for infinite length ligatures, for a test case // use: kitty --config None -o 'font_family Fira Code' -o 'font_size 4.5' sh -c // "echo '|---|--------|-------|-------------|-------------|HH'; read" // if this causes issues with non-infinite ligatures, we could choose this behavior // based on num_glyphs and/or num_cells x += roundf((float)positions[i].x_advance / 64.0f); free_processed_bitmap(&bm); } ri->canvas_width = canvas_width; ri->rendered_width = (unsigned)x; ri->x = 0; // x_advance is wrong for colored bitmaps that have been downsampled if (*was_colored) ri->rendered_width = num_glyphs == 1 ? bm.right_edge : canvas_width; return true; } static PyObject* postscript_name(PyObject *s, PyObject *a UNUSED) { Face *self = (Face*)s; const char *psname = FT_Get_Postscript_Name(self->face); if (psname) return Py_BuildValue("s", psname); Py_INCREF(self->path); return self->path; } static PyObject* identify_for_debug(PyObject *s, PyObject *a UNUSED) { Face *self = (Face*)s; FaceIndex instance; instance.val = self->face->face_index; RAII_PyObject(features, PyTuple_New(self->font_features.count)); if (!features) return NULL; char buf[128]; for (unsigned i = 0; i < self->font_features.count; i++) { hb_feature_to_string(self->font_features.features + i, buf, sizeof(buf)); PyObject *f = PyUnicode_FromString(buf); if (!f) return NULL; PyTuple_SET_ITEM(features, i, f); } return PyUnicode_FromFormat("%s: %V:%d\nFeatures: %S", FT_Get_Postscript_Name(self->face), self->path, "[path]", instance.val, features); } static PyObject* extra_data(PyObject *self, PyObject *a UNUSED) { return PyLong_FromVoidPtr(((Face*)self)->extra_data); } // NAME table {{{ static bool ensure_name_table(Face *self) { if (self->name_lookup_table) return true; RAII_PyObject(ans, PyDict_New()); if (!ans) return false; FT_SfntName temp; for (FT_UInt i = 0; i < FT_Get_Sfnt_Name_Count(self->face); i++) { FT_Error err = FT_Get_Sfnt_Name(self->face, i, &temp); if (err != 0) continue; if (!add_font_name_record(ans, temp.platform_id, temp.encoding_id, temp.language_id, temp.name_id, (const char*)temp.string, temp.string_len)) return NULL; } self->name_lookup_table = ans; Py_INCREF(ans); return true; } static PyObject* get_best_name(Face *self, PyObject *nameid) { if (!ensure_name_table(self)) return NULL; return get_best_name_from_name_table(self->name_lookup_table, nameid); } static PyObject* _get_best_name(Face *self, unsigned long nameid) { RAII_PyObject(key, PyLong_FromUnsignedLong(nameid)); return key ? get_best_name(self, key) : NULL; } // }}} static PyObject* convert_named_style_to_python(Face *face, const FT_Var_Named_Style *src, FT_Var_Axis *axes, unsigned num_of_axes) { RAII_PyObject(axis_values, PyDict_New()); if (!axis_values) return NULL; uint8_t tag_buf[5] = {0}; for (FT_UInt i = 0; i < num_of_axes; i++) { double val = src->coords[i] / 65536.0; RAII_PyObject(pval, PyFloat_FromDouble(val)); if (!pval) return NULL; if (PyDict_SetItemString(axis_values, tag_to_string(axes[i].tag, tag_buf), pval) != 0) return NULL; } RAII_PyObject(name, _get_best_name(face, src->strid)); if (!name) PyErr_Clear(); RAII_PyObject(psname, src->psid == 0xffff ? NULL : _get_best_name(face, src->psid)); if (!psname) PyErr_Clear(); return Py_BuildValue("{sO sO sO}", "axis_values", axis_values, "name", name ? name : PyUnicode_FromString(""), "psname", psname ? psname : PyUnicode_FromString("")); } static PyObject* convert_axis_to_python(Face *face, const FT_Var_Axis *src, FT_UInt flags) { PyObject *strid = _get_best_name(face, src->strid); if (!strid) { PyErr_Clear(); strid = PyUnicode_FromString(""); } uint8_t tag_buf[5] = {0}; return Py_BuildValue("{sd sd sd sO ss ss sN}", "minimum", src->minimum / 65536.0, "maximum", src->maximum / 65536.0, "default", src->def / 65536.0, "hidden", flags & FT_VAR_AXIS_FLAG_HIDDEN ? Py_True : Py_False, "name", src->name, "tag", tag_to_string(src->tag, tag_buf), "strid", strid ); } static PyObject* get_variation(Face *self, PyObject *a UNUSED) { RAII_FTMMVar(mm); FT_Error err; if ((err = FT_Get_MM_Var(self->face, &mm))) { Py_RETURN_NONE; } RAII_ALLOC(FT_Fixed, coords, malloc(mm->num_axis * sizeof(FT_Fixed))); if (!coords) return PyErr_NoMemory(); if ((err = FT_Get_Var_Design_Coordinates(self->face, mm->num_axis, coords))) { set_freetype_error("Failed to load the variation data from font with error:", err); return NULL; } RAII_PyObject(ans, PyDict_New()); if (!ans) return NULL; uint8_t tag[5]; for (FT_UInt i = 0; i < mm->num_axis; i++) { double val = coords[i] / 65536.0; tag_to_string(mm->axis[i].tag, tag); RAII_PyObject(pval, PyFloat_FromDouble(val)); if (!pval) return NULL; if (PyDict_SetItemString(ans, (const char*)tag, pval) != 0) return NULL; } Py_INCREF(ans); return ans; } static PyObject* applied_features(Face *self, PyObject *a UNUSED) { return font_features_as_dict(&self->font_features); } static PyObject* get_features(Face *self, PyObject *a UNUSED) { FT_Error err; FT_ULong length = 0; if (!ensure_name_table(self)) return NULL; RAII_PyObject(output, PyDict_New()); if (!output) return NULL; if ((err = FT_Load_Sfnt_Table(self->face, FT_MAKE_TAG('G', 'S', 'U', 'B'), 0, NULL, &length)) == 0) { RAII_ALLOC(uint8_t, table, malloc(length)); if (!table) return PyErr_NoMemory(); if ((err = FT_Load_Sfnt_Table(self->face, FT_MAKE_TAG('G', 'S', 'U', 'B'), 0, table, &length))) { set_freetype_error("Failed to load the GSUB table from font with error:", err); return NULL; } if (!read_features_from_font_table(table, length, self->name_lookup_table, output)) return NULL; } length = 0; if ((err = FT_Load_Sfnt_Table(self->face, FT_MAKE_TAG('G', 'P', 'O', 'S'), 0, NULL, &length)) == 0) { RAII_ALLOC(uint8_t, table, malloc(length)); if (!table) return PyErr_NoMemory(); if ((err = FT_Load_Sfnt_Table(self->face, FT_MAKE_TAG('G', 'P', 'O', 'S'), 0, table, &length))) { set_freetype_error("Failed to load the GSUB table from font with error:", err); return NULL; } if (!read_features_from_font_table(table, length, self->name_lookup_table, output)) return NULL; } Py_INCREF(output); return output; } static PyObject* get_variable_data(Face *self, PyObject *a UNUSED) { if (!ensure_name_table(self)) return NULL; RAII_PyObject(output, PyDict_New()); if (!output) return NULL; RAII_PyObject(axes, PyTuple_New(0)); RAII_PyObject(named_styles, PyTuple_New(0)); if (!axes || !named_styles) return NULL; FT_Error err; FT_ULong length = 0; if ((err = FT_Load_Sfnt_Table(self->face, FT_MAKE_TAG('S', 'T', 'A', 'T'), 0, NULL, &length)) == 0) { RAII_ALLOC(uint8_t, table, malloc(length)); if (!table) return PyErr_NoMemory(); if ((err = FT_Load_Sfnt_Table(self->face, FT_MAKE_TAG('S', 'T', 'A', 'T'), 0, table, &length))) { set_freetype_error("Failed to load the STAT table from font with error:", err); return NULL; } if (!read_STAT_font_table(table, length, self->name_lookup_table, output)) return NULL; } else if (!read_STAT_font_table(NULL, 0, self->name_lookup_table, output)) return NULL; if (self->is_variable) { RAII_FTMMVar(mm); if ((err = FT_Get_MM_Var(self->face, &mm))) { set_freetype_error("Failed to get variable axis data from font with error:", err); return NULL; } if (_PyTuple_Resize(&axes, mm->num_axis) == -1) return NULL; if (_PyTuple_Resize(&named_styles, mm->num_namedstyles) == -1) return NULL; for (FT_UInt i = 0; i < mm->num_namedstyles; i++) { PyObject *s = convert_named_style_to_python(self, mm->namedstyle + i, mm->axis, mm->num_axis); if (!s) return NULL; PyTuple_SET_ITEM(named_styles, i, s); } for (FT_UInt i = 0; i < mm->num_axis; i++) { FT_UInt flags; FT_Get_Var_Axis_Flags(mm, i, &flags); PyObject *s = convert_axis_to_python(self, mm->axis + i, flags); if (!s) return NULL; PyTuple_SET_ITEM(axes, i, s); } } if (PyDict_SetItemString(output, "variations_postscript_name_prefix", _get_best_name(self, 25)) != 0) return NULL; if (PyDict_SetItemString(output, "axes", axes) != 0) return NULL; if (PyDict_SetItemString(output, "named_styles", named_styles) != 0) return NULL; Py_INCREF(output); return output; } StringCanvas render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline) { Face *self = (Face*)s; StringCanvas ans = {0}; size_t num_chars = strnlen(text, 32); int max_char_width = font_units_to_pixels_x(self, self->face->max_advance_width); size_t canvas_width = max_char_width * (num_chars*2); size_t canvas_height = font_units_to_pixels_y(self, self->face->height) + 8; pixel *canvas = calloc(canvas_width * canvas_height, sizeof(pixel)); if (!canvas) return ans; size_t pen_x = 0; ProcessedBitmap pbm; for (size_t n = 0; n < num_chars; n++) { FT_UInt glyph_index = FT_Get_Char_Index(self->face, text[n]); int error = FT_Load_Glyph(self->face, glyph_index, FT_LOAD_DEFAULT); if (error) continue; error = FT_Render_Glyph(self->face->glyph, FT_RENDER_MODE_NORMAL); if (error) continue; FT_Bitmap *bitmap = &self->face->glyph->bitmap; pbm = EMPTY_PBM; populate_processed_bitmap(self->face->glyph, bitmap, &pbm, false); place_bitmap_in_canvas(canvas, &pbm, canvas_width, canvas_height, 0, 0, baseline, n, 0xffffff, pen_x, 0); pen_x += self->face->glyph->advance.x >> 6; } ans.width = pen_x; ans.height = canvas_height; ans.canvas = malloc(ans.width * ans.height); if (ans.canvas) { for (size_t row = 0; row < ans.height; row++) { unsigned char *destp = ans.canvas + (ans.width * row); pixel *srcp = canvas + (canvas_width * row); for (size_t i = 0; i < ans.width; i++) destp[i] = srcp[i] & 0xff; } } free(canvas); return ans; } static void destroy_hb_buffer(hb_buffer_t **x) { if (*x) hb_buffer_destroy(*x); } static PyObject* render_codepoint(Face *self, PyObject *args) { unsigned long cp, fg = 0xffffff; if (!PyArg_ParseTuple(args, "k|k", &cp, &fg)) return NULL; FT_UInt glyph_index = FT_Get_Char_Index(self->face, cp); ProcessedBitmap pbm = EMPTY_PBM; GlyphColorType colored; if (self->has_color && (colored = glyph_color_type(self, glyph_index)) != NOT_COLORED) { render_color_bitmap(self, glyph_index, &pbm, 0, 0, 0, 0); } else { int load_flags = get_load_flags(self->hinting, self->hintstyle, FT_LOAD_RENDER); FT_Load_Glyph(self->face, glyph_index, load_flags); FT_Render_Glyph(self->face->glyph, FT_RENDER_MODE_NORMAL); FT_Bitmap *bitmap = &self->face->glyph->bitmap; populate_processed_bitmap(self->face->glyph, bitmap, &pbm, false); } const unsigned long canvas_width = pbm.width, canvas_height = pbm.rows; RAII_PyObject(ans, PyBytes_FromStringAndSize(NULL, sizeof(pixel) * canvas_height * canvas_width)); if (!ans) return NULL; pixel *canvas = (pixel*)PyBytes_AS_STRING(ans); memset(canvas, 0, PyBytes_GET_SIZE(ans)); place_bitmap_in_canvas(canvas, &pbm, canvas_width, canvas_height, 0, 0, 0, 99999, fg, 0, 0); free_processed_bitmap(&pbm); for (pixel *c = canvas; c < canvas + canvas_width * canvas_height; c++) { uint8_t *p = (uint8_t*)c; uint8_t a = p[0], b = p[1], g = p[2], r = p[3]; p[0] = r; p[1] = g; p[2] = b; p[3] = a; } return Py_BuildValue("Okk", ans, canvas_width, canvas_height); } static PyObject* render_sample_text(Face *self, PyObject *args) { unsigned long canvas_width, canvas_height; unsigned long fg = 0xffffff; PyObject *ptext; if (!PyArg_ParseTuple(args, "Ukk|k", &ptext, &canvas_width, &canvas_height, &fg)) return NULL; FontCellMetrics fcm = cell_metrics((PyObject*)self); RAII_PyObject(pbuf, PyBytes_FromStringAndSize(NULL, sizeof(pixel) * canvas_width * canvas_height)); if (!pbuf) return NULL; memset(PyBytes_AS_STRING(pbuf), 0, PyBytes_GET_SIZE(pbuf)); if (!fcm.cell_width || !fcm.cell_height) return Py_BuildValue("OII", pbuf, fcm.cell_width, fcm.cell_height); int num_chars_per_line = canvas_width / fcm.cell_width, num_of_lines = (int)ceil((float)PyUnicode_GET_LENGTH(ptext) / (float)num_chars_per_line); canvas_height = MIN(canvas_height, num_of_lines * fcm.cell_height); __attribute__((cleanup(destroy_hb_buffer))) hb_buffer_t *hb_buffer = hb_buffer_create(); if (!hb_buffer_pre_allocate(hb_buffer, 4*PyUnicode_GET_LENGTH(ptext))) { PyErr_NoMemory(); return NULL; } for (ssize_t n = 0; n < PyUnicode_GET_LENGTH(ptext); n++) { Py_UCS4 codep = PyUnicode_READ_CHAR(ptext, n); hb_buffer_add_utf32(hb_buffer, &codep, 1, 0, 1); } hb_buffer_guess_segment_properties(hb_buffer); if (!HB_DIRECTION_IS_HORIZONTAL(hb_buffer_get_direction(hb_buffer))) goto end; hb_shape(harfbuzz_font_for_face((PyObject*)self), hb_buffer, self->font_features.features, self->font_features.count); unsigned int len = hb_buffer_get_length(hb_buffer); hb_glyph_info_t *info = hb_buffer_get_glyph_infos(hb_buffer, NULL); hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(hb_buffer, NULL); if (fcm.cell_width > canvas_width) goto end; pixel *canvas = (pixel*)PyBytes_AS_STRING(pbuf); int load_flags = get_load_flags(self->hinting, self->hintstyle, FT_LOAD_RENDER); int error; float pen_x = 0, pen_y = 0; for (unsigned int i = 0; i < len; i++) { float advance = (float)positions[i].x_advance / 64.0f; if (pen_x + advance > canvas_width) { pen_y += fcm.cell_height; pen_x = 0; if (pen_y >= canvas_height) break; } size_t x = (size_t)round(pen_x + (float)positions[i].x_offset / 64.0f); size_t y = (size_t)round(pen_y + (float)positions[i].y_offset / 64.0f); pen_x += advance; if ((error = FT_Load_Glyph(self->face, info[i].codepoint, load_flags))) continue; if ((error = FT_Render_Glyph(self->face->glyph, FT_RENDER_MODE_NORMAL))) continue; FT_Bitmap *bitmap = &self->face->glyph->bitmap; ProcessedBitmap pbm = EMPTY_PBM; populate_processed_bitmap(self->face->glyph, bitmap, &pbm, false); place_bitmap_in_canvas(canvas, &pbm, canvas_width, canvas_height, x, 0, fcm.baseline, 99999, fg, 0, y); free_processed_bitmap(&pbm); } const uint8_t *last_pixel = (uint8_t*)PyBytes_AS_STRING(pbuf) + PyBytes_GET_SIZE(pbuf) - sizeof(pixel); for (uint8_t *p = (uint8_t*)PyBytes_AS_STRING(pbuf); p <= last_pixel; p += sizeof(pixel)) { uint8_t a = p[0], b = p[1], g = p[2], r = p[3]; p[0] = r; p[1] = g; p[2] = b; p[3] = a; } end: return Py_BuildValue("OII", pbuf, fcm.cell_width, fcm.cell_height); } // Boilerplate {{{ static PyMemberDef members[] = { #define MEM(name, type) {#name, type, offsetof(Face, name), READONLY, #name} #define MMEM(name, type) {#name, type, offsetof(Face, metrics) + offsetof(FaceMetrics, name), READONLY, #name} MMEM(units_per_EM, T_UINT), MMEM(size_in_pts, T_FLOAT), MMEM(ascender, T_INT), MMEM(descender, T_INT), MMEM(height, T_INT), MMEM(max_advance_width, T_INT), MMEM(max_advance_height, T_INT), MMEM(underline_position, T_INT), MMEM(underline_thickness, T_INT), MMEM(strikethrough_position, T_INT), MMEM(strikethrough_thickness, T_INT), MEM(is_scalable, T_BOOL), MEM(is_variable, T_BOOL), MEM(has_svg, T_BOOL), MEM(has_color, T_BOOL), MEM(path, T_OBJECT_EX), {NULL} /* Sentinel */ #undef MEM #undef MMEM }; static PyMethodDef methods[] = { METHODB(postscript_name, METH_NOARGS), METHODB(identify_for_debug, METH_NOARGS), METHODB(extra_data, METH_NOARGS), METHODB(get_variable_data, METH_NOARGS), METHODB(applied_features, METH_NOARGS), METHODB(get_features, METH_NOARGS), METHODB(get_variation, METH_NOARGS), METHODB(get_best_name, METH_O), METHODB(set_size, METH_VARARGS), METHODB(render_sample_text, METH_VARARGS), METHODB(render_codepoint, METH_VARARGS), {NULL} /* Sentinel */ }; PyTypeObject Face_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.Face", .tp_new = new, .tp_basicsize = sizeof(Face), .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "FreeType Font face", .tp_methods = methods, .tp_members = members, .tp_repr = (reprfunc)repr, }; static void free_freetype(void) { cairo_debug_reset_static_data(); FT_Done_FreeType(library); } bool init_freetype_library(PyObject *m) { if (PyType_Ready(&Face_Type) < 0) return 0; if (PyModule_AddObject(m, "Face", (PyObject *)&Face_Type) != 0) return 0; Py_INCREF(&Face_Type); FreeType_Exception = PyErr_NewException("fast_data_types.FreeTypeError", NULL, NULL); if (FreeType_Exception == NULL) return false; if (PyModule_AddObject(m, "FreeTypeError", FreeType_Exception) != 0) return false; int error = FT_Init_FreeType(&library); if (error) { set_freetype_error("Failed to initialize FreeType library, with error:", error); return false; } register_at_exit_cleanup_func(FREETYPE_CLEANUP_FUNC, free_freetype); return true; } // }}} kitty-0.41.1/kitty/freetype_render_ui_text.c0000664000175000017510000006534014773370543020552 0ustar nileshnilesh/* * freetype_render_ui_text.c * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "freetype_render_ui_text.h" #include #include #include "charsets.h" #include "char-props.h" #include "wcswidth.h" #include FT_BITMAP_H #define ELLIPSIS 0x2026 typedef struct FamilyInformation { char *name; bool bold, italic; } FamilyInformation; typedef struct Face { FT_Face freetype; hb_font_t *hb; FT_UInt pixel_size; int hinting, hintstyle; struct Face *fallbacks; size_t count, capacity; } Face; typedef struct { unsigned char* buf; size_t start_x, width, stride; size_t rows; FT_Pixel_Mode pixel_mode; unsigned int left_edge, top_edge, bottom_edge, right_edge; float factor; int bitmap_left, bitmap_top; } ProcessedBitmap; typedef struct RenderCtx { bool created; Face main_face; FontConfigFace main_face_information; FamilyInformation main_face_family; hb_buffer_t *hb_buffer; } RenderCtx; #define main_face ctx->main_face #define main_face_information ctx->main_face_information #define main_face_family ctx->main_face_family #define hb_buffer ctx->hb_buffer static FT_UInt glyph_id_for_codepoint(Face *face, char_type cp) { return FT_Get_Char_Index(face->freetype, cp); } static void free_face(Face *face) { if (face->freetype) FT_Done_Face(face->freetype); if (face->hb) hb_font_destroy(face->hb); for (size_t i = 0; i < face->count; i++) free_face(face->fallbacks + i); free(face->fallbacks); memset(face, 0, sizeof(Face)); } static void cleanup(RenderCtx *ctx) { free_face(&main_face); free(main_face_information.path); main_face_information.path = NULL; free(main_face_family.name); memset(&main_face_family, 0, sizeof(FamilyInformation)); if (hb_buffer) hb_buffer_destroy(hb_buffer); hb_buffer = NULL; } void set_main_face_family(FreeTypeRenderCtx ctx_, const char *family, bool bold, bool italic) { RenderCtx *ctx = (RenderCtx*)ctx_; if ( (family == main_face_family.name || (main_face_family.name && strcmp(family, main_face_family.name) == 0)) && main_face_family.bold == bold && main_face_family.italic == italic ) return; cleanup(ctx); main_face_family.name = family ? strdup(family) : NULL; main_face_family.bold = bold; main_face_family.italic = italic; } static int get_load_flags(int hinting, int hintstyle, int base) { int flags = base; if (hinting) { if (hintstyle >= 3) flags |= FT_LOAD_TARGET_NORMAL; else if (0 < hintstyle) flags |= FT_LOAD_TARGET_LIGHT; } else flags |= FT_LOAD_NO_HINTING; return flags; } static bool load_font(FontConfigFace *info, Face *ans) { ans->freetype = native_face_from_path(info->path, info->index); if (!ans->freetype || PyErr_Occurred()) return false; ans->hb = hb_ft_font_create(ans->freetype, NULL); if (!ans->hb) { PyErr_NoMemory(); return false; } ans->hinting = info->hinting; ans->hintstyle = info->hintstyle; hb_ft_font_set_load_flags(ans->hb, get_load_flags(ans->hinting, ans->hintstyle, FT_LOAD_DEFAULT)); return true; } static int font_units_to_pixels_y(FT_Face face, int x) { return (int)ceil((double)FT_MulFix(x, face->size->metrics.y_scale) / 64.0); } static FT_UInt choose_bitmap_size(FT_Face face, FT_UInt desired_height) { unsigned short best = 0, diff = USHRT_MAX; const short limit = face->num_fixed_sizes; for (short i = 0; i < limit; i++) { unsigned short h = face->available_sizes[i].height; unsigned short d = h > (unsigned short)desired_height ? h - (unsigned short)desired_height : (unsigned short)desired_height - h; if (d < diff) { diff = d; best = i; } } FT_Select_Size(face, best); return best; } static void set_pixel_size(RenderCtx *ctx, Face *face, FT_UInt sz, bool get_metrics UNUSED) { if (sz != face->pixel_size) { if (face->freetype->num_fixed_sizes > 0 && FT_HAS_COLOR(face->freetype)) choose_bitmap_size(face->freetype, font_units_to_pixels_y(main_face.freetype, main_face.freetype->height)); else FT_Set_Pixel_Sizes(face->freetype, sz, sz); hb_ft_font_changed(face->hb); hb_ft_font_set_load_flags(face->hb, get_load_flags(face->hinting, face->hintstyle, FT_LOAD_DEFAULT)); face->pixel_size = sz; } } typedef struct RenderState { uint32_t pending_in_buffer, fg, bg; pixel *output; size_t output_width, output_height, stride; Face *current_face; float x, y, start_pos_for_current_run; int y_offset; Region src, dest; unsigned sz_px; bool truncated; bool horizontally_center; } RenderState; static void setup_regions(ProcessedBitmap *bm, RenderState *rs, int baseline) { rs->src = (Region){ .left = bm->start_x, .bottom = bm->rows, .right = bm->width + bm->start_x }; rs->dest = (Region){ .bottom = rs->output_height, .right = rs->output_width }; int xoff = (int)(rs->x + bm->bitmap_left); if (xoff < 0) rs->src.left += -xoff; else rs->dest.left = xoff; if (rs->horizontally_center) { int run_width = (int)(rs->output_width - rs->start_pos_for_current_run); rs->dest.left = (int)rs->start_pos_for_current_run + (run_width > (int)bm->width ? (run_width - bm->width)/2 : 0); } int yoff = (int)(rs->y + bm->bitmap_top); if ((yoff > 0 && yoff > baseline)) { rs->dest.top = 0; } else { rs->dest.top = baseline - yoff; } rs->dest.top += rs->y_offset; } #define ARGB(a, r, g, b) ( (a & 0xff) << 24 ) | ( (r & 0xff) << 16) | ( (g & 0xff) << 8 ) | (b & 0xff) static pixel premult_pixel(pixel p, uint16_t alpha) { #define s(x) (x * alpha / 255) uint16_t r = (p >> 16) & 0xff, g = (p >> 8) & 0xff, b = p & 0xff; return ARGB(alpha, s(r), s(g), s(b)); #undef s } static pixel alpha_blend_premult(pixel over, pixel under) { const uint16_t over_r = (over >> 16) & 0xff, over_g = (over >> 8) & 0xff, over_b = over & 0xff; const uint16_t under_r = (under >> 16) & 0xff, under_g = (under >> 8) & 0xff, under_b = under & 0xff; const uint16_t factor = 255 - ((over >> 24) & 0xff); #define ans(x) (over_##x + (factor * under_##x) / 255) return ARGB(under >> 24, ans(r), ans(g), ans(b)); #undef ans } static void render_color_bitmap(ProcessedBitmap *src, RenderState *rs) { for (size_t sr = rs->src.top, dr = rs->dest.top; sr < rs->src.bottom && dr < rs->dest.bottom; sr++, dr++) { pixel *dest_row = rs->output + rs->stride * dr; uint8_t *src_px = src->buf + src->stride * sr + 4 * rs->src.left; for (size_t sc = rs->src.left, dc = rs->dest.left; sc < rs->src.right && dc < rs->dest.right; sc++, dc++, src_px += 4) { pixel fg = premult_pixel(ARGB(src_px[3], src_px[2], src_px[1], src_px[0]), src_px[3]); dest_row[dc] = alpha_blend_premult(fg, dest_row[dc]); } } } static void render_gray_bitmap(ProcessedBitmap *src, RenderState *rs) { for (size_t sr = rs->src.top, dr = rs->dest.top; sr < rs->src.bottom && dr < rs->dest.bottom; sr++, dr++) { pixel *dest_row = rs->output + rs->stride * dr; uint8_t *src_row = src->buf + src->stride * sr; for (size_t sc = rs->src.left, dc = rs->dest.left; sc < rs->src.right && dc < rs->dest.right; sc++, dc++) { pixel fg = premult_pixel(rs->fg, src_row[sc]); dest_row[dc] = alpha_blend_premult(fg, dest_row[dc]); } } } static void populate_processed_bitmap(FT_GlyphSlotRec *slot, FT_Bitmap *bitmap, ProcessedBitmap *ans) { ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch; ans->rows = bitmap->rows; ans->start_x = 0; ans->width = bitmap->width; ans->pixel_mode = bitmap->pixel_mode; ans->bitmap_top = slot->bitmap_top; ans->bitmap_left = slot->bitmap_left; ans->buf = bitmap->buffer; } static void detect_edges(ProcessedBitmap *ans) { #define check const uint8_t *p = ans->buf + x * 4 + y * ans->stride; if (p[3] > 20) ans->right_edge = 0; ans->bottom_edge = 0; for (ssize_t x = ans->width - 1; !ans->right_edge && x > -1; x--) { for (size_t y = 0; y < ans->rows && !ans->right_edge; y++) { check ans->right_edge = x; } } for (ssize_t y = ans->rows - 1; !ans->bottom_edge && y > -1; y--) { for (size_t x = 0; x < ans->width && !ans->bottom_edge; x++) { check ans->bottom_edge = y; } } ans->left_edge = ans->width; for (size_t x = 0; ans->left_edge == ans->width && x < ans->width; x++) { for (size_t y = 0; y < ans->rows && ans->left_edge == ans->width; y++) { check ans->left_edge = x; } } ans->top_edge = ans->rows; for (size_t y = 0; ans->top_edge == ans->rows && y < ans->rows; y++) { for (size_t x = 0; x < ans->width && ans->top_edge == ans->rows; x++) { check ans->top_edge = y; } } #undef check } static Face* find_fallback_font_for(RenderCtx *ctx, char_type codep, char_type next_codep) { if (glyph_id_for_codepoint(&main_face, codep) > 0) return &main_face; for (size_t i = 0; i < main_face.count; i++) { if (glyph_id_for_codepoint(main_face.fallbacks + i, codep) > 0) return main_face.fallbacks + i; } FontConfigFace q; bool prefer_color = false; char_type string[3] = {codep, next_codep, 0}; if (wcswidth_string(string) >= 2 && char_props_for(codep).is_emoji_presentation_base) prefer_color = true; if (!fallback_font(codep, main_face_family.name, main_face_family.bold, main_face_family.italic, prefer_color, &q)) return NULL; ensure_space_for(&main_face, fallbacks, Face, main_face.count + 1, capacity, 8, true); Face *ans = main_face.fallbacks + main_face.count; bool ok = load_font(&q, ans); if (PyErr_Occurred()) PyErr_Print(); free(q.path); if (!ok) return NULL; main_face.count++; return ans; } static unsigned calculate_ellipsis_width(RenderCtx *ctx) { Face *face = find_fallback_font_for(ctx, ELLIPSIS, 0); if (!face) return 0; set_pixel_size(ctx, face, main_face.pixel_size, false); int glyph_index = FT_Get_Char_Index(face->freetype, ELLIPSIS); if (!glyph_index) return 0; int error = FT_Load_Glyph(face->freetype, glyph_index, get_load_flags(face->hinting, face->hintstyle, FT_LOAD_DEFAULT)); if (error) return 0; return (unsigned)ceilf((float)face->freetype->glyph->metrics.horiAdvance / 64.f); } static bool render_run(RenderCtx *ctx, RenderState *rs) { hb_buffer_guess_segment_properties(hb_buffer); if (!HB_DIRECTION_IS_HORIZONTAL(hb_buffer_get_direction(hb_buffer))) { PyErr_SetString(PyExc_ValueError, "Vertical text is not supported"); return false; } FT_Face face = rs->current_face->freetype; bool has_color = FT_HAS_COLOR(face); FT_UInt pixel_size = rs->sz_px; set_pixel_size(ctx, rs->current_face, pixel_size, false); hb_shape(rs->current_face->hb, hb_buffer, NULL, 0); unsigned int len = hb_buffer_get_length(hb_buffer); hb_glyph_info_t *info = hb_buffer_get_glyph_infos(hb_buffer, NULL); hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(hb_buffer, NULL); int baseline = font_units_to_pixels_y(face, face->ascender); int load_flags = get_load_flags(rs->current_face->hinting, rs->current_face->hintstyle, FT_LOAD_RENDER | (has_color ? FT_LOAD_COLOR : 0)); float pos = rs->x; unsigned int limit = len; for (unsigned int i = 0; i < len; i++) { float delta = (float)positions[i].x_offset / 64.0f + (float)positions[i].x_advance / 64.0f; if (pos + delta >= rs->output_width) { limit = i; break; } pos += delta; } if (limit < len) { unsigned ellipsis_width = calculate_ellipsis_width(ctx); while (pos + ellipsis_width >= rs->output_width && limit) { limit--; pos -= (float)positions[limit].x_offset / 64.0f + (float)positions[limit].x_advance / 64.0f; } rs->truncated = true; } rs->start_pos_for_current_run = rs->x; for (unsigned int i = 0; i < limit; i++) { rs->x += (float)positions[i].x_offset / 64.0f; rs->y += (float)positions[i].y_offset / 64.0f; if (rs->x > rs->output_width) break; int error = FT_Load_Glyph(face, info[i].codepoint, load_flags); if (error) { set_freetype_error("Failed loading glyph", error); PyErr_Print(); continue; }; ProcessedBitmap pbm = {0}; switch(face->glyph->bitmap.pixel_mode) { case FT_PIXEL_MODE_BGRA: { uint8_t *buf = NULL; unsigned text_height = font_units_to_pixels_y(main_face.freetype, main_face.freetype->height); populate_processed_bitmap(face->glyph, &face->glyph->bitmap, &pbm); unsigned bm_width = 0, bm_height = text_height; if (pbm.rows > bm_height) { double ratio = pbm.width / (double)pbm.rows; bm_width = (unsigned)(ratio * bm_height); buf = calloc((size_t)bm_height * bm_width, sizeof(pixel)); if (!buf) break; downsample_32bit_image(pbm.buf, pbm.width, pbm.rows, pbm.stride, buf, bm_width, bm_height); pbm.buf = buf; pbm.stride = 4 * bm_width; pbm.width = bm_width; pbm.rows = bm_height; detect_edges(&pbm); } setup_regions(&pbm, rs, baseline); if (bm_width) { /* printf("bottom_edge: %u top_edge: %u left_edge: %u right_edge: %u\n", */ /* pbm.bottom_edge, pbm.top_edge, pbm.left_edge, pbm.right_edge); */ rs->src.top = pbm.top_edge; rs->src.bottom = pbm.bottom_edge + 1; rs->src.left = pbm.left_edge; rs->src.right = pbm.right_edge + 1; rs->dest.left = (int)(rs->x + 2); positions[i].x_advance = (pbm.right_edge - pbm.left_edge + 2) * 64; unsigned main_baseline = font_units_to_pixels_y(main_face.freetype, main_face.freetype->ascender); unsigned symbol_height = pbm.bottom_edge - pbm.top_edge; unsigned baseline_y = main_baseline + rs->y_offset, text_bottom_y = text_height + rs->y_offset; if (symbol_height <= baseline_y) { rs->dest.top = baseline_y - symbol_height + 2; } else { if (symbol_height <= text_bottom_y) rs->dest.top = text_bottom_y - symbol_height; else rs->dest.top = 0; } rs->dest.top += main_baseline > pbm.bottom_edge ? main_baseline - pbm.bottom_edge : 0; /* printf("symbol_height: %u baseline_y: %u\n", symbol_height, baseline_y); */ } render_color_bitmap(&pbm, rs); free(buf); } break; case FT_PIXEL_MODE_MONO: { FT_Bitmap bitmap; freetype_convert_mono_bitmap(&face->glyph->bitmap, &bitmap); populate_processed_bitmap(face->glyph, &bitmap, &pbm); setup_regions(&pbm, rs, baseline); render_gray_bitmap(&pbm, rs); FT_Bitmap_Done(freetype_library(), &bitmap); } break; case FT_PIXEL_MODE_GRAY: populate_processed_bitmap(face->glyph, &face->glyph->bitmap, &pbm); setup_regions(&pbm, rs, baseline); render_gray_bitmap(&pbm, rs); break; default: PyErr_Format(PyExc_TypeError, "Unknown FreeType bitmap type: 0x%x", face->glyph->bitmap.pixel_mode); return false; break; } rs->x += (float)positions[i].x_advance / 64.0f; } return true; } static bool process_codepoint(RenderCtx *ctx, RenderState *rs, char_type codep, char_type next_codep) { bool add_to_current_buffer = false; Face *fallback_font = NULL; if (char_props_for(codep).is_combining_char) { add_to_current_buffer = true; } else if (glyph_id_for_codepoint(&main_face, codep) > 0) { add_to_current_buffer = rs->current_face == &main_face; if (!add_to_current_buffer) fallback_font = &main_face; } else { if (glyph_id_for_codepoint(rs->current_face, codep) > 0) fallback_font = rs->current_face; else fallback_font = find_fallback_font_for(ctx, codep, next_codep); add_to_current_buffer = !fallback_font || rs->current_face == fallback_font; } if (!add_to_current_buffer) { if (rs->pending_in_buffer) { if (!render_run(ctx, rs)) return false; rs->pending_in_buffer = 0; hb_buffer_clear_contents(hb_buffer); } if (fallback_font) rs->current_face = fallback_font; } hb_buffer_add_utf32(hb_buffer, &codep, 1, 0, 1); rs->pending_in_buffer += 1; return true; } bool render_single_line(FreeTypeRenderCtx ctx_, const char *text, unsigned sz_px, pixel fg, pixel bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin, bool horizontally_center_runs) { RenderCtx *ctx = (RenderCtx*)ctx_; if (!ctx->created) return false; size_t output_width = right_margin <= width ? width - right_margin : 0; bool has_text = text && text[0]; pixel pbg = premult_pixel(bg, ((bg >> 24) & 0xff)); for (size_t y = 0; y < height; y++) { pixel *px = (pixel*)(output_buf + 4 * y * width); for (size_t x = (size_t)x_offset; x < output_width; x++) px[x] = pbg; } if (!has_text) return true; hb_buffer_clear_contents(hb_buffer); if (!hb_buffer_pre_allocate(hb_buffer, 512)) { PyErr_NoMemory(); return false; } size_t text_len = strlen(text); char_type *unicode = calloc(text_len + 1, sizeof(char_type)); if (!unicode) { PyErr_NoMemory(); return false; } bool ok = false; text_len = decode_utf8_string(text, text_len, unicode); set_pixel_size(ctx, &main_face, sz_px, true); unsigned text_height = font_units_to_pixels_y(main_face.freetype, main_face.freetype->height); RenderState rs = { .current_face = &main_face, .fg = fg, .bg = bg, .horizontally_center = horizontally_center_runs, .output_width = output_width, .output_height = height, .stride = width, .output = (pixel*)output_buf, .x = x_offset, .y = y_offset, .sz_px = sz_px }; if (text_height < height) rs.y_offset = (height - text_height) / 2; for (size_t i = 0; i < text_len && rs.x < rs.output_width && !rs.truncated; i++) { if (!process_codepoint(ctx, &rs, unicode[i], unicode[i + 1])) goto end; } if (rs.pending_in_buffer && rs.x < rs.output_width && !rs.truncated) { if (!render_run(ctx, &rs)) goto end; rs.pending_in_buffer = 0; hb_buffer_clear_contents(hb_buffer); } if (rs.truncated) { hb_buffer_clear_contents(hb_buffer); rs.pending_in_buffer = 0; rs.current_face = &main_face; if (!process_codepoint(ctx, &rs, ELLIPSIS, 0)) goto end; if (!render_run(ctx, &rs)) goto end; } ok = true; end: free(unicode); return ok; } static uint8_t* render_single_char_bitmap(const FT_Bitmap *bm, size_t *result_width, size_t *result_height) { *result_width = bm->width; *result_height = bm->rows; uint8_t *rendered = malloc(*result_width * *result_height); if (!rendered) { PyErr_NoMemory(); return NULL; } for (size_t r = 0; r < bm->rows; r++) { uint8_t *src_row = bm->buffer + bm->pitch * r; uint8_t *dest_row = rendered + *result_width * r; memcpy(dest_row, src_row, *result_width); } return rendered; } typedef struct TempFontData { Face *face; FT_UInt orig_sz; } TempFontData; static void cleanup_resize(TempFontData *f) { if (f->face && f->face->freetype) { f->face->pixel_size = f->orig_sz; FT_Set_Pixel_Sizes(f->face->freetype, f->orig_sz, f->orig_sz); } } #define RAII_TempFontData(name) __attribute__((cleanup(cleanup_resize))) TempFontData name = {0} static void* report_freetype_error_for_char(int error, char ch, const char *operation) { char buf[128]; snprintf(buf, sizeof(buf), "Failed to %s glyph for character: %c, with error: ", operation, ch); set_freetype_error(buf, error); return NULL; } uint8_t* render_single_ascii_char_as_mask(FreeTypeRenderCtx ctx_, const char ch, size_t *result_width, size_t *result_height) { RenderCtx *ctx = (RenderCtx*)ctx_; if (!ctx->created) { PyErr_SetString(PyExc_RuntimeError, "freetype render ctx not created"); return NULL; } RAII_TempFontData(temp); Face *face = &main_face; int glyph_index = FT_Get_Char_Index(face->freetype, ch); if (!glyph_index) { PyErr_Format(PyExc_KeyError, "character %c not found in font", ch); return NULL; } unsigned int height = font_units_to_pixels_y(face->freetype, face->freetype->height); size_t avail_height = *result_height; if (avail_height < 4) { PyErr_Format(PyExc_ValueError, "Invalid available height: %zu", avail_height); return NULL; } float ratio = ((float)height) / avail_height; temp.face = face; temp.orig_sz = face->pixel_size; face->pixel_size = (FT_UInt)(face->pixel_size / ratio); if (face->pixel_size != temp.orig_sz) FT_Set_Pixel_Sizes(face->freetype, avail_height, avail_height); int error = FT_Load_Glyph(face->freetype, glyph_index, get_load_flags(face->hinting, face->hintstyle, FT_LOAD_DEFAULT)); if (error) return report_freetype_error_for_char(error, ch, "load"); if (face->freetype->glyph->format != FT_GLYPH_FORMAT_BITMAP) { error = FT_Render_Glyph(face->freetype->glyph, FT_RENDER_MODE_NORMAL); if (error) return report_freetype_error_for_char(error, ch, "render"); } uint8_t *rendered = NULL; switch(face->freetype->glyph->bitmap.pixel_mode) { case FT_PIXEL_MODE_MONO: { FT_Bitmap bitmap; if (!freetype_convert_mono_bitmap(&face->freetype->glyph->bitmap, &bitmap)) return NULL; rendered = render_single_char_bitmap(&bitmap, result_width, result_height); FT_Bitmap_Done(freetype_library(), &bitmap); } break; case FT_PIXEL_MODE_GRAY: rendered = render_single_char_bitmap(&face->freetype->glyph->bitmap, result_width, result_height); break; default: PyErr_Format(PyExc_TypeError, "Unknown FreeType bitmap type: 0x%x", face->freetype->glyph->bitmap.pixel_mode); return false; break; } return rendered; } FreeTypeRenderCtx create_freetype_render_context(const char *family, bool bold, bool italic) { RenderCtx *ctx = calloc(1, sizeof(RenderCtx)); main_face_family.name = family ? strdup(family) : NULL; main_face_family.bold = bold; main_face_family.italic = italic; if (!information_for_font_family(main_face_family.name, main_face_family.bold, main_face_family.italic, &main_face_information)) return NULL; if (!load_font(&main_face_information, &main_face)) return NULL; hb_buffer = hb_buffer_create(); if (!hb_buffer) { PyErr_NoMemory(); return NULL; } ctx->created = true; return (FreeTypeRenderCtx)ctx; } void release_freetype_render_context(FreeTypeRenderCtx ctx) { if (ctx) { cleanup((RenderCtx*)ctx); free(ctx); } } static PyObject* render_line(PyObject *self UNUSED, PyObject *args, PyObject *kw) { // use for testing as below // kitty +runpy "from kitty.fast_data_types import *; open('/tmp/test.rgba', 'wb').write(freetype_render_line())" && convert -size 800x60 -depth 8 /tmp/test.rgba /tmp/test.png && icat /tmp/test.png const char *text = "Test 猫 H🐱🚀b rendering with ellipsis for cut off text", *family = NULL; unsigned int width = 800, height = 60, right_margin = 0; int bold = 0, italic = 0; unsigned long fg = 0, bg = 0xfffefefe; float x_offset = 0, y_offset = 0; static const char* kwlist[] = {"text", "width", "height", "font_family", "bold", "italic", "fg", "bg", "x_offset", "y_offset", "right_margin", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kw, "|sIIzppkkffI", (char**)kwlist, &text, &width, &height, &family, &bold, &italic, &fg, &bg, &x_offset, &y_offset, &right_margin)) return NULL; PyObject *ans = PyBytes_FromStringAndSize(NULL, (Py_ssize_t)width * height * 4); if (!ans) return NULL; uint8_t *buffer = (uint8_t*) PyBytes_AS_STRING(ans); RenderCtx *ctx = (RenderCtx*)create_freetype_render_context(family, bold, italic); if (!ctx) return NULL; if (!render_single_line((FreeTypeRenderCtx)ctx, text, 3 * height / 4, 0, 0xffffffff, buffer, width, height, x_offset, y_offset, right_margin, false)) { Py_CLEAR(ans); if (!PyErr_Occurred()) PyErr_SetString(PyExc_RuntimeError, "Unknown error while rendering text"); ans = NULL; } else { // remove pre-multiplication and convert to ABGR which is what the ImageMagick .rgba filetype wants for (pixel *p = (pixel*)buffer, *end = (pixel*)(buffer + PyBytes_GET_SIZE(ans)); p < end; p++) { const uint16_t a = (*p >> 24) & 0xff; if (!a) continue; uint16_t r = (*p >> 16) & 0xff, g = (*p >> 8) & 0xff, b = *p & 0xff; #define c(x) (((x * 255) / a)) *p = ARGB(a, c(b), c(g), c(r)); #undef c } } release_freetype_render_context((FreeTypeRenderCtx)ctx); return ans; } static PyObject* path_for_font(PyObject *self UNUSED, PyObject *args) { const char *family = NULL; int bold = 0, italic = 0; if (!PyArg_ParseTuple(args, "|zpp", &family, &bold, &italic)) return NULL; FontConfigFace f; if (!information_for_font_family(family, bold, italic, &f)) return NULL; PyObject *ret = Py_BuildValue("{ss si si si}", "path", f.path, "index", f.index, "hinting", f.hinting, "hintstyle", f.hintstyle); free(f.path); return ret; } static PyObject* fallback_for_char(PyObject *self UNUSED, PyObject *args) { const char *family = NULL; int bold = 0, italic = 0; unsigned int ch; if (!PyArg_ParseTuple(args, "I|zpp", &ch, &family, &bold, &italic)) return NULL; FontConfigFace f; if (!fallback_font(ch, family, bold, italic, false, &f)) return NULL; PyObject *ret = Py_BuildValue("{ss si si si}", "path", f.path, "index", f.index, "hinting", f.hinting, "hintstyle", f.hintstyle); free(f.path); return ret; } static PyMethodDef module_methods[] = { {"fontconfig_path_for_font", (PyCFunction)(void (*) (void))(path_for_font), METH_VARARGS, NULL}, {"fontconfig_fallback_for_char", (PyCFunction)(void (*) (void))(fallback_for_char), METH_VARARGS, NULL}, {"freetype_render_line", (PyCFunction)(void (*) (void))(render_line), METH_VARARGS | METH_KEYWORDS, NULL}, {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_freetype_render_ui_text(PyObject *module) { if (PyModule_AddFunctions(module, module_methods) != 0) return false; return true; } kitty-0.41.1/kitty/freetype_render_ui_text.h0000664000175000017510000000303714773370543020552 0ustar nileshnilesh/* * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" #include typedef struct {bool created;} *FreeTypeRenderCtx; FreeTypeRenderCtx create_freetype_render_context(const char *family, bool bold, bool italic); void set_main_face_family(FreeTypeRenderCtx ctx, const char *family, bool bold, bool italic); bool render_single_line(FreeTypeRenderCtx ctx, const char *text, unsigned sz_px, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin, bool horizontally_center_runs); uint8_t* render_single_ascii_char_as_mask(FreeTypeRenderCtx ctx_, const char ch, size_t *result_width, size_t *result_height); void release_freetype_render_context(FreeTypeRenderCtx ctx); typedef struct FontConfigFace { char *path; int index; int hinting; int hintstyle; } FontConfigFace; bool information_for_font_family(const char *family, bool bold, bool italic, FontConfigFace *ans); FT_Face native_face_from_path(const char *path, int index); bool fallback_font(char_type ch, const char *family, bool bold, bool italic, bool prefer_color, FontConfigFace *ans); bool freetype_convert_mono_bitmap(FT_Bitmap *src, FT_Bitmap *dest); FT_Library freetype_library(void); void set_freetype_error(const char* prefix, int err_code); int downsample_32bit_image(uint8_t *src, unsigned src_width, unsigned src_height, unsigned src_stride, uint8_t *dest, unsigned dest_width, unsigned dest_height); kitty-0.41.1/kitty/gl-wrapper.c0000664000175000017510000000006714773370543015702 0ustar nileshnilesh#define GLAD_GL_IMPLEMENTATION #include "gl-wrapper.h" kitty-0.41.1/kitty/gl-wrapper.h0000664000175000017510000256667014773370543015732 0ustar nileshnilesh/** * Loader generated by glad 2.0.2 on Tue Dec 20 16:15:26 2022 * * SPDX-License-Identifier: (WTFPL OR CC0-1.0) AND Apache-2.0 * * Generator: C/C++ * Specification: gl * Extensions: 6 * * APIs: * - gl:core=3.1 * * Options: * - ALIAS = False * - DEBUG = True * - HEADER_ONLY = True * - LOADER = False * - MX = False * - ON_DEMAND = False * * Commandline: * --api='gl:core=3.1' --extensions='GL_ARB_copy_image,GL_ARB_instanced_arrays,GL_ARB_multisample,GL_ARB_robustness,GL_ARB_texture_storage,GL_KHR_debug' c --debug --header-only * * Online: * http://glad.sh/#api=gl%3Acore%3D3.1&extensions=GL_ARB_copy_image%2CGL_ARB_instanced_arrays%2CGL_ARB_multisample%2CGL_ARB_robustness%2CGL_ARB_texture_storage%2CGL_KHR_debug&generator=c&options=DEBUG%2CHEADER_ONLY * */ #ifndef GLAD_GL_H_ #define GLAD_GL_H_ #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wreserved-id-macro" #endif #ifdef __gl_h_ #error OpenGL (gl.h) header already included (API: gl), remove previous include! #endif #define __gl_h_ 1 #ifdef __gl3_h_ #error OpenGL (gl3.h) header already included (API: gl), remove previous include! #endif #define __gl3_h_ 1 #ifdef __glext_h_ #error OpenGL (glext.h) header already included (API: gl), remove previous include! #endif #define __glext_h_ 1 #ifdef __gl3ext_h_ #error OpenGL (gl3ext.h) header already included (API: gl), remove previous include! #endif #define __gl3ext_h_ 1 #ifdef __clang__ #pragma clang diagnostic pop #endif #define GLAD_GL #define GLAD_OPTION_GL_DEBUG #define GLAD_OPTION_GL_HEADER_ONLY #ifdef __cplusplus extern "C" { #endif #ifndef GLAD_PLATFORM_H_ #define GLAD_PLATFORM_H_ #ifndef GLAD_PLATFORM_WIN32 #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__MINGW32__) #define GLAD_PLATFORM_WIN32 1 #else #define GLAD_PLATFORM_WIN32 0 #endif #endif #ifndef GLAD_PLATFORM_APPLE #ifdef __APPLE__ #define GLAD_PLATFORM_APPLE 1 #else #define GLAD_PLATFORM_APPLE 0 #endif #endif #ifndef GLAD_PLATFORM_EMSCRIPTEN #ifdef __EMSCRIPTEN__ #define GLAD_PLATFORM_EMSCRIPTEN 1 #else #define GLAD_PLATFORM_EMSCRIPTEN 0 #endif #endif #ifndef GLAD_PLATFORM_UWP #if defined(_MSC_VER) && !defined(GLAD_INTERNAL_HAVE_WINAPIFAMILY) #ifdef __has_include #if __has_include() #define GLAD_INTERNAL_HAVE_WINAPIFAMILY 1 #endif #elif _MSC_VER >= 1700 && !_USING_V110_SDK71_ #define GLAD_INTERNAL_HAVE_WINAPIFAMILY 1 #endif #endif #ifdef GLAD_INTERNAL_HAVE_WINAPIFAMILY #include #if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) #define GLAD_PLATFORM_UWP 1 #endif #endif #ifndef GLAD_PLATFORM_UWP #define GLAD_PLATFORM_UWP 0 #endif #endif #ifdef __GNUC__ #define GLAD_GNUC_EXTENSION __extension__ #else #define GLAD_GNUC_EXTENSION #endif #define GLAD_UNUSED(x) (void)(x) #ifndef GLAD_API_CALL #if defined(GLAD_API_CALL_EXPORT) #if GLAD_PLATFORM_WIN32 || defined(__CYGWIN__) #if defined(GLAD_API_CALL_EXPORT_BUILD) #if defined(__GNUC__) #define GLAD_API_CALL __attribute__ ((dllexport)) extern #else #define GLAD_API_CALL __declspec(dllexport) extern #endif #else #if defined(__GNUC__) #define GLAD_API_CALL __attribute__ ((dllimport)) extern #else #define GLAD_API_CALL __declspec(dllimport) extern #endif #endif #elif defined(__GNUC__) && defined(GLAD_API_CALL_EXPORT_BUILD) #define GLAD_API_CALL __attribute__ ((visibility ("default"))) extern #else #define GLAD_API_CALL extern #endif #else #define GLAD_API_CALL extern #endif #endif #ifdef APIENTRY #define GLAD_API_PTR APIENTRY #elif GLAD_PLATFORM_WIN32 #define GLAD_API_PTR __stdcall #else #define GLAD_API_PTR #endif #ifndef GLAPI #define GLAPI GLAD_API_CALL #endif #ifndef GLAPIENTRY #define GLAPIENTRY GLAD_API_PTR #endif #define GLAD_MAKE_VERSION(major, minor) (major * 10000 + minor) #define GLAD_VERSION_MAJOR(version) (version / 10000) #define GLAD_VERSION_MINOR(version) (version % 10000) #define GLAD_GENERATOR_VERSION "2.0.2" typedef void (*GLADapiproc)(void); typedef GLADapiproc (*GLADloadfunc)(const char *name); typedef GLADapiproc (*GLADuserptrloadfunc)(void *userptr, const char *name); typedef void (*GLADprecallback)(const char *name, GLADapiproc apiproc, int len_args, ...); typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apiproc, int len_args, ...); #endif /* GLAD_PLATFORM_H_ */ #define GL_2D 0x0600 #define GL_2_BYTES 0x1407 #define GL_3D 0x0601 #define GL_3D_COLOR 0x0602 #define GL_3D_COLOR_TEXTURE 0x0603 #define GL_3_BYTES 0x1408 #define GL_4D_COLOR_TEXTURE 0x0604 #define GL_4_BYTES 0x1409 #define GL_ACCUM 0x0100 #define GL_ACCUM_ALPHA_BITS 0x0D5B #define GL_ACCUM_BLUE_BITS 0x0D5A #define GL_ACCUM_BUFFER_BIT 0x00000200 #define GL_ACCUM_CLEAR_VALUE 0x0B80 #define GL_ACCUM_GREEN_BITS 0x0D59 #define GL_ACCUM_RED_BITS 0x0D58 #define GL_ACTIVE_ATTRIBUTES 0x8B89 #define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A #define GL_ACTIVE_TEXTURE 0x84E0 #define GL_ACTIVE_UNIFORMS 0x8B86 #define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 #define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35 #define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 #define GL_ADD 0x0104 #define GL_ADD_SIGNED 0x8574 #define GL_ALIASED_LINE_WIDTH_RANGE 0x846E #define GL_ALIASED_POINT_SIZE_RANGE 0x846D #define GL_ALL_ATTRIB_BITS 0xFFFFFFFF #define GL_ALPHA 0x1906 #define GL_ALPHA12 0x803D #define GL_ALPHA16 0x803E #define GL_ALPHA4 0x803B #define GL_ALPHA8 0x803C #define GL_ALPHA_BIAS 0x0D1D #define GL_ALPHA_BITS 0x0D55 #define GL_ALPHA_INTEGER 0x8D97 #define GL_ALPHA_SCALE 0x0D1C #define GL_ALPHA_TEST 0x0BC0 #define GL_ALPHA_TEST_FUNC 0x0BC1 #define GL_ALPHA_TEST_REF 0x0BC2 #define GL_ALWAYS 0x0207 #define GL_AMBIENT 0x1200 #define GL_AMBIENT_AND_DIFFUSE 0x1602 #define GL_AND 0x1501 #define GL_AND_INVERTED 0x1504 #define GL_AND_REVERSE 0x1502 #define GL_ARRAY_BUFFER 0x8892 #define GL_ARRAY_BUFFER_BINDING 0x8894 #define GL_ATTACHED_SHADERS 0x8B85 #define GL_ATTRIB_STACK_DEPTH 0x0BB0 #define GL_AUTO_NORMAL 0x0D80 #define GL_AUX0 0x0409 #define GL_AUX1 0x040A #define GL_AUX2 0x040B #define GL_AUX3 0x040C #define GL_AUX_BUFFERS 0x0C00 #define GL_BACK 0x0405 #define GL_BACK_LEFT 0x0402 #define GL_BACK_RIGHT 0x0403 #define GL_BGR 0x80E0 #define GL_BGRA 0x80E1 #define GL_BGRA_INTEGER 0x8D9B #define GL_BGR_INTEGER 0x8D9A #define GL_BITMAP 0x1A00 #define GL_BITMAP_TOKEN 0x0704 #define GL_BLEND 0x0BE2 #define GL_BLEND_COLOR 0x8005 #define GL_BLEND_DST 0x0BE0 #define GL_BLEND_DST_ALPHA 0x80CA #define GL_BLEND_DST_RGB 0x80C8 #define GL_BLEND_EQUATION 0x8009 #define GL_BLEND_EQUATION_ALPHA 0x883D #define GL_BLEND_EQUATION_RGB 0x8009 #define GL_BLEND_SRC 0x0BE1 #define GL_BLEND_SRC_ALPHA 0x80CB #define GL_BLEND_SRC_RGB 0x80C9 #define GL_BLUE 0x1905 #define GL_BLUE_BIAS 0x0D1B #define GL_BLUE_BITS 0x0D54 #define GL_BLUE_INTEGER 0x8D96 #define GL_BLUE_SCALE 0x0D1A #define GL_BOOL 0x8B56 #define GL_BOOL_VEC2 0x8B57 #define GL_BOOL_VEC3 0x8B58 #define GL_BOOL_VEC4 0x8B59 #define GL_BUFFER 0x82E0 #define GL_BUFFER_ACCESS 0x88BB #define GL_BUFFER_ACCESS_FLAGS 0x911F #define GL_BUFFER_MAPPED 0x88BC #define GL_BUFFER_MAP_LENGTH 0x9120 #define GL_BUFFER_MAP_OFFSET 0x9121 #define GL_BUFFER_MAP_POINTER 0x88BD #define GL_BUFFER_SIZE 0x8764 #define GL_BUFFER_USAGE 0x8765 #define GL_BYTE 0x1400 #define GL_C3F_V3F 0x2A24 #define GL_C4F_N3F_V3F 0x2A26 #define GL_C4UB_V2F 0x2A22 #define GL_C4UB_V3F 0x2A23 #define GL_CCW 0x0901 #define GL_CLAMP 0x2900 #define GL_CLAMP_FRAGMENT_COLOR 0x891B #define GL_CLAMP_READ_COLOR 0x891C #define GL_CLAMP_TO_BORDER 0x812D #define GL_CLAMP_TO_EDGE 0x812F #define GL_CLAMP_VERTEX_COLOR 0x891A #define GL_CLEAR 0x1500 #define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 #define GL_CLIENT_ALL_ATTRIB_BITS 0xFFFFFFFF #define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 #define GL_CLIENT_PIXEL_STORE_BIT 0x00000001 #define GL_CLIENT_VERTEX_ARRAY_BIT 0x00000002 #define GL_CLIP_DISTANCE0 0x3000 #define GL_CLIP_DISTANCE1 0x3001 #define GL_CLIP_DISTANCE2 0x3002 #define GL_CLIP_DISTANCE3 0x3003 #define GL_CLIP_DISTANCE4 0x3004 #define GL_CLIP_DISTANCE5 0x3005 #define GL_CLIP_DISTANCE6 0x3006 #define GL_CLIP_DISTANCE7 0x3007 #define GL_CLIP_PLANE0 0x3000 #define GL_CLIP_PLANE1 0x3001 #define GL_CLIP_PLANE2 0x3002 #define GL_CLIP_PLANE3 0x3003 #define GL_CLIP_PLANE4 0x3004 #define GL_CLIP_PLANE5 0x3005 #define GL_COEFF 0x0A00 #define GL_COLOR 0x1800 #define GL_COLOR_ARRAY 0x8076 #define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 #define GL_COLOR_ARRAY_POINTER 0x8090 #define GL_COLOR_ARRAY_SIZE 0x8081 #define GL_COLOR_ARRAY_STRIDE 0x8083 #define GL_COLOR_ARRAY_TYPE 0x8082 #define GL_COLOR_ATTACHMENT0 0x8CE0 #define GL_COLOR_ATTACHMENT1 0x8CE1 #define GL_COLOR_ATTACHMENT10 0x8CEA #define GL_COLOR_ATTACHMENT11 0x8CEB #define GL_COLOR_ATTACHMENT12 0x8CEC #define GL_COLOR_ATTACHMENT13 0x8CED #define GL_COLOR_ATTACHMENT14 0x8CEE #define GL_COLOR_ATTACHMENT15 0x8CEF #define GL_COLOR_ATTACHMENT16 0x8CF0 #define GL_COLOR_ATTACHMENT17 0x8CF1 #define GL_COLOR_ATTACHMENT18 0x8CF2 #define GL_COLOR_ATTACHMENT19 0x8CF3 #define GL_COLOR_ATTACHMENT2 0x8CE2 #define GL_COLOR_ATTACHMENT20 0x8CF4 #define GL_COLOR_ATTACHMENT21 0x8CF5 #define GL_COLOR_ATTACHMENT22 0x8CF6 #define GL_COLOR_ATTACHMENT23 0x8CF7 #define GL_COLOR_ATTACHMENT24 0x8CF8 #define GL_COLOR_ATTACHMENT25 0x8CF9 #define GL_COLOR_ATTACHMENT26 0x8CFA #define GL_COLOR_ATTACHMENT27 0x8CFB #define GL_COLOR_ATTACHMENT28 0x8CFC #define GL_COLOR_ATTACHMENT29 0x8CFD #define GL_COLOR_ATTACHMENT3 0x8CE3 #define GL_COLOR_ATTACHMENT30 0x8CFE #define GL_COLOR_ATTACHMENT31 0x8CFF #define GL_COLOR_ATTACHMENT4 0x8CE4 #define GL_COLOR_ATTACHMENT5 0x8CE5 #define GL_COLOR_ATTACHMENT6 0x8CE6 #define GL_COLOR_ATTACHMENT7 0x8CE7 #define GL_COLOR_ATTACHMENT8 0x8CE8 #define GL_COLOR_ATTACHMENT9 0x8CE9 #define GL_COLOR_BUFFER_BIT 0x00004000 #define GL_COLOR_CLEAR_VALUE 0x0C22 #define GL_COLOR_INDEX 0x1900 #define GL_COLOR_INDEXES 0x1603 #define GL_COLOR_LOGIC_OP 0x0BF2 #define GL_COLOR_MATERIAL 0x0B57 #define GL_COLOR_MATERIAL_FACE 0x0B55 #define GL_COLOR_MATERIAL_PARAMETER 0x0B56 #define GL_COLOR_SUM 0x8458 #define GL_COLOR_WRITEMASK 0x0C23 #define GL_COMBINE 0x8570 #define GL_COMBINE_ALPHA 0x8572 #define GL_COMBINE_RGB 0x8571 #define GL_COMPARE_REF_TO_TEXTURE 0x884E #define GL_COMPARE_R_TO_TEXTURE 0x884E #define GL_COMPILE 0x1300 #define GL_COMPILE_AND_EXECUTE 0x1301 #define GL_COMPILE_STATUS 0x8B81 #define GL_COMPRESSED_ALPHA 0x84E9 #define GL_COMPRESSED_INTENSITY 0x84EC #define GL_COMPRESSED_LUMINANCE 0x84EA #define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB #define GL_COMPRESSED_RED 0x8225 #define GL_COMPRESSED_RED_RGTC1 0x8DBB #define GL_COMPRESSED_RG 0x8226 #define GL_COMPRESSED_RGB 0x84ED #define GL_COMPRESSED_RGBA 0x84EE #define GL_COMPRESSED_RG_RGTC2 0x8DBD #define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC #define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE #define GL_COMPRESSED_SLUMINANCE 0x8C4A #define GL_COMPRESSED_SLUMINANCE_ALPHA 0x8C4B #define GL_COMPRESSED_SRGB 0x8C48 #define GL_COMPRESSED_SRGB_ALPHA 0x8C49 #define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 #define GL_CONSTANT 0x8576 #define GL_CONSTANT_ALPHA 0x8003 #define GL_CONSTANT_ATTENUATION 0x1207 #define GL_CONSTANT_COLOR 0x8001 #define GL_CONTEXT_FLAGS 0x821E #define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002 #define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x00000001 #define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT_ARB 0x00000004 #define GL_COORD_REPLACE 0x8862 #define GL_COPY 0x1503 #define GL_COPY_INVERTED 0x150C #define GL_COPY_PIXEL_TOKEN 0x0706 #define GL_COPY_READ_BUFFER 0x8F36 #define GL_COPY_WRITE_BUFFER 0x8F37 #define GL_CULL_FACE 0x0B44 #define GL_CULL_FACE_MODE 0x0B45 #define GL_CURRENT_BIT 0x00000001 #define GL_CURRENT_COLOR 0x0B00 #define GL_CURRENT_FOG_COORD 0x8453 #define GL_CURRENT_FOG_COORDINATE 0x8453 #define GL_CURRENT_INDEX 0x0B01 #define GL_CURRENT_NORMAL 0x0B02 #define GL_CURRENT_PROGRAM 0x8B8D #define GL_CURRENT_QUERY 0x8865 #define GL_CURRENT_RASTER_COLOR 0x0B04 #define GL_CURRENT_RASTER_DISTANCE 0x0B09 #define GL_CURRENT_RASTER_INDEX 0x0B05 #define GL_CURRENT_RASTER_POSITION 0x0B07 #define GL_CURRENT_RASTER_POSITION_VALID 0x0B08 #define GL_CURRENT_RASTER_SECONDARY_COLOR 0x845F #define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06 #define GL_CURRENT_SECONDARY_COLOR 0x8459 #define GL_CURRENT_TEXTURE_COORDS 0x0B03 #define GL_CURRENT_VERTEX_ATTRIB 0x8626 #define GL_CW 0x0900 #define GL_DEBUG_CALLBACK_FUNCTION 0x8244 #define GL_DEBUG_CALLBACK_USER_PARAM 0x8245 #define GL_DEBUG_GROUP_STACK_DEPTH 0x826D #define GL_DEBUG_LOGGED_MESSAGES 0x9145 #define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH 0x8243 #define GL_DEBUG_OUTPUT 0x92E0 #define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 #define GL_DEBUG_SEVERITY_HIGH 0x9146 #define GL_DEBUG_SEVERITY_LOW 0x9148 #define GL_DEBUG_SEVERITY_MEDIUM 0x9147 #define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B #define GL_DEBUG_SOURCE_API 0x8246 #define GL_DEBUG_SOURCE_APPLICATION 0x824A #define GL_DEBUG_SOURCE_OTHER 0x824B #define GL_DEBUG_SOURCE_SHADER_COMPILER 0x8248 #define GL_DEBUG_SOURCE_THIRD_PARTY 0x8249 #define GL_DEBUG_SOURCE_WINDOW_SYSTEM 0x8247 #define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR 0x824D #define GL_DEBUG_TYPE_ERROR 0x824C #define GL_DEBUG_TYPE_MARKER 0x8268 #define GL_DEBUG_TYPE_OTHER 0x8251 #define GL_DEBUG_TYPE_PERFORMANCE 0x8250 #define GL_DEBUG_TYPE_POP_GROUP 0x826A #define GL_DEBUG_TYPE_PORTABILITY 0x824F #define GL_DEBUG_TYPE_PUSH_GROUP 0x8269 #define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E #define GL_DECAL 0x2101 #define GL_DECR 0x1E03 #define GL_DECR_WRAP 0x8508 #define GL_DELETE_STATUS 0x8B80 #define GL_DEPTH 0x1801 #define GL_DEPTH24_STENCIL8 0x88F0 #define GL_DEPTH32F_STENCIL8 0x8CAD #define GL_DEPTH_ATTACHMENT 0x8D00 #define GL_DEPTH_BIAS 0x0D1F #define GL_DEPTH_BITS 0x0D56 #define GL_DEPTH_BUFFER_BIT 0x00000100 #define GL_DEPTH_CLEAR_VALUE 0x0B73 #define GL_DEPTH_COMPONENT 0x1902 #define GL_DEPTH_COMPONENT16 0x81A5 #define GL_DEPTH_COMPONENT24 0x81A6 #define GL_DEPTH_COMPONENT32 0x81A7 #define GL_DEPTH_COMPONENT32F 0x8CAC #define GL_DEPTH_FUNC 0x0B74 #define GL_DEPTH_RANGE 0x0B70 #define GL_DEPTH_SCALE 0x0D1E #define GL_DEPTH_STENCIL 0x84F9 #define GL_DEPTH_STENCIL_ATTACHMENT 0x821A #define GL_DEPTH_TEST 0x0B71 #define GL_DEPTH_TEXTURE_MODE 0x884B #define GL_DEPTH_WRITEMASK 0x0B72 #define GL_DIFFUSE 0x1201 #define GL_DITHER 0x0BD0 #define GL_DOMAIN 0x0A02 #define GL_DONT_CARE 0x1100 #define GL_DOT3_RGB 0x86AE #define GL_DOT3_RGBA 0x86AF #define GL_DOUBLE 0x140A #define GL_DOUBLEBUFFER 0x0C32 #define GL_DRAW_BUFFER 0x0C01 #define GL_DRAW_BUFFER0 0x8825 #define GL_DRAW_BUFFER1 0x8826 #define GL_DRAW_BUFFER10 0x882F #define GL_DRAW_BUFFER11 0x8830 #define GL_DRAW_BUFFER12 0x8831 #define GL_DRAW_BUFFER13 0x8832 #define GL_DRAW_BUFFER14 0x8833 #define GL_DRAW_BUFFER15 0x8834 #define GL_DRAW_BUFFER2 0x8827 #define GL_DRAW_BUFFER3 0x8828 #define GL_DRAW_BUFFER4 0x8829 #define GL_DRAW_BUFFER5 0x882A #define GL_DRAW_BUFFER6 0x882B #define GL_DRAW_BUFFER7 0x882C #define GL_DRAW_BUFFER8 0x882D #define GL_DRAW_BUFFER9 0x882E #define GL_DRAW_FRAMEBUFFER 0x8CA9 #define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6 #define GL_DRAW_PIXEL_TOKEN 0x0705 #define GL_DST_ALPHA 0x0304 #define GL_DST_COLOR 0x0306 #define GL_DYNAMIC_COPY 0x88EA #define GL_DYNAMIC_DRAW 0x88E8 #define GL_DYNAMIC_READ 0x88E9 #define GL_EDGE_FLAG 0x0B43 #define GL_EDGE_FLAG_ARRAY 0x8079 #define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B #define GL_EDGE_FLAG_ARRAY_POINTER 0x8093 #define GL_EDGE_FLAG_ARRAY_STRIDE 0x808C #define GL_ELEMENT_ARRAY_BUFFER 0x8893 #define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 #define GL_EMISSION 0x1600 #define GL_ENABLE_BIT 0x00002000 #define GL_EQUAL 0x0202 #define GL_EQUIV 0x1509 #define GL_EVAL_BIT 0x00010000 #define GL_EXP 0x0800 #define GL_EXP2 0x0801 #define GL_EXTENSIONS 0x1F03 #define GL_EYE_LINEAR 0x2400 #define GL_EYE_PLANE 0x2502 #define GL_FALSE 0 #define GL_FASTEST 0x1101 #define GL_FEEDBACK 0x1C01 #define GL_FEEDBACK_BUFFER_POINTER 0x0DF0 #define GL_FEEDBACK_BUFFER_SIZE 0x0DF1 #define GL_FEEDBACK_BUFFER_TYPE 0x0DF2 #define GL_FILL 0x1B02 #define GL_FIXED_ONLY 0x891D #define GL_FLAT 0x1D00 #define GL_FLOAT 0x1406 #define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD #define GL_FLOAT_MAT2 0x8B5A #define GL_FLOAT_MAT2x3 0x8B65 #define GL_FLOAT_MAT2x4 0x8B66 #define GL_FLOAT_MAT3 0x8B5B #define GL_FLOAT_MAT3x2 0x8B67 #define GL_FLOAT_MAT3x4 0x8B68 #define GL_FLOAT_MAT4 0x8B5C #define GL_FLOAT_MAT4x2 0x8B69 #define GL_FLOAT_MAT4x3 0x8B6A #define GL_FLOAT_VEC2 0x8B50 #define GL_FLOAT_VEC3 0x8B51 #define GL_FLOAT_VEC4 0x8B52 #define GL_FOG 0x0B60 #define GL_FOG_BIT 0x00000080 #define GL_FOG_COLOR 0x0B66 #define GL_FOG_COORD 0x8451 #define GL_FOG_COORDINATE 0x8451 #define GL_FOG_COORDINATE_ARRAY 0x8457 #define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D #define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 #define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 #define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 #define GL_FOG_COORDINATE_SOURCE 0x8450 #define GL_FOG_COORD_ARRAY 0x8457 #define GL_FOG_COORD_ARRAY_BUFFER_BINDING 0x889D #define GL_FOG_COORD_ARRAY_POINTER 0x8456 #define GL_FOG_COORD_ARRAY_STRIDE 0x8455 #define GL_FOG_COORD_ARRAY_TYPE 0x8454 #define GL_FOG_COORD_SRC 0x8450 #define GL_FOG_DENSITY 0x0B62 #define GL_FOG_END 0x0B64 #define GL_FOG_HINT 0x0C54 #define GL_FOG_INDEX 0x0B61 #define GL_FOG_MODE 0x0B65 #define GL_FOG_START 0x0B63 #define GL_FRAGMENT_DEPTH 0x8452 #define GL_FRAGMENT_SHADER 0x8B30 #define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B #define GL_FRAMEBUFFER 0x8D40 #define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 #define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 #define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 #define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 #define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 #define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 #define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 #define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 #define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 #define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 #define GL_FRAMEBUFFER_BINDING 0x8CA6 #define GL_FRAMEBUFFER_COMPLETE 0x8CD5 #define GL_FRAMEBUFFER_DEFAULT 0x8218 #define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 #define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB #define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 #define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 #define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC #define GL_FRAMEBUFFER_SRGB 0x8DB9 #define GL_FRAMEBUFFER_UNDEFINED 0x8219 #define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD #define GL_FRONT 0x0404 #define GL_FRONT_AND_BACK 0x0408 #define GL_FRONT_FACE 0x0B46 #define GL_FRONT_LEFT 0x0400 #define GL_FRONT_RIGHT 0x0401 #define GL_FUNC_ADD 0x8006 #define GL_FUNC_REVERSE_SUBTRACT 0x800B #define GL_FUNC_SUBTRACT 0x800A #define GL_GENERATE_MIPMAP 0x8191 #define GL_GENERATE_MIPMAP_HINT 0x8192 #define GL_GEQUAL 0x0206 #define GL_GREATER 0x0204 #define GL_GREEN 0x1904 #define GL_GREEN_BIAS 0x0D19 #define GL_GREEN_BITS 0x0D53 #define GL_GREEN_INTEGER 0x8D95 #define GL_GREEN_SCALE 0x0D18 #define GL_GUILTY_CONTEXT_RESET_ARB 0x8253 #define GL_HALF_FLOAT 0x140B #define GL_HINT_BIT 0x00008000 #define GL_INCR 0x1E02 #define GL_INCR_WRAP 0x8507 #define GL_INDEX 0x8222 #define GL_INDEX_ARRAY 0x8077 #define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 #define GL_INDEX_ARRAY_POINTER 0x8091 #define GL_INDEX_ARRAY_STRIDE 0x8086 #define GL_INDEX_ARRAY_TYPE 0x8085 #define GL_INDEX_BITS 0x0D51 #define GL_INDEX_CLEAR_VALUE 0x0C20 #define GL_INDEX_LOGIC_OP 0x0BF1 #define GL_INDEX_MODE 0x0C30 #define GL_INDEX_OFFSET 0x0D13 #define GL_INDEX_SHIFT 0x0D12 #define GL_INDEX_WRITEMASK 0x0C21 #define GL_INFO_LOG_LENGTH 0x8B84 #define GL_INNOCENT_CONTEXT_RESET_ARB 0x8254 #define GL_INT 0x1404 #define GL_INTENSITY 0x8049 #define GL_INTENSITY12 0x804C #define GL_INTENSITY16 0x804D #define GL_INTENSITY4 0x804A #define GL_INTENSITY8 0x804B #define GL_INTERLEAVED_ATTRIBS 0x8C8C #define GL_INTERPOLATE 0x8575 #define GL_INT_SAMPLER_1D 0x8DC9 #define GL_INT_SAMPLER_1D_ARRAY 0x8DCE #define GL_INT_SAMPLER_2D 0x8DCA #define GL_INT_SAMPLER_2D_ARRAY 0x8DCF #define GL_INT_SAMPLER_2D_RECT 0x8DCD #define GL_INT_SAMPLER_3D 0x8DCB #define GL_INT_SAMPLER_BUFFER 0x8DD0 #define GL_INT_SAMPLER_CUBE 0x8DCC #define GL_INT_VEC2 0x8B53 #define GL_INT_VEC3 0x8B54 #define GL_INT_VEC4 0x8B55 #define GL_INVALID_ENUM 0x0500 #define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 #define GL_INVALID_INDEX 0xFFFFFFFF #define GL_INVALID_OPERATION 0x0502 #define GL_INVALID_VALUE 0x0501 #define GL_INVERT 0x150A #define GL_KEEP 0x1E00 #define GL_LEFT 0x0406 #define GL_LEQUAL 0x0203 #define GL_LESS 0x0201 #define GL_LIGHT0 0x4000 #define GL_LIGHT1 0x4001 #define GL_LIGHT2 0x4002 #define GL_LIGHT3 0x4003 #define GL_LIGHT4 0x4004 #define GL_LIGHT5 0x4005 #define GL_LIGHT6 0x4006 #define GL_LIGHT7 0x4007 #define GL_LIGHTING 0x0B50 #define GL_LIGHTING_BIT 0x00000040 #define GL_LIGHT_MODEL_AMBIENT 0x0B53 #define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 #define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51 #define GL_LIGHT_MODEL_TWO_SIDE 0x0B52 #define GL_LINE 0x1B01 #define GL_LINEAR 0x2601 #define GL_LINEAR_ATTENUATION 0x1208 #define GL_LINEAR_MIPMAP_LINEAR 0x2703 #define GL_LINEAR_MIPMAP_NEAREST 0x2701 #define GL_LINES 0x0001 #define GL_LINE_BIT 0x00000004 #define GL_LINE_LOOP 0x0002 #define GL_LINE_RESET_TOKEN 0x0707 #define GL_LINE_SMOOTH 0x0B20 #define GL_LINE_SMOOTH_HINT 0x0C52 #define GL_LINE_STIPPLE 0x0B24 #define GL_LINE_STIPPLE_PATTERN 0x0B25 #define GL_LINE_STIPPLE_REPEAT 0x0B26 #define GL_LINE_STRIP 0x0003 #define GL_LINE_TOKEN 0x0702 #define GL_LINE_WIDTH 0x0B21 #define GL_LINE_WIDTH_GRANULARITY 0x0B23 #define GL_LINE_WIDTH_RANGE 0x0B22 #define GL_LINK_STATUS 0x8B82 #define GL_LIST_BASE 0x0B32 #define GL_LIST_BIT 0x00020000 #define GL_LIST_INDEX 0x0B33 #define GL_LIST_MODE 0x0B30 #define GL_LOAD 0x0101 #define GL_LOGIC_OP 0x0BF1 #define GL_LOGIC_OP_MODE 0x0BF0 #define GL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 #define GL_LOWER_LEFT 0x8CA1 #define GL_LUMINANCE 0x1909 #define GL_LUMINANCE12 0x8041 #define GL_LUMINANCE12_ALPHA12 0x8047 #define GL_LUMINANCE12_ALPHA4 0x8046 #define GL_LUMINANCE16 0x8042 #define GL_LUMINANCE16_ALPHA16 0x8048 #define GL_LUMINANCE4 0x803F #define GL_LUMINANCE4_ALPHA4 0x8043 #define GL_LUMINANCE6_ALPHA2 0x8044 #define GL_LUMINANCE8 0x8040 #define GL_LUMINANCE8_ALPHA8 0x8045 #define GL_LUMINANCE_ALPHA 0x190A #define GL_MAJOR_VERSION 0x821B #define GL_MAP1_COLOR_4 0x0D90 #define GL_MAP1_GRID_DOMAIN 0x0DD0 #define GL_MAP1_GRID_SEGMENTS 0x0DD1 #define GL_MAP1_INDEX 0x0D91 #define GL_MAP1_NORMAL 0x0D92 #define GL_MAP1_TEXTURE_COORD_1 0x0D93 #define GL_MAP1_TEXTURE_COORD_2 0x0D94 #define GL_MAP1_TEXTURE_COORD_3 0x0D95 #define GL_MAP1_TEXTURE_COORD_4 0x0D96 #define GL_MAP1_VERTEX_3 0x0D97 #define GL_MAP1_VERTEX_4 0x0D98 #define GL_MAP2_COLOR_4 0x0DB0 #define GL_MAP2_GRID_DOMAIN 0x0DD2 #define GL_MAP2_GRID_SEGMENTS 0x0DD3 #define GL_MAP2_INDEX 0x0DB1 #define GL_MAP2_NORMAL 0x0DB2 #define GL_MAP2_TEXTURE_COORD_1 0x0DB3 #define GL_MAP2_TEXTURE_COORD_2 0x0DB4 #define GL_MAP2_TEXTURE_COORD_3 0x0DB5 #define GL_MAP2_TEXTURE_COORD_4 0x0DB6 #define GL_MAP2_VERTEX_3 0x0DB7 #define GL_MAP2_VERTEX_4 0x0DB8 #define GL_MAP_COLOR 0x0D10 #define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 #define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 #define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 #define GL_MAP_READ_BIT 0x0001 #define GL_MAP_STENCIL 0x0D11 #define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 #define GL_MAP_WRITE_BIT 0x0002 #define GL_MATRIX_MODE 0x0BA0 #define GL_MAX 0x8008 #define GL_MAX_3D_TEXTURE_SIZE 0x8073 #define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF #define GL_MAX_ATTRIB_STACK_DEPTH 0x0D35 #define GL_MAX_CLIENT_ATTRIB_STACK_DEPTH 0x0D3B #define GL_MAX_CLIP_DISTANCES 0x0D32 #define GL_MAX_CLIP_PLANES 0x0D32 #define GL_MAX_COLOR_ATTACHMENTS 0x8CDF #define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33 #define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS 0x8A32 #define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D #define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E #define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31 #define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C #define GL_MAX_DEBUG_GROUP_STACK_DEPTH 0x826C #define GL_MAX_DEBUG_LOGGED_MESSAGES 0x9144 #define GL_MAX_DEBUG_MESSAGE_LENGTH 0x9143 #define GL_MAX_DRAW_BUFFERS 0x8824 #define GL_MAX_ELEMENTS_INDICES 0x80E9 #define GL_MAX_ELEMENTS_VERTICES 0x80E8 #define GL_MAX_EVAL_ORDER 0x0D30 #define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D #define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 #define GL_MAX_GEOMETRY_UNIFORM_BLOCKS 0x8A2C #define GL_MAX_LABEL_LENGTH 0x82E8 #define GL_MAX_LIGHTS 0x0D31 #define GL_MAX_LIST_NESTING 0x0B31 #define GL_MAX_MODELVIEW_STACK_DEPTH 0x0D36 #define GL_MAX_NAME_STACK_DEPTH 0x0D37 #define GL_MAX_PIXEL_MAP_TABLE 0x0D34 #define GL_MAX_PROGRAM_TEXEL_OFFSET 0x8905 #define GL_MAX_PROJECTION_STACK_DEPTH 0x0D38 #define GL_MAX_RECTANGLE_TEXTURE_SIZE 0x84F8 #define GL_MAX_RENDERBUFFER_SIZE 0x84E8 #define GL_MAX_SAMPLES 0x8D57 #define GL_MAX_TEXTURE_BUFFER_SIZE 0x8C2B #define GL_MAX_TEXTURE_COORDS 0x8871 #define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 #define GL_MAX_TEXTURE_LOD_BIAS 0x84FD #define GL_MAX_TEXTURE_SIZE 0x0D33 #define GL_MAX_TEXTURE_STACK_DEPTH 0x0D39 #define GL_MAX_TEXTURE_UNITS 0x84E2 #define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 0x8C8A #define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS 0x8C8B #define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 0x8C80 #define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30 #define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F #define GL_MAX_VARYING_COMPONENTS 0x8B4B #define GL_MAX_VARYING_FLOATS 0x8B4B #define GL_MAX_VERTEX_ATTRIBS 0x8869 #define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C #define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B #define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A #define GL_MAX_VIEWPORT_DIMS 0x0D3A #define GL_MIN 0x8007 #define GL_MINOR_VERSION 0x821C #define GL_MIN_PROGRAM_TEXEL_OFFSET 0x8904 #define GL_MIRRORED_REPEAT 0x8370 #define GL_MODELVIEW 0x1700 #define GL_MODELVIEW_MATRIX 0x0BA6 #define GL_MODELVIEW_STACK_DEPTH 0x0BA3 #define GL_MODULATE 0x2100 #define GL_MULT 0x0103 #define GL_MULTISAMPLE 0x809D #define GL_MULTISAMPLE_ARB 0x809D #define GL_MULTISAMPLE_BIT 0x20000000 #define GL_MULTISAMPLE_BIT_ARB 0x20000000 #define GL_N3F_V3F 0x2A25 #define GL_NAME_STACK_DEPTH 0x0D70 #define GL_NAND 0x150E #define GL_NEAREST 0x2600 #define GL_NEAREST_MIPMAP_LINEAR 0x2702 #define GL_NEAREST_MIPMAP_NEAREST 0x2700 #define GL_NEVER 0x0200 #define GL_NICEST 0x1102 #define GL_NONE 0 #define GL_NOOP 0x1505 #define GL_NOR 0x1508 #define GL_NORMALIZE 0x0BA1 #define GL_NORMAL_ARRAY 0x8075 #define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 #define GL_NORMAL_ARRAY_POINTER 0x808F #define GL_NORMAL_ARRAY_STRIDE 0x807F #define GL_NORMAL_ARRAY_TYPE 0x807E #define GL_NORMAL_MAP 0x8511 #define GL_NOTEQUAL 0x0205 #define GL_NO_ERROR 0 #define GL_NO_RESET_NOTIFICATION_ARB 0x8261 #define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 #define GL_NUM_EXTENSIONS 0x821D #define GL_OBJECT_LINEAR 0x2401 #define GL_OBJECT_PLANE 0x2501 #define GL_ONE 1 #define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 #define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 #define GL_ONE_MINUS_DST_ALPHA 0x0305 #define GL_ONE_MINUS_DST_COLOR 0x0307 #define GL_ONE_MINUS_SRC_ALPHA 0x0303 #define GL_ONE_MINUS_SRC_COLOR 0x0301 #define GL_OPERAND0_ALPHA 0x8598 #define GL_OPERAND0_RGB 0x8590 #define GL_OPERAND1_ALPHA 0x8599 #define GL_OPERAND1_RGB 0x8591 #define GL_OPERAND2_ALPHA 0x859A #define GL_OPERAND2_RGB 0x8592 #define GL_OR 0x1507 #define GL_ORDER 0x0A01 #define GL_OR_INVERTED 0x150D #define GL_OR_REVERSE 0x150B #define GL_OUT_OF_MEMORY 0x0505 #define GL_PACK_ALIGNMENT 0x0D05 #define GL_PACK_IMAGE_HEIGHT 0x806C #define GL_PACK_LSB_FIRST 0x0D01 #define GL_PACK_ROW_LENGTH 0x0D02 #define GL_PACK_SKIP_IMAGES 0x806B #define GL_PACK_SKIP_PIXELS 0x0D04 #define GL_PACK_SKIP_ROWS 0x0D03 #define GL_PACK_SWAP_BYTES 0x0D00 #define GL_PASS_THROUGH_TOKEN 0x0700 #define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 #define GL_PIXEL_MAP_A_TO_A 0x0C79 #define GL_PIXEL_MAP_A_TO_A_SIZE 0x0CB9 #define GL_PIXEL_MAP_B_TO_B 0x0C78 #define GL_PIXEL_MAP_B_TO_B_SIZE 0x0CB8 #define GL_PIXEL_MAP_G_TO_G 0x0C77 #define GL_PIXEL_MAP_G_TO_G_SIZE 0x0CB7 #define GL_PIXEL_MAP_I_TO_A 0x0C75 #define GL_PIXEL_MAP_I_TO_A_SIZE 0x0CB5 #define GL_PIXEL_MAP_I_TO_B 0x0C74 #define GL_PIXEL_MAP_I_TO_B_SIZE 0x0CB4 #define GL_PIXEL_MAP_I_TO_G 0x0C73 #define GL_PIXEL_MAP_I_TO_G_SIZE 0x0CB3 #define GL_PIXEL_MAP_I_TO_I 0x0C70 #define GL_PIXEL_MAP_I_TO_I_SIZE 0x0CB0 #define GL_PIXEL_MAP_I_TO_R 0x0C72 #define GL_PIXEL_MAP_I_TO_R_SIZE 0x0CB2 #define GL_PIXEL_MAP_R_TO_R 0x0C76 #define GL_PIXEL_MAP_R_TO_R_SIZE 0x0CB6 #define GL_PIXEL_MAP_S_TO_S 0x0C71 #define GL_PIXEL_MAP_S_TO_S_SIZE 0x0CB1 #define GL_PIXEL_MODE_BIT 0x00000020 #define GL_PIXEL_PACK_BUFFER 0x88EB #define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED #define GL_PIXEL_UNPACK_BUFFER 0x88EC #define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF #define GL_POINT 0x1B00 #define GL_POINTS 0x0000 #define GL_POINT_BIT 0x00000002 #define GL_POINT_DISTANCE_ATTENUATION 0x8129 #define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 #define GL_POINT_SIZE 0x0B11 #define GL_POINT_SIZE_GRANULARITY 0x0B13 #define GL_POINT_SIZE_MAX 0x8127 #define GL_POINT_SIZE_MIN 0x8126 #define GL_POINT_SIZE_RANGE 0x0B12 #define GL_POINT_SMOOTH 0x0B10 #define GL_POINT_SMOOTH_HINT 0x0C51 #define GL_POINT_SPRITE 0x8861 #define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 #define GL_POINT_TOKEN 0x0701 #define GL_POLYGON 0x0009 #define GL_POLYGON_BIT 0x00000008 #define GL_POLYGON_MODE 0x0B40 #define GL_POLYGON_OFFSET_FACTOR 0x8038 #define GL_POLYGON_OFFSET_FILL 0x8037 #define GL_POLYGON_OFFSET_LINE 0x2A02 #define GL_POLYGON_OFFSET_POINT 0x2A01 #define GL_POLYGON_OFFSET_UNITS 0x2A00 #define GL_POLYGON_SMOOTH 0x0B41 #define GL_POLYGON_SMOOTH_HINT 0x0C53 #define GL_POLYGON_STIPPLE 0x0B42 #define GL_POLYGON_STIPPLE_BIT 0x00000010 #define GL_POLYGON_TOKEN 0x0703 #define GL_POSITION 0x1203 #define GL_PREVIOUS 0x8578 #define GL_PRIMARY_COLOR 0x8577 #define GL_PRIMITIVES_GENERATED 0x8C87 #define GL_PRIMITIVE_RESTART 0x8F9D #define GL_PRIMITIVE_RESTART_INDEX 0x8F9E #define GL_PROGRAM 0x82E2 #define GL_PROGRAM_PIPELINE 0x82E4 #define GL_PROJECTION 0x1701 #define GL_PROJECTION_MATRIX 0x0BA7 #define GL_PROJECTION_STACK_DEPTH 0x0BA4 #define GL_PROXY_TEXTURE_1D 0x8063 #define GL_PROXY_TEXTURE_1D_ARRAY 0x8C19 #define GL_PROXY_TEXTURE_2D 0x8064 #define GL_PROXY_TEXTURE_2D_ARRAY 0x8C1B #define GL_PROXY_TEXTURE_3D 0x8070 #define GL_PROXY_TEXTURE_CUBE_MAP 0x851B #define GL_PROXY_TEXTURE_RECTANGLE 0x84F7 #define GL_Q 0x2003 #define GL_QUADRATIC_ATTENUATION 0x1209 #define GL_QUADS 0x0007 #define GL_QUAD_STRIP 0x0008 #define GL_QUERY 0x82E3 #define GL_QUERY_BY_REGION_NO_WAIT 0x8E16 #define GL_QUERY_BY_REGION_WAIT 0x8E15 #define GL_QUERY_COUNTER_BITS 0x8864 #define GL_QUERY_NO_WAIT 0x8E14 #define GL_QUERY_RESULT 0x8866 #define GL_QUERY_RESULT_AVAILABLE 0x8867 #define GL_QUERY_WAIT 0x8E13 #define GL_R 0x2002 #define GL_R11F_G11F_B10F 0x8C3A #define GL_R16 0x822A #define GL_R16F 0x822D #define GL_R16I 0x8233 #define GL_R16UI 0x8234 #define GL_R16_SNORM 0x8F98 #define GL_R32F 0x822E #define GL_R32I 0x8235 #define GL_R32UI 0x8236 #define GL_R3_G3_B2 0x2A10 #define GL_R8 0x8229 #define GL_R8I 0x8231 #define GL_R8UI 0x8232 #define GL_R8_SNORM 0x8F94 #define GL_RASTERIZER_DISCARD 0x8C89 #define GL_READ_BUFFER 0x0C02 #define GL_READ_FRAMEBUFFER 0x8CA8 #define GL_READ_FRAMEBUFFER_BINDING 0x8CAA #define GL_READ_ONLY 0x88B8 #define GL_READ_WRITE 0x88BA #define GL_RED 0x1903 #define GL_RED_BIAS 0x0D15 #define GL_RED_BITS 0x0D52 #define GL_RED_INTEGER 0x8D94 #define GL_RED_SCALE 0x0D14 #define GL_REFLECTION_MAP 0x8512 #define GL_RENDER 0x1C00 #define GL_RENDERBUFFER 0x8D41 #define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 #define GL_RENDERBUFFER_BINDING 0x8CA7 #define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 #define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 #define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 #define GL_RENDERBUFFER_HEIGHT 0x8D43 #define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 #define GL_RENDERBUFFER_RED_SIZE 0x8D50 #define GL_RENDERBUFFER_SAMPLES 0x8CAB #define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 #define GL_RENDERBUFFER_WIDTH 0x8D42 #define GL_RENDERER 0x1F01 #define GL_RENDER_MODE 0x0C40 #define GL_REPEAT 0x2901 #define GL_REPLACE 0x1E01 #define GL_RESCALE_NORMAL 0x803A #define GL_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 #define GL_RETURN 0x0102 #define GL_RG 0x8227 #define GL_RG16 0x822C #define GL_RG16F 0x822F #define GL_RG16I 0x8239 #define GL_RG16UI 0x823A #define GL_RG16_SNORM 0x8F99 #define GL_RG32F 0x8230 #define GL_RG32I 0x823B #define GL_RG32UI 0x823C #define GL_RG8 0x822B #define GL_RG8I 0x8237 #define GL_RG8UI 0x8238 #define GL_RG8_SNORM 0x8F95 #define GL_RGB 0x1907 #define GL_RGB10 0x8052 #define GL_RGB10_A2 0x8059 #define GL_RGB12 0x8053 #define GL_RGB16 0x8054 #define GL_RGB16F 0x881B #define GL_RGB16I 0x8D89 #define GL_RGB16UI 0x8D77 #define GL_RGB16_SNORM 0x8F9A #define GL_RGB32F 0x8815 #define GL_RGB32I 0x8D83 #define GL_RGB32UI 0x8D71 #define GL_RGB4 0x804F #define GL_RGB5 0x8050 #define GL_RGB5_A1 0x8057 #define GL_RGB8 0x8051 #define GL_RGB8I 0x8D8F #define GL_RGB8UI 0x8D7D #define GL_RGB8_SNORM 0x8F96 #define GL_RGB9_E5 0x8C3D #define GL_RGBA 0x1908 #define GL_RGBA12 0x805A #define GL_RGBA16 0x805B #define GL_RGBA16F 0x881A #define GL_RGBA16I 0x8D88 #define GL_RGBA16UI 0x8D76 #define GL_RGBA16_SNORM 0x8F9B #define GL_RGBA2 0x8055 #define GL_RGBA32F 0x8814 #define GL_RGBA32I 0x8D82 #define GL_RGBA32UI 0x8D70 #define GL_RGBA4 0x8056 #define GL_RGBA8 0x8058 #define GL_RGBA8I 0x8D8E #define GL_RGBA8UI 0x8D7C #define GL_RGBA8_SNORM 0x8F97 #define GL_RGBA_INTEGER 0x8D99 #define GL_RGBA_MODE 0x0C31 #define GL_RGB_INTEGER 0x8D98 #define GL_RGB_SCALE 0x8573 #define GL_RG_INTEGER 0x8228 #define GL_RIGHT 0x0407 #define GL_S 0x2000 #define GL_SAMPLER 0x82E6 #define GL_SAMPLER_1D 0x8B5D #define GL_SAMPLER_1D_ARRAY 0x8DC0 #define GL_SAMPLER_1D_ARRAY_SHADOW 0x8DC3 #define GL_SAMPLER_1D_SHADOW 0x8B61 #define GL_SAMPLER_2D 0x8B5E #define GL_SAMPLER_2D_ARRAY 0x8DC1 #define GL_SAMPLER_2D_ARRAY_SHADOW 0x8DC4 #define GL_SAMPLER_2D_RECT 0x8B63 #define GL_SAMPLER_2D_RECT_SHADOW 0x8B64 #define GL_SAMPLER_2D_SHADOW 0x8B62 #define GL_SAMPLER_3D 0x8B5F #define GL_SAMPLER_BUFFER 0x8DC2 #define GL_SAMPLER_CUBE 0x8B60 #define GL_SAMPLER_CUBE_SHADOW 0x8DC5 #define GL_SAMPLES 0x80A9 #define GL_SAMPLES_ARB 0x80A9 #define GL_SAMPLES_PASSED 0x8914 #define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E #define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E #define GL_SAMPLE_ALPHA_TO_ONE 0x809F #define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F #define GL_SAMPLE_BUFFERS 0x80A8 #define GL_SAMPLE_BUFFERS_ARB 0x80A8 #define GL_SAMPLE_COVERAGE 0x80A0 #define GL_SAMPLE_COVERAGE_ARB 0x80A0 #define GL_SAMPLE_COVERAGE_INVERT 0x80AB #define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB #define GL_SAMPLE_COVERAGE_VALUE 0x80AA #define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA #define GL_SCISSOR_BIT 0x00080000 #define GL_SCISSOR_BOX 0x0C10 #define GL_SCISSOR_TEST 0x0C11 #define GL_SECONDARY_COLOR_ARRAY 0x845E #define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C #define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D #define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A #define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C #define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B #define GL_SELECT 0x1C02 #define GL_SELECTION_BUFFER_POINTER 0x0DF3 #define GL_SELECTION_BUFFER_SIZE 0x0DF4 #define GL_SEPARATE_ATTRIBS 0x8C8D #define GL_SEPARATE_SPECULAR_COLOR 0x81FA #define GL_SET 0x150F #define GL_SHADER 0x82E1 #define GL_SHADER_SOURCE_LENGTH 0x8B88 #define GL_SHADER_TYPE 0x8B4F #define GL_SHADE_MODEL 0x0B54 #define GL_SHADING_LANGUAGE_VERSION 0x8B8C #define GL_SHININESS 0x1601 #define GL_SHORT 0x1402 #define GL_SIGNED_NORMALIZED 0x8F9C #define GL_SINGLE_COLOR 0x81F9 #define GL_SLUMINANCE 0x8C46 #define GL_SLUMINANCE8 0x8C47 #define GL_SLUMINANCE8_ALPHA8 0x8C45 #define GL_SLUMINANCE_ALPHA 0x8C44 #define GL_SMOOTH 0x1D01 #define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 #define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 #define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 #define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 #define GL_SOURCE0_ALPHA 0x8588 #define GL_SOURCE0_RGB 0x8580 #define GL_SOURCE1_ALPHA 0x8589 #define GL_SOURCE1_RGB 0x8581 #define GL_SOURCE2_ALPHA 0x858A #define GL_SOURCE2_RGB 0x8582 #define GL_SPECULAR 0x1202 #define GL_SPHERE_MAP 0x2402 #define GL_SPOT_CUTOFF 0x1206 #define GL_SPOT_DIRECTION 0x1204 #define GL_SPOT_EXPONENT 0x1205 #define GL_SRC0_ALPHA 0x8588 #define GL_SRC0_RGB 0x8580 #define GL_SRC1_ALPHA 0x8589 #define GL_SRC1_RGB 0x8581 #define GL_SRC2_ALPHA 0x858A #define GL_SRC2_RGB 0x8582 #define GL_SRC_ALPHA 0x0302 #define GL_SRC_ALPHA_SATURATE 0x0308 #define GL_SRC_COLOR 0x0300 #define GL_SRGB 0x8C40 #define GL_SRGB8 0x8C41 #define GL_SRGB8_ALPHA8 0x8C43 #define GL_SRGB_ALPHA 0x8C42 #define GL_STACK_OVERFLOW 0x0503 #define GL_STACK_UNDERFLOW 0x0504 #define GL_STATIC_COPY 0x88E6 #define GL_STATIC_DRAW 0x88E4 #define GL_STATIC_READ 0x88E5 #define GL_STENCIL 0x1802 #define GL_STENCIL_ATTACHMENT 0x8D20 #define GL_STENCIL_BACK_FAIL 0x8801 #define GL_STENCIL_BACK_FUNC 0x8800 #define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 #define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 #define GL_STENCIL_BACK_REF 0x8CA3 #define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 #define GL_STENCIL_BACK_WRITEMASK 0x8CA5 #define GL_STENCIL_BITS 0x0D57 #define GL_STENCIL_BUFFER_BIT 0x00000400 #define GL_STENCIL_CLEAR_VALUE 0x0B91 #define GL_STENCIL_FAIL 0x0B94 #define GL_STENCIL_FUNC 0x0B92 #define GL_STENCIL_INDEX 0x1901 #define GL_STENCIL_INDEX1 0x8D46 #define GL_STENCIL_INDEX16 0x8D49 #define GL_STENCIL_INDEX4 0x8D47 #define GL_STENCIL_INDEX8 0x8D48 #define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 #define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 #define GL_STENCIL_REF 0x0B97 #define GL_STENCIL_TEST 0x0B90 #define GL_STENCIL_VALUE_MASK 0x0B93 #define GL_STENCIL_WRITEMASK 0x0B98 #define GL_STEREO 0x0C33 #define GL_STREAM_COPY 0x88E2 #define GL_STREAM_DRAW 0x88E0 #define GL_STREAM_READ 0x88E1 #define GL_SUBPIXEL_BITS 0x0D50 #define GL_SUBTRACT 0x84E7 #define GL_T 0x2001 #define GL_T2F_C3F_V3F 0x2A2A #define GL_T2F_C4F_N3F_V3F 0x2A2C #define GL_T2F_C4UB_V3F 0x2A29 #define GL_T2F_N3F_V3F 0x2A2B #define GL_T2F_V3F 0x2A27 #define GL_T4F_C4F_N3F_V4F 0x2A2D #define GL_T4F_V4F 0x2A28 #define GL_TEXTURE 0x1702 #define GL_TEXTURE0 0x84C0 #define GL_TEXTURE1 0x84C1 #define GL_TEXTURE10 0x84CA #define GL_TEXTURE11 0x84CB #define GL_TEXTURE12 0x84CC #define GL_TEXTURE13 0x84CD #define GL_TEXTURE14 0x84CE #define GL_TEXTURE15 0x84CF #define GL_TEXTURE16 0x84D0 #define GL_TEXTURE17 0x84D1 #define GL_TEXTURE18 0x84D2 #define GL_TEXTURE19 0x84D3 #define GL_TEXTURE2 0x84C2 #define GL_TEXTURE20 0x84D4 #define GL_TEXTURE21 0x84D5 #define GL_TEXTURE22 0x84D6 #define GL_TEXTURE23 0x84D7 #define GL_TEXTURE24 0x84D8 #define GL_TEXTURE25 0x84D9 #define GL_TEXTURE26 0x84DA #define GL_TEXTURE27 0x84DB #define GL_TEXTURE28 0x84DC #define GL_TEXTURE29 0x84DD #define GL_TEXTURE3 0x84C3 #define GL_TEXTURE30 0x84DE #define GL_TEXTURE31 0x84DF #define GL_TEXTURE4 0x84C4 #define GL_TEXTURE5 0x84C5 #define GL_TEXTURE6 0x84C6 #define GL_TEXTURE7 0x84C7 #define GL_TEXTURE8 0x84C8 #define GL_TEXTURE9 0x84C9 #define GL_TEXTURE_1D 0x0DE0 #define GL_TEXTURE_1D_ARRAY 0x8C18 #define GL_TEXTURE_2D 0x0DE1 #define GL_TEXTURE_2D_ARRAY 0x8C1A #define GL_TEXTURE_3D 0x806F #define GL_TEXTURE_ALPHA_SIZE 0x805F #define GL_TEXTURE_ALPHA_TYPE 0x8C13 #define GL_TEXTURE_BASE_LEVEL 0x813C #define GL_TEXTURE_BINDING_1D 0x8068 #define GL_TEXTURE_BINDING_1D_ARRAY 0x8C1C #define GL_TEXTURE_BINDING_2D 0x8069 #define GL_TEXTURE_BINDING_2D_ARRAY 0x8C1D #define GL_TEXTURE_BINDING_3D 0x806A #define GL_TEXTURE_BINDING_BUFFER 0x8C2C #define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 #define GL_TEXTURE_BINDING_RECTANGLE 0x84F6 #define GL_TEXTURE_BIT 0x00040000 #define GL_TEXTURE_BLUE_SIZE 0x805E #define GL_TEXTURE_BLUE_TYPE 0x8C12 #define GL_TEXTURE_BORDER 0x1005 #define GL_TEXTURE_BORDER_COLOR 0x1004 #define GL_TEXTURE_BUFFER 0x8C2A #define GL_TEXTURE_BUFFER_DATA_STORE_BINDING 0x8C2D #define GL_TEXTURE_COMPARE_FUNC 0x884D #define GL_TEXTURE_COMPARE_MODE 0x884C #define GL_TEXTURE_COMPONENTS 0x1003 #define GL_TEXTURE_COMPRESSED 0x86A1 #define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 #define GL_TEXTURE_COMPRESSION_HINT 0x84EF #define GL_TEXTURE_COORD_ARRAY 0x8078 #define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A #define GL_TEXTURE_COORD_ARRAY_POINTER 0x8092 #define GL_TEXTURE_COORD_ARRAY_SIZE 0x8088 #define GL_TEXTURE_COORD_ARRAY_STRIDE 0x808A #define GL_TEXTURE_COORD_ARRAY_TYPE 0x8089 #define GL_TEXTURE_CUBE_MAP 0x8513 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A #define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 #define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 #define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 #define GL_TEXTURE_DEPTH 0x8071 #define GL_TEXTURE_DEPTH_SIZE 0x884A #define GL_TEXTURE_DEPTH_TYPE 0x8C16 #define GL_TEXTURE_ENV 0x2300 #define GL_TEXTURE_ENV_COLOR 0x2201 #define GL_TEXTURE_ENV_MODE 0x2200 #define GL_TEXTURE_FILTER_CONTROL 0x8500 #define GL_TEXTURE_GEN_MODE 0x2500 #define GL_TEXTURE_GEN_Q 0x0C63 #define GL_TEXTURE_GEN_R 0x0C62 #define GL_TEXTURE_GEN_S 0x0C60 #define GL_TEXTURE_GEN_T 0x0C61 #define GL_TEXTURE_GREEN_SIZE 0x805D #define GL_TEXTURE_GREEN_TYPE 0x8C11 #define GL_TEXTURE_HEIGHT 0x1001 #define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F #define GL_TEXTURE_INTENSITY_SIZE 0x8061 #define GL_TEXTURE_INTENSITY_TYPE 0x8C15 #define GL_TEXTURE_INTERNAL_FORMAT 0x1003 #define GL_TEXTURE_LOD_BIAS 0x8501 #define GL_TEXTURE_LUMINANCE_SIZE 0x8060 #define GL_TEXTURE_LUMINANCE_TYPE 0x8C14 #define GL_TEXTURE_MAG_FILTER 0x2800 #define GL_TEXTURE_MATRIX 0x0BA8 #define GL_TEXTURE_MAX_LEVEL 0x813D #define GL_TEXTURE_MAX_LOD 0x813B #define GL_TEXTURE_MIN_FILTER 0x2801 #define GL_TEXTURE_MIN_LOD 0x813A #define GL_TEXTURE_PRIORITY 0x8066 #define GL_TEXTURE_RECTANGLE 0x84F5 #define GL_TEXTURE_RED_SIZE 0x805C #define GL_TEXTURE_RED_TYPE 0x8C10 #define GL_TEXTURE_RESIDENT 0x8067 #define GL_TEXTURE_SHARED_SIZE 0x8C3F #define GL_TEXTURE_STACK_DEPTH 0x0BA5 #define GL_TEXTURE_STENCIL_SIZE 0x88F1 #define GL_TEXTURE_WIDTH 0x1000 #define GL_TEXTURE_WRAP_R 0x8072 #define GL_TEXTURE_WRAP_S 0x2802 #define GL_TEXTURE_WRAP_T 0x2803 #define GL_TRANSFORM_BIT 0x00001000 #define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E #define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING 0x8C8F #define GL_TRANSFORM_FEEDBACK_BUFFER_MODE 0x8C7F #define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE 0x8C85 #define GL_TRANSFORM_FEEDBACK_BUFFER_START 0x8C84 #define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88 #define GL_TRANSFORM_FEEDBACK_VARYINGS 0x8C83 #define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 0x8C76 #define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 #define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 #define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 #define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 #define GL_TRIANGLES 0x0004 #define GL_TRIANGLE_FAN 0x0006 #define GL_TRIANGLE_STRIP 0x0005 #define GL_TRUE 1 #define GL_UNIFORM_ARRAY_STRIDE 0x8A3C #define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42 #define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43 #define GL_UNIFORM_BLOCK_BINDING 0x8A3F #define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40 #define GL_UNIFORM_BLOCK_INDEX 0x8A3A #define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41 #define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46 #define GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER 0x8A45 #define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44 #define GL_UNIFORM_BUFFER 0x8A11 #define GL_UNIFORM_BUFFER_BINDING 0x8A28 #define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 #define GL_UNIFORM_BUFFER_SIZE 0x8A2A #define GL_UNIFORM_BUFFER_START 0x8A29 #define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E #define GL_UNIFORM_MATRIX_STRIDE 0x8A3D #define GL_UNIFORM_NAME_LENGTH 0x8A39 #define GL_UNIFORM_OFFSET 0x8A3B #define GL_UNIFORM_SIZE 0x8A38 #define GL_UNIFORM_TYPE 0x8A37 #define GL_UNKNOWN_CONTEXT_RESET_ARB 0x8255 #define GL_UNPACK_ALIGNMENT 0x0CF5 #define GL_UNPACK_IMAGE_HEIGHT 0x806E #define GL_UNPACK_LSB_FIRST 0x0CF1 #define GL_UNPACK_ROW_LENGTH 0x0CF2 #define GL_UNPACK_SKIP_IMAGES 0x806D #define GL_UNPACK_SKIP_PIXELS 0x0CF4 #define GL_UNPACK_SKIP_ROWS 0x0CF3 #define GL_UNPACK_SWAP_BYTES 0x0CF0 #define GL_UNSIGNED_BYTE 0x1401 #define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 #define GL_UNSIGNED_BYTE_3_3_2 0x8032 #define GL_UNSIGNED_INT 0x1405 #define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B #define GL_UNSIGNED_INT_10_10_10_2 0x8036 #define GL_UNSIGNED_INT_24_8 0x84FA #define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 #define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E #define GL_UNSIGNED_INT_8_8_8_8 0x8035 #define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 #define GL_UNSIGNED_INT_SAMPLER_1D 0x8DD1 #define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY 0x8DD6 #define GL_UNSIGNED_INT_SAMPLER_2D 0x8DD2 #define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY 0x8DD7 #define GL_UNSIGNED_INT_SAMPLER_2D_RECT 0x8DD5 #define GL_UNSIGNED_INT_SAMPLER_3D 0x8DD3 #define GL_UNSIGNED_INT_SAMPLER_BUFFER 0x8DD8 #define GL_UNSIGNED_INT_SAMPLER_CUBE 0x8DD4 #define GL_UNSIGNED_INT_VEC2 0x8DC6 #define GL_UNSIGNED_INT_VEC3 0x8DC7 #define GL_UNSIGNED_INT_VEC4 0x8DC8 #define GL_UNSIGNED_NORMALIZED 0x8C17 #define GL_UNSIGNED_SHORT 0x1403 #define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 #define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 #define GL_UNSIGNED_SHORT_5_6_5 0x8363 #define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 #define GL_UPPER_LEFT 0x8CA2 #define GL_V2F 0x2A20 #define GL_V3F 0x2A21 #define GL_VALIDATE_STATUS 0x8B83 #define GL_VENDOR 0x1F00 #define GL_VERSION 0x1F02 #define GL_VERTEX_ARRAY 0x8074 #define GL_VERTEX_ARRAY_BINDING 0x85B5 #define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 #define GL_VERTEX_ARRAY_POINTER 0x808E #define GL_VERTEX_ARRAY_SIZE 0x807A #define GL_VERTEX_ARRAY_STRIDE 0x807C #define GL_VERTEX_ARRAY_TYPE 0x807B #define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F #define GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ARB 0x88FE #define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 #define GL_VERTEX_ATTRIB_ARRAY_INTEGER 0x88FD #define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A #define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 #define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 #define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 #define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 #define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 #define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 #define GL_VERTEX_SHADER 0x8B31 #define GL_VIEWPORT 0x0BA2 #define GL_VIEWPORT_BIT 0x00000800 #define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E #define GL_WRITE_ONLY 0x88B9 #define GL_XOR 0x1506 #define GL_ZERO 0 #define GL_ZOOM_X 0x0D16 #define GL_ZOOM_Y 0x0D17 #ifndef __khrplatform_h_ #define __khrplatform_h_ /* ** Copyright (c) 2008-2018 The Khronos Group Inc. ** ** Permission is hereby granted, free of charge, to any person obtaining a ** copy of this software and/or associated documentation files (the ** "Materials"), to deal in the Materials without restriction, including ** without limitation the rights to use, copy, modify, merge, publish, ** distribute, sublicense, and/or sell copies of the Materials, and to ** permit persons to whom the Materials are furnished to do so, subject to ** the following conditions: ** ** The above copyright notice and this permission notice shall be included ** in all copies or substantial portions of the Materials. ** ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. */ /* Khronos platform-specific types and definitions. * * The master copy of khrplatform.h is maintained in the Khronos EGL * Registry repository at https://github.com/KhronosGroup/EGL-Registry * The last semantic modification to khrplatform.h was at commit ID: * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 * * Adopters may modify this file to suit their platform. Adopters are * encouraged to submit platform specific modifications to the Khronos * group so that they can be included in future versions of this file. * Please submit changes by filing pull requests or issues on * the EGL Registry repository linked above. * * * See the Implementer's Guidelines for information about where this file * should be located on your system and for more details of its use: * http://www.khronos.org/registry/implementers_guide.pdf * * This file should be included as * #include * by Khronos client API header files that use its types and defines. * * The types in khrplatform.h should only be used to define API-specific types. * * Types defined in khrplatform.h: * khronos_int8_t signed 8 bit * khronos_uint8_t unsigned 8 bit * khronos_int16_t signed 16 bit * khronos_uint16_t unsigned 16 bit * khronos_int32_t signed 32 bit * khronos_uint32_t unsigned 32 bit * khronos_int64_t signed 64 bit * khronos_uint64_t unsigned 64 bit * khronos_intptr_t signed same number of bits as a pointer * khronos_uintptr_t unsigned same number of bits as a pointer * khronos_ssize_t signed size * khronos_usize_t unsigned size * khronos_float_t signed 32 bit floating point * khronos_time_ns_t unsigned 64 bit time in nanoseconds * khronos_utime_nanoseconds_t unsigned time interval or absolute time in * nanoseconds * khronos_stime_nanoseconds_t signed time interval in nanoseconds * khronos_boolean_enum_t enumerated boolean type. This should * only be used as a base type when a client API's boolean type is * an enum. Client APIs which use an integer or other type for * booleans cannot use this as the base type for their boolean. * * Tokens defined in khrplatform.h: * * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. * * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. * * Calling convention macros defined in this file: * KHRONOS_APICALL * KHRONOS_GLAD_API_PTR * KHRONOS_APIATTRIBUTES * * These may be used in function prototypes as: * * KHRONOS_APICALL void KHRONOS_GLAD_API_PTR funcname( * int arg1, * int arg2) KHRONOS_APIATTRIBUTES; */ #if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) # define KHRONOS_STATIC 1 #endif /*------------------------------------------------------------------------- * Definition of KHRONOS_APICALL *------------------------------------------------------------------------- * This precedes the return type of the function in the function prototype. */ #if defined(KHRONOS_STATIC) /* If the preprocessor constant KHRONOS_STATIC is defined, make the * header compatible with static linking. */ # define KHRONOS_APICALL #elif defined(_WIN32) # define KHRONOS_APICALL __declspec(dllimport) #elif defined (__SYMBIAN32__) # define KHRONOS_APICALL IMPORT_C #elif defined(__ANDROID__) # define KHRONOS_APICALL __attribute__((visibility("default"))) #else # define KHRONOS_APICALL #endif /*------------------------------------------------------------------------- * Definition of KHRONOS_GLAD_API_PTR *------------------------------------------------------------------------- * This follows the return type of the function and precedes the function * name in the function prototype. */ #if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) /* Win32 but not WinCE */ # define KHRONOS_GLAD_API_PTR __stdcall #else # define KHRONOS_GLAD_API_PTR #endif /*------------------------------------------------------------------------- * Definition of KHRONOS_APIATTRIBUTES *------------------------------------------------------------------------- * This follows the closing parenthesis of the function prototype arguments. */ #if defined (__ARMCC_2__) #define KHRONOS_APIATTRIBUTES __softfp #else #define KHRONOS_APIATTRIBUTES #endif /*------------------------------------------------------------------------- * basic type definitions *-----------------------------------------------------------------------*/ #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) /* * Using */ #include typedef int32_t khronos_int32_t; typedef uint32_t khronos_uint32_t; typedef int64_t khronos_int64_t; typedef uint64_t khronos_uint64_t; #define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_FLOAT 1 /* * To support platform where unsigned long cannot be used interchangeably with * inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t. * Ideally, we could just use (u)intptr_t everywhere, but this could result in * ABI breakage if khronos_uintptr_t is changed from unsigned long to * unsigned long long or similar (this results in different C++ name mangling). * To avoid changes for existing platforms, we restrict usage of intptr_t to * platforms where the size of a pointer is larger than the size of long. */ #if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__) #if __SIZEOF_POINTER__ > __SIZEOF_LONG__ #define KHRONOS_USE_INTPTR_T #endif #endif #elif defined(__VMS ) || defined(__sgi) /* * Using */ #include typedef int32_t khronos_int32_t; typedef uint32_t khronos_uint32_t; typedef int64_t khronos_int64_t; typedef uint64_t khronos_uint64_t; #define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_FLOAT 1 #elif defined(_WIN32) && !defined(__SCITECH_SNAP__) /* * Win32 */ typedef __int32 khronos_int32_t; typedef unsigned __int32 khronos_uint32_t; typedef __int64 khronos_int64_t; typedef unsigned __int64 khronos_uint64_t; #define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_FLOAT 1 #elif defined(__sun__) || defined(__digital__) /* * Sun or Digital */ typedef int khronos_int32_t; typedef unsigned int khronos_uint32_t; #if defined(__arch64__) || defined(_LP64) typedef long int khronos_int64_t; typedef unsigned long int khronos_uint64_t; #else typedef long long int khronos_int64_t; typedef unsigned long long int khronos_uint64_t; #endif /* __arch64__ */ #define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_FLOAT 1 #elif 0 /* * Hypothetical platform with no float or int64 support */ typedef int khronos_int32_t; typedef unsigned int khronos_uint32_t; #define KHRONOS_SUPPORT_INT64 0 #define KHRONOS_SUPPORT_FLOAT 0 #else /* * Generic fallback */ #include typedef int32_t khronos_int32_t; typedef uint32_t khronos_uint32_t; typedef int64_t khronos_int64_t; typedef uint64_t khronos_uint64_t; #define KHRONOS_SUPPORT_INT64 1 #define KHRONOS_SUPPORT_FLOAT 1 #endif /* * Types that are (so far) the same on all platforms */ typedef signed char khronos_int8_t; typedef unsigned char khronos_uint8_t; typedef signed short int khronos_int16_t; typedef unsigned short int khronos_uint16_t; /* * Types that differ between LLP64 and LP64 architectures - in LLP64, * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears * to be the only LLP64 architecture in current use. */ #ifdef KHRONOS_USE_INTPTR_T typedef intptr_t khronos_intptr_t; typedef uintptr_t khronos_uintptr_t; #elif defined(_WIN64) typedef signed long long int khronos_intptr_t; typedef unsigned long long int khronos_uintptr_t; #else typedef signed long int khronos_intptr_t; typedef unsigned long int khronos_uintptr_t; #endif #if defined(_WIN64) typedef signed long long int khronos_ssize_t; typedef unsigned long long int khronos_usize_t; #else typedef signed long int khronos_ssize_t; typedef unsigned long int khronos_usize_t; #endif #if KHRONOS_SUPPORT_FLOAT /* * Float type */ typedef float khronos_float_t; #endif #if KHRONOS_SUPPORT_INT64 /* Time types * * These types can be used to represent a time interval in nanoseconds or * an absolute Unadjusted System Time. Unadjusted System Time is the number * of nanoseconds since some arbitrary system event (e.g. since the last * time the system booted). The Unadjusted System Time is an unsigned * 64 bit value that wraps back to 0 every 584 years. Time intervals * may be either signed or unsigned. */ typedef khronos_uint64_t khronos_utime_nanoseconds_t; typedef khronos_int64_t khronos_stime_nanoseconds_t; #endif /* * Dummy value used to pad enum types to 32 bits. */ #ifndef KHRONOS_MAX_ENUM #define KHRONOS_MAX_ENUM 0x7FFFFFFF #endif /* * Enumerated boolean type * * Values other than zero should be considered to be true. Therefore * comparisons should not be made against KHRONOS_TRUE. */ typedef enum { KHRONOS_FALSE = 0, KHRONOS_TRUE = 1, KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM } khronos_boolean_enum_t; #endif /* __khrplatform_h_ */ typedef unsigned int GLenum; typedef unsigned char GLboolean; typedef unsigned int GLbitfield; typedef void GLvoid; typedef khronos_int8_t GLbyte; typedef khronos_uint8_t GLubyte; typedef khronos_int16_t GLshort; typedef khronos_uint16_t GLushort; typedef int GLint; typedef unsigned int GLuint; typedef khronos_int32_t GLclampx; typedef int GLsizei; typedef khronos_float_t GLfloat; typedef khronos_float_t GLclampf; typedef double GLdouble; typedef double GLclampd; typedef void *GLeglClientBufferEXT; typedef void *GLeglImageOES; typedef char GLchar; typedef char GLcharARB; #ifdef __APPLE__ typedef void *GLhandleARB; #else typedef unsigned int GLhandleARB; #endif typedef khronos_uint16_t GLhalf; typedef khronos_uint16_t GLhalfARB; typedef khronos_int32_t GLfixed; #if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060) typedef khronos_intptr_t GLintptr; #else typedef khronos_intptr_t GLintptr; #endif #if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060) typedef khronos_intptr_t GLintptrARB; #else typedef khronos_intptr_t GLintptrARB; #endif #if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060) typedef khronos_ssize_t GLsizeiptr; #else typedef khronos_ssize_t GLsizeiptr; #endif #if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060) typedef khronos_ssize_t GLsizeiptrARB; #else typedef khronos_ssize_t GLsizeiptrARB; #endif typedef khronos_int64_t GLint64; typedef khronos_int64_t GLint64EXT; typedef khronos_uint64_t GLuint64; typedef khronos_uint64_t GLuint64EXT; typedef struct __GLsync *GLsync; struct _cl_context; struct _cl_event; typedef void (GLAD_API_PTR *GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); typedef void (GLAD_API_PTR *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); typedef void (GLAD_API_PTR *GLDEBUGPROCKHR)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); typedef void (GLAD_API_PTR *GLDEBUGPROCAMD)(GLuint id,GLenum category,GLenum severity,GLsizei length,const GLchar *message,void *userParam); typedef unsigned short GLhalfNV; typedef GLintptr GLvdpauSurfaceNV; typedef void (GLAD_API_PTR *GLVULKANPROCNV)(void); #define GL_VERSION_1_0 1 GLAD_API_CALL int GLAD_GL_VERSION_1_0; #define GL_VERSION_1_1 1 GLAD_API_CALL int GLAD_GL_VERSION_1_1; #define GL_VERSION_1_2 1 GLAD_API_CALL int GLAD_GL_VERSION_1_2; #define GL_VERSION_1_3 1 GLAD_API_CALL int GLAD_GL_VERSION_1_3; #define GL_VERSION_1_4 1 GLAD_API_CALL int GLAD_GL_VERSION_1_4; #define GL_VERSION_1_5 1 GLAD_API_CALL int GLAD_GL_VERSION_1_5; #define GL_VERSION_2_0 1 GLAD_API_CALL int GLAD_GL_VERSION_2_0; #define GL_VERSION_2_1 1 GLAD_API_CALL int GLAD_GL_VERSION_2_1; #define GL_VERSION_3_0 1 GLAD_API_CALL int GLAD_GL_VERSION_3_0; #define GL_VERSION_3_1 1 GLAD_API_CALL int GLAD_GL_VERSION_3_1; #define GL_ARB_copy_image 1 GLAD_API_CALL int GLAD_GL_ARB_copy_image; #define GL_ARB_instanced_arrays 1 GLAD_API_CALL int GLAD_GL_ARB_instanced_arrays; #define GL_ARB_multisample 1 GLAD_API_CALL int GLAD_GL_ARB_multisample; #define GL_ARB_robustness 1 GLAD_API_CALL int GLAD_GL_ARB_robustness; #define GL_ARB_texture_storage 1 GLAD_API_CALL int GLAD_GL_ARB_texture_storage; #define GL_KHR_debug 1 GLAD_API_CALL int GLAD_GL_KHR_debug; typedef void (GLAD_API_PTR *PFNGLACCUMPROC)(GLenum op, GLfloat value); typedef void (GLAD_API_PTR *PFNGLACTIVETEXTUREPROC)(GLenum texture); typedef void (GLAD_API_PTR *PFNGLALPHAFUNCPROC)(GLenum func, GLfloat ref); typedef GLboolean (GLAD_API_PTR *PFNGLARETEXTURESRESIDENTPROC)(GLsizei n, const GLuint * textures, GLboolean * residences); typedef void (GLAD_API_PTR *PFNGLARRAYELEMENTPROC)(GLint i); typedef void (GLAD_API_PTR *PFNGLATTACHSHADERPROC)(GLuint program, GLuint shader); typedef void (GLAD_API_PTR *PFNGLBEGINPROC)(GLenum mode); typedef void (GLAD_API_PTR *PFNGLBEGINCONDITIONALRENDERPROC)(GLuint id, GLenum mode); typedef void (GLAD_API_PTR *PFNGLBEGINQUERYPROC)(GLenum target, GLuint id); typedef void (GLAD_API_PTR *PFNGLBEGINTRANSFORMFEEDBACKPROC)(GLenum primitiveMode); typedef void (GLAD_API_PTR *PFNGLBINDATTRIBLOCATIONPROC)(GLuint program, GLuint index, const GLchar * name); typedef void (GLAD_API_PTR *PFNGLBINDBUFFERPROC)(GLenum target, GLuint buffer); typedef void (GLAD_API_PTR *PFNGLBINDBUFFERBASEPROC)(GLenum target, GLuint index, GLuint buffer); typedef void (GLAD_API_PTR *PFNGLBINDBUFFERRANGEPROC)(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); typedef void (GLAD_API_PTR *PFNGLBINDFRAGDATALOCATIONPROC)(GLuint program, GLuint color, const GLchar * name); typedef void (GLAD_API_PTR *PFNGLBINDFRAMEBUFFERPROC)(GLenum target, GLuint framebuffer); typedef void (GLAD_API_PTR *PFNGLBINDRENDERBUFFERPROC)(GLenum target, GLuint renderbuffer); typedef void (GLAD_API_PTR *PFNGLBINDTEXTUREPROC)(GLenum target, GLuint texture); typedef void (GLAD_API_PTR *PFNGLBINDVERTEXARRAYPROC)(GLuint array); typedef void (GLAD_API_PTR *PFNGLBITMAPPROC)(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte * bitmap); typedef void (GLAD_API_PTR *PFNGLBLENDCOLORPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); typedef void (GLAD_API_PTR *PFNGLBLENDEQUATIONPROC)(GLenum mode); typedef void (GLAD_API_PTR *PFNGLBLENDEQUATIONSEPARATEPROC)(GLenum modeRGB, GLenum modeAlpha); typedef void (GLAD_API_PTR *PFNGLBLENDFUNCPROC)(GLenum sfactor, GLenum dfactor); typedef void (GLAD_API_PTR *PFNGLBLENDFUNCSEPARATEPROC)(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); typedef void (GLAD_API_PTR *PFNGLBLITFRAMEBUFFERPROC)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); typedef void (GLAD_API_PTR *PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size, const void * data, GLenum usage); typedef void (GLAD_API_PTR *PFNGLBUFFERSUBDATAPROC)(GLenum target, GLintptr offset, GLsizeiptr size, const void * data); typedef void (GLAD_API_PTR *PFNGLCALLLISTPROC)(GLuint list); typedef void (GLAD_API_PTR *PFNGLCALLLISTSPROC)(GLsizei n, GLenum type, const void * lists); typedef GLenum (GLAD_API_PTR *PFNGLCHECKFRAMEBUFFERSTATUSPROC)(GLenum target); typedef void (GLAD_API_PTR *PFNGLCLAMPCOLORPROC)(GLenum target, GLenum clamp); typedef void (GLAD_API_PTR *PFNGLCLEARPROC)(GLbitfield mask); typedef void (GLAD_API_PTR *PFNGLCLEARACCUMPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); typedef void (GLAD_API_PTR *PFNGLCLEARBUFFERFIPROC)(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); typedef void (GLAD_API_PTR *PFNGLCLEARBUFFERFVPROC)(GLenum buffer, GLint drawbuffer, const GLfloat * value); typedef void (GLAD_API_PTR *PFNGLCLEARBUFFERIVPROC)(GLenum buffer, GLint drawbuffer, const GLint * value); typedef void (GLAD_API_PTR *PFNGLCLEARBUFFERUIVPROC)(GLenum buffer, GLint drawbuffer, const GLuint * value); typedef void (GLAD_API_PTR *PFNGLCLEARCOLORPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); typedef void (GLAD_API_PTR *PFNGLCLEARDEPTHPROC)(GLdouble depth); typedef void (GLAD_API_PTR *PFNGLCLEARINDEXPROC)(GLfloat c); typedef void (GLAD_API_PTR *PFNGLCLEARSTENCILPROC)(GLint s); typedef void (GLAD_API_PTR *PFNGLCLIENTACTIVETEXTUREPROC)(GLenum texture); typedef void (GLAD_API_PTR *PFNGLCLIPPLANEPROC)(GLenum plane, const GLdouble * equation); typedef void (GLAD_API_PTR *PFNGLCOLOR3BPROC)(GLbyte red, GLbyte green, GLbyte blue); typedef void (GLAD_API_PTR *PFNGLCOLOR3BVPROC)(const GLbyte * v); typedef void (GLAD_API_PTR *PFNGLCOLOR3DPROC)(GLdouble red, GLdouble green, GLdouble blue); typedef void (GLAD_API_PTR *PFNGLCOLOR3DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLCOLOR3FPROC)(GLfloat red, GLfloat green, GLfloat blue); typedef void (GLAD_API_PTR *PFNGLCOLOR3FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLCOLOR3IPROC)(GLint red, GLint green, GLint blue); typedef void (GLAD_API_PTR *PFNGLCOLOR3IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLCOLOR3SPROC)(GLshort red, GLshort green, GLshort blue); typedef void (GLAD_API_PTR *PFNGLCOLOR3SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLCOLOR3UBPROC)(GLubyte red, GLubyte green, GLubyte blue); typedef void (GLAD_API_PTR *PFNGLCOLOR3UBVPROC)(const GLubyte * v); typedef void (GLAD_API_PTR *PFNGLCOLOR3UIPROC)(GLuint red, GLuint green, GLuint blue); typedef void (GLAD_API_PTR *PFNGLCOLOR3UIVPROC)(const GLuint * v); typedef void (GLAD_API_PTR *PFNGLCOLOR3USPROC)(GLushort red, GLushort green, GLushort blue); typedef void (GLAD_API_PTR *PFNGLCOLOR3USVPROC)(const GLushort * v); typedef void (GLAD_API_PTR *PFNGLCOLOR4BPROC)(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); typedef void (GLAD_API_PTR *PFNGLCOLOR4BVPROC)(const GLbyte * v); typedef void (GLAD_API_PTR *PFNGLCOLOR4DPROC)(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); typedef void (GLAD_API_PTR *PFNGLCOLOR4DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLCOLOR4FPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); typedef void (GLAD_API_PTR *PFNGLCOLOR4FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLCOLOR4IPROC)(GLint red, GLint green, GLint blue, GLint alpha); typedef void (GLAD_API_PTR *PFNGLCOLOR4IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLCOLOR4SPROC)(GLshort red, GLshort green, GLshort blue, GLshort alpha); typedef void (GLAD_API_PTR *PFNGLCOLOR4SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLCOLOR4UBPROC)(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); typedef void (GLAD_API_PTR *PFNGLCOLOR4UBVPROC)(const GLubyte * v); typedef void (GLAD_API_PTR *PFNGLCOLOR4UIPROC)(GLuint red, GLuint green, GLuint blue, GLuint alpha); typedef void (GLAD_API_PTR *PFNGLCOLOR4UIVPROC)(const GLuint * v); typedef void (GLAD_API_PTR *PFNGLCOLOR4USPROC)(GLushort red, GLushort green, GLushort blue, GLushort alpha); typedef void (GLAD_API_PTR *PFNGLCOLOR4USVPROC)(const GLushort * v); typedef void (GLAD_API_PTR *PFNGLCOLORMASKPROC)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); typedef void (GLAD_API_PTR *PFNGLCOLORMASKIPROC)(GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); typedef void (GLAD_API_PTR *PFNGLCOLORMATERIALPROC)(GLenum face, GLenum mode); typedef void (GLAD_API_PTR *PFNGLCOLORPOINTERPROC)(GLint size, GLenum type, GLsizei stride, const void * pointer); typedef void (GLAD_API_PTR *PFNGLCOMPILESHADERPROC)(GLuint shader); typedef void (GLAD_API_PTR *PFNGLCOMPRESSEDTEXIMAGE1DPROC)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void * data); typedef void (GLAD_API_PTR *PFNGLCOMPRESSEDTEXIMAGE2DPROC)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void * data); typedef void (GLAD_API_PTR *PFNGLCOMPRESSEDTEXIMAGE3DPROC)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void * data); typedef void (GLAD_API_PTR *PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void * data); typedef void (GLAD_API_PTR *PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void * data); typedef void (GLAD_API_PTR *PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void * data); typedef void (GLAD_API_PTR *PFNGLCOPYBUFFERSUBDATAPROC)(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); typedef void (GLAD_API_PTR *PFNGLCOPYIMAGESUBDATAPROC)(GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); typedef void (GLAD_API_PTR *PFNGLCOPYPIXELSPROC)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); typedef void (GLAD_API_PTR *PFNGLCOPYTEXIMAGE1DPROC)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); typedef void (GLAD_API_PTR *PFNGLCOPYTEXIMAGE2DPROC)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); typedef void (GLAD_API_PTR *PFNGLCOPYTEXSUBIMAGE1DPROC)(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); typedef void (GLAD_API_PTR *PFNGLCOPYTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); typedef void (GLAD_API_PTR *PFNGLCOPYTEXSUBIMAGE3DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); typedef GLuint (GLAD_API_PTR *PFNGLCREATEPROGRAMPROC)(void); typedef GLuint (GLAD_API_PTR *PFNGLCREATESHADERPROC)(GLenum type); typedef void (GLAD_API_PTR *PFNGLCULLFACEPROC)(GLenum mode); typedef void (GLAD_API_PTR *PFNGLDEBUGMESSAGECALLBACKPROC)(GLDEBUGPROC callback, const void * userParam); typedef void (GLAD_API_PTR *PFNGLDEBUGMESSAGECONTROLPROC)(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint * ids, GLboolean enabled); typedef void (GLAD_API_PTR *PFNGLDEBUGMESSAGEINSERTPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar * buf); typedef void (GLAD_API_PTR *PFNGLDELETEBUFFERSPROC)(GLsizei n, const GLuint * buffers); typedef void (GLAD_API_PTR *PFNGLDELETEFRAMEBUFFERSPROC)(GLsizei n, const GLuint * framebuffers); typedef void (GLAD_API_PTR *PFNGLDELETELISTSPROC)(GLuint list, GLsizei range); typedef void (GLAD_API_PTR *PFNGLDELETEPROGRAMPROC)(GLuint program); typedef void (GLAD_API_PTR *PFNGLDELETEQUERIESPROC)(GLsizei n, const GLuint * ids); typedef void (GLAD_API_PTR *PFNGLDELETERENDERBUFFERSPROC)(GLsizei n, const GLuint * renderbuffers); typedef void (GLAD_API_PTR *PFNGLDELETESHADERPROC)(GLuint shader); typedef void (GLAD_API_PTR *PFNGLDELETETEXTURESPROC)(GLsizei n, const GLuint * textures); typedef void (GLAD_API_PTR *PFNGLDELETEVERTEXARRAYSPROC)(GLsizei n, const GLuint * arrays); typedef void (GLAD_API_PTR *PFNGLDEPTHFUNCPROC)(GLenum func); typedef void (GLAD_API_PTR *PFNGLDEPTHMASKPROC)(GLboolean flag); typedef void (GLAD_API_PTR *PFNGLDEPTHRANGEPROC)(GLdouble n, GLdouble f); typedef void (GLAD_API_PTR *PFNGLDETACHSHADERPROC)(GLuint program, GLuint shader); typedef void (GLAD_API_PTR *PFNGLDISABLEPROC)(GLenum cap); typedef void (GLAD_API_PTR *PFNGLDISABLECLIENTSTATEPROC)(GLenum array); typedef void (GLAD_API_PTR *PFNGLDISABLEVERTEXATTRIBARRAYPROC)(GLuint index); typedef void (GLAD_API_PTR *PFNGLDISABLEIPROC)(GLenum target, GLuint index); typedef void (GLAD_API_PTR *PFNGLDRAWARRAYSPROC)(GLenum mode, GLint first, GLsizei count); typedef void (GLAD_API_PTR *PFNGLDRAWARRAYSINSTANCEDPROC)(GLenum mode, GLint first, GLsizei count, GLsizei instancecount); typedef void (GLAD_API_PTR *PFNGLDRAWBUFFERPROC)(GLenum buf); typedef void (GLAD_API_PTR *PFNGLDRAWBUFFERSPROC)(GLsizei n, const GLenum * bufs); typedef void (GLAD_API_PTR *PFNGLDRAWELEMENTSPROC)(GLenum mode, GLsizei count, GLenum type, const void * indices); typedef void (GLAD_API_PTR *PFNGLDRAWELEMENTSINSTANCEDPROC)(GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei instancecount); typedef void (GLAD_API_PTR *PFNGLDRAWPIXELSPROC)(GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels); typedef void (GLAD_API_PTR *PFNGLDRAWRANGEELEMENTSPROC)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void * indices); typedef void (GLAD_API_PTR *PFNGLEDGEFLAGPROC)(GLboolean flag); typedef void (GLAD_API_PTR *PFNGLEDGEFLAGPOINTERPROC)(GLsizei stride, const void * pointer); typedef void (GLAD_API_PTR *PFNGLEDGEFLAGVPROC)(const GLboolean * flag); typedef void (GLAD_API_PTR *PFNGLENABLEPROC)(GLenum cap); typedef void (GLAD_API_PTR *PFNGLENABLECLIENTSTATEPROC)(GLenum array); typedef void (GLAD_API_PTR *PFNGLENABLEVERTEXATTRIBARRAYPROC)(GLuint index); typedef void (GLAD_API_PTR *PFNGLENABLEIPROC)(GLenum target, GLuint index); typedef void (GLAD_API_PTR *PFNGLENDPROC)(void); typedef void (GLAD_API_PTR *PFNGLENDCONDITIONALRENDERPROC)(void); typedef void (GLAD_API_PTR *PFNGLENDLISTPROC)(void); typedef void (GLAD_API_PTR *PFNGLENDQUERYPROC)(GLenum target); typedef void (GLAD_API_PTR *PFNGLENDTRANSFORMFEEDBACKPROC)(void); typedef void (GLAD_API_PTR *PFNGLEVALCOORD1DPROC)(GLdouble u); typedef void (GLAD_API_PTR *PFNGLEVALCOORD1DVPROC)(const GLdouble * u); typedef void (GLAD_API_PTR *PFNGLEVALCOORD1FPROC)(GLfloat u); typedef void (GLAD_API_PTR *PFNGLEVALCOORD1FVPROC)(const GLfloat * u); typedef void (GLAD_API_PTR *PFNGLEVALCOORD2DPROC)(GLdouble u, GLdouble v); typedef void (GLAD_API_PTR *PFNGLEVALCOORD2DVPROC)(const GLdouble * u); typedef void (GLAD_API_PTR *PFNGLEVALCOORD2FPROC)(GLfloat u, GLfloat v); typedef void (GLAD_API_PTR *PFNGLEVALCOORD2FVPROC)(const GLfloat * u); typedef void (GLAD_API_PTR *PFNGLEVALMESH1PROC)(GLenum mode, GLint i1, GLint i2); typedef void (GLAD_API_PTR *PFNGLEVALMESH2PROC)(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); typedef void (GLAD_API_PTR *PFNGLEVALPOINT1PROC)(GLint i); typedef void (GLAD_API_PTR *PFNGLEVALPOINT2PROC)(GLint i, GLint j); typedef void (GLAD_API_PTR *PFNGLFEEDBACKBUFFERPROC)(GLsizei size, GLenum type, GLfloat * buffer); typedef void (GLAD_API_PTR *PFNGLFINISHPROC)(void); typedef void (GLAD_API_PTR *PFNGLFLUSHPROC)(void); typedef void (GLAD_API_PTR *PFNGLFLUSHMAPPEDBUFFERRANGEPROC)(GLenum target, GLintptr offset, GLsizeiptr length); typedef void (GLAD_API_PTR *PFNGLFOGCOORDPOINTERPROC)(GLenum type, GLsizei stride, const void * pointer); typedef void (GLAD_API_PTR *PFNGLFOGCOORDDPROC)(GLdouble coord); typedef void (GLAD_API_PTR *PFNGLFOGCOORDDVPROC)(const GLdouble * coord); typedef void (GLAD_API_PTR *PFNGLFOGCOORDFPROC)(GLfloat coord); typedef void (GLAD_API_PTR *PFNGLFOGCOORDFVPROC)(const GLfloat * coord); typedef void (GLAD_API_PTR *PFNGLFOGFPROC)(GLenum pname, GLfloat param); typedef void (GLAD_API_PTR *PFNGLFOGFVPROC)(GLenum pname, const GLfloat * params); typedef void (GLAD_API_PTR *PFNGLFOGIPROC)(GLenum pname, GLint param); typedef void (GLAD_API_PTR *PFNGLFOGIVPROC)(GLenum pname, const GLint * params); typedef void (GLAD_API_PTR *PFNGLFRAMEBUFFERRENDERBUFFERPROC)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); typedef void (GLAD_API_PTR *PFNGLFRAMEBUFFERTEXTURE1DPROC)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); typedef void (GLAD_API_PTR *PFNGLFRAMEBUFFERTEXTURE2DPROC)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); typedef void (GLAD_API_PTR *PFNGLFRAMEBUFFERTEXTURE3DPROC)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); typedef void (GLAD_API_PTR *PFNGLFRAMEBUFFERTEXTURELAYERPROC)(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); typedef void (GLAD_API_PTR *PFNGLFRONTFACEPROC)(GLenum mode); typedef void (GLAD_API_PTR *PFNGLFRUSTUMPROC)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); typedef void (GLAD_API_PTR *PFNGLGENBUFFERSPROC)(GLsizei n, GLuint * buffers); typedef void (GLAD_API_PTR *PFNGLGENFRAMEBUFFERSPROC)(GLsizei n, GLuint * framebuffers); typedef GLuint (GLAD_API_PTR *PFNGLGENLISTSPROC)(GLsizei range); typedef void (GLAD_API_PTR *PFNGLGENQUERIESPROC)(GLsizei n, GLuint * ids); typedef void (GLAD_API_PTR *PFNGLGENRENDERBUFFERSPROC)(GLsizei n, GLuint * renderbuffers); typedef void (GLAD_API_PTR *PFNGLGENTEXTURESPROC)(GLsizei n, GLuint * textures); typedef void (GLAD_API_PTR *PFNGLGENVERTEXARRAYSPROC)(GLsizei n, GLuint * arrays); typedef void (GLAD_API_PTR *PFNGLGENERATEMIPMAPPROC)(GLenum target); typedef void (GLAD_API_PTR *PFNGLGETACTIVEATTRIBPROC)(GLuint program, GLuint index, GLsizei bufSize, GLsizei * length, GLint * size, GLenum * type, GLchar * name); typedef void (GLAD_API_PTR *PFNGLGETACTIVEUNIFORMPROC)(GLuint program, GLuint index, GLsizei bufSize, GLsizei * length, GLint * size, GLenum * type, GLchar * name); typedef void (GLAD_API_PTR *PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC)(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei * length, GLchar * uniformBlockName); typedef void (GLAD_API_PTR *PFNGLGETACTIVEUNIFORMBLOCKIVPROC)(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETACTIVEUNIFORMNAMEPROC)(GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei * length, GLchar * uniformName); typedef void (GLAD_API_PTR *PFNGLGETACTIVEUNIFORMSIVPROC)(GLuint program, GLsizei uniformCount, const GLuint * uniformIndices, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETATTACHEDSHADERSPROC)(GLuint program, GLsizei maxCount, GLsizei * count, GLuint * shaders); typedef GLint (GLAD_API_PTR *PFNGLGETATTRIBLOCATIONPROC)(GLuint program, const GLchar * name); typedef void (GLAD_API_PTR *PFNGLGETBOOLEANI_VPROC)(GLenum target, GLuint index, GLboolean * data); typedef void (GLAD_API_PTR *PFNGLGETBOOLEANVPROC)(GLenum pname, GLboolean * data); typedef void (GLAD_API_PTR *PFNGLGETBUFFERPARAMETERIVPROC)(GLenum target, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETBUFFERPOINTERVPROC)(GLenum target, GLenum pname, void ** params); typedef void (GLAD_API_PTR *PFNGLGETBUFFERSUBDATAPROC)(GLenum target, GLintptr offset, GLsizeiptr size, void * data); typedef void (GLAD_API_PTR *PFNGLGETCLIPPLANEPROC)(GLenum plane, GLdouble * equation); typedef void (GLAD_API_PTR *PFNGLGETCOMPRESSEDTEXIMAGEPROC)(GLenum target, GLint level, void * img); typedef GLuint (GLAD_API_PTR *PFNGLGETDEBUGMESSAGELOGPROC)(GLuint count, GLsizei bufSize, GLenum * sources, GLenum * types, GLuint * ids, GLenum * severities, GLsizei * lengths, GLchar * messageLog); typedef void (GLAD_API_PTR *PFNGLGETDOUBLEVPROC)(GLenum pname, GLdouble * data); typedef GLenum (GLAD_API_PTR *PFNGLGETERRORPROC)(void); typedef void (GLAD_API_PTR *PFNGLGETFLOATVPROC)(GLenum pname, GLfloat * data); typedef GLint (GLAD_API_PTR *PFNGLGETFRAGDATALOCATIONPROC)(GLuint program, const GLchar * name); typedef void (GLAD_API_PTR *PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)(GLenum target, GLenum attachment, GLenum pname, GLint * params); typedef GLenum (GLAD_API_PTR *PFNGLGETGRAPHICSRESETSTATUSARBPROC)(void); typedef void (GLAD_API_PTR *PFNGLGETINTEGERI_VPROC)(GLenum target, GLuint index, GLint * data); typedef void (GLAD_API_PTR *PFNGLGETINTEGERVPROC)(GLenum pname, GLint * data); typedef void (GLAD_API_PTR *PFNGLGETLIGHTFVPROC)(GLenum light, GLenum pname, GLfloat * params); typedef void (GLAD_API_PTR *PFNGLGETLIGHTIVPROC)(GLenum light, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETMAPDVPROC)(GLenum target, GLenum query, GLdouble * v); typedef void (GLAD_API_PTR *PFNGLGETMAPFVPROC)(GLenum target, GLenum query, GLfloat * v); typedef void (GLAD_API_PTR *PFNGLGETMAPIVPROC)(GLenum target, GLenum query, GLint * v); typedef void (GLAD_API_PTR *PFNGLGETMATERIALFVPROC)(GLenum face, GLenum pname, GLfloat * params); typedef void (GLAD_API_PTR *PFNGLGETMATERIALIVPROC)(GLenum face, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETOBJECTLABELPROC)(GLenum identifier, GLuint name, GLsizei bufSize, GLsizei * length, GLchar * label); typedef void (GLAD_API_PTR *PFNGLGETOBJECTPTRLABELPROC)(const void * ptr, GLsizei bufSize, GLsizei * length, GLchar * label); typedef void (GLAD_API_PTR *PFNGLGETPIXELMAPFVPROC)(GLenum map, GLfloat * values); typedef void (GLAD_API_PTR *PFNGLGETPIXELMAPUIVPROC)(GLenum map, GLuint * values); typedef void (GLAD_API_PTR *PFNGLGETPIXELMAPUSVPROC)(GLenum map, GLushort * values); typedef void (GLAD_API_PTR *PFNGLGETPOINTERVPROC)(GLenum pname, void ** params); typedef void (GLAD_API_PTR *PFNGLGETPOLYGONSTIPPLEPROC)(GLubyte * mask); typedef void (GLAD_API_PTR *PFNGLGETPROGRAMINFOLOGPROC)(GLuint program, GLsizei bufSize, GLsizei * length, GLchar * infoLog); typedef void (GLAD_API_PTR *PFNGLGETPROGRAMIVPROC)(GLuint program, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETQUERYOBJECTIVPROC)(GLuint id, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETQUERYOBJECTUIVPROC)(GLuint id, GLenum pname, GLuint * params); typedef void (GLAD_API_PTR *PFNGLGETQUERYIVPROC)(GLenum target, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETRENDERBUFFERPARAMETERIVPROC)(GLenum target, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETSHADERINFOLOGPROC)(GLuint shader, GLsizei bufSize, GLsizei * length, GLchar * infoLog); typedef void (GLAD_API_PTR *PFNGLGETSHADERSOURCEPROC)(GLuint shader, GLsizei bufSize, GLsizei * length, GLchar * source); typedef void (GLAD_API_PTR *PFNGLGETSHADERIVPROC)(GLuint shader, GLenum pname, GLint * params); typedef const GLubyte * (GLAD_API_PTR *PFNGLGETSTRINGPROC)(GLenum name); typedef const GLubyte * (GLAD_API_PTR *PFNGLGETSTRINGIPROC)(GLenum name, GLuint index); typedef void (GLAD_API_PTR *PFNGLGETTEXENVFVPROC)(GLenum target, GLenum pname, GLfloat * params); typedef void (GLAD_API_PTR *PFNGLGETTEXENVIVPROC)(GLenum target, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETTEXGENDVPROC)(GLenum coord, GLenum pname, GLdouble * params); typedef void (GLAD_API_PTR *PFNGLGETTEXGENFVPROC)(GLenum coord, GLenum pname, GLfloat * params); typedef void (GLAD_API_PTR *PFNGLGETTEXGENIVPROC)(GLenum coord, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETTEXIMAGEPROC)(GLenum target, GLint level, GLenum format, GLenum type, void * pixels); typedef void (GLAD_API_PTR *PFNGLGETTEXLEVELPARAMETERFVPROC)(GLenum target, GLint level, GLenum pname, GLfloat * params); typedef void (GLAD_API_PTR *PFNGLGETTEXLEVELPARAMETERIVPROC)(GLenum target, GLint level, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETTEXPARAMETERIIVPROC)(GLenum target, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETTEXPARAMETERIUIVPROC)(GLenum target, GLenum pname, GLuint * params); typedef void (GLAD_API_PTR *PFNGLGETTEXPARAMETERFVPROC)(GLenum target, GLenum pname, GLfloat * params); typedef void (GLAD_API_PTR *PFNGLGETTEXPARAMETERIVPROC)(GLenum target, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETTRANSFORMFEEDBACKVARYINGPROC)(GLuint program, GLuint index, GLsizei bufSize, GLsizei * length, GLsizei * size, GLenum * type, GLchar * name); typedef GLuint (GLAD_API_PTR *PFNGLGETUNIFORMBLOCKINDEXPROC)(GLuint program, const GLchar * uniformBlockName); typedef void (GLAD_API_PTR *PFNGLGETUNIFORMINDICESPROC)(GLuint program, GLsizei uniformCount, const GLchar *const* uniformNames, GLuint * uniformIndices); typedef GLint (GLAD_API_PTR *PFNGLGETUNIFORMLOCATIONPROC)(GLuint program, const GLchar * name); typedef void (GLAD_API_PTR *PFNGLGETUNIFORMFVPROC)(GLuint program, GLint location, GLfloat * params); typedef void (GLAD_API_PTR *PFNGLGETUNIFORMIVPROC)(GLuint program, GLint location, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETUNIFORMUIVPROC)(GLuint program, GLint location, GLuint * params); typedef void (GLAD_API_PTR *PFNGLGETVERTEXATTRIBIIVPROC)(GLuint index, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETVERTEXATTRIBIUIVPROC)(GLuint index, GLenum pname, GLuint * params); typedef void (GLAD_API_PTR *PFNGLGETVERTEXATTRIBPOINTERVPROC)(GLuint index, GLenum pname, void ** pointer); typedef void (GLAD_API_PTR *PFNGLGETVERTEXATTRIBDVPROC)(GLuint index, GLenum pname, GLdouble * params); typedef void (GLAD_API_PTR *PFNGLGETVERTEXATTRIBFVPROC)(GLuint index, GLenum pname, GLfloat * params); typedef void (GLAD_API_PTR *PFNGLGETVERTEXATTRIBIVPROC)(GLuint index, GLenum pname, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETNCOMPRESSEDTEXIMAGEARBPROC)(GLenum target, GLint lod, GLsizei bufSize, void * img); typedef void (GLAD_API_PTR *PFNGLGETNTEXIMAGEARBPROC)(GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void * img); typedef void (GLAD_API_PTR *PFNGLGETNUNIFORMDVARBPROC)(GLuint program, GLint location, GLsizei bufSize, GLdouble * params); typedef void (GLAD_API_PTR *PFNGLGETNUNIFORMFVARBPROC)(GLuint program, GLint location, GLsizei bufSize, GLfloat * params); typedef void (GLAD_API_PTR *PFNGLGETNUNIFORMIVARBPROC)(GLuint program, GLint location, GLsizei bufSize, GLint * params); typedef void (GLAD_API_PTR *PFNGLGETNUNIFORMUIVARBPROC)(GLuint program, GLint location, GLsizei bufSize, GLuint * params); typedef void (GLAD_API_PTR *PFNGLHINTPROC)(GLenum target, GLenum mode); typedef void (GLAD_API_PTR *PFNGLINDEXMASKPROC)(GLuint mask); typedef void (GLAD_API_PTR *PFNGLINDEXPOINTERPROC)(GLenum type, GLsizei stride, const void * pointer); typedef void (GLAD_API_PTR *PFNGLINDEXDPROC)(GLdouble c); typedef void (GLAD_API_PTR *PFNGLINDEXDVPROC)(const GLdouble * c); typedef void (GLAD_API_PTR *PFNGLINDEXFPROC)(GLfloat c); typedef void (GLAD_API_PTR *PFNGLINDEXFVPROC)(const GLfloat * c); typedef void (GLAD_API_PTR *PFNGLINDEXIPROC)(GLint c); typedef void (GLAD_API_PTR *PFNGLINDEXIVPROC)(const GLint * c); typedef void (GLAD_API_PTR *PFNGLINDEXSPROC)(GLshort c); typedef void (GLAD_API_PTR *PFNGLINDEXSVPROC)(const GLshort * c); typedef void (GLAD_API_PTR *PFNGLINDEXUBPROC)(GLubyte c); typedef void (GLAD_API_PTR *PFNGLINDEXUBVPROC)(const GLubyte * c); typedef void (GLAD_API_PTR *PFNGLINITNAMESPROC)(void); typedef void (GLAD_API_PTR *PFNGLINTERLEAVEDARRAYSPROC)(GLenum format, GLsizei stride, const void * pointer); typedef GLboolean (GLAD_API_PTR *PFNGLISBUFFERPROC)(GLuint buffer); typedef GLboolean (GLAD_API_PTR *PFNGLISENABLEDPROC)(GLenum cap); typedef GLboolean (GLAD_API_PTR *PFNGLISENABLEDIPROC)(GLenum target, GLuint index); typedef GLboolean (GLAD_API_PTR *PFNGLISFRAMEBUFFERPROC)(GLuint framebuffer); typedef GLboolean (GLAD_API_PTR *PFNGLISLISTPROC)(GLuint list); typedef GLboolean (GLAD_API_PTR *PFNGLISPROGRAMPROC)(GLuint program); typedef GLboolean (GLAD_API_PTR *PFNGLISQUERYPROC)(GLuint id); typedef GLboolean (GLAD_API_PTR *PFNGLISRENDERBUFFERPROC)(GLuint renderbuffer); typedef GLboolean (GLAD_API_PTR *PFNGLISSHADERPROC)(GLuint shader); typedef GLboolean (GLAD_API_PTR *PFNGLISTEXTUREPROC)(GLuint texture); typedef GLboolean (GLAD_API_PTR *PFNGLISVERTEXARRAYPROC)(GLuint array); typedef void (GLAD_API_PTR *PFNGLLIGHTMODELFPROC)(GLenum pname, GLfloat param); typedef void (GLAD_API_PTR *PFNGLLIGHTMODELFVPROC)(GLenum pname, const GLfloat * params); typedef void (GLAD_API_PTR *PFNGLLIGHTMODELIPROC)(GLenum pname, GLint param); typedef void (GLAD_API_PTR *PFNGLLIGHTMODELIVPROC)(GLenum pname, const GLint * params); typedef void (GLAD_API_PTR *PFNGLLIGHTFPROC)(GLenum light, GLenum pname, GLfloat param); typedef void (GLAD_API_PTR *PFNGLLIGHTFVPROC)(GLenum light, GLenum pname, const GLfloat * params); typedef void (GLAD_API_PTR *PFNGLLIGHTIPROC)(GLenum light, GLenum pname, GLint param); typedef void (GLAD_API_PTR *PFNGLLIGHTIVPROC)(GLenum light, GLenum pname, const GLint * params); typedef void (GLAD_API_PTR *PFNGLLINESTIPPLEPROC)(GLint factor, GLushort pattern); typedef void (GLAD_API_PTR *PFNGLLINEWIDTHPROC)(GLfloat width); typedef void (GLAD_API_PTR *PFNGLLINKPROGRAMPROC)(GLuint program); typedef void (GLAD_API_PTR *PFNGLLISTBASEPROC)(GLuint base); typedef void (GLAD_API_PTR *PFNGLLOADIDENTITYPROC)(void); typedef void (GLAD_API_PTR *PFNGLLOADMATRIXDPROC)(const GLdouble * m); typedef void (GLAD_API_PTR *PFNGLLOADMATRIXFPROC)(const GLfloat * m); typedef void (GLAD_API_PTR *PFNGLLOADNAMEPROC)(GLuint name); typedef void (GLAD_API_PTR *PFNGLLOADTRANSPOSEMATRIXDPROC)(const GLdouble * m); typedef void (GLAD_API_PTR *PFNGLLOADTRANSPOSEMATRIXFPROC)(const GLfloat * m); typedef void (GLAD_API_PTR *PFNGLLOGICOPPROC)(GLenum opcode); typedef void (GLAD_API_PTR *PFNGLMAP1DPROC)(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble * points); typedef void (GLAD_API_PTR *PFNGLMAP1FPROC)(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat * points); typedef void (GLAD_API_PTR *PFNGLMAP2DPROC)(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble * points); typedef void (GLAD_API_PTR *PFNGLMAP2FPROC)(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat * points); typedef void * (GLAD_API_PTR *PFNGLMAPBUFFERPROC)(GLenum target, GLenum access); typedef void * (GLAD_API_PTR *PFNGLMAPBUFFERRANGEPROC)(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); typedef void (GLAD_API_PTR *PFNGLMAPGRID1DPROC)(GLint un, GLdouble u1, GLdouble u2); typedef void (GLAD_API_PTR *PFNGLMAPGRID1FPROC)(GLint un, GLfloat u1, GLfloat u2); typedef void (GLAD_API_PTR *PFNGLMAPGRID2DPROC)(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); typedef void (GLAD_API_PTR *PFNGLMAPGRID2FPROC)(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); typedef void (GLAD_API_PTR *PFNGLMATERIALFPROC)(GLenum face, GLenum pname, GLfloat param); typedef void (GLAD_API_PTR *PFNGLMATERIALFVPROC)(GLenum face, GLenum pname, const GLfloat * params); typedef void (GLAD_API_PTR *PFNGLMATERIALIPROC)(GLenum face, GLenum pname, GLint param); typedef void (GLAD_API_PTR *PFNGLMATERIALIVPROC)(GLenum face, GLenum pname, const GLint * params); typedef void (GLAD_API_PTR *PFNGLMATRIXMODEPROC)(GLenum mode); typedef void (GLAD_API_PTR *PFNGLMULTMATRIXDPROC)(const GLdouble * m); typedef void (GLAD_API_PTR *PFNGLMULTMATRIXFPROC)(const GLfloat * m); typedef void (GLAD_API_PTR *PFNGLMULTTRANSPOSEMATRIXDPROC)(const GLdouble * m); typedef void (GLAD_API_PTR *PFNGLMULTTRANSPOSEMATRIXFPROC)(const GLfloat * m); typedef void (GLAD_API_PTR *PFNGLMULTIDRAWARRAYSPROC)(GLenum mode, const GLint * first, const GLsizei * count, GLsizei drawcount); typedef void (GLAD_API_PTR *PFNGLMULTIDRAWELEMENTSPROC)(GLenum mode, const GLsizei * count, GLenum type, const void *const* indices, GLsizei drawcount); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD1DPROC)(GLenum target, GLdouble s); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD1DVPROC)(GLenum target, const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD1FPROC)(GLenum target, GLfloat s); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD1FVPROC)(GLenum target, const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD1IPROC)(GLenum target, GLint s); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD1IVPROC)(GLenum target, const GLint * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD1SPROC)(GLenum target, GLshort s); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD1SVPROC)(GLenum target, const GLshort * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD2DPROC)(GLenum target, GLdouble s, GLdouble t); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD2DVPROC)(GLenum target, const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD2FPROC)(GLenum target, GLfloat s, GLfloat t); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD2FVPROC)(GLenum target, const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD2IPROC)(GLenum target, GLint s, GLint t); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD2IVPROC)(GLenum target, const GLint * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD2SPROC)(GLenum target, GLshort s, GLshort t); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD2SVPROC)(GLenum target, const GLshort * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD3DPROC)(GLenum target, GLdouble s, GLdouble t, GLdouble r); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD3DVPROC)(GLenum target, const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD3FPROC)(GLenum target, GLfloat s, GLfloat t, GLfloat r); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD3FVPROC)(GLenum target, const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD3IPROC)(GLenum target, GLint s, GLint t, GLint r); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD3IVPROC)(GLenum target, const GLint * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD3SPROC)(GLenum target, GLshort s, GLshort t, GLshort r); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD3SVPROC)(GLenum target, const GLshort * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD4DPROC)(GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD4DVPROC)(GLenum target, const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD4FPROC)(GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD4FVPROC)(GLenum target, const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD4IPROC)(GLenum target, GLint s, GLint t, GLint r, GLint q); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD4IVPROC)(GLenum target, const GLint * v); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD4SPROC)(GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); typedef void (GLAD_API_PTR *PFNGLMULTITEXCOORD4SVPROC)(GLenum target, const GLshort * v); typedef void (GLAD_API_PTR *PFNGLNEWLISTPROC)(GLuint list, GLenum mode); typedef void (GLAD_API_PTR *PFNGLNORMAL3BPROC)(GLbyte nx, GLbyte ny, GLbyte nz); typedef void (GLAD_API_PTR *PFNGLNORMAL3BVPROC)(const GLbyte * v); typedef void (GLAD_API_PTR *PFNGLNORMAL3DPROC)(GLdouble nx, GLdouble ny, GLdouble nz); typedef void (GLAD_API_PTR *PFNGLNORMAL3DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLNORMAL3FPROC)(GLfloat nx, GLfloat ny, GLfloat nz); typedef void (GLAD_API_PTR *PFNGLNORMAL3FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLNORMAL3IPROC)(GLint nx, GLint ny, GLint nz); typedef void (GLAD_API_PTR *PFNGLNORMAL3IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLNORMAL3SPROC)(GLshort nx, GLshort ny, GLshort nz); typedef void (GLAD_API_PTR *PFNGLNORMAL3SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLNORMALPOINTERPROC)(GLenum type, GLsizei stride, const void * pointer); typedef void (GLAD_API_PTR *PFNGLOBJECTLABELPROC)(GLenum identifier, GLuint name, GLsizei length, const GLchar * label); typedef void (GLAD_API_PTR *PFNGLOBJECTPTRLABELPROC)(const void * ptr, GLsizei length, const GLchar * label); typedef void (GLAD_API_PTR *PFNGLORTHOPROC)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); typedef void (GLAD_API_PTR *PFNGLPASSTHROUGHPROC)(GLfloat token); typedef void (GLAD_API_PTR *PFNGLPIXELMAPFVPROC)(GLenum map, GLsizei mapsize, const GLfloat * values); typedef void (GLAD_API_PTR *PFNGLPIXELMAPUIVPROC)(GLenum map, GLsizei mapsize, const GLuint * values); typedef void (GLAD_API_PTR *PFNGLPIXELMAPUSVPROC)(GLenum map, GLsizei mapsize, const GLushort * values); typedef void (GLAD_API_PTR *PFNGLPIXELSTOREFPROC)(GLenum pname, GLfloat param); typedef void (GLAD_API_PTR *PFNGLPIXELSTOREIPROC)(GLenum pname, GLint param); typedef void (GLAD_API_PTR *PFNGLPIXELTRANSFERFPROC)(GLenum pname, GLfloat param); typedef void (GLAD_API_PTR *PFNGLPIXELTRANSFERIPROC)(GLenum pname, GLint param); typedef void (GLAD_API_PTR *PFNGLPIXELZOOMPROC)(GLfloat xfactor, GLfloat yfactor); typedef void (GLAD_API_PTR *PFNGLPOINTPARAMETERFPROC)(GLenum pname, GLfloat param); typedef void (GLAD_API_PTR *PFNGLPOINTPARAMETERFVPROC)(GLenum pname, const GLfloat * params); typedef void (GLAD_API_PTR *PFNGLPOINTPARAMETERIPROC)(GLenum pname, GLint param); typedef void (GLAD_API_PTR *PFNGLPOINTPARAMETERIVPROC)(GLenum pname, const GLint * params); typedef void (GLAD_API_PTR *PFNGLPOINTSIZEPROC)(GLfloat size); typedef void (GLAD_API_PTR *PFNGLPOLYGONMODEPROC)(GLenum face, GLenum mode); typedef void (GLAD_API_PTR *PFNGLPOLYGONOFFSETPROC)(GLfloat factor, GLfloat units); typedef void (GLAD_API_PTR *PFNGLPOLYGONSTIPPLEPROC)(const GLubyte * mask); typedef void (GLAD_API_PTR *PFNGLPOPATTRIBPROC)(void); typedef void (GLAD_API_PTR *PFNGLPOPCLIENTATTRIBPROC)(void); typedef void (GLAD_API_PTR *PFNGLPOPDEBUGGROUPPROC)(void); typedef void (GLAD_API_PTR *PFNGLPOPMATRIXPROC)(void); typedef void (GLAD_API_PTR *PFNGLPOPNAMEPROC)(void); typedef void (GLAD_API_PTR *PFNGLPRIMITIVERESTARTINDEXPROC)(GLuint index); typedef void (GLAD_API_PTR *PFNGLPRIORITIZETEXTURESPROC)(GLsizei n, const GLuint * textures, const GLfloat * priorities); typedef void (GLAD_API_PTR *PFNGLPUSHATTRIBPROC)(GLbitfield mask); typedef void (GLAD_API_PTR *PFNGLPUSHCLIENTATTRIBPROC)(GLbitfield mask); typedef void (GLAD_API_PTR *PFNGLPUSHDEBUGGROUPPROC)(GLenum source, GLuint id, GLsizei length, const GLchar * message); typedef void (GLAD_API_PTR *PFNGLPUSHMATRIXPROC)(void); typedef void (GLAD_API_PTR *PFNGLPUSHNAMEPROC)(GLuint name); typedef void (GLAD_API_PTR *PFNGLRASTERPOS2DPROC)(GLdouble x, GLdouble y); typedef void (GLAD_API_PTR *PFNGLRASTERPOS2DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLRASTERPOS2FPROC)(GLfloat x, GLfloat y); typedef void (GLAD_API_PTR *PFNGLRASTERPOS2FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLRASTERPOS2IPROC)(GLint x, GLint y); typedef void (GLAD_API_PTR *PFNGLRASTERPOS2IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLRASTERPOS2SPROC)(GLshort x, GLshort y); typedef void (GLAD_API_PTR *PFNGLRASTERPOS2SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLRASTERPOS3DPROC)(GLdouble x, GLdouble y, GLdouble z); typedef void (GLAD_API_PTR *PFNGLRASTERPOS3DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLRASTERPOS3FPROC)(GLfloat x, GLfloat y, GLfloat z); typedef void (GLAD_API_PTR *PFNGLRASTERPOS3FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLRASTERPOS3IPROC)(GLint x, GLint y, GLint z); typedef void (GLAD_API_PTR *PFNGLRASTERPOS3IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLRASTERPOS3SPROC)(GLshort x, GLshort y, GLshort z); typedef void (GLAD_API_PTR *PFNGLRASTERPOS3SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLRASTERPOS4DPROC)(GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (GLAD_API_PTR *PFNGLRASTERPOS4DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLRASTERPOS4FPROC)(GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (GLAD_API_PTR *PFNGLRASTERPOS4FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLRASTERPOS4IPROC)(GLint x, GLint y, GLint z, GLint w); typedef void (GLAD_API_PTR *PFNGLRASTERPOS4IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLRASTERPOS4SPROC)(GLshort x, GLshort y, GLshort z, GLshort w); typedef void (GLAD_API_PTR *PFNGLRASTERPOS4SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLREADBUFFERPROC)(GLenum src); typedef void (GLAD_API_PTR *PFNGLREADPIXELSPROC)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * pixels); typedef void (GLAD_API_PTR *PFNGLREADNPIXELSARBPROC)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void * data); typedef void (GLAD_API_PTR *PFNGLRECTDPROC)(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); typedef void (GLAD_API_PTR *PFNGLRECTDVPROC)(const GLdouble * v1, const GLdouble * v2); typedef void (GLAD_API_PTR *PFNGLRECTFPROC)(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); typedef void (GLAD_API_PTR *PFNGLRECTFVPROC)(const GLfloat * v1, const GLfloat * v2); typedef void (GLAD_API_PTR *PFNGLRECTIPROC)(GLint x1, GLint y1, GLint x2, GLint y2); typedef void (GLAD_API_PTR *PFNGLRECTIVPROC)(const GLint * v1, const GLint * v2); typedef void (GLAD_API_PTR *PFNGLRECTSPROC)(GLshort x1, GLshort y1, GLshort x2, GLshort y2); typedef void (GLAD_API_PTR *PFNGLRECTSVPROC)(const GLshort * v1, const GLshort * v2); typedef GLint (GLAD_API_PTR *PFNGLRENDERMODEPROC)(GLenum mode); typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEPROC)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); typedef void (GLAD_API_PTR *PFNGLROTATEDPROC)(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); typedef void (GLAD_API_PTR *PFNGLROTATEFPROC)(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); typedef void (GLAD_API_PTR *PFNGLSAMPLECOVERAGEPROC)(GLfloat value, GLboolean invert); typedef void (GLAD_API_PTR *PFNGLSAMPLECOVERAGEARBPROC)(GLfloat value, GLboolean invert); typedef void (GLAD_API_PTR *PFNGLSCALEDPROC)(GLdouble x, GLdouble y, GLdouble z); typedef void (GLAD_API_PTR *PFNGLSCALEFPROC)(GLfloat x, GLfloat y, GLfloat z); typedef void (GLAD_API_PTR *PFNGLSCISSORPROC)(GLint x, GLint y, GLsizei width, GLsizei height); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3BPROC)(GLbyte red, GLbyte green, GLbyte blue); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3BVPROC)(const GLbyte * v); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3DPROC)(GLdouble red, GLdouble green, GLdouble blue); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3FPROC)(GLfloat red, GLfloat green, GLfloat blue); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3IPROC)(GLint red, GLint green, GLint blue); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3SPROC)(GLshort red, GLshort green, GLshort blue); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3UBPROC)(GLubyte red, GLubyte green, GLubyte blue); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3UBVPROC)(const GLubyte * v); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3UIPROC)(GLuint red, GLuint green, GLuint blue); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3UIVPROC)(const GLuint * v); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3USPROC)(GLushort red, GLushort green, GLushort blue); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLOR3USVPROC)(const GLushort * v); typedef void (GLAD_API_PTR *PFNGLSECONDARYCOLORPOINTERPROC)(GLint size, GLenum type, GLsizei stride, const void * pointer); typedef void (GLAD_API_PTR *PFNGLSELECTBUFFERPROC)(GLsizei size, GLuint * buffer); typedef void (GLAD_API_PTR *PFNGLSHADEMODELPROC)(GLenum mode); typedef void (GLAD_API_PTR *PFNGLSHADERSOURCEPROC)(GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length); typedef void (GLAD_API_PTR *PFNGLSTENCILFUNCPROC)(GLenum func, GLint ref, GLuint mask); typedef void (GLAD_API_PTR *PFNGLSTENCILFUNCSEPARATEPROC)(GLenum face, GLenum func, GLint ref, GLuint mask); typedef void (GLAD_API_PTR *PFNGLSTENCILMASKPROC)(GLuint mask); typedef void (GLAD_API_PTR *PFNGLSTENCILMASKSEPARATEPROC)(GLenum face, GLuint mask); typedef void (GLAD_API_PTR *PFNGLSTENCILOPPROC)(GLenum fail, GLenum zfail, GLenum zpass); typedef void (GLAD_API_PTR *PFNGLSTENCILOPSEPARATEPROC)(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); typedef void (GLAD_API_PTR *PFNGLTEXBUFFERPROC)(GLenum target, GLenum internalformat, GLuint buffer); typedef void (GLAD_API_PTR *PFNGLTEXCOORD1DPROC)(GLdouble s); typedef void (GLAD_API_PTR *PFNGLTEXCOORD1DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD1FPROC)(GLfloat s); typedef void (GLAD_API_PTR *PFNGLTEXCOORD1FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD1IPROC)(GLint s); typedef void (GLAD_API_PTR *PFNGLTEXCOORD1IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD1SPROC)(GLshort s); typedef void (GLAD_API_PTR *PFNGLTEXCOORD1SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD2DPROC)(GLdouble s, GLdouble t); typedef void (GLAD_API_PTR *PFNGLTEXCOORD2DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD2FPROC)(GLfloat s, GLfloat t); typedef void (GLAD_API_PTR *PFNGLTEXCOORD2FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD2IPROC)(GLint s, GLint t); typedef void (GLAD_API_PTR *PFNGLTEXCOORD2IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD2SPROC)(GLshort s, GLshort t); typedef void (GLAD_API_PTR *PFNGLTEXCOORD2SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD3DPROC)(GLdouble s, GLdouble t, GLdouble r); typedef void (GLAD_API_PTR *PFNGLTEXCOORD3DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD3FPROC)(GLfloat s, GLfloat t, GLfloat r); typedef void (GLAD_API_PTR *PFNGLTEXCOORD3FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD3IPROC)(GLint s, GLint t, GLint r); typedef void (GLAD_API_PTR *PFNGLTEXCOORD3IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD3SPROC)(GLshort s, GLshort t, GLshort r); typedef void (GLAD_API_PTR *PFNGLTEXCOORD3SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD4DPROC)(GLdouble s, GLdouble t, GLdouble r, GLdouble q); typedef void (GLAD_API_PTR *PFNGLTEXCOORD4DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD4FPROC)(GLfloat s, GLfloat t, GLfloat r, GLfloat q); typedef void (GLAD_API_PTR *PFNGLTEXCOORD4FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD4IPROC)(GLint s, GLint t, GLint r, GLint q); typedef void (GLAD_API_PTR *PFNGLTEXCOORD4IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORD4SPROC)(GLshort s, GLshort t, GLshort r, GLshort q); typedef void (GLAD_API_PTR *PFNGLTEXCOORD4SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLTEXCOORDPOINTERPROC)(GLint size, GLenum type, GLsizei stride, const void * pointer); typedef void (GLAD_API_PTR *PFNGLTEXENVFPROC)(GLenum target, GLenum pname, GLfloat param); typedef void (GLAD_API_PTR *PFNGLTEXENVFVPROC)(GLenum target, GLenum pname, const GLfloat * params); typedef void (GLAD_API_PTR *PFNGLTEXENVIPROC)(GLenum target, GLenum pname, GLint param); typedef void (GLAD_API_PTR *PFNGLTEXENVIVPROC)(GLenum target, GLenum pname, const GLint * params); typedef void (GLAD_API_PTR *PFNGLTEXGENDPROC)(GLenum coord, GLenum pname, GLdouble param); typedef void (GLAD_API_PTR *PFNGLTEXGENDVPROC)(GLenum coord, GLenum pname, const GLdouble * params); typedef void (GLAD_API_PTR *PFNGLTEXGENFPROC)(GLenum coord, GLenum pname, GLfloat param); typedef void (GLAD_API_PTR *PFNGLTEXGENFVPROC)(GLenum coord, GLenum pname, const GLfloat * params); typedef void (GLAD_API_PTR *PFNGLTEXGENIPROC)(GLenum coord, GLenum pname, GLint param); typedef void (GLAD_API_PTR *PFNGLTEXGENIVPROC)(GLenum coord, GLenum pname, const GLint * params); typedef void (GLAD_API_PTR *PFNGLTEXIMAGE1DPROC)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void * pixels); typedef void (GLAD_API_PTR *PFNGLTEXIMAGE2DPROC)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * pixels); typedef void (GLAD_API_PTR *PFNGLTEXIMAGE3DPROC)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void * pixels); typedef void (GLAD_API_PTR *PFNGLTEXPARAMETERIIVPROC)(GLenum target, GLenum pname, const GLint * params); typedef void (GLAD_API_PTR *PFNGLTEXPARAMETERIUIVPROC)(GLenum target, GLenum pname, const GLuint * params); typedef void (GLAD_API_PTR *PFNGLTEXPARAMETERFPROC)(GLenum target, GLenum pname, GLfloat param); typedef void (GLAD_API_PTR *PFNGLTEXPARAMETERFVPROC)(GLenum target, GLenum pname, const GLfloat * params); typedef void (GLAD_API_PTR *PFNGLTEXPARAMETERIPROC)(GLenum target, GLenum pname, GLint param); typedef void (GLAD_API_PTR *PFNGLTEXPARAMETERIVPROC)(GLenum target, GLenum pname, const GLint * params); typedef void (GLAD_API_PTR *PFNGLTEXSTORAGE1DPROC)(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); typedef void (GLAD_API_PTR *PFNGLTEXSTORAGE2DPROC)(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); typedef void (GLAD_API_PTR *PFNGLTEXSTORAGE3DPROC)(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); typedef void (GLAD_API_PTR *PFNGLTEXSUBIMAGE1DPROC)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void * pixels); typedef void (GLAD_API_PTR *PFNGLTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels); typedef void (GLAD_API_PTR *PFNGLTEXSUBIMAGE3DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void * pixels); typedef void (GLAD_API_PTR *PFNGLTRANSFORMFEEDBACKVARYINGSPROC)(GLuint program, GLsizei count, const GLchar *const* varyings, GLenum bufferMode); typedef void (GLAD_API_PTR *PFNGLTRANSLATEDPROC)(GLdouble x, GLdouble y, GLdouble z); typedef void (GLAD_API_PTR *PFNGLTRANSLATEFPROC)(GLfloat x, GLfloat y, GLfloat z); typedef void (GLAD_API_PTR *PFNGLUNIFORM1FPROC)(GLint location, GLfloat v0); typedef void (GLAD_API_PTR *PFNGLUNIFORM1FVPROC)(GLint location, GLsizei count, const GLfloat * value); typedef void (GLAD_API_PTR *PFNGLUNIFORM1IPROC)(GLint location, GLint v0); typedef void (GLAD_API_PTR *PFNGLUNIFORM1IVPROC)(GLint location, GLsizei count, const GLint * value); typedef void (GLAD_API_PTR *PFNGLUNIFORM1UIPROC)(GLint location, GLuint v0); typedef void (GLAD_API_PTR *PFNGLUNIFORM1UIVPROC)(GLint location, GLsizei count, const GLuint * value); typedef void (GLAD_API_PTR *PFNGLUNIFORM2FPROC)(GLint location, GLfloat v0, GLfloat v1); typedef void (GLAD_API_PTR *PFNGLUNIFORM2FVPROC)(GLint location, GLsizei count, const GLfloat * value); typedef void (GLAD_API_PTR *PFNGLUNIFORM2IPROC)(GLint location, GLint v0, GLint v1); typedef void (GLAD_API_PTR *PFNGLUNIFORM2IVPROC)(GLint location, GLsizei count, const GLint * value); typedef void (GLAD_API_PTR *PFNGLUNIFORM2UIPROC)(GLint location, GLuint v0, GLuint v1); typedef void (GLAD_API_PTR *PFNGLUNIFORM2UIVPROC)(GLint location, GLsizei count, const GLuint * value); typedef void (GLAD_API_PTR *PFNGLUNIFORM3FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); typedef void (GLAD_API_PTR *PFNGLUNIFORM3FVPROC)(GLint location, GLsizei count, const GLfloat * value); typedef void (GLAD_API_PTR *PFNGLUNIFORM3IPROC)(GLint location, GLint v0, GLint v1, GLint v2); typedef void (GLAD_API_PTR *PFNGLUNIFORM3IVPROC)(GLint location, GLsizei count, const GLint * value); typedef void (GLAD_API_PTR *PFNGLUNIFORM3UIPROC)(GLint location, GLuint v0, GLuint v1, GLuint v2); typedef void (GLAD_API_PTR *PFNGLUNIFORM3UIVPROC)(GLint location, GLsizei count, const GLuint * value); typedef void (GLAD_API_PTR *PFNGLUNIFORM4FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); typedef void (GLAD_API_PTR *PFNGLUNIFORM4FVPROC)(GLint location, GLsizei count, const GLfloat * value); typedef void (GLAD_API_PTR *PFNGLUNIFORM4IPROC)(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); typedef void (GLAD_API_PTR *PFNGLUNIFORM4IVPROC)(GLint location, GLsizei count, const GLint * value); typedef void (GLAD_API_PTR *PFNGLUNIFORM4UIPROC)(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); typedef void (GLAD_API_PTR *PFNGLUNIFORM4UIVPROC)(GLint location, GLsizei count, const GLuint * value); typedef void (GLAD_API_PTR *PFNGLUNIFORMBLOCKBINDINGPROC)(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); typedef void (GLAD_API_PTR *PFNGLUNIFORMMATRIX2FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); typedef void (GLAD_API_PTR *PFNGLUNIFORMMATRIX2X3FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); typedef void (GLAD_API_PTR *PFNGLUNIFORMMATRIX2X4FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); typedef void (GLAD_API_PTR *PFNGLUNIFORMMATRIX3FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); typedef void (GLAD_API_PTR *PFNGLUNIFORMMATRIX3X2FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); typedef void (GLAD_API_PTR *PFNGLUNIFORMMATRIX3X4FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); typedef void (GLAD_API_PTR *PFNGLUNIFORMMATRIX4FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); typedef void (GLAD_API_PTR *PFNGLUNIFORMMATRIX4X2FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); typedef void (GLAD_API_PTR *PFNGLUNIFORMMATRIX4X3FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); typedef GLboolean (GLAD_API_PTR *PFNGLUNMAPBUFFERPROC)(GLenum target); typedef void (GLAD_API_PTR *PFNGLUSEPROGRAMPROC)(GLuint program); typedef void (GLAD_API_PTR *PFNGLVALIDATEPROGRAMPROC)(GLuint program); typedef void (GLAD_API_PTR *PFNGLVERTEX2DPROC)(GLdouble x, GLdouble y); typedef void (GLAD_API_PTR *PFNGLVERTEX2DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLVERTEX2FPROC)(GLfloat x, GLfloat y); typedef void (GLAD_API_PTR *PFNGLVERTEX2FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLVERTEX2IPROC)(GLint x, GLint y); typedef void (GLAD_API_PTR *PFNGLVERTEX2IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLVERTEX2SPROC)(GLshort x, GLshort y); typedef void (GLAD_API_PTR *PFNGLVERTEX2SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLVERTEX3DPROC)(GLdouble x, GLdouble y, GLdouble z); typedef void (GLAD_API_PTR *PFNGLVERTEX3DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLVERTEX3FPROC)(GLfloat x, GLfloat y, GLfloat z); typedef void (GLAD_API_PTR *PFNGLVERTEX3FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLVERTEX3IPROC)(GLint x, GLint y, GLint z); typedef void (GLAD_API_PTR *PFNGLVERTEX3IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLVERTEX3SPROC)(GLshort x, GLshort y, GLshort z); typedef void (GLAD_API_PTR *PFNGLVERTEX3SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLVERTEX4DPROC)(GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (GLAD_API_PTR *PFNGLVERTEX4DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLVERTEX4FPROC)(GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (GLAD_API_PTR *PFNGLVERTEX4FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLVERTEX4IPROC)(GLint x, GLint y, GLint z, GLint w); typedef void (GLAD_API_PTR *PFNGLVERTEX4IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLVERTEX4SPROC)(GLshort x, GLshort y, GLshort z, GLshort w); typedef void (GLAD_API_PTR *PFNGLVERTEX4SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB1DPROC)(GLuint index, GLdouble x); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB1DVPROC)(GLuint index, const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB1FPROC)(GLuint index, GLfloat x); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB1FVPROC)(GLuint index, const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB1SPROC)(GLuint index, GLshort x); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB1SVPROC)(GLuint index, const GLshort * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB2DPROC)(GLuint index, GLdouble x, GLdouble y); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB2DVPROC)(GLuint index, const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB2FPROC)(GLuint index, GLfloat x, GLfloat y); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB2FVPROC)(GLuint index, const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB2SPROC)(GLuint index, GLshort x, GLshort y); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB2SVPROC)(GLuint index, const GLshort * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB3DPROC)(GLuint index, GLdouble x, GLdouble y, GLdouble z); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB3DVPROC)(GLuint index, const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB3FPROC)(GLuint index, GLfloat x, GLfloat y, GLfloat z); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB3FVPROC)(GLuint index, const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB3SPROC)(GLuint index, GLshort x, GLshort y, GLshort z); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB3SVPROC)(GLuint index, const GLshort * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4NBVPROC)(GLuint index, const GLbyte * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4NIVPROC)(GLuint index, const GLint * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4NSVPROC)(GLuint index, const GLshort * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4NUBPROC)(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4NUBVPROC)(GLuint index, const GLubyte * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4NUIVPROC)(GLuint index, const GLuint * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4NUSVPROC)(GLuint index, const GLushort * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4BVPROC)(GLuint index, const GLbyte * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4DPROC)(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4DVPROC)(GLuint index, const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4FPROC)(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4FVPROC)(GLuint index, const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4IVPROC)(GLuint index, const GLint * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4SPROC)(GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4SVPROC)(GLuint index, const GLshort * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4UBVPROC)(GLuint index, const GLubyte * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4UIVPROC)(GLuint index, const GLuint * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIB4USVPROC)(GLuint index, const GLushort * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBDIVISORARBPROC)(GLuint index, GLuint divisor); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI1IPROC)(GLuint index, GLint x); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI1IVPROC)(GLuint index, const GLint * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI1UIPROC)(GLuint index, GLuint x); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI1UIVPROC)(GLuint index, const GLuint * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI2IPROC)(GLuint index, GLint x, GLint y); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI2IVPROC)(GLuint index, const GLint * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI2UIPROC)(GLuint index, GLuint x, GLuint y); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI2UIVPROC)(GLuint index, const GLuint * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI3IPROC)(GLuint index, GLint x, GLint y, GLint z); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI3IVPROC)(GLuint index, const GLint * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI3UIPROC)(GLuint index, GLuint x, GLuint y, GLuint z); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI3UIVPROC)(GLuint index, const GLuint * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI4BVPROC)(GLuint index, const GLbyte * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI4IPROC)(GLuint index, GLint x, GLint y, GLint z, GLint w); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI4IVPROC)(GLuint index, const GLint * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI4SVPROC)(GLuint index, const GLshort * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI4UBVPROC)(GLuint index, const GLubyte * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI4UIPROC)(GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI4UIVPROC)(GLuint index, const GLuint * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBI4USVPROC)(GLuint index, const GLushort * v); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBIPOINTERPROC)(GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer); typedef void (GLAD_API_PTR *PFNGLVERTEXATTRIBPOINTERPROC)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer); typedef void (GLAD_API_PTR *PFNGLVERTEXPOINTERPROC)(GLint size, GLenum type, GLsizei stride, const void * pointer); typedef void (GLAD_API_PTR *PFNGLVIEWPORTPROC)(GLint x, GLint y, GLsizei width, GLsizei height); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS2DPROC)(GLdouble x, GLdouble y); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS2DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS2FPROC)(GLfloat x, GLfloat y); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS2FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS2IPROC)(GLint x, GLint y); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS2IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS2SPROC)(GLshort x, GLshort y); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS2SVPROC)(const GLshort * v); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS3DPROC)(GLdouble x, GLdouble y, GLdouble z); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS3DVPROC)(const GLdouble * v); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS3FPROC)(GLfloat x, GLfloat y, GLfloat z); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS3FVPROC)(const GLfloat * v); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS3IPROC)(GLint x, GLint y, GLint z); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS3IVPROC)(const GLint * v); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS3SPROC)(GLshort x, GLshort y, GLshort z); typedef void (GLAD_API_PTR *PFNGLWINDOWPOS3SVPROC)(const GLshort * v); GLAD_API_CALL PFNGLACCUMPROC glad_glAccum; GLAD_API_CALL PFNGLACCUMPROC glad_debug_glAccum; #define glAccum glad_debug_glAccum GLAD_API_CALL PFNGLACTIVETEXTUREPROC glad_glActiveTexture; GLAD_API_CALL PFNGLACTIVETEXTUREPROC glad_debug_glActiveTexture; #define glActiveTexture glad_debug_glActiveTexture GLAD_API_CALL PFNGLALPHAFUNCPROC glad_glAlphaFunc; GLAD_API_CALL PFNGLALPHAFUNCPROC glad_debug_glAlphaFunc; #define glAlphaFunc glad_debug_glAlphaFunc GLAD_API_CALL PFNGLARETEXTURESRESIDENTPROC glad_glAreTexturesResident; GLAD_API_CALL PFNGLARETEXTURESRESIDENTPROC glad_debug_glAreTexturesResident; #define glAreTexturesResident glad_debug_glAreTexturesResident GLAD_API_CALL PFNGLARRAYELEMENTPROC glad_glArrayElement; GLAD_API_CALL PFNGLARRAYELEMENTPROC glad_debug_glArrayElement; #define glArrayElement glad_debug_glArrayElement GLAD_API_CALL PFNGLATTACHSHADERPROC glad_glAttachShader; GLAD_API_CALL PFNGLATTACHSHADERPROC glad_debug_glAttachShader; #define glAttachShader glad_debug_glAttachShader GLAD_API_CALL PFNGLBEGINPROC glad_glBegin; GLAD_API_CALL PFNGLBEGINPROC glad_debug_glBegin; #define glBegin glad_debug_glBegin GLAD_API_CALL PFNGLBEGINCONDITIONALRENDERPROC glad_glBeginConditionalRender; GLAD_API_CALL PFNGLBEGINCONDITIONALRENDERPROC glad_debug_glBeginConditionalRender; #define glBeginConditionalRender glad_debug_glBeginConditionalRender GLAD_API_CALL PFNGLBEGINQUERYPROC glad_glBeginQuery; GLAD_API_CALL PFNGLBEGINQUERYPROC glad_debug_glBeginQuery; #define glBeginQuery glad_debug_glBeginQuery GLAD_API_CALL PFNGLBEGINTRANSFORMFEEDBACKPROC glad_glBeginTransformFeedback; GLAD_API_CALL PFNGLBEGINTRANSFORMFEEDBACKPROC glad_debug_glBeginTransformFeedback; #define glBeginTransformFeedback glad_debug_glBeginTransformFeedback GLAD_API_CALL PFNGLBINDATTRIBLOCATIONPROC glad_glBindAttribLocation; GLAD_API_CALL PFNGLBINDATTRIBLOCATIONPROC glad_debug_glBindAttribLocation; #define glBindAttribLocation glad_debug_glBindAttribLocation GLAD_API_CALL PFNGLBINDBUFFERPROC glad_glBindBuffer; GLAD_API_CALL PFNGLBINDBUFFERPROC glad_debug_glBindBuffer; #define glBindBuffer glad_debug_glBindBuffer GLAD_API_CALL PFNGLBINDBUFFERBASEPROC glad_glBindBufferBase; GLAD_API_CALL PFNGLBINDBUFFERBASEPROC glad_debug_glBindBufferBase; #define glBindBufferBase glad_debug_glBindBufferBase GLAD_API_CALL PFNGLBINDBUFFERRANGEPROC glad_glBindBufferRange; GLAD_API_CALL PFNGLBINDBUFFERRANGEPROC glad_debug_glBindBufferRange; #define glBindBufferRange glad_debug_glBindBufferRange GLAD_API_CALL PFNGLBINDFRAGDATALOCATIONPROC glad_glBindFragDataLocation; GLAD_API_CALL PFNGLBINDFRAGDATALOCATIONPROC glad_debug_glBindFragDataLocation; #define glBindFragDataLocation glad_debug_glBindFragDataLocation GLAD_API_CALL PFNGLBINDFRAMEBUFFERPROC glad_glBindFramebuffer; GLAD_API_CALL PFNGLBINDFRAMEBUFFERPROC glad_debug_glBindFramebuffer; #define glBindFramebuffer glad_debug_glBindFramebuffer GLAD_API_CALL PFNGLBINDRENDERBUFFERPROC glad_glBindRenderbuffer; GLAD_API_CALL PFNGLBINDRENDERBUFFERPROC glad_debug_glBindRenderbuffer; #define glBindRenderbuffer glad_debug_glBindRenderbuffer GLAD_API_CALL PFNGLBINDTEXTUREPROC glad_glBindTexture; GLAD_API_CALL PFNGLBINDTEXTUREPROC glad_debug_glBindTexture; #define glBindTexture glad_debug_glBindTexture GLAD_API_CALL PFNGLBINDVERTEXARRAYPROC glad_glBindVertexArray; GLAD_API_CALL PFNGLBINDVERTEXARRAYPROC glad_debug_glBindVertexArray; #define glBindVertexArray glad_debug_glBindVertexArray GLAD_API_CALL PFNGLBITMAPPROC glad_glBitmap; GLAD_API_CALL PFNGLBITMAPPROC glad_debug_glBitmap; #define glBitmap glad_debug_glBitmap GLAD_API_CALL PFNGLBLENDCOLORPROC glad_glBlendColor; GLAD_API_CALL PFNGLBLENDCOLORPROC glad_debug_glBlendColor; #define glBlendColor glad_debug_glBlendColor GLAD_API_CALL PFNGLBLENDEQUATIONPROC glad_glBlendEquation; GLAD_API_CALL PFNGLBLENDEQUATIONPROC glad_debug_glBlendEquation; #define glBlendEquation glad_debug_glBlendEquation GLAD_API_CALL PFNGLBLENDEQUATIONSEPARATEPROC glad_glBlendEquationSeparate; GLAD_API_CALL PFNGLBLENDEQUATIONSEPARATEPROC glad_debug_glBlendEquationSeparate; #define glBlendEquationSeparate glad_debug_glBlendEquationSeparate GLAD_API_CALL PFNGLBLENDFUNCPROC glad_glBlendFunc; GLAD_API_CALL PFNGLBLENDFUNCPROC glad_debug_glBlendFunc; #define glBlendFunc glad_debug_glBlendFunc GLAD_API_CALL PFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate; GLAD_API_CALL PFNGLBLENDFUNCSEPARATEPROC glad_debug_glBlendFuncSeparate; #define glBlendFuncSeparate glad_debug_glBlendFuncSeparate GLAD_API_CALL PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer; GLAD_API_CALL PFNGLBLITFRAMEBUFFERPROC glad_debug_glBlitFramebuffer; #define glBlitFramebuffer glad_debug_glBlitFramebuffer GLAD_API_CALL PFNGLBUFFERDATAPROC glad_glBufferData; GLAD_API_CALL PFNGLBUFFERDATAPROC glad_debug_glBufferData; #define glBufferData glad_debug_glBufferData GLAD_API_CALL PFNGLBUFFERSUBDATAPROC glad_glBufferSubData; GLAD_API_CALL PFNGLBUFFERSUBDATAPROC glad_debug_glBufferSubData; #define glBufferSubData glad_debug_glBufferSubData GLAD_API_CALL PFNGLCALLLISTPROC glad_glCallList; GLAD_API_CALL PFNGLCALLLISTPROC glad_debug_glCallList; #define glCallList glad_debug_glCallList GLAD_API_CALL PFNGLCALLLISTSPROC glad_glCallLists; GLAD_API_CALL PFNGLCALLLISTSPROC glad_debug_glCallLists; #define glCallLists glad_debug_glCallLists GLAD_API_CALL PFNGLCHECKFRAMEBUFFERSTATUSPROC glad_glCheckFramebufferStatus; GLAD_API_CALL PFNGLCHECKFRAMEBUFFERSTATUSPROC glad_debug_glCheckFramebufferStatus; #define glCheckFramebufferStatus glad_debug_glCheckFramebufferStatus GLAD_API_CALL PFNGLCLAMPCOLORPROC glad_glClampColor; GLAD_API_CALL PFNGLCLAMPCOLORPROC glad_debug_glClampColor; #define glClampColor glad_debug_glClampColor GLAD_API_CALL PFNGLCLEARPROC glad_glClear; GLAD_API_CALL PFNGLCLEARPROC glad_debug_glClear; #define glClear glad_debug_glClear GLAD_API_CALL PFNGLCLEARACCUMPROC glad_glClearAccum; GLAD_API_CALL PFNGLCLEARACCUMPROC glad_debug_glClearAccum; #define glClearAccum glad_debug_glClearAccum GLAD_API_CALL PFNGLCLEARBUFFERFIPROC glad_glClearBufferfi; GLAD_API_CALL PFNGLCLEARBUFFERFIPROC glad_debug_glClearBufferfi; #define glClearBufferfi glad_debug_glClearBufferfi GLAD_API_CALL PFNGLCLEARBUFFERFVPROC glad_glClearBufferfv; GLAD_API_CALL PFNGLCLEARBUFFERFVPROC glad_debug_glClearBufferfv; #define glClearBufferfv glad_debug_glClearBufferfv GLAD_API_CALL PFNGLCLEARBUFFERIVPROC glad_glClearBufferiv; GLAD_API_CALL PFNGLCLEARBUFFERIVPROC glad_debug_glClearBufferiv; #define glClearBufferiv glad_debug_glClearBufferiv GLAD_API_CALL PFNGLCLEARBUFFERUIVPROC glad_glClearBufferuiv; GLAD_API_CALL PFNGLCLEARBUFFERUIVPROC glad_debug_glClearBufferuiv; #define glClearBufferuiv glad_debug_glClearBufferuiv GLAD_API_CALL PFNGLCLEARCOLORPROC glad_glClearColor; GLAD_API_CALL PFNGLCLEARCOLORPROC glad_debug_glClearColor; #define glClearColor glad_debug_glClearColor GLAD_API_CALL PFNGLCLEARDEPTHPROC glad_glClearDepth; GLAD_API_CALL PFNGLCLEARDEPTHPROC glad_debug_glClearDepth; #define glClearDepth glad_debug_glClearDepth GLAD_API_CALL PFNGLCLEARINDEXPROC glad_glClearIndex; GLAD_API_CALL PFNGLCLEARINDEXPROC glad_debug_glClearIndex; #define glClearIndex glad_debug_glClearIndex GLAD_API_CALL PFNGLCLEARSTENCILPROC glad_glClearStencil; GLAD_API_CALL PFNGLCLEARSTENCILPROC glad_debug_glClearStencil; #define glClearStencil glad_debug_glClearStencil GLAD_API_CALL PFNGLCLIENTACTIVETEXTUREPROC glad_glClientActiveTexture; GLAD_API_CALL PFNGLCLIENTACTIVETEXTUREPROC glad_debug_glClientActiveTexture; #define glClientActiveTexture glad_debug_glClientActiveTexture GLAD_API_CALL PFNGLCLIPPLANEPROC glad_glClipPlane; GLAD_API_CALL PFNGLCLIPPLANEPROC glad_debug_glClipPlane; #define glClipPlane glad_debug_glClipPlane GLAD_API_CALL PFNGLCOLOR3BPROC glad_glColor3b; GLAD_API_CALL PFNGLCOLOR3BPROC glad_debug_glColor3b; #define glColor3b glad_debug_glColor3b GLAD_API_CALL PFNGLCOLOR3BVPROC glad_glColor3bv; GLAD_API_CALL PFNGLCOLOR3BVPROC glad_debug_glColor3bv; #define glColor3bv glad_debug_glColor3bv GLAD_API_CALL PFNGLCOLOR3DPROC glad_glColor3d; GLAD_API_CALL PFNGLCOLOR3DPROC glad_debug_glColor3d; #define glColor3d glad_debug_glColor3d GLAD_API_CALL PFNGLCOLOR3DVPROC glad_glColor3dv; GLAD_API_CALL PFNGLCOLOR3DVPROC glad_debug_glColor3dv; #define glColor3dv glad_debug_glColor3dv GLAD_API_CALL PFNGLCOLOR3FPROC glad_glColor3f; GLAD_API_CALL PFNGLCOLOR3FPROC glad_debug_glColor3f; #define glColor3f glad_debug_glColor3f GLAD_API_CALL PFNGLCOLOR3FVPROC glad_glColor3fv; GLAD_API_CALL PFNGLCOLOR3FVPROC glad_debug_glColor3fv; #define glColor3fv glad_debug_glColor3fv GLAD_API_CALL PFNGLCOLOR3IPROC glad_glColor3i; GLAD_API_CALL PFNGLCOLOR3IPROC glad_debug_glColor3i; #define glColor3i glad_debug_glColor3i GLAD_API_CALL PFNGLCOLOR3IVPROC glad_glColor3iv; GLAD_API_CALL PFNGLCOLOR3IVPROC glad_debug_glColor3iv; #define glColor3iv glad_debug_glColor3iv GLAD_API_CALL PFNGLCOLOR3SPROC glad_glColor3s; GLAD_API_CALL PFNGLCOLOR3SPROC glad_debug_glColor3s; #define glColor3s glad_debug_glColor3s GLAD_API_CALL PFNGLCOLOR3SVPROC glad_glColor3sv; GLAD_API_CALL PFNGLCOLOR3SVPROC glad_debug_glColor3sv; #define glColor3sv glad_debug_glColor3sv GLAD_API_CALL PFNGLCOLOR3UBPROC glad_glColor3ub; GLAD_API_CALL PFNGLCOLOR3UBPROC glad_debug_glColor3ub; #define glColor3ub glad_debug_glColor3ub GLAD_API_CALL PFNGLCOLOR3UBVPROC glad_glColor3ubv; GLAD_API_CALL PFNGLCOLOR3UBVPROC glad_debug_glColor3ubv; #define glColor3ubv glad_debug_glColor3ubv GLAD_API_CALL PFNGLCOLOR3UIPROC glad_glColor3ui; GLAD_API_CALL PFNGLCOLOR3UIPROC glad_debug_glColor3ui; #define glColor3ui glad_debug_glColor3ui GLAD_API_CALL PFNGLCOLOR3UIVPROC glad_glColor3uiv; GLAD_API_CALL PFNGLCOLOR3UIVPROC glad_debug_glColor3uiv; #define glColor3uiv glad_debug_glColor3uiv GLAD_API_CALL PFNGLCOLOR3USPROC glad_glColor3us; GLAD_API_CALL PFNGLCOLOR3USPROC glad_debug_glColor3us; #define glColor3us glad_debug_glColor3us GLAD_API_CALL PFNGLCOLOR3USVPROC glad_glColor3usv; GLAD_API_CALL PFNGLCOLOR3USVPROC glad_debug_glColor3usv; #define glColor3usv glad_debug_glColor3usv GLAD_API_CALL PFNGLCOLOR4BPROC glad_glColor4b; GLAD_API_CALL PFNGLCOLOR4BPROC glad_debug_glColor4b; #define glColor4b glad_debug_glColor4b GLAD_API_CALL PFNGLCOLOR4BVPROC glad_glColor4bv; GLAD_API_CALL PFNGLCOLOR4BVPROC glad_debug_glColor4bv; #define glColor4bv glad_debug_glColor4bv GLAD_API_CALL PFNGLCOLOR4DPROC glad_glColor4d; GLAD_API_CALL PFNGLCOLOR4DPROC glad_debug_glColor4d; #define glColor4d glad_debug_glColor4d GLAD_API_CALL PFNGLCOLOR4DVPROC glad_glColor4dv; GLAD_API_CALL PFNGLCOLOR4DVPROC glad_debug_glColor4dv; #define glColor4dv glad_debug_glColor4dv GLAD_API_CALL PFNGLCOLOR4FPROC glad_glColor4f; GLAD_API_CALL PFNGLCOLOR4FPROC glad_debug_glColor4f; #define glColor4f glad_debug_glColor4f GLAD_API_CALL PFNGLCOLOR4FVPROC glad_glColor4fv; GLAD_API_CALL PFNGLCOLOR4FVPROC glad_debug_glColor4fv; #define glColor4fv glad_debug_glColor4fv GLAD_API_CALL PFNGLCOLOR4IPROC glad_glColor4i; GLAD_API_CALL PFNGLCOLOR4IPROC glad_debug_glColor4i; #define glColor4i glad_debug_glColor4i GLAD_API_CALL PFNGLCOLOR4IVPROC glad_glColor4iv; GLAD_API_CALL PFNGLCOLOR4IVPROC glad_debug_glColor4iv; #define glColor4iv glad_debug_glColor4iv GLAD_API_CALL PFNGLCOLOR4SPROC glad_glColor4s; GLAD_API_CALL PFNGLCOLOR4SPROC glad_debug_glColor4s; #define glColor4s glad_debug_glColor4s GLAD_API_CALL PFNGLCOLOR4SVPROC glad_glColor4sv; GLAD_API_CALL PFNGLCOLOR4SVPROC glad_debug_glColor4sv; #define glColor4sv glad_debug_glColor4sv GLAD_API_CALL PFNGLCOLOR4UBPROC glad_glColor4ub; GLAD_API_CALL PFNGLCOLOR4UBPROC glad_debug_glColor4ub; #define glColor4ub glad_debug_glColor4ub GLAD_API_CALL PFNGLCOLOR4UBVPROC glad_glColor4ubv; GLAD_API_CALL PFNGLCOLOR4UBVPROC glad_debug_glColor4ubv; #define glColor4ubv glad_debug_glColor4ubv GLAD_API_CALL PFNGLCOLOR4UIPROC glad_glColor4ui; GLAD_API_CALL PFNGLCOLOR4UIPROC glad_debug_glColor4ui; #define glColor4ui glad_debug_glColor4ui GLAD_API_CALL PFNGLCOLOR4UIVPROC glad_glColor4uiv; GLAD_API_CALL PFNGLCOLOR4UIVPROC glad_debug_glColor4uiv; #define glColor4uiv glad_debug_glColor4uiv GLAD_API_CALL PFNGLCOLOR4USPROC glad_glColor4us; GLAD_API_CALL PFNGLCOLOR4USPROC glad_debug_glColor4us; #define glColor4us glad_debug_glColor4us GLAD_API_CALL PFNGLCOLOR4USVPROC glad_glColor4usv; GLAD_API_CALL PFNGLCOLOR4USVPROC glad_debug_glColor4usv; #define glColor4usv glad_debug_glColor4usv GLAD_API_CALL PFNGLCOLORMASKPROC glad_glColorMask; GLAD_API_CALL PFNGLCOLORMASKPROC glad_debug_glColorMask; #define glColorMask glad_debug_glColorMask GLAD_API_CALL PFNGLCOLORMASKIPROC glad_glColorMaski; GLAD_API_CALL PFNGLCOLORMASKIPROC glad_debug_glColorMaski; #define glColorMaski glad_debug_glColorMaski GLAD_API_CALL PFNGLCOLORMATERIALPROC glad_glColorMaterial; GLAD_API_CALL PFNGLCOLORMATERIALPROC glad_debug_glColorMaterial; #define glColorMaterial glad_debug_glColorMaterial GLAD_API_CALL PFNGLCOLORPOINTERPROC glad_glColorPointer; GLAD_API_CALL PFNGLCOLORPOINTERPROC glad_debug_glColorPointer; #define glColorPointer glad_debug_glColorPointer GLAD_API_CALL PFNGLCOMPILESHADERPROC glad_glCompileShader; GLAD_API_CALL PFNGLCOMPILESHADERPROC glad_debug_glCompileShader; #define glCompileShader glad_debug_glCompileShader GLAD_API_CALL PFNGLCOMPRESSEDTEXIMAGE1DPROC glad_glCompressedTexImage1D; GLAD_API_CALL PFNGLCOMPRESSEDTEXIMAGE1DPROC glad_debug_glCompressedTexImage1D; #define glCompressedTexImage1D glad_debug_glCompressedTexImage1D GLAD_API_CALL PFNGLCOMPRESSEDTEXIMAGE2DPROC glad_glCompressedTexImage2D; GLAD_API_CALL PFNGLCOMPRESSEDTEXIMAGE2DPROC glad_debug_glCompressedTexImage2D; #define glCompressedTexImage2D glad_debug_glCompressedTexImage2D GLAD_API_CALL PFNGLCOMPRESSEDTEXIMAGE3DPROC glad_glCompressedTexImage3D; GLAD_API_CALL PFNGLCOMPRESSEDTEXIMAGE3DPROC glad_debug_glCompressedTexImage3D; #define glCompressedTexImage3D glad_debug_glCompressedTexImage3D GLAD_API_CALL PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC glad_glCompressedTexSubImage1D; GLAD_API_CALL PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC glad_debug_glCompressedTexSubImage1D; #define glCompressedTexSubImage1D glad_debug_glCompressedTexSubImage1D GLAD_API_CALL PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glad_glCompressedTexSubImage2D; GLAD_API_CALL PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glad_debug_glCompressedTexSubImage2D; #define glCompressedTexSubImage2D glad_debug_glCompressedTexSubImage2D GLAD_API_CALL PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC glad_glCompressedTexSubImage3D; GLAD_API_CALL PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC glad_debug_glCompressedTexSubImage3D; #define glCompressedTexSubImage3D glad_debug_glCompressedTexSubImage3D GLAD_API_CALL PFNGLCOPYBUFFERSUBDATAPROC glad_glCopyBufferSubData; GLAD_API_CALL PFNGLCOPYBUFFERSUBDATAPROC glad_debug_glCopyBufferSubData; #define glCopyBufferSubData glad_debug_glCopyBufferSubData GLAD_API_CALL PFNGLCOPYIMAGESUBDATAPROC glad_glCopyImageSubData; GLAD_API_CALL PFNGLCOPYIMAGESUBDATAPROC glad_debug_glCopyImageSubData; #define glCopyImageSubData glad_debug_glCopyImageSubData GLAD_API_CALL PFNGLCOPYPIXELSPROC glad_glCopyPixels; GLAD_API_CALL PFNGLCOPYPIXELSPROC glad_debug_glCopyPixels; #define glCopyPixels glad_debug_glCopyPixels GLAD_API_CALL PFNGLCOPYTEXIMAGE1DPROC glad_glCopyTexImage1D; GLAD_API_CALL PFNGLCOPYTEXIMAGE1DPROC glad_debug_glCopyTexImage1D; #define glCopyTexImage1D glad_debug_glCopyTexImage1D GLAD_API_CALL PFNGLCOPYTEXIMAGE2DPROC glad_glCopyTexImage2D; GLAD_API_CALL PFNGLCOPYTEXIMAGE2DPROC glad_debug_glCopyTexImage2D; #define glCopyTexImage2D glad_debug_glCopyTexImage2D GLAD_API_CALL PFNGLCOPYTEXSUBIMAGE1DPROC glad_glCopyTexSubImage1D; GLAD_API_CALL PFNGLCOPYTEXSUBIMAGE1DPROC glad_debug_glCopyTexSubImage1D; #define glCopyTexSubImage1D glad_debug_glCopyTexSubImage1D GLAD_API_CALL PFNGLCOPYTEXSUBIMAGE2DPROC glad_glCopyTexSubImage2D; GLAD_API_CALL PFNGLCOPYTEXSUBIMAGE2DPROC glad_debug_glCopyTexSubImage2D; #define glCopyTexSubImage2D glad_debug_glCopyTexSubImage2D GLAD_API_CALL PFNGLCOPYTEXSUBIMAGE3DPROC glad_glCopyTexSubImage3D; GLAD_API_CALL PFNGLCOPYTEXSUBIMAGE3DPROC glad_debug_glCopyTexSubImage3D; #define glCopyTexSubImage3D glad_debug_glCopyTexSubImage3D GLAD_API_CALL PFNGLCREATEPROGRAMPROC glad_glCreateProgram; GLAD_API_CALL PFNGLCREATEPROGRAMPROC glad_debug_glCreateProgram; #define glCreateProgram glad_debug_glCreateProgram GLAD_API_CALL PFNGLCREATESHADERPROC glad_glCreateShader; GLAD_API_CALL PFNGLCREATESHADERPROC glad_debug_glCreateShader; #define glCreateShader glad_debug_glCreateShader GLAD_API_CALL PFNGLCULLFACEPROC glad_glCullFace; GLAD_API_CALL PFNGLCULLFACEPROC glad_debug_glCullFace; #define glCullFace glad_debug_glCullFace GLAD_API_CALL PFNGLDEBUGMESSAGECALLBACKPROC glad_glDebugMessageCallback; GLAD_API_CALL PFNGLDEBUGMESSAGECALLBACKPROC glad_debug_glDebugMessageCallback; #define glDebugMessageCallback glad_debug_glDebugMessageCallback GLAD_API_CALL PFNGLDEBUGMESSAGECONTROLPROC glad_glDebugMessageControl; GLAD_API_CALL PFNGLDEBUGMESSAGECONTROLPROC glad_debug_glDebugMessageControl; #define glDebugMessageControl glad_debug_glDebugMessageControl GLAD_API_CALL PFNGLDEBUGMESSAGEINSERTPROC glad_glDebugMessageInsert; GLAD_API_CALL PFNGLDEBUGMESSAGEINSERTPROC glad_debug_glDebugMessageInsert; #define glDebugMessageInsert glad_debug_glDebugMessageInsert GLAD_API_CALL PFNGLDELETEBUFFERSPROC glad_glDeleteBuffers; GLAD_API_CALL PFNGLDELETEBUFFERSPROC glad_debug_glDeleteBuffers; #define glDeleteBuffers glad_debug_glDeleteBuffers GLAD_API_CALL PFNGLDELETEFRAMEBUFFERSPROC glad_glDeleteFramebuffers; GLAD_API_CALL PFNGLDELETEFRAMEBUFFERSPROC glad_debug_glDeleteFramebuffers; #define glDeleteFramebuffers glad_debug_glDeleteFramebuffers GLAD_API_CALL PFNGLDELETELISTSPROC glad_glDeleteLists; GLAD_API_CALL PFNGLDELETELISTSPROC glad_debug_glDeleteLists; #define glDeleteLists glad_debug_glDeleteLists GLAD_API_CALL PFNGLDELETEPROGRAMPROC glad_glDeleteProgram; GLAD_API_CALL PFNGLDELETEPROGRAMPROC glad_debug_glDeleteProgram; #define glDeleteProgram glad_debug_glDeleteProgram GLAD_API_CALL PFNGLDELETEQUERIESPROC glad_glDeleteQueries; GLAD_API_CALL PFNGLDELETEQUERIESPROC glad_debug_glDeleteQueries; #define glDeleteQueries glad_debug_glDeleteQueries GLAD_API_CALL PFNGLDELETERENDERBUFFERSPROC glad_glDeleteRenderbuffers; GLAD_API_CALL PFNGLDELETERENDERBUFFERSPROC glad_debug_glDeleteRenderbuffers; #define glDeleteRenderbuffers glad_debug_glDeleteRenderbuffers GLAD_API_CALL PFNGLDELETESHADERPROC glad_glDeleteShader; GLAD_API_CALL PFNGLDELETESHADERPROC glad_debug_glDeleteShader; #define glDeleteShader glad_debug_glDeleteShader GLAD_API_CALL PFNGLDELETETEXTURESPROC glad_glDeleteTextures; GLAD_API_CALL PFNGLDELETETEXTURESPROC glad_debug_glDeleteTextures; #define glDeleteTextures glad_debug_glDeleteTextures GLAD_API_CALL PFNGLDELETEVERTEXARRAYSPROC glad_glDeleteVertexArrays; GLAD_API_CALL PFNGLDELETEVERTEXARRAYSPROC glad_debug_glDeleteVertexArrays; #define glDeleteVertexArrays glad_debug_glDeleteVertexArrays GLAD_API_CALL PFNGLDEPTHFUNCPROC glad_glDepthFunc; GLAD_API_CALL PFNGLDEPTHFUNCPROC glad_debug_glDepthFunc; #define glDepthFunc glad_debug_glDepthFunc GLAD_API_CALL PFNGLDEPTHMASKPROC glad_glDepthMask; GLAD_API_CALL PFNGLDEPTHMASKPROC glad_debug_glDepthMask; #define glDepthMask glad_debug_glDepthMask GLAD_API_CALL PFNGLDEPTHRANGEPROC glad_glDepthRange; GLAD_API_CALL PFNGLDEPTHRANGEPROC glad_debug_glDepthRange; #define glDepthRange glad_debug_glDepthRange GLAD_API_CALL PFNGLDETACHSHADERPROC glad_glDetachShader; GLAD_API_CALL PFNGLDETACHSHADERPROC glad_debug_glDetachShader; #define glDetachShader glad_debug_glDetachShader GLAD_API_CALL PFNGLDISABLEPROC glad_glDisable; GLAD_API_CALL PFNGLDISABLEPROC glad_debug_glDisable; #define glDisable glad_debug_glDisable GLAD_API_CALL PFNGLDISABLECLIENTSTATEPROC glad_glDisableClientState; GLAD_API_CALL PFNGLDISABLECLIENTSTATEPROC glad_debug_glDisableClientState; #define glDisableClientState glad_debug_glDisableClientState GLAD_API_CALL PFNGLDISABLEVERTEXATTRIBARRAYPROC glad_glDisableVertexAttribArray; GLAD_API_CALL PFNGLDISABLEVERTEXATTRIBARRAYPROC glad_debug_glDisableVertexAttribArray; #define glDisableVertexAttribArray glad_debug_glDisableVertexAttribArray GLAD_API_CALL PFNGLDISABLEIPROC glad_glDisablei; GLAD_API_CALL PFNGLDISABLEIPROC glad_debug_glDisablei; #define glDisablei glad_debug_glDisablei GLAD_API_CALL PFNGLDRAWARRAYSPROC glad_glDrawArrays; GLAD_API_CALL PFNGLDRAWARRAYSPROC glad_debug_glDrawArrays; #define glDrawArrays glad_debug_glDrawArrays GLAD_API_CALL PFNGLDRAWARRAYSINSTANCEDPROC glad_glDrawArraysInstanced; GLAD_API_CALL PFNGLDRAWARRAYSINSTANCEDPROC glad_debug_glDrawArraysInstanced; #define glDrawArraysInstanced glad_debug_glDrawArraysInstanced GLAD_API_CALL PFNGLDRAWBUFFERPROC glad_glDrawBuffer; GLAD_API_CALL PFNGLDRAWBUFFERPROC glad_debug_glDrawBuffer; #define glDrawBuffer glad_debug_glDrawBuffer GLAD_API_CALL PFNGLDRAWBUFFERSPROC glad_glDrawBuffers; GLAD_API_CALL PFNGLDRAWBUFFERSPROC glad_debug_glDrawBuffers; #define glDrawBuffers glad_debug_glDrawBuffers GLAD_API_CALL PFNGLDRAWELEMENTSPROC glad_glDrawElements; GLAD_API_CALL PFNGLDRAWELEMENTSPROC glad_debug_glDrawElements; #define glDrawElements glad_debug_glDrawElements GLAD_API_CALL PFNGLDRAWELEMENTSINSTANCEDPROC glad_glDrawElementsInstanced; GLAD_API_CALL PFNGLDRAWELEMENTSINSTANCEDPROC glad_debug_glDrawElementsInstanced; #define glDrawElementsInstanced glad_debug_glDrawElementsInstanced GLAD_API_CALL PFNGLDRAWPIXELSPROC glad_glDrawPixels; GLAD_API_CALL PFNGLDRAWPIXELSPROC glad_debug_glDrawPixels; #define glDrawPixels glad_debug_glDrawPixels GLAD_API_CALL PFNGLDRAWRANGEELEMENTSPROC glad_glDrawRangeElements; GLAD_API_CALL PFNGLDRAWRANGEELEMENTSPROC glad_debug_glDrawRangeElements; #define glDrawRangeElements glad_debug_glDrawRangeElements GLAD_API_CALL PFNGLEDGEFLAGPROC glad_glEdgeFlag; GLAD_API_CALL PFNGLEDGEFLAGPROC glad_debug_glEdgeFlag; #define glEdgeFlag glad_debug_glEdgeFlag GLAD_API_CALL PFNGLEDGEFLAGPOINTERPROC glad_glEdgeFlagPointer; GLAD_API_CALL PFNGLEDGEFLAGPOINTERPROC glad_debug_glEdgeFlagPointer; #define glEdgeFlagPointer glad_debug_glEdgeFlagPointer GLAD_API_CALL PFNGLEDGEFLAGVPROC glad_glEdgeFlagv; GLAD_API_CALL PFNGLEDGEFLAGVPROC glad_debug_glEdgeFlagv; #define glEdgeFlagv glad_debug_glEdgeFlagv GLAD_API_CALL PFNGLENABLEPROC glad_glEnable; GLAD_API_CALL PFNGLENABLEPROC glad_debug_glEnable; #define glEnable glad_debug_glEnable GLAD_API_CALL PFNGLENABLECLIENTSTATEPROC glad_glEnableClientState; GLAD_API_CALL PFNGLENABLECLIENTSTATEPROC glad_debug_glEnableClientState; #define glEnableClientState glad_debug_glEnableClientState GLAD_API_CALL PFNGLENABLEVERTEXATTRIBARRAYPROC glad_glEnableVertexAttribArray; GLAD_API_CALL PFNGLENABLEVERTEXATTRIBARRAYPROC glad_debug_glEnableVertexAttribArray; #define glEnableVertexAttribArray glad_debug_glEnableVertexAttribArray GLAD_API_CALL PFNGLENABLEIPROC glad_glEnablei; GLAD_API_CALL PFNGLENABLEIPROC glad_debug_glEnablei; #define glEnablei glad_debug_glEnablei GLAD_API_CALL PFNGLENDPROC glad_glEnd; GLAD_API_CALL PFNGLENDPROC glad_debug_glEnd; #define glEnd glad_debug_glEnd GLAD_API_CALL PFNGLENDCONDITIONALRENDERPROC glad_glEndConditionalRender; GLAD_API_CALL PFNGLENDCONDITIONALRENDERPROC glad_debug_glEndConditionalRender; #define glEndConditionalRender glad_debug_glEndConditionalRender GLAD_API_CALL PFNGLENDLISTPROC glad_glEndList; GLAD_API_CALL PFNGLENDLISTPROC glad_debug_glEndList; #define glEndList glad_debug_glEndList GLAD_API_CALL PFNGLENDQUERYPROC glad_glEndQuery; GLAD_API_CALL PFNGLENDQUERYPROC glad_debug_glEndQuery; #define glEndQuery glad_debug_glEndQuery GLAD_API_CALL PFNGLENDTRANSFORMFEEDBACKPROC glad_glEndTransformFeedback; GLAD_API_CALL PFNGLENDTRANSFORMFEEDBACKPROC glad_debug_glEndTransformFeedback; #define glEndTransformFeedback glad_debug_glEndTransformFeedback GLAD_API_CALL PFNGLEVALCOORD1DPROC glad_glEvalCoord1d; GLAD_API_CALL PFNGLEVALCOORD1DPROC glad_debug_glEvalCoord1d; #define glEvalCoord1d glad_debug_glEvalCoord1d GLAD_API_CALL PFNGLEVALCOORD1DVPROC glad_glEvalCoord1dv; GLAD_API_CALL PFNGLEVALCOORD1DVPROC glad_debug_glEvalCoord1dv; #define glEvalCoord1dv glad_debug_glEvalCoord1dv GLAD_API_CALL PFNGLEVALCOORD1FPROC glad_glEvalCoord1f; GLAD_API_CALL PFNGLEVALCOORD1FPROC glad_debug_glEvalCoord1f; #define glEvalCoord1f glad_debug_glEvalCoord1f GLAD_API_CALL PFNGLEVALCOORD1FVPROC glad_glEvalCoord1fv; GLAD_API_CALL PFNGLEVALCOORD1FVPROC glad_debug_glEvalCoord1fv; #define glEvalCoord1fv glad_debug_glEvalCoord1fv GLAD_API_CALL PFNGLEVALCOORD2DPROC glad_glEvalCoord2d; GLAD_API_CALL PFNGLEVALCOORD2DPROC glad_debug_glEvalCoord2d; #define glEvalCoord2d glad_debug_glEvalCoord2d GLAD_API_CALL PFNGLEVALCOORD2DVPROC glad_glEvalCoord2dv; GLAD_API_CALL PFNGLEVALCOORD2DVPROC glad_debug_glEvalCoord2dv; #define glEvalCoord2dv glad_debug_glEvalCoord2dv GLAD_API_CALL PFNGLEVALCOORD2FPROC glad_glEvalCoord2f; GLAD_API_CALL PFNGLEVALCOORD2FPROC glad_debug_glEvalCoord2f; #define glEvalCoord2f glad_debug_glEvalCoord2f GLAD_API_CALL PFNGLEVALCOORD2FVPROC glad_glEvalCoord2fv; GLAD_API_CALL PFNGLEVALCOORD2FVPROC glad_debug_glEvalCoord2fv; #define glEvalCoord2fv glad_debug_glEvalCoord2fv GLAD_API_CALL PFNGLEVALMESH1PROC glad_glEvalMesh1; GLAD_API_CALL PFNGLEVALMESH1PROC glad_debug_glEvalMesh1; #define glEvalMesh1 glad_debug_glEvalMesh1 GLAD_API_CALL PFNGLEVALMESH2PROC glad_glEvalMesh2; GLAD_API_CALL PFNGLEVALMESH2PROC glad_debug_glEvalMesh2; #define glEvalMesh2 glad_debug_glEvalMesh2 GLAD_API_CALL PFNGLEVALPOINT1PROC glad_glEvalPoint1; GLAD_API_CALL PFNGLEVALPOINT1PROC glad_debug_glEvalPoint1; #define glEvalPoint1 glad_debug_glEvalPoint1 GLAD_API_CALL PFNGLEVALPOINT2PROC glad_glEvalPoint2; GLAD_API_CALL PFNGLEVALPOINT2PROC glad_debug_glEvalPoint2; #define glEvalPoint2 glad_debug_glEvalPoint2 GLAD_API_CALL PFNGLFEEDBACKBUFFERPROC glad_glFeedbackBuffer; GLAD_API_CALL PFNGLFEEDBACKBUFFERPROC glad_debug_glFeedbackBuffer; #define glFeedbackBuffer glad_debug_glFeedbackBuffer GLAD_API_CALL PFNGLFINISHPROC glad_glFinish; GLAD_API_CALL PFNGLFINISHPROC glad_debug_glFinish; #define glFinish glad_debug_glFinish GLAD_API_CALL PFNGLFLUSHPROC glad_glFlush; GLAD_API_CALL PFNGLFLUSHPROC glad_debug_glFlush; #define glFlush glad_debug_glFlush GLAD_API_CALL PFNGLFLUSHMAPPEDBUFFERRANGEPROC glad_glFlushMappedBufferRange; GLAD_API_CALL PFNGLFLUSHMAPPEDBUFFERRANGEPROC glad_debug_glFlushMappedBufferRange; #define glFlushMappedBufferRange glad_debug_glFlushMappedBufferRange GLAD_API_CALL PFNGLFOGCOORDPOINTERPROC glad_glFogCoordPointer; GLAD_API_CALL PFNGLFOGCOORDPOINTERPROC glad_debug_glFogCoordPointer; #define glFogCoordPointer glad_debug_glFogCoordPointer GLAD_API_CALL PFNGLFOGCOORDDPROC glad_glFogCoordd; GLAD_API_CALL PFNGLFOGCOORDDPROC glad_debug_glFogCoordd; #define glFogCoordd glad_debug_glFogCoordd GLAD_API_CALL PFNGLFOGCOORDDVPROC glad_glFogCoorddv; GLAD_API_CALL PFNGLFOGCOORDDVPROC glad_debug_glFogCoorddv; #define glFogCoorddv glad_debug_glFogCoorddv GLAD_API_CALL PFNGLFOGCOORDFPROC glad_glFogCoordf; GLAD_API_CALL PFNGLFOGCOORDFPROC glad_debug_glFogCoordf; #define glFogCoordf glad_debug_glFogCoordf GLAD_API_CALL PFNGLFOGCOORDFVPROC glad_glFogCoordfv; GLAD_API_CALL PFNGLFOGCOORDFVPROC glad_debug_glFogCoordfv; #define glFogCoordfv glad_debug_glFogCoordfv GLAD_API_CALL PFNGLFOGFPROC glad_glFogf; GLAD_API_CALL PFNGLFOGFPROC glad_debug_glFogf; #define glFogf glad_debug_glFogf GLAD_API_CALL PFNGLFOGFVPROC glad_glFogfv; GLAD_API_CALL PFNGLFOGFVPROC glad_debug_glFogfv; #define glFogfv glad_debug_glFogfv GLAD_API_CALL PFNGLFOGIPROC glad_glFogi; GLAD_API_CALL PFNGLFOGIPROC glad_debug_glFogi; #define glFogi glad_debug_glFogi GLAD_API_CALL PFNGLFOGIVPROC glad_glFogiv; GLAD_API_CALL PFNGLFOGIVPROC glad_debug_glFogiv; #define glFogiv glad_debug_glFogiv GLAD_API_CALL PFNGLFRAMEBUFFERRENDERBUFFERPROC glad_glFramebufferRenderbuffer; GLAD_API_CALL PFNGLFRAMEBUFFERRENDERBUFFERPROC glad_debug_glFramebufferRenderbuffer; #define glFramebufferRenderbuffer glad_debug_glFramebufferRenderbuffer GLAD_API_CALL PFNGLFRAMEBUFFERTEXTURE1DPROC glad_glFramebufferTexture1D; GLAD_API_CALL PFNGLFRAMEBUFFERTEXTURE1DPROC glad_debug_glFramebufferTexture1D; #define glFramebufferTexture1D glad_debug_glFramebufferTexture1D GLAD_API_CALL PFNGLFRAMEBUFFERTEXTURE2DPROC glad_glFramebufferTexture2D; GLAD_API_CALL PFNGLFRAMEBUFFERTEXTURE2DPROC glad_debug_glFramebufferTexture2D; #define glFramebufferTexture2D glad_debug_glFramebufferTexture2D GLAD_API_CALL PFNGLFRAMEBUFFERTEXTURE3DPROC glad_glFramebufferTexture3D; GLAD_API_CALL PFNGLFRAMEBUFFERTEXTURE3DPROC glad_debug_glFramebufferTexture3D; #define glFramebufferTexture3D glad_debug_glFramebufferTexture3D GLAD_API_CALL PFNGLFRAMEBUFFERTEXTURELAYERPROC glad_glFramebufferTextureLayer; GLAD_API_CALL PFNGLFRAMEBUFFERTEXTURELAYERPROC glad_debug_glFramebufferTextureLayer; #define glFramebufferTextureLayer glad_debug_glFramebufferTextureLayer GLAD_API_CALL PFNGLFRONTFACEPROC glad_glFrontFace; GLAD_API_CALL PFNGLFRONTFACEPROC glad_debug_glFrontFace; #define glFrontFace glad_debug_glFrontFace GLAD_API_CALL PFNGLFRUSTUMPROC glad_glFrustum; GLAD_API_CALL PFNGLFRUSTUMPROC glad_debug_glFrustum; #define glFrustum glad_debug_glFrustum GLAD_API_CALL PFNGLGENBUFFERSPROC glad_glGenBuffers; GLAD_API_CALL PFNGLGENBUFFERSPROC glad_debug_glGenBuffers; #define glGenBuffers glad_debug_glGenBuffers GLAD_API_CALL PFNGLGENFRAMEBUFFERSPROC glad_glGenFramebuffers; GLAD_API_CALL PFNGLGENFRAMEBUFFERSPROC glad_debug_glGenFramebuffers; #define glGenFramebuffers glad_debug_glGenFramebuffers GLAD_API_CALL PFNGLGENLISTSPROC glad_glGenLists; GLAD_API_CALL PFNGLGENLISTSPROC glad_debug_glGenLists; #define glGenLists glad_debug_glGenLists GLAD_API_CALL PFNGLGENQUERIESPROC glad_glGenQueries; GLAD_API_CALL PFNGLGENQUERIESPROC glad_debug_glGenQueries; #define glGenQueries glad_debug_glGenQueries GLAD_API_CALL PFNGLGENRENDERBUFFERSPROC glad_glGenRenderbuffers; GLAD_API_CALL PFNGLGENRENDERBUFFERSPROC glad_debug_glGenRenderbuffers; #define glGenRenderbuffers glad_debug_glGenRenderbuffers GLAD_API_CALL PFNGLGENTEXTURESPROC glad_glGenTextures; GLAD_API_CALL PFNGLGENTEXTURESPROC glad_debug_glGenTextures; #define glGenTextures glad_debug_glGenTextures GLAD_API_CALL PFNGLGENVERTEXARRAYSPROC glad_glGenVertexArrays; GLAD_API_CALL PFNGLGENVERTEXARRAYSPROC glad_debug_glGenVertexArrays; #define glGenVertexArrays glad_debug_glGenVertexArrays GLAD_API_CALL PFNGLGENERATEMIPMAPPROC glad_glGenerateMipmap; GLAD_API_CALL PFNGLGENERATEMIPMAPPROC glad_debug_glGenerateMipmap; #define glGenerateMipmap glad_debug_glGenerateMipmap GLAD_API_CALL PFNGLGETACTIVEATTRIBPROC glad_glGetActiveAttrib; GLAD_API_CALL PFNGLGETACTIVEATTRIBPROC glad_debug_glGetActiveAttrib; #define glGetActiveAttrib glad_debug_glGetActiveAttrib GLAD_API_CALL PFNGLGETACTIVEUNIFORMPROC glad_glGetActiveUniform; GLAD_API_CALL PFNGLGETACTIVEUNIFORMPROC glad_debug_glGetActiveUniform; #define glGetActiveUniform glad_debug_glGetActiveUniform GLAD_API_CALL PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC glad_glGetActiveUniformBlockName; GLAD_API_CALL PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC glad_debug_glGetActiveUniformBlockName; #define glGetActiveUniformBlockName glad_debug_glGetActiveUniformBlockName GLAD_API_CALL PFNGLGETACTIVEUNIFORMBLOCKIVPROC glad_glGetActiveUniformBlockiv; GLAD_API_CALL PFNGLGETACTIVEUNIFORMBLOCKIVPROC glad_debug_glGetActiveUniformBlockiv; #define glGetActiveUniformBlockiv glad_debug_glGetActiveUniformBlockiv GLAD_API_CALL PFNGLGETACTIVEUNIFORMNAMEPROC glad_glGetActiveUniformName; GLAD_API_CALL PFNGLGETACTIVEUNIFORMNAMEPROC glad_debug_glGetActiveUniformName; #define glGetActiveUniformName glad_debug_glGetActiveUniformName GLAD_API_CALL PFNGLGETACTIVEUNIFORMSIVPROC glad_glGetActiveUniformsiv; GLAD_API_CALL PFNGLGETACTIVEUNIFORMSIVPROC glad_debug_glGetActiveUniformsiv; #define glGetActiveUniformsiv glad_debug_glGetActiveUniformsiv GLAD_API_CALL PFNGLGETATTACHEDSHADERSPROC glad_glGetAttachedShaders; GLAD_API_CALL PFNGLGETATTACHEDSHADERSPROC glad_debug_glGetAttachedShaders; #define glGetAttachedShaders glad_debug_glGetAttachedShaders GLAD_API_CALL PFNGLGETATTRIBLOCATIONPROC glad_glGetAttribLocation; GLAD_API_CALL PFNGLGETATTRIBLOCATIONPROC glad_debug_glGetAttribLocation; #define glGetAttribLocation glad_debug_glGetAttribLocation GLAD_API_CALL PFNGLGETBOOLEANI_VPROC glad_glGetBooleani_v; GLAD_API_CALL PFNGLGETBOOLEANI_VPROC glad_debug_glGetBooleani_v; #define glGetBooleani_v glad_debug_glGetBooleani_v GLAD_API_CALL PFNGLGETBOOLEANVPROC glad_glGetBooleanv; GLAD_API_CALL PFNGLGETBOOLEANVPROC glad_debug_glGetBooleanv; #define glGetBooleanv glad_debug_glGetBooleanv GLAD_API_CALL PFNGLGETBUFFERPARAMETERIVPROC glad_glGetBufferParameteriv; GLAD_API_CALL PFNGLGETBUFFERPARAMETERIVPROC glad_debug_glGetBufferParameteriv; #define glGetBufferParameteriv glad_debug_glGetBufferParameteriv GLAD_API_CALL PFNGLGETBUFFERPOINTERVPROC glad_glGetBufferPointerv; GLAD_API_CALL PFNGLGETBUFFERPOINTERVPROC glad_debug_glGetBufferPointerv; #define glGetBufferPointerv glad_debug_glGetBufferPointerv GLAD_API_CALL PFNGLGETBUFFERSUBDATAPROC glad_glGetBufferSubData; GLAD_API_CALL PFNGLGETBUFFERSUBDATAPROC glad_debug_glGetBufferSubData; #define glGetBufferSubData glad_debug_glGetBufferSubData GLAD_API_CALL PFNGLGETCLIPPLANEPROC glad_glGetClipPlane; GLAD_API_CALL PFNGLGETCLIPPLANEPROC glad_debug_glGetClipPlane; #define glGetClipPlane glad_debug_glGetClipPlane GLAD_API_CALL PFNGLGETCOMPRESSEDTEXIMAGEPROC glad_glGetCompressedTexImage; GLAD_API_CALL PFNGLGETCOMPRESSEDTEXIMAGEPROC glad_debug_glGetCompressedTexImage; #define glGetCompressedTexImage glad_debug_glGetCompressedTexImage GLAD_API_CALL PFNGLGETDEBUGMESSAGELOGPROC glad_glGetDebugMessageLog; GLAD_API_CALL PFNGLGETDEBUGMESSAGELOGPROC glad_debug_glGetDebugMessageLog; #define glGetDebugMessageLog glad_debug_glGetDebugMessageLog GLAD_API_CALL PFNGLGETDOUBLEVPROC glad_glGetDoublev; GLAD_API_CALL PFNGLGETDOUBLEVPROC glad_debug_glGetDoublev; #define glGetDoublev glad_debug_glGetDoublev GLAD_API_CALL PFNGLGETERRORPROC glad_glGetError; GLAD_API_CALL PFNGLGETERRORPROC glad_debug_glGetError; #define glGetError glad_debug_glGetError GLAD_API_CALL PFNGLGETFLOATVPROC glad_glGetFloatv; GLAD_API_CALL PFNGLGETFLOATVPROC glad_debug_glGetFloatv; #define glGetFloatv glad_debug_glGetFloatv GLAD_API_CALL PFNGLGETFRAGDATALOCATIONPROC glad_glGetFragDataLocation; GLAD_API_CALL PFNGLGETFRAGDATALOCATIONPROC glad_debug_glGetFragDataLocation; #define glGetFragDataLocation glad_debug_glGetFragDataLocation GLAD_API_CALL PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC glad_glGetFramebufferAttachmentParameteriv; GLAD_API_CALL PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC glad_debug_glGetFramebufferAttachmentParameteriv; #define glGetFramebufferAttachmentParameteriv glad_debug_glGetFramebufferAttachmentParameteriv GLAD_API_CALL PFNGLGETGRAPHICSRESETSTATUSARBPROC glad_glGetGraphicsResetStatusARB; GLAD_API_CALL PFNGLGETGRAPHICSRESETSTATUSARBPROC glad_debug_glGetGraphicsResetStatusARB; #define glGetGraphicsResetStatusARB glad_debug_glGetGraphicsResetStatusARB GLAD_API_CALL PFNGLGETINTEGERI_VPROC glad_glGetIntegeri_v; GLAD_API_CALL PFNGLGETINTEGERI_VPROC glad_debug_glGetIntegeri_v; #define glGetIntegeri_v glad_debug_glGetIntegeri_v GLAD_API_CALL PFNGLGETINTEGERVPROC glad_glGetIntegerv; GLAD_API_CALL PFNGLGETINTEGERVPROC glad_debug_glGetIntegerv; #define glGetIntegerv glad_debug_glGetIntegerv GLAD_API_CALL PFNGLGETLIGHTFVPROC glad_glGetLightfv; GLAD_API_CALL PFNGLGETLIGHTFVPROC glad_debug_glGetLightfv; #define glGetLightfv glad_debug_glGetLightfv GLAD_API_CALL PFNGLGETLIGHTIVPROC glad_glGetLightiv; GLAD_API_CALL PFNGLGETLIGHTIVPROC glad_debug_glGetLightiv; #define glGetLightiv glad_debug_glGetLightiv GLAD_API_CALL PFNGLGETMAPDVPROC glad_glGetMapdv; GLAD_API_CALL PFNGLGETMAPDVPROC glad_debug_glGetMapdv; #define glGetMapdv glad_debug_glGetMapdv GLAD_API_CALL PFNGLGETMAPFVPROC glad_glGetMapfv; GLAD_API_CALL PFNGLGETMAPFVPROC glad_debug_glGetMapfv; #define glGetMapfv glad_debug_glGetMapfv GLAD_API_CALL PFNGLGETMAPIVPROC glad_glGetMapiv; GLAD_API_CALL PFNGLGETMAPIVPROC glad_debug_glGetMapiv; #define glGetMapiv glad_debug_glGetMapiv GLAD_API_CALL PFNGLGETMATERIALFVPROC glad_glGetMaterialfv; GLAD_API_CALL PFNGLGETMATERIALFVPROC glad_debug_glGetMaterialfv; #define glGetMaterialfv glad_debug_glGetMaterialfv GLAD_API_CALL PFNGLGETMATERIALIVPROC glad_glGetMaterialiv; GLAD_API_CALL PFNGLGETMATERIALIVPROC glad_debug_glGetMaterialiv; #define glGetMaterialiv glad_debug_glGetMaterialiv GLAD_API_CALL PFNGLGETOBJECTLABELPROC glad_glGetObjectLabel; GLAD_API_CALL PFNGLGETOBJECTLABELPROC glad_debug_glGetObjectLabel; #define glGetObjectLabel glad_debug_glGetObjectLabel GLAD_API_CALL PFNGLGETOBJECTPTRLABELPROC glad_glGetObjectPtrLabel; GLAD_API_CALL PFNGLGETOBJECTPTRLABELPROC glad_debug_glGetObjectPtrLabel; #define glGetObjectPtrLabel glad_debug_glGetObjectPtrLabel GLAD_API_CALL PFNGLGETPIXELMAPFVPROC glad_glGetPixelMapfv; GLAD_API_CALL PFNGLGETPIXELMAPFVPROC glad_debug_glGetPixelMapfv; #define glGetPixelMapfv glad_debug_glGetPixelMapfv GLAD_API_CALL PFNGLGETPIXELMAPUIVPROC glad_glGetPixelMapuiv; GLAD_API_CALL PFNGLGETPIXELMAPUIVPROC glad_debug_glGetPixelMapuiv; #define glGetPixelMapuiv glad_debug_glGetPixelMapuiv GLAD_API_CALL PFNGLGETPIXELMAPUSVPROC glad_glGetPixelMapusv; GLAD_API_CALL PFNGLGETPIXELMAPUSVPROC glad_debug_glGetPixelMapusv; #define glGetPixelMapusv glad_debug_glGetPixelMapusv GLAD_API_CALL PFNGLGETPOINTERVPROC glad_glGetPointerv; GLAD_API_CALL PFNGLGETPOINTERVPROC glad_debug_glGetPointerv; #define glGetPointerv glad_debug_glGetPointerv GLAD_API_CALL PFNGLGETPOLYGONSTIPPLEPROC glad_glGetPolygonStipple; GLAD_API_CALL PFNGLGETPOLYGONSTIPPLEPROC glad_debug_glGetPolygonStipple; #define glGetPolygonStipple glad_debug_glGetPolygonStipple GLAD_API_CALL PFNGLGETPROGRAMINFOLOGPROC glad_glGetProgramInfoLog; GLAD_API_CALL PFNGLGETPROGRAMINFOLOGPROC glad_debug_glGetProgramInfoLog; #define glGetProgramInfoLog glad_debug_glGetProgramInfoLog GLAD_API_CALL PFNGLGETPROGRAMIVPROC glad_glGetProgramiv; GLAD_API_CALL PFNGLGETPROGRAMIVPROC glad_debug_glGetProgramiv; #define glGetProgramiv glad_debug_glGetProgramiv GLAD_API_CALL PFNGLGETQUERYOBJECTIVPROC glad_glGetQueryObjectiv; GLAD_API_CALL PFNGLGETQUERYOBJECTIVPROC glad_debug_glGetQueryObjectiv; #define glGetQueryObjectiv glad_debug_glGetQueryObjectiv GLAD_API_CALL PFNGLGETQUERYOBJECTUIVPROC glad_glGetQueryObjectuiv; GLAD_API_CALL PFNGLGETQUERYOBJECTUIVPROC glad_debug_glGetQueryObjectuiv; #define glGetQueryObjectuiv glad_debug_glGetQueryObjectuiv GLAD_API_CALL PFNGLGETQUERYIVPROC glad_glGetQueryiv; GLAD_API_CALL PFNGLGETQUERYIVPROC glad_debug_glGetQueryiv; #define glGetQueryiv glad_debug_glGetQueryiv GLAD_API_CALL PFNGLGETRENDERBUFFERPARAMETERIVPROC glad_glGetRenderbufferParameteriv; GLAD_API_CALL PFNGLGETRENDERBUFFERPARAMETERIVPROC glad_debug_glGetRenderbufferParameteriv; #define glGetRenderbufferParameteriv glad_debug_glGetRenderbufferParameteriv GLAD_API_CALL PFNGLGETSHADERINFOLOGPROC glad_glGetShaderInfoLog; GLAD_API_CALL PFNGLGETSHADERINFOLOGPROC glad_debug_glGetShaderInfoLog; #define glGetShaderInfoLog glad_debug_glGetShaderInfoLog GLAD_API_CALL PFNGLGETSHADERSOURCEPROC glad_glGetShaderSource; GLAD_API_CALL PFNGLGETSHADERSOURCEPROC glad_debug_glGetShaderSource; #define glGetShaderSource glad_debug_glGetShaderSource GLAD_API_CALL PFNGLGETSHADERIVPROC glad_glGetShaderiv; GLAD_API_CALL PFNGLGETSHADERIVPROC glad_debug_glGetShaderiv; #define glGetShaderiv glad_debug_glGetShaderiv GLAD_API_CALL PFNGLGETSTRINGPROC glad_glGetString; GLAD_API_CALL PFNGLGETSTRINGPROC glad_debug_glGetString; #define glGetString glad_debug_glGetString GLAD_API_CALL PFNGLGETSTRINGIPROC glad_glGetStringi; GLAD_API_CALL PFNGLGETSTRINGIPROC glad_debug_glGetStringi; #define glGetStringi glad_debug_glGetStringi GLAD_API_CALL PFNGLGETTEXENVFVPROC glad_glGetTexEnvfv; GLAD_API_CALL PFNGLGETTEXENVFVPROC glad_debug_glGetTexEnvfv; #define glGetTexEnvfv glad_debug_glGetTexEnvfv GLAD_API_CALL PFNGLGETTEXENVIVPROC glad_glGetTexEnviv; GLAD_API_CALL PFNGLGETTEXENVIVPROC glad_debug_glGetTexEnviv; #define glGetTexEnviv glad_debug_glGetTexEnviv GLAD_API_CALL PFNGLGETTEXGENDVPROC glad_glGetTexGendv; GLAD_API_CALL PFNGLGETTEXGENDVPROC glad_debug_glGetTexGendv; #define glGetTexGendv glad_debug_glGetTexGendv GLAD_API_CALL PFNGLGETTEXGENFVPROC glad_glGetTexGenfv; GLAD_API_CALL PFNGLGETTEXGENFVPROC glad_debug_glGetTexGenfv; #define glGetTexGenfv glad_debug_glGetTexGenfv GLAD_API_CALL PFNGLGETTEXGENIVPROC glad_glGetTexGeniv; GLAD_API_CALL PFNGLGETTEXGENIVPROC glad_debug_glGetTexGeniv; #define glGetTexGeniv glad_debug_glGetTexGeniv GLAD_API_CALL PFNGLGETTEXIMAGEPROC glad_glGetTexImage; GLAD_API_CALL PFNGLGETTEXIMAGEPROC glad_debug_glGetTexImage; #define glGetTexImage glad_debug_glGetTexImage GLAD_API_CALL PFNGLGETTEXLEVELPARAMETERFVPROC glad_glGetTexLevelParameterfv; GLAD_API_CALL PFNGLGETTEXLEVELPARAMETERFVPROC glad_debug_glGetTexLevelParameterfv; #define glGetTexLevelParameterfv glad_debug_glGetTexLevelParameterfv GLAD_API_CALL PFNGLGETTEXLEVELPARAMETERIVPROC glad_glGetTexLevelParameteriv; GLAD_API_CALL PFNGLGETTEXLEVELPARAMETERIVPROC glad_debug_glGetTexLevelParameteriv; #define glGetTexLevelParameteriv glad_debug_glGetTexLevelParameteriv GLAD_API_CALL PFNGLGETTEXPARAMETERIIVPROC glad_glGetTexParameterIiv; GLAD_API_CALL PFNGLGETTEXPARAMETERIIVPROC glad_debug_glGetTexParameterIiv; #define glGetTexParameterIiv glad_debug_glGetTexParameterIiv GLAD_API_CALL PFNGLGETTEXPARAMETERIUIVPROC glad_glGetTexParameterIuiv; GLAD_API_CALL PFNGLGETTEXPARAMETERIUIVPROC glad_debug_glGetTexParameterIuiv; #define glGetTexParameterIuiv glad_debug_glGetTexParameterIuiv GLAD_API_CALL PFNGLGETTEXPARAMETERFVPROC glad_glGetTexParameterfv; GLAD_API_CALL PFNGLGETTEXPARAMETERFVPROC glad_debug_glGetTexParameterfv; #define glGetTexParameterfv glad_debug_glGetTexParameterfv GLAD_API_CALL PFNGLGETTEXPARAMETERIVPROC glad_glGetTexParameteriv; GLAD_API_CALL PFNGLGETTEXPARAMETERIVPROC glad_debug_glGetTexParameteriv; #define glGetTexParameteriv glad_debug_glGetTexParameteriv GLAD_API_CALL PFNGLGETTRANSFORMFEEDBACKVARYINGPROC glad_glGetTransformFeedbackVarying; GLAD_API_CALL PFNGLGETTRANSFORMFEEDBACKVARYINGPROC glad_debug_glGetTransformFeedbackVarying; #define glGetTransformFeedbackVarying glad_debug_glGetTransformFeedbackVarying GLAD_API_CALL PFNGLGETUNIFORMBLOCKINDEXPROC glad_glGetUniformBlockIndex; GLAD_API_CALL PFNGLGETUNIFORMBLOCKINDEXPROC glad_debug_glGetUniformBlockIndex; #define glGetUniformBlockIndex glad_debug_glGetUniformBlockIndex GLAD_API_CALL PFNGLGETUNIFORMINDICESPROC glad_glGetUniformIndices; GLAD_API_CALL PFNGLGETUNIFORMINDICESPROC glad_debug_glGetUniformIndices; #define glGetUniformIndices glad_debug_glGetUniformIndices GLAD_API_CALL PFNGLGETUNIFORMLOCATIONPROC glad_glGetUniformLocation; GLAD_API_CALL PFNGLGETUNIFORMLOCATIONPROC glad_debug_glGetUniformLocation; #define glGetUniformLocation glad_debug_glGetUniformLocation GLAD_API_CALL PFNGLGETUNIFORMFVPROC glad_glGetUniformfv; GLAD_API_CALL PFNGLGETUNIFORMFVPROC glad_debug_glGetUniformfv; #define glGetUniformfv glad_debug_glGetUniformfv GLAD_API_CALL PFNGLGETUNIFORMIVPROC glad_glGetUniformiv; GLAD_API_CALL PFNGLGETUNIFORMIVPROC glad_debug_glGetUniformiv; #define glGetUniformiv glad_debug_glGetUniformiv GLAD_API_CALL PFNGLGETUNIFORMUIVPROC glad_glGetUniformuiv; GLAD_API_CALL PFNGLGETUNIFORMUIVPROC glad_debug_glGetUniformuiv; #define glGetUniformuiv glad_debug_glGetUniformuiv GLAD_API_CALL PFNGLGETVERTEXATTRIBIIVPROC glad_glGetVertexAttribIiv; GLAD_API_CALL PFNGLGETVERTEXATTRIBIIVPROC glad_debug_glGetVertexAttribIiv; #define glGetVertexAttribIiv glad_debug_glGetVertexAttribIiv GLAD_API_CALL PFNGLGETVERTEXATTRIBIUIVPROC glad_glGetVertexAttribIuiv; GLAD_API_CALL PFNGLGETVERTEXATTRIBIUIVPROC glad_debug_glGetVertexAttribIuiv; #define glGetVertexAttribIuiv glad_debug_glGetVertexAttribIuiv GLAD_API_CALL PFNGLGETVERTEXATTRIBPOINTERVPROC glad_glGetVertexAttribPointerv; GLAD_API_CALL PFNGLGETVERTEXATTRIBPOINTERVPROC glad_debug_glGetVertexAttribPointerv; #define glGetVertexAttribPointerv glad_debug_glGetVertexAttribPointerv GLAD_API_CALL PFNGLGETVERTEXATTRIBDVPROC glad_glGetVertexAttribdv; GLAD_API_CALL PFNGLGETVERTEXATTRIBDVPROC glad_debug_glGetVertexAttribdv; #define glGetVertexAttribdv glad_debug_glGetVertexAttribdv GLAD_API_CALL PFNGLGETVERTEXATTRIBFVPROC glad_glGetVertexAttribfv; GLAD_API_CALL PFNGLGETVERTEXATTRIBFVPROC glad_debug_glGetVertexAttribfv; #define glGetVertexAttribfv glad_debug_glGetVertexAttribfv GLAD_API_CALL PFNGLGETVERTEXATTRIBIVPROC glad_glGetVertexAttribiv; GLAD_API_CALL PFNGLGETVERTEXATTRIBIVPROC glad_debug_glGetVertexAttribiv; #define glGetVertexAttribiv glad_debug_glGetVertexAttribiv GLAD_API_CALL PFNGLGETNCOMPRESSEDTEXIMAGEARBPROC glad_glGetnCompressedTexImageARB; GLAD_API_CALL PFNGLGETNCOMPRESSEDTEXIMAGEARBPROC glad_debug_glGetnCompressedTexImageARB; #define glGetnCompressedTexImageARB glad_debug_glGetnCompressedTexImageARB GLAD_API_CALL PFNGLGETNTEXIMAGEARBPROC glad_glGetnTexImageARB; GLAD_API_CALL PFNGLGETNTEXIMAGEARBPROC glad_debug_glGetnTexImageARB; #define glGetnTexImageARB glad_debug_glGetnTexImageARB GLAD_API_CALL PFNGLGETNUNIFORMDVARBPROC glad_glGetnUniformdvARB; GLAD_API_CALL PFNGLGETNUNIFORMDVARBPROC glad_debug_glGetnUniformdvARB; #define glGetnUniformdvARB glad_debug_glGetnUniformdvARB GLAD_API_CALL PFNGLGETNUNIFORMFVARBPROC glad_glGetnUniformfvARB; GLAD_API_CALL PFNGLGETNUNIFORMFVARBPROC glad_debug_glGetnUniformfvARB; #define glGetnUniformfvARB glad_debug_glGetnUniformfvARB GLAD_API_CALL PFNGLGETNUNIFORMIVARBPROC glad_glGetnUniformivARB; GLAD_API_CALL PFNGLGETNUNIFORMIVARBPROC glad_debug_glGetnUniformivARB; #define glGetnUniformivARB glad_debug_glGetnUniformivARB GLAD_API_CALL PFNGLGETNUNIFORMUIVARBPROC glad_glGetnUniformuivARB; GLAD_API_CALL PFNGLGETNUNIFORMUIVARBPROC glad_debug_glGetnUniformuivARB; #define glGetnUniformuivARB glad_debug_glGetnUniformuivARB GLAD_API_CALL PFNGLHINTPROC glad_glHint; GLAD_API_CALL PFNGLHINTPROC glad_debug_glHint; #define glHint glad_debug_glHint GLAD_API_CALL PFNGLINDEXMASKPROC glad_glIndexMask; GLAD_API_CALL PFNGLINDEXMASKPROC glad_debug_glIndexMask; #define glIndexMask glad_debug_glIndexMask GLAD_API_CALL PFNGLINDEXPOINTERPROC glad_glIndexPointer; GLAD_API_CALL PFNGLINDEXPOINTERPROC glad_debug_glIndexPointer; #define glIndexPointer glad_debug_glIndexPointer GLAD_API_CALL PFNGLINDEXDPROC glad_glIndexd; GLAD_API_CALL PFNGLINDEXDPROC glad_debug_glIndexd; #define glIndexd glad_debug_glIndexd GLAD_API_CALL PFNGLINDEXDVPROC glad_glIndexdv; GLAD_API_CALL PFNGLINDEXDVPROC glad_debug_glIndexdv; #define glIndexdv glad_debug_glIndexdv GLAD_API_CALL PFNGLINDEXFPROC glad_glIndexf; GLAD_API_CALL PFNGLINDEXFPROC glad_debug_glIndexf; #define glIndexf glad_debug_glIndexf GLAD_API_CALL PFNGLINDEXFVPROC glad_glIndexfv; GLAD_API_CALL PFNGLINDEXFVPROC glad_debug_glIndexfv; #define glIndexfv glad_debug_glIndexfv GLAD_API_CALL PFNGLINDEXIPROC glad_glIndexi; GLAD_API_CALL PFNGLINDEXIPROC glad_debug_glIndexi; #define glIndexi glad_debug_glIndexi GLAD_API_CALL PFNGLINDEXIVPROC glad_glIndexiv; GLAD_API_CALL PFNGLINDEXIVPROC glad_debug_glIndexiv; #define glIndexiv glad_debug_glIndexiv GLAD_API_CALL PFNGLINDEXSPROC glad_glIndexs; GLAD_API_CALL PFNGLINDEXSPROC glad_debug_glIndexs; #define glIndexs glad_debug_glIndexs GLAD_API_CALL PFNGLINDEXSVPROC glad_glIndexsv; GLAD_API_CALL PFNGLINDEXSVPROC glad_debug_glIndexsv; #define glIndexsv glad_debug_glIndexsv GLAD_API_CALL PFNGLINDEXUBPROC glad_glIndexub; GLAD_API_CALL PFNGLINDEXUBPROC glad_debug_glIndexub; #define glIndexub glad_debug_glIndexub GLAD_API_CALL PFNGLINDEXUBVPROC glad_glIndexubv; GLAD_API_CALL PFNGLINDEXUBVPROC glad_debug_glIndexubv; #define glIndexubv glad_debug_glIndexubv GLAD_API_CALL PFNGLINITNAMESPROC glad_glInitNames; GLAD_API_CALL PFNGLINITNAMESPROC glad_debug_glInitNames; #define glInitNames glad_debug_glInitNames GLAD_API_CALL PFNGLINTERLEAVEDARRAYSPROC glad_glInterleavedArrays; GLAD_API_CALL PFNGLINTERLEAVEDARRAYSPROC glad_debug_glInterleavedArrays; #define glInterleavedArrays glad_debug_glInterleavedArrays GLAD_API_CALL PFNGLISBUFFERPROC glad_glIsBuffer; GLAD_API_CALL PFNGLISBUFFERPROC glad_debug_glIsBuffer; #define glIsBuffer glad_debug_glIsBuffer GLAD_API_CALL PFNGLISENABLEDPROC glad_glIsEnabled; GLAD_API_CALL PFNGLISENABLEDPROC glad_debug_glIsEnabled; #define glIsEnabled glad_debug_glIsEnabled GLAD_API_CALL PFNGLISENABLEDIPROC glad_glIsEnabledi; GLAD_API_CALL PFNGLISENABLEDIPROC glad_debug_glIsEnabledi; #define glIsEnabledi glad_debug_glIsEnabledi GLAD_API_CALL PFNGLISFRAMEBUFFERPROC glad_glIsFramebuffer; GLAD_API_CALL PFNGLISFRAMEBUFFERPROC glad_debug_glIsFramebuffer; #define glIsFramebuffer glad_debug_glIsFramebuffer GLAD_API_CALL PFNGLISLISTPROC glad_glIsList; GLAD_API_CALL PFNGLISLISTPROC glad_debug_glIsList; #define glIsList glad_debug_glIsList GLAD_API_CALL PFNGLISPROGRAMPROC glad_glIsProgram; GLAD_API_CALL PFNGLISPROGRAMPROC glad_debug_glIsProgram; #define glIsProgram glad_debug_glIsProgram GLAD_API_CALL PFNGLISQUERYPROC glad_glIsQuery; GLAD_API_CALL PFNGLISQUERYPROC glad_debug_glIsQuery; #define glIsQuery glad_debug_glIsQuery GLAD_API_CALL PFNGLISRENDERBUFFERPROC glad_glIsRenderbuffer; GLAD_API_CALL PFNGLISRENDERBUFFERPROC glad_debug_glIsRenderbuffer; #define glIsRenderbuffer glad_debug_glIsRenderbuffer GLAD_API_CALL PFNGLISSHADERPROC glad_glIsShader; GLAD_API_CALL PFNGLISSHADERPROC glad_debug_glIsShader; #define glIsShader glad_debug_glIsShader GLAD_API_CALL PFNGLISTEXTUREPROC glad_glIsTexture; GLAD_API_CALL PFNGLISTEXTUREPROC glad_debug_glIsTexture; #define glIsTexture glad_debug_glIsTexture GLAD_API_CALL PFNGLISVERTEXARRAYPROC glad_glIsVertexArray; GLAD_API_CALL PFNGLISVERTEXARRAYPROC glad_debug_glIsVertexArray; #define glIsVertexArray glad_debug_glIsVertexArray GLAD_API_CALL PFNGLLIGHTMODELFPROC glad_glLightModelf; GLAD_API_CALL PFNGLLIGHTMODELFPROC glad_debug_glLightModelf; #define glLightModelf glad_debug_glLightModelf GLAD_API_CALL PFNGLLIGHTMODELFVPROC glad_glLightModelfv; GLAD_API_CALL PFNGLLIGHTMODELFVPROC glad_debug_glLightModelfv; #define glLightModelfv glad_debug_glLightModelfv GLAD_API_CALL PFNGLLIGHTMODELIPROC glad_glLightModeli; GLAD_API_CALL PFNGLLIGHTMODELIPROC glad_debug_glLightModeli; #define glLightModeli glad_debug_glLightModeli GLAD_API_CALL PFNGLLIGHTMODELIVPROC glad_glLightModeliv; GLAD_API_CALL PFNGLLIGHTMODELIVPROC glad_debug_glLightModeliv; #define glLightModeliv glad_debug_glLightModeliv GLAD_API_CALL PFNGLLIGHTFPROC glad_glLightf; GLAD_API_CALL PFNGLLIGHTFPROC glad_debug_glLightf; #define glLightf glad_debug_glLightf GLAD_API_CALL PFNGLLIGHTFVPROC glad_glLightfv; GLAD_API_CALL PFNGLLIGHTFVPROC glad_debug_glLightfv; #define glLightfv glad_debug_glLightfv GLAD_API_CALL PFNGLLIGHTIPROC glad_glLighti; GLAD_API_CALL PFNGLLIGHTIPROC glad_debug_glLighti; #define glLighti glad_debug_glLighti GLAD_API_CALL PFNGLLIGHTIVPROC glad_glLightiv; GLAD_API_CALL PFNGLLIGHTIVPROC glad_debug_glLightiv; #define glLightiv glad_debug_glLightiv GLAD_API_CALL PFNGLLINESTIPPLEPROC glad_glLineStipple; GLAD_API_CALL PFNGLLINESTIPPLEPROC glad_debug_glLineStipple; #define glLineStipple glad_debug_glLineStipple GLAD_API_CALL PFNGLLINEWIDTHPROC glad_glLineWidth; GLAD_API_CALL PFNGLLINEWIDTHPROC glad_debug_glLineWidth; #define glLineWidth glad_debug_glLineWidth GLAD_API_CALL PFNGLLINKPROGRAMPROC glad_glLinkProgram; GLAD_API_CALL PFNGLLINKPROGRAMPROC glad_debug_glLinkProgram; #define glLinkProgram glad_debug_glLinkProgram GLAD_API_CALL PFNGLLISTBASEPROC glad_glListBase; GLAD_API_CALL PFNGLLISTBASEPROC glad_debug_glListBase; #define glListBase glad_debug_glListBase GLAD_API_CALL PFNGLLOADIDENTITYPROC glad_glLoadIdentity; GLAD_API_CALL PFNGLLOADIDENTITYPROC glad_debug_glLoadIdentity; #define glLoadIdentity glad_debug_glLoadIdentity GLAD_API_CALL PFNGLLOADMATRIXDPROC glad_glLoadMatrixd; GLAD_API_CALL PFNGLLOADMATRIXDPROC glad_debug_glLoadMatrixd; #define glLoadMatrixd glad_debug_glLoadMatrixd GLAD_API_CALL PFNGLLOADMATRIXFPROC glad_glLoadMatrixf; GLAD_API_CALL PFNGLLOADMATRIXFPROC glad_debug_glLoadMatrixf; #define glLoadMatrixf glad_debug_glLoadMatrixf GLAD_API_CALL PFNGLLOADNAMEPROC glad_glLoadName; GLAD_API_CALL PFNGLLOADNAMEPROC glad_debug_glLoadName; #define glLoadName glad_debug_glLoadName GLAD_API_CALL PFNGLLOADTRANSPOSEMATRIXDPROC glad_glLoadTransposeMatrixd; GLAD_API_CALL PFNGLLOADTRANSPOSEMATRIXDPROC glad_debug_glLoadTransposeMatrixd; #define glLoadTransposeMatrixd glad_debug_glLoadTransposeMatrixd GLAD_API_CALL PFNGLLOADTRANSPOSEMATRIXFPROC glad_glLoadTransposeMatrixf; GLAD_API_CALL PFNGLLOADTRANSPOSEMATRIXFPROC glad_debug_glLoadTransposeMatrixf; #define glLoadTransposeMatrixf glad_debug_glLoadTransposeMatrixf GLAD_API_CALL PFNGLLOGICOPPROC glad_glLogicOp; GLAD_API_CALL PFNGLLOGICOPPROC glad_debug_glLogicOp; #define glLogicOp glad_debug_glLogicOp GLAD_API_CALL PFNGLMAP1DPROC glad_glMap1d; GLAD_API_CALL PFNGLMAP1DPROC glad_debug_glMap1d; #define glMap1d glad_debug_glMap1d GLAD_API_CALL PFNGLMAP1FPROC glad_glMap1f; GLAD_API_CALL PFNGLMAP1FPROC glad_debug_glMap1f; #define glMap1f glad_debug_glMap1f GLAD_API_CALL PFNGLMAP2DPROC glad_glMap2d; GLAD_API_CALL PFNGLMAP2DPROC glad_debug_glMap2d; #define glMap2d glad_debug_glMap2d GLAD_API_CALL PFNGLMAP2FPROC glad_glMap2f; GLAD_API_CALL PFNGLMAP2FPROC glad_debug_glMap2f; #define glMap2f glad_debug_glMap2f GLAD_API_CALL PFNGLMAPBUFFERPROC glad_glMapBuffer; GLAD_API_CALL PFNGLMAPBUFFERPROC glad_debug_glMapBuffer; #define glMapBuffer glad_debug_glMapBuffer GLAD_API_CALL PFNGLMAPBUFFERRANGEPROC glad_glMapBufferRange; GLAD_API_CALL PFNGLMAPBUFFERRANGEPROC glad_debug_glMapBufferRange; #define glMapBufferRange glad_debug_glMapBufferRange GLAD_API_CALL PFNGLMAPGRID1DPROC glad_glMapGrid1d; GLAD_API_CALL PFNGLMAPGRID1DPROC glad_debug_glMapGrid1d; #define glMapGrid1d glad_debug_glMapGrid1d GLAD_API_CALL PFNGLMAPGRID1FPROC glad_glMapGrid1f; GLAD_API_CALL PFNGLMAPGRID1FPROC glad_debug_glMapGrid1f; #define glMapGrid1f glad_debug_glMapGrid1f GLAD_API_CALL PFNGLMAPGRID2DPROC glad_glMapGrid2d; GLAD_API_CALL PFNGLMAPGRID2DPROC glad_debug_glMapGrid2d; #define glMapGrid2d glad_debug_glMapGrid2d GLAD_API_CALL PFNGLMAPGRID2FPROC glad_glMapGrid2f; GLAD_API_CALL PFNGLMAPGRID2FPROC glad_debug_glMapGrid2f; #define glMapGrid2f glad_debug_glMapGrid2f GLAD_API_CALL PFNGLMATERIALFPROC glad_glMaterialf; GLAD_API_CALL PFNGLMATERIALFPROC glad_debug_glMaterialf; #define glMaterialf glad_debug_glMaterialf GLAD_API_CALL PFNGLMATERIALFVPROC glad_glMaterialfv; GLAD_API_CALL PFNGLMATERIALFVPROC glad_debug_glMaterialfv; #define glMaterialfv glad_debug_glMaterialfv GLAD_API_CALL PFNGLMATERIALIPROC glad_glMateriali; GLAD_API_CALL PFNGLMATERIALIPROC glad_debug_glMateriali; #define glMateriali glad_debug_glMateriali GLAD_API_CALL PFNGLMATERIALIVPROC glad_glMaterialiv; GLAD_API_CALL PFNGLMATERIALIVPROC glad_debug_glMaterialiv; #define glMaterialiv glad_debug_glMaterialiv GLAD_API_CALL PFNGLMATRIXMODEPROC glad_glMatrixMode; GLAD_API_CALL PFNGLMATRIXMODEPROC glad_debug_glMatrixMode; #define glMatrixMode glad_debug_glMatrixMode GLAD_API_CALL PFNGLMULTMATRIXDPROC glad_glMultMatrixd; GLAD_API_CALL PFNGLMULTMATRIXDPROC glad_debug_glMultMatrixd; #define glMultMatrixd glad_debug_glMultMatrixd GLAD_API_CALL PFNGLMULTMATRIXFPROC glad_glMultMatrixf; GLAD_API_CALL PFNGLMULTMATRIXFPROC glad_debug_glMultMatrixf; #define glMultMatrixf glad_debug_glMultMatrixf GLAD_API_CALL PFNGLMULTTRANSPOSEMATRIXDPROC glad_glMultTransposeMatrixd; GLAD_API_CALL PFNGLMULTTRANSPOSEMATRIXDPROC glad_debug_glMultTransposeMatrixd; #define glMultTransposeMatrixd glad_debug_glMultTransposeMatrixd GLAD_API_CALL PFNGLMULTTRANSPOSEMATRIXFPROC glad_glMultTransposeMatrixf; GLAD_API_CALL PFNGLMULTTRANSPOSEMATRIXFPROC glad_debug_glMultTransposeMatrixf; #define glMultTransposeMatrixf glad_debug_glMultTransposeMatrixf GLAD_API_CALL PFNGLMULTIDRAWARRAYSPROC glad_glMultiDrawArrays; GLAD_API_CALL PFNGLMULTIDRAWARRAYSPROC glad_debug_glMultiDrawArrays; #define glMultiDrawArrays glad_debug_glMultiDrawArrays GLAD_API_CALL PFNGLMULTIDRAWELEMENTSPROC glad_glMultiDrawElements; GLAD_API_CALL PFNGLMULTIDRAWELEMENTSPROC glad_debug_glMultiDrawElements; #define glMultiDrawElements glad_debug_glMultiDrawElements GLAD_API_CALL PFNGLMULTITEXCOORD1DPROC glad_glMultiTexCoord1d; GLAD_API_CALL PFNGLMULTITEXCOORD1DPROC glad_debug_glMultiTexCoord1d; #define glMultiTexCoord1d glad_debug_glMultiTexCoord1d GLAD_API_CALL PFNGLMULTITEXCOORD1DVPROC glad_glMultiTexCoord1dv; GLAD_API_CALL PFNGLMULTITEXCOORD1DVPROC glad_debug_glMultiTexCoord1dv; #define glMultiTexCoord1dv glad_debug_glMultiTexCoord1dv GLAD_API_CALL PFNGLMULTITEXCOORD1FPROC glad_glMultiTexCoord1f; GLAD_API_CALL PFNGLMULTITEXCOORD1FPROC glad_debug_glMultiTexCoord1f; #define glMultiTexCoord1f glad_debug_glMultiTexCoord1f GLAD_API_CALL PFNGLMULTITEXCOORD1FVPROC glad_glMultiTexCoord1fv; GLAD_API_CALL PFNGLMULTITEXCOORD1FVPROC glad_debug_glMultiTexCoord1fv; #define glMultiTexCoord1fv glad_debug_glMultiTexCoord1fv GLAD_API_CALL PFNGLMULTITEXCOORD1IPROC glad_glMultiTexCoord1i; GLAD_API_CALL PFNGLMULTITEXCOORD1IPROC glad_debug_glMultiTexCoord1i; #define glMultiTexCoord1i glad_debug_glMultiTexCoord1i GLAD_API_CALL PFNGLMULTITEXCOORD1IVPROC glad_glMultiTexCoord1iv; GLAD_API_CALL PFNGLMULTITEXCOORD1IVPROC glad_debug_glMultiTexCoord1iv; #define glMultiTexCoord1iv glad_debug_glMultiTexCoord1iv GLAD_API_CALL PFNGLMULTITEXCOORD1SPROC glad_glMultiTexCoord1s; GLAD_API_CALL PFNGLMULTITEXCOORD1SPROC glad_debug_glMultiTexCoord1s; #define glMultiTexCoord1s glad_debug_glMultiTexCoord1s GLAD_API_CALL PFNGLMULTITEXCOORD1SVPROC glad_glMultiTexCoord1sv; GLAD_API_CALL PFNGLMULTITEXCOORD1SVPROC glad_debug_glMultiTexCoord1sv; #define glMultiTexCoord1sv glad_debug_glMultiTexCoord1sv GLAD_API_CALL PFNGLMULTITEXCOORD2DPROC glad_glMultiTexCoord2d; GLAD_API_CALL PFNGLMULTITEXCOORD2DPROC glad_debug_glMultiTexCoord2d; #define glMultiTexCoord2d glad_debug_glMultiTexCoord2d GLAD_API_CALL PFNGLMULTITEXCOORD2DVPROC glad_glMultiTexCoord2dv; GLAD_API_CALL PFNGLMULTITEXCOORD2DVPROC glad_debug_glMultiTexCoord2dv; #define glMultiTexCoord2dv glad_debug_glMultiTexCoord2dv GLAD_API_CALL PFNGLMULTITEXCOORD2FPROC glad_glMultiTexCoord2f; GLAD_API_CALL PFNGLMULTITEXCOORD2FPROC glad_debug_glMultiTexCoord2f; #define glMultiTexCoord2f glad_debug_glMultiTexCoord2f GLAD_API_CALL PFNGLMULTITEXCOORD2FVPROC glad_glMultiTexCoord2fv; GLAD_API_CALL PFNGLMULTITEXCOORD2FVPROC glad_debug_glMultiTexCoord2fv; #define glMultiTexCoord2fv glad_debug_glMultiTexCoord2fv GLAD_API_CALL PFNGLMULTITEXCOORD2IPROC glad_glMultiTexCoord2i; GLAD_API_CALL PFNGLMULTITEXCOORD2IPROC glad_debug_glMultiTexCoord2i; #define glMultiTexCoord2i glad_debug_glMultiTexCoord2i GLAD_API_CALL PFNGLMULTITEXCOORD2IVPROC glad_glMultiTexCoord2iv; GLAD_API_CALL PFNGLMULTITEXCOORD2IVPROC glad_debug_glMultiTexCoord2iv; #define glMultiTexCoord2iv glad_debug_glMultiTexCoord2iv GLAD_API_CALL PFNGLMULTITEXCOORD2SPROC glad_glMultiTexCoord2s; GLAD_API_CALL PFNGLMULTITEXCOORD2SPROC glad_debug_glMultiTexCoord2s; #define glMultiTexCoord2s glad_debug_glMultiTexCoord2s GLAD_API_CALL PFNGLMULTITEXCOORD2SVPROC glad_glMultiTexCoord2sv; GLAD_API_CALL PFNGLMULTITEXCOORD2SVPROC glad_debug_glMultiTexCoord2sv; #define glMultiTexCoord2sv glad_debug_glMultiTexCoord2sv GLAD_API_CALL PFNGLMULTITEXCOORD3DPROC glad_glMultiTexCoord3d; GLAD_API_CALL PFNGLMULTITEXCOORD3DPROC glad_debug_glMultiTexCoord3d; #define glMultiTexCoord3d glad_debug_glMultiTexCoord3d GLAD_API_CALL PFNGLMULTITEXCOORD3DVPROC glad_glMultiTexCoord3dv; GLAD_API_CALL PFNGLMULTITEXCOORD3DVPROC glad_debug_glMultiTexCoord3dv; #define glMultiTexCoord3dv glad_debug_glMultiTexCoord3dv GLAD_API_CALL PFNGLMULTITEXCOORD3FPROC glad_glMultiTexCoord3f; GLAD_API_CALL PFNGLMULTITEXCOORD3FPROC glad_debug_glMultiTexCoord3f; #define glMultiTexCoord3f glad_debug_glMultiTexCoord3f GLAD_API_CALL PFNGLMULTITEXCOORD3FVPROC glad_glMultiTexCoord3fv; GLAD_API_CALL PFNGLMULTITEXCOORD3FVPROC glad_debug_glMultiTexCoord3fv; #define glMultiTexCoord3fv glad_debug_glMultiTexCoord3fv GLAD_API_CALL PFNGLMULTITEXCOORD3IPROC glad_glMultiTexCoord3i; GLAD_API_CALL PFNGLMULTITEXCOORD3IPROC glad_debug_glMultiTexCoord3i; #define glMultiTexCoord3i glad_debug_glMultiTexCoord3i GLAD_API_CALL PFNGLMULTITEXCOORD3IVPROC glad_glMultiTexCoord3iv; GLAD_API_CALL PFNGLMULTITEXCOORD3IVPROC glad_debug_glMultiTexCoord3iv; #define glMultiTexCoord3iv glad_debug_glMultiTexCoord3iv GLAD_API_CALL PFNGLMULTITEXCOORD3SPROC glad_glMultiTexCoord3s; GLAD_API_CALL PFNGLMULTITEXCOORD3SPROC glad_debug_glMultiTexCoord3s; #define glMultiTexCoord3s glad_debug_glMultiTexCoord3s GLAD_API_CALL PFNGLMULTITEXCOORD3SVPROC glad_glMultiTexCoord3sv; GLAD_API_CALL PFNGLMULTITEXCOORD3SVPROC glad_debug_glMultiTexCoord3sv; #define glMultiTexCoord3sv glad_debug_glMultiTexCoord3sv GLAD_API_CALL PFNGLMULTITEXCOORD4DPROC glad_glMultiTexCoord4d; GLAD_API_CALL PFNGLMULTITEXCOORD4DPROC glad_debug_glMultiTexCoord4d; #define glMultiTexCoord4d glad_debug_glMultiTexCoord4d GLAD_API_CALL PFNGLMULTITEXCOORD4DVPROC glad_glMultiTexCoord4dv; GLAD_API_CALL PFNGLMULTITEXCOORD4DVPROC glad_debug_glMultiTexCoord4dv; #define glMultiTexCoord4dv glad_debug_glMultiTexCoord4dv GLAD_API_CALL PFNGLMULTITEXCOORD4FPROC glad_glMultiTexCoord4f; GLAD_API_CALL PFNGLMULTITEXCOORD4FPROC glad_debug_glMultiTexCoord4f; #define glMultiTexCoord4f glad_debug_glMultiTexCoord4f GLAD_API_CALL PFNGLMULTITEXCOORD4FVPROC glad_glMultiTexCoord4fv; GLAD_API_CALL PFNGLMULTITEXCOORD4FVPROC glad_debug_glMultiTexCoord4fv; #define glMultiTexCoord4fv glad_debug_glMultiTexCoord4fv GLAD_API_CALL PFNGLMULTITEXCOORD4IPROC glad_glMultiTexCoord4i; GLAD_API_CALL PFNGLMULTITEXCOORD4IPROC glad_debug_glMultiTexCoord4i; #define glMultiTexCoord4i glad_debug_glMultiTexCoord4i GLAD_API_CALL PFNGLMULTITEXCOORD4IVPROC glad_glMultiTexCoord4iv; GLAD_API_CALL PFNGLMULTITEXCOORD4IVPROC glad_debug_glMultiTexCoord4iv; #define glMultiTexCoord4iv glad_debug_glMultiTexCoord4iv GLAD_API_CALL PFNGLMULTITEXCOORD4SPROC glad_glMultiTexCoord4s; GLAD_API_CALL PFNGLMULTITEXCOORD4SPROC glad_debug_glMultiTexCoord4s; #define glMultiTexCoord4s glad_debug_glMultiTexCoord4s GLAD_API_CALL PFNGLMULTITEXCOORD4SVPROC glad_glMultiTexCoord4sv; GLAD_API_CALL PFNGLMULTITEXCOORD4SVPROC glad_debug_glMultiTexCoord4sv; #define glMultiTexCoord4sv glad_debug_glMultiTexCoord4sv GLAD_API_CALL PFNGLNEWLISTPROC glad_glNewList; GLAD_API_CALL PFNGLNEWLISTPROC glad_debug_glNewList; #define glNewList glad_debug_glNewList GLAD_API_CALL PFNGLNORMAL3BPROC glad_glNormal3b; GLAD_API_CALL PFNGLNORMAL3BPROC glad_debug_glNormal3b; #define glNormal3b glad_debug_glNormal3b GLAD_API_CALL PFNGLNORMAL3BVPROC glad_glNormal3bv; GLAD_API_CALL PFNGLNORMAL3BVPROC glad_debug_glNormal3bv; #define glNormal3bv glad_debug_glNormal3bv GLAD_API_CALL PFNGLNORMAL3DPROC glad_glNormal3d; GLAD_API_CALL PFNGLNORMAL3DPROC glad_debug_glNormal3d; #define glNormal3d glad_debug_glNormal3d GLAD_API_CALL PFNGLNORMAL3DVPROC glad_glNormal3dv; GLAD_API_CALL PFNGLNORMAL3DVPROC glad_debug_glNormal3dv; #define glNormal3dv glad_debug_glNormal3dv GLAD_API_CALL PFNGLNORMAL3FPROC glad_glNormal3f; GLAD_API_CALL PFNGLNORMAL3FPROC glad_debug_glNormal3f; #define glNormal3f glad_debug_glNormal3f GLAD_API_CALL PFNGLNORMAL3FVPROC glad_glNormal3fv; GLAD_API_CALL PFNGLNORMAL3FVPROC glad_debug_glNormal3fv; #define glNormal3fv glad_debug_glNormal3fv GLAD_API_CALL PFNGLNORMAL3IPROC glad_glNormal3i; GLAD_API_CALL PFNGLNORMAL3IPROC glad_debug_glNormal3i; #define glNormal3i glad_debug_glNormal3i GLAD_API_CALL PFNGLNORMAL3IVPROC glad_glNormal3iv; GLAD_API_CALL PFNGLNORMAL3IVPROC glad_debug_glNormal3iv; #define glNormal3iv glad_debug_glNormal3iv GLAD_API_CALL PFNGLNORMAL3SPROC glad_glNormal3s; GLAD_API_CALL PFNGLNORMAL3SPROC glad_debug_glNormal3s; #define glNormal3s glad_debug_glNormal3s GLAD_API_CALL PFNGLNORMAL3SVPROC glad_glNormal3sv; GLAD_API_CALL PFNGLNORMAL3SVPROC glad_debug_glNormal3sv; #define glNormal3sv glad_debug_glNormal3sv GLAD_API_CALL PFNGLNORMALPOINTERPROC glad_glNormalPointer; GLAD_API_CALL PFNGLNORMALPOINTERPROC glad_debug_glNormalPointer; #define glNormalPointer glad_debug_glNormalPointer GLAD_API_CALL PFNGLOBJECTLABELPROC glad_glObjectLabel; GLAD_API_CALL PFNGLOBJECTLABELPROC glad_debug_glObjectLabel; #define glObjectLabel glad_debug_glObjectLabel GLAD_API_CALL PFNGLOBJECTPTRLABELPROC glad_glObjectPtrLabel; GLAD_API_CALL PFNGLOBJECTPTRLABELPROC glad_debug_glObjectPtrLabel; #define glObjectPtrLabel glad_debug_glObjectPtrLabel GLAD_API_CALL PFNGLORTHOPROC glad_glOrtho; GLAD_API_CALL PFNGLORTHOPROC glad_debug_glOrtho; #define glOrtho glad_debug_glOrtho GLAD_API_CALL PFNGLPASSTHROUGHPROC glad_glPassThrough; GLAD_API_CALL PFNGLPASSTHROUGHPROC glad_debug_glPassThrough; #define glPassThrough glad_debug_glPassThrough GLAD_API_CALL PFNGLPIXELMAPFVPROC glad_glPixelMapfv; GLAD_API_CALL PFNGLPIXELMAPFVPROC glad_debug_glPixelMapfv; #define glPixelMapfv glad_debug_glPixelMapfv GLAD_API_CALL PFNGLPIXELMAPUIVPROC glad_glPixelMapuiv; GLAD_API_CALL PFNGLPIXELMAPUIVPROC glad_debug_glPixelMapuiv; #define glPixelMapuiv glad_debug_glPixelMapuiv GLAD_API_CALL PFNGLPIXELMAPUSVPROC glad_glPixelMapusv; GLAD_API_CALL PFNGLPIXELMAPUSVPROC glad_debug_glPixelMapusv; #define glPixelMapusv glad_debug_glPixelMapusv GLAD_API_CALL PFNGLPIXELSTOREFPROC glad_glPixelStoref; GLAD_API_CALL PFNGLPIXELSTOREFPROC glad_debug_glPixelStoref; #define glPixelStoref glad_debug_glPixelStoref GLAD_API_CALL PFNGLPIXELSTOREIPROC glad_glPixelStorei; GLAD_API_CALL PFNGLPIXELSTOREIPROC glad_debug_glPixelStorei; #define glPixelStorei glad_debug_glPixelStorei GLAD_API_CALL PFNGLPIXELTRANSFERFPROC glad_glPixelTransferf; GLAD_API_CALL PFNGLPIXELTRANSFERFPROC glad_debug_glPixelTransferf; #define glPixelTransferf glad_debug_glPixelTransferf GLAD_API_CALL PFNGLPIXELTRANSFERIPROC glad_glPixelTransferi; GLAD_API_CALL PFNGLPIXELTRANSFERIPROC glad_debug_glPixelTransferi; #define glPixelTransferi glad_debug_glPixelTransferi GLAD_API_CALL PFNGLPIXELZOOMPROC glad_glPixelZoom; GLAD_API_CALL PFNGLPIXELZOOMPROC glad_debug_glPixelZoom; #define glPixelZoom glad_debug_glPixelZoom GLAD_API_CALL PFNGLPOINTPARAMETERFPROC glad_glPointParameterf; GLAD_API_CALL PFNGLPOINTPARAMETERFPROC glad_debug_glPointParameterf; #define glPointParameterf glad_debug_glPointParameterf GLAD_API_CALL PFNGLPOINTPARAMETERFVPROC glad_glPointParameterfv; GLAD_API_CALL PFNGLPOINTPARAMETERFVPROC glad_debug_glPointParameterfv; #define glPointParameterfv glad_debug_glPointParameterfv GLAD_API_CALL PFNGLPOINTPARAMETERIPROC glad_glPointParameteri; GLAD_API_CALL PFNGLPOINTPARAMETERIPROC glad_debug_glPointParameteri; #define glPointParameteri glad_debug_glPointParameteri GLAD_API_CALL PFNGLPOINTPARAMETERIVPROC glad_glPointParameteriv; GLAD_API_CALL PFNGLPOINTPARAMETERIVPROC glad_debug_glPointParameteriv; #define glPointParameteriv glad_debug_glPointParameteriv GLAD_API_CALL PFNGLPOINTSIZEPROC glad_glPointSize; GLAD_API_CALL PFNGLPOINTSIZEPROC glad_debug_glPointSize; #define glPointSize glad_debug_glPointSize GLAD_API_CALL PFNGLPOLYGONMODEPROC glad_glPolygonMode; GLAD_API_CALL PFNGLPOLYGONMODEPROC glad_debug_glPolygonMode; #define glPolygonMode glad_debug_glPolygonMode GLAD_API_CALL PFNGLPOLYGONOFFSETPROC glad_glPolygonOffset; GLAD_API_CALL PFNGLPOLYGONOFFSETPROC glad_debug_glPolygonOffset; #define glPolygonOffset glad_debug_glPolygonOffset GLAD_API_CALL PFNGLPOLYGONSTIPPLEPROC glad_glPolygonStipple; GLAD_API_CALL PFNGLPOLYGONSTIPPLEPROC glad_debug_glPolygonStipple; #define glPolygonStipple glad_debug_glPolygonStipple GLAD_API_CALL PFNGLPOPATTRIBPROC glad_glPopAttrib; GLAD_API_CALL PFNGLPOPATTRIBPROC glad_debug_glPopAttrib; #define glPopAttrib glad_debug_glPopAttrib GLAD_API_CALL PFNGLPOPCLIENTATTRIBPROC glad_glPopClientAttrib; GLAD_API_CALL PFNGLPOPCLIENTATTRIBPROC glad_debug_glPopClientAttrib; #define glPopClientAttrib glad_debug_glPopClientAttrib GLAD_API_CALL PFNGLPOPDEBUGGROUPPROC glad_glPopDebugGroup; GLAD_API_CALL PFNGLPOPDEBUGGROUPPROC glad_debug_glPopDebugGroup; #define glPopDebugGroup glad_debug_glPopDebugGroup GLAD_API_CALL PFNGLPOPMATRIXPROC glad_glPopMatrix; GLAD_API_CALL PFNGLPOPMATRIXPROC glad_debug_glPopMatrix; #define glPopMatrix glad_debug_glPopMatrix GLAD_API_CALL PFNGLPOPNAMEPROC glad_glPopName; GLAD_API_CALL PFNGLPOPNAMEPROC glad_debug_glPopName; #define glPopName glad_debug_glPopName GLAD_API_CALL PFNGLPRIMITIVERESTARTINDEXPROC glad_glPrimitiveRestartIndex; GLAD_API_CALL PFNGLPRIMITIVERESTARTINDEXPROC glad_debug_glPrimitiveRestartIndex; #define glPrimitiveRestartIndex glad_debug_glPrimitiveRestartIndex GLAD_API_CALL PFNGLPRIORITIZETEXTURESPROC glad_glPrioritizeTextures; GLAD_API_CALL PFNGLPRIORITIZETEXTURESPROC glad_debug_glPrioritizeTextures; #define glPrioritizeTextures glad_debug_glPrioritizeTextures GLAD_API_CALL PFNGLPUSHATTRIBPROC glad_glPushAttrib; GLAD_API_CALL PFNGLPUSHATTRIBPROC glad_debug_glPushAttrib; #define glPushAttrib glad_debug_glPushAttrib GLAD_API_CALL PFNGLPUSHCLIENTATTRIBPROC glad_glPushClientAttrib; GLAD_API_CALL PFNGLPUSHCLIENTATTRIBPROC glad_debug_glPushClientAttrib; #define glPushClientAttrib glad_debug_glPushClientAttrib GLAD_API_CALL PFNGLPUSHDEBUGGROUPPROC glad_glPushDebugGroup; GLAD_API_CALL PFNGLPUSHDEBUGGROUPPROC glad_debug_glPushDebugGroup; #define glPushDebugGroup glad_debug_glPushDebugGroup GLAD_API_CALL PFNGLPUSHMATRIXPROC glad_glPushMatrix; GLAD_API_CALL PFNGLPUSHMATRIXPROC glad_debug_glPushMatrix; #define glPushMatrix glad_debug_glPushMatrix GLAD_API_CALL PFNGLPUSHNAMEPROC glad_glPushName; GLAD_API_CALL PFNGLPUSHNAMEPROC glad_debug_glPushName; #define glPushName glad_debug_glPushName GLAD_API_CALL PFNGLRASTERPOS2DPROC glad_glRasterPos2d; GLAD_API_CALL PFNGLRASTERPOS2DPROC glad_debug_glRasterPos2d; #define glRasterPos2d glad_debug_glRasterPos2d GLAD_API_CALL PFNGLRASTERPOS2DVPROC glad_glRasterPos2dv; GLAD_API_CALL PFNGLRASTERPOS2DVPROC glad_debug_glRasterPos2dv; #define glRasterPos2dv glad_debug_glRasterPos2dv GLAD_API_CALL PFNGLRASTERPOS2FPROC glad_glRasterPos2f; GLAD_API_CALL PFNGLRASTERPOS2FPROC glad_debug_glRasterPos2f; #define glRasterPos2f glad_debug_glRasterPos2f GLAD_API_CALL PFNGLRASTERPOS2FVPROC glad_glRasterPos2fv; GLAD_API_CALL PFNGLRASTERPOS2FVPROC glad_debug_glRasterPos2fv; #define glRasterPos2fv glad_debug_glRasterPos2fv GLAD_API_CALL PFNGLRASTERPOS2IPROC glad_glRasterPos2i; GLAD_API_CALL PFNGLRASTERPOS2IPROC glad_debug_glRasterPos2i; #define glRasterPos2i glad_debug_glRasterPos2i GLAD_API_CALL PFNGLRASTERPOS2IVPROC glad_glRasterPos2iv; GLAD_API_CALL PFNGLRASTERPOS2IVPROC glad_debug_glRasterPos2iv; #define glRasterPos2iv glad_debug_glRasterPos2iv GLAD_API_CALL PFNGLRASTERPOS2SPROC glad_glRasterPos2s; GLAD_API_CALL PFNGLRASTERPOS2SPROC glad_debug_glRasterPos2s; #define glRasterPos2s glad_debug_glRasterPos2s GLAD_API_CALL PFNGLRASTERPOS2SVPROC glad_glRasterPos2sv; GLAD_API_CALL PFNGLRASTERPOS2SVPROC glad_debug_glRasterPos2sv; #define glRasterPos2sv glad_debug_glRasterPos2sv GLAD_API_CALL PFNGLRASTERPOS3DPROC glad_glRasterPos3d; GLAD_API_CALL PFNGLRASTERPOS3DPROC glad_debug_glRasterPos3d; #define glRasterPos3d glad_debug_glRasterPos3d GLAD_API_CALL PFNGLRASTERPOS3DVPROC glad_glRasterPos3dv; GLAD_API_CALL PFNGLRASTERPOS3DVPROC glad_debug_glRasterPos3dv; #define glRasterPos3dv glad_debug_glRasterPos3dv GLAD_API_CALL PFNGLRASTERPOS3FPROC glad_glRasterPos3f; GLAD_API_CALL PFNGLRASTERPOS3FPROC glad_debug_glRasterPos3f; #define glRasterPos3f glad_debug_glRasterPos3f GLAD_API_CALL PFNGLRASTERPOS3FVPROC glad_glRasterPos3fv; GLAD_API_CALL PFNGLRASTERPOS3FVPROC glad_debug_glRasterPos3fv; #define glRasterPos3fv glad_debug_glRasterPos3fv GLAD_API_CALL PFNGLRASTERPOS3IPROC glad_glRasterPos3i; GLAD_API_CALL PFNGLRASTERPOS3IPROC glad_debug_glRasterPos3i; #define glRasterPos3i glad_debug_glRasterPos3i GLAD_API_CALL PFNGLRASTERPOS3IVPROC glad_glRasterPos3iv; GLAD_API_CALL PFNGLRASTERPOS3IVPROC glad_debug_glRasterPos3iv; #define glRasterPos3iv glad_debug_glRasterPos3iv GLAD_API_CALL PFNGLRASTERPOS3SPROC glad_glRasterPos3s; GLAD_API_CALL PFNGLRASTERPOS3SPROC glad_debug_glRasterPos3s; #define glRasterPos3s glad_debug_glRasterPos3s GLAD_API_CALL PFNGLRASTERPOS3SVPROC glad_glRasterPos3sv; GLAD_API_CALL PFNGLRASTERPOS3SVPROC glad_debug_glRasterPos3sv; #define glRasterPos3sv glad_debug_glRasterPos3sv GLAD_API_CALL PFNGLRASTERPOS4DPROC glad_glRasterPos4d; GLAD_API_CALL PFNGLRASTERPOS4DPROC glad_debug_glRasterPos4d; #define glRasterPos4d glad_debug_glRasterPos4d GLAD_API_CALL PFNGLRASTERPOS4DVPROC glad_glRasterPos4dv; GLAD_API_CALL PFNGLRASTERPOS4DVPROC glad_debug_glRasterPos4dv; #define glRasterPos4dv glad_debug_glRasterPos4dv GLAD_API_CALL PFNGLRASTERPOS4FPROC glad_glRasterPos4f; GLAD_API_CALL PFNGLRASTERPOS4FPROC glad_debug_glRasterPos4f; #define glRasterPos4f glad_debug_glRasterPos4f GLAD_API_CALL PFNGLRASTERPOS4FVPROC glad_glRasterPos4fv; GLAD_API_CALL PFNGLRASTERPOS4FVPROC glad_debug_glRasterPos4fv; #define glRasterPos4fv glad_debug_glRasterPos4fv GLAD_API_CALL PFNGLRASTERPOS4IPROC glad_glRasterPos4i; GLAD_API_CALL PFNGLRASTERPOS4IPROC glad_debug_glRasterPos4i; #define glRasterPos4i glad_debug_glRasterPos4i GLAD_API_CALL PFNGLRASTERPOS4IVPROC glad_glRasterPos4iv; GLAD_API_CALL PFNGLRASTERPOS4IVPROC glad_debug_glRasterPos4iv; #define glRasterPos4iv glad_debug_glRasterPos4iv GLAD_API_CALL PFNGLRASTERPOS4SPROC glad_glRasterPos4s; GLAD_API_CALL PFNGLRASTERPOS4SPROC glad_debug_glRasterPos4s; #define glRasterPos4s glad_debug_glRasterPos4s GLAD_API_CALL PFNGLRASTERPOS4SVPROC glad_glRasterPos4sv; GLAD_API_CALL PFNGLRASTERPOS4SVPROC glad_debug_glRasterPos4sv; #define glRasterPos4sv glad_debug_glRasterPos4sv GLAD_API_CALL PFNGLREADBUFFERPROC glad_glReadBuffer; GLAD_API_CALL PFNGLREADBUFFERPROC glad_debug_glReadBuffer; #define glReadBuffer glad_debug_glReadBuffer GLAD_API_CALL PFNGLREADPIXELSPROC glad_glReadPixels; GLAD_API_CALL PFNGLREADPIXELSPROC glad_debug_glReadPixels; #define glReadPixels glad_debug_glReadPixels GLAD_API_CALL PFNGLREADNPIXELSARBPROC glad_glReadnPixelsARB; GLAD_API_CALL PFNGLREADNPIXELSARBPROC glad_debug_glReadnPixelsARB; #define glReadnPixelsARB glad_debug_glReadnPixelsARB GLAD_API_CALL PFNGLRECTDPROC glad_glRectd; GLAD_API_CALL PFNGLRECTDPROC glad_debug_glRectd; #define glRectd glad_debug_glRectd GLAD_API_CALL PFNGLRECTDVPROC glad_glRectdv; GLAD_API_CALL PFNGLRECTDVPROC glad_debug_glRectdv; #define glRectdv glad_debug_glRectdv GLAD_API_CALL PFNGLRECTFPROC glad_glRectf; GLAD_API_CALL PFNGLRECTFPROC glad_debug_glRectf; #define glRectf glad_debug_glRectf GLAD_API_CALL PFNGLRECTFVPROC glad_glRectfv; GLAD_API_CALL PFNGLRECTFVPROC glad_debug_glRectfv; #define glRectfv glad_debug_glRectfv GLAD_API_CALL PFNGLRECTIPROC glad_glRecti; GLAD_API_CALL PFNGLRECTIPROC glad_debug_glRecti; #define glRecti glad_debug_glRecti GLAD_API_CALL PFNGLRECTIVPROC glad_glRectiv; GLAD_API_CALL PFNGLRECTIVPROC glad_debug_glRectiv; #define glRectiv glad_debug_glRectiv GLAD_API_CALL PFNGLRECTSPROC glad_glRects; GLAD_API_CALL PFNGLRECTSPROC glad_debug_glRects; #define glRects glad_debug_glRects GLAD_API_CALL PFNGLRECTSVPROC glad_glRectsv; GLAD_API_CALL PFNGLRECTSVPROC glad_debug_glRectsv; #define glRectsv glad_debug_glRectsv GLAD_API_CALL PFNGLRENDERMODEPROC glad_glRenderMode; GLAD_API_CALL PFNGLRENDERMODEPROC glad_debug_glRenderMode; #define glRenderMode glad_debug_glRenderMode GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEPROC glad_glRenderbufferStorage; GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEPROC glad_debug_glRenderbufferStorage; #define glRenderbufferStorage glad_debug_glRenderbufferStorage GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample; GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_debug_glRenderbufferStorageMultisample; #define glRenderbufferStorageMultisample glad_debug_glRenderbufferStorageMultisample GLAD_API_CALL PFNGLROTATEDPROC glad_glRotated; GLAD_API_CALL PFNGLROTATEDPROC glad_debug_glRotated; #define glRotated glad_debug_glRotated GLAD_API_CALL PFNGLROTATEFPROC glad_glRotatef; GLAD_API_CALL PFNGLROTATEFPROC glad_debug_glRotatef; #define glRotatef glad_debug_glRotatef GLAD_API_CALL PFNGLSAMPLECOVERAGEPROC glad_glSampleCoverage; GLAD_API_CALL PFNGLSAMPLECOVERAGEPROC glad_debug_glSampleCoverage; #define glSampleCoverage glad_debug_glSampleCoverage GLAD_API_CALL PFNGLSAMPLECOVERAGEARBPROC glad_glSampleCoverageARB; GLAD_API_CALL PFNGLSAMPLECOVERAGEARBPROC glad_debug_glSampleCoverageARB; #define glSampleCoverageARB glad_debug_glSampleCoverageARB GLAD_API_CALL PFNGLSCALEDPROC glad_glScaled; GLAD_API_CALL PFNGLSCALEDPROC glad_debug_glScaled; #define glScaled glad_debug_glScaled GLAD_API_CALL PFNGLSCALEFPROC glad_glScalef; GLAD_API_CALL PFNGLSCALEFPROC glad_debug_glScalef; #define glScalef glad_debug_glScalef GLAD_API_CALL PFNGLSCISSORPROC glad_glScissor; GLAD_API_CALL PFNGLSCISSORPROC glad_debug_glScissor; #define glScissor glad_debug_glScissor GLAD_API_CALL PFNGLSECONDARYCOLOR3BPROC glad_glSecondaryColor3b; GLAD_API_CALL PFNGLSECONDARYCOLOR3BPROC glad_debug_glSecondaryColor3b; #define glSecondaryColor3b glad_debug_glSecondaryColor3b GLAD_API_CALL PFNGLSECONDARYCOLOR3BVPROC glad_glSecondaryColor3bv; GLAD_API_CALL PFNGLSECONDARYCOLOR3BVPROC glad_debug_glSecondaryColor3bv; #define glSecondaryColor3bv glad_debug_glSecondaryColor3bv GLAD_API_CALL PFNGLSECONDARYCOLOR3DPROC glad_glSecondaryColor3d; GLAD_API_CALL PFNGLSECONDARYCOLOR3DPROC glad_debug_glSecondaryColor3d; #define glSecondaryColor3d glad_debug_glSecondaryColor3d GLAD_API_CALL PFNGLSECONDARYCOLOR3DVPROC glad_glSecondaryColor3dv; GLAD_API_CALL PFNGLSECONDARYCOLOR3DVPROC glad_debug_glSecondaryColor3dv; #define glSecondaryColor3dv glad_debug_glSecondaryColor3dv GLAD_API_CALL PFNGLSECONDARYCOLOR3FPROC glad_glSecondaryColor3f; GLAD_API_CALL PFNGLSECONDARYCOLOR3FPROC glad_debug_glSecondaryColor3f; #define glSecondaryColor3f glad_debug_glSecondaryColor3f GLAD_API_CALL PFNGLSECONDARYCOLOR3FVPROC glad_glSecondaryColor3fv; GLAD_API_CALL PFNGLSECONDARYCOLOR3FVPROC glad_debug_glSecondaryColor3fv; #define glSecondaryColor3fv glad_debug_glSecondaryColor3fv GLAD_API_CALL PFNGLSECONDARYCOLOR3IPROC glad_glSecondaryColor3i; GLAD_API_CALL PFNGLSECONDARYCOLOR3IPROC glad_debug_glSecondaryColor3i; #define glSecondaryColor3i glad_debug_glSecondaryColor3i GLAD_API_CALL PFNGLSECONDARYCOLOR3IVPROC glad_glSecondaryColor3iv; GLAD_API_CALL PFNGLSECONDARYCOLOR3IVPROC glad_debug_glSecondaryColor3iv; #define glSecondaryColor3iv glad_debug_glSecondaryColor3iv GLAD_API_CALL PFNGLSECONDARYCOLOR3SPROC glad_glSecondaryColor3s; GLAD_API_CALL PFNGLSECONDARYCOLOR3SPROC glad_debug_glSecondaryColor3s; #define glSecondaryColor3s glad_debug_glSecondaryColor3s GLAD_API_CALL PFNGLSECONDARYCOLOR3SVPROC glad_glSecondaryColor3sv; GLAD_API_CALL PFNGLSECONDARYCOLOR3SVPROC glad_debug_glSecondaryColor3sv; #define glSecondaryColor3sv glad_debug_glSecondaryColor3sv GLAD_API_CALL PFNGLSECONDARYCOLOR3UBPROC glad_glSecondaryColor3ub; GLAD_API_CALL PFNGLSECONDARYCOLOR3UBPROC glad_debug_glSecondaryColor3ub; #define glSecondaryColor3ub glad_debug_glSecondaryColor3ub GLAD_API_CALL PFNGLSECONDARYCOLOR3UBVPROC glad_glSecondaryColor3ubv; GLAD_API_CALL PFNGLSECONDARYCOLOR3UBVPROC glad_debug_glSecondaryColor3ubv; #define glSecondaryColor3ubv glad_debug_glSecondaryColor3ubv GLAD_API_CALL PFNGLSECONDARYCOLOR3UIPROC glad_glSecondaryColor3ui; GLAD_API_CALL PFNGLSECONDARYCOLOR3UIPROC glad_debug_glSecondaryColor3ui; #define glSecondaryColor3ui glad_debug_glSecondaryColor3ui GLAD_API_CALL PFNGLSECONDARYCOLOR3UIVPROC glad_glSecondaryColor3uiv; GLAD_API_CALL PFNGLSECONDARYCOLOR3UIVPROC glad_debug_glSecondaryColor3uiv; #define glSecondaryColor3uiv glad_debug_glSecondaryColor3uiv GLAD_API_CALL PFNGLSECONDARYCOLOR3USPROC glad_glSecondaryColor3us; GLAD_API_CALL PFNGLSECONDARYCOLOR3USPROC glad_debug_glSecondaryColor3us; #define glSecondaryColor3us glad_debug_glSecondaryColor3us GLAD_API_CALL PFNGLSECONDARYCOLOR3USVPROC glad_glSecondaryColor3usv; GLAD_API_CALL PFNGLSECONDARYCOLOR3USVPROC glad_debug_glSecondaryColor3usv; #define glSecondaryColor3usv glad_debug_glSecondaryColor3usv GLAD_API_CALL PFNGLSECONDARYCOLORPOINTERPROC glad_glSecondaryColorPointer; GLAD_API_CALL PFNGLSECONDARYCOLORPOINTERPROC glad_debug_glSecondaryColorPointer; #define glSecondaryColorPointer glad_debug_glSecondaryColorPointer GLAD_API_CALL PFNGLSELECTBUFFERPROC glad_glSelectBuffer; GLAD_API_CALL PFNGLSELECTBUFFERPROC glad_debug_glSelectBuffer; #define glSelectBuffer glad_debug_glSelectBuffer GLAD_API_CALL PFNGLSHADEMODELPROC glad_glShadeModel; GLAD_API_CALL PFNGLSHADEMODELPROC glad_debug_glShadeModel; #define glShadeModel glad_debug_glShadeModel GLAD_API_CALL PFNGLSHADERSOURCEPROC glad_glShaderSource; GLAD_API_CALL PFNGLSHADERSOURCEPROC glad_debug_glShaderSource; #define glShaderSource glad_debug_glShaderSource GLAD_API_CALL PFNGLSTENCILFUNCPROC glad_glStencilFunc; GLAD_API_CALL PFNGLSTENCILFUNCPROC glad_debug_glStencilFunc; #define glStencilFunc glad_debug_glStencilFunc GLAD_API_CALL PFNGLSTENCILFUNCSEPARATEPROC glad_glStencilFuncSeparate; GLAD_API_CALL PFNGLSTENCILFUNCSEPARATEPROC glad_debug_glStencilFuncSeparate; #define glStencilFuncSeparate glad_debug_glStencilFuncSeparate GLAD_API_CALL PFNGLSTENCILMASKPROC glad_glStencilMask; GLAD_API_CALL PFNGLSTENCILMASKPROC glad_debug_glStencilMask; #define glStencilMask glad_debug_glStencilMask GLAD_API_CALL PFNGLSTENCILMASKSEPARATEPROC glad_glStencilMaskSeparate; GLAD_API_CALL PFNGLSTENCILMASKSEPARATEPROC glad_debug_glStencilMaskSeparate; #define glStencilMaskSeparate glad_debug_glStencilMaskSeparate GLAD_API_CALL PFNGLSTENCILOPPROC glad_glStencilOp; GLAD_API_CALL PFNGLSTENCILOPPROC glad_debug_glStencilOp; #define glStencilOp glad_debug_glStencilOp GLAD_API_CALL PFNGLSTENCILOPSEPARATEPROC glad_glStencilOpSeparate; GLAD_API_CALL PFNGLSTENCILOPSEPARATEPROC glad_debug_glStencilOpSeparate; #define glStencilOpSeparate glad_debug_glStencilOpSeparate GLAD_API_CALL PFNGLTEXBUFFERPROC glad_glTexBuffer; GLAD_API_CALL PFNGLTEXBUFFERPROC glad_debug_glTexBuffer; #define glTexBuffer glad_debug_glTexBuffer GLAD_API_CALL PFNGLTEXCOORD1DPROC glad_glTexCoord1d; GLAD_API_CALL PFNGLTEXCOORD1DPROC glad_debug_glTexCoord1d; #define glTexCoord1d glad_debug_glTexCoord1d GLAD_API_CALL PFNGLTEXCOORD1DVPROC glad_glTexCoord1dv; GLAD_API_CALL PFNGLTEXCOORD1DVPROC glad_debug_glTexCoord1dv; #define glTexCoord1dv glad_debug_glTexCoord1dv GLAD_API_CALL PFNGLTEXCOORD1FPROC glad_glTexCoord1f; GLAD_API_CALL PFNGLTEXCOORD1FPROC glad_debug_glTexCoord1f; #define glTexCoord1f glad_debug_glTexCoord1f GLAD_API_CALL PFNGLTEXCOORD1FVPROC glad_glTexCoord1fv; GLAD_API_CALL PFNGLTEXCOORD1FVPROC glad_debug_glTexCoord1fv; #define glTexCoord1fv glad_debug_glTexCoord1fv GLAD_API_CALL PFNGLTEXCOORD1IPROC glad_glTexCoord1i; GLAD_API_CALL PFNGLTEXCOORD1IPROC glad_debug_glTexCoord1i; #define glTexCoord1i glad_debug_glTexCoord1i GLAD_API_CALL PFNGLTEXCOORD1IVPROC glad_glTexCoord1iv; GLAD_API_CALL PFNGLTEXCOORD1IVPROC glad_debug_glTexCoord1iv; #define glTexCoord1iv glad_debug_glTexCoord1iv GLAD_API_CALL PFNGLTEXCOORD1SPROC glad_glTexCoord1s; GLAD_API_CALL PFNGLTEXCOORD1SPROC glad_debug_glTexCoord1s; #define glTexCoord1s glad_debug_glTexCoord1s GLAD_API_CALL PFNGLTEXCOORD1SVPROC glad_glTexCoord1sv; GLAD_API_CALL PFNGLTEXCOORD1SVPROC glad_debug_glTexCoord1sv; #define glTexCoord1sv glad_debug_glTexCoord1sv GLAD_API_CALL PFNGLTEXCOORD2DPROC glad_glTexCoord2d; GLAD_API_CALL PFNGLTEXCOORD2DPROC glad_debug_glTexCoord2d; #define glTexCoord2d glad_debug_glTexCoord2d GLAD_API_CALL PFNGLTEXCOORD2DVPROC glad_glTexCoord2dv; GLAD_API_CALL PFNGLTEXCOORD2DVPROC glad_debug_glTexCoord2dv; #define glTexCoord2dv glad_debug_glTexCoord2dv GLAD_API_CALL PFNGLTEXCOORD2FPROC glad_glTexCoord2f; GLAD_API_CALL PFNGLTEXCOORD2FPROC glad_debug_glTexCoord2f; #define glTexCoord2f glad_debug_glTexCoord2f GLAD_API_CALL PFNGLTEXCOORD2FVPROC glad_glTexCoord2fv; GLAD_API_CALL PFNGLTEXCOORD2FVPROC glad_debug_glTexCoord2fv; #define glTexCoord2fv glad_debug_glTexCoord2fv GLAD_API_CALL PFNGLTEXCOORD2IPROC glad_glTexCoord2i; GLAD_API_CALL PFNGLTEXCOORD2IPROC glad_debug_glTexCoord2i; #define glTexCoord2i glad_debug_glTexCoord2i GLAD_API_CALL PFNGLTEXCOORD2IVPROC glad_glTexCoord2iv; GLAD_API_CALL PFNGLTEXCOORD2IVPROC glad_debug_glTexCoord2iv; #define glTexCoord2iv glad_debug_glTexCoord2iv GLAD_API_CALL PFNGLTEXCOORD2SPROC glad_glTexCoord2s; GLAD_API_CALL PFNGLTEXCOORD2SPROC glad_debug_glTexCoord2s; #define glTexCoord2s glad_debug_glTexCoord2s GLAD_API_CALL PFNGLTEXCOORD2SVPROC glad_glTexCoord2sv; GLAD_API_CALL PFNGLTEXCOORD2SVPROC glad_debug_glTexCoord2sv; #define glTexCoord2sv glad_debug_glTexCoord2sv GLAD_API_CALL PFNGLTEXCOORD3DPROC glad_glTexCoord3d; GLAD_API_CALL PFNGLTEXCOORD3DPROC glad_debug_glTexCoord3d; #define glTexCoord3d glad_debug_glTexCoord3d GLAD_API_CALL PFNGLTEXCOORD3DVPROC glad_glTexCoord3dv; GLAD_API_CALL PFNGLTEXCOORD3DVPROC glad_debug_glTexCoord3dv; #define glTexCoord3dv glad_debug_glTexCoord3dv GLAD_API_CALL PFNGLTEXCOORD3FPROC glad_glTexCoord3f; GLAD_API_CALL PFNGLTEXCOORD3FPROC glad_debug_glTexCoord3f; #define glTexCoord3f glad_debug_glTexCoord3f GLAD_API_CALL PFNGLTEXCOORD3FVPROC glad_glTexCoord3fv; GLAD_API_CALL PFNGLTEXCOORD3FVPROC glad_debug_glTexCoord3fv; #define glTexCoord3fv glad_debug_glTexCoord3fv GLAD_API_CALL PFNGLTEXCOORD3IPROC glad_glTexCoord3i; GLAD_API_CALL PFNGLTEXCOORD3IPROC glad_debug_glTexCoord3i; #define glTexCoord3i glad_debug_glTexCoord3i GLAD_API_CALL PFNGLTEXCOORD3IVPROC glad_glTexCoord3iv; GLAD_API_CALL PFNGLTEXCOORD3IVPROC glad_debug_glTexCoord3iv; #define glTexCoord3iv glad_debug_glTexCoord3iv GLAD_API_CALL PFNGLTEXCOORD3SPROC glad_glTexCoord3s; GLAD_API_CALL PFNGLTEXCOORD3SPROC glad_debug_glTexCoord3s; #define glTexCoord3s glad_debug_glTexCoord3s GLAD_API_CALL PFNGLTEXCOORD3SVPROC glad_glTexCoord3sv; GLAD_API_CALL PFNGLTEXCOORD3SVPROC glad_debug_glTexCoord3sv; #define glTexCoord3sv glad_debug_glTexCoord3sv GLAD_API_CALL PFNGLTEXCOORD4DPROC glad_glTexCoord4d; GLAD_API_CALL PFNGLTEXCOORD4DPROC glad_debug_glTexCoord4d; #define glTexCoord4d glad_debug_glTexCoord4d GLAD_API_CALL PFNGLTEXCOORD4DVPROC glad_glTexCoord4dv; GLAD_API_CALL PFNGLTEXCOORD4DVPROC glad_debug_glTexCoord4dv; #define glTexCoord4dv glad_debug_glTexCoord4dv GLAD_API_CALL PFNGLTEXCOORD4FPROC glad_glTexCoord4f; GLAD_API_CALL PFNGLTEXCOORD4FPROC glad_debug_glTexCoord4f; #define glTexCoord4f glad_debug_glTexCoord4f GLAD_API_CALL PFNGLTEXCOORD4FVPROC glad_glTexCoord4fv; GLAD_API_CALL PFNGLTEXCOORD4FVPROC glad_debug_glTexCoord4fv; #define glTexCoord4fv glad_debug_glTexCoord4fv GLAD_API_CALL PFNGLTEXCOORD4IPROC glad_glTexCoord4i; GLAD_API_CALL PFNGLTEXCOORD4IPROC glad_debug_glTexCoord4i; #define glTexCoord4i glad_debug_glTexCoord4i GLAD_API_CALL PFNGLTEXCOORD4IVPROC glad_glTexCoord4iv; GLAD_API_CALL PFNGLTEXCOORD4IVPROC glad_debug_glTexCoord4iv; #define glTexCoord4iv glad_debug_glTexCoord4iv GLAD_API_CALL PFNGLTEXCOORD4SPROC glad_glTexCoord4s; GLAD_API_CALL PFNGLTEXCOORD4SPROC glad_debug_glTexCoord4s; #define glTexCoord4s glad_debug_glTexCoord4s GLAD_API_CALL PFNGLTEXCOORD4SVPROC glad_glTexCoord4sv; GLAD_API_CALL PFNGLTEXCOORD4SVPROC glad_debug_glTexCoord4sv; #define glTexCoord4sv glad_debug_glTexCoord4sv GLAD_API_CALL PFNGLTEXCOORDPOINTERPROC glad_glTexCoordPointer; GLAD_API_CALL PFNGLTEXCOORDPOINTERPROC glad_debug_glTexCoordPointer; #define glTexCoordPointer glad_debug_glTexCoordPointer GLAD_API_CALL PFNGLTEXENVFPROC glad_glTexEnvf; GLAD_API_CALL PFNGLTEXENVFPROC glad_debug_glTexEnvf; #define glTexEnvf glad_debug_glTexEnvf GLAD_API_CALL PFNGLTEXENVFVPROC glad_glTexEnvfv; GLAD_API_CALL PFNGLTEXENVFVPROC glad_debug_glTexEnvfv; #define glTexEnvfv glad_debug_glTexEnvfv GLAD_API_CALL PFNGLTEXENVIPROC glad_glTexEnvi; GLAD_API_CALL PFNGLTEXENVIPROC glad_debug_glTexEnvi; #define glTexEnvi glad_debug_glTexEnvi GLAD_API_CALL PFNGLTEXENVIVPROC glad_glTexEnviv; GLAD_API_CALL PFNGLTEXENVIVPROC glad_debug_glTexEnviv; #define glTexEnviv glad_debug_glTexEnviv GLAD_API_CALL PFNGLTEXGENDPROC glad_glTexGend; GLAD_API_CALL PFNGLTEXGENDPROC glad_debug_glTexGend; #define glTexGend glad_debug_glTexGend GLAD_API_CALL PFNGLTEXGENDVPROC glad_glTexGendv; GLAD_API_CALL PFNGLTEXGENDVPROC glad_debug_glTexGendv; #define glTexGendv glad_debug_glTexGendv GLAD_API_CALL PFNGLTEXGENFPROC glad_glTexGenf; GLAD_API_CALL PFNGLTEXGENFPROC glad_debug_glTexGenf; #define glTexGenf glad_debug_glTexGenf GLAD_API_CALL PFNGLTEXGENFVPROC glad_glTexGenfv; GLAD_API_CALL PFNGLTEXGENFVPROC glad_debug_glTexGenfv; #define glTexGenfv glad_debug_glTexGenfv GLAD_API_CALL PFNGLTEXGENIPROC glad_glTexGeni; GLAD_API_CALL PFNGLTEXGENIPROC glad_debug_glTexGeni; #define glTexGeni glad_debug_glTexGeni GLAD_API_CALL PFNGLTEXGENIVPROC glad_glTexGeniv; GLAD_API_CALL PFNGLTEXGENIVPROC glad_debug_glTexGeniv; #define glTexGeniv glad_debug_glTexGeniv GLAD_API_CALL PFNGLTEXIMAGE1DPROC glad_glTexImage1D; GLAD_API_CALL PFNGLTEXIMAGE1DPROC glad_debug_glTexImage1D; #define glTexImage1D glad_debug_glTexImage1D GLAD_API_CALL PFNGLTEXIMAGE2DPROC glad_glTexImage2D; GLAD_API_CALL PFNGLTEXIMAGE2DPROC glad_debug_glTexImage2D; #define glTexImage2D glad_debug_glTexImage2D GLAD_API_CALL PFNGLTEXIMAGE3DPROC glad_glTexImage3D; GLAD_API_CALL PFNGLTEXIMAGE3DPROC glad_debug_glTexImage3D; #define glTexImage3D glad_debug_glTexImage3D GLAD_API_CALL PFNGLTEXPARAMETERIIVPROC glad_glTexParameterIiv; GLAD_API_CALL PFNGLTEXPARAMETERIIVPROC glad_debug_glTexParameterIiv; #define glTexParameterIiv glad_debug_glTexParameterIiv GLAD_API_CALL PFNGLTEXPARAMETERIUIVPROC glad_glTexParameterIuiv; GLAD_API_CALL PFNGLTEXPARAMETERIUIVPROC glad_debug_glTexParameterIuiv; #define glTexParameterIuiv glad_debug_glTexParameterIuiv GLAD_API_CALL PFNGLTEXPARAMETERFPROC glad_glTexParameterf; GLAD_API_CALL PFNGLTEXPARAMETERFPROC glad_debug_glTexParameterf; #define glTexParameterf glad_debug_glTexParameterf GLAD_API_CALL PFNGLTEXPARAMETERFVPROC glad_glTexParameterfv; GLAD_API_CALL PFNGLTEXPARAMETERFVPROC glad_debug_glTexParameterfv; #define glTexParameterfv glad_debug_glTexParameterfv GLAD_API_CALL PFNGLTEXPARAMETERIPROC glad_glTexParameteri; GLAD_API_CALL PFNGLTEXPARAMETERIPROC glad_debug_glTexParameteri; #define glTexParameteri glad_debug_glTexParameteri GLAD_API_CALL PFNGLTEXPARAMETERIVPROC glad_glTexParameteriv; GLAD_API_CALL PFNGLTEXPARAMETERIVPROC glad_debug_glTexParameteriv; #define glTexParameteriv glad_debug_glTexParameteriv GLAD_API_CALL PFNGLTEXSTORAGE1DPROC glad_glTexStorage1D; GLAD_API_CALL PFNGLTEXSTORAGE1DPROC glad_debug_glTexStorage1D; #define glTexStorage1D glad_debug_glTexStorage1D GLAD_API_CALL PFNGLTEXSTORAGE2DPROC glad_glTexStorage2D; GLAD_API_CALL PFNGLTEXSTORAGE2DPROC glad_debug_glTexStorage2D; #define glTexStorage2D glad_debug_glTexStorage2D GLAD_API_CALL PFNGLTEXSTORAGE3DPROC glad_glTexStorage3D; GLAD_API_CALL PFNGLTEXSTORAGE3DPROC glad_debug_glTexStorage3D; #define glTexStorage3D glad_debug_glTexStorage3D GLAD_API_CALL PFNGLTEXSUBIMAGE1DPROC glad_glTexSubImage1D; GLAD_API_CALL PFNGLTEXSUBIMAGE1DPROC glad_debug_glTexSubImage1D; #define glTexSubImage1D glad_debug_glTexSubImage1D GLAD_API_CALL PFNGLTEXSUBIMAGE2DPROC glad_glTexSubImage2D; GLAD_API_CALL PFNGLTEXSUBIMAGE2DPROC glad_debug_glTexSubImage2D; #define glTexSubImage2D glad_debug_glTexSubImage2D GLAD_API_CALL PFNGLTEXSUBIMAGE3DPROC glad_glTexSubImage3D; GLAD_API_CALL PFNGLTEXSUBIMAGE3DPROC glad_debug_glTexSubImage3D; #define glTexSubImage3D glad_debug_glTexSubImage3D GLAD_API_CALL PFNGLTRANSFORMFEEDBACKVARYINGSPROC glad_glTransformFeedbackVaryings; GLAD_API_CALL PFNGLTRANSFORMFEEDBACKVARYINGSPROC glad_debug_glTransformFeedbackVaryings; #define glTransformFeedbackVaryings glad_debug_glTransformFeedbackVaryings GLAD_API_CALL PFNGLTRANSLATEDPROC glad_glTranslated; GLAD_API_CALL PFNGLTRANSLATEDPROC glad_debug_glTranslated; #define glTranslated glad_debug_glTranslated GLAD_API_CALL PFNGLTRANSLATEFPROC glad_glTranslatef; GLAD_API_CALL PFNGLTRANSLATEFPROC glad_debug_glTranslatef; #define glTranslatef glad_debug_glTranslatef GLAD_API_CALL PFNGLUNIFORM1FPROC glad_glUniform1f; GLAD_API_CALL PFNGLUNIFORM1FPROC glad_debug_glUniform1f; #define glUniform1f glad_debug_glUniform1f GLAD_API_CALL PFNGLUNIFORM1FVPROC glad_glUniform1fv; GLAD_API_CALL PFNGLUNIFORM1FVPROC glad_debug_glUniform1fv; #define glUniform1fv glad_debug_glUniform1fv GLAD_API_CALL PFNGLUNIFORM1IPROC glad_glUniform1i; GLAD_API_CALL PFNGLUNIFORM1IPROC glad_debug_glUniform1i; #define glUniform1i glad_debug_glUniform1i GLAD_API_CALL PFNGLUNIFORM1IVPROC glad_glUniform1iv; GLAD_API_CALL PFNGLUNIFORM1IVPROC glad_debug_glUniform1iv; #define glUniform1iv glad_debug_glUniform1iv GLAD_API_CALL PFNGLUNIFORM1UIPROC glad_glUniform1ui; GLAD_API_CALL PFNGLUNIFORM1UIPROC glad_debug_glUniform1ui; #define glUniform1ui glad_debug_glUniform1ui GLAD_API_CALL PFNGLUNIFORM1UIVPROC glad_glUniform1uiv; GLAD_API_CALL PFNGLUNIFORM1UIVPROC glad_debug_glUniform1uiv; #define glUniform1uiv glad_debug_glUniform1uiv GLAD_API_CALL PFNGLUNIFORM2FPROC glad_glUniform2f; GLAD_API_CALL PFNGLUNIFORM2FPROC glad_debug_glUniform2f; #define glUniform2f glad_debug_glUniform2f GLAD_API_CALL PFNGLUNIFORM2FVPROC glad_glUniform2fv; GLAD_API_CALL PFNGLUNIFORM2FVPROC glad_debug_glUniform2fv; #define glUniform2fv glad_debug_glUniform2fv GLAD_API_CALL PFNGLUNIFORM2IPROC glad_glUniform2i; GLAD_API_CALL PFNGLUNIFORM2IPROC glad_debug_glUniform2i; #define glUniform2i glad_debug_glUniform2i GLAD_API_CALL PFNGLUNIFORM2IVPROC glad_glUniform2iv; GLAD_API_CALL PFNGLUNIFORM2IVPROC glad_debug_glUniform2iv; #define glUniform2iv glad_debug_glUniform2iv GLAD_API_CALL PFNGLUNIFORM2UIPROC glad_glUniform2ui; GLAD_API_CALL PFNGLUNIFORM2UIPROC glad_debug_glUniform2ui; #define glUniform2ui glad_debug_glUniform2ui GLAD_API_CALL PFNGLUNIFORM2UIVPROC glad_glUniform2uiv; GLAD_API_CALL PFNGLUNIFORM2UIVPROC glad_debug_glUniform2uiv; #define glUniform2uiv glad_debug_glUniform2uiv GLAD_API_CALL PFNGLUNIFORM3FPROC glad_glUniform3f; GLAD_API_CALL PFNGLUNIFORM3FPROC glad_debug_glUniform3f; #define glUniform3f glad_debug_glUniform3f GLAD_API_CALL PFNGLUNIFORM3FVPROC glad_glUniform3fv; GLAD_API_CALL PFNGLUNIFORM3FVPROC glad_debug_glUniform3fv; #define glUniform3fv glad_debug_glUniform3fv GLAD_API_CALL PFNGLUNIFORM3IPROC glad_glUniform3i; GLAD_API_CALL PFNGLUNIFORM3IPROC glad_debug_glUniform3i; #define glUniform3i glad_debug_glUniform3i GLAD_API_CALL PFNGLUNIFORM3IVPROC glad_glUniform3iv; GLAD_API_CALL PFNGLUNIFORM3IVPROC glad_debug_glUniform3iv; #define glUniform3iv glad_debug_glUniform3iv GLAD_API_CALL PFNGLUNIFORM3UIPROC glad_glUniform3ui; GLAD_API_CALL PFNGLUNIFORM3UIPROC glad_debug_glUniform3ui; #define glUniform3ui glad_debug_glUniform3ui GLAD_API_CALL PFNGLUNIFORM3UIVPROC glad_glUniform3uiv; GLAD_API_CALL PFNGLUNIFORM3UIVPROC glad_debug_glUniform3uiv; #define glUniform3uiv glad_debug_glUniform3uiv GLAD_API_CALL PFNGLUNIFORM4FPROC glad_glUniform4f; GLAD_API_CALL PFNGLUNIFORM4FPROC glad_debug_glUniform4f; #define glUniform4f glad_debug_glUniform4f GLAD_API_CALL PFNGLUNIFORM4FVPROC glad_glUniform4fv; GLAD_API_CALL PFNGLUNIFORM4FVPROC glad_debug_glUniform4fv; #define glUniform4fv glad_debug_glUniform4fv GLAD_API_CALL PFNGLUNIFORM4IPROC glad_glUniform4i; GLAD_API_CALL PFNGLUNIFORM4IPROC glad_debug_glUniform4i; #define glUniform4i glad_debug_glUniform4i GLAD_API_CALL PFNGLUNIFORM4IVPROC glad_glUniform4iv; GLAD_API_CALL PFNGLUNIFORM4IVPROC glad_debug_glUniform4iv; #define glUniform4iv glad_debug_glUniform4iv GLAD_API_CALL PFNGLUNIFORM4UIPROC glad_glUniform4ui; GLAD_API_CALL PFNGLUNIFORM4UIPROC glad_debug_glUniform4ui; #define glUniform4ui glad_debug_glUniform4ui GLAD_API_CALL PFNGLUNIFORM4UIVPROC glad_glUniform4uiv; GLAD_API_CALL PFNGLUNIFORM4UIVPROC glad_debug_glUniform4uiv; #define glUniform4uiv glad_debug_glUniform4uiv GLAD_API_CALL PFNGLUNIFORMBLOCKBINDINGPROC glad_glUniformBlockBinding; GLAD_API_CALL PFNGLUNIFORMBLOCKBINDINGPROC glad_debug_glUniformBlockBinding; #define glUniformBlockBinding glad_debug_glUniformBlockBinding GLAD_API_CALL PFNGLUNIFORMMATRIX2FVPROC glad_glUniformMatrix2fv; GLAD_API_CALL PFNGLUNIFORMMATRIX2FVPROC glad_debug_glUniformMatrix2fv; #define glUniformMatrix2fv glad_debug_glUniformMatrix2fv GLAD_API_CALL PFNGLUNIFORMMATRIX2X3FVPROC glad_glUniformMatrix2x3fv; GLAD_API_CALL PFNGLUNIFORMMATRIX2X3FVPROC glad_debug_glUniformMatrix2x3fv; #define glUniformMatrix2x3fv glad_debug_glUniformMatrix2x3fv GLAD_API_CALL PFNGLUNIFORMMATRIX2X4FVPROC glad_glUniformMatrix2x4fv; GLAD_API_CALL PFNGLUNIFORMMATRIX2X4FVPROC glad_debug_glUniformMatrix2x4fv; #define glUniformMatrix2x4fv glad_debug_glUniformMatrix2x4fv GLAD_API_CALL PFNGLUNIFORMMATRIX3FVPROC glad_glUniformMatrix3fv; GLAD_API_CALL PFNGLUNIFORMMATRIX3FVPROC glad_debug_glUniformMatrix3fv; #define glUniformMatrix3fv glad_debug_glUniformMatrix3fv GLAD_API_CALL PFNGLUNIFORMMATRIX3X2FVPROC glad_glUniformMatrix3x2fv; GLAD_API_CALL PFNGLUNIFORMMATRIX3X2FVPROC glad_debug_glUniformMatrix3x2fv; #define glUniformMatrix3x2fv glad_debug_glUniformMatrix3x2fv GLAD_API_CALL PFNGLUNIFORMMATRIX3X4FVPROC glad_glUniformMatrix3x4fv; GLAD_API_CALL PFNGLUNIFORMMATRIX3X4FVPROC glad_debug_glUniformMatrix3x4fv; #define glUniformMatrix3x4fv glad_debug_glUniformMatrix3x4fv GLAD_API_CALL PFNGLUNIFORMMATRIX4FVPROC glad_glUniformMatrix4fv; GLAD_API_CALL PFNGLUNIFORMMATRIX4FVPROC glad_debug_glUniformMatrix4fv; #define glUniformMatrix4fv glad_debug_glUniformMatrix4fv GLAD_API_CALL PFNGLUNIFORMMATRIX4X2FVPROC glad_glUniformMatrix4x2fv; GLAD_API_CALL PFNGLUNIFORMMATRIX4X2FVPROC glad_debug_glUniformMatrix4x2fv; #define glUniformMatrix4x2fv glad_debug_glUniformMatrix4x2fv GLAD_API_CALL PFNGLUNIFORMMATRIX4X3FVPROC glad_glUniformMatrix4x3fv; GLAD_API_CALL PFNGLUNIFORMMATRIX4X3FVPROC glad_debug_glUniformMatrix4x3fv; #define glUniformMatrix4x3fv glad_debug_glUniformMatrix4x3fv GLAD_API_CALL PFNGLUNMAPBUFFERPROC glad_glUnmapBuffer; GLAD_API_CALL PFNGLUNMAPBUFFERPROC glad_debug_glUnmapBuffer; #define glUnmapBuffer glad_debug_glUnmapBuffer GLAD_API_CALL PFNGLUSEPROGRAMPROC glad_glUseProgram; GLAD_API_CALL PFNGLUSEPROGRAMPROC glad_debug_glUseProgram; #define glUseProgram glad_debug_glUseProgram GLAD_API_CALL PFNGLVALIDATEPROGRAMPROC glad_glValidateProgram; GLAD_API_CALL PFNGLVALIDATEPROGRAMPROC glad_debug_glValidateProgram; #define glValidateProgram glad_debug_glValidateProgram GLAD_API_CALL PFNGLVERTEX2DPROC glad_glVertex2d; GLAD_API_CALL PFNGLVERTEX2DPROC glad_debug_glVertex2d; #define glVertex2d glad_debug_glVertex2d GLAD_API_CALL PFNGLVERTEX2DVPROC glad_glVertex2dv; GLAD_API_CALL PFNGLVERTEX2DVPROC glad_debug_glVertex2dv; #define glVertex2dv glad_debug_glVertex2dv GLAD_API_CALL PFNGLVERTEX2FPROC glad_glVertex2f; GLAD_API_CALL PFNGLVERTEX2FPROC glad_debug_glVertex2f; #define glVertex2f glad_debug_glVertex2f GLAD_API_CALL PFNGLVERTEX2FVPROC glad_glVertex2fv; GLAD_API_CALL PFNGLVERTEX2FVPROC glad_debug_glVertex2fv; #define glVertex2fv glad_debug_glVertex2fv GLAD_API_CALL PFNGLVERTEX2IPROC glad_glVertex2i; GLAD_API_CALL PFNGLVERTEX2IPROC glad_debug_glVertex2i; #define glVertex2i glad_debug_glVertex2i GLAD_API_CALL PFNGLVERTEX2IVPROC glad_glVertex2iv; GLAD_API_CALL PFNGLVERTEX2IVPROC glad_debug_glVertex2iv; #define glVertex2iv glad_debug_glVertex2iv GLAD_API_CALL PFNGLVERTEX2SPROC glad_glVertex2s; GLAD_API_CALL PFNGLVERTEX2SPROC glad_debug_glVertex2s; #define glVertex2s glad_debug_glVertex2s GLAD_API_CALL PFNGLVERTEX2SVPROC glad_glVertex2sv; GLAD_API_CALL PFNGLVERTEX2SVPROC glad_debug_glVertex2sv; #define glVertex2sv glad_debug_glVertex2sv GLAD_API_CALL PFNGLVERTEX3DPROC glad_glVertex3d; GLAD_API_CALL PFNGLVERTEX3DPROC glad_debug_glVertex3d; #define glVertex3d glad_debug_glVertex3d GLAD_API_CALL PFNGLVERTEX3DVPROC glad_glVertex3dv; GLAD_API_CALL PFNGLVERTEX3DVPROC glad_debug_glVertex3dv; #define glVertex3dv glad_debug_glVertex3dv GLAD_API_CALL PFNGLVERTEX3FPROC glad_glVertex3f; GLAD_API_CALL PFNGLVERTEX3FPROC glad_debug_glVertex3f; #define glVertex3f glad_debug_glVertex3f GLAD_API_CALL PFNGLVERTEX3FVPROC glad_glVertex3fv; GLAD_API_CALL PFNGLVERTEX3FVPROC glad_debug_glVertex3fv; #define glVertex3fv glad_debug_glVertex3fv GLAD_API_CALL PFNGLVERTEX3IPROC glad_glVertex3i; GLAD_API_CALL PFNGLVERTEX3IPROC glad_debug_glVertex3i; #define glVertex3i glad_debug_glVertex3i GLAD_API_CALL PFNGLVERTEX3IVPROC glad_glVertex3iv; GLAD_API_CALL PFNGLVERTEX3IVPROC glad_debug_glVertex3iv; #define glVertex3iv glad_debug_glVertex3iv GLAD_API_CALL PFNGLVERTEX3SPROC glad_glVertex3s; GLAD_API_CALL PFNGLVERTEX3SPROC glad_debug_glVertex3s; #define glVertex3s glad_debug_glVertex3s GLAD_API_CALL PFNGLVERTEX3SVPROC glad_glVertex3sv; GLAD_API_CALL PFNGLVERTEX3SVPROC glad_debug_glVertex3sv; #define glVertex3sv glad_debug_glVertex3sv GLAD_API_CALL PFNGLVERTEX4DPROC glad_glVertex4d; GLAD_API_CALL PFNGLVERTEX4DPROC glad_debug_glVertex4d; #define glVertex4d glad_debug_glVertex4d GLAD_API_CALL PFNGLVERTEX4DVPROC glad_glVertex4dv; GLAD_API_CALL PFNGLVERTEX4DVPROC glad_debug_glVertex4dv; #define glVertex4dv glad_debug_glVertex4dv GLAD_API_CALL PFNGLVERTEX4FPROC glad_glVertex4f; GLAD_API_CALL PFNGLVERTEX4FPROC glad_debug_glVertex4f; #define glVertex4f glad_debug_glVertex4f GLAD_API_CALL PFNGLVERTEX4FVPROC glad_glVertex4fv; GLAD_API_CALL PFNGLVERTEX4FVPROC glad_debug_glVertex4fv; #define glVertex4fv glad_debug_glVertex4fv GLAD_API_CALL PFNGLVERTEX4IPROC glad_glVertex4i; GLAD_API_CALL PFNGLVERTEX4IPROC glad_debug_glVertex4i; #define glVertex4i glad_debug_glVertex4i GLAD_API_CALL PFNGLVERTEX4IVPROC glad_glVertex4iv; GLAD_API_CALL PFNGLVERTEX4IVPROC glad_debug_glVertex4iv; #define glVertex4iv glad_debug_glVertex4iv GLAD_API_CALL PFNGLVERTEX4SPROC glad_glVertex4s; GLAD_API_CALL PFNGLVERTEX4SPROC glad_debug_glVertex4s; #define glVertex4s glad_debug_glVertex4s GLAD_API_CALL PFNGLVERTEX4SVPROC glad_glVertex4sv; GLAD_API_CALL PFNGLVERTEX4SVPROC glad_debug_glVertex4sv; #define glVertex4sv glad_debug_glVertex4sv GLAD_API_CALL PFNGLVERTEXATTRIB1DPROC glad_glVertexAttrib1d; GLAD_API_CALL PFNGLVERTEXATTRIB1DPROC glad_debug_glVertexAttrib1d; #define glVertexAttrib1d glad_debug_glVertexAttrib1d GLAD_API_CALL PFNGLVERTEXATTRIB1DVPROC glad_glVertexAttrib1dv; GLAD_API_CALL PFNGLVERTEXATTRIB1DVPROC glad_debug_glVertexAttrib1dv; #define glVertexAttrib1dv glad_debug_glVertexAttrib1dv GLAD_API_CALL PFNGLVERTEXATTRIB1FPROC glad_glVertexAttrib1f; GLAD_API_CALL PFNGLVERTEXATTRIB1FPROC glad_debug_glVertexAttrib1f; #define glVertexAttrib1f glad_debug_glVertexAttrib1f GLAD_API_CALL PFNGLVERTEXATTRIB1FVPROC glad_glVertexAttrib1fv; GLAD_API_CALL PFNGLVERTEXATTRIB1FVPROC glad_debug_glVertexAttrib1fv; #define glVertexAttrib1fv glad_debug_glVertexAttrib1fv GLAD_API_CALL PFNGLVERTEXATTRIB1SPROC glad_glVertexAttrib1s; GLAD_API_CALL PFNGLVERTEXATTRIB1SPROC glad_debug_glVertexAttrib1s; #define glVertexAttrib1s glad_debug_glVertexAttrib1s GLAD_API_CALL PFNGLVERTEXATTRIB1SVPROC glad_glVertexAttrib1sv; GLAD_API_CALL PFNGLVERTEXATTRIB1SVPROC glad_debug_glVertexAttrib1sv; #define glVertexAttrib1sv glad_debug_glVertexAttrib1sv GLAD_API_CALL PFNGLVERTEXATTRIB2DPROC glad_glVertexAttrib2d; GLAD_API_CALL PFNGLVERTEXATTRIB2DPROC glad_debug_glVertexAttrib2d; #define glVertexAttrib2d glad_debug_glVertexAttrib2d GLAD_API_CALL PFNGLVERTEXATTRIB2DVPROC glad_glVertexAttrib2dv; GLAD_API_CALL PFNGLVERTEXATTRIB2DVPROC glad_debug_glVertexAttrib2dv; #define glVertexAttrib2dv glad_debug_glVertexAttrib2dv GLAD_API_CALL PFNGLVERTEXATTRIB2FPROC glad_glVertexAttrib2f; GLAD_API_CALL PFNGLVERTEXATTRIB2FPROC glad_debug_glVertexAttrib2f; #define glVertexAttrib2f glad_debug_glVertexAttrib2f GLAD_API_CALL PFNGLVERTEXATTRIB2FVPROC glad_glVertexAttrib2fv; GLAD_API_CALL PFNGLVERTEXATTRIB2FVPROC glad_debug_glVertexAttrib2fv; #define glVertexAttrib2fv glad_debug_glVertexAttrib2fv GLAD_API_CALL PFNGLVERTEXATTRIB2SPROC glad_glVertexAttrib2s; GLAD_API_CALL PFNGLVERTEXATTRIB2SPROC glad_debug_glVertexAttrib2s; #define glVertexAttrib2s glad_debug_glVertexAttrib2s GLAD_API_CALL PFNGLVERTEXATTRIB2SVPROC glad_glVertexAttrib2sv; GLAD_API_CALL PFNGLVERTEXATTRIB2SVPROC glad_debug_glVertexAttrib2sv; #define glVertexAttrib2sv glad_debug_glVertexAttrib2sv GLAD_API_CALL PFNGLVERTEXATTRIB3DPROC glad_glVertexAttrib3d; GLAD_API_CALL PFNGLVERTEXATTRIB3DPROC glad_debug_glVertexAttrib3d; #define glVertexAttrib3d glad_debug_glVertexAttrib3d GLAD_API_CALL PFNGLVERTEXATTRIB3DVPROC glad_glVertexAttrib3dv; GLAD_API_CALL PFNGLVERTEXATTRIB3DVPROC glad_debug_glVertexAttrib3dv; #define glVertexAttrib3dv glad_debug_glVertexAttrib3dv GLAD_API_CALL PFNGLVERTEXATTRIB3FPROC glad_glVertexAttrib3f; GLAD_API_CALL PFNGLVERTEXATTRIB3FPROC glad_debug_glVertexAttrib3f; #define glVertexAttrib3f glad_debug_glVertexAttrib3f GLAD_API_CALL PFNGLVERTEXATTRIB3FVPROC glad_glVertexAttrib3fv; GLAD_API_CALL PFNGLVERTEXATTRIB3FVPROC glad_debug_glVertexAttrib3fv; #define glVertexAttrib3fv glad_debug_glVertexAttrib3fv GLAD_API_CALL PFNGLVERTEXATTRIB3SPROC glad_glVertexAttrib3s; GLAD_API_CALL PFNGLVERTEXATTRIB3SPROC glad_debug_glVertexAttrib3s; #define glVertexAttrib3s glad_debug_glVertexAttrib3s GLAD_API_CALL PFNGLVERTEXATTRIB3SVPROC glad_glVertexAttrib3sv; GLAD_API_CALL PFNGLVERTEXATTRIB3SVPROC glad_debug_glVertexAttrib3sv; #define glVertexAttrib3sv glad_debug_glVertexAttrib3sv GLAD_API_CALL PFNGLVERTEXATTRIB4NBVPROC glad_glVertexAttrib4Nbv; GLAD_API_CALL PFNGLVERTEXATTRIB4NBVPROC glad_debug_glVertexAttrib4Nbv; #define glVertexAttrib4Nbv glad_debug_glVertexAttrib4Nbv GLAD_API_CALL PFNGLVERTEXATTRIB4NIVPROC glad_glVertexAttrib4Niv; GLAD_API_CALL PFNGLVERTEXATTRIB4NIVPROC glad_debug_glVertexAttrib4Niv; #define glVertexAttrib4Niv glad_debug_glVertexAttrib4Niv GLAD_API_CALL PFNGLVERTEXATTRIB4NSVPROC glad_glVertexAttrib4Nsv; GLAD_API_CALL PFNGLVERTEXATTRIB4NSVPROC glad_debug_glVertexAttrib4Nsv; #define glVertexAttrib4Nsv glad_debug_glVertexAttrib4Nsv GLAD_API_CALL PFNGLVERTEXATTRIB4NUBPROC glad_glVertexAttrib4Nub; GLAD_API_CALL PFNGLVERTEXATTRIB4NUBPROC glad_debug_glVertexAttrib4Nub; #define glVertexAttrib4Nub glad_debug_glVertexAttrib4Nub GLAD_API_CALL PFNGLVERTEXATTRIB4NUBVPROC glad_glVertexAttrib4Nubv; GLAD_API_CALL PFNGLVERTEXATTRIB4NUBVPROC glad_debug_glVertexAttrib4Nubv; #define glVertexAttrib4Nubv glad_debug_glVertexAttrib4Nubv GLAD_API_CALL PFNGLVERTEXATTRIB4NUIVPROC glad_glVertexAttrib4Nuiv; GLAD_API_CALL PFNGLVERTEXATTRIB4NUIVPROC glad_debug_glVertexAttrib4Nuiv; #define glVertexAttrib4Nuiv glad_debug_glVertexAttrib4Nuiv GLAD_API_CALL PFNGLVERTEXATTRIB4NUSVPROC glad_glVertexAttrib4Nusv; GLAD_API_CALL PFNGLVERTEXATTRIB4NUSVPROC glad_debug_glVertexAttrib4Nusv; #define glVertexAttrib4Nusv glad_debug_glVertexAttrib4Nusv GLAD_API_CALL PFNGLVERTEXATTRIB4BVPROC glad_glVertexAttrib4bv; GLAD_API_CALL PFNGLVERTEXATTRIB4BVPROC glad_debug_glVertexAttrib4bv; #define glVertexAttrib4bv glad_debug_glVertexAttrib4bv GLAD_API_CALL PFNGLVERTEXATTRIB4DPROC glad_glVertexAttrib4d; GLAD_API_CALL PFNGLVERTEXATTRIB4DPROC glad_debug_glVertexAttrib4d; #define glVertexAttrib4d glad_debug_glVertexAttrib4d GLAD_API_CALL PFNGLVERTEXATTRIB4DVPROC glad_glVertexAttrib4dv; GLAD_API_CALL PFNGLVERTEXATTRIB4DVPROC glad_debug_glVertexAttrib4dv; #define glVertexAttrib4dv glad_debug_glVertexAttrib4dv GLAD_API_CALL PFNGLVERTEXATTRIB4FPROC glad_glVertexAttrib4f; GLAD_API_CALL PFNGLVERTEXATTRIB4FPROC glad_debug_glVertexAttrib4f; #define glVertexAttrib4f glad_debug_glVertexAttrib4f GLAD_API_CALL PFNGLVERTEXATTRIB4FVPROC glad_glVertexAttrib4fv; GLAD_API_CALL PFNGLVERTEXATTRIB4FVPROC glad_debug_glVertexAttrib4fv; #define glVertexAttrib4fv glad_debug_glVertexAttrib4fv GLAD_API_CALL PFNGLVERTEXATTRIB4IVPROC glad_glVertexAttrib4iv; GLAD_API_CALL PFNGLVERTEXATTRIB4IVPROC glad_debug_glVertexAttrib4iv; #define glVertexAttrib4iv glad_debug_glVertexAttrib4iv GLAD_API_CALL PFNGLVERTEXATTRIB4SPROC glad_glVertexAttrib4s; GLAD_API_CALL PFNGLVERTEXATTRIB4SPROC glad_debug_glVertexAttrib4s; #define glVertexAttrib4s glad_debug_glVertexAttrib4s GLAD_API_CALL PFNGLVERTEXATTRIB4SVPROC glad_glVertexAttrib4sv; GLAD_API_CALL PFNGLVERTEXATTRIB4SVPROC glad_debug_glVertexAttrib4sv; #define glVertexAttrib4sv glad_debug_glVertexAttrib4sv GLAD_API_CALL PFNGLVERTEXATTRIB4UBVPROC glad_glVertexAttrib4ubv; GLAD_API_CALL PFNGLVERTEXATTRIB4UBVPROC glad_debug_glVertexAttrib4ubv; #define glVertexAttrib4ubv glad_debug_glVertexAttrib4ubv GLAD_API_CALL PFNGLVERTEXATTRIB4UIVPROC glad_glVertexAttrib4uiv; GLAD_API_CALL PFNGLVERTEXATTRIB4UIVPROC glad_debug_glVertexAttrib4uiv; #define glVertexAttrib4uiv glad_debug_glVertexAttrib4uiv GLAD_API_CALL PFNGLVERTEXATTRIB4USVPROC glad_glVertexAttrib4usv; GLAD_API_CALL PFNGLVERTEXATTRIB4USVPROC glad_debug_glVertexAttrib4usv; #define glVertexAttrib4usv glad_debug_glVertexAttrib4usv GLAD_API_CALL PFNGLVERTEXATTRIBDIVISORARBPROC glad_glVertexAttribDivisorARB; GLAD_API_CALL PFNGLVERTEXATTRIBDIVISORARBPROC glad_debug_glVertexAttribDivisorARB; #define glVertexAttribDivisorARB glad_debug_glVertexAttribDivisorARB GLAD_API_CALL PFNGLVERTEXATTRIBI1IPROC glad_glVertexAttribI1i; GLAD_API_CALL PFNGLVERTEXATTRIBI1IPROC glad_debug_glVertexAttribI1i; #define glVertexAttribI1i glad_debug_glVertexAttribI1i GLAD_API_CALL PFNGLVERTEXATTRIBI1IVPROC glad_glVertexAttribI1iv; GLAD_API_CALL PFNGLVERTEXATTRIBI1IVPROC glad_debug_glVertexAttribI1iv; #define glVertexAttribI1iv glad_debug_glVertexAttribI1iv GLAD_API_CALL PFNGLVERTEXATTRIBI1UIPROC glad_glVertexAttribI1ui; GLAD_API_CALL PFNGLVERTEXATTRIBI1UIPROC glad_debug_glVertexAttribI1ui; #define glVertexAttribI1ui glad_debug_glVertexAttribI1ui GLAD_API_CALL PFNGLVERTEXATTRIBI1UIVPROC glad_glVertexAttribI1uiv; GLAD_API_CALL PFNGLVERTEXATTRIBI1UIVPROC glad_debug_glVertexAttribI1uiv; #define glVertexAttribI1uiv glad_debug_glVertexAttribI1uiv GLAD_API_CALL PFNGLVERTEXATTRIBI2IPROC glad_glVertexAttribI2i; GLAD_API_CALL PFNGLVERTEXATTRIBI2IPROC glad_debug_glVertexAttribI2i; #define glVertexAttribI2i glad_debug_glVertexAttribI2i GLAD_API_CALL PFNGLVERTEXATTRIBI2IVPROC glad_glVertexAttribI2iv; GLAD_API_CALL PFNGLVERTEXATTRIBI2IVPROC glad_debug_glVertexAttribI2iv; #define glVertexAttribI2iv glad_debug_glVertexAttribI2iv GLAD_API_CALL PFNGLVERTEXATTRIBI2UIPROC glad_glVertexAttribI2ui; GLAD_API_CALL PFNGLVERTEXATTRIBI2UIPROC glad_debug_glVertexAttribI2ui; #define glVertexAttribI2ui glad_debug_glVertexAttribI2ui GLAD_API_CALL PFNGLVERTEXATTRIBI2UIVPROC glad_glVertexAttribI2uiv; GLAD_API_CALL PFNGLVERTEXATTRIBI2UIVPROC glad_debug_glVertexAttribI2uiv; #define glVertexAttribI2uiv glad_debug_glVertexAttribI2uiv GLAD_API_CALL PFNGLVERTEXATTRIBI3IPROC glad_glVertexAttribI3i; GLAD_API_CALL PFNGLVERTEXATTRIBI3IPROC glad_debug_glVertexAttribI3i; #define glVertexAttribI3i glad_debug_glVertexAttribI3i GLAD_API_CALL PFNGLVERTEXATTRIBI3IVPROC glad_glVertexAttribI3iv; GLAD_API_CALL PFNGLVERTEXATTRIBI3IVPROC glad_debug_glVertexAttribI3iv; #define glVertexAttribI3iv glad_debug_glVertexAttribI3iv GLAD_API_CALL PFNGLVERTEXATTRIBI3UIPROC glad_glVertexAttribI3ui; GLAD_API_CALL PFNGLVERTEXATTRIBI3UIPROC glad_debug_glVertexAttribI3ui; #define glVertexAttribI3ui glad_debug_glVertexAttribI3ui GLAD_API_CALL PFNGLVERTEXATTRIBI3UIVPROC glad_glVertexAttribI3uiv; GLAD_API_CALL PFNGLVERTEXATTRIBI3UIVPROC glad_debug_glVertexAttribI3uiv; #define glVertexAttribI3uiv glad_debug_glVertexAttribI3uiv GLAD_API_CALL PFNGLVERTEXATTRIBI4BVPROC glad_glVertexAttribI4bv; GLAD_API_CALL PFNGLVERTEXATTRIBI4BVPROC glad_debug_glVertexAttribI4bv; #define glVertexAttribI4bv glad_debug_glVertexAttribI4bv GLAD_API_CALL PFNGLVERTEXATTRIBI4IPROC glad_glVertexAttribI4i; GLAD_API_CALL PFNGLVERTEXATTRIBI4IPROC glad_debug_glVertexAttribI4i; #define glVertexAttribI4i glad_debug_glVertexAttribI4i GLAD_API_CALL PFNGLVERTEXATTRIBI4IVPROC glad_glVertexAttribI4iv; GLAD_API_CALL PFNGLVERTEXATTRIBI4IVPROC glad_debug_glVertexAttribI4iv; #define glVertexAttribI4iv glad_debug_glVertexAttribI4iv GLAD_API_CALL PFNGLVERTEXATTRIBI4SVPROC glad_glVertexAttribI4sv; GLAD_API_CALL PFNGLVERTEXATTRIBI4SVPROC glad_debug_glVertexAttribI4sv; #define glVertexAttribI4sv glad_debug_glVertexAttribI4sv GLAD_API_CALL PFNGLVERTEXATTRIBI4UBVPROC glad_glVertexAttribI4ubv; GLAD_API_CALL PFNGLVERTEXATTRIBI4UBVPROC glad_debug_glVertexAttribI4ubv; #define glVertexAttribI4ubv glad_debug_glVertexAttribI4ubv GLAD_API_CALL PFNGLVERTEXATTRIBI4UIPROC glad_glVertexAttribI4ui; GLAD_API_CALL PFNGLVERTEXATTRIBI4UIPROC glad_debug_glVertexAttribI4ui; #define glVertexAttribI4ui glad_debug_glVertexAttribI4ui GLAD_API_CALL PFNGLVERTEXATTRIBI4UIVPROC glad_glVertexAttribI4uiv; GLAD_API_CALL PFNGLVERTEXATTRIBI4UIVPROC glad_debug_glVertexAttribI4uiv; #define glVertexAttribI4uiv glad_debug_glVertexAttribI4uiv GLAD_API_CALL PFNGLVERTEXATTRIBI4USVPROC glad_glVertexAttribI4usv; GLAD_API_CALL PFNGLVERTEXATTRIBI4USVPROC glad_debug_glVertexAttribI4usv; #define glVertexAttribI4usv glad_debug_glVertexAttribI4usv GLAD_API_CALL PFNGLVERTEXATTRIBIPOINTERPROC glad_glVertexAttribIPointer; GLAD_API_CALL PFNGLVERTEXATTRIBIPOINTERPROC glad_debug_glVertexAttribIPointer; #define glVertexAttribIPointer glad_debug_glVertexAttribIPointer GLAD_API_CALL PFNGLVERTEXATTRIBPOINTERPROC glad_glVertexAttribPointer; GLAD_API_CALL PFNGLVERTEXATTRIBPOINTERPROC glad_debug_glVertexAttribPointer; #define glVertexAttribPointer glad_debug_glVertexAttribPointer GLAD_API_CALL PFNGLVERTEXPOINTERPROC glad_glVertexPointer; GLAD_API_CALL PFNGLVERTEXPOINTERPROC glad_debug_glVertexPointer; #define glVertexPointer glad_debug_glVertexPointer GLAD_API_CALL PFNGLVIEWPORTPROC glad_glViewport; GLAD_API_CALL PFNGLVIEWPORTPROC glad_debug_glViewport; #define glViewport glad_debug_glViewport GLAD_API_CALL PFNGLWINDOWPOS2DPROC glad_glWindowPos2d; GLAD_API_CALL PFNGLWINDOWPOS2DPROC glad_debug_glWindowPos2d; #define glWindowPos2d glad_debug_glWindowPos2d GLAD_API_CALL PFNGLWINDOWPOS2DVPROC glad_glWindowPos2dv; GLAD_API_CALL PFNGLWINDOWPOS2DVPROC glad_debug_glWindowPos2dv; #define glWindowPos2dv glad_debug_glWindowPos2dv GLAD_API_CALL PFNGLWINDOWPOS2FPROC glad_glWindowPos2f; GLAD_API_CALL PFNGLWINDOWPOS2FPROC glad_debug_glWindowPos2f; #define glWindowPos2f glad_debug_glWindowPos2f GLAD_API_CALL PFNGLWINDOWPOS2FVPROC glad_glWindowPos2fv; GLAD_API_CALL PFNGLWINDOWPOS2FVPROC glad_debug_glWindowPos2fv; #define glWindowPos2fv glad_debug_glWindowPos2fv GLAD_API_CALL PFNGLWINDOWPOS2IPROC glad_glWindowPos2i; GLAD_API_CALL PFNGLWINDOWPOS2IPROC glad_debug_glWindowPos2i; #define glWindowPos2i glad_debug_glWindowPos2i GLAD_API_CALL PFNGLWINDOWPOS2IVPROC glad_glWindowPos2iv; GLAD_API_CALL PFNGLWINDOWPOS2IVPROC glad_debug_glWindowPos2iv; #define glWindowPos2iv glad_debug_glWindowPos2iv GLAD_API_CALL PFNGLWINDOWPOS2SPROC glad_glWindowPos2s; GLAD_API_CALL PFNGLWINDOWPOS2SPROC glad_debug_glWindowPos2s; #define glWindowPos2s glad_debug_glWindowPos2s GLAD_API_CALL PFNGLWINDOWPOS2SVPROC glad_glWindowPos2sv; GLAD_API_CALL PFNGLWINDOWPOS2SVPROC glad_debug_glWindowPos2sv; #define glWindowPos2sv glad_debug_glWindowPos2sv GLAD_API_CALL PFNGLWINDOWPOS3DPROC glad_glWindowPos3d; GLAD_API_CALL PFNGLWINDOWPOS3DPROC glad_debug_glWindowPos3d; #define glWindowPos3d glad_debug_glWindowPos3d GLAD_API_CALL PFNGLWINDOWPOS3DVPROC glad_glWindowPos3dv; GLAD_API_CALL PFNGLWINDOWPOS3DVPROC glad_debug_glWindowPos3dv; #define glWindowPos3dv glad_debug_glWindowPos3dv GLAD_API_CALL PFNGLWINDOWPOS3FPROC glad_glWindowPos3f; GLAD_API_CALL PFNGLWINDOWPOS3FPROC glad_debug_glWindowPos3f; #define glWindowPos3f glad_debug_glWindowPos3f GLAD_API_CALL PFNGLWINDOWPOS3FVPROC glad_glWindowPos3fv; GLAD_API_CALL PFNGLWINDOWPOS3FVPROC glad_debug_glWindowPos3fv; #define glWindowPos3fv glad_debug_glWindowPos3fv GLAD_API_CALL PFNGLWINDOWPOS3IPROC glad_glWindowPos3i; GLAD_API_CALL PFNGLWINDOWPOS3IPROC glad_debug_glWindowPos3i; #define glWindowPos3i glad_debug_glWindowPos3i GLAD_API_CALL PFNGLWINDOWPOS3IVPROC glad_glWindowPos3iv; GLAD_API_CALL PFNGLWINDOWPOS3IVPROC glad_debug_glWindowPos3iv; #define glWindowPos3iv glad_debug_glWindowPos3iv GLAD_API_CALL PFNGLWINDOWPOS3SPROC glad_glWindowPos3s; GLAD_API_CALL PFNGLWINDOWPOS3SPROC glad_debug_glWindowPos3s; #define glWindowPos3s glad_debug_glWindowPos3s GLAD_API_CALL PFNGLWINDOWPOS3SVPROC glad_glWindowPos3sv; GLAD_API_CALL PFNGLWINDOWPOS3SVPROC glad_debug_glWindowPos3sv; #define glWindowPos3sv glad_debug_glWindowPos3sv GLAD_API_CALL void gladSetGLPreCallback(GLADprecallback cb); GLAD_API_CALL void gladSetGLPostCallback(GLADpostcallback cb); GLAD_API_CALL void gladInstallGLDebug(void); GLAD_API_CALL void gladUninstallGLDebug(void); GLAD_API_CALL int gladLoadGLUserPtr( GLADuserptrloadfunc load, void *userptr); GLAD_API_CALL int gladLoadGL( GLADloadfunc load); #ifdef __cplusplus } #endif #endif /* Source */ #ifdef GLAD_GL_IMPLEMENTATION /** * SPDX-License-Identifier: (WTFPL OR CC0-1.0) AND Apache-2.0 */ #include #include #include #ifndef GLAD_IMPL_UTIL_C_ #define GLAD_IMPL_UTIL_C_ #ifdef _MSC_VER #define GLAD_IMPL_UTIL_SSCANF sscanf_s #else #define GLAD_IMPL_UTIL_SSCANF sscanf #endif #endif /* GLAD_IMPL_UTIL_C_ */ #ifdef __cplusplus extern "C" { #endif int GLAD_GL_VERSION_1_0 = 0; int GLAD_GL_VERSION_1_1 = 0; int GLAD_GL_VERSION_1_2 = 0; int GLAD_GL_VERSION_1_3 = 0; int GLAD_GL_VERSION_1_4 = 0; int GLAD_GL_VERSION_1_5 = 0; int GLAD_GL_VERSION_2_0 = 0; int GLAD_GL_VERSION_2_1 = 0; int GLAD_GL_VERSION_3_0 = 0; int GLAD_GL_VERSION_3_1 = 0; int GLAD_GL_ARB_copy_image = 0; int GLAD_GL_ARB_instanced_arrays = 0; int GLAD_GL_ARB_multisample = 0; int GLAD_GL_ARB_robustness = 0; int GLAD_GL_ARB_texture_storage = 0; int GLAD_GL_KHR_debug = 0; static void _pre_call_gl_callback_default(const char *name, GLADapiproc apiproc, int len_args, ...) { GLAD_UNUSED(len_args); if (apiproc == NULL) { fprintf(stderr, "GLAD: ERROR %s is NULL!\n", name); return; } if (glad_glGetError == NULL) { fprintf(stderr, "GLAD: ERROR glGetError is NULL!\n"); return; } (void) glad_glGetError(); } static void _post_call_gl_callback_default(void *ret, const char *name, GLADapiproc apiproc, int len_args, ...) { GLenum error_code; GLAD_UNUSED(ret); GLAD_UNUSED(apiproc); GLAD_UNUSED(len_args); error_code = glad_glGetError(); if (error_code != GL_NO_ERROR) { fprintf(stderr, "GLAD: ERROR %d in %s!\n", error_code, name); } } static GLADprecallback _pre_call_gl_callback = _pre_call_gl_callback_default; void gladSetGLPreCallback(GLADprecallback cb) { _pre_call_gl_callback = cb; } static GLADpostcallback _post_call_gl_callback = _post_call_gl_callback_default; void gladSetGLPostCallback(GLADpostcallback cb) { _post_call_gl_callback = cb; } PFNGLACCUMPROC glad_glAccum = NULL; static void GLAD_API_PTR glad_debug_impl_glAccum(GLenum op, GLfloat value) { _pre_call_gl_callback("glAccum", (GLADapiproc) glad_glAccum, 2, op, value); glad_glAccum(op, value); _post_call_gl_callback(NULL, "glAccum", (GLADapiproc) glad_glAccum, 2, op, value); } PFNGLACCUMPROC glad_debug_glAccum = glad_debug_impl_glAccum; PFNGLACTIVETEXTUREPROC glad_glActiveTexture = NULL; static void GLAD_API_PTR glad_debug_impl_glActiveTexture(GLenum texture) { _pre_call_gl_callback("glActiveTexture", (GLADapiproc) glad_glActiveTexture, 1, texture); glad_glActiveTexture(texture); _post_call_gl_callback(NULL, "glActiveTexture", (GLADapiproc) glad_glActiveTexture, 1, texture); } PFNGLACTIVETEXTUREPROC glad_debug_glActiveTexture = glad_debug_impl_glActiveTexture; PFNGLALPHAFUNCPROC glad_glAlphaFunc = NULL; static void GLAD_API_PTR glad_debug_impl_glAlphaFunc(GLenum func, GLfloat ref) { _pre_call_gl_callback("glAlphaFunc", (GLADapiproc) glad_glAlphaFunc, 2, func, ref); glad_glAlphaFunc(func, ref); _post_call_gl_callback(NULL, "glAlphaFunc", (GLADapiproc) glad_glAlphaFunc, 2, func, ref); } PFNGLALPHAFUNCPROC glad_debug_glAlphaFunc = glad_debug_impl_glAlphaFunc; PFNGLARETEXTURESRESIDENTPROC glad_glAreTexturesResident = NULL; static GLboolean GLAD_API_PTR glad_debug_impl_glAreTexturesResident(GLsizei n, const GLuint * textures, GLboolean * residences) { GLboolean ret; _pre_call_gl_callback("glAreTexturesResident", (GLADapiproc) glad_glAreTexturesResident, 3, n, textures, residences); ret = glad_glAreTexturesResident(n, textures, residences); _post_call_gl_callback((void*) &ret, "glAreTexturesResident", (GLADapiproc) glad_glAreTexturesResident, 3, n, textures, residences); return ret; } PFNGLARETEXTURESRESIDENTPROC glad_debug_glAreTexturesResident = glad_debug_impl_glAreTexturesResident; PFNGLARRAYELEMENTPROC glad_glArrayElement = NULL; static void GLAD_API_PTR glad_debug_impl_glArrayElement(GLint i) { _pre_call_gl_callback("glArrayElement", (GLADapiproc) glad_glArrayElement, 1, i); glad_glArrayElement(i); _post_call_gl_callback(NULL, "glArrayElement", (GLADapiproc) glad_glArrayElement, 1, i); } PFNGLARRAYELEMENTPROC glad_debug_glArrayElement = glad_debug_impl_glArrayElement; PFNGLATTACHSHADERPROC glad_glAttachShader = NULL; static void GLAD_API_PTR glad_debug_impl_glAttachShader(GLuint program, GLuint shader) { _pre_call_gl_callback("glAttachShader", (GLADapiproc) glad_glAttachShader, 2, program, shader); glad_glAttachShader(program, shader); _post_call_gl_callback(NULL, "glAttachShader", (GLADapiproc) glad_glAttachShader, 2, program, shader); } PFNGLATTACHSHADERPROC glad_debug_glAttachShader = glad_debug_impl_glAttachShader; PFNGLBEGINPROC glad_glBegin = NULL; static void GLAD_API_PTR glad_debug_impl_glBegin(GLenum mode) { _pre_call_gl_callback("glBegin", (GLADapiproc) glad_glBegin, 1, mode); glad_glBegin(mode); _post_call_gl_callback(NULL, "glBegin", (GLADapiproc) glad_glBegin, 1, mode); } PFNGLBEGINPROC glad_debug_glBegin = glad_debug_impl_glBegin; PFNGLBEGINCONDITIONALRENDERPROC glad_glBeginConditionalRender = NULL; static void GLAD_API_PTR glad_debug_impl_glBeginConditionalRender(GLuint id, GLenum mode) { _pre_call_gl_callback("glBeginConditionalRender", (GLADapiproc) glad_glBeginConditionalRender, 2, id, mode); glad_glBeginConditionalRender(id, mode); _post_call_gl_callback(NULL, "glBeginConditionalRender", (GLADapiproc) glad_glBeginConditionalRender, 2, id, mode); } PFNGLBEGINCONDITIONALRENDERPROC glad_debug_glBeginConditionalRender = glad_debug_impl_glBeginConditionalRender; PFNGLBEGINQUERYPROC glad_glBeginQuery = NULL; static void GLAD_API_PTR glad_debug_impl_glBeginQuery(GLenum target, GLuint id) { _pre_call_gl_callback("glBeginQuery", (GLADapiproc) glad_glBeginQuery, 2, target, id); glad_glBeginQuery(target, id); _post_call_gl_callback(NULL, "glBeginQuery", (GLADapiproc) glad_glBeginQuery, 2, target, id); } PFNGLBEGINQUERYPROC glad_debug_glBeginQuery = glad_debug_impl_glBeginQuery; PFNGLBEGINTRANSFORMFEEDBACKPROC glad_glBeginTransformFeedback = NULL; static void GLAD_API_PTR glad_debug_impl_glBeginTransformFeedback(GLenum primitiveMode) { _pre_call_gl_callback("glBeginTransformFeedback", (GLADapiproc) glad_glBeginTransformFeedback, 1, primitiveMode); glad_glBeginTransformFeedback(primitiveMode); _post_call_gl_callback(NULL, "glBeginTransformFeedback", (GLADapiproc) glad_glBeginTransformFeedback, 1, primitiveMode); } PFNGLBEGINTRANSFORMFEEDBACKPROC glad_debug_glBeginTransformFeedback = glad_debug_impl_glBeginTransformFeedback; PFNGLBINDATTRIBLOCATIONPROC glad_glBindAttribLocation = NULL; static void GLAD_API_PTR glad_debug_impl_glBindAttribLocation(GLuint program, GLuint index, const GLchar * name) { _pre_call_gl_callback("glBindAttribLocation", (GLADapiproc) glad_glBindAttribLocation, 3, program, index, name); glad_glBindAttribLocation(program, index, name); _post_call_gl_callback(NULL, "glBindAttribLocation", (GLADapiproc) glad_glBindAttribLocation, 3, program, index, name); } PFNGLBINDATTRIBLOCATIONPROC glad_debug_glBindAttribLocation = glad_debug_impl_glBindAttribLocation; PFNGLBINDBUFFERPROC glad_glBindBuffer = NULL; static void GLAD_API_PTR glad_debug_impl_glBindBuffer(GLenum target, GLuint buffer) { _pre_call_gl_callback("glBindBuffer", (GLADapiproc) glad_glBindBuffer, 2, target, buffer); glad_glBindBuffer(target, buffer); _post_call_gl_callback(NULL, "glBindBuffer", (GLADapiproc) glad_glBindBuffer, 2, target, buffer); } PFNGLBINDBUFFERPROC glad_debug_glBindBuffer = glad_debug_impl_glBindBuffer; PFNGLBINDBUFFERBASEPROC glad_glBindBufferBase = NULL; static void GLAD_API_PTR glad_debug_impl_glBindBufferBase(GLenum target, GLuint index, GLuint buffer) { _pre_call_gl_callback("glBindBufferBase", (GLADapiproc) glad_glBindBufferBase, 3, target, index, buffer); glad_glBindBufferBase(target, index, buffer); _post_call_gl_callback(NULL, "glBindBufferBase", (GLADapiproc) glad_glBindBufferBase, 3, target, index, buffer); } PFNGLBINDBUFFERBASEPROC glad_debug_glBindBufferBase = glad_debug_impl_glBindBufferBase; PFNGLBINDBUFFERRANGEPROC glad_glBindBufferRange = NULL; static void GLAD_API_PTR glad_debug_impl_glBindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) { _pre_call_gl_callback("glBindBufferRange", (GLADapiproc) glad_glBindBufferRange, 5, target, index, buffer, offset, size); glad_glBindBufferRange(target, index, buffer, offset, size); _post_call_gl_callback(NULL, "glBindBufferRange", (GLADapiproc) glad_glBindBufferRange, 5, target, index, buffer, offset, size); } PFNGLBINDBUFFERRANGEPROC glad_debug_glBindBufferRange = glad_debug_impl_glBindBufferRange; PFNGLBINDFRAGDATALOCATIONPROC glad_glBindFragDataLocation = NULL; static void GLAD_API_PTR glad_debug_impl_glBindFragDataLocation(GLuint program, GLuint color, const GLchar * name) { _pre_call_gl_callback("glBindFragDataLocation", (GLADapiproc) glad_glBindFragDataLocation, 3, program, color, name); glad_glBindFragDataLocation(program, color, name); _post_call_gl_callback(NULL, "glBindFragDataLocation", (GLADapiproc) glad_glBindFragDataLocation, 3, program, color, name); } PFNGLBINDFRAGDATALOCATIONPROC glad_debug_glBindFragDataLocation = glad_debug_impl_glBindFragDataLocation; PFNGLBINDFRAMEBUFFERPROC glad_glBindFramebuffer = NULL; static void GLAD_API_PTR glad_debug_impl_glBindFramebuffer(GLenum target, GLuint framebuffer) { _pre_call_gl_callback("glBindFramebuffer", (GLADapiproc) glad_glBindFramebuffer, 2, target, framebuffer); glad_glBindFramebuffer(target, framebuffer); _post_call_gl_callback(NULL, "glBindFramebuffer", (GLADapiproc) glad_glBindFramebuffer, 2, target, framebuffer); } PFNGLBINDFRAMEBUFFERPROC glad_debug_glBindFramebuffer = glad_debug_impl_glBindFramebuffer; PFNGLBINDRENDERBUFFERPROC glad_glBindRenderbuffer = NULL; static void GLAD_API_PTR glad_debug_impl_glBindRenderbuffer(GLenum target, GLuint renderbuffer) { _pre_call_gl_callback("glBindRenderbuffer", (GLADapiproc) glad_glBindRenderbuffer, 2, target, renderbuffer); glad_glBindRenderbuffer(target, renderbuffer); _post_call_gl_callback(NULL, "glBindRenderbuffer", (GLADapiproc) glad_glBindRenderbuffer, 2, target, renderbuffer); } PFNGLBINDRENDERBUFFERPROC glad_debug_glBindRenderbuffer = glad_debug_impl_glBindRenderbuffer; PFNGLBINDTEXTUREPROC glad_glBindTexture = NULL; static void GLAD_API_PTR glad_debug_impl_glBindTexture(GLenum target, GLuint texture) { _pre_call_gl_callback("glBindTexture", (GLADapiproc) glad_glBindTexture, 2, target, texture); glad_glBindTexture(target, texture); _post_call_gl_callback(NULL, "glBindTexture", (GLADapiproc) glad_glBindTexture, 2, target, texture); } PFNGLBINDTEXTUREPROC glad_debug_glBindTexture = glad_debug_impl_glBindTexture; PFNGLBINDVERTEXARRAYPROC glad_glBindVertexArray = NULL; static void GLAD_API_PTR glad_debug_impl_glBindVertexArray(GLuint array) { _pre_call_gl_callback("glBindVertexArray", (GLADapiproc) glad_glBindVertexArray, 1, array); glad_glBindVertexArray(array); _post_call_gl_callback(NULL, "glBindVertexArray", (GLADapiproc) glad_glBindVertexArray, 1, array); } PFNGLBINDVERTEXARRAYPROC glad_debug_glBindVertexArray = glad_debug_impl_glBindVertexArray; PFNGLBITMAPPROC glad_glBitmap = NULL; static void GLAD_API_PTR glad_debug_impl_glBitmap(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte * bitmap) { _pre_call_gl_callback("glBitmap", (GLADapiproc) glad_glBitmap, 7, width, height, xorig, yorig, xmove, ymove, bitmap); glad_glBitmap(width, height, xorig, yorig, xmove, ymove, bitmap); _post_call_gl_callback(NULL, "glBitmap", (GLADapiproc) glad_glBitmap, 7, width, height, xorig, yorig, xmove, ymove, bitmap); } PFNGLBITMAPPROC glad_debug_glBitmap = glad_debug_impl_glBitmap; PFNGLBLENDCOLORPROC glad_glBlendColor = NULL; static void GLAD_API_PTR glad_debug_impl_glBlendColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { _pre_call_gl_callback("glBlendColor", (GLADapiproc) glad_glBlendColor, 4, red, green, blue, alpha); glad_glBlendColor(red, green, blue, alpha); _post_call_gl_callback(NULL, "glBlendColor", (GLADapiproc) glad_glBlendColor, 4, red, green, blue, alpha); } PFNGLBLENDCOLORPROC glad_debug_glBlendColor = glad_debug_impl_glBlendColor; PFNGLBLENDEQUATIONPROC glad_glBlendEquation = NULL; static void GLAD_API_PTR glad_debug_impl_glBlendEquation(GLenum mode) { _pre_call_gl_callback("glBlendEquation", (GLADapiproc) glad_glBlendEquation, 1, mode); glad_glBlendEquation(mode); _post_call_gl_callback(NULL, "glBlendEquation", (GLADapiproc) glad_glBlendEquation, 1, mode); } PFNGLBLENDEQUATIONPROC glad_debug_glBlendEquation = glad_debug_impl_glBlendEquation; PFNGLBLENDEQUATIONSEPARATEPROC glad_glBlendEquationSeparate = NULL; static void GLAD_API_PTR glad_debug_impl_glBlendEquationSeparate(GLenum modeRGB, GLenum modeAlpha) { _pre_call_gl_callback("glBlendEquationSeparate", (GLADapiproc) glad_glBlendEquationSeparate, 2, modeRGB, modeAlpha); glad_glBlendEquationSeparate(modeRGB, modeAlpha); _post_call_gl_callback(NULL, "glBlendEquationSeparate", (GLADapiproc) glad_glBlendEquationSeparate, 2, modeRGB, modeAlpha); } PFNGLBLENDEQUATIONSEPARATEPROC glad_debug_glBlendEquationSeparate = glad_debug_impl_glBlendEquationSeparate; PFNGLBLENDFUNCPROC glad_glBlendFunc = NULL; static void GLAD_API_PTR glad_debug_impl_glBlendFunc(GLenum sfactor, GLenum dfactor) { _pre_call_gl_callback("glBlendFunc", (GLADapiproc) glad_glBlendFunc, 2, sfactor, dfactor); glad_glBlendFunc(sfactor, dfactor); _post_call_gl_callback(NULL, "glBlendFunc", (GLADapiproc) glad_glBlendFunc, 2, sfactor, dfactor); } PFNGLBLENDFUNCPROC glad_debug_glBlendFunc = glad_debug_impl_glBlendFunc; PFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate = NULL; static void GLAD_API_PTR glad_debug_impl_glBlendFuncSeparate(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha) { _pre_call_gl_callback("glBlendFuncSeparate", (GLADapiproc) glad_glBlendFuncSeparate, 4, sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha); glad_glBlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha); _post_call_gl_callback(NULL, "glBlendFuncSeparate", (GLADapiproc) glad_glBlendFuncSeparate, 4, sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha); } PFNGLBLENDFUNCSEPARATEPROC glad_debug_glBlendFuncSeparate = glad_debug_impl_glBlendFuncSeparate; PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer = NULL; static void GLAD_API_PTR glad_debug_impl_glBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { _pre_call_gl_callback("glBlitFramebuffer", (GLADapiproc) glad_glBlitFramebuffer, 10, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); glad_glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); _post_call_gl_callback(NULL, "glBlitFramebuffer", (GLADapiproc) glad_glBlitFramebuffer, 10, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); } PFNGLBLITFRAMEBUFFERPROC glad_debug_glBlitFramebuffer = glad_debug_impl_glBlitFramebuffer; PFNGLBUFFERDATAPROC glad_glBufferData = NULL; static void GLAD_API_PTR glad_debug_impl_glBufferData(GLenum target, GLsizeiptr size, const void * data, GLenum usage) { _pre_call_gl_callback("glBufferData", (GLADapiproc) glad_glBufferData, 4, target, size, data, usage); glad_glBufferData(target, size, data, usage); _post_call_gl_callback(NULL, "glBufferData", (GLADapiproc) glad_glBufferData, 4, target, size, data, usage); } PFNGLBUFFERDATAPROC glad_debug_glBufferData = glad_debug_impl_glBufferData; PFNGLBUFFERSUBDATAPROC glad_glBufferSubData = NULL; static void GLAD_API_PTR glad_debug_impl_glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void * data) { _pre_call_gl_callback("glBufferSubData", (GLADapiproc) glad_glBufferSubData, 4, target, offset, size, data); glad_glBufferSubData(target, offset, size, data); _post_call_gl_callback(NULL, "glBufferSubData", (GLADapiproc) glad_glBufferSubData, 4, target, offset, size, data); } PFNGLBUFFERSUBDATAPROC glad_debug_glBufferSubData = glad_debug_impl_glBufferSubData; PFNGLCALLLISTPROC glad_glCallList = NULL; static void GLAD_API_PTR glad_debug_impl_glCallList(GLuint list) { _pre_call_gl_callback("glCallList", (GLADapiproc) glad_glCallList, 1, list); glad_glCallList(list); _post_call_gl_callback(NULL, "glCallList", (GLADapiproc) glad_glCallList, 1, list); } PFNGLCALLLISTPROC glad_debug_glCallList = glad_debug_impl_glCallList; PFNGLCALLLISTSPROC glad_glCallLists = NULL; static void GLAD_API_PTR glad_debug_impl_glCallLists(GLsizei n, GLenum type, const void * lists) { _pre_call_gl_callback("glCallLists", (GLADapiproc) glad_glCallLists, 3, n, type, lists); glad_glCallLists(n, type, lists); _post_call_gl_callback(NULL, "glCallLists", (GLADapiproc) glad_glCallLists, 3, n, type, lists); } PFNGLCALLLISTSPROC glad_debug_glCallLists = glad_debug_impl_glCallLists; PFNGLCHECKFRAMEBUFFERSTATUSPROC glad_glCheckFramebufferStatus = NULL; static GLenum GLAD_API_PTR glad_debug_impl_glCheckFramebufferStatus(GLenum target) { GLenum ret; _pre_call_gl_callback("glCheckFramebufferStatus", (GLADapiproc) glad_glCheckFramebufferStatus, 1, target); ret = glad_glCheckFramebufferStatus(target); _post_call_gl_callback((void*) &ret, "glCheckFramebufferStatus", (GLADapiproc) glad_glCheckFramebufferStatus, 1, target); return ret; } PFNGLCHECKFRAMEBUFFERSTATUSPROC glad_debug_glCheckFramebufferStatus = glad_debug_impl_glCheckFramebufferStatus; PFNGLCLAMPCOLORPROC glad_glClampColor = NULL; static void GLAD_API_PTR glad_debug_impl_glClampColor(GLenum target, GLenum clamp) { _pre_call_gl_callback("glClampColor", (GLADapiproc) glad_glClampColor, 2, target, clamp); glad_glClampColor(target, clamp); _post_call_gl_callback(NULL, "glClampColor", (GLADapiproc) glad_glClampColor, 2, target, clamp); } PFNGLCLAMPCOLORPROC glad_debug_glClampColor = glad_debug_impl_glClampColor; PFNGLCLEARPROC glad_glClear = NULL; static void GLAD_API_PTR glad_debug_impl_glClear(GLbitfield mask) { _pre_call_gl_callback("glClear", (GLADapiproc) glad_glClear, 1, mask); glad_glClear(mask); _post_call_gl_callback(NULL, "glClear", (GLADapiproc) glad_glClear, 1, mask); } PFNGLCLEARPROC glad_debug_glClear = glad_debug_impl_glClear; PFNGLCLEARACCUMPROC glad_glClearAccum = NULL; static void GLAD_API_PTR glad_debug_impl_glClearAccum(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { _pre_call_gl_callback("glClearAccum", (GLADapiproc) glad_glClearAccum, 4, red, green, blue, alpha); glad_glClearAccum(red, green, blue, alpha); _post_call_gl_callback(NULL, "glClearAccum", (GLADapiproc) glad_glClearAccum, 4, red, green, blue, alpha); } PFNGLCLEARACCUMPROC glad_debug_glClearAccum = glad_debug_impl_glClearAccum; PFNGLCLEARBUFFERFIPROC glad_glClearBufferfi = NULL; static void GLAD_API_PTR glad_debug_impl_glClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { _pre_call_gl_callback("glClearBufferfi", (GLADapiproc) glad_glClearBufferfi, 4, buffer, drawbuffer, depth, stencil); glad_glClearBufferfi(buffer, drawbuffer, depth, stencil); _post_call_gl_callback(NULL, "glClearBufferfi", (GLADapiproc) glad_glClearBufferfi, 4, buffer, drawbuffer, depth, stencil); } PFNGLCLEARBUFFERFIPROC glad_debug_glClearBufferfi = glad_debug_impl_glClearBufferfi; PFNGLCLEARBUFFERFVPROC glad_glClearBufferfv = NULL; static void GLAD_API_PTR glad_debug_impl_glClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat * value) { _pre_call_gl_callback("glClearBufferfv", (GLADapiproc) glad_glClearBufferfv, 3, buffer, drawbuffer, value); glad_glClearBufferfv(buffer, drawbuffer, value); _post_call_gl_callback(NULL, "glClearBufferfv", (GLADapiproc) glad_glClearBufferfv, 3, buffer, drawbuffer, value); } PFNGLCLEARBUFFERFVPROC glad_debug_glClearBufferfv = glad_debug_impl_glClearBufferfv; PFNGLCLEARBUFFERIVPROC glad_glClearBufferiv = NULL; static void GLAD_API_PTR glad_debug_impl_glClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint * value) { _pre_call_gl_callback("glClearBufferiv", (GLADapiproc) glad_glClearBufferiv, 3, buffer, drawbuffer, value); glad_glClearBufferiv(buffer, drawbuffer, value); _post_call_gl_callback(NULL, "glClearBufferiv", (GLADapiproc) glad_glClearBufferiv, 3, buffer, drawbuffer, value); } PFNGLCLEARBUFFERIVPROC glad_debug_glClearBufferiv = glad_debug_impl_glClearBufferiv; PFNGLCLEARBUFFERUIVPROC glad_glClearBufferuiv = NULL; static void GLAD_API_PTR glad_debug_impl_glClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint * value) { _pre_call_gl_callback("glClearBufferuiv", (GLADapiproc) glad_glClearBufferuiv, 3, buffer, drawbuffer, value); glad_glClearBufferuiv(buffer, drawbuffer, value); _post_call_gl_callback(NULL, "glClearBufferuiv", (GLADapiproc) glad_glClearBufferuiv, 3, buffer, drawbuffer, value); } PFNGLCLEARBUFFERUIVPROC glad_debug_glClearBufferuiv = glad_debug_impl_glClearBufferuiv; PFNGLCLEARCOLORPROC glad_glClearColor = NULL; static void GLAD_API_PTR glad_debug_impl_glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { _pre_call_gl_callback("glClearColor", (GLADapiproc) glad_glClearColor, 4, red, green, blue, alpha); glad_glClearColor(red, green, blue, alpha); _post_call_gl_callback(NULL, "glClearColor", (GLADapiproc) glad_glClearColor, 4, red, green, blue, alpha); } PFNGLCLEARCOLORPROC glad_debug_glClearColor = glad_debug_impl_glClearColor; PFNGLCLEARDEPTHPROC glad_glClearDepth = NULL; static void GLAD_API_PTR glad_debug_impl_glClearDepth(GLdouble depth) { _pre_call_gl_callback("glClearDepth", (GLADapiproc) glad_glClearDepth, 1, depth); glad_glClearDepth(depth); _post_call_gl_callback(NULL, "glClearDepth", (GLADapiproc) glad_glClearDepth, 1, depth); } PFNGLCLEARDEPTHPROC glad_debug_glClearDepth = glad_debug_impl_glClearDepth; PFNGLCLEARINDEXPROC glad_glClearIndex = NULL; static void GLAD_API_PTR glad_debug_impl_glClearIndex(GLfloat c) { _pre_call_gl_callback("glClearIndex", (GLADapiproc) glad_glClearIndex, 1, c); glad_glClearIndex(c); _post_call_gl_callback(NULL, "glClearIndex", (GLADapiproc) glad_glClearIndex, 1, c); } PFNGLCLEARINDEXPROC glad_debug_glClearIndex = glad_debug_impl_glClearIndex; PFNGLCLEARSTENCILPROC glad_glClearStencil = NULL; static void GLAD_API_PTR glad_debug_impl_glClearStencil(GLint s) { _pre_call_gl_callback("glClearStencil", (GLADapiproc) glad_glClearStencil, 1, s); glad_glClearStencil(s); _post_call_gl_callback(NULL, "glClearStencil", (GLADapiproc) glad_glClearStencil, 1, s); } PFNGLCLEARSTENCILPROC glad_debug_glClearStencil = glad_debug_impl_glClearStencil; PFNGLCLIENTACTIVETEXTUREPROC glad_glClientActiveTexture = NULL; static void GLAD_API_PTR glad_debug_impl_glClientActiveTexture(GLenum texture) { _pre_call_gl_callback("glClientActiveTexture", (GLADapiproc) glad_glClientActiveTexture, 1, texture); glad_glClientActiveTexture(texture); _post_call_gl_callback(NULL, "glClientActiveTexture", (GLADapiproc) glad_glClientActiveTexture, 1, texture); } PFNGLCLIENTACTIVETEXTUREPROC glad_debug_glClientActiveTexture = glad_debug_impl_glClientActiveTexture; PFNGLCLIPPLANEPROC glad_glClipPlane = NULL; static void GLAD_API_PTR glad_debug_impl_glClipPlane(GLenum plane, const GLdouble * equation) { _pre_call_gl_callback("glClipPlane", (GLADapiproc) glad_glClipPlane, 2, plane, equation); glad_glClipPlane(plane, equation); _post_call_gl_callback(NULL, "glClipPlane", (GLADapiproc) glad_glClipPlane, 2, plane, equation); } PFNGLCLIPPLANEPROC glad_debug_glClipPlane = glad_debug_impl_glClipPlane; PFNGLCOLOR3BPROC glad_glColor3b = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3b(GLbyte red, GLbyte green, GLbyte blue) { _pre_call_gl_callback("glColor3b", (GLADapiproc) glad_glColor3b, 3, red, green, blue); glad_glColor3b(red, green, blue); _post_call_gl_callback(NULL, "glColor3b", (GLADapiproc) glad_glColor3b, 3, red, green, blue); } PFNGLCOLOR3BPROC glad_debug_glColor3b = glad_debug_impl_glColor3b; PFNGLCOLOR3BVPROC glad_glColor3bv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3bv(const GLbyte * v) { _pre_call_gl_callback("glColor3bv", (GLADapiproc) glad_glColor3bv, 1, v); glad_glColor3bv(v); _post_call_gl_callback(NULL, "glColor3bv", (GLADapiproc) glad_glColor3bv, 1, v); } PFNGLCOLOR3BVPROC glad_debug_glColor3bv = glad_debug_impl_glColor3bv; PFNGLCOLOR3DPROC glad_glColor3d = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3d(GLdouble red, GLdouble green, GLdouble blue) { _pre_call_gl_callback("glColor3d", (GLADapiproc) glad_glColor3d, 3, red, green, blue); glad_glColor3d(red, green, blue); _post_call_gl_callback(NULL, "glColor3d", (GLADapiproc) glad_glColor3d, 3, red, green, blue); } PFNGLCOLOR3DPROC glad_debug_glColor3d = glad_debug_impl_glColor3d; PFNGLCOLOR3DVPROC glad_glColor3dv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3dv(const GLdouble * v) { _pre_call_gl_callback("glColor3dv", (GLADapiproc) glad_glColor3dv, 1, v); glad_glColor3dv(v); _post_call_gl_callback(NULL, "glColor3dv", (GLADapiproc) glad_glColor3dv, 1, v); } PFNGLCOLOR3DVPROC glad_debug_glColor3dv = glad_debug_impl_glColor3dv; PFNGLCOLOR3FPROC glad_glColor3f = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3f(GLfloat red, GLfloat green, GLfloat blue) { _pre_call_gl_callback("glColor3f", (GLADapiproc) glad_glColor3f, 3, red, green, blue); glad_glColor3f(red, green, blue); _post_call_gl_callback(NULL, "glColor3f", (GLADapiproc) glad_glColor3f, 3, red, green, blue); } PFNGLCOLOR3FPROC glad_debug_glColor3f = glad_debug_impl_glColor3f; PFNGLCOLOR3FVPROC glad_glColor3fv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3fv(const GLfloat * v) { _pre_call_gl_callback("glColor3fv", (GLADapiproc) glad_glColor3fv, 1, v); glad_glColor3fv(v); _post_call_gl_callback(NULL, "glColor3fv", (GLADapiproc) glad_glColor3fv, 1, v); } PFNGLCOLOR3FVPROC glad_debug_glColor3fv = glad_debug_impl_glColor3fv; PFNGLCOLOR3IPROC glad_glColor3i = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3i(GLint red, GLint green, GLint blue) { _pre_call_gl_callback("glColor3i", (GLADapiproc) glad_glColor3i, 3, red, green, blue); glad_glColor3i(red, green, blue); _post_call_gl_callback(NULL, "glColor3i", (GLADapiproc) glad_glColor3i, 3, red, green, blue); } PFNGLCOLOR3IPROC glad_debug_glColor3i = glad_debug_impl_glColor3i; PFNGLCOLOR3IVPROC glad_glColor3iv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3iv(const GLint * v) { _pre_call_gl_callback("glColor3iv", (GLADapiproc) glad_glColor3iv, 1, v); glad_glColor3iv(v); _post_call_gl_callback(NULL, "glColor3iv", (GLADapiproc) glad_glColor3iv, 1, v); } PFNGLCOLOR3IVPROC glad_debug_glColor3iv = glad_debug_impl_glColor3iv; PFNGLCOLOR3SPROC glad_glColor3s = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3s(GLshort red, GLshort green, GLshort blue) { _pre_call_gl_callback("glColor3s", (GLADapiproc) glad_glColor3s, 3, red, green, blue); glad_glColor3s(red, green, blue); _post_call_gl_callback(NULL, "glColor3s", (GLADapiproc) glad_glColor3s, 3, red, green, blue); } PFNGLCOLOR3SPROC glad_debug_glColor3s = glad_debug_impl_glColor3s; PFNGLCOLOR3SVPROC glad_glColor3sv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3sv(const GLshort * v) { _pre_call_gl_callback("glColor3sv", (GLADapiproc) glad_glColor3sv, 1, v); glad_glColor3sv(v); _post_call_gl_callback(NULL, "glColor3sv", (GLADapiproc) glad_glColor3sv, 1, v); } PFNGLCOLOR3SVPROC glad_debug_glColor3sv = glad_debug_impl_glColor3sv; PFNGLCOLOR3UBPROC glad_glColor3ub = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3ub(GLubyte red, GLubyte green, GLubyte blue) { _pre_call_gl_callback("glColor3ub", (GLADapiproc) glad_glColor3ub, 3, red, green, blue); glad_glColor3ub(red, green, blue); _post_call_gl_callback(NULL, "glColor3ub", (GLADapiproc) glad_glColor3ub, 3, red, green, blue); } PFNGLCOLOR3UBPROC glad_debug_glColor3ub = glad_debug_impl_glColor3ub; PFNGLCOLOR3UBVPROC glad_glColor3ubv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3ubv(const GLubyte * v) { _pre_call_gl_callback("glColor3ubv", (GLADapiproc) glad_glColor3ubv, 1, v); glad_glColor3ubv(v); _post_call_gl_callback(NULL, "glColor3ubv", (GLADapiproc) glad_glColor3ubv, 1, v); } PFNGLCOLOR3UBVPROC glad_debug_glColor3ubv = glad_debug_impl_glColor3ubv; PFNGLCOLOR3UIPROC glad_glColor3ui = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3ui(GLuint red, GLuint green, GLuint blue) { _pre_call_gl_callback("glColor3ui", (GLADapiproc) glad_glColor3ui, 3, red, green, blue); glad_glColor3ui(red, green, blue); _post_call_gl_callback(NULL, "glColor3ui", (GLADapiproc) glad_glColor3ui, 3, red, green, blue); } PFNGLCOLOR3UIPROC glad_debug_glColor3ui = glad_debug_impl_glColor3ui; PFNGLCOLOR3UIVPROC glad_glColor3uiv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3uiv(const GLuint * v) { _pre_call_gl_callback("glColor3uiv", (GLADapiproc) glad_glColor3uiv, 1, v); glad_glColor3uiv(v); _post_call_gl_callback(NULL, "glColor3uiv", (GLADapiproc) glad_glColor3uiv, 1, v); } PFNGLCOLOR3UIVPROC glad_debug_glColor3uiv = glad_debug_impl_glColor3uiv; PFNGLCOLOR3USPROC glad_glColor3us = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3us(GLushort red, GLushort green, GLushort blue) { _pre_call_gl_callback("glColor3us", (GLADapiproc) glad_glColor3us, 3, red, green, blue); glad_glColor3us(red, green, blue); _post_call_gl_callback(NULL, "glColor3us", (GLADapiproc) glad_glColor3us, 3, red, green, blue); } PFNGLCOLOR3USPROC glad_debug_glColor3us = glad_debug_impl_glColor3us; PFNGLCOLOR3USVPROC glad_glColor3usv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor3usv(const GLushort * v) { _pre_call_gl_callback("glColor3usv", (GLADapiproc) glad_glColor3usv, 1, v); glad_glColor3usv(v); _post_call_gl_callback(NULL, "glColor3usv", (GLADapiproc) glad_glColor3usv, 1, v); } PFNGLCOLOR3USVPROC glad_debug_glColor3usv = glad_debug_impl_glColor3usv; PFNGLCOLOR4BPROC glad_glColor4b = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4b(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha) { _pre_call_gl_callback("glColor4b", (GLADapiproc) glad_glColor4b, 4, red, green, blue, alpha); glad_glColor4b(red, green, blue, alpha); _post_call_gl_callback(NULL, "glColor4b", (GLADapiproc) glad_glColor4b, 4, red, green, blue, alpha); } PFNGLCOLOR4BPROC glad_debug_glColor4b = glad_debug_impl_glColor4b; PFNGLCOLOR4BVPROC glad_glColor4bv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4bv(const GLbyte * v) { _pre_call_gl_callback("glColor4bv", (GLADapiproc) glad_glColor4bv, 1, v); glad_glColor4bv(v); _post_call_gl_callback(NULL, "glColor4bv", (GLADapiproc) glad_glColor4bv, 1, v); } PFNGLCOLOR4BVPROC glad_debug_glColor4bv = glad_debug_impl_glColor4bv; PFNGLCOLOR4DPROC glad_glColor4d = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4d(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha) { _pre_call_gl_callback("glColor4d", (GLADapiproc) glad_glColor4d, 4, red, green, blue, alpha); glad_glColor4d(red, green, blue, alpha); _post_call_gl_callback(NULL, "glColor4d", (GLADapiproc) glad_glColor4d, 4, red, green, blue, alpha); } PFNGLCOLOR4DPROC glad_debug_glColor4d = glad_debug_impl_glColor4d; PFNGLCOLOR4DVPROC glad_glColor4dv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4dv(const GLdouble * v) { _pre_call_gl_callback("glColor4dv", (GLADapiproc) glad_glColor4dv, 1, v); glad_glColor4dv(v); _post_call_gl_callback(NULL, "glColor4dv", (GLADapiproc) glad_glColor4dv, 1, v); } PFNGLCOLOR4DVPROC glad_debug_glColor4dv = glad_debug_impl_glColor4dv; PFNGLCOLOR4FPROC glad_glColor4f = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { _pre_call_gl_callback("glColor4f", (GLADapiproc) glad_glColor4f, 4, red, green, blue, alpha); glad_glColor4f(red, green, blue, alpha); _post_call_gl_callback(NULL, "glColor4f", (GLADapiproc) glad_glColor4f, 4, red, green, blue, alpha); } PFNGLCOLOR4FPROC glad_debug_glColor4f = glad_debug_impl_glColor4f; PFNGLCOLOR4FVPROC glad_glColor4fv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4fv(const GLfloat * v) { _pre_call_gl_callback("glColor4fv", (GLADapiproc) glad_glColor4fv, 1, v); glad_glColor4fv(v); _post_call_gl_callback(NULL, "glColor4fv", (GLADapiproc) glad_glColor4fv, 1, v); } PFNGLCOLOR4FVPROC glad_debug_glColor4fv = glad_debug_impl_glColor4fv; PFNGLCOLOR4IPROC glad_glColor4i = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4i(GLint red, GLint green, GLint blue, GLint alpha) { _pre_call_gl_callback("glColor4i", (GLADapiproc) glad_glColor4i, 4, red, green, blue, alpha); glad_glColor4i(red, green, blue, alpha); _post_call_gl_callback(NULL, "glColor4i", (GLADapiproc) glad_glColor4i, 4, red, green, blue, alpha); } PFNGLCOLOR4IPROC glad_debug_glColor4i = glad_debug_impl_glColor4i; PFNGLCOLOR4IVPROC glad_glColor4iv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4iv(const GLint * v) { _pre_call_gl_callback("glColor4iv", (GLADapiproc) glad_glColor4iv, 1, v); glad_glColor4iv(v); _post_call_gl_callback(NULL, "glColor4iv", (GLADapiproc) glad_glColor4iv, 1, v); } PFNGLCOLOR4IVPROC glad_debug_glColor4iv = glad_debug_impl_glColor4iv; PFNGLCOLOR4SPROC glad_glColor4s = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4s(GLshort red, GLshort green, GLshort blue, GLshort alpha) { _pre_call_gl_callback("glColor4s", (GLADapiproc) glad_glColor4s, 4, red, green, blue, alpha); glad_glColor4s(red, green, blue, alpha); _post_call_gl_callback(NULL, "glColor4s", (GLADapiproc) glad_glColor4s, 4, red, green, blue, alpha); } PFNGLCOLOR4SPROC glad_debug_glColor4s = glad_debug_impl_glColor4s; PFNGLCOLOR4SVPROC glad_glColor4sv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4sv(const GLshort * v) { _pre_call_gl_callback("glColor4sv", (GLADapiproc) glad_glColor4sv, 1, v); glad_glColor4sv(v); _post_call_gl_callback(NULL, "glColor4sv", (GLADapiproc) glad_glColor4sv, 1, v); } PFNGLCOLOR4SVPROC glad_debug_glColor4sv = glad_debug_impl_glColor4sv; PFNGLCOLOR4UBPROC glad_glColor4ub = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4ub(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha) { _pre_call_gl_callback("glColor4ub", (GLADapiproc) glad_glColor4ub, 4, red, green, blue, alpha); glad_glColor4ub(red, green, blue, alpha); _post_call_gl_callback(NULL, "glColor4ub", (GLADapiproc) glad_glColor4ub, 4, red, green, blue, alpha); } PFNGLCOLOR4UBPROC glad_debug_glColor4ub = glad_debug_impl_glColor4ub; PFNGLCOLOR4UBVPROC glad_glColor4ubv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4ubv(const GLubyte * v) { _pre_call_gl_callback("glColor4ubv", (GLADapiproc) glad_glColor4ubv, 1, v); glad_glColor4ubv(v); _post_call_gl_callback(NULL, "glColor4ubv", (GLADapiproc) glad_glColor4ubv, 1, v); } PFNGLCOLOR4UBVPROC glad_debug_glColor4ubv = glad_debug_impl_glColor4ubv; PFNGLCOLOR4UIPROC glad_glColor4ui = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4ui(GLuint red, GLuint green, GLuint blue, GLuint alpha) { _pre_call_gl_callback("glColor4ui", (GLADapiproc) glad_glColor4ui, 4, red, green, blue, alpha); glad_glColor4ui(red, green, blue, alpha); _post_call_gl_callback(NULL, "glColor4ui", (GLADapiproc) glad_glColor4ui, 4, red, green, blue, alpha); } PFNGLCOLOR4UIPROC glad_debug_glColor4ui = glad_debug_impl_glColor4ui; PFNGLCOLOR4UIVPROC glad_glColor4uiv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4uiv(const GLuint * v) { _pre_call_gl_callback("glColor4uiv", (GLADapiproc) glad_glColor4uiv, 1, v); glad_glColor4uiv(v); _post_call_gl_callback(NULL, "glColor4uiv", (GLADapiproc) glad_glColor4uiv, 1, v); } PFNGLCOLOR4UIVPROC glad_debug_glColor4uiv = glad_debug_impl_glColor4uiv; PFNGLCOLOR4USPROC glad_glColor4us = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4us(GLushort red, GLushort green, GLushort blue, GLushort alpha) { _pre_call_gl_callback("glColor4us", (GLADapiproc) glad_glColor4us, 4, red, green, blue, alpha); glad_glColor4us(red, green, blue, alpha); _post_call_gl_callback(NULL, "glColor4us", (GLADapiproc) glad_glColor4us, 4, red, green, blue, alpha); } PFNGLCOLOR4USPROC glad_debug_glColor4us = glad_debug_impl_glColor4us; PFNGLCOLOR4USVPROC glad_glColor4usv = NULL; static void GLAD_API_PTR glad_debug_impl_glColor4usv(const GLushort * v) { _pre_call_gl_callback("glColor4usv", (GLADapiproc) glad_glColor4usv, 1, v); glad_glColor4usv(v); _post_call_gl_callback(NULL, "glColor4usv", (GLADapiproc) glad_glColor4usv, 1, v); } PFNGLCOLOR4USVPROC glad_debug_glColor4usv = glad_debug_impl_glColor4usv; PFNGLCOLORMASKPROC glad_glColorMask = NULL; static void GLAD_API_PTR glad_debug_impl_glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) { _pre_call_gl_callback("glColorMask", (GLADapiproc) glad_glColorMask, 4, red, green, blue, alpha); glad_glColorMask(red, green, blue, alpha); _post_call_gl_callback(NULL, "glColorMask", (GLADapiproc) glad_glColorMask, 4, red, green, blue, alpha); } PFNGLCOLORMASKPROC glad_debug_glColorMask = glad_debug_impl_glColorMask; PFNGLCOLORMASKIPROC glad_glColorMaski = NULL; static void GLAD_API_PTR glad_debug_impl_glColorMaski(GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a) { _pre_call_gl_callback("glColorMaski", (GLADapiproc) glad_glColorMaski, 5, index, r, g, b, a); glad_glColorMaski(index, r, g, b, a); _post_call_gl_callback(NULL, "glColorMaski", (GLADapiproc) glad_glColorMaski, 5, index, r, g, b, a); } PFNGLCOLORMASKIPROC glad_debug_glColorMaski = glad_debug_impl_glColorMaski; PFNGLCOLORMATERIALPROC glad_glColorMaterial = NULL; static void GLAD_API_PTR glad_debug_impl_glColorMaterial(GLenum face, GLenum mode) { _pre_call_gl_callback("glColorMaterial", (GLADapiproc) glad_glColorMaterial, 2, face, mode); glad_glColorMaterial(face, mode); _post_call_gl_callback(NULL, "glColorMaterial", (GLADapiproc) glad_glColorMaterial, 2, face, mode); } PFNGLCOLORMATERIALPROC glad_debug_glColorMaterial = glad_debug_impl_glColorMaterial; PFNGLCOLORPOINTERPROC glad_glColorPointer = NULL; static void GLAD_API_PTR glad_debug_impl_glColorPointer(GLint size, GLenum type, GLsizei stride, const void * pointer) { _pre_call_gl_callback("glColorPointer", (GLADapiproc) glad_glColorPointer, 4, size, type, stride, pointer); glad_glColorPointer(size, type, stride, pointer); _post_call_gl_callback(NULL, "glColorPointer", (GLADapiproc) glad_glColorPointer, 4, size, type, stride, pointer); } PFNGLCOLORPOINTERPROC glad_debug_glColorPointer = glad_debug_impl_glColorPointer; PFNGLCOMPILESHADERPROC glad_glCompileShader = NULL; static void GLAD_API_PTR glad_debug_impl_glCompileShader(GLuint shader) { _pre_call_gl_callback("glCompileShader", (GLADapiproc) glad_glCompileShader, 1, shader); glad_glCompileShader(shader); _post_call_gl_callback(NULL, "glCompileShader", (GLADapiproc) glad_glCompileShader, 1, shader); } PFNGLCOMPILESHADERPROC glad_debug_glCompileShader = glad_debug_impl_glCompileShader; PFNGLCOMPRESSEDTEXIMAGE1DPROC glad_glCompressedTexImage1D = NULL; static void GLAD_API_PTR glad_debug_impl_glCompressedTexImage1D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void * data) { _pre_call_gl_callback("glCompressedTexImage1D", (GLADapiproc) glad_glCompressedTexImage1D, 7, target, level, internalformat, width, border, imageSize, data); glad_glCompressedTexImage1D(target, level, internalformat, width, border, imageSize, data); _post_call_gl_callback(NULL, "glCompressedTexImage1D", (GLADapiproc) glad_glCompressedTexImage1D, 7, target, level, internalformat, width, border, imageSize, data); } PFNGLCOMPRESSEDTEXIMAGE1DPROC glad_debug_glCompressedTexImage1D = glad_debug_impl_glCompressedTexImage1D; PFNGLCOMPRESSEDTEXIMAGE2DPROC glad_glCompressedTexImage2D = NULL; static void GLAD_API_PTR glad_debug_impl_glCompressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void * data) { _pre_call_gl_callback("glCompressedTexImage2D", (GLADapiproc) glad_glCompressedTexImage2D, 8, target, level, internalformat, width, height, border, imageSize, data); glad_glCompressedTexImage2D(target, level, internalformat, width, height, border, imageSize, data); _post_call_gl_callback(NULL, "glCompressedTexImage2D", (GLADapiproc) glad_glCompressedTexImage2D, 8, target, level, internalformat, width, height, border, imageSize, data); } PFNGLCOMPRESSEDTEXIMAGE2DPROC glad_debug_glCompressedTexImage2D = glad_debug_impl_glCompressedTexImage2D; PFNGLCOMPRESSEDTEXIMAGE3DPROC glad_glCompressedTexImage3D = NULL; static void GLAD_API_PTR glad_debug_impl_glCompressedTexImage3D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void * data) { _pre_call_gl_callback("glCompressedTexImage3D", (GLADapiproc) glad_glCompressedTexImage3D, 9, target, level, internalformat, width, height, depth, border, imageSize, data); glad_glCompressedTexImage3D(target, level, internalformat, width, height, depth, border, imageSize, data); _post_call_gl_callback(NULL, "glCompressedTexImage3D", (GLADapiproc) glad_glCompressedTexImage3D, 9, target, level, internalformat, width, height, depth, border, imageSize, data); } PFNGLCOMPRESSEDTEXIMAGE3DPROC glad_debug_glCompressedTexImage3D = glad_debug_impl_glCompressedTexImage3D; PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC glad_glCompressedTexSubImage1D = NULL; static void GLAD_API_PTR glad_debug_impl_glCompressedTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void * data) { _pre_call_gl_callback("glCompressedTexSubImage1D", (GLADapiproc) glad_glCompressedTexSubImage1D, 7, target, level, xoffset, width, format, imageSize, data); glad_glCompressedTexSubImage1D(target, level, xoffset, width, format, imageSize, data); _post_call_gl_callback(NULL, "glCompressedTexSubImage1D", (GLADapiproc) glad_glCompressedTexSubImage1D, 7, target, level, xoffset, width, format, imageSize, data); } PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC glad_debug_glCompressedTexSubImage1D = glad_debug_impl_glCompressedTexSubImage1D; PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glad_glCompressedTexSubImage2D = NULL; static void GLAD_API_PTR glad_debug_impl_glCompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void * data) { _pre_call_gl_callback("glCompressedTexSubImage2D", (GLADapiproc) glad_glCompressedTexSubImage2D, 9, target, level, xoffset, yoffset, width, height, format, imageSize, data); glad_glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, imageSize, data); _post_call_gl_callback(NULL, "glCompressedTexSubImage2D", (GLADapiproc) glad_glCompressedTexSubImage2D, 9, target, level, xoffset, yoffset, width, height, format, imageSize, data); } PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glad_debug_glCompressedTexSubImage2D = glad_debug_impl_glCompressedTexSubImage2D; PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC glad_glCompressedTexSubImage3D = NULL; static void GLAD_API_PTR glad_debug_impl_glCompressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void * data) { _pre_call_gl_callback("glCompressedTexSubImage3D", (GLADapiproc) glad_glCompressedTexSubImage3D, 11, target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data); glad_glCompressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data); _post_call_gl_callback(NULL, "glCompressedTexSubImage3D", (GLADapiproc) glad_glCompressedTexSubImage3D, 11, target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data); } PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC glad_debug_glCompressedTexSubImage3D = glad_debug_impl_glCompressedTexSubImage3D; PFNGLCOPYBUFFERSUBDATAPROC glad_glCopyBufferSubData = NULL; static void GLAD_API_PTR glad_debug_impl_glCopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size) { _pre_call_gl_callback("glCopyBufferSubData", (GLADapiproc) glad_glCopyBufferSubData, 5, readTarget, writeTarget, readOffset, writeOffset, size); glad_glCopyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size); _post_call_gl_callback(NULL, "glCopyBufferSubData", (GLADapiproc) glad_glCopyBufferSubData, 5, readTarget, writeTarget, readOffset, writeOffset, size); } PFNGLCOPYBUFFERSUBDATAPROC glad_debug_glCopyBufferSubData = glad_debug_impl_glCopyBufferSubData; PFNGLCOPYIMAGESUBDATAPROC glad_glCopyImageSubData = NULL; static void GLAD_API_PTR glad_debug_impl_glCopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) { _pre_call_gl_callback("glCopyImageSubData", (GLADapiproc) glad_glCopyImageSubData, 15, srcName, srcTarget, srcLevel, srcX, srcY, srcZ, dstName, dstTarget, dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth); glad_glCopyImageSubData(srcName, srcTarget, srcLevel, srcX, srcY, srcZ, dstName, dstTarget, dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth); _post_call_gl_callback(NULL, "glCopyImageSubData", (GLADapiproc) glad_glCopyImageSubData, 15, srcName, srcTarget, srcLevel, srcX, srcY, srcZ, dstName, dstTarget, dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth); } PFNGLCOPYIMAGESUBDATAPROC glad_debug_glCopyImageSubData = glad_debug_impl_glCopyImageSubData; PFNGLCOPYPIXELSPROC glad_glCopyPixels = NULL; static void GLAD_API_PTR glad_debug_impl_glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type) { _pre_call_gl_callback("glCopyPixels", (GLADapiproc) glad_glCopyPixels, 5, x, y, width, height, type); glad_glCopyPixels(x, y, width, height, type); _post_call_gl_callback(NULL, "glCopyPixels", (GLADapiproc) glad_glCopyPixels, 5, x, y, width, height, type); } PFNGLCOPYPIXELSPROC glad_debug_glCopyPixels = glad_debug_impl_glCopyPixels; PFNGLCOPYTEXIMAGE1DPROC glad_glCopyTexImage1D = NULL; static void GLAD_API_PTR glad_debug_impl_glCopyTexImage1D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border) { _pre_call_gl_callback("glCopyTexImage1D", (GLADapiproc) glad_glCopyTexImage1D, 7, target, level, internalformat, x, y, width, border); glad_glCopyTexImage1D(target, level, internalformat, x, y, width, border); _post_call_gl_callback(NULL, "glCopyTexImage1D", (GLADapiproc) glad_glCopyTexImage1D, 7, target, level, internalformat, x, y, width, border); } PFNGLCOPYTEXIMAGE1DPROC glad_debug_glCopyTexImage1D = glad_debug_impl_glCopyTexImage1D; PFNGLCOPYTEXIMAGE2DPROC glad_glCopyTexImage2D = NULL; static void GLAD_API_PTR glad_debug_impl_glCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) { _pre_call_gl_callback("glCopyTexImage2D", (GLADapiproc) glad_glCopyTexImage2D, 8, target, level, internalformat, x, y, width, height, border); glad_glCopyTexImage2D(target, level, internalformat, x, y, width, height, border); _post_call_gl_callback(NULL, "glCopyTexImage2D", (GLADapiproc) glad_glCopyTexImage2D, 8, target, level, internalformat, x, y, width, height, border); } PFNGLCOPYTEXIMAGE2DPROC glad_debug_glCopyTexImage2D = glad_debug_impl_glCopyTexImage2D; PFNGLCOPYTEXSUBIMAGE1DPROC glad_glCopyTexSubImage1D = NULL; static void GLAD_API_PTR glad_debug_impl_glCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) { _pre_call_gl_callback("glCopyTexSubImage1D", (GLADapiproc) glad_glCopyTexSubImage1D, 6, target, level, xoffset, x, y, width); glad_glCopyTexSubImage1D(target, level, xoffset, x, y, width); _post_call_gl_callback(NULL, "glCopyTexSubImage1D", (GLADapiproc) glad_glCopyTexSubImage1D, 6, target, level, xoffset, x, y, width); } PFNGLCOPYTEXSUBIMAGE1DPROC glad_debug_glCopyTexSubImage1D = glad_debug_impl_glCopyTexSubImage1D; PFNGLCOPYTEXSUBIMAGE2DPROC glad_glCopyTexSubImage2D = NULL; static void GLAD_API_PTR glad_debug_impl_glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) { _pre_call_gl_callback("glCopyTexSubImage2D", (GLADapiproc) glad_glCopyTexSubImage2D, 8, target, level, xoffset, yoffset, x, y, width, height); glad_glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); _post_call_gl_callback(NULL, "glCopyTexSubImage2D", (GLADapiproc) glad_glCopyTexSubImage2D, 8, target, level, xoffset, yoffset, x, y, width, height); } PFNGLCOPYTEXSUBIMAGE2DPROC glad_debug_glCopyTexSubImage2D = glad_debug_impl_glCopyTexSubImage2D; PFNGLCOPYTEXSUBIMAGE3DPROC glad_glCopyTexSubImage3D = NULL; static void GLAD_API_PTR glad_debug_impl_glCopyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) { _pre_call_gl_callback("glCopyTexSubImage3D", (GLADapiproc) glad_glCopyTexSubImage3D, 9, target, level, xoffset, yoffset, zoffset, x, y, width, height); glad_glCopyTexSubImage3D(target, level, xoffset, yoffset, zoffset, x, y, width, height); _post_call_gl_callback(NULL, "glCopyTexSubImage3D", (GLADapiproc) glad_glCopyTexSubImage3D, 9, target, level, xoffset, yoffset, zoffset, x, y, width, height); } PFNGLCOPYTEXSUBIMAGE3DPROC glad_debug_glCopyTexSubImage3D = glad_debug_impl_glCopyTexSubImage3D; PFNGLCREATEPROGRAMPROC glad_glCreateProgram = NULL; static GLuint GLAD_API_PTR glad_debug_impl_glCreateProgram(void) { GLuint ret; _pre_call_gl_callback("glCreateProgram", (GLADapiproc) glad_glCreateProgram, 0); ret = glad_glCreateProgram(); _post_call_gl_callback((void*) &ret, "glCreateProgram", (GLADapiproc) glad_glCreateProgram, 0); return ret; } PFNGLCREATEPROGRAMPROC glad_debug_glCreateProgram = glad_debug_impl_glCreateProgram; PFNGLCREATESHADERPROC glad_glCreateShader = NULL; static GLuint GLAD_API_PTR glad_debug_impl_glCreateShader(GLenum type) { GLuint ret; _pre_call_gl_callback("glCreateShader", (GLADapiproc) glad_glCreateShader, 1, type); ret = glad_glCreateShader(type); _post_call_gl_callback((void*) &ret, "glCreateShader", (GLADapiproc) glad_glCreateShader, 1, type); return ret; } PFNGLCREATESHADERPROC glad_debug_glCreateShader = glad_debug_impl_glCreateShader; PFNGLCULLFACEPROC glad_glCullFace = NULL; static void GLAD_API_PTR glad_debug_impl_glCullFace(GLenum mode) { _pre_call_gl_callback("glCullFace", (GLADapiproc) glad_glCullFace, 1, mode); glad_glCullFace(mode); _post_call_gl_callback(NULL, "glCullFace", (GLADapiproc) glad_glCullFace, 1, mode); } PFNGLCULLFACEPROC glad_debug_glCullFace = glad_debug_impl_glCullFace; PFNGLDEBUGMESSAGECALLBACKPROC glad_glDebugMessageCallback = NULL; static void GLAD_API_PTR glad_debug_impl_glDebugMessageCallback(GLDEBUGPROC callback, const void * userParam) { _pre_call_gl_callback("glDebugMessageCallback", (GLADapiproc) glad_glDebugMessageCallback, 2, callback, userParam); glad_glDebugMessageCallback(callback, userParam); _post_call_gl_callback(NULL, "glDebugMessageCallback", (GLADapiproc) glad_glDebugMessageCallback, 2, callback, userParam); } PFNGLDEBUGMESSAGECALLBACKPROC glad_debug_glDebugMessageCallback = glad_debug_impl_glDebugMessageCallback; PFNGLDEBUGMESSAGECONTROLPROC glad_glDebugMessageControl = NULL; static void GLAD_API_PTR glad_debug_impl_glDebugMessageControl(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint * ids, GLboolean enabled) { _pre_call_gl_callback("glDebugMessageControl", (GLADapiproc) glad_glDebugMessageControl, 6, source, type, severity, count, ids, enabled); glad_glDebugMessageControl(source, type, severity, count, ids, enabled); _post_call_gl_callback(NULL, "glDebugMessageControl", (GLADapiproc) glad_glDebugMessageControl, 6, source, type, severity, count, ids, enabled); } PFNGLDEBUGMESSAGECONTROLPROC glad_debug_glDebugMessageControl = glad_debug_impl_glDebugMessageControl; PFNGLDEBUGMESSAGEINSERTPROC glad_glDebugMessageInsert = NULL; static void GLAD_API_PTR glad_debug_impl_glDebugMessageInsert(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar * buf) { _pre_call_gl_callback("glDebugMessageInsert", (GLADapiproc) glad_glDebugMessageInsert, 6, source, type, id, severity, length, buf); glad_glDebugMessageInsert(source, type, id, severity, length, buf); _post_call_gl_callback(NULL, "glDebugMessageInsert", (GLADapiproc) glad_glDebugMessageInsert, 6, source, type, id, severity, length, buf); } PFNGLDEBUGMESSAGEINSERTPROC glad_debug_glDebugMessageInsert = glad_debug_impl_glDebugMessageInsert; PFNGLDELETEBUFFERSPROC glad_glDeleteBuffers = NULL; static void GLAD_API_PTR glad_debug_impl_glDeleteBuffers(GLsizei n, const GLuint * buffers) { _pre_call_gl_callback("glDeleteBuffers", (GLADapiproc) glad_glDeleteBuffers, 2, n, buffers); glad_glDeleteBuffers(n, buffers); _post_call_gl_callback(NULL, "glDeleteBuffers", (GLADapiproc) glad_glDeleteBuffers, 2, n, buffers); } PFNGLDELETEBUFFERSPROC glad_debug_glDeleteBuffers = glad_debug_impl_glDeleteBuffers; PFNGLDELETEFRAMEBUFFERSPROC glad_glDeleteFramebuffers = NULL; static void GLAD_API_PTR glad_debug_impl_glDeleteFramebuffers(GLsizei n, const GLuint * framebuffers) { _pre_call_gl_callback("glDeleteFramebuffers", (GLADapiproc) glad_glDeleteFramebuffers, 2, n, framebuffers); glad_glDeleteFramebuffers(n, framebuffers); _post_call_gl_callback(NULL, "glDeleteFramebuffers", (GLADapiproc) glad_glDeleteFramebuffers, 2, n, framebuffers); } PFNGLDELETEFRAMEBUFFERSPROC glad_debug_glDeleteFramebuffers = glad_debug_impl_glDeleteFramebuffers; PFNGLDELETELISTSPROC glad_glDeleteLists = NULL; static void GLAD_API_PTR glad_debug_impl_glDeleteLists(GLuint list, GLsizei range) { _pre_call_gl_callback("glDeleteLists", (GLADapiproc) glad_glDeleteLists, 2, list, range); glad_glDeleteLists(list, range); _post_call_gl_callback(NULL, "glDeleteLists", (GLADapiproc) glad_glDeleteLists, 2, list, range); } PFNGLDELETELISTSPROC glad_debug_glDeleteLists = glad_debug_impl_glDeleteLists; PFNGLDELETEPROGRAMPROC glad_glDeleteProgram = NULL; static void GLAD_API_PTR glad_debug_impl_glDeleteProgram(GLuint program) { _pre_call_gl_callback("glDeleteProgram", (GLADapiproc) glad_glDeleteProgram, 1, program); glad_glDeleteProgram(program); _post_call_gl_callback(NULL, "glDeleteProgram", (GLADapiproc) glad_glDeleteProgram, 1, program); } PFNGLDELETEPROGRAMPROC glad_debug_glDeleteProgram = glad_debug_impl_glDeleteProgram; PFNGLDELETEQUERIESPROC glad_glDeleteQueries = NULL; static void GLAD_API_PTR glad_debug_impl_glDeleteQueries(GLsizei n, const GLuint * ids) { _pre_call_gl_callback("glDeleteQueries", (GLADapiproc) glad_glDeleteQueries, 2, n, ids); glad_glDeleteQueries(n, ids); _post_call_gl_callback(NULL, "glDeleteQueries", (GLADapiproc) glad_glDeleteQueries, 2, n, ids); } PFNGLDELETEQUERIESPROC glad_debug_glDeleteQueries = glad_debug_impl_glDeleteQueries; PFNGLDELETERENDERBUFFERSPROC glad_glDeleteRenderbuffers = NULL; static void GLAD_API_PTR glad_debug_impl_glDeleteRenderbuffers(GLsizei n, const GLuint * renderbuffers) { _pre_call_gl_callback("glDeleteRenderbuffers", (GLADapiproc) glad_glDeleteRenderbuffers, 2, n, renderbuffers); glad_glDeleteRenderbuffers(n, renderbuffers); _post_call_gl_callback(NULL, "glDeleteRenderbuffers", (GLADapiproc) glad_glDeleteRenderbuffers, 2, n, renderbuffers); } PFNGLDELETERENDERBUFFERSPROC glad_debug_glDeleteRenderbuffers = glad_debug_impl_glDeleteRenderbuffers; PFNGLDELETESHADERPROC glad_glDeleteShader = NULL; static void GLAD_API_PTR glad_debug_impl_glDeleteShader(GLuint shader) { _pre_call_gl_callback("glDeleteShader", (GLADapiproc) glad_glDeleteShader, 1, shader); glad_glDeleteShader(shader); _post_call_gl_callback(NULL, "glDeleteShader", (GLADapiproc) glad_glDeleteShader, 1, shader); } PFNGLDELETESHADERPROC glad_debug_glDeleteShader = glad_debug_impl_glDeleteShader; PFNGLDELETETEXTURESPROC glad_glDeleteTextures = NULL; static void GLAD_API_PTR glad_debug_impl_glDeleteTextures(GLsizei n, const GLuint * textures) { _pre_call_gl_callback("glDeleteTextures", (GLADapiproc) glad_glDeleteTextures, 2, n, textures); glad_glDeleteTextures(n, textures); _post_call_gl_callback(NULL, "glDeleteTextures", (GLADapiproc) glad_glDeleteTextures, 2, n, textures); } PFNGLDELETETEXTURESPROC glad_debug_glDeleteTextures = glad_debug_impl_glDeleteTextures; PFNGLDELETEVERTEXARRAYSPROC glad_glDeleteVertexArrays = NULL; static void GLAD_API_PTR glad_debug_impl_glDeleteVertexArrays(GLsizei n, const GLuint * arrays) { _pre_call_gl_callback("glDeleteVertexArrays", (GLADapiproc) glad_glDeleteVertexArrays, 2, n, arrays); glad_glDeleteVertexArrays(n, arrays); _post_call_gl_callback(NULL, "glDeleteVertexArrays", (GLADapiproc) glad_glDeleteVertexArrays, 2, n, arrays); } PFNGLDELETEVERTEXARRAYSPROC glad_debug_glDeleteVertexArrays = glad_debug_impl_glDeleteVertexArrays; PFNGLDEPTHFUNCPROC glad_glDepthFunc = NULL; static void GLAD_API_PTR glad_debug_impl_glDepthFunc(GLenum func) { _pre_call_gl_callback("glDepthFunc", (GLADapiproc) glad_glDepthFunc, 1, func); glad_glDepthFunc(func); _post_call_gl_callback(NULL, "glDepthFunc", (GLADapiproc) glad_glDepthFunc, 1, func); } PFNGLDEPTHFUNCPROC glad_debug_glDepthFunc = glad_debug_impl_glDepthFunc; PFNGLDEPTHMASKPROC glad_glDepthMask = NULL; static void GLAD_API_PTR glad_debug_impl_glDepthMask(GLboolean flag) { _pre_call_gl_callback("glDepthMask", (GLADapiproc) glad_glDepthMask, 1, flag); glad_glDepthMask(flag); _post_call_gl_callback(NULL, "glDepthMask", (GLADapiproc) glad_glDepthMask, 1, flag); } PFNGLDEPTHMASKPROC glad_debug_glDepthMask = glad_debug_impl_glDepthMask; PFNGLDEPTHRANGEPROC glad_glDepthRange = NULL; static void GLAD_API_PTR glad_debug_impl_glDepthRange(GLdouble n, GLdouble f) { _pre_call_gl_callback("glDepthRange", (GLADapiproc) glad_glDepthRange, 2, n, f); glad_glDepthRange(n, f); _post_call_gl_callback(NULL, "glDepthRange", (GLADapiproc) glad_glDepthRange, 2, n, f); } PFNGLDEPTHRANGEPROC glad_debug_glDepthRange = glad_debug_impl_glDepthRange; PFNGLDETACHSHADERPROC glad_glDetachShader = NULL; static void GLAD_API_PTR glad_debug_impl_glDetachShader(GLuint program, GLuint shader) { _pre_call_gl_callback("glDetachShader", (GLADapiproc) glad_glDetachShader, 2, program, shader); glad_glDetachShader(program, shader); _post_call_gl_callback(NULL, "glDetachShader", (GLADapiproc) glad_glDetachShader, 2, program, shader); } PFNGLDETACHSHADERPROC glad_debug_glDetachShader = glad_debug_impl_glDetachShader; PFNGLDISABLEPROC glad_glDisable = NULL; static void GLAD_API_PTR glad_debug_impl_glDisable(GLenum cap) { _pre_call_gl_callback("glDisable", (GLADapiproc) glad_glDisable, 1, cap); glad_glDisable(cap); _post_call_gl_callback(NULL, "glDisable", (GLADapiproc) glad_glDisable, 1, cap); } PFNGLDISABLEPROC glad_debug_glDisable = glad_debug_impl_glDisable; PFNGLDISABLECLIENTSTATEPROC glad_glDisableClientState = NULL; static void GLAD_API_PTR glad_debug_impl_glDisableClientState(GLenum array) { _pre_call_gl_callback("glDisableClientState", (GLADapiproc) glad_glDisableClientState, 1, array); glad_glDisableClientState(array); _post_call_gl_callback(NULL, "glDisableClientState", (GLADapiproc) glad_glDisableClientState, 1, array); } PFNGLDISABLECLIENTSTATEPROC glad_debug_glDisableClientState = glad_debug_impl_glDisableClientState; PFNGLDISABLEVERTEXATTRIBARRAYPROC glad_glDisableVertexAttribArray = NULL; static void GLAD_API_PTR glad_debug_impl_glDisableVertexAttribArray(GLuint index) { _pre_call_gl_callback("glDisableVertexAttribArray", (GLADapiproc) glad_glDisableVertexAttribArray, 1, index); glad_glDisableVertexAttribArray(index); _post_call_gl_callback(NULL, "glDisableVertexAttribArray", (GLADapiproc) glad_glDisableVertexAttribArray, 1, index); } PFNGLDISABLEVERTEXATTRIBARRAYPROC glad_debug_glDisableVertexAttribArray = glad_debug_impl_glDisableVertexAttribArray; PFNGLDISABLEIPROC glad_glDisablei = NULL; static void GLAD_API_PTR glad_debug_impl_glDisablei(GLenum target, GLuint index) { _pre_call_gl_callback("glDisablei", (GLADapiproc) glad_glDisablei, 2, target, index); glad_glDisablei(target, index); _post_call_gl_callback(NULL, "glDisablei", (GLADapiproc) glad_glDisablei, 2, target, index); } PFNGLDISABLEIPROC glad_debug_glDisablei = glad_debug_impl_glDisablei; PFNGLDRAWARRAYSPROC glad_glDrawArrays = NULL; static void GLAD_API_PTR glad_debug_impl_glDrawArrays(GLenum mode, GLint first, GLsizei count) { _pre_call_gl_callback("glDrawArrays", (GLADapiproc) glad_glDrawArrays, 3, mode, first, count); glad_glDrawArrays(mode, first, count); _post_call_gl_callback(NULL, "glDrawArrays", (GLADapiproc) glad_glDrawArrays, 3, mode, first, count); } PFNGLDRAWARRAYSPROC glad_debug_glDrawArrays = glad_debug_impl_glDrawArrays; PFNGLDRAWARRAYSINSTANCEDPROC glad_glDrawArraysInstanced = NULL; static void GLAD_API_PTR glad_debug_impl_glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) { _pre_call_gl_callback("glDrawArraysInstanced", (GLADapiproc) glad_glDrawArraysInstanced, 4, mode, first, count, instancecount); glad_glDrawArraysInstanced(mode, first, count, instancecount); _post_call_gl_callback(NULL, "glDrawArraysInstanced", (GLADapiproc) glad_glDrawArraysInstanced, 4, mode, first, count, instancecount); } PFNGLDRAWARRAYSINSTANCEDPROC glad_debug_glDrawArraysInstanced = glad_debug_impl_glDrawArraysInstanced; PFNGLDRAWBUFFERPROC glad_glDrawBuffer = NULL; static void GLAD_API_PTR glad_debug_impl_glDrawBuffer(GLenum buf) { _pre_call_gl_callback("glDrawBuffer", (GLADapiproc) glad_glDrawBuffer, 1, buf); glad_glDrawBuffer(buf); _post_call_gl_callback(NULL, "glDrawBuffer", (GLADapiproc) glad_glDrawBuffer, 1, buf); } PFNGLDRAWBUFFERPROC glad_debug_glDrawBuffer = glad_debug_impl_glDrawBuffer; PFNGLDRAWBUFFERSPROC glad_glDrawBuffers = NULL; static void GLAD_API_PTR glad_debug_impl_glDrawBuffers(GLsizei n, const GLenum * bufs) { _pre_call_gl_callback("glDrawBuffers", (GLADapiproc) glad_glDrawBuffers, 2, n, bufs); glad_glDrawBuffers(n, bufs); _post_call_gl_callback(NULL, "glDrawBuffers", (GLADapiproc) glad_glDrawBuffers, 2, n, bufs); } PFNGLDRAWBUFFERSPROC glad_debug_glDrawBuffers = glad_debug_impl_glDrawBuffers; PFNGLDRAWELEMENTSPROC glad_glDrawElements = NULL; static void GLAD_API_PTR glad_debug_impl_glDrawElements(GLenum mode, GLsizei count, GLenum type, const void * indices) { _pre_call_gl_callback("glDrawElements", (GLADapiproc) glad_glDrawElements, 4, mode, count, type, indices); glad_glDrawElements(mode, count, type, indices); _post_call_gl_callback(NULL, "glDrawElements", (GLADapiproc) glad_glDrawElements, 4, mode, count, type, indices); } PFNGLDRAWELEMENTSPROC glad_debug_glDrawElements = glad_debug_impl_glDrawElements; PFNGLDRAWELEMENTSINSTANCEDPROC glad_glDrawElementsInstanced = NULL; static void GLAD_API_PTR glad_debug_impl_glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei instancecount) { _pre_call_gl_callback("glDrawElementsInstanced", (GLADapiproc) glad_glDrawElementsInstanced, 5, mode, count, type, indices, instancecount); glad_glDrawElementsInstanced(mode, count, type, indices, instancecount); _post_call_gl_callback(NULL, "glDrawElementsInstanced", (GLADapiproc) glad_glDrawElementsInstanced, 5, mode, count, type, indices, instancecount); } PFNGLDRAWELEMENTSINSTANCEDPROC glad_debug_glDrawElementsInstanced = glad_debug_impl_glDrawElementsInstanced; PFNGLDRAWPIXELSPROC glad_glDrawPixels = NULL; static void GLAD_API_PTR glad_debug_impl_glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels) { _pre_call_gl_callback("glDrawPixels", (GLADapiproc) glad_glDrawPixels, 5, width, height, format, type, pixels); glad_glDrawPixels(width, height, format, type, pixels); _post_call_gl_callback(NULL, "glDrawPixels", (GLADapiproc) glad_glDrawPixels, 5, width, height, format, type, pixels); } PFNGLDRAWPIXELSPROC glad_debug_glDrawPixels = glad_debug_impl_glDrawPixels; PFNGLDRAWRANGEELEMENTSPROC glad_glDrawRangeElements = NULL; static void GLAD_API_PTR glad_debug_impl_glDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void * indices) { _pre_call_gl_callback("glDrawRangeElements", (GLADapiproc) glad_glDrawRangeElements, 6, mode, start, end, count, type, indices); glad_glDrawRangeElements(mode, start, end, count, type, indices); _post_call_gl_callback(NULL, "glDrawRangeElements", (GLADapiproc) glad_glDrawRangeElements, 6, mode, start, end, count, type, indices); } PFNGLDRAWRANGEELEMENTSPROC glad_debug_glDrawRangeElements = glad_debug_impl_glDrawRangeElements; PFNGLEDGEFLAGPROC glad_glEdgeFlag = NULL; static void GLAD_API_PTR glad_debug_impl_glEdgeFlag(GLboolean flag) { _pre_call_gl_callback("glEdgeFlag", (GLADapiproc) glad_glEdgeFlag, 1, flag); glad_glEdgeFlag(flag); _post_call_gl_callback(NULL, "glEdgeFlag", (GLADapiproc) glad_glEdgeFlag, 1, flag); } PFNGLEDGEFLAGPROC glad_debug_glEdgeFlag = glad_debug_impl_glEdgeFlag; PFNGLEDGEFLAGPOINTERPROC glad_glEdgeFlagPointer = NULL; static void GLAD_API_PTR glad_debug_impl_glEdgeFlagPointer(GLsizei stride, const void * pointer) { _pre_call_gl_callback("glEdgeFlagPointer", (GLADapiproc) glad_glEdgeFlagPointer, 2, stride, pointer); glad_glEdgeFlagPointer(stride, pointer); _post_call_gl_callback(NULL, "glEdgeFlagPointer", (GLADapiproc) glad_glEdgeFlagPointer, 2, stride, pointer); } PFNGLEDGEFLAGPOINTERPROC glad_debug_glEdgeFlagPointer = glad_debug_impl_glEdgeFlagPointer; PFNGLEDGEFLAGVPROC glad_glEdgeFlagv = NULL; static void GLAD_API_PTR glad_debug_impl_glEdgeFlagv(const GLboolean * flag) { _pre_call_gl_callback("glEdgeFlagv", (GLADapiproc) glad_glEdgeFlagv, 1, flag); glad_glEdgeFlagv(flag); _post_call_gl_callback(NULL, "glEdgeFlagv", (GLADapiproc) glad_glEdgeFlagv, 1, flag); } PFNGLEDGEFLAGVPROC glad_debug_glEdgeFlagv = glad_debug_impl_glEdgeFlagv; PFNGLENABLEPROC glad_glEnable = NULL; static void GLAD_API_PTR glad_debug_impl_glEnable(GLenum cap) { _pre_call_gl_callback("glEnable", (GLADapiproc) glad_glEnable, 1, cap); glad_glEnable(cap); _post_call_gl_callback(NULL, "glEnable", (GLADapiproc) glad_glEnable, 1, cap); } PFNGLENABLEPROC glad_debug_glEnable = glad_debug_impl_glEnable; PFNGLENABLECLIENTSTATEPROC glad_glEnableClientState = NULL; static void GLAD_API_PTR glad_debug_impl_glEnableClientState(GLenum array) { _pre_call_gl_callback("glEnableClientState", (GLADapiproc) glad_glEnableClientState, 1, array); glad_glEnableClientState(array); _post_call_gl_callback(NULL, "glEnableClientState", (GLADapiproc) glad_glEnableClientState, 1, array); } PFNGLENABLECLIENTSTATEPROC glad_debug_glEnableClientState = glad_debug_impl_glEnableClientState; PFNGLENABLEVERTEXATTRIBARRAYPROC glad_glEnableVertexAttribArray = NULL; static void GLAD_API_PTR glad_debug_impl_glEnableVertexAttribArray(GLuint index) { _pre_call_gl_callback("glEnableVertexAttribArray", (GLADapiproc) glad_glEnableVertexAttribArray, 1, index); glad_glEnableVertexAttribArray(index); _post_call_gl_callback(NULL, "glEnableVertexAttribArray", (GLADapiproc) glad_glEnableVertexAttribArray, 1, index); } PFNGLENABLEVERTEXATTRIBARRAYPROC glad_debug_glEnableVertexAttribArray = glad_debug_impl_glEnableVertexAttribArray; PFNGLENABLEIPROC glad_glEnablei = NULL; static void GLAD_API_PTR glad_debug_impl_glEnablei(GLenum target, GLuint index) { _pre_call_gl_callback("glEnablei", (GLADapiproc) glad_glEnablei, 2, target, index); glad_glEnablei(target, index); _post_call_gl_callback(NULL, "glEnablei", (GLADapiproc) glad_glEnablei, 2, target, index); } PFNGLENABLEIPROC glad_debug_glEnablei = glad_debug_impl_glEnablei; PFNGLENDPROC glad_glEnd = NULL; static void GLAD_API_PTR glad_debug_impl_glEnd(void) { _pre_call_gl_callback("glEnd", (GLADapiproc) glad_glEnd, 0); glad_glEnd(); _post_call_gl_callback(NULL, "glEnd", (GLADapiproc) glad_glEnd, 0); } PFNGLENDPROC glad_debug_glEnd = glad_debug_impl_glEnd; PFNGLENDCONDITIONALRENDERPROC glad_glEndConditionalRender = NULL; static void GLAD_API_PTR glad_debug_impl_glEndConditionalRender(void) { _pre_call_gl_callback("glEndConditionalRender", (GLADapiproc) glad_glEndConditionalRender, 0); glad_glEndConditionalRender(); _post_call_gl_callback(NULL, "glEndConditionalRender", (GLADapiproc) glad_glEndConditionalRender, 0); } PFNGLENDCONDITIONALRENDERPROC glad_debug_glEndConditionalRender = glad_debug_impl_glEndConditionalRender; PFNGLENDLISTPROC glad_glEndList = NULL; static void GLAD_API_PTR glad_debug_impl_glEndList(void) { _pre_call_gl_callback("glEndList", (GLADapiproc) glad_glEndList, 0); glad_glEndList(); _post_call_gl_callback(NULL, "glEndList", (GLADapiproc) glad_glEndList, 0); } PFNGLENDLISTPROC glad_debug_glEndList = glad_debug_impl_glEndList; PFNGLENDQUERYPROC glad_glEndQuery = NULL; static void GLAD_API_PTR glad_debug_impl_glEndQuery(GLenum target) { _pre_call_gl_callback("glEndQuery", (GLADapiproc) glad_glEndQuery, 1, target); glad_glEndQuery(target); _post_call_gl_callback(NULL, "glEndQuery", (GLADapiproc) glad_glEndQuery, 1, target); } PFNGLENDQUERYPROC glad_debug_glEndQuery = glad_debug_impl_glEndQuery; PFNGLENDTRANSFORMFEEDBACKPROC glad_glEndTransformFeedback = NULL; static void GLAD_API_PTR glad_debug_impl_glEndTransformFeedback(void) { _pre_call_gl_callback("glEndTransformFeedback", (GLADapiproc) glad_glEndTransformFeedback, 0); glad_glEndTransformFeedback(); _post_call_gl_callback(NULL, "glEndTransformFeedback", (GLADapiproc) glad_glEndTransformFeedback, 0); } PFNGLENDTRANSFORMFEEDBACKPROC glad_debug_glEndTransformFeedback = glad_debug_impl_glEndTransformFeedback; PFNGLEVALCOORD1DPROC glad_glEvalCoord1d = NULL; static void GLAD_API_PTR glad_debug_impl_glEvalCoord1d(GLdouble u) { _pre_call_gl_callback("glEvalCoord1d", (GLADapiproc) glad_glEvalCoord1d, 1, u); glad_glEvalCoord1d(u); _post_call_gl_callback(NULL, "glEvalCoord1d", (GLADapiproc) glad_glEvalCoord1d, 1, u); } PFNGLEVALCOORD1DPROC glad_debug_glEvalCoord1d = glad_debug_impl_glEvalCoord1d; PFNGLEVALCOORD1DVPROC glad_glEvalCoord1dv = NULL; static void GLAD_API_PTR glad_debug_impl_glEvalCoord1dv(const GLdouble * u) { _pre_call_gl_callback("glEvalCoord1dv", (GLADapiproc) glad_glEvalCoord1dv, 1, u); glad_glEvalCoord1dv(u); _post_call_gl_callback(NULL, "glEvalCoord1dv", (GLADapiproc) glad_glEvalCoord1dv, 1, u); } PFNGLEVALCOORD1DVPROC glad_debug_glEvalCoord1dv = glad_debug_impl_glEvalCoord1dv; PFNGLEVALCOORD1FPROC glad_glEvalCoord1f = NULL; static void GLAD_API_PTR glad_debug_impl_glEvalCoord1f(GLfloat u) { _pre_call_gl_callback("glEvalCoord1f", (GLADapiproc) glad_glEvalCoord1f, 1, u); glad_glEvalCoord1f(u); _post_call_gl_callback(NULL, "glEvalCoord1f", (GLADapiproc) glad_glEvalCoord1f, 1, u); } PFNGLEVALCOORD1FPROC glad_debug_glEvalCoord1f = glad_debug_impl_glEvalCoord1f; PFNGLEVALCOORD1FVPROC glad_glEvalCoord1fv = NULL; static void GLAD_API_PTR glad_debug_impl_glEvalCoord1fv(const GLfloat * u) { _pre_call_gl_callback("glEvalCoord1fv", (GLADapiproc) glad_glEvalCoord1fv, 1, u); glad_glEvalCoord1fv(u); _post_call_gl_callback(NULL, "glEvalCoord1fv", (GLADapiproc) glad_glEvalCoord1fv, 1, u); } PFNGLEVALCOORD1FVPROC glad_debug_glEvalCoord1fv = glad_debug_impl_glEvalCoord1fv; PFNGLEVALCOORD2DPROC glad_glEvalCoord2d = NULL; static void GLAD_API_PTR glad_debug_impl_glEvalCoord2d(GLdouble u, GLdouble v) { _pre_call_gl_callback("glEvalCoord2d", (GLADapiproc) glad_glEvalCoord2d, 2, u, v); glad_glEvalCoord2d(u, v); _post_call_gl_callback(NULL, "glEvalCoord2d", (GLADapiproc) glad_glEvalCoord2d, 2, u, v); } PFNGLEVALCOORD2DPROC glad_debug_glEvalCoord2d = glad_debug_impl_glEvalCoord2d; PFNGLEVALCOORD2DVPROC glad_glEvalCoord2dv = NULL; static void GLAD_API_PTR glad_debug_impl_glEvalCoord2dv(const GLdouble * u) { _pre_call_gl_callback("glEvalCoord2dv", (GLADapiproc) glad_glEvalCoord2dv, 1, u); glad_glEvalCoord2dv(u); _post_call_gl_callback(NULL, "glEvalCoord2dv", (GLADapiproc) glad_glEvalCoord2dv, 1, u); } PFNGLEVALCOORD2DVPROC glad_debug_glEvalCoord2dv = glad_debug_impl_glEvalCoord2dv; PFNGLEVALCOORD2FPROC glad_glEvalCoord2f = NULL; static void GLAD_API_PTR glad_debug_impl_glEvalCoord2f(GLfloat u, GLfloat v) { _pre_call_gl_callback("glEvalCoord2f", (GLADapiproc) glad_glEvalCoord2f, 2, u, v); glad_glEvalCoord2f(u, v); _post_call_gl_callback(NULL, "glEvalCoord2f", (GLADapiproc) glad_glEvalCoord2f, 2, u, v); } PFNGLEVALCOORD2FPROC glad_debug_glEvalCoord2f = glad_debug_impl_glEvalCoord2f; PFNGLEVALCOORD2FVPROC glad_glEvalCoord2fv = NULL; static void GLAD_API_PTR glad_debug_impl_glEvalCoord2fv(const GLfloat * u) { _pre_call_gl_callback("glEvalCoord2fv", (GLADapiproc) glad_glEvalCoord2fv, 1, u); glad_glEvalCoord2fv(u); _post_call_gl_callback(NULL, "glEvalCoord2fv", (GLADapiproc) glad_glEvalCoord2fv, 1, u); } PFNGLEVALCOORD2FVPROC glad_debug_glEvalCoord2fv = glad_debug_impl_glEvalCoord2fv; PFNGLEVALMESH1PROC glad_glEvalMesh1 = NULL; static void GLAD_API_PTR glad_debug_impl_glEvalMesh1(GLenum mode, GLint i1, GLint i2) { _pre_call_gl_callback("glEvalMesh1", (GLADapiproc) glad_glEvalMesh1, 3, mode, i1, i2); glad_glEvalMesh1(mode, i1, i2); _post_call_gl_callback(NULL, "glEvalMesh1", (GLADapiproc) glad_glEvalMesh1, 3, mode, i1, i2); } PFNGLEVALMESH1PROC glad_debug_glEvalMesh1 = glad_debug_impl_glEvalMesh1; PFNGLEVALMESH2PROC glad_glEvalMesh2 = NULL; static void GLAD_API_PTR glad_debug_impl_glEvalMesh2(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2) { _pre_call_gl_callback("glEvalMesh2", (GLADapiproc) glad_glEvalMesh2, 5, mode, i1, i2, j1, j2); glad_glEvalMesh2(mode, i1, i2, j1, j2); _post_call_gl_callback(NULL, "glEvalMesh2", (GLADapiproc) glad_glEvalMesh2, 5, mode, i1, i2, j1, j2); } PFNGLEVALMESH2PROC glad_debug_glEvalMesh2 = glad_debug_impl_glEvalMesh2; PFNGLEVALPOINT1PROC glad_glEvalPoint1 = NULL; static void GLAD_API_PTR glad_debug_impl_glEvalPoint1(GLint i) { _pre_call_gl_callback("glEvalPoint1", (GLADapiproc) glad_glEvalPoint1, 1, i); glad_glEvalPoint1(i); _post_call_gl_callback(NULL, "glEvalPoint1", (GLADapiproc) glad_glEvalPoint1, 1, i); } PFNGLEVALPOINT1PROC glad_debug_glEvalPoint1 = glad_debug_impl_glEvalPoint1; PFNGLEVALPOINT2PROC glad_glEvalPoint2 = NULL; static void GLAD_API_PTR glad_debug_impl_glEvalPoint2(GLint i, GLint j) { _pre_call_gl_callback("glEvalPoint2", (GLADapiproc) glad_glEvalPoint2, 2, i, j); glad_glEvalPoint2(i, j); _post_call_gl_callback(NULL, "glEvalPoint2", (GLADapiproc) glad_glEvalPoint2, 2, i, j); } PFNGLEVALPOINT2PROC glad_debug_glEvalPoint2 = glad_debug_impl_glEvalPoint2; PFNGLFEEDBACKBUFFERPROC glad_glFeedbackBuffer = NULL; static void GLAD_API_PTR glad_debug_impl_glFeedbackBuffer(GLsizei size, GLenum type, GLfloat * buffer) { _pre_call_gl_callback("glFeedbackBuffer", (GLADapiproc) glad_glFeedbackBuffer, 3, size, type, buffer); glad_glFeedbackBuffer(size, type, buffer); _post_call_gl_callback(NULL, "glFeedbackBuffer", (GLADapiproc) glad_glFeedbackBuffer, 3, size, type, buffer); } PFNGLFEEDBACKBUFFERPROC glad_debug_glFeedbackBuffer = glad_debug_impl_glFeedbackBuffer; PFNGLFINISHPROC glad_glFinish = NULL; static void GLAD_API_PTR glad_debug_impl_glFinish(void) { _pre_call_gl_callback("glFinish", (GLADapiproc) glad_glFinish, 0); glad_glFinish(); _post_call_gl_callback(NULL, "glFinish", (GLADapiproc) glad_glFinish, 0); } PFNGLFINISHPROC glad_debug_glFinish = glad_debug_impl_glFinish; PFNGLFLUSHPROC glad_glFlush = NULL; static void GLAD_API_PTR glad_debug_impl_glFlush(void) { _pre_call_gl_callback("glFlush", (GLADapiproc) glad_glFlush, 0); glad_glFlush(); _post_call_gl_callback(NULL, "glFlush", (GLADapiproc) glad_glFlush, 0); } PFNGLFLUSHPROC glad_debug_glFlush = glad_debug_impl_glFlush; PFNGLFLUSHMAPPEDBUFFERRANGEPROC glad_glFlushMappedBufferRange = NULL; static void GLAD_API_PTR glad_debug_impl_glFlushMappedBufferRange(GLenum target, GLintptr offset, GLsizeiptr length) { _pre_call_gl_callback("glFlushMappedBufferRange", (GLADapiproc) glad_glFlushMappedBufferRange, 3, target, offset, length); glad_glFlushMappedBufferRange(target, offset, length); _post_call_gl_callback(NULL, "glFlushMappedBufferRange", (GLADapiproc) glad_glFlushMappedBufferRange, 3, target, offset, length); } PFNGLFLUSHMAPPEDBUFFERRANGEPROC glad_debug_glFlushMappedBufferRange = glad_debug_impl_glFlushMappedBufferRange; PFNGLFOGCOORDPOINTERPROC glad_glFogCoordPointer = NULL; static void GLAD_API_PTR glad_debug_impl_glFogCoordPointer(GLenum type, GLsizei stride, const void * pointer) { _pre_call_gl_callback("glFogCoordPointer", (GLADapiproc) glad_glFogCoordPointer, 3, type, stride, pointer); glad_glFogCoordPointer(type, stride, pointer); _post_call_gl_callback(NULL, "glFogCoordPointer", (GLADapiproc) glad_glFogCoordPointer, 3, type, stride, pointer); } PFNGLFOGCOORDPOINTERPROC glad_debug_glFogCoordPointer = glad_debug_impl_glFogCoordPointer; PFNGLFOGCOORDDPROC glad_glFogCoordd = NULL; static void GLAD_API_PTR glad_debug_impl_glFogCoordd(GLdouble coord) { _pre_call_gl_callback("glFogCoordd", (GLADapiproc) glad_glFogCoordd, 1, coord); glad_glFogCoordd(coord); _post_call_gl_callback(NULL, "glFogCoordd", (GLADapiproc) glad_glFogCoordd, 1, coord); } PFNGLFOGCOORDDPROC glad_debug_glFogCoordd = glad_debug_impl_glFogCoordd; PFNGLFOGCOORDDVPROC glad_glFogCoorddv = NULL; static void GLAD_API_PTR glad_debug_impl_glFogCoorddv(const GLdouble * coord) { _pre_call_gl_callback("glFogCoorddv", (GLADapiproc) glad_glFogCoorddv, 1, coord); glad_glFogCoorddv(coord); _post_call_gl_callback(NULL, "glFogCoorddv", (GLADapiproc) glad_glFogCoorddv, 1, coord); } PFNGLFOGCOORDDVPROC glad_debug_glFogCoorddv = glad_debug_impl_glFogCoorddv; PFNGLFOGCOORDFPROC glad_glFogCoordf = NULL; static void GLAD_API_PTR glad_debug_impl_glFogCoordf(GLfloat coord) { _pre_call_gl_callback("glFogCoordf", (GLADapiproc) glad_glFogCoordf, 1, coord); glad_glFogCoordf(coord); _post_call_gl_callback(NULL, "glFogCoordf", (GLADapiproc) glad_glFogCoordf, 1, coord); } PFNGLFOGCOORDFPROC glad_debug_glFogCoordf = glad_debug_impl_glFogCoordf; PFNGLFOGCOORDFVPROC glad_glFogCoordfv = NULL; static void GLAD_API_PTR glad_debug_impl_glFogCoordfv(const GLfloat * coord) { _pre_call_gl_callback("glFogCoordfv", (GLADapiproc) glad_glFogCoordfv, 1, coord); glad_glFogCoordfv(coord); _post_call_gl_callback(NULL, "glFogCoordfv", (GLADapiproc) glad_glFogCoordfv, 1, coord); } PFNGLFOGCOORDFVPROC glad_debug_glFogCoordfv = glad_debug_impl_glFogCoordfv; PFNGLFOGFPROC glad_glFogf = NULL; static void GLAD_API_PTR glad_debug_impl_glFogf(GLenum pname, GLfloat param) { _pre_call_gl_callback("glFogf", (GLADapiproc) glad_glFogf, 2, pname, param); glad_glFogf(pname, param); _post_call_gl_callback(NULL, "glFogf", (GLADapiproc) glad_glFogf, 2, pname, param); } PFNGLFOGFPROC glad_debug_glFogf = glad_debug_impl_glFogf; PFNGLFOGFVPROC glad_glFogfv = NULL; static void GLAD_API_PTR glad_debug_impl_glFogfv(GLenum pname, const GLfloat * params) { _pre_call_gl_callback("glFogfv", (GLADapiproc) glad_glFogfv, 2, pname, params); glad_glFogfv(pname, params); _post_call_gl_callback(NULL, "glFogfv", (GLADapiproc) glad_glFogfv, 2, pname, params); } PFNGLFOGFVPROC glad_debug_glFogfv = glad_debug_impl_glFogfv; PFNGLFOGIPROC glad_glFogi = NULL; static void GLAD_API_PTR glad_debug_impl_glFogi(GLenum pname, GLint param) { _pre_call_gl_callback("glFogi", (GLADapiproc) glad_glFogi, 2, pname, param); glad_glFogi(pname, param); _post_call_gl_callback(NULL, "glFogi", (GLADapiproc) glad_glFogi, 2, pname, param); } PFNGLFOGIPROC glad_debug_glFogi = glad_debug_impl_glFogi; PFNGLFOGIVPROC glad_glFogiv = NULL; static void GLAD_API_PTR glad_debug_impl_glFogiv(GLenum pname, const GLint * params) { _pre_call_gl_callback("glFogiv", (GLADapiproc) glad_glFogiv, 2, pname, params); glad_glFogiv(pname, params); _post_call_gl_callback(NULL, "glFogiv", (GLADapiproc) glad_glFogiv, 2, pname, params); } PFNGLFOGIVPROC glad_debug_glFogiv = glad_debug_impl_glFogiv; PFNGLFRAMEBUFFERRENDERBUFFERPROC glad_glFramebufferRenderbuffer = NULL; static void GLAD_API_PTR glad_debug_impl_glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) { _pre_call_gl_callback("glFramebufferRenderbuffer", (GLADapiproc) glad_glFramebufferRenderbuffer, 4, target, attachment, renderbuffertarget, renderbuffer); glad_glFramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer); _post_call_gl_callback(NULL, "glFramebufferRenderbuffer", (GLADapiproc) glad_glFramebufferRenderbuffer, 4, target, attachment, renderbuffertarget, renderbuffer); } PFNGLFRAMEBUFFERRENDERBUFFERPROC glad_debug_glFramebufferRenderbuffer = glad_debug_impl_glFramebufferRenderbuffer; PFNGLFRAMEBUFFERTEXTURE1DPROC glad_glFramebufferTexture1D = NULL; static void GLAD_API_PTR glad_debug_impl_glFramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) { _pre_call_gl_callback("glFramebufferTexture1D", (GLADapiproc) glad_glFramebufferTexture1D, 5, target, attachment, textarget, texture, level); glad_glFramebufferTexture1D(target, attachment, textarget, texture, level); _post_call_gl_callback(NULL, "glFramebufferTexture1D", (GLADapiproc) glad_glFramebufferTexture1D, 5, target, attachment, textarget, texture, level); } PFNGLFRAMEBUFFERTEXTURE1DPROC glad_debug_glFramebufferTexture1D = glad_debug_impl_glFramebufferTexture1D; PFNGLFRAMEBUFFERTEXTURE2DPROC glad_glFramebufferTexture2D = NULL; static void GLAD_API_PTR glad_debug_impl_glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) { _pre_call_gl_callback("glFramebufferTexture2D", (GLADapiproc) glad_glFramebufferTexture2D, 5, target, attachment, textarget, texture, level); glad_glFramebufferTexture2D(target, attachment, textarget, texture, level); _post_call_gl_callback(NULL, "glFramebufferTexture2D", (GLADapiproc) glad_glFramebufferTexture2D, 5, target, attachment, textarget, texture, level); } PFNGLFRAMEBUFFERTEXTURE2DPROC glad_debug_glFramebufferTexture2D = glad_debug_impl_glFramebufferTexture2D; PFNGLFRAMEBUFFERTEXTURE3DPROC glad_glFramebufferTexture3D = NULL; static void GLAD_API_PTR glad_debug_impl_glFramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset) { _pre_call_gl_callback("glFramebufferTexture3D", (GLADapiproc) glad_glFramebufferTexture3D, 6, target, attachment, textarget, texture, level, zoffset); glad_glFramebufferTexture3D(target, attachment, textarget, texture, level, zoffset); _post_call_gl_callback(NULL, "glFramebufferTexture3D", (GLADapiproc) glad_glFramebufferTexture3D, 6, target, attachment, textarget, texture, level, zoffset); } PFNGLFRAMEBUFFERTEXTURE3DPROC glad_debug_glFramebufferTexture3D = glad_debug_impl_glFramebufferTexture3D; PFNGLFRAMEBUFFERTEXTURELAYERPROC glad_glFramebufferTextureLayer = NULL; static void GLAD_API_PTR glad_debug_impl_glFramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer) { _pre_call_gl_callback("glFramebufferTextureLayer", (GLADapiproc) glad_glFramebufferTextureLayer, 5, target, attachment, texture, level, layer); glad_glFramebufferTextureLayer(target, attachment, texture, level, layer); _post_call_gl_callback(NULL, "glFramebufferTextureLayer", (GLADapiproc) glad_glFramebufferTextureLayer, 5, target, attachment, texture, level, layer); } PFNGLFRAMEBUFFERTEXTURELAYERPROC glad_debug_glFramebufferTextureLayer = glad_debug_impl_glFramebufferTextureLayer; PFNGLFRONTFACEPROC glad_glFrontFace = NULL; static void GLAD_API_PTR glad_debug_impl_glFrontFace(GLenum mode) { _pre_call_gl_callback("glFrontFace", (GLADapiproc) glad_glFrontFace, 1, mode); glad_glFrontFace(mode); _post_call_gl_callback(NULL, "glFrontFace", (GLADapiproc) glad_glFrontFace, 1, mode); } PFNGLFRONTFACEPROC glad_debug_glFrontFace = glad_debug_impl_glFrontFace; PFNGLFRUSTUMPROC glad_glFrustum = NULL; static void GLAD_API_PTR glad_debug_impl_glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) { _pre_call_gl_callback("glFrustum", (GLADapiproc) glad_glFrustum, 6, left, right, bottom, top, zNear, zFar); glad_glFrustum(left, right, bottom, top, zNear, zFar); _post_call_gl_callback(NULL, "glFrustum", (GLADapiproc) glad_glFrustum, 6, left, right, bottom, top, zNear, zFar); } PFNGLFRUSTUMPROC glad_debug_glFrustum = glad_debug_impl_glFrustum; PFNGLGENBUFFERSPROC glad_glGenBuffers = NULL; static void GLAD_API_PTR glad_debug_impl_glGenBuffers(GLsizei n, GLuint * buffers) { _pre_call_gl_callback("glGenBuffers", (GLADapiproc) glad_glGenBuffers, 2, n, buffers); glad_glGenBuffers(n, buffers); _post_call_gl_callback(NULL, "glGenBuffers", (GLADapiproc) glad_glGenBuffers, 2, n, buffers); } PFNGLGENBUFFERSPROC glad_debug_glGenBuffers = glad_debug_impl_glGenBuffers; PFNGLGENFRAMEBUFFERSPROC glad_glGenFramebuffers = NULL; static void GLAD_API_PTR glad_debug_impl_glGenFramebuffers(GLsizei n, GLuint * framebuffers) { _pre_call_gl_callback("glGenFramebuffers", (GLADapiproc) glad_glGenFramebuffers, 2, n, framebuffers); glad_glGenFramebuffers(n, framebuffers); _post_call_gl_callback(NULL, "glGenFramebuffers", (GLADapiproc) glad_glGenFramebuffers, 2, n, framebuffers); } PFNGLGENFRAMEBUFFERSPROC glad_debug_glGenFramebuffers = glad_debug_impl_glGenFramebuffers; PFNGLGENLISTSPROC glad_glGenLists = NULL; static GLuint GLAD_API_PTR glad_debug_impl_glGenLists(GLsizei range) { GLuint ret; _pre_call_gl_callback("glGenLists", (GLADapiproc) glad_glGenLists, 1, range); ret = glad_glGenLists(range); _post_call_gl_callback((void*) &ret, "glGenLists", (GLADapiproc) glad_glGenLists, 1, range); return ret; } PFNGLGENLISTSPROC glad_debug_glGenLists = glad_debug_impl_glGenLists; PFNGLGENQUERIESPROC glad_glGenQueries = NULL; static void GLAD_API_PTR glad_debug_impl_glGenQueries(GLsizei n, GLuint * ids) { _pre_call_gl_callback("glGenQueries", (GLADapiproc) glad_glGenQueries, 2, n, ids); glad_glGenQueries(n, ids); _post_call_gl_callback(NULL, "glGenQueries", (GLADapiproc) glad_glGenQueries, 2, n, ids); } PFNGLGENQUERIESPROC glad_debug_glGenQueries = glad_debug_impl_glGenQueries; PFNGLGENRENDERBUFFERSPROC glad_glGenRenderbuffers = NULL; static void GLAD_API_PTR glad_debug_impl_glGenRenderbuffers(GLsizei n, GLuint * renderbuffers) { _pre_call_gl_callback("glGenRenderbuffers", (GLADapiproc) glad_glGenRenderbuffers, 2, n, renderbuffers); glad_glGenRenderbuffers(n, renderbuffers); _post_call_gl_callback(NULL, "glGenRenderbuffers", (GLADapiproc) glad_glGenRenderbuffers, 2, n, renderbuffers); } PFNGLGENRENDERBUFFERSPROC glad_debug_glGenRenderbuffers = glad_debug_impl_glGenRenderbuffers; PFNGLGENTEXTURESPROC glad_glGenTextures = NULL; static void GLAD_API_PTR glad_debug_impl_glGenTextures(GLsizei n, GLuint * textures) { _pre_call_gl_callback("glGenTextures", (GLADapiproc) glad_glGenTextures, 2, n, textures); glad_glGenTextures(n, textures); _post_call_gl_callback(NULL, "glGenTextures", (GLADapiproc) glad_glGenTextures, 2, n, textures); } PFNGLGENTEXTURESPROC glad_debug_glGenTextures = glad_debug_impl_glGenTextures; PFNGLGENVERTEXARRAYSPROC glad_glGenVertexArrays = NULL; static void GLAD_API_PTR glad_debug_impl_glGenVertexArrays(GLsizei n, GLuint * arrays) { _pre_call_gl_callback("glGenVertexArrays", (GLADapiproc) glad_glGenVertexArrays, 2, n, arrays); glad_glGenVertexArrays(n, arrays); _post_call_gl_callback(NULL, "glGenVertexArrays", (GLADapiproc) glad_glGenVertexArrays, 2, n, arrays); } PFNGLGENVERTEXARRAYSPROC glad_debug_glGenVertexArrays = glad_debug_impl_glGenVertexArrays; PFNGLGENERATEMIPMAPPROC glad_glGenerateMipmap = NULL; static void GLAD_API_PTR glad_debug_impl_glGenerateMipmap(GLenum target) { _pre_call_gl_callback("glGenerateMipmap", (GLADapiproc) glad_glGenerateMipmap, 1, target); glad_glGenerateMipmap(target); _post_call_gl_callback(NULL, "glGenerateMipmap", (GLADapiproc) glad_glGenerateMipmap, 1, target); } PFNGLGENERATEMIPMAPPROC glad_debug_glGenerateMipmap = glad_debug_impl_glGenerateMipmap; PFNGLGETACTIVEATTRIBPROC glad_glGetActiveAttrib = NULL; static void GLAD_API_PTR glad_debug_impl_glGetActiveAttrib(GLuint program, GLuint index, GLsizei bufSize, GLsizei * length, GLint * size, GLenum * type, GLchar * name) { _pre_call_gl_callback("glGetActiveAttrib", (GLADapiproc) glad_glGetActiveAttrib, 7, program, index, bufSize, length, size, type, name); glad_glGetActiveAttrib(program, index, bufSize, length, size, type, name); _post_call_gl_callback(NULL, "glGetActiveAttrib", (GLADapiproc) glad_glGetActiveAttrib, 7, program, index, bufSize, length, size, type, name); } PFNGLGETACTIVEATTRIBPROC glad_debug_glGetActiveAttrib = glad_debug_impl_glGetActiveAttrib; PFNGLGETACTIVEUNIFORMPROC glad_glGetActiveUniform = NULL; static void GLAD_API_PTR glad_debug_impl_glGetActiveUniform(GLuint program, GLuint index, GLsizei bufSize, GLsizei * length, GLint * size, GLenum * type, GLchar * name) { _pre_call_gl_callback("glGetActiveUniform", (GLADapiproc) glad_glGetActiveUniform, 7, program, index, bufSize, length, size, type, name); glad_glGetActiveUniform(program, index, bufSize, length, size, type, name); _post_call_gl_callback(NULL, "glGetActiveUniform", (GLADapiproc) glad_glGetActiveUniform, 7, program, index, bufSize, length, size, type, name); } PFNGLGETACTIVEUNIFORMPROC glad_debug_glGetActiveUniform = glad_debug_impl_glGetActiveUniform; PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC glad_glGetActiveUniformBlockName = NULL; static void GLAD_API_PTR glad_debug_impl_glGetActiveUniformBlockName(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei * length, GLchar * uniformBlockName) { _pre_call_gl_callback("glGetActiveUniformBlockName", (GLADapiproc) glad_glGetActiveUniformBlockName, 5, program, uniformBlockIndex, bufSize, length, uniformBlockName); glad_glGetActiveUniformBlockName(program, uniformBlockIndex, bufSize, length, uniformBlockName); _post_call_gl_callback(NULL, "glGetActiveUniformBlockName", (GLADapiproc) glad_glGetActiveUniformBlockName, 5, program, uniformBlockIndex, bufSize, length, uniformBlockName); } PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC glad_debug_glGetActiveUniformBlockName = glad_debug_impl_glGetActiveUniformBlockName; PFNGLGETACTIVEUNIFORMBLOCKIVPROC glad_glGetActiveUniformBlockiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetActiveUniformBlockiv(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetActiveUniformBlockiv", (GLADapiproc) glad_glGetActiveUniformBlockiv, 4, program, uniformBlockIndex, pname, params); glad_glGetActiveUniformBlockiv(program, uniformBlockIndex, pname, params); _post_call_gl_callback(NULL, "glGetActiveUniformBlockiv", (GLADapiproc) glad_glGetActiveUniformBlockiv, 4, program, uniformBlockIndex, pname, params); } PFNGLGETACTIVEUNIFORMBLOCKIVPROC glad_debug_glGetActiveUniformBlockiv = glad_debug_impl_glGetActiveUniformBlockiv; PFNGLGETACTIVEUNIFORMNAMEPROC glad_glGetActiveUniformName = NULL; static void GLAD_API_PTR glad_debug_impl_glGetActiveUniformName(GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei * length, GLchar * uniformName) { _pre_call_gl_callback("glGetActiveUniformName", (GLADapiproc) glad_glGetActiveUniformName, 5, program, uniformIndex, bufSize, length, uniformName); glad_glGetActiveUniformName(program, uniformIndex, bufSize, length, uniformName); _post_call_gl_callback(NULL, "glGetActiveUniformName", (GLADapiproc) glad_glGetActiveUniformName, 5, program, uniformIndex, bufSize, length, uniformName); } PFNGLGETACTIVEUNIFORMNAMEPROC glad_debug_glGetActiveUniformName = glad_debug_impl_glGetActiveUniformName; PFNGLGETACTIVEUNIFORMSIVPROC glad_glGetActiveUniformsiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetActiveUniformsiv(GLuint program, GLsizei uniformCount, const GLuint * uniformIndices, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetActiveUniformsiv", (GLADapiproc) glad_glGetActiveUniformsiv, 5, program, uniformCount, uniformIndices, pname, params); glad_glGetActiveUniformsiv(program, uniformCount, uniformIndices, pname, params); _post_call_gl_callback(NULL, "glGetActiveUniformsiv", (GLADapiproc) glad_glGetActiveUniformsiv, 5, program, uniformCount, uniformIndices, pname, params); } PFNGLGETACTIVEUNIFORMSIVPROC glad_debug_glGetActiveUniformsiv = glad_debug_impl_glGetActiveUniformsiv; PFNGLGETATTACHEDSHADERSPROC glad_glGetAttachedShaders = NULL; static void GLAD_API_PTR glad_debug_impl_glGetAttachedShaders(GLuint program, GLsizei maxCount, GLsizei * count, GLuint * shaders) { _pre_call_gl_callback("glGetAttachedShaders", (GLADapiproc) glad_glGetAttachedShaders, 4, program, maxCount, count, shaders); glad_glGetAttachedShaders(program, maxCount, count, shaders); _post_call_gl_callback(NULL, "glGetAttachedShaders", (GLADapiproc) glad_glGetAttachedShaders, 4, program, maxCount, count, shaders); } PFNGLGETATTACHEDSHADERSPROC glad_debug_glGetAttachedShaders = glad_debug_impl_glGetAttachedShaders; PFNGLGETATTRIBLOCATIONPROC glad_glGetAttribLocation = NULL; static GLint GLAD_API_PTR glad_debug_impl_glGetAttribLocation(GLuint program, const GLchar * name) { GLint ret; _pre_call_gl_callback("glGetAttribLocation", (GLADapiproc) glad_glGetAttribLocation, 2, program, name); ret = glad_glGetAttribLocation(program, name); _post_call_gl_callback((void*) &ret, "glGetAttribLocation", (GLADapiproc) glad_glGetAttribLocation, 2, program, name); return ret; } PFNGLGETATTRIBLOCATIONPROC glad_debug_glGetAttribLocation = glad_debug_impl_glGetAttribLocation; PFNGLGETBOOLEANI_VPROC glad_glGetBooleani_v = NULL; static void GLAD_API_PTR glad_debug_impl_glGetBooleani_v(GLenum target, GLuint index, GLboolean * data) { _pre_call_gl_callback("glGetBooleani_v", (GLADapiproc) glad_glGetBooleani_v, 3, target, index, data); glad_glGetBooleani_v(target, index, data); _post_call_gl_callback(NULL, "glGetBooleani_v", (GLADapiproc) glad_glGetBooleani_v, 3, target, index, data); } PFNGLGETBOOLEANI_VPROC glad_debug_glGetBooleani_v = glad_debug_impl_glGetBooleani_v; PFNGLGETBOOLEANVPROC glad_glGetBooleanv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetBooleanv(GLenum pname, GLboolean * data) { _pre_call_gl_callback("glGetBooleanv", (GLADapiproc) glad_glGetBooleanv, 2, pname, data); glad_glGetBooleanv(pname, data); _post_call_gl_callback(NULL, "glGetBooleanv", (GLADapiproc) glad_glGetBooleanv, 2, pname, data); } PFNGLGETBOOLEANVPROC glad_debug_glGetBooleanv = glad_debug_impl_glGetBooleanv; PFNGLGETBUFFERPARAMETERIVPROC glad_glGetBufferParameteriv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetBufferParameteriv(GLenum target, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetBufferParameteriv", (GLADapiproc) glad_glGetBufferParameteriv, 3, target, pname, params); glad_glGetBufferParameteriv(target, pname, params); _post_call_gl_callback(NULL, "glGetBufferParameteriv", (GLADapiproc) glad_glGetBufferParameteriv, 3, target, pname, params); } PFNGLGETBUFFERPARAMETERIVPROC glad_debug_glGetBufferParameteriv = glad_debug_impl_glGetBufferParameteriv; PFNGLGETBUFFERPOINTERVPROC glad_glGetBufferPointerv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetBufferPointerv(GLenum target, GLenum pname, void ** params) { _pre_call_gl_callback("glGetBufferPointerv", (GLADapiproc) glad_glGetBufferPointerv, 3, target, pname, params); glad_glGetBufferPointerv(target, pname, params); _post_call_gl_callback(NULL, "glGetBufferPointerv", (GLADapiproc) glad_glGetBufferPointerv, 3, target, pname, params); } PFNGLGETBUFFERPOINTERVPROC glad_debug_glGetBufferPointerv = glad_debug_impl_glGetBufferPointerv; PFNGLGETBUFFERSUBDATAPROC glad_glGetBufferSubData = NULL; static void GLAD_API_PTR glad_debug_impl_glGetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void * data) { _pre_call_gl_callback("glGetBufferSubData", (GLADapiproc) glad_glGetBufferSubData, 4, target, offset, size, data); glad_glGetBufferSubData(target, offset, size, data); _post_call_gl_callback(NULL, "glGetBufferSubData", (GLADapiproc) glad_glGetBufferSubData, 4, target, offset, size, data); } PFNGLGETBUFFERSUBDATAPROC glad_debug_glGetBufferSubData = glad_debug_impl_glGetBufferSubData; PFNGLGETCLIPPLANEPROC glad_glGetClipPlane = NULL; static void GLAD_API_PTR glad_debug_impl_glGetClipPlane(GLenum plane, GLdouble * equation) { _pre_call_gl_callback("glGetClipPlane", (GLADapiproc) glad_glGetClipPlane, 2, plane, equation); glad_glGetClipPlane(plane, equation); _post_call_gl_callback(NULL, "glGetClipPlane", (GLADapiproc) glad_glGetClipPlane, 2, plane, equation); } PFNGLGETCLIPPLANEPROC glad_debug_glGetClipPlane = glad_debug_impl_glGetClipPlane; PFNGLGETCOMPRESSEDTEXIMAGEPROC glad_glGetCompressedTexImage = NULL; static void GLAD_API_PTR glad_debug_impl_glGetCompressedTexImage(GLenum target, GLint level, void * img) { _pre_call_gl_callback("glGetCompressedTexImage", (GLADapiproc) glad_glGetCompressedTexImage, 3, target, level, img); glad_glGetCompressedTexImage(target, level, img); _post_call_gl_callback(NULL, "glGetCompressedTexImage", (GLADapiproc) glad_glGetCompressedTexImage, 3, target, level, img); } PFNGLGETCOMPRESSEDTEXIMAGEPROC glad_debug_glGetCompressedTexImage = glad_debug_impl_glGetCompressedTexImage; PFNGLGETDEBUGMESSAGELOGPROC glad_glGetDebugMessageLog = NULL; static GLuint GLAD_API_PTR glad_debug_impl_glGetDebugMessageLog(GLuint count, GLsizei bufSize, GLenum * sources, GLenum * types, GLuint * ids, GLenum * severities, GLsizei * lengths, GLchar * messageLog) { GLuint ret; _pre_call_gl_callback("glGetDebugMessageLog", (GLADapiproc) glad_glGetDebugMessageLog, 8, count, bufSize, sources, types, ids, severities, lengths, messageLog); ret = glad_glGetDebugMessageLog(count, bufSize, sources, types, ids, severities, lengths, messageLog); _post_call_gl_callback((void*) &ret, "glGetDebugMessageLog", (GLADapiproc) glad_glGetDebugMessageLog, 8, count, bufSize, sources, types, ids, severities, lengths, messageLog); return ret; } PFNGLGETDEBUGMESSAGELOGPROC glad_debug_glGetDebugMessageLog = glad_debug_impl_glGetDebugMessageLog; PFNGLGETDOUBLEVPROC glad_glGetDoublev = NULL; static void GLAD_API_PTR glad_debug_impl_glGetDoublev(GLenum pname, GLdouble * data) { _pre_call_gl_callback("glGetDoublev", (GLADapiproc) glad_glGetDoublev, 2, pname, data); glad_glGetDoublev(pname, data); _post_call_gl_callback(NULL, "glGetDoublev", (GLADapiproc) glad_glGetDoublev, 2, pname, data); } PFNGLGETDOUBLEVPROC glad_debug_glGetDoublev = glad_debug_impl_glGetDoublev; PFNGLGETERRORPROC glad_glGetError = NULL; static GLenum GLAD_API_PTR glad_debug_impl_glGetError(void) { GLenum ret; _pre_call_gl_callback("glGetError", (GLADapiproc) glad_glGetError, 0); ret = glad_glGetError(); _post_call_gl_callback((void*) &ret, "glGetError", (GLADapiproc) glad_glGetError, 0); return ret; } PFNGLGETERRORPROC glad_debug_glGetError = glad_debug_impl_glGetError; PFNGLGETFLOATVPROC glad_glGetFloatv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetFloatv(GLenum pname, GLfloat * data) { _pre_call_gl_callback("glGetFloatv", (GLADapiproc) glad_glGetFloatv, 2, pname, data); glad_glGetFloatv(pname, data); _post_call_gl_callback(NULL, "glGetFloatv", (GLADapiproc) glad_glGetFloatv, 2, pname, data); } PFNGLGETFLOATVPROC glad_debug_glGetFloatv = glad_debug_impl_glGetFloatv; PFNGLGETFRAGDATALOCATIONPROC glad_glGetFragDataLocation = NULL; static GLint GLAD_API_PTR glad_debug_impl_glGetFragDataLocation(GLuint program, const GLchar * name) { GLint ret; _pre_call_gl_callback("glGetFragDataLocation", (GLADapiproc) glad_glGetFragDataLocation, 2, program, name); ret = glad_glGetFragDataLocation(program, name); _post_call_gl_callback((void*) &ret, "glGetFragDataLocation", (GLADapiproc) glad_glGetFragDataLocation, 2, program, name); return ret; } PFNGLGETFRAGDATALOCATIONPROC glad_debug_glGetFragDataLocation = glad_debug_impl_glGetFragDataLocation; PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC glad_glGetFramebufferAttachmentParameteriv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetFramebufferAttachmentParameteriv(GLenum target, GLenum attachment, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetFramebufferAttachmentParameteriv", (GLADapiproc) glad_glGetFramebufferAttachmentParameteriv, 4, target, attachment, pname, params); glad_glGetFramebufferAttachmentParameteriv(target, attachment, pname, params); _post_call_gl_callback(NULL, "glGetFramebufferAttachmentParameteriv", (GLADapiproc) glad_glGetFramebufferAttachmentParameteriv, 4, target, attachment, pname, params); } PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC glad_debug_glGetFramebufferAttachmentParameteriv = glad_debug_impl_glGetFramebufferAttachmentParameteriv; PFNGLGETGRAPHICSRESETSTATUSARBPROC glad_glGetGraphicsResetStatusARB = NULL; static GLenum GLAD_API_PTR glad_debug_impl_glGetGraphicsResetStatusARB(void) { GLenum ret; _pre_call_gl_callback("glGetGraphicsResetStatusARB", (GLADapiproc) glad_glGetGraphicsResetStatusARB, 0); ret = glad_glGetGraphicsResetStatusARB(); _post_call_gl_callback((void*) &ret, "glGetGraphicsResetStatusARB", (GLADapiproc) glad_glGetGraphicsResetStatusARB, 0); return ret; } PFNGLGETGRAPHICSRESETSTATUSARBPROC glad_debug_glGetGraphicsResetStatusARB = glad_debug_impl_glGetGraphicsResetStatusARB; PFNGLGETINTEGERI_VPROC glad_glGetIntegeri_v = NULL; static void GLAD_API_PTR glad_debug_impl_glGetIntegeri_v(GLenum target, GLuint index, GLint * data) { _pre_call_gl_callback("glGetIntegeri_v", (GLADapiproc) glad_glGetIntegeri_v, 3, target, index, data); glad_glGetIntegeri_v(target, index, data); _post_call_gl_callback(NULL, "glGetIntegeri_v", (GLADapiproc) glad_glGetIntegeri_v, 3, target, index, data); } PFNGLGETINTEGERI_VPROC glad_debug_glGetIntegeri_v = glad_debug_impl_glGetIntegeri_v; PFNGLGETINTEGERVPROC glad_glGetIntegerv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetIntegerv(GLenum pname, GLint * data) { _pre_call_gl_callback("glGetIntegerv", (GLADapiproc) glad_glGetIntegerv, 2, pname, data); glad_glGetIntegerv(pname, data); _post_call_gl_callback(NULL, "glGetIntegerv", (GLADapiproc) glad_glGetIntegerv, 2, pname, data); } PFNGLGETINTEGERVPROC glad_debug_glGetIntegerv = glad_debug_impl_glGetIntegerv; PFNGLGETLIGHTFVPROC glad_glGetLightfv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetLightfv(GLenum light, GLenum pname, GLfloat * params) { _pre_call_gl_callback("glGetLightfv", (GLADapiproc) glad_glGetLightfv, 3, light, pname, params); glad_glGetLightfv(light, pname, params); _post_call_gl_callback(NULL, "glGetLightfv", (GLADapiproc) glad_glGetLightfv, 3, light, pname, params); } PFNGLGETLIGHTFVPROC glad_debug_glGetLightfv = glad_debug_impl_glGetLightfv; PFNGLGETLIGHTIVPROC glad_glGetLightiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetLightiv(GLenum light, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetLightiv", (GLADapiproc) glad_glGetLightiv, 3, light, pname, params); glad_glGetLightiv(light, pname, params); _post_call_gl_callback(NULL, "glGetLightiv", (GLADapiproc) glad_glGetLightiv, 3, light, pname, params); } PFNGLGETLIGHTIVPROC glad_debug_glGetLightiv = glad_debug_impl_glGetLightiv; PFNGLGETMAPDVPROC glad_glGetMapdv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetMapdv(GLenum target, GLenum query, GLdouble * v) { _pre_call_gl_callback("glGetMapdv", (GLADapiproc) glad_glGetMapdv, 3, target, query, v); glad_glGetMapdv(target, query, v); _post_call_gl_callback(NULL, "glGetMapdv", (GLADapiproc) glad_glGetMapdv, 3, target, query, v); } PFNGLGETMAPDVPROC glad_debug_glGetMapdv = glad_debug_impl_glGetMapdv; PFNGLGETMAPFVPROC glad_glGetMapfv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetMapfv(GLenum target, GLenum query, GLfloat * v) { _pre_call_gl_callback("glGetMapfv", (GLADapiproc) glad_glGetMapfv, 3, target, query, v); glad_glGetMapfv(target, query, v); _post_call_gl_callback(NULL, "glGetMapfv", (GLADapiproc) glad_glGetMapfv, 3, target, query, v); } PFNGLGETMAPFVPROC glad_debug_glGetMapfv = glad_debug_impl_glGetMapfv; PFNGLGETMAPIVPROC glad_glGetMapiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetMapiv(GLenum target, GLenum query, GLint * v) { _pre_call_gl_callback("glGetMapiv", (GLADapiproc) glad_glGetMapiv, 3, target, query, v); glad_glGetMapiv(target, query, v); _post_call_gl_callback(NULL, "glGetMapiv", (GLADapiproc) glad_glGetMapiv, 3, target, query, v); } PFNGLGETMAPIVPROC glad_debug_glGetMapiv = glad_debug_impl_glGetMapiv; PFNGLGETMATERIALFVPROC glad_glGetMaterialfv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetMaterialfv(GLenum face, GLenum pname, GLfloat * params) { _pre_call_gl_callback("glGetMaterialfv", (GLADapiproc) glad_glGetMaterialfv, 3, face, pname, params); glad_glGetMaterialfv(face, pname, params); _post_call_gl_callback(NULL, "glGetMaterialfv", (GLADapiproc) glad_glGetMaterialfv, 3, face, pname, params); } PFNGLGETMATERIALFVPROC glad_debug_glGetMaterialfv = glad_debug_impl_glGetMaterialfv; PFNGLGETMATERIALIVPROC glad_glGetMaterialiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetMaterialiv(GLenum face, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetMaterialiv", (GLADapiproc) glad_glGetMaterialiv, 3, face, pname, params); glad_glGetMaterialiv(face, pname, params); _post_call_gl_callback(NULL, "glGetMaterialiv", (GLADapiproc) glad_glGetMaterialiv, 3, face, pname, params); } PFNGLGETMATERIALIVPROC glad_debug_glGetMaterialiv = glad_debug_impl_glGetMaterialiv; PFNGLGETOBJECTLABELPROC glad_glGetObjectLabel = NULL; static void GLAD_API_PTR glad_debug_impl_glGetObjectLabel(GLenum identifier, GLuint name, GLsizei bufSize, GLsizei * length, GLchar * label) { _pre_call_gl_callback("glGetObjectLabel", (GLADapiproc) glad_glGetObjectLabel, 5, identifier, name, bufSize, length, label); glad_glGetObjectLabel(identifier, name, bufSize, length, label); _post_call_gl_callback(NULL, "glGetObjectLabel", (GLADapiproc) glad_glGetObjectLabel, 5, identifier, name, bufSize, length, label); } PFNGLGETOBJECTLABELPROC glad_debug_glGetObjectLabel = glad_debug_impl_glGetObjectLabel; PFNGLGETOBJECTPTRLABELPROC glad_glGetObjectPtrLabel = NULL; static void GLAD_API_PTR glad_debug_impl_glGetObjectPtrLabel(const void * ptr, GLsizei bufSize, GLsizei * length, GLchar * label) { _pre_call_gl_callback("glGetObjectPtrLabel", (GLADapiproc) glad_glGetObjectPtrLabel, 4, ptr, bufSize, length, label); glad_glGetObjectPtrLabel(ptr, bufSize, length, label); _post_call_gl_callback(NULL, "glGetObjectPtrLabel", (GLADapiproc) glad_glGetObjectPtrLabel, 4, ptr, bufSize, length, label); } PFNGLGETOBJECTPTRLABELPROC glad_debug_glGetObjectPtrLabel = glad_debug_impl_glGetObjectPtrLabel; PFNGLGETPIXELMAPFVPROC glad_glGetPixelMapfv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetPixelMapfv(GLenum map, GLfloat * values) { _pre_call_gl_callback("glGetPixelMapfv", (GLADapiproc) glad_glGetPixelMapfv, 2, map, values); glad_glGetPixelMapfv(map, values); _post_call_gl_callback(NULL, "glGetPixelMapfv", (GLADapiproc) glad_glGetPixelMapfv, 2, map, values); } PFNGLGETPIXELMAPFVPROC glad_debug_glGetPixelMapfv = glad_debug_impl_glGetPixelMapfv; PFNGLGETPIXELMAPUIVPROC glad_glGetPixelMapuiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetPixelMapuiv(GLenum map, GLuint * values) { _pre_call_gl_callback("glGetPixelMapuiv", (GLADapiproc) glad_glGetPixelMapuiv, 2, map, values); glad_glGetPixelMapuiv(map, values); _post_call_gl_callback(NULL, "glGetPixelMapuiv", (GLADapiproc) glad_glGetPixelMapuiv, 2, map, values); } PFNGLGETPIXELMAPUIVPROC glad_debug_glGetPixelMapuiv = glad_debug_impl_glGetPixelMapuiv; PFNGLGETPIXELMAPUSVPROC glad_glGetPixelMapusv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetPixelMapusv(GLenum map, GLushort * values) { _pre_call_gl_callback("glGetPixelMapusv", (GLADapiproc) glad_glGetPixelMapusv, 2, map, values); glad_glGetPixelMapusv(map, values); _post_call_gl_callback(NULL, "glGetPixelMapusv", (GLADapiproc) glad_glGetPixelMapusv, 2, map, values); } PFNGLGETPIXELMAPUSVPROC glad_debug_glGetPixelMapusv = glad_debug_impl_glGetPixelMapusv; PFNGLGETPOINTERVPROC glad_glGetPointerv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetPointerv(GLenum pname, void ** params) { _pre_call_gl_callback("glGetPointerv", (GLADapiproc) glad_glGetPointerv, 2, pname, params); glad_glGetPointerv(pname, params); _post_call_gl_callback(NULL, "glGetPointerv", (GLADapiproc) glad_glGetPointerv, 2, pname, params); } PFNGLGETPOINTERVPROC glad_debug_glGetPointerv = glad_debug_impl_glGetPointerv; PFNGLGETPOLYGONSTIPPLEPROC glad_glGetPolygonStipple = NULL; static void GLAD_API_PTR glad_debug_impl_glGetPolygonStipple(GLubyte * mask) { _pre_call_gl_callback("glGetPolygonStipple", (GLADapiproc) glad_glGetPolygonStipple, 1, mask); glad_glGetPolygonStipple(mask); _post_call_gl_callback(NULL, "glGetPolygonStipple", (GLADapiproc) glad_glGetPolygonStipple, 1, mask); } PFNGLGETPOLYGONSTIPPLEPROC glad_debug_glGetPolygonStipple = glad_debug_impl_glGetPolygonStipple; PFNGLGETPROGRAMINFOLOGPROC glad_glGetProgramInfoLog = NULL; static void GLAD_API_PTR glad_debug_impl_glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei * length, GLchar * infoLog) { _pre_call_gl_callback("glGetProgramInfoLog", (GLADapiproc) glad_glGetProgramInfoLog, 4, program, bufSize, length, infoLog); glad_glGetProgramInfoLog(program, bufSize, length, infoLog); _post_call_gl_callback(NULL, "glGetProgramInfoLog", (GLADapiproc) glad_glGetProgramInfoLog, 4, program, bufSize, length, infoLog); } PFNGLGETPROGRAMINFOLOGPROC glad_debug_glGetProgramInfoLog = glad_debug_impl_glGetProgramInfoLog; PFNGLGETPROGRAMIVPROC glad_glGetProgramiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetProgramiv(GLuint program, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetProgramiv", (GLADapiproc) glad_glGetProgramiv, 3, program, pname, params); glad_glGetProgramiv(program, pname, params); _post_call_gl_callback(NULL, "glGetProgramiv", (GLADapiproc) glad_glGetProgramiv, 3, program, pname, params); } PFNGLGETPROGRAMIVPROC glad_debug_glGetProgramiv = glad_debug_impl_glGetProgramiv; PFNGLGETQUERYOBJECTIVPROC glad_glGetQueryObjectiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetQueryObjectiv(GLuint id, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetQueryObjectiv", (GLADapiproc) glad_glGetQueryObjectiv, 3, id, pname, params); glad_glGetQueryObjectiv(id, pname, params); _post_call_gl_callback(NULL, "glGetQueryObjectiv", (GLADapiproc) glad_glGetQueryObjectiv, 3, id, pname, params); } PFNGLGETQUERYOBJECTIVPROC glad_debug_glGetQueryObjectiv = glad_debug_impl_glGetQueryObjectiv; PFNGLGETQUERYOBJECTUIVPROC glad_glGetQueryObjectuiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetQueryObjectuiv(GLuint id, GLenum pname, GLuint * params) { _pre_call_gl_callback("glGetQueryObjectuiv", (GLADapiproc) glad_glGetQueryObjectuiv, 3, id, pname, params); glad_glGetQueryObjectuiv(id, pname, params); _post_call_gl_callback(NULL, "glGetQueryObjectuiv", (GLADapiproc) glad_glGetQueryObjectuiv, 3, id, pname, params); } PFNGLGETQUERYOBJECTUIVPROC glad_debug_glGetQueryObjectuiv = glad_debug_impl_glGetQueryObjectuiv; PFNGLGETQUERYIVPROC glad_glGetQueryiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetQueryiv(GLenum target, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetQueryiv", (GLADapiproc) glad_glGetQueryiv, 3, target, pname, params); glad_glGetQueryiv(target, pname, params); _post_call_gl_callback(NULL, "glGetQueryiv", (GLADapiproc) glad_glGetQueryiv, 3, target, pname, params); } PFNGLGETQUERYIVPROC glad_debug_glGetQueryiv = glad_debug_impl_glGetQueryiv; PFNGLGETRENDERBUFFERPARAMETERIVPROC glad_glGetRenderbufferParameteriv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetRenderbufferParameteriv(GLenum target, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetRenderbufferParameteriv", (GLADapiproc) glad_glGetRenderbufferParameteriv, 3, target, pname, params); glad_glGetRenderbufferParameteriv(target, pname, params); _post_call_gl_callback(NULL, "glGetRenderbufferParameteriv", (GLADapiproc) glad_glGetRenderbufferParameteriv, 3, target, pname, params); } PFNGLGETRENDERBUFFERPARAMETERIVPROC glad_debug_glGetRenderbufferParameteriv = glad_debug_impl_glGetRenderbufferParameteriv; PFNGLGETSHADERINFOLOGPROC glad_glGetShaderInfoLog = NULL; static void GLAD_API_PTR glad_debug_impl_glGetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei * length, GLchar * infoLog) { _pre_call_gl_callback("glGetShaderInfoLog", (GLADapiproc) glad_glGetShaderInfoLog, 4, shader, bufSize, length, infoLog); glad_glGetShaderInfoLog(shader, bufSize, length, infoLog); _post_call_gl_callback(NULL, "glGetShaderInfoLog", (GLADapiproc) glad_glGetShaderInfoLog, 4, shader, bufSize, length, infoLog); } PFNGLGETSHADERINFOLOGPROC glad_debug_glGetShaderInfoLog = glad_debug_impl_glGetShaderInfoLog; PFNGLGETSHADERSOURCEPROC glad_glGetShaderSource = NULL; static void GLAD_API_PTR glad_debug_impl_glGetShaderSource(GLuint shader, GLsizei bufSize, GLsizei * length, GLchar * source) { _pre_call_gl_callback("glGetShaderSource", (GLADapiproc) glad_glGetShaderSource, 4, shader, bufSize, length, source); glad_glGetShaderSource(shader, bufSize, length, source); _post_call_gl_callback(NULL, "glGetShaderSource", (GLADapiproc) glad_glGetShaderSource, 4, shader, bufSize, length, source); } PFNGLGETSHADERSOURCEPROC glad_debug_glGetShaderSource = glad_debug_impl_glGetShaderSource; PFNGLGETSHADERIVPROC glad_glGetShaderiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetShaderiv(GLuint shader, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetShaderiv", (GLADapiproc) glad_glGetShaderiv, 3, shader, pname, params); glad_glGetShaderiv(shader, pname, params); _post_call_gl_callback(NULL, "glGetShaderiv", (GLADapiproc) glad_glGetShaderiv, 3, shader, pname, params); } PFNGLGETSHADERIVPROC glad_debug_glGetShaderiv = glad_debug_impl_glGetShaderiv; PFNGLGETSTRINGPROC glad_glGetString = NULL; static const GLubyte * GLAD_API_PTR glad_debug_impl_glGetString(GLenum name) { const GLubyte * ret; _pre_call_gl_callback("glGetString", (GLADapiproc) glad_glGetString, 1, name); ret = glad_glGetString(name); _post_call_gl_callback((void*) &ret, "glGetString", (GLADapiproc) glad_glGetString, 1, name); return ret; } PFNGLGETSTRINGPROC glad_debug_glGetString = glad_debug_impl_glGetString; PFNGLGETSTRINGIPROC glad_glGetStringi = NULL; static const GLubyte * GLAD_API_PTR glad_debug_impl_glGetStringi(GLenum name, GLuint index) { const GLubyte * ret; _pre_call_gl_callback("glGetStringi", (GLADapiproc) glad_glGetStringi, 2, name, index); ret = glad_glGetStringi(name, index); _post_call_gl_callback((void*) &ret, "glGetStringi", (GLADapiproc) glad_glGetStringi, 2, name, index); return ret; } PFNGLGETSTRINGIPROC glad_debug_glGetStringi = glad_debug_impl_glGetStringi; PFNGLGETTEXENVFVPROC glad_glGetTexEnvfv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetTexEnvfv(GLenum target, GLenum pname, GLfloat * params) { _pre_call_gl_callback("glGetTexEnvfv", (GLADapiproc) glad_glGetTexEnvfv, 3, target, pname, params); glad_glGetTexEnvfv(target, pname, params); _post_call_gl_callback(NULL, "glGetTexEnvfv", (GLADapiproc) glad_glGetTexEnvfv, 3, target, pname, params); } PFNGLGETTEXENVFVPROC glad_debug_glGetTexEnvfv = glad_debug_impl_glGetTexEnvfv; PFNGLGETTEXENVIVPROC glad_glGetTexEnviv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetTexEnviv(GLenum target, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetTexEnviv", (GLADapiproc) glad_glGetTexEnviv, 3, target, pname, params); glad_glGetTexEnviv(target, pname, params); _post_call_gl_callback(NULL, "glGetTexEnviv", (GLADapiproc) glad_glGetTexEnviv, 3, target, pname, params); } PFNGLGETTEXENVIVPROC glad_debug_glGetTexEnviv = glad_debug_impl_glGetTexEnviv; PFNGLGETTEXGENDVPROC glad_glGetTexGendv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetTexGendv(GLenum coord, GLenum pname, GLdouble * params) { _pre_call_gl_callback("glGetTexGendv", (GLADapiproc) glad_glGetTexGendv, 3, coord, pname, params); glad_glGetTexGendv(coord, pname, params); _post_call_gl_callback(NULL, "glGetTexGendv", (GLADapiproc) glad_glGetTexGendv, 3, coord, pname, params); } PFNGLGETTEXGENDVPROC glad_debug_glGetTexGendv = glad_debug_impl_glGetTexGendv; PFNGLGETTEXGENFVPROC glad_glGetTexGenfv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetTexGenfv(GLenum coord, GLenum pname, GLfloat * params) { _pre_call_gl_callback("glGetTexGenfv", (GLADapiproc) glad_glGetTexGenfv, 3, coord, pname, params); glad_glGetTexGenfv(coord, pname, params); _post_call_gl_callback(NULL, "glGetTexGenfv", (GLADapiproc) glad_glGetTexGenfv, 3, coord, pname, params); } PFNGLGETTEXGENFVPROC glad_debug_glGetTexGenfv = glad_debug_impl_glGetTexGenfv; PFNGLGETTEXGENIVPROC glad_glGetTexGeniv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetTexGeniv(GLenum coord, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetTexGeniv", (GLADapiproc) glad_glGetTexGeniv, 3, coord, pname, params); glad_glGetTexGeniv(coord, pname, params); _post_call_gl_callback(NULL, "glGetTexGeniv", (GLADapiproc) glad_glGetTexGeniv, 3, coord, pname, params); } PFNGLGETTEXGENIVPROC glad_debug_glGetTexGeniv = glad_debug_impl_glGetTexGeniv; PFNGLGETTEXIMAGEPROC glad_glGetTexImage = NULL; static void GLAD_API_PTR glad_debug_impl_glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void * pixels) { _pre_call_gl_callback("glGetTexImage", (GLADapiproc) glad_glGetTexImage, 5, target, level, format, type, pixels); glad_glGetTexImage(target, level, format, type, pixels); _post_call_gl_callback(NULL, "glGetTexImage", (GLADapiproc) glad_glGetTexImage, 5, target, level, format, type, pixels); } PFNGLGETTEXIMAGEPROC glad_debug_glGetTexImage = glad_debug_impl_glGetTexImage; PFNGLGETTEXLEVELPARAMETERFVPROC glad_glGetTexLevelParameterfv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat * params) { _pre_call_gl_callback("glGetTexLevelParameterfv", (GLADapiproc) glad_glGetTexLevelParameterfv, 4, target, level, pname, params); glad_glGetTexLevelParameterfv(target, level, pname, params); _post_call_gl_callback(NULL, "glGetTexLevelParameterfv", (GLADapiproc) glad_glGetTexLevelParameterfv, 4, target, level, pname, params); } PFNGLGETTEXLEVELPARAMETERFVPROC glad_debug_glGetTexLevelParameterfv = glad_debug_impl_glGetTexLevelParameterfv; PFNGLGETTEXLEVELPARAMETERIVPROC glad_glGetTexLevelParameteriv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetTexLevelParameteriv", (GLADapiproc) glad_glGetTexLevelParameteriv, 4, target, level, pname, params); glad_glGetTexLevelParameteriv(target, level, pname, params); _post_call_gl_callback(NULL, "glGetTexLevelParameteriv", (GLADapiproc) glad_glGetTexLevelParameteriv, 4, target, level, pname, params); } PFNGLGETTEXLEVELPARAMETERIVPROC glad_debug_glGetTexLevelParameteriv = glad_debug_impl_glGetTexLevelParameteriv; PFNGLGETTEXPARAMETERIIVPROC glad_glGetTexParameterIiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetTexParameterIiv(GLenum target, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetTexParameterIiv", (GLADapiproc) glad_glGetTexParameterIiv, 3, target, pname, params); glad_glGetTexParameterIiv(target, pname, params); _post_call_gl_callback(NULL, "glGetTexParameterIiv", (GLADapiproc) glad_glGetTexParameterIiv, 3, target, pname, params); } PFNGLGETTEXPARAMETERIIVPROC glad_debug_glGetTexParameterIiv = glad_debug_impl_glGetTexParameterIiv; PFNGLGETTEXPARAMETERIUIVPROC glad_glGetTexParameterIuiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetTexParameterIuiv(GLenum target, GLenum pname, GLuint * params) { _pre_call_gl_callback("glGetTexParameterIuiv", (GLADapiproc) glad_glGetTexParameterIuiv, 3, target, pname, params); glad_glGetTexParameterIuiv(target, pname, params); _post_call_gl_callback(NULL, "glGetTexParameterIuiv", (GLADapiproc) glad_glGetTexParameterIuiv, 3, target, pname, params); } PFNGLGETTEXPARAMETERIUIVPROC glad_debug_glGetTexParameterIuiv = glad_debug_impl_glGetTexParameterIuiv; PFNGLGETTEXPARAMETERFVPROC glad_glGetTexParameterfv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetTexParameterfv(GLenum target, GLenum pname, GLfloat * params) { _pre_call_gl_callback("glGetTexParameterfv", (GLADapiproc) glad_glGetTexParameterfv, 3, target, pname, params); glad_glGetTexParameterfv(target, pname, params); _post_call_gl_callback(NULL, "glGetTexParameterfv", (GLADapiproc) glad_glGetTexParameterfv, 3, target, pname, params); } PFNGLGETTEXPARAMETERFVPROC glad_debug_glGetTexParameterfv = glad_debug_impl_glGetTexParameterfv; PFNGLGETTEXPARAMETERIVPROC glad_glGetTexParameteriv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetTexParameteriv(GLenum target, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetTexParameteriv", (GLADapiproc) glad_glGetTexParameteriv, 3, target, pname, params); glad_glGetTexParameteriv(target, pname, params); _post_call_gl_callback(NULL, "glGetTexParameteriv", (GLADapiproc) glad_glGetTexParameteriv, 3, target, pname, params); } PFNGLGETTEXPARAMETERIVPROC glad_debug_glGetTexParameteriv = glad_debug_impl_glGetTexParameteriv; PFNGLGETTRANSFORMFEEDBACKVARYINGPROC glad_glGetTransformFeedbackVarying = NULL; static void GLAD_API_PTR glad_debug_impl_glGetTransformFeedbackVarying(GLuint program, GLuint index, GLsizei bufSize, GLsizei * length, GLsizei * size, GLenum * type, GLchar * name) { _pre_call_gl_callback("glGetTransformFeedbackVarying", (GLADapiproc) glad_glGetTransformFeedbackVarying, 7, program, index, bufSize, length, size, type, name); glad_glGetTransformFeedbackVarying(program, index, bufSize, length, size, type, name); _post_call_gl_callback(NULL, "glGetTransformFeedbackVarying", (GLADapiproc) glad_glGetTransformFeedbackVarying, 7, program, index, bufSize, length, size, type, name); } PFNGLGETTRANSFORMFEEDBACKVARYINGPROC glad_debug_glGetTransformFeedbackVarying = glad_debug_impl_glGetTransformFeedbackVarying; PFNGLGETUNIFORMBLOCKINDEXPROC glad_glGetUniformBlockIndex = NULL; static GLuint GLAD_API_PTR glad_debug_impl_glGetUniformBlockIndex(GLuint program, const GLchar * uniformBlockName) { GLuint ret; _pre_call_gl_callback("glGetUniformBlockIndex", (GLADapiproc) glad_glGetUniformBlockIndex, 2, program, uniformBlockName); ret = glad_glGetUniformBlockIndex(program, uniformBlockName); _post_call_gl_callback((void*) &ret, "glGetUniformBlockIndex", (GLADapiproc) glad_glGetUniformBlockIndex, 2, program, uniformBlockName); return ret; } PFNGLGETUNIFORMBLOCKINDEXPROC glad_debug_glGetUniformBlockIndex = glad_debug_impl_glGetUniformBlockIndex; PFNGLGETUNIFORMINDICESPROC glad_glGetUniformIndices = NULL; static void GLAD_API_PTR glad_debug_impl_glGetUniformIndices(GLuint program, GLsizei uniformCount, const GLchar *const* uniformNames, GLuint * uniformIndices) { _pre_call_gl_callback("glGetUniformIndices", (GLADapiproc) glad_glGetUniformIndices, 4, program, uniformCount, uniformNames, uniformIndices); glad_glGetUniformIndices(program, uniformCount, uniformNames, uniformIndices); _post_call_gl_callback(NULL, "glGetUniformIndices", (GLADapiproc) glad_glGetUniformIndices, 4, program, uniformCount, uniformNames, uniformIndices); } PFNGLGETUNIFORMINDICESPROC glad_debug_glGetUniformIndices = glad_debug_impl_glGetUniformIndices; PFNGLGETUNIFORMLOCATIONPROC glad_glGetUniformLocation = NULL; static GLint GLAD_API_PTR glad_debug_impl_glGetUniformLocation(GLuint program, const GLchar * name) { GLint ret; _pre_call_gl_callback("glGetUniformLocation", (GLADapiproc) glad_glGetUniformLocation, 2, program, name); ret = glad_glGetUniformLocation(program, name); _post_call_gl_callback((void*) &ret, "glGetUniformLocation", (GLADapiproc) glad_glGetUniformLocation, 2, program, name); return ret; } PFNGLGETUNIFORMLOCATIONPROC glad_debug_glGetUniformLocation = glad_debug_impl_glGetUniformLocation; PFNGLGETUNIFORMFVPROC glad_glGetUniformfv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetUniformfv(GLuint program, GLint location, GLfloat * params) { _pre_call_gl_callback("glGetUniformfv", (GLADapiproc) glad_glGetUniformfv, 3, program, location, params); glad_glGetUniformfv(program, location, params); _post_call_gl_callback(NULL, "glGetUniformfv", (GLADapiproc) glad_glGetUniformfv, 3, program, location, params); } PFNGLGETUNIFORMFVPROC glad_debug_glGetUniformfv = glad_debug_impl_glGetUniformfv; PFNGLGETUNIFORMIVPROC glad_glGetUniformiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetUniformiv(GLuint program, GLint location, GLint * params) { _pre_call_gl_callback("glGetUniformiv", (GLADapiproc) glad_glGetUniformiv, 3, program, location, params); glad_glGetUniformiv(program, location, params); _post_call_gl_callback(NULL, "glGetUniformiv", (GLADapiproc) glad_glGetUniformiv, 3, program, location, params); } PFNGLGETUNIFORMIVPROC glad_debug_glGetUniformiv = glad_debug_impl_glGetUniformiv; PFNGLGETUNIFORMUIVPROC glad_glGetUniformuiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetUniformuiv(GLuint program, GLint location, GLuint * params) { _pre_call_gl_callback("glGetUniformuiv", (GLADapiproc) glad_glGetUniformuiv, 3, program, location, params); glad_glGetUniformuiv(program, location, params); _post_call_gl_callback(NULL, "glGetUniformuiv", (GLADapiproc) glad_glGetUniformuiv, 3, program, location, params); } PFNGLGETUNIFORMUIVPROC glad_debug_glGetUniformuiv = glad_debug_impl_glGetUniformuiv; PFNGLGETVERTEXATTRIBIIVPROC glad_glGetVertexAttribIiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetVertexAttribIiv(GLuint index, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetVertexAttribIiv", (GLADapiproc) glad_glGetVertexAttribIiv, 3, index, pname, params); glad_glGetVertexAttribIiv(index, pname, params); _post_call_gl_callback(NULL, "glGetVertexAttribIiv", (GLADapiproc) glad_glGetVertexAttribIiv, 3, index, pname, params); } PFNGLGETVERTEXATTRIBIIVPROC glad_debug_glGetVertexAttribIiv = glad_debug_impl_glGetVertexAttribIiv; PFNGLGETVERTEXATTRIBIUIVPROC glad_glGetVertexAttribIuiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetVertexAttribIuiv(GLuint index, GLenum pname, GLuint * params) { _pre_call_gl_callback("glGetVertexAttribIuiv", (GLADapiproc) glad_glGetVertexAttribIuiv, 3, index, pname, params); glad_glGetVertexAttribIuiv(index, pname, params); _post_call_gl_callback(NULL, "glGetVertexAttribIuiv", (GLADapiproc) glad_glGetVertexAttribIuiv, 3, index, pname, params); } PFNGLGETVERTEXATTRIBIUIVPROC glad_debug_glGetVertexAttribIuiv = glad_debug_impl_glGetVertexAttribIuiv; PFNGLGETVERTEXATTRIBPOINTERVPROC glad_glGetVertexAttribPointerv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetVertexAttribPointerv(GLuint index, GLenum pname, void ** pointer) { _pre_call_gl_callback("glGetVertexAttribPointerv", (GLADapiproc) glad_glGetVertexAttribPointerv, 3, index, pname, pointer); glad_glGetVertexAttribPointerv(index, pname, pointer); _post_call_gl_callback(NULL, "glGetVertexAttribPointerv", (GLADapiproc) glad_glGetVertexAttribPointerv, 3, index, pname, pointer); } PFNGLGETVERTEXATTRIBPOINTERVPROC glad_debug_glGetVertexAttribPointerv = glad_debug_impl_glGetVertexAttribPointerv; PFNGLGETVERTEXATTRIBDVPROC glad_glGetVertexAttribdv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetVertexAttribdv(GLuint index, GLenum pname, GLdouble * params) { _pre_call_gl_callback("glGetVertexAttribdv", (GLADapiproc) glad_glGetVertexAttribdv, 3, index, pname, params); glad_glGetVertexAttribdv(index, pname, params); _post_call_gl_callback(NULL, "glGetVertexAttribdv", (GLADapiproc) glad_glGetVertexAttribdv, 3, index, pname, params); } PFNGLGETVERTEXATTRIBDVPROC glad_debug_glGetVertexAttribdv = glad_debug_impl_glGetVertexAttribdv; PFNGLGETVERTEXATTRIBFVPROC glad_glGetVertexAttribfv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetVertexAttribfv(GLuint index, GLenum pname, GLfloat * params) { _pre_call_gl_callback("glGetVertexAttribfv", (GLADapiproc) glad_glGetVertexAttribfv, 3, index, pname, params); glad_glGetVertexAttribfv(index, pname, params); _post_call_gl_callback(NULL, "glGetVertexAttribfv", (GLADapiproc) glad_glGetVertexAttribfv, 3, index, pname, params); } PFNGLGETVERTEXATTRIBFVPROC glad_debug_glGetVertexAttribfv = glad_debug_impl_glGetVertexAttribfv; PFNGLGETVERTEXATTRIBIVPROC glad_glGetVertexAttribiv = NULL; static void GLAD_API_PTR glad_debug_impl_glGetVertexAttribiv(GLuint index, GLenum pname, GLint * params) { _pre_call_gl_callback("glGetVertexAttribiv", (GLADapiproc) glad_glGetVertexAttribiv, 3, index, pname, params); glad_glGetVertexAttribiv(index, pname, params); _post_call_gl_callback(NULL, "glGetVertexAttribiv", (GLADapiproc) glad_glGetVertexAttribiv, 3, index, pname, params); } PFNGLGETVERTEXATTRIBIVPROC glad_debug_glGetVertexAttribiv = glad_debug_impl_glGetVertexAttribiv; PFNGLGETNCOMPRESSEDTEXIMAGEARBPROC glad_glGetnCompressedTexImageARB = NULL; static void GLAD_API_PTR glad_debug_impl_glGetnCompressedTexImageARB(GLenum target, GLint lod, GLsizei bufSize, void * img) { _pre_call_gl_callback("glGetnCompressedTexImageARB", (GLADapiproc) glad_glGetnCompressedTexImageARB, 4, target, lod, bufSize, img); glad_glGetnCompressedTexImageARB(target, lod, bufSize, img); _post_call_gl_callback(NULL, "glGetnCompressedTexImageARB", (GLADapiproc) glad_glGetnCompressedTexImageARB, 4, target, lod, bufSize, img); } PFNGLGETNCOMPRESSEDTEXIMAGEARBPROC glad_debug_glGetnCompressedTexImageARB = glad_debug_impl_glGetnCompressedTexImageARB; PFNGLGETNTEXIMAGEARBPROC glad_glGetnTexImageARB = NULL; static void GLAD_API_PTR glad_debug_impl_glGetnTexImageARB(GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void * img) { _pre_call_gl_callback("glGetnTexImageARB", (GLADapiproc) glad_glGetnTexImageARB, 6, target, level, format, type, bufSize, img); glad_glGetnTexImageARB(target, level, format, type, bufSize, img); _post_call_gl_callback(NULL, "glGetnTexImageARB", (GLADapiproc) glad_glGetnTexImageARB, 6, target, level, format, type, bufSize, img); } PFNGLGETNTEXIMAGEARBPROC glad_debug_glGetnTexImageARB = glad_debug_impl_glGetnTexImageARB; PFNGLGETNUNIFORMDVARBPROC glad_glGetnUniformdvARB = NULL; static void GLAD_API_PTR glad_debug_impl_glGetnUniformdvARB(GLuint program, GLint location, GLsizei bufSize, GLdouble * params) { _pre_call_gl_callback("glGetnUniformdvARB", (GLADapiproc) glad_glGetnUniformdvARB, 4, program, location, bufSize, params); glad_glGetnUniformdvARB(program, location, bufSize, params); _post_call_gl_callback(NULL, "glGetnUniformdvARB", (GLADapiproc) glad_glGetnUniformdvARB, 4, program, location, bufSize, params); } PFNGLGETNUNIFORMDVARBPROC glad_debug_glGetnUniformdvARB = glad_debug_impl_glGetnUniformdvARB; PFNGLGETNUNIFORMFVARBPROC glad_glGetnUniformfvARB = NULL; static void GLAD_API_PTR glad_debug_impl_glGetnUniformfvARB(GLuint program, GLint location, GLsizei bufSize, GLfloat * params) { _pre_call_gl_callback("glGetnUniformfvARB", (GLADapiproc) glad_glGetnUniformfvARB, 4, program, location, bufSize, params); glad_glGetnUniformfvARB(program, location, bufSize, params); _post_call_gl_callback(NULL, "glGetnUniformfvARB", (GLADapiproc) glad_glGetnUniformfvARB, 4, program, location, bufSize, params); } PFNGLGETNUNIFORMFVARBPROC glad_debug_glGetnUniformfvARB = glad_debug_impl_glGetnUniformfvARB; PFNGLGETNUNIFORMIVARBPROC glad_glGetnUniformivARB = NULL; static void GLAD_API_PTR glad_debug_impl_glGetnUniformivARB(GLuint program, GLint location, GLsizei bufSize, GLint * params) { _pre_call_gl_callback("glGetnUniformivARB", (GLADapiproc) glad_glGetnUniformivARB, 4, program, location, bufSize, params); glad_glGetnUniformivARB(program, location, bufSize, params); _post_call_gl_callback(NULL, "glGetnUniformivARB", (GLADapiproc) glad_glGetnUniformivARB, 4, program, location, bufSize, params); } PFNGLGETNUNIFORMIVARBPROC glad_debug_glGetnUniformivARB = glad_debug_impl_glGetnUniformivARB; PFNGLGETNUNIFORMUIVARBPROC glad_glGetnUniformuivARB = NULL; static void GLAD_API_PTR glad_debug_impl_glGetnUniformuivARB(GLuint program, GLint location, GLsizei bufSize, GLuint * params) { _pre_call_gl_callback("glGetnUniformuivARB", (GLADapiproc) glad_glGetnUniformuivARB, 4, program, location, bufSize, params); glad_glGetnUniformuivARB(program, location, bufSize, params); _post_call_gl_callback(NULL, "glGetnUniformuivARB", (GLADapiproc) glad_glGetnUniformuivARB, 4, program, location, bufSize, params); } PFNGLGETNUNIFORMUIVARBPROC glad_debug_glGetnUniformuivARB = glad_debug_impl_glGetnUniformuivARB; PFNGLHINTPROC glad_glHint = NULL; static void GLAD_API_PTR glad_debug_impl_glHint(GLenum target, GLenum mode) { _pre_call_gl_callback("glHint", (GLADapiproc) glad_glHint, 2, target, mode); glad_glHint(target, mode); _post_call_gl_callback(NULL, "glHint", (GLADapiproc) glad_glHint, 2, target, mode); } PFNGLHINTPROC glad_debug_glHint = glad_debug_impl_glHint; PFNGLINDEXMASKPROC glad_glIndexMask = NULL; static void GLAD_API_PTR glad_debug_impl_glIndexMask(GLuint mask) { _pre_call_gl_callback("glIndexMask", (GLADapiproc) glad_glIndexMask, 1, mask); glad_glIndexMask(mask); _post_call_gl_callback(NULL, "glIndexMask", (GLADapiproc) glad_glIndexMask, 1, mask); } PFNGLINDEXMASKPROC glad_debug_glIndexMask = glad_debug_impl_glIndexMask; PFNGLINDEXPOINTERPROC glad_glIndexPointer = NULL; static void GLAD_API_PTR glad_debug_impl_glIndexPointer(GLenum type, GLsizei stride, const void * pointer) { _pre_call_gl_callback("glIndexPointer", (GLADapiproc) glad_glIndexPointer, 3, type, stride, pointer); glad_glIndexPointer(type, stride, pointer); _post_call_gl_callback(NULL, "glIndexPointer", (GLADapiproc) glad_glIndexPointer, 3, type, stride, pointer); } PFNGLINDEXPOINTERPROC glad_debug_glIndexPointer = glad_debug_impl_glIndexPointer; PFNGLINDEXDPROC glad_glIndexd = NULL; static void GLAD_API_PTR glad_debug_impl_glIndexd(GLdouble c) { _pre_call_gl_callback("glIndexd", (GLADapiproc) glad_glIndexd, 1, c); glad_glIndexd(c); _post_call_gl_callback(NULL, "glIndexd", (GLADapiproc) glad_glIndexd, 1, c); } PFNGLINDEXDPROC glad_debug_glIndexd = glad_debug_impl_glIndexd; PFNGLINDEXDVPROC glad_glIndexdv = NULL; static void GLAD_API_PTR glad_debug_impl_glIndexdv(const GLdouble * c) { _pre_call_gl_callback("glIndexdv", (GLADapiproc) glad_glIndexdv, 1, c); glad_glIndexdv(c); _post_call_gl_callback(NULL, "glIndexdv", (GLADapiproc) glad_glIndexdv, 1, c); } PFNGLINDEXDVPROC glad_debug_glIndexdv = glad_debug_impl_glIndexdv; PFNGLINDEXFPROC glad_glIndexf = NULL; static void GLAD_API_PTR glad_debug_impl_glIndexf(GLfloat c) { _pre_call_gl_callback("glIndexf", (GLADapiproc) glad_glIndexf, 1, c); glad_glIndexf(c); _post_call_gl_callback(NULL, "glIndexf", (GLADapiproc) glad_glIndexf, 1, c); } PFNGLINDEXFPROC glad_debug_glIndexf = glad_debug_impl_glIndexf; PFNGLINDEXFVPROC glad_glIndexfv = NULL; static void GLAD_API_PTR glad_debug_impl_glIndexfv(const GLfloat * c) { _pre_call_gl_callback("glIndexfv", (GLADapiproc) glad_glIndexfv, 1, c); glad_glIndexfv(c); _post_call_gl_callback(NULL, "glIndexfv", (GLADapiproc) glad_glIndexfv, 1, c); } PFNGLINDEXFVPROC glad_debug_glIndexfv = glad_debug_impl_glIndexfv; PFNGLINDEXIPROC glad_glIndexi = NULL; static void GLAD_API_PTR glad_debug_impl_glIndexi(GLint c) { _pre_call_gl_callback("glIndexi", (GLADapiproc) glad_glIndexi, 1, c); glad_glIndexi(c); _post_call_gl_callback(NULL, "glIndexi", (GLADapiproc) glad_glIndexi, 1, c); } PFNGLINDEXIPROC glad_debug_glIndexi = glad_debug_impl_glIndexi; PFNGLINDEXIVPROC glad_glIndexiv = NULL; static void GLAD_API_PTR glad_debug_impl_glIndexiv(const GLint * c) { _pre_call_gl_callback("glIndexiv", (GLADapiproc) glad_glIndexiv, 1, c); glad_glIndexiv(c); _post_call_gl_callback(NULL, "glIndexiv", (GLADapiproc) glad_glIndexiv, 1, c); } PFNGLINDEXIVPROC glad_debug_glIndexiv = glad_debug_impl_glIndexiv; PFNGLINDEXSPROC glad_glIndexs = NULL; static void GLAD_API_PTR glad_debug_impl_glIndexs(GLshort c) { _pre_call_gl_callback("glIndexs", (GLADapiproc) glad_glIndexs, 1, c); glad_glIndexs(c); _post_call_gl_callback(NULL, "glIndexs", (GLADapiproc) glad_glIndexs, 1, c); } PFNGLINDEXSPROC glad_debug_glIndexs = glad_debug_impl_glIndexs; PFNGLINDEXSVPROC glad_glIndexsv = NULL; static void GLAD_API_PTR glad_debug_impl_glIndexsv(const GLshort * c) { _pre_call_gl_callback("glIndexsv", (GLADapiproc) glad_glIndexsv, 1, c); glad_glIndexsv(c); _post_call_gl_callback(NULL, "glIndexsv", (GLADapiproc) glad_glIndexsv, 1, c); } PFNGLINDEXSVPROC glad_debug_glIndexsv = glad_debug_impl_glIndexsv; PFNGLINDEXUBPROC glad_glIndexub = NULL; static void GLAD_API_PTR glad_debug_impl_glIndexub(GLubyte c) { _pre_call_gl_callback("glIndexub", (GLADapiproc) glad_glIndexub, 1, c); glad_glIndexub(c); _post_call_gl_callback(NULL, "glIndexub", (GLADapiproc) glad_glIndexub, 1, c); } PFNGLINDEXUBPROC glad_debug_glIndexub = glad_debug_impl_glIndexub; PFNGLINDEXUBVPROC glad_glIndexubv = NULL; static void GLAD_API_PTR glad_debug_impl_glIndexubv(const GLubyte * c) { _pre_call_gl_callback("glIndexubv", (GLADapiproc) glad_glIndexubv, 1, c); glad_glIndexubv(c); _post_call_gl_callback(NULL, "glIndexubv", (GLADapiproc) glad_glIndexubv, 1, c); } PFNGLINDEXUBVPROC glad_debug_glIndexubv = glad_debug_impl_glIndexubv; PFNGLINITNAMESPROC glad_glInitNames = NULL; static void GLAD_API_PTR glad_debug_impl_glInitNames(void) { _pre_call_gl_callback("glInitNames", (GLADapiproc) glad_glInitNames, 0); glad_glInitNames(); _post_call_gl_callback(NULL, "glInitNames", (GLADapiproc) glad_glInitNames, 0); } PFNGLINITNAMESPROC glad_debug_glInitNames = glad_debug_impl_glInitNames; PFNGLINTERLEAVEDARRAYSPROC glad_glInterleavedArrays = NULL; static void GLAD_API_PTR glad_debug_impl_glInterleavedArrays(GLenum format, GLsizei stride, const void * pointer) { _pre_call_gl_callback("glInterleavedArrays", (GLADapiproc) glad_glInterleavedArrays, 3, format, stride, pointer); glad_glInterleavedArrays(format, stride, pointer); _post_call_gl_callback(NULL, "glInterleavedArrays", (GLADapiproc) glad_glInterleavedArrays, 3, format, stride, pointer); } PFNGLINTERLEAVEDARRAYSPROC glad_debug_glInterleavedArrays = glad_debug_impl_glInterleavedArrays; PFNGLISBUFFERPROC glad_glIsBuffer = NULL; static GLboolean GLAD_API_PTR glad_debug_impl_glIsBuffer(GLuint buffer) { GLboolean ret; _pre_call_gl_callback("glIsBuffer", (GLADapiproc) glad_glIsBuffer, 1, buffer); ret = glad_glIsBuffer(buffer); _post_call_gl_callback((void*) &ret, "glIsBuffer", (GLADapiproc) glad_glIsBuffer, 1, buffer); return ret; } PFNGLISBUFFERPROC glad_debug_glIsBuffer = glad_debug_impl_glIsBuffer; PFNGLISENABLEDPROC glad_glIsEnabled = NULL; static GLboolean GLAD_API_PTR glad_debug_impl_glIsEnabled(GLenum cap) { GLboolean ret; _pre_call_gl_callback("glIsEnabled", (GLADapiproc) glad_glIsEnabled, 1, cap); ret = glad_glIsEnabled(cap); _post_call_gl_callback((void*) &ret, "glIsEnabled", (GLADapiproc) glad_glIsEnabled, 1, cap); return ret; } PFNGLISENABLEDPROC glad_debug_glIsEnabled = glad_debug_impl_glIsEnabled; PFNGLISENABLEDIPROC glad_glIsEnabledi = NULL; static GLboolean GLAD_API_PTR glad_debug_impl_glIsEnabledi(GLenum target, GLuint index) { GLboolean ret; _pre_call_gl_callback("glIsEnabledi", (GLADapiproc) glad_glIsEnabledi, 2, target, index); ret = glad_glIsEnabledi(target, index); _post_call_gl_callback((void*) &ret, "glIsEnabledi", (GLADapiproc) glad_glIsEnabledi, 2, target, index); return ret; } PFNGLISENABLEDIPROC glad_debug_glIsEnabledi = glad_debug_impl_glIsEnabledi; PFNGLISFRAMEBUFFERPROC glad_glIsFramebuffer = NULL; static GLboolean GLAD_API_PTR glad_debug_impl_glIsFramebuffer(GLuint framebuffer) { GLboolean ret; _pre_call_gl_callback("glIsFramebuffer", (GLADapiproc) glad_glIsFramebuffer, 1, framebuffer); ret = glad_glIsFramebuffer(framebuffer); _post_call_gl_callback((void*) &ret, "glIsFramebuffer", (GLADapiproc) glad_glIsFramebuffer, 1, framebuffer); return ret; } PFNGLISFRAMEBUFFERPROC glad_debug_glIsFramebuffer = glad_debug_impl_glIsFramebuffer; PFNGLISLISTPROC glad_glIsList = NULL; static GLboolean GLAD_API_PTR glad_debug_impl_glIsList(GLuint list) { GLboolean ret; _pre_call_gl_callback("glIsList", (GLADapiproc) glad_glIsList, 1, list); ret = glad_glIsList(list); _post_call_gl_callback((void*) &ret, "glIsList", (GLADapiproc) glad_glIsList, 1, list); return ret; } PFNGLISLISTPROC glad_debug_glIsList = glad_debug_impl_glIsList; PFNGLISPROGRAMPROC glad_glIsProgram = NULL; static GLboolean GLAD_API_PTR glad_debug_impl_glIsProgram(GLuint program) { GLboolean ret; _pre_call_gl_callback("glIsProgram", (GLADapiproc) glad_glIsProgram, 1, program); ret = glad_glIsProgram(program); _post_call_gl_callback((void*) &ret, "glIsProgram", (GLADapiproc) glad_glIsProgram, 1, program); return ret; } PFNGLISPROGRAMPROC glad_debug_glIsProgram = glad_debug_impl_glIsProgram; PFNGLISQUERYPROC glad_glIsQuery = NULL; static GLboolean GLAD_API_PTR glad_debug_impl_glIsQuery(GLuint id) { GLboolean ret; _pre_call_gl_callback("glIsQuery", (GLADapiproc) glad_glIsQuery, 1, id); ret = glad_glIsQuery(id); _post_call_gl_callback((void*) &ret, "glIsQuery", (GLADapiproc) glad_glIsQuery, 1, id); return ret; } PFNGLISQUERYPROC glad_debug_glIsQuery = glad_debug_impl_glIsQuery; PFNGLISRENDERBUFFERPROC glad_glIsRenderbuffer = NULL; static GLboolean GLAD_API_PTR glad_debug_impl_glIsRenderbuffer(GLuint renderbuffer) { GLboolean ret; _pre_call_gl_callback("glIsRenderbuffer", (GLADapiproc) glad_glIsRenderbuffer, 1, renderbuffer); ret = glad_glIsRenderbuffer(renderbuffer); _post_call_gl_callback((void*) &ret, "glIsRenderbuffer", (GLADapiproc) glad_glIsRenderbuffer, 1, renderbuffer); return ret; } PFNGLISRENDERBUFFERPROC glad_debug_glIsRenderbuffer = glad_debug_impl_glIsRenderbuffer; PFNGLISSHADERPROC glad_glIsShader = NULL; static GLboolean GLAD_API_PTR glad_debug_impl_glIsShader(GLuint shader) { GLboolean ret; _pre_call_gl_callback("glIsShader", (GLADapiproc) glad_glIsShader, 1, shader); ret = glad_glIsShader(shader); _post_call_gl_callback((void*) &ret, "glIsShader", (GLADapiproc) glad_glIsShader, 1, shader); return ret; } PFNGLISSHADERPROC glad_debug_glIsShader = glad_debug_impl_glIsShader; PFNGLISTEXTUREPROC glad_glIsTexture = NULL; static GLboolean GLAD_API_PTR glad_debug_impl_glIsTexture(GLuint texture) { GLboolean ret; _pre_call_gl_callback("glIsTexture", (GLADapiproc) glad_glIsTexture, 1, texture); ret = glad_glIsTexture(texture); _post_call_gl_callback((void*) &ret, "glIsTexture", (GLADapiproc) glad_glIsTexture, 1, texture); return ret; } PFNGLISTEXTUREPROC glad_debug_glIsTexture = glad_debug_impl_glIsTexture; PFNGLISVERTEXARRAYPROC glad_glIsVertexArray = NULL; static GLboolean GLAD_API_PTR glad_debug_impl_glIsVertexArray(GLuint array) { GLboolean ret; _pre_call_gl_callback("glIsVertexArray", (GLADapiproc) glad_glIsVertexArray, 1, array); ret = glad_glIsVertexArray(array); _post_call_gl_callback((void*) &ret, "glIsVertexArray", (GLADapiproc) glad_glIsVertexArray, 1, array); return ret; } PFNGLISVERTEXARRAYPROC glad_debug_glIsVertexArray = glad_debug_impl_glIsVertexArray; PFNGLLIGHTMODELFPROC glad_glLightModelf = NULL; static void GLAD_API_PTR glad_debug_impl_glLightModelf(GLenum pname, GLfloat param) { _pre_call_gl_callback("glLightModelf", (GLADapiproc) glad_glLightModelf, 2, pname, param); glad_glLightModelf(pname, param); _post_call_gl_callback(NULL, "glLightModelf", (GLADapiproc) glad_glLightModelf, 2, pname, param); } PFNGLLIGHTMODELFPROC glad_debug_glLightModelf = glad_debug_impl_glLightModelf; PFNGLLIGHTMODELFVPROC glad_glLightModelfv = NULL; static void GLAD_API_PTR glad_debug_impl_glLightModelfv(GLenum pname, const GLfloat * params) { _pre_call_gl_callback("glLightModelfv", (GLADapiproc) glad_glLightModelfv, 2, pname, params); glad_glLightModelfv(pname, params); _post_call_gl_callback(NULL, "glLightModelfv", (GLADapiproc) glad_glLightModelfv, 2, pname, params); } PFNGLLIGHTMODELFVPROC glad_debug_glLightModelfv = glad_debug_impl_glLightModelfv; PFNGLLIGHTMODELIPROC glad_glLightModeli = NULL; static void GLAD_API_PTR glad_debug_impl_glLightModeli(GLenum pname, GLint param) { _pre_call_gl_callback("glLightModeli", (GLADapiproc) glad_glLightModeli, 2, pname, param); glad_glLightModeli(pname, param); _post_call_gl_callback(NULL, "glLightModeli", (GLADapiproc) glad_glLightModeli, 2, pname, param); } PFNGLLIGHTMODELIPROC glad_debug_glLightModeli = glad_debug_impl_glLightModeli; PFNGLLIGHTMODELIVPROC glad_glLightModeliv = NULL; static void GLAD_API_PTR glad_debug_impl_glLightModeliv(GLenum pname, const GLint * params) { _pre_call_gl_callback("glLightModeliv", (GLADapiproc) glad_glLightModeliv, 2, pname, params); glad_glLightModeliv(pname, params); _post_call_gl_callback(NULL, "glLightModeliv", (GLADapiproc) glad_glLightModeliv, 2, pname, params); } PFNGLLIGHTMODELIVPROC glad_debug_glLightModeliv = glad_debug_impl_glLightModeliv; PFNGLLIGHTFPROC glad_glLightf = NULL; static void GLAD_API_PTR glad_debug_impl_glLightf(GLenum light, GLenum pname, GLfloat param) { _pre_call_gl_callback("glLightf", (GLADapiproc) glad_glLightf, 3, light, pname, param); glad_glLightf(light, pname, param); _post_call_gl_callback(NULL, "glLightf", (GLADapiproc) glad_glLightf, 3, light, pname, param); } PFNGLLIGHTFPROC glad_debug_glLightf = glad_debug_impl_glLightf; PFNGLLIGHTFVPROC glad_glLightfv = NULL; static void GLAD_API_PTR glad_debug_impl_glLightfv(GLenum light, GLenum pname, const GLfloat * params) { _pre_call_gl_callback("glLightfv", (GLADapiproc) glad_glLightfv, 3, light, pname, params); glad_glLightfv(light, pname, params); _post_call_gl_callback(NULL, "glLightfv", (GLADapiproc) glad_glLightfv, 3, light, pname, params); } PFNGLLIGHTFVPROC glad_debug_glLightfv = glad_debug_impl_glLightfv; PFNGLLIGHTIPROC glad_glLighti = NULL; static void GLAD_API_PTR glad_debug_impl_glLighti(GLenum light, GLenum pname, GLint param) { _pre_call_gl_callback("glLighti", (GLADapiproc) glad_glLighti, 3, light, pname, param); glad_glLighti(light, pname, param); _post_call_gl_callback(NULL, "glLighti", (GLADapiproc) glad_glLighti, 3, light, pname, param); } PFNGLLIGHTIPROC glad_debug_glLighti = glad_debug_impl_glLighti; PFNGLLIGHTIVPROC glad_glLightiv = NULL; static void GLAD_API_PTR glad_debug_impl_glLightiv(GLenum light, GLenum pname, const GLint * params) { _pre_call_gl_callback("glLightiv", (GLADapiproc) glad_glLightiv, 3, light, pname, params); glad_glLightiv(light, pname, params); _post_call_gl_callback(NULL, "glLightiv", (GLADapiproc) glad_glLightiv, 3, light, pname, params); } PFNGLLIGHTIVPROC glad_debug_glLightiv = glad_debug_impl_glLightiv; PFNGLLINESTIPPLEPROC glad_glLineStipple = NULL; static void GLAD_API_PTR glad_debug_impl_glLineStipple(GLint factor, GLushort pattern) { _pre_call_gl_callback("glLineStipple", (GLADapiproc) glad_glLineStipple, 2, factor, pattern); glad_glLineStipple(factor, pattern); _post_call_gl_callback(NULL, "glLineStipple", (GLADapiproc) glad_glLineStipple, 2, factor, pattern); } PFNGLLINESTIPPLEPROC glad_debug_glLineStipple = glad_debug_impl_glLineStipple; PFNGLLINEWIDTHPROC glad_glLineWidth = NULL; static void GLAD_API_PTR glad_debug_impl_glLineWidth(GLfloat width) { _pre_call_gl_callback("glLineWidth", (GLADapiproc) glad_glLineWidth, 1, width); glad_glLineWidth(width); _post_call_gl_callback(NULL, "glLineWidth", (GLADapiproc) glad_glLineWidth, 1, width); } PFNGLLINEWIDTHPROC glad_debug_glLineWidth = glad_debug_impl_glLineWidth; PFNGLLINKPROGRAMPROC glad_glLinkProgram = NULL; static void GLAD_API_PTR glad_debug_impl_glLinkProgram(GLuint program) { _pre_call_gl_callback("glLinkProgram", (GLADapiproc) glad_glLinkProgram, 1, program); glad_glLinkProgram(program); _post_call_gl_callback(NULL, "glLinkProgram", (GLADapiproc) glad_glLinkProgram, 1, program); } PFNGLLINKPROGRAMPROC glad_debug_glLinkProgram = glad_debug_impl_glLinkProgram; PFNGLLISTBASEPROC glad_glListBase = NULL; static void GLAD_API_PTR glad_debug_impl_glListBase(GLuint base) { _pre_call_gl_callback("glListBase", (GLADapiproc) glad_glListBase, 1, base); glad_glListBase(base); _post_call_gl_callback(NULL, "glListBase", (GLADapiproc) glad_glListBase, 1, base); } PFNGLLISTBASEPROC glad_debug_glListBase = glad_debug_impl_glListBase; PFNGLLOADIDENTITYPROC glad_glLoadIdentity = NULL; static void GLAD_API_PTR glad_debug_impl_glLoadIdentity(void) { _pre_call_gl_callback("glLoadIdentity", (GLADapiproc) glad_glLoadIdentity, 0); glad_glLoadIdentity(); _post_call_gl_callback(NULL, "glLoadIdentity", (GLADapiproc) glad_glLoadIdentity, 0); } PFNGLLOADIDENTITYPROC glad_debug_glLoadIdentity = glad_debug_impl_glLoadIdentity; PFNGLLOADMATRIXDPROC glad_glLoadMatrixd = NULL; static void GLAD_API_PTR glad_debug_impl_glLoadMatrixd(const GLdouble * m) { _pre_call_gl_callback("glLoadMatrixd", (GLADapiproc) glad_glLoadMatrixd, 1, m); glad_glLoadMatrixd(m); _post_call_gl_callback(NULL, "glLoadMatrixd", (GLADapiproc) glad_glLoadMatrixd, 1, m); } PFNGLLOADMATRIXDPROC glad_debug_glLoadMatrixd = glad_debug_impl_glLoadMatrixd; PFNGLLOADMATRIXFPROC glad_glLoadMatrixf = NULL; static void GLAD_API_PTR glad_debug_impl_glLoadMatrixf(const GLfloat * m) { _pre_call_gl_callback("glLoadMatrixf", (GLADapiproc) glad_glLoadMatrixf, 1, m); glad_glLoadMatrixf(m); _post_call_gl_callback(NULL, "glLoadMatrixf", (GLADapiproc) glad_glLoadMatrixf, 1, m); } PFNGLLOADMATRIXFPROC glad_debug_glLoadMatrixf = glad_debug_impl_glLoadMatrixf; PFNGLLOADNAMEPROC glad_glLoadName = NULL; static void GLAD_API_PTR glad_debug_impl_glLoadName(GLuint name) { _pre_call_gl_callback("glLoadName", (GLADapiproc) glad_glLoadName, 1, name); glad_glLoadName(name); _post_call_gl_callback(NULL, "glLoadName", (GLADapiproc) glad_glLoadName, 1, name); } PFNGLLOADNAMEPROC glad_debug_glLoadName = glad_debug_impl_glLoadName; PFNGLLOADTRANSPOSEMATRIXDPROC glad_glLoadTransposeMatrixd = NULL; static void GLAD_API_PTR glad_debug_impl_glLoadTransposeMatrixd(const GLdouble * m) { _pre_call_gl_callback("glLoadTransposeMatrixd", (GLADapiproc) glad_glLoadTransposeMatrixd, 1, m); glad_glLoadTransposeMatrixd(m); _post_call_gl_callback(NULL, "glLoadTransposeMatrixd", (GLADapiproc) glad_glLoadTransposeMatrixd, 1, m); } PFNGLLOADTRANSPOSEMATRIXDPROC glad_debug_glLoadTransposeMatrixd = glad_debug_impl_glLoadTransposeMatrixd; PFNGLLOADTRANSPOSEMATRIXFPROC glad_glLoadTransposeMatrixf = NULL; static void GLAD_API_PTR glad_debug_impl_glLoadTransposeMatrixf(const GLfloat * m) { _pre_call_gl_callback("glLoadTransposeMatrixf", (GLADapiproc) glad_glLoadTransposeMatrixf, 1, m); glad_glLoadTransposeMatrixf(m); _post_call_gl_callback(NULL, "glLoadTransposeMatrixf", (GLADapiproc) glad_glLoadTransposeMatrixf, 1, m); } PFNGLLOADTRANSPOSEMATRIXFPROC glad_debug_glLoadTransposeMatrixf = glad_debug_impl_glLoadTransposeMatrixf; PFNGLLOGICOPPROC glad_glLogicOp = NULL; static void GLAD_API_PTR glad_debug_impl_glLogicOp(GLenum opcode) { _pre_call_gl_callback("glLogicOp", (GLADapiproc) glad_glLogicOp, 1, opcode); glad_glLogicOp(opcode); _post_call_gl_callback(NULL, "glLogicOp", (GLADapiproc) glad_glLogicOp, 1, opcode); } PFNGLLOGICOPPROC glad_debug_glLogicOp = glad_debug_impl_glLogicOp; PFNGLMAP1DPROC glad_glMap1d = NULL; static void GLAD_API_PTR glad_debug_impl_glMap1d(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble * points) { _pre_call_gl_callback("glMap1d", (GLADapiproc) glad_glMap1d, 6, target, u1, u2, stride, order, points); glad_glMap1d(target, u1, u2, stride, order, points); _post_call_gl_callback(NULL, "glMap1d", (GLADapiproc) glad_glMap1d, 6, target, u1, u2, stride, order, points); } PFNGLMAP1DPROC glad_debug_glMap1d = glad_debug_impl_glMap1d; PFNGLMAP1FPROC glad_glMap1f = NULL; static void GLAD_API_PTR glad_debug_impl_glMap1f(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat * points) { _pre_call_gl_callback("glMap1f", (GLADapiproc) glad_glMap1f, 6, target, u1, u2, stride, order, points); glad_glMap1f(target, u1, u2, stride, order, points); _post_call_gl_callback(NULL, "glMap1f", (GLADapiproc) glad_glMap1f, 6, target, u1, u2, stride, order, points); } PFNGLMAP1FPROC glad_debug_glMap1f = glad_debug_impl_glMap1f; PFNGLMAP2DPROC glad_glMap2d = NULL; static void GLAD_API_PTR glad_debug_impl_glMap2d(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble * points) { _pre_call_gl_callback("glMap2d", (GLADapiproc) glad_glMap2d, 10, target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points); glad_glMap2d(target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points); _post_call_gl_callback(NULL, "glMap2d", (GLADapiproc) glad_glMap2d, 10, target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points); } PFNGLMAP2DPROC glad_debug_glMap2d = glad_debug_impl_glMap2d; PFNGLMAP2FPROC glad_glMap2f = NULL; static void GLAD_API_PTR glad_debug_impl_glMap2f(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat * points) { _pre_call_gl_callback("glMap2f", (GLADapiproc) glad_glMap2f, 10, target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points); glad_glMap2f(target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points); _post_call_gl_callback(NULL, "glMap2f", (GLADapiproc) glad_glMap2f, 10, target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points); } PFNGLMAP2FPROC glad_debug_glMap2f = glad_debug_impl_glMap2f; PFNGLMAPBUFFERPROC glad_glMapBuffer = NULL; static void * GLAD_API_PTR glad_debug_impl_glMapBuffer(GLenum target, GLenum access) { void * ret; _pre_call_gl_callback("glMapBuffer", (GLADapiproc) glad_glMapBuffer, 2, target, access); ret = glad_glMapBuffer(target, access); _post_call_gl_callback((void*) &ret, "glMapBuffer", (GLADapiproc) glad_glMapBuffer, 2, target, access); return ret; } PFNGLMAPBUFFERPROC glad_debug_glMapBuffer = glad_debug_impl_glMapBuffer; PFNGLMAPBUFFERRANGEPROC glad_glMapBufferRange = NULL; static void * GLAD_API_PTR glad_debug_impl_glMapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access) { void * ret; _pre_call_gl_callback("glMapBufferRange", (GLADapiproc) glad_glMapBufferRange, 4, target, offset, length, access); ret = glad_glMapBufferRange(target, offset, length, access); _post_call_gl_callback((void*) &ret, "glMapBufferRange", (GLADapiproc) glad_glMapBufferRange, 4, target, offset, length, access); return ret; } PFNGLMAPBUFFERRANGEPROC glad_debug_glMapBufferRange = glad_debug_impl_glMapBufferRange; PFNGLMAPGRID1DPROC glad_glMapGrid1d = NULL; static void GLAD_API_PTR glad_debug_impl_glMapGrid1d(GLint un, GLdouble u1, GLdouble u2) { _pre_call_gl_callback("glMapGrid1d", (GLADapiproc) glad_glMapGrid1d, 3, un, u1, u2); glad_glMapGrid1d(un, u1, u2); _post_call_gl_callback(NULL, "glMapGrid1d", (GLADapiproc) glad_glMapGrid1d, 3, un, u1, u2); } PFNGLMAPGRID1DPROC glad_debug_glMapGrid1d = glad_debug_impl_glMapGrid1d; PFNGLMAPGRID1FPROC glad_glMapGrid1f = NULL; static void GLAD_API_PTR glad_debug_impl_glMapGrid1f(GLint un, GLfloat u1, GLfloat u2) { _pre_call_gl_callback("glMapGrid1f", (GLADapiproc) glad_glMapGrid1f, 3, un, u1, u2); glad_glMapGrid1f(un, u1, u2); _post_call_gl_callback(NULL, "glMapGrid1f", (GLADapiproc) glad_glMapGrid1f, 3, un, u1, u2); } PFNGLMAPGRID1FPROC glad_debug_glMapGrid1f = glad_debug_impl_glMapGrid1f; PFNGLMAPGRID2DPROC glad_glMapGrid2d = NULL; static void GLAD_API_PTR glad_debug_impl_glMapGrid2d(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2) { _pre_call_gl_callback("glMapGrid2d", (GLADapiproc) glad_glMapGrid2d, 6, un, u1, u2, vn, v1, v2); glad_glMapGrid2d(un, u1, u2, vn, v1, v2); _post_call_gl_callback(NULL, "glMapGrid2d", (GLADapiproc) glad_glMapGrid2d, 6, un, u1, u2, vn, v1, v2); } PFNGLMAPGRID2DPROC glad_debug_glMapGrid2d = glad_debug_impl_glMapGrid2d; PFNGLMAPGRID2FPROC glad_glMapGrid2f = NULL; static void GLAD_API_PTR glad_debug_impl_glMapGrid2f(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2) { _pre_call_gl_callback("glMapGrid2f", (GLADapiproc) glad_glMapGrid2f, 6, un, u1, u2, vn, v1, v2); glad_glMapGrid2f(un, u1, u2, vn, v1, v2); _post_call_gl_callback(NULL, "glMapGrid2f", (GLADapiproc) glad_glMapGrid2f, 6, un, u1, u2, vn, v1, v2); } PFNGLMAPGRID2FPROC glad_debug_glMapGrid2f = glad_debug_impl_glMapGrid2f; PFNGLMATERIALFPROC glad_glMaterialf = NULL; static void GLAD_API_PTR glad_debug_impl_glMaterialf(GLenum face, GLenum pname, GLfloat param) { _pre_call_gl_callback("glMaterialf", (GLADapiproc) glad_glMaterialf, 3, face, pname, param); glad_glMaterialf(face, pname, param); _post_call_gl_callback(NULL, "glMaterialf", (GLADapiproc) glad_glMaterialf, 3, face, pname, param); } PFNGLMATERIALFPROC glad_debug_glMaterialf = glad_debug_impl_glMaterialf; PFNGLMATERIALFVPROC glad_glMaterialfv = NULL; static void GLAD_API_PTR glad_debug_impl_glMaterialfv(GLenum face, GLenum pname, const GLfloat * params) { _pre_call_gl_callback("glMaterialfv", (GLADapiproc) glad_glMaterialfv, 3, face, pname, params); glad_glMaterialfv(face, pname, params); _post_call_gl_callback(NULL, "glMaterialfv", (GLADapiproc) glad_glMaterialfv, 3, face, pname, params); } PFNGLMATERIALFVPROC glad_debug_glMaterialfv = glad_debug_impl_glMaterialfv; PFNGLMATERIALIPROC glad_glMateriali = NULL; static void GLAD_API_PTR glad_debug_impl_glMateriali(GLenum face, GLenum pname, GLint param) { _pre_call_gl_callback("glMateriali", (GLADapiproc) glad_glMateriali, 3, face, pname, param); glad_glMateriali(face, pname, param); _post_call_gl_callback(NULL, "glMateriali", (GLADapiproc) glad_glMateriali, 3, face, pname, param); } PFNGLMATERIALIPROC glad_debug_glMateriali = glad_debug_impl_glMateriali; PFNGLMATERIALIVPROC glad_glMaterialiv = NULL; static void GLAD_API_PTR glad_debug_impl_glMaterialiv(GLenum face, GLenum pname, const GLint * params) { _pre_call_gl_callback("glMaterialiv", (GLADapiproc) glad_glMaterialiv, 3, face, pname, params); glad_glMaterialiv(face, pname, params); _post_call_gl_callback(NULL, "glMaterialiv", (GLADapiproc) glad_glMaterialiv, 3, face, pname, params); } PFNGLMATERIALIVPROC glad_debug_glMaterialiv = glad_debug_impl_glMaterialiv; PFNGLMATRIXMODEPROC glad_glMatrixMode = NULL; static void GLAD_API_PTR glad_debug_impl_glMatrixMode(GLenum mode) { _pre_call_gl_callback("glMatrixMode", (GLADapiproc) glad_glMatrixMode, 1, mode); glad_glMatrixMode(mode); _post_call_gl_callback(NULL, "glMatrixMode", (GLADapiproc) glad_glMatrixMode, 1, mode); } PFNGLMATRIXMODEPROC glad_debug_glMatrixMode = glad_debug_impl_glMatrixMode; PFNGLMULTMATRIXDPROC glad_glMultMatrixd = NULL; static void GLAD_API_PTR glad_debug_impl_glMultMatrixd(const GLdouble * m) { _pre_call_gl_callback("glMultMatrixd", (GLADapiproc) glad_glMultMatrixd, 1, m); glad_glMultMatrixd(m); _post_call_gl_callback(NULL, "glMultMatrixd", (GLADapiproc) glad_glMultMatrixd, 1, m); } PFNGLMULTMATRIXDPROC glad_debug_glMultMatrixd = glad_debug_impl_glMultMatrixd; PFNGLMULTMATRIXFPROC glad_glMultMatrixf = NULL; static void GLAD_API_PTR glad_debug_impl_glMultMatrixf(const GLfloat * m) { _pre_call_gl_callback("glMultMatrixf", (GLADapiproc) glad_glMultMatrixf, 1, m); glad_glMultMatrixf(m); _post_call_gl_callback(NULL, "glMultMatrixf", (GLADapiproc) glad_glMultMatrixf, 1, m); } PFNGLMULTMATRIXFPROC glad_debug_glMultMatrixf = glad_debug_impl_glMultMatrixf; PFNGLMULTTRANSPOSEMATRIXDPROC glad_glMultTransposeMatrixd = NULL; static void GLAD_API_PTR glad_debug_impl_glMultTransposeMatrixd(const GLdouble * m) { _pre_call_gl_callback("glMultTransposeMatrixd", (GLADapiproc) glad_glMultTransposeMatrixd, 1, m); glad_glMultTransposeMatrixd(m); _post_call_gl_callback(NULL, "glMultTransposeMatrixd", (GLADapiproc) glad_glMultTransposeMatrixd, 1, m); } PFNGLMULTTRANSPOSEMATRIXDPROC glad_debug_glMultTransposeMatrixd = glad_debug_impl_glMultTransposeMatrixd; PFNGLMULTTRANSPOSEMATRIXFPROC glad_glMultTransposeMatrixf = NULL; static void GLAD_API_PTR glad_debug_impl_glMultTransposeMatrixf(const GLfloat * m) { _pre_call_gl_callback("glMultTransposeMatrixf", (GLADapiproc) glad_glMultTransposeMatrixf, 1, m); glad_glMultTransposeMatrixf(m); _post_call_gl_callback(NULL, "glMultTransposeMatrixf", (GLADapiproc) glad_glMultTransposeMatrixf, 1, m); } PFNGLMULTTRANSPOSEMATRIXFPROC glad_debug_glMultTransposeMatrixf = glad_debug_impl_glMultTransposeMatrixf; PFNGLMULTIDRAWARRAYSPROC glad_glMultiDrawArrays = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiDrawArrays(GLenum mode, const GLint * first, const GLsizei * count, GLsizei drawcount) { _pre_call_gl_callback("glMultiDrawArrays", (GLADapiproc) glad_glMultiDrawArrays, 4, mode, first, count, drawcount); glad_glMultiDrawArrays(mode, first, count, drawcount); _post_call_gl_callback(NULL, "glMultiDrawArrays", (GLADapiproc) glad_glMultiDrawArrays, 4, mode, first, count, drawcount); } PFNGLMULTIDRAWARRAYSPROC glad_debug_glMultiDrawArrays = glad_debug_impl_glMultiDrawArrays; PFNGLMULTIDRAWELEMENTSPROC glad_glMultiDrawElements = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiDrawElements(GLenum mode, const GLsizei * count, GLenum type, const void *const* indices, GLsizei drawcount) { _pre_call_gl_callback("glMultiDrawElements", (GLADapiproc) glad_glMultiDrawElements, 5, mode, count, type, indices, drawcount); glad_glMultiDrawElements(mode, count, type, indices, drawcount); _post_call_gl_callback(NULL, "glMultiDrawElements", (GLADapiproc) glad_glMultiDrawElements, 5, mode, count, type, indices, drawcount); } PFNGLMULTIDRAWELEMENTSPROC glad_debug_glMultiDrawElements = glad_debug_impl_glMultiDrawElements; PFNGLMULTITEXCOORD1DPROC glad_glMultiTexCoord1d = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord1d(GLenum target, GLdouble s) { _pre_call_gl_callback("glMultiTexCoord1d", (GLADapiproc) glad_glMultiTexCoord1d, 2, target, s); glad_glMultiTexCoord1d(target, s); _post_call_gl_callback(NULL, "glMultiTexCoord1d", (GLADapiproc) glad_glMultiTexCoord1d, 2, target, s); } PFNGLMULTITEXCOORD1DPROC glad_debug_glMultiTexCoord1d = glad_debug_impl_glMultiTexCoord1d; PFNGLMULTITEXCOORD1DVPROC glad_glMultiTexCoord1dv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord1dv(GLenum target, const GLdouble * v) { _pre_call_gl_callback("glMultiTexCoord1dv", (GLADapiproc) glad_glMultiTexCoord1dv, 2, target, v); glad_glMultiTexCoord1dv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord1dv", (GLADapiproc) glad_glMultiTexCoord1dv, 2, target, v); } PFNGLMULTITEXCOORD1DVPROC glad_debug_glMultiTexCoord1dv = glad_debug_impl_glMultiTexCoord1dv; PFNGLMULTITEXCOORD1FPROC glad_glMultiTexCoord1f = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord1f(GLenum target, GLfloat s) { _pre_call_gl_callback("glMultiTexCoord1f", (GLADapiproc) glad_glMultiTexCoord1f, 2, target, s); glad_glMultiTexCoord1f(target, s); _post_call_gl_callback(NULL, "glMultiTexCoord1f", (GLADapiproc) glad_glMultiTexCoord1f, 2, target, s); } PFNGLMULTITEXCOORD1FPROC glad_debug_glMultiTexCoord1f = glad_debug_impl_glMultiTexCoord1f; PFNGLMULTITEXCOORD1FVPROC glad_glMultiTexCoord1fv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord1fv(GLenum target, const GLfloat * v) { _pre_call_gl_callback("glMultiTexCoord1fv", (GLADapiproc) glad_glMultiTexCoord1fv, 2, target, v); glad_glMultiTexCoord1fv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord1fv", (GLADapiproc) glad_glMultiTexCoord1fv, 2, target, v); } PFNGLMULTITEXCOORD1FVPROC glad_debug_glMultiTexCoord1fv = glad_debug_impl_glMultiTexCoord1fv; PFNGLMULTITEXCOORD1IPROC glad_glMultiTexCoord1i = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord1i(GLenum target, GLint s) { _pre_call_gl_callback("glMultiTexCoord1i", (GLADapiproc) glad_glMultiTexCoord1i, 2, target, s); glad_glMultiTexCoord1i(target, s); _post_call_gl_callback(NULL, "glMultiTexCoord1i", (GLADapiproc) glad_glMultiTexCoord1i, 2, target, s); } PFNGLMULTITEXCOORD1IPROC glad_debug_glMultiTexCoord1i = glad_debug_impl_glMultiTexCoord1i; PFNGLMULTITEXCOORD1IVPROC glad_glMultiTexCoord1iv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord1iv(GLenum target, const GLint * v) { _pre_call_gl_callback("glMultiTexCoord1iv", (GLADapiproc) glad_glMultiTexCoord1iv, 2, target, v); glad_glMultiTexCoord1iv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord1iv", (GLADapiproc) glad_glMultiTexCoord1iv, 2, target, v); } PFNGLMULTITEXCOORD1IVPROC glad_debug_glMultiTexCoord1iv = glad_debug_impl_glMultiTexCoord1iv; PFNGLMULTITEXCOORD1SPROC glad_glMultiTexCoord1s = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord1s(GLenum target, GLshort s) { _pre_call_gl_callback("glMultiTexCoord1s", (GLADapiproc) glad_glMultiTexCoord1s, 2, target, s); glad_glMultiTexCoord1s(target, s); _post_call_gl_callback(NULL, "glMultiTexCoord1s", (GLADapiproc) glad_glMultiTexCoord1s, 2, target, s); } PFNGLMULTITEXCOORD1SPROC glad_debug_glMultiTexCoord1s = glad_debug_impl_glMultiTexCoord1s; PFNGLMULTITEXCOORD1SVPROC glad_glMultiTexCoord1sv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord1sv(GLenum target, const GLshort * v) { _pre_call_gl_callback("glMultiTexCoord1sv", (GLADapiproc) glad_glMultiTexCoord1sv, 2, target, v); glad_glMultiTexCoord1sv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord1sv", (GLADapiproc) glad_glMultiTexCoord1sv, 2, target, v); } PFNGLMULTITEXCOORD1SVPROC glad_debug_glMultiTexCoord1sv = glad_debug_impl_glMultiTexCoord1sv; PFNGLMULTITEXCOORD2DPROC glad_glMultiTexCoord2d = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord2d(GLenum target, GLdouble s, GLdouble t) { _pre_call_gl_callback("glMultiTexCoord2d", (GLADapiproc) glad_glMultiTexCoord2d, 3, target, s, t); glad_glMultiTexCoord2d(target, s, t); _post_call_gl_callback(NULL, "glMultiTexCoord2d", (GLADapiproc) glad_glMultiTexCoord2d, 3, target, s, t); } PFNGLMULTITEXCOORD2DPROC glad_debug_glMultiTexCoord2d = glad_debug_impl_glMultiTexCoord2d; PFNGLMULTITEXCOORD2DVPROC glad_glMultiTexCoord2dv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord2dv(GLenum target, const GLdouble * v) { _pre_call_gl_callback("glMultiTexCoord2dv", (GLADapiproc) glad_glMultiTexCoord2dv, 2, target, v); glad_glMultiTexCoord2dv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord2dv", (GLADapiproc) glad_glMultiTexCoord2dv, 2, target, v); } PFNGLMULTITEXCOORD2DVPROC glad_debug_glMultiTexCoord2dv = glad_debug_impl_glMultiTexCoord2dv; PFNGLMULTITEXCOORD2FPROC glad_glMultiTexCoord2f = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord2f(GLenum target, GLfloat s, GLfloat t) { _pre_call_gl_callback("glMultiTexCoord2f", (GLADapiproc) glad_glMultiTexCoord2f, 3, target, s, t); glad_glMultiTexCoord2f(target, s, t); _post_call_gl_callback(NULL, "glMultiTexCoord2f", (GLADapiproc) glad_glMultiTexCoord2f, 3, target, s, t); } PFNGLMULTITEXCOORD2FPROC glad_debug_glMultiTexCoord2f = glad_debug_impl_glMultiTexCoord2f; PFNGLMULTITEXCOORD2FVPROC glad_glMultiTexCoord2fv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord2fv(GLenum target, const GLfloat * v) { _pre_call_gl_callback("glMultiTexCoord2fv", (GLADapiproc) glad_glMultiTexCoord2fv, 2, target, v); glad_glMultiTexCoord2fv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord2fv", (GLADapiproc) glad_glMultiTexCoord2fv, 2, target, v); } PFNGLMULTITEXCOORD2FVPROC glad_debug_glMultiTexCoord2fv = glad_debug_impl_glMultiTexCoord2fv; PFNGLMULTITEXCOORD2IPROC glad_glMultiTexCoord2i = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord2i(GLenum target, GLint s, GLint t) { _pre_call_gl_callback("glMultiTexCoord2i", (GLADapiproc) glad_glMultiTexCoord2i, 3, target, s, t); glad_glMultiTexCoord2i(target, s, t); _post_call_gl_callback(NULL, "glMultiTexCoord2i", (GLADapiproc) glad_glMultiTexCoord2i, 3, target, s, t); } PFNGLMULTITEXCOORD2IPROC glad_debug_glMultiTexCoord2i = glad_debug_impl_glMultiTexCoord2i; PFNGLMULTITEXCOORD2IVPROC glad_glMultiTexCoord2iv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord2iv(GLenum target, const GLint * v) { _pre_call_gl_callback("glMultiTexCoord2iv", (GLADapiproc) glad_glMultiTexCoord2iv, 2, target, v); glad_glMultiTexCoord2iv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord2iv", (GLADapiproc) glad_glMultiTexCoord2iv, 2, target, v); } PFNGLMULTITEXCOORD2IVPROC glad_debug_glMultiTexCoord2iv = glad_debug_impl_glMultiTexCoord2iv; PFNGLMULTITEXCOORD2SPROC glad_glMultiTexCoord2s = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord2s(GLenum target, GLshort s, GLshort t) { _pre_call_gl_callback("glMultiTexCoord2s", (GLADapiproc) glad_glMultiTexCoord2s, 3, target, s, t); glad_glMultiTexCoord2s(target, s, t); _post_call_gl_callback(NULL, "glMultiTexCoord2s", (GLADapiproc) glad_glMultiTexCoord2s, 3, target, s, t); } PFNGLMULTITEXCOORD2SPROC glad_debug_glMultiTexCoord2s = glad_debug_impl_glMultiTexCoord2s; PFNGLMULTITEXCOORD2SVPROC glad_glMultiTexCoord2sv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord2sv(GLenum target, const GLshort * v) { _pre_call_gl_callback("glMultiTexCoord2sv", (GLADapiproc) glad_glMultiTexCoord2sv, 2, target, v); glad_glMultiTexCoord2sv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord2sv", (GLADapiproc) glad_glMultiTexCoord2sv, 2, target, v); } PFNGLMULTITEXCOORD2SVPROC glad_debug_glMultiTexCoord2sv = glad_debug_impl_glMultiTexCoord2sv; PFNGLMULTITEXCOORD3DPROC glad_glMultiTexCoord3d = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord3d(GLenum target, GLdouble s, GLdouble t, GLdouble r) { _pre_call_gl_callback("glMultiTexCoord3d", (GLADapiproc) glad_glMultiTexCoord3d, 4, target, s, t, r); glad_glMultiTexCoord3d(target, s, t, r); _post_call_gl_callback(NULL, "glMultiTexCoord3d", (GLADapiproc) glad_glMultiTexCoord3d, 4, target, s, t, r); } PFNGLMULTITEXCOORD3DPROC glad_debug_glMultiTexCoord3d = glad_debug_impl_glMultiTexCoord3d; PFNGLMULTITEXCOORD3DVPROC glad_glMultiTexCoord3dv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord3dv(GLenum target, const GLdouble * v) { _pre_call_gl_callback("glMultiTexCoord3dv", (GLADapiproc) glad_glMultiTexCoord3dv, 2, target, v); glad_glMultiTexCoord3dv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord3dv", (GLADapiproc) glad_glMultiTexCoord3dv, 2, target, v); } PFNGLMULTITEXCOORD3DVPROC glad_debug_glMultiTexCoord3dv = glad_debug_impl_glMultiTexCoord3dv; PFNGLMULTITEXCOORD3FPROC glad_glMultiTexCoord3f = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord3f(GLenum target, GLfloat s, GLfloat t, GLfloat r) { _pre_call_gl_callback("glMultiTexCoord3f", (GLADapiproc) glad_glMultiTexCoord3f, 4, target, s, t, r); glad_glMultiTexCoord3f(target, s, t, r); _post_call_gl_callback(NULL, "glMultiTexCoord3f", (GLADapiproc) glad_glMultiTexCoord3f, 4, target, s, t, r); } PFNGLMULTITEXCOORD3FPROC glad_debug_glMultiTexCoord3f = glad_debug_impl_glMultiTexCoord3f; PFNGLMULTITEXCOORD3FVPROC glad_glMultiTexCoord3fv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord3fv(GLenum target, const GLfloat * v) { _pre_call_gl_callback("glMultiTexCoord3fv", (GLADapiproc) glad_glMultiTexCoord3fv, 2, target, v); glad_glMultiTexCoord3fv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord3fv", (GLADapiproc) glad_glMultiTexCoord3fv, 2, target, v); } PFNGLMULTITEXCOORD3FVPROC glad_debug_glMultiTexCoord3fv = glad_debug_impl_glMultiTexCoord3fv; PFNGLMULTITEXCOORD3IPROC glad_glMultiTexCoord3i = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord3i(GLenum target, GLint s, GLint t, GLint r) { _pre_call_gl_callback("glMultiTexCoord3i", (GLADapiproc) glad_glMultiTexCoord3i, 4, target, s, t, r); glad_glMultiTexCoord3i(target, s, t, r); _post_call_gl_callback(NULL, "glMultiTexCoord3i", (GLADapiproc) glad_glMultiTexCoord3i, 4, target, s, t, r); } PFNGLMULTITEXCOORD3IPROC glad_debug_glMultiTexCoord3i = glad_debug_impl_glMultiTexCoord3i; PFNGLMULTITEXCOORD3IVPROC glad_glMultiTexCoord3iv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord3iv(GLenum target, const GLint * v) { _pre_call_gl_callback("glMultiTexCoord3iv", (GLADapiproc) glad_glMultiTexCoord3iv, 2, target, v); glad_glMultiTexCoord3iv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord3iv", (GLADapiproc) glad_glMultiTexCoord3iv, 2, target, v); } PFNGLMULTITEXCOORD3IVPROC glad_debug_glMultiTexCoord3iv = glad_debug_impl_glMultiTexCoord3iv; PFNGLMULTITEXCOORD3SPROC glad_glMultiTexCoord3s = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord3s(GLenum target, GLshort s, GLshort t, GLshort r) { _pre_call_gl_callback("glMultiTexCoord3s", (GLADapiproc) glad_glMultiTexCoord3s, 4, target, s, t, r); glad_glMultiTexCoord3s(target, s, t, r); _post_call_gl_callback(NULL, "glMultiTexCoord3s", (GLADapiproc) glad_glMultiTexCoord3s, 4, target, s, t, r); } PFNGLMULTITEXCOORD3SPROC glad_debug_glMultiTexCoord3s = glad_debug_impl_glMultiTexCoord3s; PFNGLMULTITEXCOORD3SVPROC glad_glMultiTexCoord3sv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord3sv(GLenum target, const GLshort * v) { _pre_call_gl_callback("glMultiTexCoord3sv", (GLADapiproc) glad_glMultiTexCoord3sv, 2, target, v); glad_glMultiTexCoord3sv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord3sv", (GLADapiproc) glad_glMultiTexCoord3sv, 2, target, v); } PFNGLMULTITEXCOORD3SVPROC glad_debug_glMultiTexCoord3sv = glad_debug_impl_glMultiTexCoord3sv; PFNGLMULTITEXCOORD4DPROC glad_glMultiTexCoord4d = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord4d(GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q) { _pre_call_gl_callback("glMultiTexCoord4d", (GLADapiproc) glad_glMultiTexCoord4d, 5, target, s, t, r, q); glad_glMultiTexCoord4d(target, s, t, r, q); _post_call_gl_callback(NULL, "glMultiTexCoord4d", (GLADapiproc) glad_glMultiTexCoord4d, 5, target, s, t, r, q); } PFNGLMULTITEXCOORD4DPROC glad_debug_glMultiTexCoord4d = glad_debug_impl_glMultiTexCoord4d; PFNGLMULTITEXCOORD4DVPROC glad_glMultiTexCoord4dv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord4dv(GLenum target, const GLdouble * v) { _pre_call_gl_callback("glMultiTexCoord4dv", (GLADapiproc) glad_glMultiTexCoord4dv, 2, target, v); glad_glMultiTexCoord4dv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord4dv", (GLADapiproc) glad_glMultiTexCoord4dv, 2, target, v); } PFNGLMULTITEXCOORD4DVPROC glad_debug_glMultiTexCoord4dv = glad_debug_impl_glMultiTexCoord4dv; PFNGLMULTITEXCOORD4FPROC glad_glMultiTexCoord4f = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord4f(GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q) { _pre_call_gl_callback("glMultiTexCoord4f", (GLADapiproc) glad_glMultiTexCoord4f, 5, target, s, t, r, q); glad_glMultiTexCoord4f(target, s, t, r, q); _post_call_gl_callback(NULL, "glMultiTexCoord4f", (GLADapiproc) glad_glMultiTexCoord4f, 5, target, s, t, r, q); } PFNGLMULTITEXCOORD4FPROC glad_debug_glMultiTexCoord4f = glad_debug_impl_glMultiTexCoord4f; PFNGLMULTITEXCOORD4FVPROC glad_glMultiTexCoord4fv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord4fv(GLenum target, const GLfloat * v) { _pre_call_gl_callback("glMultiTexCoord4fv", (GLADapiproc) glad_glMultiTexCoord4fv, 2, target, v); glad_glMultiTexCoord4fv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord4fv", (GLADapiproc) glad_glMultiTexCoord4fv, 2, target, v); } PFNGLMULTITEXCOORD4FVPROC glad_debug_glMultiTexCoord4fv = glad_debug_impl_glMultiTexCoord4fv; PFNGLMULTITEXCOORD4IPROC glad_glMultiTexCoord4i = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord4i(GLenum target, GLint s, GLint t, GLint r, GLint q) { _pre_call_gl_callback("glMultiTexCoord4i", (GLADapiproc) glad_glMultiTexCoord4i, 5, target, s, t, r, q); glad_glMultiTexCoord4i(target, s, t, r, q); _post_call_gl_callback(NULL, "glMultiTexCoord4i", (GLADapiproc) glad_glMultiTexCoord4i, 5, target, s, t, r, q); } PFNGLMULTITEXCOORD4IPROC glad_debug_glMultiTexCoord4i = glad_debug_impl_glMultiTexCoord4i; PFNGLMULTITEXCOORD4IVPROC glad_glMultiTexCoord4iv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord4iv(GLenum target, const GLint * v) { _pre_call_gl_callback("glMultiTexCoord4iv", (GLADapiproc) glad_glMultiTexCoord4iv, 2, target, v); glad_glMultiTexCoord4iv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord4iv", (GLADapiproc) glad_glMultiTexCoord4iv, 2, target, v); } PFNGLMULTITEXCOORD4IVPROC glad_debug_glMultiTexCoord4iv = glad_debug_impl_glMultiTexCoord4iv; PFNGLMULTITEXCOORD4SPROC glad_glMultiTexCoord4s = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord4s(GLenum target, GLshort s, GLshort t, GLshort r, GLshort q) { _pre_call_gl_callback("glMultiTexCoord4s", (GLADapiproc) glad_glMultiTexCoord4s, 5, target, s, t, r, q); glad_glMultiTexCoord4s(target, s, t, r, q); _post_call_gl_callback(NULL, "glMultiTexCoord4s", (GLADapiproc) glad_glMultiTexCoord4s, 5, target, s, t, r, q); } PFNGLMULTITEXCOORD4SPROC glad_debug_glMultiTexCoord4s = glad_debug_impl_glMultiTexCoord4s; PFNGLMULTITEXCOORD4SVPROC glad_glMultiTexCoord4sv = NULL; static void GLAD_API_PTR glad_debug_impl_glMultiTexCoord4sv(GLenum target, const GLshort * v) { _pre_call_gl_callback("glMultiTexCoord4sv", (GLADapiproc) glad_glMultiTexCoord4sv, 2, target, v); glad_glMultiTexCoord4sv(target, v); _post_call_gl_callback(NULL, "glMultiTexCoord4sv", (GLADapiproc) glad_glMultiTexCoord4sv, 2, target, v); } PFNGLMULTITEXCOORD4SVPROC glad_debug_glMultiTexCoord4sv = glad_debug_impl_glMultiTexCoord4sv; PFNGLNEWLISTPROC glad_glNewList = NULL; static void GLAD_API_PTR glad_debug_impl_glNewList(GLuint list, GLenum mode) { _pre_call_gl_callback("glNewList", (GLADapiproc) glad_glNewList, 2, list, mode); glad_glNewList(list, mode); _post_call_gl_callback(NULL, "glNewList", (GLADapiproc) glad_glNewList, 2, list, mode); } PFNGLNEWLISTPROC glad_debug_glNewList = glad_debug_impl_glNewList; PFNGLNORMAL3BPROC glad_glNormal3b = NULL; static void GLAD_API_PTR glad_debug_impl_glNormal3b(GLbyte nx, GLbyte ny, GLbyte nz) { _pre_call_gl_callback("glNormal3b", (GLADapiproc) glad_glNormal3b, 3, nx, ny, nz); glad_glNormal3b(nx, ny, nz); _post_call_gl_callback(NULL, "glNormal3b", (GLADapiproc) glad_glNormal3b, 3, nx, ny, nz); } PFNGLNORMAL3BPROC glad_debug_glNormal3b = glad_debug_impl_glNormal3b; PFNGLNORMAL3BVPROC glad_glNormal3bv = NULL; static void GLAD_API_PTR glad_debug_impl_glNormal3bv(const GLbyte * v) { _pre_call_gl_callback("glNormal3bv", (GLADapiproc) glad_glNormal3bv, 1, v); glad_glNormal3bv(v); _post_call_gl_callback(NULL, "glNormal3bv", (GLADapiproc) glad_glNormal3bv, 1, v); } PFNGLNORMAL3BVPROC glad_debug_glNormal3bv = glad_debug_impl_glNormal3bv; PFNGLNORMAL3DPROC glad_glNormal3d = NULL; static void GLAD_API_PTR glad_debug_impl_glNormal3d(GLdouble nx, GLdouble ny, GLdouble nz) { _pre_call_gl_callback("glNormal3d", (GLADapiproc) glad_glNormal3d, 3, nx, ny, nz); glad_glNormal3d(nx, ny, nz); _post_call_gl_callback(NULL, "glNormal3d", (GLADapiproc) glad_glNormal3d, 3, nx, ny, nz); } PFNGLNORMAL3DPROC glad_debug_glNormal3d = glad_debug_impl_glNormal3d; PFNGLNORMAL3DVPROC glad_glNormal3dv = NULL; static void GLAD_API_PTR glad_debug_impl_glNormal3dv(const GLdouble * v) { _pre_call_gl_callback("glNormal3dv", (GLADapiproc) glad_glNormal3dv, 1, v); glad_glNormal3dv(v); _post_call_gl_callback(NULL, "glNormal3dv", (GLADapiproc) glad_glNormal3dv, 1, v); } PFNGLNORMAL3DVPROC glad_debug_glNormal3dv = glad_debug_impl_glNormal3dv; PFNGLNORMAL3FPROC glad_glNormal3f = NULL; static void GLAD_API_PTR glad_debug_impl_glNormal3f(GLfloat nx, GLfloat ny, GLfloat nz) { _pre_call_gl_callback("glNormal3f", (GLADapiproc) glad_glNormal3f, 3, nx, ny, nz); glad_glNormal3f(nx, ny, nz); _post_call_gl_callback(NULL, "glNormal3f", (GLADapiproc) glad_glNormal3f, 3, nx, ny, nz); } PFNGLNORMAL3FPROC glad_debug_glNormal3f = glad_debug_impl_glNormal3f; PFNGLNORMAL3FVPROC glad_glNormal3fv = NULL; static void GLAD_API_PTR glad_debug_impl_glNormal3fv(const GLfloat * v) { _pre_call_gl_callback("glNormal3fv", (GLADapiproc) glad_glNormal3fv, 1, v); glad_glNormal3fv(v); _post_call_gl_callback(NULL, "glNormal3fv", (GLADapiproc) glad_glNormal3fv, 1, v); } PFNGLNORMAL3FVPROC glad_debug_glNormal3fv = glad_debug_impl_glNormal3fv; PFNGLNORMAL3IPROC glad_glNormal3i = NULL; static void GLAD_API_PTR glad_debug_impl_glNormal3i(GLint nx, GLint ny, GLint nz) { _pre_call_gl_callback("glNormal3i", (GLADapiproc) glad_glNormal3i, 3, nx, ny, nz); glad_glNormal3i(nx, ny, nz); _post_call_gl_callback(NULL, "glNormal3i", (GLADapiproc) glad_glNormal3i, 3, nx, ny, nz); } PFNGLNORMAL3IPROC glad_debug_glNormal3i = glad_debug_impl_glNormal3i; PFNGLNORMAL3IVPROC glad_glNormal3iv = NULL; static void GLAD_API_PTR glad_debug_impl_glNormal3iv(const GLint * v) { _pre_call_gl_callback("glNormal3iv", (GLADapiproc) glad_glNormal3iv, 1, v); glad_glNormal3iv(v); _post_call_gl_callback(NULL, "glNormal3iv", (GLADapiproc) glad_glNormal3iv, 1, v); } PFNGLNORMAL3IVPROC glad_debug_glNormal3iv = glad_debug_impl_glNormal3iv; PFNGLNORMAL3SPROC glad_glNormal3s = NULL; static void GLAD_API_PTR glad_debug_impl_glNormal3s(GLshort nx, GLshort ny, GLshort nz) { _pre_call_gl_callback("glNormal3s", (GLADapiproc) glad_glNormal3s, 3, nx, ny, nz); glad_glNormal3s(nx, ny, nz); _post_call_gl_callback(NULL, "glNormal3s", (GLADapiproc) glad_glNormal3s, 3, nx, ny, nz); } PFNGLNORMAL3SPROC glad_debug_glNormal3s = glad_debug_impl_glNormal3s; PFNGLNORMAL3SVPROC glad_glNormal3sv = NULL; static void GLAD_API_PTR glad_debug_impl_glNormal3sv(const GLshort * v) { _pre_call_gl_callback("glNormal3sv", (GLADapiproc) glad_glNormal3sv, 1, v); glad_glNormal3sv(v); _post_call_gl_callback(NULL, "glNormal3sv", (GLADapiproc) glad_glNormal3sv, 1, v); } PFNGLNORMAL3SVPROC glad_debug_glNormal3sv = glad_debug_impl_glNormal3sv; PFNGLNORMALPOINTERPROC glad_glNormalPointer = NULL; static void GLAD_API_PTR glad_debug_impl_glNormalPointer(GLenum type, GLsizei stride, const void * pointer) { _pre_call_gl_callback("glNormalPointer", (GLADapiproc) glad_glNormalPointer, 3, type, stride, pointer); glad_glNormalPointer(type, stride, pointer); _post_call_gl_callback(NULL, "glNormalPointer", (GLADapiproc) glad_glNormalPointer, 3, type, stride, pointer); } PFNGLNORMALPOINTERPROC glad_debug_glNormalPointer = glad_debug_impl_glNormalPointer; PFNGLOBJECTLABELPROC glad_glObjectLabel = NULL; static void GLAD_API_PTR glad_debug_impl_glObjectLabel(GLenum identifier, GLuint name, GLsizei length, const GLchar * label) { _pre_call_gl_callback("glObjectLabel", (GLADapiproc) glad_glObjectLabel, 4, identifier, name, length, label); glad_glObjectLabel(identifier, name, length, label); _post_call_gl_callback(NULL, "glObjectLabel", (GLADapiproc) glad_glObjectLabel, 4, identifier, name, length, label); } PFNGLOBJECTLABELPROC glad_debug_glObjectLabel = glad_debug_impl_glObjectLabel; PFNGLOBJECTPTRLABELPROC glad_glObjectPtrLabel = NULL; static void GLAD_API_PTR glad_debug_impl_glObjectPtrLabel(const void * ptr, GLsizei length, const GLchar * label) { _pre_call_gl_callback("glObjectPtrLabel", (GLADapiproc) glad_glObjectPtrLabel, 3, ptr, length, label); glad_glObjectPtrLabel(ptr, length, label); _post_call_gl_callback(NULL, "glObjectPtrLabel", (GLADapiproc) glad_glObjectPtrLabel, 3, ptr, length, label); } PFNGLOBJECTPTRLABELPROC glad_debug_glObjectPtrLabel = glad_debug_impl_glObjectPtrLabel; PFNGLORTHOPROC glad_glOrtho = NULL; static void GLAD_API_PTR glad_debug_impl_glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) { _pre_call_gl_callback("glOrtho", (GLADapiproc) glad_glOrtho, 6, left, right, bottom, top, zNear, zFar); glad_glOrtho(left, right, bottom, top, zNear, zFar); _post_call_gl_callback(NULL, "glOrtho", (GLADapiproc) glad_glOrtho, 6, left, right, bottom, top, zNear, zFar); } PFNGLORTHOPROC glad_debug_glOrtho = glad_debug_impl_glOrtho; PFNGLPASSTHROUGHPROC glad_glPassThrough = NULL; static void GLAD_API_PTR glad_debug_impl_glPassThrough(GLfloat token) { _pre_call_gl_callback("glPassThrough", (GLADapiproc) glad_glPassThrough, 1, token); glad_glPassThrough(token); _post_call_gl_callback(NULL, "glPassThrough", (GLADapiproc) glad_glPassThrough, 1, token); } PFNGLPASSTHROUGHPROC glad_debug_glPassThrough = glad_debug_impl_glPassThrough; PFNGLPIXELMAPFVPROC glad_glPixelMapfv = NULL; static void GLAD_API_PTR glad_debug_impl_glPixelMapfv(GLenum map, GLsizei mapsize, const GLfloat * values) { _pre_call_gl_callback("glPixelMapfv", (GLADapiproc) glad_glPixelMapfv, 3, map, mapsize, values); glad_glPixelMapfv(map, mapsize, values); _post_call_gl_callback(NULL, "glPixelMapfv", (GLADapiproc) glad_glPixelMapfv, 3, map, mapsize, values); } PFNGLPIXELMAPFVPROC glad_debug_glPixelMapfv = glad_debug_impl_glPixelMapfv; PFNGLPIXELMAPUIVPROC glad_glPixelMapuiv = NULL; static void GLAD_API_PTR glad_debug_impl_glPixelMapuiv(GLenum map, GLsizei mapsize, const GLuint * values) { _pre_call_gl_callback("glPixelMapuiv", (GLADapiproc) glad_glPixelMapuiv, 3, map, mapsize, values); glad_glPixelMapuiv(map, mapsize, values); _post_call_gl_callback(NULL, "glPixelMapuiv", (GLADapiproc) glad_glPixelMapuiv, 3, map, mapsize, values); } PFNGLPIXELMAPUIVPROC glad_debug_glPixelMapuiv = glad_debug_impl_glPixelMapuiv; PFNGLPIXELMAPUSVPROC glad_glPixelMapusv = NULL; static void GLAD_API_PTR glad_debug_impl_glPixelMapusv(GLenum map, GLsizei mapsize, const GLushort * values) { _pre_call_gl_callback("glPixelMapusv", (GLADapiproc) glad_glPixelMapusv, 3, map, mapsize, values); glad_glPixelMapusv(map, mapsize, values); _post_call_gl_callback(NULL, "glPixelMapusv", (GLADapiproc) glad_glPixelMapusv, 3, map, mapsize, values); } PFNGLPIXELMAPUSVPROC glad_debug_glPixelMapusv = glad_debug_impl_glPixelMapusv; PFNGLPIXELSTOREFPROC glad_glPixelStoref = NULL; static void GLAD_API_PTR glad_debug_impl_glPixelStoref(GLenum pname, GLfloat param) { _pre_call_gl_callback("glPixelStoref", (GLADapiproc) glad_glPixelStoref, 2, pname, param); glad_glPixelStoref(pname, param); _post_call_gl_callback(NULL, "glPixelStoref", (GLADapiproc) glad_glPixelStoref, 2, pname, param); } PFNGLPIXELSTOREFPROC glad_debug_glPixelStoref = glad_debug_impl_glPixelStoref; PFNGLPIXELSTOREIPROC glad_glPixelStorei = NULL; static void GLAD_API_PTR glad_debug_impl_glPixelStorei(GLenum pname, GLint param) { _pre_call_gl_callback("glPixelStorei", (GLADapiproc) glad_glPixelStorei, 2, pname, param); glad_glPixelStorei(pname, param); _post_call_gl_callback(NULL, "glPixelStorei", (GLADapiproc) glad_glPixelStorei, 2, pname, param); } PFNGLPIXELSTOREIPROC glad_debug_glPixelStorei = glad_debug_impl_glPixelStorei; PFNGLPIXELTRANSFERFPROC glad_glPixelTransferf = NULL; static void GLAD_API_PTR glad_debug_impl_glPixelTransferf(GLenum pname, GLfloat param) { _pre_call_gl_callback("glPixelTransferf", (GLADapiproc) glad_glPixelTransferf, 2, pname, param); glad_glPixelTransferf(pname, param); _post_call_gl_callback(NULL, "glPixelTransferf", (GLADapiproc) glad_glPixelTransferf, 2, pname, param); } PFNGLPIXELTRANSFERFPROC glad_debug_glPixelTransferf = glad_debug_impl_glPixelTransferf; PFNGLPIXELTRANSFERIPROC glad_glPixelTransferi = NULL; static void GLAD_API_PTR glad_debug_impl_glPixelTransferi(GLenum pname, GLint param) { _pre_call_gl_callback("glPixelTransferi", (GLADapiproc) glad_glPixelTransferi, 2, pname, param); glad_glPixelTransferi(pname, param); _post_call_gl_callback(NULL, "glPixelTransferi", (GLADapiproc) glad_glPixelTransferi, 2, pname, param); } PFNGLPIXELTRANSFERIPROC glad_debug_glPixelTransferi = glad_debug_impl_glPixelTransferi; PFNGLPIXELZOOMPROC glad_glPixelZoom = NULL; static void GLAD_API_PTR glad_debug_impl_glPixelZoom(GLfloat xfactor, GLfloat yfactor) { _pre_call_gl_callback("glPixelZoom", (GLADapiproc) glad_glPixelZoom, 2, xfactor, yfactor); glad_glPixelZoom(xfactor, yfactor); _post_call_gl_callback(NULL, "glPixelZoom", (GLADapiproc) glad_glPixelZoom, 2, xfactor, yfactor); } PFNGLPIXELZOOMPROC glad_debug_glPixelZoom = glad_debug_impl_glPixelZoom; PFNGLPOINTPARAMETERFPROC glad_glPointParameterf = NULL; static void GLAD_API_PTR glad_debug_impl_glPointParameterf(GLenum pname, GLfloat param) { _pre_call_gl_callback("glPointParameterf", (GLADapiproc) glad_glPointParameterf, 2, pname, param); glad_glPointParameterf(pname, param); _post_call_gl_callback(NULL, "glPointParameterf", (GLADapiproc) glad_glPointParameterf, 2, pname, param); } PFNGLPOINTPARAMETERFPROC glad_debug_glPointParameterf = glad_debug_impl_glPointParameterf; PFNGLPOINTPARAMETERFVPROC glad_glPointParameterfv = NULL; static void GLAD_API_PTR glad_debug_impl_glPointParameterfv(GLenum pname, const GLfloat * params) { _pre_call_gl_callback("glPointParameterfv", (GLADapiproc) glad_glPointParameterfv, 2, pname, params); glad_glPointParameterfv(pname, params); _post_call_gl_callback(NULL, "glPointParameterfv", (GLADapiproc) glad_glPointParameterfv, 2, pname, params); } PFNGLPOINTPARAMETERFVPROC glad_debug_glPointParameterfv = glad_debug_impl_glPointParameterfv; PFNGLPOINTPARAMETERIPROC glad_glPointParameteri = NULL; static void GLAD_API_PTR glad_debug_impl_glPointParameteri(GLenum pname, GLint param) { _pre_call_gl_callback("glPointParameteri", (GLADapiproc) glad_glPointParameteri, 2, pname, param); glad_glPointParameteri(pname, param); _post_call_gl_callback(NULL, "glPointParameteri", (GLADapiproc) glad_glPointParameteri, 2, pname, param); } PFNGLPOINTPARAMETERIPROC glad_debug_glPointParameteri = glad_debug_impl_glPointParameteri; PFNGLPOINTPARAMETERIVPROC glad_glPointParameteriv = NULL; static void GLAD_API_PTR glad_debug_impl_glPointParameteriv(GLenum pname, const GLint * params) { _pre_call_gl_callback("glPointParameteriv", (GLADapiproc) glad_glPointParameteriv, 2, pname, params); glad_glPointParameteriv(pname, params); _post_call_gl_callback(NULL, "glPointParameteriv", (GLADapiproc) glad_glPointParameteriv, 2, pname, params); } PFNGLPOINTPARAMETERIVPROC glad_debug_glPointParameteriv = glad_debug_impl_glPointParameteriv; PFNGLPOINTSIZEPROC glad_glPointSize = NULL; static void GLAD_API_PTR glad_debug_impl_glPointSize(GLfloat size) { _pre_call_gl_callback("glPointSize", (GLADapiproc) glad_glPointSize, 1, size); glad_glPointSize(size); _post_call_gl_callback(NULL, "glPointSize", (GLADapiproc) glad_glPointSize, 1, size); } PFNGLPOINTSIZEPROC glad_debug_glPointSize = glad_debug_impl_glPointSize; PFNGLPOLYGONMODEPROC glad_glPolygonMode = NULL; static void GLAD_API_PTR glad_debug_impl_glPolygonMode(GLenum face, GLenum mode) { _pre_call_gl_callback("glPolygonMode", (GLADapiproc) glad_glPolygonMode, 2, face, mode); glad_glPolygonMode(face, mode); _post_call_gl_callback(NULL, "glPolygonMode", (GLADapiproc) glad_glPolygonMode, 2, face, mode); } PFNGLPOLYGONMODEPROC glad_debug_glPolygonMode = glad_debug_impl_glPolygonMode; PFNGLPOLYGONOFFSETPROC glad_glPolygonOffset = NULL; static void GLAD_API_PTR glad_debug_impl_glPolygonOffset(GLfloat factor, GLfloat units) { _pre_call_gl_callback("glPolygonOffset", (GLADapiproc) glad_glPolygonOffset, 2, factor, units); glad_glPolygonOffset(factor, units); _post_call_gl_callback(NULL, "glPolygonOffset", (GLADapiproc) glad_glPolygonOffset, 2, factor, units); } PFNGLPOLYGONOFFSETPROC glad_debug_glPolygonOffset = glad_debug_impl_glPolygonOffset; PFNGLPOLYGONSTIPPLEPROC glad_glPolygonStipple = NULL; static void GLAD_API_PTR glad_debug_impl_glPolygonStipple(const GLubyte * mask) { _pre_call_gl_callback("glPolygonStipple", (GLADapiproc) glad_glPolygonStipple, 1, mask); glad_glPolygonStipple(mask); _post_call_gl_callback(NULL, "glPolygonStipple", (GLADapiproc) glad_glPolygonStipple, 1, mask); } PFNGLPOLYGONSTIPPLEPROC glad_debug_glPolygonStipple = glad_debug_impl_glPolygonStipple; PFNGLPOPATTRIBPROC glad_glPopAttrib = NULL; static void GLAD_API_PTR glad_debug_impl_glPopAttrib(void) { _pre_call_gl_callback("glPopAttrib", (GLADapiproc) glad_glPopAttrib, 0); glad_glPopAttrib(); _post_call_gl_callback(NULL, "glPopAttrib", (GLADapiproc) glad_glPopAttrib, 0); } PFNGLPOPATTRIBPROC glad_debug_glPopAttrib = glad_debug_impl_glPopAttrib; PFNGLPOPCLIENTATTRIBPROC glad_glPopClientAttrib = NULL; static void GLAD_API_PTR glad_debug_impl_glPopClientAttrib(void) { _pre_call_gl_callback("glPopClientAttrib", (GLADapiproc) glad_glPopClientAttrib, 0); glad_glPopClientAttrib(); _post_call_gl_callback(NULL, "glPopClientAttrib", (GLADapiproc) glad_glPopClientAttrib, 0); } PFNGLPOPCLIENTATTRIBPROC glad_debug_glPopClientAttrib = glad_debug_impl_glPopClientAttrib; PFNGLPOPDEBUGGROUPPROC glad_glPopDebugGroup = NULL; static void GLAD_API_PTR glad_debug_impl_glPopDebugGroup(void) { _pre_call_gl_callback("glPopDebugGroup", (GLADapiproc) glad_glPopDebugGroup, 0); glad_glPopDebugGroup(); _post_call_gl_callback(NULL, "glPopDebugGroup", (GLADapiproc) glad_glPopDebugGroup, 0); } PFNGLPOPDEBUGGROUPPROC glad_debug_glPopDebugGroup = glad_debug_impl_glPopDebugGroup; PFNGLPOPMATRIXPROC glad_glPopMatrix = NULL; static void GLAD_API_PTR glad_debug_impl_glPopMatrix(void) { _pre_call_gl_callback("glPopMatrix", (GLADapiproc) glad_glPopMatrix, 0); glad_glPopMatrix(); _post_call_gl_callback(NULL, "glPopMatrix", (GLADapiproc) glad_glPopMatrix, 0); } PFNGLPOPMATRIXPROC glad_debug_glPopMatrix = glad_debug_impl_glPopMatrix; PFNGLPOPNAMEPROC glad_glPopName = NULL; static void GLAD_API_PTR glad_debug_impl_glPopName(void) { _pre_call_gl_callback("glPopName", (GLADapiproc) glad_glPopName, 0); glad_glPopName(); _post_call_gl_callback(NULL, "glPopName", (GLADapiproc) glad_glPopName, 0); } PFNGLPOPNAMEPROC glad_debug_glPopName = glad_debug_impl_glPopName; PFNGLPRIMITIVERESTARTINDEXPROC glad_glPrimitiveRestartIndex = NULL; static void GLAD_API_PTR glad_debug_impl_glPrimitiveRestartIndex(GLuint index) { _pre_call_gl_callback("glPrimitiveRestartIndex", (GLADapiproc) glad_glPrimitiveRestartIndex, 1, index); glad_glPrimitiveRestartIndex(index); _post_call_gl_callback(NULL, "glPrimitiveRestartIndex", (GLADapiproc) glad_glPrimitiveRestartIndex, 1, index); } PFNGLPRIMITIVERESTARTINDEXPROC glad_debug_glPrimitiveRestartIndex = glad_debug_impl_glPrimitiveRestartIndex; PFNGLPRIORITIZETEXTURESPROC glad_glPrioritizeTextures = NULL; static void GLAD_API_PTR glad_debug_impl_glPrioritizeTextures(GLsizei n, const GLuint * textures, const GLfloat * priorities) { _pre_call_gl_callback("glPrioritizeTextures", (GLADapiproc) glad_glPrioritizeTextures, 3, n, textures, priorities); glad_glPrioritizeTextures(n, textures, priorities); _post_call_gl_callback(NULL, "glPrioritizeTextures", (GLADapiproc) glad_glPrioritizeTextures, 3, n, textures, priorities); } PFNGLPRIORITIZETEXTURESPROC glad_debug_glPrioritizeTextures = glad_debug_impl_glPrioritizeTextures; PFNGLPUSHATTRIBPROC glad_glPushAttrib = NULL; static void GLAD_API_PTR glad_debug_impl_glPushAttrib(GLbitfield mask) { _pre_call_gl_callback("glPushAttrib", (GLADapiproc) glad_glPushAttrib, 1, mask); glad_glPushAttrib(mask); _post_call_gl_callback(NULL, "glPushAttrib", (GLADapiproc) glad_glPushAttrib, 1, mask); } PFNGLPUSHATTRIBPROC glad_debug_glPushAttrib = glad_debug_impl_glPushAttrib; PFNGLPUSHCLIENTATTRIBPROC glad_glPushClientAttrib = NULL; static void GLAD_API_PTR glad_debug_impl_glPushClientAttrib(GLbitfield mask) { _pre_call_gl_callback("glPushClientAttrib", (GLADapiproc) glad_glPushClientAttrib, 1, mask); glad_glPushClientAttrib(mask); _post_call_gl_callback(NULL, "glPushClientAttrib", (GLADapiproc) glad_glPushClientAttrib, 1, mask); } PFNGLPUSHCLIENTATTRIBPROC glad_debug_glPushClientAttrib = glad_debug_impl_glPushClientAttrib; PFNGLPUSHDEBUGGROUPPROC glad_glPushDebugGroup = NULL; static void GLAD_API_PTR glad_debug_impl_glPushDebugGroup(GLenum source, GLuint id, GLsizei length, const GLchar * message) { _pre_call_gl_callback("glPushDebugGroup", (GLADapiproc) glad_glPushDebugGroup, 4, source, id, length, message); glad_glPushDebugGroup(source, id, length, message); _post_call_gl_callback(NULL, "glPushDebugGroup", (GLADapiproc) glad_glPushDebugGroup, 4, source, id, length, message); } PFNGLPUSHDEBUGGROUPPROC glad_debug_glPushDebugGroup = glad_debug_impl_glPushDebugGroup; PFNGLPUSHMATRIXPROC glad_glPushMatrix = NULL; static void GLAD_API_PTR glad_debug_impl_glPushMatrix(void) { _pre_call_gl_callback("glPushMatrix", (GLADapiproc) glad_glPushMatrix, 0); glad_glPushMatrix(); _post_call_gl_callback(NULL, "glPushMatrix", (GLADapiproc) glad_glPushMatrix, 0); } PFNGLPUSHMATRIXPROC glad_debug_glPushMatrix = glad_debug_impl_glPushMatrix; PFNGLPUSHNAMEPROC glad_glPushName = NULL; static void GLAD_API_PTR glad_debug_impl_glPushName(GLuint name) { _pre_call_gl_callback("glPushName", (GLADapiproc) glad_glPushName, 1, name); glad_glPushName(name); _post_call_gl_callback(NULL, "glPushName", (GLADapiproc) glad_glPushName, 1, name); } PFNGLPUSHNAMEPROC glad_debug_glPushName = glad_debug_impl_glPushName; PFNGLRASTERPOS2DPROC glad_glRasterPos2d = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos2d(GLdouble x, GLdouble y) { _pre_call_gl_callback("glRasterPos2d", (GLADapiproc) glad_glRasterPos2d, 2, x, y); glad_glRasterPos2d(x, y); _post_call_gl_callback(NULL, "glRasterPos2d", (GLADapiproc) glad_glRasterPos2d, 2, x, y); } PFNGLRASTERPOS2DPROC glad_debug_glRasterPos2d = glad_debug_impl_glRasterPos2d; PFNGLRASTERPOS2DVPROC glad_glRasterPos2dv = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos2dv(const GLdouble * v) { _pre_call_gl_callback("glRasterPos2dv", (GLADapiproc) glad_glRasterPos2dv, 1, v); glad_glRasterPos2dv(v); _post_call_gl_callback(NULL, "glRasterPos2dv", (GLADapiproc) glad_glRasterPos2dv, 1, v); } PFNGLRASTERPOS2DVPROC glad_debug_glRasterPos2dv = glad_debug_impl_glRasterPos2dv; PFNGLRASTERPOS2FPROC glad_glRasterPos2f = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos2f(GLfloat x, GLfloat y) { _pre_call_gl_callback("glRasterPos2f", (GLADapiproc) glad_glRasterPos2f, 2, x, y); glad_glRasterPos2f(x, y); _post_call_gl_callback(NULL, "glRasterPos2f", (GLADapiproc) glad_glRasterPos2f, 2, x, y); } PFNGLRASTERPOS2FPROC glad_debug_glRasterPos2f = glad_debug_impl_glRasterPos2f; PFNGLRASTERPOS2FVPROC glad_glRasterPos2fv = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos2fv(const GLfloat * v) { _pre_call_gl_callback("glRasterPos2fv", (GLADapiproc) glad_glRasterPos2fv, 1, v); glad_glRasterPos2fv(v); _post_call_gl_callback(NULL, "glRasterPos2fv", (GLADapiproc) glad_glRasterPos2fv, 1, v); } PFNGLRASTERPOS2FVPROC glad_debug_glRasterPos2fv = glad_debug_impl_glRasterPos2fv; PFNGLRASTERPOS2IPROC glad_glRasterPos2i = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos2i(GLint x, GLint y) { _pre_call_gl_callback("glRasterPos2i", (GLADapiproc) glad_glRasterPos2i, 2, x, y); glad_glRasterPos2i(x, y); _post_call_gl_callback(NULL, "glRasterPos2i", (GLADapiproc) glad_glRasterPos2i, 2, x, y); } PFNGLRASTERPOS2IPROC glad_debug_glRasterPos2i = glad_debug_impl_glRasterPos2i; PFNGLRASTERPOS2IVPROC glad_glRasterPos2iv = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos2iv(const GLint * v) { _pre_call_gl_callback("glRasterPos2iv", (GLADapiproc) glad_glRasterPos2iv, 1, v); glad_glRasterPos2iv(v); _post_call_gl_callback(NULL, "glRasterPos2iv", (GLADapiproc) glad_glRasterPos2iv, 1, v); } PFNGLRASTERPOS2IVPROC glad_debug_glRasterPos2iv = glad_debug_impl_glRasterPos2iv; PFNGLRASTERPOS2SPROC glad_glRasterPos2s = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos2s(GLshort x, GLshort y) { _pre_call_gl_callback("glRasterPos2s", (GLADapiproc) glad_glRasterPos2s, 2, x, y); glad_glRasterPos2s(x, y); _post_call_gl_callback(NULL, "glRasterPos2s", (GLADapiproc) glad_glRasterPos2s, 2, x, y); } PFNGLRASTERPOS2SPROC glad_debug_glRasterPos2s = glad_debug_impl_glRasterPos2s; PFNGLRASTERPOS2SVPROC glad_glRasterPos2sv = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos2sv(const GLshort * v) { _pre_call_gl_callback("glRasterPos2sv", (GLADapiproc) glad_glRasterPos2sv, 1, v); glad_glRasterPos2sv(v); _post_call_gl_callback(NULL, "glRasterPos2sv", (GLADapiproc) glad_glRasterPos2sv, 1, v); } PFNGLRASTERPOS2SVPROC glad_debug_glRasterPos2sv = glad_debug_impl_glRasterPos2sv; PFNGLRASTERPOS3DPROC glad_glRasterPos3d = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos3d(GLdouble x, GLdouble y, GLdouble z) { _pre_call_gl_callback("glRasterPos3d", (GLADapiproc) glad_glRasterPos3d, 3, x, y, z); glad_glRasterPos3d(x, y, z); _post_call_gl_callback(NULL, "glRasterPos3d", (GLADapiproc) glad_glRasterPos3d, 3, x, y, z); } PFNGLRASTERPOS3DPROC glad_debug_glRasterPos3d = glad_debug_impl_glRasterPos3d; PFNGLRASTERPOS3DVPROC glad_glRasterPos3dv = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos3dv(const GLdouble * v) { _pre_call_gl_callback("glRasterPos3dv", (GLADapiproc) glad_glRasterPos3dv, 1, v); glad_glRasterPos3dv(v); _post_call_gl_callback(NULL, "glRasterPos3dv", (GLADapiproc) glad_glRasterPos3dv, 1, v); } PFNGLRASTERPOS3DVPROC glad_debug_glRasterPos3dv = glad_debug_impl_glRasterPos3dv; PFNGLRASTERPOS3FPROC glad_glRasterPos3f = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos3f(GLfloat x, GLfloat y, GLfloat z) { _pre_call_gl_callback("glRasterPos3f", (GLADapiproc) glad_glRasterPos3f, 3, x, y, z); glad_glRasterPos3f(x, y, z); _post_call_gl_callback(NULL, "glRasterPos3f", (GLADapiproc) glad_glRasterPos3f, 3, x, y, z); } PFNGLRASTERPOS3FPROC glad_debug_glRasterPos3f = glad_debug_impl_glRasterPos3f; PFNGLRASTERPOS3FVPROC glad_glRasterPos3fv = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos3fv(const GLfloat * v) { _pre_call_gl_callback("glRasterPos3fv", (GLADapiproc) glad_glRasterPos3fv, 1, v); glad_glRasterPos3fv(v); _post_call_gl_callback(NULL, "glRasterPos3fv", (GLADapiproc) glad_glRasterPos3fv, 1, v); } PFNGLRASTERPOS3FVPROC glad_debug_glRasterPos3fv = glad_debug_impl_glRasterPos3fv; PFNGLRASTERPOS3IPROC glad_glRasterPos3i = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos3i(GLint x, GLint y, GLint z) { _pre_call_gl_callback("glRasterPos3i", (GLADapiproc) glad_glRasterPos3i, 3, x, y, z); glad_glRasterPos3i(x, y, z); _post_call_gl_callback(NULL, "glRasterPos3i", (GLADapiproc) glad_glRasterPos3i, 3, x, y, z); } PFNGLRASTERPOS3IPROC glad_debug_glRasterPos3i = glad_debug_impl_glRasterPos3i; PFNGLRASTERPOS3IVPROC glad_glRasterPos3iv = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos3iv(const GLint * v) { _pre_call_gl_callback("glRasterPos3iv", (GLADapiproc) glad_glRasterPos3iv, 1, v); glad_glRasterPos3iv(v); _post_call_gl_callback(NULL, "glRasterPos3iv", (GLADapiproc) glad_glRasterPos3iv, 1, v); } PFNGLRASTERPOS3IVPROC glad_debug_glRasterPos3iv = glad_debug_impl_glRasterPos3iv; PFNGLRASTERPOS3SPROC glad_glRasterPos3s = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos3s(GLshort x, GLshort y, GLshort z) { _pre_call_gl_callback("glRasterPos3s", (GLADapiproc) glad_glRasterPos3s, 3, x, y, z); glad_glRasterPos3s(x, y, z); _post_call_gl_callback(NULL, "glRasterPos3s", (GLADapiproc) glad_glRasterPos3s, 3, x, y, z); } PFNGLRASTERPOS3SPROC glad_debug_glRasterPos3s = glad_debug_impl_glRasterPos3s; PFNGLRASTERPOS3SVPROC glad_glRasterPos3sv = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos3sv(const GLshort * v) { _pre_call_gl_callback("glRasterPos3sv", (GLADapiproc) glad_glRasterPos3sv, 1, v); glad_glRasterPos3sv(v); _post_call_gl_callback(NULL, "glRasterPos3sv", (GLADapiproc) glad_glRasterPos3sv, 1, v); } PFNGLRASTERPOS3SVPROC glad_debug_glRasterPos3sv = glad_debug_impl_glRasterPos3sv; PFNGLRASTERPOS4DPROC glad_glRasterPos4d = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w) { _pre_call_gl_callback("glRasterPos4d", (GLADapiproc) glad_glRasterPos4d, 4, x, y, z, w); glad_glRasterPos4d(x, y, z, w); _post_call_gl_callback(NULL, "glRasterPos4d", (GLADapiproc) glad_glRasterPos4d, 4, x, y, z, w); } PFNGLRASTERPOS4DPROC glad_debug_glRasterPos4d = glad_debug_impl_glRasterPos4d; PFNGLRASTERPOS4DVPROC glad_glRasterPos4dv = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos4dv(const GLdouble * v) { _pre_call_gl_callback("glRasterPos4dv", (GLADapiproc) glad_glRasterPos4dv, 1, v); glad_glRasterPos4dv(v); _post_call_gl_callback(NULL, "glRasterPos4dv", (GLADapiproc) glad_glRasterPos4dv, 1, v); } PFNGLRASTERPOS4DVPROC glad_debug_glRasterPos4dv = glad_debug_impl_glRasterPos4dv; PFNGLRASTERPOS4FPROC glad_glRasterPos4f = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) { _pre_call_gl_callback("glRasterPos4f", (GLADapiproc) glad_glRasterPos4f, 4, x, y, z, w); glad_glRasterPos4f(x, y, z, w); _post_call_gl_callback(NULL, "glRasterPos4f", (GLADapiproc) glad_glRasterPos4f, 4, x, y, z, w); } PFNGLRASTERPOS4FPROC glad_debug_glRasterPos4f = glad_debug_impl_glRasterPos4f; PFNGLRASTERPOS4FVPROC glad_glRasterPos4fv = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos4fv(const GLfloat * v) { _pre_call_gl_callback("glRasterPos4fv", (GLADapiproc) glad_glRasterPos4fv, 1, v); glad_glRasterPos4fv(v); _post_call_gl_callback(NULL, "glRasterPos4fv", (GLADapiproc) glad_glRasterPos4fv, 1, v); } PFNGLRASTERPOS4FVPROC glad_debug_glRasterPos4fv = glad_debug_impl_glRasterPos4fv; PFNGLRASTERPOS4IPROC glad_glRasterPos4i = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos4i(GLint x, GLint y, GLint z, GLint w) { _pre_call_gl_callback("glRasterPos4i", (GLADapiproc) glad_glRasterPos4i, 4, x, y, z, w); glad_glRasterPos4i(x, y, z, w); _post_call_gl_callback(NULL, "glRasterPos4i", (GLADapiproc) glad_glRasterPos4i, 4, x, y, z, w); } PFNGLRASTERPOS4IPROC glad_debug_glRasterPos4i = glad_debug_impl_glRasterPos4i; PFNGLRASTERPOS4IVPROC glad_glRasterPos4iv = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos4iv(const GLint * v) { _pre_call_gl_callback("glRasterPos4iv", (GLADapiproc) glad_glRasterPos4iv, 1, v); glad_glRasterPos4iv(v); _post_call_gl_callback(NULL, "glRasterPos4iv", (GLADapiproc) glad_glRasterPos4iv, 1, v); } PFNGLRASTERPOS4IVPROC glad_debug_glRasterPos4iv = glad_debug_impl_glRasterPos4iv; PFNGLRASTERPOS4SPROC glad_glRasterPos4s = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos4s(GLshort x, GLshort y, GLshort z, GLshort w) { _pre_call_gl_callback("glRasterPos4s", (GLADapiproc) glad_glRasterPos4s, 4, x, y, z, w); glad_glRasterPos4s(x, y, z, w); _post_call_gl_callback(NULL, "glRasterPos4s", (GLADapiproc) glad_glRasterPos4s, 4, x, y, z, w); } PFNGLRASTERPOS4SPROC glad_debug_glRasterPos4s = glad_debug_impl_glRasterPos4s; PFNGLRASTERPOS4SVPROC glad_glRasterPos4sv = NULL; static void GLAD_API_PTR glad_debug_impl_glRasterPos4sv(const GLshort * v) { _pre_call_gl_callback("glRasterPos4sv", (GLADapiproc) glad_glRasterPos4sv, 1, v); glad_glRasterPos4sv(v); _post_call_gl_callback(NULL, "glRasterPos4sv", (GLADapiproc) glad_glRasterPos4sv, 1, v); } PFNGLRASTERPOS4SVPROC glad_debug_glRasterPos4sv = glad_debug_impl_glRasterPos4sv; PFNGLREADBUFFERPROC glad_glReadBuffer = NULL; static void GLAD_API_PTR glad_debug_impl_glReadBuffer(GLenum src) { _pre_call_gl_callback("glReadBuffer", (GLADapiproc) glad_glReadBuffer, 1, src); glad_glReadBuffer(src); _post_call_gl_callback(NULL, "glReadBuffer", (GLADapiproc) glad_glReadBuffer, 1, src); } PFNGLREADBUFFERPROC glad_debug_glReadBuffer = glad_debug_impl_glReadBuffer; PFNGLREADPIXELSPROC glad_glReadPixels = NULL; static void GLAD_API_PTR glad_debug_impl_glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * pixels) { _pre_call_gl_callback("glReadPixels", (GLADapiproc) glad_glReadPixels, 7, x, y, width, height, format, type, pixels); glad_glReadPixels(x, y, width, height, format, type, pixels); _post_call_gl_callback(NULL, "glReadPixels", (GLADapiproc) glad_glReadPixels, 7, x, y, width, height, format, type, pixels); } PFNGLREADPIXELSPROC glad_debug_glReadPixels = glad_debug_impl_glReadPixels; PFNGLREADNPIXELSARBPROC glad_glReadnPixelsARB = NULL; static void GLAD_API_PTR glad_debug_impl_glReadnPixelsARB(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void * data) { _pre_call_gl_callback("glReadnPixelsARB", (GLADapiproc) glad_glReadnPixelsARB, 8, x, y, width, height, format, type, bufSize, data); glad_glReadnPixelsARB(x, y, width, height, format, type, bufSize, data); _post_call_gl_callback(NULL, "glReadnPixelsARB", (GLADapiproc) glad_glReadnPixelsARB, 8, x, y, width, height, format, type, bufSize, data); } PFNGLREADNPIXELSARBPROC glad_debug_glReadnPixelsARB = glad_debug_impl_glReadnPixelsARB; PFNGLRECTDPROC glad_glRectd = NULL; static void GLAD_API_PTR glad_debug_impl_glRectd(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2) { _pre_call_gl_callback("glRectd", (GLADapiproc) glad_glRectd, 4, x1, y1, x2, y2); glad_glRectd(x1, y1, x2, y2); _post_call_gl_callback(NULL, "glRectd", (GLADapiproc) glad_glRectd, 4, x1, y1, x2, y2); } PFNGLRECTDPROC glad_debug_glRectd = glad_debug_impl_glRectd; PFNGLRECTDVPROC glad_glRectdv = NULL; static void GLAD_API_PTR glad_debug_impl_glRectdv(const GLdouble * v1, const GLdouble * v2) { _pre_call_gl_callback("glRectdv", (GLADapiproc) glad_glRectdv, 2, v1, v2); glad_glRectdv(v1, v2); _post_call_gl_callback(NULL, "glRectdv", (GLADapiproc) glad_glRectdv, 2, v1, v2); } PFNGLRECTDVPROC glad_debug_glRectdv = glad_debug_impl_glRectdv; PFNGLRECTFPROC glad_glRectf = NULL; static void GLAD_API_PTR glad_debug_impl_glRectf(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2) { _pre_call_gl_callback("glRectf", (GLADapiproc) glad_glRectf, 4, x1, y1, x2, y2); glad_glRectf(x1, y1, x2, y2); _post_call_gl_callback(NULL, "glRectf", (GLADapiproc) glad_glRectf, 4, x1, y1, x2, y2); } PFNGLRECTFPROC glad_debug_glRectf = glad_debug_impl_glRectf; PFNGLRECTFVPROC glad_glRectfv = NULL; static void GLAD_API_PTR glad_debug_impl_glRectfv(const GLfloat * v1, const GLfloat * v2) { _pre_call_gl_callback("glRectfv", (GLADapiproc) glad_glRectfv, 2, v1, v2); glad_glRectfv(v1, v2); _post_call_gl_callback(NULL, "glRectfv", (GLADapiproc) glad_glRectfv, 2, v1, v2); } PFNGLRECTFVPROC glad_debug_glRectfv = glad_debug_impl_glRectfv; PFNGLRECTIPROC glad_glRecti = NULL; static void GLAD_API_PTR glad_debug_impl_glRecti(GLint x1, GLint y1, GLint x2, GLint y2) { _pre_call_gl_callback("glRecti", (GLADapiproc) glad_glRecti, 4, x1, y1, x2, y2); glad_glRecti(x1, y1, x2, y2); _post_call_gl_callback(NULL, "glRecti", (GLADapiproc) glad_glRecti, 4, x1, y1, x2, y2); } PFNGLRECTIPROC glad_debug_glRecti = glad_debug_impl_glRecti; PFNGLRECTIVPROC glad_glRectiv = NULL; static void GLAD_API_PTR glad_debug_impl_glRectiv(const GLint * v1, const GLint * v2) { _pre_call_gl_callback("glRectiv", (GLADapiproc) glad_glRectiv, 2, v1, v2); glad_glRectiv(v1, v2); _post_call_gl_callback(NULL, "glRectiv", (GLADapiproc) glad_glRectiv, 2, v1, v2); } PFNGLRECTIVPROC glad_debug_glRectiv = glad_debug_impl_glRectiv; PFNGLRECTSPROC glad_glRects = NULL; static void GLAD_API_PTR glad_debug_impl_glRects(GLshort x1, GLshort y1, GLshort x2, GLshort y2) { _pre_call_gl_callback("glRects", (GLADapiproc) glad_glRects, 4, x1, y1, x2, y2); glad_glRects(x1, y1, x2, y2); _post_call_gl_callback(NULL, "glRects", (GLADapiproc) glad_glRects, 4, x1, y1, x2, y2); } PFNGLRECTSPROC glad_debug_glRects = glad_debug_impl_glRects; PFNGLRECTSVPROC glad_glRectsv = NULL; static void GLAD_API_PTR glad_debug_impl_glRectsv(const GLshort * v1, const GLshort * v2) { _pre_call_gl_callback("glRectsv", (GLADapiproc) glad_glRectsv, 2, v1, v2); glad_glRectsv(v1, v2); _post_call_gl_callback(NULL, "glRectsv", (GLADapiproc) glad_glRectsv, 2, v1, v2); } PFNGLRECTSVPROC glad_debug_glRectsv = glad_debug_impl_glRectsv; PFNGLRENDERMODEPROC glad_glRenderMode = NULL; static GLint GLAD_API_PTR glad_debug_impl_glRenderMode(GLenum mode) { GLint ret; _pre_call_gl_callback("glRenderMode", (GLADapiproc) glad_glRenderMode, 1, mode); ret = glad_glRenderMode(mode); _post_call_gl_callback((void*) &ret, "glRenderMode", (GLADapiproc) glad_glRenderMode, 1, mode); return ret; } PFNGLRENDERMODEPROC glad_debug_glRenderMode = glad_debug_impl_glRenderMode; PFNGLRENDERBUFFERSTORAGEPROC glad_glRenderbufferStorage = NULL; static void GLAD_API_PTR glad_debug_impl_glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height) { _pre_call_gl_callback("glRenderbufferStorage", (GLADapiproc) glad_glRenderbufferStorage, 4, target, internalformat, width, height); glad_glRenderbufferStorage(target, internalformat, width, height); _post_call_gl_callback(NULL, "glRenderbufferStorage", (GLADapiproc) glad_glRenderbufferStorage, 4, target, internalformat, width, height); } PFNGLRENDERBUFFERSTORAGEPROC glad_debug_glRenderbufferStorage = glad_debug_impl_glRenderbufferStorage; PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample = NULL; static void GLAD_API_PTR glad_debug_impl_glRenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) { _pre_call_gl_callback("glRenderbufferStorageMultisample", (GLADapiproc) glad_glRenderbufferStorageMultisample, 5, target, samples, internalformat, width, height); glad_glRenderbufferStorageMultisample(target, samples, internalformat, width, height); _post_call_gl_callback(NULL, "glRenderbufferStorageMultisample", (GLADapiproc) glad_glRenderbufferStorageMultisample, 5, target, samples, internalformat, width, height); } PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_debug_glRenderbufferStorageMultisample = glad_debug_impl_glRenderbufferStorageMultisample; PFNGLROTATEDPROC glad_glRotated = NULL; static void GLAD_API_PTR glad_debug_impl_glRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z) { _pre_call_gl_callback("glRotated", (GLADapiproc) glad_glRotated, 4, angle, x, y, z); glad_glRotated(angle, x, y, z); _post_call_gl_callback(NULL, "glRotated", (GLADapiproc) glad_glRotated, 4, angle, x, y, z); } PFNGLROTATEDPROC glad_debug_glRotated = glad_debug_impl_glRotated; PFNGLROTATEFPROC glad_glRotatef = NULL; static void GLAD_API_PTR glad_debug_impl_glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) { _pre_call_gl_callback("glRotatef", (GLADapiproc) glad_glRotatef, 4, angle, x, y, z); glad_glRotatef(angle, x, y, z); _post_call_gl_callback(NULL, "glRotatef", (GLADapiproc) glad_glRotatef, 4, angle, x, y, z); } PFNGLROTATEFPROC glad_debug_glRotatef = glad_debug_impl_glRotatef; PFNGLSAMPLECOVERAGEPROC glad_glSampleCoverage = NULL; static void GLAD_API_PTR glad_debug_impl_glSampleCoverage(GLfloat value, GLboolean invert) { _pre_call_gl_callback("glSampleCoverage", (GLADapiproc) glad_glSampleCoverage, 2, value, invert); glad_glSampleCoverage(value, invert); _post_call_gl_callback(NULL, "glSampleCoverage", (GLADapiproc) glad_glSampleCoverage, 2, value, invert); } PFNGLSAMPLECOVERAGEPROC glad_debug_glSampleCoverage = glad_debug_impl_glSampleCoverage; PFNGLSAMPLECOVERAGEARBPROC glad_glSampleCoverageARB = NULL; static void GLAD_API_PTR glad_debug_impl_glSampleCoverageARB(GLfloat value, GLboolean invert) { _pre_call_gl_callback("glSampleCoverageARB", (GLADapiproc) glad_glSampleCoverageARB, 2, value, invert); glad_glSampleCoverageARB(value, invert); _post_call_gl_callback(NULL, "glSampleCoverageARB", (GLADapiproc) glad_glSampleCoverageARB, 2, value, invert); } PFNGLSAMPLECOVERAGEARBPROC glad_debug_glSampleCoverageARB = glad_debug_impl_glSampleCoverageARB; PFNGLSCALEDPROC glad_glScaled = NULL; static void GLAD_API_PTR glad_debug_impl_glScaled(GLdouble x, GLdouble y, GLdouble z) { _pre_call_gl_callback("glScaled", (GLADapiproc) glad_glScaled, 3, x, y, z); glad_glScaled(x, y, z); _post_call_gl_callback(NULL, "glScaled", (GLADapiproc) glad_glScaled, 3, x, y, z); } PFNGLSCALEDPROC glad_debug_glScaled = glad_debug_impl_glScaled; PFNGLSCALEFPROC glad_glScalef = NULL; static void GLAD_API_PTR glad_debug_impl_glScalef(GLfloat x, GLfloat y, GLfloat z) { _pre_call_gl_callback("glScalef", (GLADapiproc) glad_glScalef, 3, x, y, z); glad_glScalef(x, y, z); _post_call_gl_callback(NULL, "glScalef", (GLADapiproc) glad_glScalef, 3, x, y, z); } PFNGLSCALEFPROC glad_debug_glScalef = glad_debug_impl_glScalef; PFNGLSCISSORPROC glad_glScissor = NULL; static void GLAD_API_PTR glad_debug_impl_glScissor(GLint x, GLint y, GLsizei width, GLsizei height) { _pre_call_gl_callback("glScissor", (GLADapiproc) glad_glScissor, 4, x, y, width, height); glad_glScissor(x, y, width, height); _post_call_gl_callback(NULL, "glScissor", (GLADapiproc) glad_glScissor, 4, x, y, width, height); } PFNGLSCISSORPROC glad_debug_glScissor = glad_debug_impl_glScissor; PFNGLSECONDARYCOLOR3BPROC glad_glSecondaryColor3b = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3b(GLbyte red, GLbyte green, GLbyte blue) { _pre_call_gl_callback("glSecondaryColor3b", (GLADapiproc) glad_glSecondaryColor3b, 3, red, green, blue); glad_glSecondaryColor3b(red, green, blue); _post_call_gl_callback(NULL, "glSecondaryColor3b", (GLADapiproc) glad_glSecondaryColor3b, 3, red, green, blue); } PFNGLSECONDARYCOLOR3BPROC glad_debug_glSecondaryColor3b = glad_debug_impl_glSecondaryColor3b; PFNGLSECONDARYCOLOR3BVPROC glad_glSecondaryColor3bv = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3bv(const GLbyte * v) { _pre_call_gl_callback("glSecondaryColor3bv", (GLADapiproc) glad_glSecondaryColor3bv, 1, v); glad_glSecondaryColor3bv(v); _post_call_gl_callback(NULL, "glSecondaryColor3bv", (GLADapiproc) glad_glSecondaryColor3bv, 1, v); } PFNGLSECONDARYCOLOR3BVPROC glad_debug_glSecondaryColor3bv = glad_debug_impl_glSecondaryColor3bv; PFNGLSECONDARYCOLOR3DPROC glad_glSecondaryColor3d = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3d(GLdouble red, GLdouble green, GLdouble blue) { _pre_call_gl_callback("glSecondaryColor3d", (GLADapiproc) glad_glSecondaryColor3d, 3, red, green, blue); glad_glSecondaryColor3d(red, green, blue); _post_call_gl_callback(NULL, "glSecondaryColor3d", (GLADapiproc) glad_glSecondaryColor3d, 3, red, green, blue); } PFNGLSECONDARYCOLOR3DPROC glad_debug_glSecondaryColor3d = glad_debug_impl_glSecondaryColor3d; PFNGLSECONDARYCOLOR3DVPROC glad_glSecondaryColor3dv = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3dv(const GLdouble * v) { _pre_call_gl_callback("glSecondaryColor3dv", (GLADapiproc) glad_glSecondaryColor3dv, 1, v); glad_glSecondaryColor3dv(v); _post_call_gl_callback(NULL, "glSecondaryColor3dv", (GLADapiproc) glad_glSecondaryColor3dv, 1, v); } PFNGLSECONDARYCOLOR3DVPROC glad_debug_glSecondaryColor3dv = glad_debug_impl_glSecondaryColor3dv; PFNGLSECONDARYCOLOR3FPROC glad_glSecondaryColor3f = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3f(GLfloat red, GLfloat green, GLfloat blue) { _pre_call_gl_callback("glSecondaryColor3f", (GLADapiproc) glad_glSecondaryColor3f, 3, red, green, blue); glad_glSecondaryColor3f(red, green, blue); _post_call_gl_callback(NULL, "glSecondaryColor3f", (GLADapiproc) glad_glSecondaryColor3f, 3, red, green, blue); } PFNGLSECONDARYCOLOR3FPROC glad_debug_glSecondaryColor3f = glad_debug_impl_glSecondaryColor3f; PFNGLSECONDARYCOLOR3FVPROC glad_glSecondaryColor3fv = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3fv(const GLfloat * v) { _pre_call_gl_callback("glSecondaryColor3fv", (GLADapiproc) glad_glSecondaryColor3fv, 1, v); glad_glSecondaryColor3fv(v); _post_call_gl_callback(NULL, "glSecondaryColor3fv", (GLADapiproc) glad_glSecondaryColor3fv, 1, v); } PFNGLSECONDARYCOLOR3FVPROC glad_debug_glSecondaryColor3fv = glad_debug_impl_glSecondaryColor3fv; PFNGLSECONDARYCOLOR3IPROC glad_glSecondaryColor3i = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3i(GLint red, GLint green, GLint blue) { _pre_call_gl_callback("glSecondaryColor3i", (GLADapiproc) glad_glSecondaryColor3i, 3, red, green, blue); glad_glSecondaryColor3i(red, green, blue); _post_call_gl_callback(NULL, "glSecondaryColor3i", (GLADapiproc) glad_glSecondaryColor3i, 3, red, green, blue); } PFNGLSECONDARYCOLOR3IPROC glad_debug_glSecondaryColor3i = glad_debug_impl_glSecondaryColor3i; PFNGLSECONDARYCOLOR3IVPROC glad_glSecondaryColor3iv = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3iv(const GLint * v) { _pre_call_gl_callback("glSecondaryColor3iv", (GLADapiproc) glad_glSecondaryColor3iv, 1, v); glad_glSecondaryColor3iv(v); _post_call_gl_callback(NULL, "glSecondaryColor3iv", (GLADapiproc) glad_glSecondaryColor3iv, 1, v); } PFNGLSECONDARYCOLOR3IVPROC glad_debug_glSecondaryColor3iv = glad_debug_impl_glSecondaryColor3iv; PFNGLSECONDARYCOLOR3SPROC glad_glSecondaryColor3s = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3s(GLshort red, GLshort green, GLshort blue) { _pre_call_gl_callback("glSecondaryColor3s", (GLADapiproc) glad_glSecondaryColor3s, 3, red, green, blue); glad_glSecondaryColor3s(red, green, blue); _post_call_gl_callback(NULL, "glSecondaryColor3s", (GLADapiproc) glad_glSecondaryColor3s, 3, red, green, blue); } PFNGLSECONDARYCOLOR3SPROC glad_debug_glSecondaryColor3s = glad_debug_impl_glSecondaryColor3s; PFNGLSECONDARYCOLOR3SVPROC glad_glSecondaryColor3sv = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3sv(const GLshort * v) { _pre_call_gl_callback("glSecondaryColor3sv", (GLADapiproc) glad_glSecondaryColor3sv, 1, v); glad_glSecondaryColor3sv(v); _post_call_gl_callback(NULL, "glSecondaryColor3sv", (GLADapiproc) glad_glSecondaryColor3sv, 1, v); } PFNGLSECONDARYCOLOR3SVPROC glad_debug_glSecondaryColor3sv = glad_debug_impl_glSecondaryColor3sv; PFNGLSECONDARYCOLOR3UBPROC glad_glSecondaryColor3ub = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3ub(GLubyte red, GLubyte green, GLubyte blue) { _pre_call_gl_callback("glSecondaryColor3ub", (GLADapiproc) glad_glSecondaryColor3ub, 3, red, green, blue); glad_glSecondaryColor3ub(red, green, blue); _post_call_gl_callback(NULL, "glSecondaryColor3ub", (GLADapiproc) glad_glSecondaryColor3ub, 3, red, green, blue); } PFNGLSECONDARYCOLOR3UBPROC glad_debug_glSecondaryColor3ub = glad_debug_impl_glSecondaryColor3ub; PFNGLSECONDARYCOLOR3UBVPROC glad_glSecondaryColor3ubv = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3ubv(const GLubyte * v) { _pre_call_gl_callback("glSecondaryColor3ubv", (GLADapiproc) glad_glSecondaryColor3ubv, 1, v); glad_glSecondaryColor3ubv(v); _post_call_gl_callback(NULL, "glSecondaryColor3ubv", (GLADapiproc) glad_glSecondaryColor3ubv, 1, v); } PFNGLSECONDARYCOLOR3UBVPROC glad_debug_glSecondaryColor3ubv = glad_debug_impl_glSecondaryColor3ubv; PFNGLSECONDARYCOLOR3UIPROC glad_glSecondaryColor3ui = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3ui(GLuint red, GLuint green, GLuint blue) { _pre_call_gl_callback("glSecondaryColor3ui", (GLADapiproc) glad_glSecondaryColor3ui, 3, red, green, blue); glad_glSecondaryColor3ui(red, green, blue); _post_call_gl_callback(NULL, "glSecondaryColor3ui", (GLADapiproc) glad_glSecondaryColor3ui, 3, red, green, blue); } PFNGLSECONDARYCOLOR3UIPROC glad_debug_glSecondaryColor3ui = glad_debug_impl_glSecondaryColor3ui; PFNGLSECONDARYCOLOR3UIVPROC glad_glSecondaryColor3uiv = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3uiv(const GLuint * v) { _pre_call_gl_callback("glSecondaryColor3uiv", (GLADapiproc) glad_glSecondaryColor3uiv, 1, v); glad_glSecondaryColor3uiv(v); _post_call_gl_callback(NULL, "glSecondaryColor3uiv", (GLADapiproc) glad_glSecondaryColor3uiv, 1, v); } PFNGLSECONDARYCOLOR3UIVPROC glad_debug_glSecondaryColor3uiv = glad_debug_impl_glSecondaryColor3uiv; PFNGLSECONDARYCOLOR3USPROC glad_glSecondaryColor3us = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3us(GLushort red, GLushort green, GLushort blue) { _pre_call_gl_callback("glSecondaryColor3us", (GLADapiproc) glad_glSecondaryColor3us, 3, red, green, blue); glad_glSecondaryColor3us(red, green, blue); _post_call_gl_callback(NULL, "glSecondaryColor3us", (GLADapiproc) glad_glSecondaryColor3us, 3, red, green, blue); } PFNGLSECONDARYCOLOR3USPROC glad_debug_glSecondaryColor3us = glad_debug_impl_glSecondaryColor3us; PFNGLSECONDARYCOLOR3USVPROC glad_glSecondaryColor3usv = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColor3usv(const GLushort * v) { _pre_call_gl_callback("glSecondaryColor3usv", (GLADapiproc) glad_glSecondaryColor3usv, 1, v); glad_glSecondaryColor3usv(v); _post_call_gl_callback(NULL, "glSecondaryColor3usv", (GLADapiproc) glad_glSecondaryColor3usv, 1, v); } PFNGLSECONDARYCOLOR3USVPROC glad_debug_glSecondaryColor3usv = glad_debug_impl_glSecondaryColor3usv; PFNGLSECONDARYCOLORPOINTERPROC glad_glSecondaryColorPointer = NULL; static void GLAD_API_PTR glad_debug_impl_glSecondaryColorPointer(GLint size, GLenum type, GLsizei stride, const void * pointer) { _pre_call_gl_callback("glSecondaryColorPointer", (GLADapiproc) glad_glSecondaryColorPointer, 4, size, type, stride, pointer); glad_glSecondaryColorPointer(size, type, stride, pointer); _post_call_gl_callback(NULL, "glSecondaryColorPointer", (GLADapiproc) glad_glSecondaryColorPointer, 4, size, type, stride, pointer); } PFNGLSECONDARYCOLORPOINTERPROC glad_debug_glSecondaryColorPointer = glad_debug_impl_glSecondaryColorPointer; PFNGLSELECTBUFFERPROC glad_glSelectBuffer = NULL; static void GLAD_API_PTR glad_debug_impl_glSelectBuffer(GLsizei size, GLuint * buffer) { _pre_call_gl_callback("glSelectBuffer", (GLADapiproc) glad_glSelectBuffer, 2, size, buffer); glad_glSelectBuffer(size, buffer); _post_call_gl_callback(NULL, "glSelectBuffer", (GLADapiproc) glad_glSelectBuffer, 2, size, buffer); } PFNGLSELECTBUFFERPROC glad_debug_glSelectBuffer = glad_debug_impl_glSelectBuffer; PFNGLSHADEMODELPROC glad_glShadeModel = NULL; static void GLAD_API_PTR glad_debug_impl_glShadeModel(GLenum mode) { _pre_call_gl_callback("glShadeModel", (GLADapiproc) glad_glShadeModel, 1, mode); glad_glShadeModel(mode); _post_call_gl_callback(NULL, "glShadeModel", (GLADapiproc) glad_glShadeModel, 1, mode); } PFNGLSHADEMODELPROC glad_debug_glShadeModel = glad_debug_impl_glShadeModel; PFNGLSHADERSOURCEPROC glad_glShaderSource = NULL; static void GLAD_API_PTR glad_debug_impl_glShaderSource(GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length) { _pre_call_gl_callback("glShaderSource", (GLADapiproc) glad_glShaderSource, 4, shader, count, string, length); glad_glShaderSource(shader, count, string, length); _post_call_gl_callback(NULL, "glShaderSource", (GLADapiproc) glad_glShaderSource, 4, shader, count, string, length); } PFNGLSHADERSOURCEPROC glad_debug_glShaderSource = glad_debug_impl_glShaderSource; PFNGLSTENCILFUNCPROC glad_glStencilFunc = NULL; static void GLAD_API_PTR glad_debug_impl_glStencilFunc(GLenum func, GLint ref, GLuint mask) { _pre_call_gl_callback("glStencilFunc", (GLADapiproc) glad_glStencilFunc, 3, func, ref, mask); glad_glStencilFunc(func, ref, mask); _post_call_gl_callback(NULL, "glStencilFunc", (GLADapiproc) glad_glStencilFunc, 3, func, ref, mask); } PFNGLSTENCILFUNCPROC glad_debug_glStencilFunc = glad_debug_impl_glStencilFunc; PFNGLSTENCILFUNCSEPARATEPROC glad_glStencilFuncSeparate = NULL; static void GLAD_API_PTR glad_debug_impl_glStencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask) { _pre_call_gl_callback("glStencilFuncSeparate", (GLADapiproc) glad_glStencilFuncSeparate, 4, face, func, ref, mask); glad_glStencilFuncSeparate(face, func, ref, mask); _post_call_gl_callback(NULL, "glStencilFuncSeparate", (GLADapiproc) glad_glStencilFuncSeparate, 4, face, func, ref, mask); } PFNGLSTENCILFUNCSEPARATEPROC glad_debug_glStencilFuncSeparate = glad_debug_impl_glStencilFuncSeparate; PFNGLSTENCILMASKPROC glad_glStencilMask = NULL; static void GLAD_API_PTR glad_debug_impl_glStencilMask(GLuint mask) { _pre_call_gl_callback("glStencilMask", (GLADapiproc) glad_glStencilMask, 1, mask); glad_glStencilMask(mask); _post_call_gl_callback(NULL, "glStencilMask", (GLADapiproc) glad_glStencilMask, 1, mask); } PFNGLSTENCILMASKPROC glad_debug_glStencilMask = glad_debug_impl_glStencilMask; PFNGLSTENCILMASKSEPARATEPROC glad_glStencilMaskSeparate = NULL; static void GLAD_API_PTR glad_debug_impl_glStencilMaskSeparate(GLenum face, GLuint mask) { _pre_call_gl_callback("glStencilMaskSeparate", (GLADapiproc) glad_glStencilMaskSeparate, 2, face, mask); glad_glStencilMaskSeparate(face, mask); _post_call_gl_callback(NULL, "glStencilMaskSeparate", (GLADapiproc) glad_glStencilMaskSeparate, 2, face, mask); } PFNGLSTENCILMASKSEPARATEPROC glad_debug_glStencilMaskSeparate = glad_debug_impl_glStencilMaskSeparate; PFNGLSTENCILOPPROC glad_glStencilOp = NULL; static void GLAD_API_PTR glad_debug_impl_glStencilOp(GLenum fail, GLenum zfail, GLenum zpass) { _pre_call_gl_callback("glStencilOp", (GLADapiproc) glad_glStencilOp, 3, fail, zfail, zpass); glad_glStencilOp(fail, zfail, zpass); _post_call_gl_callback(NULL, "glStencilOp", (GLADapiproc) glad_glStencilOp, 3, fail, zfail, zpass); } PFNGLSTENCILOPPROC glad_debug_glStencilOp = glad_debug_impl_glStencilOp; PFNGLSTENCILOPSEPARATEPROC glad_glStencilOpSeparate = NULL; static void GLAD_API_PTR glad_debug_impl_glStencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass) { _pre_call_gl_callback("glStencilOpSeparate", (GLADapiproc) glad_glStencilOpSeparate, 4, face, sfail, dpfail, dppass); glad_glStencilOpSeparate(face, sfail, dpfail, dppass); _post_call_gl_callback(NULL, "glStencilOpSeparate", (GLADapiproc) glad_glStencilOpSeparate, 4, face, sfail, dpfail, dppass); } PFNGLSTENCILOPSEPARATEPROC glad_debug_glStencilOpSeparate = glad_debug_impl_glStencilOpSeparate; PFNGLTEXBUFFERPROC glad_glTexBuffer = NULL; static void GLAD_API_PTR glad_debug_impl_glTexBuffer(GLenum target, GLenum internalformat, GLuint buffer) { _pre_call_gl_callback("glTexBuffer", (GLADapiproc) glad_glTexBuffer, 3, target, internalformat, buffer); glad_glTexBuffer(target, internalformat, buffer); _post_call_gl_callback(NULL, "glTexBuffer", (GLADapiproc) glad_glTexBuffer, 3, target, internalformat, buffer); } PFNGLTEXBUFFERPROC glad_debug_glTexBuffer = glad_debug_impl_glTexBuffer; PFNGLTEXCOORD1DPROC glad_glTexCoord1d = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord1d(GLdouble s) { _pre_call_gl_callback("glTexCoord1d", (GLADapiproc) glad_glTexCoord1d, 1, s); glad_glTexCoord1d(s); _post_call_gl_callback(NULL, "glTexCoord1d", (GLADapiproc) glad_glTexCoord1d, 1, s); } PFNGLTEXCOORD1DPROC glad_debug_glTexCoord1d = glad_debug_impl_glTexCoord1d; PFNGLTEXCOORD1DVPROC glad_glTexCoord1dv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord1dv(const GLdouble * v) { _pre_call_gl_callback("glTexCoord1dv", (GLADapiproc) glad_glTexCoord1dv, 1, v); glad_glTexCoord1dv(v); _post_call_gl_callback(NULL, "glTexCoord1dv", (GLADapiproc) glad_glTexCoord1dv, 1, v); } PFNGLTEXCOORD1DVPROC glad_debug_glTexCoord1dv = glad_debug_impl_glTexCoord1dv; PFNGLTEXCOORD1FPROC glad_glTexCoord1f = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord1f(GLfloat s) { _pre_call_gl_callback("glTexCoord1f", (GLADapiproc) glad_glTexCoord1f, 1, s); glad_glTexCoord1f(s); _post_call_gl_callback(NULL, "glTexCoord1f", (GLADapiproc) glad_glTexCoord1f, 1, s); } PFNGLTEXCOORD1FPROC glad_debug_glTexCoord1f = glad_debug_impl_glTexCoord1f; PFNGLTEXCOORD1FVPROC glad_glTexCoord1fv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord1fv(const GLfloat * v) { _pre_call_gl_callback("glTexCoord1fv", (GLADapiproc) glad_glTexCoord1fv, 1, v); glad_glTexCoord1fv(v); _post_call_gl_callback(NULL, "glTexCoord1fv", (GLADapiproc) glad_glTexCoord1fv, 1, v); } PFNGLTEXCOORD1FVPROC glad_debug_glTexCoord1fv = glad_debug_impl_glTexCoord1fv; PFNGLTEXCOORD1IPROC glad_glTexCoord1i = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord1i(GLint s) { _pre_call_gl_callback("glTexCoord1i", (GLADapiproc) glad_glTexCoord1i, 1, s); glad_glTexCoord1i(s); _post_call_gl_callback(NULL, "glTexCoord1i", (GLADapiproc) glad_glTexCoord1i, 1, s); } PFNGLTEXCOORD1IPROC glad_debug_glTexCoord1i = glad_debug_impl_glTexCoord1i; PFNGLTEXCOORD1IVPROC glad_glTexCoord1iv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord1iv(const GLint * v) { _pre_call_gl_callback("glTexCoord1iv", (GLADapiproc) glad_glTexCoord1iv, 1, v); glad_glTexCoord1iv(v); _post_call_gl_callback(NULL, "glTexCoord1iv", (GLADapiproc) glad_glTexCoord1iv, 1, v); } PFNGLTEXCOORD1IVPROC glad_debug_glTexCoord1iv = glad_debug_impl_glTexCoord1iv; PFNGLTEXCOORD1SPROC glad_glTexCoord1s = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord1s(GLshort s) { _pre_call_gl_callback("glTexCoord1s", (GLADapiproc) glad_glTexCoord1s, 1, s); glad_glTexCoord1s(s); _post_call_gl_callback(NULL, "glTexCoord1s", (GLADapiproc) glad_glTexCoord1s, 1, s); } PFNGLTEXCOORD1SPROC glad_debug_glTexCoord1s = glad_debug_impl_glTexCoord1s; PFNGLTEXCOORD1SVPROC glad_glTexCoord1sv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord1sv(const GLshort * v) { _pre_call_gl_callback("glTexCoord1sv", (GLADapiproc) glad_glTexCoord1sv, 1, v); glad_glTexCoord1sv(v); _post_call_gl_callback(NULL, "glTexCoord1sv", (GLADapiproc) glad_glTexCoord1sv, 1, v); } PFNGLTEXCOORD1SVPROC glad_debug_glTexCoord1sv = glad_debug_impl_glTexCoord1sv; PFNGLTEXCOORD2DPROC glad_glTexCoord2d = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord2d(GLdouble s, GLdouble t) { _pre_call_gl_callback("glTexCoord2d", (GLADapiproc) glad_glTexCoord2d, 2, s, t); glad_glTexCoord2d(s, t); _post_call_gl_callback(NULL, "glTexCoord2d", (GLADapiproc) glad_glTexCoord2d, 2, s, t); } PFNGLTEXCOORD2DPROC glad_debug_glTexCoord2d = glad_debug_impl_glTexCoord2d; PFNGLTEXCOORD2DVPROC glad_glTexCoord2dv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord2dv(const GLdouble * v) { _pre_call_gl_callback("glTexCoord2dv", (GLADapiproc) glad_glTexCoord2dv, 1, v); glad_glTexCoord2dv(v); _post_call_gl_callback(NULL, "glTexCoord2dv", (GLADapiproc) glad_glTexCoord2dv, 1, v); } PFNGLTEXCOORD2DVPROC glad_debug_glTexCoord2dv = glad_debug_impl_glTexCoord2dv; PFNGLTEXCOORD2FPROC glad_glTexCoord2f = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord2f(GLfloat s, GLfloat t) { _pre_call_gl_callback("glTexCoord2f", (GLADapiproc) glad_glTexCoord2f, 2, s, t); glad_glTexCoord2f(s, t); _post_call_gl_callback(NULL, "glTexCoord2f", (GLADapiproc) glad_glTexCoord2f, 2, s, t); } PFNGLTEXCOORD2FPROC glad_debug_glTexCoord2f = glad_debug_impl_glTexCoord2f; PFNGLTEXCOORD2FVPROC glad_glTexCoord2fv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord2fv(const GLfloat * v) { _pre_call_gl_callback("glTexCoord2fv", (GLADapiproc) glad_glTexCoord2fv, 1, v); glad_glTexCoord2fv(v); _post_call_gl_callback(NULL, "glTexCoord2fv", (GLADapiproc) glad_glTexCoord2fv, 1, v); } PFNGLTEXCOORD2FVPROC glad_debug_glTexCoord2fv = glad_debug_impl_glTexCoord2fv; PFNGLTEXCOORD2IPROC glad_glTexCoord2i = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord2i(GLint s, GLint t) { _pre_call_gl_callback("glTexCoord2i", (GLADapiproc) glad_glTexCoord2i, 2, s, t); glad_glTexCoord2i(s, t); _post_call_gl_callback(NULL, "glTexCoord2i", (GLADapiproc) glad_glTexCoord2i, 2, s, t); } PFNGLTEXCOORD2IPROC glad_debug_glTexCoord2i = glad_debug_impl_glTexCoord2i; PFNGLTEXCOORD2IVPROC glad_glTexCoord2iv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord2iv(const GLint * v) { _pre_call_gl_callback("glTexCoord2iv", (GLADapiproc) glad_glTexCoord2iv, 1, v); glad_glTexCoord2iv(v); _post_call_gl_callback(NULL, "glTexCoord2iv", (GLADapiproc) glad_glTexCoord2iv, 1, v); } PFNGLTEXCOORD2IVPROC glad_debug_glTexCoord2iv = glad_debug_impl_glTexCoord2iv; PFNGLTEXCOORD2SPROC glad_glTexCoord2s = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord2s(GLshort s, GLshort t) { _pre_call_gl_callback("glTexCoord2s", (GLADapiproc) glad_glTexCoord2s, 2, s, t); glad_glTexCoord2s(s, t); _post_call_gl_callback(NULL, "glTexCoord2s", (GLADapiproc) glad_glTexCoord2s, 2, s, t); } PFNGLTEXCOORD2SPROC glad_debug_glTexCoord2s = glad_debug_impl_glTexCoord2s; PFNGLTEXCOORD2SVPROC glad_glTexCoord2sv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord2sv(const GLshort * v) { _pre_call_gl_callback("glTexCoord2sv", (GLADapiproc) glad_glTexCoord2sv, 1, v); glad_glTexCoord2sv(v); _post_call_gl_callback(NULL, "glTexCoord2sv", (GLADapiproc) glad_glTexCoord2sv, 1, v); } PFNGLTEXCOORD2SVPROC glad_debug_glTexCoord2sv = glad_debug_impl_glTexCoord2sv; PFNGLTEXCOORD3DPROC glad_glTexCoord3d = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord3d(GLdouble s, GLdouble t, GLdouble r) { _pre_call_gl_callback("glTexCoord3d", (GLADapiproc) glad_glTexCoord3d, 3, s, t, r); glad_glTexCoord3d(s, t, r); _post_call_gl_callback(NULL, "glTexCoord3d", (GLADapiproc) glad_glTexCoord3d, 3, s, t, r); } PFNGLTEXCOORD3DPROC glad_debug_glTexCoord3d = glad_debug_impl_glTexCoord3d; PFNGLTEXCOORD3DVPROC glad_glTexCoord3dv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord3dv(const GLdouble * v) { _pre_call_gl_callback("glTexCoord3dv", (GLADapiproc) glad_glTexCoord3dv, 1, v); glad_glTexCoord3dv(v); _post_call_gl_callback(NULL, "glTexCoord3dv", (GLADapiproc) glad_glTexCoord3dv, 1, v); } PFNGLTEXCOORD3DVPROC glad_debug_glTexCoord3dv = glad_debug_impl_glTexCoord3dv; PFNGLTEXCOORD3FPROC glad_glTexCoord3f = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord3f(GLfloat s, GLfloat t, GLfloat r) { _pre_call_gl_callback("glTexCoord3f", (GLADapiproc) glad_glTexCoord3f, 3, s, t, r); glad_glTexCoord3f(s, t, r); _post_call_gl_callback(NULL, "glTexCoord3f", (GLADapiproc) glad_glTexCoord3f, 3, s, t, r); } PFNGLTEXCOORD3FPROC glad_debug_glTexCoord3f = glad_debug_impl_glTexCoord3f; PFNGLTEXCOORD3FVPROC glad_glTexCoord3fv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord3fv(const GLfloat * v) { _pre_call_gl_callback("glTexCoord3fv", (GLADapiproc) glad_glTexCoord3fv, 1, v); glad_glTexCoord3fv(v); _post_call_gl_callback(NULL, "glTexCoord3fv", (GLADapiproc) glad_glTexCoord3fv, 1, v); } PFNGLTEXCOORD3FVPROC glad_debug_glTexCoord3fv = glad_debug_impl_glTexCoord3fv; PFNGLTEXCOORD3IPROC glad_glTexCoord3i = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord3i(GLint s, GLint t, GLint r) { _pre_call_gl_callback("glTexCoord3i", (GLADapiproc) glad_glTexCoord3i, 3, s, t, r); glad_glTexCoord3i(s, t, r); _post_call_gl_callback(NULL, "glTexCoord3i", (GLADapiproc) glad_glTexCoord3i, 3, s, t, r); } PFNGLTEXCOORD3IPROC glad_debug_glTexCoord3i = glad_debug_impl_glTexCoord3i; PFNGLTEXCOORD3IVPROC glad_glTexCoord3iv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord3iv(const GLint * v) { _pre_call_gl_callback("glTexCoord3iv", (GLADapiproc) glad_glTexCoord3iv, 1, v); glad_glTexCoord3iv(v); _post_call_gl_callback(NULL, "glTexCoord3iv", (GLADapiproc) glad_glTexCoord3iv, 1, v); } PFNGLTEXCOORD3IVPROC glad_debug_glTexCoord3iv = glad_debug_impl_glTexCoord3iv; PFNGLTEXCOORD3SPROC glad_glTexCoord3s = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord3s(GLshort s, GLshort t, GLshort r) { _pre_call_gl_callback("glTexCoord3s", (GLADapiproc) glad_glTexCoord3s, 3, s, t, r); glad_glTexCoord3s(s, t, r); _post_call_gl_callback(NULL, "glTexCoord3s", (GLADapiproc) glad_glTexCoord3s, 3, s, t, r); } PFNGLTEXCOORD3SPROC glad_debug_glTexCoord3s = glad_debug_impl_glTexCoord3s; PFNGLTEXCOORD3SVPROC glad_glTexCoord3sv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord3sv(const GLshort * v) { _pre_call_gl_callback("glTexCoord3sv", (GLADapiproc) glad_glTexCoord3sv, 1, v); glad_glTexCoord3sv(v); _post_call_gl_callback(NULL, "glTexCoord3sv", (GLADapiproc) glad_glTexCoord3sv, 1, v); } PFNGLTEXCOORD3SVPROC glad_debug_glTexCoord3sv = glad_debug_impl_glTexCoord3sv; PFNGLTEXCOORD4DPROC glad_glTexCoord4d = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord4d(GLdouble s, GLdouble t, GLdouble r, GLdouble q) { _pre_call_gl_callback("glTexCoord4d", (GLADapiproc) glad_glTexCoord4d, 4, s, t, r, q); glad_glTexCoord4d(s, t, r, q); _post_call_gl_callback(NULL, "glTexCoord4d", (GLADapiproc) glad_glTexCoord4d, 4, s, t, r, q); } PFNGLTEXCOORD4DPROC glad_debug_glTexCoord4d = glad_debug_impl_glTexCoord4d; PFNGLTEXCOORD4DVPROC glad_glTexCoord4dv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord4dv(const GLdouble * v) { _pre_call_gl_callback("glTexCoord4dv", (GLADapiproc) glad_glTexCoord4dv, 1, v); glad_glTexCoord4dv(v); _post_call_gl_callback(NULL, "glTexCoord4dv", (GLADapiproc) glad_glTexCoord4dv, 1, v); } PFNGLTEXCOORD4DVPROC glad_debug_glTexCoord4dv = glad_debug_impl_glTexCoord4dv; PFNGLTEXCOORD4FPROC glad_glTexCoord4f = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord4f(GLfloat s, GLfloat t, GLfloat r, GLfloat q) { _pre_call_gl_callback("glTexCoord4f", (GLADapiproc) glad_glTexCoord4f, 4, s, t, r, q); glad_glTexCoord4f(s, t, r, q); _post_call_gl_callback(NULL, "glTexCoord4f", (GLADapiproc) glad_glTexCoord4f, 4, s, t, r, q); } PFNGLTEXCOORD4FPROC glad_debug_glTexCoord4f = glad_debug_impl_glTexCoord4f; PFNGLTEXCOORD4FVPROC glad_glTexCoord4fv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord4fv(const GLfloat * v) { _pre_call_gl_callback("glTexCoord4fv", (GLADapiproc) glad_glTexCoord4fv, 1, v); glad_glTexCoord4fv(v); _post_call_gl_callback(NULL, "glTexCoord4fv", (GLADapiproc) glad_glTexCoord4fv, 1, v); } PFNGLTEXCOORD4FVPROC glad_debug_glTexCoord4fv = glad_debug_impl_glTexCoord4fv; PFNGLTEXCOORD4IPROC glad_glTexCoord4i = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord4i(GLint s, GLint t, GLint r, GLint q) { _pre_call_gl_callback("glTexCoord4i", (GLADapiproc) glad_glTexCoord4i, 4, s, t, r, q); glad_glTexCoord4i(s, t, r, q); _post_call_gl_callback(NULL, "glTexCoord4i", (GLADapiproc) glad_glTexCoord4i, 4, s, t, r, q); } PFNGLTEXCOORD4IPROC glad_debug_glTexCoord4i = glad_debug_impl_glTexCoord4i; PFNGLTEXCOORD4IVPROC glad_glTexCoord4iv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord4iv(const GLint * v) { _pre_call_gl_callback("glTexCoord4iv", (GLADapiproc) glad_glTexCoord4iv, 1, v); glad_glTexCoord4iv(v); _post_call_gl_callback(NULL, "glTexCoord4iv", (GLADapiproc) glad_glTexCoord4iv, 1, v); } PFNGLTEXCOORD4IVPROC glad_debug_glTexCoord4iv = glad_debug_impl_glTexCoord4iv; PFNGLTEXCOORD4SPROC glad_glTexCoord4s = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord4s(GLshort s, GLshort t, GLshort r, GLshort q) { _pre_call_gl_callback("glTexCoord4s", (GLADapiproc) glad_glTexCoord4s, 4, s, t, r, q); glad_glTexCoord4s(s, t, r, q); _post_call_gl_callback(NULL, "glTexCoord4s", (GLADapiproc) glad_glTexCoord4s, 4, s, t, r, q); } PFNGLTEXCOORD4SPROC glad_debug_glTexCoord4s = glad_debug_impl_glTexCoord4s; PFNGLTEXCOORD4SVPROC glad_glTexCoord4sv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoord4sv(const GLshort * v) { _pre_call_gl_callback("glTexCoord4sv", (GLADapiproc) glad_glTexCoord4sv, 1, v); glad_glTexCoord4sv(v); _post_call_gl_callback(NULL, "glTexCoord4sv", (GLADapiproc) glad_glTexCoord4sv, 1, v); } PFNGLTEXCOORD4SVPROC glad_debug_glTexCoord4sv = glad_debug_impl_glTexCoord4sv; PFNGLTEXCOORDPOINTERPROC glad_glTexCoordPointer = NULL; static void GLAD_API_PTR glad_debug_impl_glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const void * pointer) { _pre_call_gl_callback("glTexCoordPointer", (GLADapiproc) glad_glTexCoordPointer, 4, size, type, stride, pointer); glad_glTexCoordPointer(size, type, stride, pointer); _post_call_gl_callback(NULL, "glTexCoordPointer", (GLADapiproc) glad_glTexCoordPointer, 4, size, type, stride, pointer); } PFNGLTEXCOORDPOINTERPROC glad_debug_glTexCoordPointer = glad_debug_impl_glTexCoordPointer; PFNGLTEXENVFPROC glad_glTexEnvf = NULL; static void GLAD_API_PTR glad_debug_impl_glTexEnvf(GLenum target, GLenum pname, GLfloat param) { _pre_call_gl_callback("glTexEnvf", (GLADapiproc) glad_glTexEnvf, 3, target, pname, param); glad_glTexEnvf(target, pname, param); _post_call_gl_callback(NULL, "glTexEnvf", (GLADapiproc) glad_glTexEnvf, 3, target, pname, param); } PFNGLTEXENVFPROC glad_debug_glTexEnvf = glad_debug_impl_glTexEnvf; PFNGLTEXENVFVPROC glad_glTexEnvfv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexEnvfv(GLenum target, GLenum pname, const GLfloat * params) { _pre_call_gl_callback("glTexEnvfv", (GLADapiproc) glad_glTexEnvfv, 3, target, pname, params); glad_glTexEnvfv(target, pname, params); _post_call_gl_callback(NULL, "glTexEnvfv", (GLADapiproc) glad_glTexEnvfv, 3, target, pname, params); } PFNGLTEXENVFVPROC glad_debug_glTexEnvfv = glad_debug_impl_glTexEnvfv; PFNGLTEXENVIPROC glad_glTexEnvi = NULL; static void GLAD_API_PTR glad_debug_impl_glTexEnvi(GLenum target, GLenum pname, GLint param) { _pre_call_gl_callback("glTexEnvi", (GLADapiproc) glad_glTexEnvi, 3, target, pname, param); glad_glTexEnvi(target, pname, param); _post_call_gl_callback(NULL, "glTexEnvi", (GLADapiproc) glad_glTexEnvi, 3, target, pname, param); } PFNGLTEXENVIPROC glad_debug_glTexEnvi = glad_debug_impl_glTexEnvi; PFNGLTEXENVIVPROC glad_glTexEnviv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexEnviv(GLenum target, GLenum pname, const GLint * params) { _pre_call_gl_callback("glTexEnviv", (GLADapiproc) glad_glTexEnviv, 3, target, pname, params); glad_glTexEnviv(target, pname, params); _post_call_gl_callback(NULL, "glTexEnviv", (GLADapiproc) glad_glTexEnviv, 3, target, pname, params); } PFNGLTEXENVIVPROC glad_debug_glTexEnviv = glad_debug_impl_glTexEnviv; PFNGLTEXGENDPROC glad_glTexGend = NULL; static void GLAD_API_PTR glad_debug_impl_glTexGend(GLenum coord, GLenum pname, GLdouble param) { _pre_call_gl_callback("glTexGend", (GLADapiproc) glad_glTexGend, 3, coord, pname, param); glad_glTexGend(coord, pname, param); _post_call_gl_callback(NULL, "glTexGend", (GLADapiproc) glad_glTexGend, 3, coord, pname, param); } PFNGLTEXGENDPROC glad_debug_glTexGend = glad_debug_impl_glTexGend; PFNGLTEXGENDVPROC glad_glTexGendv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexGendv(GLenum coord, GLenum pname, const GLdouble * params) { _pre_call_gl_callback("glTexGendv", (GLADapiproc) glad_glTexGendv, 3, coord, pname, params); glad_glTexGendv(coord, pname, params); _post_call_gl_callback(NULL, "glTexGendv", (GLADapiproc) glad_glTexGendv, 3, coord, pname, params); } PFNGLTEXGENDVPROC glad_debug_glTexGendv = glad_debug_impl_glTexGendv; PFNGLTEXGENFPROC glad_glTexGenf = NULL; static void GLAD_API_PTR glad_debug_impl_glTexGenf(GLenum coord, GLenum pname, GLfloat param) { _pre_call_gl_callback("glTexGenf", (GLADapiproc) glad_glTexGenf, 3, coord, pname, param); glad_glTexGenf(coord, pname, param); _post_call_gl_callback(NULL, "glTexGenf", (GLADapiproc) glad_glTexGenf, 3, coord, pname, param); } PFNGLTEXGENFPROC glad_debug_glTexGenf = glad_debug_impl_glTexGenf; PFNGLTEXGENFVPROC glad_glTexGenfv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexGenfv(GLenum coord, GLenum pname, const GLfloat * params) { _pre_call_gl_callback("glTexGenfv", (GLADapiproc) glad_glTexGenfv, 3, coord, pname, params); glad_glTexGenfv(coord, pname, params); _post_call_gl_callback(NULL, "glTexGenfv", (GLADapiproc) glad_glTexGenfv, 3, coord, pname, params); } PFNGLTEXGENFVPROC glad_debug_glTexGenfv = glad_debug_impl_glTexGenfv; PFNGLTEXGENIPROC glad_glTexGeni = NULL; static void GLAD_API_PTR glad_debug_impl_glTexGeni(GLenum coord, GLenum pname, GLint param) { _pre_call_gl_callback("glTexGeni", (GLADapiproc) glad_glTexGeni, 3, coord, pname, param); glad_glTexGeni(coord, pname, param); _post_call_gl_callback(NULL, "glTexGeni", (GLADapiproc) glad_glTexGeni, 3, coord, pname, param); } PFNGLTEXGENIPROC glad_debug_glTexGeni = glad_debug_impl_glTexGeni; PFNGLTEXGENIVPROC glad_glTexGeniv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexGeniv(GLenum coord, GLenum pname, const GLint * params) { _pre_call_gl_callback("glTexGeniv", (GLADapiproc) glad_glTexGeniv, 3, coord, pname, params); glad_glTexGeniv(coord, pname, params); _post_call_gl_callback(NULL, "glTexGeniv", (GLADapiproc) glad_glTexGeniv, 3, coord, pname, params); } PFNGLTEXGENIVPROC glad_debug_glTexGeniv = glad_debug_impl_glTexGeniv; PFNGLTEXIMAGE1DPROC glad_glTexImage1D = NULL; static void GLAD_API_PTR glad_debug_impl_glTexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void * pixels) { _pre_call_gl_callback("glTexImage1D", (GLADapiproc) glad_glTexImage1D, 8, target, level, internalformat, width, border, format, type, pixels); glad_glTexImage1D(target, level, internalformat, width, border, format, type, pixels); _post_call_gl_callback(NULL, "glTexImage1D", (GLADapiproc) glad_glTexImage1D, 8, target, level, internalformat, width, border, format, type, pixels); } PFNGLTEXIMAGE1DPROC glad_debug_glTexImage1D = glad_debug_impl_glTexImage1D; PFNGLTEXIMAGE2DPROC glad_glTexImage2D = NULL; static void GLAD_API_PTR glad_debug_impl_glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * pixels) { _pre_call_gl_callback("glTexImage2D", (GLADapiproc) glad_glTexImage2D, 9, target, level, internalformat, width, height, border, format, type, pixels); glad_glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels); _post_call_gl_callback(NULL, "glTexImage2D", (GLADapiproc) glad_glTexImage2D, 9, target, level, internalformat, width, height, border, format, type, pixels); } PFNGLTEXIMAGE2DPROC glad_debug_glTexImage2D = glad_debug_impl_glTexImage2D; PFNGLTEXIMAGE3DPROC glad_glTexImage3D = NULL; static void GLAD_API_PTR glad_debug_impl_glTexImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void * pixels) { _pre_call_gl_callback("glTexImage3D", (GLADapiproc) glad_glTexImage3D, 10, target, level, internalformat, width, height, depth, border, format, type, pixels); glad_glTexImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels); _post_call_gl_callback(NULL, "glTexImage3D", (GLADapiproc) glad_glTexImage3D, 10, target, level, internalformat, width, height, depth, border, format, type, pixels); } PFNGLTEXIMAGE3DPROC glad_debug_glTexImage3D = glad_debug_impl_glTexImage3D; PFNGLTEXPARAMETERIIVPROC glad_glTexParameterIiv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexParameterIiv(GLenum target, GLenum pname, const GLint * params) { _pre_call_gl_callback("glTexParameterIiv", (GLADapiproc) glad_glTexParameterIiv, 3, target, pname, params); glad_glTexParameterIiv(target, pname, params); _post_call_gl_callback(NULL, "glTexParameterIiv", (GLADapiproc) glad_glTexParameterIiv, 3, target, pname, params); } PFNGLTEXPARAMETERIIVPROC glad_debug_glTexParameterIiv = glad_debug_impl_glTexParameterIiv; PFNGLTEXPARAMETERIUIVPROC glad_glTexParameterIuiv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexParameterIuiv(GLenum target, GLenum pname, const GLuint * params) { _pre_call_gl_callback("glTexParameterIuiv", (GLADapiproc) glad_glTexParameterIuiv, 3, target, pname, params); glad_glTexParameterIuiv(target, pname, params); _post_call_gl_callback(NULL, "glTexParameterIuiv", (GLADapiproc) glad_glTexParameterIuiv, 3, target, pname, params); } PFNGLTEXPARAMETERIUIVPROC glad_debug_glTexParameterIuiv = glad_debug_impl_glTexParameterIuiv; PFNGLTEXPARAMETERFPROC glad_glTexParameterf = NULL; static void GLAD_API_PTR glad_debug_impl_glTexParameterf(GLenum target, GLenum pname, GLfloat param) { _pre_call_gl_callback("glTexParameterf", (GLADapiproc) glad_glTexParameterf, 3, target, pname, param); glad_glTexParameterf(target, pname, param); _post_call_gl_callback(NULL, "glTexParameterf", (GLADapiproc) glad_glTexParameterf, 3, target, pname, param); } PFNGLTEXPARAMETERFPROC glad_debug_glTexParameterf = glad_debug_impl_glTexParameterf; PFNGLTEXPARAMETERFVPROC glad_glTexParameterfv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexParameterfv(GLenum target, GLenum pname, const GLfloat * params) { _pre_call_gl_callback("glTexParameterfv", (GLADapiproc) glad_glTexParameterfv, 3, target, pname, params); glad_glTexParameterfv(target, pname, params); _post_call_gl_callback(NULL, "glTexParameterfv", (GLADapiproc) glad_glTexParameterfv, 3, target, pname, params); } PFNGLTEXPARAMETERFVPROC glad_debug_glTexParameterfv = glad_debug_impl_glTexParameterfv; PFNGLTEXPARAMETERIPROC glad_glTexParameteri = NULL; static void GLAD_API_PTR glad_debug_impl_glTexParameteri(GLenum target, GLenum pname, GLint param) { _pre_call_gl_callback("glTexParameteri", (GLADapiproc) glad_glTexParameteri, 3, target, pname, param); glad_glTexParameteri(target, pname, param); _post_call_gl_callback(NULL, "glTexParameteri", (GLADapiproc) glad_glTexParameteri, 3, target, pname, param); } PFNGLTEXPARAMETERIPROC glad_debug_glTexParameteri = glad_debug_impl_glTexParameteri; PFNGLTEXPARAMETERIVPROC glad_glTexParameteriv = NULL; static void GLAD_API_PTR glad_debug_impl_glTexParameteriv(GLenum target, GLenum pname, const GLint * params) { _pre_call_gl_callback("glTexParameteriv", (GLADapiproc) glad_glTexParameteriv, 3, target, pname, params); glad_glTexParameteriv(target, pname, params); _post_call_gl_callback(NULL, "glTexParameteriv", (GLADapiproc) glad_glTexParameteriv, 3, target, pname, params); } PFNGLTEXPARAMETERIVPROC glad_debug_glTexParameteriv = glad_debug_impl_glTexParameteriv; PFNGLTEXSTORAGE1DPROC glad_glTexStorage1D = NULL; static void GLAD_API_PTR glad_debug_impl_glTexStorage1D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width) { _pre_call_gl_callback("glTexStorage1D", (GLADapiproc) glad_glTexStorage1D, 4, target, levels, internalformat, width); glad_glTexStorage1D(target, levels, internalformat, width); _post_call_gl_callback(NULL, "glTexStorage1D", (GLADapiproc) glad_glTexStorage1D, 4, target, levels, internalformat, width); } PFNGLTEXSTORAGE1DPROC glad_debug_glTexStorage1D = glad_debug_impl_glTexStorage1D; PFNGLTEXSTORAGE2DPROC glad_glTexStorage2D = NULL; static void GLAD_API_PTR glad_debug_impl_glTexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) { _pre_call_gl_callback("glTexStorage2D", (GLADapiproc) glad_glTexStorage2D, 5, target, levels, internalformat, width, height); glad_glTexStorage2D(target, levels, internalformat, width, height); _post_call_gl_callback(NULL, "glTexStorage2D", (GLADapiproc) glad_glTexStorage2D, 5, target, levels, internalformat, width, height); } PFNGLTEXSTORAGE2DPROC glad_debug_glTexStorage2D = glad_debug_impl_glTexStorage2D; PFNGLTEXSTORAGE3DPROC glad_glTexStorage3D = NULL; static void GLAD_API_PTR glad_debug_impl_glTexStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth) { _pre_call_gl_callback("glTexStorage3D", (GLADapiproc) glad_glTexStorage3D, 6, target, levels, internalformat, width, height, depth); glad_glTexStorage3D(target, levels, internalformat, width, height, depth); _post_call_gl_callback(NULL, "glTexStorage3D", (GLADapiproc) glad_glTexStorage3D, 6, target, levels, internalformat, width, height, depth); } PFNGLTEXSTORAGE3DPROC glad_debug_glTexStorage3D = glad_debug_impl_glTexStorage3D; PFNGLTEXSUBIMAGE1DPROC glad_glTexSubImage1D = NULL; static void GLAD_API_PTR glad_debug_impl_glTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void * pixels) { _pre_call_gl_callback("glTexSubImage1D", (GLADapiproc) glad_glTexSubImage1D, 7, target, level, xoffset, width, format, type, pixels); glad_glTexSubImage1D(target, level, xoffset, width, format, type, pixels); _post_call_gl_callback(NULL, "glTexSubImage1D", (GLADapiproc) glad_glTexSubImage1D, 7, target, level, xoffset, width, format, type, pixels); } PFNGLTEXSUBIMAGE1DPROC glad_debug_glTexSubImage1D = glad_debug_impl_glTexSubImage1D; PFNGLTEXSUBIMAGE2DPROC glad_glTexSubImage2D = NULL; static void GLAD_API_PTR glad_debug_impl_glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels) { _pre_call_gl_callback("glTexSubImage2D", (GLADapiproc) glad_glTexSubImage2D, 9, target, level, xoffset, yoffset, width, height, format, type, pixels); glad_glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels); _post_call_gl_callback(NULL, "glTexSubImage2D", (GLADapiproc) glad_glTexSubImage2D, 9, target, level, xoffset, yoffset, width, height, format, type, pixels); } PFNGLTEXSUBIMAGE2DPROC glad_debug_glTexSubImage2D = glad_debug_impl_glTexSubImage2D; PFNGLTEXSUBIMAGE3DPROC glad_glTexSubImage3D = NULL; static void GLAD_API_PTR glad_debug_impl_glTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void * pixels) { _pre_call_gl_callback("glTexSubImage3D", (GLADapiproc) glad_glTexSubImage3D, 11, target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels); glad_glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels); _post_call_gl_callback(NULL, "glTexSubImage3D", (GLADapiproc) glad_glTexSubImage3D, 11, target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels); } PFNGLTEXSUBIMAGE3DPROC glad_debug_glTexSubImage3D = glad_debug_impl_glTexSubImage3D; PFNGLTRANSFORMFEEDBACKVARYINGSPROC glad_glTransformFeedbackVaryings = NULL; static void GLAD_API_PTR glad_debug_impl_glTransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar *const* varyings, GLenum bufferMode) { _pre_call_gl_callback("glTransformFeedbackVaryings", (GLADapiproc) glad_glTransformFeedbackVaryings, 4, program, count, varyings, bufferMode); glad_glTransformFeedbackVaryings(program, count, varyings, bufferMode); _post_call_gl_callback(NULL, "glTransformFeedbackVaryings", (GLADapiproc) glad_glTransformFeedbackVaryings, 4, program, count, varyings, bufferMode); } PFNGLTRANSFORMFEEDBACKVARYINGSPROC glad_debug_glTransformFeedbackVaryings = glad_debug_impl_glTransformFeedbackVaryings; PFNGLTRANSLATEDPROC glad_glTranslated = NULL; static void GLAD_API_PTR glad_debug_impl_glTranslated(GLdouble x, GLdouble y, GLdouble z) { _pre_call_gl_callback("glTranslated", (GLADapiproc) glad_glTranslated, 3, x, y, z); glad_glTranslated(x, y, z); _post_call_gl_callback(NULL, "glTranslated", (GLADapiproc) glad_glTranslated, 3, x, y, z); } PFNGLTRANSLATEDPROC glad_debug_glTranslated = glad_debug_impl_glTranslated; PFNGLTRANSLATEFPROC glad_glTranslatef = NULL; static void GLAD_API_PTR glad_debug_impl_glTranslatef(GLfloat x, GLfloat y, GLfloat z) { _pre_call_gl_callback("glTranslatef", (GLADapiproc) glad_glTranslatef, 3, x, y, z); glad_glTranslatef(x, y, z); _post_call_gl_callback(NULL, "glTranslatef", (GLADapiproc) glad_glTranslatef, 3, x, y, z); } PFNGLTRANSLATEFPROC glad_debug_glTranslatef = glad_debug_impl_glTranslatef; PFNGLUNIFORM1FPROC glad_glUniform1f = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform1f(GLint location, GLfloat v0) { _pre_call_gl_callback("glUniform1f", (GLADapiproc) glad_glUniform1f, 2, location, v0); glad_glUniform1f(location, v0); _post_call_gl_callback(NULL, "glUniform1f", (GLADapiproc) glad_glUniform1f, 2, location, v0); } PFNGLUNIFORM1FPROC glad_debug_glUniform1f = glad_debug_impl_glUniform1f; PFNGLUNIFORM1FVPROC glad_glUniform1fv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform1fv(GLint location, GLsizei count, const GLfloat * value) { _pre_call_gl_callback("glUniform1fv", (GLADapiproc) glad_glUniform1fv, 3, location, count, value); glad_glUniform1fv(location, count, value); _post_call_gl_callback(NULL, "glUniform1fv", (GLADapiproc) glad_glUniform1fv, 3, location, count, value); } PFNGLUNIFORM1FVPROC glad_debug_glUniform1fv = glad_debug_impl_glUniform1fv; PFNGLUNIFORM1IPROC glad_glUniform1i = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform1i(GLint location, GLint v0) { _pre_call_gl_callback("glUniform1i", (GLADapiproc) glad_glUniform1i, 2, location, v0); glad_glUniform1i(location, v0); _post_call_gl_callback(NULL, "glUniform1i", (GLADapiproc) glad_glUniform1i, 2, location, v0); } PFNGLUNIFORM1IPROC glad_debug_glUniform1i = glad_debug_impl_glUniform1i; PFNGLUNIFORM1IVPROC glad_glUniform1iv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform1iv(GLint location, GLsizei count, const GLint * value) { _pre_call_gl_callback("glUniform1iv", (GLADapiproc) glad_glUniform1iv, 3, location, count, value); glad_glUniform1iv(location, count, value); _post_call_gl_callback(NULL, "glUniform1iv", (GLADapiproc) glad_glUniform1iv, 3, location, count, value); } PFNGLUNIFORM1IVPROC glad_debug_glUniform1iv = glad_debug_impl_glUniform1iv; PFNGLUNIFORM1UIPROC glad_glUniform1ui = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform1ui(GLint location, GLuint v0) { _pre_call_gl_callback("glUniform1ui", (GLADapiproc) glad_glUniform1ui, 2, location, v0); glad_glUniform1ui(location, v0); _post_call_gl_callback(NULL, "glUniform1ui", (GLADapiproc) glad_glUniform1ui, 2, location, v0); } PFNGLUNIFORM1UIPROC glad_debug_glUniform1ui = glad_debug_impl_glUniform1ui; PFNGLUNIFORM1UIVPROC glad_glUniform1uiv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform1uiv(GLint location, GLsizei count, const GLuint * value) { _pre_call_gl_callback("glUniform1uiv", (GLADapiproc) glad_glUniform1uiv, 3, location, count, value); glad_glUniform1uiv(location, count, value); _post_call_gl_callback(NULL, "glUniform1uiv", (GLADapiproc) glad_glUniform1uiv, 3, location, count, value); } PFNGLUNIFORM1UIVPROC glad_debug_glUniform1uiv = glad_debug_impl_glUniform1uiv; PFNGLUNIFORM2FPROC glad_glUniform2f = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform2f(GLint location, GLfloat v0, GLfloat v1) { _pre_call_gl_callback("glUniform2f", (GLADapiproc) glad_glUniform2f, 3, location, v0, v1); glad_glUniform2f(location, v0, v1); _post_call_gl_callback(NULL, "glUniform2f", (GLADapiproc) glad_glUniform2f, 3, location, v0, v1); } PFNGLUNIFORM2FPROC glad_debug_glUniform2f = glad_debug_impl_glUniform2f; PFNGLUNIFORM2FVPROC glad_glUniform2fv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform2fv(GLint location, GLsizei count, const GLfloat * value) { _pre_call_gl_callback("glUniform2fv", (GLADapiproc) glad_glUniform2fv, 3, location, count, value); glad_glUniform2fv(location, count, value); _post_call_gl_callback(NULL, "glUniform2fv", (GLADapiproc) glad_glUniform2fv, 3, location, count, value); } PFNGLUNIFORM2FVPROC glad_debug_glUniform2fv = glad_debug_impl_glUniform2fv; PFNGLUNIFORM2IPROC glad_glUniform2i = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform2i(GLint location, GLint v0, GLint v1) { _pre_call_gl_callback("glUniform2i", (GLADapiproc) glad_glUniform2i, 3, location, v0, v1); glad_glUniform2i(location, v0, v1); _post_call_gl_callback(NULL, "glUniform2i", (GLADapiproc) glad_glUniform2i, 3, location, v0, v1); } PFNGLUNIFORM2IPROC glad_debug_glUniform2i = glad_debug_impl_glUniform2i; PFNGLUNIFORM2IVPROC glad_glUniform2iv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform2iv(GLint location, GLsizei count, const GLint * value) { _pre_call_gl_callback("glUniform2iv", (GLADapiproc) glad_glUniform2iv, 3, location, count, value); glad_glUniform2iv(location, count, value); _post_call_gl_callback(NULL, "glUniform2iv", (GLADapiproc) glad_glUniform2iv, 3, location, count, value); } PFNGLUNIFORM2IVPROC glad_debug_glUniform2iv = glad_debug_impl_glUniform2iv; PFNGLUNIFORM2UIPROC glad_glUniform2ui = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform2ui(GLint location, GLuint v0, GLuint v1) { _pre_call_gl_callback("glUniform2ui", (GLADapiproc) glad_glUniform2ui, 3, location, v0, v1); glad_glUniform2ui(location, v0, v1); _post_call_gl_callback(NULL, "glUniform2ui", (GLADapiproc) glad_glUniform2ui, 3, location, v0, v1); } PFNGLUNIFORM2UIPROC glad_debug_glUniform2ui = glad_debug_impl_glUniform2ui; PFNGLUNIFORM2UIVPROC glad_glUniform2uiv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform2uiv(GLint location, GLsizei count, const GLuint * value) { _pre_call_gl_callback("glUniform2uiv", (GLADapiproc) glad_glUniform2uiv, 3, location, count, value); glad_glUniform2uiv(location, count, value); _post_call_gl_callback(NULL, "glUniform2uiv", (GLADapiproc) glad_glUniform2uiv, 3, location, count, value); } PFNGLUNIFORM2UIVPROC glad_debug_glUniform2uiv = glad_debug_impl_glUniform2uiv; PFNGLUNIFORM3FPROC glad_glUniform3f = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2) { _pre_call_gl_callback("glUniform3f", (GLADapiproc) glad_glUniform3f, 4, location, v0, v1, v2); glad_glUniform3f(location, v0, v1, v2); _post_call_gl_callback(NULL, "glUniform3f", (GLADapiproc) glad_glUniform3f, 4, location, v0, v1, v2); } PFNGLUNIFORM3FPROC glad_debug_glUniform3f = glad_debug_impl_glUniform3f; PFNGLUNIFORM3FVPROC glad_glUniform3fv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform3fv(GLint location, GLsizei count, const GLfloat * value) { _pre_call_gl_callback("glUniform3fv", (GLADapiproc) glad_glUniform3fv, 3, location, count, value); glad_glUniform3fv(location, count, value); _post_call_gl_callback(NULL, "glUniform3fv", (GLADapiproc) glad_glUniform3fv, 3, location, count, value); } PFNGLUNIFORM3FVPROC glad_debug_glUniform3fv = glad_debug_impl_glUniform3fv; PFNGLUNIFORM3IPROC glad_glUniform3i = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform3i(GLint location, GLint v0, GLint v1, GLint v2) { _pre_call_gl_callback("glUniform3i", (GLADapiproc) glad_glUniform3i, 4, location, v0, v1, v2); glad_glUniform3i(location, v0, v1, v2); _post_call_gl_callback(NULL, "glUniform3i", (GLADapiproc) glad_glUniform3i, 4, location, v0, v1, v2); } PFNGLUNIFORM3IPROC glad_debug_glUniform3i = glad_debug_impl_glUniform3i; PFNGLUNIFORM3IVPROC glad_glUniform3iv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform3iv(GLint location, GLsizei count, const GLint * value) { _pre_call_gl_callback("glUniform3iv", (GLADapiproc) glad_glUniform3iv, 3, location, count, value); glad_glUniform3iv(location, count, value); _post_call_gl_callback(NULL, "glUniform3iv", (GLADapiproc) glad_glUniform3iv, 3, location, count, value); } PFNGLUNIFORM3IVPROC glad_debug_glUniform3iv = glad_debug_impl_glUniform3iv; PFNGLUNIFORM3UIPROC glad_glUniform3ui = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2) { _pre_call_gl_callback("glUniform3ui", (GLADapiproc) glad_glUniform3ui, 4, location, v0, v1, v2); glad_glUniform3ui(location, v0, v1, v2); _post_call_gl_callback(NULL, "glUniform3ui", (GLADapiproc) glad_glUniform3ui, 4, location, v0, v1, v2); } PFNGLUNIFORM3UIPROC glad_debug_glUniform3ui = glad_debug_impl_glUniform3ui; PFNGLUNIFORM3UIVPROC glad_glUniform3uiv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform3uiv(GLint location, GLsizei count, const GLuint * value) { _pre_call_gl_callback("glUniform3uiv", (GLADapiproc) glad_glUniform3uiv, 3, location, count, value); glad_glUniform3uiv(location, count, value); _post_call_gl_callback(NULL, "glUniform3uiv", (GLADapiproc) glad_glUniform3uiv, 3, location, count, value); } PFNGLUNIFORM3UIVPROC glad_debug_glUniform3uiv = glad_debug_impl_glUniform3uiv; PFNGLUNIFORM4FPROC glad_glUniform4f = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) { _pre_call_gl_callback("glUniform4f", (GLADapiproc) glad_glUniform4f, 5, location, v0, v1, v2, v3); glad_glUniform4f(location, v0, v1, v2, v3); _post_call_gl_callback(NULL, "glUniform4f", (GLADapiproc) glad_glUniform4f, 5, location, v0, v1, v2, v3); } PFNGLUNIFORM4FPROC glad_debug_glUniform4f = glad_debug_impl_glUniform4f; PFNGLUNIFORM4FVPROC glad_glUniform4fv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform4fv(GLint location, GLsizei count, const GLfloat * value) { _pre_call_gl_callback("glUniform4fv", (GLADapiproc) glad_glUniform4fv, 3, location, count, value); glad_glUniform4fv(location, count, value); _post_call_gl_callback(NULL, "glUniform4fv", (GLADapiproc) glad_glUniform4fv, 3, location, count, value); } PFNGLUNIFORM4FVPROC glad_debug_glUniform4fv = glad_debug_impl_glUniform4fv; PFNGLUNIFORM4IPROC glad_glUniform4i = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3) { _pre_call_gl_callback("glUniform4i", (GLADapiproc) glad_glUniform4i, 5, location, v0, v1, v2, v3); glad_glUniform4i(location, v0, v1, v2, v3); _post_call_gl_callback(NULL, "glUniform4i", (GLADapiproc) glad_glUniform4i, 5, location, v0, v1, v2, v3); } PFNGLUNIFORM4IPROC glad_debug_glUniform4i = glad_debug_impl_glUniform4i; PFNGLUNIFORM4IVPROC glad_glUniform4iv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform4iv(GLint location, GLsizei count, const GLint * value) { _pre_call_gl_callback("glUniform4iv", (GLADapiproc) glad_glUniform4iv, 3, location, count, value); glad_glUniform4iv(location, count, value); _post_call_gl_callback(NULL, "glUniform4iv", (GLADapiproc) glad_glUniform4iv, 3, location, count, value); } PFNGLUNIFORM4IVPROC glad_debug_glUniform4iv = glad_debug_impl_glUniform4iv; PFNGLUNIFORM4UIPROC glad_glUniform4ui = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3) { _pre_call_gl_callback("glUniform4ui", (GLADapiproc) glad_glUniform4ui, 5, location, v0, v1, v2, v3); glad_glUniform4ui(location, v0, v1, v2, v3); _post_call_gl_callback(NULL, "glUniform4ui", (GLADapiproc) glad_glUniform4ui, 5, location, v0, v1, v2, v3); } PFNGLUNIFORM4UIPROC glad_debug_glUniform4ui = glad_debug_impl_glUniform4ui; PFNGLUNIFORM4UIVPROC glad_glUniform4uiv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniform4uiv(GLint location, GLsizei count, const GLuint * value) { _pre_call_gl_callback("glUniform4uiv", (GLADapiproc) glad_glUniform4uiv, 3, location, count, value); glad_glUniform4uiv(location, count, value); _post_call_gl_callback(NULL, "glUniform4uiv", (GLADapiproc) glad_glUniform4uiv, 3, location, count, value); } PFNGLUNIFORM4UIVPROC glad_debug_glUniform4uiv = glad_debug_impl_glUniform4uiv; PFNGLUNIFORMBLOCKBINDINGPROC glad_glUniformBlockBinding = NULL; static void GLAD_API_PTR glad_debug_impl_glUniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding) { _pre_call_gl_callback("glUniformBlockBinding", (GLADapiproc) glad_glUniformBlockBinding, 3, program, uniformBlockIndex, uniformBlockBinding); glad_glUniformBlockBinding(program, uniformBlockIndex, uniformBlockBinding); _post_call_gl_callback(NULL, "glUniformBlockBinding", (GLADapiproc) glad_glUniformBlockBinding, 3, program, uniformBlockIndex, uniformBlockBinding); } PFNGLUNIFORMBLOCKBINDINGPROC glad_debug_glUniformBlockBinding = glad_debug_impl_glUniformBlockBinding; PFNGLUNIFORMMATRIX2FVPROC glad_glUniformMatrix2fv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value) { _pre_call_gl_callback("glUniformMatrix2fv", (GLADapiproc) glad_glUniformMatrix2fv, 4, location, count, transpose, value); glad_glUniformMatrix2fv(location, count, transpose, value); _post_call_gl_callback(NULL, "glUniformMatrix2fv", (GLADapiproc) glad_glUniformMatrix2fv, 4, location, count, transpose, value); } PFNGLUNIFORMMATRIX2FVPROC glad_debug_glUniformMatrix2fv = glad_debug_impl_glUniformMatrix2fv; PFNGLUNIFORMMATRIX2X3FVPROC glad_glUniformMatrix2x3fv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value) { _pre_call_gl_callback("glUniformMatrix2x3fv", (GLADapiproc) glad_glUniformMatrix2x3fv, 4, location, count, transpose, value); glad_glUniformMatrix2x3fv(location, count, transpose, value); _post_call_gl_callback(NULL, "glUniformMatrix2x3fv", (GLADapiproc) glad_glUniformMatrix2x3fv, 4, location, count, transpose, value); } PFNGLUNIFORMMATRIX2X3FVPROC glad_debug_glUniformMatrix2x3fv = glad_debug_impl_glUniformMatrix2x3fv; PFNGLUNIFORMMATRIX2X4FVPROC glad_glUniformMatrix2x4fv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value) { _pre_call_gl_callback("glUniformMatrix2x4fv", (GLADapiproc) glad_glUniformMatrix2x4fv, 4, location, count, transpose, value); glad_glUniformMatrix2x4fv(location, count, transpose, value); _post_call_gl_callback(NULL, "glUniformMatrix2x4fv", (GLADapiproc) glad_glUniformMatrix2x4fv, 4, location, count, transpose, value); } PFNGLUNIFORMMATRIX2X4FVPROC glad_debug_glUniformMatrix2x4fv = glad_debug_impl_glUniformMatrix2x4fv; PFNGLUNIFORMMATRIX3FVPROC glad_glUniformMatrix3fv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value) { _pre_call_gl_callback("glUniformMatrix3fv", (GLADapiproc) glad_glUniformMatrix3fv, 4, location, count, transpose, value); glad_glUniformMatrix3fv(location, count, transpose, value); _post_call_gl_callback(NULL, "glUniformMatrix3fv", (GLADapiproc) glad_glUniformMatrix3fv, 4, location, count, transpose, value); } PFNGLUNIFORMMATRIX3FVPROC glad_debug_glUniformMatrix3fv = glad_debug_impl_glUniformMatrix3fv; PFNGLUNIFORMMATRIX3X2FVPROC glad_glUniformMatrix3x2fv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value) { _pre_call_gl_callback("glUniformMatrix3x2fv", (GLADapiproc) glad_glUniformMatrix3x2fv, 4, location, count, transpose, value); glad_glUniformMatrix3x2fv(location, count, transpose, value); _post_call_gl_callback(NULL, "glUniformMatrix3x2fv", (GLADapiproc) glad_glUniformMatrix3x2fv, 4, location, count, transpose, value); } PFNGLUNIFORMMATRIX3X2FVPROC glad_debug_glUniformMatrix3x2fv = glad_debug_impl_glUniformMatrix3x2fv; PFNGLUNIFORMMATRIX3X4FVPROC glad_glUniformMatrix3x4fv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value) { _pre_call_gl_callback("glUniformMatrix3x4fv", (GLADapiproc) glad_glUniformMatrix3x4fv, 4, location, count, transpose, value); glad_glUniformMatrix3x4fv(location, count, transpose, value); _post_call_gl_callback(NULL, "glUniformMatrix3x4fv", (GLADapiproc) glad_glUniformMatrix3x4fv, 4, location, count, transpose, value); } PFNGLUNIFORMMATRIX3X4FVPROC glad_debug_glUniformMatrix3x4fv = glad_debug_impl_glUniformMatrix3x4fv; PFNGLUNIFORMMATRIX4FVPROC glad_glUniformMatrix4fv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value) { _pre_call_gl_callback("glUniformMatrix4fv", (GLADapiproc) glad_glUniformMatrix4fv, 4, location, count, transpose, value); glad_glUniformMatrix4fv(location, count, transpose, value); _post_call_gl_callback(NULL, "glUniformMatrix4fv", (GLADapiproc) glad_glUniformMatrix4fv, 4, location, count, transpose, value); } PFNGLUNIFORMMATRIX4FVPROC glad_debug_glUniformMatrix4fv = glad_debug_impl_glUniformMatrix4fv; PFNGLUNIFORMMATRIX4X2FVPROC glad_glUniformMatrix4x2fv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value) { _pre_call_gl_callback("glUniformMatrix4x2fv", (GLADapiproc) glad_glUniformMatrix4x2fv, 4, location, count, transpose, value); glad_glUniformMatrix4x2fv(location, count, transpose, value); _post_call_gl_callback(NULL, "glUniformMatrix4x2fv", (GLADapiproc) glad_glUniformMatrix4x2fv, 4, location, count, transpose, value); } PFNGLUNIFORMMATRIX4X2FVPROC glad_debug_glUniformMatrix4x2fv = glad_debug_impl_glUniformMatrix4x2fv; PFNGLUNIFORMMATRIX4X3FVPROC glad_glUniformMatrix4x3fv = NULL; static void GLAD_API_PTR glad_debug_impl_glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat * value) { _pre_call_gl_callback("glUniformMatrix4x3fv", (GLADapiproc) glad_glUniformMatrix4x3fv, 4, location, count, transpose, value); glad_glUniformMatrix4x3fv(location, count, transpose, value); _post_call_gl_callback(NULL, "glUniformMatrix4x3fv", (GLADapiproc) glad_glUniformMatrix4x3fv, 4, location, count, transpose, value); } PFNGLUNIFORMMATRIX4X3FVPROC glad_debug_glUniformMatrix4x3fv = glad_debug_impl_glUniformMatrix4x3fv; PFNGLUNMAPBUFFERPROC glad_glUnmapBuffer = NULL; static GLboolean GLAD_API_PTR glad_debug_impl_glUnmapBuffer(GLenum target) { GLboolean ret; _pre_call_gl_callback("glUnmapBuffer", (GLADapiproc) glad_glUnmapBuffer, 1, target); ret = glad_glUnmapBuffer(target); _post_call_gl_callback((void*) &ret, "glUnmapBuffer", (GLADapiproc) glad_glUnmapBuffer, 1, target); return ret; } PFNGLUNMAPBUFFERPROC glad_debug_glUnmapBuffer = glad_debug_impl_glUnmapBuffer; PFNGLUSEPROGRAMPROC glad_glUseProgram = NULL; static void GLAD_API_PTR glad_debug_impl_glUseProgram(GLuint program) { _pre_call_gl_callback("glUseProgram", (GLADapiproc) glad_glUseProgram, 1, program); glad_glUseProgram(program); _post_call_gl_callback(NULL, "glUseProgram", (GLADapiproc) glad_glUseProgram, 1, program); } PFNGLUSEPROGRAMPROC glad_debug_glUseProgram = glad_debug_impl_glUseProgram; PFNGLVALIDATEPROGRAMPROC glad_glValidateProgram = NULL; static void GLAD_API_PTR glad_debug_impl_glValidateProgram(GLuint program) { _pre_call_gl_callback("glValidateProgram", (GLADapiproc) glad_glValidateProgram, 1, program); glad_glValidateProgram(program); _post_call_gl_callback(NULL, "glValidateProgram", (GLADapiproc) glad_glValidateProgram, 1, program); } PFNGLVALIDATEPROGRAMPROC glad_debug_glValidateProgram = glad_debug_impl_glValidateProgram; PFNGLVERTEX2DPROC glad_glVertex2d = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex2d(GLdouble x, GLdouble y) { _pre_call_gl_callback("glVertex2d", (GLADapiproc) glad_glVertex2d, 2, x, y); glad_glVertex2d(x, y); _post_call_gl_callback(NULL, "glVertex2d", (GLADapiproc) glad_glVertex2d, 2, x, y); } PFNGLVERTEX2DPROC glad_debug_glVertex2d = glad_debug_impl_glVertex2d; PFNGLVERTEX2DVPROC glad_glVertex2dv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex2dv(const GLdouble * v) { _pre_call_gl_callback("glVertex2dv", (GLADapiproc) glad_glVertex2dv, 1, v); glad_glVertex2dv(v); _post_call_gl_callback(NULL, "glVertex2dv", (GLADapiproc) glad_glVertex2dv, 1, v); } PFNGLVERTEX2DVPROC glad_debug_glVertex2dv = glad_debug_impl_glVertex2dv; PFNGLVERTEX2FPROC glad_glVertex2f = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex2f(GLfloat x, GLfloat y) { _pre_call_gl_callback("glVertex2f", (GLADapiproc) glad_glVertex2f, 2, x, y); glad_glVertex2f(x, y); _post_call_gl_callback(NULL, "glVertex2f", (GLADapiproc) glad_glVertex2f, 2, x, y); } PFNGLVERTEX2FPROC glad_debug_glVertex2f = glad_debug_impl_glVertex2f; PFNGLVERTEX2FVPROC glad_glVertex2fv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex2fv(const GLfloat * v) { _pre_call_gl_callback("glVertex2fv", (GLADapiproc) glad_glVertex2fv, 1, v); glad_glVertex2fv(v); _post_call_gl_callback(NULL, "glVertex2fv", (GLADapiproc) glad_glVertex2fv, 1, v); } PFNGLVERTEX2FVPROC glad_debug_glVertex2fv = glad_debug_impl_glVertex2fv; PFNGLVERTEX2IPROC glad_glVertex2i = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex2i(GLint x, GLint y) { _pre_call_gl_callback("glVertex2i", (GLADapiproc) glad_glVertex2i, 2, x, y); glad_glVertex2i(x, y); _post_call_gl_callback(NULL, "glVertex2i", (GLADapiproc) glad_glVertex2i, 2, x, y); } PFNGLVERTEX2IPROC glad_debug_glVertex2i = glad_debug_impl_glVertex2i; PFNGLVERTEX2IVPROC glad_glVertex2iv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex2iv(const GLint * v) { _pre_call_gl_callback("glVertex2iv", (GLADapiproc) glad_glVertex2iv, 1, v); glad_glVertex2iv(v); _post_call_gl_callback(NULL, "glVertex2iv", (GLADapiproc) glad_glVertex2iv, 1, v); } PFNGLVERTEX2IVPROC glad_debug_glVertex2iv = glad_debug_impl_glVertex2iv; PFNGLVERTEX2SPROC glad_glVertex2s = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex2s(GLshort x, GLshort y) { _pre_call_gl_callback("glVertex2s", (GLADapiproc) glad_glVertex2s, 2, x, y); glad_glVertex2s(x, y); _post_call_gl_callback(NULL, "glVertex2s", (GLADapiproc) glad_glVertex2s, 2, x, y); } PFNGLVERTEX2SPROC glad_debug_glVertex2s = glad_debug_impl_glVertex2s; PFNGLVERTEX2SVPROC glad_glVertex2sv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex2sv(const GLshort * v) { _pre_call_gl_callback("glVertex2sv", (GLADapiproc) glad_glVertex2sv, 1, v); glad_glVertex2sv(v); _post_call_gl_callback(NULL, "glVertex2sv", (GLADapiproc) glad_glVertex2sv, 1, v); } PFNGLVERTEX2SVPROC glad_debug_glVertex2sv = glad_debug_impl_glVertex2sv; PFNGLVERTEX3DPROC glad_glVertex3d = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex3d(GLdouble x, GLdouble y, GLdouble z) { _pre_call_gl_callback("glVertex3d", (GLADapiproc) glad_glVertex3d, 3, x, y, z); glad_glVertex3d(x, y, z); _post_call_gl_callback(NULL, "glVertex3d", (GLADapiproc) glad_glVertex3d, 3, x, y, z); } PFNGLVERTEX3DPROC glad_debug_glVertex3d = glad_debug_impl_glVertex3d; PFNGLVERTEX3DVPROC glad_glVertex3dv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex3dv(const GLdouble * v) { _pre_call_gl_callback("glVertex3dv", (GLADapiproc) glad_glVertex3dv, 1, v); glad_glVertex3dv(v); _post_call_gl_callback(NULL, "glVertex3dv", (GLADapiproc) glad_glVertex3dv, 1, v); } PFNGLVERTEX3DVPROC glad_debug_glVertex3dv = glad_debug_impl_glVertex3dv; PFNGLVERTEX3FPROC glad_glVertex3f = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex3f(GLfloat x, GLfloat y, GLfloat z) { _pre_call_gl_callback("glVertex3f", (GLADapiproc) glad_glVertex3f, 3, x, y, z); glad_glVertex3f(x, y, z); _post_call_gl_callback(NULL, "glVertex3f", (GLADapiproc) glad_glVertex3f, 3, x, y, z); } PFNGLVERTEX3FPROC glad_debug_glVertex3f = glad_debug_impl_glVertex3f; PFNGLVERTEX3FVPROC glad_glVertex3fv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex3fv(const GLfloat * v) { _pre_call_gl_callback("glVertex3fv", (GLADapiproc) glad_glVertex3fv, 1, v); glad_glVertex3fv(v); _post_call_gl_callback(NULL, "glVertex3fv", (GLADapiproc) glad_glVertex3fv, 1, v); } PFNGLVERTEX3FVPROC glad_debug_glVertex3fv = glad_debug_impl_glVertex3fv; PFNGLVERTEX3IPROC glad_glVertex3i = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex3i(GLint x, GLint y, GLint z) { _pre_call_gl_callback("glVertex3i", (GLADapiproc) glad_glVertex3i, 3, x, y, z); glad_glVertex3i(x, y, z); _post_call_gl_callback(NULL, "glVertex3i", (GLADapiproc) glad_glVertex3i, 3, x, y, z); } PFNGLVERTEX3IPROC glad_debug_glVertex3i = glad_debug_impl_glVertex3i; PFNGLVERTEX3IVPROC glad_glVertex3iv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex3iv(const GLint * v) { _pre_call_gl_callback("glVertex3iv", (GLADapiproc) glad_glVertex3iv, 1, v); glad_glVertex3iv(v); _post_call_gl_callback(NULL, "glVertex3iv", (GLADapiproc) glad_glVertex3iv, 1, v); } PFNGLVERTEX3IVPROC glad_debug_glVertex3iv = glad_debug_impl_glVertex3iv; PFNGLVERTEX3SPROC glad_glVertex3s = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex3s(GLshort x, GLshort y, GLshort z) { _pre_call_gl_callback("glVertex3s", (GLADapiproc) glad_glVertex3s, 3, x, y, z); glad_glVertex3s(x, y, z); _post_call_gl_callback(NULL, "glVertex3s", (GLADapiproc) glad_glVertex3s, 3, x, y, z); } PFNGLVERTEX3SPROC glad_debug_glVertex3s = glad_debug_impl_glVertex3s; PFNGLVERTEX3SVPROC glad_glVertex3sv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex3sv(const GLshort * v) { _pre_call_gl_callback("glVertex3sv", (GLADapiproc) glad_glVertex3sv, 1, v); glad_glVertex3sv(v); _post_call_gl_callback(NULL, "glVertex3sv", (GLADapiproc) glad_glVertex3sv, 1, v); } PFNGLVERTEX3SVPROC glad_debug_glVertex3sv = glad_debug_impl_glVertex3sv; PFNGLVERTEX4DPROC glad_glVertex4d = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w) { _pre_call_gl_callback("glVertex4d", (GLADapiproc) glad_glVertex4d, 4, x, y, z, w); glad_glVertex4d(x, y, z, w); _post_call_gl_callback(NULL, "glVertex4d", (GLADapiproc) glad_glVertex4d, 4, x, y, z, w); } PFNGLVERTEX4DPROC glad_debug_glVertex4d = glad_debug_impl_glVertex4d; PFNGLVERTEX4DVPROC glad_glVertex4dv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex4dv(const GLdouble * v) { _pre_call_gl_callback("glVertex4dv", (GLADapiproc) glad_glVertex4dv, 1, v); glad_glVertex4dv(v); _post_call_gl_callback(NULL, "glVertex4dv", (GLADapiproc) glad_glVertex4dv, 1, v); } PFNGLVERTEX4DVPROC glad_debug_glVertex4dv = glad_debug_impl_glVertex4dv; PFNGLVERTEX4FPROC glad_glVertex4f = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) { _pre_call_gl_callback("glVertex4f", (GLADapiproc) glad_glVertex4f, 4, x, y, z, w); glad_glVertex4f(x, y, z, w); _post_call_gl_callback(NULL, "glVertex4f", (GLADapiproc) glad_glVertex4f, 4, x, y, z, w); } PFNGLVERTEX4FPROC glad_debug_glVertex4f = glad_debug_impl_glVertex4f; PFNGLVERTEX4FVPROC glad_glVertex4fv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex4fv(const GLfloat * v) { _pre_call_gl_callback("glVertex4fv", (GLADapiproc) glad_glVertex4fv, 1, v); glad_glVertex4fv(v); _post_call_gl_callback(NULL, "glVertex4fv", (GLADapiproc) glad_glVertex4fv, 1, v); } PFNGLVERTEX4FVPROC glad_debug_glVertex4fv = glad_debug_impl_glVertex4fv; PFNGLVERTEX4IPROC glad_glVertex4i = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex4i(GLint x, GLint y, GLint z, GLint w) { _pre_call_gl_callback("glVertex4i", (GLADapiproc) glad_glVertex4i, 4, x, y, z, w); glad_glVertex4i(x, y, z, w); _post_call_gl_callback(NULL, "glVertex4i", (GLADapiproc) glad_glVertex4i, 4, x, y, z, w); } PFNGLVERTEX4IPROC glad_debug_glVertex4i = glad_debug_impl_glVertex4i; PFNGLVERTEX4IVPROC glad_glVertex4iv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex4iv(const GLint * v) { _pre_call_gl_callback("glVertex4iv", (GLADapiproc) glad_glVertex4iv, 1, v); glad_glVertex4iv(v); _post_call_gl_callback(NULL, "glVertex4iv", (GLADapiproc) glad_glVertex4iv, 1, v); } PFNGLVERTEX4IVPROC glad_debug_glVertex4iv = glad_debug_impl_glVertex4iv; PFNGLVERTEX4SPROC glad_glVertex4s = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex4s(GLshort x, GLshort y, GLshort z, GLshort w) { _pre_call_gl_callback("glVertex4s", (GLADapiproc) glad_glVertex4s, 4, x, y, z, w); glad_glVertex4s(x, y, z, w); _post_call_gl_callback(NULL, "glVertex4s", (GLADapiproc) glad_glVertex4s, 4, x, y, z, w); } PFNGLVERTEX4SPROC glad_debug_glVertex4s = glad_debug_impl_glVertex4s; PFNGLVERTEX4SVPROC glad_glVertex4sv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertex4sv(const GLshort * v) { _pre_call_gl_callback("glVertex4sv", (GLADapiproc) glad_glVertex4sv, 1, v); glad_glVertex4sv(v); _post_call_gl_callback(NULL, "glVertex4sv", (GLADapiproc) glad_glVertex4sv, 1, v); } PFNGLVERTEX4SVPROC glad_debug_glVertex4sv = glad_debug_impl_glVertex4sv; PFNGLVERTEXATTRIB1DPROC glad_glVertexAttrib1d = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib1d(GLuint index, GLdouble x) { _pre_call_gl_callback("glVertexAttrib1d", (GLADapiproc) glad_glVertexAttrib1d, 2, index, x); glad_glVertexAttrib1d(index, x); _post_call_gl_callback(NULL, "glVertexAttrib1d", (GLADapiproc) glad_glVertexAttrib1d, 2, index, x); } PFNGLVERTEXATTRIB1DPROC glad_debug_glVertexAttrib1d = glad_debug_impl_glVertexAttrib1d; PFNGLVERTEXATTRIB1DVPROC glad_glVertexAttrib1dv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib1dv(GLuint index, const GLdouble * v) { _pre_call_gl_callback("glVertexAttrib1dv", (GLADapiproc) glad_glVertexAttrib1dv, 2, index, v); glad_glVertexAttrib1dv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib1dv", (GLADapiproc) glad_glVertexAttrib1dv, 2, index, v); } PFNGLVERTEXATTRIB1DVPROC glad_debug_glVertexAttrib1dv = glad_debug_impl_glVertexAttrib1dv; PFNGLVERTEXATTRIB1FPROC glad_glVertexAttrib1f = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib1f(GLuint index, GLfloat x) { _pre_call_gl_callback("glVertexAttrib1f", (GLADapiproc) glad_glVertexAttrib1f, 2, index, x); glad_glVertexAttrib1f(index, x); _post_call_gl_callback(NULL, "glVertexAttrib1f", (GLADapiproc) glad_glVertexAttrib1f, 2, index, x); } PFNGLVERTEXATTRIB1FPROC glad_debug_glVertexAttrib1f = glad_debug_impl_glVertexAttrib1f; PFNGLVERTEXATTRIB1FVPROC glad_glVertexAttrib1fv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib1fv(GLuint index, const GLfloat * v) { _pre_call_gl_callback("glVertexAttrib1fv", (GLADapiproc) glad_glVertexAttrib1fv, 2, index, v); glad_glVertexAttrib1fv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib1fv", (GLADapiproc) glad_glVertexAttrib1fv, 2, index, v); } PFNGLVERTEXATTRIB1FVPROC glad_debug_glVertexAttrib1fv = glad_debug_impl_glVertexAttrib1fv; PFNGLVERTEXATTRIB1SPROC glad_glVertexAttrib1s = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib1s(GLuint index, GLshort x) { _pre_call_gl_callback("glVertexAttrib1s", (GLADapiproc) glad_glVertexAttrib1s, 2, index, x); glad_glVertexAttrib1s(index, x); _post_call_gl_callback(NULL, "glVertexAttrib1s", (GLADapiproc) glad_glVertexAttrib1s, 2, index, x); } PFNGLVERTEXATTRIB1SPROC glad_debug_glVertexAttrib1s = glad_debug_impl_glVertexAttrib1s; PFNGLVERTEXATTRIB1SVPROC glad_glVertexAttrib1sv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib1sv(GLuint index, const GLshort * v) { _pre_call_gl_callback("glVertexAttrib1sv", (GLADapiproc) glad_glVertexAttrib1sv, 2, index, v); glad_glVertexAttrib1sv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib1sv", (GLADapiproc) glad_glVertexAttrib1sv, 2, index, v); } PFNGLVERTEXATTRIB1SVPROC glad_debug_glVertexAttrib1sv = glad_debug_impl_glVertexAttrib1sv; PFNGLVERTEXATTRIB2DPROC glad_glVertexAttrib2d = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib2d(GLuint index, GLdouble x, GLdouble y) { _pre_call_gl_callback("glVertexAttrib2d", (GLADapiproc) glad_glVertexAttrib2d, 3, index, x, y); glad_glVertexAttrib2d(index, x, y); _post_call_gl_callback(NULL, "glVertexAttrib2d", (GLADapiproc) glad_glVertexAttrib2d, 3, index, x, y); } PFNGLVERTEXATTRIB2DPROC glad_debug_glVertexAttrib2d = glad_debug_impl_glVertexAttrib2d; PFNGLVERTEXATTRIB2DVPROC glad_glVertexAttrib2dv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib2dv(GLuint index, const GLdouble * v) { _pre_call_gl_callback("glVertexAttrib2dv", (GLADapiproc) glad_glVertexAttrib2dv, 2, index, v); glad_glVertexAttrib2dv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib2dv", (GLADapiproc) glad_glVertexAttrib2dv, 2, index, v); } PFNGLVERTEXATTRIB2DVPROC glad_debug_glVertexAttrib2dv = glad_debug_impl_glVertexAttrib2dv; PFNGLVERTEXATTRIB2FPROC glad_glVertexAttrib2f = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib2f(GLuint index, GLfloat x, GLfloat y) { _pre_call_gl_callback("glVertexAttrib2f", (GLADapiproc) glad_glVertexAttrib2f, 3, index, x, y); glad_glVertexAttrib2f(index, x, y); _post_call_gl_callback(NULL, "glVertexAttrib2f", (GLADapiproc) glad_glVertexAttrib2f, 3, index, x, y); } PFNGLVERTEXATTRIB2FPROC glad_debug_glVertexAttrib2f = glad_debug_impl_glVertexAttrib2f; PFNGLVERTEXATTRIB2FVPROC glad_glVertexAttrib2fv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib2fv(GLuint index, const GLfloat * v) { _pre_call_gl_callback("glVertexAttrib2fv", (GLADapiproc) glad_glVertexAttrib2fv, 2, index, v); glad_glVertexAttrib2fv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib2fv", (GLADapiproc) glad_glVertexAttrib2fv, 2, index, v); } PFNGLVERTEXATTRIB2FVPROC glad_debug_glVertexAttrib2fv = glad_debug_impl_glVertexAttrib2fv; PFNGLVERTEXATTRIB2SPROC glad_glVertexAttrib2s = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib2s(GLuint index, GLshort x, GLshort y) { _pre_call_gl_callback("glVertexAttrib2s", (GLADapiproc) glad_glVertexAttrib2s, 3, index, x, y); glad_glVertexAttrib2s(index, x, y); _post_call_gl_callback(NULL, "glVertexAttrib2s", (GLADapiproc) glad_glVertexAttrib2s, 3, index, x, y); } PFNGLVERTEXATTRIB2SPROC glad_debug_glVertexAttrib2s = glad_debug_impl_glVertexAttrib2s; PFNGLVERTEXATTRIB2SVPROC glad_glVertexAttrib2sv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib2sv(GLuint index, const GLshort * v) { _pre_call_gl_callback("glVertexAttrib2sv", (GLADapiproc) glad_glVertexAttrib2sv, 2, index, v); glad_glVertexAttrib2sv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib2sv", (GLADapiproc) glad_glVertexAttrib2sv, 2, index, v); } PFNGLVERTEXATTRIB2SVPROC glad_debug_glVertexAttrib2sv = glad_debug_impl_glVertexAttrib2sv; PFNGLVERTEXATTRIB3DPROC glad_glVertexAttrib3d = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib3d(GLuint index, GLdouble x, GLdouble y, GLdouble z) { _pre_call_gl_callback("glVertexAttrib3d", (GLADapiproc) glad_glVertexAttrib3d, 4, index, x, y, z); glad_glVertexAttrib3d(index, x, y, z); _post_call_gl_callback(NULL, "glVertexAttrib3d", (GLADapiproc) glad_glVertexAttrib3d, 4, index, x, y, z); } PFNGLVERTEXATTRIB3DPROC glad_debug_glVertexAttrib3d = glad_debug_impl_glVertexAttrib3d; PFNGLVERTEXATTRIB3DVPROC glad_glVertexAttrib3dv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib3dv(GLuint index, const GLdouble * v) { _pre_call_gl_callback("glVertexAttrib3dv", (GLADapiproc) glad_glVertexAttrib3dv, 2, index, v); glad_glVertexAttrib3dv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib3dv", (GLADapiproc) glad_glVertexAttrib3dv, 2, index, v); } PFNGLVERTEXATTRIB3DVPROC glad_debug_glVertexAttrib3dv = glad_debug_impl_glVertexAttrib3dv; PFNGLVERTEXATTRIB3FPROC glad_glVertexAttrib3f = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib3f(GLuint index, GLfloat x, GLfloat y, GLfloat z) { _pre_call_gl_callback("glVertexAttrib3f", (GLADapiproc) glad_glVertexAttrib3f, 4, index, x, y, z); glad_glVertexAttrib3f(index, x, y, z); _post_call_gl_callback(NULL, "glVertexAttrib3f", (GLADapiproc) glad_glVertexAttrib3f, 4, index, x, y, z); } PFNGLVERTEXATTRIB3FPROC glad_debug_glVertexAttrib3f = glad_debug_impl_glVertexAttrib3f; PFNGLVERTEXATTRIB3FVPROC glad_glVertexAttrib3fv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib3fv(GLuint index, const GLfloat * v) { _pre_call_gl_callback("glVertexAttrib3fv", (GLADapiproc) glad_glVertexAttrib3fv, 2, index, v); glad_glVertexAttrib3fv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib3fv", (GLADapiproc) glad_glVertexAttrib3fv, 2, index, v); } PFNGLVERTEXATTRIB3FVPROC glad_debug_glVertexAttrib3fv = glad_debug_impl_glVertexAttrib3fv; PFNGLVERTEXATTRIB3SPROC glad_glVertexAttrib3s = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib3s(GLuint index, GLshort x, GLshort y, GLshort z) { _pre_call_gl_callback("glVertexAttrib3s", (GLADapiproc) glad_glVertexAttrib3s, 4, index, x, y, z); glad_glVertexAttrib3s(index, x, y, z); _post_call_gl_callback(NULL, "glVertexAttrib3s", (GLADapiproc) glad_glVertexAttrib3s, 4, index, x, y, z); } PFNGLVERTEXATTRIB3SPROC glad_debug_glVertexAttrib3s = glad_debug_impl_glVertexAttrib3s; PFNGLVERTEXATTRIB3SVPROC glad_glVertexAttrib3sv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib3sv(GLuint index, const GLshort * v) { _pre_call_gl_callback("glVertexAttrib3sv", (GLADapiproc) glad_glVertexAttrib3sv, 2, index, v); glad_glVertexAttrib3sv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib3sv", (GLADapiproc) glad_glVertexAttrib3sv, 2, index, v); } PFNGLVERTEXATTRIB3SVPROC glad_debug_glVertexAttrib3sv = glad_debug_impl_glVertexAttrib3sv; PFNGLVERTEXATTRIB4NBVPROC glad_glVertexAttrib4Nbv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4Nbv(GLuint index, const GLbyte * v) { _pre_call_gl_callback("glVertexAttrib4Nbv", (GLADapiproc) glad_glVertexAttrib4Nbv, 2, index, v); glad_glVertexAttrib4Nbv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib4Nbv", (GLADapiproc) glad_glVertexAttrib4Nbv, 2, index, v); } PFNGLVERTEXATTRIB4NBVPROC glad_debug_glVertexAttrib4Nbv = glad_debug_impl_glVertexAttrib4Nbv; PFNGLVERTEXATTRIB4NIVPROC glad_glVertexAttrib4Niv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4Niv(GLuint index, const GLint * v) { _pre_call_gl_callback("glVertexAttrib4Niv", (GLADapiproc) glad_glVertexAttrib4Niv, 2, index, v); glad_glVertexAttrib4Niv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib4Niv", (GLADapiproc) glad_glVertexAttrib4Niv, 2, index, v); } PFNGLVERTEXATTRIB4NIVPROC glad_debug_glVertexAttrib4Niv = glad_debug_impl_glVertexAttrib4Niv; PFNGLVERTEXATTRIB4NSVPROC glad_glVertexAttrib4Nsv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4Nsv(GLuint index, const GLshort * v) { _pre_call_gl_callback("glVertexAttrib4Nsv", (GLADapiproc) glad_glVertexAttrib4Nsv, 2, index, v); glad_glVertexAttrib4Nsv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib4Nsv", (GLADapiproc) glad_glVertexAttrib4Nsv, 2, index, v); } PFNGLVERTEXATTRIB4NSVPROC glad_debug_glVertexAttrib4Nsv = glad_debug_impl_glVertexAttrib4Nsv; PFNGLVERTEXATTRIB4NUBPROC glad_glVertexAttrib4Nub = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4Nub(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w) { _pre_call_gl_callback("glVertexAttrib4Nub", (GLADapiproc) glad_glVertexAttrib4Nub, 5, index, x, y, z, w); glad_glVertexAttrib4Nub(index, x, y, z, w); _post_call_gl_callback(NULL, "glVertexAttrib4Nub", (GLADapiproc) glad_glVertexAttrib4Nub, 5, index, x, y, z, w); } PFNGLVERTEXATTRIB4NUBPROC glad_debug_glVertexAttrib4Nub = glad_debug_impl_glVertexAttrib4Nub; PFNGLVERTEXATTRIB4NUBVPROC glad_glVertexAttrib4Nubv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4Nubv(GLuint index, const GLubyte * v) { _pre_call_gl_callback("glVertexAttrib4Nubv", (GLADapiproc) glad_glVertexAttrib4Nubv, 2, index, v); glad_glVertexAttrib4Nubv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib4Nubv", (GLADapiproc) glad_glVertexAttrib4Nubv, 2, index, v); } PFNGLVERTEXATTRIB4NUBVPROC glad_debug_glVertexAttrib4Nubv = glad_debug_impl_glVertexAttrib4Nubv; PFNGLVERTEXATTRIB4NUIVPROC glad_glVertexAttrib4Nuiv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4Nuiv(GLuint index, const GLuint * v) { _pre_call_gl_callback("glVertexAttrib4Nuiv", (GLADapiproc) glad_glVertexAttrib4Nuiv, 2, index, v); glad_glVertexAttrib4Nuiv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib4Nuiv", (GLADapiproc) glad_glVertexAttrib4Nuiv, 2, index, v); } PFNGLVERTEXATTRIB4NUIVPROC glad_debug_glVertexAttrib4Nuiv = glad_debug_impl_glVertexAttrib4Nuiv; PFNGLVERTEXATTRIB4NUSVPROC glad_glVertexAttrib4Nusv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4Nusv(GLuint index, const GLushort * v) { _pre_call_gl_callback("glVertexAttrib4Nusv", (GLADapiproc) glad_glVertexAttrib4Nusv, 2, index, v); glad_glVertexAttrib4Nusv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib4Nusv", (GLADapiproc) glad_glVertexAttrib4Nusv, 2, index, v); } PFNGLVERTEXATTRIB4NUSVPROC glad_debug_glVertexAttrib4Nusv = glad_debug_impl_glVertexAttrib4Nusv; PFNGLVERTEXATTRIB4BVPROC glad_glVertexAttrib4bv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4bv(GLuint index, const GLbyte * v) { _pre_call_gl_callback("glVertexAttrib4bv", (GLADapiproc) glad_glVertexAttrib4bv, 2, index, v); glad_glVertexAttrib4bv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib4bv", (GLADapiproc) glad_glVertexAttrib4bv, 2, index, v); } PFNGLVERTEXATTRIB4BVPROC glad_debug_glVertexAttrib4bv = glad_debug_impl_glVertexAttrib4bv; PFNGLVERTEXATTRIB4DPROC glad_glVertexAttrib4d = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4d(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w) { _pre_call_gl_callback("glVertexAttrib4d", (GLADapiproc) glad_glVertexAttrib4d, 5, index, x, y, z, w); glad_glVertexAttrib4d(index, x, y, z, w); _post_call_gl_callback(NULL, "glVertexAttrib4d", (GLADapiproc) glad_glVertexAttrib4d, 5, index, x, y, z, w); } PFNGLVERTEXATTRIB4DPROC glad_debug_glVertexAttrib4d = glad_debug_impl_glVertexAttrib4d; PFNGLVERTEXATTRIB4DVPROC glad_glVertexAttrib4dv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4dv(GLuint index, const GLdouble * v) { _pre_call_gl_callback("glVertexAttrib4dv", (GLADapiproc) glad_glVertexAttrib4dv, 2, index, v); glad_glVertexAttrib4dv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib4dv", (GLADapiproc) glad_glVertexAttrib4dv, 2, index, v); } PFNGLVERTEXATTRIB4DVPROC glad_debug_glVertexAttrib4dv = glad_debug_impl_glVertexAttrib4dv; PFNGLVERTEXATTRIB4FPROC glad_glVertexAttrib4f = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4f(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w) { _pre_call_gl_callback("glVertexAttrib4f", (GLADapiproc) glad_glVertexAttrib4f, 5, index, x, y, z, w); glad_glVertexAttrib4f(index, x, y, z, w); _post_call_gl_callback(NULL, "glVertexAttrib4f", (GLADapiproc) glad_glVertexAttrib4f, 5, index, x, y, z, w); } PFNGLVERTEXATTRIB4FPROC glad_debug_glVertexAttrib4f = glad_debug_impl_glVertexAttrib4f; PFNGLVERTEXATTRIB4FVPROC glad_glVertexAttrib4fv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4fv(GLuint index, const GLfloat * v) { _pre_call_gl_callback("glVertexAttrib4fv", (GLADapiproc) glad_glVertexAttrib4fv, 2, index, v); glad_glVertexAttrib4fv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib4fv", (GLADapiproc) glad_glVertexAttrib4fv, 2, index, v); } PFNGLVERTEXATTRIB4FVPROC glad_debug_glVertexAttrib4fv = glad_debug_impl_glVertexAttrib4fv; PFNGLVERTEXATTRIB4IVPROC glad_glVertexAttrib4iv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4iv(GLuint index, const GLint * v) { _pre_call_gl_callback("glVertexAttrib4iv", (GLADapiproc) glad_glVertexAttrib4iv, 2, index, v); glad_glVertexAttrib4iv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib4iv", (GLADapiproc) glad_glVertexAttrib4iv, 2, index, v); } PFNGLVERTEXATTRIB4IVPROC glad_debug_glVertexAttrib4iv = glad_debug_impl_glVertexAttrib4iv; PFNGLVERTEXATTRIB4SPROC glad_glVertexAttrib4s = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4s(GLuint index, GLshort x, GLshort y, GLshort z, GLshort w) { _pre_call_gl_callback("glVertexAttrib4s", (GLADapiproc) glad_glVertexAttrib4s, 5, index, x, y, z, w); glad_glVertexAttrib4s(index, x, y, z, w); _post_call_gl_callback(NULL, "glVertexAttrib4s", (GLADapiproc) glad_glVertexAttrib4s, 5, index, x, y, z, w); } PFNGLVERTEXATTRIB4SPROC glad_debug_glVertexAttrib4s = glad_debug_impl_glVertexAttrib4s; PFNGLVERTEXATTRIB4SVPROC glad_glVertexAttrib4sv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4sv(GLuint index, const GLshort * v) { _pre_call_gl_callback("glVertexAttrib4sv", (GLADapiproc) glad_glVertexAttrib4sv, 2, index, v); glad_glVertexAttrib4sv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib4sv", (GLADapiproc) glad_glVertexAttrib4sv, 2, index, v); } PFNGLVERTEXATTRIB4SVPROC glad_debug_glVertexAttrib4sv = glad_debug_impl_glVertexAttrib4sv; PFNGLVERTEXATTRIB4UBVPROC glad_glVertexAttrib4ubv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4ubv(GLuint index, const GLubyte * v) { _pre_call_gl_callback("glVertexAttrib4ubv", (GLADapiproc) glad_glVertexAttrib4ubv, 2, index, v); glad_glVertexAttrib4ubv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib4ubv", (GLADapiproc) glad_glVertexAttrib4ubv, 2, index, v); } PFNGLVERTEXATTRIB4UBVPROC glad_debug_glVertexAttrib4ubv = glad_debug_impl_glVertexAttrib4ubv; PFNGLVERTEXATTRIB4UIVPROC glad_glVertexAttrib4uiv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4uiv(GLuint index, const GLuint * v) { _pre_call_gl_callback("glVertexAttrib4uiv", (GLADapiproc) glad_glVertexAttrib4uiv, 2, index, v); glad_glVertexAttrib4uiv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib4uiv", (GLADapiproc) glad_glVertexAttrib4uiv, 2, index, v); } PFNGLVERTEXATTRIB4UIVPROC glad_debug_glVertexAttrib4uiv = glad_debug_impl_glVertexAttrib4uiv; PFNGLVERTEXATTRIB4USVPROC glad_glVertexAttrib4usv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttrib4usv(GLuint index, const GLushort * v) { _pre_call_gl_callback("glVertexAttrib4usv", (GLADapiproc) glad_glVertexAttrib4usv, 2, index, v); glad_glVertexAttrib4usv(index, v); _post_call_gl_callback(NULL, "glVertexAttrib4usv", (GLADapiproc) glad_glVertexAttrib4usv, 2, index, v); } PFNGLVERTEXATTRIB4USVPROC glad_debug_glVertexAttrib4usv = glad_debug_impl_glVertexAttrib4usv; PFNGLVERTEXATTRIBDIVISORARBPROC glad_glVertexAttribDivisorARB = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribDivisorARB(GLuint index, GLuint divisor) { _pre_call_gl_callback("glVertexAttribDivisorARB", (GLADapiproc) glad_glVertexAttribDivisorARB, 2, index, divisor); glad_glVertexAttribDivisorARB(index, divisor); _post_call_gl_callback(NULL, "glVertexAttribDivisorARB", (GLADapiproc) glad_glVertexAttribDivisorARB, 2, index, divisor); } PFNGLVERTEXATTRIBDIVISORARBPROC glad_debug_glVertexAttribDivisorARB = glad_debug_impl_glVertexAttribDivisorARB; PFNGLVERTEXATTRIBI1IPROC glad_glVertexAttribI1i = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI1i(GLuint index, GLint x) { _pre_call_gl_callback("glVertexAttribI1i", (GLADapiproc) glad_glVertexAttribI1i, 2, index, x); glad_glVertexAttribI1i(index, x); _post_call_gl_callback(NULL, "glVertexAttribI1i", (GLADapiproc) glad_glVertexAttribI1i, 2, index, x); } PFNGLVERTEXATTRIBI1IPROC glad_debug_glVertexAttribI1i = glad_debug_impl_glVertexAttribI1i; PFNGLVERTEXATTRIBI1IVPROC glad_glVertexAttribI1iv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI1iv(GLuint index, const GLint * v) { _pre_call_gl_callback("glVertexAttribI1iv", (GLADapiproc) glad_glVertexAttribI1iv, 2, index, v); glad_glVertexAttribI1iv(index, v); _post_call_gl_callback(NULL, "glVertexAttribI1iv", (GLADapiproc) glad_glVertexAttribI1iv, 2, index, v); } PFNGLVERTEXATTRIBI1IVPROC glad_debug_glVertexAttribI1iv = glad_debug_impl_glVertexAttribI1iv; PFNGLVERTEXATTRIBI1UIPROC glad_glVertexAttribI1ui = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI1ui(GLuint index, GLuint x) { _pre_call_gl_callback("glVertexAttribI1ui", (GLADapiproc) glad_glVertexAttribI1ui, 2, index, x); glad_glVertexAttribI1ui(index, x); _post_call_gl_callback(NULL, "glVertexAttribI1ui", (GLADapiproc) glad_glVertexAttribI1ui, 2, index, x); } PFNGLVERTEXATTRIBI1UIPROC glad_debug_glVertexAttribI1ui = glad_debug_impl_glVertexAttribI1ui; PFNGLVERTEXATTRIBI1UIVPROC glad_glVertexAttribI1uiv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI1uiv(GLuint index, const GLuint * v) { _pre_call_gl_callback("glVertexAttribI1uiv", (GLADapiproc) glad_glVertexAttribI1uiv, 2, index, v); glad_glVertexAttribI1uiv(index, v); _post_call_gl_callback(NULL, "glVertexAttribI1uiv", (GLADapiproc) glad_glVertexAttribI1uiv, 2, index, v); } PFNGLVERTEXATTRIBI1UIVPROC glad_debug_glVertexAttribI1uiv = glad_debug_impl_glVertexAttribI1uiv; PFNGLVERTEXATTRIBI2IPROC glad_glVertexAttribI2i = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI2i(GLuint index, GLint x, GLint y) { _pre_call_gl_callback("glVertexAttribI2i", (GLADapiproc) glad_glVertexAttribI2i, 3, index, x, y); glad_glVertexAttribI2i(index, x, y); _post_call_gl_callback(NULL, "glVertexAttribI2i", (GLADapiproc) glad_glVertexAttribI2i, 3, index, x, y); } PFNGLVERTEXATTRIBI2IPROC glad_debug_glVertexAttribI2i = glad_debug_impl_glVertexAttribI2i; PFNGLVERTEXATTRIBI2IVPROC glad_glVertexAttribI2iv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI2iv(GLuint index, const GLint * v) { _pre_call_gl_callback("glVertexAttribI2iv", (GLADapiproc) glad_glVertexAttribI2iv, 2, index, v); glad_glVertexAttribI2iv(index, v); _post_call_gl_callback(NULL, "glVertexAttribI2iv", (GLADapiproc) glad_glVertexAttribI2iv, 2, index, v); } PFNGLVERTEXATTRIBI2IVPROC glad_debug_glVertexAttribI2iv = glad_debug_impl_glVertexAttribI2iv; PFNGLVERTEXATTRIBI2UIPROC glad_glVertexAttribI2ui = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI2ui(GLuint index, GLuint x, GLuint y) { _pre_call_gl_callback("glVertexAttribI2ui", (GLADapiproc) glad_glVertexAttribI2ui, 3, index, x, y); glad_glVertexAttribI2ui(index, x, y); _post_call_gl_callback(NULL, "glVertexAttribI2ui", (GLADapiproc) glad_glVertexAttribI2ui, 3, index, x, y); } PFNGLVERTEXATTRIBI2UIPROC glad_debug_glVertexAttribI2ui = glad_debug_impl_glVertexAttribI2ui; PFNGLVERTEXATTRIBI2UIVPROC glad_glVertexAttribI2uiv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI2uiv(GLuint index, const GLuint * v) { _pre_call_gl_callback("glVertexAttribI2uiv", (GLADapiproc) glad_glVertexAttribI2uiv, 2, index, v); glad_glVertexAttribI2uiv(index, v); _post_call_gl_callback(NULL, "glVertexAttribI2uiv", (GLADapiproc) glad_glVertexAttribI2uiv, 2, index, v); } PFNGLVERTEXATTRIBI2UIVPROC glad_debug_glVertexAttribI2uiv = glad_debug_impl_glVertexAttribI2uiv; PFNGLVERTEXATTRIBI3IPROC glad_glVertexAttribI3i = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI3i(GLuint index, GLint x, GLint y, GLint z) { _pre_call_gl_callback("glVertexAttribI3i", (GLADapiproc) glad_glVertexAttribI3i, 4, index, x, y, z); glad_glVertexAttribI3i(index, x, y, z); _post_call_gl_callback(NULL, "glVertexAttribI3i", (GLADapiproc) glad_glVertexAttribI3i, 4, index, x, y, z); } PFNGLVERTEXATTRIBI3IPROC glad_debug_glVertexAttribI3i = glad_debug_impl_glVertexAttribI3i; PFNGLVERTEXATTRIBI3IVPROC glad_glVertexAttribI3iv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI3iv(GLuint index, const GLint * v) { _pre_call_gl_callback("glVertexAttribI3iv", (GLADapiproc) glad_glVertexAttribI3iv, 2, index, v); glad_glVertexAttribI3iv(index, v); _post_call_gl_callback(NULL, "glVertexAttribI3iv", (GLADapiproc) glad_glVertexAttribI3iv, 2, index, v); } PFNGLVERTEXATTRIBI3IVPROC glad_debug_glVertexAttribI3iv = glad_debug_impl_glVertexAttribI3iv; PFNGLVERTEXATTRIBI3UIPROC glad_glVertexAttribI3ui = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI3ui(GLuint index, GLuint x, GLuint y, GLuint z) { _pre_call_gl_callback("glVertexAttribI3ui", (GLADapiproc) glad_glVertexAttribI3ui, 4, index, x, y, z); glad_glVertexAttribI3ui(index, x, y, z); _post_call_gl_callback(NULL, "glVertexAttribI3ui", (GLADapiproc) glad_glVertexAttribI3ui, 4, index, x, y, z); } PFNGLVERTEXATTRIBI3UIPROC glad_debug_glVertexAttribI3ui = glad_debug_impl_glVertexAttribI3ui; PFNGLVERTEXATTRIBI3UIVPROC glad_glVertexAttribI3uiv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI3uiv(GLuint index, const GLuint * v) { _pre_call_gl_callback("glVertexAttribI3uiv", (GLADapiproc) glad_glVertexAttribI3uiv, 2, index, v); glad_glVertexAttribI3uiv(index, v); _post_call_gl_callback(NULL, "glVertexAttribI3uiv", (GLADapiproc) glad_glVertexAttribI3uiv, 2, index, v); } PFNGLVERTEXATTRIBI3UIVPROC glad_debug_glVertexAttribI3uiv = glad_debug_impl_glVertexAttribI3uiv; PFNGLVERTEXATTRIBI4BVPROC glad_glVertexAttribI4bv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI4bv(GLuint index, const GLbyte * v) { _pre_call_gl_callback("glVertexAttribI4bv", (GLADapiproc) glad_glVertexAttribI4bv, 2, index, v); glad_glVertexAttribI4bv(index, v); _post_call_gl_callback(NULL, "glVertexAttribI4bv", (GLADapiproc) glad_glVertexAttribI4bv, 2, index, v); } PFNGLVERTEXATTRIBI4BVPROC glad_debug_glVertexAttribI4bv = glad_debug_impl_glVertexAttribI4bv; PFNGLVERTEXATTRIBI4IPROC glad_glVertexAttribI4i = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI4i(GLuint index, GLint x, GLint y, GLint z, GLint w) { _pre_call_gl_callback("glVertexAttribI4i", (GLADapiproc) glad_glVertexAttribI4i, 5, index, x, y, z, w); glad_glVertexAttribI4i(index, x, y, z, w); _post_call_gl_callback(NULL, "glVertexAttribI4i", (GLADapiproc) glad_glVertexAttribI4i, 5, index, x, y, z, w); } PFNGLVERTEXATTRIBI4IPROC glad_debug_glVertexAttribI4i = glad_debug_impl_glVertexAttribI4i; PFNGLVERTEXATTRIBI4IVPROC glad_glVertexAttribI4iv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI4iv(GLuint index, const GLint * v) { _pre_call_gl_callback("glVertexAttribI4iv", (GLADapiproc) glad_glVertexAttribI4iv, 2, index, v); glad_glVertexAttribI4iv(index, v); _post_call_gl_callback(NULL, "glVertexAttribI4iv", (GLADapiproc) glad_glVertexAttribI4iv, 2, index, v); } PFNGLVERTEXATTRIBI4IVPROC glad_debug_glVertexAttribI4iv = glad_debug_impl_glVertexAttribI4iv; PFNGLVERTEXATTRIBI4SVPROC glad_glVertexAttribI4sv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI4sv(GLuint index, const GLshort * v) { _pre_call_gl_callback("glVertexAttribI4sv", (GLADapiproc) glad_glVertexAttribI4sv, 2, index, v); glad_glVertexAttribI4sv(index, v); _post_call_gl_callback(NULL, "glVertexAttribI4sv", (GLADapiproc) glad_glVertexAttribI4sv, 2, index, v); } PFNGLVERTEXATTRIBI4SVPROC glad_debug_glVertexAttribI4sv = glad_debug_impl_glVertexAttribI4sv; PFNGLVERTEXATTRIBI4UBVPROC glad_glVertexAttribI4ubv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI4ubv(GLuint index, const GLubyte * v) { _pre_call_gl_callback("glVertexAttribI4ubv", (GLADapiproc) glad_glVertexAttribI4ubv, 2, index, v); glad_glVertexAttribI4ubv(index, v); _post_call_gl_callback(NULL, "glVertexAttribI4ubv", (GLADapiproc) glad_glVertexAttribI4ubv, 2, index, v); } PFNGLVERTEXATTRIBI4UBVPROC glad_debug_glVertexAttribI4ubv = glad_debug_impl_glVertexAttribI4ubv; PFNGLVERTEXATTRIBI4UIPROC glad_glVertexAttribI4ui = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI4ui(GLuint index, GLuint x, GLuint y, GLuint z, GLuint w) { _pre_call_gl_callback("glVertexAttribI4ui", (GLADapiproc) glad_glVertexAttribI4ui, 5, index, x, y, z, w); glad_glVertexAttribI4ui(index, x, y, z, w); _post_call_gl_callback(NULL, "glVertexAttribI4ui", (GLADapiproc) glad_glVertexAttribI4ui, 5, index, x, y, z, w); } PFNGLVERTEXATTRIBI4UIPROC glad_debug_glVertexAttribI4ui = glad_debug_impl_glVertexAttribI4ui; PFNGLVERTEXATTRIBI4UIVPROC glad_glVertexAttribI4uiv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI4uiv(GLuint index, const GLuint * v) { _pre_call_gl_callback("glVertexAttribI4uiv", (GLADapiproc) glad_glVertexAttribI4uiv, 2, index, v); glad_glVertexAttribI4uiv(index, v); _post_call_gl_callback(NULL, "glVertexAttribI4uiv", (GLADapiproc) glad_glVertexAttribI4uiv, 2, index, v); } PFNGLVERTEXATTRIBI4UIVPROC glad_debug_glVertexAttribI4uiv = glad_debug_impl_glVertexAttribI4uiv; PFNGLVERTEXATTRIBI4USVPROC glad_glVertexAttribI4usv = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribI4usv(GLuint index, const GLushort * v) { _pre_call_gl_callback("glVertexAttribI4usv", (GLADapiproc) glad_glVertexAttribI4usv, 2, index, v); glad_glVertexAttribI4usv(index, v); _post_call_gl_callback(NULL, "glVertexAttribI4usv", (GLADapiproc) glad_glVertexAttribI4usv, 2, index, v); } PFNGLVERTEXATTRIBI4USVPROC glad_debug_glVertexAttribI4usv = glad_debug_impl_glVertexAttribI4usv; PFNGLVERTEXATTRIBIPOINTERPROC glad_glVertexAttribIPointer = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer) { _pre_call_gl_callback("glVertexAttribIPointer", (GLADapiproc) glad_glVertexAttribIPointer, 5, index, size, type, stride, pointer); glad_glVertexAttribIPointer(index, size, type, stride, pointer); _post_call_gl_callback(NULL, "glVertexAttribIPointer", (GLADapiproc) glad_glVertexAttribIPointer, 5, index, size, type, stride, pointer); } PFNGLVERTEXATTRIBIPOINTERPROC glad_debug_glVertexAttribIPointer = glad_debug_impl_glVertexAttribIPointer; PFNGLVERTEXATTRIBPOINTERPROC glad_glVertexAttribPointer = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer) { _pre_call_gl_callback("glVertexAttribPointer", (GLADapiproc) glad_glVertexAttribPointer, 6, index, size, type, normalized, stride, pointer); glad_glVertexAttribPointer(index, size, type, normalized, stride, pointer); _post_call_gl_callback(NULL, "glVertexAttribPointer", (GLADapiproc) glad_glVertexAttribPointer, 6, index, size, type, normalized, stride, pointer); } PFNGLVERTEXATTRIBPOINTERPROC glad_debug_glVertexAttribPointer = glad_debug_impl_glVertexAttribPointer; PFNGLVERTEXPOINTERPROC glad_glVertexPointer = NULL; static void GLAD_API_PTR glad_debug_impl_glVertexPointer(GLint size, GLenum type, GLsizei stride, const void * pointer) { _pre_call_gl_callback("glVertexPointer", (GLADapiproc) glad_glVertexPointer, 4, size, type, stride, pointer); glad_glVertexPointer(size, type, stride, pointer); _post_call_gl_callback(NULL, "glVertexPointer", (GLADapiproc) glad_glVertexPointer, 4, size, type, stride, pointer); } PFNGLVERTEXPOINTERPROC glad_debug_glVertexPointer = glad_debug_impl_glVertexPointer; PFNGLVIEWPORTPROC glad_glViewport = NULL; static void GLAD_API_PTR glad_debug_impl_glViewport(GLint x, GLint y, GLsizei width, GLsizei height) { _pre_call_gl_callback("glViewport", (GLADapiproc) glad_glViewport, 4, x, y, width, height); glad_glViewport(x, y, width, height); _post_call_gl_callback(NULL, "glViewport", (GLADapiproc) glad_glViewport, 4, x, y, width, height); } PFNGLVIEWPORTPROC glad_debug_glViewport = glad_debug_impl_glViewport; PFNGLWINDOWPOS2DPROC glad_glWindowPos2d = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos2d(GLdouble x, GLdouble y) { _pre_call_gl_callback("glWindowPos2d", (GLADapiproc) glad_glWindowPos2d, 2, x, y); glad_glWindowPos2d(x, y); _post_call_gl_callback(NULL, "glWindowPos2d", (GLADapiproc) glad_glWindowPos2d, 2, x, y); } PFNGLWINDOWPOS2DPROC glad_debug_glWindowPos2d = glad_debug_impl_glWindowPos2d; PFNGLWINDOWPOS2DVPROC glad_glWindowPos2dv = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos2dv(const GLdouble * v) { _pre_call_gl_callback("glWindowPos2dv", (GLADapiproc) glad_glWindowPos2dv, 1, v); glad_glWindowPos2dv(v); _post_call_gl_callback(NULL, "glWindowPos2dv", (GLADapiproc) glad_glWindowPos2dv, 1, v); } PFNGLWINDOWPOS2DVPROC glad_debug_glWindowPos2dv = glad_debug_impl_glWindowPos2dv; PFNGLWINDOWPOS2FPROC glad_glWindowPos2f = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos2f(GLfloat x, GLfloat y) { _pre_call_gl_callback("glWindowPos2f", (GLADapiproc) glad_glWindowPos2f, 2, x, y); glad_glWindowPos2f(x, y); _post_call_gl_callback(NULL, "glWindowPos2f", (GLADapiproc) glad_glWindowPos2f, 2, x, y); } PFNGLWINDOWPOS2FPROC glad_debug_glWindowPos2f = glad_debug_impl_glWindowPos2f; PFNGLWINDOWPOS2FVPROC glad_glWindowPos2fv = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos2fv(const GLfloat * v) { _pre_call_gl_callback("glWindowPos2fv", (GLADapiproc) glad_glWindowPos2fv, 1, v); glad_glWindowPos2fv(v); _post_call_gl_callback(NULL, "glWindowPos2fv", (GLADapiproc) glad_glWindowPos2fv, 1, v); } PFNGLWINDOWPOS2FVPROC glad_debug_glWindowPos2fv = glad_debug_impl_glWindowPos2fv; PFNGLWINDOWPOS2IPROC glad_glWindowPos2i = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos2i(GLint x, GLint y) { _pre_call_gl_callback("glWindowPos2i", (GLADapiproc) glad_glWindowPos2i, 2, x, y); glad_glWindowPos2i(x, y); _post_call_gl_callback(NULL, "glWindowPos2i", (GLADapiproc) glad_glWindowPos2i, 2, x, y); } PFNGLWINDOWPOS2IPROC glad_debug_glWindowPos2i = glad_debug_impl_glWindowPos2i; PFNGLWINDOWPOS2IVPROC glad_glWindowPos2iv = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos2iv(const GLint * v) { _pre_call_gl_callback("glWindowPos2iv", (GLADapiproc) glad_glWindowPos2iv, 1, v); glad_glWindowPos2iv(v); _post_call_gl_callback(NULL, "glWindowPos2iv", (GLADapiproc) glad_glWindowPos2iv, 1, v); } PFNGLWINDOWPOS2IVPROC glad_debug_glWindowPos2iv = glad_debug_impl_glWindowPos2iv; PFNGLWINDOWPOS2SPROC glad_glWindowPos2s = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos2s(GLshort x, GLshort y) { _pre_call_gl_callback("glWindowPos2s", (GLADapiproc) glad_glWindowPos2s, 2, x, y); glad_glWindowPos2s(x, y); _post_call_gl_callback(NULL, "glWindowPos2s", (GLADapiproc) glad_glWindowPos2s, 2, x, y); } PFNGLWINDOWPOS2SPROC glad_debug_glWindowPos2s = glad_debug_impl_glWindowPos2s; PFNGLWINDOWPOS2SVPROC glad_glWindowPos2sv = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos2sv(const GLshort * v) { _pre_call_gl_callback("glWindowPos2sv", (GLADapiproc) glad_glWindowPos2sv, 1, v); glad_glWindowPos2sv(v); _post_call_gl_callback(NULL, "glWindowPos2sv", (GLADapiproc) glad_glWindowPos2sv, 1, v); } PFNGLWINDOWPOS2SVPROC glad_debug_glWindowPos2sv = glad_debug_impl_glWindowPos2sv; PFNGLWINDOWPOS3DPROC glad_glWindowPos3d = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos3d(GLdouble x, GLdouble y, GLdouble z) { _pre_call_gl_callback("glWindowPos3d", (GLADapiproc) glad_glWindowPos3d, 3, x, y, z); glad_glWindowPos3d(x, y, z); _post_call_gl_callback(NULL, "glWindowPos3d", (GLADapiproc) glad_glWindowPos3d, 3, x, y, z); } PFNGLWINDOWPOS3DPROC glad_debug_glWindowPos3d = glad_debug_impl_glWindowPos3d; PFNGLWINDOWPOS3DVPROC glad_glWindowPos3dv = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos3dv(const GLdouble * v) { _pre_call_gl_callback("glWindowPos3dv", (GLADapiproc) glad_glWindowPos3dv, 1, v); glad_glWindowPos3dv(v); _post_call_gl_callback(NULL, "glWindowPos3dv", (GLADapiproc) glad_glWindowPos3dv, 1, v); } PFNGLWINDOWPOS3DVPROC glad_debug_glWindowPos3dv = glad_debug_impl_glWindowPos3dv; PFNGLWINDOWPOS3FPROC glad_glWindowPos3f = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos3f(GLfloat x, GLfloat y, GLfloat z) { _pre_call_gl_callback("glWindowPos3f", (GLADapiproc) glad_glWindowPos3f, 3, x, y, z); glad_glWindowPos3f(x, y, z); _post_call_gl_callback(NULL, "glWindowPos3f", (GLADapiproc) glad_glWindowPos3f, 3, x, y, z); } PFNGLWINDOWPOS3FPROC glad_debug_glWindowPos3f = glad_debug_impl_glWindowPos3f; PFNGLWINDOWPOS3FVPROC glad_glWindowPos3fv = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos3fv(const GLfloat * v) { _pre_call_gl_callback("glWindowPos3fv", (GLADapiproc) glad_glWindowPos3fv, 1, v); glad_glWindowPos3fv(v); _post_call_gl_callback(NULL, "glWindowPos3fv", (GLADapiproc) glad_glWindowPos3fv, 1, v); } PFNGLWINDOWPOS3FVPROC glad_debug_glWindowPos3fv = glad_debug_impl_glWindowPos3fv; PFNGLWINDOWPOS3IPROC glad_glWindowPos3i = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos3i(GLint x, GLint y, GLint z) { _pre_call_gl_callback("glWindowPos3i", (GLADapiproc) glad_glWindowPos3i, 3, x, y, z); glad_glWindowPos3i(x, y, z); _post_call_gl_callback(NULL, "glWindowPos3i", (GLADapiproc) glad_glWindowPos3i, 3, x, y, z); } PFNGLWINDOWPOS3IPROC glad_debug_glWindowPos3i = glad_debug_impl_glWindowPos3i; PFNGLWINDOWPOS3IVPROC glad_glWindowPos3iv = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos3iv(const GLint * v) { _pre_call_gl_callback("glWindowPos3iv", (GLADapiproc) glad_glWindowPos3iv, 1, v); glad_glWindowPos3iv(v); _post_call_gl_callback(NULL, "glWindowPos3iv", (GLADapiproc) glad_glWindowPos3iv, 1, v); } PFNGLWINDOWPOS3IVPROC glad_debug_glWindowPos3iv = glad_debug_impl_glWindowPos3iv; PFNGLWINDOWPOS3SPROC glad_glWindowPos3s = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos3s(GLshort x, GLshort y, GLshort z) { _pre_call_gl_callback("glWindowPos3s", (GLADapiproc) glad_glWindowPos3s, 3, x, y, z); glad_glWindowPos3s(x, y, z); _post_call_gl_callback(NULL, "glWindowPos3s", (GLADapiproc) glad_glWindowPos3s, 3, x, y, z); } PFNGLWINDOWPOS3SPROC glad_debug_glWindowPos3s = glad_debug_impl_glWindowPos3s; PFNGLWINDOWPOS3SVPROC glad_glWindowPos3sv = NULL; static void GLAD_API_PTR glad_debug_impl_glWindowPos3sv(const GLshort * v) { _pre_call_gl_callback("glWindowPos3sv", (GLADapiproc) glad_glWindowPos3sv, 1, v); glad_glWindowPos3sv(v); _post_call_gl_callback(NULL, "glWindowPos3sv", (GLADapiproc) glad_glWindowPos3sv, 1, v); } PFNGLWINDOWPOS3SVPROC glad_debug_glWindowPos3sv = glad_debug_impl_glWindowPos3sv; static void glad_gl_load_GL_VERSION_1_0( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_VERSION_1_0) return; glad_glAccum = (PFNGLACCUMPROC) load(userptr, "glAccum"); glad_glAlphaFunc = (PFNGLALPHAFUNCPROC) load(userptr, "glAlphaFunc"); glad_glBegin = (PFNGLBEGINPROC) load(userptr, "glBegin"); glad_glBitmap = (PFNGLBITMAPPROC) load(userptr, "glBitmap"); glad_glBlendFunc = (PFNGLBLENDFUNCPROC) load(userptr, "glBlendFunc"); glad_glCallList = (PFNGLCALLLISTPROC) load(userptr, "glCallList"); glad_glCallLists = (PFNGLCALLLISTSPROC) load(userptr, "glCallLists"); glad_glClear = (PFNGLCLEARPROC) load(userptr, "glClear"); glad_glClearAccum = (PFNGLCLEARACCUMPROC) load(userptr, "glClearAccum"); glad_glClearColor = (PFNGLCLEARCOLORPROC) load(userptr, "glClearColor"); glad_glClearDepth = (PFNGLCLEARDEPTHPROC) load(userptr, "glClearDepth"); glad_glClearIndex = (PFNGLCLEARINDEXPROC) load(userptr, "glClearIndex"); glad_glClearStencil = (PFNGLCLEARSTENCILPROC) load(userptr, "glClearStencil"); glad_glClipPlane = (PFNGLCLIPPLANEPROC) load(userptr, "glClipPlane"); glad_glColor3b = (PFNGLCOLOR3BPROC) load(userptr, "glColor3b"); glad_glColor3bv = (PFNGLCOLOR3BVPROC) load(userptr, "glColor3bv"); glad_glColor3d = (PFNGLCOLOR3DPROC) load(userptr, "glColor3d"); glad_glColor3dv = (PFNGLCOLOR3DVPROC) load(userptr, "glColor3dv"); glad_glColor3f = (PFNGLCOLOR3FPROC) load(userptr, "glColor3f"); glad_glColor3fv = (PFNGLCOLOR3FVPROC) load(userptr, "glColor3fv"); glad_glColor3i = (PFNGLCOLOR3IPROC) load(userptr, "glColor3i"); glad_glColor3iv = (PFNGLCOLOR3IVPROC) load(userptr, "glColor3iv"); glad_glColor3s = (PFNGLCOLOR3SPROC) load(userptr, "glColor3s"); glad_glColor3sv = (PFNGLCOLOR3SVPROC) load(userptr, "glColor3sv"); glad_glColor3ub = (PFNGLCOLOR3UBPROC) load(userptr, "glColor3ub"); glad_glColor3ubv = (PFNGLCOLOR3UBVPROC) load(userptr, "glColor3ubv"); glad_glColor3ui = (PFNGLCOLOR3UIPROC) load(userptr, "glColor3ui"); glad_glColor3uiv = (PFNGLCOLOR3UIVPROC) load(userptr, "glColor3uiv"); glad_glColor3us = (PFNGLCOLOR3USPROC) load(userptr, "glColor3us"); glad_glColor3usv = (PFNGLCOLOR3USVPROC) load(userptr, "glColor3usv"); glad_glColor4b = (PFNGLCOLOR4BPROC) load(userptr, "glColor4b"); glad_glColor4bv = (PFNGLCOLOR4BVPROC) load(userptr, "glColor4bv"); glad_glColor4d = (PFNGLCOLOR4DPROC) load(userptr, "glColor4d"); glad_glColor4dv = (PFNGLCOLOR4DVPROC) load(userptr, "glColor4dv"); glad_glColor4f = (PFNGLCOLOR4FPROC) load(userptr, "glColor4f"); glad_glColor4fv = (PFNGLCOLOR4FVPROC) load(userptr, "glColor4fv"); glad_glColor4i = (PFNGLCOLOR4IPROC) load(userptr, "glColor4i"); glad_glColor4iv = (PFNGLCOLOR4IVPROC) load(userptr, "glColor4iv"); glad_glColor4s = (PFNGLCOLOR4SPROC) load(userptr, "glColor4s"); glad_glColor4sv = (PFNGLCOLOR4SVPROC) load(userptr, "glColor4sv"); glad_glColor4ub = (PFNGLCOLOR4UBPROC) load(userptr, "glColor4ub"); glad_glColor4ubv = (PFNGLCOLOR4UBVPROC) load(userptr, "glColor4ubv"); glad_glColor4ui = (PFNGLCOLOR4UIPROC) load(userptr, "glColor4ui"); glad_glColor4uiv = (PFNGLCOLOR4UIVPROC) load(userptr, "glColor4uiv"); glad_glColor4us = (PFNGLCOLOR4USPROC) load(userptr, "glColor4us"); glad_glColor4usv = (PFNGLCOLOR4USVPROC) load(userptr, "glColor4usv"); glad_glColorMask = (PFNGLCOLORMASKPROC) load(userptr, "glColorMask"); glad_glColorMaterial = (PFNGLCOLORMATERIALPROC) load(userptr, "glColorMaterial"); glad_glCopyPixels = (PFNGLCOPYPIXELSPROC) load(userptr, "glCopyPixels"); glad_glCullFace = (PFNGLCULLFACEPROC) load(userptr, "glCullFace"); glad_glDeleteLists = (PFNGLDELETELISTSPROC) load(userptr, "glDeleteLists"); glad_glDepthFunc = (PFNGLDEPTHFUNCPROC) load(userptr, "glDepthFunc"); glad_glDepthMask = (PFNGLDEPTHMASKPROC) load(userptr, "glDepthMask"); glad_glDepthRange = (PFNGLDEPTHRANGEPROC) load(userptr, "glDepthRange"); glad_glDisable = (PFNGLDISABLEPROC) load(userptr, "glDisable"); glad_glDrawBuffer = (PFNGLDRAWBUFFERPROC) load(userptr, "glDrawBuffer"); glad_glDrawPixels = (PFNGLDRAWPIXELSPROC) load(userptr, "glDrawPixels"); glad_glEdgeFlag = (PFNGLEDGEFLAGPROC) load(userptr, "glEdgeFlag"); glad_glEdgeFlagv = (PFNGLEDGEFLAGVPROC) load(userptr, "glEdgeFlagv"); glad_glEnable = (PFNGLENABLEPROC) load(userptr, "glEnable"); glad_glEnd = (PFNGLENDPROC) load(userptr, "glEnd"); glad_glEndList = (PFNGLENDLISTPROC) load(userptr, "glEndList"); glad_glEvalCoord1d = (PFNGLEVALCOORD1DPROC) load(userptr, "glEvalCoord1d"); glad_glEvalCoord1dv = (PFNGLEVALCOORD1DVPROC) load(userptr, "glEvalCoord1dv"); glad_glEvalCoord1f = (PFNGLEVALCOORD1FPROC) load(userptr, "glEvalCoord1f"); glad_glEvalCoord1fv = (PFNGLEVALCOORD1FVPROC) load(userptr, "glEvalCoord1fv"); glad_glEvalCoord2d = (PFNGLEVALCOORD2DPROC) load(userptr, "glEvalCoord2d"); glad_glEvalCoord2dv = (PFNGLEVALCOORD2DVPROC) load(userptr, "glEvalCoord2dv"); glad_glEvalCoord2f = (PFNGLEVALCOORD2FPROC) load(userptr, "glEvalCoord2f"); glad_glEvalCoord2fv = (PFNGLEVALCOORD2FVPROC) load(userptr, "glEvalCoord2fv"); glad_glEvalMesh1 = (PFNGLEVALMESH1PROC) load(userptr, "glEvalMesh1"); glad_glEvalMesh2 = (PFNGLEVALMESH2PROC) load(userptr, "glEvalMesh2"); glad_glEvalPoint1 = (PFNGLEVALPOINT1PROC) load(userptr, "glEvalPoint1"); glad_glEvalPoint2 = (PFNGLEVALPOINT2PROC) load(userptr, "glEvalPoint2"); glad_glFeedbackBuffer = (PFNGLFEEDBACKBUFFERPROC) load(userptr, "glFeedbackBuffer"); glad_glFinish = (PFNGLFINISHPROC) load(userptr, "glFinish"); glad_glFlush = (PFNGLFLUSHPROC) load(userptr, "glFlush"); glad_glFogf = (PFNGLFOGFPROC) load(userptr, "glFogf"); glad_glFogfv = (PFNGLFOGFVPROC) load(userptr, "glFogfv"); glad_glFogi = (PFNGLFOGIPROC) load(userptr, "glFogi"); glad_glFogiv = (PFNGLFOGIVPROC) load(userptr, "glFogiv"); glad_glFrontFace = (PFNGLFRONTFACEPROC) load(userptr, "glFrontFace"); glad_glFrustum = (PFNGLFRUSTUMPROC) load(userptr, "glFrustum"); glad_glGenLists = (PFNGLGENLISTSPROC) load(userptr, "glGenLists"); glad_glGetBooleanv = (PFNGLGETBOOLEANVPROC) load(userptr, "glGetBooleanv"); glad_glGetClipPlane = (PFNGLGETCLIPPLANEPROC) load(userptr, "glGetClipPlane"); glad_glGetDoublev = (PFNGLGETDOUBLEVPROC) load(userptr, "glGetDoublev"); glad_glGetError = (PFNGLGETERRORPROC) load(userptr, "glGetError"); glad_glGetFloatv = (PFNGLGETFLOATVPROC) load(userptr, "glGetFloatv"); glad_glGetIntegerv = (PFNGLGETINTEGERVPROC) load(userptr, "glGetIntegerv"); glad_glGetLightfv = (PFNGLGETLIGHTFVPROC) load(userptr, "glGetLightfv"); glad_glGetLightiv = (PFNGLGETLIGHTIVPROC) load(userptr, "glGetLightiv"); glad_glGetMapdv = (PFNGLGETMAPDVPROC) load(userptr, "glGetMapdv"); glad_glGetMapfv = (PFNGLGETMAPFVPROC) load(userptr, "glGetMapfv"); glad_glGetMapiv = (PFNGLGETMAPIVPROC) load(userptr, "glGetMapiv"); glad_glGetMaterialfv = (PFNGLGETMATERIALFVPROC) load(userptr, "glGetMaterialfv"); glad_glGetMaterialiv = (PFNGLGETMATERIALIVPROC) load(userptr, "glGetMaterialiv"); glad_glGetPixelMapfv = (PFNGLGETPIXELMAPFVPROC) load(userptr, "glGetPixelMapfv"); glad_glGetPixelMapuiv = (PFNGLGETPIXELMAPUIVPROC) load(userptr, "glGetPixelMapuiv"); glad_glGetPixelMapusv = (PFNGLGETPIXELMAPUSVPROC) load(userptr, "glGetPixelMapusv"); glad_glGetPolygonStipple = (PFNGLGETPOLYGONSTIPPLEPROC) load(userptr, "glGetPolygonStipple"); glad_glGetString = (PFNGLGETSTRINGPROC) load(userptr, "glGetString"); glad_glGetTexEnvfv = (PFNGLGETTEXENVFVPROC) load(userptr, "glGetTexEnvfv"); glad_glGetTexEnviv = (PFNGLGETTEXENVIVPROC) load(userptr, "glGetTexEnviv"); glad_glGetTexGendv = (PFNGLGETTEXGENDVPROC) load(userptr, "glGetTexGendv"); glad_glGetTexGenfv = (PFNGLGETTEXGENFVPROC) load(userptr, "glGetTexGenfv"); glad_glGetTexGeniv = (PFNGLGETTEXGENIVPROC) load(userptr, "glGetTexGeniv"); glad_glGetTexImage = (PFNGLGETTEXIMAGEPROC) load(userptr, "glGetTexImage"); glad_glGetTexLevelParameterfv = (PFNGLGETTEXLEVELPARAMETERFVPROC) load(userptr, "glGetTexLevelParameterfv"); glad_glGetTexLevelParameteriv = (PFNGLGETTEXLEVELPARAMETERIVPROC) load(userptr, "glGetTexLevelParameteriv"); glad_glGetTexParameterfv = (PFNGLGETTEXPARAMETERFVPROC) load(userptr, "glGetTexParameterfv"); glad_glGetTexParameteriv = (PFNGLGETTEXPARAMETERIVPROC) load(userptr, "glGetTexParameteriv"); glad_glHint = (PFNGLHINTPROC) load(userptr, "glHint"); glad_glIndexMask = (PFNGLINDEXMASKPROC) load(userptr, "glIndexMask"); glad_glIndexd = (PFNGLINDEXDPROC) load(userptr, "glIndexd"); glad_glIndexdv = (PFNGLINDEXDVPROC) load(userptr, "glIndexdv"); glad_glIndexf = (PFNGLINDEXFPROC) load(userptr, "glIndexf"); glad_glIndexfv = (PFNGLINDEXFVPROC) load(userptr, "glIndexfv"); glad_glIndexi = (PFNGLINDEXIPROC) load(userptr, "glIndexi"); glad_glIndexiv = (PFNGLINDEXIVPROC) load(userptr, "glIndexiv"); glad_glIndexs = (PFNGLINDEXSPROC) load(userptr, "glIndexs"); glad_glIndexsv = (PFNGLINDEXSVPROC) load(userptr, "glIndexsv"); glad_glInitNames = (PFNGLINITNAMESPROC) load(userptr, "glInitNames"); glad_glIsEnabled = (PFNGLISENABLEDPROC) load(userptr, "glIsEnabled"); glad_glIsList = (PFNGLISLISTPROC) load(userptr, "glIsList"); glad_glLightModelf = (PFNGLLIGHTMODELFPROC) load(userptr, "glLightModelf"); glad_glLightModelfv = (PFNGLLIGHTMODELFVPROC) load(userptr, "glLightModelfv"); glad_glLightModeli = (PFNGLLIGHTMODELIPROC) load(userptr, "glLightModeli"); glad_glLightModeliv = (PFNGLLIGHTMODELIVPROC) load(userptr, "glLightModeliv"); glad_glLightf = (PFNGLLIGHTFPROC) load(userptr, "glLightf"); glad_glLightfv = (PFNGLLIGHTFVPROC) load(userptr, "glLightfv"); glad_glLighti = (PFNGLLIGHTIPROC) load(userptr, "glLighti"); glad_glLightiv = (PFNGLLIGHTIVPROC) load(userptr, "glLightiv"); glad_glLineStipple = (PFNGLLINESTIPPLEPROC) load(userptr, "glLineStipple"); glad_glLineWidth = (PFNGLLINEWIDTHPROC) load(userptr, "glLineWidth"); glad_glListBase = (PFNGLLISTBASEPROC) load(userptr, "glListBase"); glad_glLoadIdentity = (PFNGLLOADIDENTITYPROC) load(userptr, "glLoadIdentity"); glad_glLoadMatrixd = (PFNGLLOADMATRIXDPROC) load(userptr, "glLoadMatrixd"); glad_glLoadMatrixf = (PFNGLLOADMATRIXFPROC) load(userptr, "glLoadMatrixf"); glad_glLoadName = (PFNGLLOADNAMEPROC) load(userptr, "glLoadName"); glad_glLogicOp = (PFNGLLOGICOPPROC) load(userptr, "glLogicOp"); glad_glMap1d = (PFNGLMAP1DPROC) load(userptr, "glMap1d"); glad_glMap1f = (PFNGLMAP1FPROC) load(userptr, "glMap1f"); glad_glMap2d = (PFNGLMAP2DPROC) load(userptr, "glMap2d"); glad_glMap2f = (PFNGLMAP2FPROC) load(userptr, "glMap2f"); glad_glMapGrid1d = (PFNGLMAPGRID1DPROC) load(userptr, "glMapGrid1d"); glad_glMapGrid1f = (PFNGLMAPGRID1FPROC) load(userptr, "glMapGrid1f"); glad_glMapGrid2d = (PFNGLMAPGRID2DPROC) load(userptr, "glMapGrid2d"); glad_glMapGrid2f = (PFNGLMAPGRID2FPROC) load(userptr, "glMapGrid2f"); glad_glMaterialf = (PFNGLMATERIALFPROC) load(userptr, "glMaterialf"); glad_glMaterialfv = (PFNGLMATERIALFVPROC) load(userptr, "glMaterialfv"); glad_glMateriali = (PFNGLMATERIALIPROC) load(userptr, "glMateriali"); glad_glMaterialiv = (PFNGLMATERIALIVPROC) load(userptr, "glMaterialiv"); glad_glMatrixMode = (PFNGLMATRIXMODEPROC) load(userptr, "glMatrixMode"); glad_glMultMatrixd = (PFNGLMULTMATRIXDPROC) load(userptr, "glMultMatrixd"); glad_glMultMatrixf = (PFNGLMULTMATRIXFPROC) load(userptr, "glMultMatrixf"); glad_glNewList = (PFNGLNEWLISTPROC) load(userptr, "glNewList"); glad_glNormal3b = (PFNGLNORMAL3BPROC) load(userptr, "glNormal3b"); glad_glNormal3bv = (PFNGLNORMAL3BVPROC) load(userptr, "glNormal3bv"); glad_glNormal3d = (PFNGLNORMAL3DPROC) load(userptr, "glNormal3d"); glad_glNormal3dv = (PFNGLNORMAL3DVPROC) load(userptr, "glNormal3dv"); glad_glNormal3f = (PFNGLNORMAL3FPROC) load(userptr, "glNormal3f"); glad_glNormal3fv = (PFNGLNORMAL3FVPROC) load(userptr, "glNormal3fv"); glad_glNormal3i = (PFNGLNORMAL3IPROC) load(userptr, "glNormal3i"); glad_glNormal3iv = (PFNGLNORMAL3IVPROC) load(userptr, "glNormal3iv"); glad_glNormal3s = (PFNGLNORMAL3SPROC) load(userptr, "glNormal3s"); glad_glNormal3sv = (PFNGLNORMAL3SVPROC) load(userptr, "glNormal3sv"); glad_glOrtho = (PFNGLORTHOPROC) load(userptr, "glOrtho"); glad_glPassThrough = (PFNGLPASSTHROUGHPROC) load(userptr, "glPassThrough"); glad_glPixelMapfv = (PFNGLPIXELMAPFVPROC) load(userptr, "glPixelMapfv"); glad_glPixelMapuiv = (PFNGLPIXELMAPUIVPROC) load(userptr, "glPixelMapuiv"); glad_glPixelMapusv = (PFNGLPIXELMAPUSVPROC) load(userptr, "glPixelMapusv"); glad_glPixelStoref = (PFNGLPIXELSTOREFPROC) load(userptr, "glPixelStoref"); glad_glPixelStorei = (PFNGLPIXELSTOREIPROC) load(userptr, "glPixelStorei"); glad_glPixelTransferf = (PFNGLPIXELTRANSFERFPROC) load(userptr, "glPixelTransferf"); glad_glPixelTransferi = (PFNGLPIXELTRANSFERIPROC) load(userptr, "glPixelTransferi"); glad_glPixelZoom = (PFNGLPIXELZOOMPROC) load(userptr, "glPixelZoom"); glad_glPointSize = (PFNGLPOINTSIZEPROC) load(userptr, "glPointSize"); glad_glPolygonMode = (PFNGLPOLYGONMODEPROC) load(userptr, "glPolygonMode"); glad_glPolygonStipple = (PFNGLPOLYGONSTIPPLEPROC) load(userptr, "glPolygonStipple"); glad_glPopAttrib = (PFNGLPOPATTRIBPROC) load(userptr, "glPopAttrib"); glad_glPopMatrix = (PFNGLPOPMATRIXPROC) load(userptr, "glPopMatrix"); glad_glPopName = (PFNGLPOPNAMEPROC) load(userptr, "glPopName"); glad_glPushAttrib = (PFNGLPUSHATTRIBPROC) load(userptr, "glPushAttrib"); glad_glPushMatrix = (PFNGLPUSHMATRIXPROC) load(userptr, "glPushMatrix"); glad_glPushName = (PFNGLPUSHNAMEPROC) load(userptr, "glPushName"); glad_glRasterPos2d = (PFNGLRASTERPOS2DPROC) load(userptr, "glRasterPos2d"); glad_glRasterPos2dv = (PFNGLRASTERPOS2DVPROC) load(userptr, "glRasterPos2dv"); glad_glRasterPos2f = (PFNGLRASTERPOS2FPROC) load(userptr, "glRasterPos2f"); glad_glRasterPos2fv = (PFNGLRASTERPOS2FVPROC) load(userptr, "glRasterPos2fv"); glad_glRasterPos2i = (PFNGLRASTERPOS2IPROC) load(userptr, "glRasterPos2i"); glad_glRasterPos2iv = (PFNGLRASTERPOS2IVPROC) load(userptr, "glRasterPos2iv"); glad_glRasterPos2s = (PFNGLRASTERPOS2SPROC) load(userptr, "glRasterPos2s"); glad_glRasterPos2sv = (PFNGLRASTERPOS2SVPROC) load(userptr, "glRasterPos2sv"); glad_glRasterPos3d = (PFNGLRASTERPOS3DPROC) load(userptr, "glRasterPos3d"); glad_glRasterPos3dv = (PFNGLRASTERPOS3DVPROC) load(userptr, "glRasterPos3dv"); glad_glRasterPos3f = (PFNGLRASTERPOS3FPROC) load(userptr, "glRasterPos3f"); glad_glRasterPos3fv = (PFNGLRASTERPOS3FVPROC) load(userptr, "glRasterPos3fv"); glad_glRasterPos3i = (PFNGLRASTERPOS3IPROC) load(userptr, "glRasterPos3i"); glad_glRasterPos3iv = (PFNGLRASTERPOS3IVPROC) load(userptr, "glRasterPos3iv"); glad_glRasterPos3s = (PFNGLRASTERPOS3SPROC) load(userptr, "glRasterPos3s"); glad_glRasterPos3sv = (PFNGLRASTERPOS3SVPROC) load(userptr, "glRasterPos3sv"); glad_glRasterPos4d = (PFNGLRASTERPOS4DPROC) load(userptr, "glRasterPos4d"); glad_glRasterPos4dv = (PFNGLRASTERPOS4DVPROC) load(userptr, "glRasterPos4dv"); glad_glRasterPos4f = (PFNGLRASTERPOS4FPROC) load(userptr, "glRasterPos4f"); glad_glRasterPos4fv = (PFNGLRASTERPOS4FVPROC) load(userptr, "glRasterPos4fv"); glad_glRasterPos4i = (PFNGLRASTERPOS4IPROC) load(userptr, "glRasterPos4i"); glad_glRasterPos4iv = (PFNGLRASTERPOS4IVPROC) load(userptr, "glRasterPos4iv"); glad_glRasterPos4s = (PFNGLRASTERPOS4SPROC) load(userptr, "glRasterPos4s"); glad_glRasterPos4sv = (PFNGLRASTERPOS4SVPROC) load(userptr, "glRasterPos4sv"); glad_glReadBuffer = (PFNGLREADBUFFERPROC) load(userptr, "glReadBuffer"); glad_glReadPixels = (PFNGLREADPIXELSPROC) load(userptr, "glReadPixels"); glad_glRectd = (PFNGLRECTDPROC) load(userptr, "glRectd"); glad_glRectdv = (PFNGLRECTDVPROC) load(userptr, "glRectdv"); glad_glRectf = (PFNGLRECTFPROC) load(userptr, "glRectf"); glad_glRectfv = (PFNGLRECTFVPROC) load(userptr, "glRectfv"); glad_glRecti = (PFNGLRECTIPROC) load(userptr, "glRecti"); glad_glRectiv = (PFNGLRECTIVPROC) load(userptr, "glRectiv"); glad_glRects = (PFNGLRECTSPROC) load(userptr, "glRects"); glad_glRectsv = (PFNGLRECTSVPROC) load(userptr, "glRectsv"); glad_glRenderMode = (PFNGLRENDERMODEPROC) load(userptr, "glRenderMode"); glad_glRotated = (PFNGLROTATEDPROC) load(userptr, "glRotated"); glad_glRotatef = (PFNGLROTATEFPROC) load(userptr, "glRotatef"); glad_glScaled = (PFNGLSCALEDPROC) load(userptr, "glScaled"); glad_glScalef = (PFNGLSCALEFPROC) load(userptr, "glScalef"); glad_glScissor = (PFNGLSCISSORPROC) load(userptr, "glScissor"); glad_glSelectBuffer = (PFNGLSELECTBUFFERPROC) load(userptr, "glSelectBuffer"); glad_glShadeModel = (PFNGLSHADEMODELPROC) load(userptr, "glShadeModel"); glad_glStencilFunc = (PFNGLSTENCILFUNCPROC) load(userptr, "glStencilFunc"); glad_glStencilMask = (PFNGLSTENCILMASKPROC) load(userptr, "glStencilMask"); glad_glStencilOp = (PFNGLSTENCILOPPROC) load(userptr, "glStencilOp"); glad_glTexCoord1d = (PFNGLTEXCOORD1DPROC) load(userptr, "glTexCoord1d"); glad_glTexCoord1dv = (PFNGLTEXCOORD1DVPROC) load(userptr, "glTexCoord1dv"); glad_glTexCoord1f = (PFNGLTEXCOORD1FPROC) load(userptr, "glTexCoord1f"); glad_glTexCoord1fv = (PFNGLTEXCOORD1FVPROC) load(userptr, "glTexCoord1fv"); glad_glTexCoord1i = (PFNGLTEXCOORD1IPROC) load(userptr, "glTexCoord1i"); glad_glTexCoord1iv = (PFNGLTEXCOORD1IVPROC) load(userptr, "glTexCoord1iv"); glad_glTexCoord1s = (PFNGLTEXCOORD1SPROC) load(userptr, "glTexCoord1s"); glad_glTexCoord1sv = (PFNGLTEXCOORD1SVPROC) load(userptr, "glTexCoord1sv"); glad_glTexCoord2d = (PFNGLTEXCOORD2DPROC) load(userptr, "glTexCoord2d"); glad_glTexCoord2dv = (PFNGLTEXCOORD2DVPROC) load(userptr, "glTexCoord2dv"); glad_glTexCoord2f = (PFNGLTEXCOORD2FPROC) load(userptr, "glTexCoord2f"); glad_glTexCoord2fv = (PFNGLTEXCOORD2FVPROC) load(userptr, "glTexCoord2fv"); glad_glTexCoord2i = (PFNGLTEXCOORD2IPROC) load(userptr, "glTexCoord2i"); glad_glTexCoord2iv = (PFNGLTEXCOORD2IVPROC) load(userptr, "glTexCoord2iv"); glad_glTexCoord2s = (PFNGLTEXCOORD2SPROC) load(userptr, "glTexCoord2s"); glad_glTexCoord2sv = (PFNGLTEXCOORD2SVPROC) load(userptr, "glTexCoord2sv"); glad_glTexCoord3d = (PFNGLTEXCOORD3DPROC) load(userptr, "glTexCoord3d"); glad_glTexCoord3dv = (PFNGLTEXCOORD3DVPROC) load(userptr, "glTexCoord3dv"); glad_glTexCoord3f = (PFNGLTEXCOORD3FPROC) load(userptr, "glTexCoord3f"); glad_glTexCoord3fv = (PFNGLTEXCOORD3FVPROC) load(userptr, "glTexCoord3fv"); glad_glTexCoord3i = (PFNGLTEXCOORD3IPROC) load(userptr, "glTexCoord3i"); glad_glTexCoord3iv = (PFNGLTEXCOORD3IVPROC) load(userptr, "glTexCoord3iv"); glad_glTexCoord3s = (PFNGLTEXCOORD3SPROC) load(userptr, "glTexCoord3s"); glad_glTexCoord3sv = (PFNGLTEXCOORD3SVPROC) load(userptr, "glTexCoord3sv"); glad_glTexCoord4d = (PFNGLTEXCOORD4DPROC) load(userptr, "glTexCoord4d"); glad_glTexCoord4dv = (PFNGLTEXCOORD4DVPROC) load(userptr, "glTexCoord4dv"); glad_glTexCoord4f = (PFNGLTEXCOORD4FPROC) load(userptr, "glTexCoord4f"); glad_glTexCoord4fv = (PFNGLTEXCOORD4FVPROC) load(userptr, "glTexCoord4fv"); glad_glTexCoord4i = (PFNGLTEXCOORD4IPROC) load(userptr, "glTexCoord4i"); glad_glTexCoord4iv = (PFNGLTEXCOORD4IVPROC) load(userptr, "glTexCoord4iv"); glad_glTexCoord4s = (PFNGLTEXCOORD4SPROC) load(userptr, "glTexCoord4s"); glad_glTexCoord4sv = (PFNGLTEXCOORD4SVPROC) load(userptr, "glTexCoord4sv"); glad_glTexEnvf = (PFNGLTEXENVFPROC) load(userptr, "glTexEnvf"); glad_glTexEnvfv = (PFNGLTEXENVFVPROC) load(userptr, "glTexEnvfv"); glad_glTexEnvi = (PFNGLTEXENVIPROC) load(userptr, "glTexEnvi"); glad_glTexEnviv = (PFNGLTEXENVIVPROC) load(userptr, "glTexEnviv"); glad_glTexGend = (PFNGLTEXGENDPROC) load(userptr, "glTexGend"); glad_glTexGendv = (PFNGLTEXGENDVPROC) load(userptr, "glTexGendv"); glad_glTexGenf = (PFNGLTEXGENFPROC) load(userptr, "glTexGenf"); glad_glTexGenfv = (PFNGLTEXGENFVPROC) load(userptr, "glTexGenfv"); glad_glTexGeni = (PFNGLTEXGENIPROC) load(userptr, "glTexGeni"); glad_glTexGeniv = (PFNGLTEXGENIVPROC) load(userptr, "glTexGeniv"); glad_glTexImage1D = (PFNGLTEXIMAGE1DPROC) load(userptr, "glTexImage1D"); glad_glTexImage2D = (PFNGLTEXIMAGE2DPROC) load(userptr, "glTexImage2D"); glad_glTexParameterf = (PFNGLTEXPARAMETERFPROC) load(userptr, "glTexParameterf"); glad_glTexParameterfv = (PFNGLTEXPARAMETERFVPROC) load(userptr, "glTexParameterfv"); glad_glTexParameteri = (PFNGLTEXPARAMETERIPROC) load(userptr, "glTexParameteri"); glad_glTexParameteriv = (PFNGLTEXPARAMETERIVPROC) load(userptr, "glTexParameteriv"); glad_glTranslated = (PFNGLTRANSLATEDPROC) load(userptr, "glTranslated"); glad_glTranslatef = (PFNGLTRANSLATEFPROC) load(userptr, "glTranslatef"); glad_glVertex2d = (PFNGLVERTEX2DPROC) load(userptr, "glVertex2d"); glad_glVertex2dv = (PFNGLVERTEX2DVPROC) load(userptr, "glVertex2dv"); glad_glVertex2f = (PFNGLVERTEX2FPROC) load(userptr, "glVertex2f"); glad_glVertex2fv = (PFNGLVERTEX2FVPROC) load(userptr, "glVertex2fv"); glad_glVertex2i = (PFNGLVERTEX2IPROC) load(userptr, "glVertex2i"); glad_glVertex2iv = (PFNGLVERTEX2IVPROC) load(userptr, "glVertex2iv"); glad_glVertex2s = (PFNGLVERTEX2SPROC) load(userptr, "glVertex2s"); glad_glVertex2sv = (PFNGLVERTEX2SVPROC) load(userptr, "glVertex2sv"); glad_glVertex3d = (PFNGLVERTEX3DPROC) load(userptr, "glVertex3d"); glad_glVertex3dv = (PFNGLVERTEX3DVPROC) load(userptr, "glVertex3dv"); glad_glVertex3f = (PFNGLVERTEX3FPROC) load(userptr, "glVertex3f"); glad_glVertex3fv = (PFNGLVERTEX3FVPROC) load(userptr, "glVertex3fv"); glad_glVertex3i = (PFNGLVERTEX3IPROC) load(userptr, "glVertex3i"); glad_glVertex3iv = (PFNGLVERTEX3IVPROC) load(userptr, "glVertex3iv"); glad_glVertex3s = (PFNGLVERTEX3SPROC) load(userptr, "glVertex3s"); glad_glVertex3sv = (PFNGLVERTEX3SVPROC) load(userptr, "glVertex3sv"); glad_glVertex4d = (PFNGLVERTEX4DPROC) load(userptr, "glVertex4d"); glad_glVertex4dv = (PFNGLVERTEX4DVPROC) load(userptr, "glVertex4dv"); glad_glVertex4f = (PFNGLVERTEX4FPROC) load(userptr, "glVertex4f"); glad_glVertex4fv = (PFNGLVERTEX4FVPROC) load(userptr, "glVertex4fv"); glad_glVertex4i = (PFNGLVERTEX4IPROC) load(userptr, "glVertex4i"); glad_glVertex4iv = (PFNGLVERTEX4IVPROC) load(userptr, "glVertex4iv"); glad_glVertex4s = (PFNGLVERTEX4SPROC) load(userptr, "glVertex4s"); glad_glVertex4sv = (PFNGLVERTEX4SVPROC) load(userptr, "glVertex4sv"); glad_glViewport = (PFNGLVIEWPORTPROC) load(userptr, "glViewport"); } static void glad_gl_load_GL_VERSION_1_1( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_VERSION_1_1) return; glad_glAreTexturesResident = (PFNGLARETEXTURESRESIDENTPROC) load(userptr, "glAreTexturesResident"); glad_glArrayElement = (PFNGLARRAYELEMENTPROC) load(userptr, "glArrayElement"); glad_glBindTexture = (PFNGLBINDTEXTUREPROC) load(userptr, "glBindTexture"); glad_glColorPointer = (PFNGLCOLORPOINTERPROC) load(userptr, "glColorPointer"); glad_glCopyTexImage1D = (PFNGLCOPYTEXIMAGE1DPROC) load(userptr, "glCopyTexImage1D"); glad_glCopyTexImage2D = (PFNGLCOPYTEXIMAGE2DPROC) load(userptr, "glCopyTexImage2D"); glad_glCopyTexSubImage1D = (PFNGLCOPYTEXSUBIMAGE1DPROC) load(userptr, "glCopyTexSubImage1D"); glad_glCopyTexSubImage2D = (PFNGLCOPYTEXSUBIMAGE2DPROC) load(userptr, "glCopyTexSubImage2D"); glad_glDeleteTextures = (PFNGLDELETETEXTURESPROC) load(userptr, "glDeleteTextures"); glad_glDisableClientState = (PFNGLDISABLECLIENTSTATEPROC) load(userptr, "glDisableClientState"); glad_glDrawArrays = (PFNGLDRAWARRAYSPROC) load(userptr, "glDrawArrays"); glad_glDrawElements = (PFNGLDRAWELEMENTSPROC) load(userptr, "glDrawElements"); glad_glEdgeFlagPointer = (PFNGLEDGEFLAGPOINTERPROC) load(userptr, "glEdgeFlagPointer"); glad_glEnableClientState = (PFNGLENABLECLIENTSTATEPROC) load(userptr, "glEnableClientState"); glad_glGenTextures = (PFNGLGENTEXTURESPROC) load(userptr, "glGenTextures"); glad_glGetPointerv = (PFNGLGETPOINTERVPROC) load(userptr, "glGetPointerv"); glad_glIndexPointer = (PFNGLINDEXPOINTERPROC) load(userptr, "glIndexPointer"); glad_glIndexub = (PFNGLINDEXUBPROC) load(userptr, "glIndexub"); glad_glIndexubv = (PFNGLINDEXUBVPROC) load(userptr, "glIndexubv"); glad_glInterleavedArrays = (PFNGLINTERLEAVEDARRAYSPROC) load(userptr, "glInterleavedArrays"); glad_glIsTexture = (PFNGLISTEXTUREPROC) load(userptr, "glIsTexture"); glad_glNormalPointer = (PFNGLNORMALPOINTERPROC) load(userptr, "glNormalPointer"); glad_glPolygonOffset = (PFNGLPOLYGONOFFSETPROC) load(userptr, "glPolygonOffset"); glad_glPopClientAttrib = (PFNGLPOPCLIENTATTRIBPROC) load(userptr, "glPopClientAttrib"); glad_glPrioritizeTextures = (PFNGLPRIORITIZETEXTURESPROC) load(userptr, "glPrioritizeTextures"); glad_glPushClientAttrib = (PFNGLPUSHCLIENTATTRIBPROC) load(userptr, "glPushClientAttrib"); glad_glTexCoordPointer = (PFNGLTEXCOORDPOINTERPROC) load(userptr, "glTexCoordPointer"); glad_glTexSubImage1D = (PFNGLTEXSUBIMAGE1DPROC) load(userptr, "glTexSubImage1D"); glad_glTexSubImage2D = (PFNGLTEXSUBIMAGE2DPROC) load(userptr, "glTexSubImage2D"); glad_glVertexPointer = (PFNGLVERTEXPOINTERPROC) load(userptr, "glVertexPointer"); } static void glad_gl_load_GL_VERSION_1_2( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_VERSION_1_2) return; glad_glCopyTexSubImage3D = (PFNGLCOPYTEXSUBIMAGE3DPROC) load(userptr, "glCopyTexSubImage3D"); glad_glDrawRangeElements = (PFNGLDRAWRANGEELEMENTSPROC) load(userptr, "glDrawRangeElements"); glad_glTexImage3D = (PFNGLTEXIMAGE3DPROC) load(userptr, "glTexImage3D"); glad_glTexSubImage3D = (PFNGLTEXSUBIMAGE3DPROC) load(userptr, "glTexSubImage3D"); } static void glad_gl_load_GL_VERSION_1_3( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_VERSION_1_3) return; glad_glActiveTexture = (PFNGLACTIVETEXTUREPROC) load(userptr, "glActiveTexture"); glad_glClientActiveTexture = (PFNGLCLIENTACTIVETEXTUREPROC) load(userptr, "glClientActiveTexture"); glad_glCompressedTexImage1D = (PFNGLCOMPRESSEDTEXIMAGE1DPROC) load(userptr, "glCompressedTexImage1D"); glad_glCompressedTexImage2D = (PFNGLCOMPRESSEDTEXIMAGE2DPROC) load(userptr, "glCompressedTexImage2D"); glad_glCompressedTexImage3D = (PFNGLCOMPRESSEDTEXIMAGE3DPROC) load(userptr, "glCompressedTexImage3D"); glad_glCompressedTexSubImage1D = (PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC) load(userptr, "glCompressedTexSubImage1D"); glad_glCompressedTexSubImage2D = (PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) load(userptr, "glCompressedTexSubImage2D"); glad_glCompressedTexSubImage3D = (PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) load(userptr, "glCompressedTexSubImage3D"); glad_glGetCompressedTexImage = (PFNGLGETCOMPRESSEDTEXIMAGEPROC) load(userptr, "glGetCompressedTexImage"); glad_glLoadTransposeMatrixd = (PFNGLLOADTRANSPOSEMATRIXDPROC) load(userptr, "glLoadTransposeMatrixd"); glad_glLoadTransposeMatrixf = (PFNGLLOADTRANSPOSEMATRIXFPROC) load(userptr, "glLoadTransposeMatrixf"); glad_glMultTransposeMatrixd = (PFNGLMULTTRANSPOSEMATRIXDPROC) load(userptr, "glMultTransposeMatrixd"); glad_glMultTransposeMatrixf = (PFNGLMULTTRANSPOSEMATRIXFPROC) load(userptr, "glMultTransposeMatrixf"); glad_glMultiTexCoord1d = (PFNGLMULTITEXCOORD1DPROC) load(userptr, "glMultiTexCoord1d"); glad_glMultiTexCoord1dv = (PFNGLMULTITEXCOORD1DVPROC) load(userptr, "glMultiTexCoord1dv"); glad_glMultiTexCoord1f = (PFNGLMULTITEXCOORD1FPROC) load(userptr, "glMultiTexCoord1f"); glad_glMultiTexCoord1fv = (PFNGLMULTITEXCOORD1FVPROC) load(userptr, "glMultiTexCoord1fv"); glad_glMultiTexCoord1i = (PFNGLMULTITEXCOORD1IPROC) load(userptr, "glMultiTexCoord1i"); glad_glMultiTexCoord1iv = (PFNGLMULTITEXCOORD1IVPROC) load(userptr, "glMultiTexCoord1iv"); glad_glMultiTexCoord1s = (PFNGLMULTITEXCOORD1SPROC) load(userptr, "glMultiTexCoord1s"); glad_glMultiTexCoord1sv = (PFNGLMULTITEXCOORD1SVPROC) load(userptr, "glMultiTexCoord1sv"); glad_glMultiTexCoord2d = (PFNGLMULTITEXCOORD2DPROC) load(userptr, "glMultiTexCoord2d"); glad_glMultiTexCoord2dv = (PFNGLMULTITEXCOORD2DVPROC) load(userptr, "glMultiTexCoord2dv"); glad_glMultiTexCoord2f = (PFNGLMULTITEXCOORD2FPROC) load(userptr, "glMultiTexCoord2f"); glad_glMultiTexCoord2fv = (PFNGLMULTITEXCOORD2FVPROC) load(userptr, "glMultiTexCoord2fv"); glad_glMultiTexCoord2i = (PFNGLMULTITEXCOORD2IPROC) load(userptr, "glMultiTexCoord2i"); glad_glMultiTexCoord2iv = (PFNGLMULTITEXCOORD2IVPROC) load(userptr, "glMultiTexCoord2iv"); glad_glMultiTexCoord2s = (PFNGLMULTITEXCOORD2SPROC) load(userptr, "glMultiTexCoord2s"); glad_glMultiTexCoord2sv = (PFNGLMULTITEXCOORD2SVPROC) load(userptr, "glMultiTexCoord2sv"); glad_glMultiTexCoord3d = (PFNGLMULTITEXCOORD3DPROC) load(userptr, "glMultiTexCoord3d"); glad_glMultiTexCoord3dv = (PFNGLMULTITEXCOORD3DVPROC) load(userptr, "glMultiTexCoord3dv"); glad_glMultiTexCoord3f = (PFNGLMULTITEXCOORD3FPROC) load(userptr, "glMultiTexCoord3f"); glad_glMultiTexCoord3fv = (PFNGLMULTITEXCOORD3FVPROC) load(userptr, "glMultiTexCoord3fv"); glad_glMultiTexCoord3i = (PFNGLMULTITEXCOORD3IPROC) load(userptr, "glMultiTexCoord3i"); glad_glMultiTexCoord3iv = (PFNGLMULTITEXCOORD3IVPROC) load(userptr, "glMultiTexCoord3iv"); glad_glMultiTexCoord3s = (PFNGLMULTITEXCOORD3SPROC) load(userptr, "glMultiTexCoord3s"); glad_glMultiTexCoord3sv = (PFNGLMULTITEXCOORD3SVPROC) load(userptr, "glMultiTexCoord3sv"); glad_glMultiTexCoord4d = (PFNGLMULTITEXCOORD4DPROC) load(userptr, "glMultiTexCoord4d"); glad_glMultiTexCoord4dv = (PFNGLMULTITEXCOORD4DVPROC) load(userptr, "glMultiTexCoord4dv"); glad_glMultiTexCoord4f = (PFNGLMULTITEXCOORD4FPROC) load(userptr, "glMultiTexCoord4f"); glad_glMultiTexCoord4fv = (PFNGLMULTITEXCOORD4FVPROC) load(userptr, "glMultiTexCoord4fv"); glad_glMultiTexCoord4i = (PFNGLMULTITEXCOORD4IPROC) load(userptr, "glMultiTexCoord4i"); glad_glMultiTexCoord4iv = (PFNGLMULTITEXCOORD4IVPROC) load(userptr, "glMultiTexCoord4iv"); glad_glMultiTexCoord4s = (PFNGLMULTITEXCOORD4SPROC) load(userptr, "glMultiTexCoord4s"); glad_glMultiTexCoord4sv = (PFNGLMULTITEXCOORD4SVPROC) load(userptr, "glMultiTexCoord4sv"); glad_glSampleCoverage = (PFNGLSAMPLECOVERAGEPROC) load(userptr, "glSampleCoverage"); } static void glad_gl_load_GL_VERSION_1_4( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_VERSION_1_4) return; glad_glBlendColor = (PFNGLBLENDCOLORPROC) load(userptr, "glBlendColor"); glad_glBlendEquation = (PFNGLBLENDEQUATIONPROC) load(userptr, "glBlendEquation"); glad_glBlendFuncSeparate = (PFNGLBLENDFUNCSEPARATEPROC) load(userptr, "glBlendFuncSeparate"); glad_glFogCoordPointer = (PFNGLFOGCOORDPOINTERPROC) load(userptr, "glFogCoordPointer"); glad_glFogCoordd = (PFNGLFOGCOORDDPROC) load(userptr, "glFogCoordd"); glad_glFogCoorddv = (PFNGLFOGCOORDDVPROC) load(userptr, "glFogCoorddv"); glad_glFogCoordf = (PFNGLFOGCOORDFPROC) load(userptr, "glFogCoordf"); glad_glFogCoordfv = (PFNGLFOGCOORDFVPROC) load(userptr, "glFogCoordfv"); glad_glMultiDrawArrays = (PFNGLMULTIDRAWARRAYSPROC) load(userptr, "glMultiDrawArrays"); glad_glMultiDrawElements = (PFNGLMULTIDRAWELEMENTSPROC) load(userptr, "glMultiDrawElements"); glad_glPointParameterf = (PFNGLPOINTPARAMETERFPROC) load(userptr, "glPointParameterf"); glad_glPointParameterfv = (PFNGLPOINTPARAMETERFVPROC) load(userptr, "glPointParameterfv"); glad_glPointParameteri = (PFNGLPOINTPARAMETERIPROC) load(userptr, "glPointParameteri"); glad_glPointParameteriv = (PFNGLPOINTPARAMETERIVPROC) load(userptr, "glPointParameteriv"); glad_glSecondaryColor3b = (PFNGLSECONDARYCOLOR3BPROC) load(userptr, "glSecondaryColor3b"); glad_glSecondaryColor3bv = (PFNGLSECONDARYCOLOR3BVPROC) load(userptr, "glSecondaryColor3bv"); glad_glSecondaryColor3d = (PFNGLSECONDARYCOLOR3DPROC) load(userptr, "glSecondaryColor3d"); glad_glSecondaryColor3dv = (PFNGLSECONDARYCOLOR3DVPROC) load(userptr, "glSecondaryColor3dv"); glad_glSecondaryColor3f = (PFNGLSECONDARYCOLOR3FPROC) load(userptr, "glSecondaryColor3f"); glad_glSecondaryColor3fv = (PFNGLSECONDARYCOLOR3FVPROC) load(userptr, "glSecondaryColor3fv"); glad_glSecondaryColor3i = (PFNGLSECONDARYCOLOR3IPROC) load(userptr, "glSecondaryColor3i"); glad_glSecondaryColor3iv = (PFNGLSECONDARYCOLOR3IVPROC) load(userptr, "glSecondaryColor3iv"); glad_glSecondaryColor3s = (PFNGLSECONDARYCOLOR3SPROC) load(userptr, "glSecondaryColor3s"); glad_glSecondaryColor3sv = (PFNGLSECONDARYCOLOR3SVPROC) load(userptr, "glSecondaryColor3sv"); glad_glSecondaryColor3ub = (PFNGLSECONDARYCOLOR3UBPROC) load(userptr, "glSecondaryColor3ub"); glad_glSecondaryColor3ubv = (PFNGLSECONDARYCOLOR3UBVPROC) load(userptr, "glSecondaryColor3ubv"); glad_glSecondaryColor3ui = (PFNGLSECONDARYCOLOR3UIPROC) load(userptr, "glSecondaryColor3ui"); glad_glSecondaryColor3uiv = (PFNGLSECONDARYCOLOR3UIVPROC) load(userptr, "glSecondaryColor3uiv"); glad_glSecondaryColor3us = (PFNGLSECONDARYCOLOR3USPROC) load(userptr, "glSecondaryColor3us"); glad_glSecondaryColor3usv = (PFNGLSECONDARYCOLOR3USVPROC) load(userptr, "glSecondaryColor3usv"); glad_glSecondaryColorPointer = (PFNGLSECONDARYCOLORPOINTERPROC) load(userptr, "glSecondaryColorPointer"); glad_glWindowPos2d = (PFNGLWINDOWPOS2DPROC) load(userptr, "glWindowPos2d"); glad_glWindowPos2dv = (PFNGLWINDOWPOS2DVPROC) load(userptr, "glWindowPos2dv"); glad_glWindowPos2f = (PFNGLWINDOWPOS2FPROC) load(userptr, "glWindowPos2f"); glad_glWindowPos2fv = (PFNGLWINDOWPOS2FVPROC) load(userptr, "glWindowPos2fv"); glad_glWindowPos2i = (PFNGLWINDOWPOS2IPROC) load(userptr, "glWindowPos2i"); glad_glWindowPos2iv = (PFNGLWINDOWPOS2IVPROC) load(userptr, "glWindowPos2iv"); glad_glWindowPos2s = (PFNGLWINDOWPOS2SPROC) load(userptr, "glWindowPos2s"); glad_glWindowPos2sv = (PFNGLWINDOWPOS2SVPROC) load(userptr, "glWindowPos2sv"); glad_glWindowPos3d = (PFNGLWINDOWPOS3DPROC) load(userptr, "glWindowPos3d"); glad_glWindowPos3dv = (PFNGLWINDOWPOS3DVPROC) load(userptr, "glWindowPos3dv"); glad_glWindowPos3f = (PFNGLWINDOWPOS3FPROC) load(userptr, "glWindowPos3f"); glad_glWindowPos3fv = (PFNGLWINDOWPOS3FVPROC) load(userptr, "glWindowPos3fv"); glad_glWindowPos3i = (PFNGLWINDOWPOS3IPROC) load(userptr, "glWindowPos3i"); glad_glWindowPos3iv = (PFNGLWINDOWPOS3IVPROC) load(userptr, "glWindowPos3iv"); glad_glWindowPos3s = (PFNGLWINDOWPOS3SPROC) load(userptr, "glWindowPos3s"); glad_glWindowPos3sv = (PFNGLWINDOWPOS3SVPROC) load(userptr, "glWindowPos3sv"); } static void glad_gl_load_GL_VERSION_1_5( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_VERSION_1_5) return; glad_glBeginQuery = (PFNGLBEGINQUERYPROC) load(userptr, "glBeginQuery"); glad_glBindBuffer = (PFNGLBINDBUFFERPROC) load(userptr, "glBindBuffer"); glad_glBufferData = (PFNGLBUFFERDATAPROC) load(userptr, "glBufferData"); glad_glBufferSubData = (PFNGLBUFFERSUBDATAPROC) load(userptr, "glBufferSubData"); glad_glDeleteBuffers = (PFNGLDELETEBUFFERSPROC) load(userptr, "glDeleteBuffers"); glad_glDeleteQueries = (PFNGLDELETEQUERIESPROC) load(userptr, "glDeleteQueries"); glad_glEndQuery = (PFNGLENDQUERYPROC) load(userptr, "glEndQuery"); glad_glGenBuffers = (PFNGLGENBUFFERSPROC) load(userptr, "glGenBuffers"); glad_glGenQueries = (PFNGLGENQUERIESPROC) load(userptr, "glGenQueries"); glad_glGetBufferParameteriv = (PFNGLGETBUFFERPARAMETERIVPROC) load(userptr, "glGetBufferParameteriv"); glad_glGetBufferPointerv = (PFNGLGETBUFFERPOINTERVPROC) load(userptr, "glGetBufferPointerv"); glad_glGetBufferSubData = (PFNGLGETBUFFERSUBDATAPROC) load(userptr, "glGetBufferSubData"); glad_glGetQueryObjectiv = (PFNGLGETQUERYOBJECTIVPROC) load(userptr, "glGetQueryObjectiv"); glad_glGetQueryObjectuiv = (PFNGLGETQUERYOBJECTUIVPROC) load(userptr, "glGetQueryObjectuiv"); glad_glGetQueryiv = (PFNGLGETQUERYIVPROC) load(userptr, "glGetQueryiv"); glad_glIsBuffer = (PFNGLISBUFFERPROC) load(userptr, "glIsBuffer"); glad_glIsQuery = (PFNGLISQUERYPROC) load(userptr, "glIsQuery"); glad_glMapBuffer = (PFNGLMAPBUFFERPROC) load(userptr, "glMapBuffer"); glad_glUnmapBuffer = (PFNGLUNMAPBUFFERPROC) load(userptr, "glUnmapBuffer"); } static void glad_gl_load_GL_VERSION_2_0( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_VERSION_2_0) return; glad_glAttachShader = (PFNGLATTACHSHADERPROC) load(userptr, "glAttachShader"); glad_glBindAttribLocation = (PFNGLBINDATTRIBLOCATIONPROC) load(userptr, "glBindAttribLocation"); glad_glBlendEquationSeparate = (PFNGLBLENDEQUATIONSEPARATEPROC) load(userptr, "glBlendEquationSeparate"); glad_glCompileShader = (PFNGLCOMPILESHADERPROC) load(userptr, "glCompileShader"); glad_glCreateProgram = (PFNGLCREATEPROGRAMPROC) load(userptr, "glCreateProgram"); glad_glCreateShader = (PFNGLCREATESHADERPROC) load(userptr, "glCreateShader"); glad_glDeleteProgram = (PFNGLDELETEPROGRAMPROC) load(userptr, "glDeleteProgram"); glad_glDeleteShader = (PFNGLDELETESHADERPROC) load(userptr, "glDeleteShader"); glad_glDetachShader = (PFNGLDETACHSHADERPROC) load(userptr, "glDetachShader"); glad_glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC) load(userptr, "glDisableVertexAttribArray"); glad_glDrawBuffers = (PFNGLDRAWBUFFERSPROC) load(userptr, "glDrawBuffers"); glad_glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC) load(userptr, "glEnableVertexAttribArray"); glad_glGetActiveAttrib = (PFNGLGETACTIVEATTRIBPROC) load(userptr, "glGetActiveAttrib"); glad_glGetActiveUniform = (PFNGLGETACTIVEUNIFORMPROC) load(userptr, "glGetActiveUniform"); glad_glGetAttachedShaders = (PFNGLGETATTACHEDSHADERSPROC) load(userptr, "glGetAttachedShaders"); glad_glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC) load(userptr, "glGetAttribLocation"); glad_glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC) load(userptr, "glGetProgramInfoLog"); glad_glGetProgramiv = (PFNGLGETPROGRAMIVPROC) load(userptr, "glGetProgramiv"); glad_glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC) load(userptr, "glGetShaderInfoLog"); glad_glGetShaderSource = (PFNGLGETSHADERSOURCEPROC) load(userptr, "glGetShaderSource"); glad_glGetShaderiv = (PFNGLGETSHADERIVPROC) load(userptr, "glGetShaderiv"); glad_glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC) load(userptr, "glGetUniformLocation"); glad_glGetUniformfv = (PFNGLGETUNIFORMFVPROC) load(userptr, "glGetUniformfv"); glad_glGetUniformiv = (PFNGLGETUNIFORMIVPROC) load(userptr, "glGetUniformiv"); glad_glGetVertexAttribPointerv = (PFNGLGETVERTEXATTRIBPOINTERVPROC) load(userptr, "glGetVertexAttribPointerv"); glad_glGetVertexAttribdv = (PFNGLGETVERTEXATTRIBDVPROC) load(userptr, "glGetVertexAttribdv"); glad_glGetVertexAttribfv = (PFNGLGETVERTEXATTRIBFVPROC) load(userptr, "glGetVertexAttribfv"); glad_glGetVertexAttribiv = (PFNGLGETVERTEXATTRIBIVPROC) load(userptr, "glGetVertexAttribiv"); glad_glIsProgram = (PFNGLISPROGRAMPROC) load(userptr, "glIsProgram"); glad_glIsShader = (PFNGLISSHADERPROC) load(userptr, "glIsShader"); glad_glLinkProgram = (PFNGLLINKPROGRAMPROC) load(userptr, "glLinkProgram"); glad_glShaderSource = (PFNGLSHADERSOURCEPROC) load(userptr, "glShaderSource"); glad_glStencilFuncSeparate = (PFNGLSTENCILFUNCSEPARATEPROC) load(userptr, "glStencilFuncSeparate"); glad_glStencilMaskSeparate = (PFNGLSTENCILMASKSEPARATEPROC) load(userptr, "glStencilMaskSeparate"); glad_glStencilOpSeparate = (PFNGLSTENCILOPSEPARATEPROC) load(userptr, "glStencilOpSeparate"); glad_glUniform1f = (PFNGLUNIFORM1FPROC) load(userptr, "glUniform1f"); glad_glUniform1fv = (PFNGLUNIFORM1FVPROC) load(userptr, "glUniform1fv"); glad_glUniform1i = (PFNGLUNIFORM1IPROC) load(userptr, "glUniform1i"); glad_glUniform1iv = (PFNGLUNIFORM1IVPROC) load(userptr, "glUniform1iv"); glad_glUniform2f = (PFNGLUNIFORM2FPROC) load(userptr, "glUniform2f"); glad_glUniform2fv = (PFNGLUNIFORM2FVPROC) load(userptr, "glUniform2fv"); glad_glUniform2i = (PFNGLUNIFORM2IPROC) load(userptr, "glUniform2i"); glad_glUniform2iv = (PFNGLUNIFORM2IVPROC) load(userptr, "glUniform2iv"); glad_glUniform3f = (PFNGLUNIFORM3FPROC) load(userptr, "glUniform3f"); glad_glUniform3fv = (PFNGLUNIFORM3FVPROC) load(userptr, "glUniform3fv"); glad_glUniform3i = (PFNGLUNIFORM3IPROC) load(userptr, "glUniform3i"); glad_glUniform3iv = (PFNGLUNIFORM3IVPROC) load(userptr, "glUniform3iv"); glad_glUniform4f = (PFNGLUNIFORM4FPROC) load(userptr, "glUniform4f"); glad_glUniform4fv = (PFNGLUNIFORM4FVPROC) load(userptr, "glUniform4fv"); glad_glUniform4i = (PFNGLUNIFORM4IPROC) load(userptr, "glUniform4i"); glad_glUniform4iv = (PFNGLUNIFORM4IVPROC) load(userptr, "glUniform4iv"); glad_glUniformMatrix2fv = (PFNGLUNIFORMMATRIX2FVPROC) load(userptr, "glUniformMatrix2fv"); glad_glUniformMatrix3fv = (PFNGLUNIFORMMATRIX3FVPROC) load(userptr, "glUniformMatrix3fv"); glad_glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC) load(userptr, "glUniformMatrix4fv"); glad_glUseProgram = (PFNGLUSEPROGRAMPROC) load(userptr, "glUseProgram"); glad_glValidateProgram = (PFNGLVALIDATEPROGRAMPROC) load(userptr, "glValidateProgram"); glad_glVertexAttrib1d = (PFNGLVERTEXATTRIB1DPROC) load(userptr, "glVertexAttrib1d"); glad_glVertexAttrib1dv = (PFNGLVERTEXATTRIB1DVPROC) load(userptr, "glVertexAttrib1dv"); glad_glVertexAttrib1f = (PFNGLVERTEXATTRIB1FPROC) load(userptr, "glVertexAttrib1f"); glad_glVertexAttrib1fv = (PFNGLVERTEXATTRIB1FVPROC) load(userptr, "glVertexAttrib1fv"); glad_glVertexAttrib1s = (PFNGLVERTEXATTRIB1SPROC) load(userptr, "glVertexAttrib1s"); glad_glVertexAttrib1sv = (PFNGLVERTEXATTRIB1SVPROC) load(userptr, "glVertexAttrib1sv"); glad_glVertexAttrib2d = (PFNGLVERTEXATTRIB2DPROC) load(userptr, "glVertexAttrib2d"); glad_glVertexAttrib2dv = (PFNGLVERTEXATTRIB2DVPROC) load(userptr, "glVertexAttrib2dv"); glad_glVertexAttrib2f = (PFNGLVERTEXATTRIB2FPROC) load(userptr, "glVertexAttrib2f"); glad_glVertexAttrib2fv = (PFNGLVERTEXATTRIB2FVPROC) load(userptr, "glVertexAttrib2fv"); glad_glVertexAttrib2s = (PFNGLVERTEXATTRIB2SPROC) load(userptr, "glVertexAttrib2s"); glad_glVertexAttrib2sv = (PFNGLVERTEXATTRIB2SVPROC) load(userptr, "glVertexAttrib2sv"); glad_glVertexAttrib3d = (PFNGLVERTEXATTRIB3DPROC) load(userptr, "glVertexAttrib3d"); glad_glVertexAttrib3dv = (PFNGLVERTEXATTRIB3DVPROC) load(userptr, "glVertexAttrib3dv"); glad_glVertexAttrib3f = (PFNGLVERTEXATTRIB3FPROC) load(userptr, "glVertexAttrib3f"); glad_glVertexAttrib3fv = (PFNGLVERTEXATTRIB3FVPROC) load(userptr, "glVertexAttrib3fv"); glad_glVertexAttrib3s = (PFNGLVERTEXATTRIB3SPROC) load(userptr, "glVertexAttrib3s"); glad_glVertexAttrib3sv = (PFNGLVERTEXATTRIB3SVPROC) load(userptr, "glVertexAttrib3sv"); glad_glVertexAttrib4Nbv = (PFNGLVERTEXATTRIB4NBVPROC) load(userptr, "glVertexAttrib4Nbv"); glad_glVertexAttrib4Niv = (PFNGLVERTEXATTRIB4NIVPROC) load(userptr, "glVertexAttrib4Niv"); glad_glVertexAttrib4Nsv = (PFNGLVERTEXATTRIB4NSVPROC) load(userptr, "glVertexAttrib4Nsv"); glad_glVertexAttrib4Nub = (PFNGLVERTEXATTRIB4NUBPROC) load(userptr, "glVertexAttrib4Nub"); glad_glVertexAttrib4Nubv = (PFNGLVERTEXATTRIB4NUBVPROC) load(userptr, "glVertexAttrib4Nubv"); glad_glVertexAttrib4Nuiv = (PFNGLVERTEXATTRIB4NUIVPROC) load(userptr, "glVertexAttrib4Nuiv"); glad_glVertexAttrib4Nusv = (PFNGLVERTEXATTRIB4NUSVPROC) load(userptr, "glVertexAttrib4Nusv"); glad_glVertexAttrib4bv = (PFNGLVERTEXATTRIB4BVPROC) load(userptr, "glVertexAttrib4bv"); glad_glVertexAttrib4d = (PFNGLVERTEXATTRIB4DPROC) load(userptr, "glVertexAttrib4d"); glad_glVertexAttrib4dv = (PFNGLVERTEXATTRIB4DVPROC) load(userptr, "glVertexAttrib4dv"); glad_glVertexAttrib4f = (PFNGLVERTEXATTRIB4FPROC) load(userptr, "glVertexAttrib4f"); glad_glVertexAttrib4fv = (PFNGLVERTEXATTRIB4FVPROC) load(userptr, "glVertexAttrib4fv"); glad_glVertexAttrib4iv = (PFNGLVERTEXATTRIB4IVPROC) load(userptr, "glVertexAttrib4iv"); glad_glVertexAttrib4s = (PFNGLVERTEXATTRIB4SPROC) load(userptr, "glVertexAttrib4s"); glad_glVertexAttrib4sv = (PFNGLVERTEXATTRIB4SVPROC) load(userptr, "glVertexAttrib4sv"); glad_glVertexAttrib4ubv = (PFNGLVERTEXATTRIB4UBVPROC) load(userptr, "glVertexAttrib4ubv"); glad_glVertexAttrib4uiv = (PFNGLVERTEXATTRIB4UIVPROC) load(userptr, "glVertexAttrib4uiv"); glad_glVertexAttrib4usv = (PFNGLVERTEXATTRIB4USVPROC) load(userptr, "glVertexAttrib4usv"); glad_glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC) load(userptr, "glVertexAttribPointer"); } static void glad_gl_load_GL_VERSION_2_1( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_VERSION_2_1) return; glad_glUniformMatrix2x3fv = (PFNGLUNIFORMMATRIX2X3FVPROC) load(userptr, "glUniformMatrix2x3fv"); glad_glUniformMatrix2x4fv = (PFNGLUNIFORMMATRIX2X4FVPROC) load(userptr, "glUniformMatrix2x4fv"); glad_glUniformMatrix3x2fv = (PFNGLUNIFORMMATRIX3X2FVPROC) load(userptr, "glUniformMatrix3x2fv"); glad_glUniformMatrix3x4fv = (PFNGLUNIFORMMATRIX3X4FVPROC) load(userptr, "glUniformMatrix3x4fv"); glad_glUniformMatrix4x2fv = (PFNGLUNIFORMMATRIX4X2FVPROC) load(userptr, "glUniformMatrix4x2fv"); glad_glUniformMatrix4x3fv = (PFNGLUNIFORMMATRIX4X3FVPROC) load(userptr, "glUniformMatrix4x3fv"); } static void glad_gl_load_GL_VERSION_3_0( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_VERSION_3_0) return; glad_glBeginConditionalRender = (PFNGLBEGINCONDITIONALRENDERPROC) load(userptr, "glBeginConditionalRender"); glad_glBeginTransformFeedback = (PFNGLBEGINTRANSFORMFEEDBACKPROC) load(userptr, "glBeginTransformFeedback"); glad_glBindBufferBase = (PFNGLBINDBUFFERBASEPROC) load(userptr, "glBindBufferBase"); glad_glBindBufferRange = (PFNGLBINDBUFFERRANGEPROC) load(userptr, "glBindBufferRange"); glad_glBindFragDataLocation = (PFNGLBINDFRAGDATALOCATIONPROC) load(userptr, "glBindFragDataLocation"); glad_glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC) load(userptr, "glBindFramebuffer"); glad_glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC) load(userptr, "glBindRenderbuffer"); glad_glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC) load(userptr, "glBindVertexArray"); glad_glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC) load(userptr, "glBlitFramebuffer"); glad_glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC) load(userptr, "glCheckFramebufferStatus"); glad_glClampColor = (PFNGLCLAMPCOLORPROC) load(userptr, "glClampColor"); glad_glClearBufferfi = (PFNGLCLEARBUFFERFIPROC) load(userptr, "glClearBufferfi"); glad_glClearBufferfv = (PFNGLCLEARBUFFERFVPROC) load(userptr, "glClearBufferfv"); glad_glClearBufferiv = (PFNGLCLEARBUFFERIVPROC) load(userptr, "glClearBufferiv"); glad_glClearBufferuiv = (PFNGLCLEARBUFFERUIVPROC) load(userptr, "glClearBufferuiv"); glad_glColorMaski = (PFNGLCOLORMASKIPROC) load(userptr, "glColorMaski"); glad_glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC) load(userptr, "glDeleteFramebuffers"); glad_glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC) load(userptr, "glDeleteRenderbuffers"); glad_glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC) load(userptr, "glDeleteVertexArrays"); glad_glDisablei = (PFNGLDISABLEIPROC) load(userptr, "glDisablei"); glad_glEnablei = (PFNGLENABLEIPROC) load(userptr, "glEnablei"); glad_glEndConditionalRender = (PFNGLENDCONDITIONALRENDERPROC) load(userptr, "glEndConditionalRender"); glad_glEndTransformFeedback = (PFNGLENDTRANSFORMFEEDBACKPROC) load(userptr, "glEndTransformFeedback"); glad_glFlushMappedBufferRange = (PFNGLFLUSHMAPPEDBUFFERRANGEPROC) load(userptr, "glFlushMappedBufferRange"); glad_glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC) load(userptr, "glFramebufferRenderbuffer"); glad_glFramebufferTexture1D = (PFNGLFRAMEBUFFERTEXTURE1DPROC) load(userptr, "glFramebufferTexture1D"); glad_glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC) load(userptr, "glFramebufferTexture2D"); glad_glFramebufferTexture3D = (PFNGLFRAMEBUFFERTEXTURE3DPROC) load(userptr, "glFramebufferTexture3D"); glad_glFramebufferTextureLayer = (PFNGLFRAMEBUFFERTEXTURELAYERPROC) load(userptr, "glFramebufferTextureLayer"); glad_glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC) load(userptr, "glGenFramebuffers"); glad_glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC) load(userptr, "glGenRenderbuffers"); glad_glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC) load(userptr, "glGenVertexArrays"); glad_glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC) load(userptr, "glGenerateMipmap"); glad_glGetBooleani_v = (PFNGLGETBOOLEANI_VPROC) load(userptr, "glGetBooleani_v"); glad_glGetFragDataLocation = (PFNGLGETFRAGDATALOCATIONPROC) load(userptr, "glGetFragDataLocation"); glad_glGetFramebufferAttachmentParameteriv = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC) load(userptr, "glGetFramebufferAttachmentParameteriv"); glad_glGetIntegeri_v = (PFNGLGETINTEGERI_VPROC) load(userptr, "glGetIntegeri_v"); glad_glGetRenderbufferParameteriv = (PFNGLGETRENDERBUFFERPARAMETERIVPROC) load(userptr, "glGetRenderbufferParameteriv"); glad_glGetStringi = (PFNGLGETSTRINGIPROC) load(userptr, "glGetStringi"); glad_glGetTexParameterIiv = (PFNGLGETTEXPARAMETERIIVPROC) load(userptr, "glGetTexParameterIiv"); glad_glGetTexParameterIuiv = (PFNGLGETTEXPARAMETERIUIVPROC) load(userptr, "glGetTexParameterIuiv"); glad_glGetTransformFeedbackVarying = (PFNGLGETTRANSFORMFEEDBACKVARYINGPROC) load(userptr, "glGetTransformFeedbackVarying"); glad_glGetUniformuiv = (PFNGLGETUNIFORMUIVPROC) load(userptr, "glGetUniformuiv"); glad_glGetVertexAttribIiv = (PFNGLGETVERTEXATTRIBIIVPROC) load(userptr, "glGetVertexAttribIiv"); glad_glGetVertexAttribIuiv = (PFNGLGETVERTEXATTRIBIUIVPROC) load(userptr, "glGetVertexAttribIuiv"); glad_glIsEnabledi = (PFNGLISENABLEDIPROC) load(userptr, "glIsEnabledi"); glad_glIsFramebuffer = (PFNGLISFRAMEBUFFERPROC) load(userptr, "glIsFramebuffer"); glad_glIsRenderbuffer = (PFNGLISRENDERBUFFERPROC) load(userptr, "glIsRenderbuffer"); glad_glIsVertexArray = (PFNGLISVERTEXARRAYPROC) load(userptr, "glIsVertexArray"); glad_glMapBufferRange = (PFNGLMAPBUFFERRANGEPROC) load(userptr, "glMapBufferRange"); glad_glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC) load(userptr, "glRenderbufferStorage"); glad_glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC) load(userptr, "glRenderbufferStorageMultisample"); glad_glTexParameterIiv = (PFNGLTEXPARAMETERIIVPROC) load(userptr, "glTexParameterIiv"); glad_glTexParameterIuiv = (PFNGLTEXPARAMETERIUIVPROC) load(userptr, "glTexParameterIuiv"); glad_glTransformFeedbackVaryings = (PFNGLTRANSFORMFEEDBACKVARYINGSPROC) load(userptr, "glTransformFeedbackVaryings"); glad_glUniform1ui = (PFNGLUNIFORM1UIPROC) load(userptr, "glUniform1ui"); glad_glUniform1uiv = (PFNGLUNIFORM1UIVPROC) load(userptr, "glUniform1uiv"); glad_glUniform2ui = (PFNGLUNIFORM2UIPROC) load(userptr, "glUniform2ui"); glad_glUniform2uiv = (PFNGLUNIFORM2UIVPROC) load(userptr, "glUniform2uiv"); glad_glUniform3ui = (PFNGLUNIFORM3UIPROC) load(userptr, "glUniform3ui"); glad_glUniform3uiv = (PFNGLUNIFORM3UIVPROC) load(userptr, "glUniform3uiv"); glad_glUniform4ui = (PFNGLUNIFORM4UIPROC) load(userptr, "glUniform4ui"); glad_glUniform4uiv = (PFNGLUNIFORM4UIVPROC) load(userptr, "glUniform4uiv"); glad_glVertexAttribI1i = (PFNGLVERTEXATTRIBI1IPROC) load(userptr, "glVertexAttribI1i"); glad_glVertexAttribI1iv = (PFNGLVERTEXATTRIBI1IVPROC) load(userptr, "glVertexAttribI1iv"); glad_glVertexAttribI1ui = (PFNGLVERTEXATTRIBI1UIPROC) load(userptr, "glVertexAttribI1ui"); glad_glVertexAttribI1uiv = (PFNGLVERTEXATTRIBI1UIVPROC) load(userptr, "glVertexAttribI1uiv"); glad_glVertexAttribI2i = (PFNGLVERTEXATTRIBI2IPROC) load(userptr, "glVertexAttribI2i"); glad_glVertexAttribI2iv = (PFNGLVERTEXATTRIBI2IVPROC) load(userptr, "glVertexAttribI2iv"); glad_glVertexAttribI2ui = (PFNGLVERTEXATTRIBI2UIPROC) load(userptr, "glVertexAttribI2ui"); glad_glVertexAttribI2uiv = (PFNGLVERTEXATTRIBI2UIVPROC) load(userptr, "glVertexAttribI2uiv"); glad_glVertexAttribI3i = (PFNGLVERTEXATTRIBI3IPROC) load(userptr, "glVertexAttribI3i"); glad_glVertexAttribI3iv = (PFNGLVERTEXATTRIBI3IVPROC) load(userptr, "glVertexAttribI3iv"); glad_glVertexAttribI3ui = (PFNGLVERTEXATTRIBI3UIPROC) load(userptr, "glVertexAttribI3ui"); glad_glVertexAttribI3uiv = (PFNGLVERTEXATTRIBI3UIVPROC) load(userptr, "glVertexAttribI3uiv"); glad_glVertexAttribI4bv = (PFNGLVERTEXATTRIBI4BVPROC) load(userptr, "glVertexAttribI4bv"); glad_glVertexAttribI4i = (PFNGLVERTEXATTRIBI4IPROC) load(userptr, "glVertexAttribI4i"); glad_glVertexAttribI4iv = (PFNGLVERTEXATTRIBI4IVPROC) load(userptr, "glVertexAttribI4iv"); glad_glVertexAttribI4sv = (PFNGLVERTEXATTRIBI4SVPROC) load(userptr, "glVertexAttribI4sv"); glad_glVertexAttribI4ubv = (PFNGLVERTEXATTRIBI4UBVPROC) load(userptr, "glVertexAttribI4ubv"); glad_glVertexAttribI4ui = (PFNGLVERTEXATTRIBI4UIPROC) load(userptr, "glVertexAttribI4ui"); glad_glVertexAttribI4uiv = (PFNGLVERTEXATTRIBI4UIVPROC) load(userptr, "glVertexAttribI4uiv"); glad_glVertexAttribI4usv = (PFNGLVERTEXATTRIBI4USVPROC) load(userptr, "glVertexAttribI4usv"); glad_glVertexAttribIPointer = (PFNGLVERTEXATTRIBIPOINTERPROC) load(userptr, "glVertexAttribIPointer"); } static void glad_gl_load_GL_VERSION_3_1( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_VERSION_3_1) return; glad_glBindBufferBase = (PFNGLBINDBUFFERBASEPROC) load(userptr, "glBindBufferBase"); glad_glBindBufferRange = (PFNGLBINDBUFFERRANGEPROC) load(userptr, "glBindBufferRange"); glad_glCopyBufferSubData = (PFNGLCOPYBUFFERSUBDATAPROC) load(userptr, "glCopyBufferSubData"); glad_glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDPROC) load(userptr, "glDrawArraysInstanced"); glad_glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDPROC) load(userptr, "glDrawElementsInstanced"); glad_glGetActiveUniformBlockName = (PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC) load(userptr, "glGetActiveUniformBlockName"); glad_glGetActiveUniformBlockiv = (PFNGLGETACTIVEUNIFORMBLOCKIVPROC) load(userptr, "glGetActiveUniformBlockiv"); glad_glGetActiveUniformName = (PFNGLGETACTIVEUNIFORMNAMEPROC) load(userptr, "glGetActiveUniformName"); glad_glGetActiveUniformsiv = (PFNGLGETACTIVEUNIFORMSIVPROC) load(userptr, "glGetActiveUniformsiv"); glad_glGetIntegeri_v = (PFNGLGETINTEGERI_VPROC) load(userptr, "glGetIntegeri_v"); glad_glGetUniformBlockIndex = (PFNGLGETUNIFORMBLOCKINDEXPROC) load(userptr, "glGetUniformBlockIndex"); glad_glGetUniformIndices = (PFNGLGETUNIFORMINDICESPROC) load(userptr, "glGetUniformIndices"); glad_glPrimitiveRestartIndex = (PFNGLPRIMITIVERESTARTINDEXPROC) load(userptr, "glPrimitiveRestartIndex"); glad_glTexBuffer = (PFNGLTEXBUFFERPROC) load(userptr, "glTexBuffer"); glad_glUniformBlockBinding = (PFNGLUNIFORMBLOCKBINDINGPROC) load(userptr, "glUniformBlockBinding"); } static void glad_gl_load_GL_ARB_copy_image( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_ARB_copy_image) return; glad_glCopyImageSubData = (PFNGLCOPYIMAGESUBDATAPROC) load(userptr, "glCopyImageSubData"); } static void glad_gl_load_GL_ARB_instanced_arrays( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_ARB_instanced_arrays) return; glad_glVertexAttribDivisorARB = (PFNGLVERTEXATTRIBDIVISORARBPROC) load(userptr, "glVertexAttribDivisorARB"); } static void glad_gl_load_GL_ARB_multisample( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_ARB_multisample) return; glad_glSampleCoverageARB = (PFNGLSAMPLECOVERAGEARBPROC) load(userptr, "glSampleCoverageARB"); } static void glad_gl_load_GL_ARB_robustness( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_ARB_robustness) return; glad_glGetGraphicsResetStatusARB = (PFNGLGETGRAPHICSRESETSTATUSARBPROC) load(userptr, "glGetGraphicsResetStatusARB"); glad_glGetnCompressedTexImageARB = (PFNGLGETNCOMPRESSEDTEXIMAGEARBPROC) load(userptr, "glGetnCompressedTexImageARB"); glad_glGetnTexImageARB = (PFNGLGETNTEXIMAGEARBPROC) load(userptr, "glGetnTexImageARB"); glad_glGetnUniformdvARB = (PFNGLGETNUNIFORMDVARBPROC) load(userptr, "glGetnUniformdvARB"); glad_glGetnUniformfvARB = (PFNGLGETNUNIFORMFVARBPROC) load(userptr, "glGetnUniformfvARB"); glad_glGetnUniformivARB = (PFNGLGETNUNIFORMIVARBPROC) load(userptr, "glGetnUniformivARB"); glad_glGetnUniformuivARB = (PFNGLGETNUNIFORMUIVARBPROC) load(userptr, "glGetnUniformuivARB"); glad_glReadnPixelsARB = (PFNGLREADNPIXELSARBPROC) load(userptr, "glReadnPixelsARB"); } static void glad_gl_load_GL_ARB_texture_storage( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_ARB_texture_storage) return; glad_glTexStorage1D = (PFNGLTEXSTORAGE1DPROC) load(userptr, "glTexStorage1D"); glad_glTexStorage2D = (PFNGLTEXSTORAGE2DPROC) load(userptr, "glTexStorage2D"); glad_glTexStorage3D = (PFNGLTEXSTORAGE3DPROC) load(userptr, "glTexStorage3D"); } static void glad_gl_load_GL_KHR_debug( GLADuserptrloadfunc load, void* userptr) { if(!GLAD_GL_KHR_debug) return; glad_glDebugMessageCallback = (PFNGLDEBUGMESSAGECALLBACKPROC) load(userptr, "glDebugMessageCallback"); glad_glDebugMessageControl = (PFNGLDEBUGMESSAGECONTROLPROC) load(userptr, "glDebugMessageControl"); glad_glDebugMessageInsert = (PFNGLDEBUGMESSAGEINSERTPROC) load(userptr, "glDebugMessageInsert"); glad_glGetDebugMessageLog = (PFNGLGETDEBUGMESSAGELOGPROC) load(userptr, "glGetDebugMessageLog"); glad_glGetObjectLabel = (PFNGLGETOBJECTLABELPROC) load(userptr, "glGetObjectLabel"); glad_glGetObjectPtrLabel = (PFNGLGETOBJECTPTRLABELPROC) load(userptr, "glGetObjectPtrLabel"); glad_glGetPointerv = (PFNGLGETPOINTERVPROC) load(userptr, "glGetPointerv"); glad_glObjectLabel = (PFNGLOBJECTLABELPROC) load(userptr, "glObjectLabel"); glad_glObjectPtrLabel = (PFNGLOBJECTPTRLABELPROC) load(userptr, "glObjectPtrLabel"); glad_glPopDebugGroup = (PFNGLPOPDEBUGGROUPPROC) load(userptr, "glPopDebugGroup"); glad_glPushDebugGroup = (PFNGLPUSHDEBUGGROUPPROC) load(userptr, "glPushDebugGroup"); } #if defined(GL_ES_VERSION_3_0) || defined(GL_VERSION_3_0) #define GLAD_GL_IS_SOME_NEW_VERSION 1 #else #define GLAD_GL_IS_SOME_NEW_VERSION 0 #endif static int glad_gl_get_extensions( int version, const char **out_exts, unsigned int *out_num_exts_i, char ***out_exts_i) { #if GLAD_GL_IS_SOME_NEW_VERSION if(GLAD_VERSION_MAJOR(version) < 3) { #else GLAD_UNUSED(version); GLAD_UNUSED(out_num_exts_i); GLAD_UNUSED(out_exts_i); #endif if (glad_glGetString == NULL) { return 0; } *out_exts = (const char *)glad_glGetString(GL_EXTENSIONS); #if GLAD_GL_IS_SOME_NEW_VERSION } else { unsigned int index = 0; unsigned int num_exts_i = 0; char **exts_i = NULL; if (glad_glGetStringi == NULL || glad_glGetIntegerv == NULL) { return 0; } glad_glGetIntegerv(GL_NUM_EXTENSIONS, (int*) &num_exts_i); if (num_exts_i > 0) { exts_i = (char **) malloc(num_exts_i * (sizeof *exts_i)); } if (exts_i == NULL) { return 0; } for(index = 0; index < num_exts_i; index++) { const char *gl_str_tmp = (const char*) glad_glGetStringi(GL_EXTENSIONS, index); size_t len = strlen(gl_str_tmp) + 1; char *local_str = (char*) malloc(len * sizeof(char)); if(local_str != NULL) { memcpy(local_str, gl_str_tmp, len * sizeof(char)); } exts_i[index] = local_str; } *out_num_exts_i = num_exts_i; *out_exts_i = exts_i; } #endif return 1; } static void glad_gl_free_extensions(char **exts_i, unsigned int num_exts_i) { if (exts_i != NULL) { unsigned int index; for(index = 0; index < num_exts_i; index++) { free((void *) (exts_i[index])); } free((void *)exts_i); exts_i = NULL; } } static int glad_gl_has_extension(int version, const char *exts, unsigned int num_exts_i, char **exts_i, const char *ext) { if(GLAD_VERSION_MAJOR(version) < 3 || !GLAD_GL_IS_SOME_NEW_VERSION) { const char *extensions; const char *loc; const char *terminator; extensions = exts; if(extensions == NULL || ext == NULL) { return 0; } while(1) { loc = strstr(extensions, ext); if(loc == NULL) { return 0; } terminator = loc + strlen(ext); if((loc == extensions || *(loc - 1) == ' ') && (*terminator == ' ' || *terminator == '\0')) { return 1; } extensions = terminator; } } else { unsigned int index; for(index = 0; index < num_exts_i; index++) { const char *e = exts_i[index]; if(strcmp(e, ext) == 0) { return 1; } } } return 0; } static GLADapiproc glad_gl_get_proc_from_userptr(void *userptr, const char* name) { return (GLAD_GNUC_EXTENSION (GLADapiproc (*)(const char *name)) userptr)(name); } static int glad_gl_find_extensions_gl( int version) { const char *exts = NULL; unsigned int num_exts_i = 0; char **exts_i = NULL; if (!glad_gl_get_extensions(version, &exts, &num_exts_i, &exts_i)) return 0; GLAD_GL_ARB_copy_image = glad_gl_has_extension(version, exts, num_exts_i, exts_i, "GL_ARB_copy_image"); GLAD_GL_ARB_instanced_arrays = glad_gl_has_extension(version, exts, num_exts_i, exts_i, "GL_ARB_instanced_arrays"); GLAD_GL_ARB_multisample = glad_gl_has_extension(version, exts, num_exts_i, exts_i, "GL_ARB_multisample"); GLAD_GL_ARB_robustness = glad_gl_has_extension(version, exts, num_exts_i, exts_i, "GL_ARB_robustness"); GLAD_GL_ARB_texture_storage = glad_gl_has_extension(version, exts, num_exts_i, exts_i, "GL_ARB_texture_storage"); GLAD_GL_KHR_debug = glad_gl_has_extension(version, exts, num_exts_i, exts_i, "GL_KHR_debug"); glad_gl_free_extensions(exts_i, num_exts_i); return 1; } static int glad_gl_find_core_gl(void) { int i; const char* version; const char* prefixes[] = { "OpenGL ES-CM ", "OpenGL ES-CL ", "OpenGL ES ", "OpenGL SC ", NULL }; int major = 0; int minor = 0; version = (const char*) glad_glGetString(GL_VERSION); if (!version) return 0; for (i = 0; prefixes[i]; i++) { const size_t length = strlen(prefixes[i]); if (strncmp(version, prefixes[i], length) == 0) { version += length; break; } } GLAD_IMPL_UTIL_SSCANF(version, "%d.%d", &major, &minor); GLAD_GL_VERSION_1_0 = (major == 1 && minor >= 0) || major > 1; GLAD_GL_VERSION_1_1 = (major == 1 && minor >= 1) || major > 1; GLAD_GL_VERSION_1_2 = (major == 1 && minor >= 2) || major > 1; GLAD_GL_VERSION_1_3 = (major == 1 && minor >= 3) || major > 1; GLAD_GL_VERSION_1_4 = (major == 1 && minor >= 4) || major > 1; GLAD_GL_VERSION_1_5 = (major == 1 && minor >= 5) || major > 1; GLAD_GL_VERSION_2_0 = (major == 2 && minor >= 0) || major > 2; GLAD_GL_VERSION_2_1 = (major == 2 && minor >= 1) || major > 2; GLAD_GL_VERSION_3_0 = (major == 3 && minor >= 0) || major > 3; GLAD_GL_VERSION_3_1 = (major == 3 && minor >= 1) || major > 3; return GLAD_MAKE_VERSION(major, minor); } int gladLoadGLUserPtr( GLADuserptrloadfunc load, void *userptr) { int version; glad_glGetString = (PFNGLGETSTRINGPROC) load(userptr, "glGetString"); if(glad_glGetString == NULL) return 0; if(glad_glGetString(GL_VERSION) == NULL) return 0; version = glad_gl_find_core_gl(); glad_gl_load_GL_VERSION_1_0(load, userptr); glad_gl_load_GL_VERSION_1_1(load, userptr); glad_gl_load_GL_VERSION_1_2(load, userptr); glad_gl_load_GL_VERSION_1_3(load, userptr); glad_gl_load_GL_VERSION_1_4(load, userptr); glad_gl_load_GL_VERSION_1_5(load, userptr); glad_gl_load_GL_VERSION_2_0(load, userptr); glad_gl_load_GL_VERSION_2_1(load, userptr); glad_gl_load_GL_VERSION_3_0(load, userptr); glad_gl_load_GL_VERSION_3_1(load, userptr); if (!glad_gl_find_extensions_gl(version)) return 0; glad_gl_load_GL_ARB_copy_image(load, userptr); glad_gl_load_GL_ARB_instanced_arrays(load, userptr); glad_gl_load_GL_ARB_multisample(load, userptr); glad_gl_load_GL_ARB_robustness(load, userptr); glad_gl_load_GL_ARB_texture_storage(load, userptr); glad_gl_load_GL_KHR_debug(load, userptr); return version; } int gladLoadGL( GLADloadfunc load) { return gladLoadGLUserPtr( glad_gl_get_proc_from_userptr, GLAD_GNUC_EXTENSION (void*) load); } void gladInstallGLDebug(void) { glad_debug_glAccum = glad_debug_impl_glAccum; glad_debug_glActiveTexture = glad_debug_impl_glActiveTexture; glad_debug_glAlphaFunc = glad_debug_impl_glAlphaFunc; glad_debug_glAreTexturesResident = glad_debug_impl_glAreTexturesResident; glad_debug_glArrayElement = glad_debug_impl_glArrayElement; glad_debug_glAttachShader = glad_debug_impl_glAttachShader; glad_debug_glBegin = glad_debug_impl_glBegin; glad_debug_glBeginConditionalRender = glad_debug_impl_glBeginConditionalRender; glad_debug_glBeginQuery = glad_debug_impl_glBeginQuery; glad_debug_glBeginTransformFeedback = glad_debug_impl_glBeginTransformFeedback; glad_debug_glBindAttribLocation = glad_debug_impl_glBindAttribLocation; glad_debug_glBindBuffer = glad_debug_impl_glBindBuffer; glad_debug_glBindBufferBase = glad_debug_impl_glBindBufferBase; glad_debug_glBindBufferRange = glad_debug_impl_glBindBufferRange; glad_debug_glBindFragDataLocation = glad_debug_impl_glBindFragDataLocation; glad_debug_glBindFramebuffer = glad_debug_impl_glBindFramebuffer; glad_debug_glBindRenderbuffer = glad_debug_impl_glBindRenderbuffer; glad_debug_glBindTexture = glad_debug_impl_glBindTexture; glad_debug_glBindVertexArray = glad_debug_impl_glBindVertexArray; glad_debug_glBitmap = glad_debug_impl_glBitmap; glad_debug_glBlendColor = glad_debug_impl_glBlendColor; glad_debug_glBlendEquation = glad_debug_impl_glBlendEquation; glad_debug_glBlendEquationSeparate = glad_debug_impl_glBlendEquationSeparate; glad_debug_glBlendFunc = glad_debug_impl_glBlendFunc; glad_debug_glBlendFuncSeparate = glad_debug_impl_glBlendFuncSeparate; glad_debug_glBlitFramebuffer = glad_debug_impl_glBlitFramebuffer; glad_debug_glBufferData = glad_debug_impl_glBufferData; glad_debug_glBufferSubData = glad_debug_impl_glBufferSubData; glad_debug_glCallList = glad_debug_impl_glCallList; glad_debug_glCallLists = glad_debug_impl_glCallLists; glad_debug_glCheckFramebufferStatus = glad_debug_impl_glCheckFramebufferStatus; glad_debug_glClampColor = glad_debug_impl_glClampColor; glad_debug_glClear = glad_debug_impl_glClear; glad_debug_glClearAccum = glad_debug_impl_glClearAccum; glad_debug_glClearBufferfi = glad_debug_impl_glClearBufferfi; glad_debug_glClearBufferfv = glad_debug_impl_glClearBufferfv; glad_debug_glClearBufferiv = glad_debug_impl_glClearBufferiv; glad_debug_glClearBufferuiv = glad_debug_impl_glClearBufferuiv; glad_debug_glClearColor = glad_debug_impl_glClearColor; glad_debug_glClearDepth = glad_debug_impl_glClearDepth; glad_debug_glClearIndex = glad_debug_impl_glClearIndex; glad_debug_glClearStencil = glad_debug_impl_glClearStencil; glad_debug_glClientActiveTexture = glad_debug_impl_glClientActiveTexture; glad_debug_glClipPlane = glad_debug_impl_glClipPlane; glad_debug_glColor3b = glad_debug_impl_glColor3b; glad_debug_glColor3bv = glad_debug_impl_glColor3bv; glad_debug_glColor3d = glad_debug_impl_glColor3d; glad_debug_glColor3dv = glad_debug_impl_glColor3dv; glad_debug_glColor3f = glad_debug_impl_glColor3f; glad_debug_glColor3fv = glad_debug_impl_glColor3fv; glad_debug_glColor3i = glad_debug_impl_glColor3i; glad_debug_glColor3iv = glad_debug_impl_glColor3iv; glad_debug_glColor3s = glad_debug_impl_glColor3s; glad_debug_glColor3sv = glad_debug_impl_glColor3sv; glad_debug_glColor3ub = glad_debug_impl_glColor3ub; glad_debug_glColor3ubv = glad_debug_impl_glColor3ubv; glad_debug_glColor3ui = glad_debug_impl_glColor3ui; glad_debug_glColor3uiv = glad_debug_impl_glColor3uiv; glad_debug_glColor3us = glad_debug_impl_glColor3us; glad_debug_glColor3usv = glad_debug_impl_glColor3usv; glad_debug_glColor4b = glad_debug_impl_glColor4b; glad_debug_glColor4bv = glad_debug_impl_glColor4bv; glad_debug_glColor4d = glad_debug_impl_glColor4d; glad_debug_glColor4dv = glad_debug_impl_glColor4dv; glad_debug_glColor4f = glad_debug_impl_glColor4f; glad_debug_glColor4fv = glad_debug_impl_glColor4fv; glad_debug_glColor4i = glad_debug_impl_glColor4i; glad_debug_glColor4iv = glad_debug_impl_glColor4iv; glad_debug_glColor4s = glad_debug_impl_glColor4s; glad_debug_glColor4sv = glad_debug_impl_glColor4sv; glad_debug_glColor4ub = glad_debug_impl_glColor4ub; glad_debug_glColor4ubv = glad_debug_impl_glColor4ubv; glad_debug_glColor4ui = glad_debug_impl_glColor4ui; glad_debug_glColor4uiv = glad_debug_impl_glColor4uiv; glad_debug_glColor4us = glad_debug_impl_glColor4us; glad_debug_glColor4usv = glad_debug_impl_glColor4usv; glad_debug_glColorMask = glad_debug_impl_glColorMask; glad_debug_glColorMaski = glad_debug_impl_glColorMaski; glad_debug_glColorMaterial = glad_debug_impl_glColorMaterial; glad_debug_glColorPointer = glad_debug_impl_glColorPointer; glad_debug_glCompileShader = glad_debug_impl_glCompileShader; glad_debug_glCompressedTexImage1D = glad_debug_impl_glCompressedTexImage1D; glad_debug_glCompressedTexImage2D = glad_debug_impl_glCompressedTexImage2D; glad_debug_glCompressedTexImage3D = glad_debug_impl_glCompressedTexImage3D; glad_debug_glCompressedTexSubImage1D = glad_debug_impl_glCompressedTexSubImage1D; glad_debug_glCompressedTexSubImage2D = glad_debug_impl_glCompressedTexSubImage2D; glad_debug_glCompressedTexSubImage3D = glad_debug_impl_glCompressedTexSubImage3D; glad_debug_glCopyBufferSubData = glad_debug_impl_glCopyBufferSubData; glad_debug_glCopyImageSubData = glad_debug_impl_glCopyImageSubData; glad_debug_glCopyPixels = glad_debug_impl_glCopyPixels; glad_debug_glCopyTexImage1D = glad_debug_impl_glCopyTexImage1D; glad_debug_glCopyTexImage2D = glad_debug_impl_glCopyTexImage2D; glad_debug_glCopyTexSubImage1D = glad_debug_impl_glCopyTexSubImage1D; glad_debug_glCopyTexSubImage2D = glad_debug_impl_glCopyTexSubImage2D; glad_debug_glCopyTexSubImage3D = glad_debug_impl_glCopyTexSubImage3D; glad_debug_glCreateProgram = glad_debug_impl_glCreateProgram; glad_debug_glCreateShader = glad_debug_impl_glCreateShader; glad_debug_glCullFace = glad_debug_impl_glCullFace; glad_debug_glDebugMessageCallback = glad_debug_impl_glDebugMessageCallback; glad_debug_glDebugMessageControl = glad_debug_impl_glDebugMessageControl; glad_debug_glDebugMessageInsert = glad_debug_impl_glDebugMessageInsert; glad_debug_glDeleteBuffers = glad_debug_impl_glDeleteBuffers; glad_debug_glDeleteFramebuffers = glad_debug_impl_glDeleteFramebuffers; glad_debug_glDeleteLists = glad_debug_impl_glDeleteLists; glad_debug_glDeleteProgram = glad_debug_impl_glDeleteProgram; glad_debug_glDeleteQueries = glad_debug_impl_glDeleteQueries; glad_debug_glDeleteRenderbuffers = glad_debug_impl_glDeleteRenderbuffers; glad_debug_glDeleteShader = glad_debug_impl_glDeleteShader; glad_debug_glDeleteTextures = glad_debug_impl_glDeleteTextures; glad_debug_glDeleteVertexArrays = glad_debug_impl_glDeleteVertexArrays; glad_debug_glDepthFunc = glad_debug_impl_glDepthFunc; glad_debug_glDepthMask = glad_debug_impl_glDepthMask; glad_debug_glDepthRange = glad_debug_impl_glDepthRange; glad_debug_glDetachShader = glad_debug_impl_glDetachShader; glad_debug_glDisable = glad_debug_impl_glDisable; glad_debug_glDisableClientState = glad_debug_impl_glDisableClientState; glad_debug_glDisableVertexAttribArray = glad_debug_impl_glDisableVertexAttribArray; glad_debug_glDisablei = glad_debug_impl_glDisablei; glad_debug_glDrawArrays = glad_debug_impl_glDrawArrays; glad_debug_glDrawArraysInstanced = glad_debug_impl_glDrawArraysInstanced; glad_debug_glDrawBuffer = glad_debug_impl_glDrawBuffer; glad_debug_glDrawBuffers = glad_debug_impl_glDrawBuffers; glad_debug_glDrawElements = glad_debug_impl_glDrawElements; glad_debug_glDrawElementsInstanced = glad_debug_impl_glDrawElementsInstanced; glad_debug_glDrawPixels = glad_debug_impl_glDrawPixels; glad_debug_glDrawRangeElements = glad_debug_impl_glDrawRangeElements; glad_debug_glEdgeFlag = glad_debug_impl_glEdgeFlag; glad_debug_glEdgeFlagPointer = glad_debug_impl_glEdgeFlagPointer; glad_debug_glEdgeFlagv = glad_debug_impl_glEdgeFlagv; glad_debug_glEnable = glad_debug_impl_glEnable; glad_debug_glEnableClientState = glad_debug_impl_glEnableClientState; glad_debug_glEnableVertexAttribArray = glad_debug_impl_glEnableVertexAttribArray; glad_debug_glEnablei = glad_debug_impl_glEnablei; glad_debug_glEnd = glad_debug_impl_glEnd; glad_debug_glEndConditionalRender = glad_debug_impl_glEndConditionalRender; glad_debug_glEndList = glad_debug_impl_glEndList; glad_debug_glEndQuery = glad_debug_impl_glEndQuery; glad_debug_glEndTransformFeedback = glad_debug_impl_glEndTransformFeedback; glad_debug_glEvalCoord1d = glad_debug_impl_glEvalCoord1d; glad_debug_glEvalCoord1dv = glad_debug_impl_glEvalCoord1dv; glad_debug_glEvalCoord1f = glad_debug_impl_glEvalCoord1f; glad_debug_glEvalCoord1fv = glad_debug_impl_glEvalCoord1fv; glad_debug_glEvalCoord2d = glad_debug_impl_glEvalCoord2d; glad_debug_glEvalCoord2dv = glad_debug_impl_glEvalCoord2dv; glad_debug_glEvalCoord2f = glad_debug_impl_glEvalCoord2f; glad_debug_glEvalCoord2fv = glad_debug_impl_glEvalCoord2fv; glad_debug_glEvalMesh1 = glad_debug_impl_glEvalMesh1; glad_debug_glEvalMesh2 = glad_debug_impl_glEvalMesh2; glad_debug_glEvalPoint1 = glad_debug_impl_glEvalPoint1; glad_debug_glEvalPoint2 = glad_debug_impl_glEvalPoint2; glad_debug_glFeedbackBuffer = glad_debug_impl_glFeedbackBuffer; glad_debug_glFinish = glad_debug_impl_glFinish; glad_debug_glFlush = glad_debug_impl_glFlush; glad_debug_glFlushMappedBufferRange = glad_debug_impl_glFlushMappedBufferRange; glad_debug_glFogCoordPointer = glad_debug_impl_glFogCoordPointer; glad_debug_glFogCoordd = glad_debug_impl_glFogCoordd; glad_debug_glFogCoorddv = glad_debug_impl_glFogCoorddv; glad_debug_glFogCoordf = glad_debug_impl_glFogCoordf; glad_debug_glFogCoordfv = glad_debug_impl_glFogCoordfv; glad_debug_glFogf = glad_debug_impl_glFogf; glad_debug_glFogfv = glad_debug_impl_glFogfv; glad_debug_glFogi = glad_debug_impl_glFogi; glad_debug_glFogiv = glad_debug_impl_glFogiv; glad_debug_glFramebufferRenderbuffer = glad_debug_impl_glFramebufferRenderbuffer; glad_debug_glFramebufferTexture1D = glad_debug_impl_glFramebufferTexture1D; glad_debug_glFramebufferTexture2D = glad_debug_impl_glFramebufferTexture2D; glad_debug_glFramebufferTexture3D = glad_debug_impl_glFramebufferTexture3D; glad_debug_glFramebufferTextureLayer = glad_debug_impl_glFramebufferTextureLayer; glad_debug_glFrontFace = glad_debug_impl_glFrontFace; glad_debug_glFrustum = glad_debug_impl_glFrustum; glad_debug_glGenBuffers = glad_debug_impl_glGenBuffers; glad_debug_glGenFramebuffers = glad_debug_impl_glGenFramebuffers; glad_debug_glGenLists = glad_debug_impl_glGenLists; glad_debug_glGenQueries = glad_debug_impl_glGenQueries; glad_debug_glGenRenderbuffers = glad_debug_impl_glGenRenderbuffers; glad_debug_glGenTextures = glad_debug_impl_glGenTextures; glad_debug_glGenVertexArrays = glad_debug_impl_glGenVertexArrays; glad_debug_glGenerateMipmap = glad_debug_impl_glGenerateMipmap; glad_debug_glGetActiveAttrib = glad_debug_impl_glGetActiveAttrib; glad_debug_glGetActiveUniform = glad_debug_impl_glGetActiveUniform; glad_debug_glGetActiveUniformBlockName = glad_debug_impl_glGetActiveUniformBlockName; glad_debug_glGetActiveUniformBlockiv = glad_debug_impl_glGetActiveUniformBlockiv; glad_debug_glGetActiveUniformName = glad_debug_impl_glGetActiveUniformName; glad_debug_glGetActiveUniformsiv = glad_debug_impl_glGetActiveUniformsiv; glad_debug_glGetAttachedShaders = glad_debug_impl_glGetAttachedShaders; glad_debug_glGetAttribLocation = glad_debug_impl_glGetAttribLocation; glad_debug_glGetBooleani_v = glad_debug_impl_glGetBooleani_v; glad_debug_glGetBooleanv = glad_debug_impl_glGetBooleanv; glad_debug_glGetBufferParameteriv = glad_debug_impl_glGetBufferParameteriv; glad_debug_glGetBufferPointerv = glad_debug_impl_glGetBufferPointerv; glad_debug_glGetBufferSubData = glad_debug_impl_glGetBufferSubData; glad_debug_glGetClipPlane = glad_debug_impl_glGetClipPlane; glad_debug_glGetCompressedTexImage = glad_debug_impl_glGetCompressedTexImage; glad_debug_glGetDebugMessageLog = glad_debug_impl_glGetDebugMessageLog; glad_debug_glGetDoublev = glad_debug_impl_glGetDoublev; glad_debug_glGetError = glad_debug_impl_glGetError; glad_debug_glGetFloatv = glad_debug_impl_glGetFloatv; glad_debug_glGetFragDataLocation = glad_debug_impl_glGetFragDataLocation; glad_debug_glGetFramebufferAttachmentParameteriv = glad_debug_impl_glGetFramebufferAttachmentParameteriv; glad_debug_glGetGraphicsResetStatusARB = glad_debug_impl_glGetGraphicsResetStatusARB; glad_debug_glGetIntegeri_v = glad_debug_impl_glGetIntegeri_v; glad_debug_glGetIntegerv = glad_debug_impl_glGetIntegerv; glad_debug_glGetLightfv = glad_debug_impl_glGetLightfv; glad_debug_glGetLightiv = glad_debug_impl_glGetLightiv; glad_debug_glGetMapdv = glad_debug_impl_glGetMapdv; glad_debug_glGetMapfv = glad_debug_impl_glGetMapfv; glad_debug_glGetMapiv = glad_debug_impl_glGetMapiv; glad_debug_glGetMaterialfv = glad_debug_impl_glGetMaterialfv; glad_debug_glGetMaterialiv = glad_debug_impl_glGetMaterialiv; glad_debug_glGetObjectLabel = glad_debug_impl_glGetObjectLabel; glad_debug_glGetObjectPtrLabel = glad_debug_impl_glGetObjectPtrLabel; glad_debug_glGetPixelMapfv = glad_debug_impl_glGetPixelMapfv; glad_debug_glGetPixelMapuiv = glad_debug_impl_glGetPixelMapuiv; glad_debug_glGetPixelMapusv = glad_debug_impl_glGetPixelMapusv; glad_debug_glGetPointerv = glad_debug_impl_glGetPointerv; glad_debug_glGetPolygonStipple = glad_debug_impl_glGetPolygonStipple; glad_debug_glGetProgramInfoLog = glad_debug_impl_glGetProgramInfoLog; glad_debug_glGetProgramiv = glad_debug_impl_glGetProgramiv; glad_debug_glGetQueryObjectiv = glad_debug_impl_glGetQueryObjectiv; glad_debug_glGetQueryObjectuiv = glad_debug_impl_glGetQueryObjectuiv; glad_debug_glGetQueryiv = glad_debug_impl_glGetQueryiv; glad_debug_glGetRenderbufferParameteriv = glad_debug_impl_glGetRenderbufferParameteriv; glad_debug_glGetShaderInfoLog = glad_debug_impl_glGetShaderInfoLog; glad_debug_glGetShaderSource = glad_debug_impl_glGetShaderSource; glad_debug_glGetShaderiv = glad_debug_impl_glGetShaderiv; glad_debug_glGetString = glad_debug_impl_glGetString; glad_debug_glGetStringi = glad_debug_impl_glGetStringi; glad_debug_glGetTexEnvfv = glad_debug_impl_glGetTexEnvfv; glad_debug_glGetTexEnviv = glad_debug_impl_glGetTexEnviv; glad_debug_glGetTexGendv = glad_debug_impl_glGetTexGendv; glad_debug_glGetTexGenfv = glad_debug_impl_glGetTexGenfv; glad_debug_glGetTexGeniv = glad_debug_impl_glGetTexGeniv; glad_debug_glGetTexImage = glad_debug_impl_glGetTexImage; glad_debug_glGetTexLevelParameterfv = glad_debug_impl_glGetTexLevelParameterfv; glad_debug_glGetTexLevelParameteriv = glad_debug_impl_glGetTexLevelParameteriv; glad_debug_glGetTexParameterIiv = glad_debug_impl_glGetTexParameterIiv; glad_debug_glGetTexParameterIuiv = glad_debug_impl_glGetTexParameterIuiv; glad_debug_glGetTexParameterfv = glad_debug_impl_glGetTexParameterfv; glad_debug_glGetTexParameteriv = glad_debug_impl_glGetTexParameteriv; glad_debug_glGetTransformFeedbackVarying = glad_debug_impl_glGetTransformFeedbackVarying; glad_debug_glGetUniformBlockIndex = glad_debug_impl_glGetUniformBlockIndex; glad_debug_glGetUniformIndices = glad_debug_impl_glGetUniformIndices; glad_debug_glGetUniformLocation = glad_debug_impl_glGetUniformLocation; glad_debug_glGetUniformfv = glad_debug_impl_glGetUniformfv; glad_debug_glGetUniformiv = glad_debug_impl_glGetUniformiv; glad_debug_glGetUniformuiv = glad_debug_impl_glGetUniformuiv; glad_debug_glGetVertexAttribIiv = glad_debug_impl_glGetVertexAttribIiv; glad_debug_glGetVertexAttribIuiv = glad_debug_impl_glGetVertexAttribIuiv; glad_debug_glGetVertexAttribPointerv = glad_debug_impl_glGetVertexAttribPointerv; glad_debug_glGetVertexAttribdv = glad_debug_impl_glGetVertexAttribdv; glad_debug_glGetVertexAttribfv = glad_debug_impl_glGetVertexAttribfv; glad_debug_glGetVertexAttribiv = glad_debug_impl_glGetVertexAttribiv; glad_debug_glGetnCompressedTexImageARB = glad_debug_impl_glGetnCompressedTexImageARB; glad_debug_glGetnTexImageARB = glad_debug_impl_glGetnTexImageARB; glad_debug_glGetnUniformdvARB = glad_debug_impl_glGetnUniformdvARB; glad_debug_glGetnUniformfvARB = glad_debug_impl_glGetnUniformfvARB; glad_debug_glGetnUniformivARB = glad_debug_impl_glGetnUniformivARB; glad_debug_glGetnUniformuivARB = glad_debug_impl_glGetnUniformuivARB; glad_debug_glHint = glad_debug_impl_glHint; glad_debug_glIndexMask = glad_debug_impl_glIndexMask; glad_debug_glIndexPointer = glad_debug_impl_glIndexPointer; glad_debug_glIndexd = glad_debug_impl_glIndexd; glad_debug_glIndexdv = glad_debug_impl_glIndexdv; glad_debug_glIndexf = glad_debug_impl_glIndexf; glad_debug_glIndexfv = glad_debug_impl_glIndexfv; glad_debug_glIndexi = glad_debug_impl_glIndexi; glad_debug_glIndexiv = glad_debug_impl_glIndexiv; glad_debug_glIndexs = glad_debug_impl_glIndexs; glad_debug_glIndexsv = glad_debug_impl_glIndexsv; glad_debug_glIndexub = glad_debug_impl_glIndexub; glad_debug_glIndexubv = glad_debug_impl_glIndexubv; glad_debug_glInitNames = glad_debug_impl_glInitNames; glad_debug_glInterleavedArrays = glad_debug_impl_glInterleavedArrays; glad_debug_glIsBuffer = glad_debug_impl_glIsBuffer; glad_debug_glIsEnabled = glad_debug_impl_glIsEnabled; glad_debug_glIsEnabledi = glad_debug_impl_glIsEnabledi; glad_debug_glIsFramebuffer = glad_debug_impl_glIsFramebuffer; glad_debug_glIsList = glad_debug_impl_glIsList; glad_debug_glIsProgram = glad_debug_impl_glIsProgram; glad_debug_glIsQuery = glad_debug_impl_glIsQuery; glad_debug_glIsRenderbuffer = glad_debug_impl_glIsRenderbuffer; glad_debug_glIsShader = glad_debug_impl_glIsShader; glad_debug_glIsTexture = glad_debug_impl_glIsTexture; glad_debug_glIsVertexArray = glad_debug_impl_glIsVertexArray; glad_debug_glLightModelf = glad_debug_impl_glLightModelf; glad_debug_glLightModelfv = glad_debug_impl_glLightModelfv; glad_debug_glLightModeli = glad_debug_impl_glLightModeli; glad_debug_glLightModeliv = glad_debug_impl_glLightModeliv; glad_debug_glLightf = glad_debug_impl_glLightf; glad_debug_glLightfv = glad_debug_impl_glLightfv; glad_debug_glLighti = glad_debug_impl_glLighti; glad_debug_glLightiv = glad_debug_impl_glLightiv; glad_debug_glLineStipple = glad_debug_impl_glLineStipple; glad_debug_glLineWidth = glad_debug_impl_glLineWidth; glad_debug_glLinkProgram = glad_debug_impl_glLinkProgram; glad_debug_glListBase = glad_debug_impl_glListBase; glad_debug_glLoadIdentity = glad_debug_impl_glLoadIdentity; glad_debug_glLoadMatrixd = glad_debug_impl_glLoadMatrixd; glad_debug_glLoadMatrixf = glad_debug_impl_glLoadMatrixf; glad_debug_glLoadName = glad_debug_impl_glLoadName; glad_debug_glLoadTransposeMatrixd = glad_debug_impl_glLoadTransposeMatrixd; glad_debug_glLoadTransposeMatrixf = glad_debug_impl_glLoadTransposeMatrixf; glad_debug_glLogicOp = glad_debug_impl_glLogicOp; glad_debug_glMap1d = glad_debug_impl_glMap1d; glad_debug_glMap1f = glad_debug_impl_glMap1f; glad_debug_glMap2d = glad_debug_impl_glMap2d; glad_debug_glMap2f = glad_debug_impl_glMap2f; glad_debug_glMapBuffer = glad_debug_impl_glMapBuffer; glad_debug_glMapBufferRange = glad_debug_impl_glMapBufferRange; glad_debug_glMapGrid1d = glad_debug_impl_glMapGrid1d; glad_debug_glMapGrid1f = glad_debug_impl_glMapGrid1f; glad_debug_glMapGrid2d = glad_debug_impl_glMapGrid2d; glad_debug_glMapGrid2f = glad_debug_impl_glMapGrid2f; glad_debug_glMaterialf = glad_debug_impl_glMaterialf; glad_debug_glMaterialfv = glad_debug_impl_glMaterialfv; glad_debug_glMateriali = glad_debug_impl_glMateriali; glad_debug_glMaterialiv = glad_debug_impl_glMaterialiv; glad_debug_glMatrixMode = glad_debug_impl_glMatrixMode; glad_debug_glMultMatrixd = glad_debug_impl_glMultMatrixd; glad_debug_glMultMatrixf = glad_debug_impl_glMultMatrixf; glad_debug_glMultTransposeMatrixd = glad_debug_impl_glMultTransposeMatrixd; glad_debug_glMultTransposeMatrixf = glad_debug_impl_glMultTransposeMatrixf; glad_debug_glMultiDrawArrays = glad_debug_impl_glMultiDrawArrays; glad_debug_glMultiDrawElements = glad_debug_impl_glMultiDrawElements; glad_debug_glMultiTexCoord1d = glad_debug_impl_glMultiTexCoord1d; glad_debug_glMultiTexCoord1dv = glad_debug_impl_glMultiTexCoord1dv; glad_debug_glMultiTexCoord1f = glad_debug_impl_glMultiTexCoord1f; glad_debug_glMultiTexCoord1fv = glad_debug_impl_glMultiTexCoord1fv; glad_debug_glMultiTexCoord1i = glad_debug_impl_glMultiTexCoord1i; glad_debug_glMultiTexCoord1iv = glad_debug_impl_glMultiTexCoord1iv; glad_debug_glMultiTexCoord1s = glad_debug_impl_glMultiTexCoord1s; glad_debug_glMultiTexCoord1sv = glad_debug_impl_glMultiTexCoord1sv; glad_debug_glMultiTexCoord2d = glad_debug_impl_glMultiTexCoord2d; glad_debug_glMultiTexCoord2dv = glad_debug_impl_glMultiTexCoord2dv; glad_debug_glMultiTexCoord2f = glad_debug_impl_glMultiTexCoord2f; glad_debug_glMultiTexCoord2fv = glad_debug_impl_glMultiTexCoord2fv; glad_debug_glMultiTexCoord2i = glad_debug_impl_glMultiTexCoord2i; glad_debug_glMultiTexCoord2iv = glad_debug_impl_glMultiTexCoord2iv; glad_debug_glMultiTexCoord2s = glad_debug_impl_glMultiTexCoord2s; glad_debug_glMultiTexCoord2sv = glad_debug_impl_glMultiTexCoord2sv; glad_debug_glMultiTexCoord3d = glad_debug_impl_glMultiTexCoord3d; glad_debug_glMultiTexCoord3dv = glad_debug_impl_glMultiTexCoord3dv; glad_debug_glMultiTexCoord3f = glad_debug_impl_glMultiTexCoord3f; glad_debug_glMultiTexCoord3fv = glad_debug_impl_glMultiTexCoord3fv; glad_debug_glMultiTexCoord3i = glad_debug_impl_glMultiTexCoord3i; glad_debug_glMultiTexCoord3iv = glad_debug_impl_glMultiTexCoord3iv; glad_debug_glMultiTexCoord3s = glad_debug_impl_glMultiTexCoord3s; glad_debug_glMultiTexCoord3sv = glad_debug_impl_glMultiTexCoord3sv; glad_debug_glMultiTexCoord4d = glad_debug_impl_glMultiTexCoord4d; glad_debug_glMultiTexCoord4dv = glad_debug_impl_glMultiTexCoord4dv; glad_debug_glMultiTexCoord4f = glad_debug_impl_glMultiTexCoord4f; glad_debug_glMultiTexCoord4fv = glad_debug_impl_glMultiTexCoord4fv; glad_debug_glMultiTexCoord4i = glad_debug_impl_glMultiTexCoord4i; glad_debug_glMultiTexCoord4iv = glad_debug_impl_glMultiTexCoord4iv; glad_debug_glMultiTexCoord4s = glad_debug_impl_glMultiTexCoord4s; glad_debug_glMultiTexCoord4sv = glad_debug_impl_glMultiTexCoord4sv; glad_debug_glNewList = glad_debug_impl_glNewList; glad_debug_glNormal3b = glad_debug_impl_glNormal3b; glad_debug_glNormal3bv = glad_debug_impl_glNormal3bv; glad_debug_glNormal3d = glad_debug_impl_glNormal3d; glad_debug_glNormal3dv = glad_debug_impl_glNormal3dv; glad_debug_glNormal3f = glad_debug_impl_glNormal3f; glad_debug_glNormal3fv = glad_debug_impl_glNormal3fv; glad_debug_glNormal3i = glad_debug_impl_glNormal3i; glad_debug_glNormal3iv = glad_debug_impl_glNormal3iv; glad_debug_glNormal3s = glad_debug_impl_glNormal3s; glad_debug_glNormal3sv = glad_debug_impl_glNormal3sv; glad_debug_glNormalPointer = glad_debug_impl_glNormalPointer; glad_debug_glObjectLabel = glad_debug_impl_glObjectLabel; glad_debug_glObjectPtrLabel = glad_debug_impl_glObjectPtrLabel; glad_debug_glOrtho = glad_debug_impl_glOrtho; glad_debug_glPassThrough = glad_debug_impl_glPassThrough; glad_debug_glPixelMapfv = glad_debug_impl_glPixelMapfv; glad_debug_glPixelMapuiv = glad_debug_impl_glPixelMapuiv; glad_debug_glPixelMapusv = glad_debug_impl_glPixelMapusv; glad_debug_glPixelStoref = glad_debug_impl_glPixelStoref; glad_debug_glPixelStorei = glad_debug_impl_glPixelStorei; glad_debug_glPixelTransferf = glad_debug_impl_glPixelTransferf; glad_debug_glPixelTransferi = glad_debug_impl_glPixelTransferi; glad_debug_glPixelZoom = glad_debug_impl_glPixelZoom; glad_debug_glPointParameterf = glad_debug_impl_glPointParameterf; glad_debug_glPointParameterfv = glad_debug_impl_glPointParameterfv; glad_debug_glPointParameteri = glad_debug_impl_glPointParameteri; glad_debug_glPointParameteriv = glad_debug_impl_glPointParameteriv; glad_debug_glPointSize = glad_debug_impl_glPointSize; glad_debug_glPolygonMode = glad_debug_impl_glPolygonMode; glad_debug_glPolygonOffset = glad_debug_impl_glPolygonOffset; glad_debug_glPolygonStipple = glad_debug_impl_glPolygonStipple; glad_debug_glPopAttrib = glad_debug_impl_glPopAttrib; glad_debug_glPopClientAttrib = glad_debug_impl_glPopClientAttrib; glad_debug_glPopDebugGroup = glad_debug_impl_glPopDebugGroup; glad_debug_glPopMatrix = glad_debug_impl_glPopMatrix; glad_debug_glPopName = glad_debug_impl_glPopName; glad_debug_glPrimitiveRestartIndex = glad_debug_impl_glPrimitiveRestartIndex; glad_debug_glPrioritizeTextures = glad_debug_impl_glPrioritizeTextures; glad_debug_glPushAttrib = glad_debug_impl_glPushAttrib; glad_debug_glPushClientAttrib = glad_debug_impl_glPushClientAttrib; glad_debug_glPushDebugGroup = glad_debug_impl_glPushDebugGroup; glad_debug_glPushMatrix = glad_debug_impl_glPushMatrix; glad_debug_glPushName = glad_debug_impl_glPushName; glad_debug_glRasterPos2d = glad_debug_impl_glRasterPos2d; glad_debug_glRasterPos2dv = glad_debug_impl_glRasterPos2dv; glad_debug_glRasterPos2f = glad_debug_impl_glRasterPos2f; glad_debug_glRasterPos2fv = glad_debug_impl_glRasterPos2fv; glad_debug_glRasterPos2i = glad_debug_impl_glRasterPos2i; glad_debug_glRasterPos2iv = glad_debug_impl_glRasterPos2iv; glad_debug_glRasterPos2s = glad_debug_impl_glRasterPos2s; glad_debug_glRasterPos2sv = glad_debug_impl_glRasterPos2sv; glad_debug_glRasterPos3d = glad_debug_impl_glRasterPos3d; glad_debug_glRasterPos3dv = glad_debug_impl_glRasterPos3dv; glad_debug_glRasterPos3f = glad_debug_impl_glRasterPos3f; glad_debug_glRasterPos3fv = glad_debug_impl_glRasterPos3fv; glad_debug_glRasterPos3i = glad_debug_impl_glRasterPos3i; glad_debug_glRasterPos3iv = glad_debug_impl_glRasterPos3iv; glad_debug_glRasterPos3s = glad_debug_impl_glRasterPos3s; glad_debug_glRasterPos3sv = glad_debug_impl_glRasterPos3sv; glad_debug_glRasterPos4d = glad_debug_impl_glRasterPos4d; glad_debug_glRasterPos4dv = glad_debug_impl_glRasterPos4dv; glad_debug_glRasterPos4f = glad_debug_impl_glRasterPos4f; glad_debug_glRasterPos4fv = glad_debug_impl_glRasterPos4fv; glad_debug_glRasterPos4i = glad_debug_impl_glRasterPos4i; glad_debug_glRasterPos4iv = glad_debug_impl_glRasterPos4iv; glad_debug_glRasterPos4s = glad_debug_impl_glRasterPos4s; glad_debug_glRasterPos4sv = glad_debug_impl_glRasterPos4sv; glad_debug_glReadBuffer = glad_debug_impl_glReadBuffer; glad_debug_glReadPixels = glad_debug_impl_glReadPixels; glad_debug_glReadnPixelsARB = glad_debug_impl_glReadnPixelsARB; glad_debug_glRectd = glad_debug_impl_glRectd; glad_debug_glRectdv = glad_debug_impl_glRectdv; glad_debug_glRectf = glad_debug_impl_glRectf; glad_debug_glRectfv = glad_debug_impl_glRectfv; glad_debug_glRecti = glad_debug_impl_glRecti; glad_debug_glRectiv = glad_debug_impl_glRectiv; glad_debug_glRects = glad_debug_impl_glRects; glad_debug_glRectsv = glad_debug_impl_glRectsv; glad_debug_glRenderMode = glad_debug_impl_glRenderMode; glad_debug_glRenderbufferStorage = glad_debug_impl_glRenderbufferStorage; glad_debug_glRenderbufferStorageMultisample = glad_debug_impl_glRenderbufferStorageMultisample; glad_debug_glRotated = glad_debug_impl_glRotated; glad_debug_glRotatef = glad_debug_impl_glRotatef; glad_debug_glSampleCoverage = glad_debug_impl_glSampleCoverage; glad_debug_glSampleCoverageARB = glad_debug_impl_glSampleCoverageARB; glad_debug_glScaled = glad_debug_impl_glScaled; glad_debug_glScalef = glad_debug_impl_glScalef; glad_debug_glScissor = glad_debug_impl_glScissor; glad_debug_glSecondaryColor3b = glad_debug_impl_glSecondaryColor3b; glad_debug_glSecondaryColor3bv = glad_debug_impl_glSecondaryColor3bv; glad_debug_glSecondaryColor3d = glad_debug_impl_glSecondaryColor3d; glad_debug_glSecondaryColor3dv = glad_debug_impl_glSecondaryColor3dv; glad_debug_glSecondaryColor3f = glad_debug_impl_glSecondaryColor3f; glad_debug_glSecondaryColor3fv = glad_debug_impl_glSecondaryColor3fv; glad_debug_glSecondaryColor3i = glad_debug_impl_glSecondaryColor3i; glad_debug_glSecondaryColor3iv = glad_debug_impl_glSecondaryColor3iv; glad_debug_glSecondaryColor3s = glad_debug_impl_glSecondaryColor3s; glad_debug_glSecondaryColor3sv = glad_debug_impl_glSecondaryColor3sv; glad_debug_glSecondaryColor3ub = glad_debug_impl_glSecondaryColor3ub; glad_debug_glSecondaryColor3ubv = glad_debug_impl_glSecondaryColor3ubv; glad_debug_glSecondaryColor3ui = glad_debug_impl_glSecondaryColor3ui; glad_debug_glSecondaryColor3uiv = glad_debug_impl_glSecondaryColor3uiv; glad_debug_glSecondaryColor3us = glad_debug_impl_glSecondaryColor3us; glad_debug_glSecondaryColor3usv = glad_debug_impl_glSecondaryColor3usv; glad_debug_glSecondaryColorPointer = glad_debug_impl_glSecondaryColorPointer; glad_debug_glSelectBuffer = glad_debug_impl_glSelectBuffer; glad_debug_glShadeModel = glad_debug_impl_glShadeModel; glad_debug_glShaderSource = glad_debug_impl_glShaderSource; glad_debug_glStencilFunc = glad_debug_impl_glStencilFunc; glad_debug_glStencilFuncSeparate = glad_debug_impl_glStencilFuncSeparate; glad_debug_glStencilMask = glad_debug_impl_glStencilMask; glad_debug_glStencilMaskSeparate = glad_debug_impl_glStencilMaskSeparate; glad_debug_glStencilOp = glad_debug_impl_glStencilOp; glad_debug_glStencilOpSeparate = glad_debug_impl_glStencilOpSeparate; glad_debug_glTexBuffer = glad_debug_impl_glTexBuffer; glad_debug_glTexCoord1d = glad_debug_impl_glTexCoord1d; glad_debug_glTexCoord1dv = glad_debug_impl_glTexCoord1dv; glad_debug_glTexCoord1f = glad_debug_impl_glTexCoord1f; glad_debug_glTexCoord1fv = glad_debug_impl_glTexCoord1fv; glad_debug_glTexCoord1i = glad_debug_impl_glTexCoord1i; glad_debug_glTexCoord1iv = glad_debug_impl_glTexCoord1iv; glad_debug_glTexCoord1s = glad_debug_impl_glTexCoord1s; glad_debug_glTexCoord1sv = glad_debug_impl_glTexCoord1sv; glad_debug_glTexCoord2d = glad_debug_impl_glTexCoord2d; glad_debug_glTexCoord2dv = glad_debug_impl_glTexCoord2dv; glad_debug_glTexCoord2f = glad_debug_impl_glTexCoord2f; glad_debug_glTexCoord2fv = glad_debug_impl_glTexCoord2fv; glad_debug_glTexCoord2i = glad_debug_impl_glTexCoord2i; glad_debug_glTexCoord2iv = glad_debug_impl_glTexCoord2iv; glad_debug_glTexCoord2s = glad_debug_impl_glTexCoord2s; glad_debug_glTexCoord2sv = glad_debug_impl_glTexCoord2sv; glad_debug_glTexCoord3d = glad_debug_impl_glTexCoord3d; glad_debug_glTexCoord3dv = glad_debug_impl_glTexCoord3dv; glad_debug_glTexCoord3f = glad_debug_impl_glTexCoord3f; glad_debug_glTexCoord3fv = glad_debug_impl_glTexCoord3fv; glad_debug_glTexCoord3i = glad_debug_impl_glTexCoord3i; glad_debug_glTexCoord3iv = glad_debug_impl_glTexCoord3iv; glad_debug_glTexCoord3s = glad_debug_impl_glTexCoord3s; glad_debug_glTexCoord3sv = glad_debug_impl_glTexCoord3sv; glad_debug_glTexCoord4d = glad_debug_impl_glTexCoord4d; glad_debug_glTexCoord4dv = glad_debug_impl_glTexCoord4dv; glad_debug_glTexCoord4f = glad_debug_impl_glTexCoord4f; glad_debug_glTexCoord4fv = glad_debug_impl_glTexCoord4fv; glad_debug_glTexCoord4i = glad_debug_impl_glTexCoord4i; glad_debug_glTexCoord4iv = glad_debug_impl_glTexCoord4iv; glad_debug_glTexCoord4s = glad_debug_impl_glTexCoord4s; glad_debug_glTexCoord4sv = glad_debug_impl_glTexCoord4sv; glad_debug_glTexCoordPointer = glad_debug_impl_glTexCoordPointer; glad_debug_glTexEnvf = glad_debug_impl_glTexEnvf; glad_debug_glTexEnvfv = glad_debug_impl_glTexEnvfv; glad_debug_glTexEnvi = glad_debug_impl_glTexEnvi; glad_debug_glTexEnviv = glad_debug_impl_glTexEnviv; glad_debug_glTexGend = glad_debug_impl_glTexGend; glad_debug_glTexGendv = glad_debug_impl_glTexGendv; glad_debug_glTexGenf = glad_debug_impl_glTexGenf; glad_debug_glTexGenfv = glad_debug_impl_glTexGenfv; glad_debug_glTexGeni = glad_debug_impl_glTexGeni; glad_debug_glTexGeniv = glad_debug_impl_glTexGeniv; glad_debug_glTexImage1D = glad_debug_impl_glTexImage1D; glad_debug_glTexImage2D = glad_debug_impl_glTexImage2D; glad_debug_glTexImage3D = glad_debug_impl_glTexImage3D; glad_debug_glTexParameterIiv = glad_debug_impl_glTexParameterIiv; glad_debug_glTexParameterIuiv = glad_debug_impl_glTexParameterIuiv; glad_debug_glTexParameterf = glad_debug_impl_glTexParameterf; glad_debug_glTexParameterfv = glad_debug_impl_glTexParameterfv; glad_debug_glTexParameteri = glad_debug_impl_glTexParameteri; glad_debug_glTexParameteriv = glad_debug_impl_glTexParameteriv; glad_debug_glTexStorage1D = glad_debug_impl_glTexStorage1D; glad_debug_glTexStorage2D = glad_debug_impl_glTexStorage2D; glad_debug_glTexStorage3D = glad_debug_impl_glTexStorage3D; glad_debug_glTexSubImage1D = glad_debug_impl_glTexSubImage1D; glad_debug_glTexSubImage2D = glad_debug_impl_glTexSubImage2D; glad_debug_glTexSubImage3D = glad_debug_impl_glTexSubImage3D; glad_debug_glTransformFeedbackVaryings = glad_debug_impl_glTransformFeedbackVaryings; glad_debug_glTranslated = glad_debug_impl_glTranslated; glad_debug_glTranslatef = glad_debug_impl_glTranslatef; glad_debug_glUniform1f = glad_debug_impl_glUniform1f; glad_debug_glUniform1fv = glad_debug_impl_glUniform1fv; glad_debug_glUniform1i = glad_debug_impl_glUniform1i; glad_debug_glUniform1iv = glad_debug_impl_glUniform1iv; glad_debug_glUniform1ui = glad_debug_impl_glUniform1ui; glad_debug_glUniform1uiv = glad_debug_impl_glUniform1uiv; glad_debug_glUniform2f = glad_debug_impl_glUniform2f; glad_debug_glUniform2fv = glad_debug_impl_glUniform2fv; glad_debug_glUniform2i = glad_debug_impl_glUniform2i; glad_debug_glUniform2iv = glad_debug_impl_glUniform2iv; glad_debug_glUniform2ui = glad_debug_impl_glUniform2ui; glad_debug_glUniform2uiv = glad_debug_impl_glUniform2uiv; glad_debug_glUniform3f = glad_debug_impl_glUniform3f; glad_debug_glUniform3fv = glad_debug_impl_glUniform3fv; glad_debug_glUniform3i = glad_debug_impl_glUniform3i; glad_debug_glUniform3iv = glad_debug_impl_glUniform3iv; glad_debug_glUniform3ui = glad_debug_impl_glUniform3ui; glad_debug_glUniform3uiv = glad_debug_impl_glUniform3uiv; glad_debug_glUniform4f = glad_debug_impl_glUniform4f; glad_debug_glUniform4fv = glad_debug_impl_glUniform4fv; glad_debug_glUniform4i = glad_debug_impl_glUniform4i; glad_debug_glUniform4iv = glad_debug_impl_glUniform4iv; glad_debug_glUniform4ui = glad_debug_impl_glUniform4ui; glad_debug_glUniform4uiv = glad_debug_impl_glUniform4uiv; glad_debug_glUniformBlockBinding = glad_debug_impl_glUniformBlockBinding; glad_debug_glUniformMatrix2fv = glad_debug_impl_glUniformMatrix2fv; glad_debug_glUniformMatrix2x3fv = glad_debug_impl_glUniformMatrix2x3fv; glad_debug_glUniformMatrix2x4fv = glad_debug_impl_glUniformMatrix2x4fv; glad_debug_glUniformMatrix3fv = glad_debug_impl_glUniformMatrix3fv; glad_debug_glUniformMatrix3x2fv = glad_debug_impl_glUniformMatrix3x2fv; glad_debug_glUniformMatrix3x4fv = glad_debug_impl_glUniformMatrix3x4fv; glad_debug_glUniformMatrix4fv = glad_debug_impl_glUniformMatrix4fv; glad_debug_glUniformMatrix4x2fv = glad_debug_impl_glUniformMatrix4x2fv; glad_debug_glUniformMatrix4x3fv = glad_debug_impl_glUniformMatrix4x3fv; glad_debug_glUnmapBuffer = glad_debug_impl_glUnmapBuffer; glad_debug_glUseProgram = glad_debug_impl_glUseProgram; glad_debug_glValidateProgram = glad_debug_impl_glValidateProgram; glad_debug_glVertex2d = glad_debug_impl_glVertex2d; glad_debug_glVertex2dv = glad_debug_impl_glVertex2dv; glad_debug_glVertex2f = glad_debug_impl_glVertex2f; glad_debug_glVertex2fv = glad_debug_impl_glVertex2fv; glad_debug_glVertex2i = glad_debug_impl_glVertex2i; glad_debug_glVertex2iv = glad_debug_impl_glVertex2iv; glad_debug_glVertex2s = glad_debug_impl_glVertex2s; glad_debug_glVertex2sv = glad_debug_impl_glVertex2sv; glad_debug_glVertex3d = glad_debug_impl_glVertex3d; glad_debug_glVertex3dv = glad_debug_impl_glVertex3dv; glad_debug_glVertex3f = glad_debug_impl_glVertex3f; glad_debug_glVertex3fv = glad_debug_impl_glVertex3fv; glad_debug_glVertex3i = glad_debug_impl_glVertex3i; glad_debug_glVertex3iv = glad_debug_impl_glVertex3iv; glad_debug_glVertex3s = glad_debug_impl_glVertex3s; glad_debug_glVertex3sv = glad_debug_impl_glVertex3sv; glad_debug_glVertex4d = glad_debug_impl_glVertex4d; glad_debug_glVertex4dv = glad_debug_impl_glVertex4dv; glad_debug_glVertex4f = glad_debug_impl_glVertex4f; glad_debug_glVertex4fv = glad_debug_impl_glVertex4fv; glad_debug_glVertex4i = glad_debug_impl_glVertex4i; glad_debug_glVertex4iv = glad_debug_impl_glVertex4iv; glad_debug_glVertex4s = glad_debug_impl_glVertex4s; glad_debug_glVertex4sv = glad_debug_impl_glVertex4sv; glad_debug_glVertexAttrib1d = glad_debug_impl_glVertexAttrib1d; glad_debug_glVertexAttrib1dv = glad_debug_impl_glVertexAttrib1dv; glad_debug_glVertexAttrib1f = glad_debug_impl_glVertexAttrib1f; glad_debug_glVertexAttrib1fv = glad_debug_impl_glVertexAttrib1fv; glad_debug_glVertexAttrib1s = glad_debug_impl_glVertexAttrib1s; glad_debug_glVertexAttrib1sv = glad_debug_impl_glVertexAttrib1sv; glad_debug_glVertexAttrib2d = glad_debug_impl_glVertexAttrib2d; glad_debug_glVertexAttrib2dv = glad_debug_impl_glVertexAttrib2dv; glad_debug_glVertexAttrib2f = glad_debug_impl_glVertexAttrib2f; glad_debug_glVertexAttrib2fv = glad_debug_impl_glVertexAttrib2fv; glad_debug_glVertexAttrib2s = glad_debug_impl_glVertexAttrib2s; glad_debug_glVertexAttrib2sv = glad_debug_impl_glVertexAttrib2sv; glad_debug_glVertexAttrib3d = glad_debug_impl_glVertexAttrib3d; glad_debug_glVertexAttrib3dv = glad_debug_impl_glVertexAttrib3dv; glad_debug_glVertexAttrib3f = glad_debug_impl_glVertexAttrib3f; glad_debug_glVertexAttrib3fv = glad_debug_impl_glVertexAttrib3fv; glad_debug_glVertexAttrib3s = glad_debug_impl_glVertexAttrib3s; glad_debug_glVertexAttrib3sv = glad_debug_impl_glVertexAttrib3sv; glad_debug_glVertexAttrib4Nbv = glad_debug_impl_glVertexAttrib4Nbv; glad_debug_glVertexAttrib4Niv = glad_debug_impl_glVertexAttrib4Niv; glad_debug_glVertexAttrib4Nsv = glad_debug_impl_glVertexAttrib4Nsv; glad_debug_glVertexAttrib4Nub = glad_debug_impl_glVertexAttrib4Nub; glad_debug_glVertexAttrib4Nubv = glad_debug_impl_glVertexAttrib4Nubv; glad_debug_glVertexAttrib4Nuiv = glad_debug_impl_glVertexAttrib4Nuiv; glad_debug_glVertexAttrib4Nusv = glad_debug_impl_glVertexAttrib4Nusv; glad_debug_glVertexAttrib4bv = glad_debug_impl_glVertexAttrib4bv; glad_debug_glVertexAttrib4d = glad_debug_impl_glVertexAttrib4d; glad_debug_glVertexAttrib4dv = glad_debug_impl_glVertexAttrib4dv; glad_debug_glVertexAttrib4f = glad_debug_impl_glVertexAttrib4f; glad_debug_glVertexAttrib4fv = glad_debug_impl_glVertexAttrib4fv; glad_debug_glVertexAttrib4iv = glad_debug_impl_glVertexAttrib4iv; glad_debug_glVertexAttrib4s = glad_debug_impl_glVertexAttrib4s; glad_debug_glVertexAttrib4sv = glad_debug_impl_glVertexAttrib4sv; glad_debug_glVertexAttrib4ubv = glad_debug_impl_glVertexAttrib4ubv; glad_debug_glVertexAttrib4uiv = glad_debug_impl_glVertexAttrib4uiv; glad_debug_glVertexAttrib4usv = glad_debug_impl_glVertexAttrib4usv; glad_debug_glVertexAttribDivisorARB = glad_debug_impl_glVertexAttribDivisorARB; glad_debug_glVertexAttribI1i = glad_debug_impl_glVertexAttribI1i; glad_debug_glVertexAttribI1iv = glad_debug_impl_glVertexAttribI1iv; glad_debug_glVertexAttribI1ui = glad_debug_impl_glVertexAttribI1ui; glad_debug_glVertexAttribI1uiv = glad_debug_impl_glVertexAttribI1uiv; glad_debug_glVertexAttribI2i = glad_debug_impl_glVertexAttribI2i; glad_debug_glVertexAttribI2iv = glad_debug_impl_glVertexAttribI2iv; glad_debug_glVertexAttribI2ui = glad_debug_impl_glVertexAttribI2ui; glad_debug_glVertexAttribI2uiv = glad_debug_impl_glVertexAttribI2uiv; glad_debug_glVertexAttribI3i = glad_debug_impl_glVertexAttribI3i; glad_debug_glVertexAttribI3iv = glad_debug_impl_glVertexAttribI3iv; glad_debug_glVertexAttribI3ui = glad_debug_impl_glVertexAttribI3ui; glad_debug_glVertexAttribI3uiv = glad_debug_impl_glVertexAttribI3uiv; glad_debug_glVertexAttribI4bv = glad_debug_impl_glVertexAttribI4bv; glad_debug_glVertexAttribI4i = glad_debug_impl_glVertexAttribI4i; glad_debug_glVertexAttribI4iv = glad_debug_impl_glVertexAttribI4iv; glad_debug_glVertexAttribI4sv = glad_debug_impl_glVertexAttribI4sv; glad_debug_glVertexAttribI4ubv = glad_debug_impl_glVertexAttribI4ubv; glad_debug_glVertexAttribI4ui = glad_debug_impl_glVertexAttribI4ui; glad_debug_glVertexAttribI4uiv = glad_debug_impl_glVertexAttribI4uiv; glad_debug_glVertexAttribI4usv = glad_debug_impl_glVertexAttribI4usv; glad_debug_glVertexAttribIPointer = glad_debug_impl_glVertexAttribIPointer; glad_debug_glVertexAttribPointer = glad_debug_impl_glVertexAttribPointer; glad_debug_glVertexPointer = glad_debug_impl_glVertexPointer; glad_debug_glViewport = glad_debug_impl_glViewport; glad_debug_glWindowPos2d = glad_debug_impl_glWindowPos2d; glad_debug_glWindowPos2dv = glad_debug_impl_glWindowPos2dv; glad_debug_glWindowPos2f = glad_debug_impl_glWindowPos2f; glad_debug_glWindowPos2fv = glad_debug_impl_glWindowPos2fv; glad_debug_glWindowPos2i = glad_debug_impl_glWindowPos2i; glad_debug_glWindowPos2iv = glad_debug_impl_glWindowPos2iv; glad_debug_glWindowPos2s = glad_debug_impl_glWindowPos2s; glad_debug_glWindowPos2sv = glad_debug_impl_glWindowPos2sv; glad_debug_glWindowPos3d = glad_debug_impl_glWindowPos3d; glad_debug_glWindowPos3dv = glad_debug_impl_glWindowPos3dv; glad_debug_glWindowPos3f = glad_debug_impl_glWindowPos3f; glad_debug_glWindowPos3fv = glad_debug_impl_glWindowPos3fv; glad_debug_glWindowPos3i = glad_debug_impl_glWindowPos3i; glad_debug_glWindowPos3iv = glad_debug_impl_glWindowPos3iv; glad_debug_glWindowPos3s = glad_debug_impl_glWindowPos3s; glad_debug_glWindowPos3sv = glad_debug_impl_glWindowPos3sv; } void gladUninstallGLDebug(void) { glad_debug_glAccum = glad_glAccum; glad_debug_glActiveTexture = glad_glActiveTexture; glad_debug_glAlphaFunc = glad_glAlphaFunc; glad_debug_glAreTexturesResident = glad_glAreTexturesResident; glad_debug_glArrayElement = glad_glArrayElement; glad_debug_glAttachShader = glad_glAttachShader; glad_debug_glBegin = glad_glBegin; glad_debug_glBeginConditionalRender = glad_glBeginConditionalRender; glad_debug_glBeginQuery = glad_glBeginQuery; glad_debug_glBeginTransformFeedback = glad_glBeginTransformFeedback; glad_debug_glBindAttribLocation = glad_glBindAttribLocation; glad_debug_glBindBuffer = glad_glBindBuffer; glad_debug_glBindBufferBase = glad_glBindBufferBase; glad_debug_glBindBufferRange = glad_glBindBufferRange; glad_debug_glBindFragDataLocation = glad_glBindFragDataLocation; glad_debug_glBindFramebuffer = glad_glBindFramebuffer; glad_debug_glBindRenderbuffer = glad_glBindRenderbuffer; glad_debug_glBindTexture = glad_glBindTexture; glad_debug_glBindVertexArray = glad_glBindVertexArray; glad_debug_glBitmap = glad_glBitmap; glad_debug_glBlendColor = glad_glBlendColor; glad_debug_glBlendEquation = glad_glBlendEquation; glad_debug_glBlendEquationSeparate = glad_glBlendEquationSeparate; glad_debug_glBlendFunc = glad_glBlendFunc; glad_debug_glBlendFuncSeparate = glad_glBlendFuncSeparate; glad_debug_glBlitFramebuffer = glad_glBlitFramebuffer; glad_debug_glBufferData = glad_glBufferData; glad_debug_glBufferSubData = glad_glBufferSubData; glad_debug_glCallList = glad_glCallList; glad_debug_glCallLists = glad_glCallLists; glad_debug_glCheckFramebufferStatus = glad_glCheckFramebufferStatus; glad_debug_glClampColor = glad_glClampColor; glad_debug_glClear = glad_glClear; glad_debug_glClearAccum = glad_glClearAccum; glad_debug_glClearBufferfi = glad_glClearBufferfi; glad_debug_glClearBufferfv = glad_glClearBufferfv; glad_debug_glClearBufferiv = glad_glClearBufferiv; glad_debug_glClearBufferuiv = glad_glClearBufferuiv; glad_debug_glClearColor = glad_glClearColor; glad_debug_glClearDepth = glad_glClearDepth; glad_debug_glClearIndex = glad_glClearIndex; glad_debug_glClearStencil = glad_glClearStencil; glad_debug_glClientActiveTexture = glad_glClientActiveTexture; glad_debug_glClipPlane = glad_glClipPlane; glad_debug_glColor3b = glad_glColor3b; glad_debug_glColor3bv = glad_glColor3bv; glad_debug_glColor3d = glad_glColor3d; glad_debug_glColor3dv = glad_glColor3dv; glad_debug_glColor3f = glad_glColor3f; glad_debug_glColor3fv = glad_glColor3fv; glad_debug_glColor3i = glad_glColor3i; glad_debug_glColor3iv = glad_glColor3iv; glad_debug_glColor3s = glad_glColor3s; glad_debug_glColor3sv = glad_glColor3sv; glad_debug_glColor3ub = glad_glColor3ub; glad_debug_glColor3ubv = glad_glColor3ubv; glad_debug_glColor3ui = glad_glColor3ui; glad_debug_glColor3uiv = glad_glColor3uiv; glad_debug_glColor3us = glad_glColor3us; glad_debug_glColor3usv = glad_glColor3usv; glad_debug_glColor4b = glad_glColor4b; glad_debug_glColor4bv = glad_glColor4bv; glad_debug_glColor4d = glad_glColor4d; glad_debug_glColor4dv = glad_glColor4dv; glad_debug_glColor4f = glad_glColor4f; glad_debug_glColor4fv = glad_glColor4fv; glad_debug_glColor4i = glad_glColor4i; glad_debug_glColor4iv = glad_glColor4iv; glad_debug_glColor4s = glad_glColor4s; glad_debug_glColor4sv = glad_glColor4sv; glad_debug_glColor4ub = glad_glColor4ub; glad_debug_glColor4ubv = glad_glColor4ubv; glad_debug_glColor4ui = glad_glColor4ui; glad_debug_glColor4uiv = glad_glColor4uiv; glad_debug_glColor4us = glad_glColor4us; glad_debug_glColor4usv = glad_glColor4usv; glad_debug_glColorMask = glad_glColorMask; glad_debug_glColorMaski = glad_glColorMaski; glad_debug_glColorMaterial = glad_glColorMaterial; glad_debug_glColorPointer = glad_glColorPointer; glad_debug_glCompileShader = glad_glCompileShader; glad_debug_glCompressedTexImage1D = glad_glCompressedTexImage1D; glad_debug_glCompressedTexImage2D = glad_glCompressedTexImage2D; glad_debug_glCompressedTexImage3D = glad_glCompressedTexImage3D; glad_debug_glCompressedTexSubImage1D = glad_glCompressedTexSubImage1D; glad_debug_glCompressedTexSubImage2D = glad_glCompressedTexSubImage2D; glad_debug_glCompressedTexSubImage3D = glad_glCompressedTexSubImage3D; glad_debug_glCopyBufferSubData = glad_glCopyBufferSubData; glad_debug_glCopyImageSubData = glad_glCopyImageSubData; glad_debug_glCopyPixels = glad_glCopyPixels; glad_debug_glCopyTexImage1D = glad_glCopyTexImage1D; glad_debug_glCopyTexImage2D = glad_glCopyTexImage2D; glad_debug_glCopyTexSubImage1D = glad_glCopyTexSubImage1D; glad_debug_glCopyTexSubImage2D = glad_glCopyTexSubImage2D; glad_debug_glCopyTexSubImage3D = glad_glCopyTexSubImage3D; glad_debug_glCreateProgram = glad_glCreateProgram; glad_debug_glCreateShader = glad_glCreateShader; glad_debug_glCullFace = glad_glCullFace; glad_debug_glDebugMessageCallback = glad_glDebugMessageCallback; glad_debug_glDebugMessageControl = glad_glDebugMessageControl; glad_debug_glDebugMessageInsert = glad_glDebugMessageInsert; glad_debug_glDeleteBuffers = glad_glDeleteBuffers; glad_debug_glDeleteFramebuffers = glad_glDeleteFramebuffers; glad_debug_glDeleteLists = glad_glDeleteLists; glad_debug_glDeleteProgram = glad_glDeleteProgram; glad_debug_glDeleteQueries = glad_glDeleteQueries; glad_debug_glDeleteRenderbuffers = glad_glDeleteRenderbuffers; glad_debug_glDeleteShader = glad_glDeleteShader; glad_debug_glDeleteTextures = glad_glDeleteTextures; glad_debug_glDeleteVertexArrays = glad_glDeleteVertexArrays; glad_debug_glDepthFunc = glad_glDepthFunc; glad_debug_glDepthMask = glad_glDepthMask; glad_debug_glDepthRange = glad_glDepthRange; glad_debug_glDetachShader = glad_glDetachShader; glad_debug_glDisable = glad_glDisable; glad_debug_glDisableClientState = glad_glDisableClientState; glad_debug_glDisableVertexAttribArray = glad_glDisableVertexAttribArray; glad_debug_glDisablei = glad_glDisablei; glad_debug_glDrawArrays = glad_glDrawArrays; glad_debug_glDrawArraysInstanced = glad_glDrawArraysInstanced; glad_debug_glDrawBuffer = glad_glDrawBuffer; glad_debug_glDrawBuffers = glad_glDrawBuffers; glad_debug_glDrawElements = glad_glDrawElements; glad_debug_glDrawElementsInstanced = glad_glDrawElementsInstanced; glad_debug_glDrawPixels = glad_glDrawPixels; glad_debug_glDrawRangeElements = glad_glDrawRangeElements; glad_debug_glEdgeFlag = glad_glEdgeFlag; glad_debug_glEdgeFlagPointer = glad_glEdgeFlagPointer; glad_debug_glEdgeFlagv = glad_glEdgeFlagv; glad_debug_glEnable = glad_glEnable; glad_debug_glEnableClientState = glad_glEnableClientState; glad_debug_glEnableVertexAttribArray = glad_glEnableVertexAttribArray; glad_debug_glEnablei = glad_glEnablei; glad_debug_glEnd = glad_glEnd; glad_debug_glEndConditionalRender = glad_glEndConditionalRender; glad_debug_glEndList = glad_glEndList; glad_debug_glEndQuery = glad_glEndQuery; glad_debug_glEndTransformFeedback = glad_glEndTransformFeedback; glad_debug_glEvalCoord1d = glad_glEvalCoord1d; glad_debug_glEvalCoord1dv = glad_glEvalCoord1dv; glad_debug_glEvalCoord1f = glad_glEvalCoord1f; glad_debug_glEvalCoord1fv = glad_glEvalCoord1fv; glad_debug_glEvalCoord2d = glad_glEvalCoord2d; glad_debug_glEvalCoord2dv = glad_glEvalCoord2dv; glad_debug_glEvalCoord2f = glad_glEvalCoord2f; glad_debug_glEvalCoord2fv = glad_glEvalCoord2fv; glad_debug_glEvalMesh1 = glad_glEvalMesh1; glad_debug_glEvalMesh2 = glad_glEvalMesh2; glad_debug_glEvalPoint1 = glad_glEvalPoint1; glad_debug_glEvalPoint2 = glad_glEvalPoint2; glad_debug_glFeedbackBuffer = glad_glFeedbackBuffer; glad_debug_glFinish = glad_glFinish; glad_debug_glFlush = glad_glFlush; glad_debug_glFlushMappedBufferRange = glad_glFlushMappedBufferRange; glad_debug_glFogCoordPointer = glad_glFogCoordPointer; glad_debug_glFogCoordd = glad_glFogCoordd; glad_debug_glFogCoorddv = glad_glFogCoorddv; glad_debug_glFogCoordf = glad_glFogCoordf; glad_debug_glFogCoordfv = glad_glFogCoordfv; glad_debug_glFogf = glad_glFogf; glad_debug_glFogfv = glad_glFogfv; glad_debug_glFogi = glad_glFogi; glad_debug_glFogiv = glad_glFogiv; glad_debug_glFramebufferRenderbuffer = glad_glFramebufferRenderbuffer; glad_debug_glFramebufferTexture1D = glad_glFramebufferTexture1D; glad_debug_glFramebufferTexture2D = glad_glFramebufferTexture2D; glad_debug_glFramebufferTexture3D = glad_glFramebufferTexture3D; glad_debug_glFramebufferTextureLayer = glad_glFramebufferTextureLayer; glad_debug_glFrontFace = glad_glFrontFace; glad_debug_glFrustum = glad_glFrustum; glad_debug_glGenBuffers = glad_glGenBuffers; glad_debug_glGenFramebuffers = glad_glGenFramebuffers; glad_debug_glGenLists = glad_glGenLists; glad_debug_glGenQueries = glad_glGenQueries; glad_debug_glGenRenderbuffers = glad_glGenRenderbuffers; glad_debug_glGenTextures = glad_glGenTextures; glad_debug_glGenVertexArrays = glad_glGenVertexArrays; glad_debug_glGenerateMipmap = glad_glGenerateMipmap; glad_debug_glGetActiveAttrib = glad_glGetActiveAttrib; glad_debug_glGetActiveUniform = glad_glGetActiveUniform; glad_debug_glGetActiveUniformBlockName = glad_glGetActiveUniformBlockName; glad_debug_glGetActiveUniformBlockiv = glad_glGetActiveUniformBlockiv; glad_debug_glGetActiveUniformName = glad_glGetActiveUniformName; glad_debug_glGetActiveUniformsiv = glad_glGetActiveUniformsiv; glad_debug_glGetAttachedShaders = glad_glGetAttachedShaders; glad_debug_glGetAttribLocation = glad_glGetAttribLocation; glad_debug_glGetBooleani_v = glad_glGetBooleani_v; glad_debug_glGetBooleanv = glad_glGetBooleanv; glad_debug_glGetBufferParameteriv = glad_glGetBufferParameteriv; glad_debug_glGetBufferPointerv = glad_glGetBufferPointerv; glad_debug_glGetBufferSubData = glad_glGetBufferSubData; glad_debug_glGetClipPlane = glad_glGetClipPlane; glad_debug_glGetCompressedTexImage = glad_glGetCompressedTexImage; glad_debug_glGetDebugMessageLog = glad_glGetDebugMessageLog; glad_debug_glGetDoublev = glad_glGetDoublev; glad_debug_glGetError = glad_glGetError; glad_debug_glGetFloatv = glad_glGetFloatv; glad_debug_glGetFragDataLocation = glad_glGetFragDataLocation; glad_debug_glGetFramebufferAttachmentParameteriv = glad_glGetFramebufferAttachmentParameteriv; glad_debug_glGetGraphicsResetStatusARB = glad_glGetGraphicsResetStatusARB; glad_debug_glGetIntegeri_v = glad_glGetIntegeri_v; glad_debug_glGetIntegerv = glad_glGetIntegerv; glad_debug_glGetLightfv = glad_glGetLightfv; glad_debug_glGetLightiv = glad_glGetLightiv; glad_debug_glGetMapdv = glad_glGetMapdv; glad_debug_glGetMapfv = glad_glGetMapfv; glad_debug_glGetMapiv = glad_glGetMapiv; glad_debug_glGetMaterialfv = glad_glGetMaterialfv; glad_debug_glGetMaterialiv = glad_glGetMaterialiv; glad_debug_glGetObjectLabel = glad_glGetObjectLabel; glad_debug_glGetObjectPtrLabel = glad_glGetObjectPtrLabel; glad_debug_glGetPixelMapfv = glad_glGetPixelMapfv; glad_debug_glGetPixelMapuiv = glad_glGetPixelMapuiv; glad_debug_glGetPixelMapusv = glad_glGetPixelMapusv; glad_debug_glGetPointerv = glad_glGetPointerv; glad_debug_glGetPolygonStipple = glad_glGetPolygonStipple; glad_debug_glGetProgramInfoLog = glad_glGetProgramInfoLog; glad_debug_glGetProgramiv = glad_glGetProgramiv; glad_debug_glGetQueryObjectiv = glad_glGetQueryObjectiv; glad_debug_glGetQueryObjectuiv = glad_glGetQueryObjectuiv; glad_debug_glGetQueryiv = glad_glGetQueryiv; glad_debug_glGetRenderbufferParameteriv = glad_glGetRenderbufferParameteriv; glad_debug_glGetShaderInfoLog = glad_glGetShaderInfoLog; glad_debug_glGetShaderSource = glad_glGetShaderSource; glad_debug_glGetShaderiv = glad_glGetShaderiv; glad_debug_glGetString = glad_glGetString; glad_debug_glGetStringi = glad_glGetStringi; glad_debug_glGetTexEnvfv = glad_glGetTexEnvfv; glad_debug_glGetTexEnviv = glad_glGetTexEnviv; glad_debug_glGetTexGendv = glad_glGetTexGendv; glad_debug_glGetTexGenfv = glad_glGetTexGenfv; glad_debug_glGetTexGeniv = glad_glGetTexGeniv; glad_debug_glGetTexImage = glad_glGetTexImage; glad_debug_glGetTexLevelParameterfv = glad_glGetTexLevelParameterfv; glad_debug_glGetTexLevelParameteriv = glad_glGetTexLevelParameteriv; glad_debug_glGetTexParameterIiv = glad_glGetTexParameterIiv; glad_debug_glGetTexParameterIuiv = glad_glGetTexParameterIuiv; glad_debug_glGetTexParameterfv = glad_glGetTexParameterfv; glad_debug_glGetTexParameteriv = glad_glGetTexParameteriv; glad_debug_glGetTransformFeedbackVarying = glad_glGetTransformFeedbackVarying; glad_debug_glGetUniformBlockIndex = glad_glGetUniformBlockIndex; glad_debug_glGetUniformIndices = glad_glGetUniformIndices; glad_debug_glGetUniformLocation = glad_glGetUniformLocation; glad_debug_glGetUniformfv = glad_glGetUniformfv; glad_debug_glGetUniformiv = glad_glGetUniformiv; glad_debug_glGetUniformuiv = glad_glGetUniformuiv; glad_debug_glGetVertexAttribIiv = glad_glGetVertexAttribIiv; glad_debug_glGetVertexAttribIuiv = glad_glGetVertexAttribIuiv; glad_debug_glGetVertexAttribPointerv = glad_glGetVertexAttribPointerv; glad_debug_glGetVertexAttribdv = glad_glGetVertexAttribdv; glad_debug_glGetVertexAttribfv = glad_glGetVertexAttribfv; glad_debug_glGetVertexAttribiv = glad_glGetVertexAttribiv; glad_debug_glGetnCompressedTexImageARB = glad_glGetnCompressedTexImageARB; glad_debug_glGetnTexImageARB = glad_glGetnTexImageARB; glad_debug_glGetnUniformdvARB = glad_glGetnUniformdvARB; glad_debug_glGetnUniformfvARB = glad_glGetnUniformfvARB; glad_debug_glGetnUniformivARB = glad_glGetnUniformivARB; glad_debug_glGetnUniformuivARB = glad_glGetnUniformuivARB; glad_debug_glHint = glad_glHint; glad_debug_glIndexMask = glad_glIndexMask; glad_debug_glIndexPointer = glad_glIndexPointer; glad_debug_glIndexd = glad_glIndexd; glad_debug_glIndexdv = glad_glIndexdv; glad_debug_glIndexf = glad_glIndexf; glad_debug_glIndexfv = glad_glIndexfv; glad_debug_glIndexi = glad_glIndexi; glad_debug_glIndexiv = glad_glIndexiv; glad_debug_glIndexs = glad_glIndexs; glad_debug_glIndexsv = glad_glIndexsv; glad_debug_glIndexub = glad_glIndexub; glad_debug_glIndexubv = glad_glIndexubv; glad_debug_glInitNames = glad_glInitNames; glad_debug_glInterleavedArrays = glad_glInterleavedArrays; glad_debug_glIsBuffer = glad_glIsBuffer; glad_debug_glIsEnabled = glad_glIsEnabled; glad_debug_glIsEnabledi = glad_glIsEnabledi; glad_debug_glIsFramebuffer = glad_glIsFramebuffer; glad_debug_glIsList = glad_glIsList; glad_debug_glIsProgram = glad_glIsProgram; glad_debug_glIsQuery = glad_glIsQuery; glad_debug_glIsRenderbuffer = glad_glIsRenderbuffer; glad_debug_glIsShader = glad_glIsShader; glad_debug_glIsTexture = glad_glIsTexture; glad_debug_glIsVertexArray = glad_glIsVertexArray; glad_debug_glLightModelf = glad_glLightModelf; glad_debug_glLightModelfv = glad_glLightModelfv; glad_debug_glLightModeli = glad_glLightModeli; glad_debug_glLightModeliv = glad_glLightModeliv; glad_debug_glLightf = glad_glLightf; glad_debug_glLightfv = glad_glLightfv; glad_debug_glLighti = glad_glLighti; glad_debug_glLightiv = glad_glLightiv; glad_debug_glLineStipple = glad_glLineStipple; glad_debug_glLineWidth = glad_glLineWidth; glad_debug_glLinkProgram = glad_glLinkProgram; glad_debug_glListBase = glad_glListBase; glad_debug_glLoadIdentity = glad_glLoadIdentity; glad_debug_glLoadMatrixd = glad_glLoadMatrixd; glad_debug_glLoadMatrixf = glad_glLoadMatrixf; glad_debug_glLoadName = glad_glLoadName; glad_debug_glLoadTransposeMatrixd = glad_glLoadTransposeMatrixd; glad_debug_glLoadTransposeMatrixf = glad_glLoadTransposeMatrixf; glad_debug_glLogicOp = glad_glLogicOp; glad_debug_glMap1d = glad_glMap1d; glad_debug_glMap1f = glad_glMap1f; glad_debug_glMap2d = glad_glMap2d; glad_debug_glMap2f = glad_glMap2f; glad_debug_glMapBuffer = glad_glMapBuffer; glad_debug_glMapBufferRange = glad_glMapBufferRange; glad_debug_glMapGrid1d = glad_glMapGrid1d; glad_debug_glMapGrid1f = glad_glMapGrid1f; glad_debug_glMapGrid2d = glad_glMapGrid2d; glad_debug_glMapGrid2f = glad_glMapGrid2f; glad_debug_glMaterialf = glad_glMaterialf; glad_debug_glMaterialfv = glad_glMaterialfv; glad_debug_glMateriali = glad_glMateriali; glad_debug_glMaterialiv = glad_glMaterialiv; glad_debug_glMatrixMode = glad_glMatrixMode; glad_debug_glMultMatrixd = glad_glMultMatrixd; glad_debug_glMultMatrixf = glad_glMultMatrixf; glad_debug_glMultTransposeMatrixd = glad_glMultTransposeMatrixd; glad_debug_glMultTransposeMatrixf = glad_glMultTransposeMatrixf; glad_debug_glMultiDrawArrays = glad_glMultiDrawArrays; glad_debug_glMultiDrawElements = glad_glMultiDrawElements; glad_debug_glMultiTexCoord1d = glad_glMultiTexCoord1d; glad_debug_glMultiTexCoord1dv = glad_glMultiTexCoord1dv; glad_debug_glMultiTexCoord1f = glad_glMultiTexCoord1f; glad_debug_glMultiTexCoord1fv = glad_glMultiTexCoord1fv; glad_debug_glMultiTexCoord1i = glad_glMultiTexCoord1i; glad_debug_glMultiTexCoord1iv = glad_glMultiTexCoord1iv; glad_debug_glMultiTexCoord1s = glad_glMultiTexCoord1s; glad_debug_glMultiTexCoord1sv = glad_glMultiTexCoord1sv; glad_debug_glMultiTexCoord2d = glad_glMultiTexCoord2d; glad_debug_glMultiTexCoord2dv = glad_glMultiTexCoord2dv; glad_debug_glMultiTexCoord2f = glad_glMultiTexCoord2f; glad_debug_glMultiTexCoord2fv = glad_glMultiTexCoord2fv; glad_debug_glMultiTexCoord2i = glad_glMultiTexCoord2i; glad_debug_glMultiTexCoord2iv = glad_glMultiTexCoord2iv; glad_debug_glMultiTexCoord2s = glad_glMultiTexCoord2s; glad_debug_glMultiTexCoord2sv = glad_glMultiTexCoord2sv; glad_debug_glMultiTexCoord3d = glad_glMultiTexCoord3d; glad_debug_glMultiTexCoord3dv = glad_glMultiTexCoord3dv; glad_debug_glMultiTexCoord3f = glad_glMultiTexCoord3f; glad_debug_glMultiTexCoord3fv = glad_glMultiTexCoord3fv; glad_debug_glMultiTexCoord3i = glad_glMultiTexCoord3i; glad_debug_glMultiTexCoord3iv = glad_glMultiTexCoord3iv; glad_debug_glMultiTexCoord3s = glad_glMultiTexCoord3s; glad_debug_glMultiTexCoord3sv = glad_glMultiTexCoord3sv; glad_debug_glMultiTexCoord4d = glad_glMultiTexCoord4d; glad_debug_glMultiTexCoord4dv = glad_glMultiTexCoord4dv; glad_debug_glMultiTexCoord4f = glad_glMultiTexCoord4f; glad_debug_glMultiTexCoord4fv = glad_glMultiTexCoord4fv; glad_debug_glMultiTexCoord4i = glad_glMultiTexCoord4i; glad_debug_glMultiTexCoord4iv = glad_glMultiTexCoord4iv; glad_debug_glMultiTexCoord4s = glad_glMultiTexCoord4s; glad_debug_glMultiTexCoord4sv = glad_glMultiTexCoord4sv; glad_debug_glNewList = glad_glNewList; glad_debug_glNormal3b = glad_glNormal3b; glad_debug_glNormal3bv = glad_glNormal3bv; glad_debug_glNormal3d = glad_glNormal3d; glad_debug_glNormal3dv = glad_glNormal3dv; glad_debug_glNormal3f = glad_glNormal3f; glad_debug_glNormal3fv = glad_glNormal3fv; glad_debug_glNormal3i = glad_glNormal3i; glad_debug_glNormal3iv = glad_glNormal3iv; glad_debug_glNormal3s = glad_glNormal3s; glad_debug_glNormal3sv = glad_glNormal3sv; glad_debug_glNormalPointer = glad_glNormalPointer; glad_debug_glObjectLabel = glad_glObjectLabel; glad_debug_glObjectPtrLabel = glad_glObjectPtrLabel; glad_debug_glOrtho = glad_glOrtho; glad_debug_glPassThrough = glad_glPassThrough; glad_debug_glPixelMapfv = glad_glPixelMapfv; glad_debug_glPixelMapuiv = glad_glPixelMapuiv; glad_debug_glPixelMapusv = glad_glPixelMapusv; glad_debug_glPixelStoref = glad_glPixelStoref; glad_debug_glPixelStorei = glad_glPixelStorei; glad_debug_glPixelTransferf = glad_glPixelTransferf; glad_debug_glPixelTransferi = glad_glPixelTransferi; glad_debug_glPixelZoom = glad_glPixelZoom; glad_debug_glPointParameterf = glad_glPointParameterf; glad_debug_glPointParameterfv = glad_glPointParameterfv; glad_debug_glPointParameteri = glad_glPointParameteri; glad_debug_glPointParameteriv = glad_glPointParameteriv; glad_debug_glPointSize = glad_glPointSize; glad_debug_glPolygonMode = glad_glPolygonMode; glad_debug_glPolygonOffset = glad_glPolygonOffset; glad_debug_glPolygonStipple = glad_glPolygonStipple; glad_debug_glPopAttrib = glad_glPopAttrib; glad_debug_glPopClientAttrib = glad_glPopClientAttrib; glad_debug_glPopDebugGroup = glad_glPopDebugGroup; glad_debug_glPopMatrix = glad_glPopMatrix; glad_debug_glPopName = glad_glPopName; glad_debug_glPrimitiveRestartIndex = glad_glPrimitiveRestartIndex; glad_debug_glPrioritizeTextures = glad_glPrioritizeTextures; glad_debug_glPushAttrib = glad_glPushAttrib; glad_debug_glPushClientAttrib = glad_glPushClientAttrib; glad_debug_glPushDebugGroup = glad_glPushDebugGroup; glad_debug_glPushMatrix = glad_glPushMatrix; glad_debug_glPushName = glad_glPushName; glad_debug_glRasterPos2d = glad_glRasterPos2d; glad_debug_glRasterPos2dv = glad_glRasterPos2dv; glad_debug_glRasterPos2f = glad_glRasterPos2f; glad_debug_glRasterPos2fv = glad_glRasterPos2fv; glad_debug_glRasterPos2i = glad_glRasterPos2i; glad_debug_glRasterPos2iv = glad_glRasterPos2iv; glad_debug_glRasterPos2s = glad_glRasterPos2s; glad_debug_glRasterPos2sv = glad_glRasterPos2sv; glad_debug_glRasterPos3d = glad_glRasterPos3d; glad_debug_glRasterPos3dv = glad_glRasterPos3dv; glad_debug_glRasterPos3f = glad_glRasterPos3f; glad_debug_glRasterPos3fv = glad_glRasterPos3fv; glad_debug_glRasterPos3i = glad_glRasterPos3i; glad_debug_glRasterPos3iv = glad_glRasterPos3iv; glad_debug_glRasterPos3s = glad_glRasterPos3s; glad_debug_glRasterPos3sv = glad_glRasterPos3sv; glad_debug_glRasterPos4d = glad_glRasterPos4d; glad_debug_glRasterPos4dv = glad_glRasterPos4dv; glad_debug_glRasterPos4f = glad_glRasterPos4f; glad_debug_glRasterPos4fv = glad_glRasterPos4fv; glad_debug_glRasterPos4i = glad_glRasterPos4i; glad_debug_glRasterPos4iv = glad_glRasterPos4iv; glad_debug_glRasterPos4s = glad_glRasterPos4s; glad_debug_glRasterPos4sv = glad_glRasterPos4sv; glad_debug_glReadBuffer = glad_glReadBuffer; glad_debug_glReadPixels = glad_glReadPixels; glad_debug_glReadnPixelsARB = glad_glReadnPixelsARB; glad_debug_glRectd = glad_glRectd; glad_debug_glRectdv = glad_glRectdv; glad_debug_glRectf = glad_glRectf; glad_debug_glRectfv = glad_glRectfv; glad_debug_glRecti = glad_glRecti; glad_debug_glRectiv = glad_glRectiv; glad_debug_glRects = glad_glRects; glad_debug_glRectsv = glad_glRectsv; glad_debug_glRenderMode = glad_glRenderMode; glad_debug_glRenderbufferStorage = glad_glRenderbufferStorage; glad_debug_glRenderbufferStorageMultisample = glad_glRenderbufferStorageMultisample; glad_debug_glRotated = glad_glRotated; glad_debug_glRotatef = glad_glRotatef; glad_debug_glSampleCoverage = glad_glSampleCoverage; glad_debug_glSampleCoverageARB = glad_glSampleCoverageARB; glad_debug_glScaled = glad_glScaled; glad_debug_glScalef = glad_glScalef; glad_debug_glScissor = glad_glScissor; glad_debug_glSecondaryColor3b = glad_glSecondaryColor3b; glad_debug_glSecondaryColor3bv = glad_glSecondaryColor3bv; glad_debug_glSecondaryColor3d = glad_glSecondaryColor3d; glad_debug_glSecondaryColor3dv = glad_glSecondaryColor3dv; glad_debug_glSecondaryColor3f = glad_glSecondaryColor3f; glad_debug_glSecondaryColor3fv = glad_glSecondaryColor3fv; glad_debug_glSecondaryColor3i = glad_glSecondaryColor3i; glad_debug_glSecondaryColor3iv = glad_glSecondaryColor3iv; glad_debug_glSecondaryColor3s = glad_glSecondaryColor3s; glad_debug_glSecondaryColor3sv = glad_glSecondaryColor3sv; glad_debug_glSecondaryColor3ub = glad_glSecondaryColor3ub; glad_debug_glSecondaryColor3ubv = glad_glSecondaryColor3ubv; glad_debug_glSecondaryColor3ui = glad_glSecondaryColor3ui; glad_debug_glSecondaryColor3uiv = glad_glSecondaryColor3uiv; glad_debug_glSecondaryColor3us = glad_glSecondaryColor3us; glad_debug_glSecondaryColor3usv = glad_glSecondaryColor3usv; glad_debug_glSecondaryColorPointer = glad_glSecondaryColorPointer; glad_debug_glSelectBuffer = glad_glSelectBuffer; glad_debug_glShadeModel = glad_glShadeModel; glad_debug_glShaderSource = glad_glShaderSource; glad_debug_glStencilFunc = glad_glStencilFunc; glad_debug_glStencilFuncSeparate = glad_glStencilFuncSeparate; glad_debug_glStencilMask = glad_glStencilMask; glad_debug_glStencilMaskSeparate = glad_glStencilMaskSeparate; glad_debug_glStencilOp = glad_glStencilOp; glad_debug_glStencilOpSeparate = glad_glStencilOpSeparate; glad_debug_glTexBuffer = glad_glTexBuffer; glad_debug_glTexCoord1d = glad_glTexCoord1d; glad_debug_glTexCoord1dv = glad_glTexCoord1dv; glad_debug_glTexCoord1f = glad_glTexCoord1f; glad_debug_glTexCoord1fv = glad_glTexCoord1fv; glad_debug_glTexCoord1i = glad_glTexCoord1i; glad_debug_glTexCoord1iv = glad_glTexCoord1iv; glad_debug_glTexCoord1s = glad_glTexCoord1s; glad_debug_glTexCoord1sv = glad_glTexCoord1sv; glad_debug_glTexCoord2d = glad_glTexCoord2d; glad_debug_glTexCoord2dv = glad_glTexCoord2dv; glad_debug_glTexCoord2f = glad_glTexCoord2f; glad_debug_glTexCoord2fv = glad_glTexCoord2fv; glad_debug_glTexCoord2i = glad_glTexCoord2i; glad_debug_glTexCoord2iv = glad_glTexCoord2iv; glad_debug_glTexCoord2s = glad_glTexCoord2s; glad_debug_glTexCoord2sv = glad_glTexCoord2sv; glad_debug_glTexCoord3d = glad_glTexCoord3d; glad_debug_glTexCoord3dv = glad_glTexCoord3dv; glad_debug_glTexCoord3f = glad_glTexCoord3f; glad_debug_glTexCoord3fv = glad_glTexCoord3fv; glad_debug_glTexCoord3i = glad_glTexCoord3i; glad_debug_glTexCoord3iv = glad_glTexCoord3iv; glad_debug_glTexCoord3s = glad_glTexCoord3s; glad_debug_glTexCoord3sv = glad_glTexCoord3sv; glad_debug_glTexCoord4d = glad_glTexCoord4d; glad_debug_glTexCoord4dv = glad_glTexCoord4dv; glad_debug_glTexCoord4f = glad_glTexCoord4f; glad_debug_glTexCoord4fv = glad_glTexCoord4fv; glad_debug_glTexCoord4i = glad_glTexCoord4i; glad_debug_glTexCoord4iv = glad_glTexCoord4iv; glad_debug_glTexCoord4s = glad_glTexCoord4s; glad_debug_glTexCoord4sv = glad_glTexCoord4sv; glad_debug_glTexCoordPointer = glad_glTexCoordPointer; glad_debug_glTexEnvf = glad_glTexEnvf; glad_debug_glTexEnvfv = glad_glTexEnvfv; glad_debug_glTexEnvi = glad_glTexEnvi; glad_debug_glTexEnviv = glad_glTexEnviv; glad_debug_glTexGend = glad_glTexGend; glad_debug_glTexGendv = glad_glTexGendv; glad_debug_glTexGenf = glad_glTexGenf; glad_debug_glTexGenfv = glad_glTexGenfv; glad_debug_glTexGeni = glad_glTexGeni; glad_debug_glTexGeniv = glad_glTexGeniv; glad_debug_glTexImage1D = glad_glTexImage1D; glad_debug_glTexImage2D = glad_glTexImage2D; glad_debug_glTexImage3D = glad_glTexImage3D; glad_debug_glTexParameterIiv = glad_glTexParameterIiv; glad_debug_glTexParameterIuiv = glad_glTexParameterIuiv; glad_debug_glTexParameterf = glad_glTexParameterf; glad_debug_glTexParameterfv = glad_glTexParameterfv; glad_debug_glTexParameteri = glad_glTexParameteri; glad_debug_glTexParameteriv = glad_glTexParameteriv; glad_debug_glTexStorage1D = glad_glTexStorage1D; glad_debug_glTexStorage2D = glad_glTexStorage2D; glad_debug_glTexStorage3D = glad_glTexStorage3D; glad_debug_glTexSubImage1D = glad_glTexSubImage1D; glad_debug_glTexSubImage2D = glad_glTexSubImage2D; glad_debug_glTexSubImage3D = glad_glTexSubImage3D; glad_debug_glTransformFeedbackVaryings = glad_glTransformFeedbackVaryings; glad_debug_glTranslated = glad_glTranslated; glad_debug_glTranslatef = glad_glTranslatef; glad_debug_glUniform1f = glad_glUniform1f; glad_debug_glUniform1fv = glad_glUniform1fv; glad_debug_glUniform1i = glad_glUniform1i; glad_debug_glUniform1iv = glad_glUniform1iv; glad_debug_glUniform1ui = glad_glUniform1ui; glad_debug_glUniform1uiv = glad_glUniform1uiv; glad_debug_glUniform2f = glad_glUniform2f; glad_debug_glUniform2fv = glad_glUniform2fv; glad_debug_glUniform2i = glad_glUniform2i; glad_debug_glUniform2iv = glad_glUniform2iv; glad_debug_glUniform2ui = glad_glUniform2ui; glad_debug_glUniform2uiv = glad_glUniform2uiv; glad_debug_glUniform3f = glad_glUniform3f; glad_debug_glUniform3fv = glad_glUniform3fv; glad_debug_glUniform3i = glad_glUniform3i; glad_debug_glUniform3iv = glad_glUniform3iv; glad_debug_glUniform3ui = glad_glUniform3ui; glad_debug_glUniform3uiv = glad_glUniform3uiv; glad_debug_glUniform4f = glad_glUniform4f; glad_debug_glUniform4fv = glad_glUniform4fv; glad_debug_glUniform4i = glad_glUniform4i; glad_debug_glUniform4iv = glad_glUniform4iv; glad_debug_glUniform4ui = glad_glUniform4ui; glad_debug_glUniform4uiv = glad_glUniform4uiv; glad_debug_glUniformBlockBinding = glad_glUniformBlockBinding; glad_debug_glUniformMatrix2fv = glad_glUniformMatrix2fv; glad_debug_glUniformMatrix2x3fv = glad_glUniformMatrix2x3fv; glad_debug_glUniformMatrix2x4fv = glad_glUniformMatrix2x4fv; glad_debug_glUniformMatrix3fv = glad_glUniformMatrix3fv; glad_debug_glUniformMatrix3x2fv = glad_glUniformMatrix3x2fv; glad_debug_glUniformMatrix3x4fv = glad_glUniformMatrix3x4fv; glad_debug_glUniformMatrix4fv = glad_glUniformMatrix4fv; glad_debug_glUniformMatrix4x2fv = glad_glUniformMatrix4x2fv; glad_debug_glUniformMatrix4x3fv = glad_glUniformMatrix4x3fv; glad_debug_glUnmapBuffer = glad_glUnmapBuffer; glad_debug_glUseProgram = glad_glUseProgram; glad_debug_glValidateProgram = glad_glValidateProgram; glad_debug_glVertex2d = glad_glVertex2d; glad_debug_glVertex2dv = glad_glVertex2dv; glad_debug_glVertex2f = glad_glVertex2f; glad_debug_glVertex2fv = glad_glVertex2fv; glad_debug_glVertex2i = glad_glVertex2i; glad_debug_glVertex2iv = glad_glVertex2iv; glad_debug_glVertex2s = glad_glVertex2s; glad_debug_glVertex2sv = glad_glVertex2sv; glad_debug_glVertex3d = glad_glVertex3d; glad_debug_glVertex3dv = glad_glVertex3dv; glad_debug_glVertex3f = glad_glVertex3f; glad_debug_glVertex3fv = glad_glVertex3fv; glad_debug_glVertex3i = glad_glVertex3i; glad_debug_glVertex3iv = glad_glVertex3iv; glad_debug_glVertex3s = glad_glVertex3s; glad_debug_glVertex3sv = glad_glVertex3sv; glad_debug_glVertex4d = glad_glVertex4d; glad_debug_glVertex4dv = glad_glVertex4dv; glad_debug_glVertex4f = glad_glVertex4f; glad_debug_glVertex4fv = glad_glVertex4fv; glad_debug_glVertex4i = glad_glVertex4i; glad_debug_glVertex4iv = glad_glVertex4iv; glad_debug_glVertex4s = glad_glVertex4s; glad_debug_glVertex4sv = glad_glVertex4sv; glad_debug_glVertexAttrib1d = glad_glVertexAttrib1d; glad_debug_glVertexAttrib1dv = glad_glVertexAttrib1dv; glad_debug_glVertexAttrib1f = glad_glVertexAttrib1f; glad_debug_glVertexAttrib1fv = glad_glVertexAttrib1fv; glad_debug_glVertexAttrib1s = glad_glVertexAttrib1s; glad_debug_glVertexAttrib1sv = glad_glVertexAttrib1sv; glad_debug_glVertexAttrib2d = glad_glVertexAttrib2d; glad_debug_glVertexAttrib2dv = glad_glVertexAttrib2dv; glad_debug_glVertexAttrib2f = glad_glVertexAttrib2f; glad_debug_glVertexAttrib2fv = glad_glVertexAttrib2fv; glad_debug_glVertexAttrib2s = glad_glVertexAttrib2s; glad_debug_glVertexAttrib2sv = glad_glVertexAttrib2sv; glad_debug_glVertexAttrib3d = glad_glVertexAttrib3d; glad_debug_glVertexAttrib3dv = glad_glVertexAttrib3dv; glad_debug_glVertexAttrib3f = glad_glVertexAttrib3f; glad_debug_glVertexAttrib3fv = glad_glVertexAttrib3fv; glad_debug_glVertexAttrib3s = glad_glVertexAttrib3s; glad_debug_glVertexAttrib3sv = glad_glVertexAttrib3sv; glad_debug_glVertexAttrib4Nbv = glad_glVertexAttrib4Nbv; glad_debug_glVertexAttrib4Niv = glad_glVertexAttrib4Niv; glad_debug_glVertexAttrib4Nsv = glad_glVertexAttrib4Nsv; glad_debug_glVertexAttrib4Nub = glad_glVertexAttrib4Nub; glad_debug_glVertexAttrib4Nubv = glad_glVertexAttrib4Nubv; glad_debug_glVertexAttrib4Nuiv = glad_glVertexAttrib4Nuiv; glad_debug_glVertexAttrib4Nusv = glad_glVertexAttrib4Nusv; glad_debug_glVertexAttrib4bv = glad_glVertexAttrib4bv; glad_debug_glVertexAttrib4d = glad_glVertexAttrib4d; glad_debug_glVertexAttrib4dv = glad_glVertexAttrib4dv; glad_debug_glVertexAttrib4f = glad_glVertexAttrib4f; glad_debug_glVertexAttrib4fv = glad_glVertexAttrib4fv; glad_debug_glVertexAttrib4iv = glad_glVertexAttrib4iv; glad_debug_glVertexAttrib4s = glad_glVertexAttrib4s; glad_debug_glVertexAttrib4sv = glad_glVertexAttrib4sv; glad_debug_glVertexAttrib4ubv = glad_glVertexAttrib4ubv; glad_debug_glVertexAttrib4uiv = glad_glVertexAttrib4uiv; glad_debug_glVertexAttrib4usv = glad_glVertexAttrib4usv; glad_debug_glVertexAttribDivisorARB = glad_glVertexAttribDivisorARB; glad_debug_glVertexAttribI1i = glad_glVertexAttribI1i; glad_debug_glVertexAttribI1iv = glad_glVertexAttribI1iv; glad_debug_glVertexAttribI1ui = glad_glVertexAttribI1ui; glad_debug_glVertexAttribI1uiv = glad_glVertexAttribI1uiv; glad_debug_glVertexAttribI2i = glad_glVertexAttribI2i; glad_debug_glVertexAttribI2iv = glad_glVertexAttribI2iv; glad_debug_glVertexAttribI2ui = glad_glVertexAttribI2ui; glad_debug_glVertexAttribI2uiv = glad_glVertexAttribI2uiv; glad_debug_glVertexAttribI3i = glad_glVertexAttribI3i; glad_debug_glVertexAttribI3iv = glad_glVertexAttribI3iv; glad_debug_glVertexAttribI3ui = glad_glVertexAttribI3ui; glad_debug_glVertexAttribI3uiv = glad_glVertexAttribI3uiv; glad_debug_glVertexAttribI4bv = glad_glVertexAttribI4bv; glad_debug_glVertexAttribI4i = glad_glVertexAttribI4i; glad_debug_glVertexAttribI4iv = glad_glVertexAttribI4iv; glad_debug_glVertexAttribI4sv = glad_glVertexAttribI4sv; glad_debug_glVertexAttribI4ubv = glad_glVertexAttribI4ubv; glad_debug_glVertexAttribI4ui = glad_glVertexAttribI4ui; glad_debug_glVertexAttribI4uiv = glad_glVertexAttribI4uiv; glad_debug_glVertexAttribI4usv = glad_glVertexAttribI4usv; glad_debug_glVertexAttribIPointer = glad_glVertexAttribIPointer; glad_debug_glVertexAttribPointer = glad_glVertexAttribPointer; glad_debug_glVertexPointer = glad_glVertexPointer; glad_debug_glViewport = glad_glViewport; glad_debug_glWindowPos2d = glad_glWindowPos2d; glad_debug_glWindowPos2dv = glad_glWindowPos2dv; glad_debug_glWindowPos2f = glad_glWindowPos2f; glad_debug_glWindowPos2fv = glad_glWindowPos2fv; glad_debug_glWindowPos2i = glad_glWindowPos2i; glad_debug_glWindowPos2iv = glad_glWindowPos2iv; glad_debug_glWindowPos2s = glad_glWindowPos2s; glad_debug_glWindowPos2sv = glad_glWindowPos2sv; glad_debug_glWindowPos3d = glad_glWindowPos3d; glad_debug_glWindowPos3dv = glad_glWindowPos3dv; glad_debug_glWindowPos3f = glad_glWindowPos3f; glad_debug_glWindowPos3fv = glad_glWindowPos3fv; glad_debug_glWindowPos3i = glad_glWindowPos3i; glad_debug_glWindowPos3iv = glad_glWindowPos3iv; glad_debug_glWindowPos3s = glad_glWindowPos3s; glad_debug_glWindowPos3sv = glad_glWindowPos3sv; } #ifdef __cplusplus } #endif #endif /* GLAD_GL_IMPLEMENTATION */ kitty-0.41.1/kitty/gl.c0000664000175000017510000002661014773370543014226 0ustar nileshnilesh/* * gl.c * Copyright (C) 2019 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "gl.h" #include #include #include "glfw-wrapper.h" #include "state.h" // GL setup and error handling {{{ static void check_for_gl_error(void UNUSED *ret, const char *name, GLADapiproc UNUSED funcptr, int UNUSED len_args, ...) { #define f(msg) fatal("OpenGL error: %s (calling function: %s)", msg, name); break; GLenum code = glad_glGetError(); switch(code) { case GL_NO_ERROR: break; case GL_INVALID_ENUM: f("An enum value is invalid (GL_INVALID_ENUM)"); case GL_INVALID_VALUE: f("An numeric value is invalid (GL_INVALID_VALUE)"); case GL_INVALID_OPERATION: f("This operation is invalid (GL_INVALID_OPERATION)"); case GL_INVALID_FRAMEBUFFER_OPERATION: f("The framebuffer object is not complete (GL_INVALID_FRAMEBUFFER_OPERATION)"); case GL_OUT_OF_MEMORY: f("There is not enough memory left to execute the command. (GL_OUT_OF_MEMORY)"); case GL_STACK_UNDERFLOW: f("An attempt has been made to perform an operation that would cause an internal stack to underflow. (GL_STACK_UNDERFLOW)"); case GL_STACK_OVERFLOW: f("An attempt has been made to perform an operation that would cause an internal stack to overflow. (GL_STACK_OVERFLOW)"); default: fatal("An unknown OpenGL error occurred with code: %d (calling function: %s)", code, name); break; } } const char* gl_version_string(void) { static char buf[256]; int gl_major = GLAD_VERSION_MAJOR(global_state.gl_version); int gl_minor = GLAD_VERSION_MINOR(global_state.gl_version); const char *gvs = (const char*)glGetString(GL_VERSION); snprintf(buf, sizeof(buf), "'%s' Detected version: %d.%d", gvs, gl_major, gl_minor); return buf; } void gl_init(void) { static bool glad_loaded = false; if (!glad_loaded) { global_state.gl_version = gladLoadGL(glfwGetProcAddress); if (!global_state.gl_version) { fatal("Loading the OpenGL library failed"); } if (!global_state.debug_rendering) { gladUninstallGLDebug(); } gladSetGLPostCallback(check_for_gl_error); #define ARB_TEST(name) \ if (!GLAD_GL_ARB_##name) { \ fatal("The OpenGL driver on this system is missing the required extension: ARB_%s", #name); \ } ARB_TEST(texture_storage); #undef ARB_TEST glad_loaded = true; int gl_major = GLAD_VERSION_MAJOR(global_state.gl_version); int gl_minor = GLAD_VERSION_MINOR(global_state.gl_version); if (global_state.debug_rendering) printf("[%.3f] GL version string: %s\n", monotonic_t_to_s_double(monotonic()), gl_version_string()); if (gl_major < OPENGL_REQUIRED_VERSION_MAJOR || (gl_major == OPENGL_REQUIRED_VERSION_MAJOR && gl_minor < OPENGL_REQUIRED_VERSION_MINOR)) { fatal("OpenGL version is %d.%d, version >= %d.%d required for kitty", gl_major, gl_minor, OPENGL_REQUIRED_VERSION_MAJOR, OPENGL_REQUIRED_VERSION_MINOR); } } } void update_surface_size(int w, int h, GLuint offscreen_texture_id) { glViewport(0, 0, w, h); if (offscreen_texture_id) { glBindTexture(GL_TEXTURE_2D, offscreen_texture_id); glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); } } void free_texture(GLuint *tex_id) { glDeleteTextures(1, tex_id); *tex_id = 0; } void free_framebuffer(GLuint *fb_id) { glDeleteFramebuffers(1, fb_id); *fb_id = 0; } // }}} // Programs {{{ static Program programs[64] = {{0}}; GLuint compile_shaders(GLenum shader_type, GLsizei count, const GLchar * const * source) { GLuint shader_id = glCreateShader(shader_type); glShaderSource(shader_id, count, source, NULL); glCompileShader(shader_id); GLint ret = GL_FALSE; glGetShaderiv(shader_id, GL_COMPILE_STATUS, &ret); if (ret != GL_TRUE) { GLsizei len; static char glbuf[4096]; glGetShaderInfoLog(shader_id, sizeof(glbuf), &len, glbuf); glDeleteShader(shader_id); const char *shader_type_name = "unknown_type"; switch(shader_type) { case GL_VERTEX_SHADER: shader_type_name = "vertex"; break; case GL_FRAGMENT_SHADER: shader_type_name = "fragment"; break; } PyErr_Format(PyExc_ValueError, "Failed to compile GLSL %s shader:\n%s", shader_type_name, glbuf); return 0; } return shader_id; } Program* program_ptr(int program) { return programs + (size_t)program; } GLuint program_id(int program) { return programs[program].id; } void init_uniforms(int program) { Program *p = programs + program; glGetProgramiv(p->id, GL_ACTIVE_UNIFORMS, &(p->num_of_uniforms)); for (GLint i = 0; i < p->num_of_uniforms; i++) { Uniform *u = p->uniforms + i; glGetActiveUniform(p->id, (GLuint)i, sizeof(u->name)/sizeof(u->name[0]), NULL, &(u->size), &(u->type), u->name); char *l = strchr(u->name, '['); if (l) *l = 0; u->location = glGetUniformLocation(p->id, u->name); u->idx = i; } } GLint get_uniform_location(int program, const char *name) { Program *p = programs + program; const size_t n = strlen(name) + 1; for (GLint i = 0; i < p->num_of_uniforms; i++) { Uniform *u = p->uniforms + i; if (strncmp(u->name, name, n) == 0) return u->location; } return -1; } GLint get_uniform_information(int program, const char *name, GLenum information_type) { GLint q; GLuint t; const char* names[] = {""}; names[0] = name; GLuint pid = program_id(program); glGetUniformIndices(pid, 1, (void*)names, &t); glGetActiveUniformsiv(pid, 1, &t, information_type, &q); return q; } GLint attrib_location(int program, const char *name) { GLint ans = glGetAttribLocation(programs[program].id, name); return ans; } GLuint block_index(int program, const char *name) { GLuint ans = glGetUniformBlockIndex(programs[program].id, name); if (ans == GL_INVALID_INDEX) { fatal("Could not find block index"); } return ans; } GLint block_size(int program, GLuint block_index) { GLint ans; glGetActiveUniformBlockiv(programs[program].id, block_index, GL_UNIFORM_BLOCK_DATA_SIZE, &ans); return ans; } void bind_program(int program) { glUseProgram(programs[program].id); } void unbind_program(void) { glUseProgram(0); } // }}} // Buffers {{{ typedef struct { GLuint id; GLsizeiptr size; GLenum usage; } Buffer; static Buffer buffers[MAX_CHILDREN * 6 + 4] = {{0}}; static ssize_t create_buffer(GLenum usage) { GLuint buffer_id; glGenBuffers(1, &buffer_id); for (size_t i = 0; i < sizeof(buffers)/sizeof(buffers[0]); i++) { if (buffers[i].id == 0) { buffers[i].id = buffer_id; buffers[i].size = 0; buffers[i].usage = usage; return i; } } glDeleteBuffers(1, &buffer_id); fatal("Too many buffers"); return -1; } static void delete_buffer(ssize_t buf_idx) { glDeleteBuffers(1, &(buffers[buf_idx].id)); buffers[buf_idx].id = 0; buffers[buf_idx].size = 0; } static GLuint bind_buffer(ssize_t buf_idx) { glBindBuffer(buffers[buf_idx].usage, buffers[buf_idx].id); return buffers[buf_idx].id; } static void unbind_buffer(ssize_t buf_idx) { glBindBuffer(buffers[buf_idx].usage, 0); } static void alloc_buffer(ssize_t idx, GLsizeiptr size, GLenum usage) { Buffer *b = buffers + idx; if (b->size == size) return; b->size = size; glBufferData(b->usage, size, NULL, usage); } static void* map_buffer(ssize_t idx, GLenum access) { void *ans = glMapBuffer(buffers[idx].usage, access); return ans; } static void unmap_buffer(ssize_t idx) { glUnmapBuffer(buffers[idx].usage); } // }}} // Vertex Array Objects (VAO) {{{ typedef struct { GLuint id; size_t num_buffers; ssize_t buffers[10]; } VAO; static VAO vaos[4*MAX_CHILDREN + 10] = {{0}}; ssize_t create_vao(void) { GLuint vao_id; glGenVertexArrays(1, &vao_id); for (size_t i = 0; i < sizeof(vaos)/sizeof(vaos[0]); i++) { if (!vaos[i].id) { vaos[i].id = vao_id; vaos[i].num_buffers = 0; glBindVertexArray(vao_id); return i; } } glDeleteVertexArrays(1, &vao_id); fatal("Too many VAOs"); return -1; } size_t add_buffer_to_vao(ssize_t vao_idx, GLenum usage) { VAO* vao = vaos + vao_idx; if (vao->num_buffers >= sizeof(vao->buffers) / sizeof(vao->buffers[0])) { fatal("Too many buffers in a single VAO"); } ssize_t buf = create_buffer(usage); vao->buffers[vao->num_buffers++] = buf; return vao->num_buffers - 1; } static void add_located_attribute_to_vao(ssize_t vao_idx, GLint aloc, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor) { VAO *vao = vaos + vao_idx; if (!vao->num_buffers) fatal("You must create a buffer for this attribute first"); ssize_t buf = vao->buffers[vao->num_buffers - 1]; bind_buffer(buf); glEnableVertexAttribArray(aloc); switch(data_type) { case GL_BYTE: case GL_UNSIGNED_BYTE: case GL_SHORT: case GL_UNSIGNED_SHORT: case GL_INT: case GL_UNSIGNED_INT: glVertexAttribIPointer(aloc, size, data_type, stride, offset); break; default: glVertexAttribPointer(aloc, size, data_type, GL_FALSE, stride, offset); break; } if (divisor) { glVertexAttribDivisorARB(aloc, divisor); } unbind_buffer(buf); } void add_attribute_to_vao(int p, ssize_t vao_idx, const char *name, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor) { GLint aloc = attrib_location(p, name); if (aloc == -1) fatal("No attribute named: %s found in this program", name); add_located_attribute_to_vao(vao_idx, aloc, size, data_type, stride, offset, divisor); } void remove_vao(ssize_t vao_idx) { VAO *vao = vaos + vao_idx; while (vao->num_buffers) { vao->num_buffers--; delete_buffer(vao->buffers[vao->num_buffers]); } glDeleteVertexArrays(1, &(vao->id)); vaos[vao_idx].id = 0; } void bind_vertex_array(ssize_t vao_idx) { glBindVertexArray(vaos[vao_idx].id); } void unbind_vertex_array(void) { glBindVertexArray(0); } ssize_t alloc_vao_buffer(ssize_t vao_idx, GLsizeiptr size, size_t bufnum, GLenum usage) { ssize_t buf_idx = vaos[vao_idx].buffers[bufnum]; bind_buffer(buf_idx); alloc_buffer(buf_idx, size, usage); return buf_idx; } void* map_vao_buffer(ssize_t vao_idx, size_t bufnum, GLenum access) { ssize_t buf_idx = vaos[vao_idx].buffers[bufnum]; bind_buffer(buf_idx); return map_buffer(buf_idx, access); } void* alloc_and_map_vao_buffer(ssize_t vao_idx, GLsizeiptr size, size_t bufnum, GLenum usage, GLenum access) { ssize_t buf_idx = alloc_vao_buffer(vao_idx, size, bufnum, usage); return map_buffer(buf_idx, access); } void bind_vao_uniform_buffer(ssize_t vao_idx, size_t bufnum, GLuint block_index) { ssize_t buf_idx = vaos[vao_idx].buffers[bufnum]; glBindBufferBase(GL_UNIFORM_BUFFER, block_index, buffers[buf_idx].id); } void unmap_vao_buffer(ssize_t vao_idx, size_t bufnum) { ssize_t buf_idx = vaos[vao_idx].buffers[bufnum]; unmap_buffer(buf_idx); unbind_buffer(buf_idx); } // }}} kitty-0.41.1/kitty/gl.h0000664000175000017510000000371314773370543014232 0ustar nileshnilesh/* * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" #include "gl-wrapper.h" typedef struct { GLint size, index; } UniformBlock; typedef struct { GLint offset, stride, size; } ArrayInformation; typedef struct { char name[256]; GLint size, location, idx; GLenum type; } Uniform; typedef struct { GLuint id; Uniform uniforms[256]; GLint num_of_uniforms; } Program; void gl_init(void); const char* gl_version_string(void); void update_surface_size(int w, int h, GLuint offscreen_texture_id); void free_texture(GLuint *tex_id); void free_framebuffer(GLuint *fb_id); void remove_vao(ssize_t vao_idx); void init_uniforms(int program); GLuint program_id(int program); Program* program_ptr(int program); GLuint block_index(int program, const char *name); GLint block_size(int program, GLuint block_index); GLint get_uniform_location(int program, const char *name); GLint get_uniform_information(int program, const char *name, GLenum information_type); GLint attrib_location(int program, const char *name); ssize_t create_vao(void); size_t add_buffer_to_vao(ssize_t vao_idx, GLenum usage); void add_attribute_to_vao(int p, ssize_t vao_idx, const char *name, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor); ssize_t alloc_vao_buffer(ssize_t vao_idx, GLsizeiptr size, size_t bufnum, GLenum usage); void* alloc_and_map_vao_buffer(ssize_t vao_idx, GLsizeiptr size, size_t bufnum, GLenum usage, GLenum access); void unmap_vao_buffer(ssize_t vao_idx, size_t bufnum); void* map_vao_buffer(ssize_t vao_idx, size_t bufnum, GLenum access); void bind_program(int program); void bind_vertex_array(ssize_t vao_idx); void bind_vao_uniform_buffer(ssize_t vao_idx, size_t bufnum, GLuint block_index); void unbind_vertex_array(void); void unbind_program(void); GLuint compile_shaders(GLenum shader_type, GLsizei count, const GLchar * const * string); kitty-0.41.1/kitty/glfw-wrapper.c0000664000175000017510000010445314773370543016243 0ustar nileshnilesh // generated by glfw.py DO NOT edit #define GFW_EXTERN #include "data-types.h" #include "glfw-wrapper.h" #include static void* handle = NULL; #define fail(msg, ...) { snprintf(buf, sizeof(buf), msg, __VA_ARGS__); return buf; } const char* load_glfw(const char* path) { static char buf[2048]; handle = dlopen(path, RTLD_LAZY); if (handle == NULL) fail("Failed to dlopen %s with error: %s", path, dlerror()); dlerror(); *(void **) (&glfwInit_impl) = dlsym(handle, "glfwInit"); if (glfwInit_impl == NULL) fail("Failed to load glfw function glfwInit with error: %s", dlerror()); *(void **) (&glfwRunMainLoop_impl) = dlsym(handle, "glfwRunMainLoop"); if (glfwRunMainLoop_impl == NULL) fail("Failed to load glfw function glfwRunMainLoop with error: %s", dlerror()); *(void **) (&glfwStopMainLoop_impl) = dlsym(handle, "glfwStopMainLoop"); if (glfwStopMainLoop_impl == NULL) fail("Failed to load glfw function glfwStopMainLoop with error: %s", dlerror()); *(void **) (&glfwAddTimer_impl) = dlsym(handle, "glfwAddTimer"); if (glfwAddTimer_impl == NULL) fail("Failed to load glfw function glfwAddTimer with error: %s", dlerror()); *(void **) (&glfwUpdateTimer_impl) = dlsym(handle, "glfwUpdateTimer"); if (glfwUpdateTimer_impl == NULL) fail("Failed to load glfw function glfwUpdateTimer with error: %s", dlerror()); *(void **) (&glfwRemoveTimer_impl) = dlsym(handle, "glfwRemoveTimer"); if (glfwRemoveTimer_impl == NULL) fail("Failed to load glfw function glfwRemoveTimer with error: %s", dlerror()); *(void **) (&glfwSetDrawTextFunction_impl) = dlsym(handle, "glfwSetDrawTextFunction"); if (glfwSetDrawTextFunction_impl == NULL) fail("Failed to load glfw function glfwSetDrawTextFunction with error: %s", dlerror()); *(void **) (&glfwSetCurrentSelectionCallback_impl) = dlsym(handle, "glfwSetCurrentSelectionCallback"); if (glfwSetCurrentSelectionCallback_impl == NULL) fail("Failed to load glfw function glfwSetCurrentSelectionCallback with error: %s", dlerror()); *(void **) (&glfwSetHasCurrentSelectionCallback_impl) = dlsym(handle, "glfwSetHasCurrentSelectionCallback"); if (glfwSetHasCurrentSelectionCallback_impl == NULL) fail("Failed to load glfw function glfwSetHasCurrentSelectionCallback with error: %s", dlerror()); *(void **) (&glfwSetIMECursorPositionCallback_impl) = dlsym(handle, "glfwSetIMECursorPositionCallback"); if (glfwSetIMECursorPositionCallback_impl == NULL) fail("Failed to load glfw function glfwSetIMECursorPositionCallback with error: %s", dlerror()); *(void **) (&glfwTerminate_impl) = dlsym(handle, "glfwTerminate"); if (glfwTerminate_impl == NULL) fail("Failed to load glfw function glfwTerminate with error: %s", dlerror()); *(void **) (&glfwInitHint_impl) = dlsym(handle, "glfwInitHint"); if (glfwInitHint_impl == NULL) fail("Failed to load glfw function glfwInitHint with error: %s", dlerror()); *(void **) (&glfwGetVersion_impl) = dlsym(handle, "glfwGetVersion"); if (glfwGetVersion_impl == NULL) fail("Failed to load glfw function glfwGetVersion with error: %s", dlerror()); *(void **) (&glfwGetVersionString_impl) = dlsym(handle, "glfwGetVersionString"); if (glfwGetVersionString_impl == NULL) fail("Failed to load glfw function glfwGetVersionString with error: %s", dlerror()); *(void **) (&glfwGetError_impl) = dlsym(handle, "glfwGetError"); if (glfwGetError_impl == NULL) fail("Failed to load glfw function glfwGetError with error: %s", dlerror()); *(void **) (&glfwSetErrorCallback_impl) = dlsym(handle, "glfwSetErrorCallback"); if (glfwSetErrorCallback_impl == NULL) fail("Failed to load glfw function glfwSetErrorCallback with error: %s", dlerror()); *(void **) (&glfwGetMonitors_impl) = dlsym(handle, "glfwGetMonitors"); if (glfwGetMonitors_impl == NULL) fail("Failed to load glfw function glfwGetMonitors with error: %s", dlerror()); *(void **) (&glfwGetPrimaryMonitor_impl) = dlsym(handle, "glfwGetPrimaryMonitor"); if (glfwGetPrimaryMonitor_impl == NULL) fail("Failed to load glfw function glfwGetPrimaryMonitor with error: %s", dlerror()); *(void **) (&glfwGetMonitorPos_impl) = dlsym(handle, "glfwGetMonitorPos"); if (glfwGetMonitorPos_impl == NULL) fail("Failed to load glfw function glfwGetMonitorPos with error: %s", dlerror()); *(void **) (&glfwGetMonitorWorkarea_impl) = dlsym(handle, "glfwGetMonitorWorkarea"); if (glfwGetMonitorWorkarea_impl == NULL) fail("Failed to load glfw function glfwGetMonitorWorkarea with error: %s", dlerror()); *(void **) (&glfwGetMonitorPhysicalSize_impl) = dlsym(handle, "glfwGetMonitorPhysicalSize"); if (glfwGetMonitorPhysicalSize_impl == NULL) fail("Failed to load glfw function glfwGetMonitorPhysicalSize with error: %s", dlerror()); *(void **) (&glfwGetMonitorContentScale_impl) = dlsym(handle, "glfwGetMonitorContentScale"); if (glfwGetMonitorContentScale_impl == NULL) fail("Failed to load glfw function glfwGetMonitorContentScale with error: %s", dlerror()); *(void **) (&glfwGetMonitorName_impl) = dlsym(handle, "glfwGetMonitorName"); if (glfwGetMonitorName_impl == NULL) fail("Failed to load glfw function glfwGetMonitorName with error: %s", dlerror()); *(void **) (&glfwSetMonitorUserPointer_impl) = dlsym(handle, "glfwSetMonitorUserPointer"); if (glfwSetMonitorUserPointer_impl == NULL) fail("Failed to load glfw function glfwSetMonitorUserPointer with error: %s", dlerror()); *(void **) (&glfwGetMonitorUserPointer_impl) = dlsym(handle, "glfwGetMonitorUserPointer"); if (glfwGetMonitorUserPointer_impl == NULL) fail("Failed to load glfw function glfwGetMonitorUserPointer with error: %s", dlerror()); *(void **) (&glfwSetMonitorCallback_impl) = dlsym(handle, "glfwSetMonitorCallback"); if (glfwSetMonitorCallback_impl == NULL) fail("Failed to load glfw function glfwSetMonitorCallback with error: %s", dlerror()); *(void **) (&glfwGetVideoModes_impl) = dlsym(handle, "glfwGetVideoModes"); if (glfwGetVideoModes_impl == NULL) fail("Failed to load glfw function glfwGetVideoModes with error: %s", dlerror()); *(void **) (&glfwGetVideoMode_impl) = dlsym(handle, "glfwGetVideoMode"); if (glfwGetVideoMode_impl == NULL) fail("Failed to load glfw function glfwGetVideoMode with error: %s", dlerror()); *(void **) (&glfwSetGamma_impl) = dlsym(handle, "glfwSetGamma"); if (glfwSetGamma_impl == NULL) fail("Failed to load glfw function glfwSetGamma with error: %s", dlerror()); *(void **) (&glfwGetGammaRamp_impl) = dlsym(handle, "glfwGetGammaRamp"); if (glfwGetGammaRamp_impl == NULL) fail("Failed to load glfw function glfwGetGammaRamp with error: %s", dlerror()); *(void **) (&glfwSetGammaRamp_impl) = dlsym(handle, "glfwSetGammaRamp"); if (glfwSetGammaRamp_impl == NULL) fail("Failed to load glfw function glfwSetGammaRamp with error: %s", dlerror()); *(void **) (&glfwDefaultWindowHints_impl) = dlsym(handle, "glfwDefaultWindowHints"); if (glfwDefaultWindowHints_impl == NULL) fail("Failed to load glfw function glfwDefaultWindowHints with error: %s", dlerror()); *(void **) (&glfwWindowHint_impl) = dlsym(handle, "glfwWindowHint"); if (glfwWindowHint_impl == NULL) fail("Failed to load glfw function glfwWindowHint with error: %s", dlerror()); *(void **) (&glfwWindowHintString_impl) = dlsym(handle, "glfwWindowHintString"); if (glfwWindowHintString_impl == NULL) fail("Failed to load glfw function glfwWindowHintString with error: %s", dlerror()); *(void **) (&glfwCreateWindow_impl) = dlsym(handle, "glfwCreateWindow"); if (glfwCreateWindow_impl == NULL) fail("Failed to load glfw function glfwCreateWindow with error: %s", dlerror()); *(void **) (&glfwToggleFullscreen_impl) = dlsym(handle, "glfwToggleFullscreen"); if (glfwToggleFullscreen_impl == NULL) fail("Failed to load glfw function glfwToggleFullscreen with error: %s", dlerror()); *(void **) (&glfwIsFullscreen_impl) = dlsym(handle, "glfwIsFullscreen"); if (glfwIsFullscreen_impl == NULL) fail("Failed to load glfw function glfwIsFullscreen with error: %s", dlerror()); *(void **) (&glfwAreSwapsAllowed_impl) = dlsym(handle, "glfwAreSwapsAllowed"); if (glfwAreSwapsAllowed_impl == NULL) fail("Failed to load glfw function glfwAreSwapsAllowed with error: %s", dlerror()); *(void **) (&glfwDestroyWindow_impl) = dlsym(handle, "glfwDestroyWindow"); if (glfwDestroyWindow_impl == NULL) fail("Failed to load glfw function glfwDestroyWindow with error: %s", dlerror()); *(void **) (&glfwWindowShouldClose_impl) = dlsym(handle, "glfwWindowShouldClose"); if (glfwWindowShouldClose_impl == NULL) fail("Failed to load glfw function glfwWindowShouldClose with error: %s", dlerror()); *(void **) (&glfwSetWindowShouldClose_impl) = dlsym(handle, "glfwSetWindowShouldClose"); if (glfwSetWindowShouldClose_impl == NULL) fail("Failed to load glfw function glfwSetWindowShouldClose with error: %s", dlerror()); *(void **) (&glfwSetWindowTitle_impl) = dlsym(handle, "glfwSetWindowTitle"); if (glfwSetWindowTitle_impl == NULL) fail("Failed to load glfw function glfwSetWindowTitle with error: %s", dlerror()); *(void **) (&glfwSetWindowIcon_impl) = dlsym(handle, "glfwSetWindowIcon"); if (glfwSetWindowIcon_impl == NULL) fail("Failed to load glfw function glfwSetWindowIcon with error: %s", dlerror()); *(void **) (&glfwGetWindowPos_impl) = dlsym(handle, "glfwGetWindowPos"); if (glfwGetWindowPos_impl == NULL) fail("Failed to load glfw function glfwGetWindowPos with error: %s", dlerror()); *(void **) (&glfwSetWindowPos_impl) = dlsym(handle, "glfwSetWindowPos"); if (glfwSetWindowPos_impl == NULL) fail("Failed to load glfw function glfwSetWindowPos with error: %s", dlerror()); *(void **) (&glfwGetWindowSize_impl) = dlsym(handle, "glfwGetWindowSize"); if (glfwGetWindowSize_impl == NULL) fail("Failed to load glfw function glfwGetWindowSize with error: %s", dlerror()); *(void **) (&glfwSetWindowSizeLimits_impl) = dlsym(handle, "glfwSetWindowSizeLimits"); if (glfwSetWindowSizeLimits_impl == NULL) fail("Failed to load glfw function glfwSetWindowSizeLimits with error: %s", dlerror()); *(void **) (&glfwSetWindowSizeIncrements_impl) = dlsym(handle, "glfwSetWindowSizeIncrements"); if (glfwSetWindowSizeIncrements_impl == NULL) fail("Failed to load glfw function glfwSetWindowSizeIncrements with error: %s", dlerror()); *(void **) (&glfwSetWindowAspectRatio_impl) = dlsym(handle, "glfwSetWindowAspectRatio"); if (glfwSetWindowAspectRatio_impl == NULL) fail("Failed to load glfw function glfwSetWindowAspectRatio with error: %s", dlerror()); *(void **) (&glfwSetWindowSize_impl) = dlsym(handle, "glfwSetWindowSize"); if (glfwSetWindowSize_impl == NULL) fail("Failed to load glfw function glfwSetWindowSize with error: %s", dlerror()); *(void **) (&glfwGetFramebufferSize_impl) = dlsym(handle, "glfwGetFramebufferSize"); if (glfwGetFramebufferSize_impl == NULL) fail("Failed to load glfw function glfwGetFramebufferSize with error: %s", dlerror()); *(void **) (&glfwGetWindowFrameSize_impl) = dlsym(handle, "glfwGetWindowFrameSize"); if (glfwGetWindowFrameSize_impl == NULL) fail("Failed to load glfw function glfwGetWindowFrameSize with error: %s", dlerror()); *(void **) (&glfwGetWindowContentScale_impl) = dlsym(handle, "glfwGetWindowContentScale"); if (glfwGetWindowContentScale_impl == NULL) fail("Failed to load glfw function glfwGetWindowContentScale with error: %s", dlerror()); *(void **) (&glfwGetDoubleClickInterval_impl) = dlsym(handle, "glfwGetDoubleClickInterval"); if (glfwGetDoubleClickInterval_impl == NULL) fail("Failed to load glfw function glfwGetDoubleClickInterval with error: %s", dlerror()); *(void **) (&glfwGetWindowOpacity_impl) = dlsym(handle, "glfwGetWindowOpacity"); if (glfwGetWindowOpacity_impl == NULL) fail("Failed to load glfw function glfwGetWindowOpacity with error: %s", dlerror()); *(void **) (&glfwSetWindowOpacity_impl) = dlsym(handle, "glfwSetWindowOpacity"); if (glfwSetWindowOpacity_impl == NULL) fail("Failed to load glfw function glfwSetWindowOpacity with error: %s", dlerror()); *(void **) (&glfwIconifyWindow_impl) = dlsym(handle, "glfwIconifyWindow"); if (glfwIconifyWindow_impl == NULL) fail("Failed to load glfw function glfwIconifyWindow with error: %s", dlerror()); *(void **) (&glfwRestoreWindow_impl) = dlsym(handle, "glfwRestoreWindow"); if (glfwRestoreWindow_impl == NULL) fail("Failed to load glfw function glfwRestoreWindow with error: %s", dlerror()); *(void **) (&glfwMaximizeWindow_impl) = dlsym(handle, "glfwMaximizeWindow"); if (glfwMaximizeWindow_impl == NULL) fail("Failed to load glfw function glfwMaximizeWindow with error: %s", dlerror()); *(void **) (&glfwShowWindow_impl) = dlsym(handle, "glfwShowWindow"); if (glfwShowWindow_impl == NULL) fail("Failed to load glfw function glfwShowWindow with error: %s", dlerror()); *(void **) (&glfwHideWindow_impl) = dlsym(handle, "glfwHideWindow"); if (glfwHideWindow_impl == NULL) fail("Failed to load glfw function glfwHideWindow with error: %s", dlerror()); *(void **) (&glfwFocusWindow_impl) = dlsym(handle, "glfwFocusWindow"); if (glfwFocusWindow_impl == NULL) fail("Failed to load glfw function glfwFocusWindow with error: %s", dlerror()); *(void **) (&glfwRequestWindowAttention_impl) = dlsym(handle, "glfwRequestWindowAttention"); if (glfwRequestWindowAttention_impl == NULL) fail("Failed to load glfw function glfwRequestWindowAttention with error: %s", dlerror()); *(void **) (&glfwWindowBell_impl) = dlsym(handle, "glfwWindowBell"); if (glfwWindowBell_impl == NULL) fail("Failed to load glfw function glfwWindowBell with error: %s", dlerror()); *(void **) (&glfwGetWindowMonitor_impl) = dlsym(handle, "glfwGetWindowMonitor"); if (glfwGetWindowMonitor_impl == NULL) fail("Failed to load glfw function glfwGetWindowMonitor with error: %s", dlerror()); *(void **) (&glfwSetWindowMonitor_impl) = dlsym(handle, "glfwSetWindowMonitor"); if (glfwSetWindowMonitor_impl == NULL) fail("Failed to load glfw function glfwSetWindowMonitor with error: %s", dlerror()); *(void **) (&glfwGetWindowAttrib_impl) = dlsym(handle, "glfwGetWindowAttrib"); if (glfwGetWindowAttrib_impl == NULL) fail("Failed to load glfw function glfwGetWindowAttrib with error: %s", dlerror()); *(void **) (&glfwSetWindowAttrib_impl) = dlsym(handle, "glfwSetWindowAttrib"); if (glfwSetWindowAttrib_impl == NULL) fail("Failed to load glfw function glfwSetWindowAttrib with error: %s", dlerror()); *(void **) (&glfwSetWindowBlur_impl) = dlsym(handle, "glfwSetWindowBlur"); if (glfwSetWindowBlur_impl == NULL) fail("Failed to load glfw function glfwSetWindowBlur with error: %s", dlerror()); *(void **) (&glfwSetWindowUserPointer_impl) = dlsym(handle, "glfwSetWindowUserPointer"); if (glfwSetWindowUserPointer_impl == NULL) fail("Failed to load glfw function glfwSetWindowUserPointer with error: %s", dlerror()); *(void **) (&glfwGetWindowUserPointer_impl) = dlsym(handle, "glfwGetWindowUserPointer"); if (glfwGetWindowUserPointer_impl == NULL) fail("Failed to load glfw function glfwGetWindowUserPointer with error: %s", dlerror()); *(void **) (&glfwSetWindowPosCallback_impl) = dlsym(handle, "glfwSetWindowPosCallback"); if (glfwSetWindowPosCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowPosCallback with error: %s", dlerror()); *(void **) (&glfwSetWindowSizeCallback_impl) = dlsym(handle, "glfwSetWindowSizeCallback"); if (glfwSetWindowSizeCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowSizeCallback with error: %s", dlerror()); *(void **) (&glfwSetWindowCloseCallback_impl) = dlsym(handle, "glfwSetWindowCloseCallback"); if (glfwSetWindowCloseCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowCloseCallback with error: %s", dlerror()); *(void **) (&glfwSetApplicationCloseCallback_impl) = dlsym(handle, "glfwSetApplicationCloseCallback"); if (glfwSetApplicationCloseCallback_impl == NULL) fail("Failed to load glfw function glfwSetApplicationCloseCallback with error: %s", dlerror()); *(void **) (&glfwSetSystemColorThemeChangeCallback_impl) = dlsym(handle, "glfwSetSystemColorThemeChangeCallback"); if (glfwSetSystemColorThemeChangeCallback_impl == NULL) fail("Failed to load glfw function glfwSetSystemColorThemeChangeCallback with error: %s", dlerror()); *(void **) (&glfwSetClipboardLostCallback_impl) = dlsym(handle, "glfwSetClipboardLostCallback"); if (glfwSetClipboardLostCallback_impl == NULL) fail("Failed to load glfw function glfwSetClipboardLostCallback with error: %s", dlerror()); *(void **) (&glfwGetCurrentSystemColorTheme_impl) = dlsym(handle, "glfwGetCurrentSystemColorTheme"); if (glfwGetCurrentSystemColorTheme_impl == NULL) fail("Failed to load glfw function glfwGetCurrentSystemColorTheme with error: %s", dlerror()); *(void **) (&glfwSetWindowRefreshCallback_impl) = dlsym(handle, "glfwSetWindowRefreshCallback"); if (glfwSetWindowRefreshCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowRefreshCallback with error: %s", dlerror()); *(void **) (&glfwSetWindowFocusCallback_impl) = dlsym(handle, "glfwSetWindowFocusCallback"); if (glfwSetWindowFocusCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowFocusCallback with error: %s", dlerror()); *(void **) (&glfwSetWindowOcclusionCallback_impl) = dlsym(handle, "glfwSetWindowOcclusionCallback"); if (glfwSetWindowOcclusionCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowOcclusionCallback with error: %s", dlerror()); *(void **) (&glfwSetWindowIconifyCallback_impl) = dlsym(handle, "glfwSetWindowIconifyCallback"); if (glfwSetWindowIconifyCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowIconifyCallback with error: %s", dlerror()); *(void **) (&glfwSetWindowMaximizeCallback_impl) = dlsym(handle, "glfwSetWindowMaximizeCallback"); if (glfwSetWindowMaximizeCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowMaximizeCallback with error: %s", dlerror()); *(void **) (&glfwSetFramebufferSizeCallback_impl) = dlsym(handle, "glfwSetFramebufferSizeCallback"); if (glfwSetFramebufferSizeCallback_impl == NULL) fail("Failed to load glfw function glfwSetFramebufferSizeCallback with error: %s", dlerror()); *(void **) (&glfwSetWindowContentScaleCallback_impl) = dlsym(handle, "glfwSetWindowContentScaleCallback"); if (glfwSetWindowContentScaleCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowContentScaleCallback with error: %s", dlerror()); *(void **) (&glfwPostEmptyEvent_impl) = dlsym(handle, "glfwPostEmptyEvent"); if (glfwPostEmptyEvent_impl == NULL) fail("Failed to load glfw function glfwPostEmptyEvent with error: %s", dlerror()); *(void **) (&glfwGetIgnoreOSKeyboardProcessing_impl) = dlsym(handle, "glfwGetIgnoreOSKeyboardProcessing"); if (glfwGetIgnoreOSKeyboardProcessing_impl == NULL) fail("Failed to load glfw function glfwGetIgnoreOSKeyboardProcessing with error: %s", dlerror()); *(void **) (&glfwSetIgnoreOSKeyboardProcessing_impl) = dlsym(handle, "glfwSetIgnoreOSKeyboardProcessing"); if (glfwSetIgnoreOSKeyboardProcessing_impl == NULL) fail("Failed to load glfw function glfwSetIgnoreOSKeyboardProcessing with error: %s", dlerror()); *(void **) (&glfwGetInputMode_impl) = dlsym(handle, "glfwGetInputMode"); if (glfwGetInputMode_impl == NULL) fail("Failed to load glfw function glfwGetInputMode with error: %s", dlerror()); *(void **) (&glfwSetInputMode_impl) = dlsym(handle, "glfwSetInputMode"); if (glfwSetInputMode_impl == NULL) fail("Failed to load glfw function glfwSetInputMode with error: %s", dlerror()); *(void **) (&glfwRawMouseMotionSupported_impl) = dlsym(handle, "glfwRawMouseMotionSupported"); if (glfwRawMouseMotionSupported_impl == NULL) fail("Failed to load glfw function glfwRawMouseMotionSupported with error: %s", dlerror()); *(void **) (&glfwGetKeyName_impl) = dlsym(handle, "glfwGetKeyName"); if (glfwGetKeyName_impl == NULL) fail("Failed to load glfw function glfwGetKeyName with error: %s", dlerror()); *(void **) (&glfwGetNativeKeyForKey_impl) = dlsym(handle, "glfwGetNativeKeyForKey"); if (glfwGetNativeKeyForKey_impl == NULL) fail("Failed to load glfw function glfwGetNativeKeyForKey with error: %s", dlerror()); *(void **) (&glfwGetKey_impl) = dlsym(handle, "glfwGetKey"); if (glfwGetKey_impl == NULL) fail("Failed to load glfw function glfwGetKey with error: %s", dlerror()); *(void **) (&glfwGetMouseButton_impl) = dlsym(handle, "glfwGetMouseButton"); if (glfwGetMouseButton_impl == NULL) fail("Failed to load glfw function glfwGetMouseButton with error: %s", dlerror()); *(void **) (&glfwGetCursorPos_impl) = dlsym(handle, "glfwGetCursorPos"); if (glfwGetCursorPos_impl == NULL) fail("Failed to load glfw function glfwGetCursorPos with error: %s", dlerror()); *(void **) (&glfwSetCursorPos_impl) = dlsym(handle, "glfwSetCursorPos"); if (glfwSetCursorPos_impl == NULL) fail("Failed to load glfw function glfwSetCursorPos with error: %s", dlerror()); *(void **) (&glfwCreateCursor_impl) = dlsym(handle, "glfwCreateCursor"); if (glfwCreateCursor_impl == NULL) fail("Failed to load glfw function glfwCreateCursor with error: %s", dlerror()); *(void **) (&glfwCreateStandardCursor_impl) = dlsym(handle, "glfwCreateStandardCursor"); if (glfwCreateStandardCursor_impl == NULL) fail("Failed to load glfw function glfwCreateStandardCursor with error: %s", dlerror()); *(void **) (&glfwDestroyCursor_impl) = dlsym(handle, "glfwDestroyCursor"); if (glfwDestroyCursor_impl == NULL) fail("Failed to load glfw function glfwDestroyCursor with error: %s", dlerror()); *(void **) (&glfwSetCursor_impl) = dlsym(handle, "glfwSetCursor"); if (glfwSetCursor_impl == NULL) fail("Failed to load glfw function glfwSetCursor with error: %s", dlerror()); *(void **) (&glfwSetKeyboardCallback_impl) = dlsym(handle, "glfwSetKeyboardCallback"); if (glfwSetKeyboardCallback_impl == NULL) fail("Failed to load glfw function glfwSetKeyboardCallback with error: %s", dlerror()); *(void **) (&glfwUpdateIMEState_impl) = dlsym(handle, "glfwUpdateIMEState"); if (glfwUpdateIMEState_impl == NULL) fail("Failed to load glfw function glfwUpdateIMEState with error: %s", dlerror()); *(void **) (&glfwSetMouseButtonCallback_impl) = dlsym(handle, "glfwSetMouseButtonCallback"); if (glfwSetMouseButtonCallback_impl == NULL) fail("Failed to load glfw function glfwSetMouseButtonCallback with error: %s", dlerror()); *(void **) (&glfwSetCursorPosCallback_impl) = dlsym(handle, "glfwSetCursorPosCallback"); if (glfwSetCursorPosCallback_impl == NULL) fail("Failed to load glfw function glfwSetCursorPosCallback with error: %s", dlerror()); *(void **) (&glfwSetCursorEnterCallback_impl) = dlsym(handle, "glfwSetCursorEnterCallback"); if (glfwSetCursorEnterCallback_impl == NULL) fail("Failed to load glfw function glfwSetCursorEnterCallback with error: %s", dlerror()); *(void **) (&glfwSetScrollCallback_impl) = dlsym(handle, "glfwSetScrollCallback"); if (glfwSetScrollCallback_impl == NULL) fail("Failed to load glfw function glfwSetScrollCallback with error: %s", dlerror()); *(void **) (&glfwSetDropCallback_impl) = dlsym(handle, "glfwSetDropCallback"); if (glfwSetDropCallback_impl == NULL) fail("Failed to load glfw function glfwSetDropCallback with error: %s", dlerror()); *(void **) (&glfwSetLiveResizeCallback_impl) = dlsym(handle, "glfwSetLiveResizeCallback"); if (glfwSetLiveResizeCallback_impl == NULL) fail("Failed to load glfw function glfwSetLiveResizeCallback with error: %s", dlerror()); *(void **) (&glfwJoystickPresent_impl) = dlsym(handle, "glfwJoystickPresent"); if (glfwJoystickPresent_impl == NULL) fail("Failed to load glfw function glfwJoystickPresent with error: %s", dlerror()); *(void **) (&glfwGetJoystickAxes_impl) = dlsym(handle, "glfwGetJoystickAxes"); if (glfwGetJoystickAxes_impl == NULL) fail("Failed to load glfw function glfwGetJoystickAxes with error: %s", dlerror()); *(void **) (&glfwGetJoystickButtons_impl) = dlsym(handle, "glfwGetJoystickButtons"); if (glfwGetJoystickButtons_impl == NULL) fail("Failed to load glfw function glfwGetJoystickButtons with error: %s", dlerror()); *(void **) (&glfwGetJoystickHats_impl) = dlsym(handle, "glfwGetJoystickHats"); if (glfwGetJoystickHats_impl == NULL) fail("Failed to load glfw function glfwGetJoystickHats with error: %s", dlerror()); *(void **) (&glfwGetJoystickName_impl) = dlsym(handle, "glfwGetJoystickName"); if (glfwGetJoystickName_impl == NULL) fail("Failed to load glfw function glfwGetJoystickName with error: %s", dlerror()); *(void **) (&glfwGetJoystickGUID_impl) = dlsym(handle, "glfwGetJoystickGUID"); if (glfwGetJoystickGUID_impl == NULL) fail("Failed to load glfw function glfwGetJoystickGUID with error: %s", dlerror()); *(void **) (&glfwSetJoystickUserPointer_impl) = dlsym(handle, "glfwSetJoystickUserPointer"); if (glfwSetJoystickUserPointer_impl == NULL) fail("Failed to load glfw function glfwSetJoystickUserPointer with error: %s", dlerror()); *(void **) (&glfwGetJoystickUserPointer_impl) = dlsym(handle, "glfwGetJoystickUserPointer"); if (glfwGetJoystickUserPointer_impl == NULL) fail("Failed to load glfw function glfwGetJoystickUserPointer with error: %s", dlerror()); *(void **) (&glfwJoystickIsGamepad_impl) = dlsym(handle, "glfwJoystickIsGamepad"); if (glfwJoystickIsGamepad_impl == NULL) fail("Failed to load glfw function glfwJoystickIsGamepad with error: %s", dlerror()); *(void **) (&glfwSetJoystickCallback_impl) = dlsym(handle, "glfwSetJoystickCallback"); if (glfwSetJoystickCallback_impl == NULL) fail("Failed to load glfw function glfwSetJoystickCallback with error: %s", dlerror()); *(void **) (&glfwUpdateGamepadMappings_impl) = dlsym(handle, "glfwUpdateGamepadMappings"); if (glfwUpdateGamepadMappings_impl == NULL) fail("Failed to load glfw function glfwUpdateGamepadMappings with error: %s", dlerror()); *(void **) (&glfwGetGamepadName_impl) = dlsym(handle, "glfwGetGamepadName"); if (glfwGetGamepadName_impl == NULL) fail("Failed to load glfw function glfwGetGamepadName with error: %s", dlerror()); *(void **) (&glfwGetGamepadState_impl) = dlsym(handle, "glfwGetGamepadState"); if (glfwGetGamepadState_impl == NULL) fail("Failed to load glfw function glfwGetGamepadState with error: %s", dlerror()); *(void **) (&glfwSetClipboardDataTypes_impl) = dlsym(handle, "glfwSetClipboardDataTypes"); if (glfwSetClipboardDataTypes_impl == NULL) fail("Failed to load glfw function glfwSetClipboardDataTypes with error: %s", dlerror()); *(void **) (&glfwGetClipboard_impl) = dlsym(handle, "glfwGetClipboard"); if (glfwGetClipboard_impl == NULL) fail("Failed to load glfw function glfwGetClipboard with error: %s", dlerror()); *(void **) (&glfwGetTime_impl) = dlsym(handle, "glfwGetTime"); if (glfwGetTime_impl == NULL) fail("Failed to load glfw function glfwGetTime with error: %s", dlerror()); *(void **) (&glfwMakeContextCurrent_impl) = dlsym(handle, "glfwMakeContextCurrent"); if (glfwMakeContextCurrent_impl == NULL) fail("Failed to load glfw function glfwMakeContextCurrent with error: %s", dlerror()); *(void **) (&glfwGetCurrentContext_impl) = dlsym(handle, "glfwGetCurrentContext"); if (glfwGetCurrentContext_impl == NULL) fail("Failed to load glfw function glfwGetCurrentContext with error: %s", dlerror()); *(void **) (&glfwSwapBuffers_impl) = dlsym(handle, "glfwSwapBuffers"); if (glfwSwapBuffers_impl == NULL) fail("Failed to load glfw function glfwSwapBuffers with error: %s", dlerror()); *(void **) (&glfwSwapInterval_impl) = dlsym(handle, "glfwSwapInterval"); if (glfwSwapInterval_impl == NULL) fail("Failed to load glfw function glfwSwapInterval with error: %s", dlerror()); *(void **) (&glfwExtensionSupported_impl) = dlsym(handle, "glfwExtensionSupported"); if (glfwExtensionSupported_impl == NULL) fail("Failed to load glfw function glfwExtensionSupported with error: %s", dlerror()); *(void **) (&glfwGetProcAddress_impl) = dlsym(handle, "glfwGetProcAddress"); if (glfwGetProcAddress_impl == NULL) fail("Failed to load glfw function glfwGetProcAddress with error: %s", dlerror()); *(void **) (&glfwVulkanSupported_impl) = dlsym(handle, "glfwVulkanSupported"); if (glfwVulkanSupported_impl == NULL) fail("Failed to load glfw function glfwVulkanSupported with error: %s", dlerror()); *(void **) (&glfwGetRequiredInstanceExtensions_impl) = dlsym(handle, "glfwGetRequiredInstanceExtensions"); if (glfwGetRequiredInstanceExtensions_impl == NULL) fail("Failed to load glfw function glfwGetRequiredInstanceExtensions with error: %s", dlerror()); *(void **) (&glfwGetCocoaWindow_impl) = dlsym(handle, "glfwGetCocoaWindow"); if (glfwGetCocoaWindow_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwGetNSGLContext_impl) = dlsym(handle, "glfwGetNSGLContext"); if (glfwGetNSGLContext_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwGetCocoaMonitor_impl) = dlsym(handle, "glfwGetCocoaMonitor"); if (glfwGetCocoaMonitor_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwSetCocoaTextInputFilter_impl) = dlsym(handle, "glfwSetCocoaTextInputFilter"); if (glfwSetCocoaTextInputFilter_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwSetCocoaURLOpenCallback_impl) = dlsym(handle, "glfwSetCocoaURLOpenCallback"); if (glfwSetCocoaURLOpenCallback_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwSetCocoaToggleFullscreenIntercept_impl) = dlsym(handle, "glfwSetCocoaToggleFullscreenIntercept"); if (glfwSetCocoaToggleFullscreenIntercept_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwSetApplicationShouldHandleReopen_impl) = dlsym(handle, "glfwSetApplicationShouldHandleReopen"); if (glfwSetApplicationShouldHandleReopen_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwSetApplicationWillFinishLaunching_impl) = dlsym(handle, "glfwSetApplicationWillFinishLaunching"); if (glfwSetApplicationWillFinishLaunching_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwGetCocoaKeyEquivalent_impl) = dlsym(handle, "glfwGetCocoaKeyEquivalent"); if (glfwGetCocoaKeyEquivalent_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwCocoaRequestRenderFrame_impl) = dlsym(handle, "glfwCocoaRequestRenderFrame"); if (glfwCocoaRequestRenderFrame_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwCocoaSetWindowResizeCallback_impl) = dlsym(handle, "glfwCocoaSetWindowResizeCallback"); if (glfwCocoaSetWindowResizeCallback_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwGetX11Display_impl) = dlsym(handle, "glfwGetX11Display"); if (glfwGetX11Display_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwGetX11Window_impl) = dlsym(handle, "glfwGetX11Window"); if (glfwGetX11Window_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwSetPrimarySelectionString_impl) = dlsym(handle, "glfwSetPrimarySelectionString"); if (glfwSetPrimarySelectionString_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwCocoaSetWindowChrome_impl) = dlsym(handle, "glfwCocoaSetWindowChrome"); if (glfwCocoaSetWindowChrome_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwGetPrimarySelectionString_impl) = dlsym(handle, "glfwGetPrimarySelectionString"); if (glfwGetPrimarySelectionString_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwGetNativeKeyForName_impl) = dlsym(handle, "glfwGetNativeKeyForName"); if (glfwGetNativeKeyForName_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwRequestWaylandFrameEvent_impl) = dlsym(handle, "glfwRequestWaylandFrameEvent"); if (glfwRequestWaylandFrameEvent_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwWaylandActivateWindow_impl) = dlsym(handle, "glfwWaylandActivateWindow"); if (glfwWaylandActivateWindow_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwWaylandMissingCapabilities_impl) = dlsym(handle, "glfwWaylandMissingCapabilities"); if (glfwWaylandMissingCapabilities_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwWaylandRunWithActivationToken_impl) = dlsym(handle, "glfwWaylandRunWithActivationToken"); if (glfwWaylandRunWithActivationToken_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwWaylandSetTitlebarColor_impl) = dlsym(handle, "glfwWaylandSetTitlebarColor"); if (glfwWaylandSetTitlebarColor_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwWaylandRedrawCSDWindowTitle_impl) = dlsym(handle, "glfwWaylandRedrawCSDWindowTitle"); if (glfwWaylandRedrawCSDWindowTitle_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwWaylandIsWindowFullyCreated_impl) = dlsym(handle, "glfwWaylandIsWindowFullyCreated"); if (glfwWaylandIsWindowFullyCreated_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwWaylandSetupLayerShellForNextWindow_impl) = dlsym(handle, "glfwWaylandSetupLayerShellForNextWindow"); if (glfwWaylandSetupLayerShellForNextWindow_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwWaylandCompositorPID_impl) = dlsym(handle, "glfwWaylandCompositorPID"); if (glfwWaylandCompositorPID_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwDBusUserNotify_impl) = dlsym(handle, "glfwDBusUserNotify"); if (glfwDBusUserNotify_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwDBusSetUserNotificationHandler_impl) = dlsym(handle, "glfwDBusSetUserNotificationHandler"); if (glfwDBusSetUserNotificationHandler_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwSetX11LaunchCommand_impl) = dlsym(handle, "glfwSetX11LaunchCommand"); if (glfwSetX11LaunchCommand_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwSetX11WindowAsDock_impl) = dlsym(handle, "glfwSetX11WindowAsDock"); if (glfwSetX11WindowAsDock_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwSetX11WindowStrut_impl) = dlsym(handle, "glfwSetX11WindowStrut"); if (glfwSetX11WindowStrut_impl == NULL) dlerror(); // clear error indicator return NULL; } void unload_glfw(void) { if (handle) { dlclose(handle); handle = NULL; } } kitty-0.41.1/kitty/glfw-wrapper.h0000664000175000017510000025712214773370543016252 0ustar nileshnilesh// // THIS FILE IS GENERATED BY glfw.py // // SAVE YOURSELF SOME TIME, DO NOT MANUALLY EDIT // #pragma once #include #include #include "monotonic.h" #ifndef GFW_EXTERN #define GFW_EXTERN extern #endif /*! @name GLFW version macros * @{ */ /*! @brief The major version number of the GLFW library. * * This is incremented when the API is changed in non-compatible ways. * @ingroup init */ #define GLFW_VERSION_MAJOR 3 /*! @brief The minor version number of the GLFW library. * * This is incremented when features are added to the API but it remains * backward-compatible. * @ingroup init */ #define GLFW_VERSION_MINOR 4 /*! @brief The revision number of the GLFW library. * * This is incremented when a bug fix release is made that does not contain any * API changes. * @ingroup init */ #define GLFW_VERSION_REVISION 0 /*! @} */ /*! @defgroup hat_state Joystick hat states * @brief Joystick hat states. * * See [joystick hat input](@ref joystick_hat) for how these are used. * * @ingroup input * @{ */ #define GLFW_HAT_CENTERED 0 #define GLFW_HAT_UP 1 #define GLFW_HAT_RIGHT 2 #define GLFW_HAT_DOWN 4 #define GLFW_HAT_LEFT 8 #define GLFW_HAT_RIGHT_UP (GLFW_HAT_RIGHT | GLFW_HAT_UP) #define GLFW_HAT_RIGHT_DOWN (GLFW_HAT_RIGHT | GLFW_HAT_DOWN) #define GLFW_HAT_LEFT_UP (GLFW_HAT_LEFT | GLFW_HAT_UP) #define GLFW_HAT_LEFT_DOWN (GLFW_HAT_LEFT | GLFW_HAT_DOWN) /*! @} */ /*! @defgroup keys Keyboard keys * @brief Keyboard key IDs. * * See [key input](@ref input_key) for how these are used. * * These key codes are inspired by the _USB HID Usage Tables v1.12_ (p. 53-60), * but re-arranged to map to 7-bit ASCII for printable keys (function keys are * put in the 256+ range). * * The naming of the key codes follow these rules: * - The US keyboard layout is used * - Names of printable alphanumeric characters are used (e.g. "A", "R", * "3", etc.) * - For non-alphanumeric characters, Unicode:ish names are used (e.g. * "COMMA", "LEFT_SQUARE_BRACKET", etc.). Note that some names do not * correspond to the Unicode standard (usually for brevity) * - Keys that lack a clear US mapping are named "WORLD_x" * - For non-printable keys, custom names are used (e.g. "F4", * "BACKSPACE", etc.) * * @ingroup input * @{ */ /* start functional key names (auto generated by gen-key-constants.py do not edit) */ typedef enum { GLFW_FKEY_FIRST = 0xe000u, GLFW_FKEY_ESCAPE = 0xe000u, GLFW_FKEY_ENTER = 0xe001u, GLFW_FKEY_TAB = 0xe002u, GLFW_FKEY_BACKSPACE = 0xe003u, GLFW_FKEY_INSERT = 0xe004u, GLFW_FKEY_DELETE = 0xe005u, GLFW_FKEY_LEFT = 0xe006u, GLFW_FKEY_RIGHT = 0xe007u, GLFW_FKEY_UP = 0xe008u, GLFW_FKEY_DOWN = 0xe009u, GLFW_FKEY_PAGE_UP = 0xe00au, GLFW_FKEY_PAGE_DOWN = 0xe00bu, GLFW_FKEY_HOME = 0xe00cu, GLFW_FKEY_END = 0xe00du, GLFW_FKEY_CAPS_LOCK = 0xe00eu, GLFW_FKEY_SCROLL_LOCK = 0xe00fu, GLFW_FKEY_NUM_LOCK = 0xe010u, GLFW_FKEY_PRINT_SCREEN = 0xe011u, GLFW_FKEY_PAUSE = 0xe012u, GLFW_FKEY_MENU = 0xe013u, GLFW_FKEY_F1 = 0xe014u, GLFW_FKEY_F2 = 0xe015u, GLFW_FKEY_F3 = 0xe016u, GLFW_FKEY_F4 = 0xe017u, GLFW_FKEY_F5 = 0xe018u, GLFW_FKEY_F6 = 0xe019u, GLFW_FKEY_F7 = 0xe01au, GLFW_FKEY_F8 = 0xe01bu, GLFW_FKEY_F9 = 0xe01cu, GLFW_FKEY_F10 = 0xe01du, GLFW_FKEY_F11 = 0xe01eu, GLFW_FKEY_F12 = 0xe01fu, GLFW_FKEY_F13 = 0xe020u, GLFW_FKEY_F14 = 0xe021u, GLFW_FKEY_F15 = 0xe022u, GLFW_FKEY_F16 = 0xe023u, GLFW_FKEY_F17 = 0xe024u, GLFW_FKEY_F18 = 0xe025u, GLFW_FKEY_F19 = 0xe026u, GLFW_FKEY_F20 = 0xe027u, GLFW_FKEY_F21 = 0xe028u, GLFW_FKEY_F22 = 0xe029u, GLFW_FKEY_F23 = 0xe02au, GLFW_FKEY_F24 = 0xe02bu, GLFW_FKEY_F25 = 0xe02cu, GLFW_FKEY_F26 = 0xe02du, GLFW_FKEY_F27 = 0xe02eu, GLFW_FKEY_F28 = 0xe02fu, GLFW_FKEY_F29 = 0xe030u, GLFW_FKEY_F30 = 0xe031u, GLFW_FKEY_F31 = 0xe032u, GLFW_FKEY_F32 = 0xe033u, GLFW_FKEY_F33 = 0xe034u, GLFW_FKEY_F34 = 0xe035u, GLFW_FKEY_F35 = 0xe036u, GLFW_FKEY_KP_0 = 0xe037u, GLFW_FKEY_KP_1 = 0xe038u, GLFW_FKEY_KP_2 = 0xe039u, GLFW_FKEY_KP_3 = 0xe03au, GLFW_FKEY_KP_4 = 0xe03bu, GLFW_FKEY_KP_5 = 0xe03cu, GLFW_FKEY_KP_6 = 0xe03du, GLFW_FKEY_KP_7 = 0xe03eu, GLFW_FKEY_KP_8 = 0xe03fu, GLFW_FKEY_KP_9 = 0xe040u, GLFW_FKEY_KP_DECIMAL = 0xe041u, GLFW_FKEY_KP_DIVIDE = 0xe042u, GLFW_FKEY_KP_MULTIPLY = 0xe043u, GLFW_FKEY_KP_SUBTRACT = 0xe044u, GLFW_FKEY_KP_ADD = 0xe045u, GLFW_FKEY_KP_ENTER = 0xe046u, GLFW_FKEY_KP_EQUAL = 0xe047u, GLFW_FKEY_KP_SEPARATOR = 0xe048u, GLFW_FKEY_KP_LEFT = 0xe049u, GLFW_FKEY_KP_RIGHT = 0xe04au, GLFW_FKEY_KP_UP = 0xe04bu, GLFW_FKEY_KP_DOWN = 0xe04cu, GLFW_FKEY_KP_PAGE_UP = 0xe04du, GLFW_FKEY_KP_PAGE_DOWN = 0xe04eu, GLFW_FKEY_KP_HOME = 0xe04fu, GLFW_FKEY_KP_END = 0xe050u, GLFW_FKEY_KP_INSERT = 0xe051u, GLFW_FKEY_KP_DELETE = 0xe052u, GLFW_FKEY_KP_BEGIN = 0xe053u, GLFW_FKEY_MEDIA_PLAY = 0xe054u, GLFW_FKEY_MEDIA_PAUSE = 0xe055u, GLFW_FKEY_MEDIA_PLAY_PAUSE = 0xe056u, GLFW_FKEY_MEDIA_REVERSE = 0xe057u, GLFW_FKEY_MEDIA_STOP = 0xe058u, GLFW_FKEY_MEDIA_FAST_FORWARD = 0xe059u, GLFW_FKEY_MEDIA_REWIND = 0xe05au, GLFW_FKEY_MEDIA_TRACK_NEXT = 0xe05bu, GLFW_FKEY_MEDIA_TRACK_PREVIOUS = 0xe05cu, GLFW_FKEY_MEDIA_RECORD = 0xe05du, GLFW_FKEY_LOWER_VOLUME = 0xe05eu, GLFW_FKEY_RAISE_VOLUME = 0xe05fu, GLFW_FKEY_MUTE_VOLUME = 0xe060u, GLFW_FKEY_LEFT_SHIFT = 0xe061u, GLFW_FKEY_LEFT_CONTROL = 0xe062u, GLFW_FKEY_LEFT_ALT = 0xe063u, GLFW_FKEY_LEFT_SUPER = 0xe064u, GLFW_FKEY_LEFT_HYPER = 0xe065u, GLFW_FKEY_LEFT_META = 0xe066u, GLFW_FKEY_RIGHT_SHIFT = 0xe067u, GLFW_FKEY_RIGHT_CONTROL = 0xe068u, GLFW_FKEY_RIGHT_ALT = 0xe069u, GLFW_FKEY_RIGHT_SUPER = 0xe06au, GLFW_FKEY_RIGHT_HYPER = 0xe06bu, GLFW_FKEY_RIGHT_META = 0xe06cu, GLFW_FKEY_ISO_LEVEL3_SHIFT = 0xe06du, GLFW_FKEY_ISO_LEVEL5_SHIFT = 0xe06eu, GLFW_FKEY_LAST = 0xe06eu } GLFWFunctionKey; /* end functional key names */ /*! @} */ /*! @defgroup mods Modifier key flags * @brief Modifier key flags. * * See [key input](@ref input_key) for how these are used. * * @ingroup input * @{ */ /*! @brief If this bit is set one or more Shift keys were held down. * * If this bit is set one or more Shift keys were held down. */ #define GLFW_MOD_SHIFT 0x0001 /*! @brief If this bit is set one or more Alt keys were held down. * * If this bit is set one or more Alt keys were held down. */ #define GLFW_MOD_ALT 0x0002 /*! @brief If this bit is set one or more Alt keys were held down. * * If this bit is set one or more Alt keys were held down. */ #define GLFW_MOD_CONTROL 0x0004 /*! @brief If this bit is set one or more Super keys were held down. * * If this bit is set one or more Super keys were held down. */ #define GLFW_MOD_SUPER 0x0008 /*! @brief If this bit is set one or more Hyper keys were held down. * * If this bit is set one or more Hyper keys were held down. */ #define GLFW_MOD_HYPER 0x0010 /*! @brief If this bit is set one or more Meta keys were held down. * * If this bit is set one or more Meta keys were held down. */ #define GLFW_MOD_META 0x0020 /*! @brief If this bit is set the Caps Lock key is enabled. * * If this bit is set the Caps Lock key is enabled and the @ref * GLFW_LOCK_KEY_MODS input mode is set. */ #define GLFW_MOD_CAPS_LOCK 0x0040 /*! @brief If this bit is set the Num Lock key is enabled. * * If this bit is set the Num Lock key is enabled and the @ref * GLFW_LOCK_KEY_MODS input mode is set. */ #define GLFW_MOD_NUM_LOCK 0x0080 #define GLFW_MOD_LAST GLFW_MOD_NUM_LOCK #define GLFW_LOCK_MASK (GLFW_MOD_NUM_LOCK | GLFW_MOD_CAPS_LOCK) /*! @} */ /*! @defgroup buttons Mouse buttons * @brief Mouse button IDs. * * See [mouse button input](@ref input_mouse_button) for how these are used. * * @ingroup input * @{ */ typedef enum GLFWMouseButton { GLFW_MOUSE_BUTTON_1 = 0, GLFW_MOUSE_BUTTON_LEFT = 0, GLFW_MOUSE_BUTTON_2 = 1, GLFW_MOUSE_BUTTON_RIGHT = 1, GLFW_MOUSE_BUTTON_3 = 2, GLFW_MOUSE_BUTTON_MIDDLE = 2, GLFW_MOUSE_BUTTON_4 = 3, GLFW_MOUSE_BUTTON_5 = 4, GLFW_MOUSE_BUTTON_6 = 5, GLFW_MOUSE_BUTTON_7 = 6, GLFW_MOUSE_BUTTON_8 = 7, GLFW_MOUSE_BUTTON_LAST = 7 } GLFWMouseButton; /*! @} */ typedef enum GLFWColorScheme { GLFW_COLOR_SCHEME_NO_PREFERENCE = 0, GLFW_COLOR_SCHEME_DARK = 1, GLFW_COLOR_SCHEME_LIGHT = 2 } GLFWColorScheme; /*! @defgroup joysticks Joysticks * @brief Joystick IDs. * * See [joystick input](@ref joystick) for how these are used. * * @ingroup input * @{ */ #define GLFW_JOYSTICK_1 0 #define GLFW_JOYSTICK_2 1 #define GLFW_JOYSTICK_3 2 #define GLFW_JOYSTICK_4 3 #define GLFW_JOYSTICK_5 4 #define GLFW_JOYSTICK_6 5 #define GLFW_JOYSTICK_7 6 #define GLFW_JOYSTICK_8 7 #define GLFW_JOYSTICK_9 8 #define GLFW_JOYSTICK_10 9 #define GLFW_JOYSTICK_11 10 #define GLFW_JOYSTICK_12 11 #define GLFW_JOYSTICK_13 12 #define GLFW_JOYSTICK_14 13 #define GLFW_JOYSTICK_15 14 #define GLFW_JOYSTICK_16 15 #define GLFW_JOYSTICK_LAST GLFW_JOYSTICK_16 /*! @} */ /*! @defgroup gamepad_buttons Gamepad buttons * @brief Gamepad buttons. * * See @ref gamepad for how these are used. * * @ingroup input * @{ */ #define GLFW_GAMEPAD_BUTTON_A 0 #define GLFW_GAMEPAD_BUTTON_B 1 #define GLFW_GAMEPAD_BUTTON_X 2 #define GLFW_GAMEPAD_BUTTON_Y 3 #define GLFW_GAMEPAD_BUTTON_LEFT_BUMPER 4 #define GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER 5 #define GLFW_GAMEPAD_BUTTON_BACK 6 #define GLFW_GAMEPAD_BUTTON_START 7 #define GLFW_GAMEPAD_BUTTON_GUIDE 8 #define GLFW_GAMEPAD_BUTTON_LEFT_THUMB 9 #define GLFW_GAMEPAD_BUTTON_RIGHT_THUMB 10 #define GLFW_GAMEPAD_BUTTON_DPAD_UP 11 #define GLFW_GAMEPAD_BUTTON_DPAD_RIGHT 12 #define GLFW_GAMEPAD_BUTTON_DPAD_DOWN 13 #define GLFW_GAMEPAD_BUTTON_DPAD_LEFT 14 #define GLFW_GAMEPAD_BUTTON_LAST GLFW_GAMEPAD_BUTTON_DPAD_LEFT #define GLFW_GAMEPAD_BUTTON_CROSS GLFW_GAMEPAD_BUTTON_A #define GLFW_GAMEPAD_BUTTON_CIRCLE GLFW_GAMEPAD_BUTTON_B #define GLFW_GAMEPAD_BUTTON_SQUARE GLFW_GAMEPAD_BUTTON_X #define GLFW_GAMEPAD_BUTTON_TRIANGLE GLFW_GAMEPAD_BUTTON_Y /*! @} */ /*! @defgroup gamepad_axes Gamepad axes * @brief Gamepad axes. * * See @ref gamepad for how these are used. * * @ingroup input * @{ */ #define GLFW_GAMEPAD_AXIS_LEFT_X 0 #define GLFW_GAMEPAD_AXIS_LEFT_Y 1 #define GLFW_GAMEPAD_AXIS_RIGHT_X 2 #define GLFW_GAMEPAD_AXIS_RIGHT_Y 3 #define GLFW_GAMEPAD_AXIS_LEFT_TRIGGER 4 #define GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER 5 #define GLFW_GAMEPAD_AXIS_LAST GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER /*! @} */ /*! @defgroup errors Error codes * @brief Error codes. * * See [error handling](@ref error_handling) for how these are used. * * @ingroup init * @{ */ /*! @brief No error has occurred. * * No error has occurred. * * @analysis Yay. */ #define GLFW_NO_ERROR 0 /*! @brief GLFW has not been initialized. * * This occurs if a GLFW function was called that must not be called unless the * library is [initialized](@ref intro_init). * * @analysis Application programmer error. Initialize GLFW before calling any * function that requires initialization. */ #define GLFW_NOT_INITIALIZED 0x00010001 /*! @brief No context is current for this thread. * * This occurs if a GLFW function was called that needs and operates on the * current OpenGL or OpenGL ES context but no context is current on the calling * thread. One such function is @ref glfwSwapInterval. * * @analysis Application programmer error. Ensure a context is current before * calling functions that require a current context. */ #define GLFW_NO_CURRENT_CONTEXT 0x00010002 /*! @brief One of the arguments to the function was an invalid enum value. * * One of the arguments to the function was an invalid enum value, for example * requesting @ref GLFW_RED_BITS with @ref glfwGetWindowAttrib. * * @analysis Application programmer error. Fix the offending call. */ #define GLFW_INVALID_ENUM 0x00010003 /*! @brief One of the arguments to the function was an invalid value. * * One of the arguments to the function was an invalid value, for example * requesting a non-existent OpenGL or OpenGL ES version like 2.7. * * Requesting a valid but unavailable OpenGL or OpenGL ES version will instead * result in a @ref GLFW_VERSION_UNAVAILABLE error. * * @analysis Application programmer error. Fix the offending call. */ #define GLFW_INVALID_VALUE 0x00010004 /*! @brief A memory allocation failed. * * A memory allocation failed. * * @analysis A bug in GLFW or the underlying operating system. Report the bug * to our [issue tracker](https://github.com/glfw/glfw/issues). */ #define GLFW_OUT_OF_MEMORY 0x00010005 /*! @brief GLFW could not find support for the requested API on the system. * * GLFW could not find support for the requested API on the system. * * @analysis The installed graphics driver does not support the requested * API, or does not support it via the chosen context creation backend. * Below are a few examples. * * @par * Some pre-installed Windows graphics drivers do not support OpenGL. AMD only * supports OpenGL ES via EGL, while Nvidia and Intel only support it via * a WGL or GLX extension. macOS does not provide OpenGL ES at all. The Mesa * EGL, OpenGL and OpenGL ES libraries do not interface with the Nvidia binary * driver. Older graphics drivers do not support Vulkan. */ #define GLFW_API_UNAVAILABLE 0x00010006 /*! @brief The requested OpenGL or OpenGL ES version is not available. * * The requested OpenGL or OpenGL ES version (including any requested context * or framebuffer hints) is not available on this machine. * * @analysis The machine does not support your requirements. If your * application is sufficiently flexible, downgrade your requirements and try * again. Otherwise, inform the user that their machine does not match your * requirements. * * @par * Future invalid OpenGL and OpenGL ES versions, for example OpenGL 4.8 if 5.0 * comes out before the 4.x series gets that far, also fail with this error and * not @ref GLFW_INVALID_VALUE, because GLFW cannot know what future versions * will exist. */ #define GLFW_VERSION_UNAVAILABLE 0x00010007 /*! @brief A platform-specific error occurred that does not match any of the * more specific categories. * * A platform-specific error occurred that does not match any of the more * specific categories. * * @analysis A bug or configuration error in GLFW, the underlying operating * system or its drivers, or a lack of required resources. Report the issue to * our [issue tracker](https://github.com/glfw/glfw/issues). */ #define GLFW_PLATFORM_ERROR 0x00010008 /*! @brief The requested format is not supported or available. * * If emitted during window creation, the requested pixel format is not * supported. * * If emitted when querying the clipboard, the contents of the clipboard could * not be converted to the requested format. * * @analysis If emitted during window creation, one or more * [hard constraints](@ref window_hints_hard) did not match any of the * available pixel formats. If your application is sufficiently flexible, * downgrade your requirements and try again. Otherwise, inform the user that * their machine does not match your requirements. * * @par * If emitted when querying the clipboard, ignore the error or report it to * the user, as appropriate. */ #define GLFW_FORMAT_UNAVAILABLE 0x00010009 /*! @brief The specified window does not have an OpenGL or OpenGL ES context. * * A window that does not have an OpenGL or OpenGL ES context was passed to * a function that requires it to have one. * * @analysis Application programmer error. Fix the offending call. */ #define GLFW_NO_WINDOW_CONTEXT 0x0001000A /*! @brief The requested feature is not provided by the platform. * * The requested feature is not provided by the platform, so GLFW is unable to * implement it. The documentation for each function notes if it could emit * this error. * * @analysis Platform or platform version limitation. The error can be ignored * unless the feature is critical to the application. * * @par * A function call that emits this error has no effect other than the error and * updating any existing out parameters. */ #define GLFW_FEATURE_UNAVAILABLE 0x0001000C /*! @brief The requested feature is not implemented for the platform. * * The requested feature has not yet been implemented in GLFW for this platform. * * @analysis An incomplete implementation of GLFW for this platform, hopefully * fixed in a future release. The error can be ignored unless the feature is * critical to the application. * * @par * A function call that emits this error has no effect other than the error and * updating any existing out parameters. */ #define GLFW_FEATURE_UNIMPLEMENTED 0x0001000D /*! @} */ /*! @addtogroup window * @{ */ /*! @brief Input focus window hint and attribute * * Input focus [window hint](@ref GLFW_FOCUSED_hint) or * [window attribute](@ref GLFW_FOCUSED_attrib). */ #define GLFW_FOCUSED 0x00020001 /*! @brief Window iconification window attribute * * Window iconification [window attribute](@ref GLFW_ICONIFIED_attrib). */ #define GLFW_ICONIFIED 0x00020002 /*! @brief Window resize-ability window hint and attribute * * Window resize-ability [window hint](@ref GLFW_RESIZABLE_hint) and * [window attribute](@ref GLFW_RESIZABLE_attrib). */ #define GLFW_RESIZABLE 0x00020003 /*! @brief Window visibility window hint and attribute * * Window visibility [window hint](@ref GLFW_VISIBLE_hint) and * [window attribute](@ref GLFW_VISIBLE_attrib). */ #define GLFW_VISIBLE 0x00020004 /*! @brief Window decoration window hint and attribute * * Window decoration [window hint](@ref GLFW_DECORATED_hint) and * [window attribute](@ref GLFW_DECORATED_attrib). */ #define GLFW_DECORATED 0x00020005 /*! @brief Window auto-iconification window hint and attribute * * Window auto-iconification [window hint](@ref GLFW_AUTO_ICONIFY_hint) and * [window attribute](@ref GLFW_AUTO_ICONIFY_attrib). */ #define GLFW_AUTO_ICONIFY 0x00020006 /*! @brief Window decoration window hint and attribute * * Window decoration [window hint](@ref GLFW_FLOATING_hint) and * [window attribute](@ref GLFW_FLOATING_attrib). */ #define GLFW_FLOATING 0x00020007 /*! @brief Window maximization window hint and attribute * * Window maximization [window hint](@ref GLFW_MAXIMIZED_hint) and * [window attribute](@ref GLFW_MAXIMIZED_attrib). */ #define GLFW_MAXIMIZED 0x00020008 /*! @brief Cursor centering window hint * * Cursor centering [window hint](@ref GLFW_CENTER_CURSOR_hint). */ #define GLFW_CENTER_CURSOR 0x00020009 /*! @brief Window framebuffer transparency hint and attribute * * Window framebuffer transparency * [window hint](@ref GLFW_TRANSPARENT_FRAMEBUFFER_hint) and * [window attribute](@ref GLFW_TRANSPARENT_FRAMEBUFFER_attrib). */ #define GLFW_TRANSPARENT_FRAMEBUFFER 0x0002000A /*! @brief Mouse cursor hover window attribute. * * Mouse cursor hover [window attribute](@ref GLFW_HOVERED_attrib). */ #define GLFW_HOVERED 0x0002000B /*! @brief Input focus on calling show window hint and attribute * * Input focus [window hint](@ref GLFW_FOCUS_ON_SHOW_hint) or * [window attribute](@ref GLFW_FOCUS_ON_SHOW_attrib). */ #define GLFW_FOCUS_ON_SHOW 0x0002000C /*! @brief Mouse input transparency window hint and attribute * * Mouse input transparency [window hint](@ref GLFW_MOUSE_PASSTHROUGH_hint) or * [window attribute](@ref GLFW_MOUSE_PASSTHROUGH_attrib). */ #define GLFW_MOUSE_PASSTHROUGH 0x0002000D /*! @brief Occlusion window attribute * * Occlusion [window attribute](@ref GLFW_OCCLUDED_attrib). */ #define GLFW_OCCLUDED 0x0002000E /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_RED_BITS). */ #define GLFW_RED_BITS 0x00021001 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_GREEN_BITS). */ #define GLFW_GREEN_BITS 0x00021002 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_BLUE_BITS). */ #define GLFW_BLUE_BITS 0x00021003 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_ALPHA_BITS). */ #define GLFW_ALPHA_BITS 0x00021004 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_DEPTH_BITS). */ #define GLFW_DEPTH_BITS 0x00021005 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_STENCIL_BITS). */ #define GLFW_STENCIL_BITS 0x00021006 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_ACCUM_RED_BITS). */ #define GLFW_ACCUM_RED_BITS 0x00021007 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_ACCUM_GREEN_BITS). */ #define GLFW_ACCUM_GREEN_BITS 0x00021008 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_ACCUM_BLUE_BITS). */ #define GLFW_ACCUM_BLUE_BITS 0x00021009 /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_ACCUM_ALPHA_BITS). */ #define GLFW_ACCUM_ALPHA_BITS 0x0002100A /*! @brief Framebuffer auxiliary buffer hint. * * Framebuffer auxiliary buffer [hint](@ref GLFW_AUX_BUFFERS). */ #define GLFW_AUX_BUFFERS 0x0002100B /*! @brief OpenGL stereoscopic rendering hint. * * OpenGL stereoscopic rendering [hint](@ref GLFW_STEREO). */ #define GLFW_STEREO 0x0002100C /*! @brief Framebuffer MSAA samples hint. * * Framebuffer MSAA samples [hint](@ref GLFW_SAMPLES). */ #define GLFW_SAMPLES 0x0002100D /*! @brief Framebuffer sRGB hint. * * Framebuffer sRGB [hint](@ref GLFW_SRGB_CAPABLE). */ #define GLFW_SRGB_CAPABLE 0x0002100E /*! @brief Monitor refresh rate hint. * * Monitor refresh rate [hint](@ref GLFW_REFRESH_RATE). */ #define GLFW_REFRESH_RATE 0x0002100F /*! @brief Framebuffer double buffering hint. * * Framebuffer double buffering [hint](@ref GLFW_DOUBLEBUFFER). */ #define GLFW_DOUBLEBUFFER 0x00021010 /*! @brief Context client API hint and attribute. * * Context client API [hint](@ref GLFW_CLIENT_API_hint) and * [attribute](@ref GLFW_CLIENT_API_attrib). */ #define GLFW_CLIENT_API 0x00022001 /*! @brief Context client API major version hint and attribute. * * Context client API major version [hint](@ref GLFW_CONTEXT_VERSION_MAJOR_hint) * and [attribute](@ref GLFW_CONTEXT_VERSION_MAJOR_attrib). */ #define GLFW_CONTEXT_VERSION_MAJOR 0x00022002 /*! @brief Context client API minor version hint and attribute. * * Context client API minor version [hint](@ref GLFW_CONTEXT_VERSION_MINOR_hint) * and [attribute](@ref GLFW_CONTEXT_VERSION_MINOR_attrib). */ #define GLFW_CONTEXT_VERSION_MINOR 0x00022003 /*! @brief Context client API revision number hint and attribute. * * Context client API revision number * [attribute](@ref GLFW_CONTEXT_REVISION_attrib). */ #define GLFW_CONTEXT_REVISION 0x00022004 /*! @brief Context robustness hint and attribute. * * Context client API revision number [hint](@ref GLFW_CONTEXT_ROBUSTNESS_hint) * and [attribute](@ref GLFW_CONTEXT_ROBUSTNESS_attrib). */ #define GLFW_CONTEXT_ROBUSTNESS 0x00022005 /*! @brief OpenGL forward-compatibility hint and attribute. * * OpenGL forward-compatibility [hint](@ref GLFW_OPENGL_FORWARD_COMPAT_hint) * and [attribute](@ref GLFW_OPENGL_FORWARD_COMPAT_attrib). */ #define GLFW_OPENGL_FORWARD_COMPAT 0x00022006 /*! @brief Debug mode context hint and attribute. * * Debug mode context [hint](@ref GLFW_CONTEXT_DEBUG_hint) and * [attribute](@ref GLFW_CONTEXT_DEBUG_attrib). */ #define GLFW_CONTEXT_DEBUG 0x00022007 /*! @brief Legacy name for compatibility. * * This is an alias for compatibility with earlier versions. */ #define GLFW_OPENGL_DEBUG_CONTEXT GLFW_CONTEXT_DEBUG /*! @brief OpenGL profile hint and attribute. * * OpenGL profile [hint](@ref GLFW_OPENGL_PROFILE_hint) and * [attribute](@ref GLFW_OPENGL_PROFILE_attrib). */ #define GLFW_OPENGL_PROFILE 0x00022008 /*! @brief Context flush-on-release hint and attribute. * * Context flush-on-release [hint](@ref GLFW_CONTEXT_RELEASE_BEHAVIOR_hint) and * [attribute](@ref GLFW_CONTEXT_RELEASE_BEHAVIOR_attrib). */ #define GLFW_CONTEXT_RELEASE_BEHAVIOR 0x00022009 /*! @brief Context error suppression hint and attribute. * * Context error suppression [hint](@ref GLFW_CONTEXT_NO_ERROR_hint) and * [attribute](@ref GLFW_CONTEXT_NO_ERROR_attrib). */ #define GLFW_CONTEXT_NO_ERROR 0x0002200A /*! @brief Context creation API hint and attribute. * * Context creation API [hint](@ref GLFW_CONTEXT_CREATION_API_hint) and * [attribute](@ref GLFW_CONTEXT_CREATION_API_attrib). */ #define GLFW_CONTEXT_CREATION_API 0x0002200B /*! @brief Window content area scaling window * [window hint](@ref GLFW_SCALE_TO_MONITOR). */ #define GLFW_SCALE_TO_MONITOR 0x0002200C /*! @brief macOS specific * [window hint](@ref GLFW_COCOA_RETINA_FRAMEBUFFER_hint). */ #define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001 /*! @brief macOS specific * [window hint](@ref GLFW_COCOA_FRAME_NAME_hint). */ #define GLFW_COCOA_FRAME_NAME 0x00023002 /*! @brief macOS specific * [window hint](@ref GLFW_COCOA_GRAPHICS_SWITCHING_hint). */ #define GLFW_COCOA_GRAPHICS_SWITCHING 0x00023003 /*! @brief macOS specific * [window hint](@ref GLFW_COCOA_COLOR_SPACE_hint). */ #define GLFW_COCOA_COLOR_SPACE 0x00023004 typedef enum { DEFAULT_COLORSPACE = 0, SRGB_COLORSPACE = 1, DISPLAY_P3_COLORSPACE = 2, } GlfwCocoaColorSpaces; /*! @brief Blur Radius. On macOS the actual radius is used. On Linux it is treated as a bool. * [window hint](@ref GLFW_BLUR_RADIUS). */ #define GLFW_BLUR_RADIUS 0x0002305 /*! @brief X11 specific * [window hint](@ref GLFW_X11_CLASS_NAME_hint). */ #define GLFW_X11_CLASS_NAME 0x00024001 /*! @brief X11 specific * [window hint](@ref GLFW_X11_CLASS_NAME_hint). */ #define GLFW_X11_INSTANCE_NAME 0x00024002 #define GLFW_WAYLAND_APP_ID 0x00025001 #define GLFW_WAYLAND_BGCOLOR 0x00025002 /*! @} */ #define GLFW_NO_API 0 #define GLFW_OPENGL_API 0x00030001 #define GLFW_OPENGL_ES_API 0x00030002 #define GLFW_NO_ROBUSTNESS 0 #define GLFW_NO_RESET_NOTIFICATION 0x00031001 #define GLFW_LOSE_CONTEXT_ON_RESET 0x00031002 #define GLFW_OPENGL_ANY_PROFILE 0 #define GLFW_OPENGL_CORE_PROFILE 0x00032001 #define GLFW_OPENGL_COMPAT_PROFILE 0x00032002 #define GLFW_CURSOR 0x00033001 #define GLFW_STICKY_KEYS 0x00033002 #define GLFW_STICKY_MOUSE_BUTTONS 0x00033003 #define GLFW_LOCK_KEY_MODS 0x00033004 #define GLFW_RAW_MOUSE_MOTION 0x00033005 #define GLFW_CURSOR_NORMAL 0x00034001 #define GLFW_CURSOR_HIDDEN 0x00034002 #define GLFW_CURSOR_DISABLED 0x00034003 #define GLFW_ANY_RELEASE_BEHAVIOR 0 #define GLFW_RELEASE_BEHAVIOR_FLUSH 0x00035001 #define GLFW_RELEASE_BEHAVIOR_NONE 0x00035002 #define GLFW_NATIVE_CONTEXT_API 0x00036001 #define GLFW_EGL_CONTEXT_API 0x00036002 #define GLFW_OSMESA_CONTEXT_API 0x00036003 #define GLFW_ANGLE_PLATFORM_TYPE_NONE 0x00037001 #define GLFW_ANGLE_PLATFORM_TYPE_OPENGL 0x00037002 #define GLFW_ANGLE_PLATFORM_TYPE_OPENGLES 0x00037003 #define GLFW_ANGLE_PLATFORM_TYPE_D3D9 0x00037004 #define GLFW_ANGLE_PLATFORM_TYPE_D3D11 0x00037005 #define GLFW_ANGLE_PLATFORM_TYPE_VULKAN 0x00037007 #define GLFW_ANGLE_PLATFORM_TYPE_METAL 0x00037008 /*! @defgroup shapes Standard cursor shapes * @brief Standard system cursor shapes. * * See [standard cursor creation](@ref cursor_standard) for how these are used. * * @ingroup input * @{ */ typedef enum { /* start mouse cursor shapes (auto generated by gen-key-constants.py do not edit) */ GLFW_DEFAULT_CURSOR, GLFW_TEXT_CURSOR, GLFW_POINTER_CURSOR, GLFW_HELP_CURSOR, GLFW_WAIT_CURSOR, GLFW_PROGRESS_CURSOR, GLFW_CROSSHAIR_CURSOR, GLFW_CELL_CURSOR, GLFW_VERTICAL_TEXT_CURSOR, GLFW_MOVE_CURSOR, GLFW_E_RESIZE_CURSOR, GLFW_NE_RESIZE_CURSOR, GLFW_NW_RESIZE_CURSOR, GLFW_N_RESIZE_CURSOR, GLFW_SE_RESIZE_CURSOR, GLFW_SW_RESIZE_CURSOR, GLFW_S_RESIZE_CURSOR, GLFW_W_RESIZE_CURSOR, GLFW_EW_RESIZE_CURSOR, GLFW_NS_RESIZE_CURSOR, GLFW_NESW_RESIZE_CURSOR, GLFW_NWSE_RESIZE_CURSOR, GLFW_ZOOM_IN_CURSOR, GLFW_ZOOM_OUT_CURSOR, GLFW_ALIAS_CURSOR, GLFW_COPY_CURSOR, GLFW_NOT_ALLOWED_CURSOR, GLFW_NO_DROP_CURSOR, GLFW_GRAB_CURSOR, GLFW_GRABBING_CURSOR, GLFW_INVALID_CURSOR, /* end mouse cursor shapes */ } GLFWCursorShape; /*! @} */ #define GLFW_CONNECTED 0x00040001 #define GLFW_DISCONNECTED 0x00040002 /*! @addtogroup init * @{ */ /*! @brief Joystick hat buttons init hint. * * Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS). */ #define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001 /*! @brief ANGLE rendering backend init hint. * * ANGLE rendering backend [init hint](@ref GLFW_ANGLE_PLATFORM_TYPE_hint). */ #define GLFW_ANGLE_PLATFORM_TYPE 0x00050002 #define GLFW_DEBUG_KEYBOARD 0x00050003 #define GLFW_DEBUG_RENDERING 0x00050004 /*! @brief macOS specific init hint. * * macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint). */ #define GLFW_COCOA_CHDIR_RESOURCES 0x00051001 /*! @brief macOS specific init hint. * * macOS specific [init hint](@ref GLFW_COCOA_MENUBAR_hint). */ #define GLFW_COCOA_MENUBAR 0x00051002 #define GLFW_WAYLAND_IME 0x00051003 /*! @} */ #define GLFW_DONT_CARE -1 /************************************************************************* * GLFW API types *************************************************************************/ /*! @brief Client API function pointer type. * * Generic function pointer used for returning client API function pointers * without forcing a cast from a regular pointer. * * @sa @ref context_glext * @sa @ref glfwGetProcAddress * * @since Added in version 3.0. * * @ingroup context */ typedef void (*GLFWglproc)(void); /*! @brief Vulkan API function pointer type. * * Generic function pointer used for returning Vulkan API function pointers * without forcing a cast from a regular pointer. * * @sa @ref vulkan_proc * @sa @ref glfwGetInstanceProcAddress * * @since Added in version 3.2. * * @ingroup vulkan */ typedef void (*GLFWvkproc)(void); /*! @brief Opaque monitor object. * * Opaque monitor object. * * @see @ref monitor_object * * @since Added in version 3.0. * * @ingroup monitor */ typedef struct GLFWmonitor GLFWmonitor; /*! @brief Opaque window object. * * Opaque window object. * * @see @ref window_object * * @since Added in version 3.0. * * @ingroup window */ typedef struct GLFWwindow GLFWwindow; /*! @brief Opaque cursor object. * * Opaque cursor object. * * @see @ref cursor_object * * @since Added in version 3.1. * * @ingroup input */ typedef struct GLFWcursor GLFWcursor; typedef enum { GLFW_RELEASE = 0, GLFW_PRESS = 1, GLFW_REPEAT = 2 } GLFWKeyAction; typedef enum { GLFW_IME_NONE, GLFW_IME_PREEDIT_CHANGED, GLFW_IME_COMMIT_TEXT, GLFW_IME_WAYLAND_DONE_EVENT, } GLFWIMEState; typedef enum { GLFW_IME_UPDATE_FOCUS = 1, GLFW_IME_UPDATE_CURSOR_POSITION = 2 } GLFWIMEUpdateType; typedef struct GLFWIMEUpdateEvent { GLFWIMEUpdateType type; const char *before_text, *at_text, *after_text; bool focused; struct { int left, top, width, height; } cursor; } GLFWIMEUpdateEvent; typedef struct GLFWkeyevent { // The [keyboard key](@ref keys) that was pressed or released. uint32_t key, shifted_key, alternate_key; // The platform-specific identifier of the key. int native_key; // The event action. Either `GLFW_PRESS`, `GLFW_RELEASE` or `GLFW_REPEAT`. GLFWKeyAction action; // Bit field describing which [modifier keys](@ref mods) were held down. int mods; // UTF-8 encoded text generated by this key event or empty string or NULL const char *text; // Used for Input Method events. Zero for normal key events. // A value of GLFW_IME_PREEDIT_CHANGED means the pre-edit text for the input event has been changed. // A value of GLFW_IME_COMMIT_TEXT means the text should be committed. GLFWIMEState ime_state; // For internal use only. On Linux it is the actual keycode reported by the windowing system, in contrast // to native_key which can be the result of a compose operation. On macOS it is the same as native_key. uint32_t native_key_id; // True if this is a synthesized event on focus change bool fake_event_on_focus_change; } GLFWkeyevent; typedef enum { GLFW_LAYER_SHELL_NONE, GLFW_LAYER_SHELL_BACKGROUND, GLFW_LAYER_SHELL_PANEL, GLFW_LAYER_SHELL_TOP, GLFW_LAYER_SHELL_OVERLAY } GLFWLayerShellType; typedef enum { GLFW_EDGE_TOP, GLFW_EDGE_BOTTOM, GLFW_EDGE_LEFT, GLFW_EDGE_RIGHT, GLFW_EDGE_CENTER, GLFW_EDGE_NONE } GLFWEdge; typedef enum { GLFW_FOCUS_NOT_ALLOWED, GLFW_FOCUS_EXCLUSIVE, GLFW_FOCUS_ON_DEMAND} GLFWFocusPolicy; typedef struct GLFWLayerShellConfig { GLFWLayerShellType type; GLFWEdge edge; char output_name[64]; GLFWFocusPolicy focus_policy; unsigned x_size_in_cells; unsigned y_size_in_cells; unsigned requested_top_margin; unsigned requested_left_margin; unsigned requested_bottom_margin; unsigned requested_right_margin; int requested_exclusive_zone; unsigned override_exclusive_zone; void (*size_callback)(GLFWwindow *window, const struct GLFWLayerShellConfig *config, unsigned monitor_width, unsigned monitor_height, uint32_t *width, uint32_t *height); struct { double xdpi, ydpi, xscale, yscale; } expected; } GLFWLayerShellConfig; typedef struct GLFWDBUSNotificationData { const char *app_name, *icon, *summary, *body, *category, **actions; size_t num_actions; int32_t timeout; uint8_t urgency; uint32_t replaces; int muted; } GLFWDBUSNotificationData; /*! @brief The function pointer type for error callbacks. * * This is the function pointer type for error callbacks. An error callback * function has the following signature: * @code * void callback_name(int error_code, const char* description) * @endcode * * @param[in] error_code An [error code](@ref errors). Future releases may add * more error codes. * @param[in] description A UTF-8 encoded string describing the error. * * @pointer_lifetime The error description string is valid until the callback * function returns. * * @sa @ref error_handling * @sa @ref glfwSetErrorCallback * * @since Added in version 3.0. * * @ingroup init */ typedef void (* GLFWerrorfun)(int,const char*); /*! @brief The function pointer type for window position callbacks. * * This is the function pointer type for window position callbacks. A window * position callback function has the following signature: * @code * void callback_name(GLFWwindow* window, int xpos, int ypos) * @endcode * * @param[in] window The window that was moved. * @param[in] xpos The new x-coordinate, in screen coordinates, of the * upper-left corner of the content area of the window. * @param[in] ypos The new y-coordinate, in screen coordinates, of the * upper-left corner of the content area of the window. * * @sa @ref window_pos * @sa @ref glfwSetWindowPosCallback * * @since Added in version 3.0. * * @ingroup window */ typedef void (* GLFWwindowposfun)(GLFWwindow*,int,int); /*! @brief The function pointer type for window size callbacks. * * This is the function pointer type for window size callbacks. A window size * callback function has the following signature: * @code * void callback_name(GLFWwindow* window, int width, int height) * @endcode * * @param[in] window The window that was resized. * @param[in] width The new width, in screen coordinates, of the window. * @param[in] height The new height, in screen coordinates, of the window. * * @sa @ref window_size * @sa @ref glfwSetWindowSizeCallback * * @since Added in version 1.0. * @glfw3 Added window handle parameter. * * @ingroup window */ typedef void (* GLFWwindowsizefun)(GLFWwindow*,int,int); /*! @brief The function pointer type for window close callbacks. * * This is the function pointer type for window close callbacks. A window * close callback function has the following signature: * @code * void function_name(GLFWwindow* window) * @endcode * * @param[in] window The window that the user attempted to close. * * @sa @ref window_close * @sa @ref glfwSetWindowCloseCallback * * @since Added in version 2.5. * @glfw3 Added window handle parameter. * * @ingroup window */ typedef void (* GLFWwindowclosefun)(GLFWwindow*); /*! @brief The function pointer type for application close callbacks. * * This is the function pointer type for application close callbacks. A application * close callback function has the following signature: * @code * void function_name(int flags) * @endcode * * @param[in] flags 0 for a user requested application quit, 1 if a fatal error occurred and application should quit ASAP * * @sa @ref glfwSetApplicationCloseCallback * * @ingroup window */ typedef void (* GLFWapplicationclosefun)(int); /*! @brief The function pointer type for system color theme change callbacks. * * This is the function pointer type for system color theme changes. * @code * void function_name(GLFWColorScheme theme_type, bool is_initial_value) * @endcode * * @param[in] theme_type 0 for unknown, 1 for dark and 2 for light * @param[in] is_initial_value true if this is the initial read of the color theme on systems where it is asynchronous such as Linux * * @sa @ref glfwSetSystemColorThemeChangeCallback * * @ingroup window */ typedef void (* GLFWsystemcolorthemechangefun)(GLFWColorScheme, bool); /*! @brief The function pointer type for window content refresh callbacks. * * This is the function pointer type for window content refresh callbacks. * A window content refresh callback function has the following signature: * @code * void function_name(GLFWwindow* window); * @endcode * * @param[in] window The window whose content needs to be refreshed. * * @sa @ref window_refresh * @sa @ref glfwSetWindowRefreshCallback * * @since Added in version 2.5. * @glfw3 Added window handle parameter. * * @ingroup window */ typedef void (* GLFWwindowrefreshfun)(GLFWwindow*); /*! @brief The function pointer type for window focus callbacks. * * This is the function pointer type for window focus callbacks. A window * focus callback function has the following signature: * @code * void function_name(GLFWwindow* window, int focused) * @endcode * * @param[in] window The window that gained or lost input focus. * @param[in] focused `true` if the window was given input focus, or * `false` if it lost it. * * @sa @ref window_focus * @sa @ref glfwSetWindowFocusCallback * * @since Added in version 3.0. * * @ingroup window */ typedef void (* GLFWwindowfocusfun)(GLFWwindow*,int); /*! @brief The function signature for window occlusion callbacks. * * This is the function signature for window occlusion callback functions. * * @param[in] window The window whose occlusion state changed. * @param[in] occluded `true` if the window was occluded, or `false` * if the window is no longer occluded. * * @sa @ref window_occlusion * @sa @ref glfwSetWindowOcclusionCallback * * @since Added in version 3.3. * * @ingroup window */ typedef void (* GLFWwindowocclusionfun)(GLFWwindow*, bool); /*! @brief The function pointer type for window iconify callbacks. * * This is the function pointer type for window iconify callbacks. A window * iconify callback function has the following signature: * @code * void function_name(GLFWwindow* window, int iconified) * @endcode * * @param[in] window The window that was iconified or restored. * @param[in] iconified `true` if the window was iconified, or * `false` if it was restored. * * @sa @ref window_iconify * @sa @ref glfwSetWindowIconifyCallback * * @since Added in version 3.0. * * @ingroup window */ typedef void (* GLFWwindowiconifyfun)(GLFWwindow*,int); /*! @brief The function pointer type for window maximize callbacks. * * This is the function pointer type for window maximize callbacks. A window * maximize callback function has the following signature: * @code * void function_name(GLFWwindow* window, int maximized) * @endcode * * @param[in] window The window that was maximized or restored. * @param[in] maximized `true` if the window was maximized, or * `false` if it was restored. * * @sa @ref window_maximize * @sa glfwSetWindowMaximizeCallback * * @since Added in version 3.3. * * @ingroup window */ typedef void (* GLFWwindowmaximizefun)(GLFWwindow*,int); /*! @brief The function pointer type for framebuffer size callbacks. * * This is the function pointer type for framebuffer size callbacks. * A framebuffer size callback function has the following signature: * @code * void function_name(GLFWwindow* window, int width, int height) * @endcode * * @param[in] window The window whose framebuffer was resized. * @param[in] width The new width, in pixels, of the framebuffer. * @param[in] height The new height, in pixels, of the framebuffer. * * @sa @ref window_fbsize * @sa @ref glfwSetFramebufferSizeCallback * * @since Added in version 3.0. * * @ingroup window */ typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int); /*! @brief The function pointer type for window content scale callbacks. * * This is the function pointer type for window content scale callbacks. * A window content scale callback function has the following signature: * @code * void function_name(GLFWwindow* window, float xscale, float yscale) * @endcode * * @param[in] window The window whose content scale changed. * @param[in] xscale The new x-axis content scale of the window. * @param[in] yscale The new y-axis content scale of the window. * * @sa @ref window_scale * @sa @ref glfwSetWindowContentScaleCallback * * @since Added in version 3.3. * * @ingroup window */ typedef void (* GLFWwindowcontentscalefun)(GLFWwindow*,float,float); /*! @brief The function pointer type for mouse button callbacks. * * This is the function pointer type for mouse button callback functions. * A mouse button callback function has the following signature: * @code * void function_name(GLFWwindow* window, int button, int action, int mods) * @endcode * * @param[in] window The window that received the event. * @param[in] button The [mouse button](@ref buttons) that was pressed or * released. * @param[in] action One of `GLFW_PRESS` or `GLFW_RELEASE`. Future releases * may add more actions. * @param[in] mods Bit field describing which [modifier keys](@ref mods) were * held down. * * @sa @ref input_mouse_button * @sa @ref glfwSetMouseButtonCallback * * @since Added in version 1.0. * @glfw3 Added window handle and modifier mask parameters. * * @ingroup input */ typedef void (* GLFWmousebuttonfun)(GLFWwindow*,int,int,int); /*! @brief The function pointer type for cursor position callbacks. * * This is the function pointer type for cursor position callbacks. A cursor * position callback function has the following signature: * @code * void function_name(GLFWwindow* window, double xpos, double ypos); * @endcode * * @param[in] window The window that received the event. * @param[in] xpos The new cursor x-coordinate, relative to the left edge of * the content area. * @param[in] ypos The new cursor y-coordinate, relative to the top edge of the * content area. * * @sa @ref cursor_pos * @sa @ref glfwSetCursorPosCallback * * @since Added in version 3.0. Replaces `GLFWmouseposfun`. * * @ingroup input */ typedef void (* GLFWcursorposfun)(GLFWwindow*,double,double); /*! @brief The function pointer type for cursor enter/leave callbacks. * * This is the function pointer type for cursor enter/leave callbacks. * A cursor enter/leave callback function has the following signature: * @code * void function_name(GLFWwindow* window, int entered) * @endcode * * @param[in] window The window that received the event. * @param[in] entered `true` if the cursor entered the window's content * area, or `false` if it left it. * * @sa @ref cursor_enter * @sa @ref glfwSetCursorEnterCallback * * @since Added in version 3.0. * * @ingroup input */ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int); /*! @brief The function pointer type for scroll callbacks. * * This is the function pointer type for scroll callbacks. A scroll callback * function has the following signature: * @code * void function_name(GLFWwindow* window, double xoffset, double yoffset) * @endcode * * @param[in] window The window that received the event. * @param[in] xoffset The scroll offset along the x-axis. * @param[in] yoffset The scroll offset along the y-axis. * @param[in] flags A bit-mask providing extra data about the event. * flags & 1 will be true if and only if the offset values are "high-precision", * typically pixel values. Otherwise the offset values are number of lines. * (flags >> 1) & 7 will have value 1 for the start of momentum scrolling, * value 2 for stationary momentum scrolling, value 3 for momentum scrolling * in progress, value 4 for momentum scrolling ended, value 5 for momentum * scrolling cancelled and value 6 if scrolling may begin soon. * @param[int] mods The keyboard modifiers * * @sa @ref scrolling * @sa @ref glfwSetScrollCallback * * @since Added in version 3.0. Replaces `GLFWmousewheelfun`. * @since Changed in version 4.0. Added `flags` parameter. * * @ingroup input */ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int,int); /*! @brief The function pointer type for key callbacks. * * This is the function pointer type for key callbacks. A keyboard * key callback function has the following signature: * @code * void function_name(GLFWwindow* window, uint32_t key, int native_key, int action, int mods) * @endcode * The semantics of this function are that the key that is interacted with on the * keyboard is reported, and the text, if any generated by the key is reported. * So, for example, if on a US-ASCII keyboard the user presses Shift+= GLFW * will report the text "+" and the key as GLFW_KEY_EQUAL. The reported key takes into * account any current keyboard maps defined in the OS. So with a dvorak mapping, pressing * the "s" key will generate text "o" and GLFW_KEY_O. * * @param[in] window The window that received the event. * @param[in] ev The key event, see GLFWkeyevent. The data in this event is only valid for * the lifetime of the callback. * * @note On X11/Wayland if a modifier other than the modifiers GLFW reports * (ctrl/shift/alt/super) is used, GLFW will report the shifted key rather * than the unshifted key. So for example, if ISO_Shift_Level_5 is used to * convert the key A into UP GLFW will report the key as UP with no modifiers. * * @sa @ref input_key * @sa @ref glfwSetKeyboardCallback * * @since Added in version 4.0. * * @ingroup input */ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); /*! @brief The function pointer type for drag and drop callbacks. * * This is the function pointer type for drop callbacks. A drop * callback function has the following signature: * @code * int function_name(GLFWwindow* window, const char* mime, const char* text) * @endcode * * @param[in] window The window that received the event. * @param[in] mime The UTF-8 encoded drop mime-type * @param[in] data The dropped data or NULL for drag enter events * @param[in] sz The size of the dropped data * @return For drag events should return the priority for the specified mime type. A priority of zero * or lower means the mime type is not accepted. Highest priority will be the finally accepted mime-type. * * @pointer_lifetime The text is valid until the * callback function returns. * * @sa @ref path_drop * @sa @ref glfwSetDropCallback * * @since Added in version 3.1. * * @ingroup input */ typedef int (* GLFWdropfun)(GLFWwindow*, const char *, const char*, size_t); typedef void (* GLFWliveresizefun)(GLFWwindow*, bool); /*! @brief The function pointer type for monitor configuration callbacks. * * This is the function pointer type for monitor configuration callbacks. * A monitor callback function has the following signature: * @code * void function_name(GLFWmonitor* monitor, int event) * @endcode * * @param[in] monitor The monitor that was connected or disconnected. * @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Future * releases may add more events. * * @sa @ref monitor_event * @sa @ref glfwSetMonitorCallback * * @since Added in version 3.0. * * @ingroup monitor */ typedef void (* GLFWmonitorfun)(GLFWmonitor*,int); /*! @brief The function pointer type for joystick configuration callbacks. * * This is the function pointer type for joystick configuration callbacks. * A joystick configuration callback function has the following signature: * @code * void function_name(int jid, int event) * @endcode * * @param[in] jid The joystick that was connected or disconnected. * @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Future * releases may add more events. * * @sa @ref joystick_event * @sa @ref glfwSetJoystickCallback * * @since Added in version 3.2. * * @ingroup input */ typedef void (* GLFWjoystickfun)(int,int); typedef void (* GLFWuserdatafun)(unsigned long long, void*); typedef void (* GLFWtickcallback)(void*); typedef void (* GLFWactivationcallback)(GLFWwindow *window, const char *token, void *data); typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin, bool is_single_glyph); typedef char* (* GLFWcurrentselectionfun)(void); typedef bool (* GLFWhascurrentselectionfun)(void); typedef void (* GLFWclipboarddatafreefun)(void* data); typedef struct GLFWDataChunk { const char *data; size_t sz; GLFWclipboarddatafreefun free; void *iter, *free_data; } GLFWDataChunk; typedef enum { GLFW_CLIPBOARD, GLFW_PRIMARY_SELECTION } GLFWClipboardType; typedef GLFWDataChunk (* GLFWclipboarditerfun)(const char *mime_type, void *iter, GLFWClipboardType ctype); typedef bool (* GLFWclipboardwritedatafun)(void *object, const char *data, size_t sz); typedef bool (* GLFWimecursorpositionfun)(GLFWwindow *window, GLFWIMEUpdateEvent *ev); typedef void (* GLFWclipboardlostfun )(GLFWClipboardType); /*! @brief Video mode type. * * This describes a single video mode. * * @sa @ref monitor_modes * @sa @ref glfwGetVideoMode * @sa @ref glfwGetVideoModes * * @since Added in version 1.0. * @glfw3 Added refresh rate member. * * @ingroup monitor */ typedef struct GLFWvidmode { /*! The width, in screen coordinates, of the video mode. */ int width; /*! The height, in screen coordinates, of the video mode. */ int height; /*! The bit depth of the red channel of the video mode. */ int redBits; /*! The bit depth of the green channel of the video mode. */ int greenBits; /*! The bit depth of the blue channel of the video mode. */ int blueBits; /*! The refresh rate, in Hz, of the video mode. */ int refreshRate; } GLFWvidmode; /*! @brief Gamma ramp. * * This describes the gamma ramp for a monitor. * * @sa @ref monitor_gamma * @sa @ref glfwGetGammaRamp * @sa @ref glfwSetGammaRamp * * @since Added in version 3.0. * * @ingroup monitor */ typedef struct GLFWgammaramp { /*! An array of value describing the response of the red channel. */ unsigned short* red; /*! An array of value describing the response of the green channel. */ unsigned short* green; /*! An array of value describing the response of the blue channel. */ unsigned short* blue; /*! The number of elements in each array. */ unsigned int size; } GLFWgammaramp; /*! @brief Image data. * * This describes a single 2D image. See the documentation for each related * function what the expected pixel format is. * * @sa @ref cursor_custom * @sa @ref window_icon * * @since Added in version 2.1. * @glfw3 Removed format and bytes-per-pixel members. * * @ingroup window */ typedef struct GLFWimage { /*! The width, in pixels, of this image. */ int width; /*! The height, in pixels, of this image. */ int height; /*! The pixel data of this image, arranged left-to-right, top-to-bottom. */ unsigned char* pixels; } GLFWimage; /*! @brief Gamepad input state * * This describes the input state of a gamepad. * * @sa @ref gamepad * @sa @ref glfwGetGamepadState * * @since Added in version 3.3. * * @ingroup input */ typedef struct GLFWgamepadstate { /*! The states of each [gamepad button](@ref gamepad_buttons), `GLFW_PRESS` * or `GLFW_RELEASE`. */ unsigned char buttons[15]; /*! The states of each [gamepad axis](@ref gamepad_axes), in the range -1.0 * to 1.0 inclusive. */ float axes[6]; } GLFWgamepadstate; /************************************************************************* * GLFW API functions *************************************************************************/ /*! @brief Initializes the GLFW library. * * This function initializes the GLFW library. Before most GLFW functions can * be used, GLFW must be initialized, and before an application terminates GLFW * should be terminated in order to free any resources allocated during or * after initialization. * * If this function fails, it calls @ref glfwTerminate before returning. If it * succeeds, you should call @ref glfwTerminate before the application exits. * * Additional calls to this function after successful initialization but before * termination will return `true` immediately. * * @return `true` if successful, or `false` if an * [error](@ref error_handling) occurred. * * @errors Possible errors include @ref GLFW_PLATFORM_ERROR. * * @remark @macos This function will change the current directory of the * application to the `Contents/Resources` subdirectory of the application's * bundle, if present. This can be disabled with the @ref * GLFW_COCOA_CHDIR_RESOURCES init hint. * * @thread_safety This function must only be called from the main thread. * * @sa @ref intro_init * @sa @ref glfwTerminate * * @since Added in version 1.0. * * @ingroup init */ typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long); typedef bool (* GLFWapplicationshouldhandlereopenfun)(int); typedef bool (* GLFWhandleurlopen)(const char*); typedef void (* GLFWapplicationwillfinishlaunchingfun)(void); typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*); typedef void (* GLFWcocoarenderframefun)(GLFWwindow*); typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id); typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*); typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, int, const char*); typedef int (*glfwInit_func)(monotonic_t, bool*); GFW_EXTERN glfwInit_func glfwInit_impl; #define glfwInit glfwInit_impl typedef void (*glfwRunMainLoop_func)(GLFWtickcallback, void*); GFW_EXTERN glfwRunMainLoop_func glfwRunMainLoop_impl; #define glfwRunMainLoop glfwRunMainLoop_impl typedef void (*glfwStopMainLoop_func)(void); GFW_EXTERN glfwStopMainLoop_func glfwStopMainLoop_impl; #define glfwStopMainLoop glfwStopMainLoop_impl typedef unsigned long long (*glfwAddTimer_func)(monotonic_t, bool, GLFWuserdatafun, void *, GLFWuserdatafun); GFW_EXTERN glfwAddTimer_func glfwAddTimer_impl; #define glfwAddTimer glfwAddTimer_impl typedef void (*glfwUpdateTimer_func)(unsigned long long, monotonic_t, bool); GFW_EXTERN glfwUpdateTimer_func glfwUpdateTimer_impl; #define glfwUpdateTimer glfwUpdateTimer_impl typedef void (*glfwRemoveTimer_func)(unsigned long); GFW_EXTERN glfwRemoveTimer_func glfwRemoveTimer_impl; #define glfwRemoveTimer glfwRemoveTimer_impl typedef GLFWdrawtextfun (*glfwSetDrawTextFunction_func)(GLFWdrawtextfun); GFW_EXTERN glfwSetDrawTextFunction_func glfwSetDrawTextFunction_impl; #define glfwSetDrawTextFunction glfwSetDrawTextFunction_impl typedef GLFWcurrentselectionfun (*glfwSetCurrentSelectionCallback_func)(GLFWcurrentselectionfun); GFW_EXTERN glfwSetCurrentSelectionCallback_func glfwSetCurrentSelectionCallback_impl; #define glfwSetCurrentSelectionCallback glfwSetCurrentSelectionCallback_impl typedef GLFWhascurrentselectionfun (*glfwSetHasCurrentSelectionCallback_func)(GLFWhascurrentselectionfun); GFW_EXTERN glfwSetHasCurrentSelectionCallback_func glfwSetHasCurrentSelectionCallback_impl; #define glfwSetHasCurrentSelectionCallback glfwSetHasCurrentSelectionCallback_impl typedef GLFWimecursorpositionfun (*glfwSetIMECursorPositionCallback_func)(GLFWimecursorpositionfun); GFW_EXTERN glfwSetIMECursorPositionCallback_func glfwSetIMECursorPositionCallback_impl; #define glfwSetIMECursorPositionCallback glfwSetIMECursorPositionCallback_impl typedef void (*glfwTerminate_func)(void); GFW_EXTERN glfwTerminate_func glfwTerminate_impl; #define glfwTerminate glfwTerminate_impl typedef void (*glfwInitHint_func)(int, int); GFW_EXTERN glfwInitHint_func glfwInitHint_impl; #define glfwInitHint glfwInitHint_impl typedef void (*glfwGetVersion_func)(int*, int*, int*); GFW_EXTERN glfwGetVersion_func glfwGetVersion_impl; #define glfwGetVersion glfwGetVersion_impl typedef const char* (*glfwGetVersionString_func)(void); GFW_EXTERN glfwGetVersionString_func glfwGetVersionString_impl; #define glfwGetVersionString glfwGetVersionString_impl typedef int (*glfwGetError_func)(const char**); GFW_EXTERN glfwGetError_func glfwGetError_impl; #define glfwGetError glfwGetError_impl typedef GLFWerrorfun (*glfwSetErrorCallback_func)(GLFWerrorfun); GFW_EXTERN glfwSetErrorCallback_func glfwSetErrorCallback_impl; #define glfwSetErrorCallback glfwSetErrorCallback_impl typedef GLFWmonitor** (*glfwGetMonitors_func)(int*); GFW_EXTERN glfwGetMonitors_func glfwGetMonitors_impl; #define glfwGetMonitors glfwGetMonitors_impl typedef GLFWmonitor* (*glfwGetPrimaryMonitor_func)(void); GFW_EXTERN glfwGetPrimaryMonitor_func glfwGetPrimaryMonitor_impl; #define glfwGetPrimaryMonitor glfwGetPrimaryMonitor_impl typedef void (*glfwGetMonitorPos_func)(GLFWmonitor*, int*, int*); GFW_EXTERN glfwGetMonitorPos_func glfwGetMonitorPos_impl; #define glfwGetMonitorPos glfwGetMonitorPos_impl typedef void (*glfwGetMonitorWorkarea_func)(GLFWmonitor*, int*, int*, int*, int*); GFW_EXTERN glfwGetMonitorWorkarea_func glfwGetMonitorWorkarea_impl; #define glfwGetMonitorWorkarea glfwGetMonitorWorkarea_impl typedef void (*glfwGetMonitorPhysicalSize_func)(GLFWmonitor*, int*, int*); GFW_EXTERN glfwGetMonitorPhysicalSize_func glfwGetMonitorPhysicalSize_impl; #define glfwGetMonitorPhysicalSize glfwGetMonitorPhysicalSize_impl typedef void (*glfwGetMonitorContentScale_func)(GLFWmonitor*, float*, float*); GFW_EXTERN glfwGetMonitorContentScale_func glfwGetMonitorContentScale_impl; #define glfwGetMonitorContentScale glfwGetMonitorContentScale_impl typedef const char* (*glfwGetMonitorName_func)(GLFWmonitor*); GFW_EXTERN glfwGetMonitorName_func glfwGetMonitorName_impl; #define glfwGetMonitorName glfwGetMonitorName_impl typedef void (*glfwSetMonitorUserPointer_func)(GLFWmonitor*, void*); GFW_EXTERN glfwSetMonitorUserPointer_func glfwSetMonitorUserPointer_impl; #define glfwSetMonitorUserPointer glfwSetMonitorUserPointer_impl typedef void* (*glfwGetMonitorUserPointer_func)(GLFWmonitor*); GFW_EXTERN glfwGetMonitorUserPointer_func glfwGetMonitorUserPointer_impl; #define glfwGetMonitorUserPointer glfwGetMonitorUserPointer_impl typedef GLFWmonitorfun (*glfwSetMonitorCallback_func)(GLFWmonitorfun); GFW_EXTERN glfwSetMonitorCallback_func glfwSetMonitorCallback_impl; #define glfwSetMonitorCallback glfwSetMonitorCallback_impl typedef const GLFWvidmode* (*glfwGetVideoModes_func)(GLFWmonitor*, int*); GFW_EXTERN glfwGetVideoModes_func glfwGetVideoModes_impl; #define glfwGetVideoModes glfwGetVideoModes_impl typedef const GLFWvidmode* (*glfwGetVideoMode_func)(GLFWmonitor*); GFW_EXTERN glfwGetVideoMode_func glfwGetVideoMode_impl; #define glfwGetVideoMode glfwGetVideoMode_impl typedef void (*glfwSetGamma_func)(GLFWmonitor*, float); GFW_EXTERN glfwSetGamma_func glfwSetGamma_impl; #define glfwSetGamma glfwSetGamma_impl typedef const GLFWgammaramp* (*glfwGetGammaRamp_func)(GLFWmonitor*); GFW_EXTERN glfwGetGammaRamp_func glfwGetGammaRamp_impl; #define glfwGetGammaRamp glfwGetGammaRamp_impl typedef void (*glfwSetGammaRamp_func)(GLFWmonitor*, const GLFWgammaramp*); GFW_EXTERN glfwSetGammaRamp_func glfwSetGammaRamp_impl; #define glfwSetGammaRamp glfwSetGammaRamp_impl typedef void (*glfwDefaultWindowHints_func)(void); GFW_EXTERN glfwDefaultWindowHints_func glfwDefaultWindowHints_impl; #define glfwDefaultWindowHints glfwDefaultWindowHints_impl typedef void (*glfwWindowHint_func)(int, int); GFW_EXTERN glfwWindowHint_func glfwWindowHint_impl; #define glfwWindowHint glfwWindowHint_impl typedef void (*glfwWindowHintString_func)(int, const char*); GFW_EXTERN glfwWindowHintString_func glfwWindowHintString_impl; #define glfwWindowHintString glfwWindowHintString_impl typedef GLFWwindow* (*glfwCreateWindow_func)(int, int, const char*, GLFWmonitor*, GLFWwindow*); GFW_EXTERN glfwCreateWindow_func glfwCreateWindow_impl; #define glfwCreateWindow glfwCreateWindow_impl typedef bool (*glfwToggleFullscreen_func)(GLFWwindow*, unsigned int); GFW_EXTERN glfwToggleFullscreen_func glfwToggleFullscreen_impl; #define glfwToggleFullscreen glfwToggleFullscreen_impl typedef bool (*glfwIsFullscreen_func)(GLFWwindow*, unsigned int); GFW_EXTERN glfwIsFullscreen_func glfwIsFullscreen_impl; #define glfwIsFullscreen glfwIsFullscreen_impl typedef bool (*glfwAreSwapsAllowed_func)(const GLFWwindow*); GFW_EXTERN glfwAreSwapsAllowed_func glfwAreSwapsAllowed_impl; #define glfwAreSwapsAllowed glfwAreSwapsAllowed_impl typedef void (*glfwDestroyWindow_func)(GLFWwindow*); GFW_EXTERN glfwDestroyWindow_func glfwDestroyWindow_impl; #define glfwDestroyWindow glfwDestroyWindow_impl typedef int (*glfwWindowShouldClose_func)(GLFWwindow*); GFW_EXTERN glfwWindowShouldClose_func glfwWindowShouldClose_impl; #define glfwWindowShouldClose glfwWindowShouldClose_impl typedef void (*glfwSetWindowShouldClose_func)(GLFWwindow*, int); GFW_EXTERN glfwSetWindowShouldClose_func glfwSetWindowShouldClose_impl; #define glfwSetWindowShouldClose glfwSetWindowShouldClose_impl typedef void (*glfwSetWindowTitle_func)(GLFWwindow*, const char*); GFW_EXTERN glfwSetWindowTitle_func glfwSetWindowTitle_impl; #define glfwSetWindowTitle glfwSetWindowTitle_impl typedef void (*glfwSetWindowIcon_func)(GLFWwindow*, int, const GLFWimage*); GFW_EXTERN glfwSetWindowIcon_func glfwSetWindowIcon_impl; #define glfwSetWindowIcon glfwSetWindowIcon_impl typedef void (*glfwGetWindowPos_func)(GLFWwindow*, int*, int*); GFW_EXTERN glfwGetWindowPos_func glfwGetWindowPos_impl; #define glfwGetWindowPos glfwGetWindowPos_impl typedef void (*glfwSetWindowPos_func)(GLFWwindow*, int, int); GFW_EXTERN glfwSetWindowPos_func glfwSetWindowPos_impl; #define glfwSetWindowPos glfwSetWindowPos_impl typedef void (*glfwGetWindowSize_func)(GLFWwindow*, int*, int*); GFW_EXTERN glfwGetWindowSize_func glfwGetWindowSize_impl; #define glfwGetWindowSize glfwGetWindowSize_impl typedef void (*glfwSetWindowSizeLimits_func)(GLFWwindow*, int, int, int, int); GFW_EXTERN glfwSetWindowSizeLimits_func glfwSetWindowSizeLimits_impl; #define glfwSetWindowSizeLimits glfwSetWindowSizeLimits_impl typedef void (*glfwSetWindowSizeIncrements_func)(GLFWwindow*, int, int); GFW_EXTERN glfwSetWindowSizeIncrements_func glfwSetWindowSizeIncrements_impl; #define glfwSetWindowSizeIncrements glfwSetWindowSizeIncrements_impl typedef void (*glfwSetWindowAspectRatio_func)(GLFWwindow*, int, int); GFW_EXTERN glfwSetWindowAspectRatio_func glfwSetWindowAspectRatio_impl; #define glfwSetWindowAspectRatio glfwSetWindowAspectRatio_impl typedef void (*glfwSetWindowSize_func)(GLFWwindow*, int, int); GFW_EXTERN glfwSetWindowSize_func glfwSetWindowSize_impl; #define glfwSetWindowSize glfwSetWindowSize_impl typedef void (*glfwGetFramebufferSize_func)(GLFWwindow*, int*, int*); GFW_EXTERN glfwGetFramebufferSize_func glfwGetFramebufferSize_impl; #define glfwGetFramebufferSize glfwGetFramebufferSize_impl typedef void (*glfwGetWindowFrameSize_func)(GLFWwindow*, int*, int*, int*, int*); GFW_EXTERN glfwGetWindowFrameSize_func glfwGetWindowFrameSize_impl; #define glfwGetWindowFrameSize glfwGetWindowFrameSize_impl typedef void (*glfwGetWindowContentScale_func)(GLFWwindow*, float*, float*); GFW_EXTERN glfwGetWindowContentScale_func glfwGetWindowContentScale_impl; #define glfwGetWindowContentScale glfwGetWindowContentScale_impl typedef monotonic_t (*glfwGetDoubleClickInterval_func)(GLFWwindow*); GFW_EXTERN glfwGetDoubleClickInterval_func glfwGetDoubleClickInterval_impl; #define glfwGetDoubleClickInterval glfwGetDoubleClickInterval_impl typedef float (*glfwGetWindowOpacity_func)(GLFWwindow*); GFW_EXTERN glfwGetWindowOpacity_func glfwGetWindowOpacity_impl; #define glfwGetWindowOpacity glfwGetWindowOpacity_impl typedef void (*glfwSetWindowOpacity_func)(GLFWwindow*, float); GFW_EXTERN glfwSetWindowOpacity_func glfwSetWindowOpacity_impl; #define glfwSetWindowOpacity glfwSetWindowOpacity_impl typedef void (*glfwIconifyWindow_func)(GLFWwindow*); GFW_EXTERN glfwIconifyWindow_func glfwIconifyWindow_impl; #define glfwIconifyWindow glfwIconifyWindow_impl typedef void (*glfwRestoreWindow_func)(GLFWwindow*); GFW_EXTERN glfwRestoreWindow_func glfwRestoreWindow_impl; #define glfwRestoreWindow glfwRestoreWindow_impl typedef void (*glfwMaximizeWindow_func)(GLFWwindow*); GFW_EXTERN glfwMaximizeWindow_func glfwMaximizeWindow_impl; #define glfwMaximizeWindow glfwMaximizeWindow_impl typedef void (*glfwShowWindow_func)(GLFWwindow*); GFW_EXTERN glfwShowWindow_func glfwShowWindow_impl; #define glfwShowWindow glfwShowWindow_impl typedef void (*glfwHideWindow_func)(GLFWwindow*); GFW_EXTERN glfwHideWindow_func glfwHideWindow_impl; #define glfwHideWindow glfwHideWindow_impl typedef void (*glfwFocusWindow_func)(GLFWwindow*); GFW_EXTERN glfwFocusWindow_func glfwFocusWindow_impl; #define glfwFocusWindow glfwFocusWindow_impl typedef void (*glfwRequestWindowAttention_func)(GLFWwindow*); GFW_EXTERN glfwRequestWindowAttention_func glfwRequestWindowAttention_impl; #define glfwRequestWindowAttention glfwRequestWindowAttention_impl typedef int (*glfwWindowBell_func)(GLFWwindow*); GFW_EXTERN glfwWindowBell_func glfwWindowBell_impl; #define glfwWindowBell glfwWindowBell_impl typedef GLFWmonitor* (*glfwGetWindowMonitor_func)(GLFWwindow*); GFW_EXTERN glfwGetWindowMonitor_func glfwGetWindowMonitor_impl; #define glfwGetWindowMonitor glfwGetWindowMonitor_impl typedef void (*glfwSetWindowMonitor_func)(GLFWwindow*, GLFWmonitor*, int, int, int, int, int); GFW_EXTERN glfwSetWindowMonitor_func glfwSetWindowMonitor_impl; #define glfwSetWindowMonitor glfwSetWindowMonitor_impl typedef int (*glfwGetWindowAttrib_func)(GLFWwindow*, int); GFW_EXTERN glfwGetWindowAttrib_func glfwGetWindowAttrib_impl; #define glfwGetWindowAttrib glfwGetWindowAttrib_impl typedef void (*glfwSetWindowAttrib_func)(GLFWwindow*, int, int); GFW_EXTERN glfwSetWindowAttrib_func glfwSetWindowAttrib_impl; #define glfwSetWindowAttrib glfwSetWindowAttrib_impl typedef int (*glfwSetWindowBlur_func)(GLFWwindow*, int); GFW_EXTERN glfwSetWindowBlur_func glfwSetWindowBlur_impl; #define glfwSetWindowBlur glfwSetWindowBlur_impl typedef void (*glfwSetWindowUserPointer_func)(GLFWwindow*, void*); GFW_EXTERN glfwSetWindowUserPointer_func glfwSetWindowUserPointer_impl; #define glfwSetWindowUserPointer glfwSetWindowUserPointer_impl typedef void* (*glfwGetWindowUserPointer_func)(GLFWwindow*); GFW_EXTERN glfwGetWindowUserPointer_func glfwGetWindowUserPointer_impl; #define glfwGetWindowUserPointer glfwGetWindowUserPointer_impl typedef GLFWwindowposfun (*glfwSetWindowPosCallback_func)(GLFWwindow*, GLFWwindowposfun); GFW_EXTERN glfwSetWindowPosCallback_func glfwSetWindowPosCallback_impl; #define glfwSetWindowPosCallback glfwSetWindowPosCallback_impl typedef GLFWwindowsizefun (*glfwSetWindowSizeCallback_func)(GLFWwindow*, GLFWwindowsizefun); GFW_EXTERN glfwSetWindowSizeCallback_func glfwSetWindowSizeCallback_impl; #define glfwSetWindowSizeCallback glfwSetWindowSizeCallback_impl typedef GLFWwindowclosefun (*glfwSetWindowCloseCallback_func)(GLFWwindow*, GLFWwindowclosefun); GFW_EXTERN glfwSetWindowCloseCallback_func glfwSetWindowCloseCallback_impl; #define glfwSetWindowCloseCallback glfwSetWindowCloseCallback_impl typedef GLFWapplicationclosefun (*glfwSetApplicationCloseCallback_func)(GLFWapplicationclosefun); GFW_EXTERN glfwSetApplicationCloseCallback_func glfwSetApplicationCloseCallback_impl; #define glfwSetApplicationCloseCallback glfwSetApplicationCloseCallback_impl typedef GLFWsystemcolorthemechangefun (*glfwSetSystemColorThemeChangeCallback_func)(GLFWsystemcolorthemechangefun); GFW_EXTERN glfwSetSystemColorThemeChangeCallback_func glfwSetSystemColorThemeChangeCallback_impl; #define glfwSetSystemColorThemeChangeCallback glfwSetSystemColorThemeChangeCallback_impl typedef GLFWclipboardlostfun (*glfwSetClipboardLostCallback_func)(GLFWclipboardlostfun); GFW_EXTERN glfwSetClipboardLostCallback_func glfwSetClipboardLostCallback_impl; #define glfwSetClipboardLostCallback glfwSetClipboardLostCallback_impl typedef GLFWColorScheme (*glfwGetCurrentSystemColorTheme_func)(bool); GFW_EXTERN glfwGetCurrentSystemColorTheme_func glfwGetCurrentSystemColorTheme_impl; #define glfwGetCurrentSystemColorTheme glfwGetCurrentSystemColorTheme_impl typedef GLFWwindowrefreshfun (*glfwSetWindowRefreshCallback_func)(GLFWwindow*, GLFWwindowrefreshfun); GFW_EXTERN glfwSetWindowRefreshCallback_func glfwSetWindowRefreshCallback_impl; #define glfwSetWindowRefreshCallback glfwSetWindowRefreshCallback_impl typedef GLFWwindowfocusfun (*glfwSetWindowFocusCallback_func)(GLFWwindow*, GLFWwindowfocusfun); GFW_EXTERN glfwSetWindowFocusCallback_func glfwSetWindowFocusCallback_impl; #define glfwSetWindowFocusCallback glfwSetWindowFocusCallback_impl typedef GLFWwindowocclusionfun (*glfwSetWindowOcclusionCallback_func)(GLFWwindow*, GLFWwindowocclusionfun); GFW_EXTERN glfwSetWindowOcclusionCallback_func glfwSetWindowOcclusionCallback_impl; #define glfwSetWindowOcclusionCallback glfwSetWindowOcclusionCallback_impl typedef GLFWwindowiconifyfun (*glfwSetWindowIconifyCallback_func)(GLFWwindow*, GLFWwindowiconifyfun); GFW_EXTERN glfwSetWindowIconifyCallback_func glfwSetWindowIconifyCallback_impl; #define glfwSetWindowIconifyCallback glfwSetWindowIconifyCallback_impl typedef GLFWwindowmaximizefun (*glfwSetWindowMaximizeCallback_func)(GLFWwindow*, GLFWwindowmaximizefun); GFW_EXTERN glfwSetWindowMaximizeCallback_func glfwSetWindowMaximizeCallback_impl; #define glfwSetWindowMaximizeCallback glfwSetWindowMaximizeCallback_impl typedef GLFWframebuffersizefun (*glfwSetFramebufferSizeCallback_func)(GLFWwindow*, GLFWframebuffersizefun); GFW_EXTERN glfwSetFramebufferSizeCallback_func glfwSetFramebufferSizeCallback_impl; #define glfwSetFramebufferSizeCallback glfwSetFramebufferSizeCallback_impl typedef GLFWwindowcontentscalefun (*glfwSetWindowContentScaleCallback_func)(GLFWwindow*, GLFWwindowcontentscalefun); GFW_EXTERN glfwSetWindowContentScaleCallback_func glfwSetWindowContentScaleCallback_impl; #define glfwSetWindowContentScaleCallback glfwSetWindowContentScaleCallback_impl typedef void (*glfwPostEmptyEvent_func)(void); GFW_EXTERN glfwPostEmptyEvent_func glfwPostEmptyEvent_impl; #define glfwPostEmptyEvent glfwPostEmptyEvent_impl typedef bool (*glfwGetIgnoreOSKeyboardProcessing_func)(void); GFW_EXTERN glfwGetIgnoreOSKeyboardProcessing_func glfwGetIgnoreOSKeyboardProcessing_impl; #define glfwGetIgnoreOSKeyboardProcessing glfwGetIgnoreOSKeyboardProcessing_impl typedef void (*glfwSetIgnoreOSKeyboardProcessing_func)(bool); GFW_EXTERN glfwSetIgnoreOSKeyboardProcessing_func glfwSetIgnoreOSKeyboardProcessing_impl; #define glfwSetIgnoreOSKeyboardProcessing glfwSetIgnoreOSKeyboardProcessing_impl typedef int (*glfwGetInputMode_func)(GLFWwindow*, int); GFW_EXTERN glfwGetInputMode_func glfwGetInputMode_impl; #define glfwGetInputMode glfwGetInputMode_impl typedef void (*glfwSetInputMode_func)(GLFWwindow*, int, int); GFW_EXTERN glfwSetInputMode_func glfwSetInputMode_impl; #define glfwSetInputMode glfwSetInputMode_impl typedef int (*glfwRawMouseMotionSupported_func)(void); GFW_EXTERN glfwRawMouseMotionSupported_func glfwRawMouseMotionSupported_impl; #define glfwRawMouseMotionSupported glfwRawMouseMotionSupported_impl typedef const char* (*glfwGetKeyName_func)(uint32_t, int); GFW_EXTERN glfwGetKeyName_func glfwGetKeyName_impl; #define glfwGetKeyName glfwGetKeyName_impl typedef int (*glfwGetNativeKeyForKey_func)(uint32_t); GFW_EXTERN glfwGetNativeKeyForKey_func glfwGetNativeKeyForKey_impl; #define glfwGetNativeKeyForKey glfwGetNativeKeyForKey_impl typedef GLFWKeyAction (*glfwGetKey_func)(GLFWwindow*, uint32_t); GFW_EXTERN glfwGetKey_func glfwGetKey_impl; #define glfwGetKey glfwGetKey_impl typedef int (*glfwGetMouseButton_func)(GLFWwindow*, int); GFW_EXTERN glfwGetMouseButton_func glfwGetMouseButton_impl; #define glfwGetMouseButton glfwGetMouseButton_impl typedef void (*glfwGetCursorPos_func)(GLFWwindow*, double*, double*); GFW_EXTERN glfwGetCursorPos_func glfwGetCursorPos_impl; #define glfwGetCursorPos glfwGetCursorPos_impl typedef void (*glfwSetCursorPos_func)(GLFWwindow*, double, double); GFW_EXTERN glfwSetCursorPos_func glfwSetCursorPos_impl; #define glfwSetCursorPos glfwSetCursorPos_impl typedef GLFWcursor* (*glfwCreateCursor_func)(const GLFWimage*, int, int, int); GFW_EXTERN glfwCreateCursor_func glfwCreateCursor_impl; #define glfwCreateCursor glfwCreateCursor_impl typedef GLFWcursor* (*glfwCreateStandardCursor_func)(GLFWCursorShape); GFW_EXTERN glfwCreateStandardCursor_func glfwCreateStandardCursor_impl; #define glfwCreateStandardCursor glfwCreateStandardCursor_impl typedef void (*glfwDestroyCursor_func)(GLFWcursor*); GFW_EXTERN glfwDestroyCursor_func glfwDestroyCursor_impl; #define glfwDestroyCursor glfwDestroyCursor_impl typedef void (*glfwSetCursor_func)(GLFWwindow*, GLFWcursor*); GFW_EXTERN glfwSetCursor_func glfwSetCursor_impl; #define glfwSetCursor glfwSetCursor_impl typedef GLFWkeyboardfun (*glfwSetKeyboardCallback_func)(GLFWwindow*, GLFWkeyboardfun); GFW_EXTERN glfwSetKeyboardCallback_func glfwSetKeyboardCallback_impl; #define glfwSetKeyboardCallback glfwSetKeyboardCallback_impl typedef void (*glfwUpdateIMEState_func)(GLFWwindow*, const GLFWIMEUpdateEvent*); GFW_EXTERN glfwUpdateIMEState_func glfwUpdateIMEState_impl; #define glfwUpdateIMEState glfwUpdateIMEState_impl typedef GLFWmousebuttonfun (*glfwSetMouseButtonCallback_func)(GLFWwindow*, GLFWmousebuttonfun); GFW_EXTERN glfwSetMouseButtonCallback_func glfwSetMouseButtonCallback_impl; #define glfwSetMouseButtonCallback glfwSetMouseButtonCallback_impl typedef GLFWcursorposfun (*glfwSetCursorPosCallback_func)(GLFWwindow*, GLFWcursorposfun); GFW_EXTERN glfwSetCursorPosCallback_func glfwSetCursorPosCallback_impl; #define glfwSetCursorPosCallback glfwSetCursorPosCallback_impl typedef GLFWcursorenterfun (*glfwSetCursorEnterCallback_func)(GLFWwindow*, GLFWcursorenterfun); GFW_EXTERN glfwSetCursorEnterCallback_func glfwSetCursorEnterCallback_impl; #define glfwSetCursorEnterCallback glfwSetCursorEnterCallback_impl typedef GLFWscrollfun (*glfwSetScrollCallback_func)(GLFWwindow*, GLFWscrollfun); GFW_EXTERN glfwSetScrollCallback_func glfwSetScrollCallback_impl; #define glfwSetScrollCallback glfwSetScrollCallback_impl typedef GLFWdropfun (*glfwSetDropCallback_func)(GLFWwindow*, GLFWdropfun); GFW_EXTERN glfwSetDropCallback_func glfwSetDropCallback_impl; #define glfwSetDropCallback glfwSetDropCallback_impl typedef GLFWliveresizefun (*glfwSetLiveResizeCallback_func)(GLFWwindow*, GLFWliveresizefun); GFW_EXTERN glfwSetLiveResizeCallback_func glfwSetLiveResizeCallback_impl; #define glfwSetLiveResizeCallback glfwSetLiveResizeCallback_impl typedef int (*glfwJoystickPresent_func)(int); GFW_EXTERN glfwJoystickPresent_func glfwJoystickPresent_impl; #define glfwJoystickPresent glfwJoystickPresent_impl typedef const float* (*glfwGetJoystickAxes_func)(int, int*); GFW_EXTERN glfwGetJoystickAxes_func glfwGetJoystickAxes_impl; #define glfwGetJoystickAxes glfwGetJoystickAxes_impl typedef const unsigned char* (*glfwGetJoystickButtons_func)(int, int*); GFW_EXTERN glfwGetJoystickButtons_func glfwGetJoystickButtons_impl; #define glfwGetJoystickButtons glfwGetJoystickButtons_impl typedef const unsigned char* (*glfwGetJoystickHats_func)(int, int*); GFW_EXTERN glfwGetJoystickHats_func glfwGetJoystickHats_impl; #define glfwGetJoystickHats glfwGetJoystickHats_impl typedef const char* (*glfwGetJoystickName_func)(int); GFW_EXTERN glfwGetJoystickName_func glfwGetJoystickName_impl; #define glfwGetJoystickName glfwGetJoystickName_impl typedef const char* (*glfwGetJoystickGUID_func)(int); GFW_EXTERN glfwGetJoystickGUID_func glfwGetJoystickGUID_impl; #define glfwGetJoystickGUID glfwGetJoystickGUID_impl typedef void (*glfwSetJoystickUserPointer_func)(int, void*); GFW_EXTERN glfwSetJoystickUserPointer_func glfwSetJoystickUserPointer_impl; #define glfwSetJoystickUserPointer glfwSetJoystickUserPointer_impl typedef void* (*glfwGetJoystickUserPointer_func)(int); GFW_EXTERN glfwGetJoystickUserPointer_func glfwGetJoystickUserPointer_impl; #define glfwGetJoystickUserPointer glfwGetJoystickUserPointer_impl typedef int (*glfwJoystickIsGamepad_func)(int); GFW_EXTERN glfwJoystickIsGamepad_func glfwJoystickIsGamepad_impl; #define glfwJoystickIsGamepad glfwJoystickIsGamepad_impl typedef GLFWjoystickfun (*glfwSetJoystickCallback_func)(GLFWjoystickfun); GFW_EXTERN glfwSetJoystickCallback_func glfwSetJoystickCallback_impl; #define glfwSetJoystickCallback glfwSetJoystickCallback_impl typedef int (*glfwUpdateGamepadMappings_func)(const char*); GFW_EXTERN glfwUpdateGamepadMappings_func glfwUpdateGamepadMappings_impl; #define glfwUpdateGamepadMappings glfwUpdateGamepadMappings_impl typedef const char* (*glfwGetGamepadName_func)(int); GFW_EXTERN glfwGetGamepadName_func glfwGetGamepadName_impl; #define glfwGetGamepadName glfwGetGamepadName_impl typedef int (*glfwGetGamepadState_func)(int, GLFWgamepadstate*); GFW_EXTERN glfwGetGamepadState_func glfwGetGamepadState_impl; #define glfwGetGamepadState glfwGetGamepadState_impl typedef void (*glfwSetClipboardDataTypes_func)(GLFWClipboardType, const char* const*, size_t, GLFWclipboarditerfun); GFW_EXTERN glfwSetClipboardDataTypes_func glfwSetClipboardDataTypes_impl; #define glfwSetClipboardDataTypes glfwSetClipboardDataTypes_impl typedef void (*glfwGetClipboard_func)(GLFWClipboardType, const char*, GLFWclipboardwritedatafun, void*); GFW_EXTERN glfwGetClipboard_func glfwGetClipboard_impl; #define glfwGetClipboard glfwGetClipboard_impl typedef monotonic_t (*glfwGetTime_func)(void); GFW_EXTERN glfwGetTime_func glfwGetTime_impl; #define glfwGetTime glfwGetTime_impl typedef void (*glfwMakeContextCurrent_func)(GLFWwindow*); GFW_EXTERN glfwMakeContextCurrent_func glfwMakeContextCurrent_impl; #define glfwMakeContextCurrent glfwMakeContextCurrent_impl typedef GLFWwindow* (*glfwGetCurrentContext_func)(void); GFW_EXTERN glfwGetCurrentContext_func glfwGetCurrentContext_impl; #define glfwGetCurrentContext glfwGetCurrentContext_impl typedef void (*glfwSwapBuffers_func)(GLFWwindow*); GFW_EXTERN glfwSwapBuffers_func glfwSwapBuffers_impl; #define glfwSwapBuffers glfwSwapBuffers_impl typedef void (*glfwSwapInterval_func)(int); GFW_EXTERN glfwSwapInterval_func glfwSwapInterval_impl; #define glfwSwapInterval glfwSwapInterval_impl typedef int (*glfwExtensionSupported_func)(const char*); GFW_EXTERN glfwExtensionSupported_func glfwExtensionSupported_impl; #define glfwExtensionSupported glfwExtensionSupported_impl typedef GLFWglproc (*glfwGetProcAddress_func)(const char*); GFW_EXTERN glfwGetProcAddress_func glfwGetProcAddress_impl; #define glfwGetProcAddress glfwGetProcAddress_impl typedef int (*glfwVulkanSupported_func)(void); GFW_EXTERN glfwVulkanSupported_func glfwVulkanSupported_impl; #define glfwVulkanSupported glfwVulkanSupported_impl typedef const char** (*glfwGetRequiredInstanceExtensions_func)(uint32_t*); GFW_EXTERN glfwGetRequiredInstanceExtensions_func glfwGetRequiredInstanceExtensions_impl; #define glfwGetRequiredInstanceExtensions glfwGetRequiredInstanceExtensions_impl typedef void* (*glfwGetCocoaWindow_func)(GLFWwindow*); GFW_EXTERN glfwGetCocoaWindow_func glfwGetCocoaWindow_impl; #define glfwGetCocoaWindow glfwGetCocoaWindow_impl typedef void* (*glfwGetNSGLContext_func)(GLFWwindow*); GFW_EXTERN glfwGetNSGLContext_func glfwGetNSGLContext_impl; #define glfwGetNSGLContext glfwGetNSGLContext_impl typedef uint32_t (*glfwGetCocoaMonitor_func)(GLFWmonitor*); GFW_EXTERN glfwGetCocoaMonitor_func glfwGetCocoaMonitor_impl; #define glfwGetCocoaMonitor glfwGetCocoaMonitor_impl typedef GLFWcocoatextinputfilterfun (*glfwSetCocoaTextInputFilter_func)(GLFWwindow*, GLFWcocoatextinputfilterfun); GFW_EXTERN glfwSetCocoaTextInputFilter_func glfwSetCocoaTextInputFilter_impl; #define glfwSetCocoaTextInputFilter glfwSetCocoaTextInputFilter_impl typedef GLFWhandleurlopen (*glfwSetCocoaURLOpenCallback_func)(GLFWhandleurlopen); GFW_EXTERN glfwSetCocoaURLOpenCallback_func glfwSetCocoaURLOpenCallback_impl; #define glfwSetCocoaURLOpenCallback glfwSetCocoaURLOpenCallback_impl typedef GLFWcocoatogglefullscreenfun (*glfwSetCocoaToggleFullscreenIntercept_func)(GLFWwindow*, GLFWcocoatogglefullscreenfun); GFW_EXTERN glfwSetCocoaToggleFullscreenIntercept_func glfwSetCocoaToggleFullscreenIntercept_impl; #define glfwSetCocoaToggleFullscreenIntercept glfwSetCocoaToggleFullscreenIntercept_impl typedef GLFWapplicationshouldhandlereopenfun (*glfwSetApplicationShouldHandleReopen_func)(GLFWapplicationshouldhandlereopenfun); GFW_EXTERN glfwSetApplicationShouldHandleReopen_func glfwSetApplicationShouldHandleReopen_impl; #define glfwSetApplicationShouldHandleReopen glfwSetApplicationShouldHandleReopen_impl typedef GLFWapplicationwillfinishlaunchingfun (*glfwSetApplicationWillFinishLaunching_func)(GLFWapplicationwillfinishlaunchingfun); GFW_EXTERN glfwSetApplicationWillFinishLaunching_func glfwSetApplicationWillFinishLaunching_impl; #define glfwSetApplicationWillFinishLaunching glfwSetApplicationWillFinishLaunching_impl typedef uint32_t (*glfwGetCocoaKeyEquivalent_func)(uint32_t, int, int*); GFW_EXTERN glfwGetCocoaKeyEquivalent_func glfwGetCocoaKeyEquivalent_impl; #define glfwGetCocoaKeyEquivalent glfwGetCocoaKeyEquivalent_impl typedef void (*glfwCocoaRequestRenderFrame_func)(GLFWwindow*, GLFWcocoarenderframefun); GFW_EXTERN glfwCocoaRequestRenderFrame_func glfwCocoaRequestRenderFrame_impl; #define glfwCocoaRequestRenderFrame glfwCocoaRequestRenderFrame_impl typedef GLFWcocoarenderframefun (*glfwCocoaSetWindowResizeCallback_func)(GLFWwindow*, GLFWcocoarenderframefun); GFW_EXTERN glfwCocoaSetWindowResizeCallback_func glfwCocoaSetWindowResizeCallback_impl; #define glfwCocoaSetWindowResizeCallback glfwCocoaSetWindowResizeCallback_impl typedef void* (*glfwGetX11Display_func)(void); GFW_EXTERN glfwGetX11Display_func glfwGetX11Display_impl; #define glfwGetX11Display glfwGetX11Display_impl typedef unsigned long (*glfwGetX11Window_func)(GLFWwindow*); GFW_EXTERN glfwGetX11Window_func glfwGetX11Window_impl; #define glfwGetX11Window glfwGetX11Window_impl typedef void (*glfwSetPrimarySelectionString_func)(GLFWwindow*, const char*); GFW_EXTERN glfwSetPrimarySelectionString_func glfwSetPrimarySelectionString_impl; #define glfwSetPrimarySelectionString glfwSetPrimarySelectionString_impl typedef void (*glfwCocoaSetWindowChrome_func)(GLFWwindow*, unsigned int, bool, unsigned int, int, unsigned int, bool, int, float, bool); GFW_EXTERN glfwCocoaSetWindowChrome_func glfwCocoaSetWindowChrome_impl; #define glfwCocoaSetWindowChrome glfwCocoaSetWindowChrome_impl typedef const char* (*glfwGetPrimarySelectionString_func)(GLFWwindow*); GFW_EXTERN glfwGetPrimarySelectionString_func glfwGetPrimarySelectionString_impl; #define glfwGetPrimarySelectionString glfwGetPrimarySelectionString_impl typedef int (*glfwGetNativeKeyForName_func)(const char*, int); GFW_EXTERN glfwGetNativeKeyForName_func glfwGetNativeKeyForName_impl; #define glfwGetNativeKeyForName glfwGetNativeKeyForName_impl typedef void (*glfwRequestWaylandFrameEvent_func)(GLFWwindow*, unsigned long long, GLFWwaylandframecallbackfunc); GFW_EXTERN glfwRequestWaylandFrameEvent_func glfwRequestWaylandFrameEvent_impl; #define glfwRequestWaylandFrameEvent glfwRequestWaylandFrameEvent_impl typedef void (*glfwWaylandActivateWindow_func)(GLFWwindow*, const char*); GFW_EXTERN glfwWaylandActivateWindow_func glfwWaylandActivateWindow_impl; #define glfwWaylandActivateWindow glfwWaylandActivateWindow_impl typedef const char* (*glfwWaylandMissingCapabilities_func)(void); GFW_EXTERN glfwWaylandMissingCapabilities_func glfwWaylandMissingCapabilities_impl; #define glfwWaylandMissingCapabilities glfwWaylandMissingCapabilities_impl typedef void (*glfwWaylandRunWithActivationToken_func)(GLFWwindow*, GLFWactivationcallback, void*); GFW_EXTERN glfwWaylandRunWithActivationToken_func glfwWaylandRunWithActivationToken_impl; #define glfwWaylandRunWithActivationToken glfwWaylandRunWithActivationToken_impl typedef bool (*glfwWaylandSetTitlebarColor_func)(GLFWwindow*, uint32_t, bool); GFW_EXTERN glfwWaylandSetTitlebarColor_func glfwWaylandSetTitlebarColor_impl; #define glfwWaylandSetTitlebarColor glfwWaylandSetTitlebarColor_impl typedef void (*glfwWaylandRedrawCSDWindowTitle_func)(GLFWwindow*); GFW_EXTERN glfwWaylandRedrawCSDWindowTitle_func glfwWaylandRedrawCSDWindowTitle_impl; #define glfwWaylandRedrawCSDWindowTitle glfwWaylandRedrawCSDWindowTitle_impl typedef bool (*glfwWaylandIsWindowFullyCreated_func)(GLFWwindow*); GFW_EXTERN glfwWaylandIsWindowFullyCreated_func glfwWaylandIsWindowFullyCreated_impl; #define glfwWaylandIsWindowFullyCreated glfwWaylandIsWindowFullyCreated_impl typedef void (*glfwWaylandSetupLayerShellForNextWindow_func)(const GLFWLayerShellConfig*); GFW_EXTERN glfwWaylandSetupLayerShellForNextWindow_func glfwWaylandSetupLayerShellForNextWindow_impl; #define glfwWaylandSetupLayerShellForNextWindow glfwWaylandSetupLayerShellForNextWindow_impl typedef pid_t (*glfwWaylandCompositorPID_func)(void); GFW_EXTERN glfwWaylandCompositorPID_func glfwWaylandCompositorPID_impl; #define glfwWaylandCompositorPID glfwWaylandCompositorPID_impl typedef unsigned long long (*glfwDBusUserNotify_func)(const GLFWDBUSNotificationData*, GLFWDBusnotificationcreatedfun, void*); GFW_EXTERN glfwDBusUserNotify_func glfwDBusUserNotify_impl; #define glfwDBusUserNotify glfwDBusUserNotify_impl typedef void (*glfwDBusSetUserNotificationHandler_func)(GLFWDBusnotificationactivatedfun); GFW_EXTERN glfwDBusSetUserNotificationHandler_func glfwDBusSetUserNotificationHandler_impl; #define glfwDBusSetUserNotificationHandler glfwDBusSetUserNotificationHandler_impl typedef int (*glfwSetX11LaunchCommand_func)(GLFWwindow*, char**, int); GFW_EXTERN glfwSetX11LaunchCommand_func glfwSetX11LaunchCommand_impl; #define glfwSetX11LaunchCommand glfwSetX11LaunchCommand_impl typedef void (*glfwSetX11WindowAsDock_func)(int32_t); GFW_EXTERN glfwSetX11WindowAsDock_func glfwSetX11WindowAsDock_impl; #define glfwSetX11WindowAsDock glfwSetX11WindowAsDock_impl typedef void (*glfwSetX11WindowStrut_func)(int32_t, uint32_t[12]); GFW_EXTERN glfwSetX11WindowStrut_func glfwSetX11WindowStrut_impl; #define glfwSetX11WindowStrut glfwSetX11WindowStrut_impl const char* load_glfw(const char* path); kitty-0.41.1/kitty/glfw.c0000664000175000017510000032625414773370543014572 0ustar nileshnilesh/* * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "state.h" #include "cleanup.h" #include "monotonic.h" #include "charsets.h" #include "control-codes.h" #include #include "glfw-wrapper.h" #include "gl.h" #ifdef __APPLE__ #include "cocoa_window.h" #else #include "freetype_render_ui_text.h" #endif #define debug debug_rendering typedef struct mouse_cursor { GLFWcursor *glfw; bool initialized, is_custom; } mouse_cursor; static mouse_cursor cursors[GLFW_INVALID_CURSOR+1] = {0}; static void apply_swap_interval(int val) { (void)val; #ifndef __APPLE__ if (val < 0) val = OPT(sync_to_monitor) && !global_state.is_wayland ? 1 : 0; glfwSwapInterval(val); #endif } void get_platform_dependent_config_values(void *glfw_window) { if (OPT(click_interval) < 0) OPT(click_interval) = glfwGetDoubleClickInterval(glfw_window); if (OPT(cursor_blink_interval) < 0) { OPT(cursor_blink_interval) = ms_to_monotonic_t(500ll); #ifdef __APPLE__ monotonic_t cbi = cocoa_cursor_blink_interval(); if (cbi >= 0) OPT(cursor_blink_interval) = cbi / 2; #endif } } static const char* appearance_name(GLFWColorScheme appearance) { const char *which = NULL; switch (appearance) { case GLFW_COLOR_SCHEME_NO_PREFERENCE: which = "no_preference"; break; case GLFW_COLOR_SCHEME_DARK: which = "dark"; break; case GLFW_COLOR_SCHEME_LIGHT: which = "light"; break; } return which; } static void on_system_color_scheme_change(GLFWColorScheme appearance, bool is_initial_value) { const char *which = appearance_name(appearance); debug("system color-scheme changed to: %s is_initial_value: %d\n", which, is_initial_value); call_boss(on_system_color_scheme_change, "sO", which, is_initial_value ? Py_True : Py_False); } static void on_clipboard_lost(GLFWClipboardType which) { call_boss(on_clipboard_lost, "s", which == GLFW_CLIPBOARD ? "clipboard" : "primary"); } static void strip_csi_(const char *title, char *buf, size_t bufsz) { enum { NORMAL, IN_ESC, IN_CSI} state = NORMAL; char *dest = buf, *last = &buf[bufsz-1]; *dest = 0; *last = 0; for (; *title && dest < last; title++) { const unsigned char ch = *title; switch (state) { case NORMAL: { if (ch == 0x1b) { state = IN_ESC; } else *(dest++) = ch; } break; case IN_ESC: { if (ch == '[') { state = IN_CSI; } else { if (ch >= ' ' && ch != DEL) *(dest++) = ch; state = NORMAL; } } break; case IN_CSI: { if (!(('0' <= ch && ch <= '9') || ch == ';' || ch == ':')) { if (ch > DEL) *(dest++) = ch; // UTF-8 multibyte state = NORMAL; } } break; } } *dest = 0; } void update_menu_bar_title(PyObject *title UNUSED) { #ifdef __APPLE__ static char buf[2048]; strip_csi_(PyUnicode_AsUTF8(title), buf, arraysz(buf)); RAII_PyObject(stitle, PyUnicode_FromString(buf)); if (stitle) cocoa_update_menu_bar_title(stitle); else PyErr_Print(); #endif } void request_tick_callback(void) { glfwPostEmptyEvent(); } static void min_size_for_os_window(OSWindow *window, int *min_width, int *min_height) { *min_width = MAX(8u, window->fonts_data->fcm.cell_width + 1); *min_height = MAX(8u, window->fonts_data->fcm.cell_height + 1); } static void get_window_dpi(GLFWwindow *w, double *x, double *y); static void get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xdpi, double *ydpi); void update_os_window_viewport(OSWindow *window, bool notify_boss) { int w, h, fw, fh; glfwGetFramebufferSize(window->handle, &fw, &fh); glfwGetWindowSize(window->handle, &w, &h); double xdpi = window->fonts_data->logical_dpi_x, ydpi = window->fonts_data->logical_dpi_y, new_xdpi, new_ydpi; float xscale, yscale; get_window_content_scale(window->handle, &xscale, &yscale, &new_xdpi, &new_ydpi); if (fw == window->viewport_width && fh == window->viewport_height && w == window->window_width && h == window->window_height && xdpi == new_xdpi && ydpi == new_ydpi) { return; // no change, ignore } int min_width, min_height; min_size_for_os_window(window, &min_width, &min_height); window->viewport_resized_at = monotonic(); if (w <= 0 || h <= 0 || fw < min_width || fh < min_height || (xscale >=1 && fw < w) || (yscale >= 1 && fh < h)) { log_error("Invalid geometry ignored: framebuffer: %dx%d window: %dx%d scale: %f %f\n", fw, fh, w, h, xscale, yscale); if (!window->viewport_updated_at_least_once) { window->viewport_width = min_width; window->viewport_height = min_height; window->window_width = min_width; window->window_height = min_height; window->viewport_x_ratio = 1; window->viewport_y_ratio = 1; window->viewport_size_dirty = true; if (notify_boss) { call_boss(on_window_resize, "KiiO", window->id, window->viewport_width, window->viewport_height, Py_False); } } return; } window->viewport_updated_at_least_once = true; window->viewport_width = fw; window->viewport_height = fh; double xr = window->viewport_x_ratio, yr = window->viewport_y_ratio; window->viewport_x_ratio = (double)window->viewport_width / (double)w; window->viewport_y_ratio = (double)window->viewport_height / (double)h; bool dpi_changed = (xr != 0.0 && xr != window->viewport_x_ratio) || (yr != 0.0 && yr != window->viewport_y_ratio) || (xdpi != new_xdpi) || (ydpi != new_ydpi); window->viewport_size_dirty = true; window->viewport_width = MAX(window->viewport_width, min_width); window->viewport_height = MAX(window->viewport_height, min_height); window->window_width = MAX(w, min_width); window->window_height = MAX(h, min_height); if (notify_boss) { call_boss(on_window_resize, "KiiO", window->id, window->viewport_width, window->viewport_height, dpi_changed ? Py_True : Py_False); } } // callbacks {{{ void update_os_window_references(void) { for (size_t i = 0; i < global_state.num_os_windows; i++) { OSWindow *w = global_state.os_windows + i; if (w->handle) glfwSetWindowUserPointer(w->handle, w); } } static OSWindow* os_window_for_glfw_window(GLFWwindow *w) { OSWindow *ans = glfwGetWindowUserPointer(w); if (ans != NULL) return ans; for (size_t i = 0; i < global_state.num_os_windows; i++) { if ((GLFWwindow*)(global_state.os_windows[i].handle) == w) { return global_state.os_windows + i; } } return NULL; } static bool set_callback_window(GLFWwindow *w) { global_state.callback_os_window = os_window_for_glfw_window(w); return global_state.callback_os_window != NULL; } static bool is_window_ready_for_callbacks(void) { OSWindow *w = global_state.callback_os_window; if (w->num_tabs == 0) return false; Tab *t = w->tabs + w->active_tab; if (t->num_windows == 0) return false; return true; } #define WINDOW_CALLBACK(name, fmt, ...) call_boss(name, "K" fmt, global_state.callback_os_window->id, __VA_ARGS__) static void show_mouse_cursor(GLFWwindow *w) { glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } void blank_os_window(OSWindow *osw) { color_type color = OPT(background); if (osw->num_tabs > 0) { Tab *t = osw->tabs + osw->active_tab; if (t->num_windows == 1) { Window *w = t->windows + t->active_window; Screen *s = w->render_data.screen; if (s) { color = colorprofile_to_color(s->color_profile, s->color_profile->overridden.default_bg, s->color_profile->configured.default_bg).rgb; } } } blank_canvas(osw->is_semi_transparent ? osw->background_opacity : 1.0f, color); } static void window_pos_callback(GLFWwindow* window, int x UNUSED, int y UNUSED) { if (!set_callback_window(window)) return; #ifdef __APPLE__ // Apple needs IME position to be accurate before the next key event OSWindow *osw = global_state.callback_os_window; if (osw->is_focused && is_window_ready_for_callbacks()) { Tab *tab = osw->tabs + osw->active_tab; Window *w = tab->windows + tab->active_window; if (w->render_data.screen) update_ime_position(w, w->render_data.screen); } #endif global_state.callback_os_window = NULL; } static void window_close_callback(GLFWwindow* window) { if (!set_callback_window(window)) return; global_state.callback_os_window->close_request = CONFIRMABLE_CLOSE_REQUESTED; global_state.has_pending_closes = true; request_tick_callback(); glfwSetWindowShouldClose(window, false); global_state.callback_os_window = NULL; } static void window_occlusion_callback(GLFWwindow *window, bool occluded) { if (!set_callback_window(window)) return; debug("OSWindow %llu occlusion state changed, occluded: %d\n", global_state.callback_os_window->id, occluded); if (!occluded) global_state.check_for_active_animated_images = true; request_tick_callback(); global_state.callback_os_window = NULL; } static void window_iconify_callback(GLFWwindow *window, int iconified) { if (!set_callback_window(window)) return; if (!iconified) global_state.check_for_active_animated_images = true; request_tick_callback(); global_state.callback_os_window = NULL; } #ifdef __APPLE__ static void cocoa_out_of_sequence_render(OSWindow *window) { make_os_window_context_current(window); window->needs_render = true; bool rendered = render_os_window(window, monotonic(), true, true); if (!rendered) { blank_os_window(window); swap_window_buffers(window); } window->needs_render = true; } static void cocoa_os_window_resized(GLFWwindow *w) { if (!set_callback_window(w)) return; if (global_state.callback_os_window->ignore_resize_events) return; cocoa_out_of_sequence_render(global_state.callback_os_window); global_state.callback_os_window = NULL; } #endif void change_live_resize_state(OSWindow *w, bool in_progress) { if (in_progress != w->live_resize.in_progress) { w->live_resize.in_progress = in_progress; w->live_resize.num_of_resize_events = 0; #ifdef __APPLE__ cocoa_out_of_sequence_render(w); #else GLFWwindow *orig_ctx = make_os_window_context_current(w); apply_swap_interval(in_progress ? 0 : -1); if (orig_ctx) glfwMakeContextCurrent(orig_ctx); #endif } } static void live_resize_callback(GLFWwindow *w, bool started) { if (!set_callback_window(w)) return; if (global_state.callback_os_window->ignore_resize_events) return; global_state.callback_os_window->live_resize.from_os_notification = true; change_live_resize_state(global_state.callback_os_window, true); global_state.has_pending_resizes = true; if (!started) { global_state.callback_os_window->live_resize.os_says_resize_complete = true; request_tick_callback(); } global_state.callback_os_window = NULL; } static void framebuffer_size_callback(GLFWwindow *w, int width, int height) { if (!set_callback_window(w)) return; if (global_state.callback_os_window->ignore_resize_events) return; int min_width, min_height; min_size_for_os_window(global_state.callback_os_window, &min_width, &min_height); if (width >= min_width && height >= min_height) { OSWindow *window = global_state.callback_os_window; global_state.has_pending_resizes = true; change_live_resize_state(global_state.callback_os_window, true); window->live_resize.last_resize_event_at = monotonic(); window->live_resize.width = MAX(0, width); window->live_resize.height = MAX(0, height); window->live_resize.num_of_resize_events++; make_os_window_context_current(window); update_surface_size(width, height, 0); request_tick_callback(); } else log_error("Ignoring resize request for tiny size: %dx%d", width, height); global_state.callback_os_window = NULL; } static void dpi_change_callback(GLFWwindow *w, float x_scale UNUSED, float y_scale UNUSED) { if (!set_callback_window(w)) return; if (global_state.callback_os_window->ignore_resize_events) return; // Ensure update_os_window_viewport() is called in the near future, it will // take care of DPI changes. OSWindow *window = global_state.callback_os_window; change_live_resize_state(global_state.callback_os_window, true); global_state.has_pending_resizes = true; window->live_resize.last_resize_event_at = monotonic(); global_state.callback_os_window = NULL; request_tick_callback(); } static void refresh_callback(GLFWwindow *w) { if (!set_callback_window(w)) return; if (!global_state.callback_os_window->redraw_count) global_state.callback_os_window->redraw_count++; global_state.callback_os_window = NULL; request_tick_callback(); } static int mods_at_last_key_or_button_event = 0; #ifndef __APPLE__ typedef struct modifier_key_state { bool left, right; } modifier_key_state; static int key_to_modifier(uint32_t key, bool *is_left) { *is_left = false; switch(key) { case GLFW_FKEY_LEFT_SHIFT: *is_left = true; /* fallthrough */ case GLFW_FKEY_RIGHT_SHIFT: return GLFW_MOD_SHIFT; case GLFW_FKEY_LEFT_CONTROL: *is_left = true; /* fallthrough */ case GLFW_FKEY_RIGHT_CONTROL: return GLFW_MOD_CONTROL; case GLFW_FKEY_LEFT_ALT: *is_left = true; /* fallthrough */ case GLFW_FKEY_RIGHT_ALT: return GLFW_MOD_ALT; case GLFW_FKEY_LEFT_SUPER: *is_left = true; /* fallthrough */ case GLFW_FKEY_RIGHT_SUPER: return GLFW_MOD_SUPER; case GLFW_FKEY_LEFT_HYPER: *is_left = true; /* fallthrough */ case GLFW_FKEY_RIGHT_HYPER: return GLFW_MOD_HYPER; case GLFW_FKEY_LEFT_META: *is_left = true; /* fallthrough */ case GLFW_FKEY_RIGHT_META: return GLFW_MOD_META; default: return -1; } } static void update_modifier_state_on_modifier_key_event(GLFWkeyevent *ev, int key_modifier, bool is_left) { // Update mods state to be what the kitty keyboard protocol requires, as on Linux modifier key events do not update modifier bits static modifier_key_state all_states[8] = {0}; modifier_key_state *state = all_states + MIN((unsigned)__builtin_ctz(key_modifier), sizeof(all_states)-1); const int modifier_was_set_before_event = ev->mods & key_modifier; const bool is_release = ev->action == GLFW_RELEASE; if (modifier_was_set_before_event) { // a press with modifier already set means other modifier key is pressed if (!is_release) { if (is_left) state->right = true; else state->left = true; } } else { // if modifier is not set before event, means both keys are released state->left = false; state->right = false; } if (is_release) { if (is_left) state->left = false; else state->right = false; if (modifier_was_set_before_event && !state->left && !state->right) ev->mods &= ~key_modifier; } else { if (is_left) state->left = true; else state->right = true; ev->mods |= key_modifier; } } #endif static void key_callback(GLFWwindow *w, GLFWkeyevent *ev) { if (!set_callback_window(w)) return; #ifndef __APPLE__ bool is_left; int key_modifier = key_to_modifier(ev->key, &is_left); if (key_modifier != -1) update_modifier_state_on_modifier_key_event(ev, key_modifier, is_left); #endif mods_at_last_key_or_button_event = ev->mods; global_state.callback_os_window->cursor_blink_zero_time = monotonic(); if (is_window_ready_for_callbacks() && !ev->fake_event_on_focus_change) on_key_input(ev); global_state.callback_os_window = NULL; request_tick_callback(); } static void cursor_enter_callback(GLFWwindow *w, int entered) { if (!set_callback_window(w)) return; if (entered) { double x, y; glfwGetCursorPos(w, &x, &y); debug_input("Mouse cursor entered window: %llu at %fx%f\n", global_state.callback_os_window->id, x, y); show_mouse_cursor(w); monotonic_t now = monotonic(); global_state.callback_os_window->last_mouse_activity_at = now; global_state.callback_os_window->mouse_x = x * global_state.callback_os_window->viewport_x_ratio; global_state.callback_os_window->mouse_y = y * global_state.callback_os_window->viewport_y_ratio; if (is_window_ready_for_callbacks()) enter_event(mods_at_last_key_or_button_event); request_tick_callback(); } else debug_input("Mouse cursor left window: %llu\n", global_state.callback_os_window->id); global_state.callback_os_window = NULL; } static void mouse_button_callback(GLFWwindow *w, int button, int action, int mods) { if (!set_callback_window(w)) return; show_mouse_cursor(w); mods_at_last_key_or_button_event = mods; monotonic_t now = monotonic(); OSWindow *window = global_state.callback_os_window; window->last_mouse_activity_at = now; if (button >= 0 && (unsigned int)button < arraysz(global_state.callback_os_window->mouse_button_pressed)) { if (!window->has_received_cursor_pos_event) { // ensure mouse position is correct window->has_received_cursor_pos_event = true; double x, y; glfwGetCursorPos(w, &x, &y); window->mouse_x = x * window->viewport_x_ratio; window->mouse_y = y * window->viewport_y_ratio; if (is_window_ready_for_callbacks()) mouse_event(-1, mods, -1); } global_state.callback_os_window->mouse_button_pressed[button] = action == GLFW_PRESS ? true : false; if (is_window_ready_for_callbacks()) mouse_event(button, mods, action); } request_tick_callback(); global_state.callback_os_window = NULL; } static void cursor_pos_callback(GLFWwindow *w, double x, double y) { if (!set_callback_window(w)) return; show_mouse_cursor(w); monotonic_t now = monotonic(); global_state.callback_os_window->last_mouse_activity_at = now; global_state.callback_os_window->cursor_blink_zero_time = now; global_state.callback_os_window->mouse_x = x * global_state.callback_os_window->viewport_x_ratio; global_state.callback_os_window->mouse_y = y * global_state.callback_os_window->viewport_y_ratio; global_state.callback_os_window->has_received_cursor_pos_event = true; if (is_window_ready_for_callbacks()) mouse_event(-1, mods_at_last_key_or_button_event, -1); request_tick_callback(); global_state.callback_os_window = NULL; } static void scroll_callback(GLFWwindow *w, double xoffset, double yoffset, int flags, int mods) { if (!set_callback_window(w)) return; show_mouse_cursor(w); monotonic_t now = monotonic(); global_state.callback_os_window->last_mouse_activity_at = now; if (is_window_ready_for_callbacks()) scroll_event(xoffset, yoffset, flags, mods); request_tick_callback(); global_state.callback_os_window = NULL; } static id_type focus_counter = 0; static void window_focus_callback(GLFWwindow *w, int focused) { if (!set_callback_window(w)) return; debug_input("\x1b[35mon_focus_change\x1b[m: window id: 0x%llu focused: %d\n", global_state.callback_os_window->id, focused); global_state.callback_os_window->is_focused = focused ? true : false; if (focused) { show_mouse_cursor(w); focus_in_event(); global_state.callback_os_window->last_focused_counter = ++focus_counter; global_state.check_for_active_animated_images = true; } monotonic_t now = monotonic(); global_state.callback_os_window->last_mouse_activity_at = now; global_state.callback_os_window->cursor_blink_zero_time = now; if (is_window_ready_for_callbacks()) { WINDOW_CALLBACK(on_focus, "O", focused ? Py_True : Py_False); GLFWIMEUpdateEvent ev = { .type = GLFW_IME_UPDATE_FOCUS, .focused = focused }; glfwUpdateIMEState(global_state.callback_os_window->handle, &ev); if (focused) { Tab *tab = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; Window *window = tab->windows + tab->active_window; if (window->render_data.screen) update_ime_position(window, window->render_data.screen); } } request_tick_callback(); global_state.callback_os_window = NULL; } static int drop_callback(GLFWwindow *w, const char *mime, const char *data, size_t sz) { if (!set_callback_window(w)) return 0; #define RETURN(x) { global_state.callback_os_window = NULL; return x; } if (!data) { if (strcmp(mime, "text/uri-list") == 0) RETURN(3); if (strcmp(mime, "text/plain;charset=utf-8") == 0) RETURN(2); if (strcmp(mime, "text/plain") == 0) RETURN(1); RETURN(0); } WINDOW_CALLBACK(on_drop, "sy#", mime, data, (Py_ssize_t)sz); request_tick_callback(); RETURN(0); #undef RETURN } static void application_close_requested_callback(int flags) { if (flags) { global_state.quit_request = IMPERATIVE_CLOSE_REQUESTED; global_state.has_pending_closes = true; request_tick_callback(); } else { if (global_state.quit_request == NO_CLOSE_REQUESTED) { global_state.has_pending_closes = true; global_state.quit_request = CONFIRMABLE_CLOSE_REQUESTED; request_tick_callback(); } } } static char* get_current_selection(void) { if (!global_state.boss) return NULL; PyObject *ret = PyObject_CallMethod(global_state.boss, "get_active_selection", NULL); if (!ret) { PyErr_Print(); return NULL; } char* ans = NULL; if (PyUnicode_Check(ret)) ans = strdup(PyUnicode_AsUTF8(ret)); Py_DECREF(ret); return ans; } static bool has_current_selection(void) { if (!global_state.boss) return false; PyObject *ret = PyObject_CallMethod(global_state.boss, "has_active_selection", NULL); if (!ret) { PyErr_Print(); return false; } bool ans = ret == Py_True; Py_DECREF(ret); return ans; } void prepare_ime_position_update_event(OSWindow *osw, Window *w, Screen *screen, GLFWIMEUpdateEvent *ev); static bool get_ime_cursor_position(GLFWwindow *glfw_window, GLFWIMEUpdateEvent *ev) { bool ans = false; OSWindow *osw = os_window_for_glfw_window(glfw_window); if (osw && osw->is_focused && osw->num_tabs > 0) { Tab *tab = osw->tabs + osw->active_tab; if (tab->num_windows > 0) { Window *w = tab->windows + tab->active_window; Screen *screen = w->render_data.screen; if (screen) { prepare_ime_position_update_event(osw, w, screen, ev); ans = true; } } } return ans; } #ifdef __APPLE__ static bool apple_url_open_callback(const char* url) { set_cocoa_pending_action(LAUNCH_URLS, url); return true; } bool draw_window_title(OSWindow *window UNUSED, const char *text, color_type fg, color_type bg, uint8_t *output_buf, size_t width, size_t height) { static char buf[2048]; strip_csi_(text, buf, arraysz(buf)); return cocoa_render_line_of_text(buf, fg, bg, output_buf, width, height); } uint8_t* draw_single_ascii_char(const char ch, size_t *result_width, size_t *result_height) { uint8_t *ans = render_single_ascii_char_as_mask(ch, result_width, result_height); if (PyErr_Occurred()) PyErr_Print(); return ans; } #else static FreeTypeRenderCtx csd_title_render_ctx = NULL; static bool ensure_csd_title_render_ctx(void) { if (!csd_title_render_ctx) { csd_title_render_ctx = create_freetype_render_context(NULL, true, false); if (!csd_title_render_ctx) { if (PyErr_Occurred()) PyErr_Print(); return false; } } return true; } static bool draw_text_callback(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin, bool is_single_glyph) { if (!set_callback_window(window)) return false; if (!ensure_csd_title_render_ctx()) return false; double xdpi, ydpi; get_window_dpi(window, &xdpi, &ydpi); unsigned px_sz = 2 * height / 3; static char title[2048]; if (!is_single_glyph) { snprintf(title, sizeof(title), " ❭ %s", text); text = title; } bool ok = render_single_line(csd_title_render_ctx, text, px_sz, fg, bg, output_buf, width, height, x_offset, y_offset, right_margin, is_single_glyph); if (!ok && PyErr_Occurred()) PyErr_Print(); return ok; } bool draw_window_title(OSWindow *window, const char *text, color_type fg, color_type bg, uint8_t *output_buf, size_t width, size_t height) { if (!ensure_csd_title_render_ctx()) return false; static char buf[2048]; strip_csi_(text, buf, arraysz(buf)); unsigned px_sz = (unsigned)(window->fonts_data->font_sz_in_pts * window->fonts_data->logical_dpi_y / 72.); px_sz = MIN(px_sz, 3 * height / 4); #define RGB2BGR(x) (x & 0xFF000000) | ((x & 0xFF0000) >> 16) | (x & 0x00FF00) | ((x & 0x0000FF) << 16) bool ok = render_single_line(csd_title_render_ctx, buf, px_sz, RGB2BGR(fg), RGB2BGR(bg), output_buf, width, height, 0, 0, 0, false); #undef RGB2BGR if (!ok && PyErr_Occurred()) PyErr_Print(); return ok; } uint8_t* draw_single_ascii_char(const char ch, size_t *result_width, size_t *result_height) { if (!ensure_csd_title_render_ctx()) return NULL; uint8_t *ans = render_single_ascii_char_as_mask(csd_title_render_ctx, ch, result_width, result_height); if (PyErr_Occurred()) PyErr_Print(); return ans; } #endif // }}} static void set_glfw_mouse_cursor(GLFWwindow *w, GLFWCursorShape shape) { if (!cursors[shape].initialized) { cursors[shape].initialized = true; cursors[shape].glfw = glfwCreateStandardCursor(shape); } if (cursors[shape].glfw) glfwSetCursor(w, cursors[shape].glfw); } static void set_glfw_mouse_pointer_shape_in_window(GLFWwindow *w, MouseShape type) { switch(type) { case INVALID_POINTER: break; /* start enum to glfw (auto generated by gen-key-constants.py do not edit) */ case DEFAULT_POINTER: set_glfw_mouse_cursor(w, GLFW_DEFAULT_CURSOR); break; case TEXT_POINTER: set_glfw_mouse_cursor(w, GLFW_TEXT_CURSOR); break; case POINTER_POINTER: set_glfw_mouse_cursor(w, GLFW_POINTER_CURSOR); break; case HELP_POINTER: set_glfw_mouse_cursor(w, GLFW_HELP_CURSOR); break; case WAIT_POINTER: set_glfw_mouse_cursor(w, GLFW_WAIT_CURSOR); break; case PROGRESS_POINTER: set_glfw_mouse_cursor(w, GLFW_PROGRESS_CURSOR); break; case CROSSHAIR_POINTER: set_glfw_mouse_cursor(w, GLFW_CROSSHAIR_CURSOR); break; case CELL_POINTER: set_glfw_mouse_cursor(w, GLFW_CELL_CURSOR); break; case VERTICAL_TEXT_POINTER: set_glfw_mouse_cursor(w, GLFW_VERTICAL_TEXT_CURSOR); break; case MOVE_POINTER: set_glfw_mouse_cursor(w, GLFW_MOVE_CURSOR); break; case E_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_E_RESIZE_CURSOR); break; case NE_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_NE_RESIZE_CURSOR); break; case NW_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_NW_RESIZE_CURSOR); break; case N_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_N_RESIZE_CURSOR); break; case SE_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_SE_RESIZE_CURSOR); break; case SW_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_SW_RESIZE_CURSOR); break; case S_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_S_RESIZE_CURSOR); break; case W_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_W_RESIZE_CURSOR); break; case EW_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_EW_RESIZE_CURSOR); break; case NS_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_NS_RESIZE_CURSOR); break; case NESW_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_NESW_RESIZE_CURSOR); break; case NWSE_RESIZE_POINTER: set_glfw_mouse_cursor(w, GLFW_NWSE_RESIZE_CURSOR); break; case ZOOM_IN_POINTER: set_glfw_mouse_cursor(w, GLFW_ZOOM_IN_CURSOR); break; case ZOOM_OUT_POINTER: set_glfw_mouse_cursor(w, GLFW_ZOOM_OUT_CURSOR); break; case ALIAS_POINTER: set_glfw_mouse_cursor(w, GLFW_ALIAS_CURSOR); break; case COPY_POINTER: set_glfw_mouse_cursor(w, GLFW_COPY_CURSOR); break; case NOT_ALLOWED_POINTER: set_glfw_mouse_cursor(w, GLFW_NOT_ALLOWED_CURSOR); break; case NO_DROP_POINTER: set_glfw_mouse_cursor(w, GLFW_NO_DROP_CURSOR); break; case GRAB_POINTER: set_glfw_mouse_cursor(w, GLFW_GRAB_CURSOR); break; case GRABBING_POINTER: set_glfw_mouse_cursor(w, GLFW_GRABBING_CURSOR); break; /* end enum to glfw */ } } void set_mouse_cursor(MouseShape type) { if (global_state.callback_os_window) { GLFWwindow *w = (GLFWwindow*)global_state.callback_os_window->handle; set_glfw_mouse_pointer_shape_in_window(w, type); } } static GLFWimage logo = {0}; static PyObject* set_default_window_icon(PyObject UNUSED *self, PyObject *args) { size_t sz; unsigned int width, height; const char *path; uint8_t *data; if(!PyArg_ParseTuple(args, "s", &path)) return NULL; if (png_path_to_bitmap(path, &data, &width, &height, &sz)) { #ifndef __APPLE__ if (!global_state.is_wayland && (width > 128 || height > 128)) { return PyErr_Format(PyExc_ValueError, "The window icon is too large (%dx%d). On X11 max window icon size is: 128x128. Create a file called ~/.config/kitty.app-128.png containing a 128x128 image to use as the window icon on X11.", width, height); } #endif logo.width = width; logo.height = height; logo.pixels = data; } Py_RETURN_NONE; } static PyObject* set_os_window_icon(PyObject UNUSED *self, PyObject *args) { size_t sz; unsigned int width, height; PyObject *what = NULL; uint8_t *data; unsigned long long id; if(!PyArg_ParseTuple(args, "K|O", &id, &what)) return NULL; OSWindow *os_window = os_window_for_id(id); if (!os_window) { PyErr_Format(PyExc_KeyError, "No OS Window with id: %llu", id); return NULL; } if (!what || what == Py_None) { glfwSetWindowIcon(os_window->handle, 0, NULL); Py_RETURN_NONE; } if (PyUnicode_Check(what)) { const char *path = PyUnicode_AsUTF8(what); if (png_path_to_bitmap(path, &data, &width, &height, &sz)) { GLFWimage img = { .pixels = data, .width = width, .height = height }; glfwSetWindowIcon(os_window->handle, 1, &img); free(data); } else { PyErr_Format(PyExc_ValueError, "%s is not a valid PNG image", path); return NULL; } Py_RETURN_NONE; } RAII_PY_BUFFER(buf); if(!PyArg_ParseTuple(args, "Ky*", &id, &buf)) return NULL; if (png_from_data(buf.buf, buf.len, "", &data, &width, &height, &sz)) { GLFWimage img = { .pixels = data, .width = width, .height = height }; glfwSetWindowIcon(os_window->handle, 1, &img); } else { PyErr_Format(PyExc_ValueError, "The supplied data of %lu bytes is not a valid PNG image", (unsigned long)buf.len); return NULL; } Py_RETURN_NONE; } void* make_os_window_context_current(OSWindow *w) { GLFWwindow *current_context = glfwGetCurrentContext(); if (w->handle != current_context) { glfwMakeContextCurrent(w->handle); return current_context; } return NULL; } void get_os_window_size(OSWindow *os_window, int *w, int *h, int *fw, int *fh) { if (w && h) glfwGetWindowSize(os_window->handle, w, h); if (fw && fh) glfwGetFramebufferSize(os_window->handle, fw, fh); } void set_os_window_size(OSWindow *os_window, int x, int y) { glfwSetWindowSize(os_window->handle, x, y); } void get_os_window_pos(OSWindow *os_window, int *x, int *y) { glfwGetWindowPos(os_window->handle, x, y); } void set_os_window_pos(OSWindow *os_window, int x, int y) { glfwSetWindowPos(os_window->handle, x, y); } static void dpi_from_scale(float xscale, float yscale, double *xdpi, double *ydpi) { #ifdef __APPLE__ const double factor = 72.0; #else const double factor = 96.0; #endif *xdpi = xscale * factor; *ydpi = yscale * factor; } static void get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xdpi, double *ydpi) { // if you change this function also change createSurface() in wl_window.c *xscale = 1; *yscale = 1; if (w) glfwGetWindowContentScale(w, xscale, yscale); else { GLFWmonitor *monitor = glfwGetPrimaryMonitor(); if (monitor) glfwGetMonitorContentScale(monitor, xscale, yscale); } // check for zero, negative, NaN or excessive values of xscale/yscale if (*xscale <= 0.0001 || *xscale != *xscale || *xscale >= 24) *xscale = 1.0; if (*yscale <= 0.0001 || *yscale != *yscale || *yscale >= 24) *yscale = 1.0; dpi_from_scale(*xscale, *yscale, xdpi, ydpi); } static void get_window_dpi(GLFWwindow *w, double *x, double *y) { float xscale, yscale; get_window_content_scale(w, &xscale, &yscale, x, y); } void get_os_window_content_scale(OSWindow *os_window, double *xdpi, double *ydpi, float *xscale, float *yscale) { get_window_content_scale(os_window->handle, xscale, yscale, xdpi, ydpi); } static bool do_toggle_fullscreen(OSWindow *w, unsigned int flags, bool restore_sizes) { int width, height, x, y; glfwGetWindowSize(w->handle, &width, &height); if (!global_state.is_wayland) glfwGetWindowPos(w->handle, &x, &y); bool was_maximized = glfwGetWindowAttrib(w->handle, GLFW_MAXIMIZED); if (glfwToggleFullscreen(w->handle, flags)) { w->before_fullscreen.is_set = true; w->before_fullscreen.w = width; w->before_fullscreen.h = height; w->before_fullscreen.x = x; w->before_fullscreen.y = y; w->before_fullscreen.was_maximized = was_maximized; return true; } if (w->before_fullscreen.is_set && restore_sizes) { glfwSetWindowSize(w->handle, w->before_fullscreen.w, w->before_fullscreen.h); if (!global_state.is_wayland) glfwSetWindowPos(w->handle, w->before_fullscreen.x, w->before_fullscreen.y); if (w->before_fullscreen.was_maximized) glfwMaximizeWindow(w->handle); } return false; } static bool toggle_fullscreen_for_os_window(OSWindow *w) { if (w && w->handle) { #ifdef __APPLE__ if (!OPT(macos_traditional_fullscreen)) return do_toggle_fullscreen(w, 1, false); #endif return do_toggle_fullscreen(w, 0, true); } return false; } bool is_os_window_fullscreen(OSWindow *w) { unsigned int flags = 0; #ifdef __APPLE__ if (!OPT(macos_traditional_fullscreen)) flags = 1; #endif if (w && w->handle) return glfwIsFullscreen(w->handle, flags); return false; } static bool toggle_maximized_for_os_window(OSWindow *w) { bool maximized = false; if (w && w->handle) { if (glfwGetWindowAttrib(w->handle, GLFW_MAXIMIZED)) { glfwRestoreWindow(w->handle); } else { glfwMaximizeWindow(w->handle); maximized = true; } } return maximized; } static void change_state_for_os_window(OSWindow *w, int state) { if (!w || !w->handle) return; switch (state) { case WINDOW_MAXIMIZED: glfwMaximizeWindow(w->handle); break; case WINDOW_MINIMIZED: glfwIconifyWindow(w->handle); break; case WINDOW_FULLSCREEN: if (!is_os_window_fullscreen(w)) toggle_fullscreen_for_os_window(w); break; case WINDOW_NORMAL: if (is_os_window_fullscreen(w)) toggle_fullscreen_for_os_window(w); else glfwRestoreWindow(w->handle); break; } } #ifdef __APPLE__ static GLFWwindow *apple_preserve_common_context = NULL; static int filter_option(int key UNUSED, int mods, unsigned int native_key UNUSED, unsigned long flags) { mods &= ~(GLFW_MOD_NUM_LOCK | GLFW_MOD_CAPS_LOCK); if ((mods == GLFW_MOD_ALT) || (mods == (GLFW_MOD_ALT | GLFW_MOD_SHIFT))) { if (OPT(macos_option_as_alt) == 3) return 1; if (cocoa_alt_option_key_pressed(flags)) return 1; } return 0; } static bool on_application_reopen(int has_visible_windows) { if (has_visible_windows) return true; set_cocoa_pending_action(NEW_OS_WINDOW, NULL); return false; } static bool intercept_cocoa_fullscreen(GLFWwindow *w) { if (!OPT(macos_traditional_fullscreen) || !set_callback_window(w)) return false; toggle_fullscreen_for_os_window(global_state.callback_os_window); global_state.callback_os_window = NULL; return true; } #endif static void init_window_chrome_state(WindowChromeState *s, color_type active_window_bg, bool is_semi_transparent, float background_opacity) { zero_at_ptr(s); const bool should_blur = background_opacity < 1.f && OPT(background_blur) > 0 && is_semi_transparent; #define SET_TCOL(val) \ s->use_system_color = false; \ switch (val & 0xff) { \ case 0: s->use_system_color = true; s->color = active_window_bg; break; \ case 1: s->color = active_window_bg; break; \ default: s->color = val >> 8; break; \ } #ifdef __APPLE__ if (OPT(macos_titlebar_color) < 0) { s->use_system_color = true; s->system_color = -OPT(macos_titlebar_color); } else { unsigned long val = OPT(macos_titlebar_color); SET_TCOL(val); } s->macos_colorspace = OPT(macos_colorspace); s->resizable = OPT(macos_window_resizable); #else if (global_state.is_wayland) { SET_TCOL(OPT(wayland_titlebar_color)); } #endif s->background_blur = should_blur ? OPT(background_blur) : 0; s->hide_window_decorations = OPT(hide_window_decorations); s->show_title_in_titlebar = (OPT(macos_show_window_title_in) & WINDOW) != 0; s->background_opacity = background_opacity; } static void apply_window_chrome_state(GLFWwindow *w, WindowChromeState new_state, int width, int height, bool window_decorations_changed) { #ifdef __APPLE__ glfwCocoaSetWindowChrome(w, new_state.color, new_state.use_system_color, new_state.system_color, new_state.background_blur, new_state.hide_window_decorations, new_state.show_title_in_titlebar, new_state.macos_colorspace, new_state.background_opacity, new_state.resizable ); // Need to resize the window again after hiding decorations or title bar to take up screen space if (window_decorations_changed) glfwSetWindowSize(w, width, height); #else if (window_decorations_changed) { bool hide_window_decorations = new_state.hide_window_decorations & 1; glfwSetWindowAttrib(w, GLFW_DECORATED, !hide_window_decorations); glfwSetWindowSize(w, width, height); } glfwSetWindowBlur(w, new_state.background_blur); if (global_state.is_wayland) { if (glfwWaylandSetTitlebarColor) glfwWaylandSetTitlebarColor(w, new_state.color, new_state.use_system_color); } #endif } void set_os_window_chrome(OSWindow *w) { if (!w->handle) return; color_type bg = OPT(background); if (w->num_tabs > w->active_tab) { Tab *tab = w->tabs + w->active_tab; if (tab->num_windows > tab->active_window) { Window *window = tab->windows + tab->active_window; ColorProfile *c; if (window->render_data.screen && (c=window->render_data.screen->color_profile)) { bg = colorprofile_to_color(c, c->overridden.default_bg, c->configured.default_bg).rgb; } } } WindowChromeState new_state; init_window_chrome_state(&new_state, bg, w->is_semi_transparent, w->background_opacity); if (memcmp(&new_state, &w->last_window_chrome, sizeof(WindowChromeState)) != 0) { int width, height; glfwGetWindowSize(w->handle, &width, &height); bool window_decorations_changed = new_state.hide_window_decorations != w->last_window_chrome.hide_window_decorations; apply_window_chrome_state(w->handle, new_state, width, height, window_decorations_changed); w->last_window_chrome = new_state; } } static PyObject* native_window_handle(GLFWwindow *w) { #ifdef __APPLE__ void *ans = glfwGetCocoaWindow(w); return PyLong_FromVoidPtr(ans); #endif if (glfwGetX11Window) return PyLong_FromUnsignedLong(glfwGetX11Window(w)); return Py_None; } static PyObject* edge_spacing_func = NULL; static double edge_spacing(GLFWEdge which) { const char* edge = "top"; switch(which) { case GLFW_EDGE_TOP: edge = "top"; break; case GLFW_EDGE_BOTTOM: edge = "bottom"; break; case GLFW_EDGE_LEFT: edge = "left"; break; case GLFW_EDGE_RIGHT: edge = "right"; break; case GLFW_EDGE_CENTER: case GLFW_EDGE_NONE: return 0; } if (!edge_spacing_func) { log_error("Attempt to call edge_spacing() without first setting edge_spacing_func"); return 100; } RAII_PyObject(ret, PyObject_CallFunction(edge_spacing_func, "s", edge)); if (!ret) { PyErr_Print(); return 100; } if (!PyFloat_Check(ret)) { log_error("edge_spacing_func() return something other than a float"); return 100; } return PyFloat_AsDouble(ret); } static void calculate_layer_shell_window_size( GLFWwindow *window, const GLFWLayerShellConfig *config, unsigned monitor_width, unsigned monitor_height, uint32_t *width, uint32_t *height) { request_tick_callback(); if (config->type == GLFW_LAYER_SHELL_BACKGROUND) { if (!*width) *width = monitor_width; if (!*height) *height = monitor_height; return; } float xscale = (float)config->expected.xscale, yscale = (float)config->expected.yscale; double xdpi = config->expected.xdpi, ydpi = config->expected.ydpi; if (glfwWaylandIsWindowFullyCreated(window)) { glfwGetWindowContentScale(window, &xscale, &yscale); dpi_from_scale(xscale, yscale, &xdpi, &ydpi); } OSWindow *os_window = os_window_for_glfw_window(window); debug("Calculating layer shell window size at scale: %f\n", xscale); FONTS_DATA_HANDLE fonts_data = load_fonts_data(os_window ? os_window->fonts_data->font_sz_in_pts : OPT(font_size), xdpi, ydpi); if (config->edge == GLFW_EDGE_LEFT || config->edge == GLFW_EDGE_RIGHT) { if (!*height) *height = monitor_height; double spacing = edge_spacing(GLFW_EDGE_LEFT) + edge_spacing(GLFW_EDGE_RIGHT); spacing *= xdpi / 72.; spacing += (fonts_data->fcm.cell_width * config->x_size_in_cells) / xscale; *width = (uint32_t)(1. + spacing); } else if (config->edge == GLFW_EDGE_TOP || config->edge == GLFW_EDGE_BOTTOM) { if (!*width) *width = monitor_width; double spacing = edge_spacing(GLFW_EDGE_TOP) + edge_spacing(GLFW_EDGE_BOTTOM); spacing *= ydpi / 72.; spacing += (fonts_data->fcm.cell_height * config->y_size_in_cells) / yscale; *height = (uint32_t)(1. + spacing); } else if (config->edge == GLFW_EDGE_CENTER) { if (!*width) *width = monitor_width; if (!*height) *height = monitor_height; } else { double spacing_x = edge_spacing(GLFW_EDGE_LEFT) + edge_spacing(GLFW_EDGE_RIGHT); spacing_x *= xdpi / 72.; spacing_x += (fonts_data->fcm.cell_width * config->x_size_in_cells) / xscale; double spacing_y = edge_spacing(GLFW_EDGE_TOP) + edge_spacing(GLFW_EDGE_BOTTOM); spacing_y *= ydpi / 72.; spacing_y += (fonts_data->fcm.cell_height * config->y_size_in_cells) / yscale; *width = (uint32_t)(1. + spacing_x); *height = (uint32_t)(1. + spacing_y); } } static bool translate_layer_shell_config(PyObject *p, GLFWLayerShellConfig *ans) { memset(ans, 0, sizeof(GLFWLayerShellConfig)); ans->size_callback = calculate_layer_shell_window_size; #define A(attr, type_check, convert) RAII_PyObject(attr, PyObject_GetAttrString(p, #attr)); if (attr == NULL) return false; if (!type_check(attr)) { PyErr_SetString(PyExc_TypeError, #attr " not of the correct type"); return false; }; ans->attr = convert(attr); A(type, PyLong_Check, PyLong_AsLong); A(edge, PyLong_Check, PyLong_AsLong); A(focus_policy, PyLong_Check, PyLong_AsLong); A(x_size_in_cells, PyLong_Check, PyLong_AsLong); A(y_size_in_cells, PyLong_Check, PyLong_AsLong); A(requested_top_margin, PyLong_Check, PyLong_AsLong); A(requested_left_margin, PyLong_Check, PyLong_AsLong); A(requested_bottom_margin, PyLong_Check, PyLong_AsLong); A(requested_right_margin, PyLong_Check, PyLong_AsLong); A(requested_exclusive_zone, PyLong_Check, PyLong_AsLong); A(override_exclusive_zone, PyBool_Check, PyLong_AsLong); #undef A #define A(attr) { \ RAII_PyObject(attr, PyObject_GetAttrString(p, #attr)); if (attr == NULL) return false; \ if (!PyUnicode_Check(attr)) { PyErr_SetString(PyExc_TypeError, #attr " not a string"); return false; };\ Py_ssize_t sz; const char *t = PyUnicode_AsUTF8AndSize(attr, &sz); \ if (sz > (ssize_t)sizeof(ans->attr)-1) { PyErr_Format(PyExc_ValueError, "%s: %s is too long", #attr, t); return false; } \ memcpy(ans->attr, t, sz); } A(output_name); return true; #undef A } static PyObject* create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { int x = INT_MIN, y = INT_MIN, window_state = WINDOW_NORMAL, disallow_override_title = 0; char *title, *wm_class_class, *wm_class_name; PyObject *optional_window_state = NULL, *load_programs = NULL, *get_window_size, *pre_show_callback, *optional_x = NULL, *optional_y = NULL, *layer_shell_config = NULL; static const char* kwlist[] = {"get_window_size", "pre_show_callback", "title", "wm_class_name", "wm_class_class", "window_state", "load_programs", "x", "y", "disallow_override_title", "layer_shell_config", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kw, "OOsss|OOOOpO", (char**)kwlist, &get_window_size, &pre_show_callback, &title, &wm_class_name, &wm_class_class, &optional_window_state, &load_programs, &optional_x, &optional_y, &disallow_override_title, &layer_shell_config)) return NULL; bool is_layer_shell = false; if (layer_shell_config && layer_shell_config != Py_None && global_state.is_wayland) { is_layer_shell = true; } else { if (optional_window_state && optional_window_state != Py_None) { if (!PyLong_Check(optional_window_state)) { PyErr_SetString(PyExc_TypeError, "window_state must be an int"); return NULL; } window_state = (int) PyLong_AsLong(optional_window_state); } if (optional_x && optional_x != Py_None) { if (!PyLong_Check(optional_x)) { PyErr_SetString(PyExc_TypeError, "x must be an int"); return NULL;} x = (int)PyLong_AsLong(optional_x); } if (optional_y && optional_y != Py_None) { if (!PyLong_Check(optional_y)) { PyErr_SetString(PyExc_TypeError, "y must be an int"); return NULL;} y = (int)PyLong_AsLong(optional_y); } if (window_state < WINDOW_NORMAL || window_state > WINDOW_MINIMIZED) window_state = WINDOW_NORMAL; } if (PyErr_Occurred()) return NULL; static bool is_first_window = true; if (is_first_window) { glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_REQUIRED_VERSION_MAJOR); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_REQUIRED_VERSION_MINOR); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true); // We don't use depth and stencil buffers glfwWindowHint(GLFW_DEPTH_BITS, 0); glfwWindowHint(GLFW_STENCIL_BITS, 0); glfwSetApplicationCloseCallback(application_close_requested_callback); glfwSetCurrentSelectionCallback(get_current_selection); glfwSetHasCurrentSelectionCallback(has_current_selection); glfwSetIMECursorPositionCallback(get_ime_cursor_position); glfwSetSystemColorThemeChangeCallback(on_system_color_scheme_change); glfwSetClipboardLostCallback(on_clipboard_lost); // Request SRGB output buffer // Prevents kitty from starting on Wayland + NVIDIA, sigh: https://github.com/kovidgoyal/kitty/issues/7021 // Remove after https://github.com/NVIDIA/egl-wayland/issues/85 is fixed. // Also apparently mesa has introduced a bug with sRGB surfaces and Wayland. // Sigh. Wayland is such a pile of steaming crap. // See https://github.com/kovidgoyal/kitty/issues/7174#issuecomment-2000033873 if (!global_state.is_wayland) glfwWindowHint(GLFW_SRGB_CAPABLE, true); #ifdef __APPLE__ cocoa_set_activation_policy(OPT(macos_hide_from_tasks)); glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, true); glfwSetApplicationShouldHandleReopen(on_application_reopen); glfwSetApplicationWillFinishLaunching(cocoa_create_global_menu); #endif } if (OPT(hide_window_decorations) & 1) glfwWindowHint(GLFW_DECORATED, false); const bool set_blur = OPT(background_blur) > 0 && OPT(background_opacity) < 1.f; glfwWindowHint(GLFW_BLUR_RADIUS, set_blur ? OPT(background_blur) : 0); #ifdef __APPLE__ glfwWindowHint(GLFW_COCOA_COLOR_SPACE, OPT(macos_colorspace)); #else glfwWindowHintString(GLFW_X11_INSTANCE_NAME, wm_class_name); glfwWindowHintString(GLFW_X11_CLASS_NAME, wm_class_class); glfwWindowHintString(GLFW_WAYLAND_APP_ID, wm_class_class); #endif if (global_state.num_os_windows >= MAX_CHILDREN) { PyErr_SetString(PyExc_ValueError, "Too many windows"); return NULL; } bool want_semi_transparent = (1.0 - OPT(background_opacity) >= 0.01) || OPT(dynamic_background_opacity); glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, want_semi_transparent); uint32_t bgcolor = OPT(background); uint32_t bgalpha = (uint32_t)((MAX(0.f, MIN((OPT(background_opacity) * 255), 255.f)))); glfwWindowHint(GLFW_WAYLAND_BGCOLOR, ((bgalpha & 0xff) << 24) | bgcolor); // We use a temp window to avoid the need to set the window size after // creation, which causes a resize event and all the associated processing. // The temp window is used to get the DPI. if (!global_state.is_wayland) glfwWindowHint(GLFW_VISIBLE, false); GLFWwindow *common_context = global_state.num_os_windows ? global_state.os_windows[0].handle : NULL; GLFWwindow *temp_window = NULL; #ifdef __APPLE__ if (!apple_preserve_common_context) { apple_preserve_common_context = glfwCreateWindow(640, 480, "kitty", NULL, common_context); } if (!common_context) common_context = apple_preserve_common_context; #endif float xscale, yscale; double xdpi, ydpi; if (global_state.is_wayland) { // Cannot use temp window on Wayland as scale is only sent by compositor after window is displayed get_window_content_scale(NULL, &xscale, &yscale, &xdpi, &ydpi); for (unsigned i = 0; i < global_state.num_os_windows; i++) { OSWindow *osw = global_state.os_windows + i; if (osw->handle && glfwGetWindowAttrib(osw->handle, GLFW_FOCUSED)) { get_window_content_scale(osw->handle, &xscale, &yscale, &xdpi, &ydpi); break; } } } else { temp_window = glfwCreateWindow(640, 480, "temp", NULL, common_context); if (temp_window == NULL) { fatal("Failed to create GLFW temp window! This usually happens because of old/broken OpenGL drivers. kitty requires working OpenGL %d.%d drivers.", OPENGL_REQUIRED_VERSION_MAJOR, OPENGL_REQUIRED_VERSION_MINOR); } get_window_content_scale(temp_window, &xscale, &yscale, &xdpi, &ydpi); } FONTS_DATA_HANDLE fonts_data = load_fonts_data(OPT(font_size), xdpi, ydpi); PyObject *ret = PyObject_CallFunction(get_window_size, "IIddff", fonts_data->fcm.cell_width, fonts_data->fcm.cell_height, fonts_data->logical_dpi_x, fonts_data->logical_dpi_y, xscale, yscale); if (ret == NULL) return NULL; int width = PyLong_AsLong(PyTuple_GET_ITEM(ret, 0)), height = PyLong_AsLong(PyTuple_GET_ITEM(ret, 1)); Py_CLEAR(ret); if (is_layer_shell) { GLFWLayerShellConfig lsc = {0}; if (!translate_layer_shell_config(layer_shell_config, &lsc)) return NULL; lsc.expected.xdpi = xdpi; lsc.expected.ydpi = ydpi; lsc.expected.xscale = xscale; lsc.expected.yscale = yscale; glfwWaylandSetupLayerShellForNextWindow(&lsc); } GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, temp_window ? temp_window : common_context); if (temp_window) { glfwDestroyWindow(temp_window); temp_window = NULL; } if (glfw_window == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to create GLFWwindow"); return NULL; } glfwMakeContextCurrent(glfw_window); if (is_first_window) gl_init(); // Will make the GPU automatically apply SRGB gamma curve on the resulting framebuffer glEnable(GL_FRAMEBUFFER_SRGB); bool is_semi_transparent = glfwGetWindowAttrib(glfw_window, GLFW_TRANSPARENT_FRAMEBUFFER); // blank the window once so that there is no initial flash of color // changing, in case the background color is not black blank_canvas(is_semi_transparent ? OPT(background_opacity) : 1.0f, OPT(background)); apply_swap_interval(-1); // On Wayland the initial swap is allowed only after the first XDG configure event if (glfwAreSwapsAllowed(glfw_window)) glfwSwapBuffers(glfw_window); glfwSetInputMode(glfw_window, GLFW_LOCK_KEY_MODS, true); PyObject *pret = PyObject_CallFunction(pre_show_callback, "N", native_window_handle(glfw_window)); if (pret == NULL) return NULL; Py_DECREF(pret); if (x != INT_MIN && y != INT_MIN) glfwSetWindowPos(glfw_window, x, y); bool is_apple = true; #ifndef __APPLE__ is_apple = false; if (!global_state.is_wayland) glfwShowWindow(glfw_window); #endif if (global_state.is_wayland || is_apple) { float n_xscale, n_yscale; double n_xdpi, n_ydpi; get_window_content_scale(glfw_window, &n_xscale, &n_yscale, &n_xdpi, &n_ydpi); if (n_xdpi != xdpi || n_ydpi != ydpi || is_layer_shell) { // this can happen if the window is moved by the OS to a different monitor when shown or with fractional scales on Wayland // it can also happen with layer shell windows if the callback is // called before the window is fully created xdpi = n_xdpi; ydpi = n_ydpi; fonts_data = load_fonts_data(OPT(font_size), xdpi, ydpi); } } if (is_first_window) { PyObject *ret = PyObject_CallFunction(load_programs, "O", is_semi_transparent ? Py_True : Py_False); if (ret == NULL) return NULL; Py_DECREF(ret); get_platform_dependent_config_values(glfw_window); GLint encoding; glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_BACK_LEFT, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, &encoding); if (encoding != GL_SRGB) log_error("The output buffer does not support sRGB color encoding, colors will be incorrect."); is_first_window = false; } OSWindow *w = add_os_window(); w->handle = glfw_window; w->disallow_title_changes = disallow_override_title; update_os_window_references(); if (!is_layer_shell) { for (size_t i = 0; i < global_state.num_os_windows; i++) { // On some platforms (macOS) newly created windows don't get the initial focus in event OSWindow *q = global_state.os_windows + i; q->is_focused = q == w ? true : false; } } w->fonts_data = fonts_data; w->shown_once = true; w->last_focused_counter = ++focus_counter; os_window_update_size_increments(w); #ifdef __APPLE__ if (OPT(macos_option_as_alt)) glfwSetCocoaTextInputFilter(glfw_window, filter_option); glfwSetCocoaToggleFullscreenIntercept(glfw_window, intercept_cocoa_fullscreen); glfwCocoaSetWindowResizeCallback(glfw_window, cocoa_os_window_resized); #endif send_prerendered_sprites_for_window(w); if (logo.pixels && logo.width && logo.height) glfwSetWindowIcon(glfw_window, 1, &logo); set_glfw_mouse_pointer_shape_in_window(glfw_window, OPT(default_pointer_shape)); update_os_window_viewport(w, false); glfwSetWindowPosCallback(glfw_window, window_pos_callback); // missing size callback glfwSetWindowCloseCallback(glfw_window, window_close_callback); glfwSetWindowRefreshCallback(glfw_window, refresh_callback); glfwSetWindowFocusCallback(glfw_window, window_focus_callback); glfwSetWindowOcclusionCallback(glfw_window, window_occlusion_callback); glfwSetWindowIconifyCallback(glfw_window, window_iconify_callback); // missing maximize/restore callback glfwSetFramebufferSizeCallback(glfw_window, framebuffer_size_callback); glfwSetLiveResizeCallback(glfw_window, live_resize_callback); glfwSetWindowContentScaleCallback(glfw_window, dpi_change_callback); glfwSetMouseButtonCallback(glfw_window, mouse_button_callback); glfwSetCursorPosCallback(glfw_window, cursor_pos_callback); glfwSetCursorEnterCallback(glfw_window, cursor_enter_callback); glfwSetScrollCallback(glfw_window, scroll_callback); glfwSetKeyboardCallback(glfw_window, key_callback); glfwSetDropCallback(glfw_window, drop_callback); monotonic_t now = monotonic(); w->is_focused = true; w->cursor_blink_zero_time = now; w->last_mouse_activity_at = now; w->is_semi_transparent = is_semi_transparent; if (want_semi_transparent && !w->is_semi_transparent) { static bool warned = false; if (!warned) { log_error("Failed to enable transparency. This happens when your desktop environment does not support compositing."); warned = true; } } init_window_chrome_state(&w->last_window_chrome, OPT(background), w->is_semi_transparent, w->background_opacity); #ifdef __APPLE__ apply_window_chrome_state(w->handle, w->last_window_chrome, width, height, OPT(hide_window_decorations) != 0); #else apply_window_chrome_state(w->handle, w->last_window_chrome, width, height, false); #endif // Update window state // We do not call glfwWindowHint to set GLFW_MAXIMIZED before the window is created. // That would cause the window to be set to maximize immediately after creation and use the wrong initial size when restored. if (window_state != WINDOW_NORMAL) change_state_for_os_window(w, window_state); #ifdef __APPLE__ // macOS: Show the window after it is ready glfwShowWindow(glfw_window); #endif w->redraw_count = 1; debug("OS Window created\n"); return PyLong_FromUnsignedLongLong(w->id); } void os_window_update_size_increments(OSWindow *window) { if (OPT(resize_in_steps)) { if (window->handle && window->fonts_data) glfwSetWindowSizeIncrements( window->handle, window->fonts_data->fcm.cell_width, window->fonts_data->fcm.cell_height); } else { if (window->handle) glfwSetWindowSizeIncrements( window->handle, GLFW_DONT_CARE, GLFW_DONT_CARE); } } #ifdef __APPLE__ static bool window_in_same_cocoa_workspace(void *w, size_t *source_workspaces, size_t source_workspace_count) { static size_t workspaces[64]; size_t workspace_count = cocoa_get_workspace_ids(w, workspaces, arraysz(workspaces)); for (size_t i = 0; i < workspace_count; i++) { for (size_t s = 0; s < source_workspace_count; s++) { if (source_workspaces[s] == workspaces[i]) return true; } } return false; } static void cocoa_focus_last_window(id_type source_window_id, size_t *source_workspaces, size_t source_workspace_count) { id_type highest_focus_number = 0; OSWindow *window_to_focus = NULL; for (size_t i = 0; i < global_state.num_os_windows; i++) { OSWindow *w = global_state.os_windows + i; if ( w->id != source_window_id && w->handle && w->shown_once && w->last_focused_counter >= highest_focus_number && (!source_workspace_count || window_in_same_cocoa_workspace(glfwGetCocoaWindow(w->handle), source_workspaces, source_workspace_count)) ) { highest_focus_number = w->last_focused_counter; window_to_focus = w; } } if (window_to_focus) { glfwFocusWindow(window_to_focus->handle); } } #endif void destroy_os_window(OSWindow *w) { #ifdef __APPLE__ static size_t source_workspaces[64]; size_t source_workspace_count = 0; #endif if (w->handle) { #ifdef __APPLE__ source_workspace_count = cocoa_get_workspace_ids(glfwGetCocoaWindow(w->handle), source_workspaces, arraysz(source_workspaces)); #endif // Ensure mouse cursor is visible and reset to default shape, needed on macOS show_mouse_cursor(w->handle); glfwSetCursor(w->handle, NULL); glfwDestroyWindow(w->handle); } w->handle = NULL; #ifdef __APPLE__ // On macOS when closing a window, any other existing windows belonging to the same application do not // automatically get focus, so we do it manually. cocoa_focus_last_window(w->id, source_workspaces, source_workspace_count); #endif } void focus_os_window(OSWindow *w, bool also_raise, const char *activation_token) { if (w->handle) { #ifdef __APPLE__ if (!also_raise) cocoa_focus_window(glfwGetCocoaWindow(w->handle)); else glfwFocusWindow(w->handle); (void)activation_token; #else if (global_state.is_wayland && activation_token && activation_token[0] && also_raise) { glfwWaylandActivateWindow(w->handle, activation_token); return; } glfwFocusWindow(w->handle); #endif } } // Global functions {{{ static void error_callback(int error, const char* description) { log_error("[glfw error %d]: %s", error, description); } #ifndef __APPLE__ static PyObject *dbus_notification_callback = NULL; static PyObject* dbus_set_notification_callback(PyObject *self UNUSED, PyObject *callback) { Py_CLEAR(dbus_notification_callback); if (callback && callback != Py_None) { dbus_notification_callback = callback; Py_INCREF(callback); GLFWDBUSNotificationData d = {.timeout=-99999, .urgency=255}; if (!glfwDBusUserNotify) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwDBusUserNotify, did you call glfw_init?"); return NULL; } glfwDBusUserNotify(&d, NULL, NULL); } Py_RETURN_NONE; } #define send_dbus_notification_event_to_python(event_type, a, b) { \ if (dbus_notification_callback) { \ const char call_args_fmt[] = {'s', \ _Generic((a), unsigned long : 'k', unsigned long long : 'K'), _Generic((b), unsigned long : 'k', const char* : 's') }; \ RAII_PyObject(ret, PyObject_CallFunction(dbus_notification_callback, call_args_fmt, event_type, a, b)); \ if (!ret) PyErr_Print(); \ } \ } static void dbus_user_notification_activated(uint32_t notification_id, int type, const char* action) { unsigned long nid = notification_id; const char *stype = "activated"; switch (type) { case 0: stype = "closed"; break; case 1: stype = "activation_token"; break; case -1: stype = "capabilities"; break; } send_dbus_notification_event_to_python(stype, nid, action); } #endif static PyObject* opengl_version_string(PyObject *self UNUSED, PyObject *args UNUSED) { return PyUnicode_FromString(global_state.gl_version ? gl_version_string() : ""); } static PyObject* glfw_init(PyObject UNUSED *self, PyObject *args) { const char* path; int debug_keyboard = 0, debug_rendering = 0, wayland_enable_ime = 0; PyObject *edge_sf; if (!PyArg_ParseTuple(args, "sO|ppp", &path, &edge_sf, &debug_keyboard, &debug_rendering, &wayland_enable_ime)) return NULL; if (!PyCallable_Check(edge_sf)) { PyErr_SetString(PyExc_TypeError, "edge_spacing_func must be a callable"); return NULL; } Py_CLEAR(edge_spacing_func); #ifdef __APPLE__ cocoa_set_uncaught_exception_handler(); #endif const char* err = load_glfw(path); if (err) { PyErr_SetString(PyExc_RuntimeError, err); return NULL; } glfwSetErrorCallback(error_callback); glfwInitHint(GLFW_DEBUG_KEYBOARD, debug_keyboard); glfwInitHint(GLFW_DEBUG_RENDERING, debug_rendering); OPT(debug_keyboard) = debug_keyboard != 0; glfwInitHint(GLFW_WAYLAND_IME, wayland_enable_ime != 0); #ifdef __APPLE__ glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, 0); glfwInitHint(GLFW_COCOA_MENUBAR, 0); #else if (glfwDBusSetUserNotificationHandler) { glfwDBusSetUserNotificationHandler(dbus_user_notification_activated); } #endif bool supports_window_occlusion = false; bool ok = glfwInit(monotonic_start_time, &supports_window_occlusion); if (ok) { #ifdef __APPLE__ glfwSetCocoaURLOpenCallback(apple_url_open_callback); #else glfwSetDrawTextFunction(draw_text_callback); #endif get_window_dpi(NULL, &global_state.default_dpi.x, &global_state.default_dpi.y); edge_spacing_func = edge_sf; Py_INCREF(edge_spacing_func); } return Py_BuildValue("OO", ok ? Py_True : Py_False, supports_window_occlusion ? Py_True : Py_False); } static PyObject* glfw_terminate(PYNOARG) { for (size_t i = 0; i < arraysz(cursors); i++) { if (cursors[i].is_custom && cursors[i].glfw) { glfwDestroyCursor(cursors[i].glfw); cursors[i] = (mouse_cursor){0}; } } glfwTerminate(); Py_CLEAR(edge_spacing_func); Py_RETURN_NONE; } static PyObject* get_physical_dpi(GLFWmonitor *m) { int width = 0, height = 0; glfwGetMonitorPhysicalSize(m, &width, &height); if (width == 0 || height == 0) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor size"); return NULL; } const GLFWvidmode *vm = glfwGetVideoMode(m); if (vm == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get video mode for monitor"); return NULL; } float dpix = (float)(vm->width / (width / 25.4)); float dpiy = (float)(vm->height / (height / 25.4)); return Py_BuildValue("ff", dpix, dpiy); } static PyObject* glfw_get_physical_dpi(PYNOARG) { GLFWmonitor *m = glfwGetPrimaryMonitor(); if (m == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor"); return NULL; } return get_physical_dpi(m); } static PyObject* glfw_get_system_color_theme(PyObject UNUSED *self, PyObject *args) { int query_if_unintialized = 1; if (!PyArg_ParseTuple(args, "|p", &query_if_unintialized)) return NULL; if (!glfwGetCurrentSystemColorTheme) { PyErr_SetString(PyExc_RuntimeError, "must initialize GFLW before calling this function"); return NULL; } const char *which = appearance_name(glfwGetCurrentSystemColorTheme(query_if_unintialized)); return PyUnicode_FromString(which); } static PyObject* glfw_get_key_name(PyObject UNUSED *self, PyObject *args) { int key, native_key; if (!PyArg_ParseTuple(args, "ii", &key, &native_key)) return NULL; if (key) { switch (key) { /* start glfw functional key names (auto generated by gen-key-constants.py do not edit) */ case GLFW_FKEY_ESCAPE: return PyUnicode_FromString("escape"); case GLFW_FKEY_ENTER: return PyUnicode_FromString("enter"); case GLFW_FKEY_TAB: return PyUnicode_FromString("tab"); case GLFW_FKEY_BACKSPACE: return PyUnicode_FromString("backspace"); case GLFW_FKEY_INSERT: return PyUnicode_FromString("insert"); case GLFW_FKEY_DELETE: return PyUnicode_FromString("delete"); case GLFW_FKEY_LEFT: return PyUnicode_FromString("left"); case GLFW_FKEY_RIGHT: return PyUnicode_FromString("right"); case GLFW_FKEY_UP: return PyUnicode_FromString("up"); case GLFW_FKEY_DOWN: return PyUnicode_FromString("down"); case GLFW_FKEY_PAGE_UP: return PyUnicode_FromString("page_up"); case GLFW_FKEY_PAGE_DOWN: return PyUnicode_FromString("page_down"); case GLFW_FKEY_HOME: return PyUnicode_FromString("home"); case GLFW_FKEY_END: return PyUnicode_FromString("end"); case GLFW_FKEY_CAPS_LOCK: return PyUnicode_FromString("caps_lock"); case GLFW_FKEY_SCROLL_LOCK: return PyUnicode_FromString("scroll_lock"); case GLFW_FKEY_NUM_LOCK: return PyUnicode_FromString("num_lock"); case GLFW_FKEY_PRINT_SCREEN: return PyUnicode_FromString("print_screen"); case GLFW_FKEY_PAUSE: return PyUnicode_FromString("pause"); case GLFW_FKEY_MENU: return PyUnicode_FromString("menu"); case GLFW_FKEY_F1: return PyUnicode_FromString("f1"); case GLFW_FKEY_F2: return PyUnicode_FromString("f2"); case GLFW_FKEY_F3: return PyUnicode_FromString("f3"); case GLFW_FKEY_F4: return PyUnicode_FromString("f4"); case GLFW_FKEY_F5: return PyUnicode_FromString("f5"); case GLFW_FKEY_F6: return PyUnicode_FromString("f6"); case GLFW_FKEY_F7: return PyUnicode_FromString("f7"); case GLFW_FKEY_F8: return PyUnicode_FromString("f8"); case GLFW_FKEY_F9: return PyUnicode_FromString("f9"); case GLFW_FKEY_F10: return PyUnicode_FromString("f10"); case GLFW_FKEY_F11: return PyUnicode_FromString("f11"); case GLFW_FKEY_F12: return PyUnicode_FromString("f12"); case GLFW_FKEY_F13: return PyUnicode_FromString("f13"); case GLFW_FKEY_F14: return PyUnicode_FromString("f14"); case GLFW_FKEY_F15: return PyUnicode_FromString("f15"); case GLFW_FKEY_F16: return PyUnicode_FromString("f16"); case GLFW_FKEY_F17: return PyUnicode_FromString("f17"); case GLFW_FKEY_F18: return PyUnicode_FromString("f18"); case GLFW_FKEY_F19: return PyUnicode_FromString("f19"); case GLFW_FKEY_F20: return PyUnicode_FromString("f20"); case GLFW_FKEY_F21: return PyUnicode_FromString("f21"); case GLFW_FKEY_F22: return PyUnicode_FromString("f22"); case GLFW_FKEY_F23: return PyUnicode_FromString("f23"); case GLFW_FKEY_F24: return PyUnicode_FromString("f24"); case GLFW_FKEY_F25: return PyUnicode_FromString("f25"); case GLFW_FKEY_F26: return PyUnicode_FromString("f26"); case GLFW_FKEY_F27: return PyUnicode_FromString("f27"); case GLFW_FKEY_F28: return PyUnicode_FromString("f28"); case GLFW_FKEY_F29: return PyUnicode_FromString("f29"); case GLFW_FKEY_F30: return PyUnicode_FromString("f30"); case GLFW_FKEY_F31: return PyUnicode_FromString("f31"); case GLFW_FKEY_F32: return PyUnicode_FromString("f32"); case GLFW_FKEY_F33: return PyUnicode_FromString("f33"); case GLFW_FKEY_F34: return PyUnicode_FromString("f34"); case GLFW_FKEY_F35: return PyUnicode_FromString("f35"); case GLFW_FKEY_KP_0: return PyUnicode_FromString("kp_0"); case GLFW_FKEY_KP_1: return PyUnicode_FromString("kp_1"); case GLFW_FKEY_KP_2: return PyUnicode_FromString("kp_2"); case GLFW_FKEY_KP_3: return PyUnicode_FromString("kp_3"); case GLFW_FKEY_KP_4: return PyUnicode_FromString("kp_4"); case GLFW_FKEY_KP_5: return PyUnicode_FromString("kp_5"); case GLFW_FKEY_KP_6: return PyUnicode_FromString("kp_6"); case GLFW_FKEY_KP_7: return PyUnicode_FromString("kp_7"); case GLFW_FKEY_KP_8: return PyUnicode_FromString("kp_8"); case GLFW_FKEY_KP_9: return PyUnicode_FromString("kp_9"); case GLFW_FKEY_KP_DECIMAL: return PyUnicode_FromString("kp_decimal"); case GLFW_FKEY_KP_DIVIDE: return PyUnicode_FromString("kp_divide"); case GLFW_FKEY_KP_MULTIPLY: return PyUnicode_FromString("kp_multiply"); case GLFW_FKEY_KP_SUBTRACT: return PyUnicode_FromString("kp_subtract"); case GLFW_FKEY_KP_ADD: return PyUnicode_FromString("kp_add"); case GLFW_FKEY_KP_ENTER: return PyUnicode_FromString("kp_enter"); case GLFW_FKEY_KP_EQUAL: return PyUnicode_FromString("kp_equal"); case GLFW_FKEY_KP_SEPARATOR: return PyUnicode_FromString("kp_separator"); case GLFW_FKEY_KP_LEFT: return PyUnicode_FromString("kp_left"); case GLFW_FKEY_KP_RIGHT: return PyUnicode_FromString("kp_right"); case GLFW_FKEY_KP_UP: return PyUnicode_FromString("kp_up"); case GLFW_FKEY_KP_DOWN: return PyUnicode_FromString("kp_down"); case GLFW_FKEY_KP_PAGE_UP: return PyUnicode_FromString("kp_page_up"); case GLFW_FKEY_KP_PAGE_DOWN: return PyUnicode_FromString("kp_page_down"); case GLFW_FKEY_KP_HOME: return PyUnicode_FromString("kp_home"); case GLFW_FKEY_KP_END: return PyUnicode_FromString("kp_end"); case GLFW_FKEY_KP_INSERT: return PyUnicode_FromString("kp_insert"); case GLFW_FKEY_KP_DELETE: return PyUnicode_FromString("kp_delete"); case GLFW_FKEY_KP_BEGIN: return PyUnicode_FromString("kp_begin"); case GLFW_FKEY_MEDIA_PLAY: return PyUnicode_FromString("media_play"); case GLFW_FKEY_MEDIA_PAUSE: return PyUnicode_FromString("media_pause"); case GLFW_FKEY_MEDIA_PLAY_PAUSE: return PyUnicode_FromString("media_play_pause"); case GLFW_FKEY_MEDIA_REVERSE: return PyUnicode_FromString("media_reverse"); case GLFW_FKEY_MEDIA_STOP: return PyUnicode_FromString("media_stop"); case GLFW_FKEY_MEDIA_FAST_FORWARD: return PyUnicode_FromString("media_fast_forward"); case GLFW_FKEY_MEDIA_REWIND: return PyUnicode_FromString("media_rewind"); case GLFW_FKEY_MEDIA_TRACK_NEXT: return PyUnicode_FromString("media_track_next"); case GLFW_FKEY_MEDIA_TRACK_PREVIOUS: return PyUnicode_FromString("media_track_previous"); case GLFW_FKEY_MEDIA_RECORD: return PyUnicode_FromString("media_record"); case GLFW_FKEY_LOWER_VOLUME: return PyUnicode_FromString("lower_volume"); case GLFW_FKEY_RAISE_VOLUME: return PyUnicode_FromString("raise_volume"); case GLFW_FKEY_MUTE_VOLUME: return PyUnicode_FromString("mute_volume"); case GLFW_FKEY_LEFT_SHIFT: return PyUnicode_FromString("left_shift"); case GLFW_FKEY_LEFT_CONTROL: return PyUnicode_FromString("left_control"); case GLFW_FKEY_LEFT_ALT: return PyUnicode_FromString("left_alt"); case GLFW_FKEY_LEFT_SUPER: return PyUnicode_FromString("left_super"); case GLFW_FKEY_LEFT_HYPER: return PyUnicode_FromString("left_hyper"); case GLFW_FKEY_LEFT_META: return PyUnicode_FromString("left_meta"); case GLFW_FKEY_RIGHT_SHIFT: return PyUnicode_FromString("right_shift"); case GLFW_FKEY_RIGHT_CONTROL: return PyUnicode_FromString("right_control"); case GLFW_FKEY_RIGHT_ALT: return PyUnicode_FromString("right_alt"); case GLFW_FKEY_RIGHT_SUPER: return PyUnicode_FromString("right_super"); case GLFW_FKEY_RIGHT_HYPER: return PyUnicode_FromString("right_hyper"); case GLFW_FKEY_RIGHT_META: return PyUnicode_FromString("right_meta"); case GLFW_FKEY_ISO_LEVEL3_SHIFT: return PyUnicode_FromString("iso_level3_shift"); case GLFW_FKEY_ISO_LEVEL5_SHIFT: return PyUnicode_FromString("iso_level5_shift"); /* end glfw functional key names */ } char buf[8] = {0}; encode_utf8(key, buf); return PyUnicode_FromString(buf); } if (!glfwGetKeyName) { return PyUnicode_FromFormat("0x%x", native_key); } return Py_BuildValue("z", glfwGetKeyName(key, native_key)); } static PyObject* glfw_window_hint(PyObject UNUSED *self, PyObject *args) { int key, val; if (!PyArg_ParseTuple(args, "ii", &key, &val)) return NULL; glfwWindowHint(key, val); Py_RETURN_NONE; } // }}} static PyObject* toggle_secure_input(PYNOARG) { #ifdef __APPLE__ cocoa_toggle_secure_keyboard_entry(); #endif Py_RETURN_NONE; } static PyObject* cocoa_hide_app(PYNOARG) { #ifdef __APPLE__ cocoa_hide(); #endif Py_RETURN_NONE; } static PyObject* cocoa_hide_other_apps(PYNOARG) { #ifdef __APPLE__ cocoa_hide_others(); #endif Py_RETURN_NONE; } static void ring_audio_bell(void) { static monotonic_t last_bell_at = -1; monotonic_t now = monotonic(); if (last_bell_at >= 0 && now - last_bell_at <= ms_to_monotonic_t(100ll)) return; last_bell_at = now; #ifdef __APPLE__ cocoa_system_beep(OPT(bell_path)); #else if (OPT(bell_path)) play_canberra_sound(OPT(bell_path), "kitty bell", true, "event", OPT(bell_theme)); else play_canberra_sound("bell", "kitty bell", false, "event", OPT(bell_theme)); #endif } static PyObject* ring_bell(PYNOARG) { ring_audio_bell(); Py_RETURN_NONE; } static PyObject* get_content_scale_for_window(PYNOARG) { OSWindow *w = global_state.callback_os_window ? global_state.callback_os_window : global_state.os_windows; float xscale, yscale; glfwGetWindowContentScale(w->handle, &xscale, &yscale); return Py_BuildValue("ff", xscale, yscale); } static void activation_token_callback(GLFWwindow *window UNUSED, const char *token, void *data) { if (!token || !token[0]) { token = ""; log_error("Wayland: Did not get activation token from compositor. Use a better compositor."); } PyObject *ret = PyObject_CallFunction(data, "s", token); if (ret == NULL) PyErr_Print(); else Py_DECREF(ret); Py_CLEAR(data); } void run_with_activation_token_in_os_window(OSWindow *w, PyObject *callback) { if (global_state.is_wayland) { Py_INCREF(callback); glfwWaylandRunWithActivationToken(w->handle, activation_token_callback, callback); } } static PyObject* toggle_fullscreen(PyObject UNUSED *self, PyObject *args) { id_type os_window_id = 0; if (!PyArg_ParseTuple(args, "|K", &os_window_id)) return NULL; OSWindow *w = os_window_id ? os_window_for_id(os_window_id) : current_os_window(); if (!w) Py_RETURN_NONE; if (toggle_fullscreen_for_os_window(w)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static PyObject* toggle_maximized(PyObject UNUSED *self, PyObject *args) { id_type os_window_id = 0; if (!PyArg_ParseTuple(args, "|K", &os_window_id)) return NULL; OSWindow *w = os_window_id ? os_window_for_id(os_window_id) : current_os_window(); if (!w) Py_RETURN_NONE; if (toggle_maximized_for_os_window(w)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static PyObject* cocoa_minimize_os_window(PyObject UNUSED *self, PyObject *args) { id_type os_window_id = 0; if (!PyArg_ParseTuple(args, "|K", &os_window_id)) return NULL; #ifdef __APPLE__ OSWindow *w = os_window_id ? os_window_for_id(os_window_id) : current_os_window(); if (!w || !w->handle) Py_RETURN_NONE; if (!glfwGetCocoaWindow) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetCocoaWindow"); return NULL; } void *window = glfwGetCocoaWindow(w->handle); if (!window) Py_RETURN_NONE; cocoa_minimize(window); #else PyErr_SetString(PyExc_RuntimeError, "cocoa_minimize_os_window() is only supported on macOS"); return NULL; #endif Py_RETURN_NONE; } static PyObject* change_os_window_state(PyObject *self UNUSED, PyObject *args) { int state; id_type wid = 0; if (!PyArg_ParseTuple(args, "i|K", &state, &wid)) return NULL; OSWindow *w = wid ? os_window_for_id(wid) : current_os_window(); if (!w || !w->handle) Py_RETURN_NONE; if (state < WINDOW_NORMAL || state > WINDOW_MINIMIZED) { PyErr_SetString(PyExc_ValueError, "Unknown window state"); return NULL; } change_state_for_os_window(w, state); Py_RETURN_NONE; } void request_window_attention(id_type kitty_window_id, bool audio_bell) { OSWindow *w = os_window_for_kitty_window(kitty_window_id); if (w) { if (audio_bell) ring_audio_bell(); if (OPT(window_alert_on_bell)) glfwRequestWindowAttention(w->handle); glfwPostEmptyEvent(); } } void set_os_window_title(OSWindow *w, const char *title) { if (!title) { if (global_state.is_wayland) glfwWaylandRedrawCSDWindowTitle(w->handle); return; } static char buf[2048]; strip_csi_(title, buf, arraysz(buf)); glfwSetWindowTitle(w->handle, buf); } void hide_mouse(OSWindow *w) { glfwSetInputMode(w->handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); } bool is_mouse_hidden(OSWindow *w) { return w->handle && glfwGetInputMode(w->handle, GLFW_CURSOR) == GLFW_CURSOR_HIDDEN; } void swap_window_buffers(OSWindow *os_window) { if (glfwAreSwapsAllowed(os_window->handle)) glfwSwapBuffers(os_window->handle); } void wakeup_main_loop(void) { glfwPostEmptyEvent(); } bool should_os_window_be_rendered(OSWindow* w) { return ( glfwGetWindowAttrib(w->handle, GLFW_ICONIFIED) || !glfwGetWindowAttrib(w->handle, GLFW_VISIBLE) || glfwGetWindowAttrib(w->handle, GLFW_OCCLUDED) || !glfwAreSwapsAllowed(w->handle) ) ? false : true; } static PyObject* primary_monitor_size(PYNOARG) { GLFWmonitor* monitor = glfwGetPrimaryMonitor(); const GLFWvidmode* mode = glfwGetVideoMode(monitor); if (mode == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get video mode for primary monitor"); return NULL; } return Py_BuildValue("ii", mode->width, mode->height); } static PyObject* primary_monitor_content_scale(PYNOARG) { GLFWmonitor* monitor = glfwGetPrimaryMonitor(); float xscale = 1.0, yscale = 1.0; if (monitor) glfwGetMonitorContentScale(monitor, &xscale, &yscale); return Py_BuildValue("ff", xscale, yscale); } static PyObject* x11_display(PYNOARG) { if (glfwGetX11Display) { return PyLong_FromVoidPtr(glfwGetX11Display()); } else log_error("Failed to load glfwGetX11Display"); Py_RETURN_NONE; } static PyObject* wayland_compositor_data(PYNOARG) { pid_t pid = -1; const char *missing_capabilities = NULL; if (global_state.is_wayland && glfwWaylandCompositorPID) { pid = glfwWaylandCompositorPID(); missing_capabilities = glfwWaylandMissingCapabilities(); } return Py_BuildValue("Ls", (long long)pid, missing_capabilities); } static PyObject* x11_window_id(PyObject UNUSED *self, PyObject *os_wid) { OSWindow *w = os_window_for_id(PyLong_AsUnsignedLongLong(os_wid)); if (!w) { PyErr_SetString(PyExc_ValueError, "No OSWindow with the specified id found"); return NULL; } if (!glfwGetX11Window) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetX11Window"); return NULL; } return PyLong_FromUnsignedLong(glfwGetX11Window(w->handle)); } static PyObject* cocoa_window_id(PyObject UNUSED *self, PyObject *os_wid) { OSWindow *w = os_window_for_id(PyLong_AsUnsignedLongLong(os_wid)); if (!w) { PyErr_SetString(PyExc_ValueError, "No OSWindow with the specified id found"); return NULL; } if (!glfwGetCocoaWindow) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetCocoaWindow"); return NULL; } #ifdef __APPLE__ return Py_BuildValue("l", (long)cocoa_window_number(glfwGetCocoaWindow(w->handle))); #else PyErr_SetString(PyExc_RuntimeError, "cocoa_window_id() is only supported on Mac"); return NULL; #endif } static GLFWCursorShape pointer_name_to_glfw_name(const char *name) { /* start name to glfw (auto generated by gen-key-constants.py do not edit) */ if (strcmp(name, "arrow") == 0) return GLFW_DEFAULT_CURSOR; if (strcmp(name, "beam") == 0) return GLFW_TEXT_CURSOR; if (strcmp(name, "text") == 0) return GLFW_TEXT_CURSOR; if (strcmp(name, "pointer") == 0) return GLFW_POINTER_CURSOR; if (strcmp(name, "hand") == 0) return GLFW_POINTER_CURSOR; if (strcmp(name, "help") == 0) return GLFW_HELP_CURSOR; if (strcmp(name, "wait") == 0) return GLFW_WAIT_CURSOR; if (strcmp(name, "progress") == 0) return GLFW_PROGRESS_CURSOR; if (strcmp(name, "crosshair") == 0) return GLFW_CROSSHAIR_CURSOR; if (strcmp(name, "cell") == 0) return GLFW_CELL_CURSOR; if (strcmp(name, "vertical-text") == 0) return GLFW_VERTICAL_TEXT_CURSOR; if (strcmp(name, "move") == 0) return GLFW_MOVE_CURSOR; if (strcmp(name, "e-resize") == 0) return GLFW_E_RESIZE_CURSOR; if (strcmp(name, "ne-resize") == 0) return GLFW_NE_RESIZE_CURSOR; if (strcmp(name, "nw-resize") == 0) return GLFW_NW_RESIZE_CURSOR; if (strcmp(name, "n-resize") == 0) return GLFW_N_RESIZE_CURSOR; if (strcmp(name, "se-resize") == 0) return GLFW_SE_RESIZE_CURSOR; if (strcmp(name, "sw-resize") == 0) return GLFW_SW_RESIZE_CURSOR; if (strcmp(name, "s-resize") == 0) return GLFW_S_RESIZE_CURSOR; if (strcmp(name, "w-resize") == 0) return GLFW_W_RESIZE_CURSOR; if (strcmp(name, "ew-resize") == 0) return GLFW_EW_RESIZE_CURSOR; if (strcmp(name, "ns-resize") == 0) return GLFW_NS_RESIZE_CURSOR; if (strcmp(name, "nesw-resize") == 0) return GLFW_NESW_RESIZE_CURSOR; if (strcmp(name, "nwse-resize") == 0) return GLFW_NWSE_RESIZE_CURSOR; if (strcmp(name, "zoom-in") == 0) return GLFW_ZOOM_IN_CURSOR; if (strcmp(name, "zoom-out") == 0) return GLFW_ZOOM_OUT_CURSOR; if (strcmp(name, "alias") == 0) return GLFW_ALIAS_CURSOR; if (strcmp(name, "copy") == 0) return GLFW_COPY_CURSOR; if (strcmp(name, "not-allowed") == 0) return GLFW_NOT_ALLOWED_CURSOR; if (strcmp(name, "no-drop") == 0) return GLFW_NO_DROP_CURSOR; if (strcmp(name, "grab") == 0) return GLFW_GRAB_CURSOR; if (strcmp(name, "grabbing") == 0) return GLFW_GRABBING_CURSOR; /* end name to glfw */ return GLFW_INVALID_CURSOR; } static PyObject* is_css_pointer_name_valid(PyObject *self UNUSED, PyObject *name) { if (!PyUnicode_Check(name)) { PyErr_SetString(PyExc_TypeError, "pointer name must be a string"); return NULL; } const char *q = PyUnicode_AsUTF8(name); if (strcmp(q, "default") == 0) { Py_RETURN_TRUE; } if (pointer_name_to_glfw_name(q) == GLFW_INVALID_CURSOR) { Py_RETURN_FALSE; } Py_RETURN_TRUE; } static const char* glfw_name_to_css_pointer_name(GLFWCursorShape q) { switch(q) { case GLFW_INVALID_CURSOR: return ""; /* start glfw to css (auto generated by gen-key-constants.py do not edit) */ case GLFW_DEFAULT_CURSOR: return "default"; case GLFW_TEXT_CURSOR: return "text"; case GLFW_POINTER_CURSOR: return "pointer"; case GLFW_HELP_CURSOR: return "help"; case GLFW_WAIT_CURSOR: return "wait"; case GLFW_PROGRESS_CURSOR: return "progress"; case GLFW_CROSSHAIR_CURSOR: return "crosshair"; case GLFW_CELL_CURSOR: return "cell"; case GLFW_VERTICAL_TEXT_CURSOR: return "vertical-text"; case GLFW_MOVE_CURSOR: return "move"; case GLFW_E_RESIZE_CURSOR: return "e-resize"; case GLFW_NE_RESIZE_CURSOR: return "ne-resize"; case GLFW_NW_RESIZE_CURSOR: return "nw-resize"; case GLFW_N_RESIZE_CURSOR: return "n-resize"; case GLFW_SE_RESIZE_CURSOR: return "se-resize"; case GLFW_SW_RESIZE_CURSOR: return "sw-resize"; case GLFW_S_RESIZE_CURSOR: return "s-resize"; case GLFW_W_RESIZE_CURSOR: return "w-resize"; case GLFW_EW_RESIZE_CURSOR: return "ew-resize"; case GLFW_NS_RESIZE_CURSOR: return "ns-resize"; case GLFW_NESW_RESIZE_CURSOR: return "nesw-resize"; case GLFW_NWSE_RESIZE_CURSOR: return "nwse-resize"; case GLFW_ZOOM_IN_CURSOR: return "zoom-in"; case GLFW_ZOOM_OUT_CURSOR: return "zoom-out"; case GLFW_ALIAS_CURSOR: return "alias"; case GLFW_COPY_CURSOR: return "copy"; case GLFW_NOT_ALLOWED_CURSOR: return "not-allowed"; case GLFW_NO_DROP_CURSOR: return "no-drop"; case GLFW_GRAB_CURSOR: return "grab"; case GLFW_GRABBING_CURSOR: return "grabbing"; /* end glfw to css */ } return ""; } static PyObject* pointer_name_to_css_name(PyObject *self UNUSED, PyObject *name) { if (!PyUnicode_Check(name)) { PyErr_SetString(PyExc_TypeError, "pointer name must be a string"); return NULL; } GLFWCursorShape s = pointer_name_to_glfw_name(PyUnicode_AsUTF8(name)); return PyUnicode_FromString(glfw_name_to_css_pointer_name(s)); } static PyObject* set_custom_cursor(PyObject *self UNUSED, PyObject *args) { int x=0, y=0; Py_ssize_t sz; PyObject *images; const char *shape; if (!PyArg_ParseTuple(args, "sO!|ii", &shape, &PyTuple_Type, &images, &x, &y)) return NULL; static GLFWimage gimages[16] = {{0}}; size_t count = MIN((size_t)PyTuple_GET_SIZE(images), arraysz(gimages)); for (size_t i = 0; i < count; i++) { if (!PyArg_ParseTuple(PyTuple_GET_ITEM(images, i), "s#ii", &gimages[i].pixels, &sz, &gimages[i].width, &gimages[i].height)) return NULL; if ((Py_ssize_t)gimages[i].width * gimages[i].height * 4 != sz) { PyErr_SetString(PyExc_ValueError, "The image data size does not match its width and height"); return NULL; } } GLFWCursorShape gshape = pointer_name_to_glfw_name(shape); if (gshape == GLFW_INVALID_CURSOR) { PyErr_Format(PyExc_KeyError, "Unknown pointer shape: %s", shape); return NULL; } GLFWcursor *c = glfwCreateCursor(gimages, x, y, count); if (c == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to create custom cursor from specified images"); return NULL; } if (cursors[gshape].initialized && cursors[gshape].is_custom && cursors[gshape].glfw) { glfwDestroyCursor(cursors[gshape].glfw); } cursors[gshape].initialized = true; cursors[gshape].is_custom = true; cursors[gshape].glfw = c; Py_RETURN_NONE; } #ifdef __APPLE__ void get_cocoa_key_equivalent(uint32_t key, int mods, char *cocoa_key, size_t key_sz, int *cocoa_mods) { memset(cocoa_key, 0, key_sz); uint32_t ans = glfwGetCocoaKeyEquivalent(key, mods, cocoa_mods); if (ans) encode_utf8(ans, cocoa_key); } static void cocoa_frame_request_callback(GLFWwindow *window) { for (size_t i = 0; i < global_state.num_os_windows; i++) { if (global_state.os_windows[i].handle == window) { global_state.os_windows[i].render_state = RENDER_FRAME_READY; global_state.os_windows[i].last_render_frame_received_at = monotonic(); request_tick_callback(); break; } } } void request_frame_render(OSWindow *w) { glfwCocoaRequestRenderFrame(w->handle, cocoa_frame_request_callback); w->render_state = RENDER_FRAME_REQUESTED; } static PyObject* py_recreate_global_menu(PyObject *self UNUSED, PyObject *args UNUSED) { cocoa_recreate_global_menu(); Py_RETURN_NONE; } static PyObject* py_clear_global_shortcuts(PyObject *self UNUSED, PyObject *args UNUSED) { cocoa_clear_global_shortcuts(); Py_RETURN_NONE; } #else static void wayland_frame_request_callback(id_type os_window_id) { for (size_t i = 0; i < global_state.num_os_windows; i++) { if (global_state.os_windows[i].id == os_window_id) { global_state.os_windows[i].render_state = RENDER_FRAME_READY; global_state.os_windows[i].last_render_frame_received_at = monotonic(); request_tick_callback(); break; } } } void request_frame_render(OSWindow *w) { // Some Wayland compositors are too fragile to handle multiple // render frame requests, see https://github.com/kovidgoyal/kitty/issues/2329 if (w->render_state != RENDER_FRAME_REQUESTED) { w->render_state = RENDER_FRAME_REQUESTED; glfwRequestWaylandFrameEvent(w->handle, w->id, wayland_frame_request_callback); } } void dbus_notification_created_callback(unsigned long long notification_id, uint32_t new_notification_id, void* data UNUSED) { unsigned long new_id = new_notification_id; send_dbus_notification_event_to_python("created", notification_id, new_id); } static PyObject* dbus_send_notification(PyObject *self UNUSED, PyObject *args, PyObject *kw) { int timeout = -1, urgency = 1; unsigned int replaces = 0; GLFWDBUSNotificationData d = {0}; static const char* kwlist[] = {"app_name", "app_icon", "title", "body", "actions", "timeout", "urgency", "replaces", "category", "muted", NULL}; PyObject *actions = NULL; if (!PyArg_ParseTupleAndKeywords(args, kw, "ssssO!|iiIsp", (char**)kwlist, &d.app_name, &d.icon, &d.summary, &d.body, &PyDict_Type, &actions, &timeout, &urgency, &replaces, &d.category, &d.muted)) return NULL; if (!glfwDBusUserNotify) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwDBusUserNotify, did you call glfw_init?"); return NULL; } d.timeout = timeout; d.urgency = urgency & 3; d.replaces = replaces; RAII_ALLOC(const char*, aclist, calloc(2*PyDict_Size(actions), sizeof(d.actions[0]))); if (!aclist) { return PyErr_NoMemory(); } PyObject *key, *value; Py_ssize_t pos = 0; d.num_actions = 0; while (PyDict_Next(actions, &pos, &key, &value)) { if (!PyUnicode_Check(key) || !PyUnicode_Check(value)) { PyErr_SetString(PyExc_TypeError, "actions must be strings"); return NULL; } if (PyUnicode_GET_LENGTH(key) == 0 || PyUnicode_GET_LENGTH(value) == 0) { PyErr_SetString(PyExc_TypeError, "actions must be non-empty strings"); return NULL; } aclist[d.num_actions] = PyUnicode_AsUTF8(key); if (!aclist[d.num_actions++]) return NULL; aclist[d.num_actions] = PyUnicode_AsUTF8(value); if (!aclist[d.num_actions++]) return NULL; } d.actions = aclist; unsigned long long notification_id = glfwDBusUserNotify(&d, dbus_notification_created_callback, NULL); return PyLong_FromUnsignedLongLong(notification_id); } static PyObject* dbus_close_notification(PyObject *self UNUSED, PyObject *args) { unsigned int id; if (!PyArg_ParseTuple(args, "I", &id)) return NULL; GLFWDBUSNotificationData d = {.timeout=-9999, .urgency=255}; if (!glfwDBusUserNotify) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwDBusUserNotify, did you call glfw_init?"); return NULL; } if (glfwDBusUserNotify(&d, NULL, &id)) Py_RETURN_TRUE; Py_RETURN_FALSE; } #endif static PyObject* get_click_interval(PyObject *self UNUSED, PyObject *args UNUSED) { return PyFloat_FromDouble(monotonic_t_to_s_double(OPT(click_interval))); } id_type add_main_loop_timer(monotonic_t interval, bool repeats, timer_callback_fun callback, void *callback_data, timer_callback_fun free_callback) { return glfwAddTimer(interval, repeats, callback, callback_data, free_callback); } void update_main_loop_timer(id_type timer_id, monotonic_t interval, bool enabled) { glfwUpdateTimer(timer_id, interval, enabled); } void remove_main_loop_timer(id_type timer_id) { glfwRemoveTimer(timer_id); } void run_main_loop(tick_callback_fun cb, void* cb_data) { glfwRunMainLoop(cb, cb_data); } void stop_main_loop(void) { #ifdef __APPLE__ if (apple_preserve_common_context) glfwDestroyWindow(apple_preserve_common_context); apple_preserve_common_context = NULL; #endif glfwStopMainLoop(); } static PyObject* strip_csi(PyObject *self UNUSED, PyObject *src) { if (!PyUnicode_Check(src)) { PyErr_SetString(PyExc_TypeError, "Unicode string expected"); return NULL; } Py_ssize_t sz; const char *title = PyUnicode_AsUTF8AndSize(src, &sz); if (!title) return NULL; RAII_ALLOC(char, buf, malloc(sz + 1)); if (!buf) { return PyErr_NoMemory(); } strip_csi_(title, buf, sz + 1); return PyUnicode_FromString(buf); } void set_ignore_os_keyboard_processing(bool enabled) { glfwSetIgnoreOSKeyboardProcessing(enabled); } static void decref_pyobj(void *x) { Py_XDECREF(x); } static GLFWDataChunk get_clipboard_data(const char *mime_type, void *iter, GLFWClipboardType ct) { GLFWDataChunk ans = {.iter=iter, .free=decref_pyobj}; if (global_state.boss == NULL) return ans; if (iter == NULL) { PyObject *c = PyObject_GetAttrString(global_state.boss, ct == GLFW_PRIMARY_SELECTION ? "primary_selection" : "clipboard"); if (c == NULL) { return ans; } PyObject *i = PyObject_CallFunction(c, "s", mime_type); Py_DECREF(c); if (!i) { return ans; } ans.iter = i; return ans; } if (mime_type == NULL) { Py_XDECREF(iter); return ans; } PyObject *ret = PyObject_CallFunctionObjArgs(iter, NULL); if (ret == NULL) return ans; ans.data = PyBytes_AS_STRING(ret); ans.sz = PyBytes_GET_SIZE(ret); ans.free_data = ret; return ans; } static PyObject* set_clipboard_data_types(PyObject *self UNUSED, PyObject *args) { PyObject *mta; int ctype; if (!PyArg_ParseTuple(args, "iO!", &ctype, &PyTuple_Type, &mta)) return NULL; if (glfwSetClipboardDataTypes) { const char **mime_types = calloc(PyTuple_GET_SIZE(mta), sizeof(char*)); if (!mime_types) return PyErr_NoMemory(); for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(mta); i++) mime_types[i] = PyUnicode_AsUTF8(PyTuple_GET_ITEM(mta, i)); glfwSetClipboardDataTypes(ctype, mime_types, PyTuple_GET_SIZE(mta), get_clipboard_data); free(mime_types); } else log_error("GLFW not initialized cannot set clipboard data"); if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } static bool write_clipboard_data(void *callback, const char *data, size_t sz) { Py_ssize_t z = sz; if (data == NULL) { PyErr_SetString(PyExc_RuntimeError, "is_self_offer"); return false; } PyObject *ret = PyObject_CallFunction(callback, "y#", data, z); bool ok = false; if (ret != NULL) { ok = true; Py_DECREF(ret); } return ok; } static PyObject* get_clipboard_mime(PyObject *self UNUSED, PyObject *args) { int ctype; const char *mime; PyObject *callback; if (!PyArg_ParseTuple(args, "izO", &ctype, &mime, &callback)) return NULL; glfwGetClipboard(ctype, mime, write_clipboard_data, callback); if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } static PyObject* make_x11_window_a_dock_window(PyObject *self UNUSED, PyObject *args UNUSED) { int x11_window_id; PyObject *dims; if (!PyArg_ParseTuple(args, "iO!", &x11_window_id, &PyTuple_Type, &dims)) return NULL; if (PyTuple_GET_SIZE(dims) != 12 ) { PyErr_SetString(PyExc_TypeError, "dimensions must be a tuple of length 12"); return NULL; } if (!glfwSetX11WindowAsDock) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetX11Window"); return NULL; } uint32_t dimensions[12]; for (Py_ssize_t i = 0; i < 12; i++) dimensions[i] = PyLong_AsUnsignedLong(PyTuple_GET_ITEM(dims, i)); if (PyErr_Occurred()) return NULL; glfwSetX11WindowAsDock(x11_window_id); glfwSetX11WindowStrut(x11_window_id, dimensions); Py_RETURN_NONE; } // Boilerplate {{{ static PyMethodDef module_methods[] = { METHODB(set_custom_cursor, METH_VARARGS), METHODB(is_css_pointer_name_valid, METH_O), METHODB(pointer_name_to_css_name, METH_O), {"create_os_window", (PyCFunction)(void (*) (void))(create_os_window), METH_VARARGS | METH_KEYWORDS, NULL}, METHODB(set_default_window_icon, METH_VARARGS), METHODB(set_os_window_icon, METH_VARARGS), METHODB(set_clipboard_data_types, METH_VARARGS), METHODB(get_clipboard_mime, METH_VARARGS), METHODB(toggle_secure_input, METH_NOARGS), METHODB(get_content_scale_for_window, METH_NOARGS), METHODB(ring_bell, METH_NOARGS), METHODB(toggle_fullscreen, METH_VARARGS), METHODB(toggle_maximized, METH_VARARGS), METHODB(change_os_window_state, METH_VARARGS), METHODB(glfw_window_hint, METH_VARARGS), METHODB(x11_display, METH_NOARGS), METHODB(wayland_compositor_data, METH_NOARGS), METHODB(get_click_interval, METH_NOARGS), METHODB(x11_window_id, METH_O), METHODB(make_x11_window_a_dock_window, METH_VARARGS), METHODB(strip_csi, METH_O), #ifndef __APPLE__ METHODB(dbus_close_notification, METH_VARARGS), METHODB(dbus_set_notification_callback, METH_O), {"dbus_send_notification", (PyCFunction)(void (*) (void))(dbus_send_notification), METH_KEYWORDS | METH_VARARGS, NULL}, #else {"cocoa_recreate_global_menu", (PyCFunction)py_recreate_global_menu, METH_NOARGS, ""}, {"cocoa_clear_global_shortcuts", (PyCFunction)py_clear_global_shortcuts, METH_NOARGS, ""}, #endif METHODB(cocoa_window_id, METH_O), METHODB(cocoa_hide_app, METH_NOARGS), METHODB(cocoa_hide_other_apps, METH_NOARGS), METHODB(cocoa_minimize_os_window, METH_VARARGS), {"glfw_init", (PyCFunction)glfw_init, METH_VARARGS, ""}, METHODB(opengl_version_string, METH_NOARGS), {"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""}, {"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""}, {"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""}, {"glfw_get_system_color_theme", (PyCFunction)glfw_get_system_color_theme, METH_VARARGS, ""}, {"glfw_primary_monitor_size", (PyCFunction)primary_monitor_size, METH_NOARGS, ""}, {"glfw_primary_monitor_content_scale", (PyCFunction)primary_monitor_content_scale, METH_NOARGS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ }; void cleanup_glfw(void) { if (logo.pixels) free(logo.pixels); logo.pixels = NULL; Py_CLEAR(edge_spacing_func); #ifndef __APPLE__ Py_CLEAR(dbus_notification_callback); release_freetype_render_context(csd_title_render_ctx); #endif } bool init_glfw(PyObject *m) { if (PyModule_AddFunctions(m, module_methods) != 0) return false; register_at_exit_cleanup_func(GLFW_CLEANUP_FUNC, cleanup_glfw); // constants {{{ #define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false; ADDC(GLFW_RELEASE); ADDC(GLFW_PRESS); ADDC(GLFW_REPEAT); ADDC(true); ADDC(false); ADDC(GLFW_PRIMARY_SELECTION); ADDC(GLFW_CLIPBOARD); ADDC(GLFW_LAYER_SHELL_NONE); ADDC(GLFW_LAYER_SHELL_PANEL); ADDC(GLFW_LAYER_SHELL_BACKGROUND); ADDC(GLFW_LAYER_SHELL_TOP); ADDC(GLFW_LAYER_SHELL_OVERLAY); ADDC(GLFW_FOCUS_NOT_ALLOWED); ADDC(GLFW_FOCUS_EXCLUSIVE); ADDC(GLFW_FOCUS_ON_DEMAND); ADDC(GLFW_EDGE_TOP); ADDC(GLFW_EDGE_BOTTOM); ADDC(GLFW_EDGE_LEFT); ADDC(GLFW_EDGE_RIGHT); ADDC(GLFW_EDGE_CENTER); ADDC(GLFW_EDGE_NONE); ADDC(GLFW_COLOR_SCHEME_NO_PREFERENCE); ADDC(GLFW_COLOR_SCHEME_DARK); ADDC(GLFW_COLOR_SCHEME_LIGHT); /* start glfw functional keys (auto generated by gen-key-constants.py do not edit) */ ADDC(GLFW_FKEY_ESCAPE); ADDC(GLFW_FKEY_ENTER); ADDC(GLFW_FKEY_TAB); ADDC(GLFW_FKEY_BACKSPACE); ADDC(GLFW_FKEY_INSERT); ADDC(GLFW_FKEY_DELETE); ADDC(GLFW_FKEY_LEFT); ADDC(GLFW_FKEY_RIGHT); ADDC(GLFW_FKEY_UP); ADDC(GLFW_FKEY_DOWN); ADDC(GLFW_FKEY_PAGE_UP); ADDC(GLFW_FKEY_PAGE_DOWN); ADDC(GLFW_FKEY_HOME); ADDC(GLFW_FKEY_END); ADDC(GLFW_FKEY_CAPS_LOCK); ADDC(GLFW_FKEY_SCROLL_LOCK); ADDC(GLFW_FKEY_NUM_LOCK); ADDC(GLFW_FKEY_PRINT_SCREEN); ADDC(GLFW_FKEY_PAUSE); ADDC(GLFW_FKEY_MENU); ADDC(GLFW_FKEY_F1); ADDC(GLFW_FKEY_F2); ADDC(GLFW_FKEY_F3); ADDC(GLFW_FKEY_F4); ADDC(GLFW_FKEY_F5); ADDC(GLFW_FKEY_F6); ADDC(GLFW_FKEY_F7); ADDC(GLFW_FKEY_F8); ADDC(GLFW_FKEY_F9); ADDC(GLFW_FKEY_F10); ADDC(GLFW_FKEY_F11); ADDC(GLFW_FKEY_F12); ADDC(GLFW_FKEY_F13); ADDC(GLFW_FKEY_F14); ADDC(GLFW_FKEY_F15); ADDC(GLFW_FKEY_F16); ADDC(GLFW_FKEY_F17); ADDC(GLFW_FKEY_F18); ADDC(GLFW_FKEY_F19); ADDC(GLFW_FKEY_F20); ADDC(GLFW_FKEY_F21); ADDC(GLFW_FKEY_F22); ADDC(GLFW_FKEY_F23); ADDC(GLFW_FKEY_F24); ADDC(GLFW_FKEY_F25); ADDC(GLFW_FKEY_F26); ADDC(GLFW_FKEY_F27); ADDC(GLFW_FKEY_F28); ADDC(GLFW_FKEY_F29); ADDC(GLFW_FKEY_F30); ADDC(GLFW_FKEY_F31); ADDC(GLFW_FKEY_F32); ADDC(GLFW_FKEY_F33); ADDC(GLFW_FKEY_F34); ADDC(GLFW_FKEY_F35); ADDC(GLFW_FKEY_KP_0); ADDC(GLFW_FKEY_KP_1); ADDC(GLFW_FKEY_KP_2); ADDC(GLFW_FKEY_KP_3); ADDC(GLFW_FKEY_KP_4); ADDC(GLFW_FKEY_KP_5); ADDC(GLFW_FKEY_KP_6); ADDC(GLFW_FKEY_KP_7); ADDC(GLFW_FKEY_KP_8); ADDC(GLFW_FKEY_KP_9); ADDC(GLFW_FKEY_KP_DECIMAL); ADDC(GLFW_FKEY_KP_DIVIDE); ADDC(GLFW_FKEY_KP_MULTIPLY); ADDC(GLFW_FKEY_KP_SUBTRACT); ADDC(GLFW_FKEY_KP_ADD); ADDC(GLFW_FKEY_KP_ENTER); ADDC(GLFW_FKEY_KP_EQUAL); ADDC(GLFW_FKEY_KP_SEPARATOR); ADDC(GLFW_FKEY_KP_LEFT); ADDC(GLFW_FKEY_KP_RIGHT); ADDC(GLFW_FKEY_KP_UP); ADDC(GLFW_FKEY_KP_DOWN); ADDC(GLFW_FKEY_KP_PAGE_UP); ADDC(GLFW_FKEY_KP_PAGE_DOWN); ADDC(GLFW_FKEY_KP_HOME); ADDC(GLFW_FKEY_KP_END); ADDC(GLFW_FKEY_KP_INSERT); ADDC(GLFW_FKEY_KP_DELETE); ADDC(GLFW_FKEY_KP_BEGIN); ADDC(GLFW_FKEY_MEDIA_PLAY); ADDC(GLFW_FKEY_MEDIA_PAUSE); ADDC(GLFW_FKEY_MEDIA_PLAY_PAUSE); ADDC(GLFW_FKEY_MEDIA_REVERSE); ADDC(GLFW_FKEY_MEDIA_STOP); ADDC(GLFW_FKEY_MEDIA_FAST_FORWARD); ADDC(GLFW_FKEY_MEDIA_REWIND); ADDC(GLFW_FKEY_MEDIA_TRACK_NEXT); ADDC(GLFW_FKEY_MEDIA_TRACK_PREVIOUS); ADDC(GLFW_FKEY_MEDIA_RECORD); ADDC(GLFW_FKEY_LOWER_VOLUME); ADDC(GLFW_FKEY_RAISE_VOLUME); ADDC(GLFW_FKEY_MUTE_VOLUME); ADDC(GLFW_FKEY_LEFT_SHIFT); ADDC(GLFW_FKEY_LEFT_CONTROL); ADDC(GLFW_FKEY_LEFT_ALT); ADDC(GLFW_FKEY_LEFT_SUPER); ADDC(GLFW_FKEY_LEFT_HYPER); ADDC(GLFW_FKEY_LEFT_META); ADDC(GLFW_FKEY_RIGHT_SHIFT); ADDC(GLFW_FKEY_RIGHT_CONTROL); ADDC(GLFW_FKEY_RIGHT_ALT); ADDC(GLFW_FKEY_RIGHT_SUPER); ADDC(GLFW_FKEY_RIGHT_HYPER); ADDC(GLFW_FKEY_RIGHT_META); ADDC(GLFW_FKEY_ISO_LEVEL3_SHIFT); ADDC(GLFW_FKEY_ISO_LEVEL5_SHIFT); /* end glfw functional keys */ // --- Modifiers --------------------------------------------------------------- ADDC(GLFW_MOD_SHIFT); ADDC(GLFW_MOD_CONTROL); ADDC(GLFW_MOD_ALT); ADDC(GLFW_MOD_SUPER); ADDC(GLFW_MOD_HYPER); ADDC(GLFW_MOD_META); ADDC(GLFW_MOD_KITTY); ADDC(GLFW_MOD_CAPS_LOCK); ADDC(GLFW_MOD_NUM_LOCK); // --- Mouse ------------------------------------------------------------------- ADDC(GLFW_MOUSE_BUTTON_1); ADDC(GLFW_MOUSE_BUTTON_2); ADDC(GLFW_MOUSE_BUTTON_3); ADDC(GLFW_MOUSE_BUTTON_4); ADDC(GLFW_MOUSE_BUTTON_5); ADDC(GLFW_MOUSE_BUTTON_6); ADDC(GLFW_MOUSE_BUTTON_7); ADDC(GLFW_MOUSE_BUTTON_8); ADDC(GLFW_MOUSE_BUTTON_LAST); ADDC(GLFW_MOUSE_BUTTON_LEFT); ADDC(GLFW_MOUSE_BUTTON_RIGHT); ADDC(GLFW_MOUSE_BUTTON_MIDDLE); // --- Joystick ---------------------------------------------------------------- ADDC(GLFW_JOYSTICK_1); ADDC(GLFW_JOYSTICK_2); ADDC(GLFW_JOYSTICK_3); ADDC(GLFW_JOYSTICK_4); ADDC(GLFW_JOYSTICK_5); ADDC(GLFW_JOYSTICK_6); ADDC(GLFW_JOYSTICK_7); ADDC(GLFW_JOYSTICK_8); ADDC(GLFW_JOYSTICK_9); ADDC(GLFW_JOYSTICK_10); ADDC(GLFW_JOYSTICK_11); ADDC(GLFW_JOYSTICK_12); ADDC(GLFW_JOYSTICK_13); ADDC(GLFW_JOYSTICK_14); ADDC(GLFW_JOYSTICK_15); ADDC(GLFW_JOYSTICK_16); ADDC(GLFW_JOYSTICK_LAST); // --- Error codes ------------------------------------------------------------- ADDC(GLFW_NOT_INITIALIZED); ADDC(GLFW_NO_CURRENT_CONTEXT); ADDC(GLFW_INVALID_ENUM); ADDC(GLFW_INVALID_VALUE); ADDC(GLFW_OUT_OF_MEMORY); ADDC(GLFW_API_UNAVAILABLE); ADDC(GLFW_VERSION_UNAVAILABLE); ADDC(GLFW_PLATFORM_ERROR); ADDC(GLFW_FORMAT_UNAVAILABLE); // --- ADDC(GLFW_FOCUSED); ADDC(GLFW_ICONIFIED); ADDC(GLFW_RESIZABLE); ADDC(GLFW_VISIBLE); ADDC(GLFW_DECORATED); ADDC(GLFW_AUTO_ICONIFY); ADDC(GLFW_FLOATING); // --- ADDC(GLFW_RED_BITS); ADDC(GLFW_GREEN_BITS); ADDC(GLFW_BLUE_BITS); ADDC(GLFW_ALPHA_BITS); ADDC(GLFW_DEPTH_BITS); ADDC(GLFW_STENCIL_BITS); ADDC(GLFW_ACCUM_RED_BITS); ADDC(GLFW_ACCUM_GREEN_BITS); ADDC(GLFW_ACCUM_BLUE_BITS); ADDC(GLFW_ACCUM_ALPHA_BITS); ADDC(GLFW_AUX_BUFFERS); ADDC(GLFW_STEREO); ADDC(GLFW_SAMPLES); ADDC(GLFW_SRGB_CAPABLE); ADDC(GLFW_REFRESH_RATE); ADDC(GLFW_DOUBLEBUFFER); // --- ADDC(GLFW_CLIENT_API); ADDC(GLFW_CONTEXT_VERSION_MAJOR); ADDC(GLFW_CONTEXT_VERSION_MINOR); ADDC(GLFW_CONTEXT_REVISION); ADDC(GLFW_CONTEXT_ROBUSTNESS); ADDC(GLFW_OPENGL_FORWARD_COMPAT); ADDC(GLFW_CONTEXT_DEBUG); ADDC(GLFW_OPENGL_PROFILE); // --- ADDC(GLFW_OPENGL_API); ADDC(GLFW_OPENGL_ES_API); // --- ADDC(GLFW_NO_ROBUSTNESS); ADDC(GLFW_NO_RESET_NOTIFICATION); ADDC(GLFW_LOSE_CONTEXT_ON_RESET); // --- ADDC(GLFW_OPENGL_ANY_PROFILE); ADDC(GLFW_OPENGL_CORE_PROFILE); ADDC(GLFW_OPENGL_COMPAT_PROFILE); // --- ADDC(GLFW_CURSOR); ADDC(GLFW_STICKY_KEYS); ADDC(GLFW_STICKY_MOUSE_BUTTONS); // --- ADDC(GLFW_CURSOR_NORMAL); ADDC(GLFW_CURSOR_HIDDEN); ADDC(GLFW_CURSOR_DISABLED); // --- ADDC(GLFW_CONNECTED); ADDC(GLFW_DISCONNECTED); #undef ADDC // }}} return true; } kitty-0.41.1/kitty/glyph-cache.c0000664000175000017510000001143714773370543016011 0ustar nileshnilesh/* * glyph-cache.c * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "glyph-cache.h" typedef struct SpritePosKey { glyph_index ligature_index, count, cell_count, keysz_in_bytes; uint8_t scale, subscale, multicell_y, vertical_align; glyph_index key[]; } SpritePosKey; static_assert(sizeof(SpritePosKey) == sizeof(glyph_index) * 4 + sizeof(uint8_t) * 4, "Fix the ordering of SpritePosKey"); #define NAME sprite_pos_map #define KEY_TY const SpritePosKey* #define VAL_TY SpritePosition* static uint64_t sprite_pos_map_hash(KEY_TY key); #define HASH_FN sprite_pos_map_hash static bool sprite_pos_map_cmpr(KEY_TY a, KEY_TY b); #define CMPR_FN sprite_pos_map_cmpr #define MA_NAME Key #define MA_BLOCK_SIZE 16u static_assert(MA_BLOCK_SIZE > sizeof(SpritePosKey) + 2, "increase arena block size"); #define MA_ARENA_NUM_BLOCKS (2048u / MA_BLOCK_SIZE) #include "arena.h" #define MA_NAME Val #define MA_BLOCK_SIZE sizeof(VAL_TY) #define MA_ARENA_NUM_BLOCKS (2048u / MA_BLOCK_SIZE) #include "arena.h" #include "kitty-verstable.h" static uint64_t sprite_pos_map_hash(const SpritePosKey *key) { return vt_hash_bytes(key, key->keysz_in_bytes + sizeof(SpritePosKey)); } static bool sprite_pos_map_cmpr(const SpritePosKey *a, const SpritePosKey *b) { return a->keysz_in_bytes == b->keysz_in_bytes && memcmp(a, b, a->keysz_in_bytes + sizeof(SpritePosKey)) == 0; } typedef struct HashTable { sprite_pos_map table; KeyMonotonicArena keys; ValMonotonicArena vals; struct { SpritePosKey *key; size_t capacity; } scratch; } HashTable; SPRITE_POSITION_MAP_HANDLE create_sprite_position_hash_table(void) { HashTable *ans = calloc(1, sizeof(HashTable)); if (ans) vt_init(&ans->table); return (SPRITE_POSITION_MAP_HANDLE)ans; } SpritePosition* find_or_create_sprite_position( SPRITE_POSITION_MAP_HANDLE map_, glyph_index *glyphs, glyph_index count, glyph_index ligature_index, glyph_index cell_count, uint8_t scale, uint8_t subscale, uint8_t multicell_y, uint8_t vertical_align, bool *created ) { HashTable *ht = (HashTable*)map_; sprite_pos_map *map = &ht->table; const size_t keysz_in_bytes = count * sizeof(glyph_index); if (!ht->scratch.key || keysz_in_bytes > ht->scratch.capacity) { const size_t newsz = sizeof(ht->scratch.key[0]) + keysz_in_bytes + 64; ht->scratch.key = realloc(ht->scratch.key, newsz); if (!ht->scratch.key) { ht->scratch.capacity = 0; return NULL; } ht->scratch.capacity = newsz - sizeof(ht->scratch.key[0]); memset(ht->scratch.key, 0, newsz); } #define scratch ht->scratch.key scratch->keysz_in_bytes = keysz_in_bytes; scratch->count = count; scratch->ligature_index = ligature_index; scratch->cell_count = cell_count; scratch->scale = scale; scratch->subscale = subscale; scratch->multicell_y = multicell_y; scratch->vertical_align = vertical_align; memcpy(scratch->key, glyphs, keysz_in_bytes); sprite_pos_map_itr n = vt_get(map, scratch); if (!vt_is_end(n)) { *created = false; return n.data->val; } SpritePosKey *key = Key_get(&ht->keys, sizeof(SpritePosKey) + scratch->keysz_in_bytes); if (!key) return NULL; SpritePosition *val = Val_get(&ht->vals, sizeof(SpritePosition)); if (!val) return NULL; memcpy(key, scratch, sizeof(scratch[0]) + scratch->keysz_in_bytes); if (vt_is_end(vt_insert(map, key, val))) return NULL; *created = true; return val; #undef scratch } void free_sprite_position_hash_table(SPRITE_POSITION_MAP_HANDLE *map) { HashTable **mapref = (HashTable**)map; if (*mapref) { vt_cleanup(&mapref[0]->table); Key_free_all(&mapref[0]->keys); Val_free_all(&mapref[0]->vals); free(mapref[0]->scratch.key); free(mapref[0]); mapref[0] = NULL; } } #define NAME glyph_props_map #define KEY_TY glyph_index #define VAL_TY GlyphProperties #include "kitty-verstable.h" GLYPH_PROPERTIES_MAP_HANDLE create_glyph_properties_hash_table(void) { glyph_props_map *ans = calloc(1, sizeof(glyph_props_map)); if (ans) vt_init(ans); return (GLYPH_PROPERTIES_MAP_HANDLE)ans; } GlyphProperties find_glyph_properties(GLYPH_PROPERTIES_MAP_HANDLE map_, glyph_index glyph) { glyph_props_map *map = (glyph_props_map*)map_; glyph_props_map_itr n = vt_get(map, glyph); if (vt_is_end(n)) return (GlyphProperties){0}; return n.data->val; } bool set_glyph_properties(GLYPH_PROPERTIES_MAP_HANDLE map_, glyph_index glyph, GlyphProperties val) { glyph_props_map *map = (glyph_props_map*)map_; return !vt_is_end(vt_insert(map, glyph, val)); } void free_glyph_properties_hash_table(GLYPH_PROPERTIES_MAP_HANDLE *map_) { glyph_props_map **mapref = (glyph_props_map**)map_; if (*mapref) { vt_cleanup(*mapref); free(*mapref); *mapref = NULL; } } kitty-0.41.1/kitty/glyph-cache.h0000664000175000017510000000300514773370543016006 0ustar nileshnilesh/* * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" typedef union SpritePosition { struct { sprite_index idx : sizeof(sprite_index) * 8; bool rendered : 1; bool colored : 1; uint32_t : 30; }; uint64_t val; } SpritePosition; static_assert(sizeof(SpritePosition) == sizeof(uint64_t), "Fix ordering of SpritePosition"); typedef struct {int x;} *SPRITE_POSITION_MAP_HANDLE; SPRITE_POSITION_MAP_HANDLE create_sprite_position_hash_table(void); void free_sprite_position_hash_table(SPRITE_POSITION_MAP_HANDLE *handle); SpritePosition* find_or_create_sprite_position(SPRITE_POSITION_MAP_HANDLE map, glyph_index *glyphs, glyph_index count, glyph_index ligature_index, glyph_index cell_count, uint8_t scale, uint8_t subscale, uint8_t multicell_y, uint8_t vertical_align, bool *created); typedef union GlyphProperties { struct { uint8_t special_set : 1; uint8_t special_val : 1; uint8_t empty_set : 1; uint8_t empty_val : 1; }; uint8_t val; } GlyphProperties; typedef struct {int x;} *GLYPH_PROPERTIES_MAP_HANDLE; GLYPH_PROPERTIES_MAP_HANDLE create_glyph_properties_hash_table(void); void free_glyph_properties_hash_table(GLYPH_PROPERTIES_MAP_HANDLE *handle); GlyphProperties find_glyph_properties(GLYPH_PROPERTIES_MAP_HANDLE map, glyph_index glyph); bool set_glyph_properties(GLYPH_PROPERTIES_MAP_HANDLE map, glyph_index glyph, GlyphProperties val); kitty-0.41.1/kitty/graphics.c0000664000175000017510000031713214773370543015426 0ustar nileshnilesh/* * graphics.c * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #define GRAPHICS_INTERNAL_APIS #include "graphics.h" #include "state.h" #include "disk-cache.h" #include "iqsort.h" #include "safe-wrappers.h" #include #include #include #include #include #include #include #include "png-reader.h" PyTypeObject GraphicsManager_Type; #define DEFAULT_STORAGE_LIMIT 320u * (1024u * 1024u) #define REPORT_ERROR(...) { log_error(__VA_ARGS__); } #define RAII_CoalescedFrameData(name, initializer) __attribute__((cleanup(cfd_free))) CoalescedFrameData name = initializer // caching {{{ #define member_size(type, member) sizeof(((type *)0)->member) #define CACHE_KEY_BUFFER_SIZE (member_size(ImageAndFrame, image_id) + member_size(ImageAndFrame, frame_id)) static size_t cache_key(const ImageAndFrame x, char *key) { memcpy(key, &x.image_id, sizeof(x.image_id)); memcpy(key + sizeof(x.image_id), &x.frame_id, sizeof(x.frame_id)); return CACHE_KEY_BUFFER_SIZE; } #define CK(x) key, cache_key(x, key) static bool add_to_cache(GraphicsManager *self, const ImageAndFrame x, const void *data, const size_t sz) { char key[CACHE_KEY_BUFFER_SIZE]; return add_to_disk_cache(self->disk_cache, CK(x), data, sz); } static bool remove_from_cache(GraphicsManager *self, const ImageAndFrame x) { char key[CACHE_KEY_BUFFER_SIZE]; return remove_from_disk_cache(self->disk_cache, CK(x)); } static bool read_from_cache(const GraphicsManager *self, const ImageAndFrame x, void **data, size_t *sz) { char key[CACHE_KEY_BUFFER_SIZE]; return read_from_disk_cache_simple(self->disk_cache, CK(x), data, sz, false); } static size_t cache_size(const GraphicsManager *self) { return disk_cache_total_size(self->disk_cache); } #undef CK // }}} static inline id_type next_id(id_type *counter) { id_type ans = ++(*counter); if (UNLIKELY(ans == 0)) ans = ++(*counter); return ans; } static const unsigned PARENT_DEPTH_LIMIT = 8; GraphicsManager* grman_alloc(bool for_paused_rendering) { GraphicsManager *self = (GraphicsManager *)GraphicsManager_Type.tp_alloc(&GraphicsManager_Type, 0); self->render_data.capacity = 64; self->render_data.item = calloc(self->render_data.capacity, sizeof(self->render_data.item[0])); self->storage_limit = DEFAULT_STORAGE_LIMIT; if (self->render_data.item == NULL) { PyErr_NoMemory(); Py_CLEAR(self); return NULL; } if (!for_paused_rendering) { self->disk_cache = create_disk_cache(); if (!self->disk_cache) { Py_CLEAR(self); return NULL; } } vt_init(&self->images_by_internal_id); return self; } #define iter_refs(img) vt_create_for_loop(ref_map_itr, i, &((img)->refs_by_internal_id)) static void free_refs_data(Image *img) { iter_refs(img) free(i.data->val); vt_cleanup(&img->refs_by_internal_id); } static void free_load_data(LoadData *ld) { free(ld->buf); ld->buf_used = 0; ld->buf_capacity = 0; ld->buf = NULL; if (ld->mapped_file) munmap(ld->mapped_file, ld->mapped_file_sz); ld->mapped_file = NULL; ld->mapped_file_sz = 0; ld->loading_for = (const ImageAndFrame){0}; } static void* clear_texture_ref(TextureRef **x) { if (*x) { if ((*x)->refcnt < 2) { if ((*x)->id) free_texture(&(*x)->id); free(*x); *x = NULL; } else (*x)->refcnt--; } return NULL; } static TextureRef* incref_texture_ref(TextureRef *ref) { if (ref) ref->refcnt++; return ref; } static TextureRef* new_texture_ref(void) { TextureRef *ans = calloc(1, sizeof(TextureRef)); if (!ans) fatal("Out of memory allocating a TextureRef"); ans->refcnt = 1; return ans; } static uint32_t texture_id_for_img(Image *img) { return img->texture ? img->texture->id : 0; } static void free_image_resources(GraphicsManager *self, Image *img) { clear_texture_ref(&img->texture); if (self->disk_cache) { ImageAndFrame key = { .image_id=img->internal_id, .frame_id = img->root_frame.id }; if (!remove_from_cache(self, key) && PyErr_Occurred()) PyErr_Print(); for (unsigned i = 0; i < img->extra_framecnt; i++) { key.frame_id = img->extra_frames[i].id; if (!remove_from_cache(self, key) && PyErr_Occurred()) PyErr_Print(); } } if (img->extra_frames) { free(img->extra_frames); img->extra_frames = NULL; } free_refs_data(img); self->used_storage = img->used_storage <= self->used_storage ? self->used_storage - img->used_storage : 0; } static void free_image(GraphicsManager *self, Image *img) { free_image_resources(self, img); free(img); } #define iter_images(grman) vt_create_for_loop(image_map_itr, i, &((grman)->images_by_internal_id)) static void free_all_images(GraphicsManager *self) { iter_images(self) free_image(self, i.data->val); vt_cleanup(&self->images_by_internal_id); } static void dealloc(GraphicsManager* self) { free_all_images(self); free(self->render_data.item); Py_CLEAR(self->disk_cache); Py_TYPE(self)->tp_free((PyObject*)self); } static Image* img_by_internal_id(const GraphicsManager *self, id_type id) { image_map_itr i = vt_get((image_map*)&self->images_by_internal_id, id); return vt_is_end(i) ? NULL : i.data->val; } static Image* img_by_client_id(const GraphicsManager *self, uint32_t id) { iter_images(((GraphicsManager*)self)) if (i.data->val->client_id == id) return i.data->val; return NULL; } static Image* img_by_client_number(const GraphicsManager *self, uint32_t number) { // get the newest image with the specified number Image *ans = NULL; iter_images(((GraphicsManager*)self)) { Image *img = i.data->val; if (img->client_number == number && (!ans || img->internal_id > ans->internal_id)) ans = img; } return ans; } static ImageRef* ref_by_internal_id(const Image *img, id_type id) { ref_map_itr i = vt_get(&((Image *)img)->refs_by_internal_id, id); return vt_is_end(i) ? NULL : i.data->val; } static ImageRef* ref_by_client_id(const Image *img, uint32_t id) { iter_refs((Image*)img) if (i.data->val->client_id == id) return i.data->val; return NULL; } static image_map_itr remove_image_itr(GraphicsManager *self, image_map_itr i) { free_image(self, i.data->val); self->layers_dirty = true; return vt_erase_itr(&self->images_by_internal_id, i); } static void remove_image(GraphicsManager *self, Image *img) { image_map_itr i = vt_get(&self->images_by_internal_id, img->internal_id); if (!vt_is_end(i)) remove_image_itr(self, i); } static void remove_images(GraphicsManager *self, bool(*predicate)(Image*), id_type skip_image_internal_id) { for (image_map_itr i = vt_first(&self->images_by_internal_id); !vt_is_end(i);) { Image *img = i.data->val; if (img->internal_id != skip_image_internal_id && predicate(img)) i = remove_image_itr(self, i); else i = vt_next(i); } } void grman_pause_rendering(GraphicsManager *self, GraphicsManager *dest) { make_window_context_current(dest->window_id); free_all_images(dest); dest->render_data.count = 0; if (self == NULL) return; dest->window_id = self->window_id; dest->layers_dirty = true; dest->last_scrolled_by = 0; iter_images(self) { Image *clone = calloc(1, sizeof(Image)), *img = i.data->val; if (!clone) continue; memcpy(clone, img, sizeof(*clone)); memset(&clone->refs_by_internal_id, 0, sizeof(clone->refs_by_internal_id)); vt_init(&clone->refs_by_internal_id); clone->extra_frames = NULL; iter_refs(img) { ImageRef *cr = malloc(sizeof(ImageRef)); if (cr) { memcpy(cr, i.data->val, sizeof(*cr)); vt_insert(&clone->refs_by_internal_id, cr->internal_id, cr); } } clone->texture = incref_texture_ref(img->texture); vt_insert(&dest->images_by_internal_id, clone->internal_id, clone); } } // Loading image data {{{ static bool trim_predicate(Image *img) { return !img->root_frame_data_loaded || !vt_size(&img->refs_by_internal_id); } static void apply_storage_quota(GraphicsManager *self, size_t storage_limit, id_type currently_added_image_internal_id) { // First remove unreferenced images, even if they have an id remove_images(self, trim_predicate, currently_added_image_internal_id); if (self->used_storage < storage_limit) return; size_t num_images = vt_size(&self->images_by_internal_id); RAII_ALLOC(Image*, sorted, malloc(num_images * sizeof(Image*))); if (!sorted) fatal("Out of memory"); Image **p = sorted; iter_images(self) { *p++ = i.data->val; } #define oldest_img_first(a, b) ((*a)->atime < (*b)->atime) QSORT(Image*, sorted, num_images, oldest_img_first); #undef oldest_img_first for (p = sorted; self->used_storage > storage_limit && num_images; p++, num_images--) remove_image(self, *p); if (!num_images || !vt_size(&self->images_by_internal_id)) self->used_storage = 0; // sanity check } static char command_response[512] = {0}; static void set_command_failed_response(const char *code, const char *fmt, ...) { va_list args; va_start(args, fmt); const size_t sz = sizeof(command_response)/sizeof(command_response[0]); const int num = snprintf(command_response, sz, "%s:", code); vsnprintf(command_response + num, sz - num, fmt, args); va_end(args); } // Decode formats {{{ #define ABRT(code, ...) { set_command_failed_response(#code, __VA_ARGS__); goto err; } static bool mmap_img_file(GraphicsManager *self, int fd, size_t sz, off_t offset) { if (!sz) { struct stat s; if (fstat(fd, &s) != 0) ABRT(EBADF, "Failed to fstat() the fd: %d file with error: [%d] %s", fd, errno, strerror(errno)); sz = s.st_size; } void *addr = mmap(0, sz, PROT_READ, MAP_SHARED, fd, offset); if (addr == MAP_FAILED) ABRT(EBADF, "Failed to map image file fd: %d at offset: %zd with size: %zu with error: [%d] %s", fd, offset, sz, errno, strerror(errno)); self->currently_loading.mapped_file = addr; self->currently_loading.mapped_file_sz = sz; return true; err: return false; } static const char* zlib_strerror(int ret) { #define Z(x) case x: return #x; static char buf[128]; switch(ret) { case Z_ERRNO: return strerror(errno); default: snprintf(buf, sizeof(buf)/sizeof(buf[0]), "Unknown error: %d", ret); return buf; Z(Z_STREAM_ERROR); Z(Z_DATA_ERROR); Z(Z_MEM_ERROR); Z(Z_BUF_ERROR); Z(Z_VERSION_ERROR); } #undef Z } static bool inflate_zlib(LoadData *load_data, uint8_t *buf, size_t bufsz) { bool ok = false; z_stream z; uint8_t *decompressed = malloc(load_data->data_sz); if (decompressed == NULL) fatal("Out of memory allocating decompression buffer"); z.zalloc = Z_NULL; z.zfree = Z_NULL; z.opaque = Z_NULL; z.avail_in = bufsz; z.next_in = (Bytef*)buf; z.avail_out = load_data->data_sz; z.next_out = decompressed; int ret; if ((ret = inflateInit(&z)) != Z_OK) ABRT(ENOMEM, "Failed to initialize inflate with error: %s", zlib_strerror(ret)); if ((ret = inflate(&z, Z_FINISH)) != Z_STREAM_END) ABRT(EINVAL, "Failed to inflate image data with error: %s", zlib_strerror(ret)); if (z.avail_out) ABRT(EINVAL, "Image data size post inflation does not match expected size"); free_load_data(load_data); load_data->buf_capacity = load_data->data_sz; load_data->buf = decompressed; load_data->buf_used = load_data->data_sz; ok = true; err: inflateEnd(&z); if (!ok) free(decompressed); return ok; } static void png_error_handler(png_read_data *d UNUSED, const char *code, const char *msg) { set_command_failed_response(code, "%s", msg); } static bool inflate_png(LoadData *load_data, uint8_t *buf, size_t bufsz) { png_read_data d = {.err_handler=png_error_handler}; inflate_png_inner(&d, buf, bufsz); if (d.ok) { free_load_data(load_data); load_data->buf = d.decompressed; load_data->buf_capacity = d.sz; load_data->buf_used = d.sz; load_data->data_sz = d.sz; load_data->width = d.width; load_data->height = d.height; } else free(d.decompressed); free(d.row_pointers); return d.ok; } #undef ABRT // }}} static bool add_trim_predicate(Image *img) { return !img->root_frame_data_loaded || (!img->client_id && !vt_size(&img->refs_by_internal_id)); } static void print_png_read_error(png_read_data *d, const char *code, const char* msg) { if (d->error.used >= d->error.capacity) { size_t cap = MAX(2 * d->error.capacity, 1024 + d->error.used); d->error.buf = realloc(d->error.buf, cap); if (!d->error.buf) return; d->error.capacity = cap; } d->error.used += snprintf(d->error.buf + d->error.used, d->error.capacity - d->error.used, "%s: %s ", code, msg); } bool png_from_data(void *png_data, size_t png_data_sz, const char *path_for_error_messages, uint8_t** data, unsigned int* width, unsigned int* height, size_t* sz) { png_read_data d = {.err_handler=print_png_read_error}; inflate_png_inner(&d, png_data, png_data_sz); if (!d.ok) { log_error("Failed to decode PNG image at: %s with error: %s", path_for_error_messages, d.error.used > 0 ? d.error.buf : ""); free(d.decompressed); free(d.row_pointers); free(d.error.buf); return false; } *data = d.decompressed; free(d.row_pointers); free(d.error.buf); *sz = d.sz; *height = d.height; *width = d.width; return true; } bool png_from_file_pointer(FILE *fp, const char *path_for_error_messages, uint8_t** data, unsigned int* width, unsigned int* height, size_t* sz) { size_t capacity = 16*1024, pos = 0; unsigned char *buf = malloc(capacity); if (!buf) { log_error("Out of memory reading PNG file at: %s", path_for_error_messages); fclose(fp); return false; } while (!feof(fp)) { if (capacity - pos < 1024) { capacity *= 2; unsigned char *new_buf = realloc(buf, capacity); if (!new_buf) { free(buf); log_error("Out of memory reading PNG file at: %s", path_for_error_messages); fclose(fp); return false; } buf = new_buf; } pos += fread(buf + pos, sizeof(char), capacity - pos, fp); int saved_errno = errno; if (ferror(fp) && saved_errno != EINTR) { log_error("Failed while reading from file: %s with error: %s", path_for_error_messages, strerror(saved_errno)); free(buf); return false; } } bool ret = png_from_data(buf, pos, path_for_error_messages, data, width, height, sz); free(buf); return ret; } bool png_path_to_bitmap(const char* path, uint8_t** data, unsigned int* width, unsigned int* height, size_t* sz) { FILE* fp = fopen(path, "r"); if (fp == NULL) { log_error("The PNG image: %s could not be opened with error: %s", path, strerror(errno)); return false; } bool ret = png_from_file_pointer(fp, path, data, width, height, sz); fclose(fp); fp = NULL; return ret; } bool image_path_to_bitmap(const char *path, uint8_t** data, unsigned int* width, unsigned int* height, size_t* sz) { *data = NULL; *sz = 0; *width = 0; *height = 0; RAII_PyObject(module, PyImport_ImportModule("kitty.render_cache")); #define fail_on_python_error { log_error("Failed to convert image at %s to bitmap with python error:", path); PyErr_Print(); return false; } if (!module) fail_on_python_error; RAII_PyObject(irc, PyObject_GetAttrString(module, "default_image_render_cache")); if (!irc) fail_on_python_error; RAII_PyObject(ret, PyObject_CallFunction(irc, "s", path)); if (!ret) fail_on_python_error; size_t w = PyLong_AsSize_t(PyTuple_GET_ITEM(ret, 0)); size_t h = PyLong_AsSize_t(PyTuple_GET_ITEM(ret, 1)); int fd = PyLong_AsLong(PyTuple_GET_ITEM(ret, 2)); #undef fail_on_python_error size_t data_size = 8 + w * h * 4; *data = mmap(NULL, data_size, PROT_READ, MAP_PRIVATE, fd, 0); int saved_errno = errno; safe_close(fd, __FILE__, __LINE__); if (*data == MAP_FAILED) { log_error("Failed to mmap bitmap data for image at %s with error: %s", path, strerror(saved_errno)); return false; } *sz = data_size; *width = w; *height = h; return true; } static Image* find_or_create_image(GraphicsManager *self, uint32_t id, bool *existing) { if (id) { Image *img = img_by_client_id(self, id); if (img) { *existing = true; return img; } } *existing = false; Image *ans = calloc(1, sizeof(Image)); if (!ans) fatal("Out of memory allocating Image object"); ans->internal_id = next_id(&self->image_id_counter); ans->texture = new_texture_ref(); vt_init(&ans->refs_by_internal_id); if (vt_is_end(vt_insert(&self->images_by_internal_id, ans->internal_id, ans))) fatal("Out of memory"); return ans; } static uint32_t get_free_client_id(const GraphicsManager *self) { size_t num_images = vt_size(&((GraphicsManager*)self)->images_by_internal_id); if (!num_images) return 1; RAII_ALLOC(uint32_t, client_ids, malloc(num_images * sizeof(uint32_t))); if (!client_ids) fatal("Out of memory"); size_t count = 0; iter_images((GraphicsManager*)self) { Image *img = i.data->val; if (img->client_id) client_ids[count++] = img->client_id; } if (!count) return 1; #define int_lt(a, b) ((*a)<(*b)) QSORT(uint32_t, client_ids, count, int_lt) #undef int_lt uint32_t prev_id = 0, ans = 1; for (size_t i = 0; i < count; i++) { if (client_ids[i] == prev_id) continue; prev_id = client_ids[i]; if (client_ids[i] != ans) break; ans = client_ids[i] + 1; } return ans; } #define ABRT(code, ...) { set_command_failed_response(code, __VA_ARGS__); self->currently_loading.loading_completed_successfully = false; free_load_data(&self->currently_loading); return NULL; } #define MAX_DATA_SZ (4u * 100000000u) enum FORMATS { RGB=24, RGBA=32, PNG=100 }; static Image* load_image_data(GraphicsManager *self, Image *img, const GraphicsCommand *g, const unsigned char transmission_type, const uint32_t data_fmt, const uint8_t *payload) { int fd; static char fname[2056] = {0}; LoadData *load_data = &self->currently_loading; switch(transmission_type) { case 'd': // direct if (load_data->buf_capacity - load_data->buf_used < g->payload_sz) { if (load_data->buf_used + g->payload_sz > MAX_DATA_SZ || data_fmt != PNG) ABRT("EFBIG", "Too much data"); load_data->buf_capacity = MIN(2 * load_data->buf_capacity, MAX_DATA_SZ); load_data->buf = realloc(load_data->buf, load_data->buf_capacity); if (load_data->buf == NULL) { load_data->buf_capacity = 0; load_data->buf_used = 0; ABRT("ENOMEM", "Out of memory"); } } memcpy(load_data->buf + load_data->buf_used, payload, g->payload_sz); load_data->buf_used += g->payload_sz; if (!g->more) { load_data->loading_completed_successfully = true; load_data->loading_for = (const ImageAndFrame){0}; } break; case 'f': // file case 't': // temporary file case 's': // POSIX shared memory if (g->payload_sz > 2048) ABRT("EINVAL", "Filename too long"); snprintf(fname, sizeof(fname)/sizeof(fname[0]), "%.*s", (int)g->payload_sz, payload); if (transmission_type == 's') fd = safe_shm_open(fname, O_RDONLY, 0); else fd = safe_open(fname, O_CLOEXEC | O_RDONLY | O_NONBLOCK, 0); // O_NONBLOCK so that opening a FIFO pipe does not block if (fd == -1) ABRT("EBADF", "Failed to open file for graphics transmission with error: [%d] %s", errno, strerror(errno)); if (global_state.boss && transmission_type != 's') { RAII_PyObject(cret_, PyObject_CallMethod(global_state.boss, "is_ok_to_read_image_file", "si", fname, fd)); if (cret_ == NULL) { PyErr_Print(); ABRT("EBADF", "Failed to check file for read permission"); } if (cret_ != Py_True) { log_error("Refusing to read image file as permission was denied"); ABRT("EPERM", "Permission denied to read image file"); } } load_data->loading_completed_successfully = mmap_img_file(self, fd, g->data_sz, g->data_offset); safe_close(fd, __FILE__, __LINE__); if (transmission_type == 't' && strstr(fname, "tty-graphics-protocol") != NULL) { if (global_state.boss) { call_boss(safe_delete_temp_file, "s", fname); } else unlink(fname); } else if (transmission_type == 's') shm_unlink(fname); if (!load_data->loading_completed_successfully) return NULL; break; default: ABRT("EINVAL", "Unknown transmission type: %c", g->transmission_type); } return img; } static Image* process_image_data(GraphicsManager *self, Image* img, const GraphicsCommand *g, const unsigned char transmission_type, const uint32_t data_fmt) { bool needs_processing = g->compressed || data_fmt == PNG; if (needs_processing) { uint8_t *buf; size_t bufsz; #define IB { if (self->currently_loading.buf) { buf = self->currently_loading.buf; bufsz = self->currently_loading.buf_used; } else { buf = self->currently_loading.mapped_file; bufsz = self->currently_loading.mapped_file_sz; } } switch(g->compressed) { case 'z': IB; if (!inflate_zlib(&self->currently_loading, buf, bufsz)) { self->currently_loading.loading_completed_successfully = false; return NULL; } break; case 0: break; default: ABRT("EINVAL", "Unknown image compression: %c", g->compressed); } switch(data_fmt) { case PNG: IB; if (!inflate_png(&self->currently_loading, buf, bufsz)) { self->currently_loading.loading_completed_successfully = false; return NULL; } break; default: break; } #undef IB self->currently_loading.data = self->currently_loading.buf; if (self->currently_loading.buf_used < self->currently_loading.data_sz) { ABRT("ENODATA", "Insufficient image data: %zu < %zu", self->currently_loading.buf_used, self->currently_loading.data_sz); } if (self->currently_loading.mapped_file) { munmap(self->currently_loading.mapped_file, self->currently_loading.mapped_file_sz); self->currently_loading.mapped_file = NULL; self->currently_loading.mapped_file_sz = 0; } } else { if (transmission_type == 'd') { if (self->currently_loading.buf_used < self->currently_loading.data_sz) { ABRT("ENODATA", "Insufficient image data: %zu < %zu", self->currently_loading.buf_used, self->currently_loading.data_sz); } else self->currently_loading.data = self->currently_loading.buf; } else { if (self->currently_loading.mapped_file_sz < self->currently_loading.data_sz) { ABRT("ENODATA", "Insufficient image data: %zu < %zu", self->currently_loading.mapped_file_sz, self->currently_loading.data_sz); } else self->currently_loading.data = self->currently_loading.mapped_file; } self->currently_loading.loading_completed_successfully = true; } return img; } static Image* initialize_load_data(GraphicsManager *self, const GraphicsCommand *g, Image *img, const unsigned char transmission_type, const uint32_t data_fmt, const uint32_t frame_id) { free_load_data(&self->currently_loading); self->currently_loading = (const LoadData){0}; self->currently_loading.start_command = *g; self->currently_loading.width = g->data_width; self->currently_loading.height = g->data_height; switch(data_fmt) { case PNG: if (g->data_sz > MAX_DATA_SZ) ABRT("EINVAL", "PNG data size too large"); self->currently_loading.is_4byte_aligned = true; self->currently_loading.is_opaque = false; self->currently_loading.data_sz = g->data_sz ? g->data_sz : 1024 * 100; break; case RGB: case RGBA: self->currently_loading.data_sz = (size_t)g->data_width * g->data_height * (data_fmt / 8); if (!self->currently_loading.data_sz) ABRT("EINVAL", "Zero width/height not allowed"); self->currently_loading.is_4byte_aligned = data_fmt == RGBA || (self->currently_loading.width % 4 == 0); self->currently_loading.is_opaque = data_fmt == RGB; break; default: ABRT("EINVAL", "Unknown image format: %u", data_fmt); } self->currently_loading.loading_for.image_id = img->internal_id; self->currently_loading.loading_for.frame_id = frame_id; if (transmission_type == 'd') { self->currently_loading.buf_capacity = self->currently_loading.data_sz + (g->compressed ? 1024 : 10); // compression header self->currently_loading.buf = malloc(self->currently_loading.buf_capacity); self->currently_loading.buf_used = 0; if (self->currently_loading.buf == NULL) { self->currently_loading.buf_capacity = 0; self->currently_loading.buf_used = 0; ABRT("ENOMEM", "Out of memory"); } } return img; } #define INIT_CHUNKED_LOAD { \ self->currently_loading.start_command.more = g->more; \ self->currently_loading.start_command.payload_sz = g->payload_sz; \ g = &self->currently_loading.start_command; \ tt = g->transmission_type ? g->transmission_type : 'd'; \ fmt = g->format ? g->format : RGBA; \ } #define MAX_IMAGE_DIMENSION 10000u static void upload_to_gpu(GraphicsManager *self, Image *img, const bool is_opaque, const bool is_4byte_aligned, const uint8_t *data) { if (!self->context_made_current_for_this_command) { if (!self->window_id) return; if (!make_window_context_current(self->window_id)) return; self->context_made_current_for_this_command = true; } if (img->texture) send_image_to_gpu(&img->texture->id, data, img->width, img->height, is_opaque, is_4byte_aligned, true, REPEAT_CLAMP); } static Image* handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, bool *is_dirty, uint32_t iid, bool is_query) { bool existing, init_img = true; Image *img = NULL; unsigned char tt = g->transmission_type ? g->transmission_type : 'd'; uint32_t fmt = g->format ? g->format : RGBA; if (tt == 'd' && self->currently_loading.loading_for.image_id) init_img = false; if (init_img) { self->currently_loading.loading_for = (const ImageAndFrame){0}; if (g->data_width > MAX_IMAGE_DIMENSION || g->data_height > MAX_IMAGE_DIMENSION) ABRT("EINVAL", "Image too large"); remove_images(self, add_trim_predicate, 0); img = find_or_create_image(self, iid, &existing); if (existing) { free_image_resources(self, img); img->texture = new_texture_ref(); img->root_frame_data_loaded = false; img->is_drawn = false; img->current_frame_shown_at = 0; img->extra_framecnt = 0; *is_dirty = true; self->layers_dirty = true; } else { img->client_id = iid; img->client_number = g->image_number; if (!img->client_id && img->client_number) { img->client_id = get_free_client_id(self); iid = img->client_id; } } img->atime = monotonic(); img->used_storage = 0; if (!initialize_load_data(self, g, img, tt, fmt, 0)) return NULL; self->currently_loading.start_command.id = iid; } else { INIT_CHUNKED_LOAD; img = img_by_internal_id(self, self->currently_loading.loading_for.image_id); if (img == NULL) { self->currently_loading.loading_for = (const ImageAndFrame){0}; ABRT("EILSEQ", "More payload loading refers to non-existent image"); } } img = load_image_data(self, img, g, tt, fmt, payload); if (!img || !self->currently_loading.loading_completed_successfully) return NULL; self->currently_loading.loading_for = (const ImageAndFrame){0}; img = process_image_data(self, img, g, tt, fmt); if (!img) return NULL; size_t required_sz = (size_t)(self->currently_loading.is_opaque ? 3 : 4) * self->currently_loading.width * self->currently_loading.height; if (self->currently_loading.data_sz != required_sz) ABRT("EINVAL", "Image dimensions: %ux%u do not match data size: %zu, expected size: %zu", self->currently_loading.width, self->currently_loading.height, self->currently_loading.data_sz, required_sz); if (self->currently_loading.loading_completed_successfully) { img->width = self->currently_loading.width; img->height = self->currently_loading.height; if (img->root_frame.id) remove_from_cache(self, (const ImageAndFrame){.image_id=img->internal_id, .frame_id=img->root_frame.id}); img->root_frame = (const Frame){ .id = ++img->frame_id_counter, .is_opaque = self->currently_loading.is_opaque, .is_4byte_aligned = self->currently_loading.is_4byte_aligned, .width = img->width, .height = img->height, }; if (!is_query) { if (!add_to_cache(self, (const ImageAndFrame){.image_id = img->internal_id, .frame_id=img->root_frame.id}, self->currently_loading.data, self->currently_loading.data_sz)) { if (PyErr_Occurred()) PyErr_Print(); ABRT("ENOSPC", "Failed to store image data in disk cache"); } upload_to_gpu(self, img, img->root_frame.is_opaque, img->root_frame.is_4byte_aligned, self->currently_loading.data); self->used_storage += required_sz; img->used_storage = required_sz; } img->root_frame_data_loaded = true; } return img; #undef MAX_DATA_SZ } static const char* finish_command_response(const GraphicsCommand *g, bool data_loaded) { static char rbuf[sizeof(command_response)/sizeof(command_response[0]) + 128]; bool is_ok_response = !command_response[0]; if (g->quiet) { if (is_ok_response || g->quiet > 1) return NULL; } if (g->id || g->image_number) { if (is_ok_response) { if (!data_loaded) return NULL; snprintf(command_response, 10, "OK"); } size_t pos = 0; rbuf[pos++] = 'G'; #define print(fmt, ...) if (arraysz(rbuf) - 1 > pos) pos += snprintf(rbuf + pos, arraysz(rbuf) - 1 - pos, fmt, __VA_ARGS__) if (g->id) print("i=%u", g->id); if (g->image_number) print(",I=%u", g->image_number); if (g->placement_id) print(",p=%u", g->placement_id); if (g->num_lines && (g->action == 'f' || g->action == 'a')) print(",r=%u", g->num_lines); print(";%s", command_response); return rbuf; #undef print } return NULL; } // }}} // Displaying images {{{ static void update_src_rect(ImageRef *ref, Image *img) { // The src rect in OpenGL co-ords [0, 1] with origin at top-left corner of image ref->src_rect.left = (float)ref->src_x / (float)img->width; ref->src_rect.right = (float)(ref->src_x + ref->src_width) / (float)img->width; ref->src_rect.top = (float)ref->src_y / (float)img->height; ref->src_rect.bottom = (float)(ref->src_y + ref->src_height) / (float)img->height; } static void update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows, CellPixelSize cell) { uint32_t t; if (num_cols == 0) { if (num_rows == 0) { t = (uint32_t)(ref->src_width + ref->cell_x_offset); num_cols = t / cell.width; if (t > num_cols * cell.width) num_cols += 1; } else { double height_px = cell.height * num_rows + ref->cell_y_offset; double width_px = height_px * ref->src_width / (double) ref->src_height; num_cols = (uint32_t)ceil(width_px / cell.width); } } if (num_rows == 0) { if (num_cols == 0) { t = (uint32_t)(ref->src_height + ref->cell_y_offset); num_rows = t / cell.height; if (t > num_rows * cell.height) num_rows += 1; } else { double width_px = cell.width * num_cols + ref->cell_x_offset; double height_px = width_px * ref->src_height / (double)ref->src_width; num_rows = (uint32_t)ceil(height_px / cell.height); } } ref->effective_num_rows = num_rows; ref->effective_num_cols = num_cols; } static ImageRef* create_ref(Image *img, ImageRef *clone_from) { ImageRef *ans = calloc(1, sizeof(ImageRef)); if (!ans) fatal("Out of memory creating ImageRef"); if (clone_from) *ans = *clone_from; ans->internal_id = next_id(&img->ref_id_counter); if (vt_is_end(vt_insert(&img->refs_by_internal_id, ans->internal_id, ans))) fatal("Out of memory"); return ans; } static inline bool is_cell_image(const ImageRef *self) { return self->virtual_ref_id != 0; } // Create a real image ref for a virtual image ref (placement) positioned in the // given cells. This is used for images positioned using Unicode placeholders. // // The image is resized to fit a box of cells with dimensions // `image_ref->columns` by `image_ref->rows`. The parameters `img_col`, // `img_row, `columns`, `rows` describe a part of this box that we want to // display. // // Parameters: // - `self` - the graphics manager // - `screen_row` - the starting row of the screen // - `screen_col` - the starting column of the screen // - `image_id` - the id of the image // - `placement_id` - the id of the placement (0 to find it automatically), it // must be a virtual placement // - `img_col` - the column of the image box we want to start with (base 0) // - `img_row` - the row of the image box we want to start with (base 0) // - `columns` - the number of columns we want to display // - `rows` - the number of rows we want to display // - `cell` - the size of a screen cell void grman_put_cell_image(GraphicsManager *self, uint32_t screen_row, uint32_t screen_col, uint32_t image_id, uint32_t placement_id, uint32_t img_col, uint32_t img_row, uint32_t columns, uint32_t rows, CellPixelSize cell) { Image *img = img_by_client_id(self, image_id); if (img == NULL) return; ImageRef *virt_img_ref = NULL; if (placement_id) { // Find the placement by the id. It must be a virtual placement. iter_refs(img) { ImageRef *r = i.data->val; if (r->is_virtual_ref && r->client_id == placement_id) { virt_img_ref = r; break; } } } else { // Find the first virtual image placement. iter_refs(img) { ImageRef *r = i.data->val; if (r->is_virtual_ref) { virt_img_ref = r; break; } } } if (!virt_img_ref) return; // Create the ref structure on stack first. We will not create a real // reference if the image is completely out of bounds. ImageRef ref = {0}; ref.virtual_ref_id = virt_img_ref->internal_id; uint32_t img_rows = virt_img_ref->num_rows; uint32_t img_columns = virt_img_ref->num_cols; // If the number of columns or rows for the image is not set, compute them // in such a way that the image is as close as possible to its natural size. if (img_columns == 0) img_columns = (img->width + cell.width - 1) / cell.width; if (img_rows == 0) img_rows = (img->height + cell.height - 1) / cell.height; ref.start_row = screen_row; ref.start_column = screen_col; ref.num_cols = columns; ref.num_rows = rows; // The image is fit to the destination box of size // (cell.width * img_columns) by (cell.height * img_rows) // The conversion from source (image) coordinates to destination (box) // coordinates is done by the following formula: // x_dst = x_src * x_scale + x_offset // y_dst = y_src * y_scale + y_offset float x_offset, y_offset, x_scale, y_scale; // Fit the image to the box while preserving aspect ratio if (img->width * img_rows * cell.height > img->height * img_columns * cell.width) { // Fit to width and center vertically. x_offset = 0; x_scale = (float)(img_columns * cell.width) / MAX(1u, img->width); y_scale = x_scale; y_offset = (img_rows * cell.height - img->height * y_scale) / 2; } else { // Fit to height and center horizontally. y_offset = 0; y_scale = (float)(img_rows * cell.height) / MAX(1u, img->height); x_scale = y_scale; x_offset = (img_columns * cell.width - img->width * x_scale) / 2; } // Now we can compute source (image) coordinates from destination (box) // coordinates by formula: // x_src = (x_dst - x_offset) / x_scale // y_src = (y_dst - y_offset) / y_scale // Destination (box) coordinates of the rectangle we want to display. uint32_t x_dst = img_col * cell.width; uint32_t y_dst = img_row * cell.height; uint32_t w_dst = columns * cell.width; uint32_t h_dst = rows * cell.height; // Compute the source coordinates of the rectangle. ref.src_x = (x_dst - x_offset) / x_scale; ref.src_y = (y_dst - y_offset) / y_scale; ref.src_width = w_dst / x_scale; ref.src_height = h_dst / y_scale; // If the top left corner is out of bounds of the source image, we can // adjust cell offsets and the starting row/column. And if the rectangle is // completely out of bounds, we can avoid creating a real reference. This // is just an optimization, the image will be displayed correctly even if we // do not do this. if (ref.src_x < 0) { ref.src_width += ref.src_x; ref.cell_x_offset = (uint32_t)(-ref.src_x * x_scale); ref.src_x = 0; uint32_t col_offset = ref.cell_x_offset / cell.width; ref.cell_x_offset %= cell.width; ref.start_column += col_offset; if (ref.num_cols <= col_offset) return; ref.num_cols -= col_offset; } if (ref.src_y < 0) { ref.src_height += ref.src_y; ref.cell_y_offset = (uint32_t)(-ref.src_y * y_scale); ref.src_y = 0; uint32_t row_offset = ref.cell_y_offset / cell.height; ref.cell_y_offset %= cell.height; ref.start_row += row_offset; if (ref.num_rows <= row_offset) return; ref.num_rows -= row_offset; } // For the bottom right corner we can remove only completely empty rows and // columns. if (ref.src_x + ref.src_width > img->width) { float redundant_w = ref.src_x + ref.src_width - img->width; uint32_t redundant_cols = (uint32_t)(redundant_w * x_scale) / cell.width; if (ref.num_cols <= redundant_cols) return; ref.src_width -= redundant_cols * cell.width / x_scale; ref.num_cols -= redundant_cols; } if (ref.src_y + ref.src_height > img->height) { float redundant_h = ref.src_y + ref.src_height - img->height; uint32_t redundant_rows = (uint32_t)(redundant_h * y_scale) / cell.height; if (ref.num_rows <= redundant_rows) return; ref.src_height -= redundant_rows * cell.height / y_scale; ref.num_rows -= redundant_rows; } // The cursor will be drawn on top of the image. ref.z_index = -1; // Create a real ref. ImageRef *real_ref = create_ref(img, &ref); img->atime = monotonic(); self->layers_dirty = true; update_src_rect(real_ref, img); update_dest_rect(real_ref, ref.num_cols, ref.num_rows, cell); } static void remove_ref(Image *img, ImageRef *ref); static ref_map_itr remove_ref_itr(Image *img, ref_map_itr x); static bool has_good_ancestry(GraphicsManager *self, ImageRef *ref) { ImageRef *r = ref; unsigned depth = 0; while (r->parent.img) { if (r == ref && depth) { set_command_failed_response("ECYCLE", "This parent reference creates a cycle"); return false; } if (depth++ >= PARENT_DEPTH_LIMIT) { set_command_failed_response("ETOODEEP", "Too many levels of parent references"); return false; } Image *parent = img_by_internal_id(self, r->parent.img); if (!parent) { set_command_failed_response("ENOENT", "One of the ancestors of this ref with image id: %llu not found", r->parent.img); return false; } ImageRef *parent_ref = ref_by_internal_id(parent, r->parent.ref); if (!parent_ref) { set_command_failed_response("ENOENT", "One of the ancestors of this ref with image id: %llu and ref id: %llu not found", r->parent.img, r->parent.ref); return false; } r = parent_ref; } return true; } static uint32_t handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img, CellPixelSize cell) { if (g->unicode_placement && g->parent_id) { set_command_failed_response("EINVAL", "Put command creating a virtual placement cannot refer to a parent"); return g->id; } if (img == NULL) { if (g->id) img = img_by_client_id(self, g->id); else if (g->image_number) img = img_by_client_number(self, g->image_number); if (img == NULL) { set_command_failed_response("ENOENT", "Put command refers to non-existent image with id: %u and number: %u", g->id, g->image_number); return g->id; } } if (!img->root_frame_data_loaded) { set_command_failed_response("ENOENT", "Put command refers to image with id: %u that could not load its data", g->id); return img->client_id; } id_type parent_id = 0, parent_placement_id = 0; if (g->parent_id) { Image *parent = img_by_client_id(self, g->parent_id); if (!parent) { set_command_failed_response("ENOPARENT", "Put command refers to a parent image with id: %u that does not exist", g->parent_id); return g->id; } if (!vt_size(&parent->refs_by_internal_id)) { set_command_failed_response("ENOPARENT", "Put command refers to a parent image with id: %u that has no placements", g->parent_id); return g->id; } ImageRef *parent_ref = vt_first(&parent->refs_by_internal_id).data->val; if (g->parent_placement_id) { parent_ref = ref_by_client_id(parent, g->parent_placement_id); if (!parent_ref) { set_command_failed_response("ENOPARENT", "Put command refers to a parent image placement with id: %u and placement id: %u that does not exist", g->parent_id, g->parent_placement_id); return g->id; } } parent_id = parent->internal_id; parent_placement_id = parent_ref->internal_id; } ImageRef *ref = NULL; if (g->placement_id && img->client_id) { iter_refs(img) { ImageRef *r = i.data->val; if (r->client_id == g->placement_id) { ref = r; if (parent_id && parent_id == img->internal_id && parent_placement_id && parent_placement_id == r->internal_id) { set_command_failed_response("EINVAL", "Put command refers to itself as its own parent"); return g->id; } if (parent_id && parent_placement_id) { id_type rp = ref->parent.img, rpp = ref->parent.ref; ref->parent.img = parent_id; ref->parent.ref = parent_placement_id; bool ok = has_good_ancestry(self, ref); ref->parent.img = rp; ref->parent.ref = rpp; if (!ok) return g->id; } break; } } } if (ref == NULL) ref = create_ref(img, NULL); *is_dirty = true; self->layers_dirty = true; img->atime = monotonic(); ref->src_x = g->x_offset; ref->src_y = g->y_offset; ref->src_width = g->width ? g->width : img->width; ref->src_height = g->height ? g->height : img->height; ref->src_width = MIN(ref->src_width, img->width - ((float)img->width > ref->src_x ? ref->src_x : (float)img->width)); ref->src_height = MIN(ref->src_height, img->height - ((float)img->height > ref->src_y ? ref->src_y : (float)img->height)); ref->z_index = g->z_index; ref->start_row = c->y; ref->start_column = c->x; ref->cell_x_offset = MIN(g->cell_x_offset, cell.width - 1); ref->cell_y_offset = MIN(g->cell_y_offset, cell.height - 1); ref->num_cols = g->num_cells; ref->num_rows = g->num_lines; if (img->client_id) ref->client_id = g->placement_id; update_src_rect(ref, img); update_dest_rect(ref, g->num_cells, g->num_lines, cell); ref->parent.img = parent_id; ref->parent.ref = parent_placement_id; ref->parent.offset.x = g->offset_from_parent_x; ref->parent.offset.y = g->offset_from_parent_y; ref->is_virtual_ref = false; if (g->unicode_placement) { ref->is_virtual_ref = true; ref->start_row = ref->start_column = 0; } if (ref->parent.img) { if (!has_good_ancestry(self, ref)) { remove_ref(img, ref); return g->id; } } else { // Move the cursor, the screen will take care of ensuring it is in bounds if (g->cursor_movement != 1 && !g->unicode_placement) { c->x += ref->effective_num_cols; if (ref->effective_num_rows) c->y += ref->effective_num_rows - 1; } } return img->client_id; } void scale_rendered_graphic(ImageRenderData *rd, float xstart, float ystart, float x_scale, float y_scale) { // Scale the graphic so that it appears at the same position and size during a live resize // this means scale factors are applied to both the position and size of the graphic. float width = rd->dest_rect.right - rd->dest_rect.left, height = rd->dest_rect.bottom - rd->dest_rect.top; rd->dest_rect.left = xstart + (rd->dest_rect.left - xstart) * x_scale; rd->dest_rect.right = rd->dest_rect.left + width * x_scale; rd->dest_rect.top = ystart + (rd->dest_rect.top - ystart) * y_scale; rd->dest_rect.bottom = rd->dest_rect.top + height * y_scale; } void gpu_data_for_image(ImageRenderData *ans, float left, float top, float right, float bottom) { // For dest rect: x-axis is from -1 to 1, y axis is from 1 to -1 static const ImageRef source_rect = { .src_rect = { .left=0, .top=0, .bottom=1, .right=1 }}; ans->src_rect = source_rect.src_rect; ans->dest_rect = (ImageRect){ .left = left, .right = right, .top = top, .bottom = bottom }; ans->group_count = 1; } static bool resolve_cell_ref(const Image *img, id_type virt_ref_id, int32_t *start_row, int32_t *start_column) { *start_row = 0; *start_column = 0; bool found = false; iter_refs((Image*)img) { ImageRef *ref = i.data->val; if (ref->virtual_ref_id == virt_ref_id) { if (!found || ref->start_row < *start_row) *start_row = ref->start_row; if (!found || ref->start_column < *start_column) *start_column = ref->start_column; found = true; } } return found; } static bool resolve_parent_offset(const GraphicsManager *self, const ImageRef *ref, int32_t *start_row, int32_t *start_column, bool *has_virtual_ancestor) { *start_row = 0; *start_column = 0; *has_virtual_ancestor = false; int32_t x = 0, y = 0; unsigned depth = 0; ImageRef cell_ref = {0}; while (ref->parent.img) { if (depth++ >= PARENT_DEPTH_LIMIT) return false; // either a cycle or too many ancestors Image *img = img_by_internal_id(self, ref->parent.img); if (!img) return false; ImageRef *parent = ref_by_internal_id(img, ref->parent.ref); if (!parent) return false; if (parent->is_virtual_ref) { *has_virtual_ancestor = true; if (!resolve_cell_ref(img, parent->internal_id, &cell_ref.start_row, &cell_ref.start_column)) return false; parent = &cell_ref; } x += ref->parent.offset.x; y += ref->parent.offset.y; ref = parent; } *start_row = ref->start_row + y; *start_column = ref->start_column + x; return true; } bool grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows, CellPixelSize cell) { if (self->last_scrolled_by != scrolled_by) self->layers_dirty = true; self->last_scrolled_by = scrolled_by; if (!self->layers_dirty) return false; self->layers_dirty = false; size_t i; self->num_of_below_refs = 0; self->num_of_negative_refs = 0; self->num_of_positive_refs = 0; ImageRect r; float screen_width = dx * num_cols, screen_height = dy * num_rows; float screen_bottom = screen_top - screen_height; float screen_width_px = num_cols * cell.width; float screen_height_px = num_rows * cell.height; float y0 = screen_top - dy * scrolled_by; // Iterate over all visible refs and create render data self->render_data.count = 0; for (image_map_itr imgitr = vt_first(&self->images_by_internal_id); !vt_is_end(imgitr); ) { Image *img = imgitr.data->val; bool was_drawn = img->is_drawn, ref_removed = false; img->is_drawn = false; for (ref_map_itr refitr = vt_first(&img->refs_by_internal_id); !vt_is_end(refitr); ) { ImageRef *ref = refitr.data->val; if (ref->is_virtual_ref) { refitr = vt_next(refitr); continue; } int32_t start_row = ref->start_row, start_column = ref->start_column; if (ref->parent.img) { bool has_virtual_ancestor; if (!resolve_parent_offset(self, ref, &start_row, &start_column, &has_virtual_ancestor)) { if (!has_virtual_ancestor) { refitr = remove_ref_itr(img, refitr); ref_removed = true; } else refitr = vt_next(refitr); continue; } } r.top = y0 - start_row * dy - dy * (float)ref->cell_y_offset / (float)cell.height; r.left = screen_left + start_column * dx + dx * (float)ref->cell_x_offset / (float) cell.width; int32_t nr = ref->num_rows, nc = ref->num_cols; if (nr) { r.bottom = y0 - (start_row + nr) * dy; if (nc) r.right = screen_left + (start_column + nc) * dx; else { double height_px = (((double)r.top - r.bottom) / screen_height) * screen_height_px; double width_px = height_px * ref->src_width / (double) ref->src_height; r.right = r.left + (float)((width_px / screen_width_px) * screen_width); } } else { if (nc) r.right = screen_left + (start_column + nc) * dx; else r.right = r.left + screen_width * (float)ref->src_width / screen_width_px; double width_px = (((double)r.right - r.left) / screen_width) * screen_width_px; double height_px = width_px * ref->src_height / (double)ref->src_width; r.bottom = r.top - (float)((height_px / screen_height_px) * screen_height); } if (r.top <= screen_bottom || r.bottom >= screen_top) { refitr = vt_next(refitr); continue; } // not visible if (ref->z_index < ((int32_t)INT32_MIN/2)) self->num_of_below_refs++; else if (ref->z_index < 0) self->num_of_negative_refs++; else self->num_of_positive_refs++; ensure_space_for(&(self->render_data), item, ImageRenderData, self->render_data.count + 1, capacity, 64, true); ImageRenderData *rd = self->render_data.item + self->render_data.count; zero_at_ptr(rd); rd->dest_rect = r; rd->src_rect = ref->src_rect; self->render_data.count++; rd->z_index = ref->z_index; rd->image_id = img->internal_id; rd->ref_id = ref->internal_id; rd->texture_id = texture_id_for_img(img); img->is_drawn = true; refitr = vt_next(refitr); } if (ref_removed && !vt_size(&img->refs_by_internal_id)) { imgitr = remove_image_itr(self, imgitr); continue; } if (img->is_drawn && !was_drawn && img->animation_state != ANIMATION_STOPPED && img->extra_framecnt && img->animation_duration) { self->has_images_needing_animation = true; global_state.check_for_active_animated_images = true; } imgitr = vt_next(imgitr); } if (!self->render_data.count) return false; // Sort visible refs in draw order (z-index, img, ref) #define lt(a, b) ( (a)->z_index < (b)->z_index || ((a)->z_index == (b)->z_index && ( \ (a)->image_id < (b)->image_id || ((a)->image_id == (b)->image_id && a->ref_id < b->ref_id))) ) QSORT(ImageRenderData, self->render_data.item, self->render_data.count, lt); #undef lt // Calculate the group counts i = 0; while (i < self->render_data.count) { id_type num_identical = 1, image_id = self->render_data.item[i].image_id, start = i; while (++i < self->render_data.count) { if (self->render_data.item[i].image_id != image_id) break; num_identical++; } while (num_identical > 0) { self->render_data.item[start++].group_count = num_identical--; } } return true; } // }}} // Animation {{{ #define DEFAULT_GAP 40 static Frame* current_frame(Image *img) { if (img->current_frame_index > img->extra_framecnt) return NULL; return img->current_frame_index ? img->extra_frames + img->current_frame_index - 1 : &img->root_frame; } static Frame* frame_for_id(Image *img, const uint32_t frame_id) { if (img->root_frame.id == frame_id) return &img->root_frame; for (unsigned i = 0; i < img->extra_framecnt; i++) { if (img->extra_frames[i].id == frame_id) return img->extra_frames + i; } return NULL; } static Frame* frame_for_number(Image *img, const uint32_t frame_number) { switch(frame_number) { case 1: return &img->root_frame; case 0: return NULL; default: if (frame_number - 2 < img->extra_framecnt) return img->extra_frames + frame_number - 2; return NULL; } } static void change_gap(Image *img, Frame *f, int32_t gap) { uint32_t prev_gap = f->gap; f->gap = MAX(0, gap); img->animation_duration = prev_gap < img->animation_duration ? img->animation_duration - prev_gap : 0; img->animation_duration += f->gap; } typedef struct { uint8_t *buf; bool is_4byte_aligned, is_opaque; } CoalescedFrameData; static void blend_on_opaque(uint8_t *under_px, const uint8_t *over_px) { const float alpha = (float)over_px[3] / 255.f; const float alpha_op = 1.f - alpha; for (unsigned i = 0; i < 3; i++) under_px[i] = (uint8_t)(over_px[i] * alpha + under_px[i] * alpha_op); } static void alpha_blend(uint8_t *dest_px, const uint8_t *src_px) { if (src_px[3]) { const float dest_a = (float)dest_px[3] / 255.f, src_a = (float)src_px[3] / 255.f; const float alpha = src_a + dest_a * (1.f - src_a); dest_px[3] = (uint8_t)(255 * alpha); if (!dest_px[3]) { dest_px[0] = 0; dest_px[1] = 0; dest_px[2] = 0; return; } for (unsigned i = 0; i < 3; i++) dest_px[i] = (uint8_t)((src_px[i] * src_a + dest_px[i] * dest_a * (1.f - src_a))/alpha); } } typedef struct { bool needs_blending; uint32_t over_px_sz, under_px_sz; uint32_t over_width, over_height, under_width, under_height, over_offset_x, over_offset_y, under_offset_x, under_offset_y; uint32_t stride; } ComposeData; #define COPY_RGB under_px[0] = over_px[0]; under_px[1] = over_px[1]; under_px[2] = over_px[2]; #define COPY_PIXELS \ if (d.needs_blending) { \ if (d.under_px_sz == 3) { \ ROW_ITER PIX_ITER blend_on_opaque(under_px, over_px); }} \ } else { \ ROW_ITER PIX_ITER alpha_blend(under_px, over_px); }} \ } \ } else { \ if (d.under_px_sz == 4) { \ if (d.over_px_sz == 4) { \ ROW_ITER PIX_ITER COPY_RGB under_px[3] = over_px[3]; }} \ } else { \ ROW_ITER PIX_ITER COPY_RGB under_px[3] = 255; }} \ } \ } else { \ ROW_ITER PIX_ITER COPY_RGB }} \ } \ } \ static void compose_rectangles(const ComposeData d, uint8_t *under_data, const uint8_t *over_data) { // compose two equal sized, non-overlapping rectangles at different offsets // does not do bounds checking on the data arrays const bool can_copy_rows = !d.needs_blending && d.over_px_sz == d.under_px_sz; const unsigned min_width = MIN(d.under_width, d.over_width); #define ROW_ITER for (unsigned y = 0; y < d.under_height && y < d.over_height; y++) { \ uint8_t *under_row = under_data + (y + d.under_offset_y) * d.under_px_sz * d.stride + (d.under_offset_x * d.under_px_sz); \ const uint8_t *over_row = over_data + (y + d.over_offset_y) * d.over_px_sz * d.stride + (d.over_offset_x * d.over_px_sz); if (can_copy_rows) { ROW_ITER memcpy(under_row, over_row, (size_t)d.over_px_sz * min_width);} return; } #define PIX_ITER for (unsigned x = 0; x < min_width; x++) { \ uint8_t *under_px = under_row + (d.under_px_sz * x); \ const uint8_t *over_px = over_row + (d.over_px_sz * x); COPY_PIXELS #undef PIX_ITER #undef ROW_ITER } static void compose(const ComposeData d, uint8_t *under_data, const uint8_t *over_data) { const bool can_copy_rows = !d.needs_blending && d.over_px_sz == d.under_px_sz; unsigned min_row_sz = d.over_offset_x < d.under_width ? d.under_width - d.over_offset_x : 0; min_row_sz = MIN(min_row_sz, d.over_width); #define ROW_ITER for (unsigned y = 0; y + d.over_offset_y < d.under_height && y < d.over_height; y++) { \ uint8_t *under_row = under_data + (y + d.over_offset_y) * d.under_px_sz * d.under_width + d.under_px_sz * d.over_offset_x; \ const uint8_t *over_row = over_data + y * d.over_px_sz * d.over_width; #define END_ITER } if (can_copy_rows) { ROW_ITER memcpy(under_row, over_row, (size_t)d.over_px_sz * min_row_sz); END_ITER return; } #define PIX_ITER for (unsigned x = 0; x < min_row_sz; x++) { \ uint8_t *under_px = under_row + (d.under_px_sz * x); \ const uint8_t *over_px = over_row + (d.over_px_sz * x); COPY_PIXELS #undef COPY_RGB #undef PIX_ITER #undef ROW_ITER #undef END_ITER } static CoalescedFrameData get_coalesced_frame_data_standalone(const Image *img, const Frame *f, uint8_t *frame_data) { CoalescedFrameData ans = {0}; bool is_full_frame = f->width == img->width && f->height == img->height && !f->x && !f->y; if (is_full_frame) { ans.buf = frame_data; ans.is_4byte_aligned = f->is_4byte_aligned; ans.is_opaque = f->is_opaque; return ans; } const unsigned bytes_per_pixel = f->is_opaque ? 3 : 4; uint8_t *base; if (f->bgcolor) { base = malloc((size_t)img->width * img->height * bytes_per_pixel); if (base) { uint8_t *p = base; const uint8_t r = (f->bgcolor >> 24) & 0xff, g = (f->bgcolor >> 16) & 0xff, b = (f->bgcolor >> 8) & 0xff, a = f->bgcolor & 0xff; if (bytes_per_pixel == 4) { for (uint32_t i = 0; i < img->width * img->height; i++) { *(p++) = r; *(p++) = g; *(p++) = b; *(p++) = a; } } else { for (uint32_t i = 0; i < img->width * img->height; i++) { *(p++) = r; *(p++) = g; *(p++) = b; } } } } else base = calloc((size_t)img->width * img->height, bytes_per_pixel); if (!base) { free(frame_data); return ans; } ComposeData d = { .over_px_sz = bytes_per_pixel, .under_px_sz = bytes_per_pixel, .over_width = f->width, .over_height = f->height, .over_offset_x = f->x, .over_offset_y = f->y, .under_width = img->width, .under_height = img->height, .needs_blending = f->alpha_blend && !f->is_opaque }; compose(d, base, frame_data); ans.buf = base; ans.is_4byte_aligned = bytes_per_pixel == 4 || (img->width % 4) == 0; ans.is_opaque = f->is_opaque; free(frame_data); return ans; } static CoalescedFrameData get_coalesced_frame_data_impl(GraphicsManager *self, Image *img, const Frame *f, unsigned count) { CoalescedFrameData ans = {0}; if (count > 32) return ans; // prevent stack overflows, infinite recursion size_t frame_data_sz; void *frame_data; ImageAndFrame key = {.image_id = img->internal_id, .frame_id = f->id}; if (!read_from_cache(self, key, &frame_data, &frame_data_sz)) return ans; if (!f->base_frame_id) return get_coalesced_frame_data_standalone(img, f, frame_data); Frame *base = frame_for_id(img, f->base_frame_id); if (!base) { free(frame_data); return ans; } CoalescedFrameData base_data = get_coalesced_frame_data_impl(self, img, base, count + 1); if (!base_data.buf) { free(frame_data); return ans; } ComposeData d = { .over_px_sz = f->is_opaque ? 3 : 4, .under_px_sz = base_data.is_opaque ? 3 : 4, .over_width = f->width, .over_height = f->height, .over_offset_x = f->x, .over_offset_y = f->y, .under_width = img->width, .under_height = img->height, .needs_blending = f->alpha_blend && !f->is_opaque }; compose(d, base_data.buf, frame_data); free(frame_data); return base_data; } static CoalescedFrameData get_coalesced_frame_data(GraphicsManager *self, Image *img, const Frame *f) { return get_coalesced_frame_data_impl(self, img, f, 0); } static void update_current_frame(GraphicsManager *self, Image *img, const CoalescedFrameData *data) { bool needs_load = data == NULL; CoalescedFrameData cfd; if (needs_load) { Frame *f = current_frame(img); if (f == NULL) return; cfd = get_coalesced_frame_data(self, img, f); if (!cfd.buf) { if (PyErr_Occurred()) PyErr_Print(); return; } data = &cfd; } upload_to_gpu(self, img, data->is_opaque, data->is_4byte_aligned, data->buf); if (needs_load) free(data->buf); img->current_frame_shown_at = monotonic(); } static bool reference_chain_too_large(Image *img, const Frame *frame) { uint32_t limit = img->width * img->height * 2; uint32_t drawn_area = frame->width * frame->height; unsigned num = 1; while (drawn_area < limit && num < 5) { if (!frame->base_frame_id || !(frame = frame_for_id(img, frame->base_frame_id))) break; drawn_area += frame->width * frame->height; num++; } return num >= 5 || drawn_area >= limit; } static Image* handle_animation_frame_load_command(GraphicsManager *self, GraphicsCommand *g, Image *img, const uint8_t *payload, bool *is_dirty) { uint32_t frame_number = g->frame_number, fmt = g->format ? g->format : RGBA; if (!frame_number || frame_number > img->extra_framecnt + 2) frame_number = img->extra_framecnt + 2; bool is_new_frame = frame_number == img->extra_framecnt + 2; g->frame_number = frame_number; unsigned char tt = g->transmission_type ? g->transmission_type : 'd'; if (tt == 'd' && self->currently_loading.loading_for.image_id == img->internal_id) { INIT_CHUNKED_LOAD; } else { self->currently_loading.loading_for = (const ImageAndFrame){0}; if (g->data_width > MAX_IMAGE_DIMENSION || g->data_height > MAX_IMAGE_DIMENSION) ABRT("EINVAL", "Image too large"); if (!initialize_load_data(self, g, img, tt, fmt, frame_number - 1)) return NULL; } LoadData *load_data = &self->currently_loading; img = load_image_data(self, img, g, tt, fmt, payload); if (!img || !load_data->loading_completed_successfully) return NULL; self->currently_loading.loading_for = (const ImageAndFrame){0}; img = process_image_data(self, img, g, tt, fmt); if (!img || !load_data->loading_completed_successfully) return img; const unsigned long bytes_per_pixel = load_data->is_opaque ? 3 : 4; if (load_data->data_sz < bytes_per_pixel * load_data->width * load_data->height) ABRT("ENODATA", "Insufficient image data %zu < %zu", load_data->data_sz, bytes_per_pixel * g->data_width, g->data_height); if (load_data->width > img->width) ABRT("EINVAL", "Frame width %u larger than image width: %u", load_data->width, img->width); if (load_data->height > img->height) ABRT("EINVAL", "Frame height %u larger than image height: %u", load_data->height, img->height); if (is_new_frame && cache_size(self) + load_data->data_sz > self->storage_limit * 5) { remove_images(self, trim_predicate, img->internal_id); if (cache_size(self) + load_data->data_sz > self->storage_limit * 5) ABRT("ENOSPC", "Cache size exceeded cannot add new frames"); } Frame transmitted_frame = { .width = load_data->width, .height = load_data->height, .x = g->x_offset, .y = g->y_offset, .is_4byte_aligned = load_data->is_4byte_aligned, .is_opaque = load_data->is_opaque, .alpha_blend = g->blend_mode != 1 && !load_data->is_opaque, .gap = g->gap > 0 ? g->gap : (g->gap < 0) ? 0 : DEFAULT_GAP, .bgcolor = g->bgcolor, }; Frame *frame; if (is_new_frame) { transmitted_frame.id = ++img->frame_id_counter; Frame *frames = realloc(img->extra_frames, sizeof(img->extra_frames[0]) * (img->extra_framecnt + 1)); if (!frames) ABRT("ENOMEM", "Out of memory"); img->extra_frames = frames; img->extra_framecnt++; frame = img->extra_frames + frame_number - 2; const ImageAndFrame key = { .image_id = img->internal_id, .frame_id = transmitted_frame.id }; if (g->other_frame_number) { Frame *other_frame = frame_for_number(img, g->other_frame_number); if (!other_frame) { img->extra_framecnt--; ABRT("EINVAL", "No frame with number: %u found", g->other_frame_number); } if (other_frame->base_frame_id && reference_chain_too_large(img, other_frame)) { // since there is a long reference chain to render this frame, make // it a fully coalesced key frame, for performance CoalescedFrameData cfd = get_coalesced_frame_data(self, img, other_frame); if (!cfd.buf) ABRT("EINVAL", "Failed to get data from frame referenced by frame: %u", frame_number); ComposeData d = { .over_px_sz = transmitted_frame.is_opaque ? 3 : 4, .under_px_sz = cfd.is_opaque ? 3: 4, .over_width = transmitted_frame.width, .over_height = transmitted_frame.height, .over_offset_x = transmitted_frame.x, .over_offset_y = transmitted_frame.y, .under_width = img->width, .under_height = img->height, .needs_blending = transmitted_frame.alpha_blend && !transmitted_frame.is_opaque }; compose(d, cfd.buf, load_data->data); free_load_data(load_data); load_data->data = cfd.buf; load_data->data_sz = (size_t)img->width * img->height * d.under_px_sz; transmitted_frame.width = img->width; transmitted_frame.height = img->height; transmitted_frame.x = 0; transmitted_frame.y = 0; transmitted_frame.is_4byte_aligned = cfd.is_4byte_aligned; transmitted_frame.is_opaque = cfd.is_opaque; } else { transmitted_frame.base_frame_id = other_frame->id; } } *frame = transmitted_frame; if (!add_to_cache(self, key, load_data->data, load_data->data_sz)) { img->extra_framecnt--; if (PyErr_Occurred()) PyErr_Print(); ABRT("ENOSPC", "Failed to cache data for image frame"); } img->animation_duration += frame->gap; if (img->animation_state == ANIMATION_LOADING) { self->has_images_needing_animation = true; global_state.check_for_active_animated_images = true; } } else { frame = frame_for_number(img, frame_number); if (!frame) ABRT("EINVAL", "No frame with number: %u found", frame_number); if (g->gap != 0) change_gap(img, frame, transmitted_frame.gap); CoalescedFrameData cfd = get_coalesced_frame_data(self, img, frame); if (!cfd.buf) ABRT("EINVAL", "No data associated with frame number: %u", frame_number); frame->alpha_blend = false; frame->base_frame_id = 0; frame->bgcolor = 0; frame->is_opaque = cfd.is_opaque; frame->is_4byte_aligned = cfd.is_4byte_aligned; frame->x = 0; frame->y = 0; frame->width = img->width; frame->height = img->height; const unsigned bytes_per_pixel = frame->is_opaque ? 3: 4; ComposeData d = { .over_px_sz = transmitted_frame.is_opaque ? 3 : 4, .under_px_sz = bytes_per_pixel, .over_width = transmitted_frame.width, .over_height = transmitted_frame.height, .over_offset_x = transmitted_frame.x, .over_offset_y = transmitted_frame.y, .under_width = frame->width, .under_height = frame->height, .needs_blending = transmitted_frame.alpha_blend && !transmitted_frame.is_opaque }; compose(d, cfd.buf, load_data->data); const ImageAndFrame key = { .image_id = img->internal_id, .frame_id = frame->id }; bool added = add_to_cache(self, key, cfd.buf, (size_t)bytes_per_pixel * frame->width * frame->height); if (added && frame == current_frame(img)) { update_current_frame(self, img, &cfd); *is_dirty = true; } free(cfd.buf); if (!added) { if (PyErr_Occurred()) PyErr_Print(); ABRT("ENOSPC", "Failed to cache data for image frame"); } } return img; } #undef ABRT static Image* handle_delete_frame_command(GraphicsManager *self, const GraphicsCommand *g, bool *is_dirty) { if (!g->id && !g->image_number) { REPORT_ERROR("Delete frame data command without image id or number"); return NULL; } Image *img = g->id ? img_by_client_id(self, g->id) : img_by_client_number(self, g->image_number); if (!img) { REPORT_ERROR("Animation command refers to non-existent image with id: %u and number: %u", g->id, g->image_number); return NULL; } uint32_t frame_number = MIN(img->extra_framecnt + 1, g->frame_number); if (!frame_number) frame_number = 1; if (!img->extra_framecnt) return g->delete_action == 'F' ? img : NULL; *is_dirty = true; ImageAndFrame key = {.image_id=img->internal_id}; bool remove_root = frame_number == 1; uint32_t removed_gap = 0; if (remove_root) { key.frame_id = img->root_frame.id; remove_from_cache(self, key); if (PyErr_Occurred()) PyErr_Print(); removed_gap = img->root_frame.gap; img->root_frame = img->extra_frames[0]; } unsigned removed_idx = remove_root ? 0 : frame_number - 2; if (!remove_root) { key.frame_id = img->extra_frames[removed_idx].id; removed_gap = img->extra_frames[removed_idx].gap; remove_from_cache(self, key); } img->animation_duration = removed_gap < img->animation_duration ? img->animation_duration - removed_gap : 0; if (PyErr_Occurred()) PyErr_Print(); if (removed_idx < img->extra_framecnt - 1) memmove(img->extra_frames + removed_idx, img->extra_frames + removed_idx + 1, sizeof(img->extra_frames[0]) * (img->extra_framecnt - 1 - removed_idx)); img->extra_framecnt--; if (img->current_frame_index > img->extra_framecnt) { img->current_frame_index = img->extra_framecnt; update_current_frame(self, img, NULL); return NULL; } if (removed_idx == img->current_frame_index) update_current_frame(self, img, NULL); else if (removed_idx < img->current_frame_index) img->current_frame_index--; return NULL; } static void handle_animation_control_command(GraphicsManager *self, bool *is_dirty, const GraphicsCommand *g, Image *img) { if (g->frame_number) { uint32_t frame_idx = g->frame_number - 1; if (frame_idx <= img->extra_framecnt) { Frame *f = frame_idx ? img->extra_frames + frame_idx - 1 : &img->root_frame; if (g->gap) change_gap(img, f, g->gap); } } if (g->other_frame_number) { uint32_t frame_idx = g->other_frame_number - 1; if (frame_idx != img->current_frame_index && frame_idx <= img->extra_framecnt) { img->current_frame_index = frame_idx; *is_dirty = true; update_current_frame(self, img, NULL); } } if (g->animation_state) { AnimationState old_state = img->animation_state; switch(g->animation_state) { case 1: img->animation_state = ANIMATION_STOPPED; break; case 2: img->animation_state = ANIMATION_LOADING; break; case 3: img->animation_state = ANIMATION_RUNNING; break; default: break; } if (img->animation_state == ANIMATION_STOPPED) { img->current_loop = 0; } else { if (old_state == ANIMATION_STOPPED) { img->current_frame_shown_at = monotonic(); img->is_drawn = true; } self->has_images_needing_animation = true; global_state.check_for_active_animated_images = true; } img->current_loop = 0; } if (g->loop_count) { img->max_loops = g->loop_count - 1; global_state.check_for_active_animated_images = true; } } static bool image_is_animatable(const Image *img) { return img->animation_state != ANIMATION_STOPPED && img->extra_framecnt && img->is_drawn && img->animation_duration && ( !img->max_loops || img->current_loop < img->max_loops); } bool scan_active_animations(GraphicsManager *self, const monotonic_t now, monotonic_t *minimum_gap, bool os_window_context_set) { bool dirtied = false; *minimum_gap = MONOTONIC_T_MAX; if (!self->has_images_needing_animation) return dirtied; self->has_images_needing_animation = false; self->context_made_current_for_this_command = os_window_context_set; iter_images(self) { Image *img = i.data->val; if (image_is_animatable(img)) { Frame *f = current_frame(img); if (f) { self->has_images_needing_animation = true; monotonic_t next_frame_at = img->current_frame_shown_at + ms_to_monotonic_t(f->gap); if (now >= next_frame_at) { do { uint32_t next = (img->current_frame_index + 1) % (img->extra_framecnt + 1); if (!next) { if (img->animation_state == ANIMATION_LOADING) goto skip_image; if (++img->current_loop >= img->max_loops && img->max_loops) goto skip_image; } img->current_frame_index = next; } while (!current_frame(img)->gap); dirtied = true; update_current_frame(self, img, NULL); f = current_frame(img); next_frame_at = img->current_frame_shown_at + ms_to_monotonic_t(f->gap); } if (next_frame_at > now && next_frame_at - now < *minimum_gap) *minimum_gap = next_frame_at - now; } } skip_image:; } return dirtied; } // }}} // {{{ composition a=c static void cfd_free(CoalescedFrameData *p) { free((p)->buf); p->buf = NULL; } static void handle_compose_command(GraphicsManager *self, bool *is_dirty, const GraphicsCommand *g, Image *img) { Frame *src_frame = frame_for_number(img, g->frame_number); if (!src_frame) { set_command_failed_response("ENOENT", "No source frame number %u exists in image id: %u\n", g->frame_number, img->client_id); return; } Frame *dest_frame = frame_for_number(img, g->other_frame_number); if (!dest_frame) { set_command_failed_response("ENOENT", "No destination frame number %u exists in image id: %u\n", g->other_frame_number, img->client_id); return; } const unsigned int width = g->width ? g->width : img->width; const unsigned int height = g->height ? g->height : img->height; const unsigned int dest_x = g->x_offset, dest_y = g->y_offset, src_x = g->cell_x_offset, src_y = g->cell_y_offset; if (dest_x + width > img->width || dest_y + height > img->height) { set_command_failed_response("EINVAL", "The destination rectangle is out of bounds"); return; } if (src_x + width > img->width || src_y + height > img->height) { set_command_failed_response("EINVAL", "The source rectangle is out of bounds"); return; } if (src_frame == dest_frame) { bool x_overlaps = MAX(src_x, dest_x) < (MIN(src_x, dest_x) + width); bool y_overlaps = MAX(src_y, dest_y) < (MIN(src_y, dest_y) + height); if (x_overlaps && y_overlaps) { set_command_failed_response("EINVAL", "The source and destination rectangles overlap and the src and destination frames are the same"); return; } } RAII_CoalescedFrameData(src_data, get_coalesced_frame_data(self, img, src_frame)); if (!src_data.buf) { set_command_failed_response("EINVAL", "Failed to get data for src frame: %u", g->frame_number - 1); return; } RAII_CoalescedFrameData(dest_data, get_coalesced_frame_data(self, img, dest_frame)); if (!dest_data.buf) { set_command_failed_response("EINVAL", "Failed to get data for destination frame: %u", g->other_frame_number - 1); return; } ComposeData d = { .over_px_sz = src_data.is_opaque ? 3 : 4, .under_px_sz = dest_data.is_opaque ? 3: 4, .needs_blending = !g->compose_mode && !src_data.is_opaque, .over_offset_x = src_x, .over_offset_y = src_y, .under_offset_x = dest_x, .under_offset_y = dest_y, .over_width = width, .over_height = height, .under_width = width, .under_height = height, .stride = img->width }; compose_rectangles(d, dest_data.buf, src_data.buf); const ImageAndFrame key = { .image_id = img->internal_id, .frame_id = dest_frame->id }; if (!add_to_cache(self, key, dest_data.buf, ((size_t)(dest_data.is_opaque ? 3 : 4)) * img->width * img->height)) { if (PyErr_Occurred()) PyErr_Print(); set_command_failed_response("ENOSPC", "Failed to store image data in disk cache"); } // frame is now a fully coalesced frame dest_frame->x = 0; dest_frame->y = 0; dest_frame->width = img->width; dest_frame->height = img->height; dest_frame->base_frame_id = 0; dest_frame->bgcolor = 0; *is_dirty = (g->other_frame_number - 1) == img->current_frame_index; if (*is_dirty) update_current_frame(self, img, &dest_data); } // }}} // Image lifetime/scrolling {{{ static ref_map_itr remove_ref_itr(Image *img, ref_map_itr x) { free(x.data->val); return vt_erase_itr(&img->refs_by_internal_id, x); } static void remove_ref(Image *img, ImageRef *ref) { ref_map_itr i = vt_get(&img->refs_by_internal_id, ref->internal_id); if (vt_is_end(i)) return; remove_ref_itr(img, i); } static void filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(const ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell, bool only_first_image, bool free_only_matched) { for (image_map_itr ii = vt_first(&self->images_by_internal_id); !vt_is_end(ii); ) { Image *img = ii.data->val; bool matched = false; for (ref_map_itr ri = vt_first(&img->refs_by_internal_id); !vt_is_end(ri); ) { ImageRef *ref = ri.data->val; if (filter_func(ref, img, data, cell)) { ri = remove_ref_itr(img, ri); self->layers_dirty = true; matched = true; } else ri = vt_next(ri); } if ((!free_only_matched || matched) && !vt_size(&img->refs_by_internal_id) && (free_images || img->client_id == 0)) ii = remove_image_itr(self, ii); else ii = vt_next(ii); if (only_first_image && matched) break; } } static void modify_refs(GraphicsManager *self, const void* data, bool (*filter_func)(ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell) { for (image_map_itr ii = vt_first(&self->images_by_internal_id); !vt_is_end(ii); ) { Image *img = ii.data->val; for (ref_map_itr ri = vt_first(&img->refs_by_internal_id); !vt_is_end(ri); ) { ImageRef *ref = ri.data->val; if (filter_func(ref, img, data, cell)) ri = remove_ref_itr(img, ri); else ri = vt_next(ri); } if (!vt_size(&img->refs_by_internal_id) && img->client_id == 0 && img->client_number == 0) { // references have all scrolled off the history buffer and the image has no way to reference it // to create new references so remove it. ii = remove_image_itr(self, ii); } else ii = vt_next(ii); } } static bool scroll_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) { if (ref->is_virtual_ref) return false; ScrollData *d = (ScrollData*)data; ref->start_row += d->amt; return ref->start_row + (int32_t)ref->effective_num_rows <= d->limit; } static bool ref_within_region(const ImageRef *ref, index_type margin_top, index_type margin_bottom) { return ref->start_row >= (int32_t)margin_top && ref->start_row + (int32_t)ref->effective_num_rows - 1 <= (int32_t)margin_bottom; } static bool ref_outside_region(const ImageRef *ref, index_type margin_top, index_type margin_bottom) { return ref->start_row + (int32_t)ref->effective_num_rows <= (int32_t)margin_top || ref->start_row > (int32_t)margin_bottom; } static bool scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data, CellPixelSize cell) { if (ref->is_virtual_ref) return false; ScrollData *d = (ScrollData*)data; if (ref_within_region(ref, d->margin_top, d->margin_bottom)) { ref->start_row += d->amt; if (ref_outside_region(ref, d->margin_top, d->margin_bottom)) return true; // Clip the image if scrolling has resulted in part of it being outside the page area uint32_t clip_amt, clipped_rows; if (ref->start_row < (int32_t)d->margin_top) { // image moved up clipped_rows = d->margin_top - ref->start_row; clip_amt = cell.height * clipped_rows; if (ref->src_height <= clip_amt) return true; ref->src_y += clip_amt; ref->src_height -= clip_amt; ref->effective_num_rows -= clipped_rows; update_src_rect(ref, img); ref->start_row += clipped_rows; } else if (ref->start_row + (int32_t)ref->effective_num_rows - 1 > (int32_t)d->margin_bottom) { // image moved down clipped_rows = ref->start_row + ref->effective_num_rows - 1 - d->margin_bottom; clip_amt = cell.height * clipped_rows; if (ref->src_height <= clip_amt) return true; ref->src_height -= clip_amt; ref->effective_num_rows -= clipped_rows; update_src_rect(ref, img); } return ref_outside_region(ref, d->margin_top, d->margin_bottom); } return false; } void grman_scroll_images(GraphicsManager *self, const ScrollData *data, CellPixelSize cell) { if (vt_size(&self->images_by_internal_id)) { self->layers_dirty = true; modify_refs(self, data, data->has_margins ? scroll_filter_margins_func : scroll_filter_func, cell); } } static bool cell_image_row_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) { if (ref->is_virtual_ref || !is_cell_image(ref)) return false; int32_t top = *(int32_t *)data; int32_t bottom = *((int32_t *)data + 1); return ref_within_region(ref, top, bottom); } static bool cell_image_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data UNUSED, CellPixelSize cell UNUSED) { return !ref->is_virtual_ref && is_cell_image(ref); } // Remove cell images within the given region. void grman_remove_cell_images(GraphicsManager *self, int32_t top, int32_t bottom) { CellPixelSize dummy = {0}; int32_t data[] = {top, bottom}; filter_refs(self, data, false, cell_image_row_filter_func, dummy, false, true); } void grman_remove_all_cell_images(GraphicsManager *self) { CellPixelSize dummy = {0}; filter_refs(self, NULL, false, cell_image_filter_func, dummy, false, true); } static bool clear_filter_func(const ImageRef *ref, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) { if (ref->is_virtual_ref) return false; return ref->start_row + (int32_t)ref->effective_num_rows > 0; } static bool clear_filter_func_noncell(const ImageRef *ref, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) { if (ref->is_virtual_ref || is_cell_image(ref)) return false; return ref->start_row + (int32_t)ref->effective_num_rows > 0; } static bool clear_all_filter_func(const ImageRef *ref UNUSED, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) { if (ref->is_virtual_ref) return false; return true; } void grman_clear(GraphicsManager *self, bool all, CellPixelSize cell) { filter_refs(self, NULL, true, all ? clear_all_filter_func : clear_filter_func, cell, false, false); } static bool id_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell UNUSED) { const GraphicsCommand *g = data; if (g->id && img->client_id == g->id) return !g->placement_id || ref->client_id == g->placement_id; return false; } static bool id_range_filter_func(const ImageRef *ref UNUSED, Image *img, const void *data, CellPixelSize cell UNUSED) { const GraphicsCommand *g = data; return img->client_id && g->x_offset <= img->client_id && img->client_id <= g->y_offset; } static bool x_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) { if (ref->is_virtual_ref || is_cell_image(ref)) return false; const GraphicsCommand *g = data; return ref->start_column <= (int32_t)g->x_offset - 1 && ((int32_t)g->x_offset - 1) < ((int32_t)(ref->start_column + ref->effective_num_cols)); } static bool y_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) { if (ref->is_virtual_ref || is_cell_image(ref)) return false; const GraphicsCommand *g = data; return ref->start_row <= (int32_t)g->y_offset - 1 && ((int32_t)g->y_offset - 1) < ((int32_t)(ref->start_row + ref->effective_num_rows)); } static bool z_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) { if (ref->is_virtual_ref || is_cell_image(ref)) return false; const GraphicsCommand *g = data; return ref->z_index == g->z_index; } static bool point_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) { if (ref->is_virtual_ref || is_cell_image(ref)) return false; return x_filter_func(ref, img, data, cell) && y_filter_func(ref, img, data, cell); } static bool point3d_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) { if (ref->is_virtual_ref || is_cell_image(ref)) return false; return z_filter_func(ref, img, data, cell) && point_filter_func(ref, img, data, cell); } static void handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, CellPixelSize cell) { if (self->currently_loading.loading_for.image_id) free_load_data(&self->currently_loading); GraphicsCommand d; if (!g->placement_id) { // special case freeing of images with no refs by id or number as // filter_refs doesnt handle this Image *img = NULL; switch(g->delete_action) { case 'I': img = img_by_client_id(self, g->id); break; case 'N': img = img_by_client_number(self, g->image_number); break; case 'R': { for (image_map_itr ii = vt_first(&self->images_by_internal_id); !vt_is_end(ii); ) { img = ii.data->val; if (id_range_filter_func(NULL, img, g, cell) && !vt_size(&img->refs_by_internal_id)) ii = remove_image_itr(self, ii); else ii = vt_next(ii); } } img = NULL; break; } if (img && !vt_size(&img->refs_by_internal_id)) { remove_image(self, img); goto end; } } switch (g->delete_action) { #define I(u, data, func) filter_refs(self, data, g->delete_action == u, func, cell, false, true); *is_dirty = true; break #define D(l, u, data, func) case l: case u: I(u, data, func) #define G(l, u, func) D(l, u, g, func) case 0: D('a', 'A', NULL, clear_filter_func_noncell); G('i', 'I', id_filter_func); G('r', 'R', id_range_filter_func); G('p', 'P', point_filter_func); G('q', 'Q', point3d_filter_func); G('x', 'X', x_filter_func); G('y', 'Y', y_filter_func); G('z', 'Z', z_filter_func); case 'c': case 'C': d.x_offset = c->x + 1; d.y_offset = c->y + 1; I('C', &d, point_filter_func); case 'n': case 'N': { Image *img = img_by_client_number(self, g->image_number); if (img) { for (ref_map_itr ri = vt_first(&img->refs_by_internal_id); !vt_is_end(ri); ) { ImageRef *ref = ri.data->val; if (!g->placement_id || g->placement_id == ref->client_id) { ri = remove_ref_itr(img, ri); self->layers_dirty = true; } else ri = vt_next(ri); } if (!vt_size(&img->refs_by_internal_id) && (g->delete_action == 'N' || img->client_id == 0)) remove_image(self, img); } } break; case 'f': case 'F': { Image *img = handle_delete_frame_command(self, g, is_dirty); if (img != NULL) { remove_image(self, img); *is_dirty = true; } break; } default: REPORT_ERROR("Unknown graphics command delete action: %c", g->delete_action); break; #undef G #undef D #undef I } end: if (!vt_size(&self->images_by_internal_id) && self->render_data.count) self->render_data.count = 0; } // }}} void grman_resize(GraphicsManager *self, index_type old_lines UNUSED, index_type lines UNUSED, index_type old_columns, index_type columns, index_type num_content_lines_before, index_type num_content_lines_after) { ImageRef *ref; Image *img; self->layers_dirty = true; if (columns == old_columns && num_content_lines_before > num_content_lines_after) { const unsigned int vertical_shrink_size = num_content_lines_before - num_content_lines_after; iter_images(self) { img = i.data->val; iter_refs(img) { ref = i.data->val; if (ref->is_virtual_ref || is_cell_image(ref)) continue; ref->start_row -= vertical_shrink_size; } } } } void grman_rescale(GraphicsManager *self, CellPixelSize cell) { ImageRef *ref; Image *img; self->layers_dirty = true; iter_images(self) { img = i.data->val; iter_refs(img) { ref = i.data->val; if (ref->is_virtual_ref || is_cell_image(ref)) continue; ref->cell_x_offset = MIN(ref->cell_x_offset, cell.width - 1); ref->cell_y_offset = MIN(ref->cell_y_offset, cell.height - 1); update_dest_rect(ref, ref->num_cols, ref->num_rows, cell); } } } const char* grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty, CellPixelSize cell) { const char *ret = NULL; command_response[0] = 0; self->context_made_current_for_this_command = false; if (g->id && g->image_number) { set_command_failed_response("EINVAL", "Must not specify both image id and image number"); return finish_command_response(g, false); } switch(g->action) { case 0: case 't': case 'T': case 'q': { uint32_t iid = g->id, q_iid = iid; bool is_query = g->action == 'q'; if (is_query) { iid = 0; if (!q_iid) { REPORT_ERROR("Query graphics command without image id"); break; } } Image *image = handle_add_command(self, g, payload, is_dirty, iid, is_query); if (!self->currently_loading.loading_for.image_id) free_load_data(&self->currently_loading); GraphicsCommand *lg = &self->currently_loading.start_command; if (g->quiet) lg->quiet = g->quiet; if (is_query) ret = finish_command_response(&(const GraphicsCommand){.id=q_iid, .quiet=g->quiet}, image != NULL); else ret = finish_command_response(lg, image != NULL); if (lg->action == 'T' && image && image->root_frame_data_loaded) handle_put_command(self, lg, c, is_dirty, image, cell); id_type added_image_id = image ? image->internal_id : 0; if (g->action == 'q') remove_images(self, add_trim_predicate, 0); if (self->used_storage > self->storage_limit) apply_storage_quota(self, self->storage_limit, added_image_id); break; } case 'a': case 'f': { if (!g->id && !g->image_number && !self->currently_loading.loading_for.image_id) { REPORT_ERROR("Add frame data command without image id or number"); break; } Image *img; if (self->currently_loading.loading_for.image_id) img = img_by_internal_id(self, self->currently_loading.loading_for.image_id); else img = g->id ? img_by_client_id(self, g->id) : img_by_client_number(self, g->image_number); if (!img) { set_command_failed_response("ENOENT", "Animation command refers to non-existent image with id: %u and number: %u", g->id, g->image_number); ret = finish_command_response(g, false); } else { GraphicsCommand ag = *g; if (ag.action == 'f') { img = handle_animation_frame_load_command(self, &ag, img, payload, is_dirty); if (!self->currently_loading.loading_for.image_id) free_load_data(&self->currently_loading); if (g->quiet) ag.quiet = g->quiet; else ag.quiet = self->currently_loading.start_command.quiet; ret = finish_command_response(&ag, img != NULL); } else if (ag.action == 'a') { handle_animation_control_command(self, is_dirty, &ag, img); } } break; } case 'p': { if (!g->id && !g->image_number) { REPORT_ERROR("Put graphics command without image id or number"); break; } uint32_t image_id = handle_put_command(self, g, c, is_dirty, NULL, cell); GraphicsCommand rg = *g; rg.id = image_id; ret = finish_command_response(&rg, true); break; } case 'd': handle_delete_command(self, g, c, is_dirty, cell); break; case 'c': if (!g->id && !g->image_number) { REPORT_ERROR("Compose frame data command without image id or number"); break; } Image *img = g->id ? img_by_client_id(self, g->id) : img_by_client_number(self, g->image_number); if (!img) { set_command_failed_response("ENOENT", "Animation command refers to non-existent image with id: %u and number: %u", g->id, g->image_number); ret = finish_command_response(g, false); } else { handle_compose_command(self, is_dirty, g, img); ret = finish_command_response(g, true); } break; default: REPORT_ERROR("Unknown graphics command action: %c", g->action); break; } return ret; } // Boilerplate {{{ static PyObject * new_graphicsmanager_object(PyTypeObject UNUSED *type, PyObject UNUSED *args, PyObject UNUSED *kwds) { PyObject *ans = (PyObject*)grman_alloc(false); if (ans == NULL) PyErr_NoMemory(); return ans; } static PyObject* image_as_dict(GraphicsManager *self, Image *img) { #define U(x) #x, (unsigned int)(img->x) #define B(x) #x, img->x ? Py_True : Py_False PyObject *frames = PyTuple_New(img->extra_framecnt); for (unsigned i = 0; i < img->extra_framecnt; i++) { Frame *f = img->extra_frames + i; CoalescedFrameData cfd = get_coalesced_frame_data(self, img, f); if (!cfd.buf) { PyErr_SetString(PyExc_RuntimeError, "Failed to get data for frame"); return NULL; } PyTuple_SET_ITEM(frames, i, Py_BuildValue( "{sI sI sy#}", "gap", f->gap, "id", f->id, "data", cfd.buf, (Py_ssize_t)((cfd.is_opaque ? 3 : 4) * img->width * img->height) )); free(cfd.buf); if (PyErr_Occurred()) { Py_CLEAR(frames); return NULL; } } CoalescedFrameData cfd = get_coalesced_frame_data(self, img, &img->root_frame); if (!cfd.buf) { PyErr_SetString(PyExc_RuntimeError, "Failed to get data for root frame"); return NULL; } PyObject *ans = Py_BuildValue("{sI sI sI sI sI sI sI " "sO sI sO " "sI sI sI " "sI sy# sN}", "texture_id", texture_id_for_img(img), U(client_id), U(width), U(height), U(internal_id), "refs.count", (unsigned int)vt_size(&img->refs_by_internal_id), U(client_number), B(root_frame_data_loaded), U(animation_state), "is_4byte_aligned", img->root_frame.is_4byte_aligned ? Py_True : Py_False, U(current_frame_index), "root_frame_gap", img->root_frame.gap, U(current_frame_index), U(animation_duration), "data", cfd.buf, (Py_ssize_t)((cfd.is_opaque ? 3 : 4) * img->width * img->height), "extra_frames", frames ); free(cfd.buf); return ans; #undef B #undef U } #define W(x) static PyObject* py##x(GraphicsManager UNUSED *self, PyObject *args) #define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL; W(image_for_client_id) { unsigned long id = PyLong_AsUnsignedLong(args); bool existing = false; Image *img = find_or_create_image(self, id, &existing); if (!existing) { Py_RETURN_NONE; } return image_as_dict(self, img); } W(image_for_client_number) { unsigned long num = PyLong_AsUnsignedLong(args); Image *img = img_by_client_number(self, num); if (!img) Py_RETURN_NONE; return image_as_dict(self, img); } W(shm_write) { const char *name, *data; Py_ssize_t sz; PA("ss#", &name, &data, &sz); int fd = shm_open(name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); if (fd == -1) { PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; } int ret = ftruncate(fd, sz); if (ret != 0) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; } void *addr = mmap(0, sz, PROT_WRITE, MAP_SHARED, fd, 0); if (addr == MAP_FAILED) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; } memcpy(addr, data, sz); if (munmap(addr, sz) != 0) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; } safe_close(fd, __FILE__, __LINE__); Py_RETURN_NONE; } W(shm_unlink) { char *name; PA("s", &name); int ret = shm_unlink(name); if (ret == -1) { PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; } Py_RETURN_NONE; } W(update_layers) { unsigned int scrolled_by, sx, sy; float xstart, ystart, dx, dy; CellPixelSize cell; PA("IffffIIII", &scrolled_by, &xstart, &ystart, &dx, &dy, &sx, &sy, &cell.width, &cell.height); grman_update_layers(self, scrolled_by, xstart, ystart, dx, dy, sx, sy, cell); PyObject *ans = PyTuple_New(self->render_data.count); for (size_t i = 0; i < self->render_data.count; i++) { ImageRenderData *r = self->render_data.item + i; #define R(which) Py_BuildValue("{sf sf sf sf}", "left", r->which.left, "top", r->which.top, "right", r->which.right, "bottom", r->which.bottom) PyTuple_SET_ITEM(ans, i, Py_BuildValue("{sN sN sI si sK sK}", "src_rect", R(src_rect), "dest_rect", R(dest_rect), "group_count", r->group_count, "z_index", r->z_index, "image_id", r->image_id, "ref_id", r->ref_id) ); #undef R } return ans; } #define M(x, va) {#x, (PyCFunction)py##x, va, ""} static PyMethodDef methods[] = { M(image_for_client_id, METH_O), M(image_for_client_number, METH_O), M(update_layers, METH_VARARGS), {NULL} /* Sentinel */ }; static PyObject* get_image_count(GraphicsManager *self, void* closure UNUSED) { return PyLong_FromSize_t(vt_size(&self->images_by_internal_id)); } static PyGetSetDef getsets[] = { {"image_count", (getter)get_image_count, NULL, NULL, NULL}, {NULL}, }; static PyMemberDef members[] = { {"storage_limit", T_PYSSIZET, offsetof(GraphicsManager, storage_limit), 0, "storage_limit"}, {"disk_cache", T_OBJECT_EX, offsetof(GraphicsManager, disk_cache), READONLY, "disk_cache"}, {NULL}, }; PyTypeObject GraphicsManager_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.GraphicsManager", .tp_basicsize = sizeof(GraphicsManager), .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "GraphicsManager", .tp_new = new_graphicsmanager_object, .tp_methods = methods, .tp_members = members, .tp_getset = getsets, }; static PyObject* pycreate_canvas(PyObject *self UNUSED, PyObject *args) { unsigned int bytes_per_pixel; unsigned int over_width, width, height, x, y; Py_ssize_t over_sz; const uint8_t *over_data; if (!PyArg_ParseTuple(args, "y#IIIIII", &over_data, &over_sz, &over_width, &x, &y, &width, &height, &bytes_per_pixel)) return NULL; size_t canvas_sz = (size_t)width * height * bytes_per_pixel; PyObject *ans = PyBytes_FromStringAndSize(NULL, canvas_sz); if (!ans) return NULL; uint8_t* canvas = (uint8_t*)PyBytes_AS_STRING(ans); memset(canvas, 0, canvas_sz); ComposeData cd = { .needs_blending = bytes_per_pixel == 4, .over_width = over_width, .over_height = over_sz / (bytes_per_pixel * over_width), .under_width = width, .under_height = height, .over_px_sz = bytes_per_pixel, .under_px_sz = bytes_per_pixel, .over_offset_x = x, .over_offset_y = y }; compose(cd, canvas, over_data); return ans; } static PyMethodDef module_methods[] = { M(shm_write, METH_VARARGS), M(shm_unlink, METH_VARARGS), M(create_canvas, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_graphics(PyObject *module) { if (PyType_Ready(&GraphicsManager_Type) < 0) return false; if (PyModule_AddObject(module, "GraphicsManager", (PyObject *)&GraphicsManager_Type) != 0) return false; if (PyModule_AddFunctions(module, module_methods) != 0) return false; if (PyModule_AddIntMacro(module, IMAGE_PLACEHOLDER_CHAR) != 0) return false; Py_INCREF(&GraphicsManager_Type); return true; } void grman_mark_layers_dirty(GraphicsManager *self) { self->layers_dirty = true; } void grman_set_window_id(GraphicsManager *self, id_type id) { self->window_id = id; } GraphicsRenderData grman_render_data(GraphicsManager *self) { GraphicsRenderData ans = { .count=self->render_data.count, .capacity=self->render_data.capacity, .images=self->render_data.item, .num_of_below_refs=self->num_of_below_refs, .num_of_negative_refs=self->num_of_negative_refs, .num_of_positive_refs=self->num_of_positive_refs }; return ans; } // }}} kitty-0.41.1/kitty/graphics.h0000664000175000017510000001725414773370543015435 0ustar nileshnilesh/* * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" #include "monotonic.h" typedef struct { unsigned char action, transmission_type, compressed, delete_action; uint32_t format, more, id, image_number, data_sz, data_offset, placement_id, quiet, parent_id, parent_placement_id; uint32_t width, height, x_offset, y_offset; union { uint32_t cursor_movement, compose_mode; }; union { uint32_t cell_x_offset, blend_mode; }; union { uint32_t cell_y_offset, bgcolor; }; union { uint32_t data_width, animation_state; }; union { uint32_t data_height, loop_count; }; union { uint32_t num_lines, frame_number; }; union { uint32_t num_cells, other_frame_number; }; union { int32_t z_index, gap; }; size_t payload_sz; bool unicode_placement; int32_t offset_from_parent_x, offset_from_parent_y; } GraphicsCommand; typedef struct { float left, top, right, bottom; } ImageRect; typedef struct { ImageRect src_rect, dest_rect; uint32_t texture_id, group_count; int z_index; id_type image_id, ref_id; } ImageRenderData; typedef struct { uint32_t texture_id; unsigned int height, width; uint8_t* bitmap; uint32_t refcnt; size_t mmap_size; } BackgroundImage; #ifdef GRAPHICS_INTERNAL_APIS typedef struct { float src_width, src_height, src_x, src_y; uint32_t cell_x_offset, cell_y_offset, num_cols, num_rows, effective_num_rows, effective_num_cols; int32_t z_index; int32_t start_row, start_column; uint32_t client_id; ImageRect src_rect; // Indicates whether this reference represents a cell ref that should be // removed when the corresponding cells are modified. // The internal id of the virtual ref this cell image was created from. Is a cell ref if this is non-zero. id_type virtual_ref_id; // Virtual refs are not displayed but they can be used as prototypes for // refs placed using unicode placeholders. bool is_virtual_ref; struct { id_type img, ref; struct { int32_t x, y; } offset; } parent; id_type internal_id; } ImageRef; typedef struct { uint32_t gap, id, width, height, x, y, base_frame_id, bgcolor; bool is_opaque, is_4byte_aligned, alpha_blend; } Frame; typedef enum { ANIMATION_STOPPED = 0, ANIMATION_LOADING = 1, ANIMATION_RUNNING = 2} AnimationState; typedef struct TextureRef { uint32_t id, refcnt; } TextureRef; #define NAME ref_map #define KEY_TY id_type #define VAL_TY ImageRef* #include "kitty-verstable.h" typedef struct { uint32_t client_id, client_number, width, height; TextureRef *texture; id_type internal_id; bool root_frame_data_loaded; id_type ref_id_counter; Frame *extra_frames, root_frame; uint32_t current_frame_index, frame_id_counter; uint64_t animation_duration; size_t extra_framecnt; monotonic_t atime; size_t used_storage; bool is_drawn; AnimationState animation_state; uint32_t max_loops, current_loop; monotonic_t current_frame_shown_at; ref_map refs_by_internal_id; } Image; typedef struct { id_type image_id; uint32_t frame_id; } ImageAndFrame; typedef struct { uint8_t *buf; size_t buf_capacity, buf_used; uint8_t *mapped_file; size_t mapped_file_sz; size_t data_sz; uint8_t *data; bool is_4byte_aligned; bool is_opaque, loading_completed_successfully; uint32_t width, height; GraphicsCommand start_command; ImageAndFrame loading_for; } LoadData; #define NAME image_map #define KEY_TY id_type #define VAL_TY Image* #include "kitty-verstable.h" typedef struct { PyObject_HEAD size_t storage_limit; LoadData currently_loading; id_type image_id_counter; struct { size_t count, capacity; ImageRenderData *item; } render_data; bool layers_dirty; // The number of images below MIN_ZINDEX / 2, then the number of refs between MIN_ZINDEX / 2 and -1 inclusive, then the number of refs above 0 inclusive. size_t num_of_below_refs, num_of_negative_refs, num_of_positive_refs; unsigned int last_scrolled_by; size_t used_storage; PyObject *disk_cache; bool has_images_needing_animation, context_made_current_for_this_command; id_type window_id; image_map images_by_internal_id; } GraphicsManager; #else typedef struct {int x;} *GraphicsManager; #endif typedef struct { int32_t amt, limit; index_type margin_top, margin_bottom; bool has_margins; } ScrollData; static inline float gl_size(const unsigned int sz, const unsigned int viewport_size) { // convert pixel sz to OpenGL coordinate system. const float px = 2.f / viewport_size; return px * sz; } static inline float clamp_position_to_nearest_pixel(float pos, const unsigned int viewport_size) { // clamp the specified opengl position to the nearest pixel const float px = 2.f / viewport_size; const float distance = pos + 1.f; const float num_of_pixels = roundf(distance / px); return -1.f + num_of_pixels * px; } static inline float gl_pos_x(const unsigned int px_from_left_margin, const unsigned int viewport_size) { const float px = 2.f / viewport_size; return -1.f + px_from_left_margin * px; } static inline float gl_pos_y(const unsigned int px_from_top_margin, const unsigned int viewport_size) { const float px = 2.f / viewport_size; return 1.f - px_from_top_margin * px; } typedef struct GraphicsRenderData { size_t count, capacity, num_of_below_refs, num_of_negative_refs, num_of_positive_refs; ImageRenderData *images; } GraphicsRenderData; GraphicsManager* grman_alloc(bool for_paused_rendering); void grman_clear(GraphicsManager*, bool, CellPixelSize fg); const char* grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty, CellPixelSize fg); void grman_put_cell_image(GraphicsManager *self, uint32_t row, uint32_t col, uint32_t image_id, uint32_t placement_id, uint32_t x, uint32_t y, uint32_t w, uint32_t h, CellPixelSize cell); bool grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows, CellPixelSize); void grman_scroll_images(GraphicsManager *self, const ScrollData*, CellPixelSize fg); void grman_resize(GraphicsManager*, index_type, index_type, index_type, index_type, index_type, index_type); void grman_rescale(GraphicsManager *self, CellPixelSize fg); void grman_remove_cell_images(GraphicsManager *self, int32_t top, int32_t bottom); void grman_remove_all_cell_images(GraphicsManager *self); void gpu_data_for_image(ImageRenderData *ans, float left, float top, float right, float bottom); bool png_from_file_pointer(FILE* fp, const char *path, uint8_t** data, unsigned int* width, unsigned int* height, size_t* sz); bool png_path_to_bitmap(const char *path, uint8_t** data, unsigned int* width, unsigned int* height, size_t* sz); bool png_from_data(void *png_data, size_t png_data_sz, const char *path_for_error_messages, uint8_t** data, unsigned int* width, unsigned int* height, size_t* sz); bool image_path_to_bitmap(const char *path, uint8_t** data, unsigned int* width, unsigned int* height, size_t* sz); bool scan_active_animations(GraphicsManager *self, const monotonic_t now, monotonic_t *minimum_gap, bool os_window_context_set); void scale_rendered_graphic(ImageRenderData*, float xstart, float ystart, float x_scale, float y_scale); void grman_pause_rendering(GraphicsManager *self, GraphicsManager *dest); void grman_mark_layers_dirty(GraphicsManager *self); void grman_set_window_id(GraphicsManager *self, id_type id); GraphicsRenderData grman_render_data(GraphicsManager *self); kitty-0.41.1/kitty/graphics_fragment.glsl0000664000175000017510000000110614773370543020017 0ustar nileshnilesh#pragma kitty_include_shader #define ALPHA_TYPE uniform sampler2D image; #ifdef ALPHA_MASK uniform vec3 amask_fg; uniform vec4 amask_bg_premult; #else uniform float inactive_text_alpha; #endif in vec2 texcoord; out vec4 color; void main() { color = texture(image, texcoord); #ifdef ALPHA_MASK color = vec4(amask_fg, color.r); color = vec4(color.rgb * color.a, color.a); color = alpha_blend_premul(color, amask_bg_premult); #else color.a *= inactive_text_alpha; #ifdef PREMULT color = vec4(color.rgb * color.a, color.a); #endif #endif } kitty-0.41.1/kitty/graphics_vertex.glsl0000664000175000017510000000126614773370543017540 0ustar nileshnileshout vec2 texcoord; uniform vec4 src_rect, dest_rect, viewport; #define left 0 #define top 1 #define right 2 #define bottom 3 const ivec2 vertex_pos_map[4] = ivec2[4]( ivec2(right, top), ivec2(right, bottom), ivec2(left, bottom), ivec2(left, top) ); void main() { ivec2 pos = vertex_pos_map[gl_VertexID]; texcoord = vec2(src_rect[pos.x], src_rect[pos.y]); gl_Position = vec4(dest_rect[pos.x], dest_rect[pos.y], 0, 1); gl_ClipDistance[left] = gl_Position.x - viewport[left]; gl_ClipDistance[right] = viewport[right] - gl_Position.x; gl_ClipDistance[top] = viewport[top] - gl_Position.y; gl_ClipDistance[bottom] = gl_Position.y - viewport[bottom]; } kitty-0.41.1/kitty/guess_mime_type.py0000664000175000017510000000540714773370543017231 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import os import stat from contextlib import suppress known_extensions = { 'asciidoc': 'text/asciidoctor', 'conf': 'text/config', 'md': 'text/markdown', 'pyj': 'text/rapydscript-ng', 'recipe': 'text/python', 'rst': 'text/restructured-text', 'rb': 'text/ruby', 'toml': 'text/toml', 'vim': 'text/vim', 'yaml': 'text/yaml', 'js': 'text/javascript', 'json': 'text/json', } text_mimes = ( 'application/x-sh', 'application/x-csh', 'application/x-shellscript', 'application/javascript', 'application/json', 'application/xml', 'application/x-yaml', 'application/yaml', 'application/x-toml', 'application/x-lua', 'application/toml', 'application/rss+xml', 'application/xhtml+xml', 'application/x-tex', 'application/x-latex', ) def is_special_file(path: str) -> str | None: name = os.path.basename(path) lname = name.lower() if lname == 'makefile' or lname.startswith('makefile.'): return 'text/makefile' if '.' not in name and name.endswith('rc'): return 'text/plain' # rc file return None def is_folder(path: str) -> bool: with suppress(OSError): return os.path.isdir(path) return False def initialize_mime_database() -> None: if hasattr(initialize_mime_database, 'inited'): return setattr(initialize_mime_database, 'inited', True) from mimetypes import init init(None) from kitty.constants import config_dir local_defs = os.path.join(config_dir, 'mime.types') if os.path.exists(local_defs): init((local_defs,)) def clear_mime_cache() -> None: if hasattr(initialize_mime_database, 'inited'): delattr(initialize_mime_database, 'inited') def guess_type(path: str, allow_filesystem_access: bool = False) -> str | None: is_dir = is_exe = False if allow_filesystem_access: with suppress(OSError): st = os.stat(path) is_dir = bool(stat.S_ISDIR(st.st_mode)) is_exe = bool(not is_dir and st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) and os.access(path, os.X_OK)) if is_dir: return 'inode/directory' from mimetypes import guess_type as stdlib_guess_type initialize_mime_database() mt = None with suppress(Exception): mt = stdlib_guess_type(path)[0] if not mt: ext = path.rpartition('.')[-1].lower() mt = known_extensions.get(ext) if mt in text_mimes: mt = f'text/{mt.split("/", 1)[-1]}' mt = mt or is_special_file(path) if not mt: if is_dir: mt = 'inode/directory' # type: ignore elif is_exe: mt = 'inode/executable' return mt kitty-0.41.1/kitty/history.c0000664000175000017510000005654214773370543015334 0ustar nileshnilesh/* * history.c * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "wcswidth.h" #include "lineops.h" #include "charsets.h" #include "resize.h" #include #include "../3rdparty/ringbuf/ringbuf.h" extern PyTypeObject Line_Type; #define SEGMENT_SIZE 2048 static void add_segment(HistoryBuf *self, index_type num) { self->segments = realloc(self->segments, sizeof(HistoryBufSegment) * (self->num_segments + num)); if (self->segments == NULL) fatal("Out of memory allocating new history buffer segment"); const size_t cpu_cells_size = self->xnum * SEGMENT_SIZE * sizeof(CPUCell); const size_t gpu_cells_size = self->xnum * SEGMENT_SIZE * sizeof(GPUCell); const size_t segment_size = cpu_cells_size + gpu_cells_size + SEGMENT_SIZE * sizeof(LineAttrs); char *mem = calloc(num, segment_size); if (!mem) fatal("Out of memory allocating new history buffer segment"); char *needs_free = mem; for (HistoryBufSegment *s = self->segments + self->num_segments; s < self->segments + self->num_segments + num; s++, mem += segment_size) { s->cpu_cells = (CPUCell*)mem; s->gpu_cells = (GPUCell*)(((uint8_t*)s->cpu_cells) + cpu_cells_size); s->line_attrs = (LineAttrs*)(((uint8_t*)s->gpu_cells) + gpu_cells_size); s->mem = NULL; } self->segments[self->num_segments].mem = needs_free; self->num_segments += num; } static void free_segment(HistoryBufSegment *s) { free(s->mem); zero_at_ptr(s); } static index_type segment_for(HistoryBuf *self, index_type y) { index_type seg_num = y / SEGMENT_SIZE; while (UNLIKELY(seg_num >= self->num_segments && SEGMENT_SIZE * self->num_segments < self->ynum)) add_segment(self, 1); if (UNLIKELY(seg_num >= self->num_segments)) fatal("Out of bounds access to history buffer line number: %u", y); return seg_num; } #define seg_ptr(which, stride) { \ index_type seg_num = segment_for(self, y); \ y -= seg_num * SEGMENT_SIZE; \ return self->segments[seg_num].which + y * stride; \ } static CPUCell* cpu_lineptr(HistoryBuf *self, index_type y) { seg_ptr(cpu_cells, self->xnum); } static GPUCell* gpu_lineptr(HistoryBuf *self, index_type y) { seg_ptr(gpu_cells, self->xnum); } static LineAttrs* attrptr(HistoryBuf *self, index_type y) { seg_ptr(line_attrs, 1); } static size_t initial_pagerhist_ringbuf_sz(size_t pagerhist_sz) { return MIN(1024u * 1024u, pagerhist_sz); } static PagerHistoryBuf* alloc_pagerhist(size_t pagerhist_sz) { PagerHistoryBuf *ph; if (!pagerhist_sz) return NULL; ph = calloc(1, sizeof(PagerHistoryBuf)); if (!ph) return NULL; size_t sz = initial_pagerhist_ringbuf_sz(pagerhist_sz); ph->ringbuf = ringbuf_new(sz); if (!ph->ringbuf) { free(ph); return NULL; } ph->maximum_size = pagerhist_sz; return ph; } static void free_pagerhist(HistoryBuf *self) { if (self->pagerhist && self->pagerhist->ringbuf) ringbuf_free((ringbuf_t*)&self->pagerhist->ringbuf); free(self->pagerhist); self->pagerhist = NULL; } static bool pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) { size_t buffer_size = ringbuf_capacity(ph->ringbuf); if (buffer_size >= ph->maximum_size) return false; size_t newsz = MIN(ph->maximum_size, buffer_size + MAX(1024u * 1024u, minsz)); ringbuf_t newbuf = ringbuf_new(newsz); if (!newbuf) return false; size_t count = ringbuf_bytes_used(ph->ringbuf); if (count) ringbuf_copy(newbuf, ph->ringbuf, count); ringbuf_free((ringbuf_t*)&ph->ringbuf); ph->ringbuf = newbuf; return true; } static void pagerhist_clear(HistoryBuf *self) { if (self->pagerhist && self->pagerhist->ringbuf) { ringbuf_reset(self->pagerhist->ringbuf); size_t rsz = initial_pagerhist_ringbuf_sz(self->pagerhist->maximum_size); void *rbuf = ringbuf_new(rsz); if (rbuf) { ringbuf_free((ringbuf_t*)&self->pagerhist->ringbuf); self->pagerhist->ringbuf = rbuf; } } } static HistoryBuf* create_historybuf(PyTypeObject *type, unsigned int xnum, unsigned int ynum, unsigned int pagerhist_sz, TextCache *tc) { if (xnum == 0 || ynum == 0) { PyErr_SetString(PyExc_ValueError, "Cannot create an empty history buffer"); return NULL; } HistoryBuf *self = (HistoryBuf *)type->tp_alloc(type, 0); if (self != NULL) { self->xnum = xnum; self->ynum = ynum; self->num_segments = 0; add_segment(self, 1); self->text_cache = tc_incref(tc); self->line = alloc_line(self->text_cache); self->line->xnum = xnum; self->pagerhist = alloc_pagerhist(pagerhist_sz); } return self; } static PyObject * new_history_object(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { unsigned int xnum = 1, ynum = 1, pagerhist_sz = 0; if (!PyArg_ParseTuple(args, "II|I", &ynum, &xnum, &pagerhist_sz)) return NULL; TextCache *tc = tc_alloc(); if (!tc) return PyErr_NoMemory(); HistoryBuf *ans = create_historybuf(type, xnum, ynum, pagerhist_sz, tc); tc_decref(tc); return (PyObject*)ans; } static void dealloc(HistoryBuf* self) { Py_CLEAR(self->line); for (size_t i = 0; i < self->num_segments; i++) free_segment(self->segments + i); free(self->segments); free_pagerhist(self); tc_decref(self->text_cache); Py_TYPE(self)->tp_free((PyObject*)self); } static index_type index_of(HistoryBuf *self, index_type lnum) { // The index (buffer position) of the line with line number lnum // This is reverse indexing, i.e. lnum = 0 corresponds to the *last* line in the buffer. if (self->count == 0) return 0; index_type idx = self->count - 1 - MIN(self->count - 1, lnum); return (self->start_of_data + idx) % self->ynum; } static bool hb_line_is_continued(HistoryBuf *self, index_type num) { if (num == 0) { size_t sz; if (self->pagerhist && self->pagerhist->ringbuf && (sz = ringbuf_bytes_used(self->pagerhist->ringbuf)) > 0) { size_t pos = ringbuf_findchr(self->pagerhist->ringbuf, '\n', sz - 1); if (pos >= sz) return true; // ringbuf does not end with a newline } return false; } return cpu_lineptr(self, num - 1)[self->xnum-1].next_char_was_wrapped; } static void init_line(HistoryBuf *self, index_type num, Line *l) { // Initialize the line l, setting its pointer to the offsets for the line at index (buffer position) num l->cpu_cells = cpu_lineptr(self, num); l->gpu_cells = gpu_lineptr(self, num); l->attrs = *attrptr(self, num); } void historybuf_init_line(HistoryBuf *self, index_type lnum, Line *l) { init_line(self, index_of(self, lnum), l); } bool historybuf_is_line_continued(HistoryBuf *self, index_type lnum) { return hb_line_is_continued(self, index_of(self, lnum)); } bool history_buf_endswith_wrap(HistoryBuf *self) { return cpu_lineptr(self, index_of(self, 0))[self->xnum-1].next_char_was_wrapped; } CPUCell* historybuf_cpu_cells(HistoryBuf *self, index_type lnum) { return cpu_lineptr(self, index_of(self, lnum)); } void historybuf_mark_line_clean(HistoryBuf *self, index_type y) { attrptr(self, index_of(self, y))->has_dirty_text = false; } void historybuf_mark_line_dirty(HistoryBuf *self, index_type y) { attrptr(self, index_of(self, y))->has_dirty_text = true; } void historybuf_set_line_has_image_placeholders(HistoryBuf *self, index_type y, bool val) { attrptr(self, index_of(self, y))->has_image_placeholders = val; } void historybuf_clear(HistoryBuf *self) { pagerhist_clear(self); self->count = 0; self->start_of_data = 0; for (size_t i = 0; i < self->num_segments; i++) free_segment(self->segments + i); free(self->segments); self->segments = NULL; self->num_segments = 0; add_segment(self, 1); } static bool pagerhist_write_bytes(PagerHistoryBuf *ph, const uint8_t *buf, size_t sz) { if (sz > ph->maximum_size) return false; if (!sz) return true; size_t space_in_ringbuf = ringbuf_bytes_free(ph->ringbuf); if (sz > space_in_ringbuf) pagerhist_extend(ph, sz); ringbuf_memcpy_into(ph->ringbuf, buf, sz); return true; } static bool pagerhist_ensure_start_is_valid_utf8(PagerHistoryBuf *ph) { uint8_t scratch[8]; size_t num = ringbuf_memcpy_from(scratch, ph->ringbuf, arraysz(scratch)); uint32_t codep; UTF8State state = UTF8_ACCEPT; size_t count = 0; size_t last_reject_at = 0; while (count < num) { decode_utf8(&state, &codep, scratch[count++]); if (state == UTF8_ACCEPT) break; if (state == UTF8_REJECT) { state = UTF8_ACCEPT; last_reject_at = count; } } if (last_reject_at) { ringbuf_memmove_from(scratch, ph->ringbuf, last_reject_at); return true; } return false; } static bool pagerhist_write_ucs4(PagerHistoryBuf *ph, const Py_UCS4 *buf, size_t sz) { uint8_t scratch[4]; for (size_t i = 0; i < sz; i++) { unsigned int num = encode_utf8(buf[i], (char*)scratch); if (!pagerhist_write_bytes(ph, scratch, num)) return false; } return true; } static void pagerhist_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) { PagerHistoryBuf *ph = self->pagerhist; if (!ph) return; Line l = {.xnum=self->xnum, .text_cache=self->text_cache}; init_line(self, self->start_of_data, &l); ANSILineState s = {.output_buf=as_ansi_buf}; as_ansi_buf->len = 0; line_as_ansi(&l, &s, 0, l.xnum, 0, true); pagerhist_write_bytes(ph, (const uint8_t*)"\x1b[m", 3); if (pagerhist_write_ucs4(ph, as_ansi_buf->buf, as_ansi_buf->len)) { char line_end[2]; size_t num = 0; line_end[num++] = '\r'; if (!l.cpu_cells[l.xnum - 1].next_char_was_wrapped) line_end[num++] = '\n'; pagerhist_write_bytes(ph, (const uint8_t*)line_end, num); } } static index_type historybuf_push(HistoryBuf *self, ANSIBuf *as_ansi_buf, bool *needs_clear) { index_type idx = (self->start_of_data + self->count) % self->ynum; if (self->count == self->ynum) { pagerhist_push(self, as_ansi_buf); self->start_of_data = (self->start_of_data + 1) % self->ynum; *needs_clear = true; } else { self->count++; *needs_clear = false; } return idx; } void historybuf_add_line(HistoryBuf *self, const Line *line, ANSIBuf *as_ansi_buf) { bool needs_clear; index_type idx = historybuf_push(self, as_ansi_buf, &needs_clear); init_line(self, idx, self->line); copy_line(line, self->line); *attrptr(self, idx) = line->attrs; } bool historybuf_pop_line(HistoryBuf *self, Line *line) { if (self->count <= 0) return false; index_type idx = (self->start_of_data + self->count - 1) % self->ynum; init_line(self, idx, line); self->count--; return true; } static PyObject* line(HistoryBuf *self, PyObject *val) { #define line_doc "Return the line with line number val. This buffer grows upwards, i.e. 0 is the most recently added line" if (self->count == 0) { PyErr_SetString(PyExc_IndexError, "This buffer is empty"); return NULL; } index_type lnum = PyLong_AsUnsignedLong(val); if (lnum >= self->count) { PyErr_SetString(PyExc_IndexError, "Out of bounds"); return NULL; } init_line(self, index_of(self, lnum), self->line); Py_INCREF(self->line); return (PyObject*)self->line; } static PyObject* __str__(HistoryBuf *self) { PyObject *lines = PyTuple_New(self->count); if (lines == NULL) return PyErr_NoMemory(); RAII_ANSIBuf(buf); for (index_type i = 0; i < self->count; i++) { init_line(self, index_of(self, i), self->line); PyObject *t = line_as_unicode(self->line, false, &buf); if (t == NULL) { Py_CLEAR(lines); return NULL; } PyTuple_SET_ITEM(lines, i, t); } PyObject *sep = PyUnicode_FromString("\n"); PyObject *ans = PyUnicode_Join(sep, lines); Py_CLEAR(lines); Py_CLEAR(sep); return ans; } static PyObject* push(HistoryBuf *self, PyObject *args) { #define push_doc "Push a line into this buffer, removing the oldest line, if necessary" Line *line; if (!PyArg_ParseTuple(args, "O!", &Line_Type, &line)) return NULL; ANSIBuf as_ansi_buf = {0}; historybuf_add_line(self, line, &as_ansi_buf); free(as_ansi_buf.buf); Py_RETURN_NONE; } static PyObject* as_ansi(HistoryBuf *self, PyObject *callback) { #define as_ansi_doc "as_ansi(callback) -> The contents of this buffer as ANSI escaped text. callback is called with each successive line." Line l = {.xnum=self->xnum, .text_cache=self->text_cache}; ANSIBuf output = {0}; ANSILineState s = {.output_buf=&output}; for(unsigned int i = 0; i < self->count; i++) { init_line(self, i, &l); output.len = 0; line_as_ansi(&l, &s, 0, l.xnum, 0, true); if (!l.cpu_cells[l.xnum - 1].next_char_was_wrapped) { ensure_space_for(&output, buf, Py_UCS4, output.len + 1, capacity, 2048, false); output.buf[output.len++] = '\n'; } PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, output.buf, output.len); if (ans == NULL) { PyErr_NoMemory(); goto end; } PyObject *ret = PyObject_CallFunctionObjArgs(callback, ans, NULL); Py_CLEAR(ans); if (ret == NULL) goto end; Py_CLEAR(ret); } end: free(output.buf); if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } static char_type pagerhist_remove_char(PagerHistoryBuf *ph, unsigned *count, uint8_t record[8]) { uint32_t codep; UTF8State state = UTF8_ACCEPT; *count = 0; size_t num = ringbuf_bytes_used(ph->ringbuf); while (num--) { record[*count] = ringbuf_move_char(ph->ringbuf); decode_utf8(&state, &codep, record[*count]); *count += 1; if (state == UTF8_REJECT) { codep = 0; break; } if (state == UTF8_ACCEPT) break; } return codep; } static void pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) { PagerHistoryBuf *ph = self->pagerhist; if (!ph->ringbuf || !ringbuf_bytes_used(ph->ringbuf)) return; PagerHistoryBuf *nph = calloc(1, sizeof(PagerHistoryBuf)); if (!nph) return; nph->maximum_size = ph->maximum_size; nph->ringbuf = ringbuf_new(MIN(ph->maximum_size, ringbuf_capacity(ph->ringbuf) + 4096)); if (!nph->ringbuf) { free(nph); return ; } ssize_t ch_width = 0; unsigned count; uint8_t record[8]; index_type num_in_current_line = 0; char_type ch; WCSState wcs_state; initialize_wcs_state(&wcs_state); #define WRITE_CHAR() { \ if (num_in_current_line + ch_width > cells_in_line) { \ pagerhist_write_bytes(nph, (const uint8_t*)"\r", 1); \ num_in_current_line = 0; \ }\ if (ch_width >= 0 || (int)num_in_current_line >= -ch_width) num_in_current_line += ch_width; \ pagerhist_write_bytes(nph, record, count); \ } while (ringbuf_bytes_used(ph->ringbuf)) { ch = pagerhist_remove_char(ph, &count, record); if (ch == '\n') { initialize_wcs_state(&wcs_state); ch_width = 1; WRITE_CHAR(); num_in_current_line = 0; } else if (ch != '\r') { ch_width = wcswidth_step(&wcs_state, ch); WRITE_CHAR(); } } free_pagerhist(self); self->pagerhist = nph; #undef WRITE_CHAR } static PyObject* pagerhist_write(HistoryBuf *self, PyObject *what) { if (self->pagerhist && self->pagerhist->maximum_size) { if (PyBytes_Check(what)) pagerhist_write_bytes(self->pagerhist, (const uint8_t*)PyBytes_AS_STRING(what), PyBytes_GET_SIZE(what)); else if (PyUnicode_Check(what) && PyUnicode_READY(what) == 0) { Py_UCS4 *buf = PyUnicode_AsUCS4Copy(what); if (buf) { pagerhist_write_ucs4(self->pagerhist, buf, PyUnicode_GET_LENGTH(what)); PyMem_Free(buf); } } } Py_RETURN_NONE; } static const uint8_t* reverse_find(const uint8_t *haystack, size_t haystack_sz, const uint8_t *needle) { const size_t needle_sz = strlen((const char*)needle); if (!needle_sz || needle_sz > haystack_sz) return NULL; const uint8_t *p = haystack + haystack_sz - (needle_sz - 1); while (--p >= haystack) { if (*p == needle[0] && memcmp(p, needle, MIN(needle_sz, haystack_sz - (p - haystack))) == 0) return p; } return NULL; } static PyObject* pagerhist_as_bytes(HistoryBuf *self, PyObject *args) { int upto_output_start = 0; if (!PyArg_ParseTuple(args, "|p", &upto_output_start)) return NULL; #define ph self->pagerhist if (!ph || !ringbuf_bytes_used(ph->ringbuf)) return PyBytes_FromStringAndSize("", 0); pagerhist_ensure_start_is_valid_utf8(ph); if (ph->rewrap_needed) pagerhist_rewrap_to(self, self->xnum); size_t sz = ringbuf_bytes_used(ph->ringbuf); PyObject *ans = PyBytes_FromStringAndSize(NULL, sz); if (!ans) return NULL; uint8_t *buf = (uint8_t*)PyBytes_AS_STRING(ans); ringbuf_memcpy_from(buf, ph->ringbuf, sz); if (upto_output_start) { const uint8_t *p = reverse_find(buf, sz, (const uint8_t*)"\x1b]133;C\x1b\\"); if (p) { PyObject *t = PyBytes_FromStringAndSize((const char*)p, sz - (p - buf)); Py_DECREF(ans); ans = t; } } return ans; #undef ph } static PyObject * pagerhist_as_text(HistoryBuf *self, PyObject *args) { PyObject *ans = NULL; PyObject *bytes = pagerhist_as_bytes(self, args); if (bytes) { ans = PyUnicode_DecodeUTF8(PyBytes_AS_STRING(bytes), PyBytes_GET_SIZE(bytes), "ignore"); Py_DECREF(bytes); } return ans; } typedef struct { Line line; HistoryBuf *self; } GetLineWrapper; static Line* get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, index_of(self, self->count - y - 1), l); return l; } static Line* get_line_wrapper(void *x, int y) { GetLineWrapper *glw = x; get_line(glw->self, y, &glw->line); return &glw->line; } PyObject* as_text_history_buf(HistoryBuf *self, PyObject *args, ANSIBuf *output) { GetLineWrapper glw = {.self=self}; glw.line.xnum = self->xnum; glw.line.text_cache = self->text_cache; PyObject *ans = as_text_generic(args, &glw, get_line_wrapper, self->count, output, true); return ans; } static PyObject* dirty_lines(HistoryBuf *self, PyObject *a UNUSED) { #define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text." PyObject *ans = PyList_New(0); for (index_type i = 0; i < self->count; i++) { if (attrptr(self, i)->has_dirty_text) { PyList_Append(ans, PyLong_FromUnsignedLong(i)); } } return ans; } static PyObject* pagerhist_rewrap(HistoryBuf *self, PyObject *xnum) { if (self->pagerhist) { pagerhist_rewrap_to(self, PyLong_AsUnsignedLong(xnum)); } Py_RETURN_NONE; } static PyObject* is_continued(HistoryBuf *self, PyObject *val) { #define is_continued_doc "is_continued(y) -> Whether the line y is continued or not" unsigned long y = PyLong_AsUnsignedLong(val); if (y >= self->count) { PyErr_SetString(PyExc_ValueError, "Out of bounds."); return NULL; } index_type num = index_of(self, self->count - y - 1); if (hb_line_is_continued(self, num)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static PyObject* endswith_wrap(HistoryBuf *self, PyObject *val UNUSED) { #define endswith_wrap_doc "endswith_wrap() -> Whether the last line is wrapped at the end of the buffer" if (history_buf_endswith_wrap(self)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } // Boilerplate {{{ static PyObject* rewrap(HistoryBuf *self, PyObject *args); #define rewrap_doc "" static PyMethodDef methods[] = { METHOD(line, METH_O) METHOD(is_continued, METH_O) METHOD(endswith_wrap, METH_NOARGS) METHOD(as_ansi, METH_O) METHODB(pagerhist_write, METH_O), METHODB(pagerhist_rewrap, METH_O), METHODB(pagerhist_as_text, METH_VARARGS), METHODB(pagerhist_as_bytes, METH_VARARGS), METHOD(dirty_lines, METH_NOARGS) METHOD(push, METH_VARARGS) METHOD(rewrap, METH_VARARGS) {NULL, NULL, 0, NULL} /* Sentinel */ }; static PyMemberDef members[] = { {"xnum", T_UINT, offsetof(HistoryBuf, xnum), READONLY, "xnum"}, {"ynum", T_UINT, offsetof(HistoryBuf, ynum), READONLY, "ynum"}, {"count", T_UINT, offsetof(HistoryBuf, count), READONLY, "count"}, {NULL} /* Sentinel */ }; PyTypeObject HistoryBuf_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.HistoryBuf", .tp_basicsize = sizeof(HistoryBuf), .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "History buffers", .tp_methods = methods, .tp_members = members, .tp_str = (reprfunc)__str__, .tp_new = new_history_object }; INIT_TYPE(HistoryBuf) HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns, unsigned int pagerhist_sz, TextCache *tc) { return create_historybuf(&HistoryBuf_Type, columns, lines, pagerhist_sz, tc); } // }}} static void history_buf_set_last_char_as_continuation(HistoryBuf *self, index_type y, bool wrapped) { if (self->count > 0) { cpu_lineptr(self, index_of(self, y))[self->xnum-1].next_char_was_wrapped = wrapped; } } index_type historybuf_next_dest_line(HistoryBuf *self, ANSIBuf *as_ansi_buf, Line *src_line, index_type dest_y, Line *dest_line, bool continued) { history_buf_set_last_char_as_continuation(self, 0, continued); bool needs_clear; index_type idx = historybuf_push(self, as_ansi_buf, &needs_clear); *attrptr(self, idx) = src_line->attrs; init_line(self, idx, dest_line); if (needs_clear) { zero_at_ptr_count(dest_line->cpu_cells, dest_line->xnum); zero_at_ptr_count(dest_line->gpu_cells, dest_line->xnum); } return dest_y + 1; } HistoryBuf* historybuf_alloc_for_rewrap(unsigned int columns, HistoryBuf *self) { if (!self) return NULL; HistoryBuf *ans = alloc_historybuf(self->ynum, columns, 0, self->text_cache); if (ans) { if (ans->num_segments < self->num_segments) add_segment(ans, self->num_segments - ans->num_segments); ans->count = 0; ans->start_of_data = 0; } return ans; } void historybuf_finish_rewrap(HistoryBuf *dest, HistoryBuf *src) { for (index_type i = 0; i < dest->count; i++) attrptr(dest, (dest->start_of_data + i) % dest->ynum)->has_dirty_text = true; dest->pagerhist = src->pagerhist; src->pagerhist = NULL; if (dest->pagerhist && dest->xnum != src->xnum && ringbuf_bytes_used(dest->pagerhist->ringbuf)) dest->pagerhist->rewrap_needed = true; } void historybuf_fast_rewrap(HistoryBuf *dest, HistoryBuf *src) { for (index_type i = 0; i < src->num_segments; i++) { memcpy(dest->segments[i].cpu_cells, src->segments[i].cpu_cells, SEGMENT_SIZE * src->xnum * sizeof(CPUCell)); memcpy(dest->segments[i].gpu_cells, src->segments[i].gpu_cells, SEGMENT_SIZE * src->xnum * sizeof(GPUCell)); memcpy(dest->segments[i].line_attrs, src->segments[i].line_attrs, SEGMENT_SIZE * sizeof(LineAttrs)); } dest->count = src->count; dest->start_of_data = src->start_of_data; } static PyObject* rewrap(HistoryBuf *self, PyObject *args) { unsigned xnum; if (!PyArg_ParseTuple(args, "I", &xnum)) return NULL; ANSIBuf as_ansi_buf = {0}; LineBuf *dummy = alloc_linebuf(4, self->xnum, self->text_cache); if (!dummy) return PyErr_NoMemory(); RAII_PyObject(cleanup, (PyObject*)dummy); (void)cleanup; TrackCursor cursors[1] = {{.is_sentinel=true}}; ResizeResult r = resize_screen_buffers(dummy, self, 8, xnum, &as_ansi_buf, cursors); free(as_ansi_buf.buf); if (!r.ok) return PyErr_NoMemory(); Py_CLEAR(r.lb); return (PyObject*)r.hb; } kitty-0.41.1/kitty/history.h0000664000175000017510000000220014773370543015317 0ustar nileshnilesh/* * history.h * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "line.h" typedef struct { GPUCell *gpu_cells; CPUCell *cpu_cells; LineAttrs *line_attrs; void *mem; } HistoryBufSegment; typedef struct { void *ringbuf; size_t maximum_size; bool rewrap_needed; } PagerHistoryBuf; typedef struct { PyObject_HEAD index_type xnum, ynum, num_segments; HistoryBufSegment *segments; PagerHistoryBuf *pagerhist; Line *line; TextCache *text_cache; index_type start_of_data, count; } HistoryBuf; HistoryBuf* alloc_historybuf(unsigned int, unsigned int, unsigned int, TextCache *tc); HistoryBuf *historybuf_alloc_for_rewrap(unsigned int columns, HistoryBuf *self); void historybuf_finish_rewrap(HistoryBuf *dest, HistoryBuf *src); void historybuf_fast_rewrap(HistoryBuf *dest, HistoryBuf *src); index_type historybuf_next_dest_line(HistoryBuf *self, ANSIBuf *as_ansi_buf, Line *src_line, index_type dest_y, Line *dest_line, bool continued); bool historybuf_is_line_continued(HistoryBuf *self, index_type lnum); kitty-0.41.1/kitty/hsluv.glsl0000664000175000017510000001412114773370543015476 0ustar nileshnilesh/* HSLUV-GLSL v4.2 HSLUV is a human-friendly alternative to HSL. ( http://www.hsluv.org ) GLSL port by William Malo ( https://github.com/williammalo ) Put this code in your fragment shader. */ // stripped down and optimized (branchless) version float divide(float num, float denom) { return num / (abs(denom) + 1e-15) * sign(denom); } vec3 divide(vec3 num, vec3 denom) { return num / (abs(denom) + 1e-15) * sign(denom); } vec3 hsluv_intersectLineLine(vec3 line1x, vec3 line1y, vec3 line2x, vec3 line2y) { return (line1y - line2y) / (line2x - line1x); } vec3 hsluv_distanceFromPole(vec3 pointx,vec3 pointy) { return sqrt(pointx*pointx + pointy*pointy); } vec3 hsluv_lengthOfRayUntilIntersect(float theta, vec3 x, vec3 y) { vec3 len = divide(y, sin(theta) - x * cos(theta)); len = mix(len, vec3(1000.0), step(len, vec3(0.0))); return len; } float hsluv_maxSafeChromaForL(float L){ mat3 m2 = mat3( 3.2409699419045214 ,-0.96924363628087983 , 0.055630079696993609, -1.5373831775700935 , 1.8759675015077207 ,-0.20397695888897657 , -0.49861076029300328 , 0.041555057407175613, 1.0569715142428786 ); float sub0 = L + 16.0; float sub1 = sub0 * sub0 * sub0 * .000000641; float sub2 = mix(L / 903.2962962962963, sub1, step(0.0088564516790356308, sub1)); vec3 top1 = (284517.0 * m2[0] - 94839.0 * m2[2]) * sub2; vec3 bottom = (632260.0 * m2[2] - 126452.0 * m2[1]) * sub2; vec3 top2 = (838422.0 * m2[2] + 769860.0 * m2[1] + 731718.0 * m2[0]) * L * sub2; vec3 bounds0x = top1 / bottom; vec3 bounds0y = top2 / bottom; vec3 bounds1x = top1 / (bottom+126452.0); vec3 bounds1y = (top2-769860.0*L) / (bottom+126452.0); vec3 xs0 = hsluv_intersectLineLine(bounds0x, bounds0y, -1.0/bounds0x, vec3(0.0) ); vec3 xs1 = hsluv_intersectLineLine(bounds1x, bounds1y, -1.0/bounds1x, vec3(0.0) ); vec3 lengths0 = hsluv_distanceFromPole( xs0, bounds0y + xs0 * bounds0x ); vec3 lengths1 = hsluv_distanceFromPole( xs1, bounds1y + xs1 * bounds1x ); return min(lengths0.r, min(lengths1.r, min(lengths0.g, min(lengths1.g, min(lengths0.b, lengths1.b))))); } float hsluv_maxChromaForLH(float L, float H) { float hrad = radians(H); mat3 m2 = mat3( 3.2409699419045214 ,-0.96924363628087983 , 0.055630079696993609, -1.5373831775700935 , 1.8759675015077207 ,-0.20397695888897657 , -0.49861076029300328 , 0.041555057407175613, 1.0569715142428786 ); float sub1 = pow(L + 16.0, 3.0) / 1560896.0; float sub2 = mix(L / 903.2962962962963, sub1, step(0.0088564516790356308, sub1)); vec3 top1 = (284517.0 * m2[0] - 94839.0 * m2[2]) * sub2; vec3 bottom = (632260.0 * m2[2] - 126452.0 * m2[1]) * sub2; vec3 top2 = (838422.0 * m2[2] + 769860.0 * m2[1] + 731718.0 * m2[0]) * L * sub2; vec3 bound0x = top1 / bottom; vec3 bound0y = top2 / bottom; vec3 bound1x = top1 / (bottom+126452.0); vec3 bound1y = (top2-769860.0*L) / (bottom+126452.0); vec3 lengths0 = hsluv_lengthOfRayUntilIntersect(hrad, bound0x, bound0y ); vec3 lengths1 = hsluv_lengthOfRayUntilIntersect(hrad, bound1x, bound1y ); return min(lengths0.r, min(lengths1.r, min(lengths0.g, min(lengths1.g, min(lengths0.b, lengths1.b))))); } vec3 hsluv_fromLinear(vec3 c) { return mix(c * 12.92, 1.055 * pow(max(c, vec3(0)), vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c)); } vec3 hsluv_toLinear(vec3 c) { return mix(c / 12.92, pow(max((c + 0.055) / (1.0 + 0.055), vec3(0)), vec3(2.4)), step(0.04045, c)); } float hsluv_yToL(float Y){ return mix(Y * 903.2962962962963, 116.0 * pow(max(Y, 0), 1.0 / 3.0) - 16.0, step(0.0088564516790356308, Y)); } float hsluv_lToY(float L) { return mix(L / 903.2962962962963, pow((max(L, 0) + 16.0) / 116.0, 3.0), step(8.0, L)); } vec3 xyzToRgb(vec3 tuple) { const mat3 m = mat3( 3.2409699419045214 ,-1.5373831775700935 ,-0.49861076029300328 , -0.96924363628087983 , 1.8759675015077207 , 0.041555057407175613, 0.055630079696993609,-0.20397695888897657, 1.0569715142428786 ); return hsluv_fromLinear(tuple*m); } vec3 rgbToXyz(vec3 tuple) { const mat3 m = mat3( 0.41239079926595948 , 0.35758433938387796, 0.18048078840183429 , 0.21263900587151036 , 0.71516867876775593, 0.072192315360733715, 0.019330818715591851, 0.11919477979462599, 0.95053215224966058 ); return hsluv_toLinear(tuple) * m; } vec3 xyzToLuv(vec3 tuple){ float X = tuple.x; float Y = tuple.y; float Z = tuple.z; float L = hsluv_yToL(Y); float div = 1. / max(dot(tuple, vec3(1, 15, 3)), 1e-15); return vec3( 1., (52. * (X*div) - 2.57179), (117.* (Y*div) - 6.08816) ) * L; } vec3 luvToXyz(vec3 tuple) { float L = tuple.x; float U = divide(tuple.y, 13.0 * L) + 0.19783000664283681; float V = divide(tuple.z, 13.0 * L) + 0.468319994938791; float Y = hsluv_lToY(L); float X = 2.25 * U * Y / V; float Z = (3./V - 5.)*Y - (X/3.); return vec3(X, Y, Z); } vec3 luvToLch(vec3 tuple) { float L = tuple.x; float U = tuple.y; float V = tuple.z; float C = length(tuple.yz); float H = degrees(atan(V,U)); H += 360.0 * step(H, 0.0); return vec3(L, C, H); } vec3 lchToLuv(vec3 tuple) { float hrad = radians(tuple.b); return vec3( tuple.r, cos(hrad) * tuple.g, sin(hrad) * tuple.g ); } vec3 hsluvToLch(vec3 tuple) { tuple.g *= hsluv_maxChromaForLH(tuple.b, tuple.r) * .01; return tuple.bgr; } vec3 lchToHsluv(vec3 tuple) { tuple.g = divide(tuple.g, hsluv_maxChromaForLH(tuple.r, tuple.b) * .01); return tuple.bgr; } vec3 lchToRgb(vec3 tuple) { return xyzToRgb(luvToXyz(lchToLuv(tuple))); } vec3 rgbToLch(vec3 tuple) { return luvToLch(xyzToLuv(rgbToXyz(tuple))); } vec3 hsluvToRgb(vec3 tuple) { return lchToRgb(hsluvToLch(tuple)); } vec3 rgbToHsluv(vec3 tuple) { return lchToHsluv(rgbToLch(tuple)); } vec3 luvToRgb(vec3 tuple){ return xyzToRgb(luvToXyz(tuple)); } kitty-0.41.1/kitty/hyperlink.c0000664000175000017510000001470514773370543015633 0ustar nileshnilesh/* * hyperlink.c * Copyright (C) 2020 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "hyperlink.h" #include "lineops.h" #include #define MAX_KEY_LEN 2048 #define MAX_ID_LEN 256 #define NAME hyperlink_map #define KEY_TY const char* #define VAL_TY hyperlink_id_type #include "kitty-verstable.h" #define hyperlink_for_loop vt_create_for_loop(hyperlink_map_itr, itr, &pool->map) typedef const char* hyperlink; typedef struct HyperLinks { hyperlink *items; size_t count, capacity; } HyperLinks; typedef struct { HyperLinks array; hyperlink_map map; hyperlink_id_type adds_since_last_gc; } HyperLinkPool; static void free_hyperlink_items(HyperLinks array) { for (size_t i = 1; i < array.count; i++) free((void*)array.items[i]); } static void clear_pool(HyperLinkPool *pool) { if (pool->array.items) { free_hyperlink_items(pool->array); free(pool->array.items); } vt_cleanup(&pool->map); zero_at_ptr(&(pool->array)); pool->adds_since_last_gc = 0; } HYPERLINK_POOL_HANDLE alloc_hyperlink_pool(void) { HyperLinkPool *ans = calloc(1, sizeof(HyperLinkPool)); if (ans) vt_init(&ans->map); return (HYPERLINK_POOL_HANDLE)ans; } void clear_hyperlink_pool(HYPERLINK_POOL_HANDLE h) { if (h) clear_pool((HyperLinkPool*)h); } void free_hyperlink_pool(HYPERLINK_POOL_HANDLE h) { if (h) { HyperLinkPool *pool = (HyperLinkPool*)h; clear_pool(pool); free(pool); } } static const char* dupstr(const char *src, size_t len) { char *ans = malloc(len+1); if (!ans) fatal("Out of memory"); memcpy(ans, src, len); ans[len] = 0; return ans; } static void process_cell(HyperLinkPool *pool, hyperlink_id_type *map, HyperLinks clone, CPUCell *c) { if (!c->hyperlink_id) return; if (c->hyperlink_id >= clone.count) { c->hyperlink_id = 0; return; } hyperlink_id_type new_id = map[c->hyperlink_id]; if (!new_id) { new_id = pool->array.count++; map[c->hyperlink_id] = new_id; pool->array.items[new_id] = clone.items[c->hyperlink_id]; clone.items[c->hyperlink_id] = NULL; if (vt_is_end(vt_insert(&pool->map, pool->array.items[new_id], new_id))) fatal("Out of memory"); } c->hyperlink_id = new_id; } static void remap_hyperlink_ids(Screen *self, bool preserve_hyperlinks_in_history, hyperlink_id_type *map, HyperLinks clone) { HyperLinkPool *pool = (HyperLinkPool*)self->hyperlink_pool; if (self->historybuf->count && preserve_hyperlinks_in_history) { for (index_type y = self->historybuf->count; y-- > 0;) { CPUCell *cells = historybuf_cpu_cells(self->historybuf, y); for (index_type x = 0; x < self->historybuf->xnum; x++) process_cell(pool, map, clone, cells + x); } } LineBuf *second = self->linebuf, *first = second == self->main_linebuf ? self->alt_linebuf : self->main_linebuf; for (index_type i = 0; i < self->lines * self->columns; i++) process_cell(pool, map, clone, first->cpu_cell_buf + i); for (index_type i = 0; i < self->lines * self->columns; i++) process_cell(pool, map, clone, second->cpu_cell_buf + i); } static void _screen_garbage_collect_hyperlink_pool(Screen *screen, bool preserve_hyperlinks_in_history) { HyperLinkPool *pool = (HyperLinkPool*)screen->hyperlink_pool; if (!pool->array.count) return; pool->adds_since_last_gc = 0; RAII_ALLOC(hyperlink_id_type, map, calloc(pool->array.count, sizeof(hyperlink_id_type))); RAII_ALLOC(void, buf, malloc(pool->array.count * sizeof(pool->array.items[0]))); if (!map || !buf) fatal("Out of memory"); HyperLinks clone = {.capacity=pool->array.count, .count=pool->array.count, .items=buf}; memcpy(buf, pool->array.items, pool->array.count * sizeof(pool->array.items[0])); vt_cleanup(&pool->map); pool->array.count = 1; // First id must be 1 remap_hyperlink_ids(screen, preserve_hyperlinks_in_history, map, clone); free_hyperlink_items(clone); } void screen_garbage_collect_hyperlink_pool(Screen *screen) { _screen_garbage_collect_hyperlink_pool(screen, true); } hyperlink_id_type get_id_for_hyperlink(Screen *screen, const char *id, const char *url) { if (!url) return 0; HyperLinkPool *pool = (HyperLinkPool*)screen->hyperlink_pool; static char key[MAX_KEY_LEN] = {0}; int keylen = snprintf(key, MAX_KEY_LEN-1, "%.*s:%s", MAX_ID_LEN, id ? id : "", url); if (keylen < 0) keylen = strlen(key); else keylen = MIN(keylen, MAX_KEY_LEN - 2); // snprintf returns how many chars it would have written in case of truncation key[keylen] = 0; hyperlink_map_itr itr = vt_get(&pool->map, key); if (!vt_is_end(itr)) return itr.data->val; if (pool->array.count >= HYPERLINK_MAX_NUMBER-1) { screen_garbage_collect_hyperlink_pool(screen); if (pool->array.count >= HYPERLINK_MAX_NUMBER - 128) { log_error("Too many hyperlinks, discarding hyperlinks in scrollback"); _screen_garbage_collect_hyperlink_pool(screen, false); if (pool->array.count >= HYPERLINK_MAX_NUMBER) { log_error("Too many hyperlinks, discarding hyperlink: %s", key); return 0; } } } if (!pool->array.count) pool->array.count = 1; // First id must be 1 ensure_space_for(&(pool->array), items, hyperlink, pool->array.count + 1, capacity, 256, false); hyperlink_id_type new_id = pool->array.count++; pool->array.items[new_id] = dupstr(key, keylen); if (vt_is_end(vt_insert(&pool->map, pool->array.items[new_id], new_id))) fatal("Out of memory"); // If there have been a lot of hyperlink adds do a garbage collect so as // not to leak too much memory over unused hyperlinks if (++pool->adds_since_last_gc > 8192) screen_garbage_collect_hyperlink_pool(screen); return new_id; } const char* get_hyperlink_for_id(const HYPERLINK_POOL_HANDLE handle, hyperlink_id_type id, bool only_url) { HyperLinkPool *pool = (HyperLinkPool*)handle; if (id >= pool->array.count) return NULL; return only_url ? strstr(pool->array.items[id], ":") + 1 : pool->array.items[id]; } PyObject* screen_hyperlinks_as_set(Screen *screen) { HyperLinkPool *pool = (HyperLinkPool*)screen->hyperlink_pool; RAII_PyObject(ans, PySet_New(0)); if (ans) { hyperlink_for_loop { RAII_PyObject(e, Py_BuildValue("sH", itr.data->key, itr.data->val)); if (!e || PySet_Add(ans, e) != 0) return NULL; } } Py_XINCREF(ans); return ans; } kitty-0.41.1/kitty/hyperlink.h0000664000175000017510000000075214773370543015635 0ustar nileshnilesh/* * Copyright (C) 2020 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "screen.h" HYPERLINK_POOL_HANDLE alloc_hyperlink_pool(void); void free_hyperlink_pool(HYPERLINK_POOL_HANDLE); void clear_hyperlink_pool(HYPERLINK_POOL_HANDLE); hyperlink_id_type get_id_for_hyperlink(Screen*, const char*, const char*); PyObject* screen_hyperlinks_as_set(Screen *screen); void screen_garbage_collect_hyperlink_pool(Screen *screen); kitty-0.41.1/kitty/iqsort.h0000664000175000017510000002613714773370543015156 0ustar nileshnilesh/* $Id: qsort.h,v 1.5 2008-01-28 18:16:49 mjt Exp $ * Adopted from GNU glibc by Mjt. * See stdlib/qsort.c in glibc */ /* Copyright (C) 1991, 1992, 1996, 1997, 1999 Free Software Foundation, Inc. This file is part of the GNU C Library. Written by Douglas C. Schmidt (schmidt@ics.uci.edu). The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ /* in-line qsort implementation. Differs from traditional qsort() routine * in that it is a macro, not a function, and instead of passing an address * of a comparison routine to the function, it is possible to inline * comparison routine, thus speeding up sorting a lot. * * Usage: * #include "iqsort.h" * #define islt(a,b) (strcmp((*a),(*b))<0) * char *arr[]; * int n; * QSORT(char*, arr, n, islt); * * The "prototype" and 4 arguments are: * QSORT(TYPE,BASE,NELT,ISLT) * 1) type of each element, TYPE, * 2) address of the beginning of the array, of type TYPE*, * 3) number of elements in the array, and * 4) comparison routine. * Array pointer and number of elements are referenced only once. * This is similar to a call * qsort(BASE,NELT,sizeof(TYPE),ISLT) * with the difference in last parameter. * Note the islt macro/routine (it receives pointers to two elements): * the only condition of interest is whenever one element is less than * another, no other conditions (greater than, equal to etc) are tested. * So, for example, to define integer sort, use: * #define islt(a,b) ((*a)<(*b)) * QSORT(int, arr, n, islt) * * The macro could be used to implement a sorting function (see examples * below), or to implement the sorting algorithm inline. That is, either * create a sorting function and use it whenever you want to sort something, * or use QSORT() macro directly instead a call to such routine. Note that * the macro expands to quite some code (compiled size of int qsort on x86 * is about 700..800 bytes). * * Using this macro directly it isn't possible to implement traditional * qsort() routine, because the macro assumes sizeof(element) == sizeof(TYPE), * while qsort() allows element size to be different. * * Several ready-to-use examples: * * Sorting array of integers: * void int_qsort(int *arr, unsigned n) { * #define int_lt(a,b) ((*a)<(*b)) * QSORT(int, arr, n, int_lt); * } * * Sorting array of string pointers: * void str_qsort(char *arr[], unsigned n) { * #define str_lt(a,b) (strcmp((*a),(*b)) < 0) * QSORT(char*, arr, n, str_lt); * } * * Sorting array of structures: * * struct elt { * int key; * ... * }; * void elt_qsort(struct elt *arr, unsigned n) { * #define elt_lt(a,b) ((a)->key < (b)->key) * QSORT(struct elt, arr, n, elt_lt); * } * * And so on. */ /* Swap two items pointed to by A and B using temporary buffer t. */ #define _QSORT_SWAP(a, b, t) ((void)((t = *a), (*a = *b), (*b = t))) /* Discontinue quicksort algorithm when partition gets below this size. This particular magic number was chosen to work best on a Sun 4/260. */ #define _QSORT_MAX_THRESH 4 /* Stack node declarations used to store unfulfilled partition obligations * (inlined in QSORT). typedef struct { QSORT_TYPE *_lo, *_hi; } qsort_stack_node; */ /* The next 4 #defines implement a very fast in-line stack abstraction. */ /* The stack needs log (total_elements) entries (we could even subtract log(MAX_THRESH)). Since total_elements has type unsigned, we get as upper bound for log (total_elements): bits per byte (CHAR_BIT) * sizeof(unsigned). */ #define _QSORT_STACK_SIZE (8 * sizeof(unsigned)) #define _QSORT_PUSH(top, low, high) \ (((top->_lo = (low)), (top->_hi = (high)), ++top)) #define _QSORT_POP(low, high, top) \ ((--top, (low = top->_lo), (high = top->_hi))) #define _QSORT_STACK_NOT_EMPTY (_stack < _top) /* Order size using quicksort. This implementation incorporates four optimizations discussed in Sedgewick: 1. Non-recursive, using an explicit stack of pointer that store the next array partition to sort. To save time, this maximum amount of space required to store an array of SIZE_MAX is allocated on the stack. Assuming a 32-bit (64 bit) integer for size_t, this needs only 32 * sizeof(stack_node) == 256 bytes (for 64 bit: 1024 bytes). Pretty cheap, actually. 2. Chose the pivot element using a median-of-three decision tree. This reduces the probability of selecting a bad pivot value and eliminates certain extraneous comparisons. 3. Only quicksorts TOTAL_ELEMS / MAX_THRESH partitions, leaving insertion sort to order the MAX_THRESH items within each partition. This is a big win, since insertion sort is faster for small, mostly sorted array segments. 4. The larger of the two sub-partitions is always pushed onto the stack first, with the algorithm then concentrating on the smaller partition. This *guarantees* no more than log (total_elems) stack size is needed (actually O(1) in this case)! */ /* The main code starts here... */ #define QSORT(QSORT_TYPE,QSORT_BASE,QSORT_NELT,QSORT_LT) \ { \ QSORT_TYPE *const _base = (QSORT_BASE); \ const unsigned _elems = (QSORT_NELT); \ QSORT_TYPE _hold; \ \ /* Don't declare two variables of type QSORT_TYPE in a single \ * statement: eg `TYPE a, b;', in case if TYPE is a pointer, \ * expands to `type* a, b;' which isn't what we want. \ */ \ \ if (_elems > _QSORT_MAX_THRESH) { \ QSORT_TYPE *_lo = _base; \ QSORT_TYPE *_hi = _lo + _elems - 1; \ struct { \ QSORT_TYPE *_hi; QSORT_TYPE *_lo; \ } _stack[_QSORT_STACK_SIZE], *_top = _stack + 1; \ \ while (_QSORT_STACK_NOT_EMPTY) { \ QSORT_TYPE *_left_ptr; QSORT_TYPE *_right_ptr; \ \ /* Select median value from among LO, MID, and HI. Rearrange \ LO and HI so the three values are sorted. This lowers the \ probability of picking a pathological pivot value and \ skips a comparison for both the LEFT_PTR and RIGHT_PTR in \ the while loops. */ \ \ QSORT_TYPE *_mid = _lo + ((_hi - _lo) >> 1); \ \ if (QSORT_LT (_mid, _lo)) \ _QSORT_SWAP (_mid, _lo, _hold); \ if (QSORT_LT (_hi, _mid)) { \ _QSORT_SWAP (_mid, _hi, _hold); \ if (QSORT_LT (_mid, _lo)) \ _QSORT_SWAP (_mid, _lo, _hold); \ } \ \ _left_ptr = _lo + 1; \ _right_ptr = _hi - 1; \ \ /* Here's the famous ``collapse the walls'' section of quicksort. \ Gotta like those tight inner loops! They are the main reason \ that this algorithm runs much faster than others. */ \ do { \ while (QSORT_LT (_left_ptr, _mid)) \ ++_left_ptr; \ \ while (QSORT_LT (_mid, _right_ptr)) \ --_right_ptr; \ \ if (_left_ptr < _right_ptr) { \ _QSORT_SWAP (_left_ptr, _right_ptr, _hold); \ if (_mid == _left_ptr) \ _mid = _right_ptr; \ else if (_mid == _right_ptr) \ _mid = _left_ptr; \ ++_left_ptr; \ --_right_ptr; \ } \ else if (_left_ptr == _right_ptr) { \ ++_left_ptr; \ --_right_ptr; \ break; \ } \ } while (_left_ptr <= _right_ptr); \ \ /* Set up pointers for next iteration. First determine whether \ left and right partitions are below the threshold size. If so, \ ignore one or both. Otherwise, push the larger partition's \ bounds on the stack and continue sorting the smaller one. */ \ \ if (_right_ptr - _lo <= _QSORT_MAX_THRESH) { \ if (_hi - _left_ptr <= _QSORT_MAX_THRESH) \ /* Ignore both small partitions. */ \ _QSORT_POP (_lo, _hi, _top); \ else \ /* Ignore small left partition. */ \ _lo = _left_ptr; \ } \ else if (_hi - _left_ptr <= _QSORT_MAX_THRESH) \ /* Ignore small right partition. */ \ _hi = _right_ptr; \ else if (_right_ptr - _lo > _hi - _left_ptr) { \ /* Push larger left partition indices. */ \ _QSORT_PUSH (_top, _lo, _right_ptr); \ _lo = _left_ptr; \ } \ else { \ /* Push larger right partition indices. */ \ _QSORT_PUSH (_top, _left_ptr, _hi); \ _hi = _right_ptr; \ } \ } \ } \ \ /* Once the BASE array is partially sorted by quicksort the rest \ is completely sorted using insertion sort, since this is efficient \ for partitions below MAX_THRESH size. BASE points to the \ beginning of the array to sort, and END_PTR points at the very \ last element in the array (*not* one beyond it!). */ \ \ { \ QSORT_TYPE *const _end_ptr = _base + _elems - 1; \ QSORT_TYPE *_tmp_ptr = _base; \ register QSORT_TYPE *_run_ptr; \ QSORT_TYPE *_thresh; \ \ _thresh = _base + _QSORT_MAX_THRESH; \ if (_thresh > _end_ptr) \ _thresh = _end_ptr; \ \ /* Find smallest element in first threshold and place it at the \ array's beginning. This is the smallest array element, \ and the operation speeds up insertion sort's inner loop. */ \ \ for (_run_ptr = _tmp_ptr + 1; _run_ptr <= _thresh; ++_run_ptr) \ if (QSORT_LT (_run_ptr, _tmp_ptr)) \ _tmp_ptr = _run_ptr; \ \ if (_tmp_ptr != _base) \ _QSORT_SWAP (_tmp_ptr, _base, _hold); \ \ /* Insertion sort, running from left-hand-side \ * up to right-hand-side. */ \ \ _run_ptr = _base + 1; \ while (++_run_ptr <= _end_ptr) { \ _tmp_ptr = _run_ptr - 1; \ while (QSORT_LT (_run_ptr, _tmp_ptr)) \ --_tmp_ptr; \ \ ++_tmp_ptr; \ if (_tmp_ptr != _run_ptr) { \ QSORT_TYPE *_trav = _run_ptr + 1; \ while (--_trav >= _run_ptr) { \ QSORT_TYPE *_hi; QSORT_TYPE *_lo; \ _hold = *_trav; \ \ for (_hi = _lo = _trav; --_lo >= _tmp_ptr; _hi = _lo) \ *_hi = *_lo; \ *_hi = _hold; \ } \ } \ } \ } \ \ } kitty-0.41.1/kitty/key_encoding.c0000664000175000017510000003765614773370543016276 0ustar nileshnilesh/* * key_encoding.c * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "keys.h" #include "charsets.h" typedef enum { SHIFT=1, ALT=2, CTRL=4, SUPER=8, HYPER=16, META=32, CAPS_LOCK=64, NUM_LOCK=128} ModifierMasks; typedef enum { PRESS = 0, REPEAT = 1, RELEASE = 2} KeyAction; #define LOCK_MASK (CAPS_LOCK | NUM_LOCK) typedef struct { uint32_t key, shifted_key, alternate_key; struct { bool shift, alt, ctrl, super, hyper, meta, numlock, capslock; unsigned value; char encoded[4]; } mods; KeyAction action; bool cursor_key_mode, disambiguate, report_all_event_types, report_alternate_key, report_text, embed_text; const char *text; bool has_text; } KeyEvent; typedef struct { uint32_t key, shifted_key, alternate_key; bool add_alternates, has_mods, add_actions, add_text; char encoded_mods[4]; const char *text; KeyAction action; } EncodingData; static void convert_glfw_mods(int mods, KeyEvent *ev, const unsigned key_encoding_flags) { if (!key_encoding_flags) mods &= ~GLFW_LOCK_MASK; ev->mods.alt = (mods & GLFW_MOD_ALT) > 0, ev->mods.ctrl = (mods & GLFW_MOD_CONTROL) > 0, ev->mods.shift = (mods & GLFW_MOD_SHIFT) > 0, ev->mods.super = (mods & GLFW_MOD_SUPER) > 0, ev->mods.hyper = (mods & GLFW_MOD_HYPER) > 0, ev->mods.meta = (mods & GLFW_MOD_META) > 0; ev->mods.numlock = (mods & GLFW_MOD_NUM_LOCK) > 0, ev->mods.capslock = (mods & GLFW_MOD_CAPS_LOCK) > 0; ev->mods.value = ev->mods.shift ? SHIFT : 0; if (ev->mods.alt) ev->mods.value |= ALT; if (ev->mods.ctrl) ev->mods.value |= CTRL; if (ev->mods.super) ev->mods.value |= SUPER; if (ev->mods.hyper) ev->mods.value |= HYPER; if (ev->mods.meta) ev->mods.value |= META; if (ev->mods.capslock) ev->mods.value |= CAPS_LOCK; if (ev->mods.numlock) ev->mods.value |= NUM_LOCK; snprintf(ev->mods.encoded, sizeof(ev->mods.encoded), "%u", ev->mods.value + 1); } static void init_encoding_data(EncodingData *ans, const KeyEvent *ev) { ans->add_actions = ev->report_all_event_types && ev->action != PRESS; ans->has_mods = ev->mods.encoded[0] && ( ev->mods.encoded[0] != '1' || ev->mods.encoded[1] ); ans->add_alternates = ev->report_alternate_key && ((ev->shifted_key > 0 && ev->mods.shift) || ev->alternate_key > 0); if (ans->add_alternates) { if (ev->mods.shift) ans->shifted_key = ev->shifted_key; ans->alternate_key = ev->alternate_key; } ans->action = ev->action; ans->key = ev->key; ans->add_text = ev->embed_text && ev->text && ev->text[0]; ans->text = ev->text; memcpy(ans->encoded_mods, ev->mods.encoded, sizeof(ans->encoded_mods)); } static int serialize(const EncodingData *data, char *output, const char csi_trailer) { int pos = 0; bool second_field_not_empty = data->has_mods || data->add_actions; bool third_field_not_empty = data->add_text; #define P(fmt, ...) pos += snprintf(output + pos, KEY_BUFFER_SIZE - 2 <= pos ? 0 : KEY_BUFFER_SIZE - 2 - pos, fmt, __VA_ARGS__) P("\x1b%s", "["); if (data->key != 1 || data->add_alternates || second_field_not_empty || third_field_not_empty) P("%u", data->key); if (data->add_alternates) { P("%s", ":"); if (data->shifted_key) P("%u", data->shifted_key); if (data->alternate_key) P(":%u", data->alternate_key); } if (second_field_not_empty || third_field_not_empty) { P("%s", ";"); if (second_field_not_empty) P("%s", data->encoded_mods); if (data->add_actions) P(":%u", data->action + 1); } if (third_field_not_empty) { const char *p = data->text; uint32_t codep; UTF8State state = UTF8_ACCEPT; bool first = true; while(*p) { if (decode_utf8(&state, &codep, *p) == UTF8_ACCEPT) { if (first) { P(";%u", codep); first = false; } else P(":%u", codep); } p++; } } #undef P output[pos++] = csi_trailer; output[pos] = 0; return pos; } static uint32_t convert_kp_key_to_normal_key(uint32_t key_number) { switch(key_number) { #define S(x) case GLFW_FKEY_KP_##x: key_number = GLFW_FKEY_##x; break; S(ENTER) S(HOME) S(END) S(INSERT) S(DELETE) S(PAGE_UP) S(PAGE_DOWN) S(UP) S(DOWN) S(LEFT) S(RIGHT) #undef S case GLFW_FKEY_KP_0: case GLFW_FKEY_KP_9: key_number = '0' + (key_number - GLFW_FKEY_KP_0); break; case GLFW_FKEY_KP_DECIMAL: key_number = '.'; break; case GLFW_FKEY_KP_DIVIDE: key_number = '/'; break; case GLFW_FKEY_KP_MULTIPLY: key_number = '*'; break; case GLFW_FKEY_KP_SUBTRACT: key_number = '-'; break; case GLFW_FKEY_KP_ADD: key_number = '+'; break; case GLFW_FKEY_KP_EQUAL: key_number = '='; break; } return key_number; } static int legacy_functional_key_encoding_with_modifiers(uint32_t key_number, const KeyEvent *ev, char *output) { const char *prefix = ev->mods.value & ALT ? "\x1b" : ""; const char *main_bytes = ""; switch (key_number) { case GLFW_FKEY_ENTER: main_bytes = "\x0d"; break; case GLFW_FKEY_ESCAPE: main_bytes = "\x1b"; break; case GLFW_FKEY_BACKSPACE: main_bytes = ev->mods.value & CTRL ? "\x08" : "\x7f"; break; case GLFW_FKEY_TAB: if (ev->mods.value & SHIFT) { prefix = ev->mods.value & ALT ? "\x1b\x1b" : "\x1b"; main_bytes = "[Z"; } else { main_bytes = "\t"; } break; default: return -1; } return snprintf(output, KEY_BUFFER_SIZE, "%s%s", prefix, main_bytes); } static int encode_function_key(const KeyEvent *ev, char *output) { #define SIMPLE(val) return snprintf(output, KEY_BUFFER_SIZE, "%s", val); char csi_trailer = 'u'; uint32_t key_number = ev->key; bool legacy_mode = !ev->report_all_event_types && !ev->disambiguate && !ev->report_text; if (ev->cursor_key_mode && legacy_mode && !ev->mods.value) { switch(key_number) { case GLFW_FKEY_UP: SIMPLE("\x1bOA"); case GLFW_FKEY_DOWN: SIMPLE("\x1bOB"); case GLFW_FKEY_RIGHT: SIMPLE("\x1bOC"); case GLFW_FKEY_LEFT: SIMPLE("\x1bOD"); case GLFW_FKEY_KP_BEGIN: SIMPLE("\x1bOE"); case GLFW_FKEY_END: SIMPLE("\x1bOF"); case GLFW_FKEY_HOME: SIMPLE("\x1bOH"); default: break; } } if (!ev->mods.value) { if (!ev->disambiguate && !ev->report_text && key_number == GLFW_FKEY_ESCAPE) SIMPLE("\x1b"); if (legacy_mode) { switch(key_number) { case GLFW_FKEY_F1: SIMPLE("\x1bOP"); case GLFW_FKEY_F2: SIMPLE("\x1bOQ"); case GLFW_FKEY_F3: SIMPLE("\x1bOR"); case GLFW_FKEY_F4: SIMPLE("\x1bOS"); default: break; } } if (!ev->report_text) { switch(key_number) { case GLFW_FKEY_ENTER: if (ev->action == RELEASE) return -1; SIMPLE("\r"); case GLFW_FKEY_BACKSPACE: if (ev->action == RELEASE) return -1; SIMPLE("\x7f"); case GLFW_FKEY_TAB: if (ev->action == RELEASE) return -1; SIMPLE("\t"); default: break; } } } else if (legacy_mode) { int num = legacy_functional_key_encoding_with_modifiers(key_number, ev, output); if (num > -1) return num; } if (!(ev->mods.value & ~LOCK_MASK) && !ev->report_text) { switch(key_number) { case GLFW_FKEY_ENTER: if (ev->action == RELEASE) return -1; SIMPLE("\r"); case GLFW_FKEY_BACKSPACE: if (ev->action == RELEASE) return -1; SIMPLE("\x7f"); case GLFW_FKEY_TAB: if (ev->action == RELEASE) return -1; SIMPLE("\t"); default: break; } } #undef SIMPLE #define S(number, trailer) key_number = number; csi_trailer = trailer; break switch(key_number) { /* start special numbers (auto generated by gen-key-constants.py do not edit) */ case GLFW_FKEY_ESCAPE: S(27, 'u'); case GLFW_FKEY_ENTER: S(13, 'u'); case GLFW_FKEY_TAB: S(9, 'u'); case GLFW_FKEY_BACKSPACE: S(127, 'u'); case GLFW_FKEY_INSERT: S(2, '~'); case GLFW_FKEY_DELETE: S(3, '~'); case GLFW_FKEY_LEFT: S(1, 'D'); case GLFW_FKEY_RIGHT: S(1, 'C'); case GLFW_FKEY_UP: S(1, 'A'); case GLFW_FKEY_DOWN: S(1, 'B'); case GLFW_FKEY_PAGE_UP: S(5, '~'); case GLFW_FKEY_PAGE_DOWN: S(6, '~'); case GLFW_FKEY_HOME: S(1, 'H'); case GLFW_FKEY_END: S(1, 'F'); case GLFW_FKEY_F1: S(1, 'P'); case GLFW_FKEY_F2: S(1, 'Q'); case GLFW_FKEY_F3: S(13, '~'); case GLFW_FKEY_F4: S(1, 'S'); case GLFW_FKEY_F5: S(15, '~'); case GLFW_FKEY_F6: S(17, '~'); case GLFW_FKEY_F7: S(18, '~'); case GLFW_FKEY_F8: S(19, '~'); case GLFW_FKEY_F9: S(20, '~'); case GLFW_FKEY_F10: S(21, '~'); case GLFW_FKEY_F11: S(23, '~'); case GLFW_FKEY_F12: S(24, '~'); case GLFW_FKEY_KP_BEGIN: S(1, 'E'); /* end special numbers */ case GLFW_FKEY_MENU: // use the same encoding as xterm for this key in legacy mode (F16) if (legacy_mode) { S(29, '~'); } break; default: break; } #undef S EncodingData ed = {0}; init_encoding_data(&ed, ev); ed.key = key_number; ed.add_alternates = false; return serialize(&ed, output, csi_trailer); } static char ctrled_key(const char key) { // {{{ switch(key) { /* start ctrl mapping (auto generated by gen-key-constants.py do not edit) */ case ' ': return 0; case '/': return 31; case '0': return 48; case '1': return 49; case '2': return 0; case '3': return 27; case '4': return 28; case '5': return 29; case '6': return 30; case '7': return 31; case '8': return 127; case '9': return 57; case '?': return 127; case '@': return 0; case '[': return 27; case '\\': return 28; case ']': return 29; case '^': return 30; case '_': return 31; case 'a': return 1; case 'b': return 2; case 'c': return 3; case 'd': return 4; case 'e': return 5; case 'f': return 6; case 'g': return 7; case 'h': return 8; case 'i': return 9; case 'j': return 10; case 'k': return 11; case 'l': return 12; case 'm': return 13; case 'n': return 14; case 'o': return 15; case 'p': return 16; case 'q': return 17; case 'r': return 18; case 's': return 19; case 't': return 20; case 'u': return 21; case 'v': return 22; case 'w': return 23; case 'x': return 24; case 'y': return 25; case 'z': return 26; case '~': return 30; /* end ctrl mapping */ default: return key; } } // }}} static int encode_printable_ascii_key_legacy(const KeyEvent *ev, char *output) { unsigned mods = ev->mods.value; if (!mods) return snprintf(output, KEY_BUFFER_SIZE, "%c", (char)ev->key); char key = ev->key; if (mods & SHIFT) { const char shifted = ev->shifted_key; if (shifted && shifted != key && (!(mods & CTRL) || key < 'a' || key > 'z')) { key = shifted; mods &= ~SHIFT; } } if (ev->mods.value == SHIFT) return snprintf(output, KEY_BUFFER_SIZE, "%c", key); if (mods == ALT) return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", key); if (mods == CTRL) return snprintf(output, KEY_BUFFER_SIZE, "%c", ctrled_key(key)); if (mods == (CTRL | ALT)) return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", ctrled_key(key)); if (key == ' ') { if (mods == (CTRL | SHIFT)) return snprintf(output, KEY_BUFFER_SIZE, "%c", ctrled_key(key)); if (mods == (ALT | SHIFT)) return snprintf(output, KEY_BUFFER_SIZE, "\x1b%c", key); } return 0; } static bool is_legacy_ascii_key(uint32_t key) { START_ALLOW_CASE_RANGE switch (key) { case 'a' ... 'z': case '0' ... '9': case '!': case '@': case '#': case '$': case '%': case '^': case '&': case '*': case '(': case ')': case '`': case '~': case '-': case '_': case '=': case '+': case '[': case '{': case ']': case '}': case '\\': case '|': case ';': case ':': case '\'': case '"': case ',': case '<': case '.': case '>': case '/': case '?': case ' ': return true; default: return false; } END_ALLOW_CASE_RANGE } static int encode_key(const KeyEvent *ev, char *output) { if (!ev->report_all_event_types && ev->action == RELEASE) return 0; if (GLFW_FKEY_FIRST <= ev->key && ev->key <= GLFW_FKEY_LAST) return encode_function_key(ev, output); EncodingData ed = {0}; init_encoding_data(&ed, ev); bool simple_encoding_ok = !ed.add_actions && !ed.add_alternates && !ed.add_text; if (simple_encoding_ok) { if (!ed.has_mods) { if (ev->report_text) return serialize(&ed, output, 'u'); return encode_utf8(ev->key, output); } if (!ev->disambiguate && !ev->report_text) { if (is_legacy_ascii_key(ev->key) || (ev->shifted_key && is_legacy_ascii_key(ev->shifted_key))) { int ret = encode_printable_ascii_key_legacy(ev, output); if (ret > 0) return ret; } unsigned mods = ev->mods.value; if ((mods == CTRL || mods == ALT || mods == (CTRL | ALT)) && ev->alternate_key && !is_legacy_ascii_key(ev->key) && is_legacy_ascii_key(ev->alternate_key)) { KeyEvent alternate = *ev; alternate.key = ev->alternate_key; alternate.alternate_key = 0; alternate.shifted_key = 0; int ret = encode_printable_ascii_key_legacy(&alternate, output); if (ret > 0) return ret; } } } return serialize(&ed, output, 'u'); } static bool startswith_ascii_control_char(const char *p) { if (!p || !*p) return true; uint32_t codep; UTF8State state = UTF8_ACCEPT; while(*p) { if (decode_utf8(&state, &codep, *p) == UTF8_ACCEPT) { return codep < 32 || codep == 127; } state = UTF8_ACCEPT; p++; } return false; } int encode_glfw_key_event(const GLFWkeyevent *e, const bool cursor_key_mode, const unsigned key_encoding_flags, char *output) { KeyEvent ev = { .key = e->key, .shifted_key = e->shifted_key, .alternate_key = e->alternate_key, .text = e->text, .cursor_key_mode = cursor_key_mode, .disambiguate = key_encoding_flags & 1, .report_all_event_types = key_encoding_flags & 2, .report_alternate_key = key_encoding_flags & 4, .report_text = key_encoding_flags & 8, .embed_text = key_encoding_flags & 16 }; if (!ev.report_text && is_modifier_key(e->key)) return 0; ev.has_text = e->text && !startswith_ascii_control_char(e->text); if (!ev.key && !ev.has_text) return 0; bool send_text_standalone = !ev.report_text; if (!ev.disambiguate && !ev.report_text && GLFW_FKEY_KP_0 <= ev.key && ev.key <= GLFW_FKEY_KP_BEGIN) { ev.key = convert_kp_key_to_normal_key(ev.key); } switch (e->action) { case GLFW_PRESS: ev.action = PRESS; break; case GLFW_REPEAT: ev.action = REPEAT; break; case GLFW_RELEASE: ev.action = RELEASE; break; } if (send_text_standalone && ev.has_text && (ev.action == PRESS || ev.action == REPEAT)) return SEND_TEXT_TO_CHILD; convert_glfw_mods(e->mods, &ev, key_encoding_flags); return encode_key(&ev, output); } kitty-0.41.1/kitty/key_encoding.py0000664000175000017510000002772114773370543016474 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2017, Kovid Goyal from enum import IntEnum from functools import lru_cache from typing import NamedTuple from . import fast_data_types as defines from .fast_data_types import KeyEvent as WindowSystemKeyEvent from .key_names import character_key_name_aliases, functional_key_name_aliases from .types import ParsedShortcut # number name mappings {{{ # start csi mapping (auto generated by gen-key-constants.py do not edit) functional_key_number_to_name_map = { 57344: 'ESCAPE', 57345: 'ENTER', 57346: 'TAB', 57347: 'BACKSPACE', 57348: 'INSERT', 57349: 'DELETE', 57350: 'LEFT', 57351: 'RIGHT', 57352: 'UP', 57353: 'DOWN', 57354: 'PAGE_UP', 57355: 'PAGE_DOWN', 57356: 'HOME', 57357: 'END', 57358: 'CAPS_LOCK', 57359: 'SCROLL_LOCK', 57360: 'NUM_LOCK', 57361: 'PRINT_SCREEN', 57362: 'PAUSE', 57363: 'MENU', 57364: 'F1', 57365: 'F2', 57366: 'F3', 57367: 'F4', 57368: 'F5', 57369: 'F6', 57370: 'F7', 57371: 'F8', 57372: 'F9', 57373: 'F10', 57374: 'F11', 57375: 'F12', 57376: 'F13', 57377: 'F14', 57378: 'F15', 57379: 'F16', 57380: 'F17', 57381: 'F18', 57382: 'F19', 57383: 'F20', 57384: 'F21', 57385: 'F22', 57386: 'F23', 57387: 'F24', 57388: 'F25', 57389: 'F26', 57390: 'F27', 57391: 'F28', 57392: 'F29', 57393: 'F30', 57394: 'F31', 57395: 'F32', 57396: 'F33', 57397: 'F34', 57398: 'F35', 57399: 'KP_0', 57400: 'KP_1', 57401: 'KP_2', 57402: 'KP_3', 57403: 'KP_4', 57404: 'KP_5', 57405: 'KP_6', 57406: 'KP_7', 57407: 'KP_8', 57408: 'KP_9', 57409: 'KP_DECIMAL', 57410: 'KP_DIVIDE', 57411: 'KP_MULTIPLY', 57412: 'KP_SUBTRACT', 57413: 'KP_ADD', 57414: 'KP_ENTER', 57415: 'KP_EQUAL', 57416: 'KP_SEPARATOR', 57417: 'KP_LEFT', 57418: 'KP_RIGHT', 57419: 'KP_UP', 57420: 'KP_DOWN', 57421: 'KP_PAGE_UP', 57422: 'KP_PAGE_DOWN', 57423: 'KP_HOME', 57424: 'KP_END', 57425: 'KP_INSERT', 57426: 'KP_DELETE', 57427: 'KP_BEGIN', 57428: 'MEDIA_PLAY', 57429: 'MEDIA_PAUSE', 57430: 'MEDIA_PLAY_PAUSE', 57431: 'MEDIA_REVERSE', 57432: 'MEDIA_STOP', 57433: 'MEDIA_FAST_FORWARD', 57434: 'MEDIA_REWIND', 57435: 'MEDIA_TRACK_NEXT', 57436: 'MEDIA_TRACK_PREVIOUS', 57437: 'MEDIA_RECORD', 57438: 'LOWER_VOLUME', 57439: 'RAISE_VOLUME', 57440: 'MUTE_VOLUME', 57441: 'LEFT_SHIFT', 57442: 'LEFT_CONTROL', 57443: 'LEFT_ALT', 57444: 'LEFT_SUPER', 57445: 'LEFT_HYPER', 57446: 'LEFT_META', 57447: 'RIGHT_SHIFT', 57448: 'RIGHT_CONTROL', 57449: 'RIGHT_ALT', 57450: 'RIGHT_SUPER', 57451: 'RIGHT_HYPER', 57452: 'RIGHT_META', 57453: 'ISO_LEVEL3_SHIFT', 57454: 'ISO_LEVEL5_SHIFT'} csi_number_to_functional_number_map = { 2: 57348, 3: 57349, 5: 57354, 6: 57355, 7: 57356, 8: 57357, 9: 57346, 11: 57364, 12: 57365, 13: 57345, 14: 57367, 15: 57368, 17: 57369, 18: 57370, 19: 57371, 20: 57372, 21: 57373, 23: 57374, 24: 57375, 27: 57344, 127: 57347} letter_trailer_to_csi_number_map = {'A': 57352, 'B': 57353, 'C': 57351, 'D': 57350, 'E': 57427, 'F': 8, 'H': 7, 'P': 11, 'Q': 12, 'S': 14} tilde_trailers = {57348, 57349, 57354, 57355, 57366, 57368, 57369, 57370, 57371, 57372, 57373, 57374, 57375} # end csi mapping # }}} @lru_cache(2) def get_name_to_functional_number_map() -> dict[str, int]: return {v: k for k, v in functional_key_number_to_name_map.items()} @lru_cache(2) def get_functional_to_csi_number_map() -> dict[int, int]: return {v: k for k, v in csi_number_to_functional_number_map.items()} @lru_cache(2) def get_csi_number_to_letter_trailer_map() -> dict[int, str]: return {v: k for k, v in letter_trailer_to_csi_number_map.items()} PRESS: int = 1 REPEAT: int = 2 RELEASE: int = 4 class EventType(IntEnum): PRESS = PRESS REPEAT = REPEAT RELEASE = RELEASE @lru_cache(maxsize=128) def parse_shortcut(spec: str) -> ParsedShortcut: if spec.endswith('+'): spec = f'{spec[:-1]}plus' parts = spec.split('+') key_name = parts[-1] key_name = functional_key_name_aliases.get(key_name.upper(), key_name) is_functional_key = key_name.upper() in get_name_to_functional_number_map() if is_functional_key: key_name = key_name.upper() else: key_name = character_key_name_aliases.get(key_name.upper(), key_name) mod_val = 0 if len(parts) > 1: mods = tuple(config_mod_map.get(x.upper(), META << 8) for x in parts[:-1]) for x in mods: mod_val |= x return ParsedShortcut(mod_val, key_name) class KeyEvent(NamedTuple): type: EventType = EventType.PRESS mods: int = 0 key: str = '' text: str = '' shifted_key: str = '' alternate_key: str = '' shift: bool = False alt: bool = False ctrl: bool = False super: bool = False hyper: bool = False meta: bool = False caps_lock: bool = False num_lock: bool = False def matches(self, spec: str | ParsedShortcut, types: int = EventType.PRESS | EventType.REPEAT) -> bool: mods = self.mods_without_locks if not self.type & types: return False if isinstance(spec, str): spec = parse_shortcut(spec) if (mods, self.key) == spec: return True is_shifted = bool(self.shifted_key and self.shift) if is_shifted and (mods & ~SHIFT, self.shifted_key) == spec: return True return False def matches_without_mods(self, spec: str | ParsedShortcut, types: int = EventType.PRESS | EventType.REPEAT) -> bool: if not self.type & types: return False if isinstance(spec, str): spec = parse_shortcut(spec) return self.key == spec[1] def matches_text(self, text: str, case_sensitive: bool = False) -> bool: if case_sensitive: return self.text == text return self.text.lower() == text.lower() @property def is_release(self) -> bool: return self.type is EventType.RELEASE @property def mods_without_locks(self) -> int: return self.mods & ~(NUM_LOCK | CAPS_LOCK) @property def has_mods(self) -> bool: return bool(self.mods_without_locks) def as_window_system_event(self) -> WindowSystemKeyEvent: action = defines.GLFW_PRESS if self.type is EventType.REPEAT: action = defines.GLFW_REPEAT elif self.type is EventType.RELEASE: action = defines.GLFW_RELEASE mods = 0 if self.mods: if self.shift: mods |= defines.GLFW_MOD_SHIFT if self.alt: mods |= defines.GLFW_MOD_ALT if self.ctrl: mods |= defines.GLFW_MOD_CONTROL if self.super: mods |= defines.GLFW_MOD_SUPER if self.hyper: mods |= defines.GLFW_MOD_HYPER if self.meta: mods |= defines.GLFW_MOD_META if self.caps_lock: mods |= defines.GLFW_MOD_CAPS_LOCK if self.num_lock: mods |= defines.GLFW_MOD_NUM_LOCK fnm = get_name_to_functional_number_map() def as_num(key: str) -> int: return (fnm.get(key) or ord(key)) if key else 0 return WindowSystemKeyEvent( key=as_num(self.key), shifted_key=as_num(self.shifted_key), alternate_key=as_num(self.alternate_key), mods=mods, action=action, text=self.text) SHIFT, ALT, CTRL, SUPER, HYPER, META, CAPS_LOCK, NUM_LOCK = 1, 2, 4, 8, 16, 32, 64, 128 enter_key = KeyEvent(key='ENTER') backspace_key = KeyEvent(key='BACKSPACE') config_mod_map = { 'SHIFT': SHIFT, '⇧': SHIFT, 'ALT': ALT, 'OPTION': ALT, 'OPT': ALT, '⌥': ALT, 'SUPER': SUPER, 'COMMAND': SUPER, 'CMD': SUPER, '⌘': SUPER, 'CONTROL': CTRL, 'CTRL': CTRL, '⌃': CTRL, 'HYPER': HYPER, 'META': META, 'NUM_LOCK': NUM_LOCK, 'CAPS_LOCK': CAPS_LOCK, } def decode_key_event(csi: str, csi_type: str) -> KeyEvent: parts = csi.split(';') def get_sub_sections(x: str, missing: int = 0) -> tuple[int, ...]: return tuple(int(y) if y else missing for y in x.split(':')) first_section = get_sub_sections(parts[0]) second_section = get_sub_sections(parts[1], 1) if len(parts) > 1 else () third_section = get_sub_sections(parts[2]) if len(parts) > 2 else () mods = (second_section[0] - 1) if second_section else 0 action = second_section[1] if len(second_section) > 1 else 1 keynum = first_section[0] if csi_type in 'ABCDEHFPQRS': keynum = letter_trailer_to_csi_number_map[csi_type] def key_name(num: int) -> str: if not num: return '' if num != 13: num = csi_number_to_functional_number_map.get(num, num) ans = functional_key_number_to_name_map.get(num) else: ans = 'ENTER' if csi_type == 'u' else 'F3' if ans is None: ans = chr(num) return ans return KeyEvent( mods=mods, shift=bool(mods & SHIFT), alt=bool(mods & ALT), ctrl=bool(mods & CTRL), super=bool(mods & SUPER), hyper=bool(mods & HYPER), meta=bool(mods & META), caps_lock=bool(mods & CAPS_LOCK), num_lock=bool(mods & NUM_LOCK), key=key_name(keynum), shifted_key=key_name(first_section[1] if len(first_section) > 1 else 0), alternate_key=key_name(first_section[2] if len(first_section) > 2 else 0), type={1: EventType.PRESS, 2: EventType.REPEAT, 3: EventType.RELEASE}[action], text=''.join(map(chr, third_section)) ) def csi_number_for_name(key_name: str) -> int: if not key_name: return 0 if key_name in ('F3', 'ENTER'): return 13 fn = get_name_to_functional_number_map().get(key_name) if fn is None: return ord(key_name) return get_functional_to_csi_number_map().get(fn, fn) def encode_key_event(key_event: KeyEvent) -> str: key = csi_number_for_name(key_event.key) shifted_key = csi_number_for_name(key_event.shifted_key) alternate_key = csi_number_for_name(key_event.alternate_key) lt = get_csi_number_to_letter_trailer_map() if key_event.key == 'ENTER': trailer = 'u' else: trailer = lt.get(key, 'u') if trailer != 'u': key = 1 mods = key_event.mods text = key_event.text ans = '\033[' if key != 1 or mods or shifted_key or alternate_key or text: ans += f'{key}' if shifted_key or alternate_key: ans += ':' + (f'{shifted_key}' if shifted_key else '') if alternate_key: ans += f':{alternate_key}' action = 1 if key_event.type is EventType.REPEAT: action = 2 elif key_event.type is EventType.RELEASE: action = 3 if mods or action > 1 or text: m = 0 if key_event.shift: m |= 1 if key_event.alt: m |= 2 if key_event.ctrl: m |= 4 if key_event.super: m |= 8 if key_event.hyper: m |= 16 if key_event.meta: m |= 32 if key_event.caps_lock: m |= 64 if key_event.num_lock: m |= 128 if action > 1 or m: ans += f';{m+1}' if action > 1: ans += f':{action}' elif text: ans += ';' if text: ans += ';' + ':'.join(map(str, map(ord, text))) fn = get_name_to_functional_number_map().get(key_event.key) if fn is not None and fn in tilde_trailers: trailer = '~' return ans + trailer def decode_key_event_as_window_system_key(text: str) -> WindowSystemKeyEvent | None: csi, trailer = text[2:-1], text[-1] try: k = decode_key_event(csi, trailer) except Exception: return None return k.as_window_system_event() kitty-0.41.1/kitty/key_names.py0000664000175000017510000000504614773370543016005 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2019, Kovid Goyal import sys from collections.abc import Callable from contextlib import suppress from typing import Optional from .constants import is_macos functional_key_name_aliases: dict[str, str] = { 'ESC': 'ESCAPE', 'PGUP': 'PAGE_UP', 'PAGEUP': 'PAGE_UP', 'PGDN': 'PAGE_DOWN', 'PAGEDOWN': 'PAGE_DOWN', 'RETURN': 'ENTER', 'ARROWUP': 'UP', 'ARROWDOWN': 'DOWN', 'ARROWRIGHT': 'RIGHT', 'ARROWLEFT': 'LEFT', 'DEL': 'DELETE', 'KP_PLUS': 'KP_ADD', 'KP_MINUS': 'KP_SUBTRACT', } character_key_name_aliases: dict[str, str] = { 'SPC': ' ', 'SPACE': ' ', 'STAR': '*', 'MULTIPLY': '*', 'PLUS': '+', 'MINUS': '-', 'BAR': '|', 'PIPE': '|', 'HYPHEN': '-', 'EQUAL': '=', 'UNDERSCORE': '_', 'COMMA': ',', 'PERIOD': '.', 'DOT': '.', 'SLASH': '/', 'BACKSLASH': '\\', 'TILDE': '~', 'GRAVE': '`', 'GRAVE_ACCENT': '`', 'APOSTROPHE': "'", 'SEMICOLON': ';', 'COLON': ':', 'LEFT_BRACKET': '[', 'RIGHT_BRACKET': ']', } LookupFunc = Callable[[str, bool], Optional[int]] def null_lookup(name: str, case_sensitive: bool = False) -> int | None: return None if is_macos: def get_key_name_lookup() -> LookupFunc: return null_lookup else: def load_libxkb_lookup() -> LookupFunc: import ctypes for suffix in ('.0', ''): with suppress(Exception): lib = ctypes.CDLL(f'libxkbcommon.so{suffix}') break else: from ctypes.util import find_library lname = find_library('xkbcommon') if lname is None: raise RuntimeError('Failed to find libxkbcommon') lib = ctypes.CDLL(lname) f = lib.xkb_keysym_from_name f.argtypes = [ctypes.c_char_p, ctypes.c_int] f.restype = ctypes.c_int def xkb_lookup(name: str, case_sensitive: bool = False) -> int | None: q = name.encode('utf-8') return f(q, int(case_sensitive)) or None return xkb_lookup def get_key_name_lookup() -> LookupFunc: ans: LookupFunc | None = getattr(get_key_name_lookup, 'ans', None) if ans is None: try: ans = load_libxkb_lookup() except Exception as e: print('Failed to load libxkbcommon.xkb_keysym_from_name with error:', e, file=sys.stderr) ans = null_lookup setattr(get_key_name_lookup, 'ans', ans) return ans kitty-0.41.1/kitty/keys.c0000664000175000017510000005713014773370543014600 0ustar nileshnilesh/* * keys.c * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "state.h" #include "keys.h" #include "screen.h" #include "glfw-wrapper.h" #include #ifndef __APPLE__ #include #endif // python KeyEvent object {{{ typedef struct { PyObject_HEAD PyObject *key, *shifted_key, *alternate_key; PyObject *mods, *action, *native_key, *ime_state; PyObject *text; } PyKeyEvent; static PyObject* convert_glfw_key_event_to_python(const GLFWkeyevent *ev); static PyObject* new_keyevent_object(PyTypeObject *type UNUSED, PyObject *args, PyObject *kw) { static char *kwds[] = {"key", "shifted_key", "alternate_key", "mods", "action", "native_key", "ime_state", "text", NULL}; GLFWkeyevent ev = {.action=GLFW_PRESS}; if (!PyArg_ParseTupleAndKeywords(args, kw, "I|IIiiiiz", kwds, &ev.key, &ev.shifted_key, &ev.alternate_key, &ev.mods, &ev.action, &ev.native_key, &ev.ime_state, &ev.text)) return NULL; return convert_glfw_key_event_to_python(&ev); } bool is_modifier_key(const uint32_t key) { START_ALLOW_CASE_RANGE switch (key) { case GLFW_FKEY_LEFT_SHIFT ... GLFW_FKEY_ISO_LEVEL5_SHIFT: case GLFW_FKEY_CAPS_LOCK: case GLFW_FKEY_SCROLL_LOCK: case GLFW_FKEY_NUM_LOCK: return true; default: return false; } END_ALLOW_CASE_RANGE } static bool is_no_action_key(const uint32_t key, const uint32_t native_key) { switch (native_key) { #ifndef __APPLE__ case XKB_KEY_XF86Fn: case XKB_KEY_XF86WakeUp: return true; #endif default: return is_modifier_key(key); } } static void dealloc(PyKeyEvent* self) { Py_CLEAR(self->key); Py_CLEAR(self->shifted_key); Py_CLEAR(self->alternate_key); Py_CLEAR(self->mods); Py_CLEAR(self->action); Py_CLEAR(self->native_key); Py_CLEAR(self->ime_state); Py_CLEAR(self->text); Py_TYPE(self)->tp_free((PyObject*)self); } static PyMemberDef members[] = { #define M(x) {#x, T_OBJECT, offsetof(PyKeyEvent, x), READONLY, #x} M(key), M(shifted_key), M(alternate_key), M(mods), M(action), M(native_key), M(ime_state), M(text), {NULL}, #undef M }; PyTypeObject PyKeyEvent_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.KeyEvent", .tp_basicsize = sizeof(PyKeyEvent), .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "A key event", .tp_members = members, .tp_new = new_keyevent_object, }; static PyObject* convert_glfw_key_event_to_python(const GLFWkeyevent *ev) { PyKeyEvent *self = (PyKeyEvent*)PyKeyEvent_Type.tp_alloc(&PyKeyEvent_Type, 0); if (!self) return NULL; #define C(x) { unsigned long t = ev->x; self->x = PyLong_FromUnsignedLong(t); if (self->x == NULL) { Py_CLEAR(self); return NULL; } } C(key); C(shifted_key); C(alternate_key); C(mods); C(action); C(native_key); C(ime_state); #undef C self->text = PyUnicode_FromString(ev->text ? ev->text : ""); if (!self->text) { Py_CLEAR(self); return NULL; } return (PyObject*)self; } // }}} static Window* active_window(void) { Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; Window *w = t->windows + t->active_window; if (!w->render_data.screen) return NULL; if (w->redirect_keys_to_overlay) { for (unsigned i = 0; i < t->num_windows; i++) { if (t->windows[i].id == w->redirect_keys_to_overlay && w->render_data.screen) return t->windows + i; } } return w; } void update_ime_focus(OSWindow *osw, bool focused) { if (!osw || !osw->handle) return; GLFWIMEUpdateEvent ev = { .focused = focused, .type = GLFW_IME_UPDATE_FOCUS }; glfwUpdateIMEState(osw->handle, &ev); } void prepare_ime_position_update_event(OSWindow *osw, Window *w, Screen *screen, GLFWIMEUpdateEvent *ev) { unsigned int cell_width = osw->fonts_data->fcm.cell_width, cell_height = osw->fonts_data->fcm.cell_height; unsigned int left = w->geometry.left, top = w->geometry.top; if (screen_is_overlay_active(screen)) { left += screen->overlay_line.cursor_x * cell_width; top += MIN(screen->overlay_line.ynum + screen->scrolled_by, screen->lines - 1) * cell_height; } else { left += screen->cursor->x * cell_width; top += screen->cursor->y * cell_height; } ev->cursor.left = left; ev->cursor.top = top; ev->cursor.width = cell_width; ev->cursor.height = cell_height; } void update_ime_position(Window* w UNUSED, Screen *screen UNUSED) { GLFWIMEUpdateEvent ev = { .type = GLFW_IME_UPDATE_CURSOR_POSITION }; #ifndef __APPLE__ prepare_ime_position_update_event(global_state.callback_os_window, w, screen, &ev); #endif glfwUpdateIMEState(global_state.callback_os_window->handle, &ev); } const char* format_mods(unsigned mods) { static char buf[128]; char *p = buf, *s; #define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, x) pr("mods: "); s = p; if (mods & GLFW_MOD_CONTROL) pr("ctrl+"); if (mods & GLFW_MOD_ALT) pr("alt+"); if (mods & GLFW_MOD_SHIFT) pr("shift+"); if (mods & GLFW_MOD_SUPER) pr("super+"); if (mods & GLFW_MOD_HYPER) pr("hyper+"); if (mods & GLFW_MOD_META) pr("meta+"); if (mods & GLFW_MOD_CAPS_LOCK) pr("capslock+"); if (mods & GLFW_MOD_NUM_LOCK) pr("numlock+"); if (p == s) pr("none"); else p--; pr(" "); #undef pr return buf; } static void send_key_to_child(id_type window_id, Screen *screen, const GLFWkeyevent *ev) { const int action = ev->action; const uint32_t key = ev->key, native_key = ev->native_key; const char *text = ev->text ? ev->text : ""; if (action == GLFW_REPEAT && !screen->modes.mDECARM) { debug("discarding repeat key event as DECARM is off\n"); return; } if (screen->scrolled_by && action == GLFW_PRESS && !is_no_action_key(key, native_key)) { screen_history_scroll(screen, SCROLL_FULL, false); // scroll back to bottom } char encoded_key[KEY_BUFFER_SIZE] = {0}; int size = encode_glfw_key_event(ev, screen->modes.mDECCKM, screen_current_key_encoding_flags(screen), encoded_key); if (size == SEND_TEXT_TO_CHILD) { schedule_write_to_child(window_id, 1, text, strlen(text)); debug("sent key as text to child (window_id: %llu): %s\n", window_id, text); } else if (size > 0) { if (size == 1 && screen->modes.mHANDLE_TERMIOS_SIGNALS) { if (screen_send_signal_for_key(screen, *encoded_key)) return; } schedule_write_to_child(window_id, 1, encoded_key, size); if (OPT(debug_keyboard)) { debug("sent encoded key to child (window_id: %llu): ", window_id); for (int ki = 0; ki < size; ki++) { if (encoded_key[ki] == 27) { debug("^[ "); } else if (encoded_key[ki] == ' ') { debug("SPC "); } else if (isprint(encoded_key[ki])) { debug("%c ", encoded_key[ki]); } else { debug("0x%x ", encoded_key[ki]); } } debug("\n"); } } else { debug("ignoring as keyboard mode does not support encoding this event\n"); } } void dispatch_buffered_keys(Window *w) { if (!w->render_data.screen || !w->buffered_keys.count) return; GLFWkeyevent *keys = w->buffered_keys.key_data; for (size_t i = 0; i < w->buffered_keys.count; i++) { debug("Sending previously buffered key "); send_key_to_child(w->id, w->render_data.screen, keys + i); } free(w->buffered_keys.key_data); zero_at_ptr(&w->buffered_keys); } void on_key_input(const GLFWkeyevent *ev) { Window *w = active_window(); const int action = ev->action, mods = ev->mods; const uint32_t key = ev->key, native_key = ev->native_key; const char *text = ev->text ? ev->text : ""; if (OPT(debug_keyboard)) { if (!key && !native_key && text[0]) { debug("\x1b[33mon_IME_input\x1b[m: text: %s ", text); } else { debug("\x1b[33mon_key_input\x1b[m: glfw key: 0x%x native_code: 0x%x action: %s %stext: '%s' state: %d ", key, native_key, (action == GLFW_RELEASE ? "RELEASE" : (action == GLFW_PRESS ? "PRESS" : "REPEAT")), format_mods(mods), text, ev->ime_state); } } if (!w) { debug("no active window, ignoring\n"); return; } send_pending_click_to_window(w, -1); if (OPT(mouse_hide_wait) < 0 && !is_no_action_key(key, native_key)) hide_mouse(global_state.callback_os_window); Screen *screen = w->render_data.screen; id_type active_window_id = w->id; switch(ev->ime_state) { case GLFW_IME_WAYLAND_DONE_EVENT: // If we update IME position here it sends GNOME's text input system into // an infinite loop. See https://github.com/kovidgoyal/kitty/issues/5105 // and also: https://github.com/kovidgoyal/kitty/pull/7283 screen_update_overlay_text(screen, text); debug("handled wayland IME done event\n"); return; case GLFW_IME_PREEDIT_CHANGED: screen_update_overlay_text(screen, text); update_ime_position(w, screen); debug("updated pre-edit text: '%s'\n", text); return; case GLFW_IME_COMMIT_TEXT: if (*text) { schedule_write_to_child(w->id, 1, text, strlen(text)); debug("committed pre-edit text: %s sent to child as text.\n", text); } else debug("committed pre-edit text: (null)\n"); screen_update_overlay_text(screen, NULL); return; case GLFW_IME_NONE: // for macOS, update ime position on every key input // because the position is required before next input // On Linux this is needed by Fig integration: https://github.com/kovidgoyal/kitty/issues/5241 update_ime_position(w, screen); break; default: debug("invalid state, ignoring\n"); return; } bool dispatch_ok = true, consumed = false; #define dispatch_key_event(name) { \ PyObject *ke = NULL, *ret = NULL; \ ke = convert_glfw_key_event_to_python(ev); if (!ke) { PyErr_Print(); return; }; \ ret = PyObject_CallMethod(global_state.boss, #name, "O", ke); Py_CLEAR(ke); \ if (ret == NULL) { PyErr_Print(); dispatch_ok = false; } \ else { consumed = ret == Py_True; Py_CLEAR(ret); } \ w = window_for_window_id(active_window_id); \ } if (action == GLFW_PRESS || action == GLFW_REPEAT) { w->last_special_key_pressed = 0; dispatch_key_event(dispatch_possible_special_key); if (dispatch_ok) { if (consumed) { debug("handled as shortcut\n"); if (w) w->last_special_key_pressed = key; return; } } if (!w) return; screen = w->render_data.screen; } else if (w->last_special_key_pressed == key) { w->last_special_key_pressed = 0; debug("ignoring release event for previous press that was handled as shortcut\n"); return; } if (w->buffered_keys.enabled) { if (w->buffered_keys.capacity < w->buffered_keys.count + 1) { w->buffered_keys.capacity = MAX(16u, w->buffered_keys.capacity + 8); GLFWkeyevent *new = malloc(w->buffered_keys.capacity * sizeof(GLFWkeyevent)); if (!new) fatal("Out of memory"); memcpy(new, w->buffered_keys.key_data, w->buffered_keys.count * sizeof(new[0])); w->buffered_keys.key_data = new; } GLFWkeyevent *k = w->buffered_keys.key_data; k[w->buffered_keys.count++] = *ev; debug("bufferring key until child is ready\n"); } else send_key_to_child(w->id, screen, ev); #undef dispatch_key_event } void fake_scroll(Window *w, int amount, bool upwards) { if (!w) return; int key = upwards ? GLFW_FKEY_UP : GLFW_FKEY_DOWN; GLFWkeyevent ev = {.key = key }; char encoded_key[KEY_BUFFER_SIZE] = {0}; Screen *screen = w->render_data.screen; uint8_t flags = screen_current_key_encoding_flags(screen); while (amount-- > 0) { ev.action = GLFW_PRESS; int size = encode_glfw_key_event(&ev, screen->modes.mDECCKM, flags, encoded_key); if (size > 0) schedule_write_to_child(w->id, 1, encoded_key, size); ev.action = GLFW_RELEASE; size = encode_glfw_key_event(&ev, screen->modes.mDECCKM, flags, encoded_key); if (size > 0) schedule_write_to_child(w->id, 1, encoded_key, size); } } #define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args) #define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL; #define M(name, arg_type) {#name, (PyCFunction)(void (*) (void))(py##name), arg_type, NULL} PYWRAP1(key_for_native_key_name) { const char *name; int case_sensitive = 0; PA("s|p", &name, &case_sensitive); #ifndef __APPLE__ if (glfwGetNativeKeyForName) { // if this function is called before GLFW is initialized glfwGetNativeKeyForName will be NULL int native_key = glfwGetNativeKeyForName(name, case_sensitive); if (native_key) return Py_BuildValue("i", native_key); } #endif Py_RETURN_NONE; } static PyObject* pyencode_key_for_tty(PyObject *self UNUSED, PyObject *args, PyObject *kw) { static char *kwds[] = {"key", "shifted_key", "alternate_key", "mods", "action", "key_encoding_flags", "text", "cursor_key_mode", NULL}; unsigned int key = 0, shifted_key = 0, alternate_key = 0, mods = 0, action = GLFW_PRESS, key_encoding_flags = 0; const char *text = NULL; int cursor_key_mode = 0; if (!PyArg_ParseTupleAndKeywords(args, kw, "I|IIIIIzp", kwds, &key, &shifted_key, &alternate_key, &mods, &action, &key_encoding_flags, &text, &cursor_key_mode)) return NULL; GLFWkeyevent ev = { .key = key, .shifted_key = shifted_key, .alternate_key = alternate_key, .text = text, .action = action, .mods = mods }; char output[KEY_BUFFER_SIZE+1] = {0}; int num = encode_glfw_key_event(&ev, cursor_key_mode, key_encoding_flags, output); if (num == SEND_TEXT_TO_CHILD) return PyUnicode_FromString(text); return PyUnicode_FromStringAndSize(output, MAX(0, num)); } static PyObject* pyinject_key(PyObject *self UNUSED, PyObject *args, PyObject *kw) { static char *kwds[] = {"key", "shifted_key", "alternate_key", "mods", "action", "text", "os_window_id", NULL}; unsigned int key = 0, shifted_key = 0, alternate_key = 0, mods = 0, action = GLFW_PRESS; unsigned long long os_window_id = 0; const char *text = NULL; if (!PyArg_ParseTupleAndKeywords(args, kw, "I|IIIIzK", kwds, &key, &shifted_key, &alternate_key, &mods, &action, &text, &os_window_id)) return NULL; id_type orig = global_state.callback_os_window ? global_state.callback_os_window->id : 0; bool found = false; if (os_window_id) { for (size_t i = 0; i < global_state.num_os_windows && !found; i++) { if (global_state.os_windows[i].id == os_window_id) { global_state.callback_os_window = global_state.os_windows + i; found = true; } } if (!found) { PyErr_Format(PyExc_IndexError, "Could not find OS Window with id: %llu", os_window_id); return NULL; } } else { if (!global_state.callback_os_window) { for (size_t i = 0; i < global_state.num_os_windows && !found; i++) { if (global_state.os_windows[i].is_focused) { global_state.callback_os_window = global_state.os_windows + i; found = true; } } if (!found && ! global_state.num_os_windows) { PyErr_SetString(PyExc_Exception, "No OS Windows available to inject key presses into"); return NULL; } global_state.callback_os_window = global_state.os_windows; found = true; } } GLFWkeyevent ev = { .key = key, .shifted_key = shifted_key, .alternate_key = alternate_key, .text = text, .action = action, .mods = mods }; on_key_input(&ev); if (orig) { found = false; for (size_t i = 0; i < global_state.num_os_windows && !found; i++) { if (global_state.os_windows[i].id == orig) { global_state.callback_os_window = global_state.os_windows + i; found = true; } } if (!found) global_state.callback_os_window = NULL; } else global_state.callback_os_window = NULL; Py_RETURN_NONE; } static PyObject* pyis_modifier_key(PyObject *self UNUSED, PyObject *a) { unsigned long key = PyLong_AsUnsignedLong(a); if (PyErr_Occurred()) return NULL; if (is_modifier_key(key)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static PyMethodDef module_methods[] = { M(key_for_native_key_name, METH_VARARGS), M(encode_key_for_tty, METH_VARARGS | METH_KEYWORDS), M(inject_key, METH_VARARGS | METH_KEYWORDS), M(is_modifier_key, METH_O), {0} }; // SingleKey {{{ typedef uint64_t keybitfield; #define KEY_BITS 51 #define MOD_BITS 12 #if 1 << (MOD_BITS-1) < GLFW_MOD_KITTY #error "Not enough mod bits" #endif typedef union Key { struct { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ keybitfield mods : MOD_BITS; keybitfield is_native: 1; keybitfield key : KEY_BITS; #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ keybitfield key : KEY_BITS; keybitfield is_native: 1; keybitfield mods : MOD_BITS; #else #error "Unsupported endianness" #endif }; keybitfield val; } Key; static PyTypeObject SingleKey_Type; static char *SingleKey_kwds[] = {"mods", "is_native", "key", NULL}; typedef struct { PyObject_HEAD Key key; bool defined_with_kitty_mod; } SingleKey; static inline void SingleKey_set_vals(SingleKey *self, long long key, unsigned short mods, int is_native) { if (key >= 0 && (unsigned long long)key <= BIT_MASK(keybitfield, KEY_BITS)) { keybitfield k = (keybitfield)(unsigned long long)key; self->key.key = k & BIT_MASK(keybitfield, KEY_BITS); } if (!(mods & 1 << (MOD_BITS + 1))) self->key.mods = mods & BIT_MASK(uint32_t, MOD_BITS); if (is_native > -1) self->key.is_native = is_native ? 1 : 0; } static PyObject * SingleKey_new(PyTypeObject *type, PyObject *args, PyObject *kw) { long long key = -1; unsigned short mods = 1 << (MOD_BITS + 1); int is_native = -1; if (!PyArg_ParseTupleAndKeywords(args, kw, "|HpL", SingleKey_kwds, &mods, &is_native, &key)) return NULL; SingleKey *self = (SingleKey *)type->tp_alloc(type, 0); if (self) SingleKey_set_vals(self, key, mods, is_native); return (PyObject*)self; } static void SingleKey_dealloc(SingleKey* self) { Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject* SingleKey_repr(PyObject *s) { SingleKey *self = (SingleKey*)s; char buf[128]; int pos = 0; pos += PyOS_snprintf(buf + pos, sizeof(buf) - pos, "SingleKey("); unsigned int mods = self->key.mods; if (mods) pos += PyOS_snprintf(buf + pos, sizeof(buf) - pos, "mods=%u, ", mods); if (self->key.is_native) pos += PyOS_snprintf(buf + pos, sizeof(buf) - pos, "is_native=True, "); unsigned long long key = self->key.key; if (key) pos += PyOS_snprintf(buf + pos, sizeof(buf) - pos, "key=%llu, ", key); if (buf[pos-1] == ' ') pos -= 2; pos += PyOS_snprintf(buf + pos, sizeof(buf) - pos, ")"); return PyUnicode_FromString(buf); } static PyObject* SingleKey_get_key(SingleKey *self, void UNUSED *closure) { const unsigned long long val = self->key.key; return PyLong_FromUnsignedLongLong(val); } static PyObject* SingleKey_get_mods(SingleKey *self, void UNUSED *closure) { const unsigned long mods = self->key.mods; return PyLong_FromUnsignedLong(mods); } static PyObject* SingleKey_get_is_native(SingleKey *self, void UNUSED *closure) { if (self->key.is_native) Py_RETURN_TRUE; Py_RETURN_FALSE; } static PyObject* SingleKey_defined_with_kitty_mod(SingleKey *self, void UNUSED *closure) { if (self->defined_with_kitty_mod || (self->key.mods & GLFW_MOD_KITTY)) Py_RETURN_TRUE; Py_RETURN_FALSE; } static PyGetSetDef SingleKey_getsetters[] = { {"key", (getter)SingleKey_get_key, NULL, "The key as an integer", NULL}, {"mods", (getter)SingleKey_get_mods, NULL, "The modifiers as an integer", NULL}, {"is_native", (getter)SingleKey_get_is_native, NULL, "A bool", NULL}, {"defined_with_kitty_mod", (getter)SingleKey_defined_with_kitty_mod, NULL, "A bool", NULL}, {NULL} /* Sentinel */ }; static Py_hash_t SingleKey_hash(PyObject *self) { Py_hash_t ans = ((SingleKey*)self)->key.val; if (ans == -1) ans = -2; return ans; } static PyObject* SingleKey_richcompare(PyObject *self, PyObject *other, int op) { if (!PyObject_TypeCheck(other, &SingleKey_Type)) { PyErr_SetString(PyExc_TypeError, "Cannot compare SingleKey to other objects"); return NULL; } SingleKey *a = (SingleKey*)self, *b = (SingleKey*)other; Py_RETURN_RICHCOMPARE(a->key.val, b->key.val, op); } static Py_ssize_t SingleKey___len__(PyObject *self UNUSED) { return 3; } static PyObject * SingleKey_item(PyObject *o, Py_ssize_t i) { SingleKey *self = (SingleKey*)o; switch(i) { case 0: return SingleKey_get_mods(self, NULL); case 1: return SingleKey_get_is_native(self, NULL); case 2: return SingleKey_get_key(self, NULL); } PyErr_SetString(PyExc_IndexError, "tuple index out of range"); return NULL; } static PySequenceMethods SingleKey_sequence_methods = { .sq_length = SingleKey___len__, .sq_item = SingleKey_item, }; static PyObject* SingleKey_resolve_kitty_mod(SingleKey *self, PyObject *km) { if (!(self->key.mods & GLFW_MOD_KITTY)) { Py_INCREF(self); return (PyObject*)self; } unsigned long kitty_mod = PyLong_AsUnsignedLong(km); if (PyErr_Occurred()) return NULL; SingleKey *ans = (SingleKey*)SingleKey_Type.tp_alloc(&SingleKey_Type, 0); if (!ans) return NULL; ans->key.val = self->key.val; ans->key.mods = (ans->key.mods & ~GLFW_MOD_KITTY) | kitty_mod; ans->defined_with_kitty_mod = true; return (PyObject*)ans; } static PyObject* SingleKey_replace(SingleKey *self, PyObject *args, PyObject *kw) { long long key = -2; unsigned short mods = 1 << (MOD_BITS + 1); int is_native = -1; if (!PyArg_ParseTupleAndKeywords(args, kw, "|HpL", SingleKey_kwds, &mods, &is_native, &key)) return NULL; SingleKey *ans = (SingleKey*)SingleKey_Type.tp_alloc(&SingleKey_Type, 0); if (ans) { if (key == -1) key = 0; ans->key.val = self->key.val; SingleKey_set_vals(ans, key, mods, is_native); } return (PyObject*)ans; } static PyMethodDef SingleKey_methods[] = { {"_replace", (PyCFunction)(void (*) (void))SingleKey_replace, METH_VARARGS | METH_KEYWORDS, ""}, {"resolve_kitty_mod", (PyCFunction)SingleKey_resolve_kitty_mod, METH_O, ""}, {NULL} /* Sentinel */ }; static PyTypeObject SingleKey_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.SingleKey", .tp_basicsize = sizeof(SingleKey), .tp_dealloc = (destructor)SingleKey_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "Compact and fast representation of a single key as defined in the config", .tp_new = SingleKey_new, .tp_hash = SingleKey_hash, .tp_richcompare = SingleKey_richcompare, .tp_as_sequence = &SingleKey_sequence_methods, .tp_repr = SingleKey_repr, .tp_methods = SingleKey_methods, .tp_getset = SingleKey_getsetters, }; // }}} bool init_keys(PyObject *module) { if (PyModule_AddFunctions(module, module_methods) != 0) return false; if (PyType_Ready(&PyKeyEvent_Type) < 0) return false; if (PyModule_AddObject(module, "KeyEvent", (PyObject *)&PyKeyEvent_Type) != 0) return 0; Py_INCREF(&PyKeyEvent_Type); ADD_TYPE(SingleKey); return true; } kitty-0.41.1/kitty/keys.h0000664000175000017510000000071514773370543014602 0ustar nileshnilesh/* * keys.h * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" #include "glfw-wrapper.h" #include #define KEY_BUFFER_SIZE 128 #define SEND_TEXT_TO_CHILD INT_MIN #define debug debug_input int encode_glfw_key_event(const GLFWkeyevent *e, const bool cursor_key_mode, const unsigned flags, char *output); bool is_modifier_key(const uint32_t key); kitty-0.41.1/kitty/keys.py0000664000175000017510000002406514773370543015007 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal from collections.abc import Callable, Iterable, Iterator from gettext import gettext as _ from typing import TYPE_CHECKING, Any, Optional from .constants import is_macos from .fast_data_types import ( GLFW_MOD_ALT, GLFW_MOD_CONTROL, GLFW_MOD_HYPER, GLFW_MOD_META, GLFW_MOD_SHIFT, GLFW_MOD_SUPER, KeyEvent, SingleKey, get_boss, get_options, is_modifier_key, ring_bell, set_ignore_os_keyboard_processing, ) from .options.types import Options from .options.utils import KeyboardMode, KeyDefinition, KeyMap from .typing import ScreenType if TYPE_CHECKING: from .window import Window mod_mask = GLFW_MOD_ALT | GLFW_MOD_CONTROL | GLFW_MOD_SHIFT | GLFW_MOD_SUPER | GLFW_MOD_META | GLFW_MOD_HYPER def keyboard_mode_name(screen: ScreenType) -> str: flags = screen.current_key_encoding_flags() if flags: return 'kitty' return 'application' if screen.cursor_key_mode else 'normal' def get_shortcut(keymap: KeyMap, ev: KeyEvent) -> list[KeyDefinition] | None: mods = ev.mods & mod_mask ans = keymap.get(SingleKey(mods, False, ev.key)) if ans is None and ev.shifted_key and mods & GLFW_MOD_SHIFT: ans = keymap.get(SingleKey(mods & (~GLFW_MOD_SHIFT), False, ev.shifted_key)) if ans is None: ans = keymap.get(SingleKey(mods, True, ev.native_key)) return ans def shortcut_matches(s: SingleKey, ev: KeyEvent) -> bool: mods = ev.mods & mod_mask smods = s.mods & mod_mask if s.is_native: return s.key == ev.native_key and smods == mods if s.key == ev.key and mods == smods: return True if ev.shifted_key and mods & GLFW_MOD_SHIFT and (mods & ~GLFW_MOD_SHIFT) == smods and ev.shifted_key == s.key: return True return False class Mappings: ' Manage all keyboard mappings ' def __init__(self, global_shortcuts:dict[str, SingleKey] | None = None, callback_on_mode_change: Callable[[], Any] = lambda: None) -> None: self.keyboard_mode_stack: list[KeyboardMode] = [] self.update_keymap(global_shortcuts) self.callback_on_mode_change = callback_on_mode_change @property def current_keyboard_mode_name(self) -> str: return self.keyboard_mode_stack[-1].name if self.keyboard_mode_stack else '' def update_keymap(self, global_shortcuts: dict[str, SingleKey] | None = None) -> None: if global_shortcuts is None: global_shortcuts = self.set_cocoa_global_shortcuts(self.get_options()) if is_macos else {} self.global_shortcuts_map: KeyMap = {v: [KeyDefinition(definition=k)] for k, v in global_shortcuts.items()} self.global_shortcuts = global_shortcuts self.keyboard_modes = self.get_options().keyboard_modes.copy() km = self.keyboard_modes[''].keymap self.keyboard_modes[''].keymap = km = km.copy() for sc in self.global_shortcuts.values(): km.pop(sc, None) def clear_keyboard_modes(self) -> None: had_mode = bool(self.keyboard_mode_stack) self.keyboard_mode_stack = [] self.set_ignore_os_keyboard_processing(False) if had_mode: self.callback_on_mode_change() def pop_keyboard_mode(self) -> bool: passthrough = True if self.keyboard_mode_stack: self.keyboard_mode_stack.pop() if not self.keyboard_mode_stack: self.set_ignore_os_keyboard_processing(False) passthrough = False self.callback_on_mode_change() return passthrough def pop_keyboard_mode_if_is(self, name: str) -> bool: if self.keyboard_mode_stack and self.keyboard_mode_stack[-1].name == name: return self.pop_keyboard_mode() return False def _push_keyboard_mode(self, mode: KeyboardMode) -> None: self.keyboard_mode_stack.append(mode) self.set_ignore_os_keyboard_processing(True) self.callback_on_mode_change() def push_keyboard_mode(self, new_mode: str) -> None: mode = self.keyboard_modes[new_mode] self._push_keyboard_mode(mode) def matching_key_actions(self, candidates: Iterable[KeyDefinition]) -> list[KeyDefinition]: w = self.get_active_window() matches = [] has_sequence_match = False for x in candidates: is_applicable = False if x.options.when_focus_on: try: if w and w in self.match_windows(x.options.when_focus_on): is_applicable = True except Exception: self.clear_keyboard_modes() self.show_error(_('Invalid key mapping'), _( 'The match expression {0} is not valid for {1}').format(x.options.when_focus_on, '--when-focus-on')) return [] else: is_applicable = True if is_applicable: matches.append(x) if x.is_sequence: has_sequence_match = True if has_sequence_match: last_terminal_idx = -1 for i, x in enumerate(matches): if not x.rest: last_terminal_idx = i if last_terminal_idx > -1: if last_terminal_idx == len(matches) -1: matches = matches[last_terminal_idx:] else: matches = matches[last_terminal_idx+1:] q = matches[-1].options.when_focus_on matches = [x for x in matches if x.options.when_focus_on == q] elif matches: matches = [matches[-1]] return matches def dispatch_possible_special_key(self, ev: KeyEvent) -> bool: # Handles shortcuts, return True if the key was consumed is_root_mode = not self.keyboard_mode_stack mode = self.keyboard_modes[''] if is_root_mode else self.keyboard_mode_stack[-1] key_action = get_shortcut(mode.keymap, ev) if key_action is None: if is_modifier_key(ev.key): return False if self.global_shortcuts_map and get_shortcut(self.global_shortcuts_map, ev): return True if not is_root_mode: if mode.sequence_keys is not None: self.pop_keyboard_mode() w = self.get_active_window() if w is not None: w.send_key_sequence(*mode.sequence_keys) return False if mode.on_unknown in ('beep', 'ignore'): if mode.on_unknown == 'beep': self.ring_bell() return True if mode.on_unknown == 'passthrough': return False if not self.pop_keyboard_mode(): self.ring_bell() return True else: final_actions = self.matching_key_actions(key_action) if final_actions: mode_pos = len(self.keyboard_mode_stack) - 1 if final_actions[0].is_sequence: if mode.sequence_keys is None: sm = KeyboardMode('__sequence__') sm.on_action = 'end' sm.sequence_keys = [ev] for fa in final_actions: sm.keymap[fa.rest[0]].append(fa.shift_sequence_and_copy()) self._push_keyboard_mode(sm) self.debug_print('\n\x1b[35mKeyPress\x1b[m matched sequence prefix, ', end='') else: if len(final_actions) == 1 and not final_actions[0].rest: self.pop_keyboard_mode() consumed = self.combine(final_actions[0].definition) if not consumed: w = self.get_active_window() if w is not None: w.send_key_sequence(*mode.sequence_keys) return consumed mode.sequence_keys.append(ev) self.debug_print('\n\x1b[35mKeyPress\x1b[m matched sequence prefix, ', end='') mode.keymap.clear() for fa in final_actions: mode.keymap[fa.rest[0]].append(fa.shift_sequence_and_copy()) return True final_action = final_actions[0] consumed = self.combine(final_action.definition) if consumed and not is_root_mode and mode.on_action == 'end': if mode_pos < len(self.keyboard_mode_stack) and self.keyboard_mode_stack[mode_pos] is mode: del self.keyboard_mode_stack[mode_pos] self.callback_on_mode_change() if not self.keyboard_mode_stack: self.set_ignore_os_keyboard_processing(False) return consumed return False # System integration {{{ def get_active_window(self) -> Optional['Window']: return get_boss().active_window def match_windows(self, expr: str) -> Iterator['Window']: return get_boss().match_windows(expr) def show_error(self, title: str, msg: str) -> None: return get_boss().show_error(title, msg) def ring_bell(self) -> None: if self.get_options().enable_audio_bell: ring_bell() def combine(self, action_definition: str) -> bool: return get_boss().combine(action_definition) def set_ignore_os_keyboard_processing(self, on: bool) -> None: set_ignore_os_keyboard_processing(on) def get_options(self) -> Options: return get_options() def debug_print(self, *args: Any, end: str = '\n') -> None: b = get_boss() if b.args.debug_keyboard: print(*args, end=end, flush=True) def set_cocoa_global_shortcuts(self, opts: Options) -> dict[str, SingleKey]: from .main import set_cocoa_global_shortcuts return set_cocoa_global_shortcuts(opts) # }}} kitty-0.41.1/kitty/kittens.c0000664000175000017510000001617214773370543015307 0ustar nileshnilesh/* * kittens.c * Copyright (C) 2018 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "data-types.h" #include "monotonic.h" #define CMD_BUF_SZ 2048 static bool append_buf(char buf[CMD_BUF_SZ], size_t *pos, PyObject *ans) { if (*pos) { PyObject *bytes = PyBytes_FromStringAndSize(buf, *pos); if (!bytes) { PyErr_NoMemory(); return false; } int ret = PyList_Append(ans, bytes); Py_CLEAR(bytes); if (ret != 0) return false; *pos = 0; } return true; } static bool add_char(char buf[CMD_BUF_SZ], size_t *pos, char ch, PyObject *ans) { if (*pos >= CMD_BUF_SZ) { if (!append_buf(buf, pos, ans)) return false; } buf[*pos] = ch; *pos += 1; return true; } static bool read_response(int fd, monotonic_t timeout, PyObject *ans) { static char buf[CMD_BUF_SZ]; size_t pos = 0; enum ReadState {START, STARTING_ESC, P, AT, K, I, T, T2, Y, HYPHEN, C, M, BODY, TRAILING_ESC}; enum ReadState state = START; char ch; monotonic_t end_time = monotonic() + timeout; while(monotonic() <= end_time) { ssize_t len = read(fd, &ch, 1); if (len == 0) continue; if (len < 0) { if (errno == EINTR || errno == EAGAIN) continue; PyErr_SetFromErrno(PyExc_OSError); return false; } end_time = monotonic() + timeout; switch(state) { case START: if (ch == 0x1b) state = STARTING_ESC; if (ch == 0x03) { PyErr_SetString(PyExc_KeyboardInterrupt, "User pressed Ctrl+C"); return false; } break; #define CASE(curr, q, next) case curr: state = ch == q ? next : START; break; CASE(STARTING_ESC, 'P', P); CASE(P, '@', AT); CASE(AT, 'k', K); CASE(K, 'i', I); CASE(I, 't', T); CASE(T, 't', T2); CASE(T2, 'y', Y); CASE(Y, '-', HYPHEN); CASE(HYPHEN, 'c', C); CASE(C, 'm', M); CASE(M, 'd', BODY); case BODY: if (ch == 0x1b) { state = TRAILING_ESC; } else { if (!add_char(buf, &pos, ch, ans)) return false; } break; case TRAILING_ESC: if (ch == '\\') return append_buf(buf, &pos, ans); if (!add_char(buf, &pos, 0x1b, ans)) return false; if (!add_char(buf, &pos, ch, ans)) return false; state = BODY; break; } } PyErr_SetString(PyExc_TimeoutError, "Timed out while waiting to read command response." " Make sure you are running this command from within the kitty terminal." " If you want to run commands from outside, then you have to setup a" " socket with the --listen-on command line flag."); return false; } static PyObject* read_command_response(PyObject *self UNUSED, PyObject *args) { double timeout; int fd; PyObject *ans; if (!PyArg_ParseTuple(args, "idO!", &fd, &timeout, &PyList_Type, &ans)) return NULL; if (!read_response(fd, s_double_to_monotonic_t(timeout), ans)) return NULL; Py_RETURN_NONE; } static PyObject* parse_input_from_terminal(PyObject *self UNUSED, PyObject *args) { enum State { NORMAL, ESC, CSI, ST, ESC_ST }; enum State state = NORMAL; PyObject *uo, *text_callback, *dcs_callback, *csi_callback, *osc_callback, *pm_callback, *apc_callback, *callback; int inbp = 0; if (!PyArg_ParseTuple(args, "OOOOOOUp", &text_callback, &dcs_callback, &csi_callback, &osc_callback, &pm_callback, &apc_callback, &uo, &inbp)) return NULL; Py_ssize_t sz = PyUnicode_GET_LENGTH(uo), pos = 0, start = 0, count = 0, consumed = 0; callback = text_callback; int kind = PyUnicode_KIND(uo); void *data = PyUnicode_DATA(uo); bool in_bracketed_paste_mode = inbp != 0; #define CALL(cb, s_, num_) {\ PyObject *fcb = cb; \ Py_ssize_t s = s_, num = num_; \ if (in_bracketed_paste_mode && fcb != text_callback) { \ fcb = text_callback; num += 2; s -= 2; \ } \ if (num > 0) { \ PyObject *ret = PyObject_CallFunction(fcb, "N", PyUnicode_Substring(uo, s, s + num)); \ if (ret == NULL) return NULL; \ Py_DECREF(ret); \ } \ consumed = s_ + num_; \ count = 0; \ } START_ALLOW_CASE_RANGE; while (pos < sz) { Py_UCS4 ch = PyUnicode_READ(kind, data, pos); switch(state) { case NORMAL: if (ch == 0x1b) { state = ESC; CALL(text_callback, start, count); start = pos; } else count++; break; case ESC: start = pos; count = 0; switch(ch) { case 'P': state = ST; callback = dcs_callback; break; case '[': state = CSI; callback = csi_callback; break; case ']': state = ST; callback = osc_callback; break; case '^': state = ST; callback = pm_callback; break; case '_': state = ST; callback = apc_callback; break; default: state = NORMAL; break; } break; case CSI: count++; switch (ch) { case 'a' ... 'z': case 'A' ... 'Z': case '@': case '`': case '{': case '|': case '}': case '~': #define IBP(w) ch == '~' && PyUnicode_READ(kind, data, start + 1) == '2' && PyUnicode_READ(kind, data, start + 2) == '0' && PyUnicode_READ(kind, data, start + 3) == w if (IBP('1')) in_bracketed_paste_mode = false; CALL(callback, start + 1, count); if (IBP('0')) in_bracketed_paste_mode = true; #undef IBP state = NORMAL; start = pos + 1; break; } break; case ESC_ST: if (ch == '\\') { CALL(callback, start + 1, count); state = NORMAL; start = pos + 1; consumed += 2; } else count += 2; break; case ST: if (ch == 0x1b) { state = ESC_ST; } else count++; break; } pos++; } if (state == NORMAL && count > 0) CALL(text_callback, start, count); return PyUnicode_Substring(uo, consumed, sz); END_ALLOW_CASE_RANGE; #undef CALL } static PyMethodDef module_methods[] = { METHODB(parse_input_from_terminal, METH_VARARGS), METHODB(read_command_response, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_kittens(PyObject *module) { if (PyModule_AddFunctions(module, module_methods) != 0) return false; return true; } kitty-0.41.1/kitty/kitty-verstable.h0000664000175000017510000000117614773370543016762 0ustar nileshnilesh/* * kitty-verstable.h * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "../3rdparty/verstable.h" #include #include #ifndef __kitty_verstable_extra_hash_functions__ #define __kitty_verstable_extra_hash_functions__ #define vt_hash_bytes XXH3_64bits static inline uint64_t vt_hash_float(float x) { return vt_hash_integer((uint64_t)x); } static inline bool vt_cmpr_float(float a, float b) { return a == b; } #define vt_create_for_loop(itr_type, itr, table) for (itr_type itr = vt_first(table); !vt_is_end(itr); itr = vt_next(itr)) #endif kitty-0.41.1/kitty/launch.py0000664000175000017510000012126314773370543015304 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2019, Kovid Goyal import os import shutil from collections.abc import Container, Iterable, Iterator, Sequence from contextlib import suppress from typing import Any, NamedTuple, TypedDict from .boss import Boss from .child import Child from .cli import parse_args from .cli_stub import LaunchCLIOptions from .clipboard import set_clipboard_string, set_primary_selection from .fast_data_types import add_timer, get_boss, get_options, get_os_window_title, patch_color_profiles from .options.utils import env as parse_env from .tabs import Tab, TabManager from .types import OverlayType, run_once from .utils import get_editor, log_error, resolve_custom_file, which from .window import CwdRequest, CwdRequestType, Watchers, Window class LaunchSpec(NamedTuple): opts: LaunchCLIOptions args: list[str] env_docs = '''\ type=list Environment variables to set in the child process. Can be specified multiple times to set different environment variables. Syntax: :code:`name=value`. Using :code:`name=` will set to empty string and just :code:`name` will remove the environment variable. ''' remote_control_password_docs = '''\ type=list Restrict the actions remote control is allowed to take. This works like :opt:`remote_control_password`. You can specify a password and list of actions just as for :opt:`remote_control_password`. For example:: --remote-control-password '"my passphrase" get-* set-colors' This password will be in effect for this window only. Note that any passwords you have defined for :opt:`remote_control_password` in :file:`kitty.conf` are also in effect. You can override them by using the same password here. You can also disable all :opt:`remote_control_password` global passwords for this window, by using:: --remote-control-password '!' This option only takes effect if :option:`--allow-remote-control` is also specified. Can be specified multiple times to create multiple passwords. This option was added to kitty in version 0.26.0 ''' @run_once def options_spec() -> str: return f""" --source-window A match expression to select the window from which data such as title, colors, env vars, screen contents, etc. are copied. When not specified the currently active window is used. See :ref:`search_syntax` for the syntax used for specifying windows. --window-title --title The title to set for the new window. By default, title is controlled by the child process. The special value :code:`current` will copy the title from the :option:`source window `. --tab-title The title for the new tab if launching in a new tab. By default, the title of the active window in the tab is used as the tab title. The special value :code:`current` will copy the title from the tab containing the :option:`source window `. --type type=choices default=window choices=window,tab,os-window,overlay,overlay-main,background,clipboard,primary Where to launch the child process: :code:`window` A new :term:`kitty window ` in the current tab :code:`tab` A new :term:`tab` in the current OS window. Not available when the :doc:`launch ` command is used in :ref:`startup sessions `. :code:`os-window` A new :term:`operating system window `. Not available when the :doc:`launch ` command is used in :ref:`startup sessions `. :code:`overlay` An :term:`overlay window ` covering the current active kitty window :code:`overlay-main` An :term:`overlay window ` covering the current active kitty window. Unlike a plain overlay window, this window is considered as a :italic:`main` window which means it is used as the active window for getting the current working directory, the input text for kittens, launch commands, etc. Useful if this overlay is intended to run for a long time as a primary window. :code:`background` The process will be run in the :italic:`background`, without a kitty window. Note that if :option:`kitten @ launch --allow-remote-control` is specified the :envvar:`KITTY_LISTEN_ON` environment variable will be set to a dedicated socket pair file descriptor that the process can use for remote control. :code:`clipboard`, :code:`primary` These two are meant to work with :option:`--stdin-source ` to copy data to the :italic:`system clipboard` or :italic:`primary selection`. #placeholder_for_formatting# --keep-focus --dont-take-focus type=bool-set Keep the focus on the currently active window instead of switching to the newly opened window. --cwd completion=type:directory kwds:current,oldest,last_reported,root The working directory for the newly launched child. Use the special value :code:`current` to use the working directory of the :option:`source window ` The special value :code:`last_reported` uses the last working directory reported by the shell (needs :ref:`shell_integration` to work). The special value :code:`oldest` works like :code:`current` but uses the working directory of the oldest foreground process associated with the currently active window rather than the newest foreground process. Finally, the special value :code:`root` refers to the process that was originally started when the window was created. --env {env_docs} --var type=list User variables to set in the created window. Can be specified multiple times to set different user variables. Syntax: :code:`name=value`. Using :code:`name=` will set to empty string. --hold type=bool-set Keep the window open even after the command being executed exits, at a shell prompt. The shell will be run after the launched command exits. --copy-colors type=bool-set Set the colors of the newly created window to be the same as the colors in the :option:`source window `. --copy-cmdline type=bool-set Ignore any specified command line and instead use the command line from the :option:`source window `. --copy-env type=bool-set Copy the environment variables from the :option:`source window ` into the newly launched child process. Note that this only copies the environment when the window was first created, as it is not possible to get updated environment variables from arbitrary processes. To copy that environment, use either the :ref:`clone-in-kitty ` feature or the kitty remote control feature with :option:`kitten @ launch --copy-env`. --location type=choices default=default choices=first,after,before,neighbor,last,vsplit,hsplit,split,default Where to place the newly created window when it is added to a tab which already has existing windows in it. :code:`after` and :code:`before` place the new window before or after the active window. :code:`neighbor` is a synonym for :code:`after`. Also applies to creating a new tab, where the value of :code:`after` will cause the new tab to be placed next to the current tab instead of at the end. The values of :code:`vsplit`, :code:`hsplit` and :code:`split` are only used by the :code:`splits` layout and control if the new window is placed in a vertical, horizontal or automatic split with the currently active window. The default is to place the window in a layout dependent manner, typically, after the currently active window. See :option:`--next-to ` to use a window other than the currently active window. --next-to A match expression to select the window next to which the new window is created. See :ref:`search_syntax` for the syntax for specifying windows. If not specified defaults to the active window. When used via remote control and a target tab is specified this option is ignored unless the matched window is in the specified tab. When using :option:`--type ` of :code:`tab`, the tab will be created in the OS Window containing the matched window. --bias type=float default=0 The bias used to alter the size of the window. It controls what fraction of available space the window takes. The exact meaning of bias depends on the current layout. * Splits layout: The bias is interpreted as a percentage between 0 and 100. When splitting a window into two, the new window will take up the specified fraction of the space alloted to the original window and the original window will take up the remainder of the space. * Vertical/horizontal layout: The bias is interpreted as adding/subtracting from the normal size of the window. It should be a number between -90 and 90. This number is the percentage of the OS Window size that should be added to the window size. So for example, if a window would normally have been size 50 in the layout inside an OS Window that is size 80 high and --bias -10 is used it will become *approximately* size 42 high. Note that sizes are approximations, you cannot use this method to create windows of fixed sizes. * Tall layout: If the window being created is the *first* window in a column, then the bias is interpreted as a percentage, as for the splits layout, splitting the OS Window width between columns. If the window is a second or subsequent window in a column the bias is interpreted as adding/subtracting from the window size as for the vertical layout above. * Fat layout: Same as tall layout except it goes by rows instead of columns. * Grid layout: The bias is interpreted the same way as for the Vertical and Horizontal layouts, as something to be added/subtracted to the normal size. However, the since in a grid layout there are rows *and* columns, the bias on the first window in a column operates on the columns. Any later windows in that column operate on the row. So, for example, if you bias the first window in a grid layout it will change the width of the first column, the second window, the width of the second column, the third window, the height of the second row and so on. The bias option was introduced in kitty version 0.36.0. --allow-remote-control type=bool-set Programs running in this window can control kitty (even if remote control is not enabled in :file:`kitty.conf`). Note that any program with the right level of permissions can still write to the pipes of any other program on the same computer and therefore can control kitty. It can, however, be useful to block programs running on other computers (for example, over SSH) or as other users. See :option:`--remote-control-password` for ways to restrict actions allowed by remote control. --remote-control-password {remote_control_password_docs} --stdin-source type=choices default=none choices=none,@selection,@screen,@screen_scrollback,@alternate,@alternate_scrollback,@first_cmd_output_on_screen,@last_cmd_output,@last_visited_cmd_output Pass the screen contents as :file:`STDIN` to the child process. :code:`@selection` is the currently selected text in the :option:`source window `. :code:`@screen` is the contents of the :option:`source window `. :code:`@screen_scrollback` is the same as :code:`@screen`, but includes the scrollback buffer as well. :code:`@alternate` is the secondary screen of the :option:`source window `. For example if you run a full screen terminal application, the secondary screen will be the screen you return to when quitting the application. :code:`@first_cmd_output_on_screen` is the output from the first command run in the shell on screen. :code:`@last_cmd_output` is the output from the last command run in the shell. :code:`@last_visited_cmd_output` is the first output below the last scrolled position via :ac:`scroll_to_prompt`, this needs :ref:`shell integration ` to work. #placeholder_for_formatting# --stdin-add-formatting type=bool-set When using :option:`--stdin-source ` add formatting escape codes, without this only plain text will be sent. --stdin-add-line-wrap-markers type=bool-set When using :option:`--stdin-source ` add a carriage return at every line wrap location (where long lines are wrapped at screen edges). This is useful if you want to pipe to program that wants to duplicate the screen layout of the screen. --marker Create a marker that highlights text in the newly created window. The syntax is the same as for the :ac:`toggle_marker` action (see :doc:`/marks`). --os-window-class Set the :italic:`WM_CLASS` property on X11 and the application id property on Wayland for the newly created OS window when using :option:`--type=os-window `. Defaults to whatever is used by the parent kitty process, which in turn defaults to :code:`kitty`. --os-window-name Set the :italic:`WM_NAME` property on X11 for the newly created OS Window when using :option:`--type=os-window `. Defaults to :option:`--os-window-class `. --os-window-title Set the title for the newly created OS window. This title will override any titles set by programs running in kitty. The special value :code:`current` will copy the title from the OS Window containing the :option:`source window `. --os-window-state type=choices default=normal choices=normal,fullscreen,maximized,minimized The initial state for the newly created OS Window. --logo completion=type:file ext:png group:"PNG images" relative:conf Path to a PNG image to use as the logo for the newly created window. See :opt:`window_logo_path`. Relative paths are resolved from the kitty configuration directory. --logo-position The position for the window logo. Only takes effect if :option:`--logo` is specified. See :opt:`window_logo_position`. --logo-alpha type=float default=-1 The amount the window logo should be faded into the background. Only takes effect if :option:`--logo` is specified. See :opt:`window_logo_alpha`. --color type=list Change colors in the newly launched window. You can either specify a path to a :file:`.conf` file with the same syntax as :file:`kitty.conf` to read the colors from, or specify them individually, for example:: --color background=white --color foreground=red --spacing type=list Set the margin and padding for the newly created window. For example: :code:`margin=20` or :code:`padding-left=10` or :code:`margin-h=30`. The shorthand form sets all values, the :code:`*-h` and :code:`*-v` variants set horizontal and vertical values. Can be specified multiple times. Note that this is ignored for overlay windows as these use the settings from the base window. --watcher -w type=list completion=type:file ext:py relative:conf group:"Python scripts" Path to a Python file. Appropriately named functions in this file will be called for various events, such as when the window is resized, focused or closed. See the section on watchers in the launch command documentation: :ref:`watchers`. Relative paths are resolved relative to the :ref:`kitty config directory `. Global watchers for all windows can be specified with :opt:`watcher` in :file:`kitty.conf`. """ def parse_launch_args(args: Sequence[str] | None = None) -> LaunchSpec: args = list(args or ()) try: opts, args = parse_args(result_class=LaunchCLIOptions, args=args, ospec=options_spec) except SystemExit as e: raise ValueError(str(e)) from e return LaunchSpec(opts, args) def get_env(opts: LaunchCLIOptions, active_child: Child | None = None, base_env: dict[str,str] | None = None) -> dict[str, str]: env: dict[str, str] = {} if opts.copy_env and active_child: env.update(active_child.foreground_environ) if base_env is not None: env.update(base_env) for x in opts.env: for k, v in parse_env(x, env): env[k] = v return env def tab_for_window(boss: Boss, opts: LaunchCLIOptions, target_tab: Tab | None, next_to: Window | None) -> Tab: def create_tab(tm: TabManager | None = None) -> Tab: if tm is None: oswid = boss.add_os_window( wclass=opts.os_window_class, wname=opts.os_window_name, window_state=opts.os_window_state, override_title=opts.os_window_title or None) tm = boss.os_window_map[oswid] tab = tm.new_tab(empty_tab=True, location=opts.location) if opts.tab_title: tab.set_title(opts.tab_title) return tab if opts.type == 'tab': tm = boss.active_tab_manager if next_to is not None and (tab := next_to.tabref()) is not None and (qtm := tab.tab_manager_ref()) is not None: tm = qtm if target_tab is not None: tm = target_tab.tab_manager_ref() or tm tab = create_tab(tm) elif opts.type == 'os-window': tab = create_tab() else: if target_tab is not None: tab = target_tab else: tab = boss.active_tab if next_to is not None and (qtab := next_to.tabref()) is not None: tab = qtab tab = tab or create_tab() return tab watcher_modules: dict[str, Any] = {} def load_watch_modules(watchers: Iterable[str]) -> Watchers | None: if not watchers: return None import runpy ans = Watchers() boss = get_boss() for path in watchers: path = resolve_custom_file(path) m = watcher_modules.get(path, None) if m is None: try: m = runpy.run_path(path, run_name='__kitty_watcher__') except Exception as err: import traceback log_error(traceback.format_exc()) log_error(f'Failed to load watcher from {path} with error: {err}') watcher_modules[path] = False continue watcher_modules[path] = m w = m.get('on_load') if callable(w): try: w(boss, {}) except Exception as err: import traceback log_error(traceback.format_exc()) log_error(f'Failed to call on_load() in watcher from {path} with error: {err}') if m is False: continue w = m.get('on_close') if callable(w): ans.on_close.append(w) w = m.get('on_resize') if callable(w): ans.on_resize.append(w) w = m.get('on_focus_change') if callable(w): ans.on_focus_change.append(w) w = m.get('on_set_user_var') if callable(w): ans.on_set_user_var.append(w) w = m.get('on_title_change') if callable(w): ans.on_title_change.append(w) w = m.get('on_cmd_startstop') if callable(w): ans.on_cmd_startstop.append(w) w = m.get('on_color_scheme_preference_change') if callable(w): ans.on_color_scheme_preference_change.append(w) return ans class LaunchKwds(TypedDict): allow_remote_control: bool remote_control_passwords: dict[str, Sequence[str]] | None cwd_from: CwdRequest | None cwd: str | None location: str | None override_title: str | None copy_colors_from: Window | None marker: str | None cmd: list[str] | None overlay_for: int | None stdin: bytes | None hold: bool bias: float | None def apply_colors(window: Window, spec: Sequence[str]) -> None: from .colors import parse_colors colors, transparent_background_colors = parse_colors(spec) profiles = window.screen.color_profile, patch_color_profiles(colors, transparent_background_colors, profiles, True) def parse_var(defn: Iterable[str]) -> Iterator[tuple[str, str]]: for item in defn: a, sep, b = item.partition('=') yield a, b class ForceWindowLaunch: def __init__(self) -> None: self.force = False def __bool__(self) -> bool: return self.force def __call__(self, force: bool) -> 'ForceWindowLaunch': self.force = force return self def __enter__(self) -> None: pass def __exit__(self, *a: object) -> None: self.force = False force_window_launch = ForceWindowLaunch() non_window_launch_types = 'background', 'clipboard', 'primary' def parse_remote_control_passwords(allow_remote_control: bool, passwords: Sequence[str]) -> dict[str, Sequence[str]] | None: remote_control_restrictions: dict[str, Sequence[str]] | None = None if allow_remote_control and passwords: from kitty.options.utils import remote_control_password remote_control_restrictions = {} for rcp in passwords: for pw, rcp_items in remote_control_password(rcp, {}): remote_control_restrictions[pw] = rcp_items return remote_control_restrictions def _launch( boss: Boss, opts: LaunchCLIOptions, args: list[str], target_tab: Tab | None = None, force_target_tab: bool = False, is_clone_launch: str = '', rc_from_window: Window | None = None, base_env: dict[str, str] | None = None, ) -> Window | None: source_window = boss.active_window_for_cwd if opts.source_window: for qw in boss.match_windows(opts.source_window, rc_from_window): source_window = qw break source_child = source_window.child if source_window else None next_to = boss.active_window if opts.next_to: for qw in boss.match_windows(opts.next_to, rc_from_window): next_to = qw break if target_tab and next_to and next_to not in target_tab: next_to = None if opts.window_title == 'current': opts.window_title = source_window.title if source_window else None if opts.tab_title == 'current': atab = boss.active_tab if source_window and (qt := source_window.tabref()): atab = qt opts.tab_title = atab.effective_title if atab else None if opts.os_window_title == 'current': tm = boss.active_tab_manager if source_window and (qt := source_window.tabref()) and (qr := qt.tab_manager_ref()): tm = qr opts.os_window_title = get_os_window_title(tm.os_window_id) if tm else None env = get_env(opts, source_child, base_env) kw: LaunchKwds = { 'allow_remote_control': opts.allow_remote_control, 'remote_control_passwords': parse_remote_control_passwords(opts.allow_remote_control, opts.remote_control_password), 'cwd_from': None, 'cwd': None, 'location': None, 'override_title': opts.window_title or None, 'copy_colors_from': None, 'marker': opts.marker or None, 'cmd': None, 'overlay_for': None, 'stdin': None, 'hold': False, 'bias': None, } spacing = {} if opts.spacing: from .rc.set_spacing import parse_spacing_settings, patch_window_edges spacing = parse_spacing_settings(opts.spacing) if opts.bias: kw['bias'] = max(-100, min(opts.bias, 100)) if opts.cwd: if opts.cwd == 'current': if source_window: kw['cwd_from'] = CwdRequest(source_window) elif opts.cwd == 'last_reported': if source_window: kw['cwd_from'] = CwdRequest(source_window, CwdRequestType.last_reported) elif opts.cwd == 'oldest': if source_window: kw['cwd_from'] = CwdRequest(source_window, CwdRequestType.oldest) elif opts.cwd == 'root': if source_window: kw['cwd_from'] = CwdRequest(source_window, CwdRequestType.root) else: kw['cwd'] = opts.cwd if opts.location != 'default': kw['location'] = opts.location if opts.copy_colors and source_window: kw['copy_colors_from'] = source_window pipe_data: dict[str, Any] = {} if opts.stdin_source != 'none': q = str(opts.stdin_source) if opts.stdin_add_formatting: if q in ('@screen', '@screen_scrollback', '@alternate', '@alternate_scrollback', '@first_cmd_output_on_screen', '@last_cmd_output', '@last_visited_cmd_output'): q = f'@ansi_{q[1:]}' if opts.stdin_add_line_wrap_markers: q += '_wrap' penv, stdin = boss.process_stdin_source(window=source_window, stdin=q, copy_pipe_data=pipe_data) if stdin: kw['stdin'] = stdin if penv: env.update(penv) cmd = args or None if opts.copy_cmdline and source_child: cmd = source_child.foreground_cmdline active = boss.active_window if cmd: final_cmd: list[str] = [] for x in cmd: if source_window and not opts.copy_cmdline: if x == '@selection': s = boss.data_for_at(which=x, window=source_window) if s: x = s elif x == '@active-kitty-window-id': if active: x = str(active.id) elif x == '@input-line-number': if 'input_line_number' in pipe_data: x = str(pipe_data['input_line_number']) elif x == '@line-count': if 'lines' in pipe_data: x = str(pipe_data['lines']) elif x in ('@cursor-x', '@cursor-y', '@scrolled-by', '@first-line-on-screen', '@last-line-on-screen'): if source_window is not None: screen = source_window.screen if x == '@scrolled-by': x = str(screen.scrolled_by) elif x == '@cursor-x': x = str(screen.cursor.x + 1) elif x == '@cursor-y': x = str(screen.cursor.y + 1) elif x == '@first-line-on-screen': x = str(screen.visual_line(0) or '') elif x == '@last-line-on-screen': x = str(screen.visual_line(screen.lines - 1) or '') final_cmd.append(x) if rc_from_window is None and final_cmd: exe = which(final_cmd[0]) if exe: final_cmd[0] = exe kw['cmd'] = final_cmd if force_window_launch and opts.type not in non_window_launch_types: opts.type = 'window' if next_to and opts.type in non_window_launch_types: next_to = None base_for_overlay = next_to if target_tab and (not base_for_overlay or base_for_overlay not in target_tab): base_for_overlay = target_tab.active_window if opts.type in ('overlay', 'overlay-main') and base_for_overlay: kw['overlay_for'] = base_for_overlay.id if opts.type == 'background': cmd = kw['cmd'] if not cmd: raise ValueError('The cmd to run must be specified when running a background process') boss.run_background_process( cmd, cwd=kw['cwd'], cwd_from=kw['cwd_from'], env=env or None, stdin=kw['stdin'], allow_remote_control=kw['allow_remote_control'], remote_control_passwords=kw['remote_control_passwords'] ) elif opts.type in ('clipboard', 'primary'): stdin = kw.get('stdin') if stdin is not None: if opts.type == 'clipboard': set_clipboard_string(stdin) boss.handle_clipboard_loss('clipboard') else: set_primary_selection(stdin) boss.handle_clipboard_loss('primary') else: kw['hold'] = opts.hold if force_target_tab and target_tab is not None: tab = target_tab else: tab = tab_for_window(boss, opts, target_tab, next_to) watchers = load_watch_modules(opts.watcher) with Window.set_ignore_focus_changes_for_new_windows(opts.keep_focus): new_window: Window = tab.new_window( env=env or None, watchers=watchers or None, is_clone_launch=is_clone_launch, next_to=next_to, **kw) if spacing: patch_window_edges(new_window, spacing) tab.relayout() if opts.color: apply_colors(new_window, opts.color) if opts.keep_focus: if active: boss.set_active_window(active, switch_os_window_if_needed=True, for_keep_focus=True) if not Window.initial_ignore_focus_changes_context_manager_in_operation: new_window.ignore_focus_changes = False if opts.logo: new_window.set_logo(opts.logo, opts.logo_position or '', opts.logo_alpha) if opts.type == 'overlay-main': new_window.overlay_type = OverlayType.main if opts.var: for key, val in parse_var(opts.var): new_window.set_user_var(key, val) return new_window return None def launch( boss: Boss, opts: LaunchCLIOptions, args: list[str], target_tab: Tab | None = None, force_target_tab: bool = False, is_clone_launch: str = '', rc_from_window: Window | None = None, base_env: dict[str, str] | None = None, ) -> Window | None: active = boss.active_window if opts.keep_focus and active: orig, active.ignore_focus_changes = active.ignore_focus_changes, True try: return _launch(boss, opts, args, target_tab, force_target_tab, is_clone_launch, rc_from_window, base_env) finally: if opts.keep_focus and active: active.ignore_focus_changes = orig @run_once def clone_safe_opts() -> frozenset[str]: return frozenset(( 'window_title', 'tab_title', 'type', 'keep_focus', 'cwd', 'env', 'var', 'hold', 'location', 'os_window_class', 'os_window_name', 'os_window_title', 'os_window_state', 'logo', 'logo_position', 'logo_alpha', 'color', 'spacing', 'next_to', )) def parse_opts_for_clone(args: list[str]) -> tuple[LaunchCLIOptions, list[str]]: unsafe, unsafe_args = parse_launch_args(args) default_opts, default_args = parse_launch_args() # only copy safe options, those that dont lead to local code exec for x in clone_safe_opts(): setattr(default_opts, x, getattr(unsafe, x)) return default_opts, unsafe_args def parse_null_env(text: str) -> dict[str, str]: ans = {} for line in text.split('\0'): if line: try: k, v = line.split('=', 1) except ValueError: continue ans[k] = v return ans def parse_message(msg: str, simple: Container[str]) -> Iterator[tuple[str, str]]: from base64 import standard_b64decode for x in msg.split(','): try: k, v = x.split('=', 1) except ValueError: continue if k not in simple: v = standard_b64decode(v).decode('utf-8', 'replace') yield k, v class EditCmd: def __init__(self, msg: str) -> None: self.tdir = '' self.args: list[str] = [] self.cwd = self.file_name = self.file_localpath = '' self.file_data = b'' self.file_inode = -1, -1 self.file_size = -1 self.version = 0 self.source_window_id = self.editor_window_id = -1 self.abort_signaled = '' simple = 'file_inode', 'file_data', 'abort_signaled', 'version' for k, v in parse_message(msg, simple): if k == 'file_inode': q = map(int, v.split(':')) self.file_inode = next(q), next(q) self.file_size = next(q) elif k == 'a': self.args.append(v) elif k == 'file_data': import base64 self.file_data = base64.standard_b64decode(v) elif k == 'version': self.version = int(v) else: setattr(self, k, v) if self.abort_signaled: return if self.version > 0: raise ValueError(f'Unsupported version received in edit protocol: {self.version}') self.opts, extra_args = parse_opts_for_clone(['--type=overlay'] + self.args) self.file_spec = extra_args.pop() self.line_number = 0 import re pat = re.compile(r'\+(-?\d+)') for x in extra_args: m = pat.match(x) if m is not None: self.line_number = int(m.group(1)) self.file_name = os.path.basename(self.file_spec) self.file_localpath = os.path.normpath(os.path.join(self.cwd, self.file_spec)) self.is_local_file = False with suppress(OSError): st = os.stat(self.file_localpath) self.is_local_file = (st.st_dev, st.st_ino) == self.file_inode and os.access(self.file_localpath, os.W_OK | os.R_OK) if not self.is_local_file: import tempfile self.tdir = tempfile.mkdtemp() self.file_localpath = os.path.join(self.tdir, self.file_name) with open(self.file_localpath, 'wb') as f: f.write(self.file_data) self.file_data = b'' self.last_mod_time = self.file_mod_time if not self.opts.cwd: self.opts.cwd = os.path.dirname(self.file_localpath) def __del__(self) -> None: if self.tdir: with suppress(OSError): shutil.rmtree(self.tdir) self.tdir = '' def read_data(self) -> bytes: with open(self.file_localpath, 'rb') as f: return f.read() @property def file_mod_time(self) -> int: return os.stat(self.file_localpath).st_mtime_ns def schedule_check(self) -> None: if not self.abort_signaled: add_timer(self.check_status, 1.0, False) def on_edit_window_close(self, window: Window) -> None: self.check_status() def check_status(self, timer_id: int | None = None) -> None: if self.abort_signaled: return boss = get_boss() source_window = boss.window_id_map.get(self.source_window_id) if source_window is not None and not self.is_local_file: mtime = self.file_mod_time if mtime != self.last_mod_time: self.last_mod_time = mtime data = self.read_data() self.send_data(source_window, 'UPDATE', data) editor_window = boss.window_id_map.get(self.editor_window_id) if editor_window is None: edits_in_flight.pop(self.source_window_id, None) if source_window is not None: self.send_data(source_window, 'DONE') self.abort_signaled = self.abort_signaled or 'closed' else: self.schedule_check() def send_data(self, window: Window, data_type: str, data: bytes = b'') -> None: window.write_to_child(f'KITTY_DATA_START\n{data_type}\n') if data: import base64 mv = memoryview(base64.standard_b64encode(data)) while mv: window.write_to_child(bytes(mv[:512])) window.write_to_child('\n') mv = mv[512:] window.write_to_child('KITTY_DATA_END\n') class CloneCmd: def __init__(self, msg: str) -> None: self.args: list[str] = [] self.env: dict[str, str] | None = None self.cwd = '' self.shell = '' self.envfmt = 'default' self.pid = -1 self.bash_version = '' self.history = '' self.parse_message(msg) self.opts = parse_opts_for_clone(self.args)[0] def parse_message(self, msg: str) -> None: simple = 'pid', 'envfmt', 'shell', 'bash_version' for k, v in parse_message(msg, simple): if k in simple: if k == 'pid': self.pid = int(v) else: setattr(self, k, v) elif k == 'a': self.args.append(v) elif k == 'env': if self.envfmt == 'bash': from .bash import parse_bash_env env = parse_bash_env(v, self.bash_version) else: env = parse_null_env(v) self.env = {k: v for k, v in env.items() if k not in { 'HOME', 'LOGNAME', 'USER', 'PWD', # some people export these. We want the shell rc files to recreate them 'PS0', 'PS1', 'PS2', 'PS3', 'PS4', 'RPS1', 'PROMPT_COMMAND', 'SHLVL', # conda state env vars 'CONDA_SHLVL', 'CONDA_PREFIX', 'CONDA_PROMPT_MODIFIER', 'CONDA_EXE', 'CONDA_PYTHON_EXE', '_CE_CONDA', '_CE_M', # skip SSH environment variables 'SSH_CLIENT', 'SSH_CONNECTION', 'SSH_ORIGINAL_COMMAND', 'SSH_TTY', 'SSH2_TTY', 'SSH_TUNNEL', 'SSH_USER_AUTH', 'SSH_AUTH_SOCK', # Dont clone KITTY_WINDOW_ID 'KITTY_WINDOW_ID', # Bash variables from "bind -x" and "complete -C" (needed not to confuse bash-preexec) 'READLINE_ARGUMENT', 'READLINE_LINE', 'READLINE_MARK', 'READLINE_POINT', 'COMP_LINE', 'COMP_POINT', 'COMP_TYPE', # GPG gpg-agent 'GPG_TTY', # Session variables of XDG 'XDG_SESSION_CLASS', 'XDG_SESSION_ID', 'XDG_SESSION_TYPE', # Session variables of GNU Screen 'STY', 'WINDOW', # Session variables of interactive shell plugins 'ATUIN_SESSION', 'ATUIN_HISTORY_ID', 'BLE_SESSION_ID', '_ble_util_fdlist_cloexec', '_ble_util_fdvars_export', '_GITSTATUS_CLIENT_PID', '_GITSTATUS_REQ_FD', '_GITSTATUS_RESP_FD', 'GITSTATUS_DAEMON_PID', 'MCFLY_SESSION_ID', 'STARSHIP_SESSION_KEY', } and not k.startswith(( # conda state env vars for multi-level virtual environments 'CONDA_PREFIX_', ))} elif k == 'cwd': self.cwd = v elif k == 'history': self.history = v edits_in_flight: dict[int, EditCmd] = {} def remote_edit(msg: str, window: Window) -> None: c = EditCmd(msg) if c.abort_signaled: q = edits_in_flight.pop(window.id, None) if q is not None: q.abort_signaled = c.abort_signaled return cmdline = get_editor(path_to_edit=c.file_localpath, line_number=c.line_number) c.opts.source_window = c.opts.next_to = f'id:{window.id}' w = launch(get_boss(), c.opts, cmdline) if w is not None: c.source_window_id = window.id c.editor_window_id = w.id q = edits_in_flight.pop(window.id, None) if q is not None: q.abort_signaled = 'replaced' edits_in_flight[window.id] = c w.actions_on_close.append(c.on_edit_window_close) c.schedule_check() def clone_and_launch(msg: str, window: Window) -> None: from .shell_integration import serialize_env c = CloneCmd(msg) if c.cwd and not c.opts.cwd: c.opts.cwd = c.cwd c.opts.copy_colors = True c.opts.copy_env = False if c.opts.type in non_window_launch_types: c.opts.type = 'window' env_to_serialize = c.env or {} if env_to_serialize.get('PATH') and env_to_serialize.get('VIRTUAL_ENV'): # only pass VIRTUAL_ENV if it is currently active if f"{env_to_serialize['VIRTUAL_ENV']}/bin" not in env_to_serialize['PATH'].split(os.pathsep): del env_to_serialize['VIRTUAL_ENV'] env_to_serialize['KITTY_CLONE_SOURCE_STRATEGIES'] = ',' + ','.join(get_options().clone_source_strategies) + ',' is_clone_launch = serialize_env(c.shell, env_to_serialize) ssh_kitten_cmdline = window.ssh_kitten_cmdline() if ssh_kitten_cmdline: from kittens.ssh.utils import patch_cmdline, set_cwd_in_cmdline, set_env_in_cmdline cmdline = ssh_kitten_cmdline if c.opts.cwd: set_cwd_in_cmdline(c.opts.cwd, cmdline) c.opts.cwd = None if c.env: set_env_in_cmdline({ 'KITTY_IS_CLONE_LAUNCH': is_clone_launch, }, cmdline) c.env = None if c.opts.env: for entry in reversed(c.opts.env): patch_cmdline('env', entry, cmdline) c.opts.env = [] else: try: cmdline = window.child.cmdline_of_pid(c.pid) except Exception: cmdline = [] if not cmdline: cmdline = list(window.child.argv) if cmdline and cmdline[0].startswith('-'): # on macOS, run via run-shell kitten if window.child.is_default_shell: cmdline = window.child.unmodified_argv else: cmdline[0] = cmdline[0][1:] cmdline[0] = which(cmdline[0]) or cmdline[0] if cmdline and cmdline[0] == window.child.final_argv0: cmdline[0] = window.child.final_exe if cmdline and cmdline == [window.child.final_exe] + window.child.argv[1:]: cmdline = window.child.unmodified_argv c.opts.source_window = f'id:{window.id}' c.opts.next_to = c.opts.next_to or c.opts.source_window launch(get_boss(), c.opts, cmdline, is_clone_launch=is_clone_launch) kitty-0.41.1/kitty/launcher/0000775000175000017510000000000014773370543015254 5ustar nileshnileshkitty-0.41.1/kitty/launcher/launcher.h0000664000175000017510000000071014773370543017224 0ustar nileshnilesh/* * launcher.h * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include typedef struct CLIOptions { const char *session, *instance_group; bool single_instance, version_requested, wait_for_single_instance_window_close; int open_url_count; char **open_urls; } CLIOptions; void single_instance_main(int argc, char *argv[], const CLIOptions *opts); kitty-0.41.1/kitty/launcher/main.c0000664000175000017510000003676414773370543016364 0ustar nileshnilesh/* * launcher.c * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #define PY_SSIZE_T_CLEAN #include #include #ifdef __APPLE__ #include #include #include #else #include #endif #include #include #include #include #include #include "launcher.h" #ifndef KITTY_LIB_PATH #define KITTY_LIB_PATH "../.." #endif #ifndef KITTY_LIB_DIR_NAME #define KITTY_LIB_DIR_NAME "lib" #endif static void cleanup_free(void *p) { free(*(void**) p); } #define RAII_ALLOC(type, name, initializer) __attribute__((cleanup(cleanup_free))) type *name = initializer #ifndef __FreeBSD__ static bool safe_realpath(const char* src, char *buf, size_t buf_sz) { RAII_ALLOC(char, ans, realpath(src, NULL)); if (ans == NULL) return false; snprintf(buf, buf_sz, "%s", ans); return true; } #endif typedef struct { const char *exe, *exe_dir, *lc_ctype, *lib_dir; char **argv; int argc; } RunData; static bool set_kitty_run_data(RunData *run_data, bool from_source, wchar_t *extensions_dir) { PyObject *ans = PyDict_New(); if (!ans) { PyErr_Print(); return false; } PyObject *exe_dir = PyUnicode_DecodeFSDefaultAndSize(run_data->exe_dir, strlen(run_data->exe_dir)); if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", run_data->exe_dir); PyErr_Print(); Py_CLEAR(ans); return false; } #define S(key, val) { if (!val) { PyErr_Print(); Py_CLEAR(ans); return false; } int ret = PyDict_SetItemString(ans, #key, val); Py_CLEAR(val); if (ret != 0) { PyErr_Print(); Py_CLEAR(ans); return false; } } S(bundle_exe_dir, exe_dir); if (from_source) { PyObject *one = Py_True; Py_INCREF(one); S(from_source, one); } if (run_data->lc_ctype) { PyObject *ctype = PyUnicode_DecodeLocaleAndSize(run_data->lc_ctype, strlen(run_data->lc_ctype), NULL); S(lc_ctype_before_python, ctype); } if (extensions_dir) { PyObject *ed = PyUnicode_FromWideChar(extensions_dir, -1); S(extensions_dir, ed); } #undef S int ret = PySys_SetObject("kitty_run_data", ans); Py_CLEAR(ans); if (ret != 0) { PyErr_Print(); return false; } return true; } #ifdef FOR_BUNDLE #include static bool canonicalize_path(const char *srcpath, char *dstpath, size_t sz) { // remove . and .. path segments bool ok = false; size_t plen = strlen(srcpath) + 1, chk; RAII_ALLOC(char, wtmp, malloc(plen)); RAII_ALLOC(char*, tokv, malloc(sizeof(char*) * plen)); if (!wtmp || !tokv) goto end; char *s, *tok, *sav; bool relpath = *srcpath != '/'; // use a buffer as strtok modifies its input memcpy(wtmp, srcpath, plen); tok = strtok_r(wtmp, "/", &sav); int ti = 0; while (tok != NULL) { if (strcmp(tok, "..") == 0) { if (ti > 0) ti--; } else if (strcmp(tok, ".") != 0) { tokv[ti++] = tok; } tok = strtok_r(NULL, "/", &sav); } chk = 0; s = dstpath; for (int i = 0; i < ti; i++) { size_t token_sz = strlen(tokv[i]); if (i > 0 || !relpath) { if (++chk >= sz) goto end; *s++ = '/'; } chk += token_sz; if (chk >= sz) goto end; memcpy(s, tokv[i], token_sz); s += token_sz; } if (s == dstpath) { if (++chk >= sz) goto end; *s++ = relpath ? '.' : '/'; } *s = '\0'; ok = true; end: return ok; } static bool canonicalize_path_wide(const char *srcpath, wchar_t *dest, size_t sz) { char buf[sz + 1]; bool ret = canonicalize_path(srcpath, buf, sz); buf[sz] = 0; mbstowcs(dest, buf, sz - 1); dest[sz-1] = 0; return ret; } static int run_embedded(RunData *run_data) { bypy_pre_initialize_interpreter(false); char extensions_dir_full[PATH_MAX+1] = {0}, python_home_full[PATH_MAX+1] = {0}; #ifdef __APPLE__ const char *python_relpath = "../Resources/Python/lib"; #else const char *python_relpath = "../" KITTY_LIB_DIR_NAME; #endif int num = snprintf(extensions_dir_full, PATH_MAX, "%s/%s/kitty-extensions", run_data->exe_dir, python_relpath); if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to extensions_dir: %s/%s\n", run_data->exe_dir, python_relpath); return 1; } wchar_t extensions_dir[num+2]; if (!canonicalize_path_wide(extensions_dir_full, extensions_dir, num+1)) { fprintf(stderr, "Failed to canonicalize the path: %s\n", extensions_dir_full); return 1; } num = snprintf(python_home_full, PATH_MAX, "%s/%s/python%s", run_data->exe_dir, python_relpath, PYVER); if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to python home: %s/%s\n", run_data->exe_dir, python_relpath); return 1; } wchar_t python_home[num+2]; if (!canonicalize_path_wide(python_home_full, python_home, num+1)) { fprintf(stderr, "Failed to canonicalize the path: %s\n", python_home_full); return 1; } bypy_initialize_interpreter( L"kitty", python_home, L"kitty_main", extensions_dir, run_data->argc, run_data->argv); if (!set_kitty_run_data(run_data, false, extensions_dir)) return 1; set_sys_bool("frozen", true); return bypy_run_interpreter(); } #else static int run_embedded(RunData *run_data) { bool from_source = false; #ifdef FROM_SOURCE from_source = true; #endif PyStatus status; PyPreConfig preconfig; PyPreConfig_InitPythonConfig(&preconfig); preconfig.utf8_mode = 1; preconfig.coerce_c_locale = 1; #ifdef SET_PYTHON_HOME preconfig.isolated = 1; #endif status = Py_PreInitialize(&preconfig); if (PyStatus_Exception(status)) goto fail; PyConfig config; PyConfig_InitPythonConfig(&config); config.parse_argv = 0; config.optimization_level = 2; status = PyConfig_SetBytesArgv(&config, run_data->argc, run_data->argv); if (PyStatus_Exception(status)) goto fail; status = PyConfig_SetBytesString(&config, &config.executable, run_data->exe); if (PyStatus_Exception(status)) goto fail; status = PyConfig_SetBytesString(&config, &config.run_filename, run_data->lib_dir); if (PyStatus_Exception(status)) goto fail; #ifdef SET_PYTHON_HOME #ifndef __APPLE__ char pyhome[256]; snprintf(pyhome, sizeof(pyhome), "%s/%s", run_data->lib_dir, SET_PYTHON_HOME); status = PyConfig_SetBytesString(&config, &config.home, pyhome); if (PyStatus_Exception(status)) goto fail; #endif config.isolated = 1; #endif status = Py_InitializeFromConfig(&config); if (PyStatus_Exception(status)) goto fail; PyConfig_Clear(&config); if (!set_kitty_run_data(run_data, from_source, NULL)) return 1; PySys_SetObject("frozen", Py_False); return Py_RunMain(); fail: PyConfig_Clear(&config); if (PyStatus_IsExit(status)) return status.exitcode; single_instance_main(-1, NULL, NULL); Py_ExitStatusException(status); } #endif // read_exe_path() {{{ #ifdef __APPLE__ static bool read_exe_path(char *exe, size_t buf_sz) { (void)buf_sz; uint32_t size = PATH_MAX; char apple[PATH_MAX+1] = {0}; if (_NSGetExecutablePath(apple, &size) != 0) { fprintf(stderr, "Failed to get path to executable\n"); return false; } if (!safe_realpath(apple, exe, buf_sz)) { fprintf(stderr, "realpath() failed on the executable's path\n"); return false; } return true; } #elif defined(__FreeBSD__) #include #include static bool read_exe_path(char *exe, size_t buf_sz) { int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; size_t length = buf_sz; int error = sysctl(name, 4, exe, &length, NULL, 0); if (error < 0 || length <= 1) { fprintf(stderr, "failed to get path to executable, sysctl() failed\n"); return false; } return true; } #elif defined(__NetBSD__) static bool read_exe_path(char *exe, size_t buf_sz) { if (!safe_realpath("/proc/curproc/exe", exe, buf_sz)) { fprintf(stderr, "Failed to read /proc/curproc/exe\n"); return false; } return true; } #elif defined(__OpenBSD__) static bool read_exe_path(char *exe, size_t buf_sz) { const char *path = getenv("PATH"); if (!path) { fprintf(stderr, "No PATH environment variable set, aborting\n"); return false; } char buf[PATH_MAX + 1] = {0}; strncpy(buf, path, PATH_MAX); char *token = strtok(buf, ":"); while (token != NULL) { char q[PATH_MAX + 1] = {0}; snprintf(q, PATH_MAX, "%s/kitty", token); if (safe_realpath(q, exe, buf_sz)) return true; token = strtok(NULL, ":"); } fprintf(stderr, "kitty not found in PATH aborting\n"); return false; } #else static bool read_exe_path(char *exe, size_t buf_sz) { if (!safe_realpath("/proc/self/exe", exe, buf_sz)) { fprintf(stderr, "Failed to read /proc/self/exe\n"); return false; } return true; } #endif // }}} static bool is_valid_fd(int fd) { // This is copied from the python source code as we need the exact same semantics // to prevent python from giving us None for sys.stdout and friends. #if defined(F_GETFD) && ( \ defined(__linux__) || \ defined(__APPLE__) || \ defined(__wasm__)) return fcntl(fd, F_GETFD) >= 0; #elif defined(__linux__) int fd2 = dup(fd); if (fd2 >= 0) { close(fd2); } return (fd2 >= 0); #else struct stat st; return (fstat(fd, &st) == 0); #endif } static bool reopen_to_null(const char *mode, FILE *stream) { errno = 0; while (true) { if (freopen("/dev/null", mode, stream) != NULL) return true; if (errno == EINTR) continue; perror("Failed to re-open STDIO handle to /dev/null"); return false; } } static bool ensure_working_stdio(void) { #define C(which, mode) { \ int fd = fileno(which); \ if (fd < 0) { if (!reopen_to_null(mode, which)) return false; } \ else if (!is_valid_fd(fd)) { \ close(fd); if (!reopen_to_null(mode, which)) return false; \ }} C(stdin, "r") C(stdout, "w") C(stderr, "w") return true; #undef C } static bool is_wrapped_kitten(const char *arg) { char buf[64]; snprintf(buf, sizeof(buf)-1, " %s ", arg); return strstr(" " WRAPPED_KITTENS " ", buf); } static void exec_kitten(int argc, char *argv[], char *exe_dir) { char exe[PATH_MAX+1] = {0}; snprintf(exe, PATH_MAX, "%s/kitten", exe_dir); char **newargv = malloc(sizeof(char*) * (argc + 1)); memcpy(newargv, argv, sizeof(char*) * argc); newargv[argc] = 0; newargv[0] = "kitten"; errno = 0; execv(exe, newargv); fprintf(stderr, "Failed to execute kitten (%s) with error: %s\n", exe, strerror(errno)); exit(1); } static void delegate_to_kitten_if_possible(int argc, char *argv[], char* exe_dir) { if (argc > 1 && argv[1][0] == '@') exec_kitten(argc, argv, exe_dir); if (argc > 2 && strcmp(argv[1], "+kitten") == 0 && is_wrapped_kitten(argv[2])) exec_kitten(argc - 1, argv + 1, exe_dir); if (argc > 3 && strcmp(argv[1], "+") == 0 && strcmp(argv[2], "kitten") == 0 && is_wrapped_kitten(argv[3])) exec_kitten(argc - 2, argv + 2, exe_dir); } static bool is_boolean_flag(const char *x) { static const char *all_boolean_options = KITTY_CLI_BOOL_OPTIONS; char buf[128]; snprintf(buf, sizeof(buf), " %s ", x); return strstr(all_boolean_options, buf) != NULL; } static void handle_fast_commandline(int argc, char *argv[]) { char current_option_expecting_argument[128] = {0}; CLIOptions opts = {0}; int first_arg = 1; if (argc > 1 && strcmp(argv[1], "+open") == 0) { first_arg = 2; } else if (argc > 2 && strcmp(argv[1], "+") == 0 && strcmp(argv[2], "open") == 0) { first_arg = 3; } for (int i = first_arg; i < argc; i++) { const char *arg = argv[i]; if (current_option_expecting_argument[0]) { handle_option_value: if (strcmp(current_option_expecting_argument, "session") == 0) { opts.session = arg; } else if (strcmp(current_option_expecting_argument, "instance-group") == 0) { opts.instance_group = arg; } current_option_expecting_argument[0] = 0; } else { if (!arg || !arg[0] || !arg[1] || arg[0] != '-' || strcmp(arg, "--") == 0) { if (first_arg > 1) { opts.open_urls = argv + i; opts.open_url_count = argc - i; } break; } if (arg[1] == '-') { // long opt const char *equal = strchr(arg, '='); if (equal == NULL) { if (strcmp(arg+2, "version") == 0) { opts.version_requested = true; } else if (strcmp(arg+2, "single-instance") == 0) { opts.single_instance = true; } else if (strcmp(arg+2, "wait-for-single-instance-window-close") == 0) { opts.wait_for_single_instance_window_close = true; } else if (!is_boolean_flag(arg+2)) { strncpy(current_option_expecting_argument, arg+2, sizeof(current_option_expecting_argument)-1); } } else { memcpy(current_option_expecting_argument, arg+2, equal - (arg + 2)); arg = equal + 1; goto handle_option_value; } } else { char buf[2] = {0}; for (int i = 1; arg[i] != 0; i++) { switch(arg[i]) { case 'v': opts.version_requested = true; break; case '1': opts.single_instance = true; break; default: buf[0] = arg[i]; buf[1] = 0; if (!is_boolean_flag(buf)) { current_option_expecting_argument[0] = arg[i]; current_option_expecting_argument[1] = 0; } } } } } } if (opts.version_requested) { if (isatty(STDOUT_FILENO)) { printf("\x1b[3mkitty\x1b[23m \x1b[32m%s\x1b[39m created by \x1b[1;34mKovid Goyal\x1b[22;39m\n", KITTY_VERSION); } else { printf("kitty %s created by Kovid Goyal\n", KITTY_VERSION); } exit(0); } unsetenv("KITTY_SI_DATA"); if (opts.single_instance) single_instance_main(argc, argv, &opts); } int main(int argc, char *argv[], char* envp[]) { if (argc < 1 || !argv) { fprintf(stderr, "Invalid argc/argv\n"); return 1; } if (!ensure_working_stdio()) return 1; char exe[PATH_MAX+1] = {0}; char exe_dir_buf[PATH_MAX+1] = {0}; RAII_ALLOC(const char, lc_ctype, NULL); #ifdef __APPLE__ lc_ctype = getenv("LC_CTYPE"); if (lc_ctype) lc_ctype = strdup(lc_ctype); #endif if (!read_exe_path(exe, sizeof(exe))) return 1; strncpy(exe_dir_buf, exe, sizeof(exe_dir_buf)); char *exe_dir = dirname(exe_dir_buf); delegate_to_kitten_if_possible(argc, argv, exe_dir); handle_fast_commandline(argc, argv); int num, ret=0; char lib[PATH_MAX+1] = {0}; if (KITTY_LIB_PATH[0] == '/') { num = snprintf(lib, PATH_MAX, "%s", KITTY_LIB_PATH); } else { num = snprintf(lib, PATH_MAX, "%s/%s", exe_dir, KITTY_LIB_PATH); } if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to kitty lib\n"); return 1; } RunData run_data = {.exe = exe, .exe_dir = exe_dir, .lib_dir = lib, .argc = argc, .argv = argv, .lc_ctype = lc_ctype}; ret = run_embedded(&run_data); single_instance_main(-1, NULL, NULL); Py_FinalizeEx(); return ret; } kitty-0.41.1/kitty/launcher/single-instance.c0000664000175000017510000002777514773370543020525 0ustar nileshnilesh/* * single-instance.c * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ // We rely on data-types.h including Python.h which defines _DARWIN_C_SOURCE // which we need for _CS_DARWIN_USER_CACHE_DIR #include "../data-types.h" #include "launcher.h" #include "../safe-wrappers.h" #include #include #include #include #include #include #include #include #define CHARSETS_STORAGE static inline #define NO_SINGLE_BYTE_CHARSETS #include "../charsets.c" #define fail_on_errno(msg) { perror(msg); do_exit(1); } void log_error(const char *fmt, ...) { va_list ar; va_start(ar, fmt); vfprintf(stderr, fmt, ar); va_end(ar); } typedef struct cleanup_data { int fd1, fd2; bool close_fd1, close_fd2; char path1[sizeof(struct sockaddr_un) + 16], path2[sizeof(struct sockaddr_un) + 16]; } cleanup_data; struct { cleanup_data si, notify; } cleanup_entries = {0}; static void do_cleanup(cleanup_data *d) { if (d->path1[0]) unlink(d->path1); if (d->path2[0]) unlink(d->path2); if (d->close_fd1) safe_close(d->fd1, __FILE__, __LINE__); if (d->close_fd2) safe_close(d->fd2, __FILE__, __LINE__); } static void cleanup(void) { do_cleanup(&cleanup_entries.notify); do_cleanup(&cleanup_entries.si); } static void do_exit(int code) { cleanup(); exit(code); } #ifndef __APPLE__ static bool is_ok_tmpdir(const char *x) { if (!x || !x[0]) return false; char path[2048]; snprintf(path, sizeof(path), "%s/kitty-si-test-tmpdir-XXXXXXXXXXXX", x); int fd = safe_mkstemp(path); if (fd > -1) { safe_close(fd, __FILE__, __LINE__); unlink(path); return true; } return false; } #endif static void get_socket_dir(char *output, size_t output_capacity) { #define ret_if_ok(x) if (is_ok_tmpdir(x)) { if (snprintf(output, output_capacity, "%s", x) < output_capacity-1); return; } #ifdef __APPLE__ if (confstr(_CS_DARWIN_USER_CACHE_DIR, output, output_capacity)) return; snprintf(output, output_capacity, "%s", "/Library/Caches"); #else #define test_env(x) { const char *e = getenv(#x); ret_if_ok(e); } test_env(XDG_RUNTIME_DIR); test_env(TMPDIR); test_env(TEMP); test_env(TMP); ret_if_ok("/tmp"); ret_if_ok("/var/tmp"); ret_if_ok("/usr/tmp"); test_env(HOME); const char *home = getpwuid(geteuid())->pw_dir; ret_if_ok(home); if (getcwd(output, output_capacity)) return; snprintf(output, output_capacity, "%s", "."); #undef test_env #endif } static void set_single_instance_socket(int fd) { if (listen(fd, 5) != 0) fail_on_errno("Failed to listen on single instance socket"); char buf[256]; snprintf(buf, sizeof(buf), "%d", fd); setenv("KITTY_SI_DATA", buf, 1); } typedef struct membuf { char *data; size_t used, capacity; } membuf; static void write_to_membuf(membuf *m, void *data, size_t sz) { ensure_space_for(m, data, char, m->used + sz, capacity, 8192, false); memcpy(m->data + m->used, data, sz); m->used += sz; } static void write_escaped_char(membuf *m, char ch) { char buf[8]; int n = snprintf(buf, sizeof(buf), "\\u%04x", ch); write_to_membuf(m, buf, n); } static void write_json_string(membuf *m, const char *src, size_t src_len) { ensure_space_for(m, data, char, m->used + 2 + 8 * src_len, capacity, 8192, false); m->data[m->used++] = '"'; uint32_t codep = 0; UTF8State state = 0, prev = UTF8_ACCEPT; for (size_t i = 0; i < src_len; i++) { switch(decode_utf8(&state, &codep, src[i])) { case UTF8_ACCEPT: switch(codep) { case '"': write_to_membuf(m, "\\\"", 2); break; case '\\': write_to_membuf(m, "\\\\", 2); break; case '\t': write_to_membuf(m, "\\t", 2); break; case '\n': write_to_membuf(m, "\\n", 2); break; case '\r': write_to_membuf(m, "\\r", 2); break; START_ALLOW_CASE_RANGE case 0 ... 8: case 11: case 12: case 14 ... 31: write_escaped_char(m, codep); break; END_ALLOW_CASE_RANGE default: m->used += encode_utf8(codep, m->data + m->used); } break; case UTF8_REJECT: state = UTF8_ACCEPT; if (prev != UTF8_ACCEPT && i > 0) i--; break; } prev = state; } m->data[m->used++] = '"'; } static void write_json_string_array(membuf *m, int argc, char *argv[]) { write_to_membuf(m, "[", 1); for (int i = 0; i < argc; i++) { if (i) write_to_membuf(m, ",", 1); write_json_string(m, argv[i], strlen(argv[i])); } write_to_membuf(m, "]", 1); } static void read_till_eof(FILE *f, membuf *m) { while (!feof(f)) { ensure_space_for(m, data, char, m->used + 8192, capacity, 4*8192, false); m->used += fread(m->data, 1, m->capacity - m->used, f); if (ferror(f)) { fclose(f); fail_on_errno("Failed to read from session file"); } } // ensure NULL termination write_to_membuf(m, "\0", 1); m->used--; fclose(f); } static bool bind_unix_socket(int s, const char *basename, struct sockaddr_un *addr, cleanup_data *cleanup) { addr->sun_family = AF_UNIX; const size_t blen = strlen(basename); // First try abstract socket addr->sun_path[0] = 0; memcpy(addr->sun_path + 1, basename, blen + 1); if (safe_bind(s, (struct sockaddr*)addr, sizeof(sa_family_t) + 1 + blen) > -1) return true; if (errno != ENOENT) return false; // Try an actual filesystem file get_socket_dir(addr->sun_path, sizeof(addr->sun_path) - blen - 2); const size_t dlen = strlen(addr->sun_path); if (snprintf(addr->sun_path + dlen, sizeof(addr->sun_path) - dlen, "/%s", basename) < blen + 1) { fprintf(stderr, "Socket directory has path too long for single instance socket file %s\n", addr->sun_path); do_exit(1); } // First lock the socket file using a separate lock file char lock_file_path[sizeof(addr->sun_path) + 16]; snprintf(lock_file_path, sizeof(lock_file_path), "%s.lock", addr->sun_path); int fd = safe_open(lock_file_path, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR); if (fd == -1) return false; cleanup->close_fd2 = true; cleanup->fd2 = fd; snprintf(cleanup->path2, sizeof(cleanup->path2), "%s", lock_file_path); if (safe_lockf(fd, F_TLOCK, 0) != 0) { int saved_errno = errno; safe_close(fd, __FILE__, __LINE__); errno = saved_errno; if (errno == EAGAIN || errno == EACCES) errno = EADDRINUSE; // client return false; } // First unlink the socket file and then try to bind it. if (unlink(addr->sun_path) != 0 && errno != ENOENT) return false; if (safe_bind(s, (struct sockaddr*)addr, sizeof(*addr)) > -1) { snprintf(cleanup->path1, sizeof(cleanup->path1), "%s", addr->sun_path); return true; } return false; } static int create_unix_socket(void) { int s = socket(AF_UNIX, SOCK_STREAM, 0); if (s < 0) fail_on_errno("Failed to create single instance socket object"); int flags; if ((flags = fcntl(s, F_GETFD)) == -1) fail_on_errno("Failed to get fcntl flags for single instance socket"); if (fcntl(s, F_SETFD, flags | FD_CLOEXEC) == -1) fail_on_errno("Failed to set single instance socket to CLOEXEC"); return s; } extern char **environ; static void talk_to_instance(int s, struct sockaddr_un *server_addr, int argc, char *argv[], const CLIOptions *opts) { cleanup_entries.si.path2[0] = 0; cleanup_entries.si.path1[0] = 0; membuf session_data = {0}; if (opts->session && opts->session[0]) { if (strcmp(opts->session, "none") == 0) { session_data.data = "none"; session_data.used = 4; } else if (strcmp(opts->session, "-") == 0) { read_till_eof(stdin, &session_data); } else { FILE *f = safe_fopen(opts->session, "r"); if (f == NULL) fail_on_errno("Failed to open session file for reading"); read_till_eof(f, &session_data); } } membuf output = {0}; #define w(literal) write_to_membuf(&output, literal, sizeof(literal)-1) w("{\"cmd\":\"new_instance\",\"session_data\":"); if (session_data.used) write_json_string(&output, session_data.data, session_data.used); else write_json_string(&output, "", 0); w(",\"args\":"); write_json_string_array(&output, argc, argv); char cwd[4096]; if (!getcwd(cwd, sizeof(cwd))) fail_on_errno("Failed to get cwd"); w(",\"cwd\":"); write_json_string(&output, cwd, strlen(cwd)); w(",\"environ\":{"); char **e = environ; for (; *e; e++) { const char *eq = strchr(*e, '='); if (eq) { if (e != environ) write_to_membuf(&output, ",", 1); write_json_string(&output, *e, eq - *e); w(":"); write_json_string(&output, eq + 1, strlen(eq + 1)); } } w("}"); w(",\"cmdline_args_for_open\":"); if (opts->open_url_count) write_json_string_array(&output, opts->open_url_count, opts->open_urls); else w("[]"); w(",\"notify_on_os_window_death\":"); int notify_socket = -1; if (opts->wait_for_single_instance_window_close) { notify_socket = create_unix_socket(); cleanup_entries.notify.fd1 = notify_socket; cleanup_entries.notify.close_fd1 = true; struct sockaddr_un server_addr; char addr[128]; snprintf(addr, sizeof(addr), "kitty-os-window-close-notify-%d-%d", getpid(), geteuid()); if (!bind_unix_socket(notify_socket, addr, &server_addr, &cleanup_entries.notify)) fail_on_errno("Failed to bind notification socket"); size_t len = strlen(server_addr.sun_path); if (len == 0) len = 1 + strlen(server_addr.sun_path +1); if (listen(notify_socket, 5) != 0) fail_on_errno("Failed to listen on notify socket"); write_json_string(&output, server_addr.sun_path, len); } else w("null"); w("}"); #undef w size_t addr_len = sizeof(sa_family_t); if (!server_addr->sun_path[0]) addr_len += 1 + strlen(server_addr->sun_path + 1); else addr_len = sizeof(*server_addr); if (safe_connect(s, (struct sockaddr*)server_addr, addr_len) != 0) { fail_on_errno("Failed to connect to single instance socket"); } size_t pos = 0; while (pos < output.used) { errno = 0; ssize_t nbytes = write(s, output.data + pos, output.used - pos); if (nbytes <= 0) { if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) continue; break; } pos += nbytes; } if (pos < output.used) fail_on_errno("Failed to write message to single instance socket"); shutdown(s, SHUT_RDWR); safe_close(s, __FILE__, __LINE__); if (notify_socket > -1) { int fd = safe_accept(notify_socket, NULL, NULL); if (fd < 0) fail_on_errno("Failed to accept connection on notify socket"); char rbuf; while (true) { ssize_t n = recv(notify_socket, &rbuf, 1, 0); if (n < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) continue; break; } shutdown(notify_socket, SHUT_RDWR); safe_close(notify_socket, __FILE__, __LINE__); } } void single_instance_main(int argc, char *argv[], const CLIOptions *opts) { if (argc == -1) { cleanup(); return; } struct sockaddr_un server_addr; char addr_buf[sizeof(server_addr.sun_path)-1]; if (opts->instance_group) snprintf(addr_buf, sizeof(addr_buf), "kitty-ipc-%d-%s", geteuid(), opts->instance_group); else snprintf(addr_buf, sizeof(addr_buf), "kitty-ipc-%d", geteuid()); int s = create_unix_socket(); cleanup_entries.si.fd1 = s; cleanup_entries.si.close_fd1 = true; if (!bind_unix_socket(s, addr_buf, &server_addr, &cleanup_entries.si)) { if (errno == EADDRINUSE) { talk_to_instance(s, &server_addr, argc, argv, opts); do_exit(0); } else fail_on_errno("Failed to bind single instance socket"); } else set_single_instance_socket(s); } kitty-0.41.1/kitty/layout/0000775000175000017510000000000014773370543014770 5ustar nileshnileshkitty-0.41.1/kitty/layout/__init__.py0000664000175000017510000000000014773370543017067 0ustar nileshnileshkitty-0.41.1/kitty/layout/base.py0000664000175000017510000004226314773370543016263 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Generator, Iterable, Iterator, Sequence from functools import partial from itertools import repeat from typing import Any, NamedTuple from kitty.borders import BorderColor from kitty.fast_data_types import Region, set_active_window, viewport_for_window from kitty.options.types import Options from kitty.types import Edges, WindowGeometry from kitty.typing import TypedDict, WindowType from kitty.window_list import WindowGroup, WindowList class BorderLine(NamedTuple): edges: Edges = Edges() color: BorderColor = BorderColor.inactive class LayoutOpts: def __init__(self, data: dict[str, str]): pass def serialized(self) -> dict[str, Any]: return {} class LayoutData(NamedTuple): content_pos: int = 0 cells_per_window: int = 0 space_before: int = 0 space_after: int = 0 content_size: int = 0 DecorationPairs = Sequence[tuple[int, int]] LayoutDimension = Generator[LayoutData, None, None] ListOfWindows = list[WindowType] class NeighborsMap(TypedDict): left: list[int] top: list[int] right: list[int] bottom: list[int] class LayoutGlobalData: draw_minimal_borders: bool = True draw_active_borders: bool = True alignment_x: int = 0 alignment_y: int = 0 central: Region = Region((0, 0, 199, 199, 200, 200)) cell_width: int = 20 cell_height: int = 20 lgd = LayoutGlobalData() def idx_for_id(win_id: int, windows: Iterable[WindowType]) -> int | None: for i, w in enumerate(windows): if w.id == win_id: return i return None def set_layout_options(opts: Options) -> None: lgd.draw_minimal_borders = opts.draw_minimal_borders and sum(opts.window_margin_width) == 0 lgd.draw_active_borders = opts.active_border_color is not None lgd.alignment_x = -1 if opts.placement_strategy.endswith('left') else 1 if opts.placement_strategy.endswith('right') else 0 lgd.alignment_y = -1 if opts.placement_strategy.startswith('top') else 1 if opts.placement_strategy.startswith('bottom') else 0 def convert_bias_map(bias: dict[int, float], number_of_windows: int, number_of_cells: int) -> Sequence[float]: cells_per_window, extra = divmod(number_of_cells, number_of_windows) cell_map = list(repeat(cells_per_window, number_of_windows)) cell_map[-1] += extra base_bias = [x / number_of_cells for x in cell_map] return distribute_indexed_bias(base_bias, bias) def calculate_cells_map( bias: None | Sequence[float] | dict[int, float], number_of_windows: int, number_of_cells: int ) -> list[int]: if isinstance(bias, dict): bias = convert_bias_map(bias, number_of_windows, number_of_cells) cells_per_window = number_of_cells // number_of_windows if bias is not None and number_of_windows > 1 and number_of_windows == len(bias) and cells_per_window > 5: cells_map = [int(b * number_of_cells) for b in bias] while min(cells_map) < 5: maxi, mini = map(cells_map.index, (max(cells_map), min(cells_map))) if maxi == mini: break cells_map[mini] += 1 cells_map[maxi] -= 1 else: cells_map = list(repeat(cells_per_window, number_of_windows)) extra = number_of_cells - sum(cells_map) if extra > 0: cells_map[-1] += extra return cells_map def layout_dimension( start_at: int, length: int, cell_length: int, decoration_pairs: DecorationPairs, alignment: int = 0, bias: None | Sequence[float] | dict[int, float] = None ) -> LayoutDimension: number_of_windows = len(decoration_pairs) number_of_cells = length // cell_length dec_vals: Iterable[int] = map(sum, decoration_pairs) space_needed_for_decorations = sum(dec_vals) extra = length - number_of_cells * cell_length while extra < space_needed_for_decorations: number_of_cells -= 1 extra = length - number_of_cells * cell_length cells_map = calculate_cells_map(bias, number_of_windows, number_of_cells) assert sum(cells_map) == number_of_cells extra = length - number_of_cells * cell_length - space_needed_for_decorations pos = start_at # start if alignment > 0: # end pos += extra elif alignment == 0: # center pos += extra // 2 last_i = len(cells_map) - 1 for i, cells_per_window in enumerate(cells_map): before_dec, after_dec = decoration_pairs[i] pos += before_dec if i == 0: before_space = pos - start_at else: before_space = before_dec content_size = cells_per_window * cell_length if i == last_i: after_space = (start_at + length) - (pos + content_size) else: after_space = after_dec yield LayoutData(pos, cells_per_window, before_space, after_space, content_size) pos += content_size + after_space class Rect(NamedTuple): left: int top: int right: int bottom: int def blank_rects_for_window(wg: WindowGeometry) -> Generator[Rect, None, None]: left_width, right_width = wg.spaces.left, wg.spaces.right top_height, bottom_height = wg.spaces.top, wg.spaces.bottom if left_width > 0: yield Rect(wg.left - left_width, wg.top - top_height, wg.left, wg.bottom + bottom_height) if top_height > 0: yield Rect(wg.left, wg.top - top_height, wg.right + right_width, wg.top) if right_width > 0: yield Rect(wg.right, wg.top, wg.right + right_width, wg.bottom + bottom_height) if bottom_height > 0: yield Rect(wg.left, wg.bottom, wg.right, wg.bottom + bottom_height) def window_geometry(xstart: int, xnum: int, ystart: int, ynum: int, left: int, top: int, right: int, bottom: int) -> WindowGeometry: return WindowGeometry( left=xstart, top=ystart, xnum=max(0, xnum), ynum=max(0, ynum), right=xstart + lgd.cell_width * xnum, bottom=ystart + lgd.cell_height * ynum, spaces=Edges(left, top, right, bottom) ) def window_geometry_from_layouts(x: LayoutData, y: LayoutData) -> WindowGeometry: return window_geometry(x.content_pos, x.cells_per_window, y.content_pos, y.cells_per_window, x.space_before, y.space_before, x.space_after, y.space_after) def layout_single_window( xdecoration_pairs: DecorationPairs, ydecoration_pairs: DecorationPairs, xalignment: int = 0, yalignment: int = 0, ) -> WindowGeometry: x = next(layout_dimension(lgd.central.left, lgd.central.width, lgd.cell_width, xdecoration_pairs, alignment=xalignment)) y = next(layout_dimension(lgd.central.top, lgd.central.height, lgd.cell_height, ydecoration_pairs, alignment=yalignment)) return window_geometry_from_layouts(x, y) def safe_increment_bias(old_val: float, increment: float = 0) -> float: return max(0.1, min(old_val + increment, 0.9)) def normalize_biases(biases: list[float]) -> list[float]: s = sum(biases) if s == 1.0: return biases return [x/s for x in biases] def distribute_indexed_bias(base_bias: Sequence[float], index_bias_map: dict[int, float]) -> Sequence[float]: if not index_bias_map: return base_bias ans = list(base_bias) limit = len(ans) for row, increment in index_bias_map.items(): if row >= limit or not increment: continue other_increment = -increment / (limit - 1) ans = [safe_increment_bias(b, increment if i == row else other_increment) for i, b in enumerate(ans)] return normalize_biases(ans) class Layout: name: str = '' needs_window_borders = True must_draw_borders = False # can be overridden to customize behavior from kittens layout_opts = LayoutOpts({}) only_active_window_visible = False def __init__(self, os_window_id: int, tab_id: int, layout_opts: str = '') -> None: self.set_owner(os_window_id, tab_id) # A set of rectangles corresponding to the blank spaces at the edges of # this layout, i.e. spaces that are not covered by any window self.blank_rects: list[Rect] = [] self.layout_opts = self.parse_layout_opts(layout_opts) assert self.name is not None self.full_name = f'{self.name}:{layout_opts}' if layout_opts else self.name self.remove_all_biases() def set_owner(self, os_window_id: int, tab_id: int) -> None: # Useful when moving a layout from one tab to another typically a detached tab being re-attached self.os_window_id = os_window_id self.tab_id = tab_id self.set_active_window_in_os_window = partial(set_active_window, os_window_id, tab_id) def bias_increment_for_cell(self, all_windows: WindowList, is_horizontal: bool) -> float: self._set_dimensions() return self.calculate_bias_increment_for_a_single_cell(all_windows, is_horizontal) def calculate_bias_increment_for_a_single_cell(self, all_windows: WindowList, is_horizontal: bool) -> float: if is_horizontal: return (lgd.cell_width + 1) / lgd.central.width return (lgd.cell_height + 1) / lgd.central.height def apply_bias(self, window_id: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool: return False def remove_all_biases(self) -> bool: return False def modify_size_of_window(self, all_windows: WindowList, window_id: int, increment: float, is_horizontal: bool = True) -> bool: idx = all_windows.group_idx_for_window(window_id) if idx is None or not increment: return False return self.apply_bias(idx, increment, all_windows, is_horizontal) def parse_layout_opts(self, layout_opts: str | None = None) -> LayoutOpts: data: dict[str, str] = {} if layout_opts: for x in layout_opts.split(';'): k, v = x.partition('=')[::2] if k and v: data[k] = v return type(self.layout_opts)(data) def nth_window(self, all_windows: WindowList, num: int) -> WindowType | None: return all_windows.active_window_in_nth_group(num, clamp=True) def activate_nth_window(self, all_windows: WindowList, num: int) -> None: all_windows.set_active_group_idx(num) def next_window(self, all_windows: WindowList, delta: int = 1) -> None: all_windows.activate_next_window_group(delta) def neighbors(self, all_windows: WindowList) -> NeighborsMap: w = all_windows.active_window assert w is not None return self.neighbors_for_window(w, all_windows) def move_window(self, all_windows: WindowList, delta: int = 1) -> bool: if all_windows.num_groups < 2 or not delta: return False return all_windows.move_window_group(by=delta) def move_window_to_group(self, all_windows: WindowList, group: int) -> bool: return all_windows.move_window_group(to_group=group) def add_window( self, all_windows: WindowList, window: WindowType, location: str | None = None, overlay_for: int | None = None, put_overlay_behind: bool = False, bias: float | None = None, next_to: WindowType | None = None, ) -> WindowType | None: if overlay_for is not None: underlay = all_windows.id_map.get(overlay_for) if underlay is not None: window.margin, window.padding = underlay.margin.copy(), underlay.padding.copy() all_windows.add_window(window, group_of=overlay_for, head_of_group=put_overlay_behind) return underlay if location == 'neighbor': location = 'after' self.add_non_overlay_window(all_windows, window, location, bias, next_to) return None def add_non_overlay_window( self, all_windows: WindowList, window: WindowType, location: str | None, bias: float | None = None, next_to: WindowType | None = None ) -> None: before = False next_to = next_to or all_windows.active_window if location is not None: if location in ('after', 'vsplit', 'hsplit'): pass elif location == 'before': before = True elif location == 'first': before = True next_to = None elif location == 'last': next_to = None all_windows.add_window(window, next_to=next_to, before=before) if bias is not None: idx = all_windows.group_idx_for_window(window) if idx is not None: self._set_dimensions() self._bias_slot(all_windows, idx, bias) def _bias_slot(self, all_windows: WindowList, idx: int, bias: float) -> bool: fractional_bias = max(10, min(abs(bias), 90)) / 100 h, v = self.calculate_bias_increment_for_a_single_cell(all_windows, True), self.calculate_bias_increment_for_a_single_cell(all_windows, False) nh, nv = lgd.central.width / lgd.cell_width, lgd.central.height / lgd.cell_height f = max(-90, min(bias, 90)) / 100. return self.bias_slot(all_windows, idx, fractional_bias, h * nh *f, v * nv * f) def bias_slot(self, all_windows: WindowList, idx: int, fractional_bias: float, cell_increment_bias_h: float, cell_increment_bias_v: float) -> bool: return False def update_visibility(self, all_windows: WindowList) -> None: active_window = all_windows.active_window for window, is_group_leader in all_windows.iter_windows_with_visibility(): is_visible = window is active_window or (is_group_leader and not self.only_active_window_visible) window.set_visible_in_layout(is_visible) def _set_dimensions(self) -> None: lgd.central, tab_bar, vw, vh, lgd.cell_width, lgd.cell_height = viewport_for_window(self.os_window_id) def __call__(self, all_windows: WindowList) -> None: self._set_dimensions() self.update_visibility(all_windows) self.blank_rects = [] self.do_layout(all_windows) def layout_single_window_group(self, wg: WindowGroup, add_blank_rects: bool = True) -> None: bw = 1 if self.must_draw_borders else 0 xdecoration_pairs = (( wg.decoration('left', border_mult=bw, is_single_window=True), wg.decoration('right', border_mult=bw, is_single_window=True), ),) ydecoration_pairs = (( wg.decoration('top', border_mult=bw, is_single_window=True), wg.decoration('bottom', border_mult=bw, is_single_window=True), ),) geom = layout_single_window(xdecoration_pairs, ydecoration_pairs, xalignment=lgd.alignment_x, yalignment=lgd.alignment_y) wg.set_geometry(geom) if add_blank_rects and wg: self.blank_rects.extend(blank_rects_for_window(geom)) def xlayout( self, groups: Iterator[WindowGroup], bias: None | Sequence[float] | dict[int, float] = None, start: int | None = None, size: int | None = None, offset: int = 0, border_mult: int = 1 ) -> LayoutDimension: decoration_pairs = tuple( (g.decoration('left', border_mult=border_mult), g.decoration('right', border_mult=border_mult)) for i, g in enumerate(groups) if i >= offset ) if start is None: start = lgd.central.left if size is None: size = lgd.central.width return layout_dimension(start, size, lgd.cell_width, decoration_pairs, bias=bias, alignment=lgd.alignment_x) def ylayout( self, groups: Iterator[WindowGroup], bias: None | Sequence[float] | dict[int, float] = None, start: int | None = None, size: int | None = None, offset: int = 0, border_mult: int = 1 ) -> LayoutDimension: decoration_pairs = tuple( (g.decoration('top', border_mult=border_mult), g.decoration('bottom', border_mult=border_mult)) for i, g in enumerate(groups) if i >= offset ) if start is None: start = lgd.central.top if size is None: size = lgd.central.height return layout_dimension(start, size, lgd.cell_height, decoration_pairs, bias=bias, alignment=lgd.alignment_y) def set_window_group_geometry(self, wg: WindowGroup, xl: LayoutData, yl: LayoutData) -> WindowGeometry: geom = window_geometry_from_layouts(xl, yl) wg.set_geometry(geom) self.blank_rects.extend(blank_rects_for_window(geom)) return geom def do_layout(self, windows: WindowList) -> None: raise NotImplementedError() def neighbors_for_window(self, window: WindowType, windows: WindowList) -> NeighborsMap: return {'left': [], 'right': [], 'top': [], 'bottom': []} def compute_needs_borders_map(self, all_windows: WindowList) -> dict[int, bool]: return all_windows.compute_needs_borders_map(lgd.draw_active_borders) def get_minimal_borders(self, windows: WindowList) -> Generator[BorderLine, None, None]: self._set_dimensions() yield from self.minimal_borders(windows) def minimal_borders(self, windows: WindowList) -> Generator[BorderLine, None, None]: return yield BorderLine() # type: ignore def layout_action(self, action_name: str, args: Sequence[str], all_windows: WindowList) -> bool | None: pass def layout_state(self) -> dict[str, Any]: return {} kitty-0.41.1/kitty/layout/grid.py0000664000175000017510000003235314773370543016275 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Callable, Generator, Sequence from functools import lru_cache from itertools import repeat from math import ceil, floor from typing import Any from kitty.borders import BorderColor from kitty.types import Edges from kitty.typing import WindowType from kitty.window_list import WindowGroup, WindowList from .base import BorderLine, Layout, LayoutData, LayoutDimension, ListOfWindows, NeighborsMap, layout_dimension, lgd from .tall import neighbors_for_tall_window @lru_cache def calc_grid_size(n: int) -> tuple[int, int, int, int]: if n <= 5: ncols = 1 if n == 1 else 2 else: for ncols in range(3, (n // 2) + 1): if ncols * ncols >= n: break nrows = n // ncols special_rows = n - (nrows * (ncols - 1)) special_col = 0 if special_rows < nrows else ncols - 1 return ncols, nrows, special_rows, special_col class Grid(Layout): name: str = 'grid' no_minimal_window_borders = True def remove_all_biases(self) -> bool: self.biased_rows: dict[int, float] = {} self.biased_cols: dict[int, float] = {} return True def column_layout( self, num: int, bias: Sequence[float] | None = None, ) -> LayoutDimension: decoration_pairs = tuple(repeat((0, 0), num)) return layout_dimension(lgd.central.left, lgd.central.width, lgd.cell_width, decoration_pairs, bias=bias, alignment=lgd.alignment_x) def row_layout( self, num: int, bias: Sequence[float] | None = None, ) -> LayoutDimension: decoration_pairs = tuple(repeat((0, 0), num)) return layout_dimension(lgd.central.top, lgd.central.height, lgd.cell_height, decoration_pairs, bias=bias, alignment=lgd.alignment_y) def variable_layout(self, layout_func: Callable[..., LayoutDimension], num_windows: int, biased_map: dict[int, float]) -> LayoutDimension: return layout_func(num_windows, bias=biased_map if num_windows > 1 else None) def position_for_window_idx(self, idx: int, num_windows: int, ncols:int , nrows: int, special_rows: int, special_col: int) -> tuple[int, int]: row_num = col_num = 0 def on_col_done(col_windows: list[int]) -> None: nonlocal col_num, row_num row_num = 0 col_num += 1 for window_idx, xl, yl in self.layout_windows( num_windows, nrows, ncols, special_rows, special_col, on_col_done): if idx == window_idx: return row_num, col_num row_num += 1 return 0, 0 def bias_slot(self, all_windows: WindowList, idx: int, fractional_bias: float, cell_increment_bias_h: float, cell_increment_bias_v: float) -> bool: num_windows = all_windows.num_groups ncols, nrows, special_rows, special_col = calc_grid_size(num_windows) row_num, col_num = self.position_for_window_idx(idx, num_windows, ncols, nrows, special_rows, special_col) if row_num == 0: b = self.biased_cols layout_func = self.column_layout bias_idx = col_num increment = cell_increment_bias_h else: b = self.biased_rows layout_func = self.row_layout bias_idx = row_num increment = cell_increment_bias_v before_layout = tuple(self.variable_layout(layout_func, num_windows, b)) b[bias_idx] = increment return tuple(self.variable_layout(layout_func, num_windows, b)) == before_layout def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool: num_windows = all_windows.num_groups ncols, nrows, special_rows, special_col = calc_grid_size(num_windows) row_num, col_num = self.position_for_window_idx(idx, num_windows, ncols, nrows, special_rows, special_col) if is_horizontal: b = self.biased_cols if ncols < 2: return False bias_idx = col_num attr = 'biased_cols' def layout_func(windows: ListOfWindows, bias: Sequence[float] | None = None) -> LayoutDimension: return self.column_layout(num_windows, bias=bias) else: b = self.biased_rows if max(nrows, special_rows) < 2: return False bias_idx = row_num attr = 'biased_rows' def layout_func(windows: ListOfWindows, bias: Sequence[float] | None = None) -> LayoutDimension: return self.row_layout(num_windows, bias=bias) before_layout = tuple(self.variable_layout(layout_func, num_windows, b)) candidate = b.copy() before = candidate.get(bias_idx, 0) candidate[bias_idx] = before + increment if before_layout == tuple(self.variable_layout(layout_func, num_windows, candidate)): return False setattr(self, attr, candidate) return True def layout_windows( self, num_windows: int, nrows: int, ncols: int, special_rows: int, special_col: int, on_col_done: Callable[[list[int]], None] = lambda col_windows: None ) -> Generator[tuple[int, LayoutData, LayoutData], None, None]: # Distribute windows top-to-bottom, left-to-right (i.e. in columns) xlayout = self.variable_layout(self.column_layout, ncols, self.biased_cols) yvals_normal = tuple(self.variable_layout(self.row_layout, nrows, self.biased_rows)) yvals_special = yvals_normal if special_rows == nrows else tuple(self.variable_layout(self.row_layout, special_rows, self.biased_rows)) pos = 0 for col in range(ncols): rows = special_rows if col == special_col else nrows yls = yvals_special if col == special_col else yvals_normal xl = next(xlayout) col_windows = [] for i, yl in enumerate(yls): window_idx = pos + i yield window_idx, xl, yl col_windows.append(window_idx) pos += rows on_col_done(col_windows) def do_layout(self, all_windows: WindowList) -> None: n = all_windows.num_groups if n == 1: self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups())) return ncols, nrows, special_rows, special_col = calc_grid_size(n) groups = tuple(all_windows.iter_all_layoutable_groups()) win_col_map: list[list[WindowGroup]] = [] def on_col_done(col_windows: list[int]) -> None: col_windows_w = [groups[i] for i in col_windows] win_col_map.append(col_windows_w) def extents(ld: LayoutData) -> tuple[int, int]: start = ld.content_pos - ld.space_before size = ld.space_before + ld.space_after + ld.content_size return start, size def layout(ld: LayoutData, cell_length: int, before_dec: int, after_dec: int) -> LayoutData: start, size = extents(ld) space_needed_for_decorations = before_dec + after_dec content_size = size - space_needed_for_decorations number_of_cells = content_size // cell_length cell_area = number_of_cells * cell_length extra = content_size - cell_area if lgd.alignment_x == 0: # center before_dec += extra // 2 elif lgd.alignment_x > 0: # end before_dec += extra return LayoutData(start + before_dec, number_of_cells, before_dec, size - cell_area - before_dec, cell_area) def position_window_in_grid_cell(window_idx: int, xl: LayoutData, yl: LayoutData) -> None: wg = groups[window_idx] edges = Edges(wg.decoration('left'), wg.decoration('top'), wg.decoration('right'), wg.decoration('bottom')) xl = layout(xl, lgd.cell_width, edges.left, edges.right) yl = layout(yl, lgd.cell_height, edges.top, edges.bottom) self.set_window_group_geometry(wg, xl, yl) for window_idx, xl, yl in self.layout_windows( n, nrows, ncols, special_rows, special_col, on_col_done): position_window_in_grid_cell(window_idx, xl, yl) def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None, None]: n = all_windows.num_groups if not lgd.draw_minimal_borders or n < 2: return needs_borders_map = all_windows.compute_needs_borders_map(lgd.draw_active_borders) ncols, nrows, special_rows, special_col = calc_grid_size(n) is_first_row: set[int] = set() is_last_row: set[int] = set() is_first_column: set[int] = set() is_last_column: set[int] = set() groups = tuple(all_windows.iter_all_layoutable_groups()) bw = groups[0].effective_border() if not bw: return xl: LayoutData = LayoutData() yl: LayoutData = LayoutData() prev_col_windows: list[int] = [] layout_data_map: dict[int, tuple[LayoutData, LayoutData]] = {} def on_col_done(col_windows: list[int]) -> None: nonlocal prev_col_windows, is_first_column if col_windows: is_first_row.add(groups[col_windows[0]].id) is_last_row.add(groups[col_windows[-1]].id) if not prev_col_windows: is_first_column = {groups[x].id for x in col_windows} prev_col_windows = col_windows all_groups_in_order: list[WindowGroup] = [] for window_idx, xl, yl in self.layout_windows(n, nrows, ncols, special_rows, special_col, on_col_done): wg = groups[window_idx] all_groups_in_order.append(wg) layout_data_map[wg.id] = xl, yl is_last_column = {groups[x].id for x in prev_col_windows} active_group = all_windows.active_group def ends(yl: LayoutData) -> tuple[int, int]: return yl.content_pos - yl.space_before, yl.content_pos + yl.content_size + yl.space_after def borders_for_window(gid: int) -> Generator[Edges, None, None]: xl, yl = layout_data_map[gid] left, right = ends(xl) top, bottom = ends(yl) first_row, last_row = gid in is_first_row, gid in is_last_row first_column, last_column = gid in is_first_column, gid in is_last_column # Horizontal if not first_row: yield Edges(left, top, right, top + bw) if not last_row: yield Edges(left, bottom - bw, right, bottom) # Vertical if not first_column: yield Edges(left, top, left + bw, bottom) if not last_column: yield Edges(right - bw, top, right, bottom) for wg in all_groups_in_order: for edges in borders_for_window(wg.id): yield BorderLine(edges) for wg in all_groups_in_order: if needs_borders_map.get(wg.id): color = BorderColor.active if wg is active_group else BorderColor.bell for edges in borders_for_window(wg.id): yield BorderLine(edges, color) def neighbors_for_window(self, window: WindowType, all_windows: WindowList) -> NeighborsMap: n = all_windows.num_groups if n < 4: return neighbors_for_tall_window(1, window, all_windows) wg = all_windows.group_for_window(window) assert wg is not None ncols, nrows, special_rows, special_col = calc_grid_size(n) blank_row: list[int | None] = [None for i in range(ncols)] matrix = tuple(blank_row[:] for j in range(max(nrows, special_rows))) wi = all_windows.iter_all_layoutable_groups() pos_map: dict[int, tuple[int, int]] = {} col_counts: list[int] = [] for col in range(ncols): rows = special_rows if col == special_col else nrows for row in range(rows): w = next(wi) matrix[row][col] = wid = w.id pos_map[wid] = row, col col_counts.append(rows) row, col = pos_map[wg.id] def neighbors(row: int, col: int) -> list[int]: try: ans = matrix[row][col] except IndexError: ans = None return [] if ans is None else [ans] def side(row: int, col: int, delta: int) -> list[int]: neighbor_col = col + delta neighbor_nrows = col_counts[neighbor_col] nrows = col_counts[col] if neighbor_nrows == nrows: return neighbors(row, neighbor_col) start_row = floor(neighbor_nrows * row / nrows) end_row = ceil(neighbor_nrows * (row + 1) / nrows) xs = [] for neighbor_row in range(start_row, end_row): xs.extend(neighbors(neighbor_row, neighbor_col)) return xs return { 'top': neighbors(row-1, col) if row else [], 'bottom': neighbors(row + 1, col), 'left': side(row, col, -1) if col else [], 'right': side(row, col, 1) if col < ncols - 1 else [], } def layout_state(self) -> dict[str, Any]: return { 'biased_cols': self.biased_cols, 'biased_rows': self.biased_rows } kitty-0.41.1/kitty/layout/interface.py0000664000175000017510000000245314773370543017306 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from .base import Layout from .grid import Grid from .splits import Splits from .stack import Stack from .tall import Fat, Tall from .vertical import Horizontal, Vertical all_layouts: dict[str, type[Layout]] = { Stack.name: Stack, Tall.name: Tall, Fat.name: Fat, Vertical.name: Vertical, Horizontal.name: Horizontal, Grid.name: Grid, Splits.name: Splits, } KeyType = tuple[str, int, int, str] class CreateLayoutObjectFor: cache: dict[KeyType, Layout] = {} def __call__( self, name: str, os_window_id: int, tab_id: int, layout_opts: str = '' ) -> Layout: key = name, os_window_id, tab_id, layout_opts ans = create_layout_object_for.cache.get(key) if ans is None: name, layout_opts = name.partition(':')[::2] ans = create_layout_object_for.cache[key] = all_layouts[name]( os_window_id, tab_id, layout_opts) return ans create_layout_object_for = CreateLayoutObjectFor() def evict_cached_layouts(tab_id: int) -> None: remove = [key for key in create_layout_object_for.cache if key[2] == tab_id] for key in remove: del create_layout_object_for.cache[key] kitty-0.41.1/kitty/layout/splits.py0000664000175000017510000007176114773370543016674 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Collection, Generator, Sequence from typing import Any, NamedTuple, Optional, Union from kitty.borders import BorderColor from kitty.types import Edges, WindowGeometry from kitty.typing import EdgeLiteral, WindowType from kitty.window_list import WindowGroup, WindowList from .base import BorderLine, Layout, LayoutOpts, NeighborsMap, blank_rects_for_window, lgd, window_geometry_from_layouts class Extent(NamedTuple): start: int = 0 end: int = 0 class Pair: def __init__(self, horizontal: bool = True): self.horizontal = horizontal self.one: Pair | int | None = None self.two: Pair | int | None = None self.bias = 0.5 self.top = self.left = self.width = self.height = 0 self.between_borders: list[Edges] = [] self.first_extent = self.second_extent = Extent() def __repr__(self) -> str: return 'Pair(horizontal={}, bias={:.2f}, one={}, two={}, between_borders={})'.format( self.horizontal, self.bias, self.one, self.two, self.between_borders) def all_window_ids(self) -> Generator[int, None, None]: if self.one is not None: if isinstance(self.one, Pair): yield from self.one.all_window_ids() else: yield self.one if self.two is not None: if isinstance(self.two, Pair): yield from self.two.all_window_ids() else: yield self.two def self_and_descendants(self) -> Generator['Pair', None, None]: yield self if isinstance(self.one, Pair): yield from self.one.self_and_descendants() if isinstance(self.two, Pair): yield from self.two.self_and_descendants() def pair_for_window(self, window_id: int) -> Optional['Pair']: if self.one == window_id or self.two == window_id: return self ans = None if isinstance(self.one, Pair): ans = self.one.pair_for_window(window_id) if ans is None and isinstance(self.two, Pair): ans = self.two.pair_for_window(window_id) return ans def swap_windows(self, a: int, b: int) -> None: pa = self.pair_for_window(a) pb = self.pair_for_window(b) if pa is None or pb is None: return if pa.one == a: if pb.one == b: pa.one, pb.one = pb.one, pa.one else: pa.one, pb.two = pb.two, pa.one else: if pb.one == b: pa.two, pb.one = pb.one, pa.two else: pa.two, pb.two = pb.two, pa.two def parent(self, root: 'Pair') -> Optional['Pair']: for q in root.self_and_descendants(): if q.one is self or q.two is self: return q return None def remove_windows(self, window_ids: Collection[int]) -> None: if isinstance(self.one, int) and self.one in window_ids: self.one = None if isinstance(self.two, int) and self.two in window_ids: self.two = None if self.one is None and self.two is not None: self.one, self.two = self.two, None @property def is_redundant(self) -> bool: return self.one is None or self.two is None def collapse_redundant_pairs(self) -> None: while isinstance(self.one, Pair) and self.one.is_redundant: self.one = self.one.one or self.one.two while isinstance(self.two, Pair) and self.two.is_redundant: self.two = self.two.one or self.two.two if isinstance(self.one, Pair): self.one.collapse_redundant_pairs() if isinstance(self.two, Pair): self.two.collapse_redundant_pairs() def balanced_add(self, window_id: int) -> 'Pair': if self.one is None or self.two is None: if self.one is None: if self.two is None: self.one = window_id return self self.one, self.two = self.two, self.one self.two = window_id return self if isinstance(self.one, Pair) and isinstance(self.two, Pair): one_count = sum(1 for _ in self.one.all_window_ids()) two_count = sum(1 for _ in self.two.all_window_ids()) q = self.one if one_count < two_count else self.two return q.balanced_add(window_id) if not isinstance(self.one, Pair) and not isinstance(self.two, Pair): pair = Pair(horizontal=self.horizontal) pair.balanced_add(self.one) pair.balanced_add(self.two) self.one, self.two = pair, window_id return self if isinstance(self.one, Pair): window_to_be_split = self.two self.two = pair = Pair(horizontal=self.horizontal) else: window_to_be_split = self.one self.one = pair = Pair(horizontal=self.horizontal) assert isinstance(window_to_be_split, int) pair.balanced_add(window_to_be_split) pair.balanced_add(window_id) return pair def split_and_add(self, existing_window_id: int, new_window_id: int, horizontal: bool, after: bool) -> 'Pair': q = (existing_window_id, new_window_id) if after else (new_window_id, existing_window_id) if self.is_redundant: pair = self pair.horizontal = horizontal self.one, self.two = q final_pair = pair else: pair = Pair(horizontal=horizontal) if self.one == existing_window_id: self.one = pair else: self.two = pair for wid in q: qp = pair.balanced_add(wid) if wid == new_window_id: final_pair = qp return final_pair def apply_window_geometry( self, window_id: int, window_geometry: WindowGeometry, id_window_map: dict[int, WindowGroup], layout_object: Layout ) -> None: wg = id_window_map[window_id] wg.set_geometry(window_geometry) layout_object.blank_rects.extend(blank_rects_for_window(window_geometry)) def effective_border(self, id_window_map: dict[int, WindowGroup]) -> int: for wid in self.all_window_ids(): return id_window_map[wid].effective_border() return 0 def minimum_width(self, id_window_map: dict[int, WindowGroup]) -> int: if self.one is None or self.two is None or not self.horizontal: return lgd.cell_width bw = self.effective_border(id_window_map) if lgd.draw_minimal_borders else 0 ans = 2 * bw if isinstance(self.one, Pair): ans += self.one.minimum_width(id_window_map) else: ans += lgd.cell_width if isinstance(self.two, Pair): ans += self.two.minimum_width(id_window_map) else: ans += lgd.cell_width return ans def minimum_height(self, id_window_map: dict[int, WindowGroup]) -> int: if self.one is None or self.two is None or self.horizontal: return lgd.cell_height bw = self.effective_border(id_window_map) if lgd.draw_minimal_borders else 0 ans = 2 * bw if isinstance(self.one, Pair): ans += self.one.minimum_height(id_window_map) else: ans += lgd.cell_height if isinstance(self.two, Pair): ans += self.two.minimum_height(id_window_map) else: ans += lgd.cell_height return ans def layout_pair( self, left: int, top: int, width: int, height: int, id_window_map: dict[int, WindowGroup], layout_object: Layout ) -> None: self.between_borders = [] self.left, self.top, self.width, self.height = left, top, width, height bw = self.effective_border(id_window_map) if lgd.draw_minimal_borders else 0 border_mult = 0 if lgd.draw_minimal_borders else 1 bw2 = bw * 2 self.first_extent = self.second_extent = Extent() if self.one is None or self.two is None: q = self.one or self.two if isinstance(q, Pair): return q.layout_pair(left, top, width, height, id_window_map, layout_object) if q is None: return wg = id_window_map[q] xl = next(layout_object.xlayout(iter((wg,)), start=left, size=width, border_mult=border_mult)) yl = next(layout_object.ylayout(iter((wg,)), start=top, size=height, border_mult=border_mult)) geom = window_geometry_from_layouts(xl, yl) self.first_extent = Extent(left, left + width) self.apply_window_geometry(q, geom, id_window_map, layout_object) return if self.horizontal: min_w1 = self.one.minimum_width(id_window_map) if isinstance(self.one, Pair) else lgd.cell_width min_w2 = self.two.minimum_width(id_window_map) if isinstance(self.two, Pair) else lgd.cell_width w1 = max(min_w1, int(self.bias * width) - bw) w2 = width - w1 - bw2 if w2 < min_w2 and w1 >= min_w1 + bw2: w2 = min_w2 w1 = width - w2 self.first_extent = Extent(max(0, left - bw), left + w1 + bw) self.second_extent = Extent(left + w1 + bw, left + width + bw) if isinstance(self.one, Pair): self.one.layout_pair(left, top, w1, height, id_window_map, layout_object) else: wg = id_window_map[self.one] yl = next(layout_object.ylayout(iter((wg,)), start=top, size=height, border_mult=border_mult)) xl = next(layout_object.xlayout(iter((wg,)), start=left, size=w1, border_mult=border_mult)) geom = window_geometry_from_layouts(xl, yl) self.apply_window_geometry(self.one, geom, id_window_map, layout_object) self.between_borders = [ Edges(left + w1, top, left + w1 + bw, top + height), Edges(left + w1 + bw, top, left + w1 + bw2, top + height), ] left += bw2 if isinstance(self.two, Pair): self.two.layout_pair(left + w1, top, w2, height, id_window_map, layout_object) else: wg = id_window_map[self.two] xl = next(layout_object.xlayout(iter((wg,)), start=left + w1, size=w2, border_mult=border_mult)) yl = next(layout_object.ylayout(iter((wg,)), start=top, size=height, border_mult=border_mult)) geom = window_geometry_from_layouts(xl, yl) self.apply_window_geometry(self.two, geom, id_window_map, layout_object) else: min_h1 = self.one.minimum_height(id_window_map) if isinstance(self.one, Pair) else lgd.cell_height min_h2 = self.two.minimum_height(id_window_map) if isinstance(self.two, Pair) else lgd.cell_height h1 = max(min_h1, int(self.bias * height) - bw) h2 = height - h1 - bw2 if h2 < min_h2 and h1 >= min_h1 + bw2: h2 = min_h2 h1 = height - h2 self.first_extent = Extent(max(0, top - bw), top + h1 + bw) self.second_extent = Extent(top + h1 + bw, top + height + bw) if isinstance(self.one, Pair): self.one.layout_pair(left, top, width, h1, id_window_map, layout_object) else: wg = id_window_map[self.one] xl = next(layout_object.xlayout(iter((wg,)), start=left, size=width, border_mult=border_mult)) yl = next(layout_object.ylayout(iter((wg,)), start=top, size=h1, border_mult=border_mult)) geom = window_geometry_from_layouts(xl, yl) self.apply_window_geometry(self.one, geom, id_window_map, layout_object) self.between_borders = [ Edges(left, top + h1, left + width, top + h1 + bw), Edges(left, top + h1 + bw, left + width, top + h1 + bw2), ] top += bw2 if isinstance(self.two, Pair): self.two.layout_pair(left, top + h1, width, h2, id_window_map, layout_object) else: wg = id_window_map[self.two] xl = next(layout_object.xlayout(iter((wg,)), start=left, size=width, border_mult=border_mult)) yl = next(layout_object.ylayout(iter((wg,)), start=top + h1, size=h2, border_mult=border_mult)) geom = window_geometry_from_layouts(xl, yl) self.apply_window_geometry(self.two, geom, id_window_map, layout_object) def set_bias(self, window_id: int, bias: int) -> None: b = max(0, min(bias, 100)) / 100 self.bias = b if window_id == self.one else (1. - b) def modify_size_of_child(self, which: int, increment: float, is_horizontal: bool, layout_object: 'Splits') -> bool: if is_horizontal == self.horizontal and not self.is_redundant: if which == 2: increment *= -1 new_bias = max(0, min(self.bias + increment, 1)) if new_bias != self.bias: self.bias = new_bias return True return False parent = self.parent(layout_object.pairs_root) if parent is not None: which = 1 if parent.one is self else 2 return parent.modify_size_of_child(which, increment, is_horizontal, layout_object) return False def borders_for_window(self, layout_object: 'Splits', window_id: int) -> Generator[Edges, None, None]: is_first = self.one == window_id if self.between_borders: yield self.between_borders[0 if is_first else 1] q = self found_same_direction = found_transverse1 = found_transverse2 = False while not (found_same_direction and found_transverse1 and found_transverse2): parent = q.parent(layout_object.pairs_root) if parent is None: break q = parent if not q.between_borders: continue if q.horizontal == self.horizontal: if not found_same_direction: if self.horizontal: is_before = q.between_borders[0].left <= self.left else: is_before = q.between_borders[0].top <= self.top if is_before == is_first: found_same_direction = True edges = q.between_borders[1 if is_before else 0] if self.horizontal: yield edges._replace(top=self.top, bottom=self.top + self.height) else: yield edges._replace(left=self.left, right=self.left + self.width) else: if self.horizontal: is_before = q.between_borders[0].top <= self.top else: is_before = q.between_borders[0].left <= self.left extent = self.first_extent if is_first else self.second_extent if is_before: if not found_transverse1: found_transverse1 = True edges = q.between_borders[1] if self.horizontal: yield edges._replace(left=extent.start, right=extent.end) else: yield edges._replace(top=extent.start, bottom=extent.end) else: if not found_transverse2: found_transverse2 = True edges = q.between_borders[0] if self.horizontal: yield edges._replace(left=extent.start, right=extent.end) else: yield edges._replace(top=extent.start, bottom=extent.end) def neighbors_for_window(self, window_id: int, ans: NeighborsMap, layout_object: 'Splits', all_windows: WindowList) -> None: def quadrant(is_horizontal: bool, is_first: bool) -> tuple[EdgeLiteral, EdgeLiteral]: if is_horizontal: if is_first: return 'left', 'right' return 'right', 'left' if is_first: return 'top', 'bottom' return 'bottom', 'top' geometries = {group.id: group.geometry for group in all_windows.groups if group.geometry} def extend(other: Union[int, 'Pair', None], edge: EdgeLiteral, which: EdgeLiteral) -> None: if not ans[which] and other: if isinstance(other, Pair): neighbors = ( w for w in other.edge_windows(edge) if is_neighbouring_geometry(geometries[w], geometries[window_id], which)) ans[which].extend(neighbors) else: ans[which].append(other) def is_neighbouring_geometry(a: WindowGeometry, b: WindowGeometry, direction: str) -> bool: def edges(g: WindowGeometry) -> tuple[int, int]: return (g.top, g.bottom) if direction in ['left', 'right'] else (g.left, g.right) a1, a2 = edges(a) b1, b2 = edges(b) return a1 < b2 and a2 > b1 other = self.two if self.one == window_id else self.one extend(other, *quadrant(self.horizontal, self.one == window_id)) child = self while True: parent = child.parent(layout_object.pairs_root) if parent is None: break other = parent.two if child is parent.one else parent.one extend(other, *quadrant(parent.horizontal, child is parent.one)) child = parent def edge_windows(self, edge: str) -> Generator[int, None, None]: if self.is_redundant: q = self.one or self.two if q: if isinstance(q, Pair): yield from q.edge_windows(edge) else: yield q edges = ('left', 'right') if self.horizontal else ('top', 'bottom') if edge in edges: q = self.one if edge in ('left', 'top') else self.two if q: if isinstance(q, Pair): yield from q.edge_windows(edge) else: yield q else: for q in (self.one, self.two): if q: if isinstance(q, Pair): yield from q.edge_windows(edge) else: yield q class SplitsLayoutOpts(LayoutOpts): default_axis_is_horizontal: bool | None = True def __init__(self, data: dict[str, str]): q = data.get('split_axis', 'horizontal') if q == 'auto': self.default_axis_is_horizontal = None else: self.default_axis_is_horizontal = q == 'horizontal' def serialized(self) -> dict[str, Any]: return {'default_axis_is_horizontal': self.default_axis_is_horizontal} class Splits(Layout): name = 'splits' needs_all_windows = True layout_opts = SplitsLayoutOpts({}) no_minimal_window_borders = True @property def default_axis_is_horizontal(self) -> bool | None: return self.layout_opts.default_axis_is_horizontal @property def pairs_root(self) -> Pair: root: Pair | None = getattr(self, '_pairs_root', None) if root is None: horizontal = self.default_axis_is_horizontal if horizontal is None: horizontal = True self._pairs_root = root = Pair(horizontal=horizontal) return root @pairs_root.setter def pairs_root(self, root: Pair) -> None: self._pairs_root = root def remove_windows(self, *windows_to_remove: int) -> None: root = self.pairs_root for pair in root.self_and_descendants(): pair.remove_windows(windows_to_remove) root.collapse_redundant_pairs() if root.one is None or root.two is None: q = root.one or root.two if isinstance(q, Pair): self.pairs_root = q def do_layout(self, all_windows: WindowList) -> None: groups = tuple(all_windows.iter_all_layoutable_groups()) window_count = len(groups) root = self.pairs_root all_present_window_ids = frozenset(w.id for w in groups) already_placed_window_ids = frozenset(root.all_window_ids()) windows_to_remove = already_placed_window_ids - all_present_window_ids if windows_to_remove: self.remove_windows(*windows_to_remove) id_window_map = {w.id: w for w in groups} id_idx_map = {w.id: i for i, w in enumerate(groups)} windows_to_add = all_present_window_ids - already_placed_window_ids if windows_to_add: for wid in sorted(windows_to_add, key=id_idx_map.__getitem__): root.balanced_add(wid) if window_count == 1: self.layout_single_window_group(groups[0]) else: root.layout_pair(lgd.central.left, lgd.central.top, lgd.central.width, lgd.central.height, id_window_map, self) def add_non_overlay_window( self, all_windows: WindowList, window: WindowType, location: str | None, bias: float | None = None, next_to: WindowType | None = None, ) -> None: horizontal = self.default_axis_is_horizontal after = True if location == 'vsplit': horizontal = True elif location == 'hsplit': horizontal = False elif location in ('before', 'first'): after = False aw = next_to or all_windows.active_window if bias: bias = max(0, min(abs(bias), 100)) / 100 if aw is not None and (ag := all_windows.group_for_window(aw)) is not None: group_id = ag.id pair = self.pairs_root.pair_for_window(group_id) if pair is not None: if location == 'split' or horizontal is None: wwidth = aw.geometry.right - aw.geometry.left wheight = aw.geometry.bottom - aw.geometry.top horizontal = wwidth >= wheight target_group = all_windows.add_window(window, next_to=aw, before=not after) parent_pair = pair.split_and_add(group_id, target_group.id, horizontal, after) if bias is not None: parent_pair.bias = bias if parent_pair.one == target_group.id else (1 - bias) return all_windows.add_window(window) p = self.pairs_root.balanced_add(window.id) if bias is not None: p.bias = bias def modify_size_of_window( self, all_windows: WindowList, window_id: int, increment: float, is_horizontal: bool = True ) -> bool: grp = all_windows.group_for_window(window_id) if grp is None: return False pair = self.pairs_root.pair_for_window(grp.id) if pair is None: return False which = 1 if pair.one == grp.id else 2 return pair.modify_size_of_child(which, increment, is_horizontal, self) def remove_all_biases(self) -> bool: for pair in self.pairs_root.self_and_descendants(): pair.bias = 0.5 return True def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None, None]: groups = tuple(all_windows.iter_all_layoutable_groups()) window_count = len(groups) if not lgd.draw_minimal_borders or window_count < 2: return for pair in self.pairs_root.self_and_descendants(): for edges in pair.between_borders: yield BorderLine(edges) needs_borders_map = all_windows.compute_needs_borders_map(lgd.draw_active_borders) ag = all_windows.active_group active_group_id = -1 if ag is None else ag.id for grp_id, needs_borders in needs_borders_map.items(): if needs_borders: qpair = self.pairs_root.pair_for_window(grp_id) if qpair is not None: color = BorderColor.active if grp_id is active_group_id else BorderColor.bell for edges in qpair.borders_for_window(self, grp_id): yield BorderLine(edges, color) def neighbors_for_window(self, window: WindowType, all_windows: WindowList) -> NeighborsMap: wg = all_windows.group_for_window(window) assert wg is not None pair = self.pairs_root.pair_for_window(wg.id) ans: NeighborsMap = {'left': [], 'right': [], 'top': [], 'bottom': []} if pair is not None: pair.neighbors_for_window(wg.id, ans, self, all_windows) return ans def move_window(self, all_windows: WindowList, delta: int = 1) -> bool: before = all_windows.active_group if before is None: return False before_idx = all_windows.active_group_idx moved = super().move_window(all_windows, delta) after = all_windows.groups[before_idx] if moved and before.id != after.id: self.pairs_root.swap_windows(before.id, after.id) return moved def move_window_to_group(self, all_windows: WindowList, group: int) -> bool: before = all_windows.active_group if before is None: return False before_idx = all_windows.active_group_idx moved = super().move_window_to_group(all_windows, group) after = all_windows.groups[before_idx] if moved and before.id != after.id: self.pairs_root.swap_windows(before.id, after.id) return moved def layout_action(self, action_name: str, args: Sequence[str], all_windows: WindowList) -> bool | None: if action_name == 'rotate': args = args or ('90',) try: amt = int(args[0]) except Exception: amt = 90 if amt not in (90, 180, 270): amt = 90 rotate = amt in (90, 270) swap = amt in (180, 270) wg = all_windows.active_group if wg is not None: pair = self.pairs_root.pair_for_window(wg.id) if pair is not None and not pair.is_redundant: if rotate: pair.horizontal = not pair.horizontal if swap: pair.one, pair.two = pair.two, pair.one return True elif action_name == 'move_to_screen_edge': count = 0 for wid in self.pairs_root.all_window_ids(): count += 1 if count > 2: break if count > 1: args = args or ('left',) which = args[0] horizontal = which in ('left', 'right') wg = all_windows.active_group if wg is not None: if count == 2: # special case, a single split pair = self.pairs_root.pair_for_window(wg.id) if pair is not None: if which in ('left', 'top'): if pair.one != wg.id: pair.one, pair.two = pair.two, pair.one pair.bias = 1. - pair.bias else: if pair.one == wg.id: pair.one, pair.two = pair.two, pair.one pair.bias = 1. - pair.bias return True else: self.remove_windows(wg.id) new_root = Pair(horizontal) if which in ('left', 'top'): new_root.balanced_add(wg.id) new_root.two = self.pairs_root else: new_root.one = self.pairs_root new_root.two = wg.id self.pairs_root = new_root return True elif action_name == 'bias': args = args or ('50',) bias = int(args[0]) wg = all_windows.active_group if wg is not None: pair = self.pairs_root.pair_for_window(wg.id) if pair is not None: pair.set_bias(wg.id, bias) return True return None def layout_state(self) -> dict[str, Any]: def add_pair(p: Pair) -> dict[str, Any]: ans: dict[str, Any] = {} ans['horizontal'] = p.horizontal ans['bias'] = p.bias if isinstance(p.one, Pair): ans['one'] = add_pair(p.one) elif p.one is not None: ans['one'] = p.one if isinstance(p.two, Pair): ans['two'] = add_pair(p.two) elif p.two is not None: ans['two'] = p.two return ans return {'pairs': add_pair(self.pairs_root)} kitty-0.41.1/kitty/layout/stack.py0000664000175000017510000000205214773370543016446 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from kitty.typing import WindowType from kitty.window_list import WindowList from .base import Layout, NeighborsMap class Stack(Layout): name = 'stack' needs_window_borders = False only_active_window_visible = True def do_layout(self, all_windows: WindowList) -> None: active_group = all_windows.active_group for group in all_windows.iter_all_layoutable_groups(): self.layout_single_window_group(group, add_blank_rects=group is active_group) def neighbors_for_window(self, window: WindowType, all_windows: WindowList) -> NeighborsMap: wg = all_windows.group_for_window(window) assert wg is not None groups = tuple(all_windows.iter_all_layoutable_groups()) idx = groups.index(wg) before = [] if wg is groups[0] else [groups[idx-1].id] after = [] if wg is groups[-1] else [groups[idx+1].id] return {'top': before, 'left': before, 'right': after, 'bottom': after} kitty-0.41.1/kitty/layout/tall.py0000664000175000017510000003646114773370543016310 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Generator, Sequence from itertools import islice, repeat from typing import Any from kitty.borders import BorderColor from kitty.conf.utils import to_bool from kitty.types import Edges from kitty.typing import EdgeLiteral, WindowType from kitty.window_list import WindowGroup, WindowList from .base import ( BorderLine, Layout, LayoutData, LayoutDimension, LayoutOpts, NeighborsMap, lgd, normalize_biases, safe_increment_bias, ) from .vertical import borders def neighbors_for_tall_window( num_full_size_windows: int, window: WindowType, all_windows: WindowList, mirrored: bool = False, main_is_horizontal: bool = True ) -> NeighborsMap: wg = all_windows.group_for_window(window) assert wg is not None groups = tuple(all_windows.iter_all_layoutable_groups()) idx = groups.index(wg) prev = None if idx == 0 else groups[idx-1] nxt = None if idx == len(groups) - 1 else groups[idx+1] ans: NeighborsMap = {'left': [], 'right': [], 'top': [], 'bottom': []} main_before: EdgeLiteral = 'left' if main_is_horizontal else 'top' main_after: EdgeLiteral = 'right' if main_is_horizontal else 'bottom' cross_before: EdgeLiteral = 'top' if main_is_horizontal else 'left' cross_after: EdgeLiteral = 'bottom' if main_is_horizontal else 'right' if mirrored: main_before, main_after = main_after, main_before if prev is not None: ans[main_before] = [prev.id] if idx < num_full_size_windows - 1: if nxt is not None: ans[main_after] = [nxt.id] elif idx == num_full_size_windows - 1: ans[main_after] = [w.id for w in groups[idx+1:]] else: ans[main_before] = [groups[num_full_size_windows - 1].id] if idx > num_full_size_windows and prev is not None: ans[cross_before] = [prev.id] if nxt is not None: ans[cross_after] = [nxt.id] return ans class TallLayoutOpts(LayoutOpts): bias: int = 50 full_size: int = 1 mirrored: bool = False def __init__(self, data: dict[str, str]): try: self.full_size = int(data.get('full_size', 1)) except Exception: self.full_size = 1 self.full_size = max(1, min(self.full_size, 100)) try: self.bias = int(data.get('bias', 50)) except Exception: self.bias = 50 self.mirrored = to_bool(data.get('mirrored', 'false')) def serialized(self) -> dict[str, Any]: return {'full_size': self.full_size, 'bias': self.bias, 'mirrored': self.mirrored} def build_bias_list(self) -> tuple[float, ...]: b = self.bias / 100 b = max(0.1, min(b, 0.9)) return tuple(repeat(b / self.full_size, self.full_size)) + (1.0 - b,) def set_bias(biases: Sequence[float], idx: int, target: float) -> list[float]: remainder = 1 - target previous_remainder = sum(x for i, x in enumerate(biases) if i != idx) ans = [1. for i in range(len(biases))] for i in range(len(biases)): if i == idx: ans[i] = target else: ans[i] = remainder * biases[i] / previous_remainder return ans class Tall(Layout): name = 'tall' main_is_horizontal = True no_minimal_window_borders = True layout_opts = TallLayoutOpts({}) main_axis_layout = Layout.xlayout perp_axis_layout = Layout.ylayout @property def num_full_size_windows(self) -> int: return self.layout_opts.full_size def remove_all_biases(self) -> bool: self.main_bias: list[float] = list(self.layout_opts.build_bias_list()) self.biased_map: dict[int, float] = {} return True def variable_layout(self, all_windows: WindowList, biased_map: dict[int, float]) -> LayoutDimension: num = all_windows.num_groups - self.num_full_size_windows bias = biased_map if num > 1 else None return self.perp_axis_layout(all_windows.iter_all_layoutable_groups(), bias=bias, offset=self.num_full_size_windows) def bias_slot(self, all_windows: WindowList, idx: int, fractional_bias: float, cell_increment_bias_h: float, cell_increment_bias_v: float) -> bool: if idx < len(self.main_bias): before_main_bias = self.main_bias self.main_bias = set_bias(self.main_bias, idx, fractional_bias) return self.main_bias != before_main_bias before_layout = tuple(self.variable_layout(all_windows, self.biased_map)) self.biased_map[idx - self.num_full_size_windows] = cell_increment_bias_v if self.main_is_horizontal else cell_increment_bias_h after_layout = tuple(self.variable_layout(all_windows, self.biased_map)) return before_layout == after_layout def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool: num_windows = all_windows.num_groups if self.main_is_horizontal == is_horizontal: before_main_bias = self.main_bias ncols = self.num_full_size_windows + 1 biased_col = idx if idx < self.num_full_size_windows else (ncols - 1) self.main_bias = [ safe_increment_bias(self.main_bias[i], increment * (1 if i == biased_col else -1)) for i in range(ncols) ] self.main_bias = normalize_biases(self.main_bias) return self.main_bias != before_main_bias num_of_short_windows = num_windows - self.num_full_size_windows if idx < self.num_full_size_windows or num_of_short_windows < 2: return False idx -= self.num_full_size_windows before_layout = tuple(self.variable_layout(all_windows, self.biased_map)) before = self.biased_map.get(idx, 0.) candidate = self.biased_map.copy() candidate[idx] = after = before + increment if before_layout == tuple(self.variable_layout(all_windows, candidate)): return False self.biased_map = candidate return before != after def simple_layout(self, all_windows: WindowList) -> Generator[tuple[WindowGroup, LayoutData, LayoutData, bool], None, None]: num = all_windows.num_groups is_fat = not self.main_is_horizontal mirrored = self.layout_opts.mirrored groups = tuple(all_windows.iter_all_layoutable_groups()) main_bias = self.main_bias[::-1] if mirrored else self.main_bias if mirrored: groups = tuple(reversed(groups)) main_bias = normalize_biases(main_bias[:num]) xlayout = self.main_axis_layout(iter(groups), bias=main_bias) for wg, xl in zip(groups, xlayout): yl = next(self.perp_axis_layout(iter((wg,)))) if is_fat: xl, yl = yl, xl yield wg, xl, yl, True def full_layout(self, all_windows: WindowList) -> Generator[tuple[WindowGroup, LayoutData, LayoutData, bool], None, None]: is_fat = not self.main_is_horizontal mirrored = self.layout_opts.mirrored groups = tuple(all_windows.iter_all_layoutable_groups()) main_bias = self.main_bias[::-1] if mirrored else self.main_bias start = lgd.central.top if is_fat else lgd.central.left size = 0 if mirrored: fsg = groups[:self.num_full_size_windows + 1] xlayout = self.main_axis_layout(reversed(fsg), bias=main_bias) for i, wg in enumerate(reversed(fsg)): xl = next(xlayout) if i == 0: size = xl.content_size + xl.space_before + xl.space_after continue yl = next(self.perp_axis_layout(iter((wg,)))) if is_fat: xl, yl = yl, xl yield wg, xl, yl, True else: xlayout = self.main_axis_layout(islice(groups, self.num_full_size_windows + 1), bias=main_bias) for i, wg in enumerate(groups): if i >= self.num_full_size_windows: break xl = next(xlayout) yl = next(self.perp_axis_layout(iter((wg,)))) start = xl.content_pos + xl.content_size + xl.space_after if is_fat: xl, yl = yl, xl yield wg, xl, yl, True size = 1 + (lgd.central.bottom if is_fat else lgd.central.right) - start ylayout = self.variable_layout(all_windows, self.biased_map) for i, wg in enumerate(all_windows.iter_all_layoutable_groups()): if i < self.num_full_size_windows: continue yl = next(ylayout) xl = next(self.main_axis_layout(iter((wg,)), start=start, size=size)) if is_fat: xl, yl = yl, xl yield wg, xl, yl, False def do_layout(self, all_windows: WindowList) -> None: num = all_windows.num_groups if num == 1: self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups())) return layouts = (self.simple_layout if num <= self.num_full_size_windows + 1 else self.full_layout)(all_windows) for wg, xl, yl, is_full_size in layouts: self.set_window_group_geometry(wg, xl, yl) def neighbors_for_window(self, window: WindowType, windows: WindowList) -> NeighborsMap: return neighbors_for_tall_window(self.num_full_size_windows, window, windows, self.layout_opts.mirrored, self.main_is_horizontal) def layout_action(self, action_name: str, args: Sequence[str], all_windows: WindowList) -> bool | None: if action_name == 'increase_num_full_size_windows': self.layout_opts.full_size += 1 self.main_bias = list(self.layout_opts.build_bias_list()) return True if action_name == 'decrease_num_full_size_windows': if self.layout_opts.full_size > 1: self.layout_opts.full_size -= 1 self.main_bias = list(self.layout_opts.build_bias_list()) return True if action_name == 'mirror': action = (args or ('toggle',))[0] ok = False if action == 'toggle': self.layout_opts.mirrored = not self.layout_opts.mirrored ok = True else: new_val = to_bool(action) if new_val != self.layout_opts.mirrored: self.layout_opts.mirrored = new_val ok = True return ok if action_name == 'bias': if len(args) == 0: raise ValueError('layout_action bias must contain at least one number between 10 and 90') biases = args[0].split() if len(biases) == 1: biases.append("50") try: i = biases.index(str(self.layout_opts.bias)) + 1 except ValueError: i = 0 try: self.layout_opts.bias = int(biases[i % len(biases)]) self.remove_all_biases() return True except Exception: return False return None def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None, None]: num = all_windows.num_groups if num < 2 or not lgd.draw_minimal_borders: return try: bw = next(all_windows.iter_all_layoutable_groups()).effective_border() except StopIteration: bw = 0 if not bw: return if num <= self.num_full_size_windows + 1: layout = (x[:3] for x in self.simple_layout(all_windows)) yield from borders(layout, self.main_is_horizontal, all_windows) return main_layouts: list[tuple[WindowGroup, LayoutData, LayoutData]] = [] perp_borders: list[BorderLine] = [] layouts = (self.simple_layout if num <= self.num_full_size_windows else self.full_layout)(all_windows) needs_borders_map = all_windows.compute_needs_borders_map(lgd.draw_active_borders) active_group = all_windows.active_group mirrored = self.layout_opts.mirrored for wg, xl, yl, is_full_size in layouts: if is_full_size: main_layouts.append((wg, xl, yl)) else: color = BorderColor.inactive if needs_borders_map.get(wg.id): color = BorderColor.active if wg is active_group else BorderColor.bell if self.main_is_horizontal: e1 = Edges( xl.content_pos - xl.space_before, yl.content_pos - yl.space_before, xl.content_pos + xl.content_size + xl.space_after, yl.content_pos - yl.space_before + bw ) e3 = Edges( xl.content_pos - xl.space_before, yl.content_pos + yl.content_size + yl.space_after - bw, xl.content_pos + xl.content_size + xl.space_after, yl.content_pos + yl.content_size + yl.space_after, ) e2 = Edges( xl.content_pos + ((xl.content_size + xl.space_after - bw) if mirrored else -xl.space_before), yl.content_pos - yl.space_before, xl.content_pos + ((xl.content_size + xl.space_after) if mirrored else (bw - xl.space_before)), yl.content_pos + yl.content_size + yl.space_after, ) else: e1 = Edges( xl.content_pos - xl.space_before, yl.content_pos - yl.space_before, xl.content_pos - xl.space_before + bw, yl.content_pos + yl.content_size + yl.space_after, ) e3 = Edges( xl.content_pos + xl.content_size + xl.space_after - bw, yl.content_pos - yl.space_before, xl.content_pos + xl.content_size + xl.space_after, yl.content_pos + yl.content_size + yl.space_after, ) e2 = Edges( xl.content_pos - xl.space_before, yl.content_pos + ((yl.content_size + yl.space_after - bw) if mirrored else -yl.space_before), xl.content_pos + xl.content_size + xl.space_after, yl.content_pos + ((yl.content_size + yl.space_after) if mirrored else (bw - yl.space_before)), ) perp_borders.append(BorderLine(e1, color)) perp_borders.append(BorderLine(e2, color)) perp_borders.append(BorderLine(e3, color)) mirrored = self.layout_opts.mirrored yield from borders( main_layouts, self.main_is_horizontal, all_windows, start_offset=int(not mirrored), end_offset=int(mirrored) ) yield from perp_borders[1:-1] def layout_state(self) -> dict[str, Any]: return { 'num_full_size_windows': self.num_full_size_windows, 'main_bias': self.main_bias, 'biased_map': self.biased_map } class Fat(Tall): name = 'fat' main_is_horizontal = False main_axis_layout = Layout.ylayout perp_axis_layout = Layout.xlayout kitty-0.41.1/kitty/layout/vertical.py0000664000175000017510000001425314773370543017160 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Generator, Iterable from typing import Any from kitty.borders import BorderColor from kitty.types import Edges from kitty.typing import WindowType from kitty.window_list import WindowGroup, WindowList from .base import BorderLine, Layout, LayoutData, LayoutDimension, NeighborsMap, lgd def borders( data: Iterable[tuple[WindowGroup, LayoutData, LayoutData]], is_horizontal: bool, all_windows: WindowList, start_offset: int = 1, end_offset: int = 1 ) -> Generator[BorderLine, None, None]: borders: list[BorderLine] = [] active_group = all_windows.active_group needs_borders_map = all_windows.compute_needs_borders_map(lgd.draw_active_borders) try: bw = next(all_windows.iter_all_layoutable_groups()).effective_border() except StopIteration: bw = 0 if not bw: return for wg, xl, yl in data: if is_horizontal: e1 = Edges( xl.content_pos - xl.space_before, yl.content_pos - yl.space_before, xl.content_pos - xl.space_before + bw, yl.content_pos + yl.content_size + yl.space_after ) e2 = Edges( xl.content_pos + xl.content_size + xl.space_after - bw, yl.content_pos - yl.space_before, xl.content_pos + xl.content_size + xl.space_after, yl.content_pos + yl.content_size + yl.space_after ) else: e1 = Edges( xl.content_pos - xl.space_before, yl.content_pos - yl.space_before, xl.content_pos + xl.content_size + xl.space_after, yl.content_pos - yl.space_before + bw ) e2 = Edges( xl.content_pos - xl.space_before, yl.content_pos + yl.content_size + yl.space_after - bw, xl.content_pos + xl.content_size + xl.space_after, yl.content_pos + yl.content_size + yl.space_after ) color = BorderColor.inactive if needs_borders_map.get(wg.id): color = BorderColor.active if wg is active_group else BorderColor.bell borders.append(BorderLine(e1, color)) borders.append(BorderLine(e2, color)) last_idx = len(borders) - 1 - end_offset for i, x in enumerate(borders): if start_offset <= i <= last_idx: yield x class Vertical(Layout): name = 'vertical' main_is_horizontal = False no_minimal_window_borders = True main_axis_layout = Layout.ylayout perp_axis_layout = Layout.xlayout def variable_layout(self, all_windows: WindowList, biased_map: dict[int, float]) -> LayoutDimension: num_windows = all_windows.num_groups bias = biased_map if num_windows > 1 else None return self.main_axis_layout(all_windows.iter_all_layoutable_groups(), bias=bias) def fixed_layout(self, wg: WindowGroup) -> LayoutDimension: return self.perp_axis_layout(iter((wg,)), border_mult=0 if lgd.draw_minimal_borders else 1) def remove_all_biases(self) -> bool: self.biased_map: dict[int, float] = {} return True def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool: if self.main_is_horizontal != is_horizontal: return False num_windows = all_windows.num_groups if num_windows < 2: return False before_layout = list(self.variable_layout(all_windows, self.biased_map)) candidate = self.biased_map.copy() before = candidate.get(idx, 0) candidate[idx] = before + increment if before_layout == list(self.variable_layout(all_windows, candidate)): return False self.biased_map = candidate return True def bias_slot(self, all_windows: WindowList, idx: int, fractional_bias: float, cell_increment_bias_h: float, cell_increment_bias_v: float) -> bool: before_layout = tuple(self.variable_layout(all_windows, self.biased_map)) self.biased_map[idx] = cell_increment_bias_h if self.main_is_horizontal else cell_increment_bias_v after_layout = tuple(self.variable_layout(all_windows, self.biased_map)) return before_layout == after_layout def generate_layout_data(self, all_windows: WindowList) -> Generator[tuple[WindowGroup, LayoutData, LayoutData], None, None]: ylayout = self.variable_layout(all_windows, self.biased_map) for wg, yl in zip(all_windows.iter_all_layoutable_groups(), ylayout): xl = next(self.fixed_layout(wg)) if self.main_is_horizontal: xl, yl = yl, xl yield wg, xl, yl def do_layout(self, all_windows: WindowList) -> None: window_count = all_windows.num_groups if window_count == 1: self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups())) return for wg, xl, yl in self.generate_layout_data(all_windows): self.set_window_group_geometry(wg, xl, yl) def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None, None]: window_count = all_windows.num_groups if window_count < 2 or not lgd.draw_minimal_borders: return yield from borders(self.generate_layout_data(all_windows), self.main_is_horizontal, all_windows) def neighbors_for_window(self, window: WindowType, all_windows: WindowList) -> NeighborsMap: wg = all_windows.group_for_window(window) assert wg is not None groups = tuple(all_windows.iter_all_layoutable_groups()) idx = groups.index(wg) lg = len(groups) if lg > 1: before = [groups[(idx - 1 + lg) % lg].id] after = [groups[(idx + 1) % lg].id] else: before, after = [], [] if self.main_is_horizontal: return {'left': before, 'right': after, 'top': [], 'bottom': []} return {'top': before, 'bottom': after, 'left': [], 'right': []} def layout_state(self) -> dict[str, Any]: return {'biased_map': self.biased_map} class Horizontal(Vertical): name = 'horizontal' main_is_horizontal = True main_axis_layout = Layout.xlayout perp_axis_layout = Layout.ylayout kitty-0.41.1/kitty/line-buf.c0000664000175000017510000005216414773370543015330 0ustar nileshnilesh/* * line-buf.c * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "data-types.h" #include "lineops.h" #include "resize.h" #include extern PyTypeObject Line_Type; extern PyTypeObject HistoryBuf_Type; static CPUCell* cpu_lineptr(LineBuf *linebuf, index_type y) { return linebuf->cpu_cell_buf + y * linebuf->xnum; } static GPUCell* gpu_lineptr(LineBuf *linebuf, index_type y) { return linebuf->gpu_cell_buf + y * linebuf->xnum; } static void clear_chars_to(LineBuf* linebuf, index_type y, char_type ch) { clear_chars_in_line(cpu_lineptr(linebuf, y), gpu_lineptr(linebuf, y), linebuf->xnum, ch); } void linebuf_clear(LineBuf *self, char_type ch) { zero_at_ptr_count(self->cpu_cell_buf, self->xnum * self->ynum); zero_at_ptr_count(self->gpu_cell_buf, self->xnum * self->ynum); zero_at_ptr_count(self->line_attrs, self->ynum); for (index_type i = 0; i < self->ynum; i++) self->line_map[i] = i; if (ch != 0) { for (index_type i = 0; i < self->ynum; i++) { clear_chars_to(self, i, ch); self->line_attrs[i].val = 0; self->line_attrs[i].has_dirty_text = true; } } } void linebuf_mark_line_dirty(LineBuf *self, index_type y) { self->line_attrs[y].has_dirty_text = true; } void linebuf_mark_line_clean(LineBuf *self, index_type y) { self->line_attrs[y].has_dirty_text = false; } void linebuf_set_line_has_image_placeholders(LineBuf *self, index_type y, bool val) { self->line_attrs[y].has_image_placeholders = val; } void linebuf_clear_attrs_and_dirty(LineBuf *self, index_type y) { self->line_attrs[y].val = 0; self->line_attrs[y].has_dirty_text = true; } static PyObject* clear(LineBuf *self, PyObject *a UNUSED) { #define clear_doc "Clear all lines in this LineBuf" linebuf_clear(self, BLANK_CHAR); Py_RETURN_NONE; } LineBuf * alloc_linebuf_(PyTypeObject *cls, unsigned int lines, unsigned int columns, TextCache *text_cache) { if (columns > 5000 || lines > 50000) { PyErr_SetString(PyExc_ValueError, "Number of rows or columns is too large."); return NULL; } const size_t area = columns * lines; if (area == 0) { PyErr_SetString(PyExc_ValueError, "Cannot create an empty LineBuf"); return NULL; } LineBuf *self = (LineBuf*)cls->tp_alloc(cls, 0); if (self != NULL) { self->xnum = columns; self->ynum = lines; self->cpu_cell_buf = PyMem_Calloc(1, area * (sizeof(CPUCell) + sizeof(GPUCell)) + lines * (sizeof(index_type) + sizeof(index_type) + sizeof(LineAttrs))); if (!self->cpu_cell_buf) { Py_CLEAR(self); return NULL; } self->gpu_cell_buf = (GPUCell*)(self->cpu_cell_buf + area); self->line_map = (index_type*)(self->gpu_cell_buf + area); self->scratch = self->line_map + lines; self->text_cache = tc_incref(text_cache); self->line = alloc_line(self->text_cache); self->line_attrs = (LineAttrs*)(self->scratch + lines); self->line->xnum = columns; for(index_type i = 0; i < lines; i++) { self->line_map[i] = i; if (BLANK_CHAR != 0) clear_chars_to(self, i, BLANK_CHAR); } } return self; } static PyObject * new_linebuf_object(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { unsigned int xnum = 1, ynum = 1; if (!PyArg_ParseTuple(args, "II", &ynum, &xnum)) return NULL; TextCache *tc = tc_alloc(); if (!tc) return PyErr_NoMemory(); PyObject *ans = (PyObject*)alloc_linebuf_(type, ynum, xnum, tc); tc_decref(tc); return ans; } static void dealloc(LineBuf* self) { self->text_cache = tc_decref(self->text_cache); PyMem_Free(self->cpu_cell_buf); Py_CLEAR(self->line); Py_TYPE(self)->tp_free((PyObject*)self); } void linebuf_init_cells(LineBuf *lb, index_type idx, CPUCell **c, GPUCell **g) { const index_type ynum = lb->line_map[idx]; *c = cpu_lineptr(lb, ynum); *g = gpu_lineptr(lb, ynum); } CPUCell* linebuf_cpu_cells_for_line(LineBuf *lb, index_type idx) { const index_type ynum = lb->line_map[idx]; return cpu_lineptr(lb, ynum); } static void init_line(LineBuf *lb, Line *l, index_type ynum) { l->cpu_cells = cpu_lineptr(lb, ynum); l->gpu_cells = gpu_lineptr(lb, ynum); } void linebuf_init_line_at(LineBuf *self, index_type idx, Line *line) { line->ynum = idx; line->xnum = self->xnum; line->attrs = self->line_attrs[idx]; init_line(self, line, self->line_map[idx]); } void linebuf_init_line(LineBuf *self, index_type idx) { linebuf_init_line_at(self, idx, self->line); } void linebuf_clear_lines(LineBuf *self, const Cursor *cursor, index_type start, index_type end) { #if BLANK_CHAR != 0 #error This implementation is incorrect for BLANK_CHAR != 0 #endif #define lineptr(which, i) which##_lineptr(self, self->line_map[i]) GPUCell *first_gpu_line = lineptr(gpu, start); const GPUCell gc = cursor_as_gpu_cell(cursor); memset_array(first_gpu_line, gc, self->xnum); const size_t cpu_stride = sizeof(CPUCell) * self->xnum; memset(lineptr(cpu, start), 0, cpu_stride); const size_t gpu_stride = sizeof(GPUCell) * self->xnum; linebuf_clear_attrs_and_dirty(self, start); for (index_type i = start + 1; i < end; i++) { memset(lineptr(cpu, i), 0, cpu_stride); memcpy(lineptr(gpu, i), first_gpu_line, gpu_stride); linebuf_clear_attrs_and_dirty(self, i); } #undef lineptr } static PyObject* line(LineBuf *self, PyObject *y) { #define line_doc "Return the specified line as a Line object. Note the Line Object is a live view into the underlying buffer. And only a single line object can be used at a time." unsigned long idx = PyLong_AsUnsignedLong(y); if (idx >= self->ynum) { PyErr_SetString(PyExc_IndexError, "Line number too large"); return NULL; } linebuf_init_line(self, idx); Py_INCREF(self->line); return (PyObject*)self->line; } CPUCell* linebuf_cpu_cell_at(LineBuf *self, index_type x, index_type y) { return &cpu_lineptr(self, self->line_map[y])[x]; } bool linebuf_line_ends_with_continuation(LineBuf *self, index_type y) { return y < self->ynum ? cpu_lineptr(self, self->line_map[y])[self->xnum - 1].next_char_was_wrapped : false; } void linebuf_set_last_char_as_continuation(LineBuf *self, index_type y, bool continued) { if (y < self->ynum) { cpu_lineptr(self, self->line_map[y])[self->xnum - 1].next_char_was_wrapped = continued; } } static PyObject* set_attribute(LineBuf *self, PyObject *args) { #define set_attribute_doc "set_attribute(which, val) -> Set the attribute on all cells in the line." unsigned int val; char *which; if (!PyArg_ParseTuple(args, "sI", &which, &val)) return NULL; for (index_type y = 0; y < self->ynum; y++) { if (!set_named_attribute_on_line(gpu_lineptr(self, y), which, val, self->xnum)) { PyErr_SetString(PyExc_KeyError, "Unknown cell attribute"); return NULL; } self->line_attrs[y].has_dirty_text = true; } Py_RETURN_NONE; } static PyObject* set_continued(LineBuf *self, PyObject *args) { #define set_continued_doc "set_continued(y, val) -> Set the continued values for the specified line." unsigned int y; int val; if (!PyArg_ParseTuple(args, "Ip", &y, &val)) return NULL; if (y > self->ynum || y < 1) { PyErr_SetString(PyExc_ValueError, "Out of bounds."); return NULL; } linebuf_set_last_char_as_continuation(self, y-1, val); Py_RETURN_NONE; } static PyObject* dirty_lines(LineBuf *self, PyObject *a UNUSED) { #define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text." PyObject *ans = PyList_New(0); for (index_type i = 0; i < self->ynum; i++) { if (self->line_attrs[i].has_dirty_text) { PyList_Append(ans, PyLong_FromUnsignedLong(i)); } } return ans; } static bool allocate_line_storage(Line *line, bool initialize) { if (initialize) { line->cpu_cells = PyMem_Calloc(line->xnum, sizeof(CPUCell)); line->gpu_cells = PyMem_Calloc(line->xnum, sizeof(GPUCell)); if (line->cpu_cells == NULL || line->gpu_cells) { PyErr_NoMemory(); return false; } if (BLANK_CHAR != 0) clear_chars_in_line(line->cpu_cells, line->gpu_cells, line->xnum, BLANK_CHAR); } else { line->cpu_cells = PyMem_Malloc(line->xnum * sizeof(CPUCell)); line->gpu_cells = PyMem_Malloc(line->xnum * sizeof(GPUCell)); if (line->cpu_cells == NULL || line->gpu_cells == NULL) { PyErr_NoMemory(); return false; } } line->needs_free = 1; return true; } static PyObject* create_line_copy_inner(LineBuf* self, index_type y) { Line src, *line; line = alloc_line(self->text_cache); if (line == NULL) return PyErr_NoMemory(); src.xnum = self->xnum; line->xnum = self->xnum; if (!allocate_line_storage(line, 0)) { Py_CLEAR(line); return PyErr_NoMemory(); } line->ynum = y; line->attrs = self->line_attrs[y]; init_line(self, &src, self->line_map[y]); copy_line(&src, line); return (PyObject*)line; } static PyObject* create_line_copy(LineBuf *self, PyObject *ynum) { #define create_line_copy_doc "Create a new Line object that is a copy of the line at ynum. Note that this line has its own copy of the data and does not refer to the data in the LineBuf." index_type y = (index_type)PyLong_AsUnsignedLong(ynum); if (y >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; } return create_line_copy_inner(self, y); } static PyObject* copy_line_to(LineBuf *self, PyObject *args) { #define copy_line_to_doc "Copy the line at ynum to the provided line object." unsigned int y; Line src, *dest; if (!PyArg_ParseTuple(args, "IO!", &y, &Line_Type, &dest)) return NULL; src.xnum = self->xnum; dest->xnum = self->xnum; dest->ynum = y; dest->attrs = self->line_attrs[y]; init_line(self, &src, self->line_map[y]); copy_line(&src, dest); Py_RETURN_NONE; } static void clear_line_(Line *l, index_type xnum) { #if BLANK_CHAR != 0 #error This implementation is incorrect for BLANK_CHAR != 0 #endif zero_at_ptr_count(l->cpu_cells, xnum); zero_at_ptr_count(l->gpu_cells, xnum); l->attrs.has_dirty_text = false; } void linebuf_clear_line(LineBuf *self, index_type y, bool clear_attrs) { #if BLANK_CHAR != 0 #error This implementation is incorrect for BLANK_CHAR != 0 #endif index_type ym = self->line_map[y]; CPUCell *c = cpu_lineptr(self, ym); GPUCell *g = gpu_lineptr(self, ym); zero_at_ptr_count(c, self->xnum); zero_at_ptr_count(g, self->xnum); if (clear_attrs) self->line_attrs[y].val = 0; } static PyObject* clear_line(LineBuf *self, PyObject *val) { #define clear_line_doc "clear_line(y) -> Clear the specified line" index_type y = (index_type)PyLong_AsUnsignedLong(val); if (y >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; } linebuf_clear_line(self, y, true); Py_RETURN_NONE; } void linebuf_index(LineBuf* self, index_type top, index_type bottom) { if (top >= self->ynum - 1 || bottom >= self->ynum || bottom <= top) return; index_type old_top = self->line_map[top]; LineAttrs old_attrs = self->line_attrs[top]; const index_type num = bottom - top; memmove(self->line_map + top, self->line_map + top + 1, sizeof(self->line_map[0]) * num); memmove(self->line_attrs + top, self->line_attrs + top + 1, sizeof(self->line_attrs[0]) * num); self->line_map[bottom] = old_top; self->line_attrs[bottom] = old_attrs; } static PyObject* pyw_index(LineBuf *self, PyObject *args) { #define index_doc "index(top, bottom) -> Scroll all lines in the range [top, bottom] by one upwards. After scrolling, bottom will be top." unsigned int top, bottom; if (!PyArg_ParseTuple(args, "II", &top, &bottom)) return NULL; linebuf_index(self, top, bottom); Py_RETURN_NONE; } void linebuf_reverse_index(LineBuf *self, index_type top, index_type bottom) { if (top >= self->ynum - 1 || bottom >= self->ynum || bottom <= top) return; index_type old_bottom = self->line_map[bottom]; LineAttrs old_attrs = self->line_attrs[bottom]; for (index_type i = bottom; i > top; i--) { self->line_map[i] = self->line_map[i - 1]; self->line_attrs[i] = self->line_attrs[i - 1]; } self->line_map[top] = old_bottom; self->line_attrs[top] = old_attrs; } static PyObject* reverse_index(LineBuf *self, PyObject *args) { #define reverse_index_doc "reverse_index(top, bottom) -> Scroll all lines in the range [top, bottom] by one down. After scrolling, top will be bottom." unsigned int top, bottom; if (!PyArg_ParseTuple(args, "II", &top, &bottom)) return NULL; linebuf_reverse_index(self, top, bottom); Py_RETURN_NONE; } static PyObject* is_continued(LineBuf *self, PyObject *val) { #define is_continued_doc "is_continued(y) -> Whether the line y is continued or not" unsigned long y = PyLong_AsUnsignedLong(val); if (y >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds."); return NULL; } if (y > 0 && linebuf_line_ends_with_continuation(self, y-1)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } void linebuf_insert_lines(LineBuf *self, unsigned int num, unsigned int y, unsigned int bottom) { index_type i; if (y >= self->ynum || y > bottom || bottom >= self->ynum) return; index_type ylimit = bottom + 1; if (ylimit < y || (num = MIN(ylimit - y, num)) < 1) return; const size_t scratch_sz = sizeof(self->scratch[0]) * num; memcpy(self->scratch, self->line_map + ylimit - num, scratch_sz); for (i = ylimit - 1; i >= y + num; i--) { self->line_map[i] = self->line_map[i - num]; self->line_attrs[i] = self->line_attrs[i - num]; } memcpy(self->line_map + y, self->scratch, scratch_sz); Line l; for (i = y; i < y + num; i++) { init_line(self, &l, self->line_map[i]); clear_line_(&l, self->xnum); self->line_attrs[i].val = 0; } } static PyObject* insert_lines(LineBuf *self, PyObject *args) { #define insert_lines_doc "insert_lines(num, y, bottom) -> Insert num blank lines at y, only changing lines in the range [y, bottom]." unsigned int y, num, bottom; if (!PyArg_ParseTuple(args, "III", &num, &y, &bottom)) return NULL; linebuf_insert_lines(self, num, y, bottom); Py_RETURN_NONE; } void linebuf_delete_lines(LineBuf *self, index_type num, index_type y, index_type bottom) { index_type i; index_type ylimit = bottom + 1; num = MIN(bottom + 1 - y, num); if (y >= self->ynum || y > bottom || bottom >= self->ynum || num < 1) return; const size_t scratch_sz = sizeof(self->scratch[0]) * num; memcpy(self->scratch, self->line_map + y, scratch_sz); for (i = y; i < ylimit && i + num < self->ynum; i++) { self->line_map[i] = self->line_map[i + num]; self->line_attrs[i] = self->line_attrs[i + num]; } memcpy(self->line_map + ylimit - num, self->scratch, scratch_sz); Line l; for (i = ylimit - num; i < ylimit; i++) { init_line(self, &l, self->line_map[i]); clear_line_(&l, self->xnum); self->line_attrs[i].val = 0; } } static PyObject* delete_lines(LineBuf *self, PyObject *args) { #define delete_lines_doc "delete_lines(num, y, bottom) -> Delete num lines at y, only changing lines in the range [y, bottom]." unsigned int y, num, bottom; if (!PyArg_ParseTuple(args, "III", &num, &y, &bottom)) return NULL; linebuf_delete_lines(self, num, y, bottom); Py_RETURN_NONE; } void linebuf_copy_line_to(LineBuf *self, Line *line, index_type where) { init_line(self, self->line, self->line_map[where]); copy_line(line, self->line); self->line_attrs[where] = line->attrs; self->line_attrs[where].has_dirty_text = true; } static PyObject* as_ansi(LineBuf *self, PyObject *callback) { #define as_ansi_doc "as_ansi(callback) -> The contents of this buffer as ANSI escaped text. callback is called with each successive line." Line l = {.xnum=self->xnum, .text_cache=self->text_cache}; // remove trailing empty lines index_type ylimit = self->ynum - 1; ANSIBuf output = {0}; ANSILineState s = {.output_buf=&output}; do { init_line(self, &l, self->line_map[ylimit]); output.len = 0; line_as_ansi(&l, &s, 0, l.xnum, 0, true); if (output.len) break; ylimit--; } while(ylimit > 0); for(index_type i = 0; i <= ylimit; i++) { bool output_newline = !linebuf_line_ends_with_continuation(self, i); output.len = 0; init_line(self, &l, self->line_map[i]); line_as_ansi(&l, &s, 0, l.xnum, 0, true); if (output_newline) { ensure_space_for(&output, buf, Py_UCS4, output.len + 1, capacity, 2048, false); output.buf[output.len++] = 10; // 10 = \n } PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, output.buf, output.len); if (ans == NULL) { PyErr_NoMemory(); goto end; } PyObject *ret = PyObject_CallFunctionObjArgs(callback, ans, NULL); Py_CLEAR(ans); if (ret == NULL) goto end; Py_CLEAR(ret); } end: free(output.buf); if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } static Line* get_line(void *x, int y) { LineBuf *self = (LineBuf*)x; linebuf_init_line(self, MAX(0, y)); return self->line; } static PyObject* as_text(LineBuf *self, PyObject *args) { ANSIBuf output = {0}; PyObject* ans = as_text_generic(args, self, get_line, self->ynum, &output, false); free(output.buf); return ans; } static PyObject* __str__(LineBuf *self) { RAII_PyObject(lines, PyTuple_New(self->ynum)); RAII_ANSIBuf(buf); if (lines == NULL) return PyErr_NoMemory(); for (index_type i = 0; i < self->ynum; i++) { init_line(self, self->line, self->line_map[i]); PyObject *t = line_as_unicode(self->line, false, &buf); if (t == NULL) return NULL; PyTuple_SET_ITEM(lines, i, t); } RAII_PyObject(sep, PyUnicode_FromString("\n")); return PyUnicode_Join(sep, lines); } // Boilerplate {{{ static PyObject* copy_old(LineBuf *self, PyObject *y); #define copy_old_doc "Copy the contents of the specified LineBuf to this LineBuf. Both must have the same number of columns, but the number of lines can be different, in which case the bottom lines are copied." static PyObject* rewrap(LineBuf *self, PyObject *args); #define rewrap_doc "rewrap(new_screen) -> Fill up new screen (which can have different size to this screen) with as much of the contents of this screen as will fit. Return lines that overflow." static PyMethodDef methods[] = { METHOD(line, METH_O) METHOD(clear_line, METH_O) METHOD(copy_old, METH_O) METHOD(copy_line_to, METH_VARARGS) METHOD(create_line_copy, METH_O) METHOD(rewrap, METH_VARARGS) METHOD(clear, METH_NOARGS) METHOD(as_ansi, METH_O) METHODB(as_text, METH_VARARGS), METHOD(set_attribute, METH_VARARGS) METHOD(set_continued, METH_VARARGS) METHOD(dirty_lines, METH_NOARGS) {"index", (PyCFunction)pyw_index, METH_VARARGS, NULL}, METHOD(reverse_index, METH_VARARGS) METHOD(insert_lines, METH_VARARGS) METHOD(delete_lines, METH_VARARGS) METHOD(is_continued, METH_O) {NULL, NULL, 0, NULL} /* Sentinel */ }; static PyMemberDef members[] = { {"xnum", T_UINT, offsetof(LineBuf, xnum), READONLY, "xnum"}, {"ynum", T_UINT, offsetof(LineBuf, ynum), READONLY, "ynum"}, {NULL} /* Sentinel */ }; PyTypeObject LineBuf_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.LineBuf", .tp_basicsize = sizeof(LineBuf), .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "Line buffers", .tp_methods = methods, .tp_members = members, .tp_str = (reprfunc)__str__, .tp_new = new_linebuf_object }; INIT_TYPE(LineBuf) // }}} static PyObject* copy_old(LineBuf *self, PyObject *y) { if (!PyObject_TypeCheck(y, &LineBuf_Type)) { PyErr_SetString(PyExc_TypeError, "Not a LineBuf object"); return NULL; } LineBuf *other = (LineBuf*)y; if (other->xnum != self->xnum) { PyErr_SetString(PyExc_ValueError, "LineBuf has a different number of columns"); return NULL; } Line sl = {.text_cache=self->text_cache}, ol = {.text_cache=self->text_cache}; sl.xnum = self->xnum; ol.xnum = other->xnum; for (index_type i = 0; i < MIN(self->ynum, other->ynum); i++) { index_type s = self->ynum - 1 - i, o = other->ynum - 1 - i; self->line_attrs[s] = other->line_attrs[o]; s = self->line_map[s]; o = other->line_map[o]; init_line(self, &sl, s); init_line(other, &ol, o); copy_line(&ol, &sl); } Py_RETURN_NONE; } static PyObject* rewrap(LineBuf *self, PyObject *args) { unsigned int lines, columns; if (!PyArg_ParseTuple(args, "II", &lines, &columns)) return NULL; TrackCursor cursors[1] = {{.is_sentinel=true}}; ANSIBuf as_ansi_buf = {0}; ResizeResult r = resize_screen_buffers(self, NULL, lines, columns, &as_ansi_buf, cursors); free(as_ansi_buf.buf); if (!r.ok) return PyErr_NoMemory(); return Py_BuildValue("NII", r.lb, r.num_content_lines_before, r.num_content_lines_after); } LineBuf * alloc_linebuf(unsigned int lines, unsigned int columns, TextCache *tc) { return alloc_linebuf_(&LineBuf_Type, lines, columns, tc); } kitty-0.41.1/kitty/line-buf.h0000664000175000017510000000073314773370543015330 0ustar nileshnilesh/* * line-buf.h * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "line.h" #include "text-cache.h" typedef struct { PyObject_HEAD GPUCell *gpu_cell_buf; CPUCell *cpu_cell_buf; index_type xnum, ynum, *line_map, *scratch; LineAttrs *line_attrs; Line *line; TextCache *text_cache; } LineBuf; LineBuf* alloc_linebuf(unsigned int, unsigned int, TextCache*); kitty-0.41.1/kitty/line.c0000664000175000017510000012227614773370543014560 0ustar nileshnilesh/* * line.c * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "state.h" #include "unicode-data.h" #include "lineops.h" #include "charsets.h" #include "control-codes.h" extern PyTypeObject Cursor_Type; static_assert(sizeof(char_type) == sizeof(Py_UCS4), "Need to perform conversion to Py_UCS4"); static void dealloc(Line* self) { if (self->needs_free) { PyMem_Free(self->cpu_cells); PyMem_Free(self->gpu_cells); } tc_decref(self->text_cache); Py_TYPE(self)->tp_free((PyObject*)self); } static unsigned nonnegative_integer_as_utf32(unsigned num, ANSIBuf *output) { unsigned num_digits = 0; if (!num) num_digits = 1; else { unsigned temp = num; while (temp > 0) { temp /= 10; num_digits++; } } ensure_space_for(output, buf, output->buf[0], output->len + num_digits, capacity, 2048, false); if (!num) output->buf[output->len++] = '0'; else { char_type *result = output->buf + output->len; unsigned i = num_digits - 1; do { uint32_t digit = num % 10; result[i--] = '0' + digit; num /= 10; output->len++; } while (num > 0); } return num_digits; } static void ensure_space_in_ansi_output_buf(ANSILineState *s, size_t extra) { ensure_space_for(s->output_buf, buf, s->output_buf->buf[0], s->output_buf->len + extra, capacity, 2048, false); } static unsigned write_multicell_ansi_prefix(ANSILineState *s, const CPUCell *mcd) { ensure_space_in_ansi_output_buf(s, 128); s->current_multicell_state = mcd; s->escape_code_written = true; unsigned pos = s->output_buf->len; #define w(x) s->output_buf->buf[s->output_buf->len++] = x w(0x1b); w(']'); for (unsigned i = 0; i < sizeof(xstr(TEXT_SIZE_CODE)) - 1; i++) w(xstr(TEXT_SIZE_CODE)[i]); w(';'); if (!mcd->natural_width) { w('w'); w('='); nonnegative_integer_as_utf32(mcd->width, s->output_buf); w(':'); } if (mcd->scale > 1) { w('s'); w('='); nonnegative_integer_as_utf32(mcd->scale, s->output_buf); w(':'); } if (mcd->subscale_n) { w('n'); w('='); nonnegative_integer_as_utf32(mcd->subscale_n, s->output_buf); w(':'); } if (mcd->subscale_d) { w('d'); w('='); nonnegative_integer_as_utf32(mcd->subscale_d, s->output_buf); w(':'); } if (mcd->valign) { w('v'); w('='); nonnegative_integer_as_utf32(mcd->valign, s->output_buf); w(':'); } if (mcd->halign) { w('h'); w('='); nonnegative_integer_as_utf32(mcd->halign, s->output_buf); w(':'); } if (s->output_buf->buf[s->output_buf->len - 1] == ':') s->output_buf->len--; w(';'); #undef w return s->output_buf->len - pos; } static void close_multicell(ANSILineState *s) { if (s->current_multicell_state) { ensure_space_in_ansi_output_buf(s, 1); s->output_buf->buf[s->output_buf->len++] = '\a'; s->current_multicell_state = NULL; } } static void start_multicell_if_needed(ANSILineState *s, const CPUCell *c) { if (!c->natural_width || c->scale > 1 || c->subscale_n || c->subscale_d || c->valign || c->halign) write_multicell_ansi_prefix(s, c); } static bool multicell_is_continuation_of_previous(const CPUCell *prev, const CPUCell *curr) { if (prev->scale != curr->scale || prev->subscale_n != curr->subscale_n || prev->subscale_d != curr->subscale_d || prev->valign != curr->valign || prev->halign != curr->halign) return false; if (prev->natural_width) return curr->natural_width; return prev->width == curr->width && !curr->natural_width; } static void text_in_cell_ansi(ANSILineState *s, const CPUCell *c, TextCache *tc, bool skip_multiline_non_zero_lines) { if (c->is_multicell) { if (c->x || (skip_multiline_non_zero_lines && c->y)) return; if (s->current_multicell_state) { if (!multicell_is_continuation_of_previous(s->current_multicell_state, c)) { close_multicell(s); start_multicell_if_needed(s, c); } } else start_multicell_if_needed(s, c); } else close_multicell(s); size_t pos = s->output_buf->len; if (c->ch_is_idx) { tc_chars_at_index_ansi(tc, c->ch_or_idx, s->output_buf); } else { ensure_space_in_ansi_output_buf(s, 2); s->output_buf->buf[s->output_buf->len++] = c->ch_or_idx; } if (s->output_buf->len > pos) { switch (s->output_buf->buf[pos]) { case 0: s->output_buf->buf[pos] = ' '; break; case '\t': { unsigned num_cells_to_skip_for_tab = 0, n = s->output_buf->len - pos; if (n - pos > 1) { num_cells_to_skip_for_tab = s->output_buf->buf[s->output_buf->len - n + 1]; s->output_buf->len -= n - 1; } const CPUCell *next = c + 1; while (num_cells_to_skip_for_tab && pos + 1 < s->limit && cell_is_char(next, ' ')) { num_cells_to_skip_for_tab--; pos++; next++; } } break; } } } unsigned int line_length(Line *self) { index_type last = self->xnum - 1; for (index_type i = 0; i < self->xnum; i++) { if (!cell_is_char(self->cpu_cells + last - i, BLANK_CHAR)) return self->xnum - i; } return 0; } // URL detection {{{ static bool is_hostname_char(char_type ch) { return ch == '[' || ch == ']' || is_url_char(ch); } static bool is_hostname_lc(const ListOfChars *lc) { for (size_t i = 0; i < lc->count; i++) if (!is_hostname_char(lc->chars[i])) return false; return true; } static bool is_url_lc(const ListOfChars *lc) { for (size_t i = 0; i < lc->count; i++) if (!is_url_char(lc->chars[i])) return false; return true; } index_type next_char_pos(const Line *self, index_type x, index_type num) { const CPUCell *ans = self->cpu_cells + x, *limit = self->cpu_cells + self->xnum; while (num-- && ans < limit) ans += ans->is_multicell ? mcd_x_limit(ans) - ans->x : 1; return ans - self->cpu_cells; } index_type prev_char_pos(const Line *self, index_type x, index_type num) { const CPUCell *ans = self->cpu_cells + x, *limit = self->cpu_cells - 1; if (ans->is_multicell) ans -= ans->x; while (num-- && --ans > limit) if (ans->is_multicell) ans -= ans->x; return ans > limit ? (index_type)(ans - self->cpu_cells) : self->xnum; } static index_type find_colon_slash(Line *self, index_type x, index_type limit, ListOfChars *lc, index_type scale) { // Find :// at or before x index_type pos = MIN(x, self->xnum - 1); enum URL_PARSER_STATES {ANY, FIRST_SLASH, SECOND_SLASH}; enum URL_PARSER_STATES state = ANY; limit = MAX(2u, limit); if (pos < limit) return 0; const CPUCell *c = self->cpu_cells + pos; index_type n; #define next_char_is(num, ch) ((n = next_char_pos(self, pos, num)) < self->xnum && cell_is_char(self->cpu_cells + n, ch) && cell_scale(self->cpu_cells + n) == scale) if (cell_is_char(c, ':')) { if (next_char_is(1, '/') && next_char_is(2, '/')) state = SECOND_SLASH; } else if (cell_is_char(c, '/')) { if (next_char_is(1, '/')) state = FIRST_SLASH; } #undef next_char_is do { text_in_cell(c, self->text_cache, lc); if (!is_hostname_lc(lc)) return false; switch(state) { case ANY: if (cell_is_char(c, '/')) state = FIRST_SLASH; break; case FIRST_SLASH: state = cell_is_char(c, '/') ? SECOND_SLASH : ANY; break; case SECOND_SLASH: if (cell_is_char(c, ':')) return pos; state = cell_is_char(c, '/') ? SECOND_SLASH : ANY; break; } pos = prev_char_pos(self, pos, 1); if (pos >= self->xnum) break; c = self->cpu_cells + pos; if (cell_scale(c) != scale) break; } while(pos >= limit); return 0; } static bool prefix_matches(Line *self, index_type at, const char_type* prefix, index_type prefix_len, index_type scale) { if (prefix_len > at) return false; while (prefix_len--) { at = prev_char_pos(self, at, 1); if (at >= self->xnum || cell_scale(self->cpu_cells + at) != scale || !cell_is_char(self->cpu_cells + at, prefix[prefix_len])) return false; } return true; } static bool has_url_prefix_at(Line *self, const index_type at, index_type *ans, index_type scale) { for (size_t i = 0; i < OPT(url_prefixes.num); i++) { index_type prefix_len = OPT(url_prefixes.values[i].len); if (at < prefix_len) continue; if (prefix_matches(self, at, OPT(url_prefixes.values[i].string), prefix_len, scale)) { *ans = prev_char_pos(self, at, prefix_len); if (*ans < self->xnum) return true; } } return false; } #define MIN_URL_LEN 5 static bool has_url_beyond_colon_slash(Line *self, const index_type x, ListOfChars *lc, const index_type scale) { unsigned num_of_slashes = 0; index_type pos = x, num_chars = 0; while ((pos = next_char_pos(self, pos, 1)) < self->xnum && num_chars++ < MIN_URL_LEN + 2) { const CPUCell *c = self->cpu_cells + pos; if (cell_scale(c) != scale) return false; text_in_cell(c, self->text_cache, lc); if (num_of_slashes < 3) { if (!is_hostname_lc(lc)) return false; if (lc->count == 1 && lc->chars[0] == '/') num_of_slashes++; } else { for (size_t n = 0; n < lc->count; n++) if (!is_url_char(lc->chars[n])) return false; } } return true; } index_type line_url_start_at(Line *self, index_type x, ListOfChars *lc) { // Find the starting cell for a URL that contains the position x. A URL is defined as // known-prefix://url-chars. If no URL is found self->xnum is returned. if (self->cpu_cells[x].is_multicell && self->cpu_cells[x].x) x = x > self->cpu_cells[x].x ? x - self->cpu_cells[x].x : 0; if (x >= self->xnum || self->xnum <= MIN_URL_LEN + 3) return self->xnum; index_type ds_pos = 0, t, scale = cell_scale(self->cpu_cells + x); // First look for :// ahead of x ds_pos = find_colon_slash(self, x + OPT(url_prefixes).max_prefix_len + 3, x < 2 ? 0 : x - 2, lc, scale); if (ds_pos != 0 && has_url_beyond_colon_slash(self, ds_pos, lc, scale)) { if (has_url_prefix_at(self, ds_pos, &t, scale) && t <= x) return t; } ds_pos = find_colon_slash(self, x, 0, lc, scale); if (ds_pos == 0 || self->xnum < ds_pos + MIN_URL_LEN + 3 || !has_url_beyond_colon_slash(self, ds_pos, lc, scale)) return self->xnum; if (has_url_prefix_at(self, ds_pos, &t, scale)) return t; return self->xnum; } static bool is_pos_ok_for_url(Line *self, index_type x, bool in_hostname, index_type last_hostname_char_pos, ListOfChars *lc) { if (x >= self->xnum) return false; text_in_cell(self->cpu_cells + x, self->text_cache, lc); if (in_hostname && x <= last_hostname_char_pos) return is_hostname_lc(lc); return is_url_lc(lc); } index_type line_url_end_at(Line *self, index_type x, bool check_short, char_type sentinel, bool next_line_starts_with_url_chars, bool in_hostname, index_type last_hostname_char_pos, ListOfChars *lc) { index_type ans = x; #define is_not_ok(n) ((sentinel && cell_is_char(self->cpu_cells + n, sentinel)) || !is_pos_ok_for_url(self, n, in_hostname, last_hostname_char_pos, lc)) if (x >= self->xnum || (check_short && self->xnum <= MIN_URL_LEN + 3) || is_not_ok(x)) return 0; index_type n = ans; while ((n = next_char_pos(self, ans, 1)) < self->xnum) { if (is_not_ok(n)) break; ans = n; } #undef is_not_ok if (next_char_pos(self, ans, 1) < self->xnum || !next_line_starts_with_url_chars) { while (ans > x && !self->cpu_cells[ans].ch_is_idx && can_strip_from_end_of_url(self->cpu_cells[ans].ch_or_idx)) { n = prev_char_pos(self, ans, 1); if (n >= self->xnum || n < x) break; ans = n; } } return ans; } bool line_startswith_url_chars(Line *self, bool in_hostname, ListOfChars *lc) { text_in_cell(self->cpu_cells, self->text_cache, lc); if (in_hostname) return is_hostname_lc(lc); return is_url_lc(lc); } index_type find_char(Line *self, index_type start, char_type ch) { do { if (cell_is_char(self->cpu_cells + start, ch)) return start; } while ((start = next_char_pos(self, start, 1)) < self->xnum); return self->xnum; } char_type get_url_sentinel(Line *line, index_type url_start) { char_type before = 0, sentinel; if (url_start > 0 && url_start < line->xnum) { index_type n = prev_char_pos(line, url_start, 1); if (n < line->xnum) before = cell_first_char(line->cpu_cells + n, line->text_cache); } switch(before) { case '"': case '\'': case '*': sentinel = before; break; case '(': sentinel = ')'; break; case '[': sentinel = ']'; break; case '{': sentinel = '}'; break; case '<': sentinel = '>'; break; default: sentinel = 0; break; } return sentinel; } static PyObject* url_start_at(Line *self, PyObject *x) { #define url_start_at_doc "url_start_at(x) -> Return the start cell number for a URL containing x or self->xnum if not found" RAII_ListOfChars(lc); return PyLong_FromUnsignedLong((unsigned long)line_url_start_at(self, PyLong_AsUnsignedLong(x), &lc)); } static PyObject* url_end_at(Line *self, PyObject *args) { #define url_end_at_doc "url_end_at(x) -> Return the end cell number for a URL containing x or 0 if not found" unsigned int x, sentinel = 0; int next_line_starts_with_url_chars = 0; if (!PyArg_ParseTuple(args, "I|Ip", &x, &sentinel, &next_line_starts_with_url_chars)) return NULL; RAII_ListOfChars(lc); return PyLong_FromUnsignedLong((unsigned long)line_url_end_at(self, x, true, sentinel, next_line_starts_with_url_chars, false, self->xnum, &lc)); } // }}} static PyObject* text_at(Line* self, Py_ssize_t xval) { #define text_at_doc "[x] -> Return the text in the specified cell" if ((unsigned)xval >= self->xnum) { PyErr_SetString(PyExc_IndexError, "Column number out of bounds"); return NULL; } const CPUCell *cell = self->cpu_cells + xval; if (cell->ch_is_idx) { RAII_ListOfChars(lc); tc_chars_at_index(self->text_cache, cell->ch_or_idx, &lc); if (cell->is_multicell) { if (cell->x || cell->y || !lc.count) return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, lc.chars, 0); return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, lc.chars + 1, lc.count - 1); } return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, lc.chars, lc.count); } Py_UCS4 ch = cell->ch_or_idx; return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, &ch, 1); } size_t cell_as_unicode_for_fallback(const ListOfChars *lc, Py_UCS4 *buf, size_t sz) { size_t n = 1; buf[0] = lc->chars[0] ? lc->chars[0] : ' '; if (buf[0] != '\t') { for (unsigned i = 1; i < lc->count && n < sz; i++) { if (lc->chars[i] != VS15 && lc->chars[i] != VS16) buf[n++] = lc->chars[i]; } } else buf[0] = ' '; return n; } size_t cell_as_utf8_for_fallback(const ListOfChars *lc, char *buf, size_t sz) { char_type ch = lc->chars[0] ? lc->chars[0] : ' '; bool include_cc = true; if (ch == '\t') { ch = ' '; include_cc = false; } size_t n = encode_utf8(ch, buf); if (include_cc) { for (unsigned i = 1; i < lc->count && sz > n + 4; i++) { char_type ch = lc->chars[i]; if (ch != VS15 && ch != VS16) n += encode_utf8(ch, buf + n); } } buf[n] = 0; return n; } bool unicode_in_range(const Line *self, const index_type start, const index_type limit, const bool include_cc, const bool add_trailing_newline, const bool skip_zero_cells, bool skip_multiline_non_zero_lines, ANSIBuf *buf) { static const size_t initial_cap = 4096; ListOfChars lc; if (!buf->buf) { buf->buf = malloc(initial_cap * sizeof(buf->buf[0])); if (!buf->buf) return false; buf->capacity = initial_cap; } for (index_type i = start; i < limit; i++) { lc.chars = buf->buf + buf->len; lc.capacity = buf->capacity - buf->len; while (!text_in_cell_without_alloc(self->cpu_cells + i, self->text_cache, &lc)) { size_t ns = MAX(initial_cap, 2 * buf->capacity); char_type *np = realloc(buf->buf, ns); if (!np) return false; buf->capacity = ns; buf->buf = np; lc.chars = buf->buf + buf->len; lc.capacity = buf->capacity - buf->len; } if (self->cpu_cells[i].is_multicell && (self->cpu_cells[i].x || (skip_multiline_non_zero_lines && self->cpu_cells[i].y))) continue; if (!lc.chars[0]) { if (skip_zero_cells) continue; lc.chars[0] = ' '; } if (lc.chars[0] == '\t') { buf->len++; unsigned num_cells_to_skip_for_tab = lc.count > 1 ? lc.chars[1] : 0; while (num_cells_to_skip_for_tab && i + 1 < limit && cell_is_char(self->cpu_cells+i+1, ' ')) { i++; num_cells_to_skip_for_tab--; } } else buf->len += include_cc ? lc.count : 1; } if (add_trailing_newline && !self->cpu_cells[self->xnum-1].next_char_was_wrapped && buf->len < buf->capacity) buf->buf[buf->len++] = '\n'; return true; } PyObject * line_as_unicode(Line* self, bool skip_zero_cells, ANSIBuf *buf) { size_t before = buf->len; if (!unicode_in_range(self, 0, xlimit_for_line(self), true, false, skip_zero_cells, true, buf)) return PyErr_NoMemory(); PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf->buf + before, buf->len - before); buf->len = before; return ans; } static PyObject* sprite_at(Line* self, PyObject *x) { #define sprite_at_doc "[x] -> Return the sprite in the specified cell" unsigned long xval = PyLong_AsUnsignedLong(x); if (xval >= self->xnum) { PyErr_SetString(PyExc_IndexError, "Column number out of bounds"); return NULL; } GPUCell *c = self->gpu_cells + xval; return Py_BuildValue("I", (unsigned int)c->sprite_idx); } static void write_sgr(const char *val, ANSIBuf *output) { #define W(c) output->buf[output->len++] = c W(0x1b); W('['); for (size_t i = 0; val[i] != 0 && i < 122; i++) W(val[i]); W('m'); #undef W } static void write_hyperlink(hyperlink_id_type hid, ANSIBuf *output) { #define W(c) output->buf[output->len++] = c const char *key = hid ? get_hyperlink_for_id(output->hyperlink_pool, hid, false) : NULL; if (!key) hid = 0; output->active_hyperlink_id = hid; W(0x1b); W(']'); W('8'); if (!hid) { W(';'); W(';'); } else { const char* partition = strstr(key, ":"); W(';'); if (partition != key) { W('i'); W('d'); W('='); while (key != partition) W(*(key++)); } W(';'); while(*(++partition)) W(*partition); } W(0x1b); W('\\'); #undef W } static void write_mark(const char *mark, ANSIBuf *output) { #define W(c) output->buf[output->len++] = c W(0x1b); W(']'); W('1'); W('3'); W('3'); W(';'); for (size_t i = 0; mark[i] != 0 && i < 32; i++) W(mark[i]); W(0x1b); W('\\'); #undef W } static void write_sgr_to_ansi_buf(ANSILineState *s, const char *val) { close_multicell(s); ensure_space_in_ansi_output_buf(s, 128); s->escape_code_written = true; write_sgr(val, s->output_buf); } static void write_ch_to_ansi_buf(ANSILineState *s, char_type ch) { close_multicell(s); ensure_space_in_ansi_output_buf(s, 1); s->output_buf->buf[s->output_buf->len++] = ch; } static void write_hyperlink_to_ansi_buf(ANSILineState *s, hyperlink_id_type hid) { close_multicell(s); ensure_space_in_ansi_output_buf(s, 2256); s->escape_code_written = true; write_hyperlink(hid, s->output_buf); } static void write_mark_to_ansi_buf(ANSILineState *s, const char *m) { close_multicell(s); ensure_space_in_ansi_output_buf(s, 64); s->escape_code_written = true; write_mark(m, s->output_buf); } bool line_as_ansi(Line *self, ANSILineState *s, index_type start_at, index_type stop_before, char_type prefix_char, bool skip_multiline_non_zero_lines) { s->limit = MIN(stop_before, xlimit_for_line(self)); s->current_multicell_state = NULL; s->escape_code_written = false; if (prefix_char) write_ch_to_ansi_buf(s, prefix_char); if (start_at == 0) { switch (self->attrs.prompt_kind) { case UNKNOWN_PROMPT_KIND: break; case PROMPT_START: write_mark_to_ansi_buf(s, "A"); break; case SECONDARY_PROMPT: write_mark_to_ansi_buf(s, "A;k=s"); break; case OUTPUT_START: write_mark_to_ansi_buf(s, "C"); break; } } if (s->limit <= start_at) return s->escape_code_written; static const GPUCell blank_cell = { 0 }; GPUCell *cell; if (s->prev_gpu_cell == NULL) s->prev_gpu_cell = &blank_cell; const CellAttrs mask_for_sgr = {.val=SGR_MASK}; #define CMP_ATTRS (cell->attrs.val & mask_for_sgr.val) != (s->prev_gpu_cell->attrs.val & mask_for_sgr.val) #define CMP(x) (cell->x != s->prev_gpu_cell->x) for (s->pos=start_at; s->pos < s->limit; s->pos++) { if (s->output_buf->hyperlink_pool) { hyperlink_id_type hid = self->cpu_cells[s->pos].hyperlink_id; if (hid != s->output_buf->active_hyperlink_id) write_hyperlink_to_ansi_buf(s, hid); } cell = &self->gpu_cells[s->pos]; if (CMP_ATTRS || CMP(fg) || CMP(bg) || CMP(decoration_fg)) { const char *sgr = cell_as_sgr(cell, s->prev_gpu_cell); if (*sgr) write_sgr_to_ansi_buf(s, sgr); } text_in_cell_ansi(s, self->cpu_cells + s->pos, self->text_cache, skip_multiline_non_zero_lines); s->prev_gpu_cell = cell; } close_multicell(s); return s->escape_code_written; #undef CMP_ATTRS #undef CMP } static PyObject* as_ansi(Line* self, PyObject *a UNUSED) { #define as_ansi_doc "Return the line's contents with ANSI (SGR) escape codes for formatting" ANSIBuf output = {0}; ANSILineState s = {.output_buf=&output}; line_as_ansi(self, &s, 0, self->xnum, 0, true); PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, output.buf, output.len); free(output.buf); return ans; } static PyObject* last_char_has_wrapped_flag(Line* self, PyObject *a UNUSED) { #define last_char_has_wrapped_flag_doc "Return True if the last cell of this line has the wrapped flags set" if (self->cpu_cells[self->xnum - 1].next_char_was_wrapped) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static PyObject* set_wrapped_flag(Line* self, PyObject *is_wrapped) { self->cpu_cells[self->xnum-1].next_char_was_wrapped = PyObject_IsTrue(is_wrapped); Py_RETURN_NONE; } static PyObject* __repr__(Line* self) { RAII_ANSIBuf(buf); RAII_PyObject(s, line_as_unicode(self, false, &buf)); if (s != NULL) return PyObject_Repr(s); return NULL; } static PyObject* __str__(Line* self) { RAII_ANSIBuf(buf); return line_as_unicode(self, false, &buf); } static PyObject* width(Line *self, PyObject *val) { #define width_doc "width(x) -> the width of the character at x" unsigned long x = PyLong_AsUnsignedLong(val); if (x >= self->xnum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; } const CPUCell *c = self->cpu_cells + x; if (!cell_has_text(c)) return 0; unsigned long ans = 1; if (c->is_multicell) ans = c->x || c->y ? 0 : c->width; return PyLong_FromUnsignedLong(ans); } static PyObject* add_combining_char(Line* self, PyObject *args) { #define add_combining_char_doc "add_combining_char(x, ch) -> Add the specified character as a combining char to the specified cell." int new_char; unsigned int x; if (!PyArg_ParseTuple(args, "IC", &x, &new_char)) return NULL; if (x >= self->xnum) { PyErr_SetString(PyExc_ValueError, "Column index out of bounds"); return NULL; } CPUCell *cell = self->cpu_cells + x; if (cell->is_multicell) { PyErr_SetString(PyExc_IndexError, "cannot set combining char in a multicell"); return NULL; } RAII_ListOfChars(lc); text_in_cell(cell, self->text_cache, &lc); ensure_space_for_chars(&lc, lc.count + 1); lc.chars[lc.count++] = new_char; cell->ch_or_idx = tc_get_or_insert_chars(self->text_cache, &lc); cell->ch_is_idx = true; Py_RETURN_NONE; } static PyObject* set_text(Line* self, PyObject *args) { #define set_text_doc "set_text(src, offset, sz, cursor) -> Set the characters and attributes from the specified text and cursor" PyObject *src; Py_ssize_t offset, sz, limit; Cursor *cursor; int kind; void *buf; if (!PyArg_ParseTuple(args, "UnnO!", &src, &offset, &sz, &Cursor_Type, &cursor)) return NULL; if (PyUnicode_READY(src) != 0) { PyErr_NoMemory(); return NULL; } kind = PyUnicode_KIND(src); buf = PyUnicode_DATA(src); limit = offset + sz; if (PyUnicode_GET_LENGTH(src) < limit) { PyErr_SetString(PyExc_ValueError, "Out of bounds offset/sz"); return NULL; } CellAttrs attrs = cursor_to_attrs(cursor); color_type fg = (cursor->fg & COL_MASK), bg = cursor->bg & COL_MASK; color_type dfg = cursor->decoration_fg & COL_MASK; for (index_type i = cursor->x; offset < limit && i < self->xnum; i++, offset++) { self->cpu_cells[i] = (CPUCell){0}; self->cpu_cells[i].ch_or_idx = PyUnicode_READ(kind, buf, offset); self->gpu_cells[i].attrs = attrs; self->gpu_cells[i].fg = fg; self->gpu_cells[i].bg = bg; self->gpu_cells[i].decoration_fg = dfg; } Py_RETURN_NONE; } static PyObject* cursor_from(Line* self, PyObject *args) { #define cursor_from_doc "cursor_from(x, y=0) -> Create a cursor object based on the formatting attributes at the specified x position. The y value of the cursor is set as specified." unsigned int x, y = 0; Cursor* ans; if (!PyArg_ParseTuple(args, "I|I", &x, &y)) return NULL; if (x >= self->xnum) { PyErr_SetString(PyExc_ValueError, "Out of bounds x"); return NULL; } ans = alloc_cursor(); if (ans == NULL) { PyErr_NoMemory(); return NULL; } ans->x = x; ans->y = y; attrs_to_cursor(self->gpu_cells[x].attrs, ans); ans->fg = self->gpu_cells[x].fg; ans->bg = self->gpu_cells[x].bg; ans->decoration_fg = self->gpu_cells[x].decoration_fg & COL_MASK; return (PyObject*)ans; } void line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch) { const CPUCell cc = {.ch_or_idx=ch}; if (at + num > self->xnum) num = self->xnum > at ? self->xnum - at : 0; memset_array(self->cpu_cells + at, cc, num); } static PyObject* clear_text(Line* self, PyObject *args) { #define clear_text_doc "clear_text(at, num, ch=BLANK_CHAR) -> Clear characters in the specified range, preserving formatting." unsigned int at, num; int ch = BLANK_CHAR; if (!PyArg_ParseTuple(args, "II|C", &at, &num, &ch)) return NULL; line_clear_text(self, at, num, ch); Py_RETURN_NONE; } void line_apply_cursor(Line *self, const Cursor *cursor, unsigned int at, unsigned int num, bool clear_char) { GPUCell gc = cursor_as_gpu_cell(cursor); if (clear_char) { #if BLANK_CHAR != 0 #error This implementation is incorrect for BLANK_CHAR != 0 #endif if (at + num > self->xnum) { num = at < self->xnum ? self->xnum - at : 0; } memset(self->cpu_cells + at, 0, num * sizeof(CPUCell)); memset_array(self->gpu_cells + at, gc, num); } else { for (index_type i = at; i < self->xnum && i < at + num; i++) { gc.attrs.mark = self->gpu_cells[i].attrs.mark; gc.sprite_idx = self->gpu_cells[i].sprite_idx; memcpy(self->gpu_cells + i, &gc, sizeof(gc)); } } } static PyObject* apply_cursor(Line* self, PyObject *args) { #define apply_cursor_doc "apply_cursor(cursor, at=0, num=1, clear_char=False) -> Apply the formatting attributes from cursor to the specified characters in this line." Cursor* cursor; unsigned int at=0, num=1; int clear_char = 0; if (!PyArg_ParseTuple(args, "O!|IIp", &Cursor_Type, &cursor, &at, &num, &clear_char)) return NULL; line_apply_cursor(self, cursor, at, num, clear_char & 1); Py_RETURN_NONE; } static color_type resolve_color(const ColorProfile *cp, color_type val, color_type defval) { switch(val & 0xff) { case 1: return cp->color_table[(val >> 8) & 0xff]; case 2: return val >> 8; default: return defval; } } bool colors_for_cell(Line *self, const ColorProfile *cp, index_type *x, color_type *fg, color_type *bg, bool *reversed) { if (*x >= self->xnum) return false; while (self->cpu_cells[*x].is_multicell && self->cpu_cells[*x].x && *x) (*x)--; *fg = resolve_color(cp, self->gpu_cells[*x].fg, *fg); *bg = resolve_color(cp, self->gpu_cells[*x].bg, *bg); if (self->gpu_cells[*x].attrs.reverse) { color_type t = *fg; *fg = *bg; *bg = t; *reversed = true; } return true; } char_type line_get_char(Line *self, index_type at) { if (self->cpu_cells[at].ch_is_idx) { RAII_ListOfChars(lc); text_in_cell(self->cpu_cells + at, self->text_cache, &lc); if (self->cpu_cells[at].is_multicell && (self->cpu_cells[at].x || self->cpu_cells[at].y)) return 0; return lc.chars[0]; } else return self->cpu_cells[at].ch_or_idx; } static void line_set_char(Line *self, unsigned int at, uint32_t ch, Cursor *cursor, hyperlink_id_type hyperlink_id) { GPUCell *g = self->gpu_cells + at; if (cursor != NULL) { g->attrs = cursor_to_attrs(cursor); g->fg = cursor->fg & COL_MASK; g->bg = cursor->bg & COL_MASK; g->decoration_fg = cursor->decoration_fg & COL_MASK; } CPUCell *c = self->cpu_cells + at; *c = (CPUCell){0}; cell_set_char(c, ch); c->hyperlink_id = hyperlink_id; if (OPT(underline_hyperlinks) == UNDERLINE_ALWAYS && hyperlink_id) { g->decoration_fg = ((OPT(url_color) & COL_MASK) << 8) | 2; g->attrs.decoration = OPT(url_style); } } static PyObject* set_char(Line *self, PyObject *args) { #define set_char_doc "set_char(at, ch, width=1, cursor=None, hyperlink_id=0) -> Set the character at the specified cell. If cursor is not None, also set attributes from that cursor." unsigned int at, width=1; int ch; Cursor *cursor = NULL; unsigned int hyperlink_id = 0; if (!PyArg_ParseTuple(args, "IC|IO!I", &at, &ch, &width, &Cursor_Type, &cursor, &hyperlink_id)) return NULL; if (at >= self->xnum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; } if (width != 1) { PyErr_SetString(PyExc_NotImplementedError, "TODO: Implement setting wide char"); return NULL; } line_set_char(self, at, ch, cursor, hyperlink_id); Py_RETURN_NONE; } static PyObject* set_attribute(Line *self, PyObject *args) { #define set_attribute_doc "set_attribute(which, val) -> Set the attribute on all cells in the line." unsigned int val; char *which; if (!PyArg_ParseTuple(args, "sI", &which, &val)) return NULL; if (!set_named_attribute_on_line(self->gpu_cells, which, val, self->xnum)) { PyErr_SetString(PyExc_KeyError, "Unknown cell attribute"); return NULL; } Py_RETURN_NONE; } static int color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsigned aix_code, unsigned complex_code) { switch(val & 0xff) { case 1: val >>= 8; if (val < 16 && simple_code) { return snprintf(buf, sz, "%lu;", (val < 8) ? simple_code + val : aix_code + (val - 8)); } return snprintf(buf, sz, "%u:5:%lu;", complex_code, val); case 2: return snprintf(buf, sz, "%u:2:%lu:%lu:%lu;", complex_code, (val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff); default: return snprintf(buf, sz, "%u;", complex_code + 1); // reset } } static const char* decoration_as_sgr(uint8_t decoration) { switch(decoration) { case 1: return "4;"; case 2: return "4:2;"; case 3: return "4:3;"; case 4: return "4:4"; case 5: return "4:5"; default: return "24;"; } } const char* cell_as_sgr(const GPUCell *cell, const GPUCell *prev) { static char buf[128]; #define SZ sizeof(buf) - (p - buf) - 2 #define P(s) { size_t len = strlen(s); if (SZ > len) { memcpy(p, s, len); p += len; } } char *p = buf; #define CA cell->attrs #define PA prev->attrs bool intensity_differs = CA.bold != PA.bold || CA.dim != PA.dim; if (intensity_differs) { if (CA.bold && CA.dim) { if (!PA.bold) P("1;"); if (!PA.dim) P("2;"); } else { P("22;"); if (CA.bold) P("1;"); if (CA.dim) P("2;"); } } if (CA.italic != PA.italic) P(CA.italic ? "3;" : "23;"); if (CA.reverse != PA.reverse) P(CA.reverse ? "7;" : "27;"); if (CA.strike != PA.strike) P(CA.strike ? "9;" : "29;"); if (cell->fg != prev->fg) p += color_as_sgr(p, SZ, cell->fg, 30, 90, 38); if (cell->bg != prev->bg) p += color_as_sgr(p, SZ, cell->bg, 40, 100, 48); if (cell->decoration_fg != prev->decoration_fg) p += color_as_sgr(p, SZ, cell->decoration_fg, 0, 0, DECORATION_FG_CODE); if (CA.decoration != PA.decoration) P(decoration_as_sgr(CA.decoration)); #undef PA #undef CA #undef P #undef SZ if (p > buf) *(p - 1) = 0; // remove trailing semi-colon *p = 0; // ensure string is null-terminated return buf; } static Py_ssize_t __len__(PyObject *self) { return (Py_ssize_t)(((Line*)self)->xnum); } static int __eq__(Line *a, Line *b) { return a->xnum == b->xnum && memcmp(a->cpu_cells, b->cpu_cells, sizeof(CPUCell) * a->xnum) == 0 && memcmp(a->gpu_cells, b->gpu_cells, sizeof(GPUCell) * a->xnum) == 0; } bool line_has_mark(Line *line, uint16_t mark) { for (index_type x = 0; x < line->xnum; x++) { const uint16_t m = line->gpu_cells[x].attrs.mark; if (m && (!mark || mark == m)) return true; } return false; } static void report_marker_error(PyObject *marker) { if (!PyObject_HasAttrString(marker, "error_reported")) { PyErr_Print(); if (PyObject_SetAttrString(marker, "error_reported", Py_True) != 0) PyErr_Clear(); } else PyErr_Clear(); } static void apply_mark(Line *line, const uint16_t mark, index_type *cell_pos, unsigned int *match_pos) { #define MARK { line->gpu_cells[x].attrs.mark = mark; } index_type x = *cell_pos; MARK; (*match_pos)++; RAII_ListOfChars(lc); text_in_cell(line->cpu_cells + x, line->text_cache, &lc); if (lc.chars[0]) { if (lc.chars[0] == '\t') { unsigned num_cells_to_skip_for_tab = lc.count > 1 ? lc.chars[1] : 0; while (num_cells_to_skip_for_tab && x + 1 < line->xnum && cell_is_char(line->cpu_cells+x+1, ' ')) { x++; num_cells_to_skip_for_tab--; MARK; } } else if (line->cpu_cells[x].is_multicell) { *match_pos += lc.count - 1; index_type x_limit = MIN(line->xnum, mcd_x_limit(line->cpu_cells + x)); for (; x < x_limit; x++) { MARK; } x--; } else { *match_pos += lc.count - 1; } } *cell_pos = x + 1; #undef MARK } static void apply_marker(PyObject *marker, Line *line, const PyObject *text) { unsigned int l=0, r=0, col=0, match_pos=0; PyObject *pl = PyLong_FromVoidPtr(&l), *pr = PyLong_FromVoidPtr(&r), *pcol = PyLong_FromVoidPtr(&col); if (!pl || !pr || !pcol) { PyErr_Clear(); return; } PyObject *iter = PyObject_CallFunctionObjArgs(marker, text, pl, pr, pcol, NULL); Py_DECREF(pl); Py_DECREF(pr); Py_DECREF(pcol); if (iter == NULL) { report_marker_error(marker); return; } PyObject *match; index_type x = 0; while ((match = PyIter_Next(iter)) && x < line->xnum) { Py_DECREF(match); while (match_pos < l && x < line->xnum) { apply_mark(line, 0, &x, &match_pos); } uint16_t am = (col & MARK_MASK); while(x < line->xnum && match_pos <= r) { apply_mark(line, am, &x, &match_pos); } } Py_DECREF(iter); while(x < line->xnum) line->gpu_cells[x++].attrs.mark = 0; if (PyErr_Occurred()) report_marker_error(marker); } void mark_text_in_line(PyObject *marker, Line *line, ANSIBuf *buf) { if (!marker) { for (index_type i = 0; i < line->xnum; i++) line->gpu_cells[i].attrs.mark = 0; return; } PyObject *text = line_as_unicode(line, false, buf); if (PyUnicode_GET_LENGTH(text) > 0) { apply_marker(marker, line, text); } else { for (index_type i = 0; i < line->xnum; i++) line->gpu_cells[i].attrs.mark = 0; } Py_DECREF(text); } PyObject* as_text_generic(PyObject *args, void *container, get_line_func get_line, index_type lines, ANSIBuf *ansibuf, bool add_trailing_newline) { #define APPEND(x) { PyObject* retval = PyObject_CallFunctionObjArgs(callback, x, NULL); if (!retval) return NULL; Py_DECREF(retval); } #define APPEND_AND_DECREF(x) { if (x == NULL) { if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } PyObject* retval = PyObject_CallFunctionObjArgs(callback, x, NULL); Py_CLEAR(x); if (!retval) return NULL; Py_DECREF(retval); } PyObject *callback; int as_ansi = 0, insert_wrap_markers = 0; if (!PyArg_ParseTuple(args, "O|pp", &callback, &as_ansi, &insert_wrap_markers)) return NULL; PyObject *t = NULL; RAII_PyObject(nl, PyUnicode_FromString("\n")); RAII_PyObject(cr, PyUnicode_FromString("\r")); RAII_PyObject(sgr_reset, PyUnicode_FromString("\x1b[m")); if (nl == NULL || cr == NULL || sgr_reset == NULL) return NULL; ANSILineState s = {.output_buf=ansibuf}; ansibuf->active_hyperlink_id = 0; bool need_newline = false; for (index_type y = 0; y < lines; y++) { Line *line = get_line(container, y); if (!line) { if (PyErr_Occurred()) return NULL; break; } if (need_newline) APPEND(nl); ansibuf->len = 0; if (as_ansi) { // less has a bug where it resets colors when it sees a \r, so work // around it by resetting SGR at the start of every line. This is // pretty sad performance wise, but I guess it will remain as it // makes writing pagers easier. // see https://github.com/kovidgoyal/kitty/issues/2381 s.prev_gpu_cell = NULL; line_as_ansi(line, &s, 0, line->xnum, 0, true); t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, ansibuf->buf, ansibuf->len); if (t && ansibuf->len > 0) APPEND(sgr_reset); } else { t = line_as_unicode(line, false, ansibuf); } APPEND_AND_DECREF(t); if (insert_wrap_markers) APPEND(cr); need_newline = !line->cpu_cells[line->xnum-1].next_char_was_wrapped; } if (need_newline && add_trailing_newline) APPEND(nl); if (ansibuf->active_hyperlink_id) { ansibuf->active_hyperlink_id = 0; t = PyUnicode_FromString("\x1b]8;;\x1b\\"); APPEND_AND_DECREF(t); } Py_RETURN_NONE; #undef APPEND #undef APPEND_AND_DECREF } // Boilerplate {{{ static PyObject* copy_char(Line* self, PyObject *args); #define copy_char_doc "copy_char(src, to, dest) -> Copy the character at src to the character dest in the line `to`" #define hyperlink_ids_doc "hyperlink_ids() -> Tuple of hyper link ids at every cell" static PyObject* hyperlink_ids(Line *self, PyObject *args UNUSED) { PyObject *ans = PyTuple_New(self->xnum); for (index_type x = 0; x < self->xnum; x++) { PyTuple_SET_ITEM(ans, x, PyLong_FromUnsignedLong(self->cpu_cells[x].hyperlink_id)); } return ans; } static PyObject * richcmp(PyObject *obj1, PyObject *obj2, int op); static PySequenceMethods sequence_methods = { .sq_length = __len__, .sq_item = (ssizeargfunc)text_at }; static PyMethodDef methods[] = { METHOD(add_combining_char, METH_VARARGS) METHOD(set_text, METH_VARARGS) METHOD(cursor_from, METH_VARARGS) METHOD(apply_cursor, METH_VARARGS) METHOD(clear_text, METH_VARARGS) METHOD(copy_char, METH_VARARGS) METHOD(set_char, METH_VARARGS) METHOD(set_attribute, METH_VARARGS) METHOD(as_ansi, METH_NOARGS) METHOD(last_char_has_wrapped_flag, METH_NOARGS) METHODB(set_wrapped_flag, METH_O), METHOD(hyperlink_ids, METH_NOARGS) METHOD(width, METH_O) METHOD(url_start_at, METH_O) METHOD(url_end_at, METH_VARARGS) METHOD(sprite_at, METH_O) {NULL} /* Sentinel */ }; PyTypeObject Line_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.Line", .tp_basicsize = sizeof(Line), .tp_dealloc = (destructor)dealloc, .tp_repr = (reprfunc)__repr__, .tp_str = (reprfunc)__str__, .tp_as_sequence = &sequence_methods, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_richcompare = richcmp, .tp_doc = "Lines", .tp_methods = methods, }; Line *alloc_line(TextCache *tc) { Line *ans = (Line*)Line_Type.tp_alloc(&Line_Type, 0); if (ans) ans->text_cache = tc_incref(tc); return ans; } RICHCMP(Line) INIT_TYPE(Line) // }}} static PyObject* copy_char(Line* self, PyObject *args) { unsigned int src, dest; Line *to; if (!PyArg_ParseTuple(args, "IO!I", &src, &Line_Type, &to, &dest)) return NULL; if (src >= self->xnum || dest >= to->xnum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; } COPY_CELL(self, src, to, dest); Py_RETURN_NONE; } kitty-0.41.1/kitty/line.h0000664000175000017510000001317114773370543014556 0ustar nileshnilesh/* * line.h * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "text-cache.h" typedef union CellAttrs { struct { uint16_t decoration : 3; uint16_t bold : 1; uint16_t italic : 1; uint16_t reverse : 1; uint16_t strike : 1; uint16_t dim : 1; uint16_t mark : 2; uint32_t : 22; }; uint32_t val; } CellAttrs; static_assert(sizeof(CellAttrs) == sizeof(uint32_t), "Fix the ordering of CellAttrs"); #define WIDTH_MASK (3u) #define DECORATION_MASK (7u) #define SGR_MASK (~(((CellAttrs){.mark=MARK_MASK}).val)) #define MAX_NUM_CODEPOINTS_PER_CELL 24u // Text presentation selector #define VS15 0xfe0e // Emoji presentation selector #define VS16 0xfe0f typedef struct { color_type fg, bg, decoration_fg; sprite_index sprite_idx; CellAttrs attrs; } GPUCell; static_assert(sizeof(GPUCell) == 20, "Fix the ordering of GPUCell"); #define SCALE_BITS 3 #define WIDTH_BITS 3 #define SUBSCALE_BITS 4 #define VALIGN_BITS 2 #define HALIGN_BITS 2 typedef union CPUCell { struct { char_type ch_or_idx: sizeof(char_type) * 8 - 1; char_type ch_is_idx: 1; char_type hyperlink_id: sizeof(hyperlink_id_type) * 8; char_type next_char_was_wrapped : 1; char_type is_multicell : 1; char_type natural_width: 1; char_type scale: SCALE_BITS; char_type subscale_n: SUBSCALE_BITS; char_type subscale_d: SUBSCALE_BITS; char_type x : WIDTH_BITS + SCALE_BITS; char_type y : SCALE_BITS; char_type width: WIDTH_BITS; char_type valign: VALIGN_BITS; char_type halign: HALIGN_BITS; char_type temp_flag: 1; char_type : 15; }; struct { char_type ch_and_idx: sizeof(char_type) * 8; char_type : 32; char_type : 32; }; } CPUCell; static_assert(sizeof(CPUCell) == 12, "Fix the ordering of CPUCell"); typedef union LineAttrs { struct { uint8_t has_dirty_text : 1; uint8_t has_image_placeholders : 1; uint8_t prompt_kind : 2; uint8_t : 4; }; uint8_t val; } LineAttrs ; static_assert(sizeof(LineAttrs) == sizeof(uint8_t), "Fix the ordering of LineAttrs"); typedef struct { PyObject_HEAD GPUCell *gpu_cells; CPUCell *cpu_cells; index_type xnum, ynum; bool needs_free; LineAttrs attrs; TextCache *text_cache; } Line; typedef struct MultiCellCommand { unsigned int width, scale, subscale_n, subscale_d, vertical_align, horizontal_align; size_t payload_sz; } MultiCellCommand; typedef struct ANSILineOutput { const GPUCell *prev_gpu_cell; const CPUCell *current_multicell_state; index_type pos, limit; ANSIBuf *output_buf; bool escape_code_written; } ANSILineState; static inline void cleanup_ansibuf(ANSIBuf *b) { free(b->buf); zero_at_ptr(b); } #define RAII_ANSIBuf(name) __attribute__((cleanup(cleanup_ansibuf))) ANSIBuf name = {0} Line* alloc_line(TextCache *text_cache); void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, unsigned int count, bool is_group); const char* cell_as_sgr(const GPUCell *, const GPUCell *); static inline bool cell_has_text(const CPUCell *c) { return c->ch_and_idx != 0; } static inline void cell_set_char(CPUCell *c, char_type ch) { c->ch_and_idx = ch & 0x7fffffff; } static inline bool cell_is_char(const CPUCell *c, char_type ch) { return c->ch_and_idx == ch; } static inline index_type cell_scale(const CPUCell *c) { return c->is_multicell ? c->scale : 1; } static inline unsigned num_codepoints_in_cell(const CPUCell *c, const TextCache *tc) { unsigned ans; if (c->ch_is_idx) { ans = tc_num_codepoints(tc, c->ch_or_idx); if (c->is_multicell) ans--; } else ans = c->ch_or_idx ? 1 : 0; return ans; } static inline unsigned mcd_x_limit(const CPUCell* mcd) { return mcd->scale * mcd->width; } static inline void text_in_cell(const CPUCell *c, const TextCache *tc, ListOfChars *ans) { if (c->ch_is_idx) { tc_chars_at_index(tc, c->ch_or_idx, ans); } else { ans->count = 1; ans->chars[0] = c->ch_or_idx; } } static inline bool text_in_cell_without_alloc(const CPUCell *c, const TextCache *tc, ListOfChars *ans) { if (c->ch_is_idx) { if (!tc_chars_at_index_without_alloc(tc, c->ch_or_idx, ans)) return false; return true; } ans->count = 1; if (ans->capacity < 1) return false; ans->chars[0] = c->ch_or_idx; return true; } static inline void cell_set_chars(CPUCell *c, TextCache *tc, const ListOfChars *lc) { if (lc->count <= 1) cell_set_char(c, lc->chars[0]); else { c->ch_or_idx = tc_get_or_insert_chars(tc, lc); c->ch_is_idx = true; } } static inline char_type cell_first_char(const CPUCell *c, const TextCache *tc) { if (c->ch_is_idx) { if (c->is_multicell && (c->x || c->y)) return 0; return tc_first_char_at_index(tc, c->ch_or_idx); } return c->ch_or_idx; } static inline CellAttrs cursor_to_attrs(const Cursor *c) { CellAttrs ans = { .decoration=c->decoration, .bold=c->bold, .italic=c->italic, .reverse=c->reverse, .strike=c->strikethrough, .dim=c->dim}; return ans; } static inline void attrs_to_cursor(const CellAttrs attrs, Cursor *c) { c->decoration = attrs.decoration; c->bold = attrs.bold; c->italic = attrs.italic; c->reverse = attrs.reverse; c->strikethrough = attrs.strike; c->dim = attrs.dim; } #define cursor_as_gpu_cell(cursor) {.attrs=cursor_to_attrs(cursor), .fg=(cursor->fg & COL_MASK), .bg=(cursor->bg & COL_MASK), .decoration_fg=cursor->decoration_fg & COL_MASK} kitty-0.41.1/kitty/linear2srgb.glsl0000664000175000017510000000061614773370543016553 0ustar nileshnileshfloat srgb2linear(float x) { // sRGB to linear conversion float lower = x / 12.92; float upper = pow((x + 0.055f) / 1.055f, 2.4f); return mix(lower, upper, step(0.04045f, x)); } float linear2srgb(float x) { // Linear to sRGB conversion. float lower = 12.92 * x; float upper = 1.055 * pow(x, 1.0f / 2.4f) - 0.055f; return mix(lower, upper, step(0.0031308f, x)); } kitty-0.41.1/kitty/lineops.h0000664000175000017510000001416414773370543015303 0ustar nileshnilesh/* * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "history.h" #include "line-buf.h" #define set_attribute_on_line(cells, which, val, xnum) { \ for (index_type i__ = 0; i__ < xnum; i__++) cells[i__].attrs.which = val; } static inline bool set_named_attribute_on_line(GPUCell *cells, const char* which, uint16_t val, index_type xnum) { // Set a single attribute on all cells in the line #define s(q) if (strcmp(#q, which) == 0) { set_attribute_on_line(cells, q, val, xnum); return true; } s(reverse); s(strike); s(dim); s(mark); s(bold); s(italic); s(decoration); return false; #undef s } static inline void copy_line(const Line *src, Line *dest) { memcpy(dest->cpu_cells, src->cpu_cells, sizeof(CPUCell) * MIN(src->xnum, dest->xnum)); memcpy(dest->gpu_cells, src->gpu_cells, sizeof(GPUCell) * MIN(src->xnum, dest->xnum)); } static inline void clear_chars_in_line(CPUCell *cpu_cells, GPUCell *gpu_cells, index_type xnum, char_type ch) { // Clear only the char part of each cell, the rest must have been cleared by a memset or similar if (ch) { static const CellAttrs empty = {0}; const CPUCell c = {.ch_or_idx=ch}; for (index_type i = 0; i < xnum; i++) { cpu_cells[i] = c; gpu_cells[i].attrs = empty; } } } static inline index_type xlimit_for_line(const Line *line) { index_type xlimit = line->xnum; while (xlimit > 0 && !line->cpu_cells[xlimit - 1].ch_and_idx) xlimit--; return xlimit; } static inline void line_save_cells(Line *line, index_type start, index_type num, GPUCell *gpu_cells, CPUCell *cpu_cells) { memcpy(gpu_cells + start, line->gpu_cells + start, sizeof(GPUCell) * num); memcpy(cpu_cells + start, line->cpu_cells + start, sizeof(CPUCell) * num); } static inline void line_reset_cells(Line *line, index_type start, index_type num, GPUCell *gpu_cells, CPUCell *cpu_cells) { memcpy(line->gpu_cells + start, gpu_cells + start, sizeof(GPUCell) * num); memcpy(line->cpu_cells + start, cpu_cells + start, sizeof(CPUCell) * num); } static inline bool line_is_empty(const Line *line) { #if BLANK_CHAR != 0 #error This implementation is incorrect for BLANK_CHAR != 0 #endif for (index_type i = 0; i < line->xnum; i++) if (line->cpu_cells[i].ch_and_idx) return false; return true; } typedef Line*(get_line_func)(void *, int); void line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch); void line_apply_cursor(Line *self, const Cursor *cursor, unsigned int at, unsigned int num, bool clear_char); char_type line_get_char(Line *self, index_type at); index_type line_url_start_at(Line *self, index_type x, ListOfChars *lc); index_type line_url_end_at(Line *self, index_type x, bool, char_type, bool, bool, index_type, ListOfChars*); bool line_startswith_url_chars(Line*, bool, ListOfChars*); char_type get_url_sentinel(Line *line, index_type url_start); index_type find_char(Line *self, index_type start, char_type ch); index_type next_char_pos(const Line *self, index_type x, index_type num); index_type prev_char_pos(const Line *self, index_type x, index_type num); bool line_as_ansi(Line *self, ANSILineState *s, index_type start_at, index_type stop_before, char_type prefix_char, bool skip_multiline_non_zero_lines) __attribute__((nonnull)); unsigned int line_length(Line *self); size_t cell_as_unicode_for_fallback(const ListOfChars *lc, Py_UCS4 *buf, size_t sz); size_t cell_as_utf8_for_fallback(const ListOfChars *lc, char *buf, size_t sz); bool unicode_in_range(const Line *self, const index_type start, const index_type limit, const bool include_cc, const bool add_trailing_newline, const bool skip_zero_cells, bool skip_multiline_non_zero_lines, ANSIBuf*); PyObject* line_as_unicode(Line *, bool, ANSIBuf*); void linebuf_init_line(LineBuf *, index_type); void linebuf_init_line_at(LineBuf *, index_type, Line*); void linebuf_init_cells(LineBuf *lb, index_type ynum, CPUCell **c, GPUCell **g); CPUCell* linebuf_cpu_cells_for_line(LineBuf *lb, index_type idx); void linebuf_clear(LineBuf *, char_type ch); void linebuf_clear_lines(LineBuf *self, const Cursor *cursor, index_type start, index_type end); void linebuf_index(LineBuf* self, index_type top, index_type bottom); void linebuf_reverse_index(LineBuf *self, index_type top, index_type bottom); void linebuf_clear_line(LineBuf *self, index_type y, bool clear_attrs); void linebuf_insert_lines(LineBuf *self, unsigned int num, unsigned int y, unsigned int bottom); void linebuf_delete_lines(LineBuf *self, index_type num, index_type y, index_type bottom); void linebuf_copy_line_to(LineBuf *, Line *, index_type); void linebuf_mark_line_dirty(LineBuf *self, index_type y); void linebuf_clear_attrs_and_dirty(LineBuf *self, index_type y); void linebuf_mark_line_clean(LineBuf *self, index_type y); void linebuf_set_line_has_image_placeholders(LineBuf *self, index_type y, bool val); void linebuf_set_last_char_as_continuation(LineBuf *self, index_type y, bool continued); CPUCell* linebuf_cpu_cell_at(LineBuf *self, index_type x, index_type y); bool linebuf_line_ends_with_continuation(LineBuf *self, index_type y); void linebuf_refresh_sprite_positions(LineBuf *self); void historybuf_add_line(HistoryBuf *self, const Line *line, ANSIBuf*); bool historybuf_pop_line(HistoryBuf *, Line *); void historybuf_init_line(HistoryBuf *self, index_type num, Line *l); bool history_buf_endswith_wrap(HistoryBuf *self); CPUCell* historybuf_cpu_cells(HistoryBuf *self, index_type num); void historybuf_mark_line_clean(HistoryBuf *self, index_type y); void historybuf_mark_line_dirty(HistoryBuf *self, index_type y); void historybuf_set_line_has_image_placeholders(HistoryBuf *self, index_type y, bool val); void historybuf_refresh_sprite_positions(HistoryBuf *self); void historybuf_clear(HistoryBuf *self); void mark_text_in_line(PyObject *marker, Line *line, ANSIBuf *buf); bool line_has_mark(Line *, uint16_t mark); PyObject* as_text_generic(PyObject *args, void *container, get_line_func get_line, index_type lines, ANSIBuf *ansibuf, bool add_trailing_newline); bool colors_for_cell(Line *self, const ColorProfile *cp, index_type *x, color_type *fg, color_type *bg, bool *reversed); kitty-0.41.1/kitty/logging.c0000664000175000017510000000451614773370543015253 0ustar nileshnilesh/* * logging.c * Copyright (C) 2018 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "data-types.h" #include "charsets.h" #include #include #include #include #ifdef __APPLE__ #include #endif static bool use_os_log = false; void log_error(const char *fmt, ...) { int n = 0; va_list ar; va_start(ar, fmt); n = vsnprintf(NULL, 0, fmt, ar); va_end(ar); if (n < 0) return; size_t size = 5 * (size_t)n + 8; RAII_ALLOC(char, arena, calloc(size, sizeof(char))); if (!arena) return; va_start(ar, fmt); n = vsnprintf(arena, size, fmt, ar); va_end(ar); char *sanbuf = arena + n + 1; char utf8buf[4]; START_ALLOW_CASE_RANGE size_t j = 0; for (char *x = arena; x < arena + n; x++) { switch(*x) { case C0_EXCEPT_NL_SPACE_TAB: { const uint32_t ch = 0x2400 + *x; const unsigned sz = encode_utf8(ch, utf8buf); for (unsigned c = 0; c < sz; c++, j++) sanbuf[j] = utf8buf[c]; } break; default: sanbuf[j++] = *x; break; } } sanbuf[j] = 0; END_ALLOW_CASE_RANGE if (!use_os_log) { // Apple's os_log already records timestamps fprintf(stderr, "[%.3f] ", monotonic_t_to_s_double(monotonic())); } #ifdef __APPLE__ if (use_os_log) os_log(OS_LOG_DEFAULT, "%{public}s", sanbuf); #endif if (!use_os_log) fprintf(stderr, "%s\n", sanbuf); #undef bufprint } static PyObject* log_error_string(PyObject *self UNUSED, PyObject *args) { const char *msg; if (!PyArg_ParseTuple(args, "s", &msg)) return NULL; log_error("%s", msg); Py_RETURN_NONE; } static PyObject* set_use_os_log(PyObject *self UNUSED, PyObject *args) { use_os_log = PyObject_IsTrue(args) ? true : false; Py_RETURN_NONE; } static PyMethodDef module_methods[] = { METHODB(log_error_string, METH_VARARGS), METHODB(set_use_os_log, METH_O), {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_logging(PyObject *module) { if (PyModule_AddFunctions(module, module_methods) != 0) return false; #ifdef __APPLE__ // This env var can be either 1 or 2 if (getenv("KITTY_LAUNCHED_BY_LAUNCH_SERVICES") != NULL) use_os_log = true; #endif return true; } kitty-0.41.1/kitty/loop-utils.c0000664000175000017510000002250514773370543015732 0ustar nileshnilesh/* * loop-utils.c * Copyright (C) 2019 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "loop-utils.h" #include "safe-wrappers.h" #ifndef HAS_SIGNAL_FD static int signal_write_fd = -1; static void handle_signal(int sig_num UNUSED, siginfo_t *si, void *ucontext UNUSED) { int save_err = errno; char *buf = (char*)si; size_t sz = sizeof(siginfo_t); while (signal_write_fd != -1 && sz) { // as long as sz is less than PIPE_BUF write will either write all or return -1 with EAGAIN // so we are guaranteed atomic writes ssize_t ret = write(signal_write_fd, buf, sz); if (ret <= 0) { if (errno == EINTR) continue; break; } sz -= ret; buf += ret; } errno = save_err; } #endif static bool init_signal_handlers(LoopData *ld) { ld->signal_read_fd = -1; sigemptyset(&ld->signals); for (size_t i = 0; i < ld->num_handled_signals; i++) sigaddset(&ld->signals, ld->handled_signals[i]); #ifdef HAS_SIGNAL_FD if (ld->num_handled_signals) { if (sigprocmask(SIG_BLOCK, &ld->signals, NULL) == -1) return false; ld->signal_read_fd = signalfd(-1, &ld->signals, SFD_NONBLOCK | SFD_CLOEXEC); if (ld->signal_read_fd == -1) return false; } #else ld->signal_fds[0] = -1; ld->signal_fds[1] = -1; if (ld->num_handled_signals) { if (!self_pipe(ld->signal_fds, true)) return false; signal_write_fd = ld->signal_fds[1]; ld->signal_read_fd = ld->signal_fds[0]; struct sigaction act = {.sa_sigaction=handle_signal, .sa_flags=SA_SIGINFO | SA_RESTART, .sa_mask = ld->signals}; for (size_t i = 0; i < ld->num_handled_signals; i++) { if (sigaction(ld->handled_signals[i], &act, NULL) != 0) return false; } } #endif return true; } bool init_loop_data(LoopData *ld, ...) { ld->num_handled_signals = 0; va_list valist; va_start(valist, ld); while (true) { int sig = va_arg(valist, int); if (!sig) break; ld->handled_signals[ld->num_handled_signals++] = sig; } va_end(valist); #ifdef HAS_EVENT_FD ld->wakeup_read_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); if (ld->wakeup_read_fd < 0) return false; #else if (!self_pipe(ld->wakeup_fds, true)) return false; ld->wakeup_read_fd = ld->wakeup_fds[0]; #endif return init_signal_handlers(ld); } #define CLOSE(which, idx) if (ld->which[idx] > -1) { safe_close(ld->which[idx], __FILE__, __LINE__); ld->which[idx] = -1; } static void remove_signal_handlers(LoopData *ld) { #ifndef HAS_SIGNAL_FD signal_write_fd = -1; CLOSE(signal_fds, 0); CLOSE(signal_fds, 1); #endif if (ld->signal_read_fd > -1) { #ifdef HAS_SIGNAL_FD safe_close(ld->signal_read_fd, __FILE__, __LINE__); sigprocmask(SIG_UNBLOCK, &ld->signals, NULL); #endif for (size_t i = 0; i < ld->num_handled_signals; i++) signal(ld->num_handled_signals, SIG_DFL); } ld->signal_read_fd = -1; ld->num_handled_signals = 0; } void free_loop_data(LoopData *ld) { #ifndef HAS_EVENT_FD CLOSE(wakeup_fds, 0); CLOSE(wakeup_fds, 1); #endif #undef CLOSE #ifdef HAS_EVENT_FD safe_close(ld->wakeup_read_fd, __FILE__, __LINE__); #endif ld->wakeup_read_fd = -1; remove_signal_handlers(ld); } void wakeup_loop(LoopData *ld, bool in_signal_handler, const char *loop_name) { while(true) { #ifdef HAS_EVENT_FD static const int64_t value = 1; ssize_t ret = write(ld->wakeup_read_fd, &value, sizeof value); #else ssize_t ret = write(ld->wakeup_fds[1], "w", 1); #endif if (ret < 0) { if (errno == EINTR) continue; if (!in_signal_handler) log_error("Failed to write to %s wakeup fd with error: %s", loop_name, strerror(errno)); } break; } } void read_signals(int fd, handle_signal_func callback, void *data) { #ifdef HAS_SIGNAL_FD static struct signalfd_siginfo fdsi[32]; siginfo_t si; while (true) { ssize_t s = read(fd, &fdsi, sizeof(fdsi)); if (s < 0) { if (errno == EINTR) continue; if (errno == EAGAIN) break; log_error("Call to read() from read_signals() failed with error: %s", strerror(errno)); break; } if (s == 0) break; size_t num_signals = s / sizeof(struct signalfd_siginfo); if (num_signals == 0 || num_signals * sizeof(struct signalfd_siginfo) != (size_t)s) { log_error("Incomplete signal read from signalfd"); break; } for (size_t i = 0; i < num_signals; i++) { si.si_signo = fdsi[i].ssi_signo; si.si_code = fdsi[i].ssi_code; si.si_pid = fdsi[i].ssi_pid; si.si_uid = fdsi[i].ssi_uid; si.si_addr = (void*)(uintptr_t)fdsi[i].ssi_addr; si.si_status = fdsi[i].ssi_status; si.si_value.sival_int = fdsi[i].ssi_int; if (!callback(&si, data)) break; } } #else static char buf[sizeof(siginfo_t) * 8]; static size_t buf_pos = 0; while(true) { ssize_t len = read(fd, buf + buf_pos, sizeof(buf) - buf_pos); if (len < 0) { if (errno == EINTR) continue; if (errno != EWOULDBLOCK && errno != EAGAIN) log_error("Call to read() from read_signals() failed with error: %s", strerror(errno)); break; } buf_pos += len; bool keep_going = true; while (keep_going && buf_pos >= sizeof(siginfo_t)) { keep_going = callback((siginfo_t*)buf, data); buf_pos -= sizeof(siginfo_t); memmove(buf, buf + sizeof(siginfo_t), buf_pos); } if (len == 0) break; } #endif } static LoopData python_loop_data = {0}; static PyObject* init_signal_handlers_py(PyObject *self UNUSED, PyObject *args) { if (python_loop_data.num_handled_signals) { PyErr_SetString(PyExc_RuntimeError, "signal handlers already initialized"); return NULL; } #ifndef HAS_SIGNAL_FD if (signal_write_fd > -1) { PyErr_SetString(PyExc_RuntimeError, "signal handlers already initialized"); return NULL; } #endif for (Py_ssize_t i = 0; i < MIN(PyTuple_GET_SIZE(args), (Py_ssize_t)arraysz(python_loop_data.handled_signals)); i++) { python_loop_data.handled_signals[python_loop_data.num_handled_signals++] = PyLong_AsLong(PyTuple_GET_ITEM(args, i)); } if (!init_signal_handlers(&python_loop_data)) return PyErr_SetFromErrno(PyExc_OSError); #ifdef HAS_SIGNAL_FD return Py_BuildValue("ii", python_loop_data.signal_read_fd, -1); #else return Py_BuildValue("ii", python_loop_data.signal_fds[0], python_loop_data.signal_fds[1]); #endif } static PyTypeObject SigInfoType; static PyStructSequence_Field sig_info_fields[] = { {"si_signo", "Signal number"}, {"si_code", "Signal code"}, {"si_pid", "Sending Process id"}, {"si_uid", "Real user id of sending process"}, {"si_addr", "Address of faulting instruction as int"}, {"si_status", "Exit value or signal"}, {"sival_int", "Signal value as int"}, {"sival_ptr", "Signal value as pointer int"}, {NULL, NULL} }; static PyStructSequence_Desc sig_info_desc = {"SigInfo", NULL, sig_info_fields, 6}; static bool handle_signal_callback_py(const siginfo_t* siginfo, void *data) { if (PyErr_Occurred()) return false; PyObject *callback = data; PyObject *ans = PyStructSequence_New(&SigInfoType); int pos = 0; #define S(x) { PyObject *t = x; if (t) { PyStructSequence_SET_ITEM(ans, pos, x); } else { Py_CLEAR(ans); return false; } pos++; } if (ans) { S(PyLong_FromLong((long)siginfo->si_signo)); S(PyLong_FromLong((long)siginfo->si_code)); S(PyLong_FromLong((long)siginfo->si_pid)); S(PyLong_FromLong((long)siginfo->si_uid)); S(PyLong_FromVoidPtr(siginfo->si_addr)); S(PyLong_FromLong((long)siginfo->si_status)); S(PyLong_FromLong((long)siginfo->si_value.sival_int)); S(PyLong_FromVoidPtr(siginfo->si_value.sival_ptr)); PyObject *ret = PyObject_CallFunctionObjArgs(callback, ans, NULL); Py_CLEAR(ans); Py_CLEAR(ret); } return (PyErr_Occurred()) ? false : true; #undef S } static PyObject* read_signals_py(PyObject *self UNUSED, PyObject *args) { int fd; PyObject *callback; if (!PyArg_ParseTuple(args, "iO", &fd, &callback)) return NULL; if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "callback must be callable"); return NULL; } read_signals(fd, handle_signal_callback_py, callback); if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } static PyObject* remove_signal_handlers_py(PyObject *self UNUSED, PyObject *args UNUSED) { if (python_loop_data.num_handled_signals) { remove_signal_handlers(&python_loop_data); } Py_RETURN_NONE; } static PyMethodDef methods[] = { {"install_signal_handlers", init_signal_handlers_py, METH_VARARGS, "Initialize an fd to read signals from" }, {"read_signals", read_signals_py, METH_VARARGS, "Read pending signals from the specified fd" }, {"remove_signal_handlers", remove_signal_handlers_py, METH_NOARGS, "Remove signal handlers" }, { NULL, NULL, 0, NULL }, }; bool init_loop_utils(PyObject *module) { if (PyStructSequence_InitType2(&SigInfoType, &sig_info_desc) != 0) return false; Py_INCREF((PyObject *) &SigInfoType); PyModule_AddObject(module, "SigInfo", (PyObject *) &SigInfoType); return PyModule_AddFunctions(module, methods) == 0; } kitty-0.41.1/kitty/loop-utils.h0000664000175000017510000000403014773370543015730 0ustar nileshnilesh/* * Copyright (C) 2019 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" #include #include #include #ifdef __has_include #if __has_include() #define HAS_SIGNAL_FD #include #endif #if __has_include() #define HAS_EVENT_FD #include #endif #else #define HAS_SIGNAL_FD #include #define HAS_EVENT_FD #include #endif typedef struct { #ifndef HAS_EVENT_FD int wakeup_fds[2]; #endif #ifndef HAS_SIGNAL_FD int signal_fds[2]; #endif sigset_t signals; int wakeup_read_fd; int signal_read_fd; int handled_signals[16]; size_t num_handled_signals; } LoopData; typedef bool(*handle_signal_func)(const siginfo_t* siginfo, void *data); bool init_loop_data(LoopData *ld, ...); void free_loop_data(LoopData *ld); void wakeup_loop(LoopData *ld, bool in_signal_handler, const char*); void read_signals(int fd, handle_signal_func callback, void *data); static inline bool self_pipe(int fds[2], bool nonblock) { #ifdef __APPLE__ int flags; flags = pipe(fds); if (flags != 0) return false; for (int i = 0; i < 2; i++) { flags = fcntl(fds[i], F_GETFD); if (flags == -1) { return false; } if (fcntl(fds[i], F_SETFD, flags | FD_CLOEXEC) == -1) { return false; } if (nonblock) { flags = fcntl(fds[i], F_GETFL); if (flags == -1) { return false; } if (fcntl(fds[i], F_SETFL, flags | O_NONBLOCK) == -1) { return false; } } } return true; #else int flags = O_CLOEXEC; if (nonblock) flags |= O_NONBLOCK; return pipe2(fds, flags) == 0; #endif } static inline void drain_fd(int fd) { static uint8_t drain_buf[1024]; while(true) { ssize_t len = read(fd, drain_buf, sizeof(drain_buf)); if (len < 0) { if (errno == EINTR) continue; break; } if (len > 0) continue; break; } } kitty-0.41.1/kitty/macos_process_info.c0000664000175000017510000001773014773370543017502 0ustar nileshnilesh/* * macos_process_info.c * Copyright (C) 2018 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "data-types.h" #include typedef void* rusage_info_t; // needed for libproc.h #include static PyObject* cwd_of_process(PyObject *self UNUSED, PyObject *pid_) { if (!PyLong_Check(pid_)) { PyErr_SetString(PyExc_TypeError, "pid must be an int"); return NULL; } long pid = PyLong_AsLong(pid_); if (pid < 0) { PyErr_SetString(PyExc_TypeError, "pid cannot be negative"); return NULL; } struct proc_vnodepathinfo vpi; int ret = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi)); if (ret < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } return PyUnicode_FromString(vpi.pvi_cdir.vip_path); } // Read the maximum argument size for processes static int get_argmax(void) { int argmax; int mib[] = { CTL_KERN, KERN_ARGMAX }; size_t size = sizeof(argmax); if (sysctl(mib, 2, &argmax, &size, NULL, 0) == 0) return argmax; return 0; } static PyObject* get_all_processes(PyObject *self UNUSED, PyObject *args UNUSED) { pid_t num = proc_listallpids(NULL, 0); if (num <= 0) return PyTuple_New(0); size_t sz = sizeof(pid_t) * num * 2; pid_t *buf = malloc(sz); if (!buf) return PyErr_NoMemory(); num = proc_listallpids(buf, sz); if (num <= 0) { free(buf); return PyTuple_New(0); } PyObject *ans = PyTuple_New(num); if (!ans) { free(buf); return NULL; } for (pid_t i = 0; i < num; i++) { long long pid = buf[i]; PyObject *t = PyLong_FromLongLong(pid); if (!t) { free(buf); Py_CLEAR(ans); return NULL; } PyTuple_SET_ITEM(ans, i, t); } return ans; } static PyObject* cmdline_of_process(PyObject *self UNUSED, PyObject *pid_) { // Taken from psutil, with thanks (BSD 3-clause license) int mib[3]; int nargs; size_t len; char *procargs = NULL; char *arg_ptr; char *arg_end; char *curr_arg; size_t argmax; PyObject *py_arg = NULL; PyObject *py_retlist = NULL; if (!PyLong_Check(pid_)) { PyErr_SetString(PyExc_TypeError, "pid must be an int"); goto error; } long pid = PyLong_AsLong(pid_); if (pid < 0) { PyErr_SetString(PyExc_TypeError, "pid cannot be negative"); goto error; } // special case for PID 0 (kernel_task) where cmdline cannot be fetched if (pid == 0) return Py_BuildValue("[]"); // read argmax and allocate memory for argument space. argmax = get_argmax(); if (!argmax) { PyErr_SetFromErrno(PyExc_OSError); goto error; } procargs = (char *)malloc(argmax); if (NULL == procargs) { PyErr_SetFromErrno(PyExc_OSError); goto error; } // read argument space mib[0] = CTL_KERN; mib[1] = KERN_PROCARGS2; mib[2] = (pid_t)pid; if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) { // In case of zombie process or non-existent process we'll get EINVAL. if (errno == EINVAL) PyErr_Format(PyExc_ValueError, "process with pid %ld either does not exist or is a zombie or you dont have permission", pid); else PyErr_SetFromErrno(PyExc_OSError); goto error; } arg_end = &procargs[argmax]; // copy the number of arguments to nargs memcpy(&nargs, procargs, sizeof(nargs)); arg_ptr = procargs + sizeof(nargs); len = strlen(arg_ptr); arg_ptr += len + 1; if (arg_ptr == arg_end) { free(procargs); return Py_BuildValue("[]"); } // skip ahead to the first argument for (; arg_ptr < arg_end; arg_ptr++) { if (*arg_ptr != '\0') break; } // iterate through arguments curr_arg = arg_ptr; py_retlist = Py_BuildValue("[]"); if (!py_retlist) goto error; while (arg_ptr < arg_end && nargs > 0) { if (*arg_ptr++ == '\0') { py_arg = PyUnicode_DecodeFSDefault(curr_arg); if (! py_arg) goto error; if (PyList_Append(py_retlist, py_arg)) goto error; Py_DECREF(py_arg); // iterate to next arg and decrement # of args curr_arg = arg_ptr; nargs--; } } free(procargs); return py_retlist; error: Py_XDECREF(py_arg); Py_XDECREF(py_retlist); if (procargs != NULL) free(procargs); return NULL; } PyObject * environ_of_process(PyObject *self UNUSED, PyObject *pid_) { // Taken from psutil, with thanks (BSD 3-clause license) int mib[3]; int nargs; char *procargs = NULL; char *procenv = NULL; char *arg_ptr; char *arg_end; char *env_start; size_t argmax; PyObject *py_ret = NULL; if (!PyLong_Check(pid_)) { PyErr_SetString(PyExc_TypeError, "pid must be an int"); goto error; } long pid = PyLong_AsLong(pid_); if (pid < 0) { PyErr_SetString(PyExc_TypeError, "pid cannot be negative"); goto error; } // special case for PID 0 (kernel_task) where cmdline cannot be fetched if (pid == 0) goto empty; // read argmax and allocate memory for argument space. argmax = get_argmax(); if (! argmax) { PyErr_SetFromErrno(PyExc_OSError); goto error; } procargs = (char *)malloc(argmax); if (NULL == procargs) { PyErr_SetFromErrno(PyExc_OSError); goto error; } // read argument space mib[0] = CTL_KERN; mib[1] = KERN_PROCARGS2; mib[2] = (pid_t)pid; if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) { // In case of zombie process or a non-existent process we'll get EINVAL // to NSP and _psosx.py will translate it to ZP. if (errno == EINVAL) PyErr_Format(PyExc_ValueError, "process with pid %ld either does not exist or is a zombie or you dont have permission", pid); else PyErr_SetFromErrno(PyExc_OSError); goto error; } arg_end = &procargs[argmax]; // copy the number of arguments to nargs memcpy(&nargs, procargs, sizeof(nargs)); // skip executable path arg_ptr = procargs + sizeof(nargs); arg_ptr = memchr(arg_ptr, '\0', arg_end - arg_ptr); if (arg_ptr == NULL || arg_ptr == arg_end) goto empty; // skip ahead to the first argument for (; arg_ptr < arg_end; arg_ptr++) { if (*arg_ptr != '\0') break; } // iterate through arguments while (arg_ptr < arg_end && nargs > 0) { if (*arg_ptr++ == '\0') nargs--; } // build an environment variable block env_start = arg_ptr; procenv = calloc(1, arg_end - arg_ptr); if (procenv == NULL) { PyErr_NoMemory(); goto error; } while (*arg_ptr != '\0' && arg_ptr < arg_end) { char *s = memchr(arg_ptr + 1, '\0', arg_end - arg_ptr); if (s == NULL) break; memcpy(procenv + (arg_ptr - env_start), arg_ptr, s - arg_ptr); arg_ptr = s + 1; } py_ret = PyUnicode_DecodeFSDefaultAndSize( procenv, arg_ptr - env_start + 1); if (!py_ret) { // XXX: don't want to free() this as per: // https://github.com/giampaolo/psutil/issues/926 // It sucks but not sure what else to do. procargs = NULL; goto error; } free(procargs); free(procenv); return py_ret; empty: if (procargs != NULL) free(procargs); return Py_BuildValue("s", ""); error: Py_XDECREF(py_ret); free(procargs); free(procenv); return NULL; } static PyMethodDef module_methods[] = { {"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""}, {"cmdline_of_process", (PyCFunction)cmdline_of_process, METH_O, ""}, {"environ_of_process", (PyCFunction)environ_of_process, METH_O, ""}, {"get_all_processes", (PyCFunction)get_all_processes, METH_NOARGS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_macos_process_info(PyObject *module) { if (PyModule_AddFunctions(module, module_methods) != 0) return false; return true; } kitty-0.41.1/kitty/main.py0000664000175000017510000005370514773370543014763 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal import locale import os import shutil import sys from collections.abc import Generator, Sequence from contextlib import contextmanager, suppress from .borders import load_borders_program from .boss import Boss from .child import set_default_env, set_LANG_in_default_env from .cli import create_opts, parse_args from .cli_stub import CLIOptions from .colors import theme_colors from .conf.utils import BadLine from .config import cached_values_for from .constants import ( appname, beam_cursor_data_file, clear_handled_signals, config_dir, glfw_path, is_macos, is_wayland, kitten_exe, kitty_exe, logo_png_file, running_in_kitty, supports_window_occlusion, website_url, ) from .fast_data_types import ( GLFW_MOD_ALT, GLFW_MOD_SHIFT, SingleKey, create_os_window, free_font_data, glfw_init, glfw_terminate, load_png_data, mask_kitty_signals_process_wide, run_at_exit_cleanup_functions, set_custom_cursor, set_default_window_icon, set_options, ) from .fonts.render import dump_font_debug, set_font_family from .options.types import Options from .options.utils import DELETE_ENV_VAR from .os_window_size import edge_spacing, initial_window_size_func from .session import create_sessions, get_os_window_sizing_data from .shaders import CompileError, load_shader_programs from .types import LayerShellConfig from .utils import ( cleanup_ssh_control_masters, detach, expandvars, get_custom_window_icon, log_error, parse_os_window_state, safe_mtime, shlex_split, startup_notification_handler, ) def set_custom_ibeam_cursor() -> None: with open(beam_cursor_data_file, 'rb') as f: data = f.read() rgba_data, width, height = load_png_data(data) c2x = os.path.splitext(beam_cursor_data_file) with open(f'{c2x[0]}@2x{c2x[1]}', 'rb') as f: data = f.read() rgba_data2, width2, height2 = load_png_data(data) images = (rgba_data, width, height), (rgba_data2, width2, height2) try: set_custom_cursor("beam", images, 4, 8) except Exception as e: log_error(f'Failed to set custom beam cursor with error: {e}') def load_all_shaders(semi_transparent: bool = False) -> None: try: load_shader_programs(semi_transparent) load_borders_program() except CompileError as err: raise SystemExit(err) def init_glfw_module(glfw_module: str = 'wayland', debug_keyboard: bool = False, debug_rendering: bool = False, wayland_enable_ime: bool = True) -> None: ok, swo = glfw_init(glfw_path(glfw_module), edge_spacing, debug_keyboard, debug_rendering, wayland_enable_ime) if not ok: raise SystemExit('GLFW initialization failed') supports_window_occlusion(swo) def init_glfw(opts: Options, debug_keyboard: bool = False, debug_rendering: bool = False) -> str: glfw_module = 'cocoa' if is_macos else ('wayland' if is_wayland(opts) else 'x11') init_glfw_module(glfw_module, debug_keyboard, debug_rendering, wayland_enable_ime=opts.wayland_enable_ime) return glfw_module def get_macos_shortcut_for( func_map: dict[tuple[str, ...], list[SingleKey]], defn: str = 'new_os_window', lookup_name: str = '' ) -> SingleKey | None: # for maximum robustness we should use opts.alias_map to resolve # aliases however this requires parsing everything on startup which could # be potentially slow. Lets just hope the user doesn't alias these # functions. ans = None candidates = [] qkey = tuple(defn.split()) candidates = func_map[qkey] if candidates: from .fast_data_types import cocoa_set_global_shortcut alt_mods = GLFW_MOD_ALT, GLFW_MOD_ALT | GLFW_MOD_SHIFT # Reverse list so that later defined keyboard shortcuts take priority over earlier defined ones for candidate in reversed(candidates): if candidate.mods in alt_mods: # Option based shortcuts dont work in the global menubar, # presumably because Apple reserves them for IME, see # https://github.com/kovidgoyal/kitty/issues/3515 continue if cocoa_set_global_shortcut(lookup_name or qkey[0], candidate[0], candidate[2]): ans = candidate break return ans def set_macos_app_custom_icon() -> None: custom_icon_mtime, custom_icon_path = get_custom_window_icon() if custom_icon_mtime is not None and custom_icon_path is not None: from .fast_data_types import cocoa_set_app_icon, cocoa_set_dock_icon krd = getattr(sys, 'kitty_run_data') bundle_path = os.path.dirname(os.path.dirname(krd.get('bundle_exe_dir'))) icon_sentinel = os.path.join(bundle_path, 'Icon\r') sentinel_mtime = safe_mtime(icon_sentinel) if sentinel_mtime is None or sentinel_mtime < custom_icon_mtime: try: cocoa_set_app_icon(custom_icon_path, bundle_path) except (FileNotFoundError, OSError) as e: log_error(str(e)) log_error('Failed to set custom app icon, ignoring') # macOS Dock does not reload icons until it is restarted, so we set # the application icon here. This will revert when kitty quits, but # can't be helped since there appears to be no way to get the dock # to reload short of killing it. cocoa_set_dock_icon(custom_icon_path) def get_icon128_path(base_path: str) -> str: # max icon size on X11 64bits is 128x128 path, ext = os.path.splitext(base_path) return f'{path}-128{ext}' def set_window_icon() -> None: custom_icon_path = get_custom_window_icon()[1] is_x11 = not is_macos and not is_wayland() try: if custom_icon_path is not None: custom_icon128_path = get_icon128_path(custom_icon_path) if is_x11 and safe_mtime(custom_icon128_path) is not None: set_default_window_icon(custom_icon128_path) else: set_default_window_icon(custom_icon_path) else: if is_x11: set_default_window_icon(get_icon128_path(logo_png_file)) except ValueError as err: log_error(err) def set_cocoa_global_shortcuts(opts: Options) -> dict[str, SingleKey]: global_shortcuts: dict[str, SingleKey] = {} if is_macos: from collections import defaultdict func_map = defaultdict(list) for single_key, v in opts.keyboard_modes[''].keymap.items(): kd = v[-1] # the last definition is the active one if kd.is_suitable_for_global_shortcut: parts = tuple(kd.definition.split()) func_map[parts].append(single_key) for ac in ('new_os_window', 'close_os_window', 'close_tab', 'edit_config_file', 'previous_tab', 'next_tab', 'new_tab', 'new_window', 'close_window', 'toggle_macos_secure_keyboard_entry', 'toggle_fullscreen', 'hide_macos_app', 'hide_macos_other_apps', 'minimize_macos_window', 'quit'): val = get_macos_shortcut_for(func_map, ac) if val is not None: global_shortcuts[ac] = val val = get_macos_shortcut_for(func_map, 'clear_terminal reset active', lookup_name='reset_terminal') if val is not None: global_shortcuts['reset_terminal'] = val val = get_macos_shortcut_for(func_map, 'clear_terminal to_cursor active', lookup_name='clear_terminal_and_scrollback') if val is not None: global_shortcuts['clear_terminal_and_scrollback'] = val val = get_macos_shortcut_for(func_map, 'clear_terminal scrollback active', lookup_name='clear_scrollback') if val is not None: global_shortcuts['clear_scrollback'] = val val = get_macos_shortcut_for(func_map, 'clear_terminal to_cursor_scroll active', lookup_name='clear_screen') if val is not None: global_shortcuts['clear_screen'] = val val = get_macos_shortcut_for(func_map, 'load_config_file', lookup_name='reload_config') if val is not None: global_shortcuts['reload_config'] = val val = get_macos_shortcut_for(func_map, f'open_url {website_url()}', lookup_name='open_kitty_website') if val is not None: global_shortcuts['open_kitty_website'] = val return global_shortcuts def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = (), talk_fd: int = -1) -> None: if is_macos: global_shortcuts = set_cocoa_global_shortcuts(opts) if opts.macos_custom_beam_cursor: set_custom_ibeam_cursor() set_macos_app_custom_icon() else: global_shortcuts = {} set_window_icon() with cached_values_for(run_app.cached_values_name) as cached_values: startup_sessions = tuple(create_sessions(opts, args, default_session=opts.startup_session)) wincls = (startup_sessions[0].os_window_class if startup_sessions else '') or args.cls or appname winname = (startup_sessions[0].os_window_name if startup_sessions else '') or args.name or wincls or appname window_state = (args.start_as if args.start_as and args.start_as != 'normal' else None) or ( getattr(startup_sessions[0], 'os_window_state', None) if startup_sessions else None ) wstate = parse_os_window_state(window_state) if window_state is not None else None with startup_notification_handler(extra_callback=run_app.first_window_callback) as pre_show_callback: window_id = create_os_window( run_app.initial_window_size_func(get_os_window_sizing_data(opts, startup_sessions[0] if startup_sessions else None), cached_values), pre_show_callback, args.title or appname, winname, wincls, wstate, load_all_shaders, disallow_override_title=bool(args.title), layer_shell_config=run_app.layer_shell_config) boss = Boss(opts, args, cached_values, global_shortcuts, talk_fd) boss.start(window_id, startup_sessions) if args.debug_font_fallback: dump_font_debug() if bad_lines or boss.misc_config_errors: boss.show_bad_config_lines(bad_lines, boss.misc_config_errors) boss.misc_config_errors = [] try: boss.child_monitor.main_loop() finally: boss.destroy() class AppRunner: def __init__(self) -> None: self.cached_values_name = 'main' self.first_window_callback = lambda window_handle: None self.layer_shell_config: LayerShellConfig | None = None self.initial_window_size_func = initial_window_size_func def __call__(self, opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = (), talk_fd: int = -1) -> None: set_options(opts, is_wayland(), args.debug_rendering, args.debug_font_fallback) if theme_colors.refresh(): theme_colors.patch_opts(opts, args.debug_rendering) try: set_font_family(opts, add_builtin_nerd_font=True) _run_app(opts, args, bad_lines, talk_fd) finally: set_options(None) free_font_data() # must free font data before glfw/freetype/fontconfig/opengl etc are finalized if is_macos: from kitty.fast_data_types import ( cocoa_set_notification_activated_callback, ) cocoa_set_notification_activated_callback(None) run_app = AppRunner() def ensure_macos_locale() -> None: # Ensure the LANG env var is set. See # https://github.com/kovidgoyal/kitty/issues/90 from .fast_data_types import cocoa_get_lang, locale_is_valid if 'LANG' not in os.environ: lang_code, country_code, identifier = cocoa_get_lang() lang = 'en_US' if identifier and locale_is_valid(identifier): lang = identifier elif lang_code and country_code and locale_is_valid(f'{lang_code}_{country_code}'): lang = f'{lang_code}_{country_code}' elif lang_code: if lang_code != 'en': with suppress(OSError): found = sorted(x for x in os.listdir('/usr/share/locale') if x.startswith(f'{lang_code}_')) if found: lang = found[0].partition('.')[0] os.environ['LANG'] = f'{lang}.UTF-8' set_LANG_in_default_env(os.environ['LANG']) @contextmanager def setup_profiling() -> Generator[None, None, None]: try: from .fast_data_types import start_profiler, stop_profiler do_profile = True except ImportError: do_profile = False if do_profile: start_profiler('/tmp/kitty-profile.log') yield if do_profile: import subprocess stop_profiler() exe = kitty_exe() cg = '/tmp/kitty-profile.callgrind' print('Post processing profile data for', exe, '...') with open(cg, 'wb') as f: subprocess.call(['pprof', '--callgrind', exe, '/tmp/kitty-profile.log'], stdout=f) try: subprocess.Popen(['kcachegrind', cg], preexec_fn=clear_handled_signals) except FileNotFoundError: subprocess.call(['pprof', '--text', exe, '/tmp/kitty-profile.log']) print('To view the graphical call data, use: kcachegrind', cg) def macos_cmdline(argv_args: list[str]) -> list[str]: try: with open(os.path.join(config_dir, 'macos-launch-services-cmdline')) as f: raw = f.read() except FileNotFoundError: return argv_args raw = raw.strip() ans = list(shlex_split(raw)) if ans and ans[0] == 'kitty': del ans[0] if '-1' in ans or '--single-instance' in ans: if 'KITTY_SI_DATA' in os.environ: # C code will already have setup single instance log_error( '--single-instance supplied in both command line arguments and macos-launch-services-cmdline,' ' ignoring any --instance-group in macos-launch-services-cmdline') else: # Re-exec with new argv so that the C code that handles single instance # can pick up the modified argv os.environ['KITTY_LAUNCHED_BY_LAUNCH_SERVICES'] = '2' # so that use_os_log is set in the re-execed process os.execl(kitty_exe(), 'kitty', *(ans + argv_args)) return ans + argv_args def expand_listen_on(listen_on: str, from_config_file: bool) -> str: if from_config_file and listen_on == 'none': return '' listen_on = expandvars(listen_on) if '{kitty_pid}' not in listen_on and from_config_file and listen_on.startswith('unix:'): listen_on += '-{kitty_pid}' listen_on = listen_on.replace('{kitty_pid}', str(os.getpid())) if listen_on.startswith('unix:'): path = listen_on[len('unix:'):] if not path.startswith('@'): if path.startswith('~'): listen_on = f'unix:{os.path.expanduser(path)}' elif not os.path.isabs(path): import tempfile listen_on = f'unix:{os.path.join(tempfile.gettempdir(), path)}' elif listen_on.startswith('tcp:') or listen_on.startswith('tcp6:'): if from_config_file: # use a random port listen_on = ':'.join(listen_on.split(':', 2)[:2]) + ':0' return listen_on def safe_samefile(a: str, b: str) -> bool: with suppress(OSError): return os.path.samefile(a, b) return os.path.abspath(os.path.realpath(a)) == os.path.abspath(os.path.realpath(b)) def prepend_if_not_present(path: str, paths_serialized: str) -> str: # prepend a path only if path/kitty is not already present, even as a symlink pq = os.path.join(path, 'kitty') for candidate in paths_serialized.split(os.pathsep): q = os.path.join(candidate, 'kitty') if safe_samefile(q, pq): return paths_serialized return path + os.pathsep + paths_serialized def ensure_kitty_in_path() -> None: # Ensure the correct kitty is in PATH krd = getattr(sys, 'kitty_run_data') rpath = krd.get('bundle_exe_dir') if not rpath: return if rpath: modify_path = is_macos or getattr(sys, 'frozen', False) or krd.get('from_source') existing = shutil.which('kitty') if modify_path or not existing: env_path = os.environ.get('PATH', '') correct_kitty = os.path.join(rpath, 'kitty') if not existing or not safe_samefile(existing, correct_kitty): os.environ['PATH'] = prepend_if_not_present(rpath, env_path) def ensure_kitten_in_path() -> None: correct_kitten = kitten_exe() existing = shutil.which('kitten') if existing and safe_samefile(existing, correct_kitten): return env_path = os.environ.get('PATH', '') os.environ['PATH'] = prepend_if_not_present(os.path.dirname(correct_kitten), env_path) def setup_manpath(env: dict[str, str]) -> None: # Ensure kitty manpages are available in frozen builds if not getattr(sys, 'frozen', False): return from .constants import local_docs mp = os.environ.get('MANPATH', env.get('MANPATH', '')) d = os.path.dirname kitty_man = os.path.join(d(d(d(local_docs()))), 'man') if not mp: env['MANPATH'] = f'{kitty_man}:' elif mp.startswith(':'): env['MANPATH'] = f':{kitty_man}:{mp}' else: env['MANPATH'] = f'{kitty_man}:{mp}' def setup_environment(opts: Options, cli_opts: CLIOptions) -> None: from_config_file = False if not cli_opts.listen_on: cli_opts.listen_on = opts.listen_on from_config_file = True if cli_opts.listen_on: cli_opts.listen_on = expand_listen_on(cli_opts.listen_on, from_config_file) env = opts.env.copy() ensure_kitty_in_path() ensure_kitten_in_path() kitty_path = shutil.which('kitty') if kitty_path: child_path = env.get('PATH') # if child_path is None it will be inherited from os.environ, # the other values mean the user doesn't want a PATH if child_path not in ('', DELETE_ENV_VAR) and child_path is not None: env['PATH'] = prepend_if_not_present(os.path.dirname(kitty_path), env['PATH']) setup_manpath(env) set_default_env(env) def set_locale() -> None: if is_macos: ensure_macos_locale() try: locale.setlocale(locale.LC_ALL, '') except Exception: log_error('Failed to set locale with LANG:', os.environ.get('LANG')) old_lang = os.environ.pop('LANG', None) if old_lang is not None: try: locale.setlocale(locale.LC_ALL, '') except Exception: log_error('Failed to set locale with no LANG') os.environ['LANG'] = old_lang set_LANG_in_default_env(old_lang) def _main() -> None: running_in_kitty(True) args = sys.argv[1:] if is_macos and os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', None) == '1': os.chdir(os.path.expanduser('~')) args = macos_cmdline(args) try: cwd_ok = os.path.isdir(os.getcwd()) except Exception: cwd_ok = False if not cwd_ok: os.chdir(os.path.expanduser('~')) if getattr(sys, 'cmdline_args_for_open', False): usage: str | None = 'file_or_url ...' appname: str | None = 'kitty +open' msg: str | None = ( 'Run kitty and open the specified files or URLs in it, using launch-actions.conf. For details' ' see https://sw.kovidgoyal.net/kitty/open_actions/#scripting-the-opening-of-files-with-kitty-on-macos' '\n\nAll the normal kitty options can be used.') else: usage = msg = appname = None cli_opts, rest = parse_args(args=args, result_class=CLIOptions, usage=usage, message=msg, appname=appname) if getattr(sys, 'cmdline_args_for_open', False): setattr(sys, 'cmdline_args_for_open', rest) cli_opts.args = [] else: cli_opts.args = rest if cli_opts.detach: if cli_opts.session == '-': from .session import PreReadSession cli_opts.session = PreReadSession(sys.stdin.read(), os.environ) detach() if cli_opts.replay_commands: from kitty.client import main as client_main client_main(cli_opts.replay_commands) return talk_fd = -1 if cli_opts.single_instance: si_data = os.environ.pop('KITTY_SI_DATA', '') if si_data: talk_fd = int(si_data) bad_lines: list[BadLine] = [] opts = create_opts(cli_opts, accumulate_bad_lines=bad_lines) setup_environment(opts, cli_opts) # set_locale on macOS uses cocoa APIs when LANG is not set, so we have to # call it after the fork try: set_locale() except Exception: log_error('Failed to set locale, ignoring') with suppress(AttributeError): # python compiled without threading sys.setswitchinterval(1000.0) # we have only a single python thread if cli_opts.watcher: from .window import global_watchers global_watchers.set_extra(cli_opts.watcher) log_error('The --watcher command line option has been deprecated in favor of using the watcher option in kitty.conf') # mask the signals now as on some platforms the display backend starts # threads. These threads must not handle the masked signals, to ensure # kitty can handle them. See https://github.com/kovidgoyal/kitty/issues/4636 mask_kitty_signals_process_wide() init_glfw(opts, cli_opts.debug_keyboard, cli_opts.debug_rendering) try: with setup_profiling(): # Avoid needing to launch threads to reap zombies run_app(opts, cli_opts, bad_lines, talk_fd) finally: glfw_terminate() cleanup_ssh_control_masters() def main() -> None: try: _main() except Exception: import traceback tb = traceback.format_exc() log_error(tb) raise SystemExit(1) finally: # we cant rely on this running during module unloading of fast_data_types as Python fails # to unload the module, due to reference cycles, I am guessing. run_at_exit_cleanup_functions() kitty-0.41.1/kitty/marks.py0000664000175000017510000000655614773370543015156 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import re from collections.abc import Callable, Generator, Iterable, Sequence from ctypes import POINTER, c_uint, c_void_p, cast from re import Pattern from typing import Union from .utils import resolve_custom_file pointer_to_uint = POINTER(c_uint) MarkerFunc = Callable[[str, int, int, int], Generator[None, None, None]] def get_output_variables(left_address: int, right_address: int, color_address: int) -> tuple[c_uint, c_uint, c_uint]: return ( cast(c_void_p(left_address), pointer_to_uint).contents, cast(c_void_p(right_address), pointer_to_uint).contents, cast(c_void_p(color_address), pointer_to_uint).contents, ) def marker_from_regex(expression: Union[str, 'Pattern[str]'], color: int, flags: int = re.UNICODE) -> MarkerFunc: color = max(1, min(color, 3)) if isinstance(expression, str): pat = re.compile(expression, flags=flags) else: pat = expression def marker(text: str, left_address: int, right_address: int, color_address: int) -> Generator[None, None, None]: left, right, colorv = get_output_variables(left_address, right_address, color_address) colorv.value = color for match in pat.finditer(text): left.value = match.start() right.value = match.end() - 1 yield return marker def marker_from_multiple_regex(regexes: Iterable[tuple[int, str]], flags: int = re.UNICODE) -> MarkerFunc: expr = '' color_map = {} for i, (color, spec) in enumerate(regexes): grp = f'mcg{i}' expr += f'|(?P<{grp}>{spec})' color_map[grp] = color expr = expr[1:] pat = re.compile(expr, flags=flags) def marker(text: str, left_address: int, right_address: int, color_address: int) -> Generator[None, None, None]: left, right, color = get_output_variables(left_address, right_address, color_address) for match in pat.finditer(text): left.value = match.start() right.value = match.end() - 1 grp = match.lastgroup color.value = color_map[grp] if grp is not None else 0 yield return marker def marker_from_text(expression: str, color: int) -> MarkerFunc: return marker_from_regex(re.escape(expression), color) def marker_from_function(func: Callable[[str], Iterable[tuple[int, int, int]]]) -> MarkerFunc: def marker(text: str, left_address: int, right_address: int, color_address: int) -> Generator[None, None, None]: left, right, colorv = get_output_variables(left_address, right_address, color_address) for (ll, r, c) in func(text): left.value = ll right.value = r colorv.value = c yield return marker def marker_from_spec(ftype: str, spec: str | Sequence[tuple[int, str]], flags: int) -> MarkerFunc: if ftype == 'regex': assert not isinstance(spec, str) if len(spec) == 1: return marker_from_regex(spec[0][1], spec[0][0], flags=flags) return marker_from_multiple_regex(spec, flags=flags) if ftype == 'function': import runpy assert isinstance(spec, str) path = resolve_custom_file(spec) return marker_from_function(runpy.run_path(path, run_name='__marker__')["marker"]) raise ValueError(f'Unknown marker type: {ftype}') kitty-0.41.1/kitty/modes.h0000664000175000017510000000504014773370543014732 0ustar nileshnilesh/* * modes.h * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once /* *Line Feed/New Line Mode*: When enabled, causes a received LF, FF, or VT to move the cursor to the first column of the next line. */ #define LNM 20 /* *Insert/Replace Mode*: When enabled, new display characters move old display characters to the right. Characters moved past the right margin are lost. Otherwise, new display characters replace old display characters at the cursor position. */ #define IRM 4 // Private modes. // Arrow keys send application sequences or cursor movement commands #define DECCKM (1 << 5) // *Column Mode*: selects the number of columns per line (80 or 132) // on the screen. #define DECCOLM (3 << 5) // Scroll speed #define DECSCLM (4 << 5) // *Screen Mode*: toggles screen-wide reverse-video mode. #define DECSCNM (5 << 5) // Auto-repeat of keys #define DECARM (8 << 5) /* *Origin Mode*: allows cursor addressing relative to a user-defined origin. This mode resets when the terminal is powered up or reset. It does not affect the erase in display (ED) function. */ #define DECOM (6 << 5) // *Auto Wrap Mode*: selects where received graphic characters appear // when the cursor is at the right margin. #define DECAWM (7 << 5) // Toggle cursor blinking #define CONTROL_CURSOR_BLINK (12 << 5) // *Text Cursor Enable Mode*: determines if the text cursor is visible. #define DECTCEM (25 << 5) // National Replacement Character Set Mode #define DECNRCM (42 << 5) // xterm mouse protocol #define MOUSE_BUTTON_TRACKING (1000 << 5) #define MOUSE_MOTION_TRACKING (1002 << 5) #define MOUSE_MOVE_TRACKING (1003 << 5) #define FOCUS_TRACKING (1004 << 5) #define MOUSE_UTF8_MODE (1005 << 5) #define MOUSE_SGR_MODE (1006 << 5) #define MOUSE_URXVT_MODE (1015 << 5) #define MOUSE_SGR_PIXEL_MODE (1016 << 5) // Save cursor (DECSC) #define SAVE_CURSOR (1048 << 5) // Alternate screen buffer #define TOGGLE_ALT_SCREEN_1 (47 << 5) #define TOGGLE_ALT_SCREEN_2 (1047 << 5) #define ALTERNATE_SCREEN (1049 << 5) // Bracketed paste mode // https://cirw.in/blog/bracketed-paste #define BRACKETED_PASTE (2004 << 5) #define BRACKETED_PASTE_START "200~" #define BRACKETED_PASTE_END "201~" // Pending updates mode #define PENDING_UPDATE (2026 << 5) // Notification of color preference change #define COLOR_PREFERENCE_NOTIFICATION (2031 << 5) // In-band resize notification mode #define INBAND_RESIZE_NOTIFICATION (2048 << 5) // Handle Ctrl-C/Ctrl-Z mode #define HANDLE_TERMIOS_SIGNALS (19997 << 5) kitty-0.41.1/kitty/monotonic.c0000664000175000017510000000033614773370543015626 0ustar nileshnilesh/* * monotonic.c * Copyright (C) 2019 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #define _POSIX_C_SOURCE 200809L #define MONOTONIC_IMPLEMENTATION #include "monotonic.h" kitty-0.41.1/kitty/monotonic.h0000664000175000017510000000453514773370543015640 0ustar nileshnilesh/* * monotonic.h * Copyright (C) 2019 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include #define MONOTONIC_T_MAX INT64_MAX #define MONOTONIC_T_MIN INT64_MIN #define MONOTONIC_T_1e6 1000000ll #define MONOTONIC_T_1e3 1000ll #define MONOTONIC_T_1e9 1000000000ll typedef int64_t monotonic_t; static inline monotonic_t s_double_to_monotonic_t(double time) { return (monotonic_t)(time * 1e9); } static inline monotonic_t ms_double_to_monotonic_t(double time) { return (monotonic_t)(time * 1e6); } static inline monotonic_t s_to_monotonic_t(monotonic_t time) { return time * MONOTONIC_T_1e9; } static inline monotonic_t ms_to_monotonic_t(monotonic_t time) { return time * MONOTONIC_T_1e6; } static inline int monotonic_t_to_ms(monotonic_t time) { return (int)(time / MONOTONIC_T_1e6); } static inline int monotonic_t_to_us(monotonic_t time) { return (int)(time / MONOTONIC_T_1e3); } static inline double monotonic_t_to_s_double(monotonic_t time) { return ((double)time) / 1e9; } extern monotonic_t monotonic_start_time; extern monotonic_t monotonic_(void); static inline monotonic_t monotonic(void) { return monotonic_() - monotonic_start_time; } static inline void init_monotonic(void) { monotonic_start_time = monotonic_(); } extern int timed_debug_print(const char *fmt, ...) __attribute__((format(printf, 1, 2))); #ifdef MONOTONIC_IMPLEMENTATION #include #include #include #include monotonic_t monotonic_start_time = 0; static inline monotonic_t calc_nano_time(struct timespec time) { return ((monotonic_t)time.tv_sec * MONOTONIC_T_1e9) + (monotonic_t)time.tv_nsec; } monotonic_t monotonic_(void) { struct timespec ts = {0}; #ifdef CLOCK_HIGHRES clock_gettime(CLOCK_HIGHRES, &ts); #elif CLOCK_MONOTONIC_RAW clock_gettime(CLOCK_MONOTONIC_RAW, &ts); #else clock_gettime(CLOCK_MONOTONIC, &ts); #endif return calc_nano_time(ts); } int timed_debug_print(const char *fmt, ...) { int result; static int starting_print = 1; if (starting_print) fprintf(stderr, "[%.3f] ", monotonic_t_to_s_double(monotonic())); va_list args; va_start(args, fmt); result = vfprintf(stderr, fmt, args); va_end(args); starting_print = fmt && strchr(fmt, '\n') != NULL; return result; } #endif kitty-0.41.1/kitty/mouse.c0000664000175000017510000013115214773370543014752 0ustar nileshnilesh/* * mouse.c * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "state.h" #include "charsets.h" #include #include #include "glfw-wrapper.h" #include "control-codes.h" extern PyTypeObject Screen_Type; static MouseShape mouse_cursor_shape = TEXT_POINTER; typedef enum MouseActions { PRESS, RELEASE, DRAG, MOVE } MouseAction; #define debug debug_input // Encoding of mouse events {{{ #define SHIFT_INDICATOR (1 << 2) #define ALT_INDICATOR (1 << 3) #define CONTROL_INDICATOR (1 << 4) #define MOTION_INDICATOR (1 << 5) #define SCROLL_BUTTON_INDICATOR (1 << 6) #define EXTRA_BUTTON_INDICATOR (1 << 7) static unsigned int button_map(int button) { switch(button) { case GLFW_MOUSE_BUTTON_LEFT: return 1; case GLFW_MOUSE_BUTTON_RIGHT: return 3; case GLFW_MOUSE_BUTTON_MIDDLE: return 2; case GLFW_MOUSE_BUTTON_4: case GLFW_MOUSE_BUTTON_5: case GLFW_MOUSE_BUTTON_6: case GLFW_MOUSE_BUTTON_7: case GLFW_MOUSE_BUTTON_8: return button + 5; default: return UINT_MAX; } } static unsigned int encode_button(unsigned int button) { if (button >= 8 && button <= 11) { return (button - 8) | EXTRA_BUTTON_INDICATOR; } else if (button >= 4 && button <= 7) { return (button - 4) | SCROLL_BUTTON_INDICATOR; } else if (button >= 1 && button <= 3) { return button - 1; } else { return UINT_MAX; } } static char mouse_event_buf[64]; static int encode_mouse_event_impl(const MousePosition *mpos, int mouse_tracking_protocol, int button, MouseAction action, int mods) { unsigned int cb = encode_button(button); if (action == MOVE) { if (cb == UINT_MAX) cb = 3; cb += 32; } else { if (cb == UINT_MAX) return 0; } if (action == DRAG || action == MOVE) cb |= MOTION_INDICATOR; else if (action == RELEASE && mouse_tracking_protocol < SGR_PROTOCOL) cb = 3; if (mods & GLFW_MOD_SHIFT) cb |= SHIFT_INDICATOR; if (mods & GLFW_MOD_ALT) cb |= ALT_INDICATOR; if (mods & GLFW_MOD_CONTROL) cb |= CONTROL_INDICATOR; int x = mpos->cell_x + 1, y = mpos->cell_y + 1; switch(mouse_tracking_protocol) { case SGR_PIXEL_PROTOCOL: x = (int)round(mpos->global_x); y = (int)round(mpos->global_y); /* fallthrough */ case SGR_PROTOCOL: return snprintf(mouse_event_buf, sizeof(mouse_event_buf), "<%d;%d;%d%s", cb, x, y, action == RELEASE ? "m" : "M"); break; case URXVT_PROTOCOL: return snprintf(mouse_event_buf, sizeof(mouse_event_buf), "%d;%d;%dM", cb + 32, x, y); break; case UTF8_PROTOCOL: mouse_event_buf[0] = 'M'; mouse_event_buf[1] = cb + 32; unsigned int sz = 2; sz += encode_utf8(x + 32, mouse_event_buf + sz); sz += encode_utf8(y + 32, mouse_event_buf + sz); return sz; break; default: if (x > 223 || y > 223) return 0; else { mouse_event_buf[0] = 'M'; mouse_event_buf[1] = cb + 32; mouse_event_buf[2] = x + 32; mouse_event_buf[3] = y + 32; return 4; } break; } return 0; } static int encode_mouse_event(Window *w, int button, MouseAction action, int mods) { Screen *screen = w->render_data.screen; return encode_mouse_event_impl(&w->mouse_pos, screen->modes.mouse_tracking_protocol, button, action, mods); } static int encode_mouse_button(Window *w, int button, MouseAction action, int mods) { if (button == GLFW_MOUSE_BUTTON_LEFT) { switch(action) { case PRESS: global_state.tracked_drag_in_window = w->id; global_state.tracked_drag_button = button; break; case RELEASE: global_state.tracked_drag_in_window = 0; global_state.tracked_drag_button = -1; break; default: break; } } return encode_mouse_event(w, button_map(button), action, mods); } static int encode_mouse_scroll(Window *w, int button, int mods) { return encode_mouse_event(w, button, PRESS, mods); } // }}} static Window* window_for_id(id_type window_id) { if (global_state.callback_os_window && global_state.callback_os_window->num_tabs) { Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; for (unsigned int i = 0; i < t->num_windows; i++) { Window *w = t->windows + i; if (w->id == window_id) return w; } } return window_for_window_id(window_id); } static bool dispatch_mouse_event(Window *w, int button, int count, int modifiers, bool grabbed) { bool handled = false; if (w->render_data.screen && w->render_data.screen->callbacks != Py_None) { PyObject *callback_ret = PyObject_CallMethod(w->render_data.screen->callbacks, "on_mouse_event", "{si si si sO}", "button", button, "repeat_count", count, "mods", modifiers, "grabbed", grabbed ? Py_True : Py_False); if (callback_ret == NULL) PyErr_Print(); else { handled = callback_ret == Py_True; Py_DECREF(callback_ret); } if (OPT(debug_keyboard)) { const char *evname = "move"; switch(count) { case -3: evname = "doubleclick"; break; case -2: evname = "click"; break; case -1: evname = "release"; break; case 1: evname = "press"; break; case 2: evname = "doublepress"; break; case 3: evname = "triplepress"; break; } const char *bname = "unknown"; switch(button) { case GLFW_MOUSE_BUTTON_LEFT: bname = "left"; break; case GLFW_MOUSE_BUTTON_MIDDLE: bname = "middle"; break; case GLFW_MOUSE_BUTTON_RIGHT: bname = "right"; break; case GLFW_MOUSE_BUTTON_4: bname = "b4"; break; case GLFW_MOUSE_BUTTON_5: bname = "b5"; break; case GLFW_MOUSE_BUTTON_6: bname = "b6"; break; case GLFW_MOUSE_BUTTON_7: bname = "b7"; break; case GLFW_MOUSE_BUTTON_8: bname = "b8"; break; } debug("\x1b[33mon_mouse_input\x1b[m: %s button: %s %sgrabbed: %d handled_in_kitty: %d\n", evname, bname, format_mods(modifiers), grabbed, handled); } } return handled; } static unsigned int window_left(Window *w) { return w->geometry.left - w->padding.left; } static unsigned int window_right(Window *w) { return w->geometry.right + w->padding.right; } static unsigned int window_top(Window *w) { return w->geometry.top - w->padding.top; } static unsigned int window_bottom(Window *w) { return w->geometry.bottom + w->padding.bottom; } static bool contains_mouse(Window *w) { double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y; return (w->visible && window_left(w) <= x && x <= window_right(w) && window_top(w) <= y && y <= window_bottom(w)); } static double distance_to_window(Window *w) { double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y; double cx = (window_left(w) + window_right(w)) / 2.0; double cy = (window_top(w) + window_bottom(w)) / 2.0; return (x - cx) * (x - cx) + (y - cy) * (y - cy); } static bool clamp_to_window = false; static bool cell_for_pos(Window *w, unsigned int *x, unsigned int *y, bool *in_left_half_of_cell, OSWindow *os_window) { WindowGeometry *g = &w->geometry; Screen *screen = w->render_data.screen; if (!screen) return false; unsigned int qx = 0, qy = 0; bool in_left_half = true; double mouse_x = global_state.callback_os_window->mouse_x; double mouse_y = global_state.callback_os_window->mouse_y; double left = g->left, top = g->top, right = g->right, bottom = g->bottom; w->mouse_pos.global_x = mouse_x - left; w->mouse_pos.global_y = mouse_y - top; if (clamp_to_window) { mouse_x = MIN(MAX(mouse_x, left), right); mouse_y = MIN(MAX(mouse_y, top), bottom); } if (mouse_x < left || mouse_y < top || mouse_x > right || mouse_y > bottom) return false; if (mouse_x >= g->right) { qx = screen->columns - 1; in_left_half = false; } else if (mouse_x >= g->left) { double xval = (double)(mouse_x - g->left) / os_window->fonts_data->fcm.cell_width; double fxval = floor(xval); qx = (unsigned int)fxval; in_left_half = (xval - fxval <= 0.5) ? true : false; } if (mouse_y >= g->bottom) qy = screen->lines - 1; else if (mouse_y >= g->top) qy = (unsigned int)((double)(mouse_y - g->top) / os_window->fonts_data->fcm.cell_height); if (qx < screen->columns && qy < screen->lines) { *x = qx; *y = qy; *in_left_half_of_cell = in_left_half; return true; } return false; } #define HANDLER(name) static void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx) static void set_mouse_cursor_when_dragging(Screen *screen) { MouseShape expected_shape = OPT(pointer_shape_when_dragging); if (screen && screen->selections.count && screen->selections.items[0].rectangle_select) expected_shape = OPT(pointer_shape_when_dragging_rectangle); if (mouse_cursor_shape != expected_shape) { mouse_cursor_shape = expected_shape; set_mouse_cursor(mouse_cursor_shape); } } static void update_drag(Window *w) { Screen *screen = w->render_data.screen; if (screen && screen->selections.in_progress) { screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, (SelectionUpdate){0}); } set_mouse_cursor_when_dragging(screen); } static bool do_drag_scroll(Window *w, bool upwards) { Screen *screen = w->render_data.screen; if (screen->linebuf == screen->main_linebuf) { screen_history_scroll(screen, SCROLL_LINE, upwards); update_drag(w); if (mouse_cursor_shape != DEFAULT_POINTER) { mouse_cursor_shape = DEFAULT_POINTER; set_mouse_cursor(mouse_cursor_shape); } return true; } return false; } bool drag_scroll(Window *w, OSWindow *frame) { unsigned int margin = frame->fonts_data->fcm.cell_height / 2; double y = frame->mouse_y; bool upwards = y <= (w->geometry.top + margin); if (upwards || y >= w->geometry.bottom - margin) { if (do_drag_scroll(w, upwards)) { frame->last_mouse_activity_at = monotonic(); return true; } } return false; } static void extend_selection(Window *w, bool ended, bool extend_nearest) { Screen *screen = w->render_data.screen; if (screen_has_selection(screen)) { screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, (SelectionUpdate){.ended=ended, .set_as_nearest_extend=extend_nearest}); } } static void set_mouse_cursor_for_screen(Screen *screen) { MouseShape s = screen_pointer_shape(screen); if (s != INVALID_POINTER) { mouse_cursor_shape = s; } else { if (screen->modes.mouse_tracking_mode == NO_TRACKING) { mouse_cursor_shape = OPT(default_pointer_shape); } else { mouse_cursor_shape = OPT(pointer_shape_when_grabbed); } } } static void handle_mouse_movement_in_kitty(Window *w, int button, bool mouse_cell_changed) { Screen *screen = w->render_data.screen; if (screen->selections.in_progress && (button == global_state.active_drag_button)) { monotonic_t now = monotonic(); if ((now - w->last_drag_scroll_at) >= ms_to_monotonic_t(20ll) || mouse_cell_changed) { update_drag(w); w->last_drag_scroll_at = now; } } } static void detect_url(Screen *screen, unsigned int x, unsigned int y) { int hid = screen_detect_url(screen, x, y); screen->current_hyperlink_under_mouse.id = 0; if (hid != 0) { mouse_cursor_shape = POINTER_POINTER; if (hid > 0) { screen->current_hyperlink_under_mouse.id = (hyperlink_id_type)hid; screen->current_hyperlink_under_mouse.x = x; screen->current_hyperlink_under_mouse.y = y; } } else set_mouse_cursor_for_screen(screen); } static bool should_handle_in_kitty(Window *w, Screen *screen, int button) { bool in_tracking_mode = ( screen->modes.mouse_tracking_mode == ANY_MODE || (screen->modes.mouse_tracking_mode == MOTION_MODE && button >= 0)); return !in_tracking_mode || global_state.active_drag_in_window == w->id; } static bool set_mouse_position(Window *w, bool *mouse_cell_changed, bool *cell_half_changed) { unsigned int x = 0, y = 0; bool in_left_half_of_cell = false; if (!cell_for_pos(w, &x, &y, &in_left_half_of_cell, global_state.callback_os_window)) return false; *mouse_cell_changed = x != w->mouse_pos.cell_x || y != w->mouse_pos.cell_y; *cell_half_changed = in_left_half_of_cell != w->mouse_pos.in_left_half_of_cell; w->mouse_pos.cell_x = x; w->mouse_pos.cell_y = y; w->mouse_pos.in_left_half_of_cell = in_left_half_of_cell; return true; } HANDLER(handle_move_event) { modifiers &= ~GLFW_LOCK_MASK; if (OPT(focus_follows_mouse)) { Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; if (window_idx != t->active_window) { call_boss(switch_focus_to, "K", t->windows[window_idx].id); } } bool mouse_cell_changed = false; bool cell_half_changed = false; if (!set_mouse_position(w, &mouse_cell_changed, &cell_half_changed)) return; Screen *screen = w->render_data.screen; if (OPT(detect_urls)) detect_url(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y); if (should_handle_in_kitty(w, screen, button)) { handle_mouse_movement_in_kitty(w, button, mouse_cell_changed | cell_half_changed); } else { if (!mouse_cell_changed && screen->modes.mouse_tracking_protocol != SGR_PIXEL_PROTOCOL) return; int sz = encode_mouse_button(w, button, button >=0 ? DRAG : MOVE, modifiers); if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf); } } } static double distance(double x1, double y1, double x2, double y2) { return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); } static void clear_click_queue(Window *w, int button) { if (0 <= button && button <= (ssize_t)arraysz(w->click_queues)) w->click_queues[button].length = 0; } #define N(n) (q->clicks[q->length - n]) static double radius_for_multiclick(void) { return 0.5 * (global_state.callback_os_window ? global_state.callback_os_window->fonts_data->fcm.cell_height : 8); } static bool release_is_click(const Window *w, int button) { const ClickQueue *q = &w->click_queues[button]; monotonic_t now = monotonic(); return (q->length > 0 && distance(N(1).x, N(1).y, MAX(0, w->mouse_pos.global_x), MAX(0, w->mouse_pos.global_y)) <= radius_for_multiclick() && now - N(1).at < OPT(click_interval)); } static unsigned multi_click_count(const Window *w, int button) { const ClickQueue *q = &w->click_queues[button]; double multi_click_allowed_radius = radius_for_multiclick(); if (q->length > 2) { // possible triple-click if ( N(1).at - N(3).at <= 2 * OPT(click_interval) && distance(N(1).x, N(1).y, N(3).x, N(3).y) <= multi_click_allowed_radius ) return 3; } if (q->length > 1) { // possible double-click if ( N(1).at - N(2).at <= OPT(click_interval) && distance(N(1).x, N(1).y, N(2).x, N(2).y) <= multi_click_allowed_radius ) return 2; } return q->length ? 1 : 0; } static void add_press(Window *w, int button, int modifiers) { if (button < 0 || button >= (ssize_t)arraysz(w->click_queues)) return; modifiers &= ~GLFW_LOCK_MASK; ClickQueue *q = &w->click_queues[button]; if (q->length == CLICK_QUEUE_SZ) { memmove(q->clicks, q->clicks + 1, sizeof(Click) * (CLICK_QUEUE_SZ - 1)); q->length--; } monotonic_t now = monotonic(); static unsigned long num = 0; N(0).at = now; N(0).button = button; N(0).modifiers = modifiers; N(0).x = MAX(0, w->mouse_pos.global_x); N(0).y = MAX(0, w->mouse_pos.global_y); N(0).num = ++num; q->length++; Screen *screen = w->render_data.screen; int count = multi_click_count(w, button); if (count > 1) { if (screen) dispatch_mouse_event(w, button, count, modifiers, screen->modes.mouse_tracking_mode != 0); if (count > 2) q->length = 0; } } #undef N bool mouse_open_url(Window *w) { Screen *screen = w->render_data.screen; detect_url(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y); return screen_open_url(screen); } bool mouse_set_last_visited_cmd_output(Window *w) { Screen *screen = w->render_data.screen; return screen_set_last_visited_prompt(screen, w->mouse_pos.cell_y); } bool mouse_select_cmd_output(Window *w) { Screen *screen = w->render_data.screen; return screen_select_cmd_output(screen, w->mouse_pos.cell_y); } bool move_cursor_to_mouse_if_at_shell_prompt(Window *w) { Screen *screen = w->render_data.screen; int y = screen_cursor_at_a_shell_prompt(screen); if (y < 0 || (unsigned)y > w->mouse_pos.cell_y) return false; if (screen_prompt_supports_click_events(screen)) { int sz = encode_mouse_event_impl(&w->mouse_pos, SGR_PROTOCOL, 1, PRESS, 0); if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf); return true; } return false; } else { return screen_fake_move_cursor_to_position(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y); } } void send_pending_click_to_window(Window *w, int i) { const id_type wid = w->id; if (i < 0) { while(true) { w = window_for_id(wid); if (!w || !w->pending_clicks.num) break; send_pending_click_to_window(w, w->pending_clicks.num - 1); } return; } PendingClick pc = w->pending_clicks.clicks[i]; remove_i_from_array(w->pending_clicks.clicks, (unsigned)i, w->pending_clicks.num); const ClickQueue *q = &w->click_queues[pc.button]; // only send click if no presses have happened since the release that triggered // the click or if the subsequent press is too far or too late for a double click if (!q->length) return; #define press(n) q->clicks[q->length - n] if ( press(1).at <= pc.at || // latest press is before click release (q->length > 1 && press(2).num == pc.press_num && ( // penultimate press is the press that belongs to this click press(1).at - press(2).at > OPT(click_interval) || // too long between the presses for it to be a double click distance(press(1).x, press(1).y, press(2).x, press(2).y) > pc.radius_for_multiclick // presses are too far apart )) ) { MousePosition current_pos = w->mouse_pos; w->mouse_pos = pc.mouse_pos; dispatch_mouse_event(w, pc.button, pc.count, pc.modifiers, pc.grabbed); w = window_for_id(wid); if (w) w->mouse_pos = current_pos; } #undef press } static void dispatch_possible_click(Window *w, int button, int modifiers) { Screen *screen = w->render_data.screen; int count = multi_click_count(w, button); if (release_is_click(w, button)) { ensure_space_for(&(w->pending_clicks), clicks, PendingClick, w->pending_clicks.num + 1, capacity, 4, true); PendingClick *pc = w->pending_clicks.clicks + w->pending_clicks.num++; zero_at_ptr(pc); const ClickQueue *q = &w->click_queues[button]; pc->press_num = q->length ? q->clicks[q->length - 1].num : 0; pc->window_id = w->id; pc->mouse_pos = w->mouse_pos; pc->at = monotonic(); pc->button = button; pc->count = count == 2 ? -3 : -2; pc->modifiers = modifiers; pc->grabbed = screen->modes.mouse_tracking_mode != 0; pc->radius_for_multiclick = radius_for_multiclick(); add_main_loop_timer(OPT(click_interval), false, dispatch_pending_clicks, NULL, NULL); } } HANDLER(handle_button_event) { modifiers &= ~GLFW_LOCK_MASK; Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; bool is_release = !global_state.callback_os_window->mouse_button_pressed[button]; if (window_idx != t->active_window && !is_release) { call_boss(switch_focus_to, "K", t->windows[window_idx].id); } Screen *screen = w->render_data.screen; if (!screen) return; bool a, b; if (!set_mouse_position(w, &a, &b)) return; id_type wid = w->id; if (!dispatch_mouse_event(w, button, is_release ? -1 : 1, modifiers, screen->modes.mouse_tracking_mode != 0)) { if (screen->modes.mouse_tracking_mode != 0) { int sz = encode_mouse_button(w, button, is_release ? RELEASE : PRESS, modifiers); if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf); } } } // the windows array might have been re-alloced in dispatch_mouse_event w = NULL; for (size_t i = 0; i < t->num_windows && !w; i++) if (t->windows[i].id == wid) w = t->windows + i; if (w) { if (is_release) dispatch_possible_click(w, button, modifiers); else add_press(w, button, modifiers); } } static int currently_pressed_button(void) { for (int i = 0; i <= GLFW_MOUSE_BUTTON_LAST; i++) { if (global_state.callback_os_window->mouse_button_pressed[i]) return i; } return -1; } HANDLER(handle_event) { modifiers &= ~GLFW_LOCK_MASK; set_mouse_cursor_for_screen(w->render_data.screen); if (button == -1) { button = currently_pressed_button(); handle_move_event(w, button, modifiers, window_idx); } else { handle_button_event(w, button, modifiers, window_idx); } } static void handle_tab_bar_mouse(int button, int modifiers, int action) { if (button > -1) { // dont report motion events, as they are expensive and useless call_boss(handle_click_on_tab, "Kdiii", global_state.callback_os_window->id, global_state.callback_os_window->mouse_x, button, modifiers, action); } } static bool mouse_in_region(Region *r) { if (r->left == r->right) return false; if (global_state.callback_os_window->mouse_y < r->top || global_state.callback_os_window->mouse_y > r->bottom) return false; if (global_state.callback_os_window->mouse_x < r->left || global_state.callback_os_window->mouse_x > r->right) return false; return true; } static Window* window_for_event(unsigned int *window_idx, bool *in_tab_bar) { Region central, tab_bar; os_window_regions(global_state.callback_os_window, ¢ral, &tab_bar); const bool in_central = mouse_in_region(¢ral); *in_tab_bar = false; const OSWindow* w = global_state.callback_os_window; if (!in_central) { if ( (tab_bar.top < central.top && w->mouse_y <= central.top) || (tab_bar.bottom > central.bottom && w->mouse_y >= central.bottom) ) *in_tab_bar = true; } if (in_central && global_state.callback_os_window->num_tabs > 0) { Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; for (unsigned int i = 0; i < t->num_windows; i++) { if (contains_mouse(t->windows + i) && t->windows[i].render_data.screen) { *window_idx = i; return t->windows + i; } } } return NULL; } static Window* closest_window_for_event(unsigned int *window_idx) { Window *ans = NULL; double closest_distance = UINT_MAX; if (global_state.callback_os_window->num_tabs > 0) { Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; for (unsigned int i = 0; i < t->num_windows; i++) { Window *w = t->windows + i; if (w->visible) { double d = distance_to_window(w); if (d < closest_distance) { ans = w; closest_distance = d; *window_idx = i; } } } } return ans; } void focus_in_event(void) { // Ensure that no URL is highlighted and the mouse cursor is in default shape bool in_tab_bar; unsigned int window_idx = 0; mouse_cursor_shape = TEXT_POINTER; Window *w = window_for_event(&window_idx, &in_tab_bar); if (w && w->render_data.screen) { screen_mark_url(w->render_data.screen, 0, 0, 0, 0); set_mouse_cursor_for_screen(w->render_data.screen); } set_mouse_cursor(mouse_cursor_shape); } void update_mouse_pointer_shape(void) { mouse_cursor_shape = TEXT_POINTER; bool in_tab_bar; unsigned int window_idx = 0; Window *w = window_for_event(&window_idx, &in_tab_bar); if (in_tab_bar) { mouse_cursor_shape = POINTER_POINTER; } else if (w && w->render_data.screen) { screen_mark_url(w->render_data.screen, 0, 0, 0, 0); set_mouse_cursor_for_screen(w->render_data.screen); } set_mouse_cursor(mouse_cursor_shape); } void enter_event(int modifiers) { #ifdef __APPLE__ // On cocoa there is no way to configure the window manager to // focus windows on mouse enter, so we do it ourselves if (OPT(focus_follows_mouse) && !global_state.callback_os_window->is_focused) { id_type wid = global_state.callback_os_window->id; focus_os_window(global_state.callback_os_window, false, NULL); if (!global_state.callback_os_window) { global_state.callback_os_window = os_window_for_id(wid); if (!global_state.callback_os_window) return; } } #endif // If the mouse is grabbed send a move event to update the cursor position // since the last report. if (global_state.redirect_mouse_handling || global_state.active_drag_in_window || global_state.tracked_drag_in_window) return; unsigned window_idx; bool in_tab_bar; Window *w = window_for_event(&window_idx, &in_tab_bar); if (!w || in_tab_bar) return; bool mouse_cell_changed = false; bool cell_half_changed = false; if (!set_mouse_position(w, &mouse_cell_changed, &cell_half_changed)) return; Screen *screen = w->render_data.screen; int button = currently_pressed_button(); if (!screen || should_handle_in_kitty(w, screen, button)) return; int sz = encode_mouse_button(w, button, button >=0 ? DRAG : MOVE, modifiers); if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf); } } static void end_drag(Window *w) { Screen *screen = w->render_data.screen; global_state.active_drag_in_window = 0; global_state.active_drag_button = -1; w->last_drag_scroll_at = 0; if (screen->selections.in_progress) { screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, (SelectionUpdate){.ended=true}); } } typedef enum MouseSelectionType { MOUSE_SELECTION_NORMAL, MOUSE_SELECTION_EXTEND, MOUSE_SELECTION_RECTANGLE, MOUSE_SELECTION_WORD, MOUSE_SELECTION_LINE, MOUSE_SELECTION_LINE_FROM_POINT, MOUSE_SELECTION_WORD_AND_LINE_FROM_POINT, MOUSE_SELECTION_MOVE_END, } MouseSelectionType; void mouse_selection(Window *w, int code, int button) { global_state.active_drag_in_window = w->id; global_state.active_drag_button = button; Screen *screen = w->render_data.screen; index_type start, end; unsigned int y1, y2; #define S(mode) {\ screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, mode); \ screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, (SelectionUpdate){.start_extended_selection=true}); } switch((MouseSelectionType)code) { case MOUSE_SELECTION_NORMAL: screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, EXTEND_CELL); break; case MOUSE_SELECTION_RECTANGLE: screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, true, EXTEND_CELL); break; case MOUSE_SELECTION_WORD: if (screen_selection_range_for_word(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, &y1, &y2, &start, &end, true)) S(EXTEND_WORD); break; case MOUSE_SELECTION_LINE: if (screen_selection_range_for_line(screen, w->mouse_pos.cell_y, &start, &end)) S(EXTEND_LINE); break; case MOUSE_SELECTION_LINE_FROM_POINT: if (screen_selection_range_for_line(screen, w->mouse_pos.cell_y, &start, &end) && end > w->mouse_pos.cell_x) S(EXTEND_LINE_FROM_POINT); break; case MOUSE_SELECTION_WORD_AND_LINE_FROM_POINT: if (screen_selection_range_for_line(screen, w->mouse_pos.cell_y, &start, &end) && end > w->mouse_pos.cell_x) S(EXTEND_WORD_AND_LINE_FROM_POINT); break; case MOUSE_SELECTION_EXTEND: extend_selection(w, false, true); break; case MOUSE_SELECTION_MOVE_END: extend_selection(w, false, false); break; } set_mouse_cursor_when_dragging(screen); #undef S } void mouse_event(const int button, int modifiers, int action) { MouseShape old_cursor = mouse_cursor_shape; bool in_tab_bar; unsigned int window_idx = 0; Window *w = NULL; if (OPT(debug_keyboard)) { if (button < 0) { debug("%s x: %.1f y: %.1f ", "\x1b[36mMove\x1b[m", global_state.callback_os_window->mouse_x, global_state.callback_os_window->mouse_y); } else { debug("%s mouse_button: %d %s", action == GLFW_RELEASE ? "\x1b[32mRelease\x1b[m" : "\x1b[31mPress\x1b[m", button, format_mods(modifiers)); } } if (global_state.redirect_mouse_handling) { w = window_for_event(&window_idx, &in_tab_bar); call_boss(mouse_event, "OK iiii dd", (in_tab_bar ? Py_True : Py_False), (w ? w->id : 0), action, modifiers, button, currently_pressed_button(), global_state.callback_os_window->mouse_x, global_state.callback_os_window->mouse_y ); debug("mouse handling redirected\n"); return; } if (global_state.active_drag_in_window) { if (button == -1) { // drag move w = window_for_id(global_state.active_drag_in_window); if (w) { if (currently_pressed_button() == global_state.active_drag_button) { clamp_to_window = true; Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; for (window_idx = 0; window_idx < t->num_windows && t->windows[window_idx].id != w->id; window_idx++); handle_move_event(w, currently_pressed_button(), modifiers, window_idx); clamp_to_window = false; debug("handled as drag move\n"); return; } } } else if (action == GLFW_RELEASE && button == global_state.active_drag_button) { w = window_for_id(global_state.active_drag_in_window); if (w) { end_drag(w); debug("handled as drag end\n"); dispatch_possible_click(w, button, modifiers); return; } } } if (global_state.tracked_drag_in_window) { if (button == -1) { // drag move w = window_for_id(global_state.tracked_drag_in_window); if (w) { if (currently_pressed_button() == GLFW_MOUSE_BUTTON_LEFT) { if (w->render_data.screen->modes.mouse_tracking_mode >= MOTION_MODE && w->render_data.screen->modes.mouse_tracking_protocol == SGR_PIXEL_PROTOCOL) { clamp_to_window = true; Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; for (window_idx = 0; window_idx < t->num_windows && t->windows[window_idx].id != w->id; window_idx++); handle_move_event(w, global_state.tracked_drag_button, modifiers, window_idx); clamp_to_window = false; debug("sent to child as drag move\n"); return; } } } } else if (action == GLFW_RELEASE && button == GLFW_MOUSE_BUTTON_LEFT) { w = window_for_id(global_state.tracked_drag_in_window); if (w && w->render_data.screen->modes.mouse_tracking_mode >= BUTTON_MODE && w->render_data.screen->modes.mouse_tracking_protocol >= SGR_PROTOCOL) { global_state.tracked_drag_in_window = 0; clamp_to_window = true; Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; for (window_idx = 0; window_idx < t->num_windows && t->windows[window_idx].id != w->id; window_idx++); debug("sent to child as drag end\n"); handle_button_event(w, button, modifiers, window_idx); clamp_to_window = false; return; } } } w = window_for_event(&window_idx, &in_tab_bar); if (in_tab_bar) { mouse_cursor_shape = POINTER_POINTER; handle_tab_bar_mouse(button, modifiers, action); debug("handled by tab bar\n"); } else if (w) { debug("grabbed: %d\n", w->render_data.screen->modes.mouse_tracking_mode != 0); handle_event(w, button, modifiers, window_idx); } else if (button == GLFW_MOUSE_BUTTON_LEFT && global_state.callback_os_window->mouse_button_pressed[button]) { // initial click, clamp it to the closest window w = closest_window_for_event(&window_idx); if (w) { clamp_to_window = true; debug("grabbed: %d\n", w->render_data.screen->modes.mouse_tracking_mode != 0); handle_event(w, button, modifiers, window_idx); clamp_to_window = false; } else debug("no window for event\n"); } else debug("\n"); if (mouse_cursor_shape != old_cursor) set_mouse_cursor(mouse_cursor_shape); } static int scale_scroll(MouseTrackingMode mouse_tracking_mode, double offset, bool is_high_resolution, double *pending_scroll_pixels, int cell_size) { // scale the scroll by the multiplier unless the mouse is grabbed. If the mouse is grabbed only change direction. #define SCALE_SCROLL(which) { double scale = OPT(which); if (mouse_tracking_mode) scale /= fabs(scale); offset *= scale; } int s = 0; if (is_high_resolution) { SCALE_SCROLL(touch_scroll_multiplier); double pixels = *pending_scroll_pixels + offset; if (fabs(pixels) < cell_size) { *pending_scroll_pixels = pixels; return 0; } s = (int)round(pixels) / cell_size; *pending_scroll_pixels = pixels - s * cell_size; } else { SCALE_SCROLL(wheel_scroll_multiplier); s = (int) round(offset); if (offset != 0) { const int min_lines = mouse_tracking_mode ? 1 : OPT(wheel_scroll_min_lines); if (min_lines > 0 && abs(s) < min_lines) s = offset > 0 ? min_lines : -min_lines; // Always add the minimum number of lines when it is negative else if (min_lines < 0) s = offset > 0 ? s - min_lines : s + min_lines; // apparently on cocoa some mice generate really small yoffset values // when scrolling slowly https://github.com/kovidgoyal/kitty/issues/1238 if (s == 0) s = offset > 0 ? 1 : -1; } *pending_scroll_pixels = 0; } return s; #undef SCALE_SCROLL } void scroll_event(double xoffset, double yoffset, int flags, int modifiers) { debug("\x1b[36mScroll\x1b[m xoffset: %f yoffset: %f flags: %x modifiers: %s\n", xoffset, yoffset, flags, format_mods(modifiers)); bool in_tab_bar; static id_type window_for_momentum_scroll = 0; static bool main_screen_for_momentum_scroll = false; unsigned int window_idx = 0; // allow scroll events even if window is not currently focused (in // which case on some platforms such as macOS the mouse location is zeroed so // window_for_event() does not work). OSWindow *osw = global_state.callback_os_window; if (!osw->is_focused && osw->handle) { double mouse_x, mouse_y; glfwGetCursorPos((GLFWwindow*)osw->handle, &mouse_x, &mouse_y); osw->mouse_x = mouse_x * osw->viewport_x_ratio; osw->mouse_y = mouse_y * osw->viewport_y_ratio; } Window *w = window_for_event(&window_idx, &in_tab_bar); if (!w && !in_tab_bar) { // fallback to last active window Tab *t = osw->tabs + osw->active_tab; if (t) w = t->windows + t->active_window; } if (!w) return; // Also update mouse cursor position while kitty OS window is not focused. // Allows scroll events to be delivered to the child with correct pointer coordinates even when // the window is not focused on macOS if (!osw->is_focused) { unsigned int x = 0, y = 0; bool in_left_half_of_cell; if (cell_for_pos(w, &x, &y, &in_left_half_of_cell, osw)) { w->mouse_pos.cell_x = x; w->mouse_pos.cell_y = y; w->mouse_pos.in_left_half_of_cell = in_left_half_of_cell; } } Screen *screen = w->render_data.screen; enum MomentumData { NoMomentumData, MomentumPhaseBegan, MomentumPhaseStationary, MomentumPhaseActive, MomentumPhaseEnded, MomentumPhaseCancelled, MomentumPhaseMayBegin }; enum MomentumData momentum_data = (flags >> 1) & 7; switch(momentum_data) { case NoMomentumData: break; case MomentumPhaseBegan: window_for_momentum_scroll = w->id; main_screen_for_momentum_scroll = screen->linebuf == screen->main_linebuf; break; case MomentumPhaseStationary: case MomentumPhaseActive: if (window_for_momentum_scroll != w->id || main_screen_for_momentum_scroll != (screen->linebuf == screen->main_linebuf)) return; break; case MomentumPhaseEnded: case MomentumPhaseCancelled: window_for_momentum_scroll = 0; break; case MomentumPhaseMayBegin: default: break; } int s; bool is_high_resolution = flags & 1; if (yoffset != 0.0) { s = scale_scroll(screen->modes.mouse_tracking_mode, yoffset, is_high_resolution, &screen->pending_scroll_pixels_y, global_state.callback_os_window->fonts_data->fcm.cell_height); if (s) { bool upwards = s > 0; if (screen->modes.mouse_tracking_mode) { int sz = encode_mouse_scroll(w, upwards ? 4 : 5, modifiers); if (sz > 0) { mouse_event_buf[sz] = 0; for (s = abs(s); s > 0; s--) { write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf); } } } else { if (screen->linebuf == screen->main_linebuf) { screen_history_scroll(screen, abs(s), upwards); if (screen->selections.in_progress) update_drag(w); } else fake_scroll(w, abs(s), upwards); } } } if (xoffset != 0.0) { s = scale_scroll(screen->modes.mouse_tracking_mode, xoffset, is_high_resolution, &screen->pending_scroll_pixels_x, global_state.callback_os_window->fonts_data->fcm.cell_width); if (s) { if (screen->modes.mouse_tracking_mode) { int sz = encode_mouse_scroll(w, s > 0 ? 6 : 7, modifiers); if (sz > 0) { mouse_event_buf[sz] = 0; for (s = abs(s); s > 0; s--) { write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf); } } } } } } static PyObject* send_mouse_event(PyObject *self UNUSED, PyObject *args, PyObject *kw) { Screen *screen; int x, y, px=0, py=0, in_left_half_of_cell=0; int button, action, mods; static const char* kwlist[] = {"screen", "cell_x", "cell_y", "button", "action", "mods", "pixel_x", "pixel_y", "in_left_half_of_cell", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kw, "O!iiiii|iip", (char**)kwlist, &Screen_Type, &screen, &x, &y, &button, &action, &mods, &px, &py, &in_left_half_of_cell)) return NULL; MouseTrackingMode mode = screen->modes.mouse_tracking_mode; if (mode == ANY_MODE || (mode == MOTION_MODE && action != MOVE) || (mode == BUTTON_MODE && (action == PRESS || action == RELEASE))) { MousePosition mpos = {.cell_x = x, .cell_y = y, .global_x = px, .global_y = py, .in_left_half_of_cell = in_left_half_of_cell}; int sz = encode_mouse_event_impl(&mpos, screen->modes.mouse_tracking_protocol, button, action, mods); if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf); Py_RETURN_TRUE; } } Py_RETURN_FALSE; } static PyObject* test_encode_mouse(PyObject *self UNUSED, PyObject *args) { unsigned int x, y; int mouse_tracking_protocol, button, action, mods; if (!PyArg_ParseTuple(args, "IIiiii", &x, &y, &mouse_tracking_protocol, &button, &action, &mods)) return NULL; MousePosition mpos = {.cell_x = x - 1, .cell_y = y - 1}; int sz = encode_mouse_event_impl(&mpos, mouse_tracking_protocol, button, action, mods); return PyUnicode_FromStringAndSize(mouse_event_buf, sz); } static PyObject* mock_mouse_selection(PyObject *self UNUSED, PyObject *args) { PyObject *capsule; int button, code; if (!PyArg_ParseTuple(args, "O!ii", &PyCapsule_Type, &capsule, &button, &code)) return NULL; Window *w = PyCapsule_GetPointer(capsule, "Window"); if (!w) return NULL; mouse_selection(w, code, button); Py_RETURN_NONE; } static PyObject* send_mock_mouse_event_to_window(PyObject *self UNUSED, PyObject *args) { PyObject *capsule; int button, modifiers, is_release, clear_clicks, in_left_half_of_cell; unsigned int x, y; if (!PyArg_ParseTuple(args, "O!iipIIpp", &PyCapsule_Type, &capsule, &button, &modifiers, &is_release, &x, &y, &clear_clicks, &in_left_half_of_cell)) return NULL; Window *w = PyCapsule_GetPointer(capsule, "Window"); if (!w) return NULL; if (clear_clicks) clear_click_queue(w, button); bool mouse_cell_changed = x != w->mouse_pos.cell_x || y != w->mouse_pos.cell_y || w->mouse_pos.in_left_half_of_cell != in_left_half_of_cell; w->mouse_pos.global_x = 10 * x; w->mouse_pos.global_y = 20 * y; w->mouse_pos.cell_x = x; w->mouse_pos.cell_y = y; w->mouse_pos.in_left_half_of_cell = in_left_half_of_cell; static int last_button_pressed = GLFW_MOUSE_BUTTON_LEFT; if (button < 0) { if (button == -2) do_drag_scroll(w, true); else if (button == -3) do_drag_scroll(w, false); else handle_mouse_movement_in_kitty(w, last_button_pressed, mouse_cell_changed); } else { if (global_state.active_drag_in_window && is_release && button == global_state.active_drag_button) { end_drag(w); } else { dispatch_mouse_event(w, button, is_release ? -1 : 1, modifiers, false); if (!is_release) { last_button_pressed = button; add_press(w, button, modifiers); } } } Py_RETURN_NONE; } static PyMethodDef module_methods[] = { {"send_mouse_event", (PyCFunction)(void (*) (void))(send_mouse_event), METH_VARARGS | METH_KEYWORDS, NULL}, METHODB(test_encode_mouse, METH_VARARGS), METHODB(send_mock_mouse_event_to_window, METH_VARARGS), METHODB(mock_mouse_selection, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_mouse(PyObject *module) { PyModule_AddIntMacro(module, PRESS); PyModule_AddIntMacro(module, RELEASE); PyModule_AddIntMacro(module, DRAG); PyModule_AddIntMacro(module, MOVE); PyModule_AddIntMacro(module, MOUSE_SELECTION_NORMAL); PyModule_AddIntMacro(module, MOUSE_SELECTION_EXTEND); PyModule_AddIntMacro(module, MOUSE_SELECTION_RECTANGLE); PyModule_AddIntMacro(module, MOUSE_SELECTION_WORD); PyModule_AddIntMacro(module, MOUSE_SELECTION_LINE); PyModule_AddIntMacro(module, MOUSE_SELECTION_LINE_FROM_POINT); PyModule_AddIntMacro(module, MOUSE_SELECTION_WORD_AND_LINE_FROM_POINT); PyModule_AddIntMacro(module, MOUSE_SELECTION_MOVE_END); if (PyModule_AddFunctions(module, module_methods) != 0) return false; return true; } kitty-0.41.1/kitty/multiprocessing.py0000664000175000017510000000626214773370543017262 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal # Monkeypatch the stdlib multiprocessing module to work with the embedded python # in kitty, when using the spawn launcher. import os from collections.abc import Callable, Sequence from concurrent.futures import ProcessPoolExecutor from multiprocessing import context, get_all_start_methods, get_context, spawn, util from typing import TYPE_CHECKING, Any, Union from .constants import kitty_exe orig_spawn_passfds = util.spawnv_passfds orig_executable = spawn.get_executable() if TYPE_CHECKING: from typing import SupportsIndex, SupportsInt from _typeshed import ReadableBuffer, SupportsTrunc ArgsType = Sequence[Union[str, ReadableBuffer, SupportsInt, SupportsIndex, SupportsTrunc]] else: ArgsType = Sequence[str] def spawnv_passfds(path: bytes, args: ArgsType, passfds: Sequence[int]) -> int: if '-c' in args: idx = args.index('-c') patched_args = [spawn.get_executable(), '+runpy'] + list(args)[idx + 1:] else: idx = args.index('--multiprocessing-fork') prog = 'from multiprocessing.spawn import spawn_main; spawn_main(%s)' prog %= ', '.join(str(item) for item in args[idx+1:]) patched_args = [spawn.get_executable(), '+runpy', prog] return orig_spawn_passfds(os.fsencode(kitty_exe()), patched_args, passfds) def monkey_patch_multiprocessing() -> None: # Use kitty to run the worker process used by multiprocessing spawn.set_executable(kitty_exe()) util.spawnv_passfds = spawnv_passfds def unmonkey_patch_multiprocessing() -> None: spawn.set_executable(orig_executable) util.spawnv_passfds = orig_spawn_passfds def get_process_pool_executor( prefer_fork: bool = False, max_workers: int | None = None, initializer: Callable[..., None] | None = None, initargs: tuple[Any, ...] = () ) -> ProcessPoolExecutor: if prefer_fork and 'fork' in get_all_start_methods(): ctx: context.DefaultContext | context.ForkContext = get_context('fork') else: monkey_patch_multiprocessing() ctx = get_context() try: return ProcessPoolExecutor(max_workers=max_workers, initializer=initializer, initargs=initargs, mp_context=ctx) except TypeError: return ProcessPoolExecutor(max_workers=max_workers, initializer=initializer, initargs=initargs) def test_spawn() -> None: monkey_patch_multiprocessing() import shutil import subprocess from queue import Empty try: from multiprocessing import get_context ctx = get_context('spawn') q = ctx.Queue() p = ctx.Process(target=q.put, args=('hello',)) p.start() try: x = q.get(timeout=8) except Empty: p.join() rc = p.exitcode if rc == 0: raise TimeoutError('Timed out waiting for response from spawned process') if shutil.which('coredumpctl'): subprocess.run(['sh', '-c', 'echo bt | coredumpctl debug']) raise SystemExit(f'Spawned process exited with return code: {rc}') assert x == 'hello' p.join() finally: unmonkey_patch_multiprocessing() kitty-0.41.1/kitty/notifications.py0000664000175000017510000013514314773370543016705 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2024, Kovid Goyal import os import re from collections import OrderedDict from collections.abc import Callable, Iterator, Sequence from contextlib import suppress from enum import Enum from functools import partial from itertools import count from typing import Any, NamedTuple, Set from weakref import ReferenceType, ref from .constants import cache_dir, config_dir, is_macos, logo_png_file, standard_icon_names, standard_sound_names, supports_window_occlusion from .fast_data_types import ( ESC_OSC, StreamingBase64Decoder, add_timer, base64_decode, current_focused_os_window_id, get_boss, get_options, os_window_is_invisible, ) from .types import run_once from .typing import WindowType from .utils import get_custom_window_icon, log_error, sanitize_control_codes debug_desktop_integration = False # set by NotificationManager def image_type(data: bytes) -> str: if data[:8] == b"\211PNG\r\n\032\n": return 'png' if data[:6] in (b'GIF87a', b'GIF89a'): return 'gif' if data[:2] == b'\xff\xd8': return 'jpeg' return 'unknown' class IconDataCache: def __init__(self, base_cache_dir: str = '', max_cache_size: int = 128 * 1024 * 1024): self.max_cache_size = max_cache_size self.key_map: dict[str, str] = {} self.hash_map: 'OrderedDict[str, Set[str]]' = OrderedDict() self.base_cache_dir = base_cache_dir self.cache_dir = '' self.total_size = 0 import struct self.seed: int = struct.unpack("!Q", os.urandom(8))[0] def _ensure_state(self) -> str: if not self.cache_dir: self.cache_dir = os.path.join(self.base_cache_dir or cache_dir(), 'notifications-icons', str(os.getpid())) os.makedirs(self.cache_dir, exist_ok=True, mode=0o700) b = get_boss() if hasattr(b, 'atexit'): b.atexit.rmtree(self.cache_dir) return self.cache_dir def __del__(self) -> None: if self.cache_dir: import shutil with suppress(FileNotFoundError): shutil.rmtree(self.cache_dir) self.cache_dir = '' def keys(self) -> Iterator[str]: yield from self.key_map.keys() def hash(self, data: bytes) -> str: from kittens.transfer.rsync import xxh128_hash_with_seed d = xxh128_hash_with_seed(data, self.seed) return d.hex() + '.' + image_type(data) def add_icon(self, key: str, data: bytes) -> str: self._ensure_state() data_hash = self.hash(data) path = os.path.join(self.cache_dir, data_hash) if not os.path.exists(path): with open(path, 'wb') as f: f.write(data) self.total_size += len(data) self.hash_map[data_hash] = self.hash_map.pop(data_hash, set()) | {key} # mark this data as being used recently if key: self.key_map[key] = data_hash self.prune() return path def get_icon(self, key: str) -> str: self._ensure_state() data_hash = self.key_map.get(key) if data_hash: self.hash_map[data_hash] = self.hash_map.pop(data_hash, set()) | {key} # mark this data as being used recently return os.path.join(self.cache_dir, data_hash) return '' def clear(self) -> None: while self.hash_map: data_hash, keys = self.hash_map.popitem(False) for key in keys: self.key_map.pop(key, None) self._remove_data_file(data_hash) def prune(self) -> None: self._ensure_state() while self.total_size > self.max_cache_size and self.hash_map: data_hash, keys = self.hash_map.popitem(False) for key in keys: self.key_map.pop(key, None) self._remove_data_file(data_hash) def _remove_data_file(self, data_hash: str) -> None: path = os.path.join(self.cache_dir, data_hash) with suppress(FileNotFoundError): sz = os.path.getsize(path) os.remove(path) self.total_size -= sz def remove_icon(self, key: str) -> None: self._ensure_state() data_hash = self.key_map.pop(key, None) if data_hash: for key in self.hash_map.pop(data_hash, set()): self.key_map.pop(key, None) self._remove_data_file(data_hash) class Urgency(Enum): Low = 0 Normal = 1 Critical = 2 class PayloadType(Enum): unknown = '' title = 'title' body = 'body' query = '?' close = 'close' icon = 'icon' alive = 'alive' buttons = 'buttons' @property def is_text(self) -> bool: return self in (PayloadType.title, PayloadType.body, PayloadType.buttons) class OnlyWhen(Enum): unset = '' always = 'always' unfocused = 'unfocused' invisible = 'invisible' class Action(Enum): focus = 'focus' report = 'report' class DataStore: def __init__(self, max_size: int = 4 * 1024 * 1024) -> None: self.buf: list[bytes] = [] self.current_size = 0 self.max_size = max_size self.truncated = 0 def __call__(self, data: bytes) -> None: if data: if self.current_size > self.max_size: self.truncated += len(data) else: self.current_size += len(data) self.buf.append(data) def finalise(self) -> bytes: return b''.join(self.buf) class EncodedDataStore: def __init__(self, data_store: DataStore) -> None: self.decoder = StreamingBase64Decoder() self.data_store = data_store @property def truncated(self) -> int: return self.data_store.truncated def add_unencoded_data(self, data: str | bytes) -> None: if isinstance(data, str): data = data.encode('utf-8') self.flush_encoded_data() self.data_store(data) def add_base64_data(self, data: str | bytes) -> None: if isinstance(data, str): data = data.encode('ascii') try: decoded = self.decoder.decode(data) except ValueError: log_error('Ignoring invalid base64 encoded data in notification request') else: self.data_store(decoded) def flush_encoded_data(self) -> None: if self.decoder.needs_more_data(): log_error('Received incomplete encoded data for notification request') self.decoder.reset() def finalise(self) -> bytes: self.flush_encoded_data() return self.data_store.finalise() def limit_size(x: str, limit: int = 1024) -> str: if len(x) > limit: x = x[:limit] return x class NotificationCommand: # data received from client and eventually displayed/processed title: str = '' body: str = '' actions: frozenset[Action] = frozenset((Action.focus,)) only_when: OnlyWhen = OnlyWhen.unset urgency: Urgency | None = None icon_data_key: str = '' icon_names: tuple[str, ...] = () application_name: str = '' notification_types: tuple[str, ...] = () timeout: int = -2 buttons: tuple[str, ...] = () sound_name: str = '' # event callbacks on_activation: Callable[['NotificationCommand', int], None] | None = None on_close: Callable[['NotificationCommand'], None] | None = None on_update: Callable[['NotificationCommand', 'NotificationCommand'], None] | None = None # metadata identifier: str = '' done: bool = True channel_id: int = 0 desktop_notification_id: int = -1 close_response_requested: bool | None = None icon_path: str = '' # payload handling current_payload_type: PayloadType = PayloadType.title current_payload_buffer: EncodedDataStore | None = None # desktop integration specific fields created_by_desktop: bool = False activation_token: str = '' def __init__(self, icon_data_cache: 'ReferenceType[IconDataCache]', log: 'Log') -> None: self.icon_data_cache_ref = icon_data_cache self.log = log @property def report_requested(self) -> bool: return Action.report in self.actions @property def focus_requested(self) -> bool: return Action.focus in self.actions def __repr__(self) -> str: fields = {} for x in ('title', 'body', 'identifier', 'actions', 'urgency', 'done'): val = getattr(self, x) if val: fields[x] = val return f'NotificationCommand{fields}' def parse_metadata(self, metadata: str, prev: 'NotificationCommand') -> tuple[PayloadType, bool]: payload_type = PayloadType.title payload_is_encoded = False if metadata: for part in metadata.split(':'): if not part: continue k, v = part.split('=', 1) if k == 'p': try: payload_type = PayloadType(v) except ValueError: payload_type = PayloadType.unknown elif k == 'i': self.identifier = sanitize_id(v) elif k == 'e': payload_is_encoded = v == '1' elif k == 'd': self.done = v != '0' elif k == 'a': for ax in v.split(','): if remove := ax.startswith('-'): ax = ax.lstrip('+-') try: ac = Action(ax) except ValueError: pass else: if remove: self.actions -= {ac} else: self.actions = self.actions.union({ac}) elif k == 'o': with suppress(ValueError): self.only_when = OnlyWhen(v) elif k == 'u': with suppress(Exception): self.urgency = Urgency(int(v)) elif k == 'c': self.close_response_requested = v != '0' elif k == 'g': self.icon_data_key = sanitize_id(v) elif k == 'n': try: self.icon_names += (base64_decode(v).decode('utf-8'),) except Exception: self.log('Ignoring invalid icon name in notification: {v!r}') elif k == 'f': try: self.application_name = base64_decode(v).decode('utf-8') except Exception: self.log('Ignoring invalid application_name in notification: {v!r}') elif k == 't': try: self.notification_types += (base64_decode(v).decode('utf-8'),) except Exception: self.log('Ignoring invalid notification type in notification: {v!r}') elif k == 'w': try: self.timeout = max(-1, int(v)) except Exception: self.log('Ignoring invalid timeout in notification: {v!r}') elif k == 's': try: self.sound_name = base64_decode(v).decode('utf-8') except Exception: self.log('Ignoring invalid sound name in notification: {v!r}') if not prev.done and prev.identifier == self.identifier: self.merge_metadata(prev) return payload_type, payload_is_encoded def merge_metadata(self, prev: 'NotificationCommand') -> None: self.actions = prev.actions.union(self.actions) self.title = prev.title self.body = prev.body if self.only_when is OnlyWhen.unset: self.only_when = prev.only_when if self.urgency is None: self.urgency = prev.urgency if self.close_response_requested is None: self.close_response_requested = prev.close_response_requested if not self.icon_data_key: self.icon_data_key = prev.icon_data_key if prev.icon_names: self.icon_names = prev.icon_names + self.icon_names if not self.application_name: self.application_name = prev.application_name if prev.notification_types: self.notification_types = prev.notification_types + self.notification_types if prev.buttons: self.buttons += prev.buttons if not self.sound_name: self.sound_name = prev.sound_name if self.timeout < -1: self.timeout = prev.timeout self.icon_path = prev.icon_path def create_payload_buffer(self, payload_type: PayloadType) -> EncodedDataStore: self.current_payload_type = payload_type return EncodedDataStore(DataStore()) def set_payload(self, payload_type: PayloadType, payload_is_encoded: bool, payload: str, prev_cmd: 'NotificationCommand') -> None: if prev_cmd.current_payload_type is payload_type: self.current_payload_type = payload_type self.current_payload_buffer = prev_cmd.current_payload_buffer prev_cmd.current_payload_buffer = None else: if prev_cmd.current_payload_buffer: self.current_payload_type = prev_cmd.current_payload_type self.commit_data(prev_cmd.current_payload_buffer.finalise(), prev_cmd.current_payload_buffer.truncated) if self.current_payload_buffer is None: self.current_payload_buffer = self.create_payload_buffer(payload_type) if payload_is_encoded: self.current_payload_buffer.add_base64_data(payload) else: self.current_payload_buffer.add_unencoded_data(payload) def commit_data(self, data: bytes, truncated: int) -> None: if not data: return if self.current_payload_type.is_text: if truncated: text = ' too long, truncated' else: text = data.decode('utf-8', 'replace') if self.current_payload_type is PayloadType.title: self.title = limit_size(self.title + text) elif self.current_payload_type is PayloadType.body: self.body = limit_size(self.body + text) elif self.current_payload_type is PayloadType.icon: if truncated: self.log('Ignoring too long notification icon data') else: icd = self.icon_data_cache_ref() if icd: self.icon_path = icd.add_icon(self.icon_data_key, data) elif self.current_payload_type is PayloadType.buttons: self.buttons += tuple(limit_size(x, 256) for x in text.split('\u2028') if x) self.buttons = self.buttons[:8] def finalise(self) -> None: if self.current_payload_buffer: self.commit_data(self.current_payload_buffer.finalise(), self.current_payload_buffer.truncated) self.current_payload_buffer = None if self.icon_data_key and not self.icon_path: icd = self.icon_data_cache_ref() if icd: self.icon_path = icd.get_icon(self.icon_data_key) if self.title: self.title = sanitize_text(self.title) self.body = sanitize_text(self.body) else: self.title = sanitize_text(self.body) self.body = '' self.urgency = Urgency.Normal if self.urgency is None else self.urgency self.close_response_requested = bool(self.close_response_requested) self.timeout = max(-1, self.timeout) self.sound_name = self.sound_name or 'system' def matches_rule_item(self, location:str, query:str) -> bool: import re pat = re.compile(query) if location == 'type': for x in self.notification_types: if pat.search(x) is not None: return True val = {'title': self.title, 'body': self.body, 'app': self.application_name}[location] return pat.search(val) is not None def matches_rule(self, rule: str) -> bool: if rule == 'all': return True from .search_query_parser import search def get_matches(location: str, query: str, candidates: set['NotificationCommand']) -> set['NotificationCommand']: return {x for x in candidates if x.matches_rule_item(location, query)} try: return self in search(rule, ('title', 'body', 'app', 'type'), {self}, get_matches) except Exception as e: self.log(f'Ignoring invalid filter_notification rule: {rule} with error: {e}') return False class DesktopIntegration: supports_close_events: bool = True supports_body: bool = True supports_buttons: bool = True supports_sound: bool = True supports_sound_names: str = 'xdg-names' supports_timeout_natively: bool = True def __init__(self, notification_manager: 'NotificationManager'): self.notification_manager = notification_manager self.initialize() def initialize(self) -> None: pass def query_live_notifications(self, channel_id: int, identifier: str) -> None: raise NotImplementedError('Implement me in subclass') def close_notification(self, desktop_notification_id: int) -> bool: raise NotImplementedError('Implement me in subclass') def notify(self, nc: NotificationCommand, existing_desktop_notification_id: int | None) -> int: raise NotImplementedError('Implement me in subclass') def on_new_version_notification_activation(self, cmd: NotificationCommand, which: int) -> None: from .update_check import notification_activated notification_activated() def payload_type_supported(self, x: PayloadType) -> bool: if x is PayloadType.body and not self.supports_body: return False if x is PayloadType.buttons and not self.supports_buttons: return False return True def query_response(self, identifier: str) -> str: actions = ','.join(x.value for x in Action) when = ','.join(x.value for x in OnlyWhen if x.value) urgency = ','.join(str(x.value) for x in Urgency) i = f'i={identifier or "0"}:' p = ','.join(x.value for x in PayloadType if x.value and self.payload_type_supported(x)) c = ':c=1' if self.supports_close_events else '' s = 'system,silent,' + ','.join(sorted(standard_sound_names)) return f'99;{i}p=?;a={actions}:o={when}:u={urgency}:p={p}{c}:w=1:s={s}' class MacOSNotificationCategory(NamedTuple): id: str buttons: tuple[str, ...] = () button_ids: tuple[str, ...] = () class MacOSIntegration(DesktopIntegration): supports_close_events: bool = False supports_sound_names: str = '' supports_timeout_natively: bool = False def initialize(self) -> None: from .fast_data_types import cocoa_set_notification_activated_callback self.id_counter = count(start=1) self.live_notification_queries: list[tuple[int, str]] = [] self.failed_icons: OrderedDict[str, bool] = OrderedDict() self.icd_key_prefix = os.urandom(16).hex() self.category_cache: OrderedDict[tuple[str, ...], MacOSNotificationCategory] = OrderedDict() self.category_id_counter = count(start=2) self.buttons_id_counter = count(start=1) self.default_category = MacOSNotificationCategory('1') self.current_categories: frozenset[MacOSNotificationCategory] = frozenset() cocoa_set_notification_activated_callback(self.notification_activated) def query_live_notifications(self, channel_id: int, identifier: str) -> None: from .fast_data_types import cocoa_live_delivered_notifications if not cocoa_live_delivered_notifications(): self.notification_manager.send_live_response(channel_id, identifier, ()) else: self.live_notification_queries.append((channel_id, identifier)) def close_notification(self, desktop_notification_id: int) -> bool: from .fast_data_types import cocoa_remove_delivered_notification close_succeeded = cocoa_remove_delivered_notification(str(desktop_notification_id)) if debug_desktop_integration: log_error(f'Close request for {desktop_notification_id=} {"succeeded" if close_succeeded else "failed"}') return close_succeeded def get_icon_for_name(self, name: str) -> str: from .fast_data_types import cocoa_bundle_image_as_png if name in self.failed_icons: return '' image_type, image_name = 1, name if sic := standard_icon_names.get(name): image_name = sic[1] image_type = 2 icd = self.notification_manager.icon_data_cache icd_key = self.icd_key_prefix + name ans = icd.get_icon(icd_key) if ans: return ans try: data = cocoa_bundle_image_as_png(image_name, image_type=image_type) except Exception as err: if debug_desktop_integration: self.notification_manager.log(f'Failed to get icon for {name} with error: {err}') self.failed_icons[name] = True if len(self.failed_icons) > 256: self.failed_icons.popitem(False) else: return icd.add_icon(icd_key, data) return '' def category_for_notification(self, nc: NotificationCommand) -> MacOSNotificationCategory: key = nc.buttons if not key: return self.default_category if ans := self.category_cache.get(key): self.category_cache.pop(key) self.category_cache[key] = ans return ans ans = self.category_cache[key] = MacOSNotificationCategory( str(next(self.category_id_counter)), nc.buttons, tuple(str(next(self.buttons_id_counter)) for x in nc.buttons) ) if len(self.category_cache) > 32: self.category_cache.popitem(False) return ans def notify(self, nc: NotificationCommand, existing_desktop_notification_id: int | None) -> int: desktop_notification_id = existing_desktop_notification_id or next(self.id_counter) from .fast_data_types import cocoa_send_notification # If the body is not set macos makes the title the body and uses # "kitty" as the title. So use a single space for the body in this # case. Although https://developer.apple.com/documentation/usernotifications/unnotificationcontent/body?language=objc # says printf style strings are stripped this does not actually happen, so dont double % for %% escaping. body = (nc.body or ' ') assert nc.urgency is not None image_path = '' if nc.icon_names: for name in nc.icon_names: if image_path := self.get_icon_for_name(name): break image_path = image_path or nc.icon_path if not image_path and nc.application_name: image_path = self.get_icon_for_name(nc.application_name) category = self.category_for_notification(nc) categories = tuple(self.category_cache.values()) sc = frozenset(categories) if sc == self.current_categories: categories = () else: self.current_categories = sc cocoa_send_notification( nc.application_name or 'kitty', str(desktop_notification_id), nc.title, body, category=category, categories=categories, image_path=image_path, urgency=nc.urgency.value, muted=nc.sound_name == 'silent' or nc.sound_name in standard_sound_names, ) return desktop_notification_id def notification_activated(self, event: str, ident: str, button_id: str) -> None: if event == 'live': live_ids = tuple(int(x) for x in ident.split(',') if x) if debug_desktop_integration: log_error(f'Live notifications: {live_ids}') self.notification_manager.purge_dead_notifications(live_ids) self.live_notification_queries, queries = [], self.live_notification_queries for channel_id, req_id in queries: self.notification_manager.send_live_response(channel_id, req_id, live_ids) return if debug_desktop_integration: log_error(f'Notification {ident=} {event=} {button_id=}') try: desktop_notification_id = int(ident) except Exception: log_error(f'Got unexpected notification activated event with id: {ident!r} from cocoa') return if event == 'created': n = self.notification_manager.notification_created(desktop_notification_id) # so that we purge dead notifications, check for live notifications # after a few seconds, cant check right away as cocoa does not # report the created notification as live. add_timer(self.check_live_delivered_notifications, 5.0, False) if n and n.sound_name in standard_sound_names: from .fast_data_types import cocoa_play_system_sound_by_id_async cocoa_play_system_sound_by_id_async(standard_sound_names[n.sound_name][1]) elif event == 'activated': self.notification_manager.notification_activated(desktop_notification_id, 0) elif event == 'creation_failed': self.notification_manager.notification_closed(desktop_notification_id) elif event == 'closed': # sadly Crapple never delivers these events self.notification_manager.notification_closed(desktop_notification_id) elif event == 'button': if n := self.notification_manager.in_progress_notification_commands.get(desktop_notification_id): if debug_desktop_integration: log_error('Button matches notification:', n) for c in self.current_categories: if c.buttons == n.buttons and button_id in c.button_ids: if debug_desktop_integration: log_error('Button number:', c.button_ids.index(button_id) + 1) self.notification_manager.notification_activated(desktop_notification_id, c.button_ids.index(button_id) + 1) break else: if debug_desktop_integration: log_error('No category found with buttons:', n.buttons) log_error('Current categories:', self.current_categories) def check_live_delivered_notifications(self, *a: object) -> None: from .fast_data_types import cocoa_live_delivered_notifications cocoa_live_delivered_notifications() class FreeDesktopIntegration(DesktopIntegration): supports_body_markup: bool = True def initialize(self) -> None: from .fast_data_types import dbus_set_notification_callback dbus_set_notification_callback(self.dispatch_event_from_desktop) # map the id returned by the notification daemon to the # desktop_notification_id we use for the notification self.dbus_to_desktop: 'OrderedDict[int, int]' = OrderedDict() self.desktop_to_dbus: dict[int, int] = {} def query_live_notifications(self, channel_id: int, identifier: str) -> None: self.notification_manager.send_live_response(channel_id, identifier, tuple(self.desktop_to_dbus)) def close_notification(self, desktop_notification_id: int) -> bool: from .fast_data_types import dbus_close_notification close_succeeded = False if dbus_id := self.get_dbus_notification_id(desktop_notification_id, 'close_request'): close_succeeded = dbus_close_notification(dbus_id) if debug_desktop_integration: log_error(f'Close request for {desktop_notification_id=} {"succeeded" if close_succeeded else "failed"}') return close_succeeded def get_desktop_notification_id(self, dbus_notification_id: int, event: str) -> int | None: q = self.dbus_to_desktop.get(dbus_notification_id) if q is None: if debug_desktop_integration: log_error(f'Could not find desktop_notification_id for {dbus_notification_id=} for event {event}') return q def get_dbus_notification_id(self, desktop_notification_id: int, event: str) ->int | None: q = self.desktop_to_dbus.get(desktop_notification_id) if q is None: if debug_desktop_integration: log_error(f'Could not find dbus_notification_id for {desktop_notification_id=} for event {event}') return q def created(self, dbus_notification_id: int, desktop_notification_id: int) -> None: self.dbus_to_desktop[desktop_notification_id] = dbus_notification_id self.desktop_to_dbus[dbus_notification_id] = desktop_notification_id if len(self.dbus_to_desktop) > 128: k, v = self.dbus_to_desktop.popitem(False) self.desktop_to_dbus.pop(v, None) if n := self.notification_manager.notification_created(dbus_notification_id): # self.supports_sound does not tell us if the notification server # supports named sounds or not so we play the named sound # ourselves and tell the server to mute any sound it might play. if n.sound_name not in ('system', 'silent'): sn = standard_sound_names[n.sound_name][0] if n.sound_name in standard_sound_names else n.sound_name from .fast_data_types import play_desktop_sound_async play_desktop_sound_async(sn, event_id='desktop notification') def dispatch_event_from_desktop(self, event_type: str, dbus_notification_id: int, extra: int | str) -> None: if event_type == 'capabilities': capabilities = frozenset(str(extra).splitlines()) self.supports_body = 'body' in capabilities self.supports_buttons = 'actions' in capabilities self.supports_body_markup = 'body-markup' in capabilities self.supports_sound = 'sound' in capabilities if debug_desktop_integration: log_error('Got notification server capabilities:', capabilities) return if debug_desktop_integration: log_error(f'Got notification event from desktop: {event_type=} {dbus_notification_id=} {extra=}') if event_type == 'created': self.created(dbus_notification_id, int(extra)) return if desktop_notification_id := self.get_desktop_notification_id(dbus_notification_id, event_type): if event_type == 'activation_token': self.notification_manager.notification_activation_token_received(desktop_notification_id, str(extra)) elif event_type == 'activated': button = 0 if extra == 'default' else int(extra) self.notification_manager.notification_activated(desktop_notification_id, button) elif event_type == 'closed': self.notification_manager.notification_closed(desktop_notification_id) def notify(self, nc: NotificationCommand, existing_desktop_notification_id: int | None) -> int: from .fast_data_types import dbus_send_notification from .xdg import icon_exists, icon_for_appname app_icon = '' if nc.icon_names: for name in nc.icon_names: if sn := standard_icon_names.get(name): app_icon = sn[0] break if icon_exists(name): app_icon = name break if not app_icon: app_icon = nc.icon_path or nc.icon_names[0] else: app_icon = nc.icon_path or icon_for_appname(nc.application_name) if not app_icon: app_icon = get_custom_window_icon()[1] or logo_png_file body = nc.body if self.supports_body_markup: body = body.replace('<', '<\u200c').replace('&', '&\u200c') # prevent HTML markup from being recognized assert nc.urgency is not None replaces_dbus_id = 0 if existing_desktop_notification_id: replaces_dbus_id = self.get_dbus_notification_id(existing_desktop_notification_id, 'notify') or 0 actions = {'default': ' '} # dbus requires string to not be empty for i, b in enumerate(nc.buttons): actions[str(i+1)] = b desktop_notification_id = dbus_send_notification( app_name=nc.application_name or 'kitty', app_icon=app_icon, title=nc.title, body=body, actions=actions, timeout=nc.timeout, urgency=nc.urgency.value, replaces=replaces_dbus_id, category=(nc.notification_types or ('',))[0], muted=nc.sound_name == 'silent' or nc.sound_name != 'system', ) if debug_desktop_integration: log_error(f'Requested creation of notification with {desktop_notification_id=}') if existing_desktop_notification_id and replaces_dbus_id: self.dbus_to_desktop.pop(replaces_dbus_id, None) self.desktop_to_dbus.pop(existing_desktop_notification_id, None) return desktop_notification_id class UIState(NamedTuple): has_keyboard_focus: bool is_visible: bool class Channel: def window_for_id(self, channel_id: int) -> WindowType | None: boss = get_boss() if channel_id: return boss.window_id_map.get(channel_id) return boss.active_window def ui_state(self, channel_id: int) -> UIState: has_focus = is_visible = False boss = get_boss() if w := self.window_for_id(channel_id): os_window_active = w.os_window_id == current_focused_os_window_id() has_focus = w.is_active and os_window_active is_visible = os_window_active if supports_window_occlusion(): is_visible = not os_window_is_invisible(w.os_window_id) is_visible = is_visible and w.tabref() is boss.active_tab and w.is_visible_in_layout return UIState(has_focus, is_visible) def send(self, channel_id: int, osc_escape_code: str) -> bool: if w := self.window_for_id(channel_id): if not w.destroyed: w.screen.send_escape_code_to_child(ESC_OSC, osc_escape_code) return True return False def focus(self, channel_id: int, activation_token: str) -> None: if debug_desktop_integration: log_error(f'Focusing window: {channel_id} with activation_token: {activation_token}') boss = get_boss() if w := self.window_for_id(channel_id): boss.set_active_window(w, switch_os_window_if_needed=True, activation_token=activation_token) sanitize_text = sanitize_control_codes @run_once def sanitize_identifier_pat() -> 're.Pattern[str]': return re.compile(r'[^a-zA-Z0-9-_+.]+') def sanitize_id(v: str) -> str: return sanitize_identifier_pat().sub('', v)[:512] class Log: def __call__(self, *a: Any, **kw: str) -> None: log_error(*a, **kw) class NotificationManager: def __init__( self, desktop_integration: MacOSIntegration | FreeDesktopIntegration | None = None, channel: Channel = Channel(), log: Log = Log(), debug: bool = False, base_cache_dir: str = '', ): global debug_desktop_integration debug_desktop_integration = debug if desktop_integration is None: self.desktop_integration = MacOSIntegration(self) if is_macos else FreeDesktopIntegration(self) else: self.desktop_integration = desktop_integration self.channel = channel self.base_cache_dir = base_cache_dir self.log = log self.icon_data_cache = IconDataCache(base_cache_dir=self.base_cache_dir) script_path = os.path.join(config_dir, 'notifications.py') self.filter_script: Callable[[NotificationCommand], bool] = lambda nc: False if os.path.exists(script_path): import runpy try: m = runpy.run_path(script_path) self.filter_script = m['main'] except Exception as e: self.log(f'Failed to load {script_path} with error: {e}') self.reset() def reset(self) -> None: self.icon_data_cache.clear() self.in_progress_notification_commands: 'OrderedDict[int, NotificationCommand]' = OrderedDict() self.in_progress_notification_commands_by_client_id: dict[str, NotificationCommand] = {} self.pending_commands: dict[int, NotificationCommand] = {} def notification_created(self, desktop_notification_id: int) -> NotificationCommand | None: if n := self.in_progress_notification_commands.get(desktop_notification_id): n.created_by_desktop = True if n.timeout > 0 and not self.desktop_integration.supports_timeout_natively: add_timer(partial(self.expire_notification, desktop_notification_id, id(n)), n.timeout / 1000, False) return n return None def notification_activation_token_received(self, desktop_notification_id: int, token: str) -> None: if n := self.in_progress_notification_commands.get(desktop_notification_id): n.activation_token = token def notification_activated(self, desktop_notification_id: int, button: int) -> None: if n := self.in_progress_notification_commands.get(desktop_notification_id): if not n.close_response_requested: self.purge_notification(n) if n.focus_requested: self.channel.focus(n.channel_id, n.activation_token) if n.report_requested: self.channel.send(n.channel_id, f'99;i={n.identifier or "0"};{button or ""}') if n.on_activation: try: n.on_activation(n, button) except Exception as e: self.log('Notification on_activation handler failed with error:', e) def notification_replaced(self, old_cmd: NotificationCommand, new_cmd: NotificationCommand) -> None: if old_cmd.desktop_notification_id != new_cmd.desktop_notification_id: self.in_progress_notification_commands.pop(old_cmd.desktop_notification_id, None) if old_cmd.on_update is not None: try: old_cmd.on_update(old_cmd, new_cmd) except Exception as e: self.log('Notification on_update handler failed with error:', e) def notification_closed(self, desktop_notification_id: int) -> None: if n := self.in_progress_notification_commands.get(desktop_notification_id): self.purge_notification(n) if n.close_response_requested and self.desktop_integration.supports_close_events: self.send_closed_response(n.channel_id, n.identifier) if n.on_close is not None: try: n.on_close(n) except Exception as e: self.log('Notification on_close handler failed with error:', e) def create_notification_cmd(self) -> NotificationCommand: return NotificationCommand(ref(self.icon_data_cache), self.log) def send_test_notification(self) -> None: boss = get_boss() if w := boss.active_window: from time import monotonic cmd = self.create_notification_cmd() now = monotonic() cmd.title = f'Test {now}' cmd.body = f'At: {now}' cmd.on_activation = print self.notify_with_command(cmd, w.id) def send_new_version_notification(self, version: str) -> None: cmd = self.create_notification_cmd() cmd.title = 'kitty update available!' cmd.body = f'kitty version {version} released' cmd.on_activation = self.desktop_integration.on_new_version_notification_activation self.notify_with_command(cmd, 0) def is_notification_allowed(self, cmd: NotificationCommand, channel_id: int, apply_filter_rules: bool = True) -> bool: if cmd.only_when is not OnlyWhen.always and cmd.only_when is not OnlyWhen.unset: ui_state = self.channel.ui_state(channel_id) if ui_state.has_keyboard_focus: return False if cmd.only_when is OnlyWhen.invisible and ui_state.is_visible: return False return True @property def filter_rules(self) -> Iterator[str]: return iter(get_options().filter_notification.keys()) def is_notification_filtered(self, cmd: NotificationCommand) -> bool: if self.filter_script(cmd): self.log(f'Notification {cmd.title!r} filtered out by script') return True for rule in self.filter_rules: if cmd.matches_rule(rule): self.log(f'Notification {cmd.title!r} filtered out by filter_notification rule: {rule}') return True return False def notify_with_command(self, cmd: NotificationCommand, channel_id: int) -> int | None: cmd.channel_id = channel_id cmd.finalise() if not cmd.title or not self.is_notification_allowed(cmd, channel_id) or self.is_notification_filtered(cmd): return None existing_desktop_notification_id: int | None = None existing_cmd = self.in_progress_notification_commands_by_client_id.get(cmd.identifier) if cmd.identifier else None if existing_cmd: existing_desktop_notification_id = existing_cmd.desktop_notification_id desktop_notification_id = self.desktop_integration.notify(cmd, existing_desktop_notification_id) self.register_in_progress_notification(cmd, desktop_notification_id) if existing_cmd: self.notification_replaced(existing_cmd, cmd) if not self.desktop_integration.supports_close_events and cmd.close_response_requested: self.send_closed_response(channel_id, cmd.identifier, untracked=True) return desktop_notification_id def expire_notification(self, desktop_notification_id: int, command_id: int, timer_id: int) -> None: if n := self.in_progress_notification_commands.get(desktop_notification_id): if id(n) == command_id: self.desktop_integration.close_notification(desktop_notification_id) def register_in_progress_notification(self, cmd: NotificationCommand, desktop_notification_id: int) -> None: cmd.desktop_notification_id = desktop_notification_id self.in_progress_notification_commands[desktop_notification_id] = cmd if cmd.identifier: self.in_progress_notification_commands_by_client_id[cmd.identifier] = cmd if len(self.in_progress_notification_commands) > 128: _, cmd = self.in_progress_notification_commands.popitem(False) self.in_progress_notification_commands_by_client_id.pop(cmd.identifier, None) def parse_notification_cmd( self, prev_cmd: NotificationCommand, channel_id: int, raw: str ) -> NotificationCommand | None: metadata, payload = raw.partition(';')[::2] cmd = self.create_notification_cmd() try: payload_type, payload_is_encoded = cmd.parse_metadata(metadata, prev_cmd) except Exception: self.log('Malformed metadata section in OSC 99: ' + metadata) return None if payload_type is PayloadType.query: self.channel.send(channel_id, self.desktop_integration.query_response(cmd.identifier)) return None if payload_type is PayloadType.alive: if cmd.identifier: self.desktop_integration.query_live_notifications(channel_id, cmd.identifier) return None if payload_type is PayloadType.close: if cmd.identifier: to_close = self.in_progress_notification_commands_by_client_id.get(cmd.identifier) if to_close: if not self.desktop_integration.close_notification(to_close.desktop_notification_id): if to_close.close_response_requested: self.send_closed_response(to_close.channel_id, to_close.identifier) self.purge_notification(to_close) return None if payload_type is PayloadType.unknown: self.log(f'OSC 99: unknown payload type: {payload_type}, ignoring payload') payload = '' cmd.set_payload(payload_type, payload_is_encoded, payload, prev_cmd) return cmd def send_closed_response(self, channel_id: int, client_id: str, untracked: bool = False) -> None: payload = 'untracked' if untracked else '' self.channel.send(channel_id, f'99;i={client_id}:p={PayloadType.close.value};{payload}') def send_live_response(self, channel_id: int, client_id: str, live_desktop_ids: Sequence[int]) -> None: ids = [] for desktop_notification_id in live_desktop_ids: if n := self.in_progress_notification_commands.get(desktop_notification_id): if n.identifier and n.channel_id == channel_id: ids.append(n.identifier) self.channel.send(channel_id, f'99;i={client_id}:p={PayloadType.alive.value};{",".join(ids)}') def purge_dead_notifications(self, live_desktop_ids: Sequence[int]) -> None: for d in set(self.in_progress_notification_commands) - set(live_desktop_ids): if debug_desktop_integration: log_error(f'Purging dead notification {d} from list of live notifications:', live_desktop_ids) self.purge_notification(self.in_progress_notification_commands[d]) def purge_notification(self, cmd: NotificationCommand) -> None: self.in_progress_notification_commands_by_client_id.pop(cmd.identifier, None) self.in_progress_notification_commands.pop(cmd.desktop_notification_id, None) def handle_notification_cmd(self, channel_id: int, osc_code: int, raw: str) -> None: if osc_code == 99: cmd = self.pending_commands.pop(channel_id, None) or self.create_notification_cmd() q = self.parse_notification_cmd(cmd, channel_id, raw) if q is not None: if q.done: self.notify_with_command(q, channel_id) else: self.pending_commands[channel_id] = q elif osc_code == 9: n = self.create_notification_cmd() n.title = raw self.notify_with_command(n, channel_id) elif osc_code == 777: n = self.create_notification_cmd() parts = raw.split(';', 1) n.title, n.body = parts[0], (parts[1] if len(parts) > 1 else '') self.notify_with_command(n, channel_id) def close_notification(self, desktop_notification_id: int) -> None: self.desktop_integration.close_notification(desktop_notification_id) def cleanup(self) -> None: del self.icon_data_cache kitty-0.41.1/kitty/open_actions.py0000664000175000017510000002217614773370543016516 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import os import posixpath import shlex from collections.abc import Iterable, Iterator from contextlib import suppress from typing import Any, NamedTuple, cast from urllib.parse import ParseResult, unquote, urlparse from .conf.utils import KeyAction, to_cmdline_implementation from .constants import config_dir from .fast_data_types import get_options from .guess_mime_type import guess_type from .options.utils import ActionAlias, MapType, resolve_aliases_and_parse_actions from .types import run_once from .typing import MatchType from .utils import expandvars, get_editor, log_error, resolved_shell class MatchCriteria(NamedTuple): type: MatchType value: str class OpenAction(NamedTuple): match_criteria: tuple[MatchCriteria, ...] actions: tuple[KeyAction, ...] def parse(lines: Iterable[str]) -> Iterator[OpenAction]: match_criteria: list[MatchCriteria] = [] raw_actions: list[str] = [] alias_map: dict[str, list[ActionAlias]] = {} entries = [] for line in lines: line = line.strip() if line.startswith('#'): continue if not line: if match_criteria and raw_actions: entries.append((tuple(match_criteria), tuple(raw_actions))) match_criteria = [] raw_actions = [] continue parts = line.split(maxsplit=1) if len(parts) != 2: continue key, rest = parts key = key.lower() if key == 'action': raw_actions.append(rest) elif key in ('mime', 'ext', 'protocol', 'file', 'path', 'url', 'fragment_matches'): if key != 'url': rest = rest.lower() match_criteria.append(MatchCriteria(cast(MatchType, key), rest)) elif key == 'action_alias': try: alias_name, alias_val = rest.split(maxsplit=1) except Exception: continue alias_map[alias_name] = [ActionAlias(alias_name, alias_val)] else: log_error(f'Ignoring malformed open actions line: {line}') if match_criteria and raw_actions: entries.append((tuple(match_criteria), tuple(raw_actions))) with to_cmdline_implementation.filter_env_vars( 'URL', 'FILE_PATH', 'FILE', 'FRAGMENT', 'URL_PATH', 'NETLOC', EDITOR=shlex.join(get_editor()), SHELL=resolved_shell(get_options())[0] ): for (mc, action_defns) in entries: actions: list[KeyAction] = [] for defn in action_defns: actions.extend(resolve_aliases_and_parse_actions(defn, alias_map, MapType.OPEN_ACTION)) yield OpenAction(mc, tuple(actions)) def url_matches_criterion(purl: 'ParseResult', url: str, unquoted_path: str, mc: MatchCriteria) -> bool: if mc.type == 'url': import re try: pat = re.compile(mc.value) except re.error: return False return pat.search(unquote(url)) is not None if mc.type == 'mime': import fnmatch mt = guess_type(unquoted_path, allow_filesystem_access=purl.scheme in ('', 'file')) if not mt: return False mt = mt.lower() for mpat in mc.value.split(','): mpat = mpat.strip() with suppress(Exception): if fnmatch.fnmatchcase(mt, mpat): return True return False if mc.type == 'ext': if not purl.path: return False path = unquoted_path.lower() for ext in mc.value.split(','): ext = ext.strip() if path.endswith(f'.{ext}'): return True return False if mc.type == 'protocol': protocol = (purl.scheme or 'file').lower() for key in mc.value.split(','): if key.strip() == protocol: return True return False if mc.type == 'fragment_matches': import re try: pat = re.compile(mc.value) except re.error: return False return pat.search(unquote(purl.fragment)) is not None if mc.type == 'path': import fnmatch try: return fnmatch.fnmatchcase(unquoted_path.lower(), mc.value) except Exception: return False if mc.type == 'file': import fnmatch try: fname = posixpath.basename(unquoted_path) except Exception: return False try: return fnmatch.fnmatchcase(fname.lower(), mc.value) except Exception: return False def url_matches_criteria(purl: 'ParseResult', url: str, unquoted_path: str, criteria: Iterable[MatchCriteria]) -> bool: for x in criteria: try: if not url_matches_criterion(purl, url, unquoted_path, x): return False except Exception: return False return True def actions_for_url_from_list(url: str, actions: Iterable[OpenAction]) -> Iterator[KeyAction]: try: purl = urlparse(url) except Exception: return path = unquote(purl.path) up = purl.path netloc = unquote(purl.netloc) if purl.netloc else '' if purl.query: up += f'?{purl.query}' frag = unquote(purl.fragment) if purl.fragment else '' if frag: up += f'#{purl.fragment}' env = { 'URL': url, 'FILE_PATH': path, 'URL_PATH': up, 'FILE': posixpath.basename(path), 'FRAGMENT': frag, 'NETLOC': netloc, } def expand(x: Any) -> Any: as_bytes = isinstance(x, bytes) if as_bytes: x = x.decode('utf-8') if isinstance(x, str): ans = expandvars(x, env, fallback_to_os_env=False) if as_bytes: return ans.encode('utf-8') return ans return x for action in actions: if url_matches_criteria(purl, url, path, action.match_criteria): for ac in action.actions: yield ac._replace(args=tuple(map(expand, ac.args))) return actions_cache: dict[str, tuple[os.stat_result, tuple[OpenAction, ...]]] = {} def load_actions_from_path(path: str) -> tuple[OpenAction, ...]: try: st = os.stat(path) except OSError: return () x = actions_cache.get(path) if x is None or x[0].st_mtime != st.st_mtime: with open(path) as f: actions_cache[path] = st, tuple(parse(f)) else: return x[1] return actions_cache[path][1] def load_open_actions() -> tuple[OpenAction, ...]: return load_actions_from_path(os.path.join(config_dir, 'open-actions.conf')) def load_launch_actions() -> tuple[OpenAction, ...]: return load_actions_from_path(os.path.join(config_dir, 'launch-actions.conf')) def clear_caches() -> None: actions_cache.clear() @run_once def default_open_actions() -> tuple[OpenAction, ...]: return tuple(parse('''\ # Open kitty HTML docs links protocol kitty+doc action show_kitty_doc $URL_PATH '''.splitlines())) @run_once def default_launch_actions() -> tuple[OpenAction, ...]: return tuple(parse('''\ # Open script files. Change confirm-always to confirm-never or confirm-if-needed to # disable confirmation for all or executable files respectively. protocol file ext sh,command,tool action launch --hold --type=os-window kitten __shebang__ confirm-always $FILE_PATH $SHELL # Open shell specific script files protocol file ext fish,bash,zsh action launch --hold --type=os-window kitten __shebang__ confirm-always $FILE_PATH __ext__ # Open directories protocol file mime inode/directory action launch --type=os-window --cwd -- $FILE_PATH # Open executable file. Remove kitten __confirm_and_run_exe__ to execute # without confirmation. protocol file mime inode/executable,application/vnd.microsoft.portable-executable action launch --hold --type=os-window -- kitten __confirm_and_run_exe__ $FILE_PATH # Open text files without fragments in the editor protocol file mime text/* action launch --type=os-window -- $EDITOR -- $FILE_PATH # Open image files with icat protocol file mime image/* action launch --type=os-window kitten icat --hold -- $FILE_PATH # Open ssh URLs with ssh command protocol ssh action launch --type=os-window ssh -- $URL '''.splitlines())) def actions_for_url(url: str, actions_spec: str | None = None) -> Iterator[KeyAction]: if actions_spec is None: actions = load_open_actions() else: actions = tuple(parse(actions_spec.splitlines())) found = False for action in actions_for_url_from_list(url, actions): found = True yield action if not found: yield from actions_for_url_from_list(url, default_open_actions()) def actions_for_launch(url: str) -> Iterator[KeyAction]: # Custom launch actions using kitty URL scheme needs to be prefixed with `kitty:///launch/` if url.startswith('kitty://') and not url.startswith('kitty:///launch/'): return found = False for action in actions_for_url_from_list(url, load_launch_actions()): found = True yield action if not found: yield from actions_for_url_from_list(url, default_launch_actions()) kitty-0.41.1/kitty/options/0000775000175000017510000000000014773370543015146 5ustar nileshnileshkitty-0.41.1/kitty/options/__init__.py0000664000175000017510000000000014773370543017245 0ustar nileshnileshkitty-0.41.1/kitty/options/definition.py0000664000175000017510000042662614773370543017670 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal # After editing this file run ./gen-config.py to apply the changes import string from kitty.conf.types import Action, Definition from kitty.constants import website_url from kitty.options.utils import pointer_shape_names definition = Definition( 'kitty', Action('map', 'parse_map', {'keyboard_modes': 'KeyboardModeMap', 'alias_map': 'AliasMap'}, ['KeyDefinition', 'kitty.fast_data_types.SingleKey']), Action('mouse_map', 'parse_mouse_map', {'mousemap': 'MouseMap'}, ['MouseMapping']), has_color_table=True, ) definition.add_deprecation('deprecated_hide_window_decorations_aliases', 'x11_hide_window_decorations', 'macos_hide_titlebar') definition.add_deprecation('deprecated_macos_show_window_title_in_menubar_alias', 'macos_show_window_title_in_menubar') definition.add_deprecation('deprecated_send_text', 'send_text') definition.add_deprecation('deprecated_adjust_line_height', 'adjust_line_height', 'adjust_column_width', 'adjust_baseline') agr = definition.add_group egr = definition.end_group opt = definition.add_option map = definition.add_map mma = definition.add_mouse_map # fonts {{{ agr('fonts', 'Fonts', ''' kitty has very powerful font management. You can configure individual font faces and even specify special fonts for particular characters. ''') opt('font_family', 'monospace', option_type='parse_font_spec', long_text=''' You can specify different fonts for the bold/italic/bold-italic variants. The easiest way to select fonts is to run the ``kitten choose-fonts`` command which will present a nice UI for you to select the fonts you want with previews and support for selecting variable fonts and font features. If you want to learn to select fonts manually, read the :ref:`font specification syntax `. ''' ) opt('bold_font', 'auto', option_type='parse_font_spec') opt('italic_font', 'auto', option_type='parse_font_spec') opt('bold_italic_font', 'auto', option_type='parse_font_spec') opt('font_size', '11.0', option_type='to_font_size', ctype='double', long_text='Font size (in pts).' ) opt('force_ltr', 'no', option_type='to_bool', ctype='bool', long_text=''' kitty does not support BIDI (bidirectional text), however, for RTL scripts, words are automatically displayed in RTL. That is to say, in an RTL script, the words "HELLO WORLD" display in kitty as "WORLD HELLO", and if you try to select a substring of an RTL-shaped string, you will get the character that would be there had the string been LTR. For example, assuming the Hebrew word ירושלים, selecting the character that on the screen appears to be ם actually writes into the selection buffer the character י. kitty's default behavior is useful in conjunction with a filter to reverse the word order, however, if you wish to manipulate RTL glyphs, it can be very challenging to work with, so this option is provided to turn it off. Furthermore, this option can be used with the command line program :link:`GNU FriBidi ` to get BIDI support, because it will force kitty to always treat the text as LTR, which FriBidi expects for terminals. ''' ) opt('+symbol_map', 'U+E0A0-U+E0A3,U+E0C0-U+E0C7 PowerlineSymbols', option_type='symbol_map', add_to_default=False, long_text=''' Map the specified Unicode codepoints to a particular font. Useful if you need special rendering for some symbols, such as for Powerline. Avoids the need for patched fonts. Each Unicode code point is specified in the form ``U+``. You can specify multiple code points, separated by commas and ranges separated by hyphens. This option can be specified multiple times. The syntax is:: symbol_map codepoints Font Family Name ''' ) opt('+narrow_symbols', 'U+E0A0-U+E0A3,U+E0C0-U+E0C7 1', option_type='narrow_symbols', add_to_default=False, long_text=''' Usually, for Private Use Unicode characters and some symbol/dingbat characters, if the character is followed by one or more spaces, kitty will use those extra cells to render the character larger, if the character in the font has a wide aspect ratio. Using this option you can force kitty to restrict the specified code points to render in the specified number of cells (defaulting to one cell). This option can be specified multiple times. The syntax is:: narrow_symbols codepoints [optionally the number of cells] ''' ) opt('disable_ligatures', 'never', option_type='disable_ligatures', ctype='int', long_text=''' Choose how you want to handle multi-character ligatures. The default is to always render them. You can tell kitty to not render them when the cursor is over them by using :code:`cursor` to make editing easier, or have kitty never render them at all by using :code:`always`, if you don't like them. The ligature strategy can be set per-window either using the kitty remote control facility or by defining shortcuts for it in :file:`kitty.conf`, for example:: map alt+1 disable_ligatures_in active always map alt+2 disable_ligatures_in all never map alt+3 disable_ligatures_in tab cursor Note that this refers to programming ligatures, typically implemented using the :code:`calt` OpenType feature. For disabling general ligatures, use the :opt:`font_features` option. ''' ) opt('+font_features', 'none', option_type='font_features', ctype='!font_features', add_to_default=False, long_text=''' Choose exactly which OpenType features to enable or disable. Note that for the main fonts, features can be specified when selecting the font using the choose-fonts kitten. This setting is useful for fallback fonts. Some fonts might have features worthwhile in a terminal. For example, Fira Code includes a discretionary feature, :code:`zero`, which in that font changes the appearance of the zero (0), to make it more easily distinguishable from Ø. Fira Code also includes other discretionary features known as Stylistic Sets which have the tags :code:`ss01` through :code:`ss20`. For the exact syntax to use for individual features, see the :link:`HarfBuzz documentation `. Note that this code is indexed by PostScript name, and not the font family. This allows you to define very precise feature settings; e.g. you can disable a feature in the italic font but not in the regular font. On Linux, font features are first read from the FontConfig database and then this option is applied, so they can be configured in a single, central place. To get the PostScript name for a font, use the ``fc-scan file.ttf`` command on Linux or the `Font Book tool on macOS `__. Enable alternate zero and oldstyle numerals:: font_features FiraCode-Retina +zero +onum Enable only alternate zero in the bold font:: font_features FiraCode-Bold +zero Disable the normal ligatures, but keep the :code:`calt` feature which (in this font) breaks up monotony:: font_features TT2020StyleB-Regular -liga +calt In conjunction with :opt:`force_ltr`, you may want to disable Arabic shaping entirely, and only look at their isolated forms if they show up in a document. You can do this with e.g.:: font_features UnifontMedium +isol -medi -fina -init ''' ) opt('+modify_font', '', option_type='modify_font', ctype='!modify_font', add_to_default=False, long_text=''' Modify font characteristics such as the position or thickness of the underline and strikethrough. The modifications can have the suffix :code:`px` for pixels or :code:`%` for percentage of original value. No suffix means use pts. For example:: modify_font underline_position -2 modify_font underline_thickness 150% modify_font strikethrough_position 2px Additionally, you can modify the size of the cell in which each font glyph is rendered and the baseline at which the glyph is placed in the cell. For example:: modify_font cell_width 80% modify_font cell_height -2px modify_font baseline 3 Note that modifying the baseline will automatically adjust the underline and strikethrough positions by the same amount. Increasing the baseline raises glyphs inside the cell and decreasing it lowers them. Decreasing the cell size might cause rendering artifacts, so use with care. ''') opt('box_drawing_scale', '0.001, 1, 1.5, 2', option_type='box_drawing_scale', ctype='!box_drawing_scale', long_text=''' The sizes of the lines used for the box drawing Unicode characters. These values are in pts. They will be scaled by the monitor DPI to arrive at a pixel value. There must be four values corresponding to thin, normal, thick, and very thick lines. ''' ) opt('undercurl_style', 'thin-sparse', ctype='undercurl_style', choices=('thin-sparse', 'thin-dense', 'thick-sparse', 'thick-dense'), long_text=''' The style with which undercurls are rendered. This option takes the form :code:`(thin|thick)-(sparse|dense)`. Thin and thick control the thickness of the undercurl. Sparse and dense control how often the curl oscillates. With sparse the curl will peak once per character, with dense twice. Changing this option dynamically via reloading the config or remote control is undefined. ''' ) opt('underline_exclusion', '1', option_type='underline_exclusion', ctype='!underline_exclusion', long_text=''' By default kitty renders gaps in underlines when they overlap with descenders (the parts of letters below the baseline, such as for y, q, p etc.). This option controls the thickness of the gaps. It can be either a unitless number in which case it is a fraction of the underline thickness as specified in the font or it can have a suffix of :code:`px` for pixels or :code:`pt` for points. Set to zero to disable the gaps. Changing this option dynamically via reloading the config or remote control is undefined. ''') opt('text_composition_strategy', 'platform', ctype='!text_composition_strategy', long_text=''' Control how kitty composites text glyphs onto the background color. The default value of :code:`platform` tries for text rendering as close to "native" for the platform kitty is running on as possible. A value of :code:`legacy` uses the old (pre kitty 0.28) strategy for how glyphs are composited. This will make dark text on light backgrounds look thicker and light text on dark backgrounds thinner. It might also make some text appear like the strokes are uneven. You can fine tune the actual contrast curve used for glyph composition by specifying up to two space-separated numbers for this setting. The first number is the gamma adjustment, which controls the thickness of dark text on light backgrounds. Increasing the value will make text appear thicker. The default value for this is :code:`1.0` on Linux and :code:`1.7` on macOS. Valid values are :code:`0.01` and above. The result is scaled based on the luminance difference between the background and the foreground. Dark text on light backgrounds receives the full impact of the curve while light text on dark backgrounds is affected very little. The second number is an additional multiplicative contrast. It is percentage ranging from :code:`0` to :code:`100`. The default value is :code:`0` on Linux and :code:`30` on macOS. If you wish to achieve similar looking thickness in light and dark themes, a good way to experiment is start by setting the value to :code:`1.0 0` and use a dark theme. Then adjust the second parameter until it looks good. Then switch to a light theme and adjust the first parameter until the perceived thickness matches the dark theme. ''') opt('text_fg_override_threshold', '0', option_type='text_fg_override_threshold', long_text=''' A setting to prevent low contrast between foreground and background colors. Useful when working with applications that use colors that do not contrast well with your preferred color scheme. The default value is :code:`0`, which means no color overriding is performed. There are two modes of operation: A value with the suffix :code:`ratio` represents the minimum accepted contrast ratio between the foreground and background color. Possible values range from :code:`0.0 ratio` to :code:`21.0 ratio`. For example, to meet :link:`WCAG level AA ` a value of :code:`4.5 ratio` can be provided. The algorithm is implemented using :link:`HSLuv ` which enables it to change the perceived lightness of a color just as much as needed without really changing its hue and saturation. A value with the suffix :code:`%` represents the minimum accepted difference in luminance between the foreground and background color, below which kitty will override the foreground color. It is percentage ranging from :code:`0 %` to :code:`100 %`. If the difference in luminance of the foreground and background is below this threshold, the foreground color will be set to white if the background is dark or black if the background is light. WARNING: Some programs use characters (such as block characters) for graphics display and may expect to be able to set the foreground and background to the same color (or similar colors). If you see unexpected stripes, dots, lines, incorrect color, no color where you expect color, or any kind of graphic display problem try setting :opt:`text_fg_override_threshold` to :code:`0` to see if this is the cause of the problem or consider using the :code:`ratio` mode of operation described above instead of the :code:`%` mode of operation. ''') egr() # }}} # cursor {{{ agr('cursor', 'Text cursor customization') opt('cursor', '#cccccc', option_type='to_color_or_none', long_text=''' Default text cursor color. If set to the special value :code:`none` the cursor will be rendered with a "reverse video" effect. Its color will be the color of the text in the cell it is over and the text will be rendered with the background color of the cell. Note that if the program running in the terminal sets a cursor color, this takes precedence. Also, the cursor colors are modified if the cell background and foreground colors have very low contrast. Note that some themes set this value, so if you want to override it, place your value after the lines where the theme file is included. ''' ) opt('cursor_text_color', '#111111', option_type='cursor_text_color', long_text=''' The color of text under the cursor. If you want it rendered with the background color of the cell underneath instead, use the special keyword: `background`. Note that if :opt:`cursor` is set to :code:`none` then this option is ignored. Note that some themes set this value, so if you want to override it, place your value after the lines where the theme file is included. ''' ) opt('cursor_shape', 'block', option_type='to_cursor_shape', ctype='int', long_text=''' The cursor shape can be one of :code:`block`, :code:`beam`, :code:`underline`. Note that when reloading the config this will be changed only if the cursor shape has not been set by the program running in the terminal. This sets the default cursor shape, applications running in the terminal can override it. In particular, :ref:`shell integration ` in kitty sets the cursor shape to :code:`beam` at shell prompts. You can avoid this by setting :opt:`shell_integration` to :code:`no-cursor`. ''' ) opt('cursor_shape_unfocused', 'hollow', option_type='to_cursor_unfocused_shape', ctype='int', long_text=''' Defines the text cursor shape when the OS window is not focused. The unfocused cursor shape can be one of :code:`block`, :code:`beam`, :code:`underline`, :code:`hollow` and :code:`unchanged` (leave the cursor shape as it is). ''') opt('cursor_beam_thickness', '1.5', option_type='positive_float', ctype='float', long_text='The thickness of the beam cursor (in pts).' ) opt('cursor_underline_thickness', '2.0', option_type='positive_float', ctype='float', long_text='The thickness of the underline cursor (in pts).' ) opt('cursor_blink_interval', '-1', option_type='cursor_blink_interval', ctype='!cursor_blink_interval', long_text=''' The interval to blink the cursor (in seconds). Set to zero to disable blinking. Negative values mean use system default. Note that the minimum interval will be limited to :opt:`repaint_delay`. You can also animate the cursor blink by specifying an :term:`easing function`. For example, setting this to option to :code:`0.5 ease-in-out` will cause the cursor blink to be animated over a second, in the first half of the second it will go from opaque to transparent and then back again over the next half. You can specify different easing functions for the two halves, for example: :code:`-1 linear ease-out`. kitty supports all the :link:`CSS easing functions `. Note that turning on animations uses extra power as it means the screen is redrawn multiple times per blink interval. See also, :opt:`cursor_stop_blinking_after`. ''' ) opt('cursor_stop_blinking_after', '15.0', option_type='positive_float', ctype='time', long_text=''' Stop blinking cursor after the specified number of seconds of keyboard inactivity. Set to zero to never stop blinking. ''' ) opt('cursor_trail', '0', option_type='positive_int', ctype='time-ms', long_text=''' Set this to a value larger than zero to enable a "cursor trail" animation. This is an animation that shows a "trail" following the movement of the text cursor. It makes it easy to follow large cursor jumps and makes for a cool visual effect of the cursor zooming around the screen. The actual value of this option controls when the animation is triggered. It is a number of milliseconds. The trail animation only follows cursors that have stayed in their position for longer than the specified number of milliseconds. This prevents trails from appearing for cursors that rapidly change their positions during UI updates in complex applications. See :opt:`cursor_trail_decay` to control the animation speed and :opt:`cursor_trail_start_threshold` to control when a cursor trail is started. ''' ) opt('cursor_trail_decay', '0.1 0.4', option_type='cursor_trail_decay', ctype='!cursor_trail_decay', long_text=''' Controls the decay times for the cursor trail effect when the :opt:`cursor_trail` is enabled. This option accepts two positive float values specifying the fastest and slowest decay times in seconds. The first value corresponds to the fastest decay time (minimum), and the second value corresponds to the slowest decay time (maximum). The second value must be equal to or greater than the first value. Smaller values result in a faster decay of the cursor trail. Adjust these values to control how quickly the cursor trail fades away. ''', ) opt('cursor_trail_start_threshold', '2', option_type='positive_int', ctype='int', long_text=''' Set the distance threshold for starting the cursor trail. This option accepts a positive integer value that represents the minimum number of cells the cursor must move before the trail is started. When the cursor moves less than this threshold, the trail is skipped, reducing unnecessary cursor trail animation. ''' ) egr() # }}} # scrollback {{{ agr('scrollback', 'Scrollback') opt('scrollback_lines', '2000', option_type='scrollback_lines', long_text=''' Number of lines of history to keep in memory for scrolling back. Memory is allocated on demand. Negative numbers are (effectively) infinite scrollback. Note that using very large scrollback is not recommended as it can slow down performance of the terminal and also use large amounts of RAM. Instead, consider using :opt:`scrollback_pager_history_size`. Note that on config reload if this is changed it will only affect newly created windows, not existing ones. ''' ) opt('scrollback_indicator_opacity', '1.0', option_type='unit_float', ctype='float', long_text=''' The opacity of the scrollback indicator which is a small colored rectangle that moves along the right hand side of the window as you scroll, indicating what fraction you have scrolled. The default is one which means fully opaque, aka visible. Set to a value between zero and one to make the indicator less visible.''') opt('scrollback_pager', 'less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER', option_type='to_cmdline', long_text=''' Program with which to view scrollback in a new window. The scrollback buffer is passed as STDIN to this program. If you change it, make sure the program you use can handle ANSI escape sequences for colors and text formatting. INPUT_LINE_NUMBER in the command line above will be replaced by an integer representing which line should be at the top of the screen. Similarly CURSOR_LINE and CURSOR_COLUMN will be replaced by the current cursor position or set to 0 if there is no cursor, for example, when showing the last command output. ''' ) opt('scrollback_pager_history_size', '0', option_type='scrollback_pager_history_size', ctype='uint', long_text=''' Separate scrollback history size (in MB), used only for browsing the scrollback buffer with pager. This separate buffer is not available for interactive scrolling but will be piped to the pager program when viewing scrollback buffer in a separate window. The current implementation stores the data in UTF-8, so approximately 10000 lines per megabyte at 100 chars per line, for pure ASCII, unformatted text. A value of zero or less disables this feature. The maximum allowed size is 4GB. Note that on config reload if this is changed it will only affect newly created windows, not existing ones. ''' ) opt('scrollback_fill_enlarged_window', 'no', option_type='to_bool', ctype='bool', long_text='Fill new space with lines from the scrollback buffer after enlarging a window.' ) opt('wheel_scroll_multiplier', '5.0', option_type='float', ctype='double', long_text=''' Multiplier for the number of lines scrolled by the mouse wheel. Note that this is only used for low precision scrolling devices, not for high precision scrolling devices on platforms such as macOS and Wayland. Use negative numbers to change scroll direction. See also :opt:`wheel_scroll_min_lines`. ''' ) opt('wheel_scroll_min_lines', '1', option_type='int', ctype='int', long_text=''' The minimum number of lines scrolled by the mouse wheel. The :opt:`scroll multiplier ` only takes effect after it reaches this number. Note that this is only used for low precision scrolling devices like wheel mice that scroll by very small amounts when using the wheel. With a negative number, the minimum number of lines will always be added. ''' ) opt('touch_scroll_multiplier', '1.0', option_type='float', ctype='double', long_text=''' Multiplier for the number of lines scrolled by a touchpad. Note that this is only used for high precision scrolling devices on platforms such as macOS and Wayland. Use negative numbers to change scroll direction. ''' ) egr() # }}} # mouse {{{ agr('mouse', 'Mouse') opt('mouse_hide_wait', '3.0', macos_default='0.0', option_type='float', ctype='time', long_text=''' Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to zero to disable mouse cursor hiding. Set to a negative value to hide the mouse cursor immediately when typing text. Disabled by default on macOS as getting it to work robustly with the ever-changing sea of bugs that is Cocoa is too much effort. ''' ) opt('url_color', '#0087bd', option_type='to_color', ctype='color_as_int', long_text=''' The color and style for highlighting URLs on mouse-over. :opt:`url_style` can be one of: :code:`none`, :code:`straight`, :code:`double`, :code:`curly`, :code:`dotted`, :code:`dashed`. ''' ) opt('url_style', 'curly', option_type='url_style', ctype='uint', ) opt('open_url_with', 'default', option_type='to_cmdline', long_text=''' The program to open clicked URLs. The special value :code:`default` will first look for any URL handlers defined via the :doc:`open_actions` facility and if non are found, it will use the Operating System's default URL handler (:program:`open` on macOS and :program:`xdg-open` on Linux). ''' ) opt('url_prefixes', 'file ftp ftps gemini git gopher http https irc ircs kitty mailto news sftp ssh', option_type='url_prefixes', ctype='!url_prefixes', long_text=''' The set of URL prefixes to look for when detecting a URL under the mouse cursor. ''' ) opt('detect_urls', 'yes', option_type='to_bool', ctype='bool', long_text=''' Detect URLs under the mouse. Detected URLs are highlighted with an underline and the mouse cursor becomes a hand over them. Even if this option is disabled, URLs are still clickable. See also the :opt:`underline_hyperlinks` option to control how hyperlinks (as opposed to plain text URLs) are displayed. ''' ) opt('url_excluded_characters', '', option_type='python_string', ctype='!url_excluded_characters', long_text=''' Additional characters to be disallowed from URLs, when detecting URLs under the mouse cursor. By default, all characters that are legal in URLs are allowed. Additionally, newlines are allowed (but stripped). This is to accommodate programs such as mutt that add hard line breaks even for continued lines. :code:`\\\\n` can be added to this option to disable this behavior. Special characters can be specified using backslash escapes, to specify a backslash use a double backslash. ''' ) opt('show_hyperlink_targets', 'no', option_type='to_bool', ctype='bool', long_text=''' When the mouse hovers over a terminal hyperlink, show the actual URL that will be activated when the hyperlink is clicked. ''') opt('underline_hyperlinks', 'hover', choices=('hover', 'always', 'never'), ctype='underline_hyperlinks', long_text=''' Control how hyperlinks are underlined. They can either be underlined on mouse :code:`hover`, :code:`always` (i.e. permanently underlined) or :code:`never` which means that kitty will not apply any underline styling to hyperlinks. Note that the value of :code:`always` only applies to real (OSC 8) hyperlinks not text that is detected to be a URL on mouse hover. Uses the :opt:`url_style` and :opt:`url_color` settings for the underline style. Note that reloading the config and changing this value to/from :code:`always` will only affect text subsequently received by kitty. ''') opt('copy_on_select', 'no', option_type='copy_on_select', long_text=''' Copy to clipboard or a private buffer on select. With this set to :code:`clipboard`, selecting text with the mouse will cause the text to be copied to clipboard. Useful on platforms such as macOS that do not have the concept of primary selection. You can instead specify a name such as :code:`a1` to copy to a private kitty buffer. Map a shortcut with the :code:`paste_from_buffer` action to paste from this private buffer. For example:: copy_on_select a1 map shift+cmd+v paste_from_buffer a1 Note that copying to the clipboard is a security risk, as all programs, including websites open in your browser can read the contents of the system clipboard. ''' ) opt('clear_selection_on_clipboard_loss', 'no', option_type='to_bool', long_text=''' When the contents of the clipboard no longer reflect the current selection, clear it. This is primarily useful on platforms such as Linux where selecting text automatically copies it to a special "primary selection" clipboard or if you have :opt:`copy_on_select` set to :code:`clipboard`. Note that on macOS the system does not provide notifications when the clipboard owner is changed, so there, copying to clipboard in a non-kitty application will not clear selections even if :opt:`copy_on_select` is enabled. ''') opt('paste_actions', 'quote-urls-at-prompt,confirm', option_type='paste_actions', long_text=''' A comma separated list of actions to take when pasting text into the terminal. The supported paste actions are: :code:`quote-urls-at-prompt`: If the text being pasted is a URL and the cursor is at a shell prompt, automatically quote the URL (needs :opt:`shell_integration`). :code:`replace-dangerous-control-codes` Replace dangerous control codes from pasted text, without confirmation. :code:`replace-newline` Replace the newline character from pasted text, without confirmation. :code:`confirm`: Confirm the paste if the text to be pasted contains any terminal control codes as this can be dangerous, leading to code execution if the shell/program running in the terminal does not properly handle these. :code:`confirm-if-large` Confirm the paste if it is very large (larger than 16KB) as pasting large amounts of text into shells can be very slow. :code:`filter`: Run the filter_paste() function from the file :file:`paste-actions.py` in the kitty config directory on the pasted text. The text returned by the function will be actually pasted. :code:`no-op`: Has no effect. ''' ) opt('strip_trailing_spaces', 'never', choices=('always', 'never', 'smart'), long_text=''' Remove spaces at the end of lines when copying to clipboard. A value of :code:`smart` will do it when using normal selections, but not rectangle selections. A value of :code:`always` will always do it. ''' ) opt('select_by_word_characters', '@-./_~?&=%+#', ctype='!select_by_word_characters', long_text=''' Characters considered part of a word when double clicking. In addition to these characters any character that is marked as an alphanumeric character in the Unicode database will be matched. ''' ) opt('select_by_word_characters_forward', '', ctype='!select_by_word_characters_forward', long_text=''' Characters considered part of a word when extending the selection forward on double clicking. In addition to these characters any character that is marked as an alphanumeric character in the Unicode database will be matched. If empty (default) :opt:`select_by_word_characters` will be used for both directions. ''' ) opt('click_interval', '-1.0', option_type='float', ctype='time', long_text=''' The interval between successive clicks to detect double/triple clicks (in seconds). Negative numbers will use the system default instead, if available, or fallback to 0.5. ''' ) opt('focus_follows_mouse', 'no', option_type='to_bool', ctype='bool', long_text=''' Set the active window to the window under the mouse when moving the mouse around. On macOS, this will also cause the OS Window under the mouse to be focused automatically when the mouse enters it. ''' ) opt('pointer_shape_when_grabbed', 'arrow', choices=pointer_shape_names, ctype='pointer_shape', long_text=''' The shape of the mouse pointer when the program running in the terminal grabs the mouse. ''' ) opt('default_pointer_shape', 'beam', choices=pointer_shape_names, ctype='pointer_shape', long_text=''' The default shape of the mouse pointer. ''' ) opt('pointer_shape_when_dragging', 'beam crosshair', option_type='pointer_shape_when_dragging', ctype='!dragging_pointer_shape', long_text=''' The default shape of the mouse pointer when dragging across text. The optional second value sets the shape when dragging in rectangular selection mode. ''' ) # mouse.mousemap {{{ agr('mouse.mousemap', 'Mouse actions', ''' Mouse buttons can be mapped to perform arbitrary actions. The syntax is: .. code-block:: none mouse_map button-name event-type modes action Where :code:`button-name` is one of :code:`left`, :code:`middle`, :code:`right`, :code:`b1` ... :code:`b8` with added keyboard modifiers. For example: :code:`ctrl+shift+left` refers to holding the :kbd:`Ctrl+Shift` keys while clicking with the left mouse button. The value :code:`b1` ... :code:`b8` can be used to refer to up to eight buttons on a mouse. :code:`event-type` is one of :code:`press`, :code:`release`, :code:`doublepress`, :code:`triplepress`, :code:`click`, :code:`doubleclick`. :code:`modes` indicates whether the action is performed when the mouse is grabbed by the program running in the terminal, or not. The values are :code:`grabbed` or :code:`ungrabbed` or a comma separated combination of them. :code:`grabbed` refers to when the program running in the terminal has requested mouse events. Note that the click and double click events have a delay of :opt:`click_interval` to disambiguate from double and triple presses. You can run kitty with the :option:`kitty --debug-input` command line option to see mouse events. See the builtin actions below to get a sense of what is possible. If you want to unmap a button, map it to nothing. For example, to disable opening of URLs with a plain click:: mouse_map left click ungrabbed See all the mappable actions including mouse actions :doc:`here
`. .. note:: Once a selection is started, releasing the button that started it will automatically end it and no release event will be dispatched. ''') opt('clear_all_mouse_actions', 'no', option_type='clear_all_mouse_actions', long_text=''' Remove all mouse action definitions up to this point. Useful, for instance, to remove the default mouse actions. ''' ) mma('Click the link under the mouse or move the cursor', 'click_url_or_select left click ungrabbed mouse_handle_click selection link prompt', long_text=''' First check for a selection and if one exists do nothing. Then check for a link under the mouse cursor and if one exists, click it. Finally check if the click happened at the current shell prompt and if so, move the cursor to the click location. Note that this requires :ref:`shell integration ` to work. ''' ) mma('Click the link under the mouse or move the cursor even when grabbed', 'click_url_or_select_grabbed shift+left click grabbed,ungrabbed mouse_handle_click selection link prompt', long_text=''' Same as above, except that the action is performed even when the mouse is grabbed by the program running in the terminal. ''' ) mma('Click the link under the mouse cursor', 'click_url ctrl+shift+left release grabbed,ungrabbed mouse_handle_click link', long_text=''' Variant with :kbd:`Ctrl+Shift` is present because the simple click based version has an unavoidable delay of :opt:`click_interval`, to disambiguate clicks from double clicks. ''' ) mma('Discard press event for link click', 'click_url_discard ctrl+shift+left press grabbed discard_event', long_text=''' Prevent this press event from being sent to the program that has grabbed the mouse, as the corresponding release event is used to open a URL. ''' ) mma('Paste from the primary selection', 'paste_selection middle release ungrabbed paste_from_selection', ) mma('Start selecting text', 'start_simple_selection left press ungrabbed mouse_selection normal', ) mma('Start selecting text in a rectangle', 'start_rectangle_selection ctrl+alt+left press ungrabbed mouse_selection rectangle', ) mma('Select a word', 'select_word left doublepress ungrabbed mouse_selection word', ) mma('Select a line', 'select_line left triplepress ungrabbed mouse_selection line', ) mma('Select line from point', 'select_line_from_point ctrl+alt+left triplepress ungrabbed mouse_selection line_from_point', long_text='Select from the clicked point to the end of the line.' ' If you would like to select the word at the point and then extend to the rest of the line,' ' change `line_from_point` to `word_and_line_from_point`.' ) mma('Extend the current selection', 'extend_selection right press ungrabbed mouse_selection extend', long_text=''' If you want only the end of the selection to be moved instead of the nearest boundary, use :code:`move-end` instead of :code:`extend`. ''' ) mma('Paste from the primary selection even when grabbed', 'paste_selection_grabbed shift+middle release ungrabbed,grabbed paste_selection', ) mma('Discard press event for middle click paste', 'paste_selection_grabbed shift+middle press grabbed discard_event', ) mma('Start selecting text even when grabbed', 'start_simple_selection_grabbed shift+left press ungrabbed,grabbed mouse_selection normal', ) mma('Start selecting text in a rectangle even when grabbed', 'start_rectangle_selection_grabbed ctrl+shift+alt+left press ungrabbed,grabbed mouse_selection rectangle', ) mma('Select a word even when grabbed', 'select_word_grabbed shift+left doublepress ungrabbed,grabbed mouse_selection word', ) mma('Select a line even when grabbed', 'select_line_grabbed shift+left triplepress ungrabbed,grabbed mouse_selection line', ) mma('Select line from point even when grabbed', 'select_line_from_point_grabbed ctrl+shift+alt+left triplepress ungrabbed,grabbed mouse_selection line_from_point', long_text='Select from the clicked point to the end of the line even when grabbed.' ' If you would like to select the word at the point and then extend to the rest of the line,' ' change `line_from_point` to `word_and_line_from_point`.' ) mma('Extend the current selection even when grabbed', 'extend_selection_grabbed shift+right press ungrabbed,grabbed mouse_selection extend', ) mma('Show clicked command output in pager', 'show_clicked_cmd_output_ungrabbed ctrl+shift+right press ungrabbed mouse_show_command_output', long_text='Requires :ref:`shell integration ` to work.' ) egr() # }}} egr() # }}} # performance {{{ agr('performance', 'Performance tuning') opt('repaint_delay', '10', option_type='positive_int', ctype='time-ms', long_text=''' Delay between screen updates (in milliseconds). Decreasing it, increases frames-per-second (FPS) at the cost of more CPU usage. The default value yields ~100 FPS which is more than sufficient for most uses. Note that to actually achieve 100 FPS, you have to either set :opt:`sync_to_monitor` to :code:`no` or use a monitor with a high refresh rate. Also, to minimize latency when there is pending input to be processed, this option is ignored. ''' ) opt('input_delay', '3', option_type='positive_int', ctype='time-ms', long_text=''' Delay before input from the program running in the terminal is processed (in milliseconds). Note that decreasing it will increase responsiveness, but also increase CPU usage and might cause flicker in full screen programs that redraw the entire screen on each loop, because kitty is so fast that partial screen updates will be drawn. This setting is ignored when the input buffer is almost full. ''' ) opt('sync_to_monitor', 'yes', option_type='to_bool', ctype='bool', long_text=''' Sync screen updates to the refresh rate of the monitor. This prevents :link:`screen tearing ` when scrolling. However, it limits the rendering speed to the refresh rate of your monitor. With a very high speed mouse/high keyboard repeat rate, you may notice some slight input latency. If so, set this to :code:`no`. ''' ) egr() # }}} # bell {{{ agr('bell', 'Terminal bell') opt('enable_audio_bell', 'yes', option_type='to_bool', ctype='bool', long_text=''' The audio bell. Useful to disable it in environments that require silence. ''' ) opt('visual_bell_duration', '0.0', option_type='visual_bell_duration', ctype='!visual_bell_duration', long_text=''' The visual bell duration (in seconds). Flash the screen when a bell occurs for the specified number of seconds. Set to zero to disable. The flash is animated, fading in and out over the specified duration. The :term:`easing function` used for the fading can be controlled. For example, :code:`2.0 linear` will casuse the flash to fade in and out linearly. The default if unspecified is to use :code:`ease-in-out` which fades slowly at the start, middle and end. You can specify different easing functions for the fade-in and fade-out parts, like this: :code:`2.0 ease-in linear`. kitty supports all the :link:`CSS easing functions `. ''' ) opt('visual_bell_color', 'none', option_type='to_color_or_none', long_text=''' The color used by visual bell. Set to :code:`none` will fall back to selection background color. If you feel that the visual bell is too bright, you can set it to a darker color. ''' ) opt('window_alert_on_bell', 'yes', option_type='to_bool', ctype='bool', long_text=''' Request window attention on bell. Makes the dock icon bounce on macOS or the taskbar flash on Linux. ''' ) opt('bell_on_tab', '"🔔 "', option_type='bell_on_tab', long_text=''' Some text or a Unicode symbol to show on the tab if a window in the tab that does not have focus has a bell. If you want to use leading or trailing spaces, surround the text with quotes. See :opt:`tab_title_template` for how this is rendered. For backwards compatibility, values of :code:`yes`, :code:`y` and :code:`true` are converted to the default bell symbol and :code:`no`, :code:`n`, :code:`false` and :code:`none` are converted to the empty string. ''' ) opt('command_on_bell', 'none', option_type='to_cmdline', long_text=''' Program to run when a bell occurs. The environment variable :envvar:`KITTY_CHILD_CMDLINE` can be used to get the program running in the window in which the bell occurred. ''' ) opt('bell_path', 'none', option_type='config_or_absolute_path', ctype='!bell_path', long_text=''' Path to a sound file to play as the bell sound. If set to :code:`none`, the system default bell sound is used. Must be in a format supported by the operating systems sound API, such as WAV or OGA on Linux (libcanberra) or AIFF, MP3 or WAV on macOS (NSSound). ''' ) opt('linux_bell_theme', '__custom', ctype='!bell_theme', long_text=''' The XDG Sound Theme kitty will use to play the bell sound. Defaults to the custom theme name specified in the :link:`XDG Sound theme specification , falling back to the default freedesktop theme if it does not exist. To change your sound theme desktop wide, create :file:`~/.local/share/sounds/__custom/index.theme` with the contents: [Sound Theme] Inherits=name-of-the-sound-theme-you-want-to-use Replace :code:`name-of-the-sound-theme-you-want-to-use` with the actual theme name. Now all compliant applications should use sounds from this theme. ''') egr() # }}} # window {{{ agr('window', 'Window layout') opt('remember_window_size', 'yes', option_type='to_bool', long_text=''' If enabled, the :term:`OS Window ` size will be remembered so that new instances of kitty will have the same size as the previous instance. If disabled, the :term:`OS Window ` will initially have size configured by initial_window_width/height, in pixels. You can use a suffix of "c" on the width/height values to have them interpreted as number of cells instead of pixels. ''' ) opt('initial_window_width', '640', option_type='window_size', ) opt('initial_window_height', '400', option_type='window_size', ) opt('enabled_layouts', '*', option_type='to_layout_names', long_text=''' The enabled window layouts. A comma separated list of layout names. The special value :code:`all` means all layouts. The first listed layout will be used as the startup layout. Default configuration is all layouts in alphabetical order. For a list of available layouts, see the :ref:`layouts`. ''' ) opt('window_resize_step_cells', '2', option_type='positive_int', long_text=''' The step size (in units of cell width/cell height) to use when resizing kitty windows in a layout with the shortcut :sc:`start_resizing_window`. The cells value is used for horizontal resizing, and the lines value is used for vertical resizing. ''' ) opt('window_resize_step_lines', '2', option_type='positive_int', ) opt('window_border_width', '0.5pt', option_type='window_border_width', long_text=''' The width of window borders. Can be either in pixels (px) or pts (pt). Values in pts will be rounded to the nearest number of pixels based on screen resolution. If not specified, the unit is assumed to be pts. Note that borders are displayed only when more than one window is visible. They are meant to separate multiple windows. ''' ) opt('draw_minimal_borders', 'yes', option_type='to_bool', long_text=''' Draw only the minimum borders needed. This means that only the borders that separate the window from a neighbor are drawn. Note that setting a non-zero :opt:`window_margin_width` overrides this and causes all borders to be drawn. ''' ) opt('window_margin_width', '0', option_type='edge_width', long_text=''' The window margin (in pts) (blank area outside the border). A single value sets all four sides. Two values set the vertical and horizontal sides. Three values set top, horizontal and bottom. Four values set top, right, bottom and left. ''' ) opt('single_window_margin_width', '-1', option_type='optional_edge_width', long_text=''' The window margin to use when only a single window is visible (in pts). Negative values will cause the value of :opt:`window_margin_width` to be used instead. A single value sets all four sides. Two values set the vertical and horizontal sides. Three values set top, horizontal and bottom. Four values set top, right, bottom and left. ''' ) opt('window_padding_width', '0', option_type='edge_width', long_text=''' The window padding (in pts) (blank area between the text and the window border). A single value sets all four sides. Two values set the vertical and horizontal sides. Three values set top, horizontal and bottom. Four values set top, right, bottom and left. ''' ) opt('single_window_padding_width', '-1', option_type='optional_edge_width', long_text=''' The window padding to use when only a single window is visible (in pts). Negative values will cause the value of :opt:`window_padding_width` to be used instead. A single value sets all four sides. Two values set the vertical and horizontal sides. Three values set top, horizontal and bottom. Four values set top, right, bottom and left. ''' ) opt('placement_strategy', 'center', choices=('top-left', 'top', 'top-right', 'left', 'center', 'right', 'bottom-left', 'bottom', 'bottom-right'), long_text=''' When the window size is not an exact multiple of the cell size, the cell area of the terminal window will have some extra padding on the sides. You can control how that padding is distributed with this option. Using a value of :code:`center` means the cell area will be placed centrally. A value of :code:`top-left` means the padding will be only at the bottom and right edges. The value can be one of: :code:`top-left`, :code:`top`, :code:`top-right`, :code:`left`, :code:`center`, :code:`right`, :code:`bottom-left`, :code:`bottom`, :code:`bottom-right`. ''' ) opt('active_border_color', '#00ff00', option_type='to_color_or_none', ctype='active_border_color', long_text=''' The color for the border of the active window. Set this to :code:`none` to not draw borders around the active window. ''' ) opt('inactive_border_color', '#cccccc', option_type='to_color', ctype='color_as_int', long_text='The color for the border of inactive windows.' ) opt('bell_border_color', '#ff5a00', option_type='to_color', ctype='color_as_int', long_text='The color for the border of inactive windows in which a bell has occurred.' ) opt('inactive_text_alpha', '1.0', option_type='unit_float', ctype='float', long_text=''' Fade the text in inactive windows by the specified amount (a number between zero and one, with zero being fully faded). ''' ) opt('hide_window_decorations', 'no', option_type='hide_window_decorations', ctype='uint', long_text=''' Hide the window decorations (title-bar and window borders) with :code:`yes`. On macOS, :code:`titlebar-only` and :code:`titlebar-and-corners` can be used to only hide the titlebar and the rounded corners. Whether this works and exactly what effect it has depends on the window manager/operating system. Note that the effects of changing this option when reloading config are undefined. When using :code:`titlebar-only`, it is useful to also set :opt:`window_margin_width` and :opt:`placement_strategy` to prevent the rounded corners from clipping text. Or use :code:`titlebar-and-corners`. ''' ) opt('window_logo_path', 'none', option_type='config_or_absolute_path', ctype='!window_logo_path', long_text=''' Path to a logo image. Must be in PNG/JPEG/WEBP/GIF/TIFF/BMP format. Relative paths are interpreted relative to the kitty config directory. The logo is displayed in a corner of every kitty window. The position is controlled by :opt:`window_logo_position`. Individual windows can be configured to have different logos either using the :ac:`launch` action or the :doc:`remote control ` facility. ''' ) opt('window_logo_position', 'bottom-right', choices=('top-left', 'top', 'top-right', 'left', 'center', 'right', 'bottom-left', 'bottom', 'bottom-right'), ctype='bganchor', long_text=''' Where to position the window logo in the window. The value can be one of: :code:`top-left`, :code:`top`, :code:`top-right`, :code:`left`, :code:`center`, :code:`right`, :code:`bottom-left`, :code:`bottom`, :code:`bottom-right`. ''' ) opt('window_logo_alpha', '0.5', option_type='unit_float', ctype='float', long_text=''' The amount the logo should be faded into the background. With zero being fully faded and one being fully opaque. ''' ) opt('window_logo_scale', '0', option_type='window_logo_scale', ctype='!window_logo_scale', long_text=''' The percentage (0-100] of the window size to which the logo should scale. Using a single number means the logo is scaled to that percentage of the shortest window dimension, while preserving aspect ratio of the logo image. Using two numbers means the width and height of the logo are scaled to the respective percentage of the window's width and height. Using zero as the percentage disables scaling in that dimension. A single zero (the default) disables all scaling of the window logo. ''') opt('resize_debounce_time', '0.1 0.5', option_type='resize_debounce_time', ctype='!resize_debounce_time', long_text=''' The time to wait (in seconds) before asking the program running in kitty to resize and redraw the screen during a live resize of the OS window, when no new resize events have been received, i.e. when resizing is either paused or finished. On platforms such as macOS, where the operating system sends events corresponding to the start and end of a live resize, the second number is used for redraw-after-pause since kitty can distinguish between a pause and end of resizing. On such systems the first number is ignored and redraw is immediate after end of resize. On other systems only the first number is used so that kitty is "ready" quickly after the end of resizing, while not also continuously redrawing, to save energy. ''' ) opt('resize_in_steps', 'no', option_type='to_bool', ctype='bool', long_text=''' Resize the OS window in steps as large as the cells, instead of with the usual pixel accuracy. Combined with :opt:`initial_window_width` and :opt:`initial_window_height` in number of cells, this option can be used to keep the margins as small as possible when resizing the OS window. Note that this does not currently work on Wayland. ''' ) opt('visual_window_select_characters', defval=string.digits[1:] + '0' + string.ascii_uppercase, option_type='visual_window_select_characters', long_text=r''' The list of characters for visual window selection. For example, for selecting a window to focus on with :sc:`focus_visible_window`. The value should be a series of unique numbers or alphabets, case insensitive, from the set :code:`0-9A-Z\`-=[];',./\\`. Specify your preference as a string of characters. ''' ) opt('confirm_os_window_close', '-1', option_type='confirm_close', long_text=''' Ask for confirmation when closing an OS window or a tab with at least this number of kitty windows in it by window manager (e.g. clicking the window close button or pressing the operating system shortcut to close windows) or by the :ac:`close_tab` action. A value of zero disables confirmation. This confirmation also applies to requests to quit the entire application (all OS windows, via the :ac:`quit` action). Negative values are converted to positive ones, however, with :opt:`shell_integration` enabled, using negative values means windows sitting at a shell prompt are not counted, only windows where some command is currently running. You can also have backgrounded jobs prevent closing, by adding :code:`count-background` to the setting, for example: :code:`-1 count-background`. Note that if you want confirmation when closing individual windows, you can map the :ac:`close_window_with_confirmation` action. ''') egr() # }}} # tabbar {{{ agr('tabbar', 'Tab bar') opt('tab_bar_edge', 'bottom', option_type='tab_bar_edge', ctype='int', long_text='The edge to show the tab bar on, :code:`top` or :code:`bottom`.' ) opt('tab_bar_margin_width', '0.0', option_type='positive_float', long_text='The margin to the left and right of the tab bar (in pts).' ) opt('tab_bar_margin_height', '0.0 0.0', option_type='tab_bar_margin_height', ctype='!tab_bar_margin_height', long_text=''' The margin above and below the tab bar (in pts). The first number is the margin between the edge of the OS Window and the tab bar. The second number is the margin between the tab bar and the contents of the current tab. ''' ) opt('tab_bar_style', 'fade', choices=('fade', 'hidden', 'powerline', 'separator', 'slant', 'custom'), ctype='!tab_bar_style', long_text=''' The tab bar style, can be one of: :code:`fade` Each tab's edges fade into the background color. (See also :opt:`tab_fade`) :code:`slant` Tabs look like the tabs in a physical file. :code:`separator` Tabs are separated by a configurable separator. (See also :opt:`tab_separator`) :code:`powerline` Tabs are shown as a continuous line with "fancy" separators. (See also :opt:`tab_powerline_style`) :code:`custom` A user-supplied Python function called draw_tab is loaded from the file :file:`tab_bar.py` in the kitty config directory. For examples of how to write such a function, see the functions named :code:`draw_tab_with_*` in kitty's source code: :file:`kitty/tab_bar.py`. See also :disc:`this discussion <4447>` for examples from kitty users. :code:`hidden` The tab bar is hidden. If you use this, you might want to create a mapping for the :ac:`select_tab` action which presents you with a list of tabs and allows for easy switching to a tab. ''' ) opt('tab_bar_align', 'left', choices=('left', 'center', 'right'), long_text=''' The horizontal alignment of the tab bar, can be one of: :code:`left`, :code:`center`, :code:`right`. ''' ) opt('tab_bar_min_tabs', '2', option_type='tab_bar_min_tabs', ctype='uint', long_text='The minimum number of tabs that must exist before the tab bar is shown.' ) opt('tab_switch_strategy', 'previous', choices=('last', 'left', 'previous', 'right'), long_text=''' The algorithm to use when switching to a tab when the current tab is closed. The default of :code:`previous` will switch to the last used tab. A value of :code:`left` will switch to the tab to the left of the closed tab. A value of :code:`right` will switch to the tab to the right of the closed tab. A value of :code:`last` will switch to the right-most tab. ''' ) opt('tab_fade', '0.25 0.5 0.75 1', option_type='tab_fade', long_text=''' Control how each tab fades into the background when using :code:`fade` for the :opt:`tab_bar_style`. Each number is an alpha (between zero and one) that controls how much the corresponding cell fades into the background, with zero being no fade and one being full fade. You can change the number of cells used by adding/removing entries to this list. ''' ) opt('tab_separator', '" ┇"', option_type='tab_separator', long_text=''' The separator between tabs in the tab bar when using :code:`separator` as the :opt:`tab_bar_style`. ''' ) opt('tab_powerline_style', 'angled', choices=('angled', 'round', 'slanted'), long_text=''' The powerline separator style between tabs in the tab bar when using :code:`powerline` as the :opt:`tab_bar_style`, can be one of: :code:`angled`, :code:`slanted`, :code:`round`. ''' ) opt('tab_activity_symbol', 'none', option_type='tab_activity_symbol', long_text=''' Some text or a Unicode symbol to show on the tab if a window in the tab that does not have focus has some activity. If you want to use leading or trailing spaces, surround the text with quotes. See :opt:`tab_title_template` for how this is rendered. ''' ) opt('tab_title_max_length', '0', option_type='positive_int', long_text=''' The maximum number of cells that can be used to render the text in a tab. A value of zero means that no limit is applied. ''' ) opt('tab_title_template', '"{fmt.fg.red}{bell_symbol}{activity_symbol}{fmt.fg.tab}{tab.last_focused_progress_percent}{title}"', option_type='tab_title_template', long_text=''' A template to render the tab title. The default just renders the title with optional symbols for bell and activity. If you wish to include the tab-index as well, use something like: :code:`{index}:{title}`. Useful if you have shortcuts mapped for :code:`goto_tab N`. If you prefer to see the index as a superscript, use :code:`{sup.index}`. All data available is: :code:`title` The current tab title. :code:`index` The tab index usable with :ac:`goto_tab N ` shortcuts. :code:`layout_name` The current layout name. :code:`num_windows` The number of windows in the tab. :code:`num_window_groups` The number of window groups (a window group is a window and all of its overlay windows) in the tab. :code:`tab.active_wd` The working directory of the currently active window in the tab (expensive, requires syscall). Use :code:`tab.active_oldest_wd` to get the directory of the oldest foreground process rather than the newest. :code:`tab.active_exe` The name of the executable running in the foreground of the currently active window in the tab (expensive, requires syscall). Use :code:`tab.active_oldest_exe` for the oldest foreground process. :code:`max_title_length` The maximum title length available. :code:`keyboard_mode` The name of the current :ref:`keyboard mode ` or the empty string if no keyboard mode is active. :code:`tab.last_focused_progress_percent` If a command running in a window reports the progress for a task, show this progress as a percentage from the most recently focused window in the tab. Empty string if no progress is reported. :code:`tab.progress_percent` If a command running in a window reports the progress for a task, show this progress as a percentage from all windows in the tab, averaged. Empty string is no progress is reported. Note that formatting is done by Python's string formatting machinery, so you can use, for instance, :code:`{layout_name[:2].upper()}` to show only the first two letters of the layout name, upper-cased. If you want to style the text, you can use styling directives, for example: ``{fmt.fg.red}red{fmt.fg.tab}normal{fmt.bg._00FF00}greenbg{fmt.bg.tab}``. Similarly, for bold and italic: ``{fmt.bold}bold{fmt.nobold}normal{fmt.italic}italic{fmt.noitalic}``. The 256 eight terminal colors can be used as ``fmt.fg.color0`` through ``fmt.fg.color255``. Note that for backward compatibility, if :code:`{bell_symbol}` or :code:`{activity_symbol}` are not present in the template, they are prepended to it. ''' ) opt('active_tab_title_template', 'none', option_type='active_tab_title_template', long_text=''' Template to use for active tabs. If not specified falls back to :opt:`tab_title_template`. ''' ) opt('active_tab_foreground', '#000', option_type='to_color', long_text='Tab bar colors and styles.' ) opt('active_tab_background', '#eee', option_type='to_color', ) opt('active_tab_font_style', 'bold-italic', option_type='tab_font_style', ) opt('inactive_tab_foreground', '#444', option_type='to_color', ) opt('inactive_tab_background', '#999', option_type='to_color', ) opt('inactive_tab_font_style', 'normal', option_type='tab_font_style', ) opt('tab_bar_background', 'none', option_type='to_color_or_none', ctype='color_or_none_as_int', long_text=''' Background color for the tab bar. Defaults to using the terminal background color. ''' ) opt('tab_bar_margin_color', 'none', option_type='to_color_or_none', ctype='color_or_none_as_int', long_text=''' Color for the tab bar margin area. Defaults to using the terminal background color for margins above and below the tab bar. For side margins the default color is chosen to match the background color of the neighboring tab. ''' ) egr() # }}} # colors {{{ agr('colors', 'Color scheme') opt('foreground', '#dddddd', option_type='to_color', ctype='color_as_int', long_text='The foreground and background colors.' ) opt('background', '#000000', option_type='to_color', ctype='color_as_int', ) opt( 'background_opacity', '1.0', option_type='unit_float', ctype='float', long_text=""" The opacity of the background. A number between zero and one, where one is opaque and zero is fully transparent. This will only work if supported by the OS (for instance, when using a compositor under X11). Note that it only sets the background color's opacity in cells that have the same background color as the default terminal background, so that things like the status bar in vim, powerline prompts, etc. still look good. But it means that if you use a color theme with a background color in your editor, it will not be rendered as transparent. Instead you should change the default background color in your kitty config and not use a background color in the editor color scheme. Or use the escape codes to set the terminals default colors in a shell script to launch your editor. See also :opt:`transparent_background_colors`. Be aware that using a value less than 1.0 is a (possibly significant) performance hit. When using a low value for this setting, it is desirable that you set the :opt:`background` color to a color the matches the general color of the desktop background, for best text rendering. Note that to workaround window managers not doing gamma-corrected blending kitty makes background_opacity non-linear which means, especially for light backgrounds you might need to make the value much lower than you expect to get good results, see :iss:`6218` for details. If you want to dynamically change transparency of windows, set :opt:`dynamic_background_opacity` to :code:`yes` (this is off by default as it has a performance cost). Changing this option when reloading the config will only work if :opt:`dynamic_background_opacity` was enabled in the original config. """, ) opt('background_blur', '0', option_type='int', ctype='int', long_text=''' Set to a positive value to enable background blur (blurring of the visuals behind a transparent window) on platforms that support it. Only takes effect when :opt:`background_opacity` is less than one. On macOS, this will also control the :italic:`blur radius` (amount of blurring). Setting it to too high a value will cause severe performance issues and/or rendering artifacts. Usually, values up to 64 work well. Note that this might cause performance issues, depending on how the platform implements it, so use with care. Currently supported on macOS and KDE. ''') opt('background_image', 'none', option_type='config_or_absolute_path', ctype='!background_image', long_text='Path to a background image. Must be in PNG/JPEG/WEBP/TIFF/GIF/BMP format.' ) opt('background_image_layout', 'tiled', choices=('mirror-tiled', 'scaled', 'tiled', 'clamped', 'centered', 'cscaled'), ctype='bglayout', long_text=''' Whether to tile, scale or clamp the background image. The value can be one of :code:`tiled`, :code:`mirror-tiled`, :code:`scaled`, :code:`clamped`, :code:`centered` or :code:`cscaled`. The :code:`scaled` and :code:`cscaled` values scale the image to the window size, with :code:`cscaled` preserving the image aspect ratio. ''' ) opt('background_image_linear', 'no', option_type='to_bool', ctype='bool', long_text='When background image is scaled, whether linear interpolation should be used.' ) opt('transparent_background_colors', '', option_type='transparent_background_colors', long_text=''' A space separated list of upto 7 colors, with opacity. When the background color of a cell matches one of these colors, it is rendered semi-transparent using the specified opacity. Useful in more complex UIs like editors where you could want more than a single background color to be rendered as transparent, for instance, for a cursor highlight line background or a highlighted block. Terminal applications can set this color using :ref:`The kitty color control ` escape code. The syntax for specifying colors is: :code:`color@opacity`, where the :code:`@opacity` part is optional. When unspecified, the value of :opt:`background_opacity` is used. For example:: transparent_background_colors red@0.5 #00ff00@0.3 ''' ) opt('dynamic_background_opacity', 'no', option_type='to_bool', ctype='bool', long_text=''' Allow changing of the :opt:`background_opacity` dynamically, using either keyboard shortcuts (:sc:`increase_background_opacity` and :sc:`decrease_background_opacity`) or the remote control facility. Changing this option by reloading the config is not supported. ''' ) opt('background_tint', '0.0', option_type='unit_float', ctype='float', long_text=''' How much to tint the background image by the background color. This option makes it easier to read the text. Tinting is done using the current background color for each window. This option applies only if :opt:`background_opacity` is set and transparent windows are supported or :opt:`background_image` is set. ''' ) opt('background_tint_gaps', '1.0', option_type='unit_float', ctype='float', long_text=''' How much to tint the background image at the window gaps by the background color, after applying :opt:`background_tint`. Since this is multiplicative with :opt:`background_tint`, it can be used to lighten the tint over the window gaps for a *separated* look. ''' ) opt('dim_opacity', '0.4', option_type='unit_float', ctype='float', long_text=''' How much to dim text that has the DIM/FAINT attribute set. One means no dimming and zero means fully dimmed (i.e. invisible). ''' ) opt('selection_foreground', '#000000', option_type='to_color_or_none', long_text=''' The foreground and background colors for text selected with the mouse. Setting both of these to :code:`none` will cause a "reverse video" effect for selections, where the selection will be the cell text color and the text will become the cell background color. Setting only selection_foreground to :code:`none` will cause the foreground color to be used unchanged. Note that these colors can be overridden by the program running in the terminal. ''' ) opt('selection_background', '#fffacd', option_type='to_color_or_none', ) # colors.table {{{ agr('colors.table', 'The color table', ''' The 256 terminal colors. There are 8 basic colors, each color has a dull and bright version, for the first 16 colors. You can set the remaining 240 colors as color16 to color255. ''') opt('color0', '#000000', option_type='to_color', long_text='black' ) opt('color8', '#767676', option_type='to_color', ) opt('color1', '#cc0403', option_type='to_color', long_text='red' ) opt('color9', '#f2201f', option_type='to_color', ) opt('color2', '#19cb00', option_type='to_color', long_text='green' ) opt('color10', '#23fd00', option_type='to_color', ) opt('color3', '#cecb00', option_type='to_color', long_text='yellow' ) opt('color11', '#fffd00', option_type='to_color', ) opt('color4', '#0d73cc', option_type='to_color', long_text='blue' ) opt('color12', '#1a8fff', option_type='to_color', ) opt('color5', '#cb1ed1', option_type='to_color', long_text='magenta' ) opt('color13', '#fd28ff', option_type='to_color', ) opt('color6', '#0dcdcd', option_type='to_color', long_text='cyan' ) opt('color14', '#14ffff', option_type='to_color', ) opt('color7', '#dddddd', option_type='to_color', long_text='white' ) opt('color15', '#ffffff', option_type='to_color', ) opt('mark1_foreground', 'black', option_type='to_color', long_text='Color for marks of type 1' ) opt('mark1_background', '#98d3cb', option_type='to_color', long_text='Color for marks of type 1 (light steel blue)' ) opt('mark2_foreground', 'black', option_type='to_color', long_text='Color for marks of type 2' ) opt('mark2_background', '#f2dcd3', option_type='to_color', long_text='Color for marks of type 1 (beige)' ) opt('mark3_foreground', 'black', option_type='to_color', long_text='Color for marks of type 3' ) opt('mark3_background', '#f274bc', option_type='to_color', long_text='Color for marks of type 3 (violet)' ) opt('color16', '#000000', option_type='to_color', documented=False, ) opt('color17', '#00005f', option_type='to_color', documented=False, ) opt('color18', '#000087', option_type='to_color', documented=False, ) opt('color19', '#0000af', option_type='to_color', documented=False, ) opt('color20', '#0000d7', option_type='to_color', documented=False, ) opt('color21', '#0000ff', option_type='to_color', documented=False, ) opt('color22', '#005f00', option_type='to_color', documented=False, ) opt('color23', '#005f5f', option_type='to_color', documented=False, ) opt('color24', '#005f87', option_type='to_color', documented=False, ) opt('color25', '#005faf', option_type='to_color', documented=False, ) opt('color26', '#005fd7', option_type='to_color', documented=False, ) opt('color27', '#005fff', option_type='to_color', documented=False, ) opt('color28', '#008700', option_type='to_color', documented=False, ) opt('color29', '#00875f', option_type='to_color', documented=False, ) opt('color30', '#008787', option_type='to_color', documented=False, ) opt('color31', '#0087af', option_type='to_color', documented=False, ) opt('color32', '#0087d7', option_type='to_color', documented=False, ) opt('color33', '#0087ff', option_type='to_color', documented=False, ) opt('color34', '#00af00', option_type='to_color', documented=False, ) opt('color35', '#00af5f', option_type='to_color', documented=False, ) opt('color36', '#00af87', option_type='to_color', documented=False, ) opt('color37', '#00afaf', option_type='to_color', documented=False, ) opt('color38', '#00afd7', option_type='to_color', documented=False, ) opt('color39', '#00afff', option_type='to_color', documented=False, ) opt('color40', '#00d700', option_type='to_color', documented=False, ) opt('color41', '#00d75f', option_type='to_color', documented=False, ) opt('color42', '#00d787', option_type='to_color', documented=False, ) opt('color43', '#00d7af', option_type='to_color', documented=False, ) opt('color44', '#00d7d7', option_type='to_color', documented=False, ) opt('color45', '#00d7ff', option_type='to_color', documented=False, ) opt('color46', '#00ff00', option_type='to_color', documented=False, ) opt('color47', '#00ff5f', option_type='to_color', documented=False, ) opt('color48', '#00ff87', option_type='to_color', documented=False, ) opt('color49', '#00ffaf', option_type='to_color', documented=False, ) opt('color50', '#00ffd7', option_type='to_color', documented=False, ) opt('color51', '#00ffff', option_type='to_color', documented=False, ) opt('color52', '#5f0000', option_type='to_color', documented=False, ) opt('color53', '#5f005f', option_type='to_color', documented=False, ) opt('color54', '#5f0087', option_type='to_color', documented=False, ) opt('color55', '#5f00af', option_type='to_color', documented=False, ) opt('color56', '#5f00d7', option_type='to_color', documented=False, ) opt('color57', '#5f00ff', option_type='to_color', documented=False, ) opt('color58', '#5f5f00', option_type='to_color', documented=False, ) opt('color59', '#5f5f5f', option_type='to_color', documented=False, ) opt('color60', '#5f5f87', option_type='to_color', documented=False, ) opt('color61', '#5f5faf', option_type='to_color', documented=False, ) opt('color62', '#5f5fd7', option_type='to_color', documented=False, ) opt('color63', '#5f5fff', option_type='to_color', documented=False, ) opt('color64', '#5f8700', option_type='to_color', documented=False, ) opt('color65', '#5f875f', option_type='to_color', documented=False, ) opt('color66', '#5f8787', option_type='to_color', documented=False, ) opt('color67', '#5f87af', option_type='to_color', documented=False, ) opt('color68', '#5f87d7', option_type='to_color', documented=False, ) opt('color69', '#5f87ff', option_type='to_color', documented=False, ) opt('color70', '#5faf00', option_type='to_color', documented=False, ) opt('color71', '#5faf5f', option_type='to_color', documented=False, ) opt('color72', '#5faf87', option_type='to_color', documented=False, ) opt('color73', '#5fafaf', option_type='to_color', documented=False, ) opt('color74', '#5fafd7', option_type='to_color', documented=False, ) opt('color75', '#5fafff', option_type='to_color', documented=False, ) opt('color76', '#5fd700', option_type='to_color', documented=False, ) opt('color77', '#5fd75f', option_type='to_color', documented=False, ) opt('color78', '#5fd787', option_type='to_color', documented=False, ) opt('color79', '#5fd7af', option_type='to_color', documented=False, ) opt('color80', '#5fd7d7', option_type='to_color', documented=False, ) opt('color81', '#5fd7ff', option_type='to_color', documented=False, ) opt('color82', '#5fff00', option_type='to_color', documented=False, ) opt('color83', '#5fff5f', option_type='to_color', documented=False, ) opt('color84', '#5fff87', option_type='to_color', documented=False, ) opt('color85', '#5fffaf', option_type='to_color', documented=False, ) opt('color86', '#5fffd7', option_type='to_color', documented=False, ) opt('color87', '#5fffff', option_type='to_color', documented=False, ) opt('color88', '#870000', option_type='to_color', documented=False, ) opt('color89', '#87005f', option_type='to_color', documented=False, ) opt('color90', '#870087', option_type='to_color', documented=False, ) opt('color91', '#8700af', option_type='to_color', documented=False, ) opt('color92', '#8700d7', option_type='to_color', documented=False, ) opt('color93', '#8700ff', option_type='to_color', documented=False, ) opt('color94', '#875f00', option_type='to_color', documented=False, ) opt('color95', '#875f5f', option_type='to_color', documented=False, ) opt('color96', '#875f87', option_type='to_color', documented=False, ) opt('color97', '#875faf', option_type='to_color', documented=False, ) opt('color98', '#875fd7', option_type='to_color', documented=False, ) opt('color99', '#875fff', option_type='to_color', documented=False, ) opt('color100', '#878700', option_type='to_color', documented=False, ) opt('color101', '#87875f', option_type='to_color', documented=False, ) opt('color102', '#878787', option_type='to_color', documented=False, ) opt('color103', '#8787af', option_type='to_color', documented=False, ) opt('color104', '#8787d7', option_type='to_color', documented=False, ) opt('color105', '#8787ff', option_type='to_color', documented=False, ) opt('color106', '#87af00', option_type='to_color', documented=False, ) opt('color107', '#87af5f', option_type='to_color', documented=False, ) opt('color108', '#87af87', option_type='to_color', documented=False, ) opt('color109', '#87afaf', option_type='to_color', documented=False, ) opt('color110', '#87afd7', option_type='to_color', documented=False, ) opt('color111', '#87afff', option_type='to_color', documented=False, ) opt('color112', '#87d700', option_type='to_color', documented=False, ) opt('color113', '#87d75f', option_type='to_color', documented=False, ) opt('color114', '#87d787', option_type='to_color', documented=False, ) opt('color115', '#87d7af', option_type='to_color', documented=False, ) opt('color116', '#87d7d7', option_type='to_color', documented=False, ) opt('color117', '#87d7ff', option_type='to_color', documented=False, ) opt('color118', '#87ff00', option_type='to_color', documented=False, ) opt('color119', '#87ff5f', option_type='to_color', documented=False, ) opt('color120', '#87ff87', option_type='to_color', documented=False, ) opt('color121', '#87ffaf', option_type='to_color', documented=False, ) opt('color122', '#87ffd7', option_type='to_color', documented=False, ) opt('color123', '#87ffff', option_type='to_color', documented=False, ) opt('color124', '#af0000', option_type='to_color', documented=False, ) opt('color125', '#af005f', option_type='to_color', documented=False, ) opt('color126', '#af0087', option_type='to_color', documented=False, ) opt('color127', '#af00af', option_type='to_color', documented=False, ) opt('color128', '#af00d7', option_type='to_color', documented=False, ) opt('color129', '#af00ff', option_type='to_color', documented=False, ) opt('color130', '#af5f00', option_type='to_color', documented=False, ) opt('color131', '#af5f5f', option_type='to_color', documented=False, ) opt('color132', '#af5f87', option_type='to_color', documented=False, ) opt('color133', '#af5faf', option_type='to_color', documented=False, ) opt('color134', '#af5fd7', option_type='to_color', documented=False, ) opt('color135', '#af5fff', option_type='to_color', documented=False, ) opt('color136', '#af8700', option_type='to_color', documented=False, ) opt('color137', '#af875f', option_type='to_color', documented=False, ) opt('color138', '#af8787', option_type='to_color', documented=False, ) opt('color139', '#af87af', option_type='to_color', documented=False, ) opt('color140', '#af87d7', option_type='to_color', documented=False, ) opt('color141', '#af87ff', option_type='to_color', documented=False, ) opt('color142', '#afaf00', option_type='to_color', documented=False, ) opt('color143', '#afaf5f', option_type='to_color', documented=False, ) opt('color144', '#afaf87', option_type='to_color', documented=False, ) opt('color145', '#afafaf', option_type='to_color', documented=False, ) opt('color146', '#afafd7', option_type='to_color', documented=False, ) opt('color147', '#afafff', option_type='to_color', documented=False, ) opt('color148', '#afd700', option_type='to_color', documented=False, ) opt('color149', '#afd75f', option_type='to_color', documented=False, ) opt('color150', '#afd787', option_type='to_color', documented=False, ) opt('color151', '#afd7af', option_type='to_color', documented=False, ) opt('color152', '#afd7d7', option_type='to_color', documented=False, ) opt('color153', '#afd7ff', option_type='to_color', documented=False, ) opt('color154', '#afff00', option_type='to_color', documented=False, ) opt('color155', '#afff5f', option_type='to_color', documented=False, ) opt('color156', '#afff87', option_type='to_color', documented=False, ) opt('color157', '#afffaf', option_type='to_color', documented=False, ) opt('color158', '#afffd7', option_type='to_color', documented=False, ) opt('color159', '#afffff', option_type='to_color', documented=False, ) opt('color160', '#d70000', option_type='to_color', documented=False, ) opt('color161', '#d7005f', option_type='to_color', documented=False, ) opt('color162', '#d70087', option_type='to_color', documented=False, ) opt('color163', '#d700af', option_type='to_color', documented=False, ) opt('color164', '#d700d7', option_type='to_color', documented=False, ) opt('color165', '#d700ff', option_type='to_color', documented=False, ) opt('color166', '#d75f00', option_type='to_color', documented=False, ) opt('color167', '#d75f5f', option_type='to_color', documented=False, ) opt('color168', '#d75f87', option_type='to_color', documented=False, ) opt('color169', '#d75faf', option_type='to_color', documented=False, ) opt('color170', '#d75fd7', option_type='to_color', documented=False, ) opt('color171', '#d75fff', option_type='to_color', documented=False, ) opt('color172', '#d78700', option_type='to_color', documented=False, ) opt('color173', '#d7875f', option_type='to_color', documented=False, ) opt('color174', '#d78787', option_type='to_color', documented=False, ) opt('color175', '#d787af', option_type='to_color', documented=False, ) opt('color176', '#d787d7', option_type='to_color', documented=False, ) opt('color177', '#d787ff', option_type='to_color', documented=False, ) opt('color178', '#d7af00', option_type='to_color', documented=False, ) opt('color179', '#d7af5f', option_type='to_color', documented=False, ) opt('color180', '#d7af87', option_type='to_color', documented=False, ) opt('color181', '#d7afaf', option_type='to_color', documented=False, ) opt('color182', '#d7afd7', option_type='to_color', documented=False, ) opt('color183', '#d7afff', option_type='to_color', documented=False, ) opt('color184', '#d7d700', option_type='to_color', documented=False, ) opt('color185', '#d7d75f', option_type='to_color', documented=False, ) opt('color186', '#d7d787', option_type='to_color', documented=False, ) opt('color187', '#d7d7af', option_type='to_color', documented=False, ) opt('color188', '#d7d7d7', option_type='to_color', documented=False, ) opt('color189', '#d7d7ff', option_type='to_color', documented=False, ) opt('color190', '#d7ff00', option_type='to_color', documented=False, ) opt('color191', '#d7ff5f', option_type='to_color', documented=False, ) opt('color192', '#d7ff87', option_type='to_color', documented=False, ) opt('color193', '#d7ffaf', option_type='to_color', documented=False, ) opt('color194', '#d7ffd7', option_type='to_color', documented=False, ) opt('color195', '#d7ffff', option_type='to_color', documented=False, ) opt('color196', '#ff0000', option_type='to_color', documented=False, ) opt('color197', '#ff005f', option_type='to_color', documented=False, ) opt('color198', '#ff0087', option_type='to_color', documented=False, ) opt('color199', '#ff00af', option_type='to_color', documented=False, ) opt('color200', '#ff00d7', option_type='to_color', documented=False, ) opt('color201', '#ff00ff', option_type='to_color', documented=False, ) opt('color202', '#ff5f00', option_type='to_color', documented=False, ) opt('color203', '#ff5f5f', option_type='to_color', documented=False, ) opt('color204', '#ff5f87', option_type='to_color', documented=False, ) opt('color205', '#ff5faf', option_type='to_color', documented=False, ) opt('color206', '#ff5fd7', option_type='to_color', documented=False, ) opt('color207', '#ff5fff', option_type='to_color', documented=False, ) opt('color208', '#ff8700', option_type='to_color', documented=False, ) opt('color209', '#ff875f', option_type='to_color', documented=False, ) opt('color210', '#ff8787', option_type='to_color', documented=False, ) opt('color211', '#ff87af', option_type='to_color', documented=False, ) opt('color212', '#ff87d7', option_type='to_color', documented=False, ) opt('color213', '#ff87ff', option_type='to_color', documented=False, ) opt('color214', '#ffaf00', option_type='to_color', documented=False, ) opt('color215', '#ffaf5f', option_type='to_color', documented=False, ) opt('color216', '#ffaf87', option_type='to_color', documented=False, ) opt('color217', '#ffafaf', option_type='to_color', documented=False, ) opt('color218', '#ffafd7', option_type='to_color', documented=False, ) opt('color219', '#ffafff', option_type='to_color', documented=False, ) opt('color220', '#ffd700', option_type='to_color', documented=False, ) opt('color221', '#ffd75f', option_type='to_color', documented=False, ) opt('color222', '#ffd787', option_type='to_color', documented=False, ) opt('color223', '#ffd7af', option_type='to_color', documented=False, ) opt('color224', '#ffd7d7', option_type='to_color', documented=False, ) opt('color225', '#ffd7ff', option_type='to_color', documented=False, ) opt('color226', '#ffff00', option_type='to_color', documented=False, ) opt('color227', '#ffff5f', option_type='to_color', documented=False, ) opt('color228', '#ffff87', option_type='to_color', documented=False, ) opt('color229', '#ffffaf', option_type='to_color', documented=False, ) opt('color230', '#ffffd7', option_type='to_color', documented=False, ) opt('color231', '#ffffff', option_type='to_color', documented=False, ) opt('color232', '#080808', option_type='to_color', documented=False, ) opt('color233', '#121212', option_type='to_color', documented=False, ) opt('color234', '#1c1c1c', option_type='to_color', documented=False, ) opt('color235', '#262626', option_type='to_color', documented=False, ) opt('color236', '#303030', option_type='to_color', documented=False, ) opt('color237', '#3a3a3a', option_type='to_color', documented=False, ) opt('color238', '#444444', option_type='to_color', documented=False, ) opt('color239', '#4e4e4e', option_type='to_color', documented=False, ) opt('color240', '#585858', option_type='to_color', documented=False, ) opt('color241', '#626262', option_type='to_color', documented=False, ) opt('color242', '#6c6c6c', option_type='to_color', documented=False, ) opt('color243', '#767676', option_type='to_color', documented=False, ) opt('color244', '#808080', option_type='to_color', documented=False, ) opt('color245', '#8a8a8a', option_type='to_color', documented=False, ) opt('color246', '#949494', option_type='to_color', documented=False, ) opt('color247', '#9e9e9e', option_type='to_color', documented=False, ) opt('color248', '#a8a8a8', option_type='to_color', documented=False, ) opt('color249', '#b2b2b2', option_type='to_color', documented=False, ) opt('color250', '#bcbcbc', option_type='to_color', documented=False, ) opt('color251', '#c6c6c6', option_type='to_color', documented=False, ) opt('color252', '#d0d0d0', option_type='to_color', documented=False, ) opt('color253', '#dadada', option_type='to_color', documented=False, ) opt('color254', '#e4e4e4', option_type='to_color', documented=False, ) opt('color255', '#eeeeee', option_type='to_color', documented=False, ) egr() # }}} egr() # }}} # advanced {{{ agr('advanced', 'Advanced') opt('shell', '.', long_text=''' The shell program to execute. The default value of :code:`.` means to use the value of of the :envvar:`SHELL` environment variable or if unset, whatever shell is set as the default shell for the current user. Note that on macOS if you change this, you might need to add :code:`--login` and :code:`--interactive` to ensure that the shell starts in interactive mode and reads its startup rc files. Environment variables are expanded in this setting. ''' ) opt('editor', '.', long_text=''' The terminal based text editor (such as :program:`vim` or :program:`nano`) to use when editing the kitty config file or similar tasks. The default value of :code:`.` means to use the environment variables :envvar:`VISUAL` and :envvar:`EDITOR` in that order. If these variables aren't set, kitty will run your :opt:`shell` (:code:`$SHELL -l -i -c env`) to see if your shell startup rc files set :envvar:`VISUAL` or :envvar:`EDITOR`. If that doesn't work, kitty will cycle through various known editors (:program:`vim`, :program:`emacs`, etc.) and take the first one that exists on your system. ''' ) opt('close_on_child_death', 'no', option_type='to_bool', ctype='bool', long_text=''' Close the window when the child process (usually the shell) exits. With the default value :code:`no`, the terminal will remain open when the child exits as long as there are still other processes outputting to the terminal (for example disowned or backgrounded processes). When enabled with :code:`yes`, the window will close as soon as the child process exits. Note that setting it to :code:`yes` means that any background processes still using the terminal can fail silently because their stdout/stderr/stdin no longer work. ''' ) opt('+remote_control_password', '', option_type='remote_control_password', add_to_default=False, has_secret=True, long_text=''' Allow other programs to control kitty using passwords. This option can be specified multiple times to add multiple passwords. If no passwords are present kitty will ask the user for permission if a program tries to use remote control with a password. A password can also *optionally* be associated with a set of allowed remote control actions. For example:: remote_control_password "my passphrase" get-colors set-colors focus-window focus-tab Only the specified actions will be allowed when using this password. Glob patterns can be used too, for example:: remote_control_password "my passphrase" set-tab-* resize-* To get a list of available actions, run:: kitten @ --help A set of actions to be allowed when no password is sent can be specified by using an empty password. For example:: remote_control_password "" *-colors Finally, the path to a python module can be specified that provides a function :code:`is_cmd_allowed` that is used to check every remote control command. For example:: remote_control_password "my passphrase" my_rc_command_checker.py Relative paths are resolved from the kitty configuration directory. See :ref:`rc_custom_auth` for details. ''') opt('allow_remote_control', 'no', choices=('password', 'socket-only', 'socket', 'no', 'n', 'false', 'yes', 'y', 'true'), long_text=''' Allow other programs to control kitty. If you turn this on, other programs can control all aspects of kitty, including sending text to kitty windows, opening new windows, closing windows, reading the content of windows, etc. Note that this even works over SSH connections. The default setting of :code:`no` prevents any form of remote control. The meaning of the various values are: :code:`password` Remote control requests received over both the TTY device and the socket are confirmed based on passwords, see :opt:`remote_control_password`. :code:`socket-only` Remote control requests received over a socket are accepted unconditionally. Requests received over the TTY are denied. See :opt:`listen_on`. :code:`socket` Remote control requests received over a socket are accepted unconditionally. Requests received over the TTY are confirmed based on password. :code:`no` Remote control is completely disabled. :code:`yes` Remote control requests are always accepted. ''' ) opt('listen_on', 'none', long_text=''' Listen to the specified socket for remote control connections. Note that this will apply to all kitty instances. It can be overridden by the :option:`kitty --listen-on` command line option. For UNIX sockets, such as :code:`unix:${TEMP}/mykitty` or :code:`unix:@mykitty` (on Linux). Environment variables are expanded and relative paths are resolved with respect to the temporary directory. If :code:`{kitty_pid}` is present, then it is replaced by the PID of the kitty process, otherwise the PID of the kitty process is appended to the value, with a hyphen. For TCP sockets such as :code:`tcp:localhost:0` a random port is always used even if a non-zero port number is specified. See the help for :option:`kitty --listen-on` for more details. Note that this will be ignored unless :opt:`allow_remote_control` is set to either: :code:`yes`, :code:`socket` or :code:`socket-only`. Changing this option by reloading the config is not supported. ''' ) opt('+env', '', option_type='env', add_to_default=False, long_text=''' Specify the environment variables to be set in all child processes. Using the name with an equal sign (e.g. :code:`env VAR=`) will set it to the empty string. Specifying only the name (e.g. :code:`env VAR`) will remove the variable from the child process' environment. Note that environment variables are expanded recursively, for example:: env VAR1=a env VAR2=${HOME}/${VAR1}/b The value of :code:`VAR2` will be :code:`/a/b`. ''' ) opt('+filter_notification', '', option_type='filter_notification', add_to_default=False, long_text=''' Specify rules to filter out notifications sent by applications running in kitty. Can be specified multiple times to create multiple filter rules. A rule specification is of the form :code:`field:regexp`. A filter rule can match on any of the fields: :code:`title`, :code:`body`, :code:`app`, :code:`type`. The special value of :code:`all` filters out all notifications. Rules can be combined using Boolean operators. Some examples:: filter_notification title:hello or body:"abc.*def" # filter out notification from vim except for ones about updates, (?i) # makes matching case insensitive. filter_notification app:"[ng]?vim" and not body:"(?i)update" # filter out all notifications filter_notification all The field :code:`app` is the name of the application sending the notification and :code:`type` is the type of the notification. Not all applications will send these fields, so you can also match on the title and body of the notification text. More sophisticated programmatic filtering and custom actions on notifications can be done by creating a notifications.py file in the kitty config directory (:file:`~/.config/kitty`). An annotated sample is :link:`available `. ''') opt('+watcher', '', option_type='store_multiple', add_to_default=False, long_text=''' Path to python file which will be loaded for :ref:`watchers`. Can be specified more than once to load multiple watchers. The watchers will be added to every kitty window. Relative paths are resolved relative to the kitty config directory. Note that reloading the config will only affect windows created after the reload. ''' ) opt('+exe_search_path', '', option_type='store_multiple', add_to_default=False, long_text=''' Control where kitty finds the programs to run. The default search order is: First search the system wide :code:`PATH`, then :file:`~/.local/bin` and :file:`~/bin`. If still not found, the :code:`PATH` defined in the login shell after sourcing all its startup files is tried. Finally, if present, the :code:`PATH` specified by the :opt:`env` option is tried. This option allows you to prepend, append, or remove paths from this search order. It can be specified multiple times for multiple paths. A simple path will be prepended to the search order. A path that starts with the :code:`+` sign will be append to the search order, after :file:`~/bin` above. A path that starts with the :code:`-` sign will be removed from the entire search order. For example:: exe_search_path /some/prepended/path exe_search_path +/some/appended/path exe_search_path -/some/excluded/path ''' ) opt('update_check_interval', '24', option_type='float', long_text=''' The interval to periodically check if an update to kitty is available (in hours). If an update is found, a system notification is displayed informing you of the available update. The default is to check every 24 hours, set to zero to disable. Update checking is only done by the official binary builds. Distro packages or source builds do not do update checking. Changing this option by reloading the config is not supported. ''' ) opt('startup_session', 'none', option_type='config_or_absolute_path', long_text=''' Path to a session file to use for all kitty instances. Can be overridden by using the :option:`kitty --session` :code:`=none` command line option for individual instances. See :ref:`sessions` in the kitty documentation for details. Note that relative paths are interpreted with respect to the kitty config directory. Environment variables in the path are expanded. Changing this option by reloading the config is not supported. Note that if kitty is invoked with command line arguments specifying a command to run, this option is ignored. ''' ) opt('clipboard_control', 'write-clipboard write-primary read-clipboard-ask read-primary-ask', option_type='clipboard_control', long_text=''' Allow programs running in kitty to read and write from the clipboard. You can control exactly which actions are allowed. The possible actions are: :code:`write-clipboard`, :code:`read-clipboard`, :code:`write-primary`, :code:`read-primary`, :code:`read-clipboard-ask`, :code:`read-primary-ask`. The default is to allow writing to the clipboard and primary selection and to ask for permission when a program tries to read from the clipboard. Note that disabling the read confirmation is a security risk as it means that any program, even the ones running on a remote server via SSH can read your clipboard. See also :opt:`clipboard_max_size`. ''' ) opt('clipboard_max_size', '512', option_type='positive_float', long_text=''' The maximum size (in MB) of data from programs running in kitty that will be stored for writing to the system clipboard. A value of zero means no size limit is applied. See also :opt:`clipboard_control`. ''' ) opt('file_transfer_confirmation_bypass', '', has_secret=True, long_text=''' The password that can be supplied to the :doc:`file transfer kitten ` to skip the transfer confirmation prompt. This should only be used when initiating transfers from trusted computers, over trusted networks or encrypted transports, as it allows any programs running on the remote machine to read/write to the local filesystem, without permission. ''' ) opt('allow_hyperlinks', 'yes', option_type='allow_hyperlinks', ctype='bool', long_text=''' Process :term:`hyperlink ` escape sequences (OSC 8). If disabled OSC 8 escape sequences are ignored. Otherwise they become clickable links, that you can click with the mouse or by using the :doc:`hints kitten `. The special value of :code:`ask` means that kitty will ask before opening the link when clicked. ''' ) opt('shell_integration', 'enabled', option_type='shell_integration', long_text=''' Enable shell integration on supported shells. This enables features such as jumping to previous prompts, browsing the output of the previous command in a pager, etc. on supported shells. Set to :code:`disabled` to turn off shell integration, completely. It is also possible to disable individual features, set to a space separated list of these values: :code:`no-rc`, :code:`no-cursor`, :code:`no-title`, :code:`no-cwd`, :code:`no-prompt-mark`, :code:`no-complete`, :code:`no-sudo`. See :ref:`Shell integration ` for details. ''' ) opt('allow_cloning', 'ask', choices=('yes', 'y', 'true', 'no', 'n', 'false', 'ask'), long_text=''' Control whether programs running in the terminal can request new windows to be created. The canonical example is :ref:`clone-in-kitty `. By default, kitty will ask for permission for each clone request. Allowing cloning unconditionally gives programs running in the terminal (including over SSH) permission to execute arbitrary code, as the user who is running the terminal, on the computer that the terminal is running on. ''' ) opt('clone_source_strategies', 'venv,conda,env_var,path', option_type='clone_source_strategies', long_text=''' Control what shell code is sourced when running :command:`clone-in-kitty` in the newly cloned window. The supported strategies are: :code:`venv` Source the file :file:`$VIRTUAL_ENV/bin/activate`. This is used by the Python stdlib venv module and allows cloning venvs automatically. :code:`conda` Run :code:`conda activate $CONDA_DEFAULT_ENV`. This supports the virtual environments created by :program:`conda`. :code:`env_var` Execute the contents of the environment variable :envvar:`KITTY_CLONE_SOURCE_CODE` with :code:`eval`. :code:`path` Source the file pointed to by the environment variable :envvar:`KITTY_CLONE_SOURCE_PATH`. This option must be a comma separated list of the above values. Only the first valid match, in the order specified, is sourced. ''' ) opt('notify_on_cmd_finish', 'never', option_type='notify_on_cmd_finish', long_text=''' Show a desktop notification when a long-running command finishes (needs :opt:`shell_integration`). The possible values are: :code:`never` Never send a notification. :code:`unfocused` Only send a notification when the window does not have keyboard focus. :code:`invisible` Only send a notification when the window both is unfocused and not visible to the user, for example, because it is in an inactive tab or its OS window is not currently visible (on platforms that support OS window visibility querying this considers an OS Window visible iff it is active). :code:`always` Always send a notification, regardless of window state. There are two optional arguments: First, the minimum duration for what is considered a long running command. The default is 5 seconds. Specify a second argument to set the duration. For example: :code:`invisible 15`. Do not set the value too small, otherwise a command that launches a new OS Window and exits will spam a notification. Second, the action to perform. The default is :code:`notify`. The possible values are: :code:`notify` Send a desktop notification. The subsequent arguments are optional and specify when the notification is automatically cleared. The set of possible events when the notification is cleared are: :code:`focus` and :code:`next`. :code:`focus` means that when the notification policy is :code:`unfocused` or :code:`invisible` the notification is automatically cleared when the window regains focus. The value of :code:`next` means that the previous notification is cleared when the next notification is shown. The default when no arguments are specified is: :code:`focus next`. :code:`bell` Ring the terminal bell. :code:`command` Run a custom command. All subsequent arguments are the cmdline to run. Some more examples:: # Send a notification when a command takes more than 5 seconds in an unfocused window notify_on_cmd_finish unfocused # Send a notification when a command takes more than 10 seconds in a invisible window notify_on_cmd_finish invisible 10.0 # Ring a bell when a command takes more than 10 seconds in a invisible window notify_on_cmd_finish invisible 10.0 bell # Run 'notify-send' when a command takes more than 10 seconds in a invisible window # Here %c is replaced by the current command line and %s by the job exit code notify_on_cmd_finish invisible 10.0 command notify-send "job finished with status: %s" %c # Do not clear previous notification when next command finishes or window regains focus notify_on_cmd_finish invisible 5.0 notify ''' ) opt('term', 'xterm-kitty', long_text=''' The value of the :envvar:`TERM` environment variable to set. Changing this can break many terminal programs, only change it if you know what you are doing, not because you read some advice on "Stack Overflow" to change it. The :envvar:`TERM` variable is used by various programs to get information about the capabilities and behavior of the terminal. If you change it, depending on what programs you run, and how different the terminal you are changing it to is, various things from key-presses, to colors, to various advanced features may not work. Changing this option by reloading the config will only affect newly created windows. ''' ) opt('terminfo_type', 'path', choices=('path', 'direct', 'none'), long_text=''' The value of the :envvar:`TERMINFO` environment variable to set. This variable is used by programs running in the terminal to search for terminfo databases. The default value of :code:`path` causes kitty to set it to a filesystem location containing the kitty terminfo database. A value of :code:`direct` means put the entire database into the env var directly. This can be useful when connecting to containers, for example. But, note that not all software supports this. A value of :code:`none` means do not touch the variable. ''' ) opt('forward_stdio', 'no', option_type='to_bool', long_text=''' Forward STDOUT and STDERR of the kitty process to child processes. This is useful for debugging as it allows child processes to print to kitty's STDOUT directly. For example, :code:`echo hello world >&$KITTY_STDIO_FORWARDED` in a shell will print to the parent kitty's STDOUT. Sets the :code:`KITTY_STDIO_FORWARDED=fdnum` environment variable so child processes know about the forwarding. Note that on macOS this prevents the shell from being run via the login utility so getlogin() will not work in programs run in this session. ''') opt('+menu_map', '', option_type='menu_map', add_to_default=False, ctype='!menu_map', long_text=''' Specify entries for various menus in kitty. Currently only the global menubar on macOS is supported. For example:: menu_map global "Actions::Launch something special" launch --hold --type=os-window sh -c "echo hello world" This will create a menu entry named "Launch something special" in an "Actions" menu in the macOS global menubar. Sub-menus can be created by adding more levels separated by the :code:`::` characters. ''' ) egr() # }}} # os {{{ agr('os', 'OS specific tweaks') opt('wayland_titlebar_color', 'system', option_type='titlebar_color', ctype='uint', long_text=''' The color of the kitty window's titlebar on Wayland systems with client side window decorations such as GNOME. A value of :code:`system` means to use the default system colors, a value of :code:`background` means to use the background color of the currently active kitty window and finally you can use an arbitrary color, such as :code:`#12af59` or :code:`red`. ''' ) opt('macos_titlebar_color', 'system', option_type='macos_titlebar_color', ctype='int', long_text=''' The color of the kitty window's titlebar on macOS. A value of :code:`system` means to use the default system color, :code:`light` or :code:`dark` can also be used to set it explicitly. A value of :code:`background` means to use the background color of the currently active window and finally you can use an arbitrary color, such as :code:`#12af59` or :code:`red`. WARNING: This option works by using a hack when arbitrary color (or :code:`background`) is configured, as there is no proper Cocoa API for it. It sets the background color of the entire window and makes the titlebar transparent. As such it is incompatible with :opt:`background_opacity`. If you want to use both, you are probably better off just hiding the titlebar with :opt:`hide_window_decorations`. ''' ) opt('macos_option_as_alt', 'no', option_type='macos_option_as_alt', ctype='uint', long_text=''' Use the :kbd:`Option` key as an :kbd:`Alt` key on macOS. With this set to :code:`no`, kitty will use the macOS native :kbd:`Option+Key` to enter Unicode character behavior. This will break any :kbd:`Alt+Key` keyboard shortcuts in your terminal programs, but you can use the macOS Unicode input technique. You can use the values: :code:`left`, :code:`right` or :code:`both` to use only the left, right or both :kbd:`Option` keys as :kbd:`Alt`, instead. Note that kitty itself always treats :kbd:`Option` the same as :kbd:`Alt`. This means you cannot use this option to configure different kitty shortcuts for :kbd:`Option+Key` vs. :kbd:`Alt+Key`. Also, any kitty shortcuts using :kbd:`Option/Alt+Key` will take priority, so that any such key presses will not be passed to terminal programs running inside kitty. Changing this option by reloading the config is not supported. ''' ) opt('macos_hide_from_tasks', 'no', option_type='to_bool', ctype='bool', long_text=''' Hide the kitty window from running tasks on macOS (:kbd:`⌘+Tab` and the Dock). Changing this option by reloading the config is not supported. ''' ) opt('macos_quit_when_last_window_closed', 'no', option_type='to_bool', ctype='bool', long_text=''' Have kitty quit when all the top-level windows are closed on macOS. By default, kitty will stay running, even with no open windows, as is the expected behavior on macOS. ''' ) opt('macos_window_resizable', 'yes', option_type='to_bool', ctype='bool', long_text=''' Disable this if you want kitty top-level OS windows to not be resizable on macOS. ''' ) opt('macos_thicken_font', '0', option_type='positive_float', ctype='float', long_text=''' Draw an extra border around the font with the given width, to increase legibility at small font sizes on macOS. For example, a value of :code:`0.75` will result in rendering that looks similar to sub-pixel antialiasing at common font sizes. Note that in modern kitty, this option is obsolete (although still supported). Consider using :opt:`text_composition_strategy` instead. ''' ) opt('macos_traditional_fullscreen', 'no', option_type='to_bool', ctype='bool', long_text=''' Use the macOS traditional full-screen transition, that is faster, but less pretty. ''' ) opt('macos_show_window_title_in', 'all', choices=('all', 'menubar', 'none', 'window'), ctype='window_title_in', long_text=''' Control where the window title is displayed on macOS. A value of :code:`window` will show the title of the currently active window at the top of the macOS window. A value of :code:`menubar` will show the title of the currently active window in the macOS global menu bar, making use of otherwise wasted space. A value of :code:`all` will show the title in both places, and :code:`none` hides the title. See :opt:`macos_menubar_title_max_length` for how to control the length of the title in the menu bar. ''' ) opt('macos_menubar_title_max_length', '0', option_type='positive_int', ctype='int', long_text=''' The maximum number of characters from the window title to show in the macOS global menu bar. Values less than one means that there is no maximum limit. ''' ) opt('macos_custom_beam_cursor', 'no', option_type='to_bool', long_text=''' Use a custom mouse cursor for macOS that is easier to see on both light and dark backgrounds. Nowadays, the default macOS cursor already comes with a white border. WARNING: this might make your mouse cursor invisible on dual GPU machines. Changing this option by reloading the config is not supported. ''' ) opt('macos_colorspace', 'srgb', choices=('srgb', 'default', 'displayp3'), ctype='macos_colorspace', long_text=''' The colorspace in which to interpret terminal colors. The default of :code:`srgb` will cause colors to match those seen in web browsers. The value of :code:`default` will use whatever the native colorspace of the display is. The value of :code:`displayp3` will use Apple's special snowflake display P3 color space, which will result in over saturated (brighter) colors with some color shift. Reloading configuration will change this value only for newly created OS windows. ''') opt('linux_display_server', 'auto', choices=('auto', 'wayland', 'x11'), long_text=''' Choose between Wayland and X11 backends. By default, an appropriate backend based on the system state is chosen automatically. Set it to :code:`x11` or :code:`wayland` to force the choice. Changing this option by reloading the config is not supported. ''' ) opt('wayland_enable_ime', 'yes', option_type='to_bool', ctype='bool', long_text=''' Enable Input Method Extension on Wayland. This is typically used for inputting text in East Asian languages. However, its implementation in Wayland is often buggy and introduces latency into the input loop, so disable this if you know you dont need it. Changing this option by reloading the config is not supported, it will not have any effect. ''') egr() # }}} # shortcuts {{{ agr('shortcuts', 'Keyboard shortcuts', ''' Keys are identified simply by their lowercase Unicode characters. For example: :code:`a` for the :kbd:`A` key, :code:`[` for the left square bracket key, etc. For functional keys, such as :kbd:`Enter` or :kbd:`Escape`, the names are present at :ref:`Functional key definitions `. For modifier keys, the names are :kbd:`ctrl` (:kbd:`control`, :kbd:`⌃`), :kbd:`shift` (:kbd:`⇧`), :kbd:`alt` (:kbd:`opt`, :kbd:`option`, :kbd:`⌥`), :kbd:`super` (:kbd:`cmd`, :kbd:`command`, :kbd:`⌘`). Simple shortcut mapping is done with the :code:`map` directive. For full details on advanced mapping including modal and per application maps, see :doc:`mapping`. Some quick examples to illustrate common tasks:: # unmap a keyboard shortcut, passing it to the program running in kitty map kitty_mod+space # completely ignore a keyboard event map ctrl+alt+f1 discard_event # combine multiple actions map kitty_mod+e combine : new_window : next_layout # multi-key shortcuts map ctrl+x>ctrl+y>z action The full list of actions that can be mapped to key presses is available :doc:`here `. ''') opt('kitty_mod', 'ctrl+shift', option_type='to_modifiers', long_text=''' Special modifier key alias for default shortcuts. You can change the value of this option to alter all default shortcuts that use :opt:`kitty_mod`. ''' ) opt('clear_all_shortcuts', 'no', option_type='clear_all_shortcuts', long_text=''' Remove all shortcut definitions up to this point. Useful, for instance, to remove the default shortcuts. ''' ) opt('+action_alias', 'launch_tab launch --type=tab --cwd=current', option_type='action_alias', add_to_default=False, long_text=''' Define action aliases to avoid repeating the same options in multiple mappings. Aliases can be defined for any action and will be expanded recursively. For example, the above alias allows you to create mappings to launch a new tab in the current working directory without duplication:: map f1 launch_tab vim map f2 launch_tab emacs Similarly, to alias kitten invocation:: action_alias hints kitten hints --hints-offset=0 ''' ) opt('+kitten_alias', 'hints hints --hints-offset=0', option_type='kitten_alias', add_to_default=False, long_text=''' Like :opt:`action_alias` above, but specifically for kittens. Generally, prefer to use :opt:`action_alias`. This option is a legacy version, present for backwards compatibility. It causes all invocations of the aliased kitten to be substituted. So the example above will cause all invocations of the hints kitten to have the :option:`--hints-offset=0 ` option applied. ''' ) # shortcuts.clipboard {{{ agr('shortcuts.clipboard', 'Clipboard') map('Copy to clipboard', 'copy_to_clipboard kitty_mod+c copy_to_clipboard', long_text=''' There is also a :ac:`copy_or_interrupt` action that can be optionally mapped to :kbd:`Ctrl+C`. It will copy only if there is a selection and send an interrupt otherwise. Similarly, :ac:`copy_and_clear_or_interrupt` will copy and clear the selection or send an interrupt if there is no selection. ''' ) map('Copy to clipboard', 'copy_to_clipboard cmd+c copy_to_clipboard', only='macos', ) map('Paste from clipboard', 'paste_from_clipboard kitty_mod+v paste_from_clipboard', ) map('Paste from clipboard', 'paste_from_clipboard cmd+v paste_from_clipboard', only='macos', ) map('Paste from selection', 'paste_from_selection kitty_mod+s paste_from_selection', ) map('Paste from selection', 'paste_from_selection shift+insert paste_from_selection', ) map('Pass selection to program', 'pass_selection_to_program kitty_mod+o pass_selection_to_program', long_text=''' You can also pass the contents of the current selection to any program with :ac:`pass_selection_to_program`. By default, the system's open program is used, but you can specify your own, the selection will be passed as a command line argument to the program. For example:: map kitty_mod+o pass_selection_to_program firefox You can pass the current selection to a terminal program running in a new kitty window, by using the :code:`@selection` placeholder:: map kitty_mod+y new_window less @selection ''' ) egr() # }}} # shortcuts.scrolling {{{ agr('shortcuts.scrolling', 'Scrolling') map('Scroll line up', 'scroll_line_up kitty_mod+up scroll_line_up', ) map('Scroll line up', 'scroll_line_up kitty_mod+k scroll_line_up', ) map('Scroll line up', 'scroll_line_up opt+cmd+page_up scroll_line_up', only='macos', ) map('Scroll line up', 'scroll_line_up cmd+up scroll_line_up', only='macos', ) map('Scroll line down', 'scroll_line_down kitty_mod+down scroll_line_down', ) map('Scroll line down', 'scroll_line_down kitty_mod+j scroll_line_down', ) map('Scroll line down', 'scroll_line_down opt+cmd+page_down scroll_line_down', only='macos', ) map('Scroll line down', 'scroll_line_down cmd+down scroll_line_down', only='macos', ) map('Scroll page up', 'scroll_page_up kitty_mod+page_up scroll_page_up', ) map('Scroll page up', 'scroll_page_up cmd+page_up scroll_page_up', only='macos', ) map('Scroll page down', 'scroll_page_down kitty_mod+page_down scroll_page_down', ) map('Scroll page down', 'scroll_page_down cmd+page_down scroll_page_down', only='macos', ) map('Scroll to top', 'scroll_home kitty_mod+home scroll_home', ) map('Scroll to top', 'scroll_home cmd+home scroll_home', only='macos', ) map('Scroll to bottom', 'scroll_end kitty_mod+end scroll_end', ) map('Scroll to bottom', 'scroll_end cmd+end scroll_end', only='macos', ) map('Scroll to previous shell prompt', 'scroll_to_previous_prompt kitty_mod+z scroll_to_prompt -1', long_text=''' Use a parameter of :code:`0` for :ac:`scroll_to_prompt` to scroll to the last jumped to or the last clicked position. Requires :ref:`shell integration ` to work. ''' ) map('Scroll to next shell prompt', 'scroll_to_next_prompt kitty_mod+x scroll_to_prompt 1') map('Browse scrollback buffer in pager', 'show_scrollback kitty_mod+h show_scrollback', long_text=''' You can pipe the contents of the current screen and history buffer as :file:`STDIN` to an arbitrary program using :option:`launch --stdin-source`. For example, the following opens the scrollback buffer in less in an :term:`overlay` window:: map f1 launch --stdin-source=@screen_scrollback --stdin-add-formatting --type=overlay less +G -R For more details on piping screen and buffer contents to external programs, see :doc:`launch`. ''' ) map('Browse output of the last shell command in pager', 'show_last_command_output kitty_mod+g show_last_command_output', long_text=''' You can also define additional shortcuts to get the command output. For example, to get the first command output on screen:: map f1 show_first_command_output_on_screen To get the command output that was last accessed by a keyboard action or mouse action:: map f1 show_last_visited_command_output You can pipe the output of the last command run in the shell using the :ac:`launch` action. For example, the following opens the output in less in an :term:`overlay` window:: map f1 launch --stdin-source=@last_cmd_output --stdin-add-formatting --type=overlay less +G -R To get the output of the first command on the screen, use :code:`@first_cmd_output_on_screen`. To get the output of the last jumped to command, use :code:`@last_visited_cmd_output`. Requires :ref:`shell integration ` to work. ''' ) egr() # }}} # shortcuts.window {{{ agr('shortcuts.window', 'Window management') map('New window', 'new_window kitty_mod+enter new_window', long_text=''' You can open a new :term:`kitty window ` running an arbitrary program, for example:: map kitty_mod+y launch mutt You can open a new window with the current working directory set to the working directory of the current window using:: map ctrl+alt+enter launch --cwd=current You can open a new window that is allowed to control kitty via the kitty remote control facility with :option:`launch --allow-remote-control`. Any programs running in that window will be allowed to control kitty. For example:: map ctrl+enter launch --allow-remote-control some_program You can open a new window next to the currently active window or as the first window, with:: map ctrl+n launch --location=neighbor map ctrl+f launch --location=first For more details, see :doc:`launch`. ''' ) map('New window', 'new_window cmd+enter new_window', only='macos', ) map('New OS window', 'new_os_window kitty_mod+n new_os_window', long_text=''' Works like :ac:`new_window` above, except that it opens a top-level :term:`OS window `. In particular you can use :ac:`new_os_window_with_cwd` to open a window with the current working directory. ''' ) map('New OS window', 'new_os_window cmd+n new_os_window', only='macos', ) map('Close window', 'close_window kitty_mod+w close_window', ) map('Close window', 'close_window shift+cmd+d close_window', only='macos', ) map('Next window', 'next_window kitty_mod+] next_window', ) map('Previous window', 'previous_window kitty_mod+[ previous_window', ) map('Move window forward', 'move_window_forward kitty_mod+f move_window_forward', ) map('Move window backward', 'move_window_backward kitty_mod+b move_window_backward', ) map('Move window to top', 'move_window_to_top kitty_mod+` move_window_to_top', ) map('Start resizing window', 'start_resizing_window kitty_mod+r start_resizing_window', ) map('Start resizing window', 'start_resizing_window cmd+r start_resizing_window', only='macos', ) map('First window', 'first_window kitty_mod+1 first_window', ) map('First window', 'first_window cmd+1 first_window', only='macos', ) map('Second window', 'second_window kitty_mod+2 second_window', ) map('Second window', 'second_window cmd+2 second_window', only='macos', ) map('Third window', 'third_window kitty_mod+3 third_window', ) map('Third window', 'third_window cmd+3 third_window', only='macos', ) map('Fourth window', 'fourth_window kitty_mod+4 fourth_window', ) map('Fourth window', 'fourth_window cmd+4 fourth_window', only='macos', ) map('Fifth window', 'fifth_window kitty_mod+5 fifth_window', ) map('Fifth window', 'fifth_window cmd+5 fifth_window', only='macos', ) map('Sixth window', 'sixth_window kitty_mod+6 sixth_window', ) map('Sixth window', 'sixth_window cmd+6 sixth_window', only='macos', ) map('Seventh window', 'seventh_window kitty_mod+7 seventh_window', ) map('Seventh window', 'seventh_window cmd+7 seventh_window', only='macos', ) map('Eighth window', 'eighth_window kitty_mod+8 eighth_window', ) map('Eighth window', 'eighth_window cmd+8 eighth_window', only='macos', ) map('Ninth window', 'ninth_window kitty_mod+9 ninth_window', ) map('Ninth window', 'ninth_window cmd+9 ninth_window', only='macos', ) map('Tenth window', 'tenth_window kitty_mod+0 tenth_window', ) map('Visually select and focus window', 'focus_visible_window kitty_mod+f7 focus_visible_window', long_text=''' Display overlay numbers and alphabets on the window, and switch the focus to the window when you press the key. When there are only two windows, the focus will be switched directly without displaying the overlay. You can change the overlay characters and their order with option :opt:`visual_window_select_characters`. ''' ) map('Visually swap window with another', 'swap_with_window kitty_mod+f8 swap_with_window', long_text=''' Works like :ac:`focus_visible_window` above, but swaps the window. ''' ) egr() # }}} # shortcuts.tab {{{ agr('shortcuts.tab', 'Tab management') map('Next tab', 'next_tab kitty_mod+right next_tab', ) map('Next tab', 'next_tab shift+cmd+] next_tab', only='macos', ) map('Next tab', 'next_tab ctrl+tab next_tab', ) map('Previous tab', 'previous_tab kitty_mod+left previous_tab', ) map('Previous tab', 'previous_tab shift+cmd+[ previous_tab', only='macos', ) map('Previous tab', 'previous_tab ctrl+shift+tab previous_tab', ) map('New tab', 'new_tab kitty_mod+t new_tab', ) map('New tab', 'new_tab cmd+t new_tab', only='macos', ) map('Close tab', 'close_tab kitty_mod+q close_tab', ) map('Close tab', 'close_tab cmd+w close_tab', only='macos', ) map('Close OS window', 'close_os_window shift+cmd+w close_os_window', only='macos', ) map('Move tab forward', 'move_tab_forward kitty_mod+. move_tab_forward', ) map('Move tab backward', 'move_tab_backward kitty_mod+, move_tab_backward', ) map('Set tab title', 'set_tab_title kitty_mod+alt+t set_tab_title', ) map('Set tab title', 'set_tab_title shift+cmd+i set_tab_title', only='macos', ) egr(''' You can also create shortcuts to go to specific :term:`tabs `, with :code:`1` being the first tab, :code:`2` the second tab and :code:`-1` being the previously active tab, :code:`-2` being the tab active before the previously active tab and so on. Any number larger than the number of tabs goes to the last tab and any number less than the number of previously used tabs in the history goes to the oldest previously used tab in the history:: map ctrl+alt+1 goto_tab 1 map ctrl+alt+2 goto_tab 2 Just as with :ac:`new_window` above, you can also pass the name of arbitrary commands to run when using :ac:`new_tab` and :ac:`new_tab_with_cwd`. Finally, if you want the new tab to open next to the current tab rather than at the end of the tabs list, use:: map ctrl+t new_tab !neighbor [optional cmd to run] ''') # }}} # shortcuts.layout {{{ agr('shortcuts.layout', 'Layout management') map('Next layout', 'next_layout kitty_mod+l next_layout', ) egr(''' You can also create shortcuts to switch to specific :term:`layouts `:: map ctrl+alt+t goto_layout tall map ctrl+alt+s goto_layout stack Similarly, to switch back to the previous layout:: map ctrl+alt+p last_used_layout There is also a :ac:`toggle_layout` action that switches to the named layout or back to the previous layout if in the named layout. Useful to temporarily "zoom" the active window by switching to the stack layout:: map ctrl+alt+z toggle_layout stack ''') # }}} # shortcuts.fonts {{{ agr('shortcuts.fonts', 'Font sizes', ''' You can change the font size for all top-level kitty OS windows at a time or only the current one. ''') map('Increase font size', 'increase_font_size kitty_mod+equal change_font_size all +2.0', ) map('Increase font size', 'increase_font_size kitty_mod+plus change_font_size all +2.0', ) map('Increase font size', 'increase_font_size kitty_mod+kp_add change_font_size all +2.0', ) map('Increase font size', 'increase_font_size cmd+plus change_font_size all +2.0', only='macos', ) map('Increase font size', 'increase_font_size cmd+equal change_font_size all +2.0', only='macos', ) map('Increase font size', 'increase_font_size shift+cmd+equal change_font_size all +2.0', only='macos', ) map('Decrease font size', 'decrease_font_size kitty_mod+minus change_font_size all -2.0', ) map('Decrease font size', 'decrease_font_size kitty_mod+kp_subtract change_font_size all -2.0', ) map('Decrease font size', 'decrease_font_size cmd+minus change_font_size all -2.0', only='macos', ) map('Decrease font size', 'decrease_font_size shift+cmd+minus change_font_size all -2.0', only='macos', ) map('Reset font size', 'reset_font_size kitty_mod+backspace change_font_size all 0', ) map('Reset font size', 'reset_font_size cmd+0 change_font_size all 0', only='macos', ) egr(''' To setup shortcuts for specific font sizes:: map kitty_mod+f6 change_font_size all 10.0 To setup shortcuts to change only the current OS window's font size:: map kitty_mod+f6 change_font_size current 10.0 ''') # }}} # shortcuts.selection {{{ agr('shortcuts.selection', 'Select and act on visible text', ''' Use the hints kitten to select text and either pass it to an external program or insert it into the terminal or copy it to the clipboard. ''') map('Open URL', 'open_url kitty_mod+e open_url_with_hints', long_text=''' Open a currently visible URL using the keyboard. The program used to open the URL is specified in :opt:`open_url_with`. ''' ) map('Insert selected path', 'insert_selected_path kitty_mod+p>f kitten hints --type path --program -', long_text=''' Select a path/filename and insert it into the terminal. Useful, for instance to run :program:`git` commands on a filename output from a previous :program:`git` command. ''' ) map('Open selected path', 'open_selected_path kitty_mod+p>shift+f kitten hints --type path', long_text='Select a path/filename and open it with the default open program.' ) map('Insert selected line', 'insert_selected_line kitty_mod+p>l kitten hints --type line --program -', long_text=''' Select a line of text and insert it into the terminal. Useful for the output of things like: ``ls -1``. ''' ) map('Insert selected word', 'insert_selected_word kitty_mod+p>w kitten hints --type word --program -', long_text='Select words and insert into terminal.' ) map('Insert selected hash', 'insert_selected_hash kitty_mod+p>h kitten hints --type hash --program -', long_text=''' Select something that looks like a hash and insert it into the terminal. Useful with :program:`git`, which uses SHA1 hashes to identify commits. ''' ) map('Open the selected file at the selected line', 'goto_file_line kitty_mod+p>n kitten hints --type linenum', long_text=''' Select something that looks like :code:`filename:linenum` and open it in your default editor at the specified line number. ''' ) map('Open the selected hyperlink', 'open_selected_hyperlink kitty_mod+p>y kitten hints --type hyperlink', long_text=''' Select a :term:`hyperlink ` (i.e. a URL that has been marked as such by the terminal program, for example, by ``ls --hyperlink=auto``). ''' ) egr(''' The hints kitten has many more modes of operation that you can map to different shortcuts. For a full description see :doc:`hints kitten `. ''') # }}} # shortcuts.misc {{{ agr('shortcuts.misc', 'Miscellaneous') map('Show documentation', 'show_kitty_doc kitty_mod+f1 show_kitty_doc overview') map('Toggle fullscreen', 'toggle_fullscreen kitty_mod+f11 toggle_fullscreen', ) map('Toggle fullscreen', 'toggle_fullscreen ctrl+cmd+f toggle_fullscreen', only='macos', ) map('Toggle maximized', 'toggle_maximized kitty_mod+f10 toggle_maximized', ) map('Toggle macOS secure keyboard entry', 'toggle_macos_secure_keyboard_entry opt+cmd+s toggle_macos_secure_keyboard_entry', only='macos', ) map('Unicode input', 'input_unicode_character kitty_mod+u kitten unicode_input', ) map('Unicode input', 'input_unicode_character ctrl+cmd+space kitten unicode_input', only='macos', ) map('Edit config file', 'edit_config_file kitty_mod+f2 edit_config_file', ) map('Edit config file', 'edit_config_file cmd+, edit_config_file', only='macos', ) map('Open the kitty command shell', 'kitty_shell kitty_mod+escape kitty_shell window', long_text=''' Open the kitty shell in a new :code:`window` / :code:`tab` / :code:`overlay` / :code:`os_window` to control kitty using commands. ''' ) map('Increase background opacity', 'increase_background_opacity kitty_mod+a>m set_background_opacity +0.1', ) map('Decrease background opacity', 'decrease_background_opacity kitty_mod+a>l set_background_opacity -0.1', ) map('Make background fully opaque', 'full_background_opacity kitty_mod+a>1 set_background_opacity 1', ) map('Reset background opacity', 'reset_background_opacity kitty_mod+a>d set_background_opacity default', ) map('Reset the terminal', 'reset_terminal kitty_mod+delete clear_terminal reset active', long_text=r''' You can create shortcuts to clear/reset the terminal. For example:: # Reset the terminal map f1 clear_terminal reset active # Clear the terminal screen by erasing all contents map f1 clear_terminal clear active # Clear the terminal scrollback by erasing it map f1 clear_terminal scrollback active # Scroll the contents of the screen into the scrollback map f1 clear_terminal scroll active # Clear everything on screen up to the line with the cursor or the start of the current prompt (needs shell integration) map f1 clear_terminal to_cursor active # Same as above except cleared lines are moved into scrollback map f1 clear_terminal to_cursor_scroll active If you want to operate on all kitty windows instead of just the current one, use :italic:`all` instead of :italic:`active`. Some useful functions that can be defined in the shell rc files to perform various kinds of clearing of the current window: .. code-block:: sh clear-only-screen() { printf "\e[H\e[2J" } clear-screen-and-scrollback() { printf "\e[H\e[3J" } clear-screen-saving-contents-in-scrollback() { printf "\e[H\e[22J" } For instance, using these escape codes, it is possible to remap :kbd:`Ctrl+L` to both scroll the current screen contents into the scrollback buffer and clear the screen, instead of just clearing the screen. For ZSH, in :file:`~/.zshrc`, add: .. code-block:: zsh ctrl_l() { builtin print -rn -- $'\r\e[0J\e[H\e[22J' >"$TTY" builtin zle .reset-prompt builtin zle -R } zle -N ctrl_l bindkey '^l' ctrl_l Alternatively, you can just add :code:`map ctrl+l clear_terminal to_cursor_scroll active` to :file:`kitty.conf` which works with no changes to the shell rc files, but only clears up to the prompt, it does not clear any text at the prompt itself. ''' ) map('Reset the terminal', 'reset_terminal opt+cmd+r clear_terminal reset active', only='macos', ) map('Clear to start', 'clear_terminal_and_scrollback cmd+k clear_terminal to_cursor active', only='macos', ) map('Clear scrollback', 'clear_scrollback option+cmd+k clear_terminal scrollback active', only='macos', ) map('Clear screen', 'clear_screen cmd+ctrl+l clear_terminal to_cursor_scroll active', only='macos', ) map('Reload kitty.conf', 'reload_config_file kitty_mod+f5 load_config_file', long_text=''' Reload :file:`kitty.conf`, applying any changes since the last time it was loaded. Note that a handful of options cannot be dynamically changed and require a full restart of kitty. Particularly, when changing shortcuts for actions located on the macOS global menu bar, a full restart is needed. You can also map a keybinding to load a different config file, for example:: map f5 load_config /path/to/alternative/kitty.conf Note that all options from the original :file:`kitty.conf` are discarded, in other words the new configuration *replace* the old ones. ''' ) map('Reload kitty.conf', 'reload_config_file ctrl+cmd+, load_config_file', only='macos' ) map('Debug kitty configuration', 'debug_config kitty_mod+f6 debug_config', long_text=''' Show details about exactly what configuration kitty is running with and its host environment. Useful for debugging issues. ''' ) map('Debug kitty configuration', 'debug_config opt+cmd+, debug_config', only='macos' ) map('Send arbitrary text on key presses', 'send_text ctrl+shift+alt+h send_text all Hello World', add_to_default=False, long_text=''' You can tell kitty to send arbitrary (UTF-8) encoded text to the client program when pressing specified shortcut keys. For example:: map ctrl+alt+a send_text all Special text This will send "Special text" when you press the :kbd:`Ctrl+Alt+A` key combination. The text to be sent decodes :link:`ANSI C escapes ` so you can use escapes like :code:`\\\\e` to send control codes or :code:`\\\\u21fb` to send Unicode characters (or you can just input the Unicode characters directly as UTF-8 text). You can use ``kitten show-key`` to get the key escape codes you want to emulate. The first argument to :code:`send_text` is the keyboard modes in which to activate the shortcut. The possible values are :code:`normal`, :code:`application`, :code:`kitty` or a comma separated combination of them. The modes :code:`normal` and :code:`application` refer to the DECCKM cursor key mode for terminals, and :code:`kitty` refers to the kitty extended keyboard protocol. The special value :code:`all` means all of them. Some more examples:: # Output a word and move the cursor to the start of the line (like typing and pressing Home) map ctrl+alt+a send_text normal Word\\e[H map ctrl+alt+a send_text application Word\\eOH # Run a command at a shell prompt (like typing the command and pressing Enter) map ctrl+alt+a send_text normal,application some command with arguments\\r ''' ) map('Open kitty Website', f'open_kitty_website shift+cmd+/ open_url {website_url()}', only='macos', ) map('Hide macOS kitty application', 'hide_macos_app cmd+h hide_macos_app', only='macos', ) map('Hide macOS other applications', 'hide_macos_other_apps opt+cmd+h hide_macos_other_apps', only='macos', ) map('Minimize macOS window', 'minimize_macos_window cmd+m minimize_macos_window', only='macos', ) map('Quit kitty', 'quit cmd+q quit', only='macos', ) egr() # }}} egr() # }}} kitty-0.41.1/kitty/options/parse.py0000664000175000017510000017074514773370543016650 0ustar nileshnilesh# generated by gen-config.py DO NOT edit # isort: skip_file import typing import collections.abc # noqa: F401, RUF100 from kitty.conf.utils import ( merge_dicts, positive_float, positive_int, python_string, to_bool, to_cmdline, to_color, to_color_or_none, unit_float ) from kitty.options.utils import ( action_alias, active_tab_title_template, allow_hyperlinks, bell_on_tab, box_drawing_scale, clear_all_mouse_actions, clear_all_shortcuts, clipboard_control, clone_source_strategies, config_or_absolute_path, confirm_close, copy_on_select, cursor_blink_interval, cursor_text_color, cursor_trail_decay, deprecated_adjust_line_height, deprecated_hide_window_decorations_aliases, deprecated_macos_show_window_title_in_menubar_alias, deprecated_send_text, disable_ligatures, edge_width, env, filter_notification, font_features, hide_window_decorations, macos_option_as_alt, macos_titlebar_color, menu_map, modify_font, narrow_symbols, notify_on_cmd_finish, optional_edge_width, parse_font_spec, parse_map, parse_mouse_map, paste_actions, pointer_shape_when_dragging, remote_control_password, resize_debounce_time, scrollback_lines, scrollback_pager_history_size, shell_integration, store_multiple, symbol_map, tab_activity_symbol, tab_bar_edge, tab_bar_margin_height, tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator, tab_title_template, text_fg_override_threshold, titlebar_color, to_cursor_shape, to_cursor_unfocused_shape, to_font_size, to_layout_names, to_modifiers, transparent_background_colors, underline_exclusion, url_prefixes, url_style, visual_bell_duration, visual_window_select_characters, window_border_width, window_logo_scale, window_size ) class Parser: def action_alias(self, val: str, ans: dict[str, typing.Any]) -> None: for k, v in action_alias(val): ans["action_alias"][k] = v def active_border_color(self, val: str, ans: dict[str, typing.Any]) -> None: ans['active_border_color'] = to_color_or_none(val) def active_tab_background(self, val: str, ans: dict[str, typing.Any]) -> None: ans['active_tab_background'] = to_color(val) def active_tab_font_style(self, val: str, ans: dict[str, typing.Any]) -> None: ans['active_tab_font_style'] = tab_font_style(val) def active_tab_foreground(self, val: str, ans: dict[str, typing.Any]) -> None: ans['active_tab_foreground'] = to_color(val) def active_tab_title_template(self, val: str, ans: dict[str, typing.Any]) -> None: ans['active_tab_title_template'] = active_tab_title_template(val) def allow_cloning(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_allow_cloning: raise ValueError(f"The value {val} is not a valid choice for allow_cloning") ans["allow_cloning"] = val choices_for_allow_cloning = frozenset(('yes', 'y', 'true', 'no', 'n', 'false', 'ask')) def allow_hyperlinks(self, val: str, ans: dict[str, typing.Any]) -> None: ans['allow_hyperlinks'] = allow_hyperlinks(val) def allow_remote_control(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_allow_remote_control: raise ValueError(f"The value {val} is not a valid choice for allow_remote_control") ans["allow_remote_control"] = val choices_for_allow_remote_control = frozenset(('password', 'socket-only', 'socket', 'no', 'n', 'false', 'yes', 'y', 'true')) def background(self, val: str, ans: dict[str, typing.Any]) -> None: ans['background'] = to_color(val) def background_blur(self, val: str, ans: dict[str, typing.Any]) -> None: ans['background_blur'] = int(val) def background_image(self, val: str, ans: dict[str, typing.Any]) -> None: ans['background_image'] = config_or_absolute_path(val) def background_image_layout(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_background_image_layout: raise ValueError(f"The value {val} is not a valid choice for background_image_layout") ans["background_image_layout"] = val choices_for_background_image_layout = frozenset(('mirror-tiled', 'scaled', 'tiled', 'clamped', 'centered', 'cscaled')) def background_image_linear(self, val: str, ans: dict[str, typing.Any]) -> None: ans['background_image_linear'] = to_bool(val) def background_opacity(self, val: str, ans: dict[str, typing.Any]) -> None: ans['background_opacity'] = unit_float(val) def background_tint(self, val: str, ans: dict[str, typing.Any]) -> None: ans['background_tint'] = unit_float(val) def background_tint_gaps(self, val: str, ans: dict[str, typing.Any]) -> None: ans['background_tint_gaps'] = unit_float(val) def bell_border_color(self, val: str, ans: dict[str, typing.Any]) -> None: ans['bell_border_color'] = to_color(val) def bell_on_tab(self, val: str, ans: dict[str, typing.Any]) -> None: ans['bell_on_tab'] = bell_on_tab(val) def bell_path(self, val: str, ans: dict[str, typing.Any]) -> None: ans['bell_path'] = config_or_absolute_path(val) def bold_font(self, val: str, ans: dict[str, typing.Any]) -> None: ans['bold_font'] = parse_font_spec(val) def bold_italic_font(self, val: str, ans: dict[str, typing.Any]) -> None: ans['bold_italic_font'] = parse_font_spec(val) def box_drawing_scale(self, val: str, ans: dict[str, typing.Any]) -> None: ans['box_drawing_scale'] = box_drawing_scale(val) def clear_all_mouse_actions(self, val: str, ans: dict[str, typing.Any]) -> None: clear_all_mouse_actions(val, ans) def clear_all_shortcuts(self, val: str, ans: dict[str, typing.Any]) -> None: clear_all_shortcuts(val, ans) def clear_selection_on_clipboard_loss(self, val: str, ans: dict[str, typing.Any]) -> None: ans['clear_selection_on_clipboard_loss'] = to_bool(val) def click_interval(self, val: str, ans: dict[str, typing.Any]) -> None: ans['click_interval'] = float(val) def clipboard_control(self, val: str, ans: dict[str, typing.Any]) -> None: ans['clipboard_control'] = clipboard_control(val) def clipboard_max_size(self, val: str, ans: dict[str, typing.Any]) -> None: ans['clipboard_max_size'] = positive_float(val) def clone_source_strategies(self, val: str, ans: dict[str, typing.Any]) -> None: ans['clone_source_strategies'] = clone_source_strategies(val) def close_on_child_death(self, val: str, ans: dict[str, typing.Any]) -> None: ans['close_on_child_death'] = to_bool(val) def color0(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color0'] = to_color(val) def color1(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color1'] = to_color(val) def color2(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color2'] = to_color(val) def color3(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color3'] = to_color(val) def color4(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color4'] = to_color(val) def color5(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color5'] = to_color(val) def color6(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color6'] = to_color(val) def color7(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color7'] = to_color(val) def color8(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color8'] = to_color(val) def color9(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color9'] = to_color(val) def color10(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color10'] = to_color(val) def color11(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color11'] = to_color(val) def color12(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color12'] = to_color(val) def color13(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color13'] = to_color(val) def color14(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color14'] = to_color(val) def color15(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color15'] = to_color(val) def color16(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color16'] = to_color(val) def color17(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color17'] = to_color(val) def color18(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color18'] = to_color(val) def color19(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color19'] = to_color(val) def color20(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color20'] = to_color(val) def color21(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color21'] = to_color(val) def color22(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color22'] = to_color(val) def color23(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color23'] = to_color(val) def color24(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color24'] = to_color(val) def color25(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color25'] = to_color(val) def color26(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color26'] = to_color(val) def color27(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color27'] = to_color(val) def color28(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color28'] = to_color(val) def color29(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color29'] = to_color(val) def color30(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color30'] = to_color(val) def color31(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color31'] = to_color(val) def color32(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color32'] = to_color(val) def color33(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color33'] = to_color(val) def color34(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color34'] = to_color(val) def color35(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color35'] = to_color(val) def color36(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color36'] = to_color(val) def color37(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color37'] = to_color(val) def color38(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color38'] = to_color(val) def color39(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color39'] = to_color(val) def color40(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color40'] = to_color(val) def color41(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color41'] = to_color(val) def color42(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color42'] = to_color(val) def color43(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color43'] = to_color(val) def color44(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color44'] = to_color(val) def color45(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color45'] = to_color(val) def color46(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color46'] = to_color(val) def color47(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color47'] = to_color(val) def color48(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color48'] = to_color(val) def color49(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color49'] = to_color(val) def color50(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color50'] = to_color(val) def color51(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color51'] = to_color(val) def color52(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color52'] = to_color(val) def color53(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color53'] = to_color(val) def color54(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color54'] = to_color(val) def color55(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color55'] = to_color(val) def color56(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color56'] = to_color(val) def color57(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color57'] = to_color(val) def color58(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color58'] = to_color(val) def color59(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color59'] = to_color(val) def color60(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color60'] = to_color(val) def color61(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color61'] = to_color(val) def color62(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color62'] = to_color(val) def color63(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color63'] = to_color(val) def color64(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color64'] = to_color(val) def color65(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color65'] = to_color(val) def color66(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color66'] = to_color(val) def color67(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color67'] = to_color(val) def color68(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color68'] = to_color(val) def color69(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color69'] = to_color(val) def color70(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color70'] = to_color(val) def color71(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color71'] = to_color(val) def color72(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color72'] = to_color(val) def color73(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color73'] = to_color(val) def color74(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color74'] = to_color(val) def color75(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color75'] = to_color(val) def color76(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color76'] = to_color(val) def color77(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color77'] = to_color(val) def color78(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color78'] = to_color(val) def color79(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color79'] = to_color(val) def color80(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color80'] = to_color(val) def color81(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color81'] = to_color(val) def color82(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color82'] = to_color(val) def color83(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color83'] = to_color(val) def color84(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color84'] = to_color(val) def color85(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color85'] = to_color(val) def color86(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color86'] = to_color(val) def color87(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color87'] = to_color(val) def color88(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color88'] = to_color(val) def color89(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color89'] = to_color(val) def color90(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color90'] = to_color(val) def color91(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color91'] = to_color(val) def color92(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color92'] = to_color(val) def color93(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color93'] = to_color(val) def color94(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color94'] = to_color(val) def color95(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color95'] = to_color(val) def color96(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color96'] = to_color(val) def color97(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color97'] = to_color(val) def color98(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color98'] = to_color(val) def color99(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color99'] = to_color(val) def color100(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color100'] = to_color(val) def color101(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color101'] = to_color(val) def color102(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color102'] = to_color(val) def color103(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color103'] = to_color(val) def color104(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color104'] = to_color(val) def color105(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color105'] = to_color(val) def color106(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color106'] = to_color(val) def color107(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color107'] = to_color(val) def color108(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color108'] = to_color(val) def color109(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color109'] = to_color(val) def color110(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color110'] = to_color(val) def color111(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color111'] = to_color(val) def color112(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color112'] = to_color(val) def color113(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color113'] = to_color(val) def color114(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color114'] = to_color(val) def color115(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color115'] = to_color(val) def color116(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color116'] = to_color(val) def color117(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color117'] = to_color(val) def color118(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color118'] = to_color(val) def color119(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color119'] = to_color(val) def color120(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color120'] = to_color(val) def color121(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color121'] = to_color(val) def color122(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color122'] = to_color(val) def color123(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color123'] = to_color(val) def color124(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color124'] = to_color(val) def color125(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color125'] = to_color(val) def color126(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color126'] = to_color(val) def color127(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color127'] = to_color(val) def color128(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color128'] = to_color(val) def color129(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color129'] = to_color(val) def color130(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color130'] = to_color(val) def color131(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color131'] = to_color(val) def color132(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color132'] = to_color(val) def color133(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color133'] = to_color(val) def color134(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color134'] = to_color(val) def color135(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color135'] = to_color(val) def color136(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color136'] = to_color(val) def color137(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color137'] = to_color(val) def color138(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color138'] = to_color(val) def color139(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color139'] = to_color(val) def color140(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color140'] = to_color(val) def color141(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color141'] = to_color(val) def color142(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color142'] = to_color(val) def color143(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color143'] = to_color(val) def color144(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color144'] = to_color(val) def color145(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color145'] = to_color(val) def color146(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color146'] = to_color(val) def color147(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color147'] = to_color(val) def color148(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color148'] = to_color(val) def color149(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color149'] = to_color(val) def color150(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color150'] = to_color(val) def color151(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color151'] = to_color(val) def color152(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color152'] = to_color(val) def color153(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color153'] = to_color(val) def color154(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color154'] = to_color(val) def color155(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color155'] = to_color(val) def color156(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color156'] = to_color(val) def color157(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color157'] = to_color(val) def color158(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color158'] = to_color(val) def color159(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color159'] = to_color(val) def color160(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color160'] = to_color(val) def color161(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color161'] = to_color(val) def color162(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color162'] = to_color(val) def color163(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color163'] = to_color(val) def color164(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color164'] = to_color(val) def color165(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color165'] = to_color(val) def color166(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color166'] = to_color(val) def color167(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color167'] = to_color(val) def color168(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color168'] = to_color(val) def color169(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color169'] = to_color(val) def color170(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color170'] = to_color(val) def color171(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color171'] = to_color(val) def color172(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color172'] = to_color(val) def color173(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color173'] = to_color(val) def color174(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color174'] = to_color(val) def color175(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color175'] = to_color(val) def color176(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color176'] = to_color(val) def color177(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color177'] = to_color(val) def color178(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color178'] = to_color(val) def color179(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color179'] = to_color(val) def color180(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color180'] = to_color(val) def color181(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color181'] = to_color(val) def color182(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color182'] = to_color(val) def color183(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color183'] = to_color(val) def color184(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color184'] = to_color(val) def color185(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color185'] = to_color(val) def color186(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color186'] = to_color(val) def color187(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color187'] = to_color(val) def color188(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color188'] = to_color(val) def color189(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color189'] = to_color(val) def color190(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color190'] = to_color(val) def color191(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color191'] = to_color(val) def color192(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color192'] = to_color(val) def color193(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color193'] = to_color(val) def color194(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color194'] = to_color(val) def color195(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color195'] = to_color(val) def color196(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color196'] = to_color(val) def color197(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color197'] = to_color(val) def color198(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color198'] = to_color(val) def color199(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color199'] = to_color(val) def color200(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color200'] = to_color(val) def color201(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color201'] = to_color(val) def color202(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color202'] = to_color(val) def color203(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color203'] = to_color(val) def color204(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color204'] = to_color(val) def color205(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color205'] = to_color(val) def color206(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color206'] = to_color(val) def color207(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color207'] = to_color(val) def color208(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color208'] = to_color(val) def color209(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color209'] = to_color(val) def color210(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color210'] = to_color(val) def color211(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color211'] = to_color(val) def color212(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color212'] = to_color(val) def color213(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color213'] = to_color(val) def color214(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color214'] = to_color(val) def color215(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color215'] = to_color(val) def color216(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color216'] = to_color(val) def color217(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color217'] = to_color(val) def color218(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color218'] = to_color(val) def color219(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color219'] = to_color(val) def color220(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color220'] = to_color(val) def color221(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color221'] = to_color(val) def color222(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color222'] = to_color(val) def color223(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color223'] = to_color(val) def color224(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color224'] = to_color(val) def color225(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color225'] = to_color(val) def color226(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color226'] = to_color(val) def color227(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color227'] = to_color(val) def color228(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color228'] = to_color(val) def color229(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color229'] = to_color(val) def color230(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color230'] = to_color(val) def color231(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color231'] = to_color(val) def color232(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color232'] = to_color(val) def color233(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color233'] = to_color(val) def color234(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color234'] = to_color(val) def color235(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color235'] = to_color(val) def color236(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color236'] = to_color(val) def color237(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color237'] = to_color(val) def color238(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color238'] = to_color(val) def color239(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color239'] = to_color(val) def color240(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color240'] = to_color(val) def color241(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color241'] = to_color(val) def color242(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color242'] = to_color(val) def color243(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color243'] = to_color(val) def color244(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color244'] = to_color(val) def color245(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color245'] = to_color(val) def color246(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color246'] = to_color(val) def color247(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color247'] = to_color(val) def color248(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color248'] = to_color(val) def color249(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color249'] = to_color(val) def color250(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color250'] = to_color(val) def color251(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color251'] = to_color(val) def color252(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color252'] = to_color(val) def color253(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color253'] = to_color(val) def color254(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color254'] = to_color(val) def color255(self, val: str, ans: dict[str, typing.Any]) -> None: ans['color255'] = to_color(val) def command_on_bell(self, val: str, ans: dict[str, typing.Any]) -> None: ans['command_on_bell'] = to_cmdline(val) def confirm_os_window_close(self, val: str, ans: dict[str, typing.Any]) -> None: ans['confirm_os_window_close'] = confirm_close(val) def copy_on_select(self, val: str, ans: dict[str, typing.Any]) -> None: ans['copy_on_select'] = copy_on_select(val) def cursor(self, val: str, ans: dict[str, typing.Any]) -> None: ans['cursor'] = to_color_or_none(val) def cursor_beam_thickness(self, val: str, ans: dict[str, typing.Any]) -> None: ans['cursor_beam_thickness'] = positive_float(val) def cursor_blink_interval(self, val: str, ans: dict[str, typing.Any]) -> None: ans['cursor_blink_interval'] = cursor_blink_interval(val) def cursor_shape(self, val: str, ans: dict[str, typing.Any]) -> None: ans['cursor_shape'] = to_cursor_shape(val) def cursor_shape_unfocused(self, val: str, ans: dict[str, typing.Any]) -> None: ans['cursor_shape_unfocused'] = to_cursor_unfocused_shape(val) def cursor_stop_blinking_after(self, val: str, ans: dict[str, typing.Any]) -> None: ans['cursor_stop_blinking_after'] = positive_float(val) def cursor_text_color(self, val: str, ans: dict[str, typing.Any]) -> None: ans['cursor_text_color'] = cursor_text_color(val) def cursor_trail(self, val: str, ans: dict[str, typing.Any]) -> None: ans['cursor_trail'] = positive_int(val) def cursor_trail_decay(self, val: str, ans: dict[str, typing.Any]) -> None: ans['cursor_trail_decay'] = cursor_trail_decay(val) def cursor_trail_start_threshold(self, val: str, ans: dict[str, typing.Any]) -> None: ans['cursor_trail_start_threshold'] = positive_int(val) def cursor_underline_thickness(self, val: str, ans: dict[str, typing.Any]) -> None: ans['cursor_underline_thickness'] = positive_float(val) def default_pointer_shape(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_default_pointer_shape: raise ValueError(f"The value {val} is not a valid choice for default_pointer_shape") ans["default_pointer_shape"] = val choices_for_default_pointer_shape = frozenset(('arrow', 'beam', 'text', 'pointer', 'hand', 'help', 'wait', 'progress', 'crosshair', 'cell', 'vertical-text', 'move', 'e-resize', 'ne-resize', 'nw-resize', 'n-resize', 'se-resize', 'sw-resize', 's-resize', 'w-resize', 'ew-resize', 'ns-resize', 'nesw-resize', 'nwse-resize', 'zoom-in', 'zoom-out', 'alias', 'copy', 'not-allowed', 'no-drop', 'grab', 'grabbing')) def detect_urls(self, val: str, ans: dict[str, typing.Any]) -> None: ans['detect_urls'] = to_bool(val) def dim_opacity(self, val: str, ans: dict[str, typing.Any]) -> None: ans['dim_opacity'] = unit_float(val) def disable_ligatures(self, val: str, ans: dict[str, typing.Any]) -> None: ans['disable_ligatures'] = disable_ligatures(val) def draw_minimal_borders(self, val: str, ans: dict[str, typing.Any]) -> None: ans['draw_minimal_borders'] = to_bool(val) def dynamic_background_opacity(self, val: str, ans: dict[str, typing.Any]) -> None: ans['dynamic_background_opacity'] = to_bool(val) def editor(self, val: str, ans: dict[str, typing.Any]) -> None: ans['editor'] = str(val) def enable_audio_bell(self, val: str, ans: dict[str, typing.Any]) -> None: ans['enable_audio_bell'] = to_bool(val) def enabled_layouts(self, val: str, ans: dict[str, typing.Any]) -> None: ans['enabled_layouts'] = to_layout_names(val) def env(self, val: str, ans: dict[str, typing.Any]) -> None: for k, v in env(val, ans["env"]): ans["env"][k] = v def exe_search_path(self, val: str, ans: dict[str, typing.Any]) -> None: for k, v in store_multiple(val, ans["exe_search_path"]): ans["exe_search_path"][k] = v def file_transfer_confirmation_bypass(self, val: str, ans: dict[str, typing.Any]) -> None: ans['file_transfer_confirmation_bypass'] = str(val) def filter_notification(self, val: str, ans: dict[str, typing.Any]) -> None: for k, v in filter_notification(val, ans["filter_notification"]): ans["filter_notification"][k] = v def focus_follows_mouse(self, val: str, ans: dict[str, typing.Any]) -> None: ans['focus_follows_mouse'] = to_bool(val) def font_family(self, val: str, ans: dict[str, typing.Any]) -> None: ans['font_family'] = parse_font_spec(val) def font_features(self, val: str, ans: dict[str, typing.Any]) -> None: for k, v in font_features(val): ans["font_features"][k] = v def font_size(self, val: str, ans: dict[str, typing.Any]) -> None: ans['font_size'] = to_font_size(val) def force_ltr(self, val: str, ans: dict[str, typing.Any]) -> None: ans['force_ltr'] = to_bool(val) def foreground(self, val: str, ans: dict[str, typing.Any]) -> None: ans['foreground'] = to_color(val) def forward_stdio(self, val: str, ans: dict[str, typing.Any]) -> None: ans['forward_stdio'] = to_bool(val) def hide_window_decorations(self, val: str, ans: dict[str, typing.Any]) -> None: ans['hide_window_decorations'] = hide_window_decorations(val) def inactive_border_color(self, val: str, ans: dict[str, typing.Any]) -> None: ans['inactive_border_color'] = to_color(val) def inactive_tab_background(self, val: str, ans: dict[str, typing.Any]) -> None: ans['inactive_tab_background'] = to_color(val) def inactive_tab_font_style(self, val: str, ans: dict[str, typing.Any]) -> None: ans['inactive_tab_font_style'] = tab_font_style(val) def inactive_tab_foreground(self, val: str, ans: dict[str, typing.Any]) -> None: ans['inactive_tab_foreground'] = to_color(val) def inactive_text_alpha(self, val: str, ans: dict[str, typing.Any]) -> None: ans['inactive_text_alpha'] = unit_float(val) def initial_window_height(self, val: str, ans: dict[str, typing.Any]) -> None: ans['initial_window_height'] = window_size(val) def initial_window_width(self, val: str, ans: dict[str, typing.Any]) -> None: ans['initial_window_width'] = window_size(val) def input_delay(self, val: str, ans: dict[str, typing.Any]) -> None: ans['input_delay'] = positive_int(val) def italic_font(self, val: str, ans: dict[str, typing.Any]) -> None: ans['italic_font'] = parse_font_spec(val) def kitten_alias(self, val: str, ans: dict[str, typing.Any]) -> None: for k, v in action_alias(val): ans["kitten_alias"][k] = v def kitty_mod(self, val: str, ans: dict[str, typing.Any]) -> None: ans['kitty_mod'] = to_modifiers(val) def linux_bell_theme(self, val: str, ans: dict[str, typing.Any]) -> None: ans['linux_bell_theme'] = str(val) def linux_display_server(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_linux_display_server: raise ValueError(f"The value {val} is not a valid choice for linux_display_server") ans["linux_display_server"] = val choices_for_linux_display_server = frozenset(('auto', 'wayland', 'x11')) def listen_on(self, val: str, ans: dict[str, typing.Any]) -> None: ans['listen_on'] = str(val) def macos_colorspace(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_macos_colorspace: raise ValueError(f"The value {val} is not a valid choice for macos_colorspace") ans["macos_colorspace"] = val choices_for_macos_colorspace = frozenset(('srgb', 'default', 'displayp3')) def macos_custom_beam_cursor(self, val: str, ans: dict[str, typing.Any]) -> None: ans['macos_custom_beam_cursor'] = to_bool(val) def macos_hide_from_tasks(self, val: str, ans: dict[str, typing.Any]) -> None: ans['macos_hide_from_tasks'] = to_bool(val) def macos_menubar_title_max_length(self, val: str, ans: dict[str, typing.Any]) -> None: ans['macos_menubar_title_max_length'] = positive_int(val) def macos_option_as_alt(self, val: str, ans: dict[str, typing.Any]) -> None: ans['macos_option_as_alt'] = macos_option_as_alt(val) def macos_quit_when_last_window_closed(self, val: str, ans: dict[str, typing.Any]) -> None: ans['macos_quit_when_last_window_closed'] = to_bool(val) def macos_show_window_title_in(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_macos_show_window_title_in: raise ValueError(f"The value {val} is not a valid choice for macos_show_window_title_in") ans["macos_show_window_title_in"] = val choices_for_macos_show_window_title_in = frozenset(('all', 'menubar', 'none', 'window')) def macos_thicken_font(self, val: str, ans: dict[str, typing.Any]) -> None: ans['macos_thicken_font'] = positive_float(val) def macos_titlebar_color(self, val: str, ans: dict[str, typing.Any]) -> None: ans['macos_titlebar_color'] = macos_titlebar_color(val) def macos_traditional_fullscreen(self, val: str, ans: dict[str, typing.Any]) -> None: ans['macos_traditional_fullscreen'] = to_bool(val) def macos_window_resizable(self, val: str, ans: dict[str, typing.Any]) -> None: ans['macos_window_resizable'] = to_bool(val) def mark1_background(self, val: str, ans: dict[str, typing.Any]) -> None: ans['mark1_background'] = to_color(val) def mark1_foreground(self, val: str, ans: dict[str, typing.Any]) -> None: ans['mark1_foreground'] = to_color(val) def mark2_background(self, val: str, ans: dict[str, typing.Any]) -> None: ans['mark2_background'] = to_color(val) def mark2_foreground(self, val: str, ans: dict[str, typing.Any]) -> None: ans['mark2_foreground'] = to_color(val) def mark3_background(self, val: str, ans: dict[str, typing.Any]) -> None: ans['mark3_background'] = to_color(val) def mark3_foreground(self, val: str, ans: dict[str, typing.Any]) -> None: ans['mark3_foreground'] = to_color(val) def menu_map(self, val: str, ans: dict[str, typing.Any]) -> None: for k, v in menu_map(val, ans["menu_map"]): ans["menu_map"][k] = v def modify_font(self, val: str, ans: dict[str, typing.Any]) -> None: for k, v in modify_font(val): ans["modify_font"][k] = v def mouse_hide_wait(self, val: str, ans: dict[str, typing.Any]) -> None: ans['mouse_hide_wait'] = float(val) def narrow_symbols(self, val: str, ans: dict[str, typing.Any]) -> None: for k, v in narrow_symbols(val): ans["narrow_symbols"][k] = v def notify_on_cmd_finish(self, val: str, ans: dict[str, typing.Any]) -> None: ans['notify_on_cmd_finish'] = notify_on_cmd_finish(val) def open_url_with(self, val: str, ans: dict[str, typing.Any]) -> None: ans['open_url_with'] = to_cmdline(val) def paste_actions(self, val: str, ans: dict[str, typing.Any]) -> None: ans['paste_actions'] = paste_actions(val) def placement_strategy(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_placement_strategy: raise ValueError(f"The value {val} is not a valid choice for placement_strategy") ans["placement_strategy"] = val choices_for_placement_strategy = frozenset(('top-left', 'top', 'top-right', 'left', 'center', 'right', 'bottom-left', 'bottom', 'bottom-right')) def pointer_shape_when_dragging(self, val: str, ans: dict[str, typing.Any]) -> None: ans['pointer_shape_when_dragging'] = pointer_shape_when_dragging(val) def pointer_shape_when_grabbed(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_pointer_shape_when_grabbed: raise ValueError(f"The value {val} is not a valid choice for pointer_shape_when_grabbed") ans["pointer_shape_when_grabbed"] = val choices_for_pointer_shape_when_grabbed = choices_for_default_pointer_shape def remember_window_size(self, val: str, ans: dict[str, typing.Any]) -> None: ans['remember_window_size'] = to_bool(val) def remote_control_password(self, val: str, ans: dict[str, typing.Any]) -> None: for k, v in remote_control_password(val, ans["remote_control_password"]): ans["remote_control_password"][k] = v def repaint_delay(self, val: str, ans: dict[str, typing.Any]) -> None: ans['repaint_delay'] = positive_int(val) def resize_debounce_time(self, val: str, ans: dict[str, typing.Any]) -> None: ans['resize_debounce_time'] = resize_debounce_time(val) def resize_in_steps(self, val: str, ans: dict[str, typing.Any]) -> None: ans['resize_in_steps'] = to_bool(val) def scrollback_fill_enlarged_window(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollback_fill_enlarged_window'] = to_bool(val) def scrollback_indicator_opacity(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollback_indicator_opacity'] = unit_float(val) def scrollback_lines(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollback_lines'] = scrollback_lines(val) def scrollback_pager(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollback_pager'] = to_cmdline(val) def scrollback_pager_history_size(self, val: str, ans: dict[str, typing.Any]) -> None: ans['scrollback_pager_history_size'] = scrollback_pager_history_size(val) def select_by_word_characters(self, val: str, ans: dict[str, typing.Any]) -> None: ans['select_by_word_characters'] = str(val) def select_by_word_characters_forward(self, val: str, ans: dict[str, typing.Any]) -> None: ans['select_by_word_characters_forward'] = str(val) def selection_background(self, val: str, ans: dict[str, typing.Any]) -> None: ans['selection_background'] = to_color_or_none(val) def selection_foreground(self, val: str, ans: dict[str, typing.Any]) -> None: ans['selection_foreground'] = to_color_or_none(val) def shell(self, val: str, ans: dict[str, typing.Any]) -> None: ans['shell'] = str(val) def shell_integration(self, val: str, ans: dict[str, typing.Any]) -> None: ans['shell_integration'] = shell_integration(val) def show_hyperlink_targets(self, val: str, ans: dict[str, typing.Any]) -> None: ans['show_hyperlink_targets'] = to_bool(val) def single_window_margin_width(self, val: str, ans: dict[str, typing.Any]) -> None: ans['single_window_margin_width'] = optional_edge_width(val) def single_window_padding_width(self, val: str, ans: dict[str, typing.Any]) -> None: ans['single_window_padding_width'] = optional_edge_width(val) def startup_session(self, val: str, ans: dict[str, typing.Any]) -> None: ans['startup_session'] = config_or_absolute_path(val) def strip_trailing_spaces(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_strip_trailing_spaces: raise ValueError(f"The value {val} is not a valid choice for strip_trailing_spaces") ans["strip_trailing_spaces"] = val choices_for_strip_trailing_spaces = frozenset(('always', 'never', 'smart')) def symbol_map(self, val: str, ans: dict[str, typing.Any]) -> None: for k, v in symbol_map(val): ans["symbol_map"][k] = v def sync_to_monitor(self, val: str, ans: dict[str, typing.Any]) -> None: ans['sync_to_monitor'] = to_bool(val) def tab_activity_symbol(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_activity_symbol'] = tab_activity_symbol(val) def tab_bar_align(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_tab_bar_align: raise ValueError(f"The value {val} is not a valid choice for tab_bar_align") ans["tab_bar_align"] = val choices_for_tab_bar_align = frozenset(('left', 'center', 'right')) def tab_bar_background(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_bar_background'] = to_color_or_none(val) def tab_bar_edge(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_bar_edge'] = tab_bar_edge(val) def tab_bar_margin_color(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_bar_margin_color'] = to_color_or_none(val) def tab_bar_margin_height(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_bar_margin_height'] = tab_bar_margin_height(val) def tab_bar_margin_width(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_bar_margin_width'] = positive_float(val) def tab_bar_min_tabs(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_bar_min_tabs'] = tab_bar_min_tabs(val) def tab_bar_style(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_tab_bar_style: raise ValueError(f"The value {val} is not a valid choice for tab_bar_style") ans["tab_bar_style"] = val choices_for_tab_bar_style = frozenset(('fade', 'hidden', 'powerline', 'separator', 'slant', 'custom')) def tab_fade(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_fade'] = tab_fade(val) def tab_powerline_style(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_tab_powerline_style: raise ValueError(f"The value {val} is not a valid choice for tab_powerline_style") ans["tab_powerline_style"] = val choices_for_tab_powerline_style = frozenset(('angled', 'round', 'slanted')) def tab_separator(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_separator'] = tab_separator(val) def tab_switch_strategy(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_tab_switch_strategy: raise ValueError(f"The value {val} is not a valid choice for tab_switch_strategy") ans["tab_switch_strategy"] = val choices_for_tab_switch_strategy = frozenset(('last', 'left', 'previous', 'right')) def tab_title_max_length(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_title_max_length'] = positive_int(val) def tab_title_template(self, val: str, ans: dict[str, typing.Any]) -> None: ans['tab_title_template'] = tab_title_template(val) def term(self, val: str, ans: dict[str, typing.Any]) -> None: ans['term'] = str(val) def terminfo_type(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_terminfo_type: raise ValueError(f"The value {val} is not a valid choice for terminfo_type") ans["terminfo_type"] = val choices_for_terminfo_type = frozenset(('path', 'direct', 'none')) def text_composition_strategy(self, val: str, ans: dict[str, typing.Any]) -> None: ans['text_composition_strategy'] = str(val) def text_fg_override_threshold(self, val: str, ans: dict[str, typing.Any]) -> None: ans['text_fg_override_threshold'] = text_fg_override_threshold(val) def touch_scroll_multiplier(self, val: str, ans: dict[str, typing.Any]) -> None: ans['touch_scroll_multiplier'] = float(val) def transparent_background_colors(self, val: str, ans: dict[str, typing.Any]) -> None: ans['transparent_background_colors'] = transparent_background_colors(val) def undercurl_style(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_undercurl_style: raise ValueError(f"The value {val} is not a valid choice for undercurl_style") ans["undercurl_style"] = val choices_for_undercurl_style = frozenset(('thin-sparse', 'thin-dense', 'thick-sparse', 'thick-dense')) def underline_exclusion(self, val: str, ans: dict[str, typing.Any]) -> None: ans['underline_exclusion'] = underline_exclusion(val) def underline_hyperlinks(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_underline_hyperlinks: raise ValueError(f"The value {val} is not a valid choice for underline_hyperlinks") ans["underline_hyperlinks"] = val choices_for_underline_hyperlinks = frozenset(('hover', 'always', 'never')) def update_check_interval(self, val: str, ans: dict[str, typing.Any]) -> None: ans['update_check_interval'] = float(val) def url_color(self, val: str, ans: dict[str, typing.Any]) -> None: ans['url_color'] = to_color(val) def url_excluded_characters(self, val: str, ans: dict[str, typing.Any]) -> None: ans['url_excluded_characters'] = python_string(val) def url_prefixes(self, val: str, ans: dict[str, typing.Any]) -> None: ans['url_prefixes'] = url_prefixes(val) def url_style(self, val: str, ans: dict[str, typing.Any]) -> None: ans['url_style'] = url_style(val) def visual_bell_color(self, val: str, ans: dict[str, typing.Any]) -> None: ans['visual_bell_color'] = to_color_or_none(val) def visual_bell_duration(self, val: str, ans: dict[str, typing.Any]) -> None: ans['visual_bell_duration'] = visual_bell_duration(val) def visual_window_select_characters(self, val: str, ans: dict[str, typing.Any]) -> None: ans['visual_window_select_characters'] = visual_window_select_characters(val) def watcher(self, val: str, ans: dict[str, typing.Any]) -> None: for k, v in store_multiple(val, ans["watcher"]): ans["watcher"][k] = v def wayland_enable_ime(self, val: str, ans: dict[str, typing.Any]) -> None: ans['wayland_enable_ime'] = to_bool(val) def wayland_titlebar_color(self, val: str, ans: dict[str, typing.Any]) -> None: ans['wayland_titlebar_color'] = titlebar_color(val) def wheel_scroll_min_lines(self, val: str, ans: dict[str, typing.Any]) -> None: ans['wheel_scroll_min_lines'] = int(val) def wheel_scroll_multiplier(self, val: str, ans: dict[str, typing.Any]) -> None: ans['wheel_scroll_multiplier'] = float(val) def window_alert_on_bell(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_alert_on_bell'] = to_bool(val) def window_border_width(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_border_width'] = window_border_width(val) def window_logo_alpha(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_logo_alpha'] = unit_float(val) def window_logo_path(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_logo_path'] = config_or_absolute_path(val) def window_logo_position(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_window_logo_position: raise ValueError(f"The value {val} is not a valid choice for window_logo_position") ans["window_logo_position"] = val choices_for_window_logo_position = choices_for_placement_strategy def window_logo_scale(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_logo_scale'] = window_logo_scale(val) def window_margin_width(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_margin_width'] = edge_width(val) def window_padding_width(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_padding_width'] = edge_width(val) def window_resize_step_cells(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_resize_step_cells'] = positive_int(val) def window_resize_step_lines(self, val: str, ans: dict[str, typing.Any]) -> None: ans['window_resize_step_lines'] = positive_int(val) def x11_hide_window_decorations(self, val: str, ans: dict[str, typing.Any]) -> None: deprecated_hide_window_decorations_aliases('x11_hide_window_decorations', val, ans) def macos_hide_titlebar(self, val: str, ans: dict[str, typing.Any]) -> None: deprecated_hide_window_decorations_aliases('macos_hide_titlebar', val, ans) def macos_show_window_title_in_menubar(self, val: str, ans: dict[str, typing.Any]) -> None: deprecated_macos_show_window_title_in_menubar_alias('macos_show_window_title_in_menubar', val, ans) def send_text(self, val: str, ans: dict[str, typing.Any]) -> None: deprecated_send_text('send_text', val, ans) def adjust_line_height(self, val: str, ans: dict[str, typing.Any]) -> None: deprecated_adjust_line_height('adjust_line_height', val, ans) def adjust_column_width(self, val: str, ans: dict[str, typing.Any]) -> None: deprecated_adjust_line_height('adjust_column_width', val, ans) def adjust_baseline(self, val: str, ans: dict[str, typing.Any]) -> None: deprecated_adjust_line_height('adjust_baseline', val, ans) def map(self, val: str, ans: dict[str, typing.Any]) -> None: for k in parse_map(val): ans['map'].append(k) def mouse_map(self, val: str, ans: dict[str, typing.Any]) -> None: for k in parse_mouse_map(val): ans['mouse_map'].append(k) def create_result_dict() -> dict[str, typing.Any]: return { 'action_alias': {}, 'env': {}, 'exe_search_path': {}, 'filter_notification': {}, 'font_features': {}, 'kitten_alias': {}, 'menu_map': {}, 'modify_font': {}, 'narrow_symbols': {}, 'remote_control_password': {}, 'symbol_map': {}, 'watcher': {}, 'map': [], 'mouse_map': [], } actions: frozenset[str] = frozenset(('map', 'mouse_map')) def merge_result_dicts(defaults: dict[str, typing.Any], vals: dict[str, typing.Any]) -> dict[str, typing.Any]: ans = {} for k, v in defaults.items(): if isinstance(v, dict): ans[k] = merge_dicts(v, vals.get(k, {})) elif k in actions: ans[k] = v + vals.get(k, []) else: ans[k] = vals.get(k, v) return ans parser = Parser() def parse_conf_item(key: str, val: str, ans: dict[str, typing.Any]) -> bool: func = getattr(parser, key, None) if func is not None: func(val, ans) return True return False kitty-0.41.1/kitty/options/to-c-generated.h0000664000175000017510000013102514773370543020117 0ustar nileshnilesh// generated by gen-config.py DO NOT edit // vim:fileencoding=utf-8 #pragma once #include "to-c.h" static void convert_from_python_font_size(PyObject *val, Options *opts) { opts->font_size = PyFloat_AsDouble(val); } static void convert_from_opts_font_size(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "font_size"); if (ret == NULL) return; convert_from_python_font_size(ret, opts); Py_DECREF(ret); } static void convert_from_python_force_ltr(PyObject *val, Options *opts) { opts->force_ltr = PyObject_IsTrue(val); } static void convert_from_opts_force_ltr(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "force_ltr"); if (ret == NULL) return; convert_from_python_force_ltr(ret, opts); Py_DECREF(ret); } static void convert_from_python_disable_ligatures(PyObject *val, Options *opts) { opts->disable_ligatures = PyLong_AsLong(val); } static void convert_from_opts_disable_ligatures(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "disable_ligatures"); if (ret == NULL) return; convert_from_python_disable_ligatures(ret, opts); Py_DECREF(ret); } static void convert_from_python_font_features(PyObject *val, Options *opts) { font_features(val, opts); } static void convert_from_opts_font_features(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "font_features"); if (ret == NULL) return; convert_from_python_font_features(ret, opts); Py_DECREF(ret); } static void convert_from_python_modify_font(PyObject *val, Options *opts) { modify_font(val, opts); } static void convert_from_opts_modify_font(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "modify_font"); if (ret == NULL) return; convert_from_python_modify_font(ret, opts); Py_DECREF(ret); } static void convert_from_python_box_drawing_scale(PyObject *val, Options *opts) { box_drawing_scale(val, opts); } static void convert_from_opts_box_drawing_scale(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "box_drawing_scale"); if (ret == NULL) return; convert_from_python_box_drawing_scale(ret, opts); Py_DECREF(ret); } static void convert_from_python_undercurl_style(PyObject *val, Options *opts) { opts->undercurl_style = undercurl_style(val); } static void convert_from_opts_undercurl_style(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "undercurl_style"); if (ret == NULL) return; convert_from_python_undercurl_style(ret, opts); Py_DECREF(ret); } static void convert_from_python_underline_exclusion(PyObject *val, Options *opts) { underline_exclusion(val, opts); } static void convert_from_opts_underline_exclusion(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "underline_exclusion"); if (ret == NULL) return; convert_from_python_underline_exclusion(ret, opts); Py_DECREF(ret); } static void convert_from_python_text_composition_strategy(PyObject *val, Options *opts) { text_composition_strategy(val, opts); } static void convert_from_opts_text_composition_strategy(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "text_composition_strategy"); if (ret == NULL) return; convert_from_python_text_composition_strategy(ret, opts); Py_DECREF(ret); } static void convert_from_python_cursor_shape(PyObject *val, Options *opts) { opts->cursor_shape = PyLong_AsLong(val); } static void convert_from_opts_cursor_shape(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "cursor_shape"); if (ret == NULL) return; convert_from_python_cursor_shape(ret, opts); Py_DECREF(ret); } static void convert_from_python_cursor_shape_unfocused(PyObject *val, Options *opts) { opts->cursor_shape_unfocused = PyLong_AsLong(val); } static void convert_from_opts_cursor_shape_unfocused(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "cursor_shape_unfocused"); if (ret == NULL) return; convert_from_python_cursor_shape_unfocused(ret, opts); Py_DECREF(ret); } static void convert_from_python_cursor_beam_thickness(PyObject *val, Options *opts) { opts->cursor_beam_thickness = PyFloat_AsFloat(val); } static void convert_from_opts_cursor_beam_thickness(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "cursor_beam_thickness"); if (ret == NULL) return; convert_from_python_cursor_beam_thickness(ret, opts); Py_DECREF(ret); } static void convert_from_python_cursor_underline_thickness(PyObject *val, Options *opts) { opts->cursor_underline_thickness = PyFloat_AsFloat(val); } static void convert_from_opts_cursor_underline_thickness(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "cursor_underline_thickness"); if (ret == NULL) return; convert_from_python_cursor_underline_thickness(ret, opts); Py_DECREF(ret); } static void convert_from_python_cursor_blink_interval(PyObject *val, Options *opts) { cursor_blink_interval(val, opts); } static void convert_from_opts_cursor_blink_interval(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "cursor_blink_interval"); if (ret == NULL) return; convert_from_python_cursor_blink_interval(ret, opts); Py_DECREF(ret); } static void convert_from_python_cursor_stop_blinking_after(PyObject *val, Options *opts) { opts->cursor_stop_blinking_after = parse_s_double_to_monotonic_t(val); } static void convert_from_opts_cursor_stop_blinking_after(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "cursor_stop_blinking_after"); if (ret == NULL) return; convert_from_python_cursor_stop_blinking_after(ret, opts); Py_DECREF(ret); } static void convert_from_python_cursor_trail(PyObject *val, Options *opts) { opts->cursor_trail = parse_ms_long_to_monotonic_t(val); } static void convert_from_opts_cursor_trail(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "cursor_trail"); if (ret == NULL) return; convert_from_python_cursor_trail(ret, opts); Py_DECREF(ret); } static void convert_from_python_cursor_trail_decay(PyObject *val, Options *opts) { cursor_trail_decay(val, opts); } static void convert_from_opts_cursor_trail_decay(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "cursor_trail_decay"); if (ret == NULL) return; convert_from_python_cursor_trail_decay(ret, opts); Py_DECREF(ret); } static void convert_from_python_cursor_trail_start_threshold(PyObject *val, Options *opts) { opts->cursor_trail_start_threshold = PyLong_AsLong(val); } static void convert_from_opts_cursor_trail_start_threshold(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "cursor_trail_start_threshold"); if (ret == NULL) return; convert_from_python_cursor_trail_start_threshold(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollback_indicator_opacity(PyObject *val, Options *opts) { opts->scrollback_indicator_opacity = PyFloat_AsFloat(val); } static void convert_from_opts_scrollback_indicator_opacity(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollback_indicator_opacity"); if (ret == NULL) return; convert_from_python_scrollback_indicator_opacity(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollback_pager_history_size(PyObject *val, Options *opts) { opts->scrollback_pager_history_size = PyLong_AsUnsignedLong(val); } static void convert_from_opts_scrollback_pager_history_size(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollback_pager_history_size"); if (ret == NULL) return; convert_from_python_scrollback_pager_history_size(ret, opts); Py_DECREF(ret); } static void convert_from_python_scrollback_fill_enlarged_window(PyObject *val, Options *opts) { opts->scrollback_fill_enlarged_window = PyObject_IsTrue(val); } static void convert_from_opts_scrollback_fill_enlarged_window(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "scrollback_fill_enlarged_window"); if (ret == NULL) return; convert_from_python_scrollback_fill_enlarged_window(ret, opts); Py_DECREF(ret); } static void convert_from_python_wheel_scroll_multiplier(PyObject *val, Options *opts) { opts->wheel_scroll_multiplier = PyFloat_AsDouble(val); } static void convert_from_opts_wheel_scroll_multiplier(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "wheel_scroll_multiplier"); if (ret == NULL) return; convert_from_python_wheel_scroll_multiplier(ret, opts); Py_DECREF(ret); } static void convert_from_python_wheel_scroll_min_lines(PyObject *val, Options *opts) { opts->wheel_scroll_min_lines = PyLong_AsLong(val); } static void convert_from_opts_wheel_scroll_min_lines(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "wheel_scroll_min_lines"); if (ret == NULL) return; convert_from_python_wheel_scroll_min_lines(ret, opts); Py_DECREF(ret); } static void convert_from_python_touch_scroll_multiplier(PyObject *val, Options *opts) { opts->touch_scroll_multiplier = PyFloat_AsDouble(val); } static void convert_from_opts_touch_scroll_multiplier(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "touch_scroll_multiplier"); if (ret == NULL) return; convert_from_python_touch_scroll_multiplier(ret, opts); Py_DECREF(ret); } static void convert_from_python_mouse_hide_wait(PyObject *val, Options *opts) { opts->mouse_hide_wait = parse_s_double_to_monotonic_t(val); } static void convert_from_opts_mouse_hide_wait(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "mouse_hide_wait"); if (ret == NULL) return; convert_from_python_mouse_hide_wait(ret, opts); Py_DECREF(ret); } static void convert_from_python_url_color(PyObject *val, Options *opts) { opts->url_color = color_as_int(val); } static void convert_from_opts_url_color(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "url_color"); if (ret == NULL) return; convert_from_python_url_color(ret, opts); Py_DECREF(ret); } static void convert_from_python_url_style(PyObject *val, Options *opts) { opts->url_style = PyLong_AsUnsignedLong(val); } static void convert_from_opts_url_style(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "url_style"); if (ret == NULL) return; convert_from_python_url_style(ret, opts); Py_DECREF(ret); } static void convert_from_python_url_prefixes(PyObject *val, Options *opts) { url_prefixes(val, opts); } static void convert_from_opts_url_prefixes(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "url_prefixes"); if (ret == NULL) return; convert_from_python_url_prefixes(ret, opts); Py_DECREF(ret); } static void convert_from_python_detect_urls(PyObject *val, Options *opts) { opts->detect_urls = PyObject_IsTrue(val); } static void convert_from_opts_detect_urls(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "detect_urls"); if (ret == NULL) return; convert_from_python_detect_urls(ret, opts); Py_DECREF(ret); } static void convert_from_python_url_excluded_characters(PyObject *val, Options *opts) { url_excluded_characters(val, opts); } static void convert_from_opts_url_excluded_characters(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "url_excluded_characters"); if (ret == NULL) return; convert_from_python_url_excluded_characters(ret, opts); Py_DECREF(ret); } static void convert_from_python_show_hyperlink_targets(PyObject *val, Options *opts) { opts->show_hyperlink_targets = PyObject_IsTrue(val); } static void convert_from_opts_show_hyperlink_targets(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "show_hyperlink_targets"); if (ret == NULL) return; convert_from_python_show_hyperlink_targets(ret, opts); Py_DECREF(ret); } static void convert_from_python_underline_hyperlinks(PyObject *val, Options *opts) { opts->underline_hyperlinks = underline_hyperlinks(val); } static void convert_from_opts_underline_hyperlinks(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "underline_hyperlinks"); if (ret == NULL) return; convert_from_python_underline_hyperlinks(ret, opts); Py_DECREF(ret); } static void convert_from_python_select_by_word_characters(PyObject *val, Options *opts) { select_by_word_characters(val, opts); } static void convert_from_opts_select_by_word_characters(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "select_by_word_characters"); if (ret == NULL) return; convert_from_python_select_by_word_characters(ret, opts); Py_DECREF(ret); } static void convert_from_python_select_by_word_characters_forward(PyObject *val, Options *opts) { select_by_word_characters_forward(val, opts); } static void convert_from_opts_select_by_word_characters_forward(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "select_by_word_characters_forward"); if (ret == NULL) return; convert_from_python_select_by_word_characters_forward(ret, opts); Py_DECREF(ret); } static void convert_from_python_click_interval(PyObject *val, Options *opts) { opts->click_interval = parse_s_double_to_monotonic_t(val); } static void convert_from_opts_click_interval(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "click_interval"); if (ret == NULL) return; convert_from_python_click_interval(ret, opts); Py_DECREF(ret); } static void convert_from_python_focus_follows_mouse(PyObject *val, Options *opts) { opts->focus_follows_mouse = PyObject_IsTrue(val); } static void convert_from_opts_focus_follows_mouse(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "focus_follows_mouse"); if (ret == NULL) return; convert_from_python_focus_follows_mouse(ret, opts); Py_DECREF(ret); } static void convert_from_python_pointer_shape_when_grabbed(PyObject *val, Options *opts) { opts->pointer_shape_when_grabbed = pointer_shape(val); } static void convert_from_opts_pointer_shape_when_grabbed(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "pointer_shape_when_grabbed"); if (ret == NULL) return; convert_from_python_pointer_shape_when_grabbed(ret, opts); Py_DECREF(ret); } static void convert_from_python_default_pointer_shape(PyObject *val, Options *opts) { opts->default_pointer_shape = pointer_shape(val); } static void convert_from_opts_default_pointer_shape(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "default_pointer_shape"); if (ret == NULL) return; convert_from_python_default_pointer_shape(ret, opts); Py_DECREF(ret); } static void convert_from_python_pointer_shape_when_dragging(PyObject *val, Options *opts) { dragging_pointer_shape(val, opts); } static void convert_from_opts_pointer_shape_when_dragging(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "pointer_shape_when_dragging"); if (ret == NULL) return; convert_from_python_pointer_shape_when_dragging(ret, opts); Py_DECREF(ret); } static void convert_from_python_repaint_delay(PyObject *val, Options *opts) { opts->repaint_delay = parse_ms_long_to_monotonic_t(val); } static void convert_from_opts_repaint_delay(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "repaint_delay"); if (ret == NULL) return; convert_from_python_repaint_delay(ret, opts); Py_DECREF(ret); } static void convert_from_python_input_delay(PyObject *val, Options *opts) { opts->input_delay = parse_ms_long_to_monotonic_t(val); } static void convert_from_opts_input_delay(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "input_delay"); if (ret == NULL) return; convert_from_python_input_delay(ret, opts); Py_DECREF(ret); } static void convert_from_python_sync_to_monitor(PyObject *val, Options *opts) { opts->sync_to_monitor = PyObject_IsTrue(val); } static void convert_from_opts_sync_to_monitor(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "sync_to_monitor"); if (ret == NULL) return; convert_from_python_sync_to_monitor(ret, opts); Py_DECREF(ret); } static void convert_from_python_enable_audio_bell(PyObject *val, Options *opts) { opts->enable_audio_bell = PyObject_IsTrue(val); } static void convert_from_opts_enable_audio_bell(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "enable_audio_bell"); if (ret == NULL) return; convert_from_python_enable_audio_bell(ret, opts); Py_DECREF(ret); } static void convert_from_python_visual_bell_duration(PyObject *val, Options *opts) { visual_bell_duration(val, opts); } static void convert_from_opts_visual_bell_duration(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "visual_bell_duration"); if (ret == NULL) return; convert_from_python_visual_bell_duration(ret, opts); Py_DECREF(ret); } static void convert_from_python_window_alert_on_bell(PyObject *val, Options *opts) { opts->window_alert_on_bell = PyObject_IsTrue(val); } static void convert_from_opts_window_alert_on_bell(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "window_alert_on_bell"); if (ret == NULL) return; convert_from_python_window_alert_on_bell(ret, opts); Py_DECREF(ret); } static void convert_from_python_bell_path(PyObject *val, Options *opts) { bell_path(val, opts); } static void convert_from_opts_bell_path(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "bell_path"); if (ret == NULL) return; convert_from_python_bell_path(ret, opts); Py_DECREF(ret); } static void convert_from_python_linux_bell_theme(PyObject *val, Options *opts) { bell_theme(val, opts); } static void convert_from_opts_linux_bell_theme(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "linux_bell_theme"); if (ret == NULL) return; convert_from_python_linux_bell_theme(ret, opts); Py_DECREF(ret); } static void convert_from_python_active_border_color(PyObject *val, Options *opts) { opts->active_border_color = active_border_color(val); } static void convert_from_opts_active_border_color(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "active_border_color"); if (ret == NULL) return; convert_from_python_active_border_color(ret, opts); Py_DECREF(ret); } static void convert_from_python_inactive_border_color(PyObject *val, Options *opts) { opts->inactive_border_color = color_as_int(val); } static void convert_from_opts_inactive_border_color(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "inactive_border_color"); if (ret == NULL) return; convert_from_python_inactive_border_color(ret, opts); Py_DECREF(ret); } static void convert_from_python_bell_border_color(PyObject *val, Options *opts) { opts->bell_border_color = color_as_int(val); } static void convert_from_opts_bell_border_color(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "bell_border_color"); if (ret == NULL) return; convert_from_python_bell_border_color(ret, opts); Py_DECREF(ret); } static void convert_from_python_inactive_text_alpha(PyObject *val, Options *opts) { opts->inactive_text_alpha = PyFloat_AsFloat(val); } static void convert_from_opts_inactive_text_alpha(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "inactive_text_alpha"); if (ret == NULL) return; convert_from_python_inactive_text_alpha(ret, opts); Py_DECREF(ret); } static void convert_from_python_hide_window_decorations(PyObject *val, Options *opts) { opts->hide_window_decorations = PyLong_AsUnsignedLong(val); } static void convert_from_opts_hide_window_decorations(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "hide_window_decorations"); if (ret == NULL) return; convert_from_python_hide_window_decorations(ret, opts); Py_DECREF(ret); } static void convert_from_python_window_logo_path(PyObject *val, Options *opts) { window_logo_path(val, opts); } static void convert_from_opts_window_logo_path(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "window_logo_path"); if (ret == NULL) return; convert_from_python_window_logo_path(ret, opts); Py_DECREF(ret); } static void convert_from_python_window_logo_position(PyObject *val, Options *opts) { opts->window_logo_position = bganchor(val); } static void convert_from_opts_window_logo_position(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "window_logo_position"); if (ret == NULL) return; convert_from_python_window_logo_position(ret, opts); Py_DECREF(ret); } static void convert_from_python_window_logo_alpha(PyObject *val, Options *opts) { opts->window_logo_alpha = PyFloat_AsFloat(val); } static void convert_from_opts_window_logo_alpha(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "window_logo_alpha"); if (ret == NULL) return; convert_from_python_window_logo_alpha(ret, opts); Py_DECREF(ret); } static void convert_from_python_window_logo_scale(PyObject *val, Options *opts) { window_logo_scale(val, opts); } static void convert_from_opts_window_logo_scale(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "window_logo_scale"); if (ret == NULL) return; convert_from_python_window_logo_scale(ret, opts); Py_DECREF(ret); } static void convert_from_python_resize_debounce_time(PyObject *val, Options *opts) { resize_debounce_time(val, opts); } static void convert_from_opts_resize_debounce_time(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "resize_debounce_time"); if (ret == NULL) return; convert_from_python_resize_debounce_time(ret, opts); Py_DECREF(ret); } static void convert_from_python_resize_in_steps(PyObject *val, Options *opts) { opts->resize_in_steps = PyObject_IsTrue(val); } static void convert_from_opts_resize_in_steps(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "resize_in_steps"); if (ret == NULL) return; convert_from_python_resize_in_steps(ret, opts); Py_DECREF(ret); } static void convert_from_python_tab_bar_edge(PyObject *val, Options *opts) { opts->tab_bar_edge = PyLong_AsLong(val); } static void convert_from_opts_tab_bar_edge(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "tab_bar_edge"); if (ret == NULL) return; convert_from_python_tab_bar_edge(ret, opts); Py_DECREF(ret); } static void convert_from_python_tab_bar_margin_height(PyObject *val, Options *opts) { tab_bar_margin_height(val, opts); } static void convert_from_opts_tab_bar_margin_height(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "tab_bar_margin_height"); if (ret == NULL) return; convert_from_python_tab_bar_margin_height(ret, opts); Py_DECREF(ret); } static void convert_from_python_tab_bar_style(PyObject *val, Options *opts) { tab_bar_style(val, opts); } static void convert_from_opts_tab_bar_style(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "tab_bar_style"); if (ret == NULL) return; convert_from_python_tab_bar_style(ret, opts); Py_DECREF(ret); } static void convert_from_python_tab_bar_min_tabs(PyObject *val, Options *opts) { opts->tab_bar_min_tabs = PyLong_AsUnsignedLong(val); } static void convert_from_opts_tab_bar_min_tabs(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "tab_bar_min_tabs"); if (ret == NULL) return; convert_from_python_tab_bar_min_tabs(ret, opts); Py_DECREF(ret); } static void convert_from_python_tab_bar_background(PyObject *val, Options *opts) { opts->tab_bar_background = color_or_none_as_int(val); } static void convert_from_opts_tab_bar_background(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "tab_bar_background"); if (ret == NULL) return; convert_from_python_tab_bar_background(ret, opts); Py_DECREF(ret); } static void convert_from_python_tab_bar_margin_color(PyObject *val, Options *opts) { opts->tab_bar_margin_color = color_or_none_as_int(val); } static void convert_from_opts_tab_bar_margin_color(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "tab_bar_margin_color"); if (ret == NULL) return; convert_from_python_tab_bar_margin_color(ret, opts); Py_DECREF(ret); } static void convert_from_python_foreground(PyObject *val, Options *opts) { opts->foreground = color_as_int(val); } static void convert_from_opts_foreground(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "foreground"); if (ret == NULL) return; convert_from_python_foreground(ret, opts); Py_DECREF(ret); } static void convert_from_python_background(PyObject *val, Options *opts) { opts->background = color_as_int(val); } static void convert_from_opts_background(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "background"); if (ret == NULL) return; convert_from_python_background(ret, opts); Py_DECREF(ret); } static void convert_from_python_background_opacity(PyObject *val, Options *opts) { opts->background_opacity = PyFloat_AsFloat(val); } static void convert_from_opts_background_opacity(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "background_opacity"); if (ret == NULL) return; convert_from_python_background_opacity(ret, opts); Py_DECREF(ret); } static void convert_from_python_background_blur(PyObject *val, Options *opts) { opts->background_blur = PyLong_AsLong(val); } static void convert_from_opts_background_blur(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "background_blur"); if (ret == NULL) return; convert_from_python_background_blur(ret, opts); Py_DECREF(ret); } static void convert_from_python_background_image(PyObject *val, Options *opts) { background_image(val, opts); } static void convert_from_opts_background_image(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "background_image"); if (ret == NULL) return; convert_from_python_background_image(ret, opts); Py_DECREF(ret); } static void convert_from_python_background_image_layout(PyObject *val, Options *opts) { opts->background_image_layout = bglayout(val); } static void convert_from_opts_background_image_layout(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "background_image_layout"); if (ret == NULL) return; convert_from_python_background_image_layout(ret, opts); Py_DECREF(ret); } static void convert_from_python_background_image_linear(PyObject *val, Options *opts) { opts->background_image_linear = PyObject_IsTrue(val); } static void convert_from_opts_background_image_linear(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "background_image_linear"); if (ret == NULL) return; convert_from_python_background_image_linear(ret, opts); Py_DECREF(ret); } static void convert_from_python_dynamic_background_opacity(PyObject *val, Options *opts) { opts->dynamic_background_opacity = PyObject_IsTrue(val); } static void convert_from_opts_dynamic_background_opacity(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "dynamic_background_opacity"); if (ret == NULL) return; convert_from_python_dynamic_background_opacity(ret, opts); Py_DECREF(ret); } static void convert_from_python_background_tint(PyObject *val, Options *opts) { opts->background_tint = PyFloat_AsFloat(val); } static void convert_from_opts_background_tint(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "background_tint"); if (ret == NULL) return; convert_from_python_background_tint(ret, opts); Py_DECREF(ret); } static void convert_from_python_background_tint_gaps(PyObject *val, Options *opts) { opts->background_tint_gaps = PyFloat_AsFloat(val); } static void convert_from_opts_background_tint_gaps(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "background_tint_gaps"); if (ret == NULL) return; convert_from_python_background_tint_gaps(ret, opts); Py_DECREF(ret); } static void convert_from_python_dim_opacity(PyObject *val, Options *opts) { opts->dim_opacity = PyFloat_AsFloat(val); } static void convert_from_opts_dim_opacity(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "dim_opacity"); if (ret == NULL) return; convert_from_python_dim_opacity(ret, opts); Py_DECREF(ret); } static void convert_from_python_close_on_child_death(PyObject *val, Options *opts) { opts->close_on_child_death = PyObject_IsTrue(val); } static void convert_from_opts_close_on_child_death(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "close_on_child_death"); if (ret == NULL) return; convert_from_python_close_on_child_death(ret, opts); Py_DECREF(ret); } static void convert_from_python_allow_hyperlinks(PyObject *val, Options *opts) { opts->allow_hyperlinks = PyObject_IsTrue(val); } static void convert_from_opts_allow_hyperlinks(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "allow_hyperlinks"); if (ret == NULL) return; convert_from_python_allow_hyperlinks(ret, opts); Py_DECREF(ret); } static void convert_from_python_menu_map(PyObject *val, Options *opts) { menu_map(val, opts); } static void convert_from_opts_menu_map(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "menu_map"); if (ret == NULL) return; convert_from_python_menu_map(ret, opts); Py_DECREF(ret); } static void convert_from_python_wayland_titlebar_color(PyObject *val, Options *opts) { opts->wayland_titlebar_color = PyLong_AsUnsignedLong(val); } static void convert_from_opts_wayland_titlebar_color(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "wayland_titlebar_color"); if (ret == NULL) return; convert_from_python_wayland_titlebar_color(ret, opts); Py_DECREF(ret); } static void convert_from_python_macos_titlebar_color(PyObject *val, Options *opts) { opts->macos_titlebar_color = PyLong_AsLong(val); } static void convert_from_opts_macos_titlebar_color(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "macos_titlebar_color"); if (ret == NULL) return; convert_from_python_macos_titlebar_color(ret, opts); Py_DECREF(ret); } static void convert_from_python_macos_option_as_alt(PyObject *val, Options *opts) { opts->macos_option_as_alt = PyLong_AsUnsignedLong(val); } static void convert_from_opts_macos_option_as_alt(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "macos_option_as_alt"); if (ret == NULL) return; convert_from_python_macos_option_as_alt(ret, opts); Py_DECREF(ret); } static void convert_from_python_macos_hide_from_tasks(PyObject *val, Options *opts) { opts->macos_hide_from_tasks = PyObject_IsTrue(val); } static void convert_from_opts_macos_hide_from_tasks(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "macos_hide_from_tasks"); if (ret == NULL) return; convert_from_python_macos_hide_from_tasks(ret, opts); Py_DECREF(ret); } static void convert_from_python_macos_quit_when_last_window_closed(PyObject *val, Options *opts) { opts->macos_quit_when_last_window_closed = PyObject_IsTrue(val); } static void convert_from_opts_macos_quit_when_last_window_closed(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "macos_quit_when_last_window_closed"); if (ret == NULL) return; convert_from_python_macos_quit_when_last_window_closed(ret, opts); Py_DECREF(ret); } static void convert_from_python_macos_window_resizable(PyObject *val, Options *opts) { opts->macos_window_resizable = PyObject_IsTrue(val); } static void convert_from_opts_macos_window_resizable(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "macos_window_resizable"); if (ret == NULL) return; convert_from_python_macos_window_resizable(ret, opts); Py_DECREF(ret); } static void convert_from_python_macos_thicken_font(PyObject *val, Options *opts) { opts->macos_thicken_font = PyFloat_AsFloat(val); } static void convert_from_opts_macos_thicken_font(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "macos_thicken_font"); if (ret == NULL) return; convert_from_python_macos_thicken_font(ret, opts); Py_DECREF(ret); } static void convert_from_python_macos_traditional_fullscreen(PyObject *val, Options *opts) { opts->macos_traditional_fullscreen = PyObject_IsTrue(val); } static void convert_from_opts_macos_traditional_fullscreen(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "macos_traditional_fullscreen"); if (ret == NULL) return; convert_from_python_macos_traditional_fullscreen(ret, opts); Py_DECREF(ret); } static void convert_from_python_macos_show_window_title_in(PyObject *val, Options *opts) { opts->macos_show_window_title_in = window_title_in(val); } static void convert_from_opts_macos_show_window_title_in(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "macos_show_window_title_in"); if (ret == NULL) return; convert_from_python_macos_show_window_title_in(ret, opts); Py_DECREF(ret); } static void convert_from_python_macos_menubar_title_max_length(PyObject *val, Options *opts) { opts->macos_menubar_title_max_length = PyLong_AsLong(val); } static void convert_from_opts_macos_menubar_title_max_length(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "macos_menubar_title_max_length"); if (ret == NULL) return; convert_from_python_macos_menubar_title_max_length(ret, opts); Py_DECREF(ret); } static void convert_from_python_macos_colorspace(PyObject *val, Options *opts) { opts->macos_colorspace = macos_colorspace(val); } static void convert_from_opts_macos_colorspace(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "macos_colorspace"); if (ret == NULL) return; convert_from_python_macos_colorspace(ret, opts); Py_DECREF(ret); } static void convert_from_python_wayland_enable_ime(PyObject *val, Options *opts) { opts->wayland_enable_ime = PyObject_IsTrue(val); } static void convert_from_opts_wayland_enable_ime(PyObject *py_opts, Options *opts) { PyObject *ret = PyObject_GetAttrString(py_opts, "wayland_enable_ime"); if (ret == NULL) return; convert_from_python_wayland_enable_ime(ret, opts); Py_DECREF(ret); } static bool convert_opts_from_python_opts(PyObject *py_opts, Options *opts) { convert_from_opts_font_size(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_force_ltr(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_disable_ligatures(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_font_features(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_modify_font(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_box_drawing_scale(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_undercurl_style(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_underline_exclusion(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_text_composition_strategy(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_cursor_shape(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_cursor_shape_unfocused(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_cursor_beam_thickness(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_cursor_underline_thickness(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_cursor_blink_interval(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_cursor_stop_blinking_after(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_cursor_trail(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_cursor_trail_decay(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_cursor_trail_start_threshold(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollback_indicator_opacity(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollback_pager_history_size(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_scrollback_fill_enlarged_window(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_wheel_scroll_multiplier(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_wheel_scroll_min_lines(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_touch_scroll_multiplier(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_mouse_hide_wait(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_url_color(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_url_style(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_url_prefixes(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_detect_urls(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_url_excluded_characters(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_show_hyperlink_targets(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_underline_hyperlinks(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_select_by_word_characters(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_select_by_word_characters_forward(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_click_interval(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_focus_follows_mouse(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_pointer_shape_when_grabbed(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_default_pointer_shape(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_pointer_shape_when_dragging(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_repaint_delay(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_input_delay(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_sync_to_monitor(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_enable_audio_bell(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_visual_bell_duration(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_window_alert_on_bell(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_bell_path(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_linux_bell_theme(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_active_border_color(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_inactive_border_color(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_bell_border_color(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_inactive_text_alpha(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_hide_window_decorations(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_window_logo_path(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_window_logo_position(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_window_logo_alpha(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_window_logo_scale(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_resize_debounce_time(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_resize_in_steps(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_tab_bar_edge(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_tab_bar_margin_height(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_tab_bar_style(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_tab_bar_min_tabs(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_tab_bar_background(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_tab_bar_margin_color(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_foreground(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_background(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_background_opacity(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_background_blur(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_background_image(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_background_image_layout(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_background_image_linear(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_dynamic_background_opacity(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_background_tint(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_background_tint_gaps(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_dim_opacity(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_close_on_child_death(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_allow_hyperlinks(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_menu_map(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_wayland_titlebar_color(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_macos_titlebar_color(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_macos_option_as_alt(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_macos_hide_from_tasks(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_macos_quit_when_last_window_closed(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_macos_window_resizable(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_macos_thicken_font(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_macos_traditional_fullscreen(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_macos_show_window_title_in(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_macos_menubar_title_max_length(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_macos_colorspace(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_wayland_enable_ime(py_opts, opts); if (PyErr_Occurred()) return false; return true; } kitty-0.41.1/kitty/options/to-c.h0000664000175000017510000004670014773370543016170 0ustar nileshnilesh/* * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "../state.h" #include "../colors.h" #include "../fonts.h" static inline float PyFloat_AsFloat(PyObject *o) { return (float)PyFloat_AsDouble(o); } static inline color_type color_as_int(PyObject *color) { if (!PyObject_TypeCheck(color, &Color_Type)) { PyErr_SetString(PyExc_TypeError, "Not a Color object"); return 0; } Color *c = (Color*)color; return c->color.val & 0xffffff; } static inline color_type color_or_none_as_int(PyObject *color) { if (color == Py_None) return 0; return color_as_int(color); } static inline color_type active_border_color(PyObject *color) { if (color == Py_None) return 0x00ff00; return color_as_int(color); } static inline monotonic_t parse_s_double_to_monotonic_t(PyObject *val) { return s_double_to_monotonic_t(PyFloat_AsDouble(val)); } static inline monotonic_t parse_ms_long_to_monotonic_t(PyObject *val) { return ms_to_monotonic_t(PyLong_AsUnsignedLong(val)); } static inline WindowTitleIn window_title_in(PyObject *title_in) { const char *in = PyUnicode_AsUTF8(title_in); switch(in[0]) { case 'a': return ALL; case 'w': return WINDOW; case 'm': return MENUBAR; case 'n': return NONE; default: break; } return ALL; } static inline unsigned undercurl_style(PyObject *x) { RAII_PyObject(thick, PyUnicode_FromString("thick")); RAII_PyObject(dense, PyUnicode_FromString("dense")); unsigned ans = 0; int ret; switch ((ret = PyUnicode_Find(x, dense, 0, PyUnicode_GET_LENGTH(x), 1))) { case -2: PyErr_Clear(); case -1: break; default: ans |= 1; } switch ((ret = PyUnicode_Find(x, thick, 0, PyUnicode_GET_LENGTH(x), 1))) { case -2: PyErr_Clear(); case -1: break; default: ans |= 2; } return ans; } static inline UnderlineHyperlinks underline_hyperlinks(PyObject *x) { const char *in = PyUnicode_AsUTF8(x); switch(in[0]) { case 'a': return UNDERLINE_ALWAYS; case 'n': return UNDERLINE_NEVER; default : return UNDERLINE_ON_HOVER; } } static inline BackgroundImageLayout bglayout(PyObject *layout_name) { const char *name = PyUnicode_AsUTF8(layout_name); switch(name[0]) { case 't': return TILING; case 'm': return MIRRORED; case 's': return SCALED; case 'c': { return name[1] == 'l' ? CLAMPED : (name[1] == 's' ? CENTER_SCALED : CENTER_CLAMPED); } default: break; } return TILING; } static inline ImageAnchorPosition bganchor(PyObject *anchor_name) { const char *name = PyUnicode_AsUTF8(anchor_name); ImageAnchorPosition anchor = {0.5f, 0.5f, 0.5f, 0.5f}; if (strstr(name, "top") != NULL) { anchor.canvas_y = 0.f; anchor.image_y = 0.f; } else if (strstr(name, "bottom") != NULL) { anchor.canvas_y = 1.f; anchor.image_y = 1.f; } if (strstr(name, "left") != NULL) { anchor.canvas_x = 0.f; anchor.image_x = 0.f; } else if (strstr(name, "right") != NULL) { anchor.canvas_x = 1.f; anchor.image_x = 1.f; } return anchor; } #define STR_SETTER(name) { \ free(opts->name); opts->name = NULL; \ if (src == Py_None || !PyUnicode_Check(src)) return; \ Py_ssize_t sz; \ const char *s = PyUnicode_AsUTF8AndSize(src, &sz); \ opts->name = calloc(sz + 1, sizeof(s[0])); \ if (opts->name) memcpy(opts->name, s, sz); \ } static inline void background_image(PyObject *src, Options *opts) { STR_SETTER(background_image); } static inline void bell_path(PyObject *src, Options *opts) { STR_SETTER(bell_path); } static inline void bell_theme(PyObject *src, Options *opts) { STR_SETTER(bell_theme); } static inline void window_logo_path(PyObject *src, Options *opts) { STR_SETTER(default_window_logo); } #undef STR_SETTER static void add_easing_function(Animation *a, PyObject *e, double y_at_start, double y_at_end) { #define G(name) RAII_PyObject(name, PyObject_GetAttrString(e, #name)) #define D(container, idx) PyFloat_AsDouble(PyTuple_GET_ITEM(container, idx)) #define EQ(x, val) (PyUnicode_CompareWithASCIIString((x), val) == 0) G(type); if (EQ(type, "cubic-bezier")) { G(cubic_bezier_points); add_cubic_bezier_animation(a, y_at_start, y_at_end, D(cubic_bezier_points, 0), D(cubic_bezier_points, 1), D(cubic_bezier_points, 2), D(cubic_bezier_points, 3)); } else if (EQ(type, "linear")) { G(linear_x); G(linear_y); size_t count = PyTuple_GET_SIZE(linear_x); RAII_ALLOC(double, x, malloc(2 * sizeof(double) * count)); if (x) { double *y = x + count; for (size_t i = 0; i < count; i++) { x[i] = D(linear_x, i); y[i] = D(linear_y, i); } add_linear_animation(a, y_at_start, y_at_end, count, x, y); } } else if (EQ(type, "steps")) { G(num_steps); G(jump_type); EasingStep jt = EASING_STEP_END; if (EQ(jump_type, "start")) jt = EASING_STEP_START; else if (EQ(jump_type, "none")) jt = EASING_STEP_NONE; else if (EQ(jump_type, "both")) jt = EASING_STEP_BOTH; add_steps_animation(a, y_at_start, y_at_end, PyLong_AsSize_t(num_steps), jt); } #undef EQ #undef D #undef G } #define parse_animation(duration, name, start, end) \ opts->duration = parse_s_double_to_monotonic_t(PyTuple_GET_ITEM(src, 0)); \ opts->animation.name = free_animation(opts->animation.name); \ if (PyObject_IsTrue(PyTuple_GET_ITEM(src, 1)) && (opts->animation.name = alloc_animation()) != NULL) { \ add_easing_function(opts->animation.name, PyTuple_GET_ITEM(src, 1), start, end); \ if (PyObject_IsTrue(PyTuple_GET_ITEM(src, 2))) { \ add_easing_function(opts->animation.name, PyTuple_GET_ITEM(src, 2), end, start); \ } else { \ add_easing_function(opts->animation.name, PyTuple_GET_ITEM(src, 1), end, start); \ } \ } \ static inline void cursor_blink_interval(PyObject *src, Options *opts) { parse_animation(cursor_blink_interval, cursor, 1, 0); } static inline void visual_bell_duration(PyObject *src, Options *opts) { parse_animation(visual_bell_duration, visual_bell, 0, 1); } #undef parse_animation static inline void cursor_trail_decay(PyObject *src, Options *opts) { opts->cursor_trail_decay_fast = PyFloat_AsFloat(PyTuple_GET_ITEM(src, 0)); opts->cursor_trail_decay_slow = PyFloat_AsFloat(PyTuple_GET_ITEM(src, 1)); } static void parse_font_mod_size(PyObject *val, float *sz, AdjustmentUnit *unit) { PyObject *mv = PyObject_GetAttrString(val, "mod_value"); if (mv) { *sz = PyFloat_AsFloat(PyTuple_GET_ITEM(mv, 0)); long u = PyLong_AsLong(PyTuple_GET_ITEM(mv, 1)); switch (u) { case POINT: case PERCENT: case PIXEL: *unit = u; break; } } } static inline void modify_font(PyObject *mf, Options *opts) { #define S(which) { PyObject *v = PyDict_GetItemString(mf, #which); if (v) parse_font_mod_size(v, &opts->which.val, &opts->which.unit); } S(underline_position); S(underline_thickness); S(strikethrough_thickness); S(strikethrough_position); S(cell_height); S(cell_width); S(baseline); #undef S } static inline void free_font_features(Options *opts) { if (opts->font_features.entries) { for (size_t i = 0; i < opts->font_features.num; i++) { free((void*)opts->font_features.entries[i].psname); free((void*)opts->font_features.entries[i].features); } free(opts->font_features.entries); } memset(&opts->font_features, 0, sizeof(opts->font_features)); } static inline void font_features(PyObject *mf, Options *opts) { free_font_features(opts); opts->font_features.num = PyDict_GET_SIZE(mf); if (!opts->font_features.num) return; opts->font_features.entries = calloc(opts->font_features.num, sizeof(opts->font_features.entries[0])); if (!opts->font_features.entries) { PyErr_NoMemory(); return; } PyObject *key, *value; Py_ssize_t pos = 0, i = 0; while (PyDict_Next(mf, &pos, &key, &value)) { __typeof__(opts->font_features.entries) e = opts->font_features.entries + i++; Py_ssize_t psname_sz; const char *psname = PyUnicode_AsUTF8AndSize(key, &psname_sz); e->psname = strndup(psname, psname_sz); if (!e->psname) { PyErr_NoMemory(); return; } e->num = PyTuple_GET_SIZE(value); if (e->num) { e->features = calloc(e->num, sizeof(e->features[0])); if (!e->features) { PyErr_NoMemory(); return; } for (size_t n = 0; n < e->num; n++) { ParsedFontFeature *f = (ParsedFontFeature*)PyTuple_GET_ITEM(value, n); e->features[n] = f->feature; } } } } static inline MouseShape pointer_shape(PyObject *shape_name) { const char *name = PyUnicode_AsUTF8(shape_name); if (!name) return TEXT_POINTER; /* start pointer shapes (auto generated by gen-key-constants.py do not edit) */ else if (strcmp(name, "arrow") == 0) return DEFAULT_POINTER; else if (strcmp(name, "beam") == 0) return TEXT_POINTER; else if (strcmp(name, "text") == 0) return TEXT_POINTER; else if (strcmp(name, "pointer") == 0) return POINTER_POINTER; else if (strcmp(name, "hand") == 0) return POINTER_POINTER; else if (strcmp(name, "help") == 0) return HELP_POINTER; else if (strcmp(name, "wait") == 0) return WAIT_POINTER; else if (strcmp(name, "progress") == 0) return PROGRESS_POINTER; else if (strcmp(name, "crosshair") == 0) return CROSSHAIR_POINTER; else if (strcmp(name, "cell") == 0) return CELL_POINTER; else if (strcmp(name, "vertical-text") == 0) return VERTICAL_TEXT_POINTER; else if (strcmp(name, "move") == 0) return MOVE_POINTER; else if (strcmp(name, "e-resize") == 0) return E_RESIZE_POINTER; else if (strcmp(name, "ne-resize") == 0) return NE_RESIZE_POINTER; else if (strcmp(name, "nw-resize") == 0) return NW_RESIZE_POINTER; else if (strcmp(name, "n-resize") == 0) return N_RESIZE_POINTER; else if (strcmp(name, "se-resize") == 0) return SE_RESIZE_POINTER; else if (strcmp(name, "sw-resize") == 0) return SW_RESIZE_POINTER; else if (strcmp(name, "s-resize") == 0) return S_RESIZE_POINTER; else if (strcmp(name, "w-resize") == 0) return W_RESIZE_POINTER; else if (strcmp(name, "ew-resize") == 0) return EW_RESIZE_POINTER; else if (strcmp(name, "ns-resize") == 0) return NS_RESIZE_POINTER; else if (strcmp(name, "nesw-resize") == 0) return NESW_RESIZE_POINTER; else if (strcmp(name, "nwse-resize") == 0) return NWSE_RESIZE_POINTER; else if (strcmp(name, "zoom-in") == 0) return ZOOM_IN_POINTER; else if (strcmp(name, "zoom-out") == 0) return ZOOM_OUT_POINTER; else if (strcmp(name, "alias") == 0) return ALIAS_POINTER; else if (strcmp(name, "copy") == 0) return COPY_POINTER; else if (strcmp(name, "not-allowed") == 0) return NOT_ALLOWED_POINTER; else if (strcmp(name, "no-drop") == 0) return NO_DROP_POINTER; else if (strcmp(name, "grab") == 0) return GRAB_POINTER; else if (strcmp(name, "grabbing") == 0) return GRABBING_POINTER; /* end pointer shapes */ return TEXT_POINTER; } static inline void dragging_pointer_shape(PyObject *parts, Options *opts) { opts->pointer_shape_when_dragging = pointer_shape(PyTuple_GET_ITEM(parts, 0)); opts->pointer_shape_when_dragging_rectangle = pointer_shape(PyTuple_GET_ITEM(parts, 1)); } static inline int macos_colorspace(PyObject *csname) { if (PyUnicode_CompareWithASCIIString(csname, "srgb") == 0) return 1; if (PyUnicode_CompareWithASCIIString(csname, "displayp3") == 0) return 2; return 0; } static inline void free_url_prefixes(Options *opts) { opts->url_prefixes.num = 0; opts->url_prefixes.max_prefix_len = 0; if (opts->url_prefixes.values) { free(opts->url_prefixes.values); opts->url_prefixes.values = NULL; } } static inline void url_prefixes(PyObject *up, Options *opts) { if (!PyTuple_Check(up)) { PyErr_SetString(PyExc_TypeError, "url_prefixes must be a tuple"); return; } free_url_prefixes(opts); opts->url_prefixes.values = calloc(PyTuple_GET_SIZE(up), sizeof(UrlPrefix)); if (!opts->url_prefixes.values) { PyErr_NoMemory(); return; } opts->url_prefixes.num = PyTuple_GET_SIZE(up); for (size_t i = 0; i < opts->url_prefixes.num; i++) { PyObject *t = PyTuple_GET_ITEM(up, i); if (!PyUnicode_Check(t)) { PyErr_SetString(PyExc_TypeError, "url_prefixes must be strings"); return; } opts->url_prefixes.values[i].len = MIN(arraysz(opts->url_prefixes.values[i].string) - 1, (size_t)PyUnicode_GET_LENGTH(t)); int kind = PyUnicode_KIND(t); opts->url_prefixes.max_prefix_len = MAX(opts->url_prefixes.max_prefix_len, opts->url_prefixes.values[i].len); for (size_t x = 0; x < opts->url_prefixes.values[i].len; x++) { opts->url_prefixes.values[i].string[x] = PyUnicode_READ(kind, PyUnicode_DATA(t), x); } } } static inline void free_menu_map(Options *opts) { if (opts->global_menu.entries) { for (size_t i=0; i < opts->global_menu.count; i++) { struct MenuItem *e = opts->global_menu.entries + i; if (e->definition) { free((void*)e->definition); } if (e->location) { for (size_t l=0; l < e->location_count; l++) { free((void*)e->location[l]); } free(e->location); } } free(opts->global_menu.entries); opts->global_menu.entries = NULL; } opts->global_menu.count = 0; } static inline void menu_map(PyObject *entry_dict, Options *opts) { if (!PyDict_Check(entry_dict)) { PyErr_SetString(PyExc_TypeError, "menu_map entries must be a dict"); return; } free_menu_map(opts); size_t maxnum = PyDict_Size(entry_dict); opts->global_menu.count = 0; opts->global_menu.entries = calloc(maxnum, sizeof(opts->global_menu.entries[0])); if (!opts->global_menu.entries) { PyErr_NoMemory(); return; } PyObject *key, *value; Py_ssize_t pos = 0; while (PyDict_Next(entry_dict, &pos, &key, &value)) { if (PyTuple_Check(key) && PyTuple_GET_SIZE(key) > 1 && PyUnicode_Check(value) && PyUnicode_CompareWithASCIIString(PyTuple_GET_ITEM(key, 0), "global") == 0) { struct MenuItem *e = opts->global_menu.entries + opts->global_menu.count++; e->location_count = PyTuple_GET_SIZE(key) - 1; e->location = calloc(e->location_count, sizeof(e->location[0])); if (!e->location) { PyErr_NoMemory(); return; } e->definition = strdup(PyUnicode_AsUTF8(value)); if (!e->definition) { PyErr_NoMemory(); return; } for (size_t i = 0; i < e->location_count; i++) { e->location[i] = strdup(PyUnicode_AsUTF8(PyTuple_GET_ITEM(key, i+1))); if (!e->location[i]) { PyErr_NoMemory(); return; } } } } } static inline void underline_exclusion(PyObject *val, Options *opts) { if (!PyTuple_Check(val)) { PyErr_SetString(PyExc_TypeError, "underline_exclusion must be a tuple"); return; } opts->underline_exclusion.thickness = PyFloat_AsFloat(PyTuple_GET_ITEM(val, 0)); if (!PyUnicode_GET_LENGTH(PyTuple_GET_ITEM(val, 1))) opts->underline_exclusion.unit = 0; else if (PyUnicode_CompareWithASCIIString(PyTuple_GET_ITEM(val, 1), "px")) opts->underline_exclusion.unit = 1; else if (PyUnicode_CompareWithASCIIString(PyTuple_GET_ITEM(val, 1), "pt")) opts->underline_exclusion.unit = 2; else opts->underline_exclusion.unit = 0; } static inline void box_drawing_scale(PyObject *val, Options *opts) { for (unsigned i = 0; i < MIN(arraysz(opts->box_drawing_scale), (size_t)PyTuple_GET_SIZE(val)); i++) { opts->box_drawing_scale[i] = PyFloat_AsFloat(PyTuple_GET_ITEM(val, i)); } } static inline void text_composition_strategy(PyObject *val, Options *opts) { if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "text_rendering_strategy must be a string"); return; } opts->text_old_gamma = false; opts->text_gamma_adjustment = 1.0f; opts->text_contrast = 0.f; if (PyUnicode_CompareWithASCIIString(val, "platform") == 0) { #ifdef __APPLE__ opts->text_gamma_adjustment = 1.7f; opts->text_contrast = 30.f; #endif } else if (PyUnicode_CompareWithASCIIString(val, "legacy") == 0) { opts->text_old_gamma = true; } else { RAII_PyObject(parts, PyUnicode_Split(val, NULL, 2)); int size = PyList_GET_SIZE(parts); if (size < 1 || 2 < size) { PyErr_SetString(PyExc_ValueError, "text_rendering_strategy must be of the form number:[number]"); return; } if (size > 0) { RAII_PyObject(ga, PyFloat_FromString(PyList_GET_ITEM(parts, 0))); if (PyErr_Occurred()) return; opts->text_gamma_adjustment = MAX(0.01f, PyFloat_AsFloat(ga)); } if (size > 1) { RAII_PyObject(contrast, PyFloat_FromString(PyList_GET_ITEM(parts, 1))); if (PyErr_Occurred()) return; opts->text_contrast = MAX(0.0f, PyFloat_AsFloat(contrast)); opts->text_contrast = MIN(100.0f, opts->text_contrast); } } } static char_type* list_of_chars(PyObject *chars) { if (!PyUnicode_Check(chars)) { PyErr_SetString(PyExc_TypeError, "list_of_chars must be a string"); return NULL; } char_type *ans = calloc(PyUnicode_GET_LENGTH(chars) + 1, sizeof(char_type)); if (ans) { for (ssize_t i = 0; i < PyUnicode_GET_LENGTH(chars); i++) { ans[i] = PyUnicode_READ(PyUnicode_KIND(chars), PyUnicode_DATA(chars), i); } } return ans; } static inline void url_excluded_characters(PyObject *chars, Options *opts) { free(opts->url_excluded_characters); opts->url_excluded_characters = list_of_chars(chars); } static inline void select_by_word_characters(PyObject *chars, Options *opts) { free(opts->select_by_word_characters); opts->select_by_word_characters = list_of_chars(chars); } static inline void select_by_word_characters_forward(PyObject *chars, Options *opts) { free(opts->select_by_word_characters_forward); opts->select_by_word_characters_forward = list_of_chars(chars); } static inline void tab_bar_style(PyObject *val, Options *opts) { opts->tab_bar_hidden = PyUnicode_CompareWithASCIIString(val, "hidden") == 0 ? true: false; } static inline void tab_bar_margin_height(PyObject *val, Options *opts) { if (!PyTuple_Check(val) || PyTuple_GET_SIZE(val) != 2) { PyErr_SetString(PyExc_TypeError, "tab_bar_margin_height is not a 2-item tuple"); return; } opts->tab_bar_margin_height.outer = PyFloat_AsDouble(PyTuple_GET_ITEM(val, 0)); opts->tab_bar_margin_height.inner = PyFloat_AsDouble(PyTuple_GET_ITEM(val, 1)); } static inline void window_logo_scale(PyObject *src, Options *opts) { opts->window_logo_scale.width = PyFloat_AsFloat(PyTuple_GET_ITEM(src, 0)); opts->window_logo_scale.height = PyFloat_AsFloat(PyTuple_GET_ITEM(src, 1)); } static inline void resize_debounce_time(PyObject *src, Options *opts) { opts->resize_debounce_time.on_end = s_double_to_monotonic_t(PyFloat_AsDouble(PyTuple_GET_ITEM(src, 0))); opts->resize_debounce_time.on_pause = s_double_to_monotonic_t(PyFloat_AsDouble(PyTuple_GET_ITEM(src, 1))); } static inline void free_allocs_in_options(Options *opts) { free_menu_map(opts); free_url_prefixes(opts); free_font_features(opts); #define F(x) free(opts->x); opts->x = NULL; F(select_by_word_characters); F(url_excluded_characters); F(select_by_word_characters_forward); F(background_image); F(bell_path); F(bell_theme); F(default_window_logo); #undef F } kitty-0.41.1/kitty/options/types.py0000664000175000017510000012764414773370543016702 0ustar nileshnilesh# generated by gen-config.py DO NOT edit # isort: skip_file import typing import collections.abc # noqa: F401, RUF100 from array import array from kitty.constants import is_macos import kitty.constants from kitty.fast_data_types import Color, SingleKey import kitty.fast_data_types from kitty.fonts import FontSpec import kitty.fonts from kitty.options.utils import ( AliasMap, KeyDefinition, KeyboardModeMap, MouseMap, MouseMapping, NotifyOnCmdFinish, TabBarMarginHeight ) import kitty.options.utils from kitty.types import FloatEdges import kitty.types choices_for_allow_cloning = typing.Literal['yes', 'y', 'true', 'no', 'n', 'false', 'ask'] choices_for_allow_remote_control = typing.Literal['password', 'socket-only', 'socket', 'no', 'n', 'false', 'yes', 'y', 'true'] choices_for_background_image_layout = typing.Literal['mirror-tiled', 'scaled', 'tiled', 'clamped', 'centered', 'cscaled'] choices_for_default_pointer_shape = typing.Literal['arrow', 'beam', 'text', 'pointer', 'hand', 'help', 'wait', 'progress', 'crosshair', 'cell', 'vertical-text', 'move', 'e-resize', 'ne-resize', 'nw-resize', 'n-resize', 'se-resize', 'sw-resize', 's-resize', 'w-resize', 'ew-resize', 'ns-resize', 'nesw-resize', 'nwse-resize', 'zoom-in', 'zoom-out', 'alias', 'copy', 'not-allowed', 'no-drop', 'grab', 'grabbing'] choices_for_linux_display_server = typing.Literal['auto', 'wayland', 'x11'] choices_for_macos_colorspace = typing.Literal['srgb', 'default', 'displayp3'] choices_for_macos_show_window_title_in = typing.Literal['all', 'menubar', 'none', 'window'] choices_for_placement_strategy = typing.Literal['top-left', 'top', 'top-right', 'left', 'center', 'right', 'bottom-left', 'bottom', 'bottom-right'] choices_for_pointer_shape_when_grabbed = choices_for_default_pointer_shape choices_for_strip_trailing_spaces = typing.Literal['always', 'never', 'smart'] choices_for_tab_bar_align = typing.Literal['left', 'center', 'right'] choices_for_tab_bar_style = typing.Literal['fade', 'hidden', 'powerline', 'separator', 'slant', 'custom'] choices_for_tab_powerline_style = typing.Literal['angled', 'round', 'slanted'] choices_for_tab_switch_strategy = typing.Literal['last', 'left', 'previous', 'right'] choices_for_terminfo_type = typing.Literal['path', 'direct', 'none'] choices_for_undercurl_style = typing.Literal['thin-sparse', 'thin-dense', 'thick-sparse', 'thick-dense'] choices_for_underline_hyperlinks = typing.Literal['hover', 'always', 'never'] choices_for_window_logo_position = choices_for_placement_strategy option_names = ( 'action_alias', 'active_border_color', 'active_tab_background', 'active_tab_font_style', 'active_tab_foreground', 'active_tab_title_template', 'allow_cloning', 'allow_hyperlinks', 'allow_remote_control', 'background', 'background_blur', 'background_image', 'background_image_layout', 'background_image_linear', 'background_opacity', 'background_tint', 'background_tint_gaps', 'bell_border_color', 'bell_on_tab', 'bell_path', 'bold_font', 'bold_italic_font', 'box_drawing_scale', 'clear_all_mouse_actions', 'clear_all_shortcuts', 'clear_selection_on_clipboard_loss', 'click_interval', 'clipboard_control', 'clipboard_max_size', 'clone_source_strategies', 'close_on_child_death', 'color0', 'color1', 'color2', 'color3', 'color4', 'color5', 'color6', 'color7', 'color8', 'color9', 'color10', 'color11', 'color12', 'color13', 'color14', 'color15', 'color16', 'color17', 'color18', 'color19', 'color20', 'color21', 'color22', 'color23', 'color24', 'color25', 'color26', 'color27', 'color28', 'color29', 'color30', 'color31', 'color32', 'color33', 'color34', 'color35', 'color36', 'color37', 'color38', 'color39', 'color40', 'color41', 'color42', 'color43', 'color44', 'color45', 'color46', 'color47', 'color48', 'color49', 'color50', 'color51', 'color52', 'color53', 'color54', 'color55', 'color56', 'color57', 'color58', 'color59', 'color60', 'color61', 'color62', 'color63', 'color64', 'color65', 'color66', 'color67', 'color68', 'color69', 'color70', 'color71', 'color72', 'color73', 'color74', 'color75', 'color76', 'color77', 'color78', 'color79', 'color80', 'color81', 'color82', 'color83', 'color84', 'color85', 'color86', 'color87', 'color88', 'color89', 'color90', 'color91', 'color92', 'color93', 'color94', 'color95', 'color96', 'color97', 'color98', 'color99', 'color100', 'color101', 'color102', 'color103', 'color104', 'color105', 'color106', 'color107', 'color108', 'color109', 'color110', 'color111', 'color112', 'color113', 'color114', 'color115', 'color116', 'color117', 'color118', 'color119', 'color120', 'color121', 'color122', 'color123', 'color124', 'color125', 'color126', 'color127', 'color128', 'color129', 'color130', 'color131', 'color132', 'color133', 'color134', 'color135', 'color136', 'color137', 'color138', 'color139', 'color140', 'color141', 'color142', 'color143', 'color144', 'color145', 'color146', 'color147', 'color148', 'color149', 'color150', 'color151', 'color152', 'color153', 'color154', 'color155', 'color156', 'color157', 'color158', 'color159', 'color160', 'color161', 'color162', 'color163', 'color164', 'color165', 'color166', 'color167', 'color168', 'color169', 'color170', 'color171', 'color172', 'color173', 'color174', 'color175', 'color176', 'color177', 'color178', 'color179', 'color180', 'color181', 'color182', 'color183', 'color184', 'color185', 'color186', 'color187', 'color188', 'color189', 'color190', 'color191', 'color192', 'color193', 'color194', 'color195', 'color196', 'color197', 'color198', 'color199', 'color200', 'color201', 'color202', 'color203', 'color204', 'color205', 'color206', 'color207', 'color208', 'color209', 'color210', 'color211', 'color212', 'color213', 'color214', 'color215', 'color216', 'color217', 'color218', 'color219', 'color220', 'color221', 'color222', 'color223', 'color224', 'color225', 'color226', 'color227', 'color228', 'color229', 'color230', 'color231', 'color232', 'color233', 'color234', 'color235', 'color236', 'color237', 'color238', 'color239', 'color240', 'color241', 'color242', 'color243', 'color244', 'color245', 'color246', 'color247', 'color248', 'color249', 'color250', 'color251', 'color252', 'color253', 'color254', 'color255', 'command_on_bell', 'confirm_os_window_close', 'copy_on_select', 'cursor', 'cursor_beam_thickness', 'cursor_blink_interval', 'cursor_shape', 'cursor_shape_unfocused', 'cursor_stop_blinking_after', 'cursor_text_color', 'cursor_trail', 'cursor_trail_decay', 'cursor_trail_start_threshold', 'cursor_underline_thickness', 'default_pointer_shape', 'detect_urls', 'dim_opacity', 'disable_ligatures', 'draw_minimal_borders', 'dynamic_background_opacity', 'editor', 'enable_audio_bell', 'enabled_layouts', 'env', 'exe_search_path', 'file_transfer_confirmation_bypass', 'filter_notification', 'focus_follows_mouse', 'font_family', 'font_features', 'font_size', 'force_ltr', 'foreground', 'forward_stdio', 'hide_window_decorations', 'inactive_border_color', 'inactive_tab_background', 'inactive_tab_font_style', 'inactive_tab_foreground', 'inactive_text_alpha', 'initial_window_height', 'initial_window_width', 'input_delay', 'italic_font', 'kitten_alias', 'kitty_mod', 'linux_bell_theme', 'linux_display_server', 'listen_on', 'macos_colorspace', 'macos_custom_beam_cursor', 'macos_hide_from_tasks', 'macos_menubar_title_max_length', 'macos_option_as_alt', 'macos_quit_when_last_window_closed', 'macos_show_window_title_in', 'macos_thicken_font', 'macos_titlebar_color', 'macos_traditional_fullscreen', 'macos_window_resizable', 'map', 'mark1_background', 'mark1_foreground', 'mark2_background', 'mark2_foreground', 'mark3_background', 'mark3_foreground', 'menu_map', 'modify_font', 'mouse_hide_wait', 'mouse_map', 'narrow_symbols', 'notify_on_cmd_finish', 'open_url_with', 'paste_actions', 'placement_strategy', 'pointer_shape_when_dragging', 'pointer_shape_when_grabbed', 'remember_window_size', 'remote_control_password', 'repaint_delay', 'resize_debounce_time', 'resize_in_steps', 'scrollback_fill_enlarged_window', 'scrollback_indicator_opacity', 'scrollback_lines', 'scrollback_pager', 'scrollback_pager_history_size', 'select_by_word_characters', 'select_by_word_characters_forward', 'selection_background', 'selection_foreground', 'shell', 'shell_integration', 'show_hyperlink_targets', 'single_window_margin_width', 'single_window_padding_width', 'startup_session', 'strip_trailing_spaces', 'symbol_map', 'sync_to_monitor', 'tab_activity_symbol', 'tab_bar_align', 'tab_bar_background', 'tab_bar_edge', 'tab_bar_margin_color', 'tab_bar_margin_height', 'tab_bar_margin_width', 'tab_bar_min_tabs', 'tab_bar_style', 'tab_fade', 'tab_powerline_style', 'tab_separator', 'tab_switch_strategy', 'tab_title_max_length', 'tab_title_template', 'term', 'terminfo_type', 'text_composition_strategy', 'text_fg_override_threshold', 'touch_scroll_multiplier', 'transparent_background_colors', 'undercurl_style', 'underline_exclusion', 'underline_hyperlinks', 'update_check_interval', 'url_color', 'url_excluded_characters', 'url_prefixes', 'url_style', 'visual_bell_color', 'visual_bell_duration', 'visual_window_select_characters', 'watcher', 'wayland_enable_ime', 'wayland_titlebar_color', 'wheel_scroll_min_lines', 'wheel_scroll_multiplier', 'window_alert_on_bell', 'window_border_width', 'window_logo_alpha', 'window_logo_path', 'window_logo_position', 'window_logo_scale', 'window_margin_width', 'window_padding_width', 'window_resize_step_cells', 'window_resize_step_lines', ) class Options: active_border_color: kitty.fast_data_types.Color | None = Color(0, 255, 0) active_tab_background: Color = Color(238, 238, 238) active_tab_font_style: tuple[bool, bool] = (True, True) active_tab_foreground: Color = Color(0, 0, 0) active_tab_title_template: str | None = None allow_cloning: choices_for_allow_cloning = 'ask' allow_hyperlinks: int = 1 allow_remote_control: choices_for_allow_remote_control = 'no' background: Color = Color(0, 0, 0) background_blur: int = 0 background_image: str | None = None background_image_layout: choices_for_background_image_layout = 'tiled' background_image_linear: bool = False background_opacity: float = 1.0 background_tint: float = 0 background_tint_gaps: float = 1.0 bell_border_color: Color = Color(255, 90, 0) bell_on_tab: str = '🔔 ' bell_path: str | None = None bold_font: FontSpec = FontSpec(family=None, style=None, postscript_name=None, full_name=None, system='auto', axes=(), variable_name=None, features=(), created_from_string='auto') bold_italic_font: FontSpec = FontSpec(family=None, style=None, postscript_name=None, full_name=None, system='auto', axes=(), variable_name=None, features=(), created_from_string='auto') box_drawing_scale: tuple[float, float, float, float] = (0.001, 1.0, 1.5, 2.0) clear_all_mouse_actions: bool = False clear_all_shortcuts: bool = False clear_selection_on_clipboard_loss: bool = False click_interval: float = -1.0 clipboard_control: tuple[str, ...] = ('write-clipboard', 'write-primary', 'read-clipboard-ask', 'read-primary-ask') clipboard_max_size: float = 512.0 clone_source_strategies: frozenset[str] = frozenset({'conda', 'env_var', 'path', 'venv'}) close_on_child_death: bool = False command_on_bell: list[str] = ['none'] confirm_os_window_close: tuple[int, bool] = (-1, False) copy_on_select: str = '' cursor: kitty.fast_data_types.Color | None = Color(204, 204, 204) cursor_beam_thickness: float = 1.5 cursor_blink_interval: tuple[float, kitty.options.utils.EasingFunction, kitty.options.utils.EasingFunction] = (-1.0, kitty.options.utils.EasingFunction(), kitty.options.utils.EasingFunction()) cursor_shape: int = 1 cursor_shape_unfocused: int = 4 cursor_stop_blinking_after: float = 15.0 cursor_text_color: kitty.fast_data_types.Color | None = Color(17, 17, 17) cursor_trail: int = 0 cursor_trail_decay: tuple[float, float] = (0.1, 0.4) cursor_trail_start_threshold: int = 2 cursor_underline_thickness: float = 2.0 default_pointer_shape: choices_for_default_pointer_shape = 'beam' detect_urls: bool = True dim_opacity: float = 0.4 disable_ligatures: int = 0 draw_minimal_borders: bool = True dynamic_background_opacity: bool = False editor: str = '.' enable_audio_bell: bool = True enabled_layouts: list[str] = ['fat', 'grid', 'horizontal', 'splits', 'stack', 'tall', 'vertical'] file_transfer_confirmation_bypass: str = '' focus_follows_mouse: bool = False font_family: FontSpec = FontSpec(family=None, style=None, postscript_name=None, full_name=None, system='monospace', axes=(), variable_name=None, features=(), created_from_string='monospace') font_size: float = 11.0 force_ltr: bool = False foreground: Color = Color(221, 221, 221) forward_stdio: bool = False hide_window_decorations: int = 0 inactive_border_color: Color = Color(204, 204, 204) inactive_tab_background: Color = Color(153, 153, 153) inactive_tab_font_style: tuple[bool, bool] = (False, False) inactive_tab_foreground: Color = Color(68, 68, 68) inactive_text_alpha: float = 1.0 initial_window_height: tuple[int, str] = (400, 'px') initial_window_width: tuple[int, str] = (640, 'px') input_delay: int = 3 italic_font: FontSpec = FontSpec(family=None, style=None, postscript_name=None, full_name=None, system='auto', axes=(), variable_name=None, features=(), created_from_string='auto') kitty_mod: int = 5 linux_bell_theme: str = '__custom' linux_display_server: choices_for_linux_display_server = 'auto' listen_on: str = 'none' macos_colorspace: choices_for_macos_colorspace = 'srgb' macos_custom_beam_cursor: bool = False macos_hide_from_tasks: bool = False macos_menubar_title_max_length: int = 0 macos_option_as_alt: int = 0 macos_quit_when_last_window_closed: bool = False macos_show_window_title_in: choices_for_macos_show_window_title_in = 'all' macos_thicken_font: float = 0 macos_titlebar_color: int = 0 macos_traditional_fullscreen: bool = False macos_window_resizable: bool = True mark1_background: Color = Color(152, 211, 203) mark1_foreground: Color = Color(0, 0, 0) mark2_background: Color = Color(242, 220, 211) mark2_foreground: Color = Color(0, 0, 0) mark3_background: Color = Color(242, 116, 188) mark3_foreground: Color = Color(0, 0, 0) mouse_hide_wait: float = 0.0 if is_macos else 3.0 notify_on_cmd_finish: NotifyOnCmdFinish = NotifyOnCmdFinish(when='never', duration=5.0, action='notify', cmdline=(), clear_on=('focus', 'next')) open_url_with: list[str] = ['default'] paste_actions: frozenset[str] = frozenset({'confirm', 'quote-urls-at-prompt'}) placement_strategy: choices_for_placement_strategy = 'center' pointer_shape_when_dragging: tuple[str, str] = ('beam', 'crosshair') pointer_shape_when_grabbed: choices_for_pointer_shape_when_grabbed = 'arrow' remember_window_size: bool = True repaint_delay: int = 10 resize_debounce_time: tuple[float, float] = (0.1, 0.5) resize_in_steps: bool = False scrollback_fill_enlarged_window: bool = False scrollback_indicator_opacity: float = 1.0 scrollback_lines: int = 2000 scrollback_pager: list[str] = ['less', '--chop-long-lines', '--RAW-CONTROL-CHARS', '+INPUT_LINE_NUMBER'] scrollback_pager_history_size: int = 0 select_by_word_characters: str = '@-./_~?&=%+#' select_by_word_characters_forward: str = '' selection_background: kitty.fast_data_types.Color | None = Color(255, 250, 205) selection_foreground: kitty.fast_data_types.Color | None = Color(0, 0, 0) shell: str = '.' shell_integration: frozenset[str] = frozenset({'enabled'}) show_hyperlink_targets: bool = False single_window_margin_width: FloatEdges = FloatEdges(left=-1.0, top=-1.0, right=-1.0, bottom=-1.0) single_window_padding_width: FloatEdges = FloatEdges(left=-1.0, top=-1.0, right=-1.0, bottom=-1.0) startup_session: str | None = None strip_trailing_spaces: choices_for_strip_trailing_spaces = 'never' sync_to_monitor: bool = True tab_activity_symbol: str = '' tab_bar_align: choices_for_tab_bar_align = 'left' tab_bar_background: kitty.fast_data_types.Color | None = None tab_bar_edge: int = 8 tab_bar_margin_color: kitty.fast_data_types.Color | None = None tab_bar_margin_height: TabBarMarginHeight = TabBarMarginHeight(outer=0, inner=0) tab_bar_margin_width: float = 0 tab_bar_min_tabs: int = 2 tab_bar_style: choices_for_tab_bar_style = 'fade' tab_fade: tuple[float, ...] = (0.25, 0.5, 0.75, 1.0) tab_powerline_style: choices_for_tab_powerline_style = 'angled' tab_separator: str = ' ┇' tab_switch_strategy: choices_for_tab_switch_strategy = 'previous' tab_title_max_length: int = 0 tab_title_template: str = '{fmt.fg.red}{bell_symbol}{activity_symbol}{fmt.fg.tab}{tab.last_focused_progress_percent}{title}' term: str = 'xterm-kitty' terminfo_type: choices_for_terminfo_type = 'path' text_composition_strategy: str = 'platform' text_fg_override_threshold: tuple[float, typing.Literal['%', 'ratio']] = (0.0, '%') touch_scroll_multiplier: float = 1.0 transparent_background_colors: tuple[tuple[kitty.fast_data_types.Color, float], ...] = () undercurl_style: choices_for_undercurl_style = 'thin-sparse' underline_exclusion: tuple[float, typing.Literal['', 'px', 'pt']] = (1.0, '') underline_hyperlinks: choices_for_underline_hyperlinks = 'hover' update_check_interval: float = 24.0 url_color: Color = Color(0, 135, 189) url_excluded_characters: str = '' url_prefixes: tuple[str, ...] = ('file', 'ftp', 'ftps', 'gemini', 'git', 'gopher', 'http', 'https', 'irc', 'ircs', 'kitty', 'mailto', 'news', 'sftp', 'ssh') url_style: int = 3 visual_bell_color: kitty.fast_data_types.Color | None = None visual_bell_duration: tuple[float, kitty.options.utils.EasingFunction, kitty.options.utils.EasingFunction] = (0.0, kitty.options.utils.EasingFunction(), kitty.options.utils.EasingFunction()) visual_window_select_characters: str = '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ' wayland_enable_ime: bool = True wayland_titlebar_color: int = 0 wheel_scroll_min_lines: int = 1 wheel_scroll_multiplier: float = 5.0 window_alert_on_bell: bool = True window_border_width: tuple[float, str] = (0.5, 'pt') window_logo_alpha: float = 0.5 window_logo_path: str | None = None window_logo_position: choices_for_window_logo_position = 'bottom-right' window_logo_scale: tuple[float, float] = (0, -1.0) window_margin_width: FloatEdges = FloatEdges(left=0, top=0, right=0, bottom=0) window_padding_width: FloatEdges = FloatEdges(left=0, top=0, right=0, bottom=0) window_resize_step_cells: int = 2 window_resize_step_lines: int = 2 action_alias: dict[str, str] = {} env: dict[str, str] = {} exe_search_path: dict[str, str] = {} filter_notification: dict[str, str] = {} font_features: dict[str, tuple[kitty.fast_data_types.ParsedFontFeature, ...]] = {} kitten_alias: dict[str, str] = {} menu_map: dict[tuple[str, ...], str] = {} modify_font: dict[str, kitty.fonts.FontModification] = {} narrow_symbols: dict[tuple[int, int], int] = {} remote_control_password: dict[str, collections.abc.Sequence[str]] = {} symbol_map: dict[tuple[int, int], str] = {} watcher: dict[str, str] = {} map: list[kitty.options.utils.KeyDefinition] = [] keyboard_modes: KeyboardModeMap = {} alias_map: AliasMap = AliasMap() mouse_map: list[kitty.options.utils.MouseMapping] = [] mousemap: MouseMap = {} color_table: "array[int]" = array("L", ( 0x000000, 0xcc0403, 0x19cb00, 0xcecb00, 0x0d73cc, 0xcb1ed1, 0x0dcdcd, 0xdddddd, 0x767676, 0xf2201f, 0x23fd00, 0xfffd00, 0x1a8fff, 0xfd28ff, 0x14ffff, 0xffffff, 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af, 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f, 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, 0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f, 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff, 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af, 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af, 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff, 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff, 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e, 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee, )) config_paths: tuple[str, ...] = () all_config_paths: tuple[str, ...] = () config_overrides: tuple[str, ...] = () def __init__(self, options_dict: dict[str, typing.Any] | None = None) -> None: self.color_table = array(self.color_table.typecode, self.color_table) if options_dict is not None: null = object() for key in option_names: val = options_dict.get(key, null) if val is not null: setattr(self, key, val) @property def _fields(self) -> tuple[str, ...]: return option_names def __iter__(self) -> typing.Iterator[str]: return iter(self._fields) def __len__(self) -> int: return len(self._fields) def _copy_of_val(self, name: str) -> typing.Any: ans = getattr(self, name) if isinstance(ans, dict): ans = ans.copy() elif isinstance(ans, list): ans = ans[:] return ans def _asdict(self) -> dict[str, typing.Any]: return {k: self._copy_of_val(k) for k in self} def _replace(self, **kw: typing.Any) -> "Options": ans = Options() for name in self: setattr(ans, name, self._copy_of_val(name)) for name, val in kw.items(): setattr(ans, name, val) return ans def __getitem__(self, key: int | str) -> typing.Any: k = option_names[key] if isinstance(key, int) else key try: return getattr(self, k) except AttributeError: pass raise KeyError(f"No option named: {k}") def __getattr__(self, key: str) -> typing.Any: if key.startswith("color"): q = key[5:] if q.isdigit(): k = int(q) if 0 <= k <= 255: x = self.color_table[k] return Color((x >> 16) & 255, (x >> 8) & 255, x & 255) raise AttributeError(key) def __setattr__(self, key: str, val: typing.Any) -> typing.Any: if key.startswith("color"): q = key[5:] if q.isdigit(): k = int(q) if 0 <= k <= 255: self.color_table[k] = int(val) return object.__setattr__(self, key, val) defaults = Options() defaults.action_alias = {} defaults.env = {} defaults.exe_search_path = {} defaults.filter_notification = {} defaults.font_features = {} defaults.kitten_alias = {} defaults.menu_map = {} defaults.modify_font = {} defaults.narrow_symbols = {} defaults.remote_control_password = {} defaults.symbol_map = {} defaults.watcher = {} defaults.map = [ # copy_to_clipboard KeyDefinition(trigger=SingleKey(mods=256, key=99), definition='copy_to_clipboard'), # paste_from_clipboard KeyDefinition(trigger=SingleKey(mods=256, key=118), definition='paste_from_clipboard'), # paste_from_selection KeyDefinition(trigger=SingleKey(mods=256, key=115), definition='paste_from_selection'), # paste_from_selection KeyDefinition(trigger=SingleKey(mods=1, key=57348), definition='paste_from_selection'), # pass_selection_to_program KeyDefinition(trigger=SingleKey(mods=256, key=111), definition='pass_selection_to_program'), # scroll_line_up KeyDefinition(trigger=SingleKey(mods=256, key=57352), definition='scroll_line_up'), # scroll_line_up KeyDefinition(trigger=SingleKey(mods=256, key=107), definition='scroll_line_up'), # scroll_line_down KeyDefinition(trigger=SingleKey(mods=256, key=57353), definition='scroll_line_down'), # scroll_line_down KeyDefinition(trigger=SingleKey(mods=256, key=106), definition='scroll_line_down'), # scroll_page_up KeyDefinition(trigger=SingleKey(mods=256, key=57354), definition='scroll_page_up'), # scroll_page_down KeyDefinition(trigger=SingleKey(mods=256, key=57355), definition='scroll_page_down'), # scroll_home KeyDefinition(trigger=SingleKey(mods=256, key=57356), definition='scroll_home'), # scroll_end KeyDefinition(trigger=SingleKey(mods=256, key=57357), definition='scroll_end'), # scroll_to_previous_prompt KeyDefinition(trigger=SingleKey(mods=256, key=122), definition='scroll_to_prompt -1'), # scroll_to_next_prompt KeyDefinition(trigger=SingleKey(mods=256, key=120), definition='scroll_to_prompt 1'), # show_scrollback KeyDefinition(trigger=SingleKey(mods=256, key=104), definition='show_scrollback'), # show_last_command_output KeyDefinition(trigger=SingleKey(mods=256, key=103), definition='show_last_command_output'), # new_window KeyDefinition(trigger=SingleKey(mods=256, key=57345), definition='new_window'), # new_os_window KeyDefinition(trigger=SingleKey(mods=256, key=110), definition='new_os_window'), # close_window KeyDefinition(trigger=SingleKey(mods=256, key=119), definition='close_window'), # next_window KeyDefinition(trigger=SingleKey(mods=256, key=93), definition='next_window'), # previous_window KeyDefinition(trigger=SingleKey(mods=256, key=91), definition='previous_window'), # move_window_forward KeyDefinition(trigger=SingleKey(mods=256, key=102), definition='move_window_forward'), # move_window_backward KeyDefinition(trigger=SingleKey(mods=256, key=98), definition='move_window_backward'), # move_window_to_top KeyDefinition(trigger=SingleKey(mods=256, key=96), definition='move_window_to_top'), # start_resizing_window KeyDefinition(trigger=SingleKey(mods=256, key=114), definition='start_resizing_window'), # first_window KeyDefinition(trigger=SingleKey(mods=256, key=49), definition='first_window'), # second_window KeyDefinition(trigger=SingleKey(mods=256, key=50), definition='second_window'), # third_window KeyDefinition(trigger=SingleKey(mods=256, key=51), definition='third_window'), # fourth_window KeyDefinition(trigger=SingleKey(mods=256, key=52), definition='fourth_window'), # fifth_window KeyDefinition(trigger=SingleKey(mods=256, key=53), definition='fifth_window'), # sixth_window KeyDefinition(trigger=SingleKey(mods=256, key=54), definition='sixth_window'), # seventh_window KeyDefinition(trigger=SingleKey(mods=256, key=55), definition='seventh_window'), # eighth_window KeyDefinition(trigger=SingleKey(mods=256, key=56), definition='eighth_window'), # ninth_window KeyDefinition(trigger=SingleKey(mods=256, key=57), definition='ninth_window'), # tenth_window KeyDefinition(trigger=SingleKey(mods=256, key=48), definition='tenth_window'), # focus_visible_window KeyDefinition(trigger=SingleKey(mods=256, key=57370), definition='focus_visible_window'), # swap_with_window KeyDefinition(trigger=SingleKey(mods=256, key=57371), definition='swap_with_window'), # next_tab KeyDefinition(trigger=SingleKey(mods=256, key=57351), definition='next_tab'), # next_tab KeyDefinition(trigger=SingleKey(mods=4, key=57346), definition='next_tab'), # previous_tab KeyDefinition(trigger=SingleKey(mods=256, key=57350), definition='previous_tab'), # previous_tab KeyDefinition(trigger=SingleKey(mods=5, key=57346), definition='previous_tab'), # new_tab KeyDefinition(trigger=SingleKey(mods=256, key=116), definition='new_tab'), # close_tab KeyDefinition(trigger=SingleKey(mods=256, key=113), definition='close_tab'), # move_tab_forward KeyDefinition(trigger=SingleKey(mods=256, key=46), definition='move_tab_forward'), # move_tab_backward KeyDefinition(trigger=SingleKey(mods=256, key=44), definition='move_tab_backward'), # set_tab_title KeyDefinition(trigger=SingleKey(mods=258, key=116), definition='set_tab_title'), # next_layout KeyDefinition(trigger=SingleKey(mods=256, key=108), definition='next_layout'), # increase_font_size KeyDefinition(trigger=SingleKey(mods=256, key=61), definition='change_font_size all +2.0'), # increase_font_size KeyDefinition(trigger=SingleKey(mods=256, key=43), definition='change_font_size all +2.0'), # increase_font_size KeyDefinition(trigger=SingleKey(mods=256, key=57413), definition='change_font_size all +2.0'), # decrease_font_size KeyDefinition(trigger=SingleKey(mods=256, key=45), definition='change_font_size all -2.0'), # decrease_font_size KeyDefinition(trigger=SingleKey(mods=256, key=57412), definition='change_font_size all -2.0'), # reset_font_size KeyDefinition(trigger=SingleKey(mods=256, key=57347), definition='change_font_size all 0'), # open_url KeyDefinition(trigger=SingleKey(mods=256, key=101), definition='open_url_with_hints'), # insert_selected_path KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=102),), definition='kitten hints --type path --program -'), # open_selected_path KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(mods=1, key=102),), definition='kitten hints --type path'), # insert_selected_line KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=108),), definition='kitten hints --type line --program -'), # insert_selected_word KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=119),), definition='kitten hints --type word --program -'), # insert_selected_hash KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=104),), definition='kitten hints --type hash --program -'), # goto_file_line KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=110),), definition='kitten hints --type linenum'), # open_selected_hyperlink KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=112), rest=(SingleKey(key=121),), definition='kitten hints --type hyperlink'), # show_kitty_doc KeyDefinition(trigger=SingleKey(mods=256, key=57364), definition='show_kitty_doc overview'), # toggle_fullscreen KeyDefinition(trigger=SingleKey(mods=256, key=57374), definition='toggle_fullscreen'), # toggle_maximized KeyDefinition(trigger=SingleKey(mods=256, key=57373), definition='toggle_maximized'), # input_unicode_character KeyDefinition(trigger=SingleKey(mods=256, key=117), definition='kitten unicode_input'), # edit_config_file KeyDefinition(trigger=SingleKey(mods=256, key=57365), definition='edit_config_file'), # kitty_shell KeyDefinition(trigger=SingleKey(mods=256, key=57344), definition='kitty_shell window'), # increase_background_opacity KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=97), rest=(SingleKey(key=109),), definition='set_background_opacity +0.1'), # decrease_background_opacity KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=97), rest=(SingleKey(key=108),), definition='set_background_opacity -0.1'), # full_background_opacity KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=97), rest=(SingleKey(key=49),), definition='set_background_opacity 1'), # reset_background_opacity KeyDefinition(is_sequence=True, trigger=SingleKey(mods=256, key=97), rest=(SingleKey(key=100),), definition='set_background_opacity default'), # reset_terminal KeyDefinition(trigger=SingleKey(mods=256, key=57349), definition='clear_terminal reset active'), # reload_config_file KeyDefinition(trigger=SingleKey(mods=256, key=57368), definition='load_config_file'), # debug_config KeyDefinition(trigger=SingleKey(mods=256, key=57369), definition='debug_config'), ] if is_macos: defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=99), definition='copy_to_clipboard')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=118), definition='paste_from_clipboard')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=57354), definition='scroll_line_up')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57352), definition='scroll_line_up')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=57355), definition='scroll_line_down')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57353), definition='scroll_line_down')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57354), definition='scroll_page_up')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57355), definition='scroll_page_down')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57356), definition='scroll_home')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57357), definition='scroll_end')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57345), definition='new_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=110), definition='new_os_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=100), definition='close_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=114), definition='start_resizing_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=49), definition='first_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=50), definition='second_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=51), definition='third_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=52), definition='fourth_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=53), definition='fifth_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=54), definition='sixth_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=55), definition='seventh_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=56), definition='eighth_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57), definition='ninth_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=93), definition='next_tab')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=91), definition='previous_tab')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=116), definition='new_tab')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=119), definition='close_tab')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=119), definition='close_os_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=105), definition='set_tab_title')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=43), definition='change_font_size all +2.0')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=61), definition='change_font_size all +2.0')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=61), definition='change_font_size all +2.0')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=45), definition='change_font_size all -2.0')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=45), definition='change_font_size all -2.0')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=48), definition='change_font_size all 0')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=12, key=102), definition='toggle_fullscreen')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=115), definition='toggle_macos_secure_keyboard_entry')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=12, key=32), definition='kitten unicode_input')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=44), definition='edit_config_file')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=114), definition='clear_terminal reset active')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=107), definition='clear_terminal to_cursor active')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=107), definition='clear_terminal scrollback active')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=12, key=108), definition='clear_terminal to_cursor_scroll active')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=12, key=44), definition='load_config_file')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=44), definition='debug_config')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=47), definition='open_url https://sw.kovidgoyal.net/kitty/')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=104), definition='hide_macos_app')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=104), definition='hide_macos_other_apps')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=109), definition='minimize_macos_window')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=113), definition='quit')) defaults.mouse_map = [ # click_url_or_select MouseMapping(repeat_count=-2, definition='mouse_handle_click selection link prompt'), # click_url_or_select_grabbed MouseMapping(mods=1, repeat_count=-2, grabbed=True, definition='mouse_handle_click selection link prompt'), # click_url_or_select_grabbed MouseMapping(mods=1, repeat_count=-2, definition='mouse_handle_click selection link prompt'), # click_url MouseMapping(mods=5, repeat_count=-1, grabbed=True, definition='mouse_handle_click link'), # click_url MouseMapping(mods=5, repeat_count=-1, definition='mouse_handle_click link'), # click_url_discard MouseMapping(mods=5, grabbed=True, definition='discard_event'), # paste_selection MouseMapping(button=2, repeat_count=-1, definition='paste_from_selection'), # start_simple_selection MouseMapping(definition='mouse_selection normal'), # start_rectangle_selection MouseMapping(mods=6, definition='mouse_selection rectangle'), # select_word MouseMapping(repeat_count=2, definition='mouse_selection word'), # select_line MouseMapping(repeat_count=3, definition='mouse_selection line'), # select_line_from_point MouseMapping(mods=6, repeat_count=3, definition='mouse_selection line_from_point'), # extend_selection MouseMapping(button=1, definition='mouse_selection extend'), # paste_selection_grabbed MouseMapping(button=2, mods=1, repeat_count=-1, grabbed=True, definition='paste_selection'), # paste_selection_grabbed MouseMapping(button=2, mods=1, repeat_count=-1, definition='paste_selection'), # paste_selection_grabbed MouseMapping(button=2, mods=1, grabbed=True, definition='discard_event'), # start_simple_selection_grabbed MouseMapping(mods=1, grabbed=True, definition='mouse_selection normal'), # start_simple_selection_grabbed MouseMapping(mods=1, definition='mouse_selection normal'), # start_rectangle_selection_grabbed MouseMapping(mods=7, grabbed=True, definition='mouse_selection rectangle'), # start_rectangle_selection_grabbed MouseMapping(mods=7, definition='mouse_selection rectangle'), # select_word_grabbed MouseMapping(mods=1, repeat_count=2, grabbed=True, definition='mouse_selection word'), # select_word_grabbed MouseMapping(mods=1, repeat_count=2, definition='mouse_selection word'), # select_line_grabbed MouseMapping(mods=1, repeat_count=3, grabbed=True, definition='mouse_selection line'), # select_line_grabbed MouseMapping(mods=1, repeat_count=3, definition='mouse_selection line'), # select_line_from_point_grabbed MouseMapping(mods=7, repeat_count=3, grabbed=True, definition='mouse_selection line_from_point'), # select_line_from_point_grabbed MouseMapping(mods=7, repeat_count=3, definition='mouse_selection line_from_point'), # extend_selection_grabbed MouseMapping(button=1, mods=1, grabbed=True, definition='mouse_selection extend'), # extend_selection_grabbed MouseMapping(button=1, mods=1, definition='mouse_selection extend'), # show_clicked_cmd_output_ungrabbed MouseMapping(button=1, mods=5, definition='mouse_show_command_output'), ] nullable_colors = frozenset({ 'cursor', 'cursor_text_color', 'visual_bell_color', 'active_border_color', 'tab_bar_background', 'tab_bar_margin_color', 'selection_foreground', 'selection_background' }) secret_options = ('remote_control_password', 'file_transfer_confirmation_bypass')kitty-0.41.1/kitty/options/utils.py0000664000175000017510000015467214773370543016677 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import enum import re import sys from collections import defaultdict from collections.abc import Callable, Container, Iterable, Iterator, Sequence from contextlib import suppress from dataclasses import dataclass, fields from functools import lru_cache from typing import ( Any, Generic, Literal, NamedTuple, TypeVar, cast, get_args, ) import kitty.fast_data_types as defines from kitty.conf.utils import ( CurrentlyParsing, KeyAction, KeyFuncWrapper, currently_parsing, number_with_unit, percent, positive_float, positive_int, python_string, to_bool, to_cmdline, to_color, uniq, unit_float, ) from kitty.constants import is_macos from kitty.fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_HOLLOW, CURSOR_UNDERLINE, NO_CURSOR_SHAPE, Color, Shlex, SingleKey from kitty.fonts import FontModification, FontSpec, ModificationType, ModificationUnit, ModificationValue from kitty.key_names import character_key_name_aliases, functional_key_name_aliases, get_key_name_lookup from kitty.rgb import color_as_int from kitty.types import FloatEdges, MouseEvent from kitty.utils import expandvars, log_error, resolve_abs_or_config_path, shlex_split KeyMap = dict[SingleKey, list['KeyDefinition']] MouseMap = dict[MouseEvent, str] KeySequence = tuple[SingleKey, ...] MINIMUM_FONT_SIZE = 4 default_tab_separator = ' ┇' mod_map = {'⌃': 'CONTROL', 'CTRL': 'CONTROL', '⇧': 'SHIFT', '⌥': 'ALT', 'OPTION': 'ALT', 'OPT': 'ALT', '⌘': 'SUPER', 'COMMAND': 'SUPER', 'CMD': 'SUPER', 'KITTY_MOD': 'KITTY'} character_key_name_aliases_with_ascii_lowercase: dict[str, str] = character_key_name_aliases.copy() for x in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': character_key_name_aliases_with_ascii_lowercase[x] = x.lower() sequence_sep = '>' mouse_button_map = {'left': 'b1', 'middle': 'b3', 'right': 'b2'} mouse_trigger_count_map = {'doubleclick': -3, 'click': -2, 'release': -1, 'press': 1, 'doublepress': 2, 'triplepress': 3} FuncArgsType = tuple[str, Sequence[Any]] func_with_args = KeyFuncWrapper[FuncArgsType]() DELETE_ENV_VAR = '_delete_this_env_var_' class MapType(enum.Enum): MAP = 'map' MOUSE_MAP = 'mouse_map' OPEN_ACTION = 'open_action' class InvalidMods(ValueError): pass # Actions {{{ @func_with_args( 'pass_selection_to_program', 'new_window', 'new_tab', 'new_os_window', 'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd', 'launch', 'mouse_handle_click', 'show_error', ) def shlex_parse(func: str, rest: str) -> FuncArgsType: return func, to_cmdline(rest) def parse_send_text_bytes(text: str) -> bytes: return defines.expand_ansi_c_escapes(text).encode('utf-8') @func_with_args('scroll_prompt_to_top') def scroll_prompt_to_top(func: str, rest: str) -> FuncArgsType: return func, [to_bool(rest) if rest else False] @func_with_args('send_text') def send_text_parse(func: str, rest: str) -> FuncArgsType: args = rest.split(maxsplit=1) mode = '' data = b'' if len(args) > 1: mode = args[0] try: data = parse_send_text_bytes(args[1]) except Exception: log_error('Ignoring invalid send_text string: ' + args[1]) return func, [mode, data] @func_with_args('send_key') def send_key(func: str, rest: str) -> FuncArgsType: return func, rest.split() @func_with_args('run_kitten', 'run_simple_kitten', 'kitten') def kitten_parse(func: str, rest: str) -> FuncArgsType: parts = to_cmdline(rest) if func == 'kitten': return func, parts return 'kitten', parts[1:] @func_with_args('open_url') def open_url_parse(func: str, rest: str) -> FuncArgsType: from urllib.parse import urlparse url = '' try: url = python_string(rest) tokens = urlparse(url) if not all((tokens.scheme, tokens.netloc,)): raise ValueError('Invalid URL') except Exception: log_error('Ignoring invalid URL string: ' + rest) return func, (url,) @func_with_args('goto_tab') def goto_tab_parse(func: str, rest: str) -> FuncArgsType: n = int(rest) if n < 0: n += 1 # goto_tab subtracts 1 from its argument, this maps both zero and -1 to previous tab for backwards compat. return func, (n,) @func_with_args('detach_window') def detach_window_parse(func: str, rest: str) -> FuncArgsType: if rest not in ('new', 'new-tab', 'new-tab-left', 'new-tab-right', 'ask', 'tab-prev', 'tab-left', 'tab-right'): log_error(f'Ignoring invalid detach_window argument: {rest}') rest = 'new' return func, (rest,) @func_with_args('close_window_with_confirmation') def close_window_with_confirmation(func: str, rest: str) -> FuncArgsType: ignore_shell = rest == 'ignore-shell' return func, (ignore_shell,) @func_with_args('detach_tab') def detach_tab_parse(func: str, rest: str) -> FuncArgsType: if rest not in ('new', 'ask'): log_error(f'Ignoring invalid detach_tab argument: {rest}') rest = 'new' return func, (rest,) @func_with_args( 'set_background_opacity', 'goto_layout', 'toggle_layout', 'toggle_tab', 'kitty_shell', 'show_kitty_doc', 'set_tab_title', 'push_keyboard_mode', 'dump_lines_with_attrs', 'set_window_title', ) def simple_parse(func: str, rest: str) -> FuncArgsType: return func, (rest,) @func_with_args('set_font_size') def float_parse(func: str, rest: str) -> FuncArgsType: return func, (float(rest),) @func_with_args('signal_child') def signal_child_parse(func: str, rest: str) -> FuncArgsType: import signal signals = [] for q in rest.split(): try: signum = getattr(signal, q.upper()) except AttributeError: log_error(f'Unknown signal: {rest} ignoring') else: signals.append(signum) return func, tuple(signals) @func_with_args('change_font_size') def parse_change_font_size(func: str, rest: str) -> tuple[str, tuple[bool, str | None, float]]: vals = rest.strip().split(maxsplit=1) if len(vals) != 2: log_error(f'Invalid change_font_size specification: {rest}, treating it as default') return func, (True, None, 0) c_all = vals[0].lower() == 'all' sign: str | None = None amt = vals[1] if amt[0] in '+-': sign = amt[0] amt = amt[1:] return func, (c_all, sign, float(amt.strip())) @func_with_args('clear_terminal') def clear_terminal(func: str, rest: str) -> FuncArgsType: vals = rest.strip().split(maxsplit=1) if len(vals) != 2: log_error('clear_terminal needs two arguments, using defaults') args = ['reset', True] else: action = vals[0].lower() if action not in ('reset', 'scroll', 'scrollback', 'clear', 'to_cursor', 'to_cursor_scroll'): log_error(f'{action} is unknown for clear_terminal, using reset') action = 'reset' args = [action, vals[1].lower() == 'active'] return func, args @func_with_args('copy_to_buffer') def copy_to_buffer(func: str, rest: str) -> FuncArgsType: return func, [rest] @func_with_args('paste_from_buffer') def paste_from_buffer(func: str, rest: str) -> FuncArgsType: return func, [rest] @func_with_args('paste') def paste_parse(func: str, rest: str) -> FuncArgsType: text = '' try: text = defines.expand_ansi_c_escapes(rest) except Exception: log_error('Ignoring invalid paste string: ' + rest) return func, [text] @func_with_args('neighboring_window') def neighboring_window(func: str, rest: str) -> FuncArgsType: rest = rest.lower() rest = {'up': 'top', 'down': 'bottom'}.get(rest, rest) if rest not in ('left', 'right', 'top', 'bottom'): log_error(f'Invalid neighbor specification: {rest}') rest = 'right' return func, [rest] @func_with_args('resize_window') def resize_window(func: str, rest: str) -> FuncArgsType: vals = rest.strip().split(maxsplit=1) if len(vals) > 2: log_error('resize_window needs one or two arguments, using defaults') args = ['wider', 1] else: quality = vals[0].lower() if quality not in ('reset', 'taller', 'shorter', 'wider', 'narrower'): log_error(f'Invalid quality specification: {quality}') quality = 'wider' increment = 1 if len(vals) == 2: try: increment = int(vals[1]) except Exception: log_error(f'Invalid increment specification: {vals[1]}') args = [quality, increment] return func, args @func_with_args('move_window') def move_window(func: str, rest: str) -> FuncArgsType: rest = rest.lower() rest = {'up': 'top', 'down': 'bottom'}.get(rest, rest) prest: int | str = rest try: prest = int(prest) except Exception: if prest not in ('left', 'right', 'top', 'bottom'): log_error(f'Invalid move_window specification: {rest}') prest = 0 return func, [prest] @func_with_args('pipe') def pipe(func: str, rest: str) -> FuncArgsType: r = list(shlex_split(rest)) if len(r) < 3: log_error('Too few arguments to pipe function') r = ['none', 'none', 'true'] return func, r @func_with_args('set_colors') def set_colors(func: str, rest: str) -> FuncArgsType: r = list(shlex_split(rest)) if len(r) < 1: log_error('Too few arguments to set_colors function') return func, r @func_with_args('remote_control') def remote_control(func: str, rest: str) -> FuncArgsType: func, args = shlex_parse(func, rest) if len(args) < 1: log_error('Too few arguments to remote_control function') return func, args @func_with_args('remote_control_script') def remote_control_script(func: str, rest: str) -> FuncArgsType: func, args = shlex_parse(func, rest) if len(args) < 1: log_error('Too few arguments to remote_control_script function') return func, args @func_with_args('nth_os_window', 'nth_window', 'scroll_to_prompt', 'visual_window_select_action_trigger', 'next_layout') def single_integer_arg(func: str, rest: str) -> FuncArgsType: try: num = int(rest) except Exception: if rest: log_error(f'Invalid number for {func}: {rest}') num = -1 if func == 'scroll_to_prompt' else 1 return func, [num] @func_with_args('sleep') def sleep(func: str, sleep_time: str) -> FuncArgsType: mult = 1 sleep_time = sleep_time or '1' if sleep_time[-1] in 'shmd': mult = {'s': 1, 'm': 60, 'h': 3600, 'd': 24 * 3600}[sleep_time[-1]] sleep_time = sleep_time[:-1] return func, [abs(float(sleep_time)) * mult] @func_with_args('disable_ligatures_in') def disable_ligatures_in(func: str, rest: str) -> FuncArgsType: parts = rest.split(maxsplit=1) if len(parts) == 1: where, strategy = 'active', parts[0] else: where, strategy = parts if where not in ('active', 'all', 'tab'): raise ValueError(f'{where} is not a valid set of windows to disable ligatures in') if strategy not in ('never', 'always', 'cursor'): raise ValueError(f'{strategy} is not a valid disable ligatures strategy') return func, [where, strategy] @func_with_args('layout_action') def layout_action(func: str, rest: str) -> FuncArgsType: parts = rest.split(maxsplit=1) if not parts: raise ValueError('layout_action must have at least one argument') return func, [parts[0], tuple(parts[1:])] def parse_marker_spec(ftype: str, parts: Sequence[str]) -> tuple[str, str | tuple[tuple[int, str], ...], int]: flags = re.UNICODE if ftype in ('text', 'itext', 'regex', 'iregex'): if ftype.startswith('i'): flags |= re.IGNORECASE if not parts or len(parts) % 2 != 0: raise ValueError('Mark group number and text/regex are not specified in pairs: {}'.format(' '.join(parts))) ans = [] for i in range(0, len(parts), 2): try: color = max(1, min(int(parts[i]), 3)) except Exception: raise ValueError(f'Mark group in marker specification is not an integer: {parts[i]}') sspec = parts[i + 1] if 'regex' not in ftype: sspec = re.escape(sspec) ans.append((color, sspec)) ftype = 'regex' spec: str | tuple[tuple[int, str], ...] = tuple(ans) elif ftype == 'function': spec = ' '.join(parts) else: raise ValueError(f'Unknown marker type: {ftype}') return ftype, spec, flags @func_with_args('toggle_marker') def toggle_marker(func: str, rest: str) -> FuncArgsType: parts = rest.split(maxsplit=1) if len(parts) != 2: raise ValueError(f'{rest} is not a valid marker specification') ftype, spec = parts parts = list(shlex_split(spec)) return func, list(parse_marker_spec(ftype, parts)) @func_with_args('scroll_to_mark') def scroll_to_mark(func: str, rest: str) -> FuncArgsType: parts = rest.split() if not parts or not rest: return func, [True, 0] if len(parts) == 1: q = parts[0].lower() if q in ('prev', 'previous', 'next'): return func, [q != 'next', 0] try: return func, [True, max(0, min(int(q), 3))] except Exception: raise ValueError(f'{rest} is not a valid scroll_to_mark destination') return func, [parts[0] != 'next', max(0, min(int(parts[1]), 3))] @func_with_args('mouse_selection') def mouse_selection(func: str, rest: str) -> FuncArgsType: cmap = getattr(mouse_selection, 'code_map', None) if cmap is None: cmap = { 'normal': defines.MOUSE_SELECTION_NORMAL, 'extend': defines.MOUSE_SELECTION_EXTEND, 'move-end': defines.MOUSE_SELECTION_MOVE_END, 'rectangle': defines.MOUSE_SELECTION_RECTANGLE, 'word': defines.MOUSE_SELECTION_WORD, 'line': defines.MOUSE_SELECTION_LINE, 'line_from_point': defines.MOUSE_SELECTION_LINE_FROM_POINT, 'word_and_line_from_point': defines.MOUSE_SELECTION_WORD_AND_LINE_FROM_POINT, } setattr(mouse_selection, 'code_map', cmap) return func, [cmap[rest]] @func_with_args('load_config_file') def load_config_file(func: str, rest: str) -> FuncArgsType: return func, list(shlex_split(rest)) # }}} def parse_mods(parts: Iterable[str], sc: str) -> int | None: def map_mod(m: str) -> str: return mod_map.get(m, m) mods = 0 for m in parts: try: mods |= getattr(defines, f'GLFW_MOD_{map_mod(m.upper())}') except AttributeError: if m.upper() != 'NONE': log_error(f'Shortcut: {sc} has unknown modifier, ignoring') return None return mods def to_modifiers(val: str) -> int: return parse_mods(val.split('+'), val) or 0 def parse_shortcut(sc: str) -> SingleKey: if sc.endswith('+') and len(sc) > 1: sc = f'{sc[:-1]}plus' parts = sc.split('+') mods = 0 if len(parts) > 1: mods = parse_mods(parts[:-1], sc) or 0 if not mods: raise InvalidMods('Invalid shortcut') q = parts[-1] q = character_key_name_aliases_with_ascii_lowercase.get(q.upper(), q) is_native = False if q.startswith('0x'): try: key = int(q, 16) except Exception: key = 0 else: is_native = True else: try: key = ord(q) except Exception: uq = q.upper() uq = functional_key_name_aliases.get(uq, uq) x: int | None = getattr(defines, f'GLFW_FKEY_{uq}', None) if x is None: lf = get_key_name_lookup() key = lf(q, False) or 0 is_native = key > 0 else: key = x return SingleKey(mods, is_native, key or 0) def to_font_size(x: str) -> float: return max(MINIMUM_FONT_SIZE, float(x)) def disable_ligatures(x: str) -> int: cmap = {'never': 0, 'cursor': 1, 'always': 2} return cmap.get(x.lower(), 0) def box_drawing_scale(x: str) -> tuple[float, float, float, float]: ans = tuple(float(q.strip()) for q in x.split(',')) if len(ans) != 4: raise ValueError('Invalid box_drawing scale, must have four entries') return ans[0], ans[1], ans[2], ans[3] def cursor_text_color(x: str) -> Color | None: if x.lower() == 'background': return None return to_color(x) cshapes = { 'block': CURSOR_BLOCK, 'beam': CURSOR_BEAM, 'underline': CURSOR_UNDERLINE } cshapes_unfocused = { 'block': CURSOR_BLOCK, 'beam': CURSOR_BEAM, 'underline': CURSOR_UNDERLINE, 'hollow': CURSOR_HOLLOW, 'unchanged': NO_CURSOR_SHAPE, } def to_cursor_shape(x: str) -> int: try: return cshapes[x.lower()] except KeyError: raise ValueError( 'Invalid cursor shape: {} allowed values are {}'.format( x, ', '.join(cshapes) ) ) def to_cursor_unfocused_shape(x: str) -> int: try: return cshapes_unfocused[x.lower()] except KeyError: raise ValueError( 'Invalid unfocused cursor shape: {} allowed values are {}'.format( x, ', '.join(cshapes_unfocused) ) ) def cursor_trail_decay(x: str) -> tuple[float, float]: fast, slow = map(positive_float, x.split()) slow = max(slow, fast) return fast, slow def scrollback_lines(x: str) -> int: ans = int(x) if ans < 0: ans = 2 ** 32 - 1 return ans def scrollback_pager_history_size(x: str) -> int: ans = int(max(0, float(x)) * 1024 * 1024) return min(ans, 4096 * 1024 * 1024 - 1) # "single" for backwards compat url_style_map = {'none': 0, 'single': 1, 'straight': 1, 'double': 2, 'curly': 3, 'dotted': 4, 'dashed': 5} def url_style(x: str) -> int: return url_style_map.get(x, url_style_map['curly']) def url_prefixes(x: str) -> tuple[str, ...]: return tuple(a.lower() for a in x.replace(',', ' ').split()) def copy_on_select(raw: str) -> str: q = raw.lower() # boolean values special cased for backwards compat if q in ('y', 'yes', 'true', 'clipboard'): return 'clipboard' if q in ('n', 'no', 'false', ''): return '' return raw def window_size(val: str) -> tuple[int, str]: val = val.lower() unit = 'cells' if val.endswith('c') else 'px' return positive_int(val.rstrip('c')), unit def parse_layout_names(parts: Iterable[str]) -> list[str]: from kitty.layout.interface import all_layouts ans = [] for p in parts: p = p.lower() if p in ('*', 'all'): ans.extend(sorted(all_layouts)) continue name = p.partition(':')[0] if name not in all_layouts: raise ValueError(f'The window layout {p} is unknown') ans.append(p) return uniq(ans) def to_layout_names(raw: str) -> list[str]: return parse_layout_names(x.strip() for x in raw.split(',')) def window_border_width(x: str | int | float) -> tuple[float, str]: unit = 'pt' if isinstance(x, str): trailer = x[-2:] if trailer in ('px', 'pt'): unit = trailer val = float(x[:-2]) else: val = float(x) else: val = float(x) return max(0, val), unit def edge_width(x: str, converter: Callable[[str], float] = positive_float) -> FloatEdges: parts = str(x).split() num = len(parts) if num == 1: val = converter(parts[0]) return FloatEdges(val, val, val, val) if num == 2: v = converter(parts[0]) h = converter(parts[1]) return FloatEdges(h, v, h, v) if num == 3: top, h, bottom = map(converter, parts) return FloatEdges(h, top, h, bottom) top, right, bottom, left = map(converter, parts) return FloatEdges(left, top, right, bottom) def optional_edge_width(x: str) -> FloatEdges: return edge_width(x, float) def hide_window_decorations(x: str) -> int: if x == 'titlebar-only': return 0b10 if x == 'titlebar-and-corners': return 0b100 if to_bool(x): return 0b01 return 0b00 def resize_draw_strategy(x: str) -> int: cmap = {'static': 0, 'scale': 1, 'blank': 2, 'size': 3} return cmap.get(x.lower(), 0) def window_logo_scale(x: str) -> tuple[float, float]: parts = x.split(maxsplit=1) if len(parts) == 1: return positive_float(parts[0]), -1.0 return positive_float(parts[0]), positive_float(parts[1]) def resize_debounce_time(x: str) -> tuple[float, float]: parts = x.split(maxsplit=1) if len(parts) == 1: return positive_float(parts[0]), 0.5 return positive_float(parts[0]), positive_float(parts[1]) def visual_window_select_characters(x: str) -> str: import string valid_characters = string.digits + string.ascii_uppercase + "-=[]\\;',./`" ans = x.upper() ans_chars = set(ans) if not ans_chars.issubset(set(valid_characters)): raise ValueError(f'Invalid characters in visual_window_select_characters: {x} Only numbers (0-9) and alphabets (a-z,A-Z) are allowed. Ignoring.') if len(ans_chars) < len(x): raise ValueError(f'Invalid characters in visual_window_select_characters: {x} Contains identical numbers or alphabets, case insensitive. Ignoring.') return ans def tab_separator(x: str) -> str: for q in '\'"': if x.startswith(q) and x.endswith(q): x = x[1:-1] if not x: return '' break if not x.strip(): x = ('\xa0' * len(x)) if x else default_tab_separator return x def tab_bar_edge(x: str) -> int: return {'top': defines.TOP_EDGE, 'bottom': defines.BOTTOM_EDGE}.get(x.lower(), defines.BOTTOM_EDGE) def tab_font_style(x: str) -> tuple[bool, bool]: return { 'bold-italic': (True, True), 'bold': (True, False), 'italic': (False, True) }.get(x.lower().replace('_', '-'), (False, False)) def tab_bar_min_tabs(x: str) -> int: return max(1, positive_int(x)) def tab_fade(x: str) -> tuple[float, ...]: return tuple(map(unit_float, x.split())) def tab_activity_symbol(x: str) -> str: if x == 'none': return '' return tab_title_template(x) def bell_on_tab(x: str) -> str: xl = x.lower() if xl in ('yes', 'y', 'true'): return '🔔 ' if xl in ('no', 'n', 'false', 'none'): return '' return tab_title_template(x) def tab_title_template(x: str) -> str: if x: for q in '\'"': if x.startswith(q) and x.endswith(q): x = x[1:-1] break return x def active_tab_title_template(x: str) -> str | None: x = tab_title_template(x) return None if x == 'none' else x def text_fg_override_threshold(x: str) -> tuple[float, Literal['%', 'ratio']]: val, unit = number_with_unit(x, '%', 'ratio') return val, cast(Literal['%', 'ratio'], unit) ClearOn = Literal['next', 'focus'] default_clear_on: tuple[ClearOn, ...] = 'focus', 'next' all_clear_on = get_args(ClearOn) class NotifyOnCmdFinish(NamedTuple): when: str = 'never' duration: float = 5.0 action: str = 'notify' cmdline: tuple[str, ...] = () clear_on: tuple[ClearOn, ...] = default_clear_on def notify_on_cmd_finish(x: str) -> NotifyOnCmdFinish: parts = x.split(maxsplit=3) if parts[0] not in ('never', 'unfocused', 'invisible', 'always'): raise ValueError(f'Unknown notify_on_cmd_finish value: {parts[0]}') when = parts[0] duration = 5.0 if len(parts) > 1: duration = float(parts[1]) action = 'notify' cmdline: tuple[str, ...] = () clear_on = default_clear_on if len(parts) > 2: if parts[2] not in ('notify', 'bell', 'command'): raise ValueError(f'Unknown notify_on_cmd_finish action: {parts[2]}') action = parts[2] if action == 'notify': if len(parts) > 3: con: list[ClearOn] = [] for x in parts[3].split(): if x not in all_clear_on: raise ValueError( f'notify_on_cmd_finish: notify clear_on value "{x}" is invalid. Valid values are: {", ".join(all_clear_on)}') con.append(cast(ClearOn, x)) clear_on = tuple(con) elif action == 'command': if len(parts) > 3: cmdline = tuple(to_cmdline(parts[3])) else: raise ValueError('notify_on_cmd_finish `command` action needs a command line') return NotifyOnCmdFinish(when, duration, action, cmdline, clear_on) def config_or_absolute_path(x: str, env: dict[str, str] | None = None) -> str | None: if not x or x.lower() == 'none': return None return resolve_abs_or_config_path(x, env) def filter_notification(val: str, current_val: dict[str, str]) -> Iterable[tuple[str, str]]: yield val, '' def remote_control_password(val: str, current_val: dict[str, str]) -> Iterable[tuple[str, Sequence[str]]]: val = val.strip() if val: parts = to_cmdline(val, expand=False) if parts[0].startswith('-'): # this is done so in the future we can add --options to the cmd # line of remote_control_password raise ValueError('Passwords are not allowed to start with hyphens, ignoring this password') if len(parts) == 1: yield parts[0], () else: yield parts[0], tuple(parts[1:]) def clipboard_control(x: str) -> tuple[str, ...]: return tuple(x.lower().split()) def allow_hyperlinks(x: str) -> int: if x == 'ask': return 0b11 return 1 if to_bool(x) else 0 def titlebar_color(x: str) -> int: x = x.strip('"') if x == 'system': return 0 if x == 'background': return 1 try: return (color_as_int(to_color(x)) << 8) | 2 except ValueError: log_error(f'Ignoring invalid title bar color: {x}') return 0 def macos_titlebar_color(x: str) -> int: x = x.strip('"') if x == 'light': return -1 if x == 'dark': return -2 return titlebar_color(x) def macos_option_as_alt(x: str) -> int: x = x.lower() if x == 'both': return 0b11 if x == 'left': return 0b10 if x == 'right': return 0b01 if to_bool(x): return 0b11 return 0 class TabBarMarginHeight(NamedTuple): outer: float = 0 inner: float = 0 def __bool__(self) -> bool: return (self.outer + self.inner) > 0 def tab_bar_margin_height(x: str) -> TabBarMarginHeight: parts = x.split(maxsplit=1) if len(parts) != 2: log_error(f'Invalid tab_bar_margin_height: {x}, ignoring') return TabBarMarginHeight() ans = map(positive_float, parts) return TabBarMarginHeight(next(ans), next(ans)) def clone_source_strategies(x: str) -> frozenset[str]: return frozenset({'venv', 'conda', 'path', 'env_var'} & set(x.lower().split(','))) def clear_all_mouse_actions(val: str, dict_with_parse_results: dict[str, Any] | None = None) -> bool: ans = to_bool(val) if ans and dict_with_parse_results is not None: dict_with_parse_results['mouse_map'] = [None] return ans def clear_all_shortcuts(val: str, dict_with_parse_results: dict[str, Any] | None = None) -> bool: ans = to_bool(val) if ans and dict_with_parse_results is not None: dict_with_parse_results['map'] = [None] return ans def font_features(val: str) -> Iterable[tuple[str, tuple[defines.ParsedFontFeature, ...]]]: if val == 'none': return parts = val.split() if len(parts) < 2: log_error(f"Ignoring invalid font_features {val}") return if parts[0]: features = [] for feat in parts[1:]: try: features.append(defines.ParsedFontFeature(feat)) except ValueError: log_error(f'Ignoring invalid font feature: {feat}') yield parts[0], tuple(features) def modify_font(val: str) -> Iterable[tuple[str, FontModification]]: parts = val.split() pos, plen = 0, len(parts) if plen < 2: log_error(f"Ignoring invalid modify_font: {val}") return mtype: ModificationType | None = getattr(ModificationType, parts[pos], None) if mtype is None: log_error(f"Ignoring invalid modify_font with unknown modification type: {parts[pos]}") return pos += 1 font_name = '' if mtype is ModificationType.size: font_name = parts[pos] pos += 1 if plen - pos < 1: log_error(f"Ignoring invalid modify_font: {val}") return sz = parts[pos] pos += 1 munit = ModificationUnit.pt if sz.endswith('%'): munit = ModificationUnit.percent sz = sz[:-1] elif sz.endswith('px'): munit = ModificationUnit.pixel sz = sz[:-2] try: mvalue = float(sz) except Exception: log_error(f'Ignoring modify_font with invalid size: {sz}') return key = mtype.name if font_name: key += f':{font_name}' yield key, FontModification(mtype, ModificationValue(mvalue, munit), font_name) def env(val: str, current_val: dict[str, str]) -> Iterable[tuple[str, str]]: val = val.strip() if val: if '=' in val: key, v = val.split('=', 1) key, v = key.strip(), v.strip() if key: if v: v = expandvars(v, current_val) yield key, v else: yield val, DELETE_ENV_VAR def store_multiple(val: str, current_val: Container[str]) -> Iterable[tuple[str, str]]: val = val.strip() if val not in current_val: yield val, val def menu_map(val: str, current_val: Container[str]) -> Iterable[tuple[tuple[str, ...], str]]: parts = val.split(maxsplit=1) if len(parts) != 2: raise ValueError(f'Ignoring invalid menu action: {val}') if parts[0] != 'global': raise ValueError(f'Unknown menu type: {parts[0]}. Known types: global') start = 0 if parts[1].startswith('"'): start = 1 idx = parts[1].find('"', 1) if idx == -1: raise ValueError(f'The menu entry name in {val} must end with a double quote') else: idx = parts[1].find(' ') if idx == -1: raise ValueError(f'The menu entry {val} must have an action') location = ('global',) + tuple(parts[1][start:idx].split('::')) yield location, parts[1][idx+1:].lstrip() allowed_shell_integration_values = frozenset({'enabled', 'disabled', 'no-rc', 'no-cursor', 'no-title', 'no-prompt-mark', 'no-complete', 'no-cwd', 'no-sudo'}) def shell_integration(x: str) -> frozenset[str]: q = frozenset(x.lower().split()) if not q.issubset(allowed_shell_integration_values): log_error(f'Invalid shell integration options: {q - allowed_shell_integration_values}, ignoring') return q & allowed_shell_integration_values or frozenset({'invalid'}) return q def confirm_close(x: str) -> tuple[int, bool]: parts = x.split(maxsplit=1) num = int(parts[0]) allow_background = len(parts) > 1 and parts[1] == 'count-background' return num, allow_background def underline_exclusion(x: str) -> tuple[float, Literal['', 'px', 'pt']]: try: return float(x), '' except Exception: unit: Literal['pt', 'px'] = x[-2:] # type: ignore if unit not in ('px', 'pt'): raise ValueError(f'Invalid underline_exclusion with unrecognized unit: {x}') try: val = float(x[:-2]) except Exception: raise ValueError(f'Invalid underline_exclusion with non numberic value: {x}') return val, unit def paste_actions(x: str) -> frozenset[str]: s = frozenset({'quote-urls-at-prompt', 'confirm', 'filter', 'confirm-if-large', 'replace-dangerous-control-codes', 'replace-newline', 'no-op'}) q = frozenset(x.lower().split(',')) if not q.issubset(s): raise ValueError(f'Invalid paste actions: {q - s}, ignoring') return q def action_alias(val: str) -> Iterable[tuple[str, str]]: parts = val.split(maxsplit=1) if len(parts) > 1: alias_name, rest = parts yield alias_name, rest kitten_alias = action_alias def symbol_map_parser(val: str, min_size: int = 2) -> Iterable[tuple[tuple[int, int], str]]: parts = val.split() if len(parts) < min_size: raise ValueError('must have codepoints AND font name') family = ' '.join(parts[1:]) def to_chr(x: str) -> int: if not x.startswith('U+'): raise ValueError(f'{x} is not a unicode codepoint of the form U+number') return int(x[2:], 16) for x in parts[0].split(','): a_, b_ = x.replace('–', '-').partition('-')[::2] b_ = b_ or a_ a, b = map(to_chr, (a_, b_)) if b < a or max(a, b) > sys.maxunicode or min(a, b) < 1: raise ValueError(f'Invalid range: {a:x} - {b:x}') yield (a, b), family def symbol_map(val: str) -> Iterable[tuple[tuple[int, int], str]]: yield from symbol_map_parser(val) def narrow_symbols(val: str) -> Iterable[tuple[tuple[int, int], int]]: for x, y in symbol_map_parser(val, min_size=1): yield x, int(y or 1) def parse_key_action(action: str, action_type: MapType = MapType.MAP) -> KeyAction: parts = action.strip().split(maxsplit=1) func = parts[0] if len(parts) == 1: return KeyAction(func, ()) rest = parts[1] parser = func_with_args.get(func) if parser is None: raise KeyError(f'Unknown action: {func}') func, args = parser(func, rest) return KeyAction(func, tuple(args)) class ActionAlias(NamedTuple): name: str value: str replace_second_arg: bool = False class AliasMap: def __init__(self) -> None: self.aliases: dict[str, list[ActionAlias]] = {} def append(self, name: str, aa: ActionAlias) -> None: self.aliases.setdefault(name, []).append(aa) def update(self, aa: 'AliasMap') -> None: self.aliases.update(aa.aliases) @lru_cache(maxsize=256) def resolve_aliases(self, definition: str, map_type: MapType = MapType.MAP) -> tuple[KeyAction, ...]: return tuple(resolve_aliases_and_parse_actions(definition, self.aliases, map_type)) def build_action_aliases(raw: dict[str, str], first_arg_replacement: str = '') -> AliasMap: ans = AliasMap() if first_arg_replacement: for alias_name, rest in raw.items(): ans.append(first_arg_replacement, ActionAlias(alias_name, rest, True)) else: for alias_name, rest in raw.items(): ans.append(alias_name, ActionAlias(alias_name, rest)) return ans def resolve_aliases_and_parse_actions( defn: str, aliases: dict[str, list[ActionAlias]], map_type: MapType ) -> Iterator[KeyAction]: parts = defn.split(maxsplit=1) if len(parts) == 1: possible_alias = defn rest = '' else: possible_alias = parts[0] rest = parts[1] for alias in aliases.get(possible_alias, ()): if alias.replace_second_arg: # kitten_alias if not rest: continue parts = rest.split(maxsplit=1) if parts[0] != alias.name: continue new_defn = f'{possible_alias} {alias.value}{f" {parts[1]}" if len(parts) > 1 else ""}' new_aliases = aliases.copy() new_aliases[possible_alias] = [a for a in aliases[possible_alias] if a is not alias] yield from resolve_aliases_and_parse_actions(new_defn, new_aliases, map_type) return else: # action_alias new_defn = f'{alias.value} {rest}' if rest else alias.value new_aliases = aliases.copy() new_aliases.pop(possible_alias) yield from resolve_aliases_and_parse_actions(new_defn, new_aliases, map_type) return if possible_alias == 'combine': sep, rest = rest.split(maxsplit=1) parts = re.split(fr'\s*{re.escape(sep)}\s*', rest) for x in parts: if x: yield from resolve_aliases_and_parse_actions(x, aliases, map_type) else: yield parse_key_action(defn, map_type) class BaseDefinition: no_op_actions = frozenset(('noop', 'no-op', 'no_op')) map_type: MapType = MapType.MAP definition_location: CurrentlyParsing def __init__(self, definition: str = '') -> None: if definition in BaseDefinition.no_op_actions: definition = '' self.definition = definition self.definition_location = currently_parsing.__copy__() def pretty_repr(self, *fields: str) -> str: kwds = [] defaults = self.__class__() for f in fields: val = getattr(self, f) if val != getattr(defaults, f): kwds.append(f'{f}={val!r}') if self.definition: kwds.append(f'definition={self.definition!r}') return f'{self.__class__.__name__}({", ".join(kwds)})' def resolve_key_mods(kitty_mod: int, mods: int) -> int: return SingleKey(mods=mods).resolve_kitty_mod(kitty_mod).mods class MouseMapping(BaseDefinition): map_type: MapType = MapType.MOUSE_MAP def __init__( self, button: int = 0, mods: int = 0, repeat_count: int = 1, grabbed: bool = False, definition: str = '' ): super().__init__(definition) self.button = button self.mods = mods self.repeat_count = repeat_count self.grabbed = grabbed def __repr__(self) -> str: return self.pretty_repr('button', 'mods', 'repeat_count', 'grabbed') def resolve_and_copy(self, kitty_mod: int) -> 'MouseMapping': ans = MouseMapping( self.button, resolve_key_mods(kitty_mod, self.mods), self.repeat_count, self.grabbed, self.definition) ans.definition_location = self.definition_location return ans @property def trigger(self) -> MouseEvent: return MouseEvent(self.button, self.mods, self.repeat_count, self.grabbed) T = TypeVar('T') class LiteralField(Generic[T]): def __init__(self, vals: tuple[T, ...]): self._vals = vals def __set_name__(self, owner: object, name: str) -> None: self._name = "_" + name def __get__(self, obj: object, type: type | None = None) -> T: if obj is None: return self._vals[0] return getattr(obj, self._name, self._vals[0]) def __set__(self, obj: object, value: str) -> None: if value not in self._vals: raise KeyError(f'Invalid value for {self._name[1:]}: {value!r}') object.__setattr__(obj, self._name, value) OnUnknown = Literal['beep', 'end', 'ignore', 'passthrough'] OnAction = Literal['keep', 'end'] @dataclass(init=False, frozen=True) class KeyMapOptions: when_focus_on: str = '' new_mode: str = '' mode: str = '' on_unknown: LiteralField[OnUnknown] = LiteralField[OnUnknown](get_args(OnUnknown)) on_action: LiteralField[OnAction] = LiteralField[OnAction](get_args(OnAction)) default_key_map_options = KeyMapOptions() allowed_key_map_options = frozenset(f.name for f in fields(KeyMapOptions)) class KeyDefinition(BaseDefinition): def __init__( self, is_sequence: bool = False, trigger: SingleKey = SingleKey(), rest: tuple[SingleKey, ...] = (), definition: str = '', options: KeyMapOptions = default_key_map_options ): super().__init__(definition) self.is_sequence = is_sequence self.trigger = trigger self.rest = rest self.options = options @property def is_suitable_for_global_shortcut(self) -> bool: return not self.options.when_focus_on and not self.options.mode and not self.options.new_mode and not self.is_sequence @property def full_key_sequence_to_trigger(self) -> tuple[SingleKey, ...]: return (self.trigger,) + self.rest @property def unique_identity_within_keymap(self) -> tuple[tuple[SingleKey, ...], str]: return self.full_key_sequence_to_trigger, self.options.when_focus_on def __repr__(self) -> str: return self.pretty_repr('is_sequence', 'trigger', 'rest', 'options') def human_repr(self) -> str: ans = self.definition or 'no-op' if self.options.when_focus_on: ans = f'[--when-focus-on={self.options.when_focus_on}]{ans}' return ans def shift_sequence_and_copy(self) -> 'KeyDefinition': return KeyDefinition(self.is_sequence, self.trigger, self.rest[1:], self.definition, self.options) def resolve_and_copy(self, kitty_mod: int) -> 'KeyDefinition': def r(k: SingleKey) -> SingleKey: return k.resolve_kitty_mod(kitty_mod) ans = KeyDefinition( self.is_sequence, r(self.trigger), tuple(map(r, self.rest)), self.definition, self.options ) ans.definition_location = self.definition_location return ans class KeyboardMode: on_unknown: OnUnknown = get_args(OnUnknown)[0] on_action : OnAction = get_args(OnAction)[0] sequence_keys: list[defines.KeyEvent] | None = None def __init__(self, name: str = '') -> None: self.name = name self.keymap: KeyMap = defaultdict(list) KeyboardModeMap = dict[str, KeyboardMode] def parse_options_for_map(val: str) -> tuple[KeyMapOptions, str]: expecting_arg = '' ans = KeyMapOptions() s = Shlex(val) while (tok := s.next_word())[0] > -1: x = tok[1] if expecting_arg: object.__setattr__(ans, expecting_arg, x) expecting_arg = '' elif x.startswith('--'): expecting_arg = x[2:] k, sep, v = expecting_arg.partition('=') k = k.replace('-', '_') expecting_arg = k if expecting_arg not in allowed_key_map_options: raise KeyError(f'The map option {x} is unknown. Allowed options: {", ".join(allowed_key_map_options)}') if sep == '=': object.__setattr__(ans, k, v) expecting_arg = '' else: return ans, val[tok[0]:] return ans, '' def parse_map(val: str) -> Iterable[KeyDefinition]: parts = val.split(maxsplit=1) options = default_key_map_options if len(parts) == 2: sc, action = parts if sc.startswith('--'): options, leftover = parse_options_for_map(val) parts = leftover.split(maxsplit=1) if len(parts) == 1: sc, action = parts[0], '' else: sc = parts[0] action = ' '.join(parts[1:]) else: sc, action = val, '' sc, action = sc.strip().strip(sequence_sep), action.strip() if not sc: return is_sequence = sequence_sep in sc if is_sequence: trigger: SingleKey | None = None restl: list[SingleKey] = [] for part in sc.split(sequence_sep): try: mods, is_native, key = parse_shortcut(part) except InvalidMods: return if key == 0: if mods is not None: log_error(f'Shortcut: {sc} has unknown key, ignoring') return if trigger is None: trigger = SingleKey(mods, is_native, key) else: restl.append(SingleKey(mods, is_native, key)) rest = tuple(restl) else: try: mods, is_native, key = parse_shortcut(sc) except InvalidMods: return if key == 0: if mods is not None: log_error(f'Shortcut: {sc} has unknown key, ignoring') return if is_sequence: if trigger is not None: yield KeyDefinition(True, trigger, rest, definition=action, options=options) else: assert key is not None yield KeyDefinition(False, SingleKey(mods, is_native, key), definition=action, options=options) def parse_mouse_map(val: str) -> Iterable[MouseMapping]: parts = val.split(maxsplit=3) if len(parts) == 4: xbutton, event, modes, action = parts elif len(parts) > 2: xbutton, event, modes = parts action = '' else: log_error(f'Ignoring invalid mouse action: {val}') return kparts = xbutton.split('+') if len(kparts) > 1: mparts, obutton = kparts[:-1], kparts[-1].lower() mods = parse_mods(mparts, obutton) if mods is None: return else: obutton = parts[0].lower() mods = 0 try: b = mouse_button_map.get(obutton, obutton)[1:] button = getattr(defines, f'GLFW_MOUSE_BUTTON_{b}') except Exception: log_error(f'Mouse button: {xbutton} not recognized, ignoring') return try: count = mouse_trigger_count_map[event.lower()] except KeyError: log_error(f'Mouse event type: {event} not recognized, ignoring') return specified_modes = frozenset(modes.lower().split(',')) if specified_modes - {'grabbed', 'ungrabbed'}: log_error(f'Mouse modes: {modes} not recognized, ignoring') return for mode in sorted(specified_modes): yield MouseMapping(button, mods, count, mode == 'grabbed', definition=action) def parse_font_spec(spec: str) -> FontSpec: return FontSpec.from_setting(spec) JumpTypes = Literal['start', 'end', 'none', 'both'] class EasingFunction(NamedTuple): type: Literal['steps', 'linear', 'cubic-bezier', ''] = '' num_steps: int = 0 jump_type: JumpTypes = 'end' linear_x: tuple[float, ...] = () linear_y: tuple[float, ...] = () cubic_bezier_points: tuple[float, ...] = () def __repr__(self) -> str: fields = ', '.join(f'{f}={getattr(self, f)!r}' for f in self._fields if getattr(self, f) != self._field_defaults[f]) return f'kitty.options.utils.EasingFunction({fields})' def __bool__(self) -> bool: return bool(self.type) @classmethod def cubic_bezier(cls, params: str) -> 'EasingFunction': parts = params.replace(',', ' ').split() if len(parts) != 4: raise ValueError('cubic-bezier easing function must have four points') return cls(type='cubic-bezier', cubic_bezier_points=( unit_float(parts[0]), float(parts[1]), unit_float(parts[2]), float(parts[3]))) @classmethod def linear(cls, params: str) -> 'EasingFunction': parts = params.split(',') if len(parts) < 2: raise ValueError('Must specify at least two points for the linear easing function') xaxis: list[float] = [] yaxis: list[float] = [] def balance(end: float) -> None: extra = len(yaxis) - len(xaxis) if extra <= 0: return start = xaxis[-1] if xaxis else 0. delta = (end - start) / max(1, extra - 1) if delta <= 0.: raise ValueError(f'Linear easing curve must have strictly increasing points: {params} does not') if xaxis: for i in range(extra): xaxis.append(start + (i+1) * delta) else: for i in range(extra): xaxis.append(i * delta) def add_point(y: float, x: float | None = None) -> None: if x is None: yaxis.append(y) else: x = unit_float(x) balance(x) xaxis.append(x) yaxis.append(y) for r in parts: points = r.strip().split() y = unit_float(points[0]) if len(points) == 1: add_point(y) elif len(points) == 2: add_point(y, percent(points[1])) elif len(points) == 3: add_point(y, percent(points[1])) add_point(y, percent(points[2])) else: raise ValueError(f'{r} has too many points for a linear easing curve parameter') balance(1) return cls(type='linear', linear_x=tuple(xaxis), linear_y=tuple(yaxis)) @classmethod def steps(cls, params: str) -> 'EasingFunction': parts = params.replace(',', ' ').split() jump_type: JumpTypes = 'end' if len(parts) == 2: n = int(parts[0]) jt = parts[1] mapping: dict[str, JumpTypes] = { 'jump-start': 'start', 'start': 'start', 'end': 'end', 'jump-end': 'end', 'jump-none': 'none', 'jump-both': 'both' } try: jump_type = mapping[jt.lower()] except KeyError: raise KeyError(f'{jt} is not a valid jump type for a linear easing function') if jump_type == 'none': n = max(2, n) else: n = max(1, n) else: n = max(1, int(parts[0])) return cls(type='steps', jump_type=jump_type, num_steps=n) def parse_animation(spec: str, interval: float = -1.) -> tuple[float, EasingFunction, EasingFunction]: with suppress(Exception): interval = float(spec) return interval, EasingFunction(), EasingFunction() m = [EasingFunction(), EasingFunction()] def parse_func(func_name: str, params: str) -> None: idx = 1 if m[0] else 0 if m[idx]: raise ValueError(f'{spec} specified more than two easing functions') if func_name == 'cubic-bezier': m[idx] = EasingFunction.cubic_bezier(params) elif func_name == 'linear': m[idx] = EasingFunction.linear(params) elif func_name == 'steps': m[idx] = EasingFunction.steps(params) else: raise KeyError(f'{func_name} is not a valid easing function') for match in re.finditer(r'([-+.0-9a-zA-Z]+)(?:\(([^)]*)\)){0,1}', spec): func_name, params = match.group(1, 2) if params: parse_func(func_name, params) continue with suppress(Exception): interval = float(func_name) continue if func_name == 'ease-in-out': parse_func('cubic-bezier', '0.42, 0, 0.58, 1') elif func_name == 'linear': parse_func('cubic-bezier', '0, 0, 1, 1') elif func_name == 'ease': parse_func('cubic-bezier', '0.25, 0.1, 0.25, 1') elif func_name == 'ease-out': parse_func('cubic-bezier', '0, 0, 0.58, 1') elif func_name == 'ease-in': parse_func('cubic-bezier', '0.42, 0, 1, 1') elif func_name == 'step-start': parse_func('steps', '1, start') elif func_name == 'step-end': parse_func('steps', '1, end') else: raise KeyError(f'{func_name} is not a valid easing function') return interval, m[0], m[1] def cursor_blink_interval(spec: str) -> tuple[float, EasingFunction, EasingFunction]: return parse_animation(spec) def visual_bell_duration(spec: str) -> tuple[float, EasingFunction, EasingFunction]: return parse_animation(spec, interval=0.) pointer_shape_names = ( # start pointer shape names (auto generated by gen-key-constants.py do not edit) 'arrow', 'beam', 'text', 'pointer', 'hand', 'help', 'wait', 'progress', 'crosshair', 'cell', 'vertical-text', 'move', 'e-resize', 'ne-resize', 'nw-resize', 'n-resize', 'se-resize', 'sw-resize', 's-resize', 'w-resize', 'ew-resize', 'ns-resize', 'nesw-resize', 'nwse-resize', 'zoom-in', 'zoom-out', 'alias', 'copy', 'not-allowed', 'no-drop', 'grab', 'grabbing', # end pointer shape names ) def pointer_shape_when_dragging(spec: str) -> tuple[str, str]: parts = spec.split(maxsplit=1) first = parts[0] if first not in pointer_shape_names: raise ValueError(f'{first} is not a valid pointer shape name') second = parts[1] if len(parts) > 1 else first if second not in pointer_shape_names: raise ValueError(f'{second} is not a valid pointer shape name') return first, second def transparent_background_colors(spec: str) -> tuple[tuple[Color, float], ...]: if not spec: return () ans: list[tuple[Color, float]] = [] seen: dict[Color, int] = {} for part in spec.split(): col, sep, alpha = part.partition('@') c = to_color(col) o = max(-1, min(float(alpha) if alpha else -1, 1)) if (idx := seen.get(c)) is not None: ans[idx] = c, o continue seen[c] = len(ans) ans.append((c, o)) return tuple(ans[:7]) def deprecated_hide_window_decorations_aliases(key: str, val: str, ans: dict[str, Any]) -> None: if not hasattr(deprecated_hide_window_decorations_aliases, key): setattr(deprecated_hide_window_decorations_aliases, key, True) log_error(f'The option {key} is deprecated. Use hide_window_decorations instead.') if to_bool(val): if is_macos and key == 'macos_hide_titlebar' or (not is_macos and key == 'x11_hide_window_decorations'): ans['hide_window_decorations'] = True def deprecated_macos_show_window_title_in_menubar_alias(key: str, val: str, ans: dict[str, Any]) -> None: if not hasattr(deprecated_macos_show_window_title_in_menubar_alias, key): setattr(deprecated_macos_show_window_title_in_menubar_alias, 'key', True) log_error(f'The option {key} is deprecated. Use macos_show_window_title_in menubar instead.') macos_show_window_title_in = ans.get('macos_show_window_title_in', 'all') if to_bool(val): if macos_show_window_title_in == 'none': macos_show_window_title_in = 'menubar' elif macos_show_window_title_in == 'window': macos_show_window_title_in = 'all' else: if macos_show_window_title_in == 'all': macos_show_window_title_in = 'window' elif macos_show_window_title_in == 'menubar': macos_show_window_title_in = 'none' ans['macos_show_window_title_in'] = macos_show_window_title_in def deprecated_send_text(key: str, val: str, ans: dict[str, Any]) -> None: parts = val.split(' ') def abort(msg: str) -> None: log_error(f'Send text: {val} is invalid ({msg}), ignoring') if len(parts) < 3: return abort('Incomplete') mode, sc = parts[:2] text = ' '.join(parts[2:]) key_str = f'{sc} send_text {mode} {text}' for k in parse_map(key_str): ans['map'].append(k) def deprecated_adjust_line_height(key: str, x: str, opts_dict: dict[str, Any]) -> None: fm = {'adjust_line_height': 'cell_height', 'adjust_baseline': 'baseline', 'adjust_column_width': 'cell_width'}[key] mtype = getattr(ModificationType, fm) if x.endswith('%'): ans = float(x[:-1].strip()) if ans < 0: log_error(f'Percentage adjustments of {key} must be positive numbers') return opts_dict['modify_font'][fm] = FontModification(mtype, ModificationValue(ans, ModificationUnit.percent)) else: opts_dict['modify_font'][fm] = FontModification(mtype, ModificationValue(int(x), ModificationUnit.pixel)) kitty-0.41.1/kitty/os_window_size.py0000664000175000017510000000654014773370543017074 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Callable from typing import Any, NamedTuple from .constants import is_macos, is_wayland from .fast_data_types import get_options from .options.types import Options from .types import FloatEdges from .typing import EdgeLiteral from .utils import log_error class WindowSize(NamedTuple): size: int unit: str class WindowSizes(NamedTuple): width: WindowSize height: WindowSize class WindowSizeData(NamedTuple): initial_window_sizes: WindowSizes remember_window_size: bool single_window_margin_width: FloatEdges window_margin_width: FloatEdges single_window_padding_width: FloatEdges window_padding_width: FloatEdges def sanitize_window_size(x: Any) -> int: ans = int(x) return max(20, min(ans, 50000)) def edge_spacing(which: EdgeLiteral, opts: WindowSizeData | Options | None = None) -> float: if opts is None: opts = get_options() margin: float = getattr(opts.single_window_margin_width, which) if margin < 0: margin = getattr(opts.window_margin_width, which) padding: float = getattr(opts.single_window_padding_width, which) if padding < 0: padding = getattr(opts.window_padding_width, which) return float(padding + margin) def initial_window_size_func(opts: WindowSizeData, cached_values: dict[str, Any]) -> Callable[[int, int, float, float, float, float], tuple[int, int]]: if 'window-size' in cached_values and opts.remember_window_size: ws = cached_values['window-size'] try: w, h = map(sanitize_window_size, ws) def initial_window_size(*a: Any) -> tuple[int, int]: return w, h return initial_window_size except Exception: log_error('Invalid cached window size, ignoring') w, w_unit = opts.initial_window_sizes.width h, h_unit = opts.initial_window_sizes.height def get_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y: float, xscale: float, yscale: float) -> tuple[int, int]: if not is_macos and not is_wayland(): # Not sure what the deal with scaling on X11 is xscale = yscale = 1 def effective_margin(which: EdgeLiteral) -> float: ans: float = getattr(opts.single_window_margin_width, which) if ans < 0: ans = getattr(opts.window_margin_width, which) return ans def effective_padding(which: EdgeLiteral) -> float: ans: float = getattr(opts.single_window_padding_width, which) if ans < 0: ans = getattr(opts.window_padding_width, which) return ans if w_unit == 'cells': spacing = effective_margin('left') + effective_margin('right') spacing += effective_padding('left') + effective_padding('right') width = cell_width * w / xscale + (dpi_x / 72) * spacing + 1 else: width = w if h_unit == 'cells': spacing = effective_margin('top') + effective_margin('bottom') spacing += effective_padding('top') + effective_padding('bottom') height = cell_height * h / yscale + (dpi_y / 72) * spacing + 1 else: height = h return int(width), int(height) return get_window_size kitty-0.41.1/kitty/parse-graphics-command.h0000664000175000017510000002774514773370543020167 0ustar nileshnilesh// This file is generated by apc_parsers.py do not edit! #pragma once #include "base64.h" static inline void parse_graphics_code(PS *self, uint8_t *parser_buf, const size_t parser_buf_pos) { unsigned int pos = 1; enum PARSER_STATES { KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE, PAYLOAD }; enum PARSER_STATES state = KEY, value_state = FLAG; GraphicsCommand g = {0}; unsigned int i, code; uint64_t lcode; int64_t accumulator; bool is_negative; (void)is_negative; size_t sz; enum KEYS { action = 'a', delete_action = 'd', transmission_type = 't', compressed = 'o', format = 'f', more = 'm', id = 'i', image_number = 'I', placement_id = 'p', quiet = 'q', width = 'w', height = 'h', x_offset = 'x', y_offset = 'y', data_height = 'v', data_width = 's', data_sz = 'S', data_offset = 'O', num_cells = 'c', num_lines = 'r', cell_x_offset = 'X', cell_y_offset = 'Y', z_index = 'z', cursor_movement = 'C', unicode_placement = 'U', parent_id = 'P', parent_placement_id = 'Q', offset_from_parent_x = 'H', offset_from_parent_y = 'V' }; enum KEYS key = 'a'; if (parser_buf[pos] == ';') state = AFTER_VALUE; while (pos < parser_buf_pos) { switch (state) { case KEY: key = parser_buf[pos++]; state = EQUAL; switch (key) { case action: value_state = FLAG; break; case delete_action: value_state = FLAG; break; case transmission_type: value_state = FLAG; break; case compressed: value_state = FLAG; break; case format: value_state = UINT; break; case more: value_state = UINT; break; case id: value_state = UINT; break; case image_number: value_state = UINT; break; case placement_id: value_state = UINT; break; case quiet: value_state = UINT; break; case width: value_state = UINT; break; case height: value_state = UINT; break; case x_offset: value_state = UINT; break; case y_offset: value_state = UINT; break; case data_height: value_state = UINT; break; case data_width: value_state = UINT; break; case data_sz: value_state = UINT; break; case data_offset: value_state = UINT; break; case num_cells: value_state = UINT; break; case num_lines: value_state = UINT; break; case cell_x_offset: value_state = UINT; break; case cell_y_offset: value_state = UINT; break; case z_index: value_state = INT; break; case cursor_movement: value_state = UINT; break; case unicode_placement: value_state = UINT; break; case parent_id: value_state = UINT; break; case parent_placement_id: value_state = UINT; break; case offset_from_parent_x: value_state = INT; break; case offset_from_parent_y: value_state = INT; break; default: REPORT_ERROR("Malformed GraphicsCommand control block, invalid key " "character: 0x%x", key); return; } break; case EQUAL: if (parser_buf[pos++] != '=') { REPORT_ERROR("Malformed GraphicsCommand control block, no = after key, " "found: 0x%x instead", parser_buf[pos - 1]); return; } state = value_state; break; case FLAG: switch (key) { case action: { g.action = parser_buf[pos++]; if (g.action != 'T' && g.action != 'a' && g.action != 'c' && g.action != 'd' && g.action != 'f' && g.action != 'p' && g.action != 'q' && g.action != 't') { REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " "value for action: 0x%x", g.action); return; }; } break; case delete_action: { g.delete_action = parser_buf[pos++]; if (g.delete_action != 'A' && g.delete_action != 'C' && g.delete_action != 'F' && g.delete_action != 'I' && g.delete_action != 'N' && g.delete_action != 'P' && g.delete_action != 'Q' && g.delete_action != 'R' && g.delete_action != 'X' && g.delete_action != 'Y' && g.delete_action != 'Z' && g.delete_action != 'a' && g.delete_action != 'c' && g.delete_action != 'f' && g.delete_action != 'i' && g.delete_action != 'n' && g.delete_action != 'p' && g.delete_action != 'q' && g.delete_action != 'r' && g.delete_action != 'x' && g.delete_action != 'y' && g.delete_action != 'z') { REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " "value for delete_action: 0x%x", g.delete_action); return; }; } break; case transmission_type: { g.transmission_type = parser_buf[pos++]; if (g.transmission_type != 'd' && g.transmission_type != 'f' && g.transmission_type != 's' && g.transmission_type != 't') { REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " "value for transmission_type: 0x%x", g.transmission_type); return; }; } break; case compressed: { g.compressed = parser_buf[pos++]; if (g.compressed != 'z') { REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag " "value for compressed: 0x%x", g.compressed); return; }; } break; default: break; } state = AFTER_VALUE; break; case INT: #define READ_UINT \ for (i = pos, accumulator = 0; i < MIN(parser_buf_pos, pos + 10); i++) { \ int64_t n = parser_buf[i] - '0'; \ if (n < 0 || n > 9) \ break; \ accumulator += n * digit_multipliers[i - pos]; \ } \ if (i == pos) { \ REPORT_ERROR("Malformed GraphicsCommand control block, expecting an " \ "integer value for key: %c", \ key & 0xFF); \ return; \ } \ lcode = accumulator / digit_multipliers[i - pos - 1]; \ pos = i; \ if (lcode > UINT32_MAX) { \ REPORT_ERROR( \ "Malformed GraphicsCommand control block, number is too large"); \ return; \ } \ code = lcode; is_negative = false; if (parser_buf[pos] == '-') { is_negative = true; pos++; } #define I(x) \ case x: \ g.x = is_negative ? 0 - (int32_t)code : (int32_t)code; \ break READ_UINT; switch (key) { I(z_index); I(offset_from_parent_x); I(offset_from_parent_y); default: break; } state = AFTER_VALUE; break; #undef I case UINT: READ_UINT; #define U(x) \ case x: \ g.x = code; \ break switch (key) { U(format); U(more); U(id); U(image_number); U(placement_id); U(quiet); U(width); U(height); U(x_offset); U(y_offset); U(data_height); U(data_width); U(data_sz); U(data_offset); U(num_cells); U(num_lines); U(cell_x_offset); U(cell_y_offset); U(cursor_movement); U(unicode_placement); U(parent_id); U(parent_placement_id); default: break; } state = AFTER_VALUE; break; #undef U #undef READ_UINT case AFTER_VALUE: switch (parser_buf[pos++]) { default: REPORT_ERROR("Malformed GraphicsCommand control block, expecting a , " "or semi-colon after a value, found: 0x%x", parser_buf[pos - 1]); return; case ',': state = KEY; break; case ';': state = PAYLOAD; break; } break; case PAYLOAD: { sz = parser_buf_pos - pos; g.payload_sz = MAX(BUF_EXTRA, sz); if (!base64_decode8(parser_buf + pos, sz, parser_buf, &g.payload_sz)) { g.payload_sz = MAX(BUF_EXTRA, sz); REPORT_ERROR("Failed to parse GraphicsCommand command payload with " "error: invalid base64 data in chunk of size: %zu " "with output buffer size: %zu", sz, g.payload_sz); return; } pos = parser_buf_pos; } break; } // end switch } // end while switch (state) { case EQUAL: REPORT_ERROR("Malformed GraphicsCommand control block, no = after key"); return; case INT: case UINT: REPORT_ERROR( "Malformed GraphicsCommand control block, expecting an integer value"); return; case FLAG: REPORT_ERROR( "Malformed GraphicsCommand control block, expecting a flag value"); return; default: break; } REPORT_VA_COMMAND( "K s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI " "sI sI sI sI si si si ss#}", self->window_id, "graphics_command", "action", g.action, "delete_action", g.delete_action, "transmission_type", g.transmission_type, "compressed", g.compressed, "format", (unsigned int)g.format, "more", (unsigned int)g.more, "id", (unsigned int)g.id, "image_number", (unsigned int)g.image_number, "placement_id", (unsigned int)g.placement_id, "quiet", (unsigned int)g.quiet, "width", (unsigned int)g.width, "height", (unsigned int)g.height, "x_offset", (unsigned int)g.x_offset, "y_offset", (unsigned int)g.y_offset, "data_height", (unsigned int)g.data_height, "data_width", (unsigned int)g.data_width, "data_sz", (unsigned int)g.data_sz, "data_offset", (unsigned int)g.data_offset, "num_cells", (unsigned int)g.num_cells, "num_lines", (unsigned int)g.num_lines, "cell_x_offset", (unsigned int)g.cell_x_offset, "cell_y_offset", (unsigned int)g.cell_y_offset, "cursor_movement", (unsigned int)g.cursor_movement, "unicode_placement", (unsigned int)g.unicode_placement, "parent_id", (unsigned int)g.parent_id, "parent_placement_id", (unsigned int)g.parent_placement_id, "z_index", (int)g.z_index, "offset_from_parent_x", (int)g.offset_from_parent_x, "offset_from_parent_y", (int)g.offset_from_parent_y, "", (char *)parser_buf, g.payload_sz); screen_handle_graphics_command(self->screen, &g, parser_buf); } kitty-0.41.1/kitty/parse-multicell-command.h0000664000175000017510000001402014773370543020337 0ustar nileshnilesh// This file is generated by apc_parsers.py do not edit! #pragma once #include "base64.h" static inline void parse_multicell_code(PS *self, uint8_t *parser_buf, const size_t parser_buf_pos) { unsigned int pos = 0; size_t payload_start = 0; enum PARSER_STATES { KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE, PAYLOAD }; enum PARSER_STATES state = KEY, value_state = FLAG; MultiCellCommand g = {0}; unsigned int i, code; uint64_t lcode; int64_t accumulator; bool is_negative; (void)is_negative; size_t sz; enum KEYS { width = 'w', scale = 's', subscale_n = 'n', subscale_d = 'd', vertical_align = 'v', horizontal_align = 'h' }; enum KEYS key = 'a'; if (parser_buf[pos] == ';') state = AFTER_VALUE; while (pos < parser_buf_pos) { switch (state) { case KEY: key = parser_buf[pos++]; state = EQUAL; switch (key) { case width: value_state = UINT; break; case scale: value_state = UINT; break; case subscale_n: value_state = UINT; break; case subscale_d: value_state = UINT; break; case vertical_align: value_state = UINT; break; case horizontal_align: value_state = UINT; break; default: REPORT_ERROR("Malformed MultiCellCommand control block, invalid key " "character: 0x%x", key); return; } break; case EQUAL: if (parser_buf[pos++] != '=') { REPORT_ERROR("Malformed MultiCellCommand control block, no = after " "key, found: 0x%x instead", parser_buf[pos - 1]); return; } state = value_state; break; case FLAG: switch (key) { default: break; } state = AFTER_VALUE; break; case INT: #define READ_UINT \ for (i = pos, accumulator = 0; i < MIN(parser_buf_pos, pos + 10); i++) { \ int64_t n = parser_buf[i] - '0'; \ if (n < 0 || n > 9) \ break; \ accumulator += n * digit_multipliers[i - pos]; \ } \ if (i == pos) { \ REPORT_ERROR("Malformed MultiCellCommand control block, expecting an " \ "integer value for key: %c", \ key & 0xFF); \ return; \ } \ lcode = accumulator / digit_multipliers[i - pos - 1]; \ pos = i; \ if (lcode > UINT32_MAX) { \ REPORT_ERROR( \ "Malformed MultiCellCommand control block, number is too large"); \ return; \ } \ code = lcode; is_negative = false; if (parser_buf[pos] == '-') { is_negative = true; pos++; } #define I(x) \ case x: \ g.x = is_negative ? 0 - (int32_t)code : (int32_t)code; \ break READ_UINT; switch (key) { ; default: break; } state = AFTER_VALUE; break; #undef I case UINT: READ_UINT; #define U(x) \ case x: \ g.x = code; \ break switch (key) { U(width); U(scale); U(subscale_n); U(subscale_d); U(vertical_align); U(horizontal_align); default: break; } state = AFTER_VALUE; break; #undef U #undef READ_UINT case AFTER_VALUE: switch (parser_buf[pos++]) { default: REPORT_ERROR("Malformed MultiCellCommand control block, expecting a : " "or semi-colon after a value, found: 0x%x", parser_buf[pos - 1]); return; case ':': state = KEY; break; case ';': state = PAYLOAD; break; } break; case PAYLOAD: { sz = parser_buf_pos - pos; payload_start = pos; g.payload_sz = sz; pos = parser_buf_pos; } break; } // end switch } // end while switch (state) { case EQUAL: REPORT_ERROR("Malformed MultiCellCommand control block, no = after key"); return; case INT: case UINT: REPORT_ERROR( "Malformed MultiCellCommand control block, expecting an integer value"); return; case FLAG: REPORT_ERROR( "Malformed MultiCellCommand control block, expecting a flag value"); return; default: break; } REPORT_VA_COMMAND( "K s { sI sI sI sI sI sI ss#}", self->window_id, "multicell_command", "width", (unsigned int)g.width, "scale", (unsigned int)g.scale, "subscale_n", (unsigned int)g.subscale_n, "subscale_d", (unsigned int)g.subscale_d, "vertical_align", (unsigned int)g.vertical_align, "horizontal_align", (unsigned int)g.horizontal_align, "", (char *)parser_buf + payload_start, g.payload_sz); screen_handle_multicell_command(self->screen, &g, parser_buf + payload_start); } kitty-0.41.1/kitty/png-reader.c0000664000175000017510000001430714773370543015650 0ustar nileshnilesh/* * png-reader.c * Copyright (C) 2018 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "data-types.h" #include "png-reader.h" #include "cleanup.h" #include "state.h" #include static cmsHPROFILE srgb_profile = NULL; struct fake_file { const uint8_t *buf; size_t sz, cur; }; static void read_png_from_buffer(png_structp png, png_bytep out, png_size_t length) { struct fake_file *f = png_get_io_ptr(png); if (f) { size_t amt = MIN(length, f->sz - f->cur); memcpy(out, f->buf + f->cur, amt); f->cur += amt; } } struct custom_error_handler { jmp_buf jb; png_read_data *d; }; static void read_png_error_handler(png_structp png_ptr, png_const_charp msg) { struct custom_error_handler *eh; eh = png_get_error_ptr(png_ptr); if (eh == NULL) fatal("read_png_error_handler: could not retrieve error handler"); if(eh->d->err_handler) eh->d->err_handler(eh->d, "EBADPNG", msg); longjmp(eh->jb, 1); } static void read_png_warn_handler(png_structp UNUSED png_ptr, png_const_charp msg) { if (global_state.debug_rendering) log_error("libpng WARNING: %s", msg); } #define ABRT(code, msg) { if(d->err_handler) d->err_handler(d, #code, msg); goto err; } void inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz) { struct fake_file f = {.buf = buf, .sz = bufsz}; png_structp png = NULL; png_infop info = NULL; struct custom_error_handler eh = {.d = d}; png = png_create_read_struct(PNG_LIBPNG_VER_STRING, &eh, read_png_error_handler, read_png_warn_handler); if (!png) ABRT(ENOMEM, "Failed to create PNG read structure"); info = png_create_info_struct(png); if (!info) ABRT(ENOMEM, "Failed to create PNG info structure"); if (setjmp(eh.jb)) goto err; png_set_read_fn(png, &f, read_png_from_buffer); png_read_info(png, info); png_byte color_type, bit_depth; d->width = png_get_image_width(png, info); d->height = png_get_image_height(png, info); color_type = png_get_color_type(png, info); bit_depth = png_get_bit_depth(png, info); double image_gamma; int intent; cmsHPROFILE input_profile = NULL; cmsHTRANSFORM colorspace_transform = NULL; if (png_get_sRGB(png, info, &intent)) { // do nothing since we output sRGB } else if (png_get_gAMA(png, info, &image_gamma)) { if (image_gamma != 0 && fabs(image_gamma - 1.0/2.2) > 0.0001) png_set_gamma(png, 2.2, image_gamma); } else { // Look for an embedded color profile png_charp name; int compression_type; png_bytep profdata; png_uint_32 proflen; if (png_get_iCCP(png, info, &name, &compression_type, &profdata, &proflen) & PNG_INFO_iCCP) { input_profile = cmsOpenProfileFromMem(profdata, proflen); if (input_profile) { if (!srgb_profile) { srgb_profile = cmsCreate_sRGBProfile(); if (!srgb_profile) ABRT(ENOMEM, "Out of memory allocating sRGB colorspace profile"); } colorspace_transform = cmsCreateTransform( input_profile, TYPE_RGBA_8, srgb_profile, TYPE_RGBA_8, INTENT_PERCEPTUAL, 0); } } } // Ensure we get RGBA data out of libpng if (bit_depth == 16) png_set_strip_16(png); if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); // PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth. if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); // These color_type don't have an alpha channel then fill it with 0xff. if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) png_set_filler(png, 0xFF, PNG_FILLER_AFTER); if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png); png_read_update_info(png, info); png_uint_32 rowbytes = png_get_rowbytes(png, info); d->sz = sizeof(png_byte) * rowbytes * d->height; d->decompressed = malloc(d->sz + 16); if (d->decompressed == NULL) ABRT(ENOMEM, "Out of memory allocating decompression buffer for PNG"); d->row_pointers = malloc(d->height * sizeof(png_bytep)); if (d->row_pointers == NULL) ABRT(ENOMEM, "Out of memory allocating row_pointers buffer for PNG"); for (size_t i = 0; i < (size_t)d->height; i++) d->row_pointers[i] = d->decompressed + i * rowbytes * sizeof(png_byte); png_read_image(png, d->row_pointers); if (colorspace_transform) { for (int i = 0; i < d->height; i++) { cmsDoTransform(colorspace_transform, d->row_pointers[i], d->row_pointers[i], d->width); } cmsDeleteTransform(colorspace_transform); } if (input_profile) cmsCloseProfile(input_profile); d->ok = true; err: if (png) png_destroy_read_struct(&png, info ? &info : NULL, NULL); return; } static void png_error_handler(png_read_data *d UNUSED, const char *code, const char *msg) { if (!PyErr_Occurred()) PyErr_Format(PyExc_ValueError, "[%s] %s", code, msg); } static PyObject* load_png_data(PyObject *self UNUSED, PyObject *args) { Py_ssize_t sz; const char *data; if (!PyArg_ParseTuple(args, "s#", &data, &sz)) return NULL; png_read_data d = {.err_handler=png_error_handler}; inflate_png_inner(&d, (const uint8_t*)data, sz); PyObject *ans = NULL; if (d.ok && !PyErr_Occurred()) { ans = Py_BuildValue("y#ii", d.decompressed, (int)d.sz, d.width, d.height); } else { if (!PyErr_Occurred()) PyErr_SetString(PyExc_ValueError, "Unknown error while reading PNG data"); } free(d.decompressed); free(d.row_pointers); return ans; } static PyMethodDef module_methods[] = { METHODB(load_png_data, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; static void unload(void) { if (srgb_profile) cmsCloseProfile(srgb_profile); srgb_profile = NULL; } bool init_png_reader(PyObject *module) { if (PyModule_AddFunctions(module, module_methods) != 0) return false; register_at_exit_cleanup_func(PNG_READER_CLEANUP_FUNC, unload); return true; } kitty-0.41.1/kitty/png-reader.h0000664000175000017510000000123514773370543015651 0ustar nileshnilesh/* * Copyright (C) 2018 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include #include #include typedef struct png_read_data png_read_data; typedef void(*png_error_handler_func)(png_read_data *d, const char*, const char*); typedef struct png_read_data { uint8_t *decompressed; bool ok; png_bytep *row_pointers; int width, height; size_t sz; png_error_handler_func err_handler; struct { char *buf; size_t used, capacity; } error; } png_read_data; void inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz); kitty-0.41.1/kitty/progress.py0000664000175000017510000000312614773370543015673 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2025, Kovid Goyal from enum import Enum from .fast_data_types import monotonic from .utils import log_error class ProgressState(Enum): unset = 0 set = 1 error = 2 indeterminate = 3 paused = 4 class Progress: state: ProgressState = ProgressState.unset percent: int = 0 last_update_at: float = 0. clear_timeout: float = 60.0 finished_clear_timeout: float = 5.0 def update(self, st: int, percent: int = -1) -> None: self.last_update_at = monotonic() if st == 0: self.state = ProgressState.unset self.percent = 0 elif st == 1: self.state = ProgressState.set self.percent = max(0, min(percent, 100)) elif st == 2: self.state = ProgressState.error self.percent = 0 elif st == 3: self.state = ProgressState.indeterminate self.percent = 0 elif st == 4: self.state = ProgressState.paused if percent > -1: self.percent = max(0, min(percent, 100)) else: log_error(f'Unknown OSC 9;4 state: {st}') def clear_progress(self) -> bool: time_since_last_update = monotonic() - self.last_update_at threshold = self.finished_clear_timeout if self.percent == 100 and self.state is ProgressState.set else self.clear_timeout if time_since_last_update >= threshold: self.state = ProgressState.unset self.percent = 0 return True return False kitty-0.41.1/kitty/rc/0000775000175000017510000000000014773370543014057 5ustar nileshnileshkitty-0.41.1/kitty/rc/__init__.py0000664000175000017510000000000014773370543016156 0ustar nileshnileshkitty-0.41.1/kitty/rc/action.py0000664000175000017510000000511014773370543015703 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import ( MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, RemoteControlErrorWithoutTraceback, ResponseType, Window, ) if TYPE_CHECKING: from kitty.cli_stub import ActionRCOptions as CLIOptions class Action(RemoteCommand): protocol_spec = __doc__ = ''' action+/str: The action to perform. Of the form: action [optional args...] match_window/str: Window to run the action on self/bool: Whether to use the window this command is run in as the active window ''' short_desc = 'Run the specified mappable action' desc = ( 'Run the specified mappable action. For a list of all available mappable actions, see :doc:`actions`.' ' Any arguments for ACTION should follow the action. Note that parsing of arguments is action dependent' ' so for best results specify all arguments as single string on the command line in the same format as you would' ' use for that action in kitty.conf.' ) options_spec = '''\ --self type=bool-set Run the action on the window this command is run in instead of the active window. --no-response type=bool-set default=false Don't wait for a response indicating the success of the action. Note that using this option means that you will not be notified of failures. ''' + '\n\n' + MATCH_WINDOW_OPTION args = RemoteCommand.Args( spec='ACTION [ARGS FOR ACTION...]', json_field='action', minimum_count=1, completion=RemoteCommand.CompletionSpec.from_string('type:special group:complete_actions') ) def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'action': ' '.join(args), 'self': opts.self, 'match_window': opts.match} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: w = self.windows_for_match_payload(boss, window, payload_get) if w: window = w[0] ac = payload_get('action') if not ac: raise RemoteControlErrorWithoutTraceback('Must specify an action') try: consumed = boss.combine(str(ac), window, raise_error=True) except (Exception, SystemExit) as e: raise RemoteControlErrorWithoutTraceback(str(e)) if not consumed: raise RemoteControlErrorWithoutTraceback(f'Unknown action: {ac}') return None action = Action() kitty-0.41.1/kitty/rc/base.py0000664000175000017510000004663514773370543015361 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Callable, Iterable, Iterator from contextlib import suppress from dataclasses import dataclass, field from io import BytesIO from typing import TYPE_CHECKING, Any, NoReturn, Optional, Union, cast from kitty.cli import CompletionSpec, get_defaults_from_seq, parse_args, parse_option_spec from kitty.cli_stub import RCOptions as R from kitty.constants import list_kitty_resources, running_in_kitty from kitty.types import AsyncResponse if TYPE_CHECKING: from kitty.boss import Boss as B from kitty.tabs import Tab as T from kitty.window import Window as W Window = W Boss = B Tab = T else: Boss = Window = Tab = object RCOptions = R class NoResponse: pass class RemoteControlError(Exception): pass class RemoteControlErrorWithoutTraceback(Exception): hide_traceback = True class MatchError(ValueError): hide_traceback = True def __init__(self, expression: str, target: str = 'windows'): ValueError.__init__(self, f'No matching {target} for expression: {expression}') class OpacityError(ValueError): hide_traceback = True class UnknownLayout(ValueError): hide_traceback = True class StreamError(ValueError): hide_traceback = True class PayloadGetter: def __init__(self, cmd: 'RemoteCommand', payload: dict[str, Any]): self.payload = payload self.cmd = cmd def __call__(self, key: str, opt_name: str | None = None, missing: Any = None) -> Any: ans = self.payload.get(key, payload_get) if ans is not payload_get: return ans return self.cmd.get_default(opt_name or key, missing=missing) no_response = NoResponse() payload_get = object() ResponseType = Union[bool, str, None, NoResponse, AsyncResponse] CmdReturnType = Union[dict[str, Any], list[Any], tuple[Any, ...], str, int, float, bool] CmdGenerator = Iterator[CmdReturnType] PayloadType = Optional[Union[CmdReturnType, CmdGenerator]] PayloadGetType = PayloadGetter ArgsType = list[str] ImageCompletion = CompletionSpec.from_string('type:file group:"Images"') ImageCompletion.extensions = 'png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'tiff' SUPPORTED_IMAGE_FORMATS = tuple(x.upper() for x in ImageCompletion.extensions if x != 'jpg') MATCH_WINDOW_OPTION = '''\ --match -m The window to match. Match specifications are of the form: :italic:`field:query`. Where :italic:`field` can be one of: :code:`id`, :code:`title`, :code:`pid`, :code:`cwd`, :code:`cmdline`, :code:`num`, :code:`env`, :code:`var`, :code:`state`, :code:`neighbor`, and :code:`recent`. :italic:`query` is the expression to match. Expressions can be either a number or a regular expression, and can be :ref:`combined using Boolean operators `. The special value :code:`all` matches all windows. For numeric fields: :code:`id`, :code:`pid`, :code:`num` and :code:`recent`, the expression is interpreted as a number, not a regular expression. Negative values for :code:`id` match from the highest id number down, in particular, -1 is the most recently created window. The field :code:`num` refers to the window position in the current tab, starting from zero and counting clockwise (this is the same as the order in which the windows are reported by the :ref:`kitten @ ls ` command). The window id of the current window is available as the :envvar:`KITTY_WINDOW_ID` environment variable. The field :code:`recent` refers to recently active windows in the currently active tab, with zero being the currently active window, one being the previously active window and so on. The field :code:`neighbor` refers to a neighbor of the active window in the specified direction, which can be: :code:`left`, :code:`right`, :code:`top` or :code:`bottom`. When using the :code:`env` field to match on environment variables, you can specify only the environment variable name or a name and value, for example, :code:`env:MY_ENV_VAR=2`. Similarly, the :code:`var` field matches on user variables set on the window. You can specify name or name and value as with the :code:`env` field. The field :code:`state` matches on the state of the window. Supported states are: :code:`active`, :code:`focused`, :code:`needs_attention`, :code:`parent_active`, :code:`parent_focused`, :code:`self`, :code:`overlay_parent`. Active windows are the windows that are active in their parent tab. There is only one focused window and it is the window to which keyboard events are delivered. If no window is focused, the last focused window is matched. The value :code:`self` matches the window in which the remote control command is run. The value :code:`overlay_parent` matches the window that is under the :code:`self` window, when the self window is an overlay. Note that you can use the :ref:`kitten @ ls ` command to get a list of windows. ''' MATCH_TAB_OPTION = '''\ --match -m The tab to match. Match specifications are of the form: :italic:`field:query`. Where :italic:`field` can be one of: :code:`id`, :code:`index`, :code:`title`, :code:`window_id`, :code:`window_title`, :code:`pid`, :code:`cwd`, :code:`cmdline` :code:`env`, :code:`var`, :code:`state` and :code:`recent`. :italic:`query` is the expression to match. Expressions can be either a number or a regular expression, and can be :ref:`combined using Boolean operators `. The special value :code:`all` matches all tabs. For numeric fields: :code:`id`, :code:`index`, :code:`window_id`, :code:`pid` and :code:`recent`, the expression is interpreted as a number, not a regular expression. Negative values for :code:`id`/:code:`window_id` match from the highest id number down, in particular, -1 is the most recently created tab/window. When using :code:`title` or :code:`id`, first a matching tab is looked for, and if not found a matching window is looked for, and the tab for that window is used. You can also use :code:`window_id` and :code:`window_title` to match the tab that contains the window with the specified id or title. The :code:`index` number is used to match the nth tab in the currently active OS window. The :code:`recent` number matches recently active tabs in the currently active OS window, with zero being the currently active tab, one the previously active tab and so on. When using the :code:`env` field to match on environment variables, you can specify only the environment variable name or a name and value, for example, :code:`env:MY_ENV_VAR=2`. Tabs containing any window with the specified environment variables are matched. Similarly, :code:`var` matches tabs containing any window with the specified user variable. The field :code:`state` matches on the state of the tab. Supported states are: :code:`active`, :code:`focused`, :code:`needs_attention`, :code:`parent_active` and :code:`parent_focused`. Active tabs are the tabs that are active in their parent OS window. There is only one focused tab and it is the tab to which keyboard events are delivered. If no tab is focused, the last focused tab is matched. Note that you can use the :ref:`kitten @ ls ` command to get a list of tabs. ''' class ParsingOfArgsFailed(ValueError): pass class AsyncResponder: def __init__(self, payload_get: PayloadGetType, window: Window | None) -> None: self.async_id: str = payload_get('async_id', missing='') self.peer_id: int = payload_get('peer_id', missing=0) self.window_id: int = getattr(window, 'id', 0) def send_data(self, data: Any) -> None: from kitty.remote_control import send_response_to_client send_response_to_client(data=data, peer_id=self.peer_id, window_id=self.window_id, async_id=self.async_id) def send_error(self, error: str) -> None: from kitty.remote_control import send_response_to_client send_response_to_client(error=error, peer_id=self.peer_id, window_id=self.window_id, async_id=self.async_id) @dataclass(frozen=True) class ArgsHandling: json_field: str = '' count: int | None = None spec: str = '' completion: CompletionSpec = field(default_factory=CompletionSpec) value_if_unspecified: tuple[str, ...] = () minimum_count: int = -1 first_rest: tuple[str, str] | None = None special_parse: str = '' args_choices: Callable[[], Iterable[str]] | None = None @property def args_count(self) -> int | None: if not self.spec: return 0 return self.count def as_go_completion_code(self, go_name: str) -> Iterator[str]: c = self.args_count if c is not None: yield f'{go_name}.StopCompletingAtArg = {c}' if self.completion: yield from self.completion.as_go_code(go_name + '.ArgCompleter', ' = ') def as_go_code(self, cmd_name: str, field_types: dict[str, str], handled_fields: set[str]) -> Iterator[str]: c = self.args_count if c == 0: yield f'if len(args) != 0 {{ return fmt.Errorf("%s", "Unknown extra argument(s) supplied to {cmd_name}") }}' return if c is not None: yield f'if len(args) != {c} {{ return fmt.Errorf("%s", "Must specify exactly {c} argument(s) for {cmd_name}") }}' if self.value_if_unspecified: yield 'if len(args) == 0 {' for x in self.value_if_unspecified: yield f'args = append(args, "{x}")' yield '}' if self.minimum_count > -1: if self.minimum_count == 1: yield f'if len(args) < {self.minimum_count} {{ return fmt.Errorf("%s", "Must specify at least one argument to {cmd_name}") }}' else: yield f'if len(args) < {self.minimum_count} {{ return fmt.Errorf("%s", "Must specify at least {self.minimum_count} arguments to {cmd_name}") }}' if self.args_choices: achoices = tuple(self.args_choices()) yield 'achoices := map[string]bool{' + ' '.join(f'"{x}":true,' for x in achoices) + '}' yield 'for _, a := range args {' yield 'if !achoices[a] { return fmt.Errorf("Not a valid choice: %s. Allowed values are: %s", a, "' + ', '.join(achoices) + '") }' yield '}' if self.json_field: jf = self.json_field dest = f'payload.{jf.capitalize()}' jt = field_types[jf] if self.first_rest: yield f'payload.{self.first_rest[0].capitalize()} = escaped_string(args[0])' yield f'payload.{self.first_rest[1].capitalize()} = escape_list_of_strings(args[1:])' handled_fields.add(self.first_rest[0]) handled_fields.add(self.first_rest[1]) return handled_fields.add(self.json_field) if self.special_parse: if self.special_parse.startswith('!'): yield f'io_data.multiple_payload_generator, err = {self.special_parse[1:]}' elif self.special_parse.startswith('+'): fields, sp = self.special_parse[1:].split(':', 1) handled_fields.update(set(fields.split(','))) if sp.startswith('!'): yield f'io_data.multiple_payload_generator, err = {sp[1:]}' else: yield f'err = {sp}' else: yield f'{dest}, err = {self.special_parse}' yield 'if err != nil { return err }' return if jt == 'list.str': yield f'{dest} = escape_list_of_strings(args)' return if jt == 'str': if c == 1: yield f'{dest} = escaped_string(args[0])' else: yield f'{dest} = escaped_string(strings.Join(args, " "))' return if jt.startswith('choices.'): yield f'if len(args) != 1 {{ return fmt.Errorf("%s", "Must specify exactly 1 argument for {cmd_name}") }}' choices = ", ".join(f'"{x}"' for x in jt.split('.')[1:]) yield 'switch(args[0]) {' yield f'case {choices}:\n\t{dest} = args[0]' yield f'default: return fmt.Errorf("%s is not a valid choice. Allowed values: %s", args[0], `{choices}`)' yield '}' return if jt == 'dict.str': yield f'{dest} = parse_key_val_args(args)' return raise TypeError(f'Unknown args handling for cmd: {cmd_name}') class StreamInFlight: def __init__(self) -> None: self.stream_id = '' self.tempfile: BytesIO | None = None def handle_data(self, stream_id: str, data: bytes) -> AsyncResponse | BytesIO: from ..remote_control import close_active_stream def abort_stream() -> None: close_active_stream(self.stream_id) self.stream_id = '' if self.tempfile is not None: self.tempfile.close() self.tempfile = None if stream_id != self.stream_id: abort_stream() self.stream_id = stream_id if self.tempfile is None: self.tempfile = BytesIO() t = self.tempfile if data: if (t.tell() + len(data)) > 128 * 1024 * 1024: abort_stream() raise StreamError('Too much data being sent') t.write(data) return AsyncResponse() close_active_stream(self.stream_id) self.stream_id = '' self.tempfile = None t.flush() return t class RemoteCommand: Args = ArgsHandling CompletionSpec = CompletionSpec name: str = '' short_desc: str = '' desc: str = '' args: ArgsHandling = ArgsHandling() options_spec: str | None = None response_timeout: float = 10. # seconds string_return_is_error: bool = False defaults: dict[str, Any] | None = None is_asynchronous: bool = False options_class: type[RCOptions] = RCOptions protocol_spec: str = '' argspec = args_count = args_completion = ArgsHandling() field_to_option_map: dict[str, str] | None = None reads_streaming_data: bool = False disallow_responses: bool = False def __init__(self) -> None: self.desc = self.desc or self.short_desc self.name = self.__class__.__module__.split('.')[-1].replace('_', '-') self.stream_in_flight = StreamInFlight() def fatal(self, msg: str) -> NoReturn: if running_in_kitty(): raise RemoteControlError(msg) raise SystemExit(msg) def get_default(self, name: str, missing: Any = None) -> Any: if self.options_spec: if self.defaults is None: self.defaults = get_defaults_from_seq(parse_option_spec(self.options_spec)[0]) return self.defaults.get(name, missing) return missing def windows_for_match_payload(self, boss: 'Boss', window: Optional['Window'], payload_get: PayloadGetType) -> list['Window']: if payload_get('all'): windows = list(boss.all_windows) else: self_window = window if payload_get('self') in (None, True): window = window or boss.active_window else: window = boss.active_window or window windows = [window] if window else [] if payload_get('match'): windows = list(boss.match_windows(payload_get('match'), self_window)) if not windows: raise MatchError(payload_get('match')) return windows def tabs_for_match_payload(self, boss: 'Boss', window: Optional['Window'], payload_get: PayloadGetType) -> list['Tab']: if payload_get('all'): return list(boss.all_tabs) match = payload_get('match') if match: tabs = list(boss.match_tabs(match)) if not tabs: raise MatchError(match, 'tabs') return tabs if window and payload_get('self') in (None, True): q = boss.tab_for_window(window) if q: return [q] t = boss.active_tab if t: return [t] return [] def windows_for_payload( self, boss: 'Boss', window: Optional['Window'], payload_get: PayloadGetType, window_match_name: str = 'match_window', tab_match_name: str = 'match_tab', ) -> list['Window']: if payload_get('all'): windows = list(boss.all_windows) else: window = window or boss.active_window windows = [window] if window else [] if payload_get(window_match_name): windows = list(boss.match_windows(payload_get(window_match_name), window)) if not windows: raise MatchError(payload_get(window_match_name)) if payload_get(tab_match_name): tabs = tuple(boss.match_tabs(payload_get(tab_match_name))) if not tabs: raise MatchError(payload_get(tab_match_name), 'tabs') windows = [] for tab in tabs: windows += list(tab) return windows def create_async_responder(self, payload_get: PayloadGetType, window: Window | None) -> AsyncResponder: return AsyncResponder(payload_get, window) def message_to_kitty(self, global_opts: RCOptions, opts: Any, args: ArgsType) -> PayloadType: raise NotImplementedError() def response_from_kitty(self, boss: 'Boss', window: Optional['Window'], payload_get: PayloadGetType) -> ResponseType: raise NotImplementedError() def cancel_async_request(self, boss: 'Boss', window: Optional['Window'], payload_get: PayloadGetType) -> None: pass def handle_streamed_data(self, data: bytes, payload_get: PayloadGetType) -> BytesIO | AsyncResponse: stream_id = payload_get('stream_id') if not stream_id or not isinstance(stream_id, str): raise StreamError('No stream_id in rc payload') return self.stream_in_flight.handle_data(stream_id, data) def cli_params_for(command: RemoteCommand) -> tuple[Callable[[], str], str, str, str]: return (command.options_spec or '\n').format, command.args.spec, command.desc, f'kitten @ {command.name}' def parse_subcommand_cli(command: RemoteCommand, args: ArgsType) -> tuple[Any, ArgsType]: opts, items = parse_args(args[1:], *cli_params_for(command), result_class=command.options_class) if command.args.args_count is not None and command.args.args_count != len(items): if command.args.args_count == 0: raise SystemExit(f'Unknown extra argument(s) supplied to {command.name}') raise SystemExit(f'Must specify exactly {command.args.args_count} argument(s) for {command.name}') return opts, items def display_subcommand_help(func: RemoteCommand) -> None: with suppress(SystemExit): parse_args(['--help'], (func.options_spec or '\n').format, func.args.spec, func.desc, func.name) def command_for_name(cmd_name: str) -> RemoteCommand: from importlib import import_module cmd_name = cmd_name.replace('-', '_') try: m = import_module(f'kitty.rc.{cmd_name}') except ImportError: raise KeyError(f'Unknown kitty remote control command: {cmd_name}') return cast(RemoteCommand, getattr(m, cmd_name)) def all_command_names() -> frozenset[str]: def ok(name: str) -> bool: root, _, ext = name.rpartition('.') return bool(ext in ('py', 'pyc', 'pyo') and root and root not in ('base', '__init__')) return frozenset({x.rpartition('.')[0] for x in filter(ok, list_kitty_resources('kitty.rc'))}) kitty-0.41.1/kitty/rc/close_tab.py0000664000175000017510000000406214773370543016366 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_TAB_OPTION, ArgsType, Boss, MatchError, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import CloseTabRCOptions as CLIOptions class CloseTab(RemoteCommand): protocol_spec = __doc__ = ''' match/str: Which tab to close self/bool: Boolean indicating whether to close the tab of the window the command is run in ignore_no_match/bool: Boolean indicating whether no matches should be ignored or return an error ''' short_desc = 'Close the specified tabs' desc = '''\ Close an arbitrary set of tabs. The :code:`--match` option can be used to specify complex sets of tabs to close. For example, to close all non-focused tabs in the currently focused OS window, use:: kitten @ close-tab --match "not state:focused and state:parent_focused" ''' options_spec = MATCH_TAB_OPTION + '''\n --no-response type=bool-set default=false Don't wait for a response indicating the success of the action. Note that using this option means that you will not be notified of failures. --self type=bool-set Close the tab of the window this command is run in, rather than the active tab. --ignore-no-match type=bool-set Do not return an error if no tabs are matched to be closed. ''' def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'match': opts.match, 'self': opts.self, 'ignore_no_match': opts.ignore_no_match} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: try: tabs = self.tabs_for_match_payload(boss, window, payload_get) except MatchError: if payload_get('ignore_no_match'): return None raise for tab in tuple(tabs): if tab: boss.close_tab_no_confirm(tab) return None close_tab = CloseTab() kitty-0.41.1/kitty/rc/close_window.py0000664000175000017510000000344514773370543017133 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import CloseWindowRCOptions as CLIOptions class CloseWindow(RemoteCommand): protocol_spec = __doc__ = ''' match/str: Which window to close self/bool: Boolean indicating whether to close the window the command is run in ignore_no_match/bool: Boolean indicating whether no matches should be ignored or return an error ''' short_desc = 'Close the specified windows' options_spec = MATCH_WINDOW_OPTION + '''\n --no-response type=bool-set default=false Don't wait for a response indicating the success of the action. Note that using this option means that you will not be notified of failures. --self type=bool-set Close the window this command is run in, rather than the active window. --ignore-no-match type=bool-set Do not return an error if no windows are matched to be closed. ''' def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'match': opts.match, 'self': opts.self, 'ignore_no_match': opts.ignore_no_match} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: try: windows = self.windows_for_match_payload(boss, window, payload_get) except MatchError: if payload_get('ignore_no_match'): return None raise for window in tuple(windows): if window: boss.mark_window_for_close(window) return None close_window = CloseWindow() kitty-0.41.1/kitty/rc/create_marker.py0000664000175000017510000000406314773370543017240 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from kitty.options.utils import parse_marker_spec from .base import MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import CreateMarkerRCOptions as CLIOptions class CreateMarker(RemoteCommand): protocol_spec = __doc__ = ''' match/str: Which window to create the marker in self/bool: Boolean indicating whether to create marker in the window the command is run in marker_spec/list.str: A list or arguments that define the marker specification, for example: ['text', '1', 'ERROR'] ''' short_desc = 'Create a marker that highlights specified text' desc = ( 'Create a marker which can highlight text in the specified window. For example:' ' :code:`create_marker text 1 ERROR`. For full details see: :doc:`marks`' ) options_spec = MATCH_WINDOW_OPTION + '''\n --self type=bool-set Apply marker to the window this command is run in, rather than the active window. ''' args = RemoteCommand.Args(spec='MARKER SPECIFICATION', json_field='marker_spec', minimum_count=2) def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: if len(args) < 2: self.fatal('Invalid marker specification: {}'.format(' '.join(args))) try: parse_marker_spec(args[0], args[1:]) except Exception as err: self.fatal(f"Failed to parse marker specification {' '.join(args)} with error: {err}") return {'match': opts.match, 'self': opts.self, 'marker_spec': args} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: args = payload_get('marker_spec') for window in self.windows_for_match_payload(boss, window, payload_get): if window: window.set_marker(args) return None create_marker = CreateMarker() kitty-0.41.1/kitty/rc/detach_tab.py0000664000175000017510000000364014773370543016512 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_TAB_OPTION, ArgsType, Boss, MatchError, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import DetachTabRCOptions as CLIOptions class DetachTab(RemoteCommand): protocol_spec = __doc__ = ''' match/str: Which tab to detach target_tab/str: Which tab to move the detached tab to the OS window it is run in self/bool: Boolean indicating whether to detach the tab the command is run in ''' short_desc = 'Detach the specified tabs and place them in a different/new OS window' desc = ( 'Detach the specified tabs and either move them into a new OS window' ' or add them to the OS window containing the tab specified by :option:`kitten @ detach-tab --target-tab`' ) options_spec = MATCH_TAB_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--target-tab -t') + '''\n --self type=bool-set Detach the tab this command is run in, rather than the active tab. ''' def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'match': opts.match, 'target_tab': opts.target_tab, 'self': opts.self} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: match = payload_get('target_tab') kwargs = {} if match: targets = tuple(boss.match_tabs(match)) if not targets: raise MatchError(match, 'tabs') if targets[0]: kwargs['target_os_window_id'] = targets[0].os_window_id for tab in self.tabs_for_match_payload(boss, window, payload_get): if tab: boss._move_tab_to(tab=tab, **kwargs) return None detach_tab = DetachTab() kitty-0.41.1/kitty/rc/detach_window.py0000664000175000017510000000537314773370543017260 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import DetachWindowRCOptions as CLIOptions class DetachWindow(RemoteCommand): protocol_spec = __doc__ = ''' match/str: Which window to detach target_tab/str: Which tab to move the detached window to self/bool: Boolean indicating whether to detach the window the command is run in stay_in_tab/bool: Boolean indicating focus should remain in the active tab after windows are moved ''' short_desc = 'Detach the specified windows and place them in a different/new tab' desc = ( 'Detach the specified windows and either move them into a new tab, a new OS window' ' or add them to the specified tab. Use the special value :code:`new` for :option:`kitten @ detach-window --target-tab`' ' to move to a new tab. If no target tab is specified the windows are moved to a new OS window.' ) options_spec = ( MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--target-tab -t') + '''Use the special value :code:`new` to move to a new tab. --self type=bool-set Detach the window this command is run in, rather than the active window. --stay-in-tab type=bool-set Keep the focus on a window in the currently focused tab after moving the specified windows. ''') def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'match': opts.match, 'target_tab': opts.target_tab, 'self': opts.self, 'stay_in_tab': opts.stay_in_tab} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: windows = self.windows_for_match_payload(boss, window, payload_get) match = payload_get('target_tab') target_tab_id: str | int | None = None newval: str | int = 'new' if match: if match == 'new': target_tab_id = newval else: tabs = tuple(boss.match_tabs(match)) if not tabs: raise MatchError(match, 'tabs') target_tab_id = tabs[0].id kwargs = {'target_os_window_id': newval} if target_tab_id is None else {'target_tab_id': target_tab_id} tab = boss.active_tab for window in windows: if window: boss._move_window_to(window=window, **kwargs) if payload_get('stay_in_tab') and tab is not None: tab.make_active() return None detach_window = DetachWindow() kitty-0.41.1/kitty/rc/disable_ligatures.py0000664000175000017510000000427114773370543020117 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import DisableLigaturesRCOptions as CLIOptions class DisableLigatures(RemoteCommand): protocol_spec = __doc__ = ''' strategy+/choices.never.always.cursor: One of :code:`never`, :code:`always` or :code:`cursor` match_window/str: Window to change opacity in match_tab/str: Tab to change opacity in all/bool: Boolean indicating operate on all windows ''' short_desc = 'Control ligature rendering' desc = ( 'Control ligature rendering for the specified windows/tabs (defaults to active window). The :italic:`STRATEGY`' ' can be one of: :code:`never`, :code:`always`, :code:`cursor`.' ) options_spec = '''\ --all -a type=bool-set By default, ligatures are only affected in the active window. This option will cause ligatures to be changed in all windows. ''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t') args = RemoteCommand.Args(spec='STRATEGY', count=1, json_field='strategy') def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: if not args: self.fatal( 'You must specify the STRATEGY for disabling ligatures, must be one of' ' never, always or cursor') strategy = args[0] if strategy not in ('never', 'always', 'cursor'): self.fatal(f'{strategy} is not a valid disable_ligatures strategy') return { 'strategy': strategy, 'match_window': opts.match, 'match_tab': opts.match_tab, 'all': opts.all, } def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: windows = self.windows_for_payload(boss, window, payload_get) boss.disable_ligatures_in(windows, payload_get('strategy')) return None # }}} disable_ligatures = DisableLigatures() kitty-0.41.1/kitty/rc/env.py0000664000175000017510000000346014773370543015224 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import Any from .base import ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window class Env(RemoteCommand): protocol_spec = __doc__ = ''' env+/dict.str: Dictionary of environment variables to values. When a env var ends with = it is removed from the environment. ''' short_desc = 'Change environment variables seen by future children' desc = ( 'Change the environment variables that will be seen in newly launched windows.' ' Similar to the :opt:`env` option in :file:`kitty.conf`, but affects running kitty instances.' ' If no = is present, the variable is removed from the environment.' ) args = RemoteCommand.Args(spec='env_var1=val env_var2=val ...', minimum_count=1, json_field='env') def message_to_kitty(self, global_opts: RCOptions, opts: Any, args: ArgsType) -> PayloadType: if len(args) < 1: self.fatal('Must specify at least one env var to set') env = {} for x in args: if '=' in x: key, val = x.split('=', 1) env[key] = val else: env[x + '='] = '' return {'env': env} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: from kitty.child import default_env, set_default_env from kitty.utils import expandvars new_env = payload_get('env') or {} env = default_env().copy() for k, v in new_env.items(): if k.endswith('='): env.pop(k[:-1], None) else: env[k] = expandvars(v or '', env) set_default_env(env) return None env = Env() kitty-0.41.1/kitty/rc/focus_tab.py0000664000175000017510000000241014773370543016373 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import FocusTabRCOptions as CLIOptions class FocusTab(RemoteCommand): protocol_spec = __doc__ = ''' match/str: The tab to focus ''' short_desc = 'Focus the specified tab' desc = 'The active window in the specified tab will be focused.' options_spec = MATCH_TAB_OPTION + ''' --no-response type=bool-set default=false Don't wait for a response indicating the success of the action. Note that using this option means that you will not be notified of failures. ''' def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'match': opts.match, 'no_response': opts.no_response} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: for tab in self.tabs_for_match_payload(boss, window, payload_get): if tab: boss.set_active_tab(tab) break return None focus_tab = FocusTab() kitty-0.41.1/kitty/rc/focus_window.py0000664000175000017510000000272014773370543017140 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from kitty.fast_data_types import focus_os_window from .base import MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import FocusWindowRCOptions as CLIOptions class FocusWindow(RemoteCommand): protocol_spec = __doc__ = ''' match/str: The window to focus ''' short_desc = 'Focus the specified window' desc = 'Focus the specified window, if no window is specified, focus the window this command is run inside.' options_spec = MATCH_WINDOW_OPTION + '''\n\n --no-response type=bool-set default=false Don't wait for a response from kitty. This means that even if no matching window is found, the command will exit with a success code. ''' def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'match': opts.match} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: for window in self.windows_for_match_payload(boss, window, payload_get): if window: os_window_id = boss.set_active_window(window) if os_window_id: focus_os_window(os_window_id, True) break return None focus_window = FocusWindow() kitty-0.41.1/kitty/rc/get_colors.py0000664000175000017510000000503014773370543016567 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from kitty.fast_data_types import Color from kitty.rgb import color_as_sharp, color_from_int from kitty.utils import natsort_ints from .base import MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import GetColorsRCOptions as CLIOptions class GetColors(RemoteCommand): protocol_spec = __doc__ = ''' match/str: The window to get the colors for configured/bool: Boolean indicating whether to get configured or current colors ''' short_desc = 'Get terminal colors' desc = ( 'Get the terminal colors for the specified window (defaults to active window).' ' Colors will be output to STDOUT in the same syntax as used for :file:`kitty.conf`.' '\n\nTo get a single color use:' '\n get-colors | grep "^background " | tr -s | cut -d" " -f2' '\n\nChange background above to whatever color you are interested in.' ) options_spec = '''\ --configured -c type=bool-set Instead of outputting the colors for the specified window, output the currently configured colors. ''' + '\n\n' + MATCH_WINDOW_OPTION def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'configured': opts.configured, 'match': opts.match} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: from kitty.fast_data_types import get_options opts = get_options() ans = {k: getattr(opts, k) for k in opts if isinstance(getattr(opts, k), Color)} if not payload_get('configured'): windows = self.windows_for_match_payload(boss, window, payload_get) if windows and windows[0]: for k, v in windows[0].current_colors.items(): if v is None: ans.pop(k, None) elif isinstance(v, int): ans[k] = color_from_int(v) tab = windows[0].tabref() tm = None if tab is None else tab.tab_manager_ref() if tm is not None: ans.update(tm.tab_bar.current_colors) all_keys = natsort_ints(ans) maxlen = max(map(len, all_keys)) return '\n'.join(('{:%ds} {}' % maxlen).format(key, color_as_sharp(ans[key])) for key in all_keys) # }}} get_colors = GetColors() kitty-0.41.1/kitty/rc/get_text.py0000664000175000017510000001200314773370543016250 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import GetTextRCOptions as CLIOptions class GetText(RemoteCommand): protocol_spec = __doc__ = ''' match/str: The window to get text from extent/choices.screen.first_cmd_output_on_screen.last_cmd_output.last_visited_cmd_output.all.selection: \ One of :code:`screen`, :code:`first_cmd_output_on_screen`, :code:`last_cmd_output`, \ :code:`last_visited_cmd_output`, :code:`all`, or :code:`selection` ansi/bool: Boolean, if True send ANSI formatting codes cursor/bool: Boolean, if True send cursor position/style as ANSI codes wrap_markers/bool: Boolean, if True add wrap markers to output clear_selection/bool: Boolean, if True clear the selection in the matched window self/bool: Boolean, if True use window the command was run in ''' short_desc = 'Get text from the specified window' options_spec = MATCH_WINDOW_OPTION + '''\n --extent default=screen choices=screen, all, selection, first_cmd_output_on_screen, last_cmd_output, last_visited_cmd_output, last_non_empty_output What text to get. The default of :code:`screen` means all text currently on the screen. :code:`all` means all the screen+scrollback and :code:`selection` means the currently selected text. :code:`first_cmd_output_on_screen` means the output of the first command that was run in the window on screen. :code:`last_cmd_output` means the output of the last command that was run in the window. :code:`last_visited_cmd_output` means the first command output below the last scrolled position via scroll_to_prompt. :code:`last_non_empty_output` is the output from the last command run in the window that had some non empty output. The last four require :ref:`shell_integration` to be enabled. --ansi type=bool-set By default, only plain text is returned. With this flag, the text will include the ANSI formatting escape codes for colors, bold, italic, etc. --add-cursor type=bool-set Add ANSI escape codes specifying the cursor position and style to the end of the text. --add-wrap-markers type=bool-set Add carriage returns at every line wrap location (where long lines are wrapped at screen edges). --clear-selection type=bool-set Clear the selection in the matched window, if any. --self type=bool-set Get text from the window this command is run in, rather than the active window. ''' field_to_option_map = {'wrap_markers': 'add_wrap_markers', 'cursor': 'add_cursor'} def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return { 'match': opts.match, 'extent': opts.extent, 'ansi': opts.ansi, 'cursor': opts.add_cursor, 'wrap_markers': opts.add_wrap_markers, 'clear_selection': opts.clear_selection, 'self': opts.self, } def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: from kitty.window import CommandOutput windows = self.windows_for_match_payload(boss, window, payload_get) if windows and windows[0]: window = windows[0] else: return None if payload_get('extent') == 'selection': ans = window.text_for_selection(as_ansi=payload_get('ansi')) elif payload_get('extent') == 'first_cmd_output_on_screen': ans = window.cmd_output( CommandOutput.first_on_screen, as_ansi=bool(payload_get('ansi')), add_wrap_markers=bool(payload_get('wrap_markers')), ) elif payload_get('extent') == 'last_cmd_output': ans = window.cmd_output( CommandOutput.last_run, as_ansi=bool(payload_get('ansi')), add_wrap_markers=bool(payload_get('wrap_markers')), ) elif payload_get('extent') == 'last_non_empty_output': ans = window.cmd_output( CommandOutput.last_non_empty, as_ansi=bool(payload_get('ansi')), add_wrap_markers=bool(payload_get('wrap_markers')), ) elif payload_get('extent') == 'last_visited_cmd_output': ans = window.cmd_output( CommandOutput.last_visited, as_ansi=bool(payload_get('ansi')), add_wrap_markers=bool(payload_get('wrap_markers')), ) else: ans = window.as_text( as_ansi=bool(payload_get('ansi')), add_history=payload_get('extent') == 'all', add_cursor=bool(payload_get('cursor')), add_wrap_markers=bool(payload_get('wrap_markers')), ) if payload_get('clear_selection'): window.clear_selection() return ans get_text = GetText() kitty-0.41.1/kitty/rc/goto_layout.py0000664000175000017510000000415714773370543017005 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Iterable from typing import TYPE_CHECKING from .base import MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, UnknownLayout, Window if TYPE_CHECKING: from kitty.cli_stub import GotoLayoutRCOptions as CLIOptions def layout_names() -> Iterable[str]: from kitty.layout.interface import all_layouts return all_layouts.keys() class GotoLayout(RemoteCommand): protocol_spec = __doc__ = ''' layout+/str: The new layout name match/str: Which tab to change the layout of ''' short_desc = 'Set the window layout' desc = ( 'Set the window layout in the specified tabs (or the active tab if not specified).' ' You can use special match value :code:`all` to set the layout in all tabs.' ' In case there are multiple layouts with the same name but different options,' ' specify the full layout definition or a unique prefix of the full definition.' ) options_spec = MATCH_TAB_OPTION args = RemoteCommand.Args( spec='LAYOUT_NAME', count=1, json_field='layout', completion=RemoteCommand.CompletionSpec.from_string('type:keyword group:"Layout" kwds:' + ','.join(layout_names())), ) def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: if len(args) != 1: self.fatal('Exactly one layout must be specified') return {'layout': args[0], 'match': opts.match} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: tabs = self.tabs_for_match_payload(boss, window, payload_get) for tab in tabs: if tab: try: tab.goto_layout(payload_get('layout'), raise_exception=True) except ValueError: raise UnknownLayout('The layout {} is unknown or disabled or the name is ambiguous'.format(payload_get('layout'))) return None goto_layout = GotoLayout() kitty-0.41.1/kitty/rc/kitten.py0000664000175000017510000000376614773370543015743 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import KittenRCOptions as CLIOptions class Kitten(RemoteCommand): protocol_spec = __doc__ = ''' kitten+/str: The name of the kitten to run args/list.str: Arguments to pass to the kitten as a list match/str: The window to run the kitten over ''' short_desc = 'Run a kitten' desc = ( 'Run a kitten over the specified windows (active window by default).' ' The :italic:`kitten_name` can be either the name of a builtin kitten' ' or the path to a Python file containing a custom kitten. If a relative path' ' is used it is searched for in the :ref:`kitty config directory `. If the kitten is a' ' :italic:`no_ui` kitten and its handle response method returns a string or boolean, this' ' is printed out to stdout.' ) options_spec = MATCH_WINDOW_OPTION args = RemoteCommand.Args(spec='kitten_name', json_field='kitten', minimum_count=1, first_rest=('kitten', 'args')) def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: if len(args) < 1: self.fatal('Must specify kitten name') return {'match': opts.match, 'args': list(args)[1:], 'kitten': args[0]} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: retval = None for window in self.windows_for_match_payload(boss, window, payload_get): if window: retval = boss.run_kitten_with_metadata(payload_get('kitten'), args=tuple(payload_get('args') or ()), window=window) break if isinstance(retval, (str, bool)): return retval return None kitten = Kitten() kitty-0.41.1/kitty/rc/last_used_layout.py0000664000175000017510000000271414773370543020015 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import LastUsedLayoutRCOptions as CLIOptions class LastUsedLayout(RemoteCommand): protocol_spec = __doc__ = ''' match/str: Which tab to change the layout of all/bool: Boolean to match all tabs ''' short_desc = 'Switch to the last used layout' desc = ( 'Switch to the last used window layout in the specified tabs (or the active tab if not specified).' ) options_spec = '''\ --all -a type=bool-set Change the layout in all tabs. --no-response type=bool-set default=false Don't wait for a response from kitty. This means that even if no matching tab is found, the command will exit with a success code. ''' + '\n\n\n' + MATCH_TAB_OPTION def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'match': opts.match, 'all': opts.all, 'no_response': opts.no_response} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: for tab in self.tabs_for_match_payload(boss, window, payload_get): if tab: tab.last_used_layout() return None last_used_layout = LastUsedLayout() kitty-0.41.1/kitty/rc/launch.py0000664000175000017510000001337014773370543015707 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from kitty.cli_stub import LaunchCLIOptions from kitty.launch import launch as do_launch from kitty.launch import options_spec as launch_options_spec from kitty.launch import parse_launch_args from .base import MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import LaunchRCOptions as CLIOptions class Launch(RemoteCommand): protocol_spec = __doc__ = ''' args+/list.str: The command line to run in the new window, as a list, use an empty list to run the default shell match/str: The tab to open the new window in next_to/str: The window next to which to create the new window or empty string to use active window source_window/str: The window to use as source for data or empty string to use active window window_title/str: Title for the new window cwd/str: Working directory for the new window env/list.str: List of environment variables of the form NAME=VALUE var/list.str: List of user variables of the form NAME=VALUE tab_title/str: Title for the new tab type/choices.window.tab.os-window.overlay.overlay-main.background.clipboard.primary: The type of window to open keep_focus/bool: Boolean indicating whether the current window should retain focus or not copy_colors/bool: Boolean indicating whether to copy the colors from the current window copy_cmdline/bool: Boolean indicating whether to copy the cmdline from the current window copy_env/list.str=copy_local_env: List of strings representing the local env vars hold/bool: Boolean indicating whether to keep window open after cmd exits location/choices.first.after.before.neighbor.last.vsplit.hsplit.split.default: Where in the tab to open the new window allow_remote_control/bool: Boolean indicating whether to allow remote control from the new window remote_control_password/list.str: A list of remote control passwords stdin_source/choices.none.@selection.@screen.@screen_scrollback.@alternate.@alternate_scrollback.\ @first_cmd_output_on_screen.@last_cmd_output.@last_visited_cmd_output: Where to get stdin for the process from stdin_add_formatting/bool: Boolean indicating whether to add formatting codes to stdin stdin_add_line_wrap_markers/bool: Boolean indicating whether to add line wrap markers to stdin spacing/list.str: A list of spacing specifications, see the docs for the set-spacing command marker/str: Specification for marker for new window, for example: "text 1 ERROR" logo/str: Path to window logo logo_position/str: Window logo position as string or empty string to use default logo_alpha/float: Window logo alpha or -1 to use default self/bool: Boolean, if True use tab the command was run in os_window_title/str: Title for OS Window os_window_name/str: WM_NAME for OS Window os_window_class/str: WM_CLASS for OS Window os_window_state/choices.normal.fullscreen.maximized.minimized: The initial state for OS Window color/list.str: list of color specifications such as foreground=red watcher/list.str: list of paths to watcher files bias/float: The bias with which to create the new window in the current layout ''' short_desc = 'Run an arbitrary process in a new window/tab' desc = ( 'Prints out the id of the newly opened window. Any command line arguments' ' are assumed to be the command line used to run in the new window, if none' ' are provided, the default shell is run. For example::\n\n' ' kitten @ launch --title=Email mutt' ) options_spec = MATCH_TAB_OPTION + '\n\n' + '''\ --no-response type=bool-set Do not print out the id of the newly created window. --self type=bool-set If specified the tab containing the window this command is run in is used instead of the active tab ''' + '\n\n' + launch_options_spec().replace(':option:`launch', ':option:`kitten @ launch') args = RemoteCommand.Args(spec='[CMD ...]', json_field='args', completion=RemoteCommand.CompletionSpec.from_string( 'type:special group:cli.CompleteExecutableFirstArg')) def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: ans = {'args': args or []} for attr, val in opts.__dict__.items(): ans[attr] = val return ans def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: default_opts = parse_launch_args()[0] opts = LaunchCLIOptions() for key, default_value in default_opts.__dict__.items(): if key == 'copy_env': continue val = payload_get(key) if val is None: val = default_value setattr(opts, key, val) ceval = payload_get('copy_env') opts.copy_env = False base_env: dict[str, str] | None = None if ceval: if isinstance(ceval, list): base_env = {} for x in ceval: k, v = x.partition('=')[::2] base_env[k] = v elif isinstance(ceval, bool): opts.copy_env = ceval target_tab = None tabs = self.tabs_for_match_payload(boss, window, payload_get) if tabs and tabs[0]: target_tab = tabs[0] elif payload_get('type') not in ('background', 'os-window', 'tab', 'window'): return None w = do_launch(boss, opts, payload_get('args') or [], target_tab=target_tab, rc_from_window=window, base_env=base_env) return None if payload_get('no_response') else str(getattr(w, 'id', 0)) launch = Launch() kitty-0.41.1/kitty/rc/load_config.py0000664000175000017510000000527614773370543016707 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from kitty.constants import appname from .base import ( ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window, ) if TYPE_CHECKING: from kitty.cli_stub import LoadConfigRCOptions as CLIOptions class LoadConfig(RemoteCommand): protocol_spec = __doc__ = ''' paths/list.str: List of config file paths to load override/list.str: List of individual config overrides ignore_overrides/bool: Whether to apply previous overrides ''' short_desc = '(Re)load a config file' desc = ( '(Re)load the specified kitty.conf config files(s). If no files are specified the previously specified config file is reloaded.' ' Note that the specified paths must exist and be readable by the kitty process on the computer that process is running on.' ' Relative paths are resolved with respect to the kitty config directory on the computer running kitty.' ) options_spec = f'''\ --ignore-overrides type=bool-set By default, any config overrides previously specified at the kitty invocation command line or a previous load-config-file command are respected. Use this option to have them ignored instead. --override -o type=list completion=type:special group:complete_kitty_override Override individual configuration options, can be specified multiple times. Syntax: :italic:`name=value`. For example: :option:`{appname} -o` font_size=20 --no-response type=bool-set default=false Don't wait for a response indicating the success of the action. Note that using this option means that you will not be notified of failures. ''' args = RemoteCommand.Args(spec='CONF_FILE ...', json_field='paths', completion=RemoteCommand.CompletionSpec.from_string('type:file group:"CONF files", ext:conf')) def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'paths': args, 'override': opts.override, 'ignore_overrides': opts.ignore_overrides} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: from kitty.cli import parse_override from kitty.utils import resolve_abs_or_config_path paths = tuple(map(resolve_abs_or_config_path, payload_get('paths', missing=()))) boss.load_config_file( *paths, apply_overrides=not payload_get('ignore_overrides', missing=False), overrides=tuple(map(parse_override, payload_get('override', missing=()))) ) return None load_config = LoadConfig() kitty-0.41.1/kitty/rc/ls.py0000664000175000017510000000761214773370543015055 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import json from collections.abc import Callable from typing import TYPE_CHECKING from kitty.constants import appname from .base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Tab, Window if TYPE_CHECKING: from kitty.cli_stub import LSRCOptions as CLIOptions class LS(RemoteCommand): protocol_spec = __doc__ = ''' all_env_vars/bool: Whether to send all environment variables for every window rather than just differing ones match/str: Window to change colors in match_tab/str: Tab to change colors in self/bool: Boolean indicating whether to list only the window the command is run in ''' short_desc = 'List tabs/windows' desc = ( 'List windows. The list is returned as JSON tree. The top-level is a list of' f' operating system {appname} windows. Each OS window has an :italic:`id` and a list' ' of :italic:`tabs`. Each tab has its own :italic:`id`, a :italic:`title` and a list of :italic:`windows`.' ' Each window has an :italic:`id`, :italic:`title`, :italic:`current working directory`, :italic:`process id (PID)`,' ' :italic:`command-line` and :italic:`environment` of the process running in the window. Additionally, when' ' running the command inside a kitty window, that window can be identified by the :italic:`is_self` parameter.\n\n' 'You can use these criteria to select windows/tabs for the other commands.\n\n' 'You can limit the windows/tabs in the output by using the :option:`--match` and :option:`--match-tab` options.' ) options_spec = '''\ --all-env-vars type=bool-set Show all environment variables in output, not just differing ones. --self type=bool-set Only list the window this command is run in. ''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t', 1) def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'all_env_vars': opts.all_env_vars, 'match': opts.match, 'match_tab': opts.match_tab} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: tab_filter: Callable[[Tab], bool] | None = None window_filter: Callable[[Window], bool] | None = None if payload_get('self'): def wf(w: Window) -> bool: return w is window window_filter = wf elif payload_get('match') is not None or payload_get('match_tab') is not None: window_ids = frozenset(w.id for w in self.windows_for_payload(boss, window, payload_get, window_match_name='match')) def wf(w: Window) -> bool: return w.id in window_ids window_filter = wf data = list(boss.list_os_windows(window, tab_filter, window_filter)) if not payload_get('all_env_vars'): all_env_blocks: list[dict[str, str]] = [] common_env_vars: set[tuple[str, str]] = set() for osw in data: for tab in osw.get('tabs', ()): for w in tab.get('windows', ()): env: dict[str, str] = w.get('env', {}) frozen_env = set(env.items()) if all_env_blocks: common_env_vars &= frozen_env else: common_env_vars = frozen_env all_env_blocks.append(env) if common_env_vars and len(all_env_blocks) > 1: remove_env_vars = {k for k, v in common_env_vars} for env in all_env_blocks: for r in remove_env_vars: env.pop(r, None) return json.dumps(data, indent=2, sort_keys=True) ls = LS() kitty-0.41.1/kitty/rc/new_window.py0000664000175000017510000000641314773370543016615 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import NewWindowRCOptions as CLIOptions class NewWindow(RemoteCommand): protocol_spec = __doc__ = ''' args+/list.str: The command line to run in the new window, as a list, use an empty list to run the default shell match/str: The tab to open the new window in title/str: Title for the new window cwd/str: Working directory for the new window keep_focus/bool: Boolean indicating whether the current window should retain focus or not window_type/choices.kitty.os: One of :code:`kitty` or :code:`os` new_tab/bool: Boolean indicating whether to open a new tab tab_title/str: Title for the new tab ''' short_desc = 'Open new window' desc = ( 'DEPRECATED: Use the :ref:`launch ` command instead.\n\n' 'Open a new window in the specified tab. If you use the :option:`kitten @ new-window --match` option' ' the first matching tab is used. Otherwise the currently active tab is used.' ' Prints out the id of the newly opened window' ' (unless :option:`--no-response` is used). Any command line arguments' ' are assumed to be the command line used to run in the new window, if none' ' are provided, the default shell is run. For example::\n\n' ' kitten @ new-window --title Email mutt' ) options_spec = MATCH_TAB_OPTION + '''\n --title The title for the new window. By default it will use the title set by the program running in it. --cwd The initial working directory for the new window. Defaults to whatever the working directory for the kitty process you are talking to is. --keep-focus --dont-take-focus type=bool-set Keep the current window focused instead of switching to the newly opened window. --window-type default=kitty choices=kitty,os What kind of window to open. A kitty window or a top-level OS window. --new-tab type=bool-set Open a new tab. --tab-title Set the title of the tab, when open a new tab. --no-response type=bool-set default=false Don't wait for a response giving the id of the newly opened window. Note that using this option means that you will not be notified of failures and that the id of the new window will not be printed out. ''' args = RemoteCommand.Args(spec='[CMD ...]', json_field='args') def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: ans = {'args': args or [], 'type': 'window'} for attr, val in opts.__dict__.items(): if attr == 'new_tab': if val: ans['type'] = 'tab' elif attr == 'window_type': if val == 'os' and ans['type'] != 'tab': ans['type'] = 'os-window' else: ans[attr] = val return ans def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: from .launch import launch return launch.response_from_kitty(boss, window, payload_get) new_window = NewWindow() kitty-0.41.1/kitty/rc/remove_marker.py0000664000175000017510000000235114773370543017270 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import RemoveMarkerRCOptions as CLIOptions class RemoveMarker(RemoteCommand): protocol_spec = __doc__ = ''' match/str: Which window to remove the marker from self/bool: Boolean indicating whether to detach the window the command is run in ''' short_desc = 'Remove the currently set marker, if any.' options_spec = MATCH_WINDOW_OPTION + '''\n --self type=bool-set Apply marker to the window this command is run in, rather than the active window. ''' def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'match': opts.match, 'self': opts.self} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: for window in self.windows_for_match_payload(boss, window, payload_get): if window: window.remove_marker() return None remove_marker = RemoveMarker() kitty-0.41.1/kitty/rc/resize_os_window.py0000664000175000017510000000626614773370543020034 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import ResizeOSWindowRCOptions as CLIOptions class ResizeOSWindow(RemoteCommand): protocol_spec = __doc__ = ''' match/str: Which window to resize self/bool: Boolean indicating whether to close the window the command is run in incremental/bool: Boolean indicating whether to adjust the size incrementally action/choices.resize.toggle-fullscreen.toggle-maximized: One of :code:`resize, toggle-fullscreen` or :code:`toggle-maximized` unit/choices.cells.pixels: One of :code:`cells` or :code:`pixels` width/int: Integer indicating desired window width height/int: Integer indicating desired window height ''' short_desc = 'Resize the specified OS Windows' desc = ( 'Resize the specified OS Windows.' ' Note that some window managers/environments do not allow applications to resize' ' their windows, for example, tiling window managers.' ) options_spec = MATCH_WINDOW_OPTION + '''\n --action default=resize choices=resize,toggle-fullscreen,toggle-maximized The action to perform. --unit default=cells choices=cells,pixels The unit in which to interpret specified sizes. --width default=0 type=int Change the width of the window. Zero leaves the width unchanged. --height default=0 type=int Change the height of the window. Zero leaves the height unchanged. --incremental type=bool-set Treat the specified sizes as increments on the existing window size instead of absolute sizes. --self type=bool-set Resize the window this command is run in, rather than the active window. --no-response type=bool-set default=false Don't wait for a response indicating the success of the action. Note that using this option means that you will not be notified of failures. ''' def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return { 'match': opts.match, 'action': opts.action, 'unit': opts.unit, 'width': opts.width, 'height': opts.height, 'self': opts.self, 'incremental': opts.incremental } def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: windows = self.windows_for_match_payload(boss, window, payload_get) if windows: ac = payload_get('action') for os_window_id in {w.os_window_id for w in windows if w}: if ac == 'resize': boss.resize_os_window( os_window_id, width=payload_get('width'), height=payload_get('height'), unit=payload_get('unit'), incremental=payload_get('incremental') ) elif ac == 'toggle-fullscreen': boss.toggle_fullscreen(os_window_id) elif ac == 'toggle-maximized': boss.toggle_maximized(os_window_id) return None resize_os_window = ResizeOSWindow() kitty-0.41.1/kitty/rc/resize_window.py0000664000175000017510000000446414773370543017331 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import ResizeWindowRCOptions as CLIOptions class ResizeWindow(RemoteCommand): protocol_spec = __doc__ = ''' match/str: Which window to resize self/bool: Boolean indicating whether to resize the window the command is run in increment/int: Integer specifying the resize increment axis/choices.horizontal.vertical.reset: One of :code:`horizontal, vertical` or :code:`reset` ''' short_desc = 'Resize the specified windows' desc = ( 'Resize the specified windows in the current layout.' ' Note that not all layouts can resize all windows in all directions.' ) options_spec = MATCH_WINDOW_OPTION + '''\n --increment -i type=int default=2 The number of cells to change the size by, can be negative to decrease the size. --axis -a type=choices choices=horizontal,vertical,reset default=horizontal The axis along which to resize. If :code:`horizontal`, it will make the window wider or narrower by the specified increment. If :code:`vertical`, it will make the window taller or shorter by the specified increment. The special value :code:`reset` will reset the layout to its default configuration. --self type=bool-set Resize the window this command is run in, rather than the active window. ''' string_return_is_error = True def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'match': opts.match, 'increment': opts.increment, 'axis': opts.axis, 'self': opts.self} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: windows = self.windows_for_match_payload(boss, window, payload_get) resized: bool | None | str = False if windows and windows[0]: resized = boss.resize_layout_window( windows[0], increment=payload_get('increment'), is_horizontal=payload_get('axis') == 'horizontal', reset=payload_get('axis') == 'reset' ) return resized resize_window = ResizeWindow() kitty-0.41.1/kitty/rc/run.py0000664000175000017510000001205214773370543015235 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import sys from base64 import standard_b64decode, standard_b64encode from typing import TYPE_CHECKING from kitty.launch import env_docs, remote_control_password_docs from kitty.options.utils import env as parse_env from kitty.types import AsyncResponse from .base import ( ArgsType, Boss, CmdGenerator, ParsingOfArgsFailed, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window, ) if TYPE_CHECKING: from kitty.cli_stub import RunRCOptions as CLIOptions class Run(RemoteCommand): protocol_spec = __doc__ = ''' data+/str: Chunk of STDIN data, base64 encoded no more than 4096 bytes. Must send an empty chunk to indicate end of data. cmdline+/list.str: The command line to run env/list.str: List of environment variables of the form NAME=VALUE allow_remote_control/bool: A boolean indicating whether to allow remote control remote_control_password/list.str: A list of remote control passwords ''' short_desc = 'Run a program on the computer in which kitty is running and get the output' desc = ( 'Run the specified program on the computer in which kitty is running. When STDIN is not a TTY it is forwarded' ' to the program as its STDIN. STDOUT and STDERR from the the program are forwarded here. The exit status of this' ' invocation will be the exit status of the executed program. If you wish to just run a program without waiting for a response, ' ' use @ launch --type=background instead.' ) options_spec = f'''\n --env {env_docs} --allow-remote-control type=bool-set The executed program will have privileges to run remote control commands in kitty. --remote-control-password {remote_control_password_docs} ''' args = RemoteCommand.Args( spec='CMD ...', json_field='data', special_parse='+cmdline:!read_run_data(io_data, args, &payload)', minimum_count=1, completion=RemoteCommand.CompletionSpec.from_string('type:special group:cli.CompleteExecutableFirstArg') ) reads_streaming_data = True is_asynchronous = True def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: if not args: self.fatal('Must specify command to run') import secrets ret = { 'stream_id': secrets.token_urlsafe(), 'cmdline': args, 'env': opts.env, 'allow_remote_control': opts.allow_remote_control, 'remote_control_password': opts.remote_control_password, 'data': '', } def pipe() -> CmdGenerator: if sys.stdin.isatty(): yield ret else: limit = 4096 while True: data = sys.stdin.buffer.read(limit) if not data: break ret['data'] = standard_b64encode(data).decode("ascii") yield ret return pipe() def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: import os import tempfile data = payload_get('data') q = self.handle_streamed_data(standard_b64decode(data) if data else b'', payload_get) if isinstance(q, AsyncResponse): return q stdin_data = q.getvalue() from kitty.launch import parse_remote_control_passwords cmdline = payload_get('cmdline') allow_remote_control = payload_get('allow_remote_control') pw = payload_get('remote_control_password') rcp = parse_remote_control_passwords(allow_remote_control, pw) if not cmdline: raise ParsingOfArgsFailed('No cmdline to run specified') responder = self.create_async_responder(payload_get, window) stdout, stderr = tempfile.TemporaryFile(), tempfile.TemporaryFile() def on_death(exit_status: int, err: Exception | None) -> None: with stdout, stderr: if err: responder.send_error(f'Failed to run: {cmdline} with err: {err}') else: exit_code = os.waitstatus_to_exitcode(exit_status) stdout.seek(0) stderr.seek(0) responder.send_data({ 'stdout': standard_b64encode(stdout.read()).decode('ascii'), 'stderr': standard_b64encode(stderr.read()).decode('ascii'), 'exit_code': exit_code, 'exit_status': exit_status, }) env: dict[str, str] = {} for x in payload_get('env') or (): for k, v in parse_env(x, env): env[k] = v boss.run_background_process( cmdline, env=env, stdin=stdin_data, stdout=stdout.fileno(), stderr=stderr.fileno(), notify_on_death=on_death, remote_control_passwords=rcp, allow_remote_control=allow_remote_control ) return AsyncResponse() run = Run() kitty-0.41.1/kitty/rc/scroll_window.py0000664000175000017510000001017014773370543017315 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import ScrollWindowRCOptions as CLIOptions class ScrollWindow(RemoteCommand): protocol_spec = __doc__ = ''' amount+/list.scroll_amount: The amount to scroll, a two item list with the first item being \ either a number or the keywords, start and end. \ And the second item being either 'p' for pages or 'l' for lines or 'u' for unscrolling by lines, or 'r' for scrolling ot prompt. match/str: The window to scroll ''' short_desc = 'Scroll the specified windows' desc = ( 'Scroll the specified windows, if no window is specified, scroll the window this command is run inside.' ' :italic:`SCROLL_AMOUNT` can be either the keywords :code:`start` or :code:`end` or an' ' argument of the form :italic:`[unit][+-]`. :code:`unit` can be :code:`l` for lines, :code:`p` for pages,' ' :code:`u` for unscroll and :code:`r` for scroll to prompt. If unspecifed, :code:`l` is the default.' ' For example, :code:`30` will scroll down 30 lines, :code:`2p-`' ' will scroll up 2 pages and :code:`0.5p` will scroll down half page.' ' :code:`3u` will *unscroll* by 3 lines, which means that 3 lines will move from the' ' scrollback buffer onto the top of the screen. :code:`1r-` will scroll to the previous prompt and 1r to the next prompt.' ' See :ac:`scroll_to_prompt` for details on how scrolling to prompt works.' ) options_spec = MATCH_WINDOW_OPTION + '''\n --no-response type=bool-set default=false Don't wait for a response indicating the success of the action. Note that using this option means that you will not be notified of failures. ''' args = RemoteCommand.Args(spec='SCROLL_AMOUNT', count=1, special_parse='parse_scroll_amount(args[0])', json_field='amount') def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: if len(args) < 1: self.fatal('Scroll amount must be specified') amt = args[0] amount: tuple[str | float, str | None] = (amt, None) if amt not in ('start', 'end'): pages = 'p' in amt unscroll = 'u' in amt prompt = 'r' in amt mult = -1 if amt.endswith('-') and not unscroll else 1 q = float(amt.rstrip('+-plur')) if not pages and not q.is_integer(): self.fatal('The number must be an integer') amount = q * mult, 'p' if pages else ('u' if unscroll else ('r' if prompt else 'l')) # defaults to scroll the window this command is run in return {'match': opts.match, 'amount': amount, 'self': True} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: amt = payload_get('amount') for window in self.windows_for_match_payload(boss, window, payload_get): if window: if amt[0] in ('start', 'end'): getattr(window, {'start': 'scroll_home'}.get(amt[0], 'scroll_end'))() else: amt, unit = amt if unit == 'u': window.screen.reverse_scroll(int(abs(amt)), True) elif unit == 'r': window.scroll_to_prompt(int(amt)) else: unit = 'page' if unit == 'p' else 'line' if unit == 'page' and not isinstance(amt, int) and not amt.is_integer(): amt = round(window.screen.lines * amt) unit = 'line' direction = 'up' if amt < 0 else 'down' func = getattr(window, f'scroll_{unit}_{direction}') for i in range(int(abs(amt))): func() return None scroll_window = ScrollWindow() kitty-0.41.1/kitty/rc/select_window.py0000664000175000017510000000621414773370543017302 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING, Optional from kitty.types import AsyncResponse from .base import MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import SelectWindowRCOptions as CLIOptions from kitty.tabs import Tab class SelectWindow(RemoteCommand): protocol_spec = __doc__ = ''' match/str: The tab to open the new window in self/bool: Boolean, if True use tab the command was run in title/str: A title for this selection exclude_active/bool: Exclude the currently active window from the list to pick reactivate_prev_tab/bool: Reactivate the previously activated tab when finished ''' short_desc = 'Visually select a window in the specified tab' desc = ( 'Prints out the id of the selected window. Other commands' ' can then be chained to make use of it.' ) options_spec = MATCH_TAB_OPTION + '\n\n' + '''\ --response-timeout type=float default=60 The time in seconds to wait for the user to select a window. --self type=bool-set Select window from the tab containing the window this command is run in, instead of the active tab. --title A title that will be displayed to the user to describe what this selection is for. --exclude-active type=bool-set Exclude the currently active window from the list of windows to pick. --reactivate-prev-tab type=bool-set When the selection is finished, the tab in the same OS window that was activated before the selection will be reactivated. The last activated OS window will also be refocused. ''' is_asynchronous = True def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: ans = {'self': opts.self, 'match': opts.match, 'title': opts.title, 'exclude_active': opts.exclude_active, 'reactivate_prev_tab': opts.reactivate_prev_tab} return ans def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: responder = self.create_async_responder(payload_get, window) def callback(tab: Optional['Tab'], window: Window | None) -> None: if window: responder.send_data(window.id) else: responder.send_error('No window selected') for tab in self.tabs_for_match_payload(boss, window, payload_get): if tab: if payload_get('exclude_active'): wids = tab.all_window_ids_except_active_window else: wids = set() boss.visual_window_select_action( tab, callback, payload_get('title') or 'Choose window', only_window_ids=wids, reactivate_prev_tab=payload_get('reactivate_prev_tab') ) break return AsyncResponse() def cancel_async_request(self, boss: 'Boss', window: Optional['Window'], payload_get: PayloadGetType) -> None: boss.cancel_current_visual_select() select_window = SelectWindow() kitty-0.41.1/kitty/rc/send_key.py0000664000175000017510000000474514773370543016244 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import ( MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window, ) if TYPE_CHECKING: from kitty.cli_stub import SendKeyRCOptions as CLIOptions class SendKey(RemoteCommand): disallow_responses = True protocol_spec = __doc__ = ''' keys+/list.str: The keys to send match/str: A string indicating the window to send text to match_tab/str: A string indicating the tab to send text to all/bool: A boolean indicating all windows should be matched. exclude_active/bool: A boolean that prevents sending text to the active window ''' short_desc = 'Send arbitrary key presses to the specified windows' desc = ( 'Send arbitrary key presses to specified windows. All specified keys are sent first as press events' ' then as release events in reverse order. Keys are sent to the programs running in the windows.' ' They are sent only if the current keyboard mode for the program supports the particular key.' ' For example: send-key ctrl+a ctrl+b. Note that errors are not reported, for technical reasons,' ' so send-key always succeeds, even if no key was sent to any window.' ) # since send-key can send data over the tty to the window in which it was # run --no-reponse is always in effect for it, hence errors are not # reported. options_spec = MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t') + '''\n --all type=bool-set Match all windows. --exclude-active type=bool-set Do not send text to the active window, even if it is one of the matched windows. ''' args = RemoteCommand.Args(spec='[KEYS TO SEND ...]', json_field='keys') def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: ret = {'match': opts.match, 'keys': args, 'match_tab': opts.match_tab, 'all': opts.all, 'exclude_active': opts.exclude_active} return ret def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: windows = self.windows_for_payload(boss, None, payload_get, window_match_name='match') keys = payload_get('keys') for w in windows: w.send_key(*keys) return None send_key = SendKey() kitty-0.41.1/kitty/rc/send_text.py0000664000175000017510000002346714773370543016442 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import base64 import sys from typing import TYPE_CHECKING, Any from kitty.fast_data_types import KeyEvent as WindowSystemKeyEvent from kitty.fast_data_types import get_boss from kitty.key_encoding import decode_key_event_as_window_system_key from kitty.options.utils import parse_send_text_bytes from kitty.utils import sanitize_for_bracketed_paste from .base import ( MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, CmdGenerator, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window, ) if TYPE_CHECKING: from kitty.cli_stub import SendTextRCOptions as CLIOptions class Session: id: str window_ids: set[int] def __init__(self, id: str): self.id = id self.window_ids = set() sessions_map: dict[str, Session] = {} class SessionAction: def __init__(self, sid: str): self.sid = sid class ClearSession(SessionAction): def __call__(self, *a: Any) -> None: s = sessions_map.pop(self.sid, None) if s is not None: boss = get_boss() for wid in s.window_ids: qw = boss.window_id_map.get(wid) if qw is not None: qw.screen.render_unfocused_cursor = False class FocusChangedSession(SessionAction): def __call__(self, window: Window, focused: bool) -> None: s = sessions_map.get(self.sid) if s is not None: boss = get_boss() for wid in s.window_ids: qw = boss.window_id_map.get(wid) if qw is not None: qw.screen.render_unfocused_cursor = focused class SendText(RemoteCommand): disallow_responses = True protocol_spec = __doc__ = ''' data+/str: The data being sent. Can be either: text: followed by text or base64: followed by standard base64 encoded bytes match/str: A string indicating the window to send text to match_tab/str: A string indicating the tab to send text to all/bool: A boolean indicating all windows should be matched. exclude_active/bool: A boolean that prevents sending text to the active window session_id/str: A string that identifies a "broadcast session" bracketed_paste/choices.disable.auto.enable: Whether to wrap the text in bracketed paste escape codes ''' short_desc = 'Send arbitrary text to specified windows' desc = ( 'Send arbitrary text to specified windows. The text follows Python' ' escaping rules. So you can use :link:`escapes `' " like :code:`'\\\\e'` to send control codes" " and :code:`'\\\\u21fa'` to send Unicode characters. Remember to use single-quotes otherwise" ' the backslash is interpreted as a shell escape character. If you use the :option:`kitten @ send-text --match` option' ' the text will be sent to all matched windows. By default, text is sent to' ' only the currently active window. Note that errors are not reported, for technical reasons,' ' so send-text always succeeds, even if no text was sent to any window.' ) # since send-text can send data over the tty to the window in which it was # run --no-reponse is always in effect for it, hence errors are not # reported. options_spec = MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t') + '''\n --all type=bool-set Match all windows. --exclude-active type=bool-set Do not send text to the active window, even if it is one of the matched windows. --stdin type=bool-set Read the text to be sent from :italic:`stdin`. Note that in this case the text is sent as is, not interpreted for escapes. If stdin is a terminal, you can press :kbd:`Ctrl+D` to end reading. --from-file Path to a file whose contents you wish to send. Note that in this case the file contents are sent as is, not interpreted for escapes. --bracketed-paste choices=disable,auto,enable default=disable When sending text to a window, wrap the text in bracketed paste escape codes. The default is to not do this. A value of :code:`auto` means, bracketed paste will be used only if the program running in the window has turned on bracketed paste mode. ''' args = RemoteCommand.Args(spec='[TEXT TO SEND]', json_field='data', special_parse='+session_id:parse_send_text(io_data, args)') def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: limit = 1024 ret = { 'match': opts.match, 'data': '', 'match_tab': opts.match_tab, 'all': opts.all, 'exclude_active': opts.exclude_active, 'bracketed_paste': opts.bracketed_paste, } def pipe() -> CmdGenerator: if sys.stdin.isatty(): ret['exclude_active'] = True keep_going = True from kitty.utils import TTYIO with TTYIO(read_with_timeout=False) as tty: while keep_going: if not tty.wait_till_read_available(): break data = tty.read(limit) if not data: break decoded_data = data.decode('utf-8') if '\x04' in decoded_data: decoded_data = decoded_data[:decoded_data.index('\x04')] keep_going = False ret['data'] = f'text:{decoded_data}' yield ret else: while True: data = sys.stdin.buffer.read(limit) if not data: break ret['data'] = f'base64:{base64.standard_b64encode(data).decode("ascii")}' yield ret def chunks(text: str) -> CmdGenerator: data = parse_send_text_bytes(text) while data: b = base64.standard_b64encode(data[:limit]).decode("ascii") ret['data'] = f'base64:{b}' yield ret data = data[limit:] def file_pipe(path: str) -> CmdGenerator: with open(path, 'rb') as f: while True: data = f.read(limit) if not data: break ret['data'] = f'base64:{base64.standard_b64encode(data).decode("ascii")}' yield ret sources = [] if opts.stdin: sources.append(pipe()) if opts.from_file: sources.append(file_pipe(opts.from_file)) text = ' '.join(args) sources.append(chunks(text)) def chain() -> CmdGenerator: for src in sources: yield from src return chain() def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: sid = payload_get('session_id', '') windows = self.windows_for_payload(boss, window, payload_get, window_match_name='match') pdata: str = payload_get('data') encoding, _, q = pdata.partition(':') session = '' if encoding == 'text': data: bytes | WindowSystemKeyEvent = q.encode('utf-8') elif encoding == 'base64': data = base64.standard_b64decode(q) elif encoding == 'kitty-key': bdata = base64.standard_b64decode(q) candidate = decode_key_event_as_window_system_key(bdata.decode('ascii')) if candidate is None: raise ValueError(f'Could not decode window system key: {q}') data = candidate elif encoding == 'session': session = q else: raise TypeError(f'Invalid encoding for send-text data: {encoding}') exclude_active = payload_get('exclude_active') actual_windows = (w for w in windows if w is not None and (not exclude_active or w is not boss.active_window)) def create_or_update_session() -> Session: s = sessions_map.setdefault(sid, Session(sid)) return s if session == 'end': s = create_or_update_session() for w in actual_windows: w.screen.render_unfocused_cursor = False s.window_ids.discard(w.id) ClearSession(sid)() elif session == 'start': s = create_or_update_session() if window is not None: def is_ok(x: Any) -> bool: return not isinstance(x, SessionAction) or x.sid != sid window.actions_on_removal = list(filter(is_ok, window.actions_on_removal)) window.actions_on_focus_change = list(filter(is_ok, window.actions_on_focus_change)) window.actions_on_removal.append(ClearSession(sid)) window.actions_on_focus_change.append(FocusChangedSession(sid)) for w in actual_windows: w.screen.render_unfocused_cursor = True s.window_ids.add(w.id) else: bp = payload_get('bracketed_paste') if sid: s = create_or_update_session() for w in actual_windows: if sid: w.screen.render_unfocused_cursor = True s.window_ids.add(w.id) if isinstance(data, WindowSystemKeyEvent): kdata = w.encoded_key(data) if kdata: w.write_to_child(kdata) else: if bp == 'enable' or (bp == 'auto' and w.screen.in_bracketed_paste_mode): data = b'\x1b[200~' + sanitize_for_bracketed_paste(data) + b'\x1b[201~' w.write_to_child(data) return None send_text = SendText() kitty-0.41.1/kitty/rc/set_background_image.py0000664000175000017510000001070114773370543020564 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import os from base64 import standard_b64decode, standard_b64encode from io import BytesIO from typing import TYPE_CHECKING from kitty.types import AsyncResponse from kitty.utils import is_png from .base import ( MATCH_WINDOW_OPTION, SUPPORTED_IMAGE_FORMATS, ArgsType, Boss, CmdGenerator, ImageCompletion, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window, ) if TYPE_CHECKING: from kitty.cli_stub import SetBackgroundImageRCOptions as CLIOptions layout_choices = 'tiled,scaled,mirror-tiled,clamped,configured' class SetBackgroundImage(RemoteCommand): protocol_spec = __doc__ = ''' data+/str: Chunk of at most 512 bytes of PNG data, base64 encoded. Must send an empty chunk to indicate end of image. \ Or the special value - to indicate image must be removed. match/str: Window to change opacity in layout/choices.{layout_choices.replace(",", ".")}: The image layout all/bool: Boolean indicating operate on all windows configured/bool: Boolean indicating if the configured value should be changed ''' short_desc = 'Set the background image' desc = ( 'Set the background image for the specified OS windows. You must specify the path to an image that' ' will be used as the background. If you specify the special value :code:`none` then any existing image will' ' be removed. Supported image formats are: ' ) + ', '.join(SUPPORTED_IMAGE_FORMATS) options_spec = f'''\ --all -a type=bool-set By default, background image is only changed for the currently active OS window. This option will cause the image to be changed in all windows. --configured -c type=bool-set Change the configured background image which is used for new OS windows. --layout type=choices choices={layout_choices} default=configured How the image should be displayed. A value of :code:`configured` will use the configured value. --no-response type=bool-set default=false Don't wait for a response from kitty. This means that even if setting the background image failed, the command will exit with a success code. ''' + '\n\n' + MATCH_WINDOW_OPTION args = RemoteCommand.Args(spec='PATH_TO_PNG_IMAGE', count=1, json_field='data', special_parse='!read_window_logo(io_data, args[0])', completion=ImageCompletion) reads_streaming_data = True def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: if len(args) != 1: self.fatal('Must specify path to exactly one PNG image') path = os.path.expanduser(args[0]) import secrets ret = { 'match': opts.match, 'configured': opts.configured, 'layout': opts.layout, 'all': opts.all, 'stream_id': secrets.token_urlsafe(), } if path.lower() == 'none': ret['data'] = '-' return ret if not is_png(path): self.fatal(f'{path} is not a PNG image') def file_pipe(path: str) -> CmdGenerator: with open(path, 'rb') as f: while True: data = f.read(512) if not data: break ret['data'] = standard_b64encode(data).decode('ascii') yield ret ret['data'] = '' yield ret return file_pipe(path) def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: data = payload_get('data') windows = self.windows_for_payload(boss, window, payload_get, window_match_name='match') os_windows = tuple({w.os_window_id for w in windows if w}) layout = payload_get('layout') if data == '-': path = None tfile = BytesIO() else: q = self.handle_streamed_data(standard_b64decode(data) if data else b'', payload_get) if isinstance(q, AsyncResponse): return q path = '/image/from/remote/control' tfile = q try: boss.set_background_image(path, os_windows, payload_get('configured'), layout, tfile.getvalue()) except ValueError as err: err.hide_traceback = True # type: ignore raise return None set_background_image = SetBackgroundImage() kitty-0.41.1/kitty/rc/set_background_opacity.py0000664000175000017510000000604514773370543021160 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import ( MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, OpacityError, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window, ) if TYPE_CHECKING: from kitty.cli_stub import SetBackgroundOpacityRCOptions as CLIOptions class SetBackgroundOpacity(RemoteCommand): protocol_spec = __doc__ = ''' opacity+/float: A number between 0 and 1 match_window/str: Window to change opacity in match_tab/str: Tab to change opacity in all/bool: Boolean indicating operate on all windows toggle/bool: Boolean indicating if opacity should be toggled between the default and the specified value ''' short_desc = 'Set the background opacity' desc = ( 'Set the background opacity for the specified windows. This will only work if you have turned on' ' :opt:`dynamic_background_opacity` in :file:`kitty.conf`. The background opacity affects all kitty windows in a' ' single OS window. For example::\n\n' ' kitten @ set-background-opacity 0.5' ) options_spec = '''\ --all -a type=bool-set By default, background opacity are only changed for the currently active OS window. This option will cause background opacity to be changed in all windows. --toggle type=bool-set When specified, the background opacity for the matching OS windows will be reset to default if it is currently equal to the specified value, otherwise it will be set to the specified value. ''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t') args = RemoteCommand.Args(spec='OPACITY', count=1, json_field='opacity', special_parse='parse_opacity(args[0])') def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: opacity = max(0, min(float(args[0]), 1)) return { 'opacity': opacity, 'match_window': opts.match, 'all': opts.all, 'match_tab': opts.match_tab, 'toggle': opts.toggle, } def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: from kitty.fast_data_types import background_opacity_of, get_options opts = get_options() if not opts.dynamic_background_opacity: raise OpacityError('You must turn on the dynamic_background_opacity option in kitty.conf to be able to set background opacity') windows = self.windows_for_payload(boss, window, payload_get) for os_window_id in {w.os_window_id for w in windows if w}: val: float = payload_get('opacity') or 0. if payload_get('toggle'): current = background_opacity_of(os_window_id) if current == val: val = opts.background_opacity boss._set_os_window_background_opacity(os_window_id, val) return None set_background_opacity = SetBackgroundOpacity() kitty-0.41.1/kitty/rc/set_colors.py0000664000175000017510000001043514773370543016610 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from kitty.cli import emph from kitty.fast_data_types import Color from .base import ( MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, ParsingOfArgsFailed, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window, ) if TYPE_CHECKING: from kitty.cli_stub import SetColorsRCOptions as CLIOptions class SetColors(RemoteCommand): protocol_spec = __doc__ = ''' colors+/dict.colors: An object mapping names to colors as 24-bit RGB integers or null for nullable colors. Or a string for transparent_background_colors. match_window/str: Window to change colors in match_tab/str: Tab to change colors in all/bool: Boolean indicating change colors everywhere or not configured/bool: Boolean indicating whether to change the configured colors. Must be True if reset is True reset/bool: Boolean indicating colors should be reset to startup values ''' short_desc = 'Set terminal colors' desc = ( 'Set the terminal colors for the specified windows/tabs (defaults to active window).' ' You can either specify the path to a conf file' ' (in the same format as :file:`kitty.conf`) to read the colors from or you can specify individual colors,' ' for example::\n\n' ' kitten @ set-colors foreground=red background=white' ) options_spec = '''\ --all -a type=bool-set By default, colors are only changed for the currently active window. This option will cause colors to be changed in all windows. --configured -c type=bool-set Also change the configured colors (i.e. the colors kitty will use for new windows or after a reset). --reset type=bool-set Restore all colors to the values they had at kitty startup. Note that if you specify this option, any color arguments are ignored and :option:`kitten @ set-colors --configured` and :option:`kitten @ set-colors --all` are implied. ''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t') args = RemoteCommand.Args(spec='COLOR_OR_FILE ...', json_field='colors', special_parse='parse_colors_and_files(args)', completion=RemoteCommand.CompletionSpec.from_string('type:file group:"CONF files", ext:conf')) def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: final_colors: dict[str, int | None | str] = {} transparent_background_colors: tuple[tuple[Color, float], ...] = () from kitty.colors import parse_colors if not opts.reset: try: fc, transparent_background_colors = parse_colors(args) except FileNotFoundError as err: raise ParsingOfArgsFailed(f'The colors configuration file {emph(err.filename)} was not found.') from err except Exception as err: raise ParsingOfArgsFailed(str(err)) from err final_colors.update(fc) if transparent_background_colors: final_colors['transparent_background_colors'] = ' '.join(f'{c.as_sharp}@{f}' for c, f in transparent_background_colors) ans = { 'match_window': opts.match, 'match_tab': opts.match_tab, 'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset, 'colors': final_colors, 'reset': opts.reset, } return ans def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: from kitty.colors import patch_colors windows = self.windows_for_payload(boss, window, payload_get) colors: dict[str, int | None] = payload_get('colors') or {} if payload_get('reset'): colors = {k: None if v is None else int(v) for k, v in boss.color_settings_at_startup.items()} tbc = colors.get('transparent_background_colors') if tbc: from kitty.options.utils import transparent_background_colors parsed_tbc = transparent_background_colors(str(tbc)) else: parsed_tbc = () patch_colors(colors, parsed_tbc, bool(payload_get('configured')), windows=windows) return None set_colors = SetColors() kitty-0.41.1/kitty/rc/set_enabled_layouts.py0000664000175000017510000000521214773370543020456 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Iterable from typing import TYPE_CHECKING from kitty.fast_data_types import get_options from kitty.options.utils import parse_layout_names from .base import MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import SetEnabledLayoutsRCOptions as CLIOptions def layout_names() -> Iterable[str]: from kitty.layout.interface import all_layouts return all_layouts.keys() class SetEnabledLayouts(RemoteCommand): protocol_spec = __doc__ = ''' layouts+/list.str: The list of layout names match/str: Which tab to change the layout of configured/bool: Boolean indicating whether to change the configured value ''' short_desc = 'Set the enabled layouts in tabs' desc = ( 'Set the enabled layouts in the specified tabs (or the active tab if not specified).' ' You can use special match value :code:`all` to set the enabled layouts in all tabs. If the' ' current layout of the tab is not included in the enabled layouts, its layout is changed' ' to the first enabled layout.' ) options_spec = MATCH_TAB_OPTION + '''\n\n --configured type=bool-set Change the default enabled layout value so that the new value takes effect for all newly created tabs as well. ''' args = RemoteCommand.Args( spec='LAYOUT ...', minimum_count=1, json_field='layouts', completion=RemoteCommand.CompletionSpec.from_string('type:keyword group:"Layout" kwds:' + ','.join(layout_names())), args_choices=layout_names) def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: if len(args) < 1: self.fatal('At least one layout must be specified') a: list[str] = [] for x in args: a.extend(y.strip() for y in x.split(',')) try: layouts = parse_layout_names(a) except ValueError as err: self.fatal(str(err)) return {'layouts': layouts, 'match': opts.match} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: tabs = self.tabs_for_match_payload(boss, window, payload_get) layouts = parse_layout_names(payload_get('layouts')) if payload_get('configured'): get_options().enabled_layouts = list(layouts) for tab in tabs: if tab: tab.set_enabled_layouts(layouts) return None set_enabled_layouts = SetEnabledLayouts() kitty-0.41.1/kitty/rc/set_font_size.py0000664000175000017510000000436114773370543017310 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import SetFontSizeRCOptions as CLIOptions class SetFontSize(RemoteCommand): protocol_spec = __doc__ = ''' size+/float: The new font size in pts (a positive number). If absent is assumed to be zero which means reset to default. all/bool: Boolean whether to change font size in the current window or all windows increment_op/choices.+.-: The string ``+`` or ``-`` to interpret size as an increment ''' short_desc = 'Set the font size in the active top-level OS window' desc = ( 'Sets the font size to the specified size, in pts. Note' ' that in kitty all sub-windows in the same OS window' ' must have the same font size. A value of zero' ' resets the font size to default. Prefixing the value' ' with a :code:`+` or :code:`-` increments the font size by the specified' ' amount. Use -- before using - to have it not mistaken for a option. For example:' ' kitten @ set-font-size -- -2' ) args = RemoteCommand.Args(spec='FONT_SIZE', count=1, special_parse='+increment_op:parse_set_font_size(args[0], &payload)', json_field='size') options_spec = '''\ --all -a type=bool-set By default, the font size is only changed in the active OS window, this option will cause it to be changed in all OS windows. It also changes the font size for any newly created OS Windows in the future. ''' def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: if not args: self.fatal('No font size specified') fs = args[0] inc = fs[0] if fs and fs[0] in '+-' else None return {'size': abs(float(fs)), 'all': opts.all, 'increment_op': inc} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: boss.change_font_size( payload_get('all'), payload_get('increment_op'), payload_get('size') or 0) return None set_font_size = SetFontSize() kitty-0.41.1/kitty/rc/set_spacing.py0000664000175000017510000001230414773370543016730 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Iterable from typing import TYPE_CHECKING from .base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import SetSpacingRCOptions as CLIOptions from kitty.options.types import Options def patch_window_edges(w: Window, s: dict[str, float | None]) -> None: for k, v in s.items(): which, edge = k.lower().split('-', 1) if edge == 'left': w.patch_edge_width(which, 'left', v) elif edge == 'right': w.patch_edge_width(which, 'right', v) elif edge == 'top': w.patch_edge_width(which, 'top', v) elif edge == 'bottom': w.patch_edge_width(which, 'bottom', v) def patch_configured_edges(opts: 'Options', s: dict[str, float | None]) -> None: for k, val in s.items(): if val is None: continue which, edge = k.lower().split('-', 1) q = f'window_{which}_width' new_edges = getattr(opts, q)._replace(**{edge: val}) setattr(opts, q, new_edges) def parse_spacing_settings(args: Iterable[str]) -> dict[str, float | None]: mapper: dict[str, list[str]] = {} for q in ('margin', 'padding'): mapper[q] = f'{q}-left {q}-top {q}-right {q}-bottom'.split() mapper[f'{q}-h'] = mapper[f'{q}-horizontal'] = f'{q}-left {q}-right'.split() mapper[f'{q}-v'] = mapper[f'{q}-vertical'] = f'{q}-top {q}-bottom'.split() for edge in ('left', 'top', 'right', 'bottom'): mapper[f'{q}-{edge}'] = [f'{q}-{edge}'] settings: dict[str, float | None] = {} for spec in args: parts = spec.split('=', 1) if len(parts) != 2: raise ValueError(f'{spec} is not a valid setting') which = mapper.get(parts[0].lower()) if not which: raise ValueError(f'{parts[0]} is not a valid edge specification') if parts[1].lower() == 'default': val = None else: try: val = float(parts[1]) except Exception: raise ValueError(f'{parts[1]} is not a number') for q in which: settings[q] = val return settings class SetSpacing(RemoteCommand): protocol_spec = __doc__ = ''' settings+/dict.spacing: An object mapping margins/paddings using canonical form {'margin-top': 50, 'padding-left': null} etc match_window/str: Window to change paddings and margins in match_tab/str: Tab to change paddings and margins in all/bool: Boolean indicating change paddings and margins everywhere or not configured/bool: Boolean indicating whether to change the configured paddings and margins. Must be True if reset is True ''' short_desc = 'Set window paddings and margins' desc = ( 'Set the paddings and margins for the specified windows (defaults to active window).' ' For example: :code:`margin=20` or :code:`padding-left=10` or :code:`margin-h=30`. The shorthand form sets' ' all values, the :code:`*-h` and :code:`*-v` variants set horizontal and vertical values.' ' The special value :code:`default` resets to using the default value.' ' If you specify a tab rather than a window, all windows in that tab are affected.' ) options_spec = '''\ --all -a type=bool-set By default, settings are only changed for the currently active window. This option will cause paddings and margins to be changed in all windows. --configured -c type=bool-set Also change the configured paddings and margins (i.e. the settings kitty will use for new windows). ''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t') args = RemoteCommand.Args(spec='MARGIN_OR_PADDING ...', minimum_count=1, json_field='settings', special_parse='parse_set_spacing(args)') def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: if not args: self.fatal('At least one setting must be specified') try: settings = parse_spacing_settings(args) except Exception as e: self.fatal(str(e)) ans = { 'match_window': opts.match, 'match_tab': opts.match_tab, 'all': opts.all, 'configured': opts.configured, 'settings': settings } return ans def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: windows = self.windows_for_payload(boss, window, payload_get) settings: dict[str, float | None] = payload_get('settings') dirtied_tabs = {} from kitty.fast_data_types import get_options if payload_get('configured'): patch_configured_edges(get_options(), settings) for w in windows: if w: patch_window_edges(w, settings) tab = w.tabref() if tab is not None: dirtied_tabs[tab.id] = tab for tab in dirtied_tabs.values(): tab.relayout() return None set_spacing = SetSpacing() kitty-0.41.1/kitty/rc/set_tab_color.py0000664000175000017510000000570014773370543017252 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from kitty.rgb import to_color from .base import MATCH_TAB_OPTION, ArgsType, Boss, ParsingOfArgsFailed, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import SetTabColorRCOptions as CLIOptions valid_color_names = frozenset('active_fg active_bg inactive_fg inactive_bg'.split()) def parse_colors(args: ArgsType) -> dict[str, int | None]: ans: dict[str, int | None] = {} for spec in args: key, val = spec.split('=', 1) key = key.lower() if key.lower() not in valid_color_names: raise KeyError(f'{key} is not a valid color name') if val.lower() == 'none': col: int | None = None else: q = to_color(val, validate=True) if q is not None: col = int(q) ans[key.lower()] = col return ans class SetTabColor(RemoteCommand): protocol_spec = __doc__ = ''' colors+/dict.colors: An object mapping names to colors as 24-bit RGB integers. A color value of null indicates it should be unset. match/str: Which tab to change the color of self/bool: Boolean indicating whether to use the tab of the window the command is run in ''' short_desc = 'Change the color of the specified tabs in the tab bar' desc = f''' {short_desc} The foreground and background colors when active and inactive can be overridden using this command. \ The syntax for specifying colors is: active_fg=color active_bg=color inactive_fg=color \ inactive_bg=color. Where color can be either a color name or a value of the form #rrggbb or \ the keyword NONE to revert to using the default colors. ''' options_spec = MATCH_TAB_OPTION + '''\n --self type=bool-set Close the tab this command is run in, rather than the active tab. ''' args = RemoteCommand.Args(spec='COLORS', json_field='colors', minimum_count=1, special_parse='parse_tab_colors(args)') def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: try: colors = parse_colors(args) except Exception as err: raise ParsingOfArgsFailed(str(err)) from err if not colors: raise ParsingOfArgsFailed('No colors specified') return {'match': opts.match, 'self': opts.self, 'colors': colors} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: colors = payload_get('colors') s = {k: None if colors[k] is None else int(colors[k]) for k in valid_color_names if k in colors} for tab in self.tabs_for_match_payload(boss, window, payload_get): if tab: for k, v in s.items(): setattr(tab, k, v) tab.mark_tab_bar_dirty() return None set_tab_color = SetTabColor() kitty-0.41.1/kitty/rc/set_tab_title.py0000664000175000017510000000301414773370543017251 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import SetTabTitleRCOptions as CLIOptions class SetTabTitle(RemoteCommand): protocol_spec = __doc__ = ''' title+/str: The new title match/str: Which tab to change the title of ''' short_desc = 'Set the tab title' desc = ( 'Set the title for the specified tabs. If you use the :option:`kitten @ set-tab-title --match` option' ' the title will be set for all matched tabs. By default, only the tab' ' in which the command is run is affected. If you do not specify a title, the' ' title of the currently active window in the tab is used.' ) options_spec = MATCH_TAB_OPTION args = RemoteCommand.Args(spec='TITLE ...', json_field='title', special_parse='expand_ansi_c_escapes_in_args(args...)') def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'title': ' '.join(args), 'match': opts.match} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: for tab in self.tabs_for_match_payload(boss, window, payload_get): if tab: tab.set_title(payload_get('title')) return None set_tab_title = SetTabTitle() kitty-0.41.1/kitty/rc/set_user_vars.py0000664000175000017510000000376214773370543017325 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import SetUserVarsRCOptions as CLIOptions class SetUserVars(RemoteCommand): protocol_spec = __doc__ = ''' var/list.str: List of user variables of the form NAME=VALUE match/str: Which windows to change the title in ''' short_desc = 'Set user variables on a window' desc = ( 'Set user variables for the specified windows. If you use the :option:`kitten @ set-user-vars --match` option' ' the variables will be set for all matched windows. By default, only the window' ' in which the command is run is affected. If you do not specify any variables, the' ' current variables are printed out, one per line. To unset a variable specify just its name.' ) options_spec = MATCH_WINDOW_OPTION args = RemoteCommand.Args(json_field='var', spec='[NAME=VALUE ...]') def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: return {'match': opts.match, 'var': args, 'self': True} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: val = {} for x in payload_get('var') or (): a, sep, b = x.partition('=') if sep: val[a] = b else: val[a] = None lines = [] for window in self.windows_for_match_payload(boss, window, payload_get): if window: if val: for k, v in val.items(): window.set_user_var(k, v) else: lines.append('\n'.join(f'{k}={v}' for k, v in window.user_vars.items())) return '\n\n'.join(lines) set_user_vars = SetUserVars() kitty-0.41.1/kitty/rc/set_window_logo.py0000664000175000017510000001023214773370543017631 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal import os from base64 import standard_b64decode, standard_b64encode from io import BytesIO from typing import TYPE_CHECKING from kitty.types import AsyncResponse from kitty.utils import is_png from .base import ( MATCH_WINDOW_OPTION, SUPPORTED_IMAGE_FORMATS, ArgsType, Boss, CmdGenerator, ImageCompletion, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window, ) if TYPE_CHECKING: from kitty.cli_stub import SetWindowLogoRCOptions as CLIOptions class SetWindowLogo(RemoteCommand): protocol_spec = __doc__ = ''' data+/str: Chunk of PNG data, base64 encoded no more than 2048 bytes. Must send an empty chunk to indicate end of image. \ Or the special value :code:`-` to indicate image must be removed. position/str: The logo position as a string, empty string means default alpha/float: The logo alpha between :code:`0` and :code:`1`. :code:`-1` means use default match/str: Which window to change the logo in self/bool: Boolean indicating whether to act on the window the command is run in ''' short_desc = 'Set the window logo' desc = ( 'Set the logo image for the specified windows. You must specify the path to an image that' ' will be used as the logo. If you specify the special value :code:`none` then any existing logo will' ' be removed. Supported image formats are: ' ) + ', '.join(SUPPORTED_IMAGE_FORMATS) options_spec = MATCH_WINDOW_OPTION + '''\n --self type=bool-set Act on the window this command is run in, rather than the active window. --position The position for the window logo. See :opt:`window_logo_position`. --alpha type=float default=-1 The amount the window logo should be faded into the background. See :opt:`window_logo_position`. --no-response type=bool-set default=false Don't wait for a response from kitty. This means that even if setting the image failed, the command will exit with a success code. ''' args = RemoteCommand.Args(spec='PATH_TO_PNG_IMAGE', count=1, json_field='data', special_parse='!read_window_logo(io_data, args[0])', completion=ImageCompletion) reads_streaming_data = True def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: if len(args) != 1: self.fatal('Must specify path to exactly one PNG image') path = os.path.expanduser(args[0]) import secrets ret = { 'match': opts.match, 'self': opts.self, 'alpha': opts.alpha, 'position': opts.position, 'stream_id': secrets.token_urlsafe(), } if path.lower() == 'none': ret['data'] = '-' return ret if not is_png(path): self.fatal(f'{path} is not a PNG image') def file_pipe(path: str) -> CmdGenerator: with open(path, 'rb') as f: while True: data = f.read(512) if not data: break ret['data'] = standard_b64encode(data).decode('ascii') yield ret ret['data'] = '' yield ret return file_pipe(path) def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: data = payload_get('data') alpha = float(payload_get('alpha', '-1')) position = payload_get('position') or '' if data == '-': path = '' tfile = BytesIO() else: q = self.handle_streamed_data(standard_b64decode(data) if data else b'', payload_get) if isinstance(q, AsyncResponse): return q import hashlib path = '/from/remote/control/' + hashlib.sha1(q.getvalue()).hexdigest() tfile = q for window in self.windows_for_match_payload(boss, window, payload_get): if window: window.set_logo(path, position, alpha, tfile.getvalue()) return None set_window_logo = SetWindowLogo() kitty-0.41.1/kitty/rc/set_window_title.py0000664000175000017510000000461114773370543020016 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import SetWindowTitleRCOptions as CLIOptions class SetWindowTitle(RemoteCommand): protocol_spec = __doc__ = ''' title/str: The new title match/str: Which windows to change the title in temporary/bool: Boolean indicating if the change is temporary or permanent ''' short_desc = 'Set the window title' desc = ( 'Set the title for the specified windows. If you use the :option:`kitten @ set-window-title --match` option' ' the title will be set for all matched windows. By default, only the window' ' in which the command is run is affected. If you do not specify a title, the' ' last title set by the child process running in the window will be used.' ) options_spec = '''\ --temporary type=bool-set By default, the title will be permanently changed and programs running in the window will not be able to change it again. If you want to allow other programs to change it afterwards, use this option. ''' + '\n\n' + MATCH_WINDOW_OPTION args = RemoteCommand.Args(json_field='title', spec='[TITLE ...]', special_parse='expand_ansi_c_escapes_in_args(args...)') def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: ans = {'match': opts.match, 'temporary': opts.temporary} title = ' '.join(args) if title: ans['title'] = title # defaults to set the window title this command is run in ans['self'] = True return ans def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: title = payload_get('title') if payload_get('temporary') and title is not None: title = memoryview(title.encode('utf-8')) for window in self.windows_for_match_payload(boss, window, payload_get): if window: if payload_get('temporary'): window.override_title = None window.title_changed(title) else: window.set_title(title) return None set_window_title = SetWindowTitle() kitty-0.41.1/kitty/rc/signal_child.py0000664000175000017510000000427514773370543017061 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal from typing import TYPE_CHECKING from .base import MATCH_WINDOW_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window if TYPE_CHECKING: from kitty.cli_stub import SignalChildRCOptions as CLIOptions class SignalChild(RemoteCommand): protocol_spec = __doc__ = ''' signals+/list.str: The signals, a list of names, such as :code:`SIGTERM`, :code:`SIGKILL`, :code:`SIGUSR1`, etc. match/str: Which windows to send the signals to ''' short_desc = 'Send a signal to the foreground process in the specified windows' desc = ( 'Send one or more signals to the foreground process in the specified windows.' ' If you use the :option:`kitten @ signal-child --match` option' ' the signal will be sent for all matched windows. By default, only the active' ' window is affected. If you do not specify any signals, :code:`SIGINT` is sent by default.' ' You can also map :ac:`signal_child` to a shortcut in :file:`kitty.conf`, for example::\n\n' ' map f1 signal_child SIGTERM' ) options_spec = '''\ --no-response type=bool-set default=false Don't wait for a response indicating the success of the action. Note that using this option means that you will not be notified of failures. ''' + '\n\n' + MATCH_WINDOW_OPTION args = RemoteCommand.Args(json_field='signals', spec='[SIGNAL_NAME ...]', value_if_unspecified=('SIGINT',)) def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: # defaults to signal the window this command is run in return {'match': opts.match, 'self': True, 'signals': [x.upper() for x in args] or ['SIGINT']} def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType: import signal signals = tuple(getattr(signal, x) for x in payload_get('signals')) for window in self.windows_for_match_payload(boss, window, payload_get): if window: window.signal_child(*signals) return None signal_child = SignalChild() kitty-0.41.1/kitty/remote_control.py0000664000175000017510000004532314773370543017067 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import base64 import json import os import re import sys from collections.abc import Iterable, Iterator, Sequence from contextlib import suppress from functools import lru_cache, partial from time import time_ns from types import GeneratorType from typing import ( TYPE_CHECKING, Any, Optional, cast, ) from .cli import parse_args from .cli_stub import RCOptions from .constants import RC_ENCRYPTION_PROTOCOL_VERSION, appname, version from .fast_data_types import ( AES256GCMDecrypt, AES256GCMEncrypt, EllipticCurveKey, get_boss, get_options, monotonic, read_command_response, send_data_to_peer, ) from .rc.base import NoResponse, PayloadGetter, all_command_names, command_for_name from .types import AsyncResponse from .typing import BossType, WindowType from .utils import TTYIO, log_error, parse_address_spec, resolve_custom_file active_async_requests: dict[str, float] = {} active_streams: dict[str, str] = {} if TYPE_CHECKING: from .window import Window def encode_response_for_peer(response: Any) -> bytes: return b'\x1bP@kitty-cmd' + json.dumps(response).encode('utf-8') + b'\x1b\\' def parse_cmd(serialized_cmd: memoryview, encryption_key: EllipticCurveKey) -> dict[str, Any]: # See https://github.com/python/cpython/issues/74379 for why we cant use # memoryview directly :(( try: pcmd = json.loads(bytes(serialized_cmd)) except Exception: log_error('Failed to parse JSON payload of remote command, ignoring it') return {} if not isinstance(pcmd, dict) or 'version' not in pcmd: log_error('JSON payload of remote command is invalid, must be an object with a version field') return {} pcmd.pop('password', None) if 'encrypted' in pcmd: if pcmd.get('enc_proto', '1') != RC_ENCRYPTION_PROTOCOL_VERSION: log_error(f'Ignoring encrypted rc command with unsupported protocol: {pcmd.get("enc_proto")}') return {} pubkey = pcmd.get('pubkey', '') if not pubkey: log_error('Ignoring encrypted rc command without a public key') d = AES256GCMDecrypt(encryption_key.derive_secret(base64.b85decode(pubkey)), base64.b85decode(pcmd['iv']), base64.b85decode(pcmd['tag'])) data = d.add_data_to_be_decrypted(base64.b85decode(pcmd['encrypted']), True) pcmd = json.loads(data) if not isinstance(pcmd, dict) or 'version' not in pcmd: return {} delta = time_ns() - pcmd.pop('timestamp') if abs(delta) > 5 * 60 * 1e9: log_error( f'Ignoring encrypted rc command with timestamp {delta / 1e9:.1f} seconds from now.' ' Could be an attempt at a replay attack or an incorrect clock on a remote machine.') return {} return pcmd class CMDChecker: def __call__(self, pcmd: dict[str, Any], window: Optional['Window'], from_socket: bool, extra_data: dict[str, Any]) -> bool | None: return False @lru_cache(maxsize=64) def is_cmd_allowed_loader(path: str) -> CMDChecker: import runpy try: m = runpy.run_path(path) func: CMDChecker = m['is_cmd_allowed'] except Exception as e: log_error(f'Failed to load cmd check function from {path} with error: {e}') func = CMDChecker() return func @lru_cache(maxsize=1024) def fnmatch_pattern(pat: str) -> 're.Pattern[str]': from fnmatch import translate return re.compile(translate(pat)) def remote_control_allowed( pcmd: dict[str, Any], remote_control_passwords: dict[str, Sequence[str]] | None, window: Optional['Window'], extra_data: dict[str, Any] ) -> bool: if not remote_control_passwords: return True pw = pcmd.get('password', '') auth_items = remote_control_passwords.get(pw) if pw == '!': auth_items = None if auth_items is None: if '!' in remote_control_passwords: raise PermissionError() return False from .remote_control import password_authorizer pa = password_authorizer(auth_items) if not pa.is_cmd_allowed(pcmd, window, False, extra_data): raise PermissionError() return True class PasswordAuthorizer: def __init__(self, auth_items: Iterable[str]) -> None: self.command_patterns = [] self.function_checkers = [] self.name = '' for item in auth_items: if item.endswith('.py'): path = os.path.abspath(resolve_custom_file(item)) self.function_checkers.append(is_cmd_allowed_loader(path)) else: self.command_patterns.append(fnmatch_pattern(item)) def is_cmd_allowed(self, pcmd: dict[str, Any], window: Optional['Window'], from_socket: bool, extra_data: dict[str, Any]) -> bool: cmd_name = pcmd.get('cmd') if not cmd_name: return False if not self.function_checkers and not self.command_patterns: return True for x in self.command_patterns: if x.match(cmd_name) is not None: return True for f in self.function_checkers: try: ret = f(pcmd, window, from_socket, extra_data) except Exception as e: import traceback traceback.print_exc() log_error(f'There was an error using a custom RC auth function, blocking the remote command. Error: {e}') ret = False if ret is not None: return ret return False @lru_cache(maxsize=256) def password_authorizer(auth_items: frozenset[str]) -> PasswordAuthorizer: return PasswordAuthorizer(auth_items) user_password_allowed: dict[str, bool] = {} def is_cmd_allowed(pcmd: dict[str, Any], window: Optional['Window'], from_socket: bool, extra_data: dict[str, Any]) -> bool | None: sid = pcmd.get('stream_id', '') if sid and active_streams.get(sid, '') == pcmd['cmd']: return True if 'cancel_async' in pcmd and pcmd.get('async_id'): # we allow these without authentication as they are sent on error # conditions and we can't have users prompted for these. The worst side # effect of a malicious cancel_async request is that it can prevent # another async request from getting a result, if it knows the async_id # of that request. return True pw = pcmd.get('password', '') if not pw: auth_items = get_options().remote_control_password.get('') if auth_items is None: return False pa = password_authorizer(auth_items) return pa.is_cmd_allowed(pcmd, window, from_socket, extra_data) q = user_password_allowed.get(pw) if q is not None: return q auth_items = get_options().remote_control_password.get(pw) if auth_items is None: return None pa = password_authorizer(auth_items) return pa.is_cmd_allowed(pcmd, window, from_socket, extra_data) def set_user_password_allowed(pwd: str, allowed: bool = True) -> None: user_password_allowed[pwd] = allowed def close_active_stream(stream_id: str) -> None: active_streams.pop(stream_id, None) def handle_cmd( boss: BossType, window: WindowType | None, cmd: dict[str, Any], peer_id: int, self_window: WindowType | None ) -> dict[str, Any] | None | AsyncResponse: v = cmd['version'] no_response = cmd.get('no_response', False) if tuple(v)[:2] > version[:2]: if no_response: return None return {'ok': False, 'error': 'The kitty client you are using to send remote commands is newer than this kitty instance. This is not supported.'} c = command_for_name(cmd['cmd']) payload = cmd.get('payload') or {} payload['peer_id'] = peer_id async_id = str(cmd.get('async', '')) stream_id = str(cmd.get('stream_id', '')) stream = bool(cmd.get('stream', False)) if (stream or stream_id) and not c.reads_streaming_data: return {'ok': False, 'error': 'Streaming send of data is not supported for this command'} if stream_id: payload['stream_id'] = stream_id active_streams[stream_id] = cmd['cmd'] if len(active_streams) > 32: oldest = next(iter(active_streams)) del active_streams[oldest] if async_id: payload['async_id'] = async_id if 'cancel_async' in cmd: active_async_requests.pop(async_id, None) c.cancel_async_request(boss, self_window or window, PayloadGetter(c, payload)) return None active_async_requests[async_id] = monotonic() if len(active_async_requests) > 32: oldest = next(iter(active_async_requests)) del active_async_requests[oldest] try: ans = c.response_from_kitty(boss, self_window or window, PayloadGetter(c, payload)) except Exception: if no_response: # don't report errors if --no-response was used return None raise if isinstance(ans, NoResponse): return None if isinstance(ans, AsyncResponse): if stream: return {'ok': True, 'stream': True} return ans response: dict[str, Any] = {'ok': True} if ans is not None: response['data'] = ans if not no_response: return response return None global_options_spec = partial('''\ --to An address for the kitty instance to control. Corresponds to the address given to the kitty instance via the :option:`kitty --listen-on` option or the :opt:`listen_on` setting in :file:`kitty.conf`. If not specified, the environment variable :envvar:`KITTY_LISTEN_ON` is checked. If that is also not found, messages are sent to the controlling terminal for this process, i.e. they will only work if this process is run within a kitty window. --password A password to use when contacting kitty. This will cause kitty to ask the user for permission to perform the specified action, unless the password has been accepted before or is pre-configured in :file:`kitty.conf`. To use a blank password specify :option:`kitten @ --use-password` as :code:`always`. --password-file completion=type:file relative:conf kwds:- default=rc-pass A file from which to read the password. Trailing whitespace is ignored. Relative paths are resolved from the kitty configuration directory. Use - to read from STDIN. Use :code:`fd:num` to read from the file descriptor :code:`num`. Used if no :option:`kitten @ --password` is supplied. Defaults to checking for the :file:`rc-pass` file in the kitty configuration directory. --password-env default=KITTY_RC_PASSWORD The name of an environment variable to read the password from. Used if no :option:`kitten @ --password-file` is supplied. Defaults to checking the environment variable :envvar:`KITTY_RC_PASSWORD`. --use-password default=if-available choices=if-available,never,always If no password is available, kitty will usually just send the remote control command without a password. This option can be used to force it to :code:`always` or :code:`never` use the supplied password. If set to always and no password is provided, the blank password is used. '''.format, appname=appname) def encode_send(send: Any) -> bytes: es = ('@kitty-cmd' + json.dumps(send)).encode('ascii') return b'\x1bP' + es + b'\x1b\\' class SocketClosed(EOFError): pass class SocketIO: def __init__(self, to: str): self.family, self.address = parse_address_spec(to)[:2] def __enter__(self) -> None: import socket self.socket = socket.socket(self.family) self.socket.setblocking(True) self.socket.connect(self.address) def __exit__(self, *a: Any) -> None: import socket with suppress(OSError): # on some OSes such as macOS the socket is already closed at this point self.socket.shutdown(socket.SHUT_RDWR) self.socket.close() def send(self, data: bytes | Iterable[str | bytes]) -> None: import socket with self.socket.makefile('wb') as out: if isinstance(data, bytes): out.write(data) else: for chunk in data: if isinstance(chunk, str): chunk = chunk.encode('utf-8') out.write(chunk) out.flush() self.socket.shutdown(socket.SHUT_WR) def simple_recv(self, timeout: float) -> bytes: dcs = re.compile(br'\x1bP@kitty-cmd([^\x1b]+)\x1b\\') self.socket.settimeout(timeout) st = monotonic() with self.socket.makefile('rb') as src: data = src.read() m = dcs.search(data) if m is None: if monotonic() - st > timeout: raise TimeoutError('Timed out while waiting to read cmd response') raise SocketClosed('Remote control connection was closed by kitty without any response being received') return bytes(m.group(1)) class RCIO(TTYIO): def simple_recv(self, timeout: float) -> bytes: ans: list[bytes] = [] read_command_response(self.tty_fd, timeout, ans) return b''.join(ans) def do_io( to: str | None, original_cmd: dict[str, Any], no_response: bool, response_timeout: float, encrypter: 'CommandEncrypter' ) -> dict[str, Any]: payload = original_cmd.get('payload') if not isinstance(payload, GeneratorType): send_data: bytes | Iterator[bytes] = encode_send(encrypter(original_cmd)) else: def send_generator() -> Iterator[bytes]: assert payload is not None for chunk in payload: original_cmd['payload'] = chunk yield encode_send(encrypter(original_cmd)) send_data = send_generator() io: SocketIO | RCIO = SocketIO(to) if to else RCIO() with io: io.send(send_data) if no_response: return {'ok': True} received = io.simple_recv(timeout=response_timeout) return cast(dict[str, Any], json.loads(received.decode('ascii'))) cli_msg = ( 'Control {appname} by sending it commands. Set the' ' :opt:`allow_remote_control` option in :file:`kitty.conf` or use a password, for this' ' to work.' ).format(appname=appname) def parse_rc_args(args: list[str]) -> tuple[RCOptions, list[str]]: cmap = {name: command_for_name(name) for name in sorted(all_command_names())} cmds = (f' :green:`{cmd.name}`\n {cmd.short_desc}' for c, cmd in cmap.items()) msg = cli_msg + ( '\n\n:title:`Commands`:\n{cmds}\n\n' 'You can get help for each individual command by using:\n' '{appname} @ :italic:`command` -h').format(appname=appname, cmds='\n'.join(cmds)) return parse_args(args[1:], global_options_spec, 'command ...', msg, f'{appname} @', result_class=RCOptions) def encode_as_base85(data: bytes) -> str: return base64.b85encode(data).decode('ascii') class CommandEncrypter: encrypts: bool = True def __init__(self, pubkey: bytes, encryption_version: str, password: str) -> None: skey = EllipticCurveKey() self.secret = skey.derive_secret(pubkey) self.pubkey = skey.public self.encryption_version = encryption_version self.password = password def __call__(self, cmd: dict[str, Any]) -> dict[str, Any]: encrypter = AES256GCMEncrypt(self.secret) cmd['timestamp'] = time_ns() cmd['password'] = self.password raw = json.dumps(cmd).encode('utf-8') encrypted = encrypter.add_data_to_be_encrypted(raw, True) ans = { 'version': version, 'iv': encode_as_base85(encrypter.iv), 'tag': encode_as_base85(encrypter.tag), 'pubkey': encode_as_base85(self.pubkey), 'encrypted': encode_as_base85(encrypted), } if self.encryption_version != '1': ans['enc_proto'] = self.encryption_version return ans def adjust_response_timeout_for_password(self, response_timeout: float) -> float: return max(response_timeout, 120) class NoEncryption(CommandEncrypter): encrypts: bool = False def __init__(self) -> None: ... def __call__(self, cmd: dict[str, Any]) -> dict[str, Any]: return cmd def adjust_response_timeout_for_password(self, response_timeout: float) -> float: return response_timeout def create_basic_command(name: str, payload: Any = None, no_response: bool = False, is_asynchronous: bool = False) -> dict[str, Any]: ans = {'cmd': name, 'version': version, 'no_response': no_response} if payload is not None: ans['payload'] = payload if is_asynchronous: from kitty.short_uuid import uuid4 ans['async'] = uuid4() return ans def send_response_to_client(data: Any = None, error: str = '', peer_id: int = 0, window_id: int = 0, async_id: str = '') -> None: if active_async_requests.pop(async_id, None) is None: return if error: response: dict[str, bool | int | str] = {'ok': False, 'error': error} else: response = {'ok': True, 'data': data} if peer_id > 0: send_data_to_peer(peer_id, encode_response_for_peer(response)) elif window_id > 0: w = get_boss().window_id_map[window_id] if w is not None: w.send_cmd_response(response) def get_password(opts: RCOptions) -> str: if opts.use_password == 'never': return '' ans = '' if opts.password: ans = opts.password if not ans and opts.password_file: if opts.password_file == '-': if sys.stdin.isatty(): from getpass import getpass ans = getpass() else: ans = sys.stdin.read().rstrip() try: tty_fd = os.open(os.ctermid(), os.O_RDONLY | os.O_CLOEXEC) except OSError: pass else: with open(tty_fd, closefd=True): os.dup2(tty_fd, sys.stdin.fileno()) else: try: with open(resolve_custom_file(opts.password_file)) as f: ans = f.read().rstrip() except OSError: pass if not ans and opts.password_env: ans = os.environ.get(opts.password_env, '') if not ans and opts.use_password == 'always': raise SystemExit('No password was found') if ans and len(ans) > 1024: raise SystemExit('Specified password is too long') return ans def get_pubkey() -> tuple[str, bytes]: raw = os.environ.get('KITTY_PUBLIC_KEY', '') if not raw: raise SystemExit('Password usage requested but KITTY_PUBLIC_KEY environment variable is not available') version, pubkey = raw.split(':', 1) if version != RC_ENCRYPTION_PROTOCOL_VERSION: raise SystemExit('KITTY_PUBLIC_KEY has unknown version, if you are running on a remote system, update kitty on this system') from base64 import b85decode return version, b85decode(pubkey) kitty-0.41.1/kitty/render_cache.py0000664000175000017510000001076214773370543016435 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2024, Kovid Goyal import os import time from collections.abc import Iterator from contextlib import closing, suppress from functools import partial from .constants import cache_dir, kitten_exe from .utils import lock_file, unlock_file class ImageRenderCache: lock_file_name = '.lock' def __init__(self, subdirname: str = 'rgba', max_entries: int = 32, cache_path: str = ''): self.subdirname = subdirname self.cache_path = cache_path self.cache_dir = '' self.max_entries = max_entries def ensure_subdir(self) -> None: if not self.cache_dir: import stat x = os.path.abspath(os.path.join(self.cache_path or cache_dir(), self.subdirname)) os.makedirs(x, mode=stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC, exist_ok=True) self.cache_dir = x def __enter__(self) -> None: self.ensure_subdir() self.lock_file = open(os.path.join(self.cache_dir, self.lock_file_name), 'wb') try: lock_file(self.lock_file) except Exception: self.lock_file.close() raise def __exit__(self, *a: object) -> None: with closing(self.lock_file): unlock_file(self.lock_file) def entries(self) -> 'Iterator[os.DirEntry[str]]': for x in os.scandir(self.cache_dir): if x.name != self.lock_file_name: yield x def prune_entries(self) -> None: entries = list(self.entries()) if len(entries) <= self.max_entries: return def sort_key(e: 'os.DirEntry[str]') -> float: with suppress(OSError): st = e.stat() return st.st_mtime return 0. entries.sort(key=sort_key, reverse=True) for e in entries[self.max_entries:]: with suppress(FileNotFoundError): os.remove(e.path) def touch(self, path: str) -> None: os.utime(path, follow_symlinks=False) def render_image(self, src_path: str, output_path: str) -> None: import stat import subprocess try: with open(src_path, 'rb') as src, open(output_path, 'wb', opener=partial(os.open, mode=stat.S_IREAD | stat.S_IWRITE)) as output: cp = subprocess.run([kitten_exe(), '__convert_image__', 'RGBA'], stdin=src, stdout=output, stderr=subprocess.PIPE) if cp.returncode != 0: raise ValueError(f'Failed to convert {src_path} to RGBA data with error: {cp.stderr.decode("utf-8", "replace")}') if output.seek(0, os.SEEK_END) < 8: raise ValueError(f'Failed to convert {src_path} to RGBA data, no output written. stderr: {cp.stderr.decode("utf-8", "replace")}') except Exception: with suppress(Exception): os.unlink(output_path) raise def read_metadata(self, output_path: str) -> tuple[int, int, int]: with open(output_path, 'rb') as f: header = f.read(8) import struct width, height = struct.unpack(' str: import struct from hashlib import sha256 src_info = os.stat(src_path) output_name = sha256(struct.pack('@qqqq', src_info.st_dev, src_info.st_ino, src_info.st_size, src_info.st_mtime_ns)).hexdigest() with self: output_path = os.path.join(self.cache_dir, output_name) with suppress(OSError): self.touch(output_path) return output_path self.render_image(src_path, output_path) self.prune_entries() return output_path def __call__(self, src: str) -> tuple[int, int, int]: return self.read_metadata(self.render(src)) class ImageRenderCacheForTesting(ImageRenderCache): def __init__(self, cache_path: str): super().__init__(max_entries=2, cache_path=cache_path) self.current_time = time.time_ns() self.num_of_renders = 0 def render_image(self, src_path: str, output_path: str) -> None: super().render_image(src_path, output_path) self.touch(output_path) self.num_of_renders += 1 def touch(self, path:str) -> None: self.current_time += 3 * int(1e9) os.utime(path, ns=(self.current_time, self.current_time)) default_image_render_cache = ImageRenderCache() kitty-0.41.1/kitty/resize.c0000664000175000017510000002754114773370543015131 0ustar nileshnilesh/* * resize.c * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "resize.h" #include "lineops.h" typedef struct Rewrap { struct { LineBuf *lb; HistoryBuf *hb; index_type x, y, hb_count; Line line, scratch_line; } src, dest; ANSIBuf *as_ansi_buf; TrackCursor *cursors; LineBuf *sb; index_type num_content_lines_before, src_x_limit; bool prev_src_line_ended_with_wrap, current_src_line_has_multline_cells, current_dest_line_has_multiline_cells; bool dest_line_from_linebuf, src_is_in_linebuf; } Rewrap; static void setup_line(TextCache *tc, index_type xnum, Line *l) { l->text_cache = tc; l->xnum = xnum; } #define src_xnum (r->src.lb->xnum) #define dest_xnum (r->dest.lb->xnum) static void exclude_empty_lines_at_bottom(Rewrap *r) { index_type first, i; bool is_empty = true; // Find the first line that contains some content #define self (r->src.lb) first = self->ynum; do { first--; CPUCell *cells = linebuf_cpu_cells_for_line(self, first); for(i = 0; i < self->xnum; i++) { if (cells[i].ch_or_idx || cells[i].ch_is_idx) { is_empty = false; break; } } } while(is_empty && first > 0); if (!is_empty) r->num_content_lines_before = first + 1; #undef self } static void init_src_line_basic(Rewrap *r, index_type y, Line *dest, bool update_state) { if (r->src_is_in_linebuf) { linebuf_init_line_at(r->src.lb, y - r->src.hb_count, dest); } else if (y >= r->src.hb_count) { if (update_state) r->src_is_in_linebuf = true; linebuf_init_line_at(r->src.lb, y - r->src.hb_count, dest); } else { // historybuf_init_line uses reverse indexing historybuf_init_line(r->src.hb, r->src.hb->count - y - 1, dest); } } static bool init_src_line(Rewrap *r) { bool newline_needed = !r->prev_src_line_ended_with_wrap; init_src_line_basic(r, r->src.y, &r->src.line, true); r->src_x_limit = src_xnum; r->prev_src_line_ended_with_wrap = r->src.line.cpu_cells[src_xnum - 1].next_char_was_wrapped; r->src.line.cpu_cells[src_xnum - 1].next_char_was_wrapped = false; // Trim trailing blanks while (r->src_x_limit && r->src.line.cpu_cells[r->src_x_limit - 1].ch_and_idx == BLANK_CHAR) r->src_x_limit--; r->src.x = 0; r->current_src_line_has_multline_cells = false; for (index_type i = 0; i < r->src_x_limit; i++) if (r->src.line.cpu_cells[i].is_multicell && r->src.line.cpu_cells[i].scale > 1) { r->current_src_line_has_multline_cells = true; break; } return newline_needed; } #define set_dest_line_attrs(dest_y) r->dest.lb->line_attrs[dest_y] = r->src.line.attrs; r->src.line.attrs.prompt_kind = UNKNOWN_PROMPT_KIND; static void first_dest_line(Rewrap *r) { if (r->src.hb_count) { historybuf_next_dest_line(r->dest.hb, r->as_ansi_buf, &r->src.line, 0, &r->dest.line, false); r->src.line.attrs.prompt_kind = UNKNOWN_PROMPT_KIND; } else { r->dest_line_from_linebuf = true; linebuf_init_line_at(r->dest.lb, 0, &r->dest.line); set_dest_line_attrs(0); } } static index_type linebuf_next_dest_line(Rewrap *r, bool continued) { #define dest_y r->dest.y LineBuf *dest = r->dest.lb; linebuf_set_last_char_as_continuation(dest, dest_y, continued); if (dest_y >= dest->ynum - 1) { linebuf_index(dest, 0, dest->ynum - 1); if (r->dest.hb != NULL) { linebuf_init_line(dest, dest->ynum - 1); dest->line->attrs.has_dirty_text = true; historybuf_add_line(r->dest.hb, dest->line, r->as_ansi_buf); } linebuf_clear_line(dest, dest->ynum - 1, true); } else dest_y++; linebuf_init_line_at(dest, dest_y, &r->dest.line); set_dest_line_attrs(dest_y); return dest_y; #undef dest_y } static void next_dest_line(Rewrap *r, bool continued) { r->dest.x = 0; r->current_dest_line_has_multiline_cells = false; if (r->dest_line_from_linebuf) { r->dest.y = linebuf_next_dest_line(r, continued); } else if (r->src_is_in_linebuf) { r->dest_line_from_linebuf = true; r->dest.y = 0; linebuf_init_line_at(r->dest.lb, 0, &r->dest.line); set_dest_line_attrs(0); if (continued && r->dest.hb && r->dest.hb->count) { historybuf_init_line(r->dest.hb, 0, r->dest.hb->line); r->dest.hb->line->cpu_cells[dest_xnum-1].next_char_was_wrapped = true; } } else { r->dest.y = historybuf_next_dest_line(r->dest.hb, r->as_ansi_buf, &r->src.line, r->dest.y, &r->dest.line, continued); r->src.line.attrs.prompt_kind = UNKNOWN_PROMPT_KIND; } if (r->sb->line_attrs[0].has_dirty_text) { CPUCell *cpu_cells; GPUCell *gpu_cells; linebuf_init_cells(r->sb, 0, &cpu_cells, &gpu_cells); memcpy(r->dest.line.cpu_cells, cpu_cells, dest_xnum * sizeof(cpu_cells[0])); memcpy(r->dest.line.gpu_cells, gpu_cells, dest_xnum * sizeof(gpu_cells[0])); r->current_dest_line_has_multiline_cells = true; } linebuf_index(r->sb, 0, r->sb->ynum - 1); if (r->sb->line_attrs[r->sb->ynum - 1].has_dirty_text) { linebuf_clear_line(r->sb, r->sb->ynum - 1, true); } } static void update_tracked_cursors(Rewrap *r, index_type num_cells, index_type src_y, index_type dest_y, index_type x_limit) { if (!r->src_is_in_linebuf) return; src_y -= r->src.hb_count; for (TrackCursor *t = r->cursors; !t->is_sentinel; t++) { if (t->y == src_y && r->src.x <= t->x && (t->x < r->src.x + num_cells || t->x >= x_limit)) { t->dest_y = dest_y; t->dest_x = r->dest.x + (t->x - r->src.x); if (t->dest_x > dest_xnum) t->dest_x = dest_xnum; } } } static bool find_space_in_dest_line(Rewrap *r, index_type num_cells) { while (r->dest.x + num_cells <= dest_xnum) { index_type before = r->dest.x; for (index_type x = r->dest.x; x < r->dest.x + num_cells; x++) { if (r->dest.line.cpu_cells[x].is_multicell) { r->dest.x = x + mcd_x_limit(r->dest.line.cpu_cells + x); break; } } if (before == r->dest.x) return true; } return false; } static void find_space_in_dest(Rewrap *r, index_type num_cells) { while (!find_space_in_dest_line(r, num_cells)) next_dest_line(r, true); } static void copy_range(Line *src, index_type src_at, Line* dest, index_type dest_at, index_type num) { memcpy(dest->cpu_cells + dest_at, src->cpu_cells + src_at, num * sizeof(CPUCell)); memcpy(dest->gpu_cells + dest_at, src->gpu_cells + src_at, num * sizeof(GPUCell)); } static void copy_multiline_extra_lines(Rewrap *r, CPUCell *src_cell, index_type mc_width) { for (index_type i = 1; i < src_cell->scale; i++) { init_src_line_basic(r, r->src.y + i, &r->src.scratch_line, false); linebuf_init_line_at(r->sb, i - 1, &r->dest.scratch_line); linebuf_mark_line_dirty(r->sb, i - 1); copy_range(&r->src.scratch_line, r->src.x, &r->dest.scratch_line, r->dest.x, mc_width); update_tracked_cursors(r, mc_width, r->src.y + i, r->dest.y + i, src_xnum + 10000 /* ensure cursor is moved only if in region being copied */); } } static void multiline_copy_src_to_dest(Rewrap *r) { CPUCell *c; index_type mc_width; while (r->src.x < r->src_x_limit) { c = &r->src.line.cpu_cells[r->src.x]; if (c->is_multicell) { mc_width = mcd_x_limit(c); if (mc_width > dest_xnum) { update_tracked_cursors(r, mc_width, r->src.y, r->dest.y, r->src_x_limit); r->src.x += mc_width; continue; } else if (c->y) { r->src.x += mc_width; continue; } } else mc_width = 1; find_space_in_dest(r, mc_width); copy_range(&r->src.line, r->src.x, &r->dest.line, r->dest.x, mc_width); update_tracked_cursors(r, mc_width, r->src.y, r->dest.y, r->src_x_limit); if (c->scale > 1) copy_multiline_extra_lines(r, c, mc_width); r->src.x += mc_width; r->dest.x += mc_width; } } static void fast_copy_src_to_dest(Rewrap *r) { CPUCell *c; while (r->src.x < r->src_x_limit) { if (r->dest.x >= dest_xnum) { next_dest_line(r, true); if (r->current_dest_line_has_multiline_cells) { multiline_copy_src_to_dest(r); return; } } index_type num = MIN(r->src_x_limit - r->src.x, dest_xnum - r->dest.x); if (num && (c = &r->src.line.cpu_cells[r->src.x + num - 1])->is_multicell && c->x != mcd_x_limit(c) - 1) { // we have a split multicell at the right edge of the copy region multiline_copy_src_to_dest(r); return; } copy_range(&r->src.line, r->src.x, &r->dest.line, r->dest.x, num); update_tracked_cursors(r, num, r->src.y, r->dest.y, r->src_x_limit); r->src.x += num; r->dest.x += num; } } static void rewrap(Rewrap *r) { r->src.hb_count = r->src.hb ? r->src.hb->count : 0; // Fast path if (r->dest.lb->xnum == r->src.lb->xnum && r->dest.lb->ynum == r->src.lb->ynum) { memcpy(r->dest.lb->line_map, r->src.lb->line_map, sizeof(index_type) * r->src.lb->ynum); memcpy(r->dest.lb->line_attrs, r->src.lb->line_attrs, sizeof(LineAttrs) * r->src.lb->ynum); memcpy(r->dest.lb->cpu_cell_buf, r->src.lb->cpu_cell_buf, (size_t)r->src.lb->xnum * r->src.lb->ynum * sizeof(CPUCell)); memcpy(r->dest.lb->gpu_cell_buf, r->src.lb->gpu_cell_buf, (size_t)r->src.lb->xnum * r->src.lb->ynum * sizeof(GPUCell)); r->num_content_lines_before = r->src.lb->ynum; if (r->dest.hb && r->src.hb) historybuf_fast_rewrap(r->dest.hb, r->src.hb); r->dest.y = r->src.lb->ynum - 1; return; } setup_line(r->src.lb->text_cache, src_xnum, &r->src.line); setup_line(r->src.lb->text_cache, dest_xnum, &r->dest.line); setup_line(r->src.lb->text_cache, src_xnum, &r->src.scratch_line); setup_line(r->src.lb->text_cache, dest_xnum, &r->dest.scratch_line); exclude_empty_lines_at_bottom(r); for (; r->src.y < r->num_content_lines_before + r->src.hb_count; r->src.y++) { if (init_src_line(r)) { if (r->src.y) next_dest_line(r, false); else first_dest_line(r); } if (r->current_src_line_has_multline_cells || r->current_dest_line_has_multiline_cells) multiline_copy_src_to_dest(r); else fast_copy_src_to_dest(r); } } ResizeResult resize_screen_buffers(LineBuf *lb, HistoryBuf *hb, index_type lines, index_type columns, ANSIBuf *as_ansi_buf, TrackCursor *cursors) { ResizeResult ans = {0}; ans.lb = alloc_linebuf(lines, columns, lb->text_cache); if (!ans.lb) return ans; RAII_PyObject(raii_nlb, (PyObject*)ans.lb); (void) raii_nlb; if (hb) { ans.hb = historybuf_alloc_for_rewrap(columns, hb); if (!ans.hb) return ans; } RAII_PyObject(raii_nhb, (PyObject*)ans.hb); (void) raii_nhb; Rewrap r = { .src = {.lb=lb, .hb=hb}, .dest = {.lb=ans.lb, .hb=ans.hb}, .as_ansi_buf = as_ansi_buf, .cursors = cursors, }; r.sb = alloc_linebuf(SCALE_BITS << 1, columns, lb->text_cache); if (!r.sb) return ans; RAII_PyObject(scratch, (PyObject*)r.sb); (void)scratch; for (TrackCursor *t = cursors; !t->is_sentinel; t++) { t->dest_x = t->x; t->dest_y = t->y; } rewrap(&r); ans.num_content_lines_before = r.num_content_lines_before; ans.num_content_lines_after = MIN(r.dest.y + 1, ans.lb->ynum); if (hb) historybuf_finish_rewrap(ans.hb, hb); for (unsigned i = 0; i < ans.num_content_lines_after; i++) linebuf_mark_line_dirty(ans.lb, i); for (TrackCursor *t = cursors; !t->is_sentinel; t++) { t->dest_x = MIN(t->dest_x, columns); t->dest_y = MIN(t->dest_y, lines); } Py_INCREF(raii_nlb); Py_XINCREF(raii_nhb); ans.ok = true; return ans; } kitty-0.41.1/kitty/resize.h0000664000175000017510000000114514773370543015126 0ustar nileshnilesh/* * resize.h * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "line-buf.h" #include "history.h" typedef struct TrackCursor { index_type x, y; index_type dest_x, dest_y; bool is_sentinel; } TrackCursor; typedef struct ResizeResult { LineBuf *lb; HistoryBuf *hb; bool ok; index_type num_content_lines_before, num_content_lines_after; } ResizeResult; ResizeResult resize_screen_buffers(LineBuf *lb, HistoryBuf *hb, index_type lines, index_type columns, ANSIBuf *as_ansi_buf, TrackCursor *cursors); kitty-0.41.1/kitty/rgb.py0000664000175000017510000006761214773370543014613 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2017, Kovid Goyal import re from contextlib import suppress from .fast_data_types import Color def alpha_blend_channel(top_color: int, bottom_color: int, alpha: float) -> int: return int(alpha * top_color + (1 - alpha) * bottom_color) def alpha_blend(top_color: Color, bottom_color: Color, alpha: float) -> Color: return Color( alpha_blend_channel(top_color.red, bottom_color.red, alpha), alpha_blend_channel(top_color.green, bottom_color.green, alpha), alpha_blend_channel(top_color.blue, bottom_color.blue, alpha) ) def parse_single_color(c: str) -> int: if len(c) == 1: c += c return int(c[:2], 16) def parse_sharp(spec: str) -> Color | None: if len(spec) in (3, 6, 9, 12): part_len = len(spec) // 3 colors = re.findall(fr'[a-fA-F0-9]{{{part_len}}}', spec) return Color(*map(parse_single_color, colors)) return None def parse_rgb(spec: str) -> Color | None: colors = spec.split('/') if len(colors) == 3: return Color(*map(parse_single_color, colors)) return None def parse_single_intensity(x: str) -> int: return int(max(0, min(abs(float(x)), 1)) * 255) def parse_rgbi(spec: str) -> Color | None: colors = spec.split('/') if len(colors) == 3: return Color(*map(parse_single_intensity, colors)) return None def color_from_int(x: int) -> Color: return Color((x >> 16) & 255, (x >> 8) & 255, x & 255) def color_as_int(x: Color) -> int: return int(x) def color_as_sharp(x: Color) -> str: return x.as_sharp def color_as_sgr(x: Color) -> str: return x.as_sgr def to_color(raw: str, validate: bool = False) -> Color | None: # See man XParseColor x = raw.strip().lower() ans = color_names.get(x) if ans is not None: return ans val: Color | None = None with suppress(Exception): if raw.startswith('#'): val = parse_sharp(raw[1:]) else: k, sep, v = raw.partition(':') if k == 'rgb': val = parse_rgb(v) elif k == 'rgbi': val = parse_rgbi(v) if val is None and validate: raise ValueError(f'Invalid color name: {raw!r}') return val # BEGIN_DATA_SECTION {{{ color_names = { 'alice blue': Color(240, 248, 255), 'aliceblue': Color(240, 248, 255), 'antique white': Color(250, 235, 215), 'antiquewhite': Color(250, 235, 215), 'antiquewhite1': Color(255, 239, 219), 'antiquewhite2': Color(238, 223, 204), 'antiquewhite3': Color(205, 192, 176), 'antiquewhite4': Color(139, 131, 120), 'aquamarine': Color(127, 255, 212), 'aquamarine1': Color(127, 255, 212), 'aquamarine2': Color(118, 238, 198), 'aquamarine3': Color(102, 205, 170), 'aquamarine4': Color(69, 139, 116), 'azure': Color(240, 255, 255), 'azure1': Color(240, 255, 255), 'azure2': Color(224, 238, 238), 'azure3': Color(193, 205, 205), 'azure4': Color(131, 139, 139), 'beige': Color(245, 245, 220), 'bisque': Color(255, 228, 196), 'bisque1': Color(255, 228, 196), 'bisque2': Color(238, 213, 183), 'bisque3': Color(205, 183, 158), 'bisque4': Color(139, 125, 107), 'black': Color(0, 0, 0), 'blanched almond': Color(255, 235, 205), 'blanchedalmond': Color(255, 235, 205), 'blue': Color(0, 0, 255), 'blue violet': Color(138, 43, 226), 'blue1': Color(0, 0, 255), 'blue2': Color(0, 0, 238), 'blue3': Color(0, 0, 205), 'blue4': Color(0, 0, 139), 'blueviolet': Color(138, 43, 226), 'brown': Color(165, 42, 42), 'brown1': Color(255, 64, 64), 'brown2': Color(238, 59, 59), 'brown3': Color(205, 51, 51), 'brown4': Color(139, 35, 35), 'burlywood': Color(222, 184, 135), 'burlywood1': Color(255, 211, 155), 'burlywood2': Color(238, 197, 145), 'burlywood3': Color(205, 170, 125), 'burlywood4': Color(139, 115, 85), 'cadet blue': Color(95, 158, 160), 'cadetblue': Color(95, 158, 160), 'cadetblue1': Color(152, 245, 255), 'cadetblue2': Color(142, 229, 238), 'cadetblue3': Color(122, 197, 205), 'cadetblue4': Color(83, 134, 139), 'chartreuse': Color(127, 255, 0), 'chartreuse1': Color(127, 255, 0), 'chartreuse2': Color(118, 238, 0), 'chartreuse3': Color(102, 205, 0), 'chartreuse4': Color(69, 139, 0), 'chocolate': Color(210, 105, 30), 'chocolate1': Color(255, 127, 36), 'chocolate2': Color(238, 118, 33), 'chocolate3': Color(205, 102, 29), 'chocolate4': Color(139, 69, 19), 'coral': Color(255, 127, 80), 'coral1': Color(255, 114, 86), 'coral2': Color(238, 106, 80), 'coral3': Color(205, 91, 69), 'coral4': Color(139, 62, 47), 'cornflower blue': Color(100, 149, 237), 'cornflowerblue': Color(100, 149, 237), 'cornsilk': Color(255, 248, 220), 'cornsilk1': Color(255, 248, 220), 'cornsilk2': Color(238, 232, 205), 'cornsilk3': Color(205, 200, 177), 'cornsilk4': Color(139, 136, 120), 'cyan': Color(0, 255, 255), 'cyan1': Color(0, 255, 255), 'cyan2': Color(0, 238, 238), 'cyan3': Color(0, 205, 205), 'cyan4': Color(0, 139, 139), 'dark blue': Color(0, 0, 139), 'dark cyan': Color(0, 139, 139), 'dark goldenrod': Color(184, 134, 11), 'dark gray': Color(169, 169, 169), 'dark green': Color(0, 100, 0), 'dark grey': Color(169, 169, 169), 'dark khaki': Color(189, 183, 107), 'dark magenta': Color(139, 0, 139), 'dark olive green': Color(85, 107, 47), 'dark orange': Color(255, 140, 0), 'dark orchid': Color(153, 50, 204), 'dark red': Color(139, 0, 0), 'dark salmon': Color(233, 150, 122), 'dark sea green': Color(143, 188, 143), 'dark slate blue': Color(72, 61, 139), 'dark slate gray': Color(47, 79, 79), 'dark slate grey': Color(47, 79, 79), 'dark turquoise': Color(0, 206, 209), 'dark violet': Color(148, 0, 211), 'darkblue': Color(0, 0, 139), 'darkcyan': Color(0, 139, 139), 'darkgoldenrod': Color(184, 134, 11), 'darkgoldenrod1': Color(255, 185, 15), 'darkgoldenrod2': Color(238, 173, 14), 'darkgoldenrod3': Color(205, 149, 12), 'darkgoldenrod4': Color(139, 101, 8), 'darkgray': Color(169, 169, 169), 'darkgreen': Color(0, 100, 0), 'darkgrey': Color(169, 169, 169), 'darkkhaki': Color(189, 183, 107), 'darkmagenta': Color(139, 0, 139), 'darkolivegreen': Color(85, 107, 47), 'darkolivegreen1': Color(202, 255, 112), 'darkolivegreen2': Color(188, 238, 104), 'darkolivegreen3': Color(162, 205, 90), 'darkolivegreen4': Color(110, 139, 61), 'darkorange': Color(255, 140, 0), 'darkorange1': Color(255, 127, 0), 'darkorange2': Color(238, 118, 0), 'darkorange3': Color(205, 102, 0), 'darkorange4': Color(139, 69, 0), 'darkorchid': Color(153, 50, 204), 'darkorchid1': Color(191, 62, 255), 'darkorchid2': Color(178, 58, 238), 'darkorchid3': Color(154, 50, 205), 'darkorchid4': Color(104, 34, 139), 'darkred': Color(139, 0, 0), 'darksalmon': Color(233, 150, 122), 'darkseagreen': Color(143, 188, 143), 'darkseagreen1': Color(193, 255, 193), 'darkseagreen2': Color(180, 238, 180), 'darkseagreen3': Color(155, 205, 155), 'darkseagreen4': Color(105, 139, 105), 'darkslateblue': Color(72, 61, 139), 'darkslategray': Color(47, 79, 79), 'darkslategray1': Color(151, 255, 255), 'darkslategray2': Color(141, 238, 238), 'darkslategray3': Color(121, 205, 205), 'darkslategray4': Color(82, 139, 139), 'darkslategrey': Color(47, 79, 79), 'darkturquoise': Color(0, 206, 209), 'darkviolet': Color(148, 0, 211), 'debianred': Color(215, 7, 81), 'deep pink': Color(255, 20, 147), 'deep sky blue': Color(0, 191, 255), 'deeppink': Color(255, 20, 147), 'deeppink1': Color(255, 20, 147), 'deeppink2': Color(238, 18, 137), 'deeppink3': Color(205, 16, 118), 'deeppink4': Color(139, 10, 80), 'deepskyblue': Color(0, 191, 255), 'deepskyblue1': Color(0, 191, 255), 'deepskyblue2': Color(0, 178, 238), 'deepskyblue3': Color(0, 154, 205), 'deepskyblue4': Color(0, 104, 139), 'dim gray': Color(105, 105, 105), 'dim grey': Color(105, 105, 105), 'dimgray': Color(105, 105, 105), 'dimgrey': Color(105, 105, 105), 'dodger blue': Color(30, 144, 255), 'dodgerblue': Color(30, 144, 255), 'dodgerblue1': Color(30, 144, 255), 'dodgerblue2': Color(28, 134, 238), 'dodgerblue3': Color(24, 116, 205), 'dodgerblue4': Color(16, 78, 139), 'firebrick': Color(178, 34, 34), 'firebrick1': Color(255, 48, 48), 'firebrick2': Color(238, 44, 44), 'firebrick3': Color(205, 38, 38), 'firebrick4': Color(139, 26, 26), 'floral white': Color(255, 250, 240), 'floralwhite': Color(255, 250, 240), 'forest green': Color(34, 139, 34), 'forestgreen': Color(34, 139, 34), 'gainsboro': Color(220, 220, 220), 'ghost white': Color(248, 248, 255), 'ghostwhite': Color(248, 248, 255), 'gold': Color(255, 215, 0), 'gold1': Color(255, 215, 0), 'gold2': Color(238, 201, 0), 'gold3': Color(205, 173, 0), 'gold4': Color(139, 117, 0), 'goldenrod': Color(218, 165, 32), 'goldenrod1': Color(255, 193, 37), 'goldenrod2': Color(238, 180, 34), 'goldenrod3': Color(205, 155, 29), 'goldenrod4': Color(139, 105, 20), 'gray': Color(190, 190, 190), 'gray0': Color(0, 0, 0), 'gray1': Color(3, 3, 3), 'gray10': Color(26, 26, 26), 'gray100': Color(255, 255, 255), 'gray11': Color(28, 28, 28), 'gray12': Color(31, 31, 31), 'gray13': Color(33, 33, 33), 'gray14': Color(36, 36, 36), 'gray15': Color(38, 38, 38), 'gray16': Color(41, 41, 41), 'gray17': Color(43, 43, 43), 'gray18': Color(46, 46, 46), 'gray19': Color(48, 48, 48), 'gray2': Color(5, 5, 5), 'gray20': Color(51, 51, 51), 'gray21': Color(54, 54, 54), 'gray22': Color(56, 56, 56), 'gray23': Color(59, 59, 59), 'gray24': Color(61, 61, 61), 'gray25': Color(64, 64, 64), 'gray26': Color(66, 66, 66), 'gray27': Color(69, 69, 69), 'gray28': Color(71, 71, 71), 'gray29': Color(74, 74, 74), 'gray3': Color(8, 8, 8), 'gray30': Color(77, 77, 77), 'gray31': Color(79, 79, 79), 'gray32': Color(82, 82, 82), 'gray33': Color(84, 84, 84), 'gray34': Color(87, 87, 87), 'gray35': Color(89, 89, 89), 'gray36': Color(92, 92, 92), 'gray37': Color(94, 94, 94), 'gray38': Color(97, 97, 97), 'gray39': Color(99, 99, 99), 'gray4': Color(10, 10, 10), 'gray40': Color(102, 102, 102), 'gray41': Color(105, 105, 105), 'gray42': Color(107, 107, 107), 'gray43': Color(110, 110, 110), 'gray44': Color(112, 112, 112), 'gray45': Color(115, 115, 115), 'gray46': Color(117, 117, 117), 'gray47': Color(120, 120, 120), 'gray48': Color(122, 122, 122), 'gray49': Color(125, 125, 125), 'gray5': Color(13, 13, 13), 'gray50': Color(127, 127, 127), 'gray51': Color(130, 130, 130), 'gray52': Color(133, 133, 133), 'gray53': Color(135, 135, 135), 'gray54': Color(138, 138, 138), 'gray55': Color(140, 140, 140), 'gray56': Color(143, 143, 143), 'gray57': Color(145, 145, 145), 'gray58': Color(148, 148, 148), 'gray59': Color(150, 150, 150), 'gray6': Color(15, 15, 15), 'gray60': Color(153, 153, 153), 'gray61': Color(156, 156, 156), 'gray62': Color(158, 158, 158), 'gray63': Color(161, 161, 161), 'gray64': Color(163, 163, 163), 'gray65': Color(166, 166, 166), 'gray66': Color(168, 168, 168), 'gray67': Color(171, 171, 171), 'gray68': Color(173, 173, 173), 'gray69': Color(176, 176, 176), 'gray7': Color(18, 18, 18), 'gray70': Color(179, 179, 179), 'gray71': Color(181, 181, 181), 'gray72': Color(184, 184, 184), 'gray73': Color(186, 186, 186), 'gray74': Color(189, 189, 189), 'gray75': Color(191, 191, 191), 'gray76': Color(194, 194, 194), 'gray77': Color(196, 196, 196), 'gray78': Color(199, 199, 199), 'gray79': Color(201, 201, 201), 'gray8': Color(20, 20, 20), 'gray80': Color(204, 204, 204), 'gray81': Color(207, 207, 207), 'gray82': Color(209, 209, 209), 'gray83': Color(212, 212, 212), 'gray84': Color(214, 214, 214), 'gray85': Color(217, 217, 217), 'gray86': Color(219, 219, 219), 'gray87': Color(222, 222, 222), 'gray88': Color(224, 224, 224), 'gray89': Color(227, 227, 227), 'gray9': Color(23, 23, 23), 'gray90': Color(229, 229, 229), 'gray91': Color(232, 232, 232), 'gray92': Color(235, 235, 235), 'gray93': Color(237, 237, 237), 'gray94': Color(240, 240, 240), 'gray95': Color(242, 242, 242), 'gray96': Color(245, 245, 245), 'gray97': Color(247, 247, 247), 'gray98': Color(250, 250, 250), 'gray99': Color(252, 252, 252), 'green': Color(0, 255, 0), 'green yellow': Color(173, 255, 47), 'green1': Color(0, 255, 0), 'green2': Color(0, 238, 0), 'green3': Color(0, 205, 0), 'green4': Color(0, 139, 0), 'greenyellow': Color(173, 255, 47), 'grey': Color(190, 190, 190), 'grey0': Color(0, 0, 0), 'grey1': Color(3, 3, 3), 'grey10': Color(26, 26, 26), 'grey100': Color(255, 255, 255), 'grey11': Color(28, 28, 28), 'grey12': Color(31, 31, 31), 'grey13': Color(33, 33, 33), 'grey14': Color(36, 36, 36), 'grey15': Color(38, 38, 38), 'grey16': Color(41, 41, 41), 'grey17': Color(43, 43, 43), 'grey18': Color(46, 46, 46), 'grey19': Color(48, 48, 48), 'grey2': Color(5, 5, 5), 'grey20': Color(51, 51, 51), 'grey21': Color(54, 54, 54), 'grey22': Color(56, 56, 56), 'grey23': Color(59, 59, 59), 'grey24': Color(61, 61, 61), 'grey25': Color(64, 64, 64), 'grey26': Color(66, 66, 66), 'grey27': Color(69, 69, 69), 'grey28': Color(71, 71, 71), 'grey29': Color(74, 74, 74), 'grey3': Color(8, 8, 8), 'grey30': Color(77, 77, 77), 'grey31': Color(79, 79, 79), 'grey32': Color(82, 82, 82), 'grey33': Color(84, 84, 84), 'grey34': Color(87, 87, 87), 'grey35': Color(89, 89, 89), 'grey36': Color(92, 92, 92), 'grey37': Color(94, 94, 94), 'grey38': Color(97, 97, 97), 'grey39': Color(99, 99, 99), 'grey4': Color(10, 10, 10), 'grey40': Color(102, 102, 102), 'grey41': Color(105, 105, 105), 'grey42': Color(107, 107, 107), 'grey43': Color(110, 110, 110), 'grey44': Color(112, 112, 112), 'grey45': Color(115, 115, 115), 'grey46': Color(117, 117, 117), 'grey47': Color(120, 120, 120), 'grey48': Color(122, 122, 122), 'grey49': Color(125, 125, 125), 'grey5': Color(13, 13, 13), 'grey50': Color(127, 127, 127), 'grey51': Color(130, 130, 130), 'grey52': Color(133, 133, 133), 'grey53': Color(135, 135, 135), 'grey54': Color(138, 138, 138), 'grey55': Color(140, 140, 140), 'grey56': Color(143, 143, 143), 'grey57': Color(145, 145, 145), 'grey58': Color(148, 148, 148), 'grey59': Color(150, 150, 150), 'grey6': Color(15, 15, 15), 'grey60': Color(153, 153, 153), 'grey61': Color(156, 156, 156), 'grey62': Color(158, 158, 158), 'grey63': Color(161, 161, 161), 'grey64': Color(163, 163, 163), 'grey65': Color(166, 166, 166), 'grey66': Color(168, 168, 168), 'grey67': Color(171, 171, 171), 'grey68': Color(173, 173, 173), 'grey69': Color(176, 176, 176), 'grey7': Color(18, 18, 18), 'grey70': Color(179, 179, 179), 'grey71': Color(181, 181, 181), 'grey72': Color(184, 184, 184), 'grey73': Color(186, 186, 186), 'grey74': Color(189, 189, 189), 'grey75': Color(191, 191, 191), 'grey76': Color(194, 194, 194), 'grey77': Color(196, 196, 196), 'grey78': Color(199, 199, 199), 'grey79': Color(201, 201, 201), 'grey8': Color(20, 20, 20), 'grey80': Color(204, 204, 204), 'grey81': Color(207, 207, 207), 'grey82': Color(209, 209, 209), 'grey83': Color(212, 212, 212), 'grey84': Color(214, 214, 214), 'grey85': Color(217, 217, 217), 'grey86': Color(219, 219, 219), 'grey87': Color(222, 222, 222), 'grey88': Color(224, 224, 224), 'grey89': Color(227, 227, 227), 'grey9': Color(23, 23, 23), 'grey90': Color(229, 229, 229), 'grey91': Color(232, 232, 232), 'grey92': Color(235, 235, 235), 'grey93': Color(237, 237, 237), 'grey94': Color(240, 240, 240), 'grey95': Color(242, 242, 242), 'grey96': Color(245, 245, 245), 'grey97': Color(247, 247, 247), 'grey98': Color(250, 250, 250), 'grey99': Color(252, 252, 252), 'honeydew': Color(240, 255, 240), 'honeydew1': Color(240, 255, 240), 'honeydew2': Color(224, 238, 224), 'honeydew3': Color(193, 205, 193), 'honeydew4': Color(131, 139, 131), 'hot pink': Color(255, 105, 180), 'hotpink': Color(255, 105, 180), 'hotpink1': Color(255, 110, 180), 'hotpink2': Color(238, 106, 167), 'hotpink3': Color(205, 96, 144), 'hotpink4': Color(139, 58, 98), 'indian red': Color(205, 92, 92), 'indianred': Color(205, 92, 92), 'indianred1': Color(255, 106, 106), 'indianred2': Color(238, 99, 99), 'indianred3': Color(205, 85, 85), 'indianred4': Color(139, 58, 58), 'ivory': Color(255, 255, 240), 'ivory1': Color(255, 255, 240), 'ivory2': Color(238, 238, 224), 'ivory3': Color(205, 205, 193), 'ivory4': Color(139, 139, 131), 'khaki': Color(240, 230, 140), 'khaki1': Color(255, 246, 143), 'khaki2': Color(238, 230, 133), 'khaki3': Color(205, 198, 115), 'khaki4': Color(139, 134, 78), 'lavender': Color(230, 230, 250), 'lavender blush': Color(255, 240, 245), 'lavenderblush': Color(255, 240, 245), 'lavenderblush1': Color(255, 240, 245), 'lavenderblush2': Color(238, 224, 229), 'lavenderblush3': Color(205, 193, 197), 'lavenderblush4': Color(139, 131, 134), 'lawn green': Color(124, 252, 0), 'lawngreen': Color(124, 252, 0), 'lemon chiffon': Color(255, 250, 205), 'lemonchiffon': Color(255, 250, 205), 'lemonchiffon1': Color(255, 250, 205), 'lemonchiffon2': Color(238, 233, 191), 'lemonchiffon3': Color(205, 201, 165), 'lemonchiffon4': Color(139, 137, 112), 'light blue': Color(173, 216, 230), 'light coral': Color(240, 128, 128), 'light cyan': Color(224, 255, 255), 'light goldenrod': Color(238, 221, 130), 'light goldenrod yellow': Color(250, 250, 210), 'light gray': Color(211, 211, 211), 'light green': Color(144, 238, 144), 'light grey': Color(211, 211, 211), 'light pink': Color(255, 182, 193), 'light salmon': Color(255, 160, 122), 'light sea green': Color(32, 178, 170), 'light sky blue': Color(135, 206, 250), 'light slate blue': Color(132, 112, 255), 'light slate gray': Color(119, 136, 153), 'light slate grey': Color(119, 136, 153), 'light steel blue': Color(176, 196, 222), 'light yellow': Color(255, 255, 224), 'lightblue': Color(173, 216, 230), 'lightblue1': Color(191, 239, 255), 'lightblue2': Color(178, 223, 238), 'lightblue3': Color(154, 192, 205), 'lightblue4': Color(104, 131, 139), 'lightcoral': Color(240, 128, 128), 'lightcyan': Color(224, 255, 255), 'lightcyan1': Color(224, 255, 255), 'lightcyan2': Color(209, 238, 238), 'lightcyan3': Color(180, 205, 205), 'lightcyan4': Color(122, 139, 139), 'lightgoldenrod': Color(238, 221, 130), 'lightgoldenrod1': Color(255, 236, 139), 'lightgoldenrod2': Color(238, 220, 130), 'lightgoldenrod3': Color(205, 190, 112), 'lightgoldenrod4': Color(139, 129, 76), 'lightgoldenrodyellow': Color(250, 250, 210), 'lightgray': Color(211, 211, 211), 'lightgreen': Color(144, 238, 144), 'lightgrey': Color(211, 211, 211), 'lightpink': Color(255, 182, 193), 'lightpink1': Color(255, 174, 185), 'lightpink2': Color(238, 162, 173), 'lightpink3': Color(205, 140, 149), 'lightpink4': Color(139, 95, 101), 'lightsalmon': Color(255, 160, 122), 'lightsalmon1': Color(255, 160, 122), 'lightsalmon2': Color(238, 149, 114), 'lightsalmon3': Color(205, 129, 98), 'lightsalmon4': Color(139, 87, 66), 'lightseagreen': Color(32, 178, 170), 'lightskyblue': Color(135, 206, 250), 'lightskyblue1': Color(176, 226, 255), 'lightskyblue2': Color(164, 211, 238), 'lightskyblue3': Color(141, 182, 205), 'lightskyblue4': Color(96, 123, 139), 'lightslateblue': Color(132, 112, 255), 'lightslategray': Color(119, 136, 153), 'lightslategrey': Color(119, 136, 153), 'lightsteelblue': Color(176, 196, 222), 'lightsteelblue1': Color(202, 225, 255), 'lightsteelblue2': Color(188, 210, 238), 'lightsteelblue3': Color(162, 181, 205), 'lightsteelblue4': Color(110, 123, 139), 'lightyellow': Color(255, 255, 224), 'lightyellow1': Color(255, 255, 224), 'lightyellow2': Color(238, 238, 209), 'lightyellow3': Color(205, 205, 180), 'lightyellow4': Color(139, 139, 122), 'lime green': Color(50, 205, 50), 'limegreen': Color(50, 205, 50), 'linen': Color(250, 240, 230), 'magenta': Color(255, 0, 255), 'magenta1': Color(255, 0, 255), 'magenta2': Color(238, 0, 238), 'magenta3': Color(205, 0, 205), 'magenta4': Color(139, 0, 139), 'maroon': Color(176, 48, 96), 'maroon1': Color(255, 52, 179), 'maroon2': Color(238, 48, 167), 'maroon3': Color(205, 41, 144), 'maroon4': Color(139, 28, 98), 'medium aquamarine': Color(102, 205, 170), 'medium blue': Color(0, 0, 205), 'medium orchid': Color(186, 85, 211), 'medium purple': Color(147, 112, 219), 'medium sea green': Color(60, 179, 113), 'medium slate blue': Color(123, 104, 238), 'medium spring green': Color(0, 250, 154), 'medium turquoise': Color(72, 209, 204), 'medium violet red': Color(199, 21, 133), 'mediumaquamarine': Color(102, 205, 170), 'mediumblue': Color(0, 0, 205), 'mediumorchid': Color(186, 85, 211), 'mediumorchid1': Color(224, 102, 255), 'mediumorchid2': Color(209, 95, 238), 'mediumorchid3': Color(180, 82, 205), 'mediumorchid4': Color(122, 55, 139), 'mediumpurple': Color(147, 112, 219), 'mediumpurple1': Color(171, 130, 255), 'mediumpurple2': Color(159, 121, 238), 'mediumpurple3': Color(137, 104, 205), 'mediumpurple4': Color(93, 71, 139), 'mediumseagreen': Color(60, 179, 113), 'mediumslateblue': Color(123, 104, 238), 'mediumspringgreen': Color(0, 250, 154), 'mediumturquoise': Color(72, 209, 204), 'mediumvioletred': Color(199, 21, 133), 'midnight blue': Color(25, 25, 112), 'midnightblue': Color(25, 25, 112), 'mint cream': Color(245, 255, 250), 'mintcream': Color(245, 255, 250), 'misty rose': Color(255, 228, 225), 'mistyrose': Color(255, 228, 225), 'mistyrose1': Color(255, 228, 225), 'mistyrose2': Color(238, 213, 210), 'mistyrose3': Color(205, 183, 181), 'mistyrose4': Color(139, 125, 123), 'moccasin': Color(255, 228, 181), 'navajo white': Color(255, 222, 173), 'navajowhite': Color(255, 222, 173), 'navajowhite1': Color(255, 222, 173), 'navajowhite2': Color(238, 207, 161), 'navajowhite3': Color(205, 179, 139), 'navajowhite4': Color(139, 121, 94), 'navy': Color(0, 0, 128), 'navy blue': Color(0, 0, 128), 'navyblue': Color(0, 0, 128), 'old lace': Color(253, 245, 230), 'oldlace': Color(253, 245, 230), 'olive drab': Color(107, 142, 35), 'olivedrab': Color(107, 142, 35), 'olivedrab1': Color(192, 255, 62), 'olivedrab2': Color(179, 238, 58), 'olivedrab3': Color(154, 205, 50), 'olivedrab4': Color(105, 139, 34), 'orange': Color(255, 165, 0), 'orange red': Color(255, 69, 0), 'orange1': Color(255, 165, 0), 'orange2': Color(238, 154, 0), 'orange3': Color(205, 133, 0), 'orange4': Color(139, 90, 0), 'orangered': Color(255, 69, 0), 'orangered1': Color(255, 69, 0), 'orangered2': Color(238, 64, 0), 'orangered3': Color(205, 55, 0), 'orangered4': Color(139, 37, 0), 'orchid': Color(218, 112, 214), 'orchid1': Color(255, 131, 250), 'orchid2': Color(238, 122, 233), 'orchid3': Color(205, 105, 201), 'orchid4': Color(139, 71, 137), 'pale goldenrod': Color(238, 232, 170), 'pale green': Color(152, 251, 152), 'pale turquoise': Color(175, 238, 238), 'pale violet red': Color(219, 112, 147), 'palegoldenrod': Color(238, 232, 170), 'palegreen': Color(152, 251, 152), 'palegreen1': Color(154, 255, 154), 'palegreen2': Color(144, 238, 144), 'palegreen3': Color(124, 205, 124), 'palegreen4': Color(84, 139, 84), 'paleturquoise': Color(175, 238, 238), 'paleturquoise1': Color(187, 255, 255), 'paleturquoise2': Color(174, 238, 238), 'paleturquoise3': Color(150, 205, 205), 'paleturquoise4': Color(102, 139, 139), 'palevioletred': Color(219, 112, 147), 'palevioletred1': Color(255, 130, 171), 'palevioletred2': Color(238, 121, 159), 'palevioletred3': Color(205, 104, 137), 'palevioletred4': Color(139, 71, 93), 'papaya whip': Color(255, 239, 213), 'papayawhip': Color(255, 239, 213), 'peach puff': Color(255, 218, 185), 'peachpuff': Color(255, 218, 185), 'peachpuff1': Color(255, 218, 185), 'peachpuff2': Color(238, 203, 173), 'peachpuff3': Color(205, 175, 149), 'peachpuff4': Color(139, 119, 101), 'peru': Color(205, 133, 63), 'pink': Color(255, 192, 203), 'pink1': Color(255, 181, 197), 'pink2': Color(238, 169, 184), 'pink3': Color(205, 145, 158), 'pink4': Color(139, 99, 108), 'plum': Color(221, 160, 221), 'plum1': Color(255, 187, 255), 'plum2': Color(238, 174, 238), 'plum3': Color(205, 150, 205), 'plum4': Color(139, 102, 139), 'powder blue': Color(176, 224, 230), 'powderblue': Color(176, 224, 230), 'purple': Color(160, 32, 240), 'purple1': Color(155, 48, 255), 'purple2': Color(145, 44, 238), 'purple3': Color(125, 38, 205), 'purple4': Color(85, 26, 139), 'red': Color(255, 0, 0), 'red1': Color(255, 0, 0), 'red2': Color(238, 0, 0), 'red3': Color(205, 0, 0), 'red4': Color(139, 0, 0), 'rosy brown': Color(188, 143, 143), 'rosybrown': Color(188, 143, 143), 'rosybrown1': Color(255, 193, 193), 'rosybrown2': Color(238, 180, 180), 'rosybrown3': Color(205, 155, 155), 'rosybrown4': Color(139, 105, 105), 'royal blue': Color(65, 105, 225), 'royalblue': Color(65, 105, 225), 'royalblue1': Color(72, 118, 255), 'royalblue2': Color(67, 110, 238), 'royalblue3': Color(58, 95, 205), 'royalblue4': Color(39, 64, 139), 'saddle brown': Color(139, 69, 19), 'saddlebrown': Color(139, 69, 19), 'salmon': Color(250, 128, 114), 'salmon1': Color(255, 140, 105), 'salmon2': Color(238, 130, 98), 'salmon3': Color(205, 112, 84), 'salmon4': Color(139, 76, 57), 'sandy brown': Color(244, 164, 96), 'sandybrown': Color(244, 164, 96), 'sea green': Color(46, 139, 87), 'seagreen': Color(46, 139, 87), 'seagreen1': Color(84, 255, 159), 'seagreen2': Color(78, 238, 148), 'seagreen3': Color(67, 205, 128), 'seagreen4': Color(46, 139, 87), 'seashell': Color(255, 245, 238), 'seashell1': Color(255, 245, 238), 'seashell2': Color(238, 229, 222), 'seashell3': Color(205, 197, 191), 'seashell4': Color(139, 134, 130), 'sienna': Color(160, 82, 45), 'sienna1': Color(255, 130, 71), 'sienna2': Color(238, 121, 66), 'sienna3': Color(205, 104, 57), 'sienna4': Color(139, 71, 38), 'sky blue': Color(135, 206, 235), 'skyblue': Color(135, 206, 235), 'skyblue1': Color(135, 206, 255), 'skyblue2': Color(126, 192, 238), 'skyblue3': Color(108, 166, 205), 'skyblue4': Color(74, 112, 139), 'slate blue': Color(106, 90, 205), 'slate gray': Color(112, 128, 144), 'slate grey': Color(112, 128, 144), 'slateblue': Color(106, 90, 205), 'slateblue1': Color(131, 111, 255), 'slateblue2': Color(122, 103, 238), 'slateblue3': Color(105, 89, 205), 'slateblue4': Color(71, 60, 139), 'slategray': Color(112, 128, 144), 'slategray1': Color(198, 226, 255), 'slategray2': Color(185, 211, 238), 'slategray3': Color(159, 182, 205), 'slategray4': Color(108, 123, 139), 'slategrey': Color(112, 128, 144), 'snow': Color(255, 250, 250), 'snow1': Color(255, 250, 250), 'snow2': Color(238, 233, 233), 'snow3': Color(205, 201, 201), 'snow4': Color(139, 137, 137), 'spring green': Color(0, 255, 127), 'springgreen': Color(0, 255, 127), 'springgreen1': Color(0, 255, 127), 'springgreen2': Color(0, 238, 118), 'springgreen3': Color(0, 205, 102), 'springgreen4': Color(0, 139, 69), 'steel blue': Color(70, 130, 180), 'steelblue': Color(70, 130, 180), 'steelblue1': Color(99, 184, 255), 'steelblue2': Color(92, 172, 238), 'steelblue3': Color(79, 148, 205), 'steelblue4': Color(54, 100, 139), 'tan': Color(210, 180, 140), 'tan1': Color(255, 165, 79), 'tan2': Color(238, 154, 73), 'tan3': Color(205, 133, 63), 'tan4': Color(139, 90, 43), 'thistle': Color(216, 191, 216), 'thistle1': Color(255, 225, 255), 'thistle2': Color(238, 210, 238), 'thistle3': Color(205, 181, 205), 'thistle4': Color(139, 123, 139), 'tomato': Color(255, 99, 71), 'tomato1': Color(255, 99, 71), 'tomato2': Color(238, 92, 66), 'tomato3': Color(205, 79, 57), 'tomato4': Color(139, 54, 38), 'turquoise': Color(64, 224, 208), 'turquoise1': Color(0, 245, 255), 'turquoise2': Color(0, 229, 238), 'turquoise3': Color(0, 197, 205), 'turquoise4': Color(0, 134, 139), 'violet': Color(238, 130, 238), 'violet red': Color(208, 32, 144), 'violetred': Color(208, 32, 144), 'violetred1': Color(255, 62, 150), 'violetred2': Color(238, 58, 140), 'violetred3': Color(205, 50, 120), 'violetred4': Color(139, 34, 82), 'wheat': Color(245, 222, 179), 'wheat1': Color(255, 231, 186), 'wheat2': Color(238, 216, 174), 'wheat3': Color(205, 186, 150), 'wheat4': Color(139, 126, 102), 'white': Color(255, 255, 255), 'white smoke': Color(245, 245, 245), 'whitesmoke': Color(245, 245, 245), 'yellow': Color(255, 255, 0), 'yellow green': Color(154, 205, 50), 'yellow1': Color(255, 255, 0), 'yellow2': Color(238, 238, 0), 'yellow3': Color(205, 205, 0), 'yellow4': Color(139, 139, 0), 'yellowgreen': Color(154, 205, 50)} # END_DATA_SECTION }}} kitty-0.41.1/kitty/rowcolumn-diacritics.c0000664000175000017510000001143514773370543017764 0ustar nileshnilesh// Unicode data, built from the Unicode Standard 16.0.0 // Code generated by wcwidth.py, DO NOT EDIT. #include "data-types.h" START_ALLOW_CASE_RANGE int diacritic_to_num(char_type code) { switch (code) { case 0x305 ... 0x306: return code - 0x305 + 1; case 0x30d ... 0x30f: return code - 0x30d + 2; case 0x310 ... 0x311: return code - 0x310 + 4; case 0x312 ... 0x313: return code - 0x312 + 5; case 0x33d ... 0x340: return code - 0x33d + 6; case 0x346 ... 0x347: return code - 0x346 + 9; case 0x34a ... 0x34d: return code - 0x34a + 10; case 0x350 ... 0x353: return code - 0x350 + 13; case 0x357 ... 0x358: return code - 0x357 + 16; case 0x35b ... 0x35c: return code - 0x35b + 17; case 0x363 ... 0x370: return code - 0x363 + 18; case 0x483 ... 0x488: return code - 0x483 + 31; case 0x592 ... 0x596: return code - 0x592 + 36; case 0x597 ... 0x59a: return code - 0x597 + 40; case 0x59c ... 0x5a2: return code - 0x59c + 43; case 0x5a8 ... 0x5aa: return code - 0x5a8 + 49; case 0x5ab ... 0x5ad: return code - 0x5ab + 51; case 0x5af ... 0x5b0: return code - 0x5af + 53; case 0x5c4 ... 0x5c5: return code - 0x5c4 + 54; case 0x610 ... 0x618: return code - 0x610 + 55; case 0x657 ... 0x65c: return code - 0x657 + 63; case 0x65d ... 0x65f: return code - 0x65d + 68; case 0x6d6 ... 0x6dd: return code - 0x6d6 + 70; case 0x6df ... 0x6e3: return code - 0x6df + 77; case 0x6e4 ... 0x6e5: return code - 0x6e4 + 81; case 0x6e7 ... 0x6e9: return code - 0x6e7 + 82; case 0x6eb ... 0x6ed: return code - 0x6eb + 84; case 0x730 ... 0x731: return code - 0x730 + 86; case 0x732 ... 0x734: return code - 0x732 + 87; case 0x735 ... 0x737: return code - 0x735 + 89; case 0x73a ... 0x73b: return code - 0x73a + 91; case 0x73d ... 0x73e: return code - 0x73d + 92; case 0x73f ... 0x742: return code - 0x73f + 93; case 0x743 ... 0x744: return code - 0x743 + 96; case 0x745 ... 0x746: return code - 0x745 + 97; case 0x747 ... 0x748: return code - 0x747 + 98; case 0x749 ... 0x74b: return code - 0x749 + 99; case 0x7eb ... 0x7f2: return code - 0x7eb + 101; case 0x7f3 ... 0x7f4: return code - 0x7f3 + 108; case 0x816 ... 0x81a: return code - 0x816 + 109; case 0x81b ... 0x824: return code - 0x81b + 113; case 0x825 ... 0x828: return code - 0x825 + 122; case 0x829 ... 0x82e: return code - 0x829 + 125; case 0x951 ... 0x952: return code - 0x951 + 130; case 0x953 ... 0x955: return code - 0x953 + 131; case 0xf82 ... 0xf84: return code - 0xf82 + 133; case 0xf86 ... 0xf88: return code - 0xf86 + 135; case 0x135d ... 0x1360: return code - 0x135d + 137; case 0x17dd ... 0x17de: return code - 0x17dd + 140; case 0x193a ... 0x193b: return code - 0x193a + 141; case 0x1a17 ... 0x1a18: return code - 0x1a17 + 142; case 0x1a75 ... 0x1a7d: return code - 0x1a75 + 143; case 0x1b6b ... 0x1b6c: return code - 0x1b6b + 151; case 0x1b6d ... 0x1b74: return code - 0x1b6d + 152; case 0x1cd0 ... 0x1cd3: return code - 0x1cd0 + 159; case 0x1cda ... 0x1cdc: return code - 0x1cda + 162; case 0x1ce0 ... 0x1ce1: return code - 0x1ce0 + 164; case 0x1dc0 ... 0x1dc2: return code - 0x1dc0 + 165; case 0x1dc3 ... 0x1dca: return code - 0x1dc3 + 167; case 0x1dcb ... 0x1dcd: return code - 0x1dcb + 174; case 0x1dd1 ... 0x1de7: return code - 0x1dd1 + 176; case 0x1dfe ... 0x1dff: return code - 0x1dfe + 198; case 0x20d0 ... 0x20d2: return code - 0x20d0 + 199; case 0x20d4 ... 0x20d8: return code - 0x20d4 + 201; case 0x20db ... 0x20dd: return code - 0x20db + 205; case 0x20e1 ... 0x20e2: return code - 0x20e1 + 207; case 0x20e7 ... 0x20e8: return code - 0x20e7 + 208; case 0x20e9 ... 0x20ea: return code - 0x20e9 + 209; case 0x20f0 ... 0x20f1: return code - 0x20f0 + 210; case 0x2cef ... 0x2cf2: return code - 0x2cef + 211; case 0x2de0 ... 0x2e00: return code - 0x2de0 + 214; case 0xa66f ... 0xa670: return code - 0xa66f + 246; case 0xa67c ... 0xa67e: return code - 0xa67c + 247; case 0xa6f0 ... 0xa6f2: return code - 0xa6f0 + 249; case 0xa8e0 ... 0xa8f2: return code - 0xa8e0 + 251; case 0xaab0 ... 0xaab1: return code - 0xaab0 + 269; case 0xaab2 ... 0xaab4: return code - 0xaab2 + 270; case 0xaab7 ... 0xaab9: return code - 0xaab7 + 272; case 0xaabe ... 0xaac0: return code - 0xaabe + 274; case 0xaac1 ... 0xaac2: return code - 0xaac1 + 276; case 0xfe20 ... 0xfe27: return code - 0xfe20 + 277; case 0x10a0f ... 0x10a10: return code - 0x10a0f + 284; case 0x10a38 ... 0x10a39: return code - 0x10a38 + 285; case 0x1d185 ... 0x1d18a: return code - 0x1d185 + 286; case 0x1d1aa ... 0x1d1ae: return code - 0x1d1aa + 291; case 0x1d242 ... 0x1d245: return code - 0x1d242 + 295; } return 0; } END_ALLOW_CASE_RANGE kitty-0.41.1/kitty/safe-wrappers.h0000664000175000017510000000521014773370543016401 0ustar nileshnilesh/* * Copyright (C) 2021 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" #include #include #include #include #include static inline int safe_lockf(int fd, int function, off_t size) { while (true) { int ret = lockf(fd, function, size); if (ret != 0 && (errno == EINTR)) continue; return ret; } } static inline int safe_connect(int socket_fd, struct sockaddr *addr, socklen_t addrlen) { while (true) { int ret = connect(socket_fd, addr, addrlen); if (ret < 0 && (errno == EINTR || errno == EAGAIN)) continue; return ret; } } static inline int safe_bind(int socket_fd, struct sockaddr *addr, socklen_t addrlen) { while (true) { int ret = bind(socket_fd, addr, addrlen); if (ret < 0 && (errno == EINTR || errno == EAGAIN)) continue; return ret; } } static inline int safe_accept(int socket_fd, struct sockaddr *addr, socklen_t *addrlen) { while (true) { int ret = accept(socket_fd, addr, addrlen); if (ret < 0 && (errno == EINTR || errno == EAGAIN)) continue; return ret; } } static inline int safe_mkstemp(char *template) { while (true) { int fd = mkstemp(template); if (fd == -1 && errno == EINTR) continue; if (fd > -1) { int flags = fcntl(fd, F_GETFD); if (flags > -1) fcntl(fd, F_SETFD, flags | FD_CLOEXEC); } return fd; } } static inline int safe_open(const char *path, int flags, mode_t mode) { while (true) { int fd = open(path, flags, mode); if (fd == -1 && errno == EINTR) continue; return fd; } } static inline FILE* safe_fopen(const char *path, const char *mode) { while (true) { FILE *f = fopen(path, mode); if (f == NULL && (errno == EINTR || errno == EAGAIN)) continue; return f; } } static inline int safe_shm_open(const char *path, int flags, mode_t mode) { while (true) { int fd = shm_open(path, flags, mode); if (fd == -1 && errno == EINTR) continue; return fd; } } static inline void safe_close(int fd, const char* file UNUSED, const int line UNUSED) { #if 0 printf("Closing fd: %d from file: %s line: %d\n", fd, file, line); #endif while(close(fd) != 0 && errno == EINTR); } static inline int safe_dup(int a) { int ret; while((ret = dup(a)) < 0 && errno == EINTR); return ret; } static inline int safe_dup2(int a, int b) { int ret; while((ret = dup2(a, b)) < 0 && errno == EINTR); return ret; } kitty-0.41.1/kitty/screen.c0000664000175000017510000071270014773370543015105 0ustar nileshnilesh/* * screen.c * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #define EXTRA_INIT { \ PyModule_AddIntMacro(module, SCROLL_LINE); PyModule_AddIntMacro(module, SCROLL_PAGE); PyModule_AddIntMacro(module, SCROLL_FULL); \ PyModule_AddIntMacro(module, EXTEND_CELL); PyModule_AddIntMacro(module, EXTEND_WORD); PyModule_AddIntMacro(module, EXTEND_LINE); \ PyModule_AddIntMacro(module, SCALE_BITS); PyModule_AddIntMacro(module, WIDTH_BITS); PyModule_AddIntMacro(module, SUBSCALE_BITS); \ if (PyModule_AddFunctions(module, module_methods) != 0) return false; \ } #include "data-types.h" #include "control-codes.h" #include "screen.h" #include "state.h" #include "iqsort.h" #include "fonts.h" #include "charsets.h" #include "lineops.h" #include "hyperlink.h" #include #include #include #include #include #include "unicode-data.h" #include "modes.h" #include "char-props.h" #include "wcswidth.h" #include #include "keys.h" #include "vt-parser.h" #include "resize.h" static const ScreenModes empty_modes = {0, .mDECAWM=true, .mDECTCEM=true, .mDECARM=true}; #define CSI_REP_MAX_REPETITIONS 65535u // Constructor/destructor {{{ static void clear_selection(Selections *selections) { selections->in_progress = false; selections->extend_mode = EXTEND_CELL; selections->count = 0; } static void clear_all_selections(Screen *self) { clear_selection(&self->selections); clear_selection(&self->url_ranges); } static void init_tabstops(bool *tabstops, index_type count) { // In terminfo we specify the number of initial tabstops (it) as 8 for (unsigned int t=0; t < count; t++) { tabstops[t] = t % 8 == 0 ? true : false; } } static bool init_overlay_line(Screen *self, index_type columns, bool keep_active) { PyMem_Free(self->overlay_line.cpu_cells); PyMem_Free(self->overlay_line.gpu_cells); PyMem_Free(self->overlay_line.original_line.cpu_cells); PyMem_Free(self->overlay_line.original_line.gpu_cells); self->overlay_line.cpu_cells = PyMem_Calloc(columns, sizeof(CPUCell)); self->overlay_line.gpu_cells = PyMem_Calloc(columns, sizeof(GPUCell)); self->overlay_line.original_line.cpu_cells = PyMem_Calloc(columns, sizeof(CPUCell)); self->overlay_line.original_line.gpu_cells = PyMem_Calloc(columns, sizeof(GPUCell)); if (!self->overlay_line.cpu_cells || !self->overlay_line.gpu_cells || !self->overlay_line.original_line.cpu_cells || !self->overlay_line.original_line.gpu_cells) { PyErr_NoMemory(); return false; } if (!keep_active) { self->overlay_line.is_active = false; self->overlay_line.xnum = 0; } self->overlay_line.is_dirty = true; self->overlay_line.ynum = 0; self->overlay_line.xstart = 0; self->overlay_line.cursor_x = 0; self->overlay_line.last_ime_pos.x = 0; self->overlay_line.last_ime_pos.y = 0; return true; } static void deactivate_overlay_line(Screen *self); static void update_overlay_position(Screen *self); static void render_overlay_line(Screen *self, Line *line, FONTS_DATA_HANDLE fonts_data); static void update_overlay_line_data(Screen *self, uint8_t *data); #define CALLBACK(...) \ if (self->callbacks != Py_None) { \ PyObject *callback_ret = PyObject_CallMethod(self->callbacks, __VA_ARGS__); \ if (callback_ret == NULL) PyErr_Print(); else Py_DECREF(callback_ret); \ } static PyObject* new_screen_object(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { Screen *self; int ret = 0; PyObject *callbacks = Py_None, *test_child = Py_None; unsigned int columns=80, lines=24, scrollback=0, cell_width=10, cell_height=20; id_type window_id=0; if (!PyArg_ParseTuple(args, "|OIIIIIKO", &callbacks, &lines, &columns, &scrollback, &cell_width, &cell_height, &window_id, &test_child)) return NULL; self = (Screen *)type->tp_alloc(type, 0); if (self != NULL) { if ((ret = pthread_mutex_init(&self->write_buf_lock, NULL)) != 0) { Py_CLEAR(self); PyErr_Format(PyExc_RuntimeError, "Failed to create Screen write_buf_lock mutex: %s", strerror(ret)); return NULL; } self->vt_parser = alloc_vt_parser(window_id); if (self->vt_parser == NULL) { Py_CLEAR(self); return PyErr_NoMemory(); } self->text_cache = tc_alloc(); if (!self->text_cache) { Py_CLEAR(self); return PyErr_NoMemory(); } self->reload_all_gpu_data = true; self->cell_size.width = cell_width; self->cell_size.height = cell_height; self->columns = columns; self->lines = lines; self->write_buf_sz = BUFSIZ; self->write_buf = PyMem_RawMalloc(self->write_buf_sz); if (self->write_buf == NULL) { Py_CLEAR(self); return PyErr_NoMemory(); } self->window_id = window_id; self->modes = empty_modes; self->saved_modes = empty_modes; self->is_dirty = true; self->scroll_changed = false; self->margin_top = 0; self->margin_bottom = self->lines - 1; self->history_line_added_count = 0; reset_vt_parser(self->vt_parser); self->callbacks = callbacks; Py_INCREF(callbacks); self->test_child = test_child; Py_INCREF(test_child); self->cursor = alloc_cursor(); self->color_profile = alloc_color_profile(); self->main_linebuf = alloc_linebuf(lines, columns, self->text_cache); self->alt_linebuf = alloc_linebuf(lines, columns, self->text_cache); self->linebuf = self->main_linebuf; self->historybuf = alloc_historybuf(MAX(scrollback, lines), columns, OPT(scrollback_pager_history_size), self->text_cache); self->main_grman = grman_alloc(false); self->alt_grman = grman_alloc(false); self->active_hyperlink_id = 0; self->grman = self->main_grman; self->disable_ligatures = OPT(disable_ligatures); self->main_tabstops = PyMem_Calloc(2 * self->columns, sizeof(bool)); self->lc = alloc_list_of_chars(); if ( self->cursor == NULL || self->main_linebuf == NULL || self->alt_linebuf == NULL || self->main_tabstops == NULL || self->historybuf == NULL || self->main_grman == NULL || self->alt_grman == NULL || self->color_profile == NULL || self->lc == NULL ) { Py_CLEAR(self); return NULL; } grman_set_window_id(self->main_grman, self->window_id); grman_set_window_id(self->alt_grman, self->window_id); self->alt_tabstops = self->main_tabstops + self->columns; self->tabstops = self->main_tabstops; init_tabstops(self->main_tabstops, self->columns); init_tabstops(self->alt_tabstops, self->columns); self->key_encoding_flags = self->main_key_encoding_flags; if (!init_overlay_line(self, self->columns, false)) { Py_CLEAR(self); return NULL; } self->hyperlink_pool = alloc_hyperlink_pool(); if (!self->hyperlink_pool) { Py_CLEAR(self); return PyErr_NoMemory(); } self->as_ansi_buf.hyperlink_pool = self->hyperlink_pool; } return (PyObject*) self; } static Line* range_line_(Screen *self, int y); void screen_reset(Screen *self) { screen_pause_rendering(self, false, 0); self->main_pointer_shape_stack.count = 0; self->alternate_pointer_shape_stack.count = 0; if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self, true, true); if (screen_is_overlay_active(self)) { deactivate_overlay_line(self); // Cancel IME composition update_ime_position_for_window(self->window_id, false, -1); } Py_CLEAR(self->last_reported_cwd); self->cursor_render_info.render_even_when_unfocused = false; memset(self->main_key_encoding_flags, 0, sizeof(self->main_key_encoding_flags)); memset(self->alt_key_encoding_flags, 0, sizeof(self->alt_key_encoding_flags)); self->display_window_char = 0; self->prompt_settings.val = 0; self->last_graphic_char = 0; self->main_savepoint.is_valid = false; self->alt_savepoint.is_valid = false; linebuf_clear(self->linebuf, BLANK_CHAR); historybuf_clear(self->historybuf); clear_hyperlink_pool(self->hyperlink_pool); grman_clear(self->main_grman, false, self->cell_size); // dont delete images in scrollback grman_clear(self->alt_grman, true, self->cell_size); self->modes = empty_modes; self->saved_modes = empty_modes; self->active_hyperlink_id = 0; zero_at_ptr(&self->color_profile->overridden); reset_vt_parser(self->vt_parser); zero_at_ptr(&self->charset); self->margin_top = 0; self->margin_bottom = self->lines - 1; screen_normal_keypad_mode(self); init_tabstops(self->main_tabstops, self->columns); init_tabstops(self->alt_tabstops, self->columns); cursor_reset(self->cursor); self->is_dirty = true; clear_all_selections(self); screen_cursor_position(self, 1, 1); set_dynamic_color(self, 110, NULL); set_dynamic_color(self, 111, NULL); set_color_table_color(self, 104, NULL); } void screen_dirty_sprite_positions(Screen *self) { self->is_dirty = true; for (index_type i = 0; i < self->lines; i++) { linebuf_mark_line_dirty(self->main_linebuf, i); linebuf_mark_line_dirty(self->alt_linebuf, i); } for (index_type i = 0; i < self->historybuf->count; i++) historybuf_mark_line_dirty(self->historybuf, i); } typedef struct CursorTrack { index_type num_content_lines; bool is_beyond_content; struct { index_type x, y; } before; struct { index_type x, y; } after; struct { index_type x, y; } temp; } CursorTrack; static bool rewrap(Screen *screen, unsigned int lines, unsigned int columns, index_type *nclb, index_type *ncla, CursorTrack *cursor, CursorTrack *main_saved_cursor, CursorTrack *alt_saved_cursor, bool main_is_active) { TrackCursor cursors[3]; cursors[2].is_sentinel = true; cursors[0] = (TrackCursor){.x=main_saved_cursor->before.x, .y=main_saved_cursor->before.y}; if (main_is_active) cursors[1] = (TrackCursor){.x=cursor->before.x, .y=cursor->before.y}; else cursors[1].is_sentinel = true; ResizeResult mr = resize_screen_buffers(screen->main_linebuf, screen->historybuf, lines, columns, &screen->as_ansi_buf, cursors); if (!mr.ok) { PyErr_NoMemory(); return false; } main_saved_cursor->temp.x = cursors[0].dest_x; main_saved_cursor->temp.y = cursors[0].dest_y; if (main_is_active) { cursor->temp.x = cursors[1].dest_x; cursor->temp.y = cursors[1].dest_y; } cursors[0] = (TrackCursor){.x=alt_saved_cursor->before.x, .y=alt_saved_cursor->before.y}; if (!main_is_active) cursors[1] = (TrackCursor){.x=cursor->before.x, .y=cursor->before.y}; else cursors[1].is_sentinel = true; ResizeResult ar = resize_screen_buffers(screen->alt_linebuf, NULL, lines, columns, &screen->as_ansi_buf, cursors); if (!ar.ok) { Py_DecRef((PyObject*)mr.lb); Py_DecRef((PyObject*)mr.hb); PyErr_NoMemory(); return false; } alt_saved_cursor->temp.x = cursors[0].dest_x; alt_saved_cursor->temp.y = cursors[0].dest_y; if (!main_is_active) { cursor->temp.x = cursors[1].dest_x; cursor->temp.y = cursors[1].dest_y; } Py_CLEAR(screen->main_linebuf); Py_CLEAR(screen->alt_linebuf); Py_CLEAR(screen->historybuf); screen->main_linebuf = mr.lb; screen->historybuf = mr.hb; screen->alt_linebuf = ar.lb; screen->linebuf = main_is_active ? screen->main_linebuf : screen->alt_linebuf; if (main_is_active) { *nclb = mr.num_content_lines_before; *ncla = mr.num_content_lines_after; } else { *nclb = ar.num_content_lines_before; *ncla = ar.num_content_lines_after; } return true; } static bool is_selection_empty(const Selection *s) { int start_y = (int)s->start.y - (int)s->start_scrolled_by, end_y = (int)s->end.y - (int)s->end_scrolled_by; return s->start.x == s->end.x && s->start.in_left_half_of_cell == s->end.in_left_half_of_cell && start_y == end_y; } static void index_selection(const Screen *self, Selections *selections, bool up) { for (size_t i = 0; i < selections->count; i++) { Selection *s = selections->items + i; if (up) { if (s->start.y == 0) s->start_scrolled_by += 1; else { s->start.y--; if (s->input_start.y) s->input_start.y--; if (s->input_current.y) s->input_current.y--; if (s->initial_extent.start.y) s->initial_extent.start.y--; if (s->initial_extent.end.y) s->initial_extent.end.y--; } if (s->end.y == 0) s->end_scrolled_by += 1; else s->end.y--; } else { if (s->start.y >= self->lines - 1) s->start_scrolled_by -= 1; else { s->start.y++; if (s->input_start.y < self->lines - 1) s->input_start.y++; if (s->input_current.y < self->lines - 1) s->input_current.y++; } if (s->end.y >= self->lines - 1) s->end_scrolled_by -= 1; else s->end.y++; } } } #define INDEX_GRAPHICS(amtv) { \ bool is_main = self->linebuf == self->main_linebuf; \ static ScrollData s; \ s.amt = amtv; s.limit = is_main ? -self->historybuf->ynum : 0; \ s.has_margins = self->margin_top != 0 || self->margin_bottom != self->lines - 1; \ s.margin_top = top; s.margin_bottom = bottom; \ grman_scroll_images(self->grman, &s, self->cell_size); \ } #define INDEX_DOWN \ linebuf_reverse_index(self->linebuf, top, bottom); \ linebuf_clear_line(self->linebuf, top, true); \ if (self->linebuf == self->main_linebuf && self->last_visited_prompt.is_set) { \ if (self->last_visited_prompt.scrolled_by > 0) self->last_visited_prompt.scrolled_by--; \ else if(self->last_visited_prompt.y < self->lines - 1) self->last_visited_prompt.y++; \ else self->last_visited_prompt.is_set = false; \ } \ INDEX_GRAPHICS(1) \ self->is_dirty = true; \ index_selection(self, &self->selections, false); \ clear_selection(&self->url_ranges); static void nuke_in_line(CPUCell *cp, GPUCell *gp, index_type start, index_type x_limit, char_type ch) { for (index_type x = start; x < x_limit; x++) { cell_set_char(cp + x, ch); cp[x].is_multicell = false; clear_sprite_position(gp[x]); } } static void nuke_multicell_char_at(Screen *self, index_type x_, index_type y_, bool replace_with_spaces) { CPUCell *cp; GPUCell *gp; linebuf_init_cells(self->linebuf, y_, &cp, &gp); index_type num_lines_above = cp[x_].y; index_type y_max_limit = MIN(self->lines, y_ + cp[x_].scale - num_lines_above); while (cp[x_].x && x_ > 0) x_--; index_type x_limit = MIN(self->columns, x_ + mcd_x_limit(&cp[x_])); char_type ch = replace_with_spaces ? ' ' : 0; for (index_type y = y_; y < y_max_limit; y++) { linebuf_init_cells(self->linebuf, y, &cp, &gp); nuke_in_line(cp, gp, x_, x_limit, ch); linebuf_mark_line_dirty(self->linebuf, y); } int y_min_limit = -1; if (self->linebuf == self->main_linebuf) y_min_limit = -(self->historybuf->count + 1); for (int y = (int)y_ - 1; y > y_min_limit && num_lines_above; y--, num_lines_above--) { Line *line = range_line_(self, y); cp = line->cpu_cells; gp = line->gpu_cells; nuke_in_line(cp, gp, x_, x_limit, ch); if (y > -1) linebuf_mark_line_dirty(self->linebuf, y); else historybuf_mark_line_dirty(self->historybuf, -(y + 1)); } self->is_dirty = true; } static void nuke_multiline_char_intersecting_with(Screen *self, index_type x_start, index_type x_limit, index_type y_start, index_type y_limit, bool replace_with_spaces) { for (index_type y = y_start; y < y_limit; y++) { CPUCell *cp; GPUCell *gp; linebuf_init_cells(self->linebuf, y, &cp, &gp); for (index_type x = x_start; x < x_limit; x++) { if (cp[x].is_multicell && cp[x].scale > 1) nuke_multicell_char_at(self, x, y, replace_with_spaces); } } } static void nuke_multicell_char_intersecting_with(Screen *self, index_type x_start, index_type x_limit, index_type y_start, index_type y_limit, bool replace_with_spaces) { for (index_type y = y_start; y < y_limit; y++) { CPUCell *cp; GPUCell *gp; linebuf_init_cells(self->linebuf, y, &cp, &gp); for (index_type x = x_start; x < x_limit; x++) { if (cp[x].is_multicell) nuke_multicell_char_at(self, x, y, replace_with_spaces); } } } static void nuke_split_multicell_char_at_left_boundary(Screen *self, index_type x, index_type y, bool replace_with_spaces) { CPUCell *cp = linebuf_cpu_cells_for_line(self->linebuf, y); if (cp[x].is_multicell && cp[x].x) { nuke_multicell_char_at(self, x, y, replace_with_spaces); // remove split multicell char at left edge } } static void nuke_split_multicell_char_at_right_boundary(Screen *self, index_type x, index_type y, bool replace_with_spaces) { CPUCell *cp = linebuf_cpu_cells_for_line(self->linebuf, y); CPUCell *c = cp + x; if (c->is_multicell) { unsigned max_x = mcd_x_limit(c) - 1; if (c->x < max_x) { nuke_multicell_char_at(self, x, y, replace_with_spaces); } } } static void nuke_incomplete_single_line_multicell_chars_in_range( Screen *self, index_type start, index_type limit, index_type y, bool replace_with_spaces ) { CPUCell *cpu_cells; GPUCell *gpu_cells; linebuf_init_cells(self->linebuf, y, &cpu_cells, &gpu_cells); for (index_type x = start; x < limit; x++) { if (cpu_cells[x].is_multicell) { index_type mcd_x_limit = x + cpu_cells[x].width - cpu_cells[x].x; if (cpu_cells[x].x || mcd_x_limit > limit) nuke_in_line(cpu_cells, gpu_cells, x, MIN(mcd_x_limit, limit), replace_with_spaces ? ' ': 0); x = mcd_x_limit; } } } static index_type prevent_current_prompt_from_rewrapping(Screen *self, LineBuf *prompt_copy, index_type *num_of_prompt_lines_above_cursor) { index_type num_of_prompt_lines = 0; *num_of_prompt_lines_above_cursor = 0; if (!self->prompt_settings.redraws_prompts_at_all) return num_of_prompt_lines; int y = self->cursor->y; while (y >= 0) { linebuf_init_line(self->main_linebuf, y); Line *line = self->linebuf->line; switch (line->attrs.prompt_kind) { case UNKNOWN_PROMPT_KIND: break; case PROMPT_START: case SECONDARY_PROMPT: goto found; break; case OUTPUT_START: return num_of_prompt_lines; } y--; } found: if (y < 0) return num_of_prompt_lines; // we have identified a prompt at which the cursor is present, the shell // will redraw this prompt. However when doing so it gets confused if the // cursor vertical position relative to the first prompt line changes. This // can easily be seen for instance in zsh when a right side prompt is used // so when resizing, simply blank all lines after the current // prompt and trust the shell to redraw them. LineBuf *orig = self->linebuf; self->linebuf = self->main_linebuf; // technically only need to nuke partial multichar cells but since we dont // know what the shell will do in terms of clearing, best to be safe and // nuke all nuke_multiline_char_intersecting_with(self, 0, self->columns, y, self->main_linebuf->ynum, true); self->linebuf = orig; for (; y < (int)self->main_linebuf->ynum; y++) { linebuf_init_line(self->main_linebuf, y); linebuf_copy_line_to(prompt_copy, self->main_linebuf->line, num_of_prompt_lines++); linebuf_clear_line(self->main_linebuf, y, false); if (y <= (int)self->cursor->y) { linebuf_init_line(self->main_linebuf, y); // this is needed because screen_resize() checks to see if the cursor is beyond the content, // so insert some fake content cell_set_char(self->main_linebuf->line->cpu_cells, ' '); if (y < (int)self->cursor->y) (*num_of_prompt_lines_above_cursor)++; } } return num_of_prompt_lines; } static bool linebuf_is_line_continued(LineBuf *linebuf, index_type y) { return y ? linebuf_line_ends_with_continuation(linebuf, y - 1) : false; } static bool preserve_blank_output_start_line(Cursor *cursor, LineBuf *linebuf) { if (cursor->x == 0 && cursor->y < linebuf->ynum && !linebuf_is_line_continued(linebuf, cursor->y)) { linebuf_init_line(linebuf, cursor->y); if (!cell_has_text(linebuf->line->cpu_cells)) { // we have a blank output start line, we need it to be preserved by // reflow, so insert a dummy char cell_set_char(linebuf->line->cpu_cells + cursor->x++, '<'); return true; } } return false; } static void remove_blank_output_line_reservation_marker(Cursor *cursor, LineBuf *linebuf) { if (cursor->y < linebuf->ynum) { linebuf_init_line(linebuf, cursor->y); cell_set_char(linebuf->line->cpu_cells, 0); cursor->x = 0; } } static bool screen_resize(Screen *self, unsigned int lines, unsigned int columns) { screen_pause_rendering(self, false, 0); lines = MAX(1u, lines); columns = MAX(1u, columns); bool is_main = self->linebuf == self->main_linebuf; index_type num_content_lines_before, num_content_lines_after; bool main_has_blank_line = false, alt_has_blank_line = false; if (is_main) { main_has_blank_line = preserve_blank_output_start_line(self->cursor, self->linebuf); if (self->alt_savepoint.is_valid) alt_has_blank_line = preserve_blank_output_start_line(&self->alt_savepoint.cursor, self->alt_linebuf); } else { if (self->main_savepoint.is_valid) main_has_blank_line = preserve_blank_output_start_line(&self->main_savepoint.cursor, self->main_linebuf); alt_has_blank_line = preserve_blank_output_start_line(self->cursor, self->linebuf); } unsigned int lines_after_cursor_before_resize = self->lines - self->cursor->y; CursorTrack cursor = {.before = {self->cursor->x, self->cursor->y}}; CursorTrack main_saved_cursor = {.before = {self->main_savepoint.cursor.x, self->main_savepoint.cursor.y}}; CursorTrack alt_saved_cursor = {.before = {self->alt_savepoint.cursor.x, self->alt_savepoint.cursor.y}}; #define setup_cursor(which) { \ which.after.x = which.temp.x; which.after.y = which.temp.y; \ which.is_beyond_content = num_content_lines_before > 0 && self->cursor->y >= num_content_lines_before; \ which.num_content_lines = num_content_lines_after; \ } // Resize overlay line if (!init_overlay_line(self, columns, true)) return false; // Resize main linebuf RAII_PyObject(prompt_copy, NULL); index_type num_of_prompt_lines = 0, num_of_prompt_lines_above_cursor = 0; if (is_main) { prompt_copy = (PyObject*)alloc_linebuf(self->lines, self->columns, self->text_cache); num_of_prompt_lines = prevent_current_prompt_from_rewrapping(self, (LineBuf*)prompt_copy, &num_of_prompt_lines_above_cursor); } if (!rewrap(self, lines, columns, &num_content_lines_before, &num_content_lines_after, &cursor, &main_saved_cursor, &alt_saved_cursor, is_main)) return false; setup_cursor(cursor); /* printf("old_cursor: (%u, %u) new_cursor: (%u, %u) beyond_content: %d\n", self->cursor->x, self->cursor->y, cursor.after.x, cursor.after.y, cursor.is_beyond_content); */ setup_cursor(main_saved_cursor); grman_remove_all_cell_images(self->main_grman); grman_resize(self->main_grman, self->lines, lines, self->columns, columns, num_content_lines_before, num_content_lines_after); setup_cursor(alt_saved_cursor); grman_remove_all_cell_images(self->alt_grman); grman_resize(self->alt_grman, self->lines, lines, self->columns, columns, num_content_lines_before, num_content_lines_after); #undef setup_cursor /* printf("\nold_size: (%u, %u) new_size: (%u, %u)\n", self->columns, self->lines, columns, lines); */ self->lines = lines; self->columns = columns; self->margin_top = 0; self->margin_bottom = self->lines - 1; PyMem_Free(self->main_tabstops); self->main_tabstops = PyMem_Calloc(2*self->columns, sizeof(bool)); if (self->main_tabstops == NULL) { PyErr_NoMemory(); return false; } self->alt_tabstops = self->main_tabstops + self->columns; self->tabstops = self->main_tabstops; init_tabstops(self->main_tabstops, self->columns); init_tabstops(self->alt_tabstops, self->columns); self->is_dirty = true; clear_all_selections(self); self->last_visited_prompt.is_set = false; #define S(c, w) c->x = MIN(w.after.x, self->columns - 1); c->y = MIN(w.after.y, self->lines - 1); S(self->cursor, cursor); S((&(self->main_savepoint.cursor)), main_saved_cursor); S((&(self->alt_savepoint.cursor)), alt_saved_cursor); #undef S if (cursor.is_beyond_content) { self->cursor->y = cursor.num_content_lines; if (self->cursor->y >= self->lines) { self->cursor->y = self->lines - 1; screen_index(self); } } if (is_main && OPT(scrollback_fill_enlarged_window)) { const unsigned int top = 0, bottom = self->lines-1; Savepoint *sp = is_main ? &self->main_savepoint : &self->alt_savepoint; while (self->cursor->y + 1 < self->lines && self->lines - self->cursor->y > lines_after_cursor_before_resize) { if (!historybuf_pop_line(self->historybuf, self->alt_linebuf->line)) break; INDEX_DOWN; linebuf_copy_line_to(self->main_linebuf, self->alt_linebuf->line, 0); self->cursor->y++; sp->cursor.y = MIN(sp->cursor.y + 1, self->lines - 1); } } if (main_has_blank_line) remove_blank_output_line_reservation_marker(is_main ? self->cursor : &self->main_savepoint.cursor, self->main_linebuf); if (alt_has_blank_line) remove_blank_output_line_reservation_marker(is_main ? &self->alt_savepoint.cursor : self->cursor, self->alt_linebuf); if (num_of_prompt_lines) { // Copy the old prompt lines without any reflow this prevents // flickering of prompt during resize. The flicker is caused by the // prompt being first cleared by kitty then sometime later redrawn by // the shell. LineBuf *src = (LineBuf*)prompt_copy; for (index_type src_line = 0, y = num_of_prompt_lines_above_cursor <= self->cursor->y ? self->cursor->y - num_of_prompt_lines_above_cursor : 0; src_line < num_of_prompt_lines && y < self->lines; y++, src_line++ ) { linebuf_init_line(src, src_line); linebuf_copy_line_to(self->main_linebuf, src->line, y); } } return true; } void screen_rescale_images(Screen *self) { grman_remove_all_cell_images(self->main_grman); grman_remove_all_cell_images(self->alt_grman); grman_rescale(self->main_grman, self->cell_size); grman_rescale(self->alt_grman, self->cell_size); } static PyObject* reset_callbacks(Screen *self, PyObject *a UNUSED) { Py_CLEAR(self->callbacks); self->callbacks = Py_None; Py_INCREF(self->callbacks); Py_RETURN_NONE; } static void dealloc(Screen* self) { pthread_mutex_destroy(&self->write_buf_lock); free_vt_parser(self->vt_parser); self->vt_parser = NULL; self->text_cache = tc_decref(self->text_cache); Py_CLEAR(self->main_grman); Py_CLEAR(self->alt_grman); Py_CLEAR(self->last_reported_cwd); PyMem_RawFree(self->write_buf); Py_CLEAR(self->callbacks); Py_CLEAR(self->test_child); Py_CLEAR(self->cursor); Py_CLEAR(self->main_linebuf); Py_CLEAR(self->alt_linebuf); Py_CLEAR(self->historybuf); Py_CLEAR(self->color_profile); Py_CLEAR(self->marker); PyMem_Free(self->overlay_line.cpu_cells); PyMem_Free(self->overlay_line.gpu_cells); PyMem_Free(self->overlay_line.original_line.cpu_cells); PyMem_Free(self->overlay_line.original_line.gpu_cells); Py_CLEAR(self->overlay_line.overlay_text); PyMem_Free(self->main_tabstops); Py_CLEAR(self->paused_rendering.linebuf); Py_CLEAR(self->paused_rendering.grman); free(self->selections.items); free(self->url_ranges.items); free(self->paused_rendering.url_ranges.items); free(self->paused_rendering.selections.items); free_hyperlink_pool(self->hyperlink_pool); free(self->as_ansi_buf.buf); free(self->last_rendered_window_char.canvas); if (self->lc) { cleanup_list_of_chars(self->lc); free(self->lc); self->lc = NULL; } Py_TYPE(self)->tp_free((PyObject*)self); } // }}} // Draw text {{{ typedef struct text_loop_state { bool image_placeholder_marked; const CPUCell cc; const GPUCell g; CPUCell *cp; GPUCell *gp; } text_loop_state; static void continue_to_next_line(Screen *self) { linebuf_set_last_char_as_continuation(self->linebuf, self->cursor->y, true); self->cursor->x = 0; screen_linefeed(self); } static bool selection_has_screen_line(const Selections *selections, const int y) { for (size_t i = 0; i < selections->count; i++) { const Selection *s = selections->items + i; if (!is_selection_empty(s)) { int start = (int)s->start.y - s->start_scrolled_by; int end = (int)s->end.y - s->end_scrolled_by; int top = MIN(start, end); int bottom = MAX(start, end); if (top <= y && y <= bottom) return true; } } return false; } static bool selection_intersects_screen_lines(const Selections *selections, int a, int b) { if (a > b) SWAP(a, b); for (size_t i = 0; i < selections->count; i++) { const Selection *s = selections->items + i; if (!is_selection_empty(s)) { int start = (int)s->start.y - s->start_scrolled_by; int end = (int)s->end.y - s->end_scrolled_by; int top = MIN(start, end); int bottom = MAX(start, end); if ((top <= a && bottom >= a) || (top >= a && top <= b)) return true; } } return false; } static void clear_intersecting_selections(Screen *self, index_type y) { if (selection_has_screen_line(&self->selections, y)) clear_selection(&self->selections); if (selection_has_screen_line(&self->url_ranges, y)) clear_selection(&self->url_ranges); } static void init_text_loop_line(Screen *self, text_loop_state *s) { linebuf_init_cells(self->linebuf, self->cursor->y, &s->cp, &s->gp); clear_intersecting_selections(self, self->cursor->y); linebuf_mark_line_dirty(self->linebuf, self->cursor->y); s->image_placeholder_marked = false; } static void zero_cells(text_loop_state *s, CPUCell *c, GPUCell *g) { *c = s->cc; *g = s->g; } typedef Line*(linefunc_t)(Screen*, int); static void init_line_(Screen *self, index_type y, Line *line) { linebuf_init_line_at(self->linebuf, y, line); } static Line* init_line(Screen *self, index_type y) { init_line_(self, y, self->linebuf->line); return self->linebuf->line; } static void visual_line(Screen *self, int y_, Line *line) { index_type y = MAX(0, y_); if (self->scrolled_by) { if (y < self->scrolled_by) { historybuf_init_line(self->historybuf, self->scrolled_by - 1 - y, line); return; } y -= self->scrolled_by; } init_line_(self, y, line); } static Line* visual_line_(Screen *self, int y_) { index_type y = MAX(0, y_); if (self->scrolled_by) { if (y < self->scrolled_by) { historybuf_init_line(self->historybuf, self->scrolled_by - 1 - y, self->historybuf->line); return self->historybuf->line; } y -= self->scrolled_by; } return init_line(self, y); } static bool visual_line_is_continued(Screen *self, int y_) { index_type y = MAX(0, y_); if (self->scrolled_by) { if (y < self->scrolled_by) return historybuf_is_line_continued(self->historybuf, self->scrolled_by - 1 - y); y -= self->scrolled_by; } if (y) return linebuf_is_line_continued(self->linebuf, y); return self->linebuf == self->main_linebuf ? history_buf_endswith_wrap(self->historybuf) : false; } static Line* range_line_(Screen *self, int y) { if (y < 0) { historybuf_init_line(self->historybuf, -(y + 1), self->historybuf->line); return self->historybuf->line; } return init_line(self, y); } static void range_line(Screen *self, int y, Line *line) { if (y < 0) historybuf_init_line(self->historybuf, -(y + 1), line); else init_line_(self, y, line); } static Line* checked_range_line(Screen *self, int y) { if (-(int)self->historybuf->count <= y && y < (int)self->lines) return range_line_(self, y); return NULL; } static bool range_line_is_continued(Screen *self, int y) { if (!(-(int)self->historybuf->count <= y && y < (int)self->lines)) return false; if (y < 0) return historybuf_is_line_continued(self->historybuf, -(y + 1)); if (y) return linebuf_is_line_continued(self->linebuf, y); return self->linebuf == self->main_linebuf ? history_buf_endswith_wrap(self->historybuf) : false; } static void insert_characters(Screen *self, index_type at, index_type num, index_type y, bool replace_with_spaces) { // insert num chars at x=at setting them to the value of the num chars at [at, at + num) // multiline chars at x >= at are deleted and multicell chars split at x=at // and x=at + num - 1 are deleted nuke_multiline_char_intersecting_with(self, at, self->columns, y, y + 1, replace_with_spaces); nuke_split_multicell_char_at_left_boundary(self, at, y, replace_with_spaces); CPUCell *cp; GPUCell *gp; linebuf_init_cells(self->linebuf, y, &cp, &gp); // right shift for(index_type i = self->columns - 1; i >= at + num; i--) { cp[i] = cp[i - num]; gp[i] = gp[i - num]; } nuke_incomplete_single_line_multicell_chars_in_range(self, at, at + num, y, replace_with_spaces); nuke_split_multicell_char_at_right_boundary(self, self->columns - 1, y, replace_with_spaces); } static bool halve_multicell_width(Screen *self, index_type x_, index_type y_) { CPUCell *cp; GPUCell *gp; linebuf_init_cells(self->linebuf, y_, &cp, &gp); int y_min_limit = -1; if (self->linebuf == self->main_linebuf) y_min_limit = -(self->historybuf->count + 1); int expected_y_min_limit = ((int)y_) - cp[x_].scale; if (expected_y_min_limit < y_min_limit) return false; y_min_limit = expected_y_min_limit; unsigned new_width = cp[x_].width / 2; while (cp[x_].x && x_ > 0) x_--; index_type x_limit = MIN(self->columns, x_ + mcd_x_limit(&cp[x_])); index_type half_x_limit = x_limit / 2; int y_max_limit = MIN(self->lines, y_ + cp[x_].scale); for (int y = y_min_limit + 1; y < y_max_limit; y++) { Line *line = range_line_(self, y); cp = line->cpu_cells; gp = line->gpu_cells; for (index_type x = 0; x < half_x_limit; x++) cp[x].width = new_width; for (index_type x = half_x_limit; x < x_limit; x++) { cp[x] = (CPUCell){0}; clear_sprite_position(gp[x]); } if (y > -1) linebuf_mark_line_dirty(self->linebuf, y); } self->is_dirty = true; return true; } void set_active_hyperlink(Screen *self, char *id, char *url) { if (OPT(allow_hyperlinks)) { if (!url || !url[0]) { self->active_hyperlink_id = 0; return; } self->active_hyperlink_id = get_id_for_hyperlink(self, id, url); } } static bool is_flag_pair(char_type a, char_type b) { return is_flag_codepoint(a) && is_flag_codepoint(b); } static bool add_combining_char(Screen *self, char_type ch, index_type x, index_type y) { CPUCell *cpu_cells = linebuf_cpu_cells_for_line(self->linebuf, y); CPUCell *cell = cpu_cells + x; if (!cell_has_text(cell) || (cell->is_multicell && cell->y)) return false; // don't allow adding combining chars to a null cell text_in_cell(cell, self->text_cache, self->lc); if (self->lc->count >= MAX_NUM_CODEPOINTS_PER_CELL) return false; // don't allow too many combining chars to prevent DoS attacks ensure_space_for_chars(self->lc, self->lc->count + 1); self->lc->chars[self->lc->count++] = ch; cell->ch_or_idx = tc_get_or_insert_chars(self->text_cache, self->lc); cell->ch_is_idx = true; if (cell->is_multicell) { char_type ch_and_idx = cell->ch_and_idx; while (cell->x && x) cell = cpu_cells + --x; index_type x_limit = MIN(x + mcd_x_limit(cell), self->columns); for (index_type v = y; v < y + cell->scale; v++) { cpu_cells = linebuf_cpu_cells_for_line(self->linebuf, v); for (index_type h = x; h < x_limit; h++) cpu_cells[h].ch_and_idx = ch_and_idx; linebuf_mark_line_dirty(self->linebuf, v); } } return true; } static bool draw_second_flag_codepoint(Screen *self, char_type ch) { index_type xpos = 0, ypos = 0; if (self->cursor->x > 1) { ypos = self->cursor->y; xpos = self->cursor->x - 2; } else if (self->cursor->y > 0 && self->columns > 1) { ypos = self->cursor->y - 1; xpos = self->columns - 2; } else return false; CPUCell *cp = linebuf_cpu_cells_for_line(self->linebuf, ypos); CPUCell *cell = cp + xpos; text_in_cell(cell, self->text_cache, self->lc); if (self->lc->count != 1 || !is_flag_pair(self->lc->chars[0], ch)) return false; add_combining_char(self, ch, xpos, ypos); return true; } static bool has_multiline_cells_in_span(const CPUCell *cells, const index_type start, const index_type count) { for (index_type x = start; x < start + count; x++) if (cells[x].y) return true; return false; } static bool move_cursor_past_multicell(Screen *self, index_type required_width) { if (required_width > self->columns) return false; index_type orig_x = self->cursor->x, orig_y = self->cursor->y; while(true) { CPUCell *cp = linebuf_cpu_cells_for_line(self->linebuf, self->cursor->y); while (self->cursor->x + required_width <= self->columns) { if (!has_multiline_cells_in_span(cp, self->cursor->x, required_width)) { if (cp[self->cursor->x].is_multicell) nuke_multicell_char_at(self, self->cursor->x, self->cursor->y, cp[self->cursor->x].x != 0); return true; } self->cursor->x++; } if (self->modes.mDECAWM || has_multiline_cells_in_span(cp, self->columns - required_width, required_width)) { continue_to_next_line(self); } else { self->cursor->x = self->columns - required_width; if (cp[self->cursor->x].is_multicell) nuke_multicell_char_at(self, self->cursor->x, self->cursor->y, cp[self->cursor->x].x != 0); return true; } } self->cursor->x = orig_x; self->cursor->y = orig_y; return false; } static void move_widened_char_past_multiline_chars(Screen *self, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type xpos, index_type ypos) { self->cursor->x = xpos; self->cursor->y = ypos; if (move_cursor_past_multicell(self, 2)) { CPUCell *cp; GPUCell *gp; clear_sprite_position(*gpu_cell); linebuf_init_cells(self->linebuf, self->cursor->y, &cp, &gp); cp[self->cursor->x] = *cpu_cell; gp[self->cursor->x] = *gpu_cell; self->cursor->x++; cp[self->cursor->x] = *cpu_cell; gp[self->cursor->x] = *gpu_cell; cp[self->cursor->x].x = 1; self->cursor->x++; } *cpu_cell = (CPUCell){0}; *gpu_cell = (GPUCell){0}; } static bool is_emoji_presentation_base(char_type ch) { return char_props_for(ch).is_emoji_presentation_base == 1; } static void draw_combining_char(Screen *self, text_loop_state *s, char_type ch) { bool has_prev_char = false; index_type xpos = 0, ypos = 0; if (self->cursor->x > 0) { ypos = self->cursor->y; xpos = self->cursor->x - 1; has_prev_char = true; } else if (self->cursor->y > 0) { ypos = self->cursor->y - 1; xpos = self->columns - 1; has_prev_char = true; } if (!has_prev_char) return; CPUCell *cp; GPUCell *gp; linebuf_init_cells(self->linebuf, ypos, &cp, &gp); while (xpos && cp[xpos].is_multicell && cp[xpos].x) xpos--; if (!add_combining_char(self, ch, xpos, ypos) || self->lc->count < 2) return; unsigned base_pos = self->lc->count - 2; if (ch == VS16) { // emoji presentation variation marker makes default text presentation emoji (narrow emoji) into wide emoji CPUCell *cpu_cell = cp + xpos; GPUCell *gpu_cell = gp + xpos; if (self->lc->chars[base_pos + 1] == VS16 && !cpu_cell->is_multicell && is_emoji_presentation_base(self->lc->chars[base_pos])) { cpu_cell->is_multicell = true; cpu_cell->width = 2; cpu_cell->natural_width = true; if (!cpu_cell->scale) cpu_cell->scale = 1; if (xpos + 1 < self->columns) { CPUCell *second = cp + xpos + 1; if (second->is_multicell) { if (second->y) { move_widened_char_past_multiline_chars(self, cpu_cell, gpu_cell, xpos, ypos); return; } nuke_multicell_char_at(self, xpos + 1, ypos, false); } zero_cells(s, second, gp + xpos + 1); self->cursor->x++; *second = *cpu_cell; second->x = 1; } else move_widened_char_past_multiline_chars(self, cpu_cell, gpu_cell, xpos, ypos); } } else if (ch == VS15) { const CPUCell *cpu_cell = cp + xpos; if (self->lc->chars[base_pos + 1] == VS15 && cpu_cell->is_multicell && cpu_cell->width == 2 && is_emoji_presentation_base(self->lc->chars[base_pos])) { index_type deltax = (cpu_cell->scale * cpu_cell->width) / 2; if (halve_multicell_width(self, xpos, ypos)) self->cursor->x -= deltax; } } } static void screen_on_input(Screen *self) { if (!self->has_activity_since_last_focus && !self->has_focus && self->callbacks != Py_None) { PyObject *ret = PyObject_CallMethod(self->callbacks, "on_activity_since_last_focus", NULL); if (ret == NULL) PyErr_Print(); else { if (ret == Py_True) self->has_activity_since_last_focus = true; Py_DECREF(ret); } } } static void replace_multicell_char_under_cursor_with_spaces(Screen *self) { nuke_multicell_char_at(self, self->cursor->x, self->cursor->y, true); } static void screen_change_charset(Screen *self, uint32_t which) { switch(which) { case 0: self->charset.current_num = 0; self->charset.current = self->charset.zero; break; case 1: self->charset.current_num = 1; self->charset.current = self->charset.one; break; } } void screen_designate_charset(Screen *self, uint32_t which, uint32_t as) { switch(which) { case 0: self->charset.zero = translation_table(as); if (self->charset.current_num == 0) self->charset.current = self->charset.zero; break; case 1: self->charset.one = translation_table(as); if (self->charset.current_num == 1) self->charset.current = self->charset.one; break; } } static uint32_t map_char(Screen *self, const uint32_t ch) { return UNLIKELY(self->charset.current && ch < 256) ? self->charset.current[ch] : ch; } static void draw_control_char(Screen *self, text_loop_state *s, uint32_t ch) { switch (ch) { case BEL: screen_bell(self); break; case BS: screen_backspace(self); break; case HT: if (UNLIKELY(self->cursor->x >= self->columns)) { if (self->modes.mDECAWM) { // xterm discards the TAB in this case so match its behavior continue_to_next_line(self); init_text_loop_line(self, s); } else if (self->columns > 0){ self->cursor->x = self->columns - 1; if (s->cp[self->cursor->x].is_multicell) { if (s->cp[self->cursor->x].y) move_cursor_past_multicell(self, 1); else replace_multicell_char_under_cursor_with_spaces(self); } screen_tab(self); } } else screen_tab(self); break; case SI: screen_change_charset(self, 0); break; case SO: screen_change_charset(self, 1); break; case LF: case VT: case FF: screen_linefeed(self); init_text_loop_line(self, s); break; case CR: screen_carriage_return(self); break; default: break; } } static void draw_text_loop(Screen *self, const uint32_t *chars, size_t num_chars, text_loop_state *s) { init_text_loop_line(self, s); for (size_t i = 0; i < num_chars; i++) { uint32_t ch = map_char(self, chars[i]); if (ch < ' ') { draw_control_char(self, s, ch); continue; } if (self->cursor->x < self->columns && s->cp[self->cursor->x].is_multicell && !char_props_for(ch).is_combining_char) { if (s->cp[self->cursor->x].y) { move_cursor_past_multicell(self, 1); init_text_loop_line(self, s); } else nuke_multicell_char_at(self, self->cursor->x, self->cursor->y, s->cp[self->cursor->x].x != 0); } int char_width = 1; if (ch > DEL) { // not printable ASCII CharProps cp = char_props_for(ch); if (cp.is_invalid) continue; if (UNLIKELY(cp.is_combining_char)) { if (UNLIKELY(is_flag_codepoint(ch))) { if (draw_second_flag_codepoint(self, ch)) continue; } else { draw_combining_char(self, s, ch); continue; } } char_width = wcwidth_std(cp); if (UNLIKELY(char_width < 1)) { if (char_width == 0) continue; char_width = 1; } } self->last_graphic_char = ch; if (UNLIKELY(self->columns < self->cursor->x + (unsigned int)char_width)) { if (self->modes.mDECAWM) { continue_to_next_line(self); init_text_loop_line(self, s); } else self->cursor->x = self->columns - char_width; CPUCell *c = &s->cp[self->cursor->x]; if (c->is_multicell) { if (c->y) { move_cursor_past_multicell(self, char_width); init_text_loop_line(self, s); } nuke_multicell_char_at(self, self->cursor->x, self->cursor->y, c->x > 0); } } if (self->modes.mIRM) insert_characters(self, self->cursor->x, char_width, self->cursor->y, true); if (UNLIKELY(!s->image_placeholder_marked && ch == IMAGE_PLACEHOLDER_CHAR)) { linebuf_set_line_has_image_placeholders(self->linebuf, self->cursor->y, true); s->image_placeholder_marked = true; } CPUCell *fc = s->cp + self->cursor->x; if (char_width == 2) { CPUCell *second = fc + 1; if (second->is_multicell) { if (second->y) { self->cursor->x++; move_cursor_past_multicell(self, 2); fc = s->cp + self->cursor->x; second = fc + 1; } else nuke_multicell_char_at(self, self->cursor->x + 1, self->cursor->y, true); } zero_cells(s, fc, s->gp + self->cursor->x); *fc = (CPUCell){.ch_or_idx=ch, .is_multicell=true, .width=2, .scale=1, .natural_width=true}; *second = *fc; second->x = 1; s->gp[self->cursor->x + 1] = s->gp[self->cursor->x]; self->cursor->x += 2; } else { zero_cells(s, fc, s->gp + self->cursor->x); cell_set_char(fc, ch); self->cursor->x++; fc->is_multicell = false; } } #undef init_line } #define PREPARE_FOR_DRAW_TEXT \ const bool force_underline = OPT(underline_hyperlinks) == UNDERLINE_ALWAYS && self->active_hyperlink_id != 0; \ CellAttrs attrs = cursor_to_attrs(self->cursor); \ if (force_underline) attrs.decoration = OPT(url_style); \ text_loop_state s={ \ .cc=(CPUCell){.hyperlink_id=self->active_hyperlink_id}, \ .g=(GPUCell){ \ .attrs=attrs, \ .fg=self->cursor->fg & COL_MASK, .bg=self->cursor->bg & COL_MASK, \ .decoration_fg=force_underline ? ((OPT(url_color) & COL_MASK) << 8) | 2 : self->cursor->decoration_fg & COL_MASK, \ } \ }; static void draw_text(Screen *self, const uint32_t *chars, size_t num_chars) { PREPARE_FOR_DRAW_TEXT; self->is_dirty = true; draw_text_loop(self, chars, num_chars, &s); } void screen_draw_text(Screen *self, const uint32_t *chars, size_t num_chars) { screen_on_input(self); draw_text(self, chars, num_chars); } static void draw_codepoint(Screen *self, char_type ch) { uint32_t lch = self->last_graphic_char; draw_text(self, &ch, 1); self->last_graphic_char = lch; } void screen_align(Screen *self) { self->margin_top = 0; self->margin_bottom = self->lines - 1; screen_cursor_position(self, 1, 1); linebuf_clear(self->linebuf, 'E'); } static size_t decode_utf8_safe_string(const uint8_t *src, size_t sz, uint32_t *dest) { // dest must be an array of size at least sz uint32_t codep = 0; UTF8State state = 0, prev = UTF8_ACCEPT; size_t i = 0, d = 0; for (; i < sz; i++) { switch(decode_utf8(&state, &codep, src[i])) { case UTF8_ACCEPT: // Ignore C0 and C1 chars if (codep >= ' ' && !(DEL <= codep && codep <= 159)) dest[d++] = codep; break; case UTF8_REJECT: state = UTF8_ACCEPT; if (prev != UTF8_ACCEPT && i > 0) i--; break; } prev = state; } return d; } static void handle_fixed_width_multicell_command(Screen *self, CPUCell mcd, ListOfChars *lc) { index_type width = mcd.width * mcd.scale; index_type height = mcd.scale; index_type max_height = self->margin_bottom - self->margin_top + 1; if (width > self->columns || height > max_height) return; lc->count = MIN(lc->count, MAX_NUM_CODEPOINTS_PER_CELL); PREPARE_FOR_DRAW_TEXT; mcd.hyperlink_id = s.cc.hyperlink_id; cell_set_chars(&mcd, self->text_cache, lc); move_cursor_past_multicell(self, width); if (height > 1) { index_type available_height = self->margin_bottom - self->cursor->y + 1; if (height > available_height) { index_type extra_lines = height - available_height; screen_scroll(self, extra_lines); self->cursor->y -= extra_lines; } } if (self->modes.mIRM) { for (index_type y = self->cursor->y; y < self->cursor->y + height; y++) { if (self->modes.mIRM) insert_characters(self, self->cursor->x, width, y, true); } } for (index_type y = self->cursor->y; y < self->cursor->y + height; y++) { linebuf_init_cells(self->linebuf, y, &s.cp, &s.gp); linebuf_mark_line_dirty(self->linebuf, y); mcd.x = 0; mcd.y = y - self->cursor->y; for (index_type x = self->cursor->x; x < self->cursor->x + width; x++, mcd.x++) { if (s.cp[x].is_multicell) nuke_multicell_char_at(self, x, y, s.cp[x].x + s.cp[x].y > 0); s.cp[x] = mcd; s.gp[x] = s.g; } } self->cursor->x += width; self->is_dirty = true; } static void handle_variable_width_multicell_command(Screen *self, CPUCell mcd, ListOfChars *lc) { ensure_space_for_chars(lc, lc->count + 1); lc->chars[lc->count] = 0; mcd.width = wcswidth_string(lc->chars); if (!mcd.width) { lc->count = 0; return; } handle_fixed_width_multicell_command(self, mcd, lc); lc->count = 0; } void screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const uint8_t *payload) { screen_on_input(self); if (!cmd->payload_sz) return; ensure_space_for_chars(self->lc, cmd->payload_sz + 1); self->lc->count = decode_utf8_safe_string(payload, cmd->payload_sz, self->lc->chars); if (!self->lc->count) return; #define M(x) ( (1u << x) - 1u) CPUCell mcd = { .width=MIN(cmd->width, M(WIDTH_BITS)), .scale=MAX(1u, MIN(cmd->scale, M(SCALE_BITS))), .subscale_n=MIN(cmd->subscale_n, M(SUBSCALE_BITS)), .subscale_d=MIN(cmd->subscale_d, M(SUBSCALE_BITS)), .valign=MIN(cmd->vertical_align, M(VALIGN_BITS)), .halign=MIN(cmd->horizontal_align, M(HALIGN_BITS)), .is_multicell=true }; #undef M if (mcd.width) handle_fixed_width_multicell_command(self, mcd, self->lc); else { RAII_ListOfChars(lc); mcd.natural_width = true; for (unsigned i = 0; i < self->lc->count; i++) { char_type ch = self->lc->chars[i]; CharProps cp = char_props_for(ch); if (cp.is_invalid) continue; if (cp.is_combining_char) { if (is_flag_codepoint(ch)) { if (lc.count == 1) { if (is_flag_pair(lc.chars[0], ch)) { lc.chars[lc.count++] = ch; handle_variable_width_multicell_command(self, mcd, &lc); } else { handle_variable_width_multicell_command(self, mcd, &lc); lc.chars[lc.count++] = ch; } } else { handle_variable_width_multicell_command(self, mcd, &lc); lc.chars[lc.count++] = ch; } } else { if (!lc.count) continue; lc.chars[lc.count++] = ch; } } else { if (lc.count) handle_variable_width_multicell_command(self, mcd, &lc); lc.chars[lc.count++] = ch; } } if (lc.count) handle_variable_width_multicell_command(self, mcd, &lc); } } // }}} // Graphics {{{ void screen_alignment_display(Screen *self) { // https://www.vt100.net/docs/vt510-rm/DECALN.html screen_cursor_position(self, 1, 1); self->margin_top = 0; self->margin_bottom = self->lines - 1; for (unsigned int y = 0; y < self->linebuf->ynum; y++) { linebuf_init_line(self->linebuf, y); line_clear_text(self->linebuf->line, 0, self->linebuf->xnum, 'E'); linebuf_mark_line_dirty(self->linebuf, y); } } void select_graphic_rendition(Screen *self, int *params, unsigned int count, bool is_group, Region *region_) { if (region_) { Region region = *region_; if (!region.top) region.top = 1; if (!region.left) region.left = 1; if (!region.bottom) region.bottom = self->lines; if (!region.right) region.right = self->columns; if (self->modes.mDECOM) { region.top += self->margin_top; region.bottom += self->margin_top; } region.left -= 1; region.top -= 1; region.right -= 1; region.bottom -= 1; // switch to zero based indexing if (self->modes.mDECSACE) { index_type x = MIN(region.left, self->columns - 1); index_type num = region.right >= x ? region.right - x + 1 : 0; num = MIN(num, self->columns - x); for (index_type y = region.top; y < MIN(region.bottom + 1, self->lines); y++) { linebuf_init_line(self->linebuf, y); apply_sgr_to_cells(self->linebuf->line->gpu_cells + x, num, params, count, is_group); } } else { index_type x, num; if (region.top == region.bottom) { linebuf_init_line(self->linebuf, region.top); x = MIN(region.left, self->columns-1); num = MIN(self->columns - x, region.right - x + 1); apply_sgr_to_cells(self->linebuf->line->gpu_cells + x, num, params, count, is_group); } else { for (index_type y = region.top; y < MIN(region.bottom + 1, self->lines); y++) { if (y == region.top) { x = MIN(region.left, self->columns - 1); num = self->columns - x; } else if (y == region.bottom) { x = 0; num = MIN(region.right + 1, self->columns); } else { x = 0; num = self->columns; } linebuf_init_line(self->linebuf, y); apply_sgr_to_cells(self->linebuf->line->gpu_cells + x, num, params, count, is_group); } } } } else cursor_from_sgr(self->cursor, params, count, is_group); } static void write_to_test_child(Screen *self, const char *data, size_t sz) { PyObject *r = PyObject_CallMethod(self->test_child, "write", "y#", data, sz); if (r == NULL) PyErr_Print(); Py_CLEAR(r); } static bool write_to_child(Screen *self, const char *data, size_t sz) { bool written = false; if (self->window_id) written = schedule_write_to_child(self->window_id, 1, data, sz); if (self->test_child != Py_None) { write_to_test_child(self, data, sz); } return written; } static void get_prefix_and_suffix_for_escape_code(unsigned char which, const char ** prefix, const char ** suffix) { *suffix = "\033\\"; switch(which) { case ESC_DCS: *prefix = "\033P"; break; case ESC_CSI: *prefix = "\033["; *suffix = ""; break; case ESC_OSC: *prefix = "\033]"; break; case ESC_PM: *prefix = "\033^"; break; case ESC_APC: *prefix = "\033_"; break; default: fatal("Unknown escape code to write: %u", which); } } bool write_escape_code_to_child(Screen *self, unsigned char which, const char *data) { bool written = false; const char *prefix, *suffix; get_prefix_and_suffix_for_escape_code(which, &prefix, &suffix); if (self->window_id) { if (suffix[0]) { written = schedule_write_to_child(self->window_id, 3, prefix, strlen(prefix), data, strlen(data), suffix, strlen(suffix)); } else { written = schedule_write_to_child(self->window_id, 2, prefix, strlen(prefix), data, strlen(data)); } } if (self->test_child != Py_None) { write_to_test_child(self, prefix, strlen(prefix)); write_to_test_child(self, data, strlen(data)); if (suffix[0]) write_to_test_child(self, suffix, strlen(suffix)); } return written; } static bool write_escape_code_to_child_python(Screen *self, unsigned char which, PyObject *data) { bool written = false; const char *prefix, *suffix; get_prefix_and_suffix_for_escape_code(which, &prefix, &suffix); if (self->window_id) written = schedule_write_to_child_python(self->window_id, prefix, data, suffix); if (self->test_child != Py_None) { write_to_test_child(self, prefix, strlen(prefix)); for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(data); i++) { PyObject *t = PyTuple_GET_ITEM(data, i); if (PyBytes_Check(t)) write_to_test_child(self, PyBytes_AS_STRING(t), PyBytes_GET_SIZE(t)); else { Py_ssize_t sz; const char *d = PyUnicode_AsUTF8AndSize(t, &sz); if (d) write_to_test_child(self, d, sz); } } if (suffix[0]) write_to_test_child(self, suffix, strlen(suffix)); } return written; } static bool cursor_within_margins(Screen *self) { return self->margin_top <= self->cursor->y && self->cursor->y <= self->margin_bottom; } // Remove all cell images from a portion of the screen and mark lines that // contain image placeholders as dirty to make sure they are redrawn. This is // needed when we perform commands that may move some lines without marking them // as dirty (like screen_insert_lines) and at the same time don't move image // references (i.e. unlike screen_scroll, which moves everything). static void screen_dirty_line_graphics(Screen *self, const unsigned int top, const unsigned int bottom, const bool main_buf) { bool need_to_remove = false; const unsigned int limit = MIN(bottom+1, self->lines); LineBuf *linebuf = main_buf ? self->main_linebuf : self->alt_linebuf; for (unsigned int y = top; y < limit; y++) { if (linebuf->line_attrs[y].has_image_placeholders) { need_to_remove = true; linebuf_mark_line_dirty(linebuf, y); self->is_dirty = true; } } if (need_to_remove) grman_remove_cell_images(main_buf ? self->main_grman : self->alt_grman, top, bottom); } void screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const uint8_t *payload) { unsigned int x = self->cursor->x, y = self->cursor->y; const char *response = grman_handle_command(self->grman, cmd, payload, self->cursor, &self->is_dirty, self->cell_size); if (response != NULL) write_escape_code_to_child(self, ESC_APC, response); if (x != self->cursor->x || y != self->cursor->y) { bool in_margins = cursor_within_margins(self); if (self->cursor->x >= self->columns) { self->cursor->x = 0; self->cursor->y++; } if (self->cursor->y > self->margin_bottom) screen_scroll(self, self->cursor->y - self->margin_bottom); screen_ensure_bounds(self, false, in_margins); } if (cmd->unicode_placement) { // Make sure the placeholders are redrawn if we add or change a virtual placement. screen_dirty_line_graphics(self, 0, self->lines, self->linebuf == self->main_linebuf); } } // }}} // Modes {{{ void screen_toggle_screen_buffer(Screen *self, bool save_cursor, bool clear_alt_screen) { bool to_alt = self->linebuf == self->main_linebuf; self->active_hyperlink_id = 0; if (to_alt) { if (clear_alt_screen) { linebuf_clear(self->alt_linebuf, BLANK_CHAR); grman_clear(self->alt_grman, true, self->cell_size); } if (save_cursor) screen_save_cursor(self); self->linebuf = self->alt_linebuf; self->tabstops = self->alt_tabstops; self->key_encoding_flags = self->alt_key_encoding_flags; self->grman = self->alt_grman; screen_cursor_position(self, 1, 1); cursor_reset(self->cursor); } else { self->linebuf = self->main_linebuf; self->tabstops = self->main_tabstops; self->key_encoding_flags = self->main_key_encoding_flags; if (save_cursor) screen_restore_cursor(self); self->grman = self->main_grman; } screen_history_scroll(self, SCROLL_FULL, false); self->is_dirty = true; grman_mark_layers_dirty(self->grman); clear_all_selections(self); global_state.check_for_active_animated_images = true; } void screen_normal_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI void screen_alternate_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI static void set_mode_from_const(Screen *self, unsigned int mode, bool val) { #define SIMPLE_MODE(name) \ case name: \ self->modes.m##name = val; break; #define MOUSE_MODE(name, attr, value) \ case name: \ self->modes.attr = val ? value : 0; break; bool private; switch(mode) { SIMPLE_MODE(LNM) SIMPLE_MODE(IRM) SIMPLE_MODE(DECARM) SIMPLE_MODE(BRACKETED_PASTE) SIMPLE_MODE(FOCUS_TRACKING) SIMPLE_MODE(COLOR_PREFERENCE_NOTIFICATION) SIMPLE_MODE(HANDLE_TERMIOS_SIGNALS) MOUSE_MODE(MOUSE_BUTTON_TRACKING, mouse_tracking_mode, BUTTON_MODE) MOUSE_MODE(MOUSE_MOTION_TRACKING, mouse_tracking_mode, MOTION_MODE) MOUSE_MODE(MOUSE_MOVE_TRACKING, mouse_tracking_mode, ANY_MODE) MOUSE_MODE(MOUSE_UTF8_MODE, mouse_tracking_protocol, UTF8_PROTOCOL) MOUSE_MODE(MOUSE_SGR_MODE, mouse_tracking_protocol, SGR_PROTOCOL) MOUSE_MODE(MOUSE_SGR_PIXEL_MODE, mouse_tracking_protocol, SGR_PIXEL_PROTOCOL) MOUSE_MODE(MOUSE_URXVT_MODE, mouse_tracking_protocol, URXVT_PROTOCOL) case DECSCLM: case DECNRCM: break; // we ignore these modes case DECCKM: self->modes.mDECCKM = val; break; case DECTCEM: self->modes.mDECTCEM = val; break; case DECSCNM: // Render screen in reverse video if (self->modes.mDECSCNM != val) { self->modes.mDECSCNM = val; self->is_dirty = true; } break; case DECOM: self->modes.mDECOM = val; // According to `vttest`, DECOM should also home the cursor, see // vttest/main.c:369. screen_cursor_position(self, 1, 1); break; case DECAWM: self->modes.mDECAWM = val; break; case DECCOLM: self->modes.mDECCOLM = val; if (val) { // When DECCOLM mode is set, the screen is erased and the cursor // moves to the home position. screen_erase_in_display(self, 2, false); screen_cursor_position(self, 1, 1); } break; case CONTROL_CURSOR_BLINK: self->cursor->non_blinking = !val; break; case SAVE_CURSOR: screen_save_cursor(self); break; case TOGGLE_ALT_SCREEN_1: case TOGGLE_ALT_SCREEN_2: case ALTERNATE_SCREEN: if (val && self->linebuf == self->main_linebuf) screen_toggle_screen_buffer(self, mode == ALTERNATE_SCREEN, mode == ALTERNATE_SCREEN); else if (!val && self->linebuf != self->main_linebuf) screen_toggle_screen_buffer(self, mode == ALTERNATE_SCREEN, mode == ALTERNATE_SCREEN); break; case 7727 << 5: log_error("Application escape mode is not supported, the extended keyboard protocol should be used instead"); break; case PENDING_MODE << 5: if (!screen_pause_rendering(self, val, 0)) { log_error("Pending mode change to already current mode (%d) requested. Either pending mode expired or there is an application bug.", val); } break; case INBAND_RESIZE_NOTIFICATION: self->modes.mINBAND_RESIZE_NOTIFICATION = val; if (val) CALLBACK("notify_child_of_resize", NULL); break; default: private = mode >= 1 << 5; if (private) mode >>= 5; log_error("%s %s %u %s", ERROR_PREFIX, "Unsupported screen mode: ", mode, private ? "(private)" : ""); } #undef SIMPLE_MODE #undef MOUSE_MODE } void screen_set_mode(Screen *self, unsigned int mode) { set_mode_from_const(self, mode, true); } void screen_decsace(Screen *self, unsigned int val) { self->modes.mDECSACE = val == 2 ? true : false; } void screen_reset_mode(Screen *self, unsigned int mode) { set_mode_from_const(self, mode, false); } void screen_modify_other_keys(Screen *self, unsigned int val) { // Only report an error about modifyOtherKeys if the kitty keyboard // protocol is not in effect and the application is trying to turn it on. There are some applications that try to enable both. debug_input("modifyOtherKeys: %u\n", val); if (!screen_current_key_encoding_flags(self) && val) { log_error("The application is trying to use xterm's modifyOtherKeys. This is superseded by the kitty keyboard protocol https://sw.kovidgoyal.net/kitty/keyboard-protocol. The application should be updated to use that."); } } uint8_t screen_current_key_encoding_flags(Screen *self) { for (unsigned i = arraysz(self->main_key_encoding_flags); i-- > 0; ) { if (self->key_encoding_flags[i] & 0x80) return self->key_encoding_flags[i] & 0x7f; } return 0; } void screen_report_key_encoding_flags(Screen *self) { char buf[16] = {0}; debug_input("\x1b[35mReporting key encoding flags: %u\x1b[39m\n", screen_current_key_encoding_flags(self)); snprintf(buf, sizeof(buf), "?%uu", screen_current_key_encoding_flags(self)); write_escape_code_to_child(self, ESC_CSI, buf); } void screen_set_key_encoding_flags(Screen *self, uint32_t val, uint32_t how) { unsigned idx = 0; for (unsigned i = arraysz(self->main_key_encoding_flags); i-- > 0; ) { if (self->key_encoding_flags[i] & 0x80) { idx = i; break; } } uint8_t q = val & 0x7f; if (how == 1) self->key_encoding_flags[idx] = q; else if (how == 2) self->key_encoding_flags[idx] |= q; else if (how == 3) self->key_encoding_flags[idx] &= ~q; self->key_encoding_flags[idx] |= 0x80; debug_input("\x1b[35mSet key encoding flags to: %u\x1b[39m\n", screen_current_key_encoding_flags(self)); } void screen_push_key_encoding_flags(Screen *self, uint32_t val) { uint8_t q = val & 0x7f; const unsigned sz = arraysz(self->main_key_encoding_flags); unsigned current_idx = 0; for (unsigned i = arraysz(self->main_key_encoding_flags); i-- > 0; ) { if (self->key_encoding_flags[i] & 0x80) { current_idx = i; break; } } if (current_idx == sz - 1) memmove(self->key_encoding_flags, self->key_encoding_flags + 1, (sz - 1) * sizeof(self->main_key_encoding_flags[0])); else self->key_encoding_flags[current_idx++] |= 0x80; self->key_encoding_flags[current_idx] = 0x80 | q; debug_input("\x1b[35mPushed key encoding flags to: %u\x1b[39m\n", screen_current_key_encoding_flags(self)); } void screen_pop_key_encoding_flags(Screen *self, uint32_t num) { for (unsigned i = arraysz(self->main_key_encoding_flags); num && i-- > 0; ) { if (self->key_encoding_flags[i] & 0x80) { num--; self->key_encoding_flags[i] = 0; } } debug_input("\x1b[35mPopped key encoding flags to: %u\x1b[39m\n", screen_current_key_encoding_flags(self)); } // }}} // Cursor {{{ MouseShape screen_pointer_shape(Screen *self) { if (self->linebuf == self->main_linebuf) { if (self->main_pointer_shape_stack.count) return self->main_pointer_shape_stack.stack[self->main_pointer_shape_stack.count-1]; } else { if (self->alternate_pointer_shape_stack.count) return self->alternate_pointer_shape_stack.stack[self->alternate_pointer_shape_stack.count-1]; } return INVALID_POINTER; } static PyObject* current_pointer_shape(Screen *self, PyObject *args UNUSED) { MouseShape s = screen_pointer_shape(self); const char *ans = "0"; switch(s) { case INVALID_POINTER: break; /* start enum to css (auto generated by gen-key-constants.py do not edit) */ case DEFAULT_POINTER: ans = "default"; break; case TEXT_POINTER: ans = "text"; break; case POINTER_POINTER: ans = "pointer"; break; case HELP_POINTER: ans = "help"; break; case WAIT_POINTER: ans = "wait"; break; case PROGRESS_POINTER: ans = "progress"; break; case CROSSHAIR_POINTER: ans = "crosshair"; break; case CELL_POINTER: ans = "cell"; break; case VERTICAL_TEXT_POINTER: ans = "vertical-text"; break; case MOVE_POINTER: ans = "move"; break; case E_RESIZE_POINTER: ans = "e-resize"; break; case NE_RESIZE_POINTER: ans = "ne-resize"; break; case NW_RESIZE_POINTER: ans = "nw-resize"; break; case N_RESIZE_POINTER: ans = "n-resize"; break; case SE_RESIZE_POINTER: ans = "se-resize"; break; case SW_RESIZE_POINTER: ans = "sw-resize"; break; case S_RESIZE_POINTER: ans = "s-resize"; break; case W_RESIZE_POINTER: ans = "w-resize"; break; case EW_RESIZE_POINTER: ans = "ew-resize"; break; case NS_RESIZE_POINTER: ans = "ns-resize"; break; case NESW_RESIZE_POINTER: ans = "nesw-resize"; break; case NWSE_RESIZE_POINTER: ans = "nwse-resize"; break; case ZOOM_IN_POINTER: ans = "zoom-in"; break; case ZOOM_OUT_POINTER: ans = "zoom-out"; break; case ALIAS_POINTER: ans = "alias"; break; case COPY_POINTER: ans = "copy"; break; case NOT_ALLOWED_POINTER: ans = "not-allowed"; break; case NO_DROP_POINTER: ans = "no-drop"; break; case GRAB_POINTER: ans = "grab"; break; case GRABBING_POINTER: ans = "grabbing"; break; /* end enum to css */ } return PyUnicode_FromString(ans); } static PyObject* change_pointer_shape(Screen *self, PyObject *args) { char op; const char *css_name, *b; if (!PyArg_ParseTuple(args, "ss", &b, &css_name)) return NULL; op = b[0]; uint8_t *count, *stack; if (self->main_linebuf == self->linebuf) { count = &self->main_pointer_shape_stack.count; stack = self->main_pointer_shape_stack.stack; } else { count = &self->alternate_pointer_shape_stack.count; stack = self->alternate_pointer_shape_stack.stack; } if (op == '<') { if (*count) *count -= 1; } else { MouseShape s = INVALID_POINTER; if (css_name[0] == 0) s = INVALID_POINTER; /* start css to enum (auto generated by gen-key-constants.py do not edit) */ else if (strcmp("default", css_name) == 0) s = DEFAULT_POINTER; else if (strcmp("text", css_name) == 0) s = TEXT_POINTER; else if (strcmp("pointer", css_name) == 0) s = POINTER_POINTER; else if (strcmp("help", css_name) == 0) s = HELP_POINTER; else if (strcmp("wait", css_name) == 0) s = WAIT_POINTER; else if (strcmp("progress", css_name) == 0) s = PROGRESS_POINTER; else if (strcmp("crosshair", css_name) == 0) s = CROSSHAIR_POINTER; else if (strcmp("cell", css_name) == 0) s = CELL_POINTER; else if (strcmp("vertical-text", css_name) == 0) s = VERTICAL_TEXT_POINTER; else if (strcmp("move", css_name) == 0) s = MOVE_POINTER; else if (strcmp("e-resize", css_name) == 0) s = E_RESIZE_POINTER; else if (strcmp("ne-resize", css_name) == 0) s = NE_RESIZE_POINTER; else if (strcmp("nw-resize", css_name) == 0) s = NW_RESIZE_POINTER; else if (strcmp("n-resize", css_name) == 0) s = N_RESIZE_POINTER; else if (strcmp("se-resize", css_name) == 0) s = SE_RESIZE_POINTER; else if (strcmp("sw-resize", css_name) == 0) s = SW_RESIZE_POINTER; else if (strcmp("s-resize", css_name) == 0) s = S_RESIZE_POINTER; else if (strcmp("w-resize", css_name) == 0) s = W_RESIZE_POINTER; else if (strcmp("ew-resize", css_name) == 0) s = EW_RESIZE_POINTER; else if (strcmp("ns-resize", css_name) == 0) s = NS_RESIZE_POINTER; else if (strcmp("nesw-resize", css_name) == 0) s = NESW_RESIZE_POINTER; else if (strcmp("nwse-resize", css_name) == 0) s = NWSE_RESIZE_POINTER; else if (strcmp("zoom-in", css_name) == 0) s = ZOOM_IN_POINTER; else if (strcmp("zoom-out", css_name) == 0) s = ZOOM_OUT_POINTER; else if (strcmp("alias", css_name) == 0) s = ALIAS_POINTER; else if (strcmp("copy", css_name) == 0) s = COPY_POINTER; else if (strcmp("not-allowed", css_name) == 0) s = NOT_ALLOWED_POINTER; else if (strcmp("no-drop", css_name) == 0) s = NO_DROP_POINTER; else if (strcmp("grab", css_name) == 0) s = GRAB_POINTER; else if (strcmp("grabbing", css_name) == 0) s = GRABBING_POINTER; else if (strcmp("left_ptr", css_name) == 0) s = DEFAULT_POINTER; else if (strcmp("xterm", css_name) == 0) s = TEXT_POINTER; else if (strcmp("ibeam", css_name) == 0) s = TEXT_POINTER; else if (strcmp("pointing_hand", css_name) == 0) s = POINTER_POINTER; else if (strcmp("hand2", css_name) == 0) s = POINTER_POINTER; else if (strcmp("hand", css_name) == 0) s = POINTER_POINTER; else if (strcmp("question_arrow", css_name) == 0) s = HELP_POINTER; else if (strcmp("whats_this", css_name) == 0) s = HELP_POINTER; else if (strcmp("clock", css_name) == 0) s = WAIT_POINTER; else if (strcmp("watch", css_name) == 0) s = WAIT_POINTER; else if (strcmp("half-busy", css_name) == 0) s = PROGRESS_POINTER; else if (strcmp("left_ptr_watch", css_name) == 0) s = PROGRESS_POINTER; else if (strcmp("tcross", css_name) == 0) s = CROSSHAIR_POINTER; else if (strcmp("plus", css_name) == 0) s = CELL_POINTER; else if (strcmp("cross", css_name) == 0) s = CELL_POINTER; else if (strcmp("fleur", css_name) == 0) s = MOVE_POINTER; else if (strcmp("pointer-move", css_name) == 0) s = MOVE_POINTER; else if (strcmp("right_side", css_name) == 0) s = E_RESIZE_POINTER; else if (strcmp("top_right_corner", css_name) == 0) s = NE_RESIZE_POINTER; else if (strcmp("top_left_corner", css_name) == 0) s = NW_RESIZE_POINTER; else if (strcmp("top_side", css_name) == 0) s = N_RESIZE_POINTER; else if (strcmp("bottom_right_corner", css_name) == 0) s = SE_RESIZE_POINTER; else if (strcmp("bottom_left_corner", css_name) == 0) s = SW_RESIZE_POINTER; else if (strcmp("bottom_side", css_name) == 0) s = S_RESIZE_POINTER; else if (strcmp("left_side", css_name) == 0) s = W_RESIZE_POINTER; else if (strcmp("sb_h_double_arrow", css_name) == 0) s = EW_RESIZE_POINTER; else if (strcmp("split_h", css_name) == 0) s = EW_RESIZE_POINTER; else if (strcmp("sb_v_double_arrow", css_name) == 0) s = NS_RESIZE_POINTER; else if (strcmp("split_v", css_name) == 0) s = NS_RESIZE_POINTER; else if (strcmp("size_bdiag", css_name) == 0) s = NESW_RESIZE_POINTER; else if (strcmp("size-bdiag", css_name) == 0) s = NESW_RESIZE_POINTER; else if (strcmp("size_fdiag", css_name) == 0) s = NWSE_RESIZE_POINTER; else if (strcmp("size-fdiag", css_name) == 0) s = NWSE_RESIZE_POINTER; else if (strcmp("zoom_in", css_name) == 0) s = ZOOM_IN_POINTER; else if (strcmp("zoom_out", css_name) == 0) s = ZOOM_OUT_POINTER; else if (strcmp("dnd-link", css_name) == 0) s = ALIAS_POINTER; else if (strcmp("dnd-copy", css_name) == 0) s = COPY_POINTER; else if (strcmp("forbidden", css_name) == 0) s = NOT_ALLOWED_POINTER; else if (strcmp("crossed_circle", css_name) == 0) s = NOT_ALLOWED_POINTER; else if (strcmp("dnd-no-drop", css_name) == 0) s = NO_DROP_POINTER; else if (strcmp("openhand", css_name) == 0) s = GRAB_POINTER; else if (strcmp("hand1", css_name) == 0) s = GRAB_POINTER; else if (strcmp("closedhand", css_name) == 0) s = GRABBING_POINTER; else if (strcmp("dnd-none", css_name) == 0) s = GRABBING_POINTER; /* end css to enum */ if (s == INVALID_POINTER && css_name[0] != 0) { PyErr_Format(PyExc_KeyError, "Not a known pointer shape: %s", css_name); return NULL; } if (op == '=') { if (!*count) *count += 1; stack[*count - 1] = s; } else if (op == '>') { if ((*count + 1u) >= arraysz(self->main_pointer_shape_stack.stack)) { remove_i_from_array(stack, 0, *count); } *count += 1; stack[*count - 1] = s; } else { PyErr_SetString(PyExc_KeyError, "Not a known stack operation"); return NULL; } } Py_RETURN_NONE; } bool screen_is_cursor_visible(const Screen *self) { return self->paused_rendering.expires_at ? self->paused_rendering.cursor_visible : self->modes.mDECTCEM; } void screen_backspace(Screen *self) { screen_cursor_back(self, 1, -1); } void screen_tab(Screen *self) { // Move to the next tab space, or the end of the screen if there aren't anymore left. unsigned int found = 0; for (unsigned int i = self->cursor->x + 1; i < self->columns; i++) { if (self->tabstops[i]) { found = i; break; } } if (!found) found = self->columns - 1; if (found != self->cursor->x) { if (self->cursor->x < self->columns) { CPUCell *cpu_cell = linebuf_cpu_cells_for_line(self->linebuf, self->cursor->y) + self->cursor->x; combining_type diff = found - self->cursor->x; bool ok = true; for (combining_type i = 0; i < diff; i++) { CPUCell *c = cpu_cell + i; if (cell_has_text(c) && !cell_is_char(c, ' ')) { ok = false; break; } } if (ok) { for (combining_type i = 0; i < diff; i++) { CPUCell *c = cpu_cell + i; cell_set_char(c, ' '); } self->lc->count = 2; self->lc->chars[0] = '\t'; self->lc->chars[1] = diff; cell_set_chars(cpu_cell, self->text_cache, self->lc); } } self->cursor->x = found; } } void screen_backtab(Screen *self, unsigned int count) { // Move back count tabs if (!count) count = 1; int i; while (count > 0 && self->cursor->x > 0) { count--; for (i = self->cursor->x - 1; i >= 0; i--) { if (self->tabstops[i]) { self->cursor->x = i; break; } } if (i <= 0) self->cursor->x = 0; } } void screen_clear_tab_stop(Screen *self, unsigned int how) { switch(how) { case 0: if (self->cursor->x < self->columns) self->tabstops[self->cursor->x] = false; break; case 2: break; // no-op case 3: for (unsigned int i = 0; i < self->columns; i++) self->tabstops[i] = false; break; default: log_error("%s %s %u", ERROR_PREFIX, "Unsupported clear tab stop mode: ", how); break; } } void screen_set_tab_stop(Screen *self) { if (self->cursor->x < self->columns) self->tabstops[self->cursor->x] = true; } void screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/) { if (count == 0) count = 1; if (move_direction < 0 && count > self->cursor->x) self->cursor->x = 0; else self->cursor->x += move_direction * count; screen_ensure_bounds(self, false, cursor_within_margins(self)); } void screen_cursor_forward(Screen *self, unsigned int count/*=1*/) { screen_cursor_back(self, count, 1); } void screen_cursor_up(Screen *self, unsigned int count/*=1*/, bool do_carriage_return/*=false*/, int move_direction/*=-1*/) { bool in_margins = cursor_within_margins(self); if (count == 0) count = 1; if (move_direction < 0 && count > self->cursor->y) self->cursor->y = 0; else self->cursor->y += move_direction * count; if (do_carriage_return) self->cursor->x = 0; screen_ensure_bounds(self, true, in_margins); } void screen_cursor_up1(Screen *self, unsigned int count/*=1*/) { screen_cursor_up(self, count, true, -1); } void screen_cursor_down(Screen *self, unsigned int count/*=1*/) { screen_cursor_up(self, count, false, 1); } void screen_cursor_down1(Screen *self, unsigned int count/*=1*/) { screen_cursor_up(self, count, true, 1); } void screen_cursor_to_column(Screen *self, unsigned int column) { unsigned int x = MAX(column, 1u) - 1; if (x != self->cursor->x) { self->cursor->x = x; screen_ensure_bounds(self, false, cursor_within_margins(self)); } } #define INDEX_UP(add_to_history) \ linebuf_index(self->linebuf, top, bottom); \ INDEX_GRAPHICS(-1) \ if (add_to_history) { \ /* Only add to history when no top margin has been set */ \ linebuf_init_line(self->linebuf, bottom); \ historybuf_add_line(self->historybuf, self->linebuf->line, &self->as_ansi_buf); \ self->history_line_added_count++; \ if (self->last_visited_prompt.is_set) { \ if (self->last_visited_prompt.scrolled_by < self->historybuf->count) self->last_visited_prompt.scrolled_by++; \ else self->last_visited_prompt.is_set = false; \ } \ } \ linebuf_clear_line(self->linebuf, bottom, true); \ self->is_dirty = true; \ index_selection(self, &self->selections, true); \ clear_selection(&self->url_ranges); void screen_index(Screen *self) { // Move cursor down one line, scrolling screen if needed unsigned int top = self->margin_top, bottom = self->margin_bottom; if (self->cursor->y == bottom) { const bool add_to_history = self->linebuf == self->main_linebuf && self->margin_top == 0; INDEX_UP(add_to_history); } else screen_cursor_down(self, 1); } static void screen_index_without_adding_to_history(Screen *self) { // Move cursor down one line, scrolling screen if needed unsigned int top = self->margin_top, bottom = self->margin_bottom; if (self->cursor->y == bottom) { INDEX_UP(false); } else screen_cursor_down(self, 1); } void screen_scroll(Screen *self, unsigned int count) { // Scroll the screen up by count lines, not moving the cursor unsigned int top = self->margin_top, bottom = self->margin_bottom; const bool add_to_history = self->linebuf == self->main_linebuf && self->margin_top == 0; while (count > 0) { count--; INDEX_UP(add_to_history); } } void screen_reverse_index(Screen *self) { // Move cursor up one line, scrolling screen if needed unsigned int top = self->margin_top, bottom = self->margin_bottom; if (self->cursor->y == top) { INDEX_DOWN; } else screen_cursor_up(self, 1, false, -1); } static void _reverse_scroll(Screen *self, unsigned int count, bool fill_from_scrollback) { // Scroll the screen down by count lines, not moving the cursor unsigned int top = self->margin_top, bottom = self->margin_bottom; fill_from_scrollback = fill_from_scrollback && self->linebuf == self->main_linebuf; if (fill_from_scrollback) { unsigned limit = MAX(self->lines, self->historybuf->count); count = MIN(limit, count); } else count = MIN(self->lines, count); while (count-- > 0) { bool copied = false; if (fill_from_scrollback) copied = historybuf_pop_line(self->historybuf, self->alt_linebuf->line); INDEX_DOWN; if (copied) linebuf_copy_line_to(self->main_linebuf, self->alt_linebuf->line, 0); } } void screen_reverse_scroll(Screen *self, unsigned int count) { _reverse_scroll(self, count, false); } void screen_reverse_scroll_and_fill_from_scrollback(Screen *self, unsigned int count) { _reverse_scroll(self, count, true); } void screen_carriage_return(Screen *self) { self->cursor->x = 0; } void screen_linefeed(Screen *self) { bool in_margins = cursor_within_margins(self); screen_index(self); if (self->modes.mLNM) screen_carriage_return(self); screen_ensure_bounds(self, false, in_margins); } #define buffer_push(self, ans) { \ ans = (self)->buf + (((self)->start_of_data + (self)->count) % SAVEPOINTS_SZ); \ if ((self)->count == SAVEPOINTS_SZ) (self)->start_of_data = ((self)->start_of_data + 1) % SAVEPOINTS_SZ; \ else (self)->count++; \ } #define buffer_pop(self, ans) { \ if ((self)->count == 0) ans = NULL; \ else { \ (self)->count--; \ ans = (self)->buf + (((self)->start_of_data + (self)->count) % SAVEPOINTS_SZ); \ } \ } void screen_save_cursor(Screen *self) { Savepoint *sp = self->linebuf == self->main_linebuf ? &self->main_savepoint : &self->alt_savepoint; cursor_copy_to(self->cursor, &(sp->cursor)); sp->mDECOM = self->modes.mDECOM; sp->mDECAWM = self->modes.mDECAWM; sp->mDECSCNM = self->modes.mDECSCNM; memcpy(&sp->charset, &self->charset, sizeof(self->charset)); sp->is_valid = true; } static void copy_specific_mode(Screen *self, unsigned int mode, const ScreenModes *src, ScreenModes *dest) { #define SIMPLE_MODE(name) case name: dest->m##name = src->m##name; break; #define SIDE_EFFECTS(name) case name: if (do_side_effects) set_mode_from_const(self, name, src->m##name); else dest->m##name = src->m##name; break; const bool do_side_effects = dest == &self->modes; switch(mode) { SIMPLE_MODE(LNM) // kitty extension SIMPLE_MODE(IRM) // kitty extension SIMPLE_MODE(DECARM) SIMPLE_MODE(BRACKETED_PASTE) SIMPLE_MODE(FOCUS_TRACKING) SIMPLE_MODE(COLOR_PREFERENCE_NOTIFICATION) SIMPLE_MODE(INBAND_RESIZE_NOTIFICATION) SIMPLE_MODE(DECCKM) SIMPLE_MODE(DECTCEM) SIMPLE_MODE(DECAWM) case MOUSE_BUTTON_TRACKING: case MOUSE_MOTION_TRACKING: case MOUSE_MOVE_TRACKING: dest->mouse_tracking_mode = src->mouse_tracking_mode; break; case MOUSE_UTF8_MODE: case MOUSE_SGR_MODE: case MOUSE_URXVT_MODE: dest->mouse_tracking_protocol = src->mouse_tracking_protocol; break; case DECSCLM: case DECNRCM: break; // we ignore these modes case DECSCNM: if (dest->mDECSCNM != src->mDECSCNM) { dest->mDECSCNM = src->mDECSCNM; if (do_side_effects) self->is_dirty = true; } break; SIDE_EFFECTS(DECOM) SIDE_EFFECTS(DECCOLM) } #undef SIMPLE_MODE #undef SIDE_EFFECTS } void screen_save_mode(Screen *self, unsigned int mode) { // XTSAVE copy_specific_mode(self, mode, &self->modes, &self->saved_modes); } void screen_restore_mode(Screen *self, unsigned int mode) { // XTRESTORE copy_specific_mode(self, mode, &self->saved_modes, &self->modes); } static void copy_specific_modes(Screen *self, const ScreenModes *src, ScreenModes *dest) { copy_specific_mode(self, LNM, src, dest); copy_specific_mode(self, IRM, src, dest); copy_specific_mode(self, DECARM, src, dest); copy_specific_mode(self, BRACKETED_PASTE, src, dest); copy_specific_mode(self, FOCUS_TRACKING, src, dest); copy_specific_mode(self, COLOR_PREFERENCE_NOTIFICATION, src, dest); copy_specific_mode(self, INBAND_RESIZE_NOTIFICATION, src, dest); copy_specific_mode(self, DECCKM, src, dest); copy_specific_mode(self, DECTCEM, src, dest); copy_specific_mode(self, DECAWM, src, dest); copy_specific_mode(self, MOUSE_BUTTON_TRACKING, src, dest); copy_specific_mode(self, MOUSE_UTF8_MODE, src, dest); copy_specific_mode(self, DECSCNM, src, dest); } void screen_save_modes(Screen *self) { // kitty extension to XTSAVE that saves a bunch of no side-effect modes copy_specific_modes(self, &self->modes, &self->saved_modes); } void screen_restore_cursor(Screen *self) { Savepoint *sp = self->linebuf == self->main_linebuf ? &self->main_savepoint : &self->alt_savepoint; if (!sp->is_valid) { screen_cursor_position(self, 1, 1); screen_reset_mode(self, DECOM); screen_reset_mode(self, DECSCNM); zero_at_ptr(&self->charset); } else { set_mode_from_const(self, DECOM, sp->mDECOM); set_mode_from_const(self, DECAWM, sp->mDECAWM); set_mode_from_const(self, DECSCNM, sp->mDECSCNM); cursor_copy_to(&(sp->cursor), self->cursor); memcpy(&self->charset, &sp->charset, sizeof(self->charset)); screen_ensure_bounds(self, false, false); } } void screen_restore_modes(Screen *self) { // kitty extension to XTRESTORE that saves a bunch of no side-effect modes copy_specific_modes(self, &self->saved_modes, &self->modes); } void screen_ensure_bounds(Screen *self, bool force_use_margins/*=false*/, bool in_margins) { unsigned int top, bottom; if (in_margins && (force_use_margins || self->modes.mDECOM)) { top = self->margin_top; bottom = self->margin_bottom; } else { top = 0; bottom = self->lines - 1; } self->cursor->x = MIN(self->cursor->x, self->columns - 1); self->cursor->y = MAX(top, MIN(self->cursor->y, bottom)); } void screen_cursor_position(Screen *self, unsigned int line, unsigned int column) { bool in_margins = cursor_within_margins(self); line = (line == 0 ? 1 : line) - 1; column = (column == 0 ? 1: column) - 1; if (self->modes.mDECOM) { line += self->margin_top; line = MAX(self->margin_top, MIN(line, self->margin_bottom)); } self->cursor->position_changed_by_client_at = self->parsing_at; self->cursor->x = column; self->cursor->y = line; screen_ensure_bounds(self, false, in_margins); } void screen_cursor_to_line(Screen *self, unsigned int line) { screen_cursor_position(self, line, self->cursor->x + 1); } int screen_cursor_at_a_shell_prompt(const Screen *self) { if (self->cursor->y >= self->lines || self->linebuf != self->main_linebuf || !screen_is_cursor_visible(self)) return -1; for (index_type y=self->cursor->y + 1; y-- > 0; ) { switch(self->linebuf->line_attrs[y].prompt_kind) { case OUTPUT_START: return -1; case PROMPT_START: case SECONDARY_PROMPT: return y; case UNKNOWN_PROMPT_KIND: break; } } return -1; } bool screen_prompt_supports_click_events(const Screen *self) { return (bool) self->prompt_settings.supports_click_events; } bool screen_fake_move_cursor_to_position(Screen *self, index_type start_x, index_type start_y) { SelectionBoundary a = {.x=start_x, .y=start_y}, b = {.x=self->cursor->x, .y=self->cursor->y}; SelectionBoundary *start, *end; int key; if (a.y < b.y || (a.y == b.y && a.x < b.x)) { start = &a; end = &b; key = GLFW_FKEY_LEFT; } else { start = &b; end = &a; key = GLFW_FKEY_RIGHT; } unsigned int count = 0; for (unsigned y = start->y, x = start->x; y <= end->y && y < self->lines; y++) { unsigned x_limit = y == end->y ? end->x : self->columns; x_limit = MIN(x_limit, self->columns); bool found_non_empty_cell = false; while (x < x_limit) { const CPUCell *c = linebuf_cpu_cell_at(self->linebuf, x, y); if (!cell_has_text(c)) { // we only stop counting the cells in the line at an empty cell // if at least one non-empty cell is found. zsh uses empty cells // between the end of the text ad the right prompt. fish uses empty // cells at the start of a line when editing multiline text if (!found_non_empty_cell) { x++; continue; } count += 1; break; } found_non_empty_cell = true; if (c->is_multicell) { x += mcd_x_limit(c); } else x++; count += 1; // zsh requires a single arrow press to move past dualwidth chars } if (!found_non_empty_cell) count++; // blank line x = 0; } if (count) { char output[KEY_BUFFER_SIZE+1] = {0}; if (self->prompt_settings.uses_special_keys_for_cursor_movement) { const char *k = key == GLFW_FKEY_RIGHT ? "1" : "1;1"; int num = snprintf(output, KEY_BUFFER_SIZE, "\x1b[%su", k); for (unsigned i = 0; i < count; i++) write_to_child(self, output, num); } else { GLFWkeyevent ev = { .key = key, .action = GLFW_PRESS }; int num = encode_glfw_key_event(&ev, false, 0, output); if (num != SEND_TEXT_TO_CHILD) { for (unsigned i = 0; i < count; i++) write_to_child(self, output, num); } } } return count > 0; } // }}} // Editing {{{ void screen_erase_in_line(Screen *self, unsigned int how, bool private) { /*Erases a line in a specific way. :param int how: defines the way the line should be erased in: * ``0`` -- Erases from cursor to end of line, including cursor position. * ``1`` -- Erases from beginning of line to cursor, including cursor position. * ``2`` -- Erases complete line. :param bool private: when ``True`` character attributes are left unchanged. */ unsigned int s = 0, n = 0; switch(how) { case 0: s = self->cursor->x; n = self->columns - self->cursor->x; break; case 1: n = self->cursor->x + 1; break; case 2: n = self->columns; break; default: break; } if (n > 0) { nuke_multicell_char_intersecting_with(self, s, n, self->cursor->y, self->cursor->y + 1, false); screen_dirty_line_graphics(self, self->cursor->y, self->cursor->y, self->linebuf == self->main_linebuf); linebuf_init_line(self->linebuf, self->cursor->y); if (private) { line_clear_text(self->linebuf->line, s, n, BLANK_CHAR); } else { line_apply_cursor(self->linebuf->line, self->cursor, s, n, true); } self->is_dirty = true; clear_intersecting_selections(self, self->cursor->y); linebuf_mark_line_dirty(self->linebuf, self->cursor->y); } } static void dirty_scroll(Screen *self) { self->scroll_changed = true; screen_pause_rendering(self, false, 0); } static void screen_clear_scrollback(Screen *self) { historybuf_clear(self->historybuf); if (self->scrolled_by != 0) { self->scrolled_by = 0; dirty_scroll(self); } LineBuf *orig = self->linebuf; self->linebuf = self->main_linebuf; CPUCell *cells = linebuf_cpu_cells_for_line(self->linebuf, 0); for (index_type x = 0; x < self->columns; x++) { CPUCell *c = cells + x; if (c->is_multicell && c->y > 0) { // multiline char that extended into scrollback nuke_multicell_char_at(self, x, 0, false); } } self->linebuf = orig; } static Line* visual_line_(Screen *self, int y_); static void screen_move_into_scrollback(Screen *self) { if (self->linebuf != self->main_linebuf || self->margin_top != 0 || self->margin_bottom != self->lines - 1) return; unsigned int num_of_lines_to_move = self->lines; while (num_of_lines_to_move) { Line *line = visual_line_(self, num_of_lines_to_move-1); if (!line_is_empty(line)) break; num_of_lines_to_move--; } if (num_of_lines_to_move) { unsigned int top, bottom; const bool add_to_history = self->linebuf == self->main_linebuf && self->margin_top == 0; for (; num_of_lines_to_move; num_of_lines_to_move--) { top = 0, bottom = num_of_lines_to_move - 1; INDEX_UP(add_to_history); } } } void screen_erase_in_display(Screen *self, unsigned int how, bool private) { /* Erases display in a specific way. :param int how: defines the way the screen should be erased: * ``0`` -- Erases from cursor to end of screen, including cursor position. * ``1`` -- Erases from beginning of screen to cursor, including cursor position. * ``2`` -- Erases complete display. All lines are erased and changed to single-width. Cursor does not move. * ``22`` -- Copy screen contents into scrollback if in main screen, then do the same as ``2``. * ``3`` -- Erase complete display and scrollback buffer as well. :param bool private: when ``True`` character attributes are left unchanged */ unsigned int a, b; bool nuke_multicell_chars = true; switch(how) { case 0: a = self->cursor->y + 1; b = self->lines; break; case 1: a = 0; b = self->cursor->y; break; case 22: screen_move_into_scrollback(self); nuke_multicell_chars = false; // they have been moved into scrollback and we would get double deletions how = 2; /* fallthrough */ case 2: case 3: grman_clear(self->grman, how == 3, self->cell_size); a = 0; b = self->lines; nuke_multicell_chars = false; break; default: return; } if (b > a) { if (how != 3) screen_dirty_line_graphics(self, a, b, self->linebuf == self->main_linebuf); if (private) { for (unsigned int i=a; i < b; i++) { linebuf_init_line(self->linebuf, i); line_clear_text(self->linebuf->line, 0, self->columns, BLANK_CHAR); linebuf_set_last_char_as_continuation(self->linebuf, i, false); linebuf_clear_attrs_and_dirty(self->linebuf, i); } } else linebuf_clear_lines(self->linebuf, self->cursor, a, b); if (nuke_multicell_chars) nuke_multicell_char_intersecting_with(self, 0, self->columns, a, b, false); self->is_dirty = true; if (selection_intersects_screen_lines(&self->selections, a, b)) clear_selection(&self->selections); if (selection_intersects_screen_lines(&self->url_ranges, a, b)) clear_selection(&self->url_ranges); } if (how < 2) { screen_erase_in_line(self, how, private); if (how == 1) linebuf_clear_attrs_and_dirty(self->linebuf, self->cursor->y); } if (how == 3 && self->linebuf == self->main_linebuf) { screen_clear_scrollback(self); } } void screen_insert_lines(Screen *self, unsigned int count) { unsigned int top = self->margin_top, bottom = self->margin_bottom; if (count == 0) count = 1; if (top <= self->cursor->y && self->cursor->y <= bottom) { // remove split multiline chars at top edge CPUCell *cells = linebuf_cpu_cells_for_line(self->linebuf, self->cursor->y); for (index_type x = 0; x < self->columns; x++) { if (cells[x].is_multicell && cells[x].y) nuke_multicell_char_at(self, x, self->cursor->y, false); } screen_dirty_line_graphics(self, top, bottom, self->linebuf == self->main_linebuf); linebuf_insert_lines(self->linebuf, count, self->cursor->y, bottom); self->is_dirty = true; clear_all_selections(self); screen_carriage_return(self); // remove split multiline chars at bottom of screen cells = linebuf_cpu_cells_for_line(self->linebuf, bottom); for (index_type x = 0; x < self->columns; x++) { if (cells[x].is_multicell) { index_type y_limit = cells[x].scale; if (cells[x].y + 1u < y_limit) { index_type orig = self->lines; self->lines = bottom + 1; nuke_multicell_char_at(self, x, bottom, false); self->lines = orig; } } } } } static void screen_scroll_until_cursor_prompt(Screen *self, bool add_to_scrollback) { bool in_margins = cursor_within_margins(self); int q = screen_cursor_at_a_shell_prompt(self); unsigned int y = q > -1 ? (unsigned int)q : self->cursor->y; unsigned int num_lines_to_scroll = MIN(self->margin_bottom, y); unsigned int final_y = num_lines_to_scroll <= self->cursor->y ? self->cursor->y - num_lines_to_scroll : 0; self->cursor->y = self->margin_bottom; if (add_to_scrollback) while (num_lines_to_scroll--) screen_index(self); else while (num_lines_to_scroll--) screen_index_without_adding_to_history(self); self->cursor->y = final_y; screen_ensure_bounds(self, false, in_margins); } void screen_delete_lines(Screen *self, unsigned int count) { unsigned int top = self->margin_top, bottom = self->margin_bottom; if (count == 0) count = 1; if (top <= self->cursor->y && self->cursor->y <= bottom) { index_type y = self->cursor->y; nuke_multiline_char_intersecting_with(self, 0, self->columns, y, y + 1, false); y += count; y = MIN(bottom, y); nuke_multiline_char_intersecting_with(self, 0, self->columns, y, y + 1, false); screen_dirty_line_graphics(self, top, bottom, self->linebuf == self->main_linebuf); linebuf_delete_lines(self->linebuf, count, self->cursor->y, bottom); self->is_dirty = true; clear_all_selections(self); screen_carriage_return(self); } } void screen_insert_characters(Screen *self, unsigned int count) { const unsigned int bottom = self->lines ? self->lines - 1 : 0; if (count == 0) count = 1; if (self->cursor->y <= bottom) { unsigned int x = self->cursor->x; unsigned int num = MIN(self->columns - x, count); insert_characters(self, x, num, self->cursor->y, false); linebuf_init_line(self->linebuf, self->cursor->y); line_apply_cursor(self->linebuf->line, self->cursor, x, num, true); linebuf_mark_line_dirty(self->linebuf, self->cursor->y); self->is_dirty = true; clear_intersecting_selections(self, self->cursor->y); } } void screen_repeat_character(Screen *self, unsigned int count) { if (self->last_graphic_char) { if (count == 0) count = 1; unsigned int num = MIN(count, CSI_REP_MAX_REPETITIONS); alignas(64) uint32_t buf[64]; for (unsigned i = 0; i < arraysz(buf); i++) buf[i] = self->last_graphic_char; for (unsigned i = 0; i < num; i += arraysz(buf)) screen_draw_text(self, buf, MIN(num - i, arraysz(buf))); } } static void remove_characters(Screen *self, index_type at, index_type num, index_type y, bool replace_with_spaces) { // delete num chars at x=at setting them to the value of the num chars at [at + num, at + num + num) // multiline chars at x >= at are deleted and multicell chars split at x=at // and x=at + num - 1 are deleted nuke_multiline_char_intersecting_with(self, at, self->columns, y, y + 1, replace_with_spaces); nuke_split_multicell_char_at_left_boundary(self, at, y, replace_with_spaces); CPUCell *cp; GPUCell *gp; linebuf_init_cells(self->linebuf, y, &cp, &gp); // left shift for (index_type i = at; i < self->columns - num; i++) { cp[i] = cp[i+num]; gp[i] = gp[i+num]; } nuke_incomplete_single_line_multicell_chars_in_range(self, at, self->columns, y, replace_with_spaces); } void screen_delete_characters(Screen *self, unsigned int count) { // Delete characters, later characters are moved left const unsigned int bottom = self->lines ? self->lines - 1 : 0; if (count == 0) count = 1; if (self->cursor->y <= bottom) { unsigned int x = self->cursor->x; unsigned int num = MIN(self->columns - x, count); remove_characters(self, x, num, self->cursor->y, false); linebuf_init_line(self->linebuf, self->cursor->y); line_apply_cursor(self->linebuf->line, self->cursor, self->columns - num, num, true); linebuf_mark_line_dirty(self->linebuf, self->cursor->y); self->is_dirty = true; clear_intersecting_selections(self, self->cursor->y); } } void screen_erase_characters(Screen *self, unsigned int count) { // Delete characters clearing the cells if (count == 0) count = 1; unsigned int x = self->cursor->x; unsigned int num = MIN(self->columns - x, count); nuke_multicell_char_intersecting_with(self, x, x + num, self->cursor->y, self->cursor->y + 1, false); linebuf_init_line(self->linebuf, self->cursor->y); line_apply_cursor(self->linebuf->line, self->cursor, x, num, true); linebuf_mark_line_dirty(self->linebuf, self->cursor->y); self->is_dirty = true; clear_intersecting_selections(self, self->cursor->y); } // }}} // Device control {{{ bool screen_invert_colors(Screen *self) { return self->paused_rendering.expires_at ? self->paused_rendering.inverted : (self->modes.mDECSCNM ? true : false); } void screen_bell(Screen *self) { if (self->ignore_bells.start) { monotonic_t now = monotonic(); if (now < self->ignore_bells.start + self->ignore_bells.duration) { self->ignore_bells.start = now; return; } self->ignore_bells.start = 0; } request_window_attention(self->window_id, OPT(enable_audio_bell)); if (OPT(visual_bell_duration) > 0.0f) self->start_visual_bell_at = monotonic(); CALLBACK("on_bell", NULL); } void report_device_attributes(Screen *self, unsigned int mode, char start_modifier) { if (mode == 0) { switch(start_modifier) { case 0: write_escape_code_to_child(self, ESC_CSI, "?62;c"); break; case '>': write_escape_code_to_child(self, ESC_CSI, ">1;" xstr(PRIMARY_VERSION) ";" xstr(SECONDARY_VERSION) "c"); // VT-220 + primary version + secondary version break; } } } void screen_xtversion(Screen *self, unsigned int mode) { if (mode == 0) { write_escape_code_to_child(self, ESC_DCS, ">|kitty(" XT_VERSION ")"); } } void screen_report_size(Screen *self, unsigned int which) { char buf[32] = {0}; unsigned int code = 0; unsigned int width = 0, height = 0; switch(which) { case 14: code = 4; width = self->cell_size.width * self->columns; height = self->cell_size.height * self->lines; break; case 16: code = 6; width = self->cell_size.width; height = self->cell_size.height; break; case 18: code = 8; width = self->columns; height = self->lines; break; } if (code) { snprintf(buf, sizeof(buf), "%u;%u;%ut", code, height, width); write_escape_code_to_child(self, ESC_CSI, buf); } } void screen_manipulate_title_stack(Screen *self, unsigned int op, unsigned int which) { CALLBACK("manipulate_title_stack", "OOO", op == 23 ? Py_True : Py_False, which == 0 || which == 2 ? Py_True : Py_False, which == 0 || which == 1 ? Py_True : Py_False ); } void report_device_status(Screen *self, unsigned int which, bool private) { unsigned int x, y; static char buf[64]; switch(which) { case 5: // device status write_escape_code_to_child(self, ESC_CSI, "0n"); break; case 6: // cursor position x = self->cursor->x; y = self->cursor->y; if (x >= self->columns) { if (y < self->lines - 1) { x = 0; y++; } else x--; } if (self->modes.mDECOM) y -= MAX(y, self->margin_top); // 1-based indexing int sz = snprintf(buf, sizeof(buf) - 1, "%s%u;%uR", (private ? "?": ""), y + 1, x + 1); if (sz > 0) write_escape_code_to_child(self, ESC_CSI, buf); break; case 996: // https://github.com/contour-terminal/contour/blob/master/docs/vt-extensions/color-palette-update-notifications.md if (private) { CALLBACK("report_color_scheme_preference", NULL); } break; } } void report_mode_status(Screen *self, unsigned int which, bool private) { unsigned int q = private ? which << 5 : which; unsigned int ans = 0; char buf[50] = {0}; switch(q) { #define KNOWN_MODE(x) \ case x: \ ans = self->modes.m##x ? 1 : 2; break; KNOWN_MODE(LNM); KNOWN_MODE(IRM); KNOWN_MODE(DECTCEM); KNOWN_MODE(DECSCNM); KNOWN_MODE(DECOM); KNOWN_MODE(DECAWM); KNOWN_MODE(DECCOLM); KNOWN_MODE(DECARM); KNOWN_MODE(DECCKM); KNOWN_MODE(BRACKETED_PASTE); KNOWN_MODE(FOCUS_TRACKING); KNOWN_MODE(COLOR_PREFERENCE_NOTIFICATION); KNOWN_MODE(INBAND_RESIZE_NOTIFICATION); #undef KNOWN_MODE case ALTERNATE_SCREEN: ans = self->linebuf == self->alt_linebuf ? 1 : 2; break; case MOUSE_BUTTON_TRACKING: ans = self->modes.mouse_tracking_mode == BUTTON_MODE ? 1 : 2; break; case MOUSE_MOTION_TRACKING: ans = self->modes.mouse_tracking_mode == MOTION_MODE ? 1 : 2; break; case MOUSE_MOVE_TRACKING: ans = self->modes.mouse_tracking_mode == ANY_MODE ? 1 : 2; break; case MOUSE_SGR_MODE: ans = self->modes.mouse_tracking_protocol == SGR_PROTOCOL ? 1 : 2; break; case MOUSE_UTF8_MODE: ans = self->modes.mouse_tracking_protocol == UTF8_PROTOCOL ? 1 : 2; break; case MOUSE_SGR_PIXEL_MODE: ans = self->modes.mouse_tracking_protocol == SGR_PIXEL_PROTOCOL ? 1 : 2; break; case PENDING_UPDATE: ans = self->paused_rendering.expires_at ? 1 : 2; break; } int sz = snprintf(buf, sizeof(buf) - 1, "%s%u;%u$y", (private ? "?" : ""), which, ans); if (sz > 0) write_escape_code_to_child(self, ESC_CSI, buf); } void screen_set_margins(Screen *self, unsigned int top, unsigned int bottom) { if (!top) top = 1; if (!bottom) bottom = self->lines; top = MIN(self->lines, top); bottom = MIN(self->lines, bottom); top--; bottom--; // 1 based indexing if (bottom > top) { // Even though VT102 and VT220 require DECSTBM to ignore regions // of width less than 2, some programs (like aptitude for example) // rely on it. Practicality beats purity. self->margin_top = top; self->margin_bottom = bottom; // The cursor moves to the home position when the top and // bottom margins of the scrolling region (DECSTBM) changes. screen_cursor_position(self, 1, 1); } } void screen_set_cursor(Screen *self, unsigned int mode, uint8_t secondary) { uint8_t shape; bool blink; switch(secondary) { case 0: // DECLL break; case '"': // DECCSA break; case ' ': // DECSCUSR shape = 0; blink = true; if (mode > 0) { blink = mode % 2; shape = (mode < 3) ? CURSOR_BLOCK : (mode < 5) ? CURSOR_UNDERLINE : (mode < 7) ? CURSOR_BEAM : NO_CURSOR_SHAPE; } if (shape != self->cursor->shape || blink != !self->cursor->non_blinking) { self->cursor->shape = shape; self->cursor->non_blinking = !blink; } break; } } void set_title(Screen *self, PyObject *title) { CALLBACK("title_changed", "O", title); } void desktop_notify(Screen *self, unsigned int osc_code, PyObject *data) { CALLBACK("desktop_notify", "IO", osc_code, data); } void set_icon(Screen *self, PyObject *icon) { CALLBACK("icon_changed", "O", icon); } void set_dynamic_color(Screen *self, unsigned int code, PyObject *color) { if (color == NULL) { CALLBACK("set_dynamic_color", "I", code); } else { CALLBACK("set_dynamic_color", "IO", code, color); } } void color_control(Screen *self, unsigned int code, PyObject *spec) { if (spec) CALLBACK("color_control", "IO", code, spec); } void clipboard_control(Screen *self, int code, PyObject *data) { if (code == 52 || code == -52) { CALLBACK("clipboard_control", "OO", data, code == -52 ? Py_True: Py_False); } else { CALLBACK("clipboard_control", "OO", data, Py_None);} } void file_transmission(Screen *self, PyObject *data) { CALLBACK("file_transmission", "O", data); } static void parse_prompt_mark(Screen *self, char *buf, PromptKind *pk) { char *saveptr, *str = buf; while (true) { const char *token = strtok_r(str, ";", &saveptr); str = NULL; if (token == NULL) return; if (strcmp(token, "k=s") == 0) *pk = SECONDARY_PROMPT; else if (strcmp(token, "redraw=0") == 0) self->prompt_settings.redraws_prompts_at_all = 0; else if (strcmp(token, "special_key=1") == 0) self->prompt_settings.uses_special_keys_for_cursor_movement = 1; else if (strcmp(token, "click_events=1") == 0) self->prompt_settings.supports_click_events = 1; } } void shell_prompt_marking(Screen *self, char *buf) { if (self->cursor->y < self->lines) { char ch = buf[0]; switch (ch) { case 'A': { PromptKind pk = PROMPT_START; self->prompt_settings.redraws_prompts_at_all = 1; self->prompt_settings.uses_special_keys_for_cursor_movement = 0; parse_prompt_mark(self, buf+1, &pk); self->linebuf->line_attrs[self->cursor->y].prompt_kind = pk; if (pk == PROMPT_START) CALLBACK("cmd_output_marking", "O", Py_False); } break; case 'C': { self->linebuf->line_attrs[self->cursor->y].prompt_kind = OUTPUT_START; const char *cmdline = ""; if (strstr(buf + 1, ";cmdline") == buf + 1) { cmdline = buf + 2; } RAII_PyObject(c, PyUnicode_DecodeUTF8(cmdline, strlen(cmdline), "replace")); if (c) { CALLBACK("cmd_output_marking", "OO", Py_True, c); } else PyErr_Print(); } break; case 'D': { const char *exit_status = buf[1] == ';' ? buf + 2 : ""; CALLBACK("cmd_output_marking", "Os", Py_None, exit_status); } break; } } } static bool screen_history_scroll_to_prompt(Screen *self, int num_of_prompts_to_jump) { if (self->linebuf != self->main_linebuf) return false; unsigned int old = self->scrolled_by; if (num_of_prompts_to_jump == 0) { if (!self->last_visited_prompt.is_set || self->last_visited_prompt.scrolled_by > self->historybuf->count || self->last_visited_prompt.y >= self->lines) return false; self->scrolled_by = self->last_visited_prompt.scrolled_by; } else { int delta = num_of_prompts_to_jump < 0 ? -1 : 1; num_of_prompts_to_jump = num_of_prompts_to_jump < 0 ? -num_of_prompts_to_jump : num_of_prompts_to_jump; int y = -self->scrolled_by; #define ensure_y_ok if (y >= (int)self->lines || -y > (int)self->historybuf->count) return false; ensure_y_ok; while (num_of_prompts_to_jump) { y += delta; ensure_y_ok; if (range_line_(self, y)->attrs.prompt_kind == PROMPT_START) { num_of_prompts_to_jump--; } } #undef ensure_y_ok self->scrolled_by = y >= 0 ? 0 : -y; screen_set_last_visited_prompt(self, 0); } if (old != self->scrolled_by) dirty_scroll(self); return old != self->scrolled_by; } void set_color_table_color(Screen *self, unsigned int code, PyObject *color) { if (color == NULL) { CALLBACK("set_color_table_color", "I", code); } else { CALLBACK("set_color_table_color", "IO", code, color); } } void process_cwd_notification(Screen *self, unsigned int code, const char *data, size_t sz) { if (code == 7) { PyObject *x = PyBytes_FromStringAndSize(data, sz); if (x) { Py_CLEAR(self->last_reported_cwd); self->last_reported_cwd = x; } else { PyErr_Clear(); } } // we ignore OSC 6 document reporting as we dont have a use for it } bool screen_send_signal_for_key(Screen *self, char key) { int ret = 0; if (self->callbacks != Py_None) { int cchar = key; PyObject *callback_ret = PyObject_CallMethod(self->callbacks, "send_signal_for_key", "c", cchar); if (callback_ret) { ret = PyObject_IsTrue(callback_ret); Py_DECREF(callback_ret); } else { PyErr_Print(); } } return ret != 0; } void screen_push_colors(Screen *self, unsigned int idx) { if (colorprofile_push_colors(self->color_profile, idx)) self->color_profile->dirty = true; } void screen_pop_colors(Screen *self, unsigned int idx) { color_type bg_before = colorprofile_to_color(self->color_profile, self->color_profile->overridden.default_bg, self->color_profile->configured.default_bg).rgb; if (colorprofile_pop_colors(self->color_profile, idx)) { self->color_profile->dirty = true; color_type bg_after = colorprofile_to_color(self->color_profile, self->color_profile->overridden.default_bg, self->color_profile->configured.default_bg).rgb; CALLBACK("color_profile_popped", "O", bg_before == bg_after ? Py_False : Py_True); } } void screen_report_color_stack(Screen *self) { unsigned int idx, count; colorprofile_report_stack(self->color_profile, &idx, &count); char buf[128] = {0}; snprintf(buf, arraysz(buf), "%u;%u#Q", idx, count); write_escape_code_to_child(self, ESC_CSI, buf); } void screen_handle_kitty_dcs(Screen *self, const char *callback_name, PyObject *cmd) { CALLBACK(callback_name, "O", cmd); } void screen_request_capabilities(Screen *self, char c, const char *query) { static char buf[128]; int shape = 0; switch(c) { case '+': { CALLBACK("request_capabilities", "s", query); } break; case '$': // report status DECRQSS if (strcmp(" q", query) == 0) { // cursor shape DECSCUSR switch(self->cursor->shape) { case NO_CURSOR_SHAPE: case CURSOR_HOLLOW: case NUM_OF_CURSOR_SHAPES: shape = 1; break; case CURSOR_BLOCK: shape = self->cursor->non_blinking ? 2 : 0; break; case CURSOR_UNDERLINE: shape = self->cursor->non_blinking ? 4 : 3; break; case CURSOR_BEAM: shape = self->cursor->non_blinking ? 6 : 5; break; } shape = snprintf(buf, sizeof(buf), "1$r%d q", shape); } else if (strcmp("m", query) == 0) { // SGR shape = snprintf(buf, sizeof(buf), "1$r%sm", cursor_as_sgr(self->cursor)); } else if (strcmp("r", query) == 0) { // DECSTBM shape = snprintf(buf, sizeof(buf), "1$r%u;%ur", self->margin_top + 1, self->margin_bottom + 1); } else if (strcmp("*x", query) == 0) { // DECSACE shape = snprintf(buf, sizeof(buf), "1$r%d*x", self->modes.mDECSACE ? 1 : 0); } else { shape = snprintf(buf, sizeof(buf), "0$r"); } if (shape > 0) write_escape_code_to_child(self, ESC_DCS, buf); break; } } // }}} // Rendering {{{ void screen_check_pause_rendering(Screen *self, monotonic_t now) { if (self->paused_rendering.expires_at && now > self->paused_rendering.expires_at) screen_pause_rendering(self, false, 0); } static bool copy_selections(Selections *dest, const Selections *src) { if (dest->capacity < src->count) { dest->items = realloc(dest->items, sizeof(dest->items[0]) * src->count); if (!dest->items) { dest->capacity = 0; dest->count = 0; return false; } dest->capacity = src->count; } dest->count = src->count; for (unsigned i = 0; i < dest->count; i++) memcpy(dest->items + i, src->items + i, sizeof(dest->items[0])); dest->last_rendered_count = src->last_rendered_count; return true; } bool screen_pause_rendering(Screen *self, bool pause, int for_in_ms) { if (!pause) { if (!self->paused_rendering.expires_at) return false; self->paused_rendering.expires_at = 0; // ensure cell data is updated on GPU self->is_dirty = true; // ensure selection data is updated on GPU self->selections.last_rendered_count = SIZE_MAX; self->url_ranges.last_rendered_count = SIZE_MAX; // free grman data grman_pause_rendering(NULL, self->paused_rendering.grman); return true; } if (self->paused_rendering.expires_at) return false; if (!self->paused_rendering.grman) self->paused_rendering.grman = grman_alloc(true); if (!self->paused_rendering.grman) return false; if (for_in_ms <= 0) for_in_ms = 2000; self->paused_rendering.expires_at = monotonic() + ms_to_monotonic_t(for_in_ms); self->paused_rendering.inverted = self->modes.mDECSCNM; self->paused_rendering.scrolled_by = self->scrolled_by; self->paused_rendering.cell_data_updated = false; self->paused_rendering.cursor_visible = self->modes.mDECTCEM; memcpy(&self->paused_rendering.cursor, self->cursor, sizeof(self->paused_rendering.cursor)); memcpy(&self->paused_rendering.color_profile, self->color_profile, sizeof(self->paused_rendering.color_profile)); if (!self->paused_rendering.linebuf || self->paused_rendering.linebuf->xnum != self->columns || self->paused_rendering.linebuf->ynum != self->lines) { if (self->paused_rendering.linebuf) Py_CLEAR(self->paused_rendering.linebuf); self->paused_rendering.linebuf = alloc_linebuf(self->lines, self->columns, self->text_cache); if (!self->paused_rendering.linebuf) { PyErr_Clear(); self->paused_rendering.expires_at = 0; return false; } } for (index_type y = 0; y < self->lines; y++) { Line *src = visual_line_(self, y); linebuf_init_line(self->paused_rendering.linebuf, y); copy_line(src, self->paused_rendering.linebuf->line); self->paused_rendering.linebuf->line_attrs[y] = src->attrs; } copy_selections(&self->paused_rendering.selections, &self->selections); copy_selections(&self->paused_rendering.url_ranges, &self->url_ranges); grman_pause_rendering(self->grman, self->paused_rendering.grman); return true; } static color_type effective_cell_edge_color(char_type ch, color_type fg, color_type bg, bool is_left_edge) { START_ALLOW_CASE_RANGE if (ch == 0x2588) return fg; // full block if (is_left_edge) { switch (ch) { case 0x2589 ... 0x258f: // left eighth blocks case 0xe0b0: case 0xe0b4: case 0xe0b8: case 0xe0bc: // powerline blocks case 0x1fb6a: // 🭪 return fg; } } else { switch (ch) { case 0x2590: // right half block case 0x1fb87 ... 0x1fb8b: // eighth right blocks case 0xe0b2: case 0xe0b6: case 0xe0ba: case 0xe0be: case 0x1fb68: // 🭨 return fg; } } return bg; END_ALLOW_CASE_RANGE } bool get_line_edge_colors(Screen *self, color_type *left, color_type *right) { // Return the color at the left and right edges of the line with the cursor on it Line *line = range_line_(self, self->cursor->y); if (!line) return false; color_type left_cell_fg = OPT(foreground), left_cell_bg = OPT(background), right_cell_bg = OPT(background), right_cell_fg = OPT(foreground); index_type cell_color_x = 0; char_type left_char = line_get_char(line, cell_color_x); bool reversed = false; colors_for_cell(line, self->color_profile, &cell_color_x, &left_cell_fg, &left_cell_bg, &reversed); if (line->xnum > 0) cell_color_x = line->xnum - 1; char_type right_char = line_get_char(line, cell_color_x); colors_for_cell(line, self->color_profile, &cell_color_x, &right_cell_fg, &right_cell_bg, &reversed); *left = effective_cell_edge_color(left_char, left_cell_fg, left_cell_bg, true); *right = effective_cell_edge_color(right_char, right_cell_fg, right_cell_bg, false); return true; } static void update_line_data(Line *line, unsigned int dest_y, uint8_t *data) { size_t base = sizeof(GPUCell) * dest_y * line->xnum; memcpy(data + base, line->gpu_cells, line->xnum * sizeof(GPUCell)); } static void screen_reset_dirty(Screen *self) { self->is_dirty = false; self->history_line_added_count = 0; } static bool screen_has_marker(Screen *self) { return self->marker != NULL; } static uint32_t diacritic_to_rowcolumn(char_type c) { return diacritic_to_num(c); } static uint32_t color_to_id(color_type c) { // Just take 24 most significant bits of the color. This works both for // 24-bit and 8-bit colors. return (c >> 8) & 0xffffff; } // Scan the line and create cell images in place of unicode placeholders // reserved for image placement. static void screen_render_line_graphics(Screen *self, Line *line, int32_t row) { // If there are no image placeholders now, no need to rescan the line. if (!line->attrs.has_image_placeholders) return; // Remove existing images. grman_remove_cell_images(self->grman, row, row); // The placeholders might be erased. We will update the attribute. line->attrs.has_image_placeholders = false; index_type i; uint32_t run_length = 0; uint32_t prev_img_id_lower24bits = 0; uint32_t prev_placement_id = 0; // Note that the following values are 1-based, zero means unknown or incorrect. uint32_t prev_img_id_higher8bits = 0; uint32_t prev_img_row = 0; uint32_t prev_img_col = 0; for (i = 0; i < line->xnum; i++) { CPUCell *cpu_cell = line->cpu_cells + i; GPUCell *gpu_cell = line->gpu_cells + i; uint32_t cur_img_id_lower24bits = 0; uint32_t cur_placement_id = 0; uint32_t cur_img_id_higher8bits = 0; uint32_t cur_img_row = 0; uint32_t cur_img_col = 0; if (cell_first_char(cpu_cell, self->text_cache) == IMAGE_PLACEHOLDER_CHAR) { line->attrs.has_image_placeholders = true; // The lower 24 bits of the image id are encoded in the foreground // color, and the placement id is (optionally) in the underline color. cur_img_id_lower24bits = color_to_id(gpu_cell->fg); cur_placement_id = color_to_id(gpu_cell->decoration_fg); text_in_cell(cpu_cell, self->text_cache, self->lc); // If the char has diacritics, use them as row and column indices. if (self->lc->count > 1 && self->lc->chars[1]) cur_img_row = diacritic_to_rowcolumn(self->lc->chars[1]); if (self->lc->count > 2 && self->lc->chars[2]) cur_img_col = diacritic_to_rowcolumn(self->lc->chars[2]); // The third diacritic is used to encode the higher 8 bits of the // image id (optional). if (self->lc->count > 3 && self->lc->chars[3]) cur_img_id_higher8bits = diacritic_to_rowcolumn(self->lc->chars[3]); } // The current run is continued if the lower 24 bits of the image id and // the placement id are the same as in the previous cell and everything // else is unknown or compatible with the previous cell. if (run_length > 0 && cur_img_id_lower24bits == prev_img_id_lower24bits && cur_placement_id == prev_placement_id && (!cur_img_row || cur_img_row == prev_img_row) && (!cur_img_col || cur_img_col == prev_img_col + 1) && (!cur_img_id_higher8bits || cur_img_id_higher8bits == prev_img_id_higher8bits)) { // This cell continues the current run. run_length++; // If some values are unknown, infer them from the previous cell. cur_img_row = MAX(prev_img_row, 1u); cur_img_col = prev_img_col + 1; cur_img_id_higher8bits = MAX(prev_img_id_higher8bits, 1u); } else { // This cell breaks the current run. Render the current run if it // has a non-zero length. if (run_length > 0) { uint32_t img_id = prev_img_id_lower24bits | (prev_img_id_higher8bits - 1) << 24; grman_put_cell_image( self->grman, row, i - run_length, img_id, prev_placement_id, prev_img_col - run_length, prev_img_row - 1, run_length, 1, self->cell_size); } // Start a new run. if (cell_first_char(cpu_cell, self->text_cache) == IMAGE_PLACEHOLDER_CHAR) { run_length = 1; if (!cur_img_col) cur_img_col = 1; if (!cur_img_row) cur_img_row = 1; if (!cur_img_id_higher8bits) cur_img_id_higher8bits = 1; } } prev_img_id_lower24bits = cur_img_id_lower24bits; prev_img_id_higher8bits = cur_img_id_higher8bits; prev_placement_id = cur_placement_id; prev_img_row = cur_img_row; prev_img_col = cur_img_col; } if (run_length > 0) { // Render the last run. uint32_t img_id = prev_img_id_lower24bits | (prev_img_id_higher8bits - 1) << 24; grman_put_cell_image(self->grman, row, i - run_length, img_id, prev_placement_id, prev_img_col - run_length, prev_img_row - 1, run_length, 1, self->cell_size); } } // This functions is similar to screen_update_cell_data, but it only updates // line graphics (cell images) and then marks lines as clean. It's used // exclusively for testing unicode placeholders. static void screen_update_only_line_graphics_data(Screen *self) { unsigned int history_line_added_count = self->history_line_added_count; index_type lnum; if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count); screen_reset_dirty(self); self->scroll_changed = false; for (index_type y = 0; y < MIN(self->lines, self->scrolled_by); y++) { lnum = self->scrolled_by - 1 - y; historybuf_init_line(self->historybuf, lnum, self->historybuf->line); screen_render_line_graphics(self, self->historybuf->line, y - self->scrolled_by); if (self->historybuf->line->attrs.has_dirty_text) { historybuf_mark_line_clean(self->historybuf, lnum); } } for (index_type y = self->scrolled_by; y < self->lines; y++) { lnum = y - self->scrolled_by; linebuf_init_line(self->linebuf, lnum); if (self->linebuf->line->attrs.has_dirty_text) { screen_render_line_graphics(self, self->linebuf->line, y - self->scrolled_by); linebuf_mark_line_clean(self->linebuf, lnum); } } } void screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_data, bool cursor_has_moved) { if (self->paused_rendering.expires_at) { if (!self->paused_rendering.cell_data_updated) { LineBuf *linebuf = self->paused_rendering.linebuf; for (index_type y = 0; y < self->lines; y++) { linebuf_init_line(linebuf, y); if (linebuf->line->attrs.has_dirty_text) { render_line(fonts_data, linebuf->line, y, &self->paused_rendering.cursor, self->disable_ligatures, self->lc); screen_render_line_graphics(self, linebuf->line, y); if (linebuf->line->attrs.has_dirty_text && screen_has_marker(self)) mark_text_in_line( self->marker, linebuf->line, &self->as_ansi_buf); linebuf_mark_line_clean(linebuf, y); } update_line_data(linebuf->line, y, address); } } return; } const bool is_overlay_active = screen_is_overlay_active(self); unsigned int history_line_added_count = self->history_line_added_count; index_type lnum; screen_reset_dirty(self); update_overlay_position(self); if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count); self->scroll_changed = false; for (index_type y = 0; y < MIN(self->lines, self->scrolled_by); y++) { lnum = self->scrolled_by - 1 - y; historybuf_init_line(self->historybuf, lnum, self->historybuf->line); // we render line graphics even if the line is not dirty as graphics commands received after // the unicode placeholder was first scanned can alter it. screen_render_line_graphics(self, self->historybuf->line, y - self->scrolled_by); if (self->historybuf->line->attrs.has_dirty_text) { render_line(fonts_data, self->historybuf->line, lnum, self->cursor, self->disable_ligatures, self->lc); if (screen_has_marker(self)) mark_text_in_line(self->marker, self->historybuf->line, &self->as_ansi_buf); historybuf_mark_line_clean(self->historybuf, lnum); } update_line_data(self->historybuf->line, y, address); } for (index_type y = self->scrolled_by; y < self->lines; y++) { lnum = y - self->scrolled_by; linebuf_init_line(self->linebuf, lnum); if (self->linebuf->line->attrs.has_dirty_text || (cursor_has_moved && (self->cursor->y == lnum || self->last_rendered.cursor_y == lnum))) { render_line(fonts_data, self->linebuf->line, lnum, self->cursor, self->disable_ligatures, self->lc); screen_render_line_graphics(self, self->linebuf->line, y - self->scrolled_by); if (self->linebuf->line->attrs.has_dirty_text && screen_has_marker(self)) mark_text_in_line( self->marker, self->linebuf->line, &self->as_ansi_buf); if (is_overlay_active && lnum == self->overlay_line.ynum) render_overlay_line(self, self->linebuf->line, fonts_data); linebuf_mark_line_clean(self->linebuf, lnum); } update_line_data(self->linebuf->line, y, address); } if (is_overlay_active && self->overlay_line.ynum + self->scrolled_by < self->lines) { if (self->overlay_line.is_dirty) { linebuf_init_line(self->linebuf, self->overlay_line.ynum); render_overlay_line(self, self->linebuf->line, fonts_data); } update_overlay_line_data(self, address); } } static bool selection_boundary_less_than(const SelectionBoundary *a, const SelectionBoundary *b) { // y -values must be absolutized (aka adjusted with scrolled_by) // this means the oldest line has the highest value and is thus the least if (a->y > b->y) return true; if (a->y < b->y) return false; if (a->x < b->x) return true; if (a->x > b->x) return false; if (a->in_left_half_of_cell && !b->in_left_half_of_cell) return true; return false; } static index_type num_cells_between_selection_boundaries(const Screen *self, const SelectionBoundary *a, const SelectionBoundary *b) { const SelectionBoundary *before, *after; if (selection_boundary_less_than(a, b)) { before = a; after = b; } else { before = b; after = a; } index_type ans = 0; if (before->y + 1 < after->y) ans += self->columns * (after->y - before->y - 1); if (before->y == after->y) ans += after->x - before->x; else ans += (self->columns - before->x) + after->x; return ans; } static index_type num_lines_between_selection_boundaries(const SelectionBoundary *a, const SelectionBoundary *b) { const SelectionBoundary *before, *after; if (selection_boundary_less_than(a, b)) { before = a; after = b; } else { before = b; after = a; } return before->y - after->y; } static bool selection_is_left_to_right(const Selection *self) { return self->input_start.x < self->input_current.x || (self->input_start.x == self->input_current.x && self->input_start.in_left_half_of_cell); } static void iteration_data(const Selection *sel, IterationData *ans, unsigned x_limit, int min_y, unsigned add_scrolled_by) { memset(ans, 0, sizeof(IterationData)); const SelectionBoundary *start = &sel->start, *end = &sel->end; int start_y = (int)start->y - sel->start_scrolled_by, end_y = (int)end->y - sel->end_scrolled_by; // empty selection if (start->x == end->x && start_y == end_y && start->in_left_half_of_cell == end->in_left_half_of_cell) return; if (sel->rectangle_select) { // empty selection if (start->x == end->x && (!start->in_left_half_of_cell || end->in_left_half_of_cell)) return; ans->y = MIN(start_y, end_y); ans->y_limit = MAX(start_y, end_y) + 1; index_type x, x_limit; bool left_to_right = selection_is_left_to_right(sel); if (start->x == end->x) { x = start->x; x_limit = start->x + 1; } else { if (left_to_right) { x = start->x + (start->in_left_half_of_cell ? 0 : 1); x_limit = 1 + end->x + (end->in_left_half_of_cell ? -1: 0); } else { x = end->x + (end->in_left_half_of_cell ? 0 : 1); x_limit = 1 + start->x + (start->in_left_half_of_cell ? -1 : 0); } } ans->first.x = x; ans->body.x = x; ans->last.x = x; ans->first.x_limit = x_limit; ans->body.x_limit = x_limit; ans->last.x_limit = x_limit; } else { index_type line_limit = x_limit; if (start_y == end_y) { if (start->x == end->x) { if (start->in_left_half_of_cell && !end->in_left_half_of_cell) { // single cell selection ans->first.x = start->x; ans->body.x = start->x; ans->last.x = start->x; ans->first.x_limit = start->x + 1; ans->body.x_limit = start->x + 1; ans->last.x_limit = start->x + 1; } else return; // empty selection } // single line selection else if (start->x <= end->x) { ans->first.x = start->x + (start->in_left_half_of_cell ? 0 : 1); ans->first.x_limit = 1 + end->x + (end->in_left_half_of_cell ? -1 : 0); } else { ans->first.x = end->x + (end->in_left_half_of_cell ? 0 : 1); ans->first.x_limit = 1 + start->x + (start->in_left_half_of_cell ? -1 : 0); } } else if (start_y < end_y) { // downwards ans->body.x_limit = line_limit; ans->first.x_limit = line_limit; ans->first.x = start->x + (start->in_left_half_of_cell ? 0 : 1); ans->last.x_limit = 1 + end->x + (end->in_left_half_of_cell ? -1 : 0); } else { // upwards ans->body.x_limit = line_limit; ans->first.x_limit = line_limit; ans->first.x = end->x + (end->in_left_half_of_cell ? 0 : 1); ans->last.x_limit = 1 + start->x + (start->in_left_half_of_cell ? -1 : 0); } ans->y = MIN(start_y, end_y); ans->y_limit = MAX(start_y, end_y) + 1; } ans->y += add_scrolled_by; ans->y_limit += add_scrolled_by; ans->y = MAX(ans->y, min_y); ans->y_limit = MAX(ans->y, ans->y_limit); // iteration is from y to y_limit } static XRange xrange_for_iteration(const IterationData *idata, const int y, const Line *line) { XRange ans = {.x_limit=xlimit_for_line(line)}; if (y == idata->y) { ans.x_limit = MIN(idata->first.x_limit, ans.x_limit); ans.x = idata->first.x; } else if (y == idata->y_limit - 1) { ans.x_limit = MIN(idata->last.x_limit, ans.x_limit); ans.x = idata->last.x; } else { ans.x_limit = MIN(idata->body.x_limit, ans.x_limit); ans.x = idata->body.x; } return ans; } static XRange xrange_for_iteration_with_multicells(const IterationData *idata, const int y, const Line *line) { XRange ans = xrange_for_iteration(idata, y, line); if (ans.x_limit > ans.x) { CPUCell *c; index_type ml; if (ans.x && (c = &line->cpu_cells[ans.x])->is_multicell && c->x) ans.x = ans.x > c->x ? ans.x - c->x : 0; if (ans.x_limit < line->xnum && (c = &line->cpu_cells[ans.x_limit-1])->is_multicell && c->x + 1u < (ml = mcd_x_limit(c))) { ans.x_limit += ml - 1 - c->x; if (ans.x_limit > line->xnum) ans.x_limit = line->xnum; } } return ans; } static bool iteration_data_is_empty(const Screen *self, const IterationData *idata) { if (idata->y >= idata->y_limit) return true; index_type xl = MIN(idata->first.x_limit, self->columns); if (idata->first.x < xl) return false; xl = MIN(idata->body.x_limit, self->columns); if (idata->body.x < xl) return false; xl = MIN(idata->last.x_limit, self->columns); if (idata->last.x < xl) return false; return true; } static void apply_selection(Screen *self, uint8_t *data, Selection *s, uint8_t set_mask) { iteration_data(s, &s->last_rendered, self->columns, -self->historybuf->count, self->scrolled_by); Line *line; const int y_min = MAX(0, s->last_rendered.y), y_limit = MIN(s->last_rendered.y_limit, (int)self->lines); for (int y = y_min; y < y_limit; y++) { if (self->paused_rendering.expires_at) { linebuf_init_line(self->paused_rendering.linebuf, y); line = self->paused_rendering.linebuf->line; } else line = visual_line_(self, y); uint8_t *line_start = data + self->columns * y; XRange xr = xrange_for_iteration_with_multicells(&s->last_rendered, y, line); for (index_type x = xr.x; x < xr.x_limit; x++) { line_start[x] |= set_mask; CPUCell *c = &line->cpu_cells[x]; if (c->is_multicell && c->scale > 1) { for (int ym = MAX(0, y - c->y); ym < y; ym++) data[self->columns * ym + x] |= set_mask; for (int ym = y + 1; ym < MIN((int)self->lines, y + c->scale - c->y); ym++) data[self->columns * ym + x] |= set_mask; } } } s->last_rendered.y = MAX(0, s->last_rendered.y); } bool screen_has_selection(Screen *self) { IterationData idata; for (size_t i = 0; i < self->selections.count; i++) { Selection *s = self->selections.items + i; if (!is_selection_empty(s)) { iteration_data(s, &idata, self->columns, -self->historybuf->count, self->scrolled_by); if (!iteration_data_is_empty(self, &idata)) return true; } } return false; } void screen_apply_selection(Screen *self, void *address, size_t size) { memset(address, 0, size); Selections *sel = self->paused_rendering.expires_at ? &self->paused_rendering.selections : &self->selections; for (size_t i = 0; i < sel->count; i++) apply_selection(self, address, sel->items + i, 1); sel->last_rendered_count = sel->count; sel = self->paused_rendering.expires_at ? &self->paused_rendering.url_ranges : &self->url_ranges; for (size_t i = 0; i < sel->count; i++) { Selection *s = sel->items + i; if (OPT(underline_hyperlinks) == UNDERLINE_NEVER && s->is_hyperlink) continue; apply_selection(self, address, s, 2); } sel->last_rendered_count = sel->count; } static index_type limit_without_trailing_whitespace(const Line *line, index_type limit) { if (!limit) return limit; if (limit > line->xnum) limit = line->xnum; while (limit > 0) { const CPUCell *cell = line->cpu_cells + limit - 1; if (cell->is_multicell && (cell->x || cell->y)) { limit--; continue; } if (cell->ch_is_idx) break; switch(cell->ch_or_idx) { case ' ': case '\t': case '\n': case '\r': case 0: break; default: return limit; } limit--; } return limit; } static void flag_selection_to_extract_text(Screen *self, const Selection *s, int *miny, int *y_limit) { IterationData idata; bool has_history = self->linebuf == self->main_linebuf; iteration_data(s, &idata, self->columns, has_history ? -self->historybuf->count : 0, 0); Line *line; *miny = idata.y; *y_limit = MIN(idata.y_limit, (int)self->lines); if (*miny >= *y_limit) return; static const int max_scale = ( (1u << SCALE_BITS) - 1u); for (int y = idata.y - max_scale; y < *y_limit; y++) { line = checked_range_line(self, y); if (line) for (index_type x = 0; x < line->xnum; x++) line->cpu_cells[x].temp_flag = 0; } Line temp = {.xnum=self->columns, .text_cache=self->text_cache}; for (int y = idata.y; y < *y_limit; y++) { range_line(self, y, &temp); CPUCell *c; XRange xr = xrange_for_iteration_with_multicells(&idata, y, &temp); for (index_type x = xr.x; x < xr.x_limit; x++) { c = temp.cpu_cells + x; c->temp_flag = 1; if (c->is_multicell && c->y) { for (int ym = y - c->y; ym < y; ym++) { line = checked_range_line(self, ym); if (line) { line->cpu_cells[x].temp_flag = 1; *miny = MIN(*miny, ym); } } } } } // remove lines from bottom that contain only y > 0 cells from multicell while (*y_limit > *miny) { range_line(self, *y_limit - 1, &temp); for (index_type x = 0; x < temp.xnum; x++) { if (temp.cpu_cells[x].temp_flag && temp.cpu_cells[x].ch_and_idx && (!temp.cpu_cells[x].is_multicell || !temp.cpu_cells[x].y)) return; } (*y_limit)--; } } static PyObject* text_for_range(Screen *self, const Selection *sel, bool insert_newlines, bool strip_trailing_whitespace) { int min_y, y_limit; flag_selection_to_extract_text(self, sel, &min_y, &y_limit); if (min_y >= y_limit) return PyTuple_New(0); size_t before = self->as_ansi_buf.len; RAII_PyObject(ans, PyTuple_New(y_limit - min_y)); RAII_PyObject(nl, PyUnicode_FromString("\n")); RAII_PyObject(empty, PyUnicode_FromString("")); if (!ans || !nl || !empty) return NULL; for (int i = 0, y = min_y; y < y_limit; y++, i++) { Line *line = range_line_(self, y); index_type x_limit = line->xnum, x_start = 0; while (x_limit && !line->cpu_cells[x_limit - 1].temp_flag) x_limit--; while (x_start < x_limit && !line->cpu_cells[x_start].temp_flag) x_start++; bool is_only_whitespace_line = false; if (strip_trailing_whitespace) { index_type new_limit = limit_without_trailing_whitespace(line, x_limit); if (new_limit != x_limit) { x_limit = new_limit; is_only_whitespace_line = new_limit <= x_start; } } const bool is_first_line = y == min_y, is_last_line = y + 1 >= y_limit; const bool add_trailing_newline = insert_newlines && !is_last_line; PyObject *text = NULL; if (x_limit <= x_start && (is_only_whitespace_line || line_is_empty(line))) { // we want a newline on only whitespace lines even if they are continued text = add_trailing_newline ? nl : empty; text = Py_NewRef(text); } else { while (x_start < x_limit) { index_type end = x_start; while (end < x_limit && line->cpu_cells[end].temp_flag) end++; if (!unicode_in_range(line, x_start, end, true, add_trailing_newline, false, !is_first_line, &self->as_ansi_buf)) return PyErr_NoMemory(); x_start = MAX(x_start + 1, end); } text = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, self->as_ansi_buf.buf + before, self->as_ansi_buf.len - before); } self->as_ansi_buf.len = before; if (!text) return NULL; PyTuple_SET_ITEM(ans, i, text); } return Py_NewRef(ans); } static PyObject* ansi_for_range(Screen *self, const Selection *sel, bool insert_newlines, bool strip_trailing_whitespace) { int min_y, y_limit; flag_selection_to_extract_text(self, sel, &min_y, &y_limit); if (min_y >= y_limit) return PyTuple_New(0); ANSILineState s = {.output_buf=&self->as_ansi_buf}; s.output_buf->active_hyperlink_id = 0; s.output_buf->len = 0; RAII_PyObject(ans, PyTuple_New(y_limit - min_y + 1)); RAII_PyObject(nl, PyUnicode_FromString("\n")); if (!ans || !nl) return NULL; bool has_escape_codes = false; bool need_newline = false; for (int i = 0, y = min_y; y < y_limit; y++, i++) { const bool is_first_line = y == min_y; s.output_buf->len = 0; Line *line = range_line_(self, y); index_type x_limit = line->xnum, x_start = 0; while (x_limit && !line->cpu_cells[x_limit - 1].temp_flag) x_limit--; while (x_start < x_limit && !line->cpu_cells[x_start].temp_flag) x_start++; bool is_only_whitespace_line = false; if (strip_trailing_whitespace) { index_type new_limit = limit_without_trailing_whitespace(line, x_limit); if (new_limit != x_limit) { x_limit = new_limit; is_only_whitespace_line = new_limit <= x_start; } } if (x_limit <= x_start && (is_only_whitespace_line || line_is_empty(line))) { // we want a newline on only whitespace lines even if they are continued if (insert_newlines) need_newline = true; } else { char_type prefix_char = need_newline ? '\n' : 0; while (x_start < x_limit) { index_type end = x_start; while (end < x_limit && line->cpu_cells[end].temp_flag) end++; if (line_as_ansi(line, &s, x_start, end, prefix_char, !is_first_line)) has_escape_codes = true; need_newline = insert_newlines && !line->cpu_cells[line->xnum-1].next_char_was_wrapped; prefix_char = 0; x_start = MAX(x_start + 1, end); } PyObject *t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, s.output_buf->buf, s.output_buf->len); if (!t) return NULL; PyTuple_SET_ITEM(ans, i, t); } } PyObject *t = PyUnicode_FromFormat("%s%s", has_escape_codes ? "\x1b[m" : "", s.output_buf->active_hyperlink_id ? "\x1b]8;;\x1b\\" : ""); if (!t) return NULL; PyTuple_SET_ITEM(ans, PyTuple_GET_SIZE(ans) - 1, t); return Py_NewRef(ans); } static hyperlink_id_type hyperlink_id_for_range(Screen *self, const Selection *sel) { IterationData idata; iteration_data(sel, &idata, self->columns, -self->historybuf->count, 0); for (int i = 0, y = idata.y; y < idata.y_limit && y < (int)self->lines; y++, i++) { Line *line = range_line_(self, y); XRange xr = xrange_for_iteration(&idata, y, line); for (index_type x = xr.x; x < xr.x_limit; x++) { if (line->cpu_cells[x].hyperlink_id) return line->cpu_cells[x].hyperlink_id; } } return 0; } static PyObject* extend_tuple(PyObject *a, PyObject *b) { Py_ssize_t bs = PyTuple_GET_SIZE(b); if (bs < 1) return a; Py_ssize_t off = PyTuple_GET_SIZE(a); if (_PyTuple_Resize(&a, off + bs) != 0) return NULL; for (Py_ssize_t y = 0; y < bs; y++) { PyObject *t = PyTuple_GET_ITEM(b, y); Py_INCREF(t); PyTuple_SET_ITEM(a, off + y, t); } return a; } static PyObject* current_url_text(Screen *self, PyObject *args UNUSED) { RAII_PyObject(empty_string, PyUnicode_FromString("")); if (!empty_string) return NULL; RAII_PyObject(ans, NULL); for (size_t i = 0; i < self->url_ranges.count; i++) { Selection *s = self->url_ranges.items + i; if (!is_selection_empty(s)) { RAII_PyObject(temp, text_for_range(self, s, false, false)); if (!temp) return NULL; RAII_PyObject(text, PyUnicode_Join(empty_string, temp)); if (!text) return NULL; if (ans) { PyObject *t = PyUnicode_Concat(ans, text); if (!t) return NULL; Py_CLEAR(ans); ans = t; } else ans = Py_NewRef(text); } } return Py_NewRef(ans ? ans : Py_None); } bool screen_open_url(Screen *self) { if (!self->url_ranges.count) return false; hyperlink_id_type hid = hyperlink_id_for_range(self, self->url_ranges.items); if (hid) { const char *url = get_hyperlink_for_id(self->hyperlink_pool, hid, true); if (url) { CALLBACK("open_url", "sH", url, hid); return true; } } PyObject *text = current_url_text(self, NULL); if (!text) { if (PyErr_Occurred()) PyErr_Print(); return false; } bool found = false; if (PyUnicode_Check(text)) { CALLBACK("open_url", "OH", text, 0); found = true; } Py_CLEAR(text); return found; } // }}} // URLs {{{ static index_type get_last_hostname_char_pos(Line *line, index_type url_start) { index_type slash_count = 0; while (url_start < line->xnum) { index_type pos = find_char(line, url_start, '/'); if (pos >= line->xnum) return line->xnum; if (++slash_count > 2) return prev_char_pos(line, pos, 1); url_start = next_char_pos(line, pos, 1); } return line->xnum; } static void extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type sentinel, bool newlines_allowed, index_type last_hostname_char_pos, index_type scale) { unsigned int count = 0; bool has_newline = false; index_type orig_y = *y; while (count++ < 10) { bool in_hostname = last_hostname_char_pos >= line->xnum; has_newline = !line->cpu_cells[line->xnum-1].next_char_was_wrapped; if (next_char_pos(line, *x, 1) < line->xnum || (!newlines_allowed && has_newline)) break; bool next_line_starts_with_url_chars = false; line = screen_visual_line(screen, *y + 2 * scale); if (line) { next_line_starts_with_url_chars = line_startswith_url_chars(line, in_hostname, screen->lc); has_newline = !visual_line_is_continued(screen, *y + 2 * scale); if (next_line_starts_with_url_chars && has_newline && !newlines_allowed) next_line_starts_with_url_chars = false; if (sentinel && next_line_starts_with_url_chars && cell_is_char(line->cpu_cells, sentinel)) next_line_starts_with_url_chars = false; } line = screen_visual_line(screen, *y + scale); if (!line) break; if (in_hostname) { last_hostname_char_pos = find_char(line, 0, '/'); if (last_hostname_char_pos < line->xnum) { last_hostname_char_pos = prev_char_pos(line, last_hostname_char_pos, 1); if (last_hostname_char_pos >= line->xnum) in_hostname = false; } } index_type new_x = line_url_end_at(line, 0, false, sentinel, next_line_starts_with_url_chars, in_hostname, last_hostname_char_pos, screen->lc); if (!new_x && !line_startswith_url_chars(line, in_hostname, screen->lc)) break; *y += scale; *x = new_x; } if (sentinel && *x == 0 && *y > orig_y) { line = screen_visual_line(screen, *y); if (line && cell_is_char(line->cpu_cells, sentinel)) { *y -= scale; *x = line->xnum - 1; if (line->cpu_cells[*x].is_multicell) *x -= line->cpu_cells[*x].x; } } } int screen_detect_url(Screen *screen, unsigned int x, unsigned int y) { bool has_url = false; index_type url_start, url_end = 0; Line *line = screen_visual_line(screen, y); if (!line || x >= screen->columns) return 0; if (line->cpu_cells[x].is_multicell && line->cpu_cells[x].scale > 1 && line->cpu_cells[x].y) { if (line->cpu_cells[x].y > y) return 0; y -= line->cpu_cells[x].y; line = screen_visual_line(screen, y); } if (line->cpu_cells[x].is_multicell && line->cpu_cells[x].x) x = x > line->cpu_cells[x].x ? x - line->cpu_cells[x].x : 0; hyperlink_id_type hid; if ((hid = line->cpu_cells[x].hyperlink_id)) { screen_mark_hyperlink(screen, x, y); return hid; } char_type sentinel = 0; const bool newlines_allowed = !is_excluded_from_url('\n'); index_type last_hostname_char_pos = screen->columns; url_start = line_url_start_at(line, x, screen->lc); Line scratch = {.xnum=line->xnum, .text_cache=line->text_cache}; index_type scale = 1; if (url_start < line->xnum) { scale = cell_scale(line->cpu_cells + url_start); bool next_line_starts_with_url_chars = false; if (y + scale < screen->lines) { visual_line(screen, y + scale, &scratch); next_line_starts_with_url_chars = line_startswith_url_chars(&scratch, last_hostname_char_pos >= line->xnum, screen->lc); if (next_line_starts_with_url_chars && !newlines_allowed && !visual_line_is_continued(screen, y + scale)) next_line_starts_with_url_chars = false; } sentinel = get_url_sentinel(line, url_start); last_hostname_char_pos = get_last_hostname_char_pos(line, url_start); url_end = line_url_end_at(line, x, true, sentinel, next_line_starts_with_url_chars, x <= last_hostname_char_pos, last_hostname_char_pos, screen->lc); } has_url = url_end > url_start; if (has_url) { index_type y_extended = y; extend_url(screen, line, &url_end, &y_extended, sentinel, newlines_allowed, last_hostname_char_pos, scale); screen_mark_url(screen, url_start, y, url_end, y_extended); } else { screen_mark_url(screen, 0, 0, 0, 0); } return has_url ? -1 : 0; } // }}} // IME Overlay {{{ bool screen_is_overlay_active(Screen *self) { return self->overlay_line.is_active; } static void deactivate_overlay_line(Screen *self) { if (self->overlay_line.is_active && self->overlay_line.xnum && self->overlay_line.ynum < self->lines) { self->is_dirty = true; linebuf_mark_line_dirty(self->linebuf, self->overlay_line.ynum); } self->overlay_line.is_active = false; self->overlay_line.is_dirty = true; self->overlay_line.ynum = 0; self->overlay_line.xstart = 0; self->overlay_line.cursor_x = 0; } void screen_update_overlay_text(Screen *self, const char *utf8_text) { if (screen_is_overlay_active(self)) deactivate_overlay_line(self); if (!utf8_text || !utf8_text[0]) return; PyObject *text = PyUnicode_FromString(utf8_text); if (!text) return; Py_XDECREF(self->overlay_line.overlay_text); // Calculate the total number of cells for initial overlay cursor position RAII_PyObject(text_len, wcswidth_std(NULL, text)); self->overlay_line.overlay_text = text; self->overlay_line.is_active = true; self->overlay_line.is_dirty = true; self->overlay_line.xstart = self->cursor->x; self->overlay_line.xnum = !text_len ? 0 : PyLong_AsLong(text_len); self->overlay_line.text_len = self->overlay_line.xnum; self->overlay_line.cursor_x = MIN(self->overlay_line.xstart + self->overlay_line.xnum, self->columns); self->overlay_line.ynum = self->cursor->y; cursor_copy_to(self->cursor, &(self->overlay_line.original_line.cursor)); linebuf_mark_line_dirty(self->linebuf, self->overlay_line.ynum); self->is_dirty = true; // Since we are typing, scroll to the bottom if (self->scrolled_by != 0) { self->scrolled_by = 0; dirty_scroll(self); } } static void screen_draw_overlay_line(Screen *self) { if (!self->overlay_line.overlay_text) return; // Right-align the overlay to ensure that the pre-edit text just entered is visible when the cursor is near the end of the line. index_type xstart = self->overlay_line.text_len <= self->columns ? self->columns - self->overlay_line.text_len : 0; if (self->overlay_line.xstart < xstart) xstart = self->overlay_line.xstart; index_type columns_exceeded = self->overlay_line.text_len <= self->columns ? 0 : self->overlay_line.text_len - self->columns; bool orig_line_wrap_mode = self->modes.mDECAWM; bool orig_cursor_enable_mode = self->modes.mDECTCEM; bool orig_insert_replace_mode = self->modes.mIRM; self->modes.mDECAWM = false; self->modes.mDECTCEM = false; self->modes.mIRM = false; Cursor *orig_cursor = self->cursor; self->cursor = &(self->overlay_line.original_line.cursor); self->cursor->reverse ^= true; self->cursor->x = xstart; self->cursor->y = self->overlay_line.ynum; self->overlay_line.xnum = 0; if (xstart > 0) { // remove any multicell characters temporarily that intersect the left boundary, // the characters are not actually removed, just deleted on this line CPUCell *c = self->linebuf->line->cpu_cells + xstart; while (c->is_multicell && c->x && c < self->linebuf->line->cpu_cells + self->columns) { c->is_multicell = false; c->ch_or_idx = ' '; c->ch_is_idx = false; c++; } } index_type before; const int kind = PyUnicode_KIND(self->overlay_line.overlay_text); const void *data = PyUnicode_DATA(self->overlay_line.overlay_text); const Py_ssize_t sz = PyUnicode_GET_LENGTH(self->overlay_line.overlay_text); for (Py_ssize_t pos = 0; pos < sz; pos++) { before = self->cursor->x; draw_codepoint(self, PyUnicode_READ(kind, data, pos)); index_type len = self->cursor->x - before; if (columns_exceeded > 0) { // Reset the cursor to maintain right alignment when the overlay exceeds the screen width. if (columns_exceeded > len) { columns_exceeded -= len; len = 0; } else { len = len > columns_exceeded ? len - columns_exceeded : 0; columns_exceeded = 0; if (len > 0) { // When the last character is a split multicell, make sure the next character is visible. CPUCell *c = self->linebuf->line->cpu_cells + len - 1; if (c->is_multicell) { if (c->x < mcd_x_limit(c) - 1) { do { c->is_multicell = false; c->ch_is_idx = false; c->ch_or_idx = ' '; if (!c->x) break; c--; } while(c->is_multicell && c >= self->linebuf->line->cpu_cells); } } } } self->cursor->x = len; } self->overlay_line.xnum += len; } self->overlay_line.cursor_x = self->cursor->x; self->cursor->reverse ^= true; self->cursor = orig_cursor; self->modes.mDECAWM = orig_line_wrap_mode; self->modes.mDECTCEM = orig_cursor_enable_mode; self->modes.mIRM = orig_insert_replace_mode; } static void update_overlay_position(Screen *self) { if (screen_is_overlay_active(self) && screen_is_cursor_visible(self)) { bool cursor_update = false; if (self->cursor->x != self->overlay_line.xstart) { cursor_update = true; self->overlay_line.xstart = self->cursor->x; self->overlay_line.cursor_x = MIN(self->overlay_line.xstart + self->overlay_line.xnum, self->columns); } if (self->cursor->y != self->overlay_line.ynum) { cursor_update = true; linebuf_mark_line_dirty(self->linebuf, self->overlay_line.ynum); self->overlay_line.ynum = self->cursor->y; } if (cursor_update) { linebuf_mark_line_dirty(self->linebuf, self->overlay_line.ynum); self->overlay_line.is_dirty = true; self->is_dirty = true; } } } static void render_overlay_line(Screen *self, Line *line, FONTS_DATA_HANDLE fonts_data) { #define ol self->overlay_line line_save_cells(line, 0, line->xnum, ol.original_line.gpu_cells, ol.original_line.cpu_cells); screen_draw_overlay_line(self); render_line(fonts_data, line, ol.ynum, self->cursor, self->disable_ligatures, self->lc); line_save_cells(line, 0, line->xnum, ol.gpu_cells, ol.cpu_cells); line_reset_cells(line, 0, line->xnum, ol.original_line.gpu_cells, ol.original_line.cpu_cells); ol.is_dirty = false; const index_type y = MIN(ol.ynum + self->scrolled_by, self->lines - 1); if (ol.last_ime_pos.x != ol.cursor_x || ol.last_ime_pos.y != y) { ol.last_ime_pos.x = ol.cursor_x; ol.last_ime_pos.y = y; update_ime_position_for_window(self->window_id, false, 0); } #undef ol } static void update_overlay_line_data(Screen *self, uint8_t *data) { const size_t base = sizeof(GPUCell) * (self->overlay_line.ynum + self->scrolled_by) * self->columns; memcpy(data + base, self->overlay_line.gpu_cells, self->columns * sizeof(GPUCell)); } // }}} // Python interface {{{ #define WRAP0(name) static PyObject* name(Screen *self, PyObject *a UNUSED) { screen_##name(self); Py_RETURN_NONE; } #define WRAP0x(name) static PyObject* xxx_##name(Screen *self, PyObject *a UNUSED) { screen_##name(self); Py_RETURN_NONE; } #define WRAP1(name, defval) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; if(!PyArg_ParseTuple(args, "|I", &v)) return NULL; screen_##name(self, v); Py_RETURN_NONE; } #define WRAP1B(name, defval) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; int b=false; if(!PyArg_ParseTuple(args, "|Ip", &v, &b)) return NULL; screen_##name(self, v, b); Py_RETURN_NONE; } #define WRAP1E(name, defval, ...) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; if(!PyArg_ParseTuple(args, "|I", &v)) return NULL; screen_##name(self, v, __VA_ARGS__); Py_RETURN_NONE; } #define WRAP2(name, defval1, defval2) static PyObject* name(Screen *self, PyObject *args) { unsigned int a=defval1, b=defval2; if(!PyArg_ParseTuple(args, "|II", &a, &b)) return NULL; screen_##name(self, a, b); Py_RETURN_NONE; } #define WRAP2B(name) static PyObject* name(Screen *self, PyObject *args) { unsigned int a, b; int p; if(!PyArg_ParseTuple(args, "IIp", &a, &b, &p)) return NULL; screen_##name(self, a, b, (bool)p); Py_RETURN_NONE; } WRAP0(garbage_collect_hyperlink_pool) static PyObject* has_selection(Screen *self, PyObject *a UNUSED) { if (screen_has_selection(self)) Py_RETURN_TRUE; Py_RETURN_FALSE; } static PyObject* hyperlinks_as_set(Screen *self, PyObject *args UNUSED) { return screen_hyperlinks_as_set(self); } static PyObject* hyperlink_for_id(Screen *self, PyObject *val) { unsigned long id = PyLong_AsUnsignedLong(val); if (id > HYPERLINK_MAX_NUMBER) { PyErr_SetString(PyExc_IndexError, "Out of bounds"); return NULL; } return Py_BuildValue("s", get_hyperlink_for_id(self->hyperlink_pool, id, true)); } static Line* get_visual_line(void *x, int y) { return visual_line_(x, y); } static Line* get_range_line(void *x, int y) { return range_line_(x, y); } static PyObject* as_text(Screen *self, PyObject *args) { return as_text_generic(args, self, get_visual_line, self->lines, &self->as_ansi_buf, false); } static PyObject* as_text_non_visual(Screen *self, PyObject *args) { return as_text_generic(args, self, get_range_line, self->lines, &self->as_ansi_buf, false); } static PyObject* as_text_for_history_buf(Screen *self, PyObject *args) { return as_text_history_buf(self->historybuf, args, &self->as_ansi_buf); } static PyObject* as_text_generic_wrapper(Screen *self, PyObject *args, get_line_func get_line) { return as_text_generic(args, self, get_line, self->lines, &self->as_ansi_buf, false); } static PyObject* as_text_alternate(Screen *self, PyObject *args) { LineBuf *original = self->linebuf; self->linebuf = original == self->main_linebuf ? self->alt_linebuf : self->main_linebuf; PyObject *ans = as_text_generic_wrapper(self, args, get_range_line); self->linebuf = original; return ans; } typedef struct OutputOffset { Screen *screen; int start; unsigned num_lines; bool reached_upper_limit; } OutputOffset; static Line* get_line_from_offset(void *x, int y) { OutputOffset *r = x; return range_line_(r->screen, r->start + y); } static bool find_cmd_output(Screen *self, OutputOffset *oo, index_type start_screen_y, unsigned int scrolled_by, int direction, bool on_screen_only) { bool found_prompt = false, found_output = false, found_next_prompt = false; int start = 0, end = 0; int init_y = start_screen_y - scrolled_by, y1 = init_y, y2 = init_y; const int upward_limit = -self->historybuf->count; const int downward_limit = self->lines - 1; const int screen_limit = -scrolled_by + downward_limit; Line *line = NULL; // find around if (direction == 0) { line = checked_range_line(self, y1); if (line && line->attrs.prompt_kind == PROMPT_START) { found_prompt = true; // change direction to downwards to find command output direction = 1; } else if (line && line->attrs.prompt_kind == OUTPUT_START && !range_line_is_continued(self, y1)) { found_output = true; start = y1; found_prompt = true; direction = 1; } y1--; y2++; } // find upwards if (direction <= 0) { // find around: only needs to find the first output start // find upwards: find prompt after the output, and the first output while (y1 >= upward_limit) { line = checked_range_line(self, y1); if (line && line->attrs.prompt_kind == PROMPT_START && !range_line_is_continued(self, y1)) { if (direction == 0) { found_prompt = true; break; } found_next_prompt = true; end = y1; } else if (line && line->attrs.prompt_kind == OUTPUT_START && !range_line_is_continued(self, y1)) { found_output = true; start = y1; found_prompt = true; break; } y1--; } if (y1 < upward_limit) { oo->reached_upper_limit = true; found_output = direction != 0; start = upward_limit; found_prompt = direction != 0; } } // find downwards if (direction >= 0) { while (y2 <= downward_limit) { if (on_screen_only && !found_output && y2 > screen_limit) break; line = checked_range_line(self, y2); if (line && line->attrs.prompt_kind == PROMPT_START) { if (!found_prompt) { if (direction == 0) { found_next_prompt = true; end = y2; break; } found_prompt = true; } else if (found_prompt && !found_output) { // skip fetching wrapped prompt lines while (range_line_is_continued(self, y2)) { y2++; } } else if (found_output && !found_next_prompt) { found_next_prompt = true; end = y2; break; } } else if (line && line->attrs.prompt_kind == OUTPUT_START && !found_output) { found_output = true; start = y2; if (!found_prompt) found_prompt = true; } y2++; } } if (found_next_prompt) { oo->num_lines = end >= start ? end - start : 0; } else if (found_output) { end = (direction < 0 ? MIN(init_y, downward_limit) : downward_limit) + 1; oo->num_lines = end >= start ? end - start : 0; } else return false; oo->start = start; return oo->num_lines > 0; } static PyObject* cmd_output(Screen *self, PyObject *args) { unsigned int which = 0; RAII_PyObject(which_args, PyTuple_GetSlice(args, 0, 1)); RAII_PyObject(as_text_args, PyTuple_GetSlice(args, 1, PyTuple_GET_SIZE(args))); if (!which_args || !as_text_args) return NULL; if (!PyArg_ParseTuple(which_args, "I", &which)) return NULL; if (self->linebuf != self->main_linebuf) Py_RETURN_NONE; OutputOffset oo = {.screen=self}; bool found = false; switch (which) { case 0: // last run cmd // When scrolled, the starting point of the search for the last command output // is actually out of the screen, so add the number of scrolled lines found = find_cmd_output(self, &oo, self->cursor->y + self->scrolled_by, self->scrolled_by, -1, false); break; case 1: // first on screen found = find_cmd_output(self, &oo, 0, self->scrolled_by, 1, true); break; case 2: // last visited cmd if (self->last_visited_prompt.scrolled_by <= self->historybuf->count && self->last_visited_prompt.is_set) { found = find_cmd_output(self, &oo, self->last_visited_prompt.y, self->last_visited_prompt.scrolled_by, 0, false); } break; case 3: { // last non-empty output int y = self->cursor->y; Line *line; bool reached_upper_limit = false; while (!found && !reached_upper_limit) { line = checked_range_line(self, y); if (!line || (line->attrs.prompt_kind == OUTPUT_START && !range_line_is_continued(self, y))) { int start = line ? y : y + 1; reached_upper_limit = !line; int y2 = start; unsigned int num_lines = 0; bool found_content = false; while ((line = checked_range_line(self, y2)) && line->attrs.prompt_kind != PROMPT_START) { if (!found_content) found_content = !line_is_empty(line); num_lines++; y2++; } if (found_content) { found = true; oo.reached_upper_limit = reached_upper_limit; oo.start = start; oo.num_lines = num_lines; break; } } y--; } } break; default: PyErr_Format(PyExc_KeyError, "%u is not a valid type of command", which); return NULL; } if (found) { RAII_PyObject(ret, as_text_generic(as_text_args, &oo, get_line_from_offset, oo.num_lines, &self->as_ansi_buf, false)); if (!ret) return NULL; } if (oo.reached_upper_limit && self->linebuf == self->main_linebuf && OPT(scrollback_pager_history_size) > 0) Py_RETURN_TRUE; Py_RETURN_FALSE; } bool screen_set_last_visited_prompt(Screen *self, index_type y) { if (y >= self->lines) return false; self->last_visited_prompt.scrolled_by = self->scrolled_by; self->last_visited_prompt.y = y; self->last_visited_prompt.is_set = true; return true; } bool screen_select_cmd_output(Screen *self, index_type y) { if (y >= self->lines) return false; OutputOffset oo = {.screen=self}; if (!find_cmd_output(self, &oo, y, self->scrolled_by, 0, true)) return false; screen_start_selection(self, 0, y, true, false, EXTEND_LINE); Selection *s = self->selections.items; #define S(which, offset_y, scrolled_by) \ if (offset_y < 0) { \ s->scrolled_by = -(offset_y); s->which.y = 0; \ } else { \ s->scrolled_by = 0; s->which.y = offset_y; \ } S(start, oo.start, start_scrolled_by); S(end, oo.start + (int)oo.num_lines - 1, end_scrolled_by); #undef S s->start.x = 0; s->start.in_left_half_of_cell = true; s->end.x = self->columns; s->end.in_left_half_of_cell = false; self->selections.in_progress = false; call_boss(set_primary_selection, NULL); return true; } static PyObject* screen_truncate_point_for_length(PyObject UNUSED *self, PyObject *args) { PyObject *str; unsigned int num_cells, start_pos = 0; if (!PyArg_ParseTuple(args, "UI|I", &str, &num_cells, &start_pos)) return NULL; if (PyUnicode_READY(str) != 0) return NULL; int kind = PyUnicode_KIND(str); void *data = PyUnicode_DATA(str); Py_ssize_t len = PyUnicode_GET_LENGTH(str), i; char_type prev_ch = 0; int prev_width = 0; bool in_sgr = false; unsigned long width_so_far = 0; for (i = start_pos; i < len && width_so_far < num_cells; i++) { char_type ch = PyUnicode_READ(kind, data, i); if (in_sgr) { if (ch == 'm') in_sgr = false; continue; } if (ch == 0x1b && i + 1 < len && PyUnicode_READ(kind, data, i + 1) == '[') { in_sgr = true; continue; } if (ch == 0xfe0f) { if (is_emoji_presentation_base(prev_ch) && prev_width == 1) { width_so_far += 1; prev_width = 2; } else prev_width = 0; } else { int w = wcwidth_std(char_props_for(ch)); switch(w) { case -1: case 0: prev_width = 0; break; case 2: prev_width = 2; break; default: prev_width = 1; break; } if (width_so_far + prev_width > num_cells) { break; } width_so_far += prev_width; } prev_ch = ch; } return PyLong_FromUnsignedLong(i); } static PyObject* line(Screen *self, PyObject *val) { unsigned long y = PyLong_AsUnsignedLong(val); if (y >= self->lines) { PyErr_SetString(PyExc_IndexError, "Out of bounds"); return NULL; } linebuf_init_line(self->linebuf, y); Py_INCREF(self->linebuf->line); return (PyObject*) self->linebuf->line; } Line* screen_visual_line(Screen *self, index_type y) { if (y >= self->lines) return NULL; return visual_line_(self, y); } static PyObject* pyvisual_line(Screen *self, PyObject *args) { // The line corresponding to the yth visual line, taking into account scrolling unsigned int y; if (!PyArg_ParseTuple(args, "I", &y)) return NULL; if (y >= self->lines) { Py_RETURN_NONE; } return Py_BuildValue("O", visual_line_(self, y)); } static PyObject* draw(Screen *self, PyObject *src) { if (!PyUnicode_Check(src)) { PyErr_SetString(PyExc_TypeError, "A unicode string is required"); return NULL; } if (PyUnicode_READY(src) != 0) { return PyErr_NoMemory(); } Py_UCS4 *buf = PyUnicode_AsUCS4Copy(src); if (!buf) return NULL; draw_text(self, buf, PyUnicode_GetLength(src)); PyMem_Free(buf); Py_RETURN_NONE; } static PyObject* apply_sgr(Screen *self, PyObject *src) { if (!PyUnicode_Check(src)) { PyErr_SetString(PyExc_TypeError, "A unicode string is required"); return NULL; } if (PyUnicode_READY(src) != 0) { return PyErr_NoMemory(); } Py_ssize_t sz; const char *s = PyUnicode_AsUTF8AndSize(src, &sz); if (s == NULL) return NULL; if (!parse_sgr(self, (const uint8_t*)s, sz, "parse_sgr", false)) { PyErr_Format(PyExc_ValueError, "Invalid SGR: %s", PyUnicode_AsUTF8(src)); return NULL; } Py_RETURN_NONE; } static PyObject* reset_mode(Screen *self, PyObject *args) { int private = false; unsigned int mode; if (!PyArg_ParseTuple(args, "I|p", &mode, &private)) return NULL; if (private) mode <<= 5; screen_reset_mode(self, mode); Py_RETURN_NONE; } static PyObject* _select_graphic_rendition(Screen *self, PyObject *args) { int params[256] = {0}; for (int i = 0; i < PyTuple_GET_SIZE(args); i++) { params[i] = PyLong_AsLong(PyTuple_GET_ITEM(args, i)); } select_graphic_rendition(self, params, PyTuple_GET_SIZE(args), false, NULL); Py_RETURN_NONE; } static PyObject* set_mode(Screen *self, PyObject *args) { int private = false; unsigned int mode; if (!PyArg_ParseTuple(args, "I|p", &mode, &private)) return NULL; if (private) mode <<= 5; screen_set_mode(self, mode); Py_RETURN_NONE; } static PyObject* reset_dirty(Screen *self, PyObject *a UNUSED) { screen_reset_dirty(self); Py_RETURN_NONE; } static PyObject* set_window_char(Screen *self, PyObject *a) { const char *text = ""; if (!PyArg_ParseTuple(a, "|s", &text)) return NULL; self->display_window_char = text[0]; self->is_dirty = true; Py_RETURN_NONE; } static PyObject* is_using_alternate_linebuf(Screen *self, PyObject *a UNUSED) { if (self->linebuf == self->alt_linebuf) Py_RETURN_TRUE; Py_RETURN_FALSE; } WRAP1E(cursor_back, 1, -1) WRAP1B(erase_in_line, 0) WRAP1B(erase_in_display, 0) static PyObject* scroll_until_cursor_prompt(Screen *self, PyObject *args) { int b=false; if(!PyArg_ParseTuple(args, "|p", &b)) return NULL; screen_scroll_until_cursor_prompt(self, b); Py_RETURN_NONE; } WRAP0(clear_scrollback) #define MODE_GETSET(name, uname) \ static PyObject* name##_get(Screen *self, void UNUSED *closure) { PyObject *ans = self->modes.m##uname ? Py_True : Py_False; Py_INCREF(ans); return ans; } \ static int name##_set(Screen *self, PyObject *val, void UNUSED *closure) { if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } set_mode_from_const(self, uname, PyObject_IsTrue(val) ? true : false); return 0; } MODE_GETSET(in_bracketed_paste_mode, BRACKETED_PASTE) MODE_GETSET(focus_tracking_enabled, FOCUS_TRACKING) MODE_GETSET(color_preference_notification, COLOR_PREFERENCE_NOTIFICATION) MODE_GETSET(in_band_resize_notification, INBAND_RESIZE_NOTIFICATION) MODE_GETSET(auto_repeat_enabled, DECARM) MODE_GETSET(cursor_visible, DECTCEM) MODE_GETSET(cursor_key_mode, DECCKM) static PyObject* disable_ligatures_get(Screen *self, void UNUSED *closure) { const char *ans = NULL; switch(self->disable_ligatures) { case DISABLE_LIGATURES_NEVER: ans = "never"; break; case DISABLE_LIGATURES_CURSOR: ans = "cursor"; break; case DISABLE_LIGATURES_ALWAYS: ans = "always"; break; } return PyUnicode_FromString(ans); } static int disable_ligatures_set(Screen *self, PyObject *val, void UNUSED *closure) { if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "unicode string expected"); return -1; } if (PyUnicode_READY(val) != 0) return -1; const char *q = PyUnicode_AsUTF8(val); DisableLigature dl = DISABLE_LIGATURES_NEVER; if (strcmp(q, "always") == 0) dl = DISABLE_LIGATURES_ALWAYS; else if (strcmp(q, "cursor") == 0) dl = DISABLE_LIGATURES_CURSOR; if (dl != self->disable_ligatures) { self->disable_ligatures = dl; screen_dirty_sprite_positions(self); } return 0; } static PyObject* render_unfocused_cursor_get(Screen *self, void UNUSED *closure) { if (self->cursor_render_info.render_even_when_unfocused) Py_RETURN_TRUE; Py_RETURN_FALSE; } static int render_unfocused_cursor_set(Screen *self, PyObject *val, void UNUSED *closure) { if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } self->cursor_render_info.render_even_when_unfocused = PyObject_IsTrue(val); return 0; } static PyObject* cursor_up(Screen *self, PyObject *args) { unsigned int count = 1; int do_carriage_return = false, move_direction = -1; if (!PyArg_ParseTuple(args, "|Ipi", &count, &do_carriage_return, &move_direction)) return NULL; screen_cursor_up(self, count, do_carriage_return, move_direction); Py_RETURN_NONE; } static PyObject* update_selection(Screen *self, PyObject *args) { unsigned int x, y; int in_left_half_of_cell = 0, ended = 1, nearest = 0; if (!PyArg_ParseTuple(args, "II|ppp", &x, &y, &in_left_half_of_cell, &ended, &nearest)) return NULL; screen_update_selection(self, x, y, in_left_half_of_cell, (SelectionUpdate){.ended = ended, .set_as_nearest_extend=nearest}); Py_RETURN_NONE; } static PyObject* clear_selection_(Screen *s, PyObject *args UNUSED) { clear_selection(&s->selections); Py_RETURN_NONE; } static PyObject* resize(Screen *self, PyObject *args) { unsigned int a=1, b=1; if(!PyArg_ParseTuple(args, "|II", &a, &b)) return NULL; screen_resize(self, a, b); if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } WRAP0x(index) WRAP0(reverse_index) WRAP0(reset) WRAP0(set_tab_stop) WRAP1(clear_tab_stop, 0) WRAP0(backspace) WRAP0(tab) WRAP0(linefeed) WRAP0(carriage_return) WRAP2(set_margins, 1, 1) WRAP2(detect_url, 0, 0) WRAP0(rescale_images) static PyObject* current_key_encoding_flags(Screen *self, PyObject *args UNUSED) { unsigned long ans = screen_current_key_encoding_flags(self); return PyLong_FromUnsignedLong(ans); } static PyObject* ignore_bells_for(Screen *self, PyObject *args) { double duration = 1; if (!PyArg_ParseTuple(args, "|d", &duration)) return NULL; self->ignore_bells.start = monotonic(); self->ignore_bells.duration = s_double_to_monotonic_t(duration); Py_RETURN_NONE; } static PyObject* start_selection(Screen *self, PyObject *args) { unsigned int x, y; int rectangle_select = 0, extend_mode = EXTEND_CELL, in_left_half_of_cell = 1; if (!PyArg_ParseTuple(args, "II|pip", &x, &y, &rectangle_select, &extend_mode, &in_left_half_of_cell)) return NULL; screen_start_selection(self, x, y, in_left_half_of_cell, rectangle_select, extend_mode); Py_RETURN_NONE; } static PyObject* is_rectangle_select(Screen *self, PyObject *a UNUSED) { if (self->selections.count && self->selections.items[0].rectangle_select) Py_RETURN_TRUE; Py_RETURN_FALSE; } static PyObject* copy_colors_from(Screen *self, Screen *other) { copy_color_profile(self->color_profile, other->color_profile); Py_RETURN_NONE; } static PyObject* text_for_selections(Screen *self, Selections *selections, bool ansi, bool strip_trailing_whitespace) { PyObject *lines = NULL; for (size_t i = 0; i < selections->count; i++) { PyObject *temp = ansi ? ansi_for_range(self, selections->items +i, true, strip_trailing_whitespace) : text_for_range(self, selections->items + i, true, strip_trailing_whitespace); if (temp) { if (lines) { lines = extend_tuple(lines, temp); Py_DECREF(temp); } else lines = temp; } else break; } if (PyErr_Occurred()) { Py_CLEAR(lines); return NULL; } if (!lines) lines = PyTuple_New(0); return lines; } static PyObject* text_for_selection(Screen *self, PyObject *args) { int ansi = 0, strip_trailing_whitespace = 0; if (!PyArg_ParseTuple(args, "|pp", &ansi, &strip_trailing_whitespace)) return NULL; return text_for_selections(self, &self->selections, ansi, strip_trailing_whitespace); } static PyObject* text_for_marked_url(Screen *self, PyObject *args) { int ansi = 0, strip_trailing_whitespace = 0; if (!PyArg_ParseTuple(args, "|pp", &ansi, &strip_trailing_whitespace)) return NULL; return text_for_selections(self, &self->url_ranges, ansi, strip_trailing_whitespace); } static bool cell_is_blank(const CPUCell *c) { return !cell_has_text(c) || cell_is_char(c, ' '); } bool screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end) { if (y >= self->lines) { return false; } Line *line = visual_line_(self, y); index_type xlimit = line->xnum, xstart = 0; while (xlimit > 0 && cell_is_blank(line->cpu_cells + xlimit - 1)) xlimit--; while (xstart < xlimit && cell_is_blank(line->cpu_cells + xstart)) xstart++; *start = xstart; *end = xlimit > 0 ? xlimit - 1 : 0; return true; } static bool is_opt_word_char(char_type ch, bool forward) { if (forward && OPT(select_by_word_characters_forward)) { for (const char_type *p = OPT(select_by_word_characters_forward); *p; p++) { if (ch == *p) return true; } if (*OPT(select_by_word_characters_forward)) { return false; } } if (OPT(select_by_word_characters)) { for (const char_type *p = OPT(select_by_word_characters); *p; p++) { if (ch == *p) return true; } } return false; } static bool is_char_ok_for_word_extension(Line* line, index_type x, bool forward) { char_type ch = cell_first_char(line->cpu_cells + x, line->text_cache); if (char_props_for(ch).is_word_char || is_opt_word_char(ch, forward)) return true; // pass : from :// so that common URLs are matched return ch == ':' && x + 2 < line->xnum && cell_is_char(line->cpu_cells + x + 1, '/') && cell_is_char(line->cpu_cells + x + 2, '/'); } bool screen_selection_range_for_word(Screen *self, const index_type x, const index_type y, index_type *y1, index_type *y2, index_type *s, index_type *e, bool initial_selection) { if (y >= self->lines || x >= self->columns) return false; index_type start, end; Line *line = visual_line_(self, y); *y1 = y; *y2 = y; #define is_ok(x, forward) is_char_ok_for_word_extension(line, x, forward) if (!is_ok(x, false)) { if (initial_selection) return false; *s = x; *e = x; return true; } start = x; end = x; while(true) { while(start > 0 && is_ok(start - 1, false)) start--; if (start > 0 || !visual_line_is_continued(self, y) || *y1 == 0) break; line = visual_line_(self, *y1 - 1); if (!is_ok(self->columns - 1, false)) break; (*y1)--; start = self->columns - 1; } line = visual_line_(self, *y2); while(true) { while(end < self->columns - 1 && is_ok(end + 1, true)) end++; if (end < self->columns - 1 || *y2 >= self->lines - 1) break; line = visual_line_(self, *y2 + 1); if (!visual_line_is_continued(self, *y2 + 1) || !is_ok(0, true)) break; (*y2)++; end = 0; } *s = start; *e = end; return true; #undef is_ok } bool screen_history_scroll(Screen *self, int amt, bool upwards) { switch(amt) { case SCROLL_LINE: amt = 1; break; case SCROLL_PAGE: amt = self->lines - 1; break; case SCROLL_FULL: amt = self->historybuf->count; break; default: amt = MAX(0, amt); break; } if (!upwards) { amt = MIN((unsigned int)amt, self->scrolled_by); amt *= -1; } if (amt == 0) return false; unsigned int new_scroll = MIN(self->scrolled_by + amt, self->historybuf->count); if (new_scroll != self->scrolled_by) { self->scrolled_by = new_scroll; dirty_scroll(self); return true; } return false; } static PyObject* scroll(Screen *self, PyObject *args) { int amt, upwards; if (!PyArg_ParseTuple(args, "ip", &amt, &upwards)) return NULL; if (screen_history_scroll(self, amt, upwards)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static PyObject* scroll_to_prompt(Screen *self, PyObject *args) { int num_of_prompts = -1; if (!PyArg_ParseTuple(args, "|i", &num_of_prompts)) return NULL; if (screen_history_scroll_to_prompt(self, num_of_prompts)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static PyObject* set_last_visited_prompt(Screen *self, PyObject *args) { index_type visual_y = 0; if (!PyArg_ParseTuple(args, "|I", &visual_y)) return NULL; if (screen_set_last_visited_prompt(self, visual_y)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } bool screen_is_selection_dirty(Screen *self) { IterationData q; if (self->paused_rendering.expires_at) return false; if (self->scrolled_by != self->last_rendered.scrolled_by) return true; if (self->selections.last_rendered_count != self->selections.count || self->url_ranges.last_rendered_count != self->url_ranges.count) return true; for (size_t i = 0; i < self->selections.count; i++) { iteration_data(self->selections.items + i, &q, self->columns, 0, self->scrolled_by); if (memcmp(&q, &self->selections.items[i].last_rendered, sizeof(IterationData)) != 0) return true; } for (size_t i = 0; i < self->url_ranges.count; i++) { iteration_data(self->url_ranges.items + i, &q, self->columns, 0, self->scrolled_by); if (memcmp(&q, &self->url_ranges.items[i].last_rendered, sizeof(IterationData)) != 0) return true; } return false; } void screen_start_selection(Screen *self, index_type x, index_type y, bool in_left_half_of_cell, bool rectangle_select, SelectionExtendMode extend_mode) { screen_pause_rendering(self, false, 0); #define A(attr, val) self->selections.items->attr = val; ensure_space_for(&self->selections, items, Selection, self->selections.count + 1, capacity, 1, false); memset(self->selections.items, 0, sizeof(Selection)); self->selections.count = 1; self->selections.in_progress = true; self->selections.extend_mode = extend_mode; self->selections.items[0].last_rendered.y = INT_MAX; A(start.x, x); A(end.x, x); A(start.y, y); A(end.y, y); A(start_scrolled_by, self->scrolled_by); A(end_scrolled_by, self->scrolled_by); A(rectangle_select, rectangle_select); A(start.in_left_half_of_cell, in_left_half_of_cell); A(end.in_left_half_of_cell, in_left_half_of_cell); A(input_start.x, x); A(input_start.y, y); A(input_start.in_left_half_of_cell, in_left_half_of_cell); A(input_current.x, x); A(input_current.y, y); A(input_current.in_left_half_of_cell, in_left_half_of_cell); #undef A } static void add_url_range(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y, bool is_hyperlink) { #define A(attr, val) r->attr = val; ensure_space_for(&self->url_ranges, items, Selection, self->url_ranges.count + 8, capacity, 8, false); Selection *r = self->url_ranges.items + self->url_ranges.count++; memset(r, 0, sizeof(Selection)); r->last_rendered.y = INT_MAX; r->is_hyperlink = is_hyperlink; A(start.x, start_x); A(end.x, end_x); A(start.y, start_y); A(end.y, end_y); A(start_scrolled_by, self->scrolled_by); A(end_scrolled_by, self->scrolled_by); A(start.in_left_half_of_cell, true); #undef A } void screen_mark_url(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y) { self->url_ranges.count = 0; if (start_x || start_y || end_x || end_y) add_url_range(self, start_x, start_y, end_x, end_y, false); } static bool mark_hyperlinks_in_line(Screen *self, Line *line, hyperlink_id_type id, index_type y, bool *found_nonzero_multiline) { index_type start = 0; bool found = false; bool in_range = false; *found_nonzero_multiline = false; for (index_type x = 0; x < line->xnum; x++) { bool has_hyperlink = line->cpu_cells[x].hyperlink_id == id; bool is_nonzero_multiline = line->cpu_cells[x].is_multicell && line->cpu_cells[x].y > 0; if (has_hyperlink && is_nonzero_multiline) { has_hyperlink = false; *found_nonzero_multiline = true; } if (in_range) { if (!has_hyperlink) { add_url_range(self, start, y, x - 1, y, true); in_range = false; start = 0; } } else { if (has_hyperlink) { start = x; in_range = true; found = true; } } } if (in_range) add_url_range(self, start, y, self->columns - 1, y, true); return found; } static void sort_ranges(const Screen *self, Selections *s) { IterationData a; for (size_t i = 0; i < s->count; i++) { iteration_data(s->items + i, &a, self->columns, 0, 0); s->items[i].sort_x = a.first.x; s->items[i].sort_y = a.y; } #define range_lt(a, b) ((a)->sort_y < (b)->sort_y || ((a)->sort_y == (b)->sort_y && (a)->sort_x < (b)->sort_x)) QSORT(Selection, s->items, s->count, range_lt); #undef range_lt } hyperlink_id_type screen_mark_hyperlink(Screen *self, index_type x, index_type y) { self->url_ranges.count = 0; Line *line = screen_visual_line(self, y); hyperlink_id_type id = line->cpu_cells[x].hyperlink_id; if (!id) return 0; index_type ypos = y, last_marked_line = y; bool found_nonzero_multiline; do { if (mark_hyperlinks_in_line(self, line, id, ypos, &found_nonzero_multiline) || found_nonzero_multiline) last_marked_line = ypos; if (ypos == 0) break; ypos--; line = screen_visual_line(self, ypos); } while (last_marked_line - ypos < 5); ypos = y + 1; last_marked_line = y; while (ypos < self->lines - 1 && ypos - last_marked_line < 5) { line = screen_visual_line(self, ypos); if (mark_hyperlinks_in_line(self, line, id, ypos, &found_nonzero_multiline)) last_marked_line = ypos; ypos++; } if (self->url_ranges.count > 1) sort_ranges(self, &self->url_ranges); return id; } static index_type continue_line_upwards(Screen *self, index_type top_line, SelectionBoundary *start, SelectionBoundary *end) { while (top_line > 0 && visual_line_is_continued(self, top_line)) { if (!screen_selection_range_for_line(self, top_line - 1, &start->x, &end->x)) break; top_line--; } return top_line; } static index_type continue_line_downwards(Screen *self, index_type bottom_line, SelectionBoundary *start, SelectionBoundary *end) { while (bottom_line + 1 < self->lines && visual_line_is_continued(self, bottom_line + 1)) { if (!screen_selection_range_for_line(self, bottom_line + 1, &start->x, &end->x)) break; bottom_line++; } return bottom_line; } static int clamp_selection_input_to_multicell(Screen *self, const Selection *s, index_type x, index_type y, bool in_left_half_of_cell) { int delta = 0; int abs_y = y - self->scrolled_by, abs_start_y = s->start.y - s->start_scrolled_by; if (abs_y == abs_start_y) return delta; Line *line = checked_range_line(self, abs_start_y); CPUCell *start, *current; if (!line || s->start.x >= line->xnum || !(start = &line->cpu_cells[s->start.x])->is_multicell || start->scale < 2) return delta; int abs_start_top = abs_start_y - start->y; line = checked_range_line(self, abs_y); if (x > s->start.x && in_left_half_of_cell) x--; else if (x < s->start.x && !in_left_half_of_cell) x++; if (!line || x >= line->xnum) return delta; current = line->cpu_cells + x; if (!current->is_multicell) return delta; int abs_current_top = abs_y - current->y; if (current->scale == start->scale && current->subscale_n == start->subscale_n && current->subscale_d == start->subscale_d && abs_current_top == abs_start_top) delta = abs_y - abs_start_y; return delta; } static void do_update_selection(Screen *self, Selection *s, index_type x, index_type y, bool in_left_half_of_cell, SelectionUpdate upd) { s->input_current.x = x; s->input_current.y = y; s->input_current.in_left_half_of_cell = in_left_half_of_cell; SelectionBoundary start, end, *a = &s->start, *b = &s->end, abs_start, abs_end, abs_current_input; #define set_abs(which, initializer, scrolled_by) which = initializer; which.y = scrolled_by + self->lines - 1 - which.y; set_abs(abs_start, s->start, s->start_scrolled_by); set_abs(abs_end, s->end, s->end_scrolled_by); set_abs(abs_current_input, s->input_current, self->scrolled_by); bool return_word_sel_to_start_line = false; if (upd.set_as_nearest_extend || self->selections.extension_in_progress) { self->selections.extension_in_progress = true; bool start_is_nearer = false; if (self->selections.extend_mode == EXTEND_LINE || self->selections.extend_mode == EXTEND_LINE_FROM_POINT || self->selections.extend_mode == EXTEND_WORD_AND_LINE_FROM_POINT) { if (abs_start.y == abs_end.y) { if (abs_current_input.y == abs_start.y) start_is_nearer = selection_boundary_less_than(&abs_start, &abs_end) ? (abs_current_input.x <= abs_start.x) : (abs_current_input.x <= abs_end.x); else start_is_nearer = selection_boundary_less_than(&abs_start, &abs_end) ? (abs_current_input.y > abs_start.y) : (abs_current_input.y < abs_end.y); } else { start_is_nearer = num_lines_between_selection_boundaries(&abs_start, &abs_current_input) < num_lines_between_selection_boundaries(&abs_end, &abs_current_input); } } else start_is_nearer = num_cells_between_selection_boundaries(self, &abs_start, &abs_current_input) < num_cells_between_selection_boundaries(self, &abs_end, &abs_current_input); if (start_is_nearer) s->adjusting_start = true; } else if (!upd.start_extended_selection && self->selections.extend_mode != EXTEND_CELL) { SelectionBoundary abs_initial_start, abs_initial_end; set_abs(abs_initial_start, s->initial_extent.start, s->initial_extent.scrolled_by); set_abs(abs_initial_end, s->initial_extent.end, s->initial_extent.scrolled_by); if (self->selections.extend_mode == EXTEND_WORD) { if (abs_current_input.y == abs_initial_start.y && abs_start.y != abs_end.y) { if (abs_start.y != abs_initial_start.y) s->adjusting_start = true; else if (abs_end.y != abs_initial_start.y) s->adjusting_start = false; else s->adjusting_start = selection_boundary_less_than(&abs_current_input, &abs_initial_end); return_word_sel_to_start_line = true; } else { if (s->adjusting_start) s->adjusting_start = selection_boundary_less_than(&abs_current_input, &abs_initial_end); else s->adjusting_start = selection_boundary_less_than(&abs_current_input, &abs_initial_start); } } else { const unsigned int initial_line = abs_initial_start.y; if (initial_line == abs_current_input.y) { s->adjusting_start = false; s->start = s->initial_extent.start; s->start_scrolled_by = s->initial_extent.scrolled_by; s->end = s->initial_extent.end; s->end_scrolled_by = s->initial_extent.scrolled_by; } else { s->adjusting_start = abs_current_input.y > initial_line; } } } #undef set_abs bool adjusted_boundary_is_before; if (s->adjusting_start) adjusted_boundary_is_before = selection_boundary_less_than(&abs_start, &abs_end); else { adjusted_boundary_is_before = selection_boundary_less_than(&abs_end, &abs_start); } switch(self->selections.extend_mode) { case EXTEND_WORD: { if (!s->adjusting_start) { a = &s->end; b = &s->start; } const bool word_found_at_cursor = screen_selection_range_for_word(self, s->input_current.x, s->input_current.y, &start.y, &end.y, &start.x, &end.x, true); bool adjust_both_ends = is_selection_empty(s); if (return_word_sel_to_start_line) { index_type ox = a->x; if (s->adjusting_start) { *a = s->initial_extent.start; if (ox < a->x) a->x = ox; } else { *a = s->initial_extent.end; if (ox > a->x) a->x = ox; } } else if (word_found_at_cursor) { if (adjusted_boundary_is_before) { *a = start; a->in_left_half_of_cell = true; if (adjust_both_ends) { *b = end; b->in_left_half_of_cell = false; } } else { *a = end; a->in_left_half_of_cell = false; if (adjust_both_ends) { *b = start; b->in_left_half_of_cell = true; } } if (s->adjusting_start || adjust_both_ends) s->start_scrolled_by = self->scrolled_by; if (!s->adjusting_start || adjust_both_ends) s->end_scrolled_by = self->scrolled_by; } else { *a = s->input_current; if (s->adjusting_start) s->start_scrolled_by = self->scrolled_by; else s->end_scrolled_by = self->scrolled_by; } break; } case EXTEND_LINE_FROM_POINT: case EXTEND_WORD_AND_LINE_FROM_POINT: case EXTEND_LINE: { bool adjust_both_ends = is_selection_empty(s); if (s->adjusting_start || adjust_both_ends) s->start_scrolled_by = self->scrolled_by; if (!s->adjusting_start || adjust_both_ends) s->end_scrolled_by = self->scrolled_by; index_type top_line, bottom_line; SelectionBoundary up_start, up_end, down_start, down_end; if (adjust_both_ends) { // empty initial selection top_line = s->input_current.y; bottom_line = s->input_current.y; if (screen_selection_range_for_line(self, top_line, &up_start.x, &up_end.x)) { #define S \ s->start.y = top_line; s->end.y = bottom_line; \ s->start.in_left_half_of_cell = true; s->end.in_left_half_of_cell = false; \ s->start.x = up_start.x; s->end.x = bottom_line == top_line ? up_end.x : down_end.x; down_start = up_start; down_end = up_end; bottom_line = continue_line_downwards(self, bottom_line, &down_start, &down_end); if (self->selections.extend_mode == EXTEND_LINE_FROM_POINT) { if (x <= up_end.x) { S; s->start.x = MAX(x, up_start.x); } } else if (self->selections.extend_mode == EXTEND_WORD_AND_LINE_FROM_POINT) { if (x <= up_end.x) { S; s->start.x = MAX(x, up_start.x); } const bool word_found_at_cursor = screen_selection_range_for_word(self, s->input_current.x, s->input_current.y, &start.y, &end.y, &start.x, &end.x, true); if (word_found_at_cursor) { *a = start; a->in_left_half_of_cell = true; } } else { top_line = continue_line_upwards(self, top_line, &up_start, &up_end); S; } } #undef S } else { // extending an existing selection top_line = s->input_current.y; bottom_line = s->input_current.y; if (screen_selection_range_for_line(self, top_line, &up_start.x, &up_end.x)) { down_start = up_start; down_end = up_end; top_line = continue_line_upwards(self, top_line, &up_start, &up_end); bottom_line = continue_line_downwards(self, bottom_line, &down_start, &down_end); if (!s->adjusting_start) { a = &s->end; b = &s->start; } if (adjusted_boundary_is_before) { a->in_left_half_of_cell = true; a->x = up_start.x; a->y = top_line; } else { a->in_left_half_of_cell = false; a->x = down_end.x; a->y = bottom_line; } // allow selecting whitespace at the start of the top line if (a->y == top_line && s->input_current.y == top_line && s->input_current.x < a->x && adjusted_boundary_is_before) a->x = s->input_current.x; } } } break; case EXTEND_CELL: if (s->adjusting_start) b = &s->start; b->x = x; b->y = y; b->in_left_half_of_cell = in_left_half_of_cell; if (s->adjusting_start) s->start_scrolled_by = self->scrolled_by; else s->end_scrolled_by = self->scrolled_by; break; } if (!self->selections.in_progress) { s->adjusting_start = false; self->selections.extension_in_progress = false; call_boss(set_primary_selection, NULL); } else { if (upd.start_extended_selection && self->selections.extend_mode != EXTEND_CELL) { s->initial_extent.start = s->start; s->initial_extent.end = s->end; s->initial_extent.scrolled_by = s->start_scrolled_by; } } } void screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_half_of_cell, SelectionUpdate upd) { if (!self->selections.count) return; self->selections.in_progress = !upd.ended; Selection *s = self->selections.items; int delta = clamp_selection_input_to_multicell(self, s, x, y, in_left_half_of_cell); index_type orig = self->scrolled_by; if (delta) { int new_y = y - delta; if (new_y < 0) { y = 0; self->scrolled_by += - new_y; } else y = new_y; } do_update_selection(self, s, x, y, in_left_half_of_cell, upd); self->scrolled_by = orig; } static PyObject* mark_as_dirty(Screen *self, PyObject *a UNUSED) { self->is_dirty = true; Py_RETURN_NONE; } static PyObject* reload_all_gpu_data(Screen *self, PyObject *a UNUSED) { self->reload_all_gpu_data = true; Py_RETURN_NONE; } static PyObject* current_char_width(Screen *self, PyObject *a UNUSED) { #define current_char_width_doc "The width of the character under the cursor" unsigned long ans = 1; if (self->cursor->x < self->columns && self->cursor->y < self->lines) { const CPUCell *c = linebuf_cpu_cells_for_line(self->linebuf, self->cursor->y) + self->cursor->x; if (c->is_multicell) { if (c->x || c->y) ans = 0; else ans = c->width; } } return PyLong_FromUnsignedLong(ans); } static PyObject* is_main_linebuf(Screen *self, PyObject *a UNUSED) { PyObject *ans = (self->linebuf == self->main_linebuf) ? Py_True : Py_False; Py_INCREF(ans); return ans; } static PyObject* toggle_alt_screen(Screen *self, PyObject *a UNUSED) { screen_toggle_screen_buffer(self, true, true); Py_RETURN_NONE; } static PyObject* pause_rendering(Screen *self, PyObject *args) { int msec = 100; int pause = 1; if (!PyArg_ParseTuple(args, "|pi", &msec)) return NULL; if (screen_pause_rendering(self, pause, msec)) Py_RETURN_TRUE; Py_RETURN_FALSE; } static PyObject* send_escape_code_to_child(Screen *self, PyObject *args) { int code; PyObject *O; if (!PyArg_ParseTuple(args, "iO", &code, &O)) return NULL; bool written = false; if (PyBytes_Check(O)) written = write_escape_code_to_child(self, code, PyBytes_AS_STRING(O)); else if (PyUnicode_Check(O)) { const char *t = PyUnicode_AsUTF8(O); if (t) written = write_escape_code_to_child(self, code, t); else return NULL; } else if (PyTuple_Check(O)) written = write_escape_code_to_child_python(self, code, O); else PyErr_SetString(PyExc_TypeError, "escape code must be str, bytes or tuple"); if (PyErr_Occurred()) return NULL; if (written) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } } static void screen_mark_all(Screen *self) { for (index_type y = 0; y < self->main_linebuf->ynum; y++) { linebuf_init_line(self->main_linebuf, y); mark_text_in_line(self->marker, self->main_linebuf->line, &self->as_ansi_buf); } for (index_type y = 0; y < self->alt_linebuf->ynum; y++) { linebuf_init_line(self->alt_linebuf, y); mark_text_in_line(self->marker, self->alt_linebuf->line, &self->as_ansi_buf); } for (index_type y = 0; y < self->historybuf->count; y++) { historybuf_init_line(self->historybuf, y, self->historybuf->line); mark_text_in_line(self->marker, self->historybuf->line, &self->as_ansi_buf); } self->is_dirty = true; } static PyObject* set_marker(Screen *self, PyObject *args) { PyObject *marker = NULL; if (!PyArg_ParseTuple(args, "|O", &marker)) return NULL; if (!marker) { if (self->marker) { Py_CLEAR(self->marker); screen_mark_all(self); } Py_RETURN_NONE; } if (!PyCallable_Check(marker)) { PyErr_SetString(PyExc_TypeError, "marker must be a callable"); return NULL; } self->marker = marker; Py_INCREF(marker); screen_mark_all(self); Py_RETURN_NONE; } static PyObject* scroll_to_next_mark(Screen *self, PyObject *args) { int backwards = 1; unsigned int mark = 0; if (!PyArg_ParseTuple(args, "|Ip", &mark, &backwards)) return NULL; if (!screen_has_marker(self) || self->linebuf == self->alt_linebuf) Py_RETURN_FALSE; if (backwards) { for (unsigned int y = self->scrolled_by; y < self->historybuf->count; y++) { historybuf_init_line(self->historybuf, y, self->historybuf->line); if (line_has_mark(self->historybuf->line, mark)) { screen_history_scroll(self, y - self->scrolled_by + 1, true); Py_RETURN_TRUE; } } } else { Line *line; for (unsigned int y = self->scrolled_by; y > 0; y--) { if (y > self->lines) { historybuf_init_line(self->historybuf, y - self->lines, self->historybuf->line); line = self->historybuf->line; } else { linebuf_init_line(self->linebuf, self->lines - y); line = self->linebuf->line; } if (line_has_mark(line, mark)) { screen_history_scroll(self, self->scrolled_by - y + 1, false); Py_RETURN_TRUE; } } } Py_RETURN_FALSE; } static PyObject* marked_cells(Screen *self, PyObject *o UNUSED) { RAII_PyObject(ans, PyList_New(0)); if (!ans) return ans; for (index_type y = 0; y < self->lines; y++) { linebuf_init_line(self->linebuf, y); for (index_type x = 0; x < self->columns; x++) { GPUCell *gpu_cell = self->linebuf->line->gpu_cells + x; const unsigned int mark = gpu_cell->attrs.mark; if (mark) { RAII_PyObject(t, Py_BuildValue("III", x, y, mark)); if (!t) { return NULL; } if (PyList_Append(ans, t) != 0) return NULL; } } } return Py_NewRef(ans); } static PyObject* paste_(Screen *self, PyObject *bytes, bool allow_bracketed_paste) { const char *data; Py_ssize_t sz; if (PyBytes_Check(bytes)) { data = PyBytes_AS_STRING(bytes); sz = PyBytes_GET_SIZE(bytes); } else if (PyMemoryView_Check(bytes)) { RAII_PyObject(mv, PyMemoryView_GetContiguous(bytes, PyBUF_READ, PyBUF_C_CONTIGUOUS)); if (mv == NULL) return NULL; Py_buffer *buf = PyMemoryView_GET_BUFFER(mv); data = buf->buf; sz = buf->len; } else { PyErr_SetString(PyExc_TypeError, "Must paste() bytes"); return NULL; } if (allow_bracketed_paste && self->modes.mBRACKETED_PASTE) write_escape_code_to_child(self, ESC_CSI, BRACKETED_PASTE_START); write_to_child(self, data, sz); if (allow_bracketed_paste && self->modes.mBRACKETED_PASTE) write_escape_code_to_child(self, ESC_CSI, BRACKETED_PASTE_END); Py_RETURN_NONE; } static PyObject* paste(Screen *self, PyObject *bytes) { return paste_(self, bytes, true); } static PyObject* paste_bytes(Screen *self, PyObject *bytes) { return paste_(self, bytes, false); } static PyObject* focus_changed(Screen *self, PyObject *has_focus_) { bool previous = self->has_focus; bool has_focus = PyObject_IsTrue(has_focus_) ? true : false; if (has_focus != previous) { self->has_focus = has_focus; if (has_focus) self->has_activity_since_last_focus = false; else if (screen_is_overlay_active(self)) deactivate_overlay_line(self); if (self->modes.mFOCUS_TRACKING) write_escape_code_to_child(self, ESC_CSI, has_focus ? "I" : "O"); Py_RETURN_TRUE; } Py_RETURN_FALSE; } static PyObject* has_focus(Screen *self, PyObject *args UNUSED) { if (self->has_focus) Py_RETURN_TRUE; Py_RETURN_FALSE; } static PyObject* has_activity_since_last_focus(Screen *self, PyObject *args UNUSED) { if (self->has_activity_since_last_focus) Py_RETURN_TRUE; Py_RETURN_FALSE; } WRAP2(cursor_position, 1, 1) #define COUNT_WRAP(name) WRAP1(name, 1) COUNT_WRAP(insert_lines) COUNT_WRAP(delete_lines) COUNT_WRAP(delete_characters) COUNT_WRAP(erase_characters) COUNT_WRAP(cursor_up1) COUNT_WRAP(cursor_down) COUNT_WRAP(cursor_down1) COUNT_WRAP(cursor_forward) static PyObject* py_insert_characters(Screen *self, PyObject *count_) { if (!PyLong_Check(count_)) { PyErr_SetString(PyExc_TypeError, "count must be an integer"); return NULL; } unsigned long count = PyLong_AsUnsignedLong(count_); screen_insert_characters(self, count); Py_RETURN_NONE; } static PyObject* screen_is_emoji_presentation_base(PyObject UNUSED *self, PyObject *code_) { unsigned long code = PyLong_AsUnsignedLong(code_); if (is_emoji_presentation_base(code)) Py_RETURN_TRUE; Py_RETURN_FALSE; } static PyObject* hyperlink_at(Screen *self, PyObject *args) { unsigned int x, y; if (!PyArg_ParseTuple(args, "II", &x, &y)) return NULL; screen_mark_hyperlink(self, x, y); if (!self->url_ranges.count) Py_RETURN_NONE; hyperlink_id_type hid = hyperlink_id_for_range(self, self->url_ranges.items); if (!hid) Py_RETURN_NONE; const char *url = get_hyperlink_for_id(self->hyperlink_pool, hid, true); return Py_BuildValue("s", url); } static PyObject* reverse_scroll(Screen *self, PyObject *args) { int fill_from_scrollback = 0; unsigned int amt; if (!PyArg_ParseTuple(args, "I|p", &amt, &fill_from_scrollback)) return NULL; _reverse_scroll(self, amt, fill_from_scrollback); Py_RETURN_NONE; } static PyObject* scroll_prompt_to_bottom(Screen *self, PyObject *args UNUSED) { if (self->linebuf != self->main_linebuf || !self->historybuf->count) Py_RETURN_NONE; int q = screen_cursor_at_a_shell_prompt(self); index_type limit_y = q > -1 ? (unsigned int)q : self->cursor->y; index_type y = self->lines - 1; // not before prompt or cursor line while (y > limit_y) { Line *line = checked_range_line(self, y); if (!line || line_length(line)) break; y--; } // don't scroll back beyond the history buffer range unsigned int count = MIN(self->lines - (y + 1), self->historybuf->count); if (count > 0) { _reverse_scroll(self, count, true); screen_cursor_down(self, count); } // always scroll to the bottom if (self->scrolled_by != 0) { self->scrolled_by = 0; dirty_scroll(self); } Py_RETURN_NONE; } static void dump_line_with_attrs(Screen *self, int y, PyObject *accum) { Line *line = range_line_(self, y); RAII_PyObject(u, PyUnicode_FromFormat("\x1b[31m%d: \x1b[39m", y++)); if (!u) return; RAII_PyObject(r1, PyObject_CallOneArg(accum, u)); if (!r1) return; #define call_string(s) { RAII_PyObject(ret, PyObject_CallFunction(accum, "s", s)); if (!ret) return; } switch (line->attrs.prompt_kind) { case UNKNOWN_PROMPT_KIND: break; case PROMPT_START: call_string("\x1b[32mprompt \x1b[39m"); break; case SECONDARY_PROMPT: call_string("\x1b[32msecondary_prompt \x1b[39m"); break; case OUTPUT_START: call_string("\x1b[33moutput \x1b[39m"); break; } if (range_line_is_continued(self, y)) call_string("continued "); if (line->attrs.has_dirty_text) call_string("dirty "); call_string("\n"); RAII_PyObject(t, line_as_unicode(line, false, &self->as_ansi_buf)); if (!t) return; RAII_PyObject(r2, PyObject_CallOneArg(accum, t)); if (!r2) return; call_string("\n"); #undef call_string } static PyObject* dump_lines_with_attrs(Screen *self, PyObject *args) { PyObject *accum; int which_screen = -1; if (!PyArg_ParseTuple(args, "O|i", &accum, &which_screen)) return NULL; LineBuf *orig = self->linebuf; switch(which_screen) { case 0: self->linebuf = self->main_linebuf; break; case 1: self->linebuf = self->alt_linebuf; break; } int y = (self->linebuf == self->main_linebuf) ? -self->historybuf->count : 0; while (y < (int)self->lines && !PyErr_Occurred()) dump_line_with_attrs(self, y++, accum); self->linebuf = orig; if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } static PyObject* cursor_at_prompt(Screen *self, PyObject *args UNUSED) { int y = screen_cursor_at_a_shell_prompt(self); if (y > -1) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } static PyObject* line_edge_colors(Screen *self, PyObject *a UNUSED) { color_type left, right; if (!get_line_edge_colors(self, &left, &right)) { PyErr_SetString(PyExc_IndexError, "Line number out of range"); return NULL; } return Py_BuildValue("kk", (unsigned long)left, (unsigned long)right); } static PyObject* current_selections(Screen *self, PyObject *a UNUSED) { PyObject *ans = PyBytes_FromStringAndSize(NULL, (Py_ssize_t)self->lines * self->columns); if (!ans) return NULL; screen_apply_selection(self, PyBytes_AS_STRING(ans), PyBytes_GET_SIZE(ans)); return ans; } WRAP0(update_only_line_graphics_data) WRAP0(bell) #define MND(name, args) {#name, (PyCFunction)name, args, #name}, #define MODEFUNC(name) MND(name, METH_NOARGS) MND(set_##name, METH_O) static PyObject* test_create_write_buffer(Screen *screen UNUSED, PyObject *args UNUSED) { size_t s; uint8_t *buf = vt_parser_create_write_buffer(screen->vt_parser, &s); return PyMemoryView_FromMemory((char*)buf, s, PyBUF_WRITE); } static PyObject* test_commit_write_buffer(Screen *screen, PyObject *args) { RAII_PY_BUFFER(srcbuf); RAII_PY_BUFFER(destbuf); if (!PyArg_ParseTuple(args, "y*y*", &srcbuf, &destbuf)) return NULL; size_t s = MIN(srcbuf.len, destbuf.len); memcpy(destbuf.buf, srcbuf.buf, s); vt_parser_commit_write(screen->vt_parser, s); return PyLong_FromSize_t(s); } static PyObject* test_parse_written_data(Screen *screen, PyObject *args) { ParseData pd = {.now=monotonic()}; if (!PyArg_ParseTuple(args, "|O", &pd.dump_callback)) return NULL; if (pd.dump_callback && pd.dump_callback != Py_None) parse_worker_dump(screen, &pd, true); else parse_worker(screen, &pd, true); Py_RETURN_NONE; } static PyObject* multicell_data_as_dict(CPUCell mcd) { return Py_BuildValue("{sI sI sI sI sO sI sI}", "scale", (unsigned int)mcd.scale, "width", (unsigned int)mcd.width, "subscale_n", (unsigned int)mcd.subscale_n, "subscale_d", (unsigned int)mcd.subscale_d, "natural_width", mcd.natural_width ? Py_True : Py_False, "vertical_align", mcd.valign, "horizontal_align", mcd.halign); } static PyObject* cpu_cell_as_dict(CPUCell *c, TextCache *tc, ListOfChars *lc, HYPERLINK_POOL_HANDLE h) { text_in_cell(c, tc, lc); RAII_PyObject(mcd, c->is_multicell ? multicell_data_as_dict(*c) : Py_NewRef(Py_None)); if ((c->is_multicell && (c->x + c->y)) || (lc->count == 1 && lc->chars[0] == 0)) lc->count = 0; RAII_PyObject(text, PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, lc->chars, lc->count)); const char *url = c->hyperlink_id ? get_hyperlink_for_id(h, c->hyperlink_id, false) : NULL; RAII_PyObject(hyperlink, url ? PyUnicode_FromString(url) : Py_NewRef(Py_None)); return Py_BuildValue("{sO sO sI sI sO sO}", "text", text, "hyperlink", hyperlink, "x", (unsigned int)c->x, "y", (unsigned int)c->y, "mcd", mcd, "next_char_was_wrapped", c->next_char_was_wrapped ? Py_True : Py_False ); } static PyObject* cpu_cells(Screen *self, PyObject *args) { int y, x = -1; if (!PyArg_ParseTuple(args, "i|i", &y, &x)) return NULL; if (y >= (int)self->lines) { PyErr_SetString(PyExc_IndexError, "y out of bounds"); return NULL; } CPUCell *cells; if (y >= 0) cells = linebuf_cpu_cells_for_line(self->linebuf, y); else { Line *l = self->linebuf == self->main_linebuf ? checked_range_line(self, y) : NULL; if (!l) { PyErr_SetString(PyExc_IndexError, "y out of bounds"); return NULL; } cells = l->cpu_cells; } if (x > -1) { if (x >= (int)self->columns) { PyErr_SetString(PyExc_IndexError, "x out of bounds"); return NULL; } return cpu_cell_as_dict(cells + x, self->text_cache, self->lc, self->hyperlink_pool); } index_type start_x = 0, x_limit = self->columns; RAII_PyObject(ans, PyTuple_New(x_limit - start_x)); if (ans) { for (index_type x = start_x; x < x_limit; x++) { PyObject *d = cpu_cell_as_dict(cells + x, self->text_cache, self->lc, self->hyperlink_pool); if (!d) return NULL; PyTuple_SET_ITEM(ans, x, d); } } return Py_NewRef(ans); } static PyObject* test_ch_and_idx(PyObject *self UNUSED, PyObject *val) { CPUCell c = {0}; if (PyLong_Check(val)) { unsigned long x = PyLong_AsUnsignedLong(val); c.ch_and_idx = x; } else if (PyTuple_Check(val)) { c.ch_is_idx = PyLong_AsUnsignedLong(PyTuple_GET_ITEM(val, 0)); c.ch_or_idx = PyLong_AsUnsignedLong(PyTuple_GET_ITEM(val, 1)); } unsigned long is_idx = c.ch_is_idx, idx = c.ch_or_idx, ca = c.ch_and_idx; return Py_BuildValue("kkk", is_idx, idx, ca); } static PyMethodDef methods[] = { METHODB(test_create_write_buffer, METH_NOARGS), METHODB(test_commit_write_buffer, METH_VARARGS), METHODB(test_parse_written_data, METH_VARARGS), MND(line_edge_colors, METH_NOARGS) MND(line, METH_O) MND(dump_lines_with_attrs, METH_VARARGS) MND(cpu_cells, METH_VARARGS) MND(cursor_at_prompt, METH_NOARGS) {"visual_line", (PyCFunction)pyvisual_line, METH_VARARGS, ""}, MND(current_url_text, METH_NOARGS) MND(draw, METH_O) MND(apply_sgr, METH_O) MND(cursor_position, METH_VARARGS) MND(set_window_char, METH_VARARGS) MND(set_mode, METH_VARARGS) MND(reset_mode, METH_VARARGS) MND(reset, METH_NOARGS) MND(reset_dirty, METH_NOARGS) MND(is_using_alternate_linebuf, METH_NOARGS) MND(is_main_linebuf, METH_NOARGS) MND(cursor_back, METH_VARARGS) MND(erase_in_line, METH_VARARGS) MND(erase_in_display, METH_VARARGS) MND(clear_scrollback, METH_NOARGS) MND(scroll_until_cursor_prompt, METH_VARARGS) MND(hyperlinks_as_set, METH_NOARGS) MND(garbage_collect_hyperlink_pool, METH_NOARGS) MND(hyperlink_for_id, METH_O) MND(reverse_scroll, METH_VARARGS) MND(scroll_prompt_to_bottom, METH_NOARGS) METHOD(current_char_width, METH_NOARGS) MND(insert_lines, METH_VARARGS) MND(delete_lines, METH_VARARGS) {"insert_characters", (PyCFunction)py_insert_characters, METH_O, ""}, MND(delete_characters, METH_VARARGS) MND(erase_characters, METH_VARARGS) MND(current_pointer_shape, METH_NOARGS) MND(change_pointer_shape, METH_VARARGS) MND(cursor_up, METH_VARARGS) MND(cursor_up1, METH_VARARGS) MND(cursor_down, METH_VARARGS) MND(cursor_down1, METH_VARARGS) MND(cursor_forward, METH_VARARGS) {"index", (PyCFunction)xxx_index, METH_VARARGS, ""}, {"has_selection", (PyCFunction)has_selection, METH_VARARGS, ""}, MND(as_text, METH_VARARGS) MND(as_text_non_visual, METH_VARARGS) MND(as_text_for_history_buf, METH_VARARGS) MND(as_text_alternate, METH_VARARGS) MND(cmd_output, METH_VARARGS) MND(tab, METH_NOARGS) MND(backspace, METH_NOARGS) MND(linefeed, METH_NOARGS) MND(carriage_return, METH_NOARGS) MND(set_tab_stop, METH_NOARGS) MND(clear_tab_stop, METH_VARARGS) MND(start_selection, METH_VARARGS) MND(update_selection, METH_VARARGS) {"clear_selection", (PyCFunction)clear_selection_, METH_NOARGS, ""}, MND(reverse_index, METH_NOARGS) MND(mark_as_dirty, METH_NOARGS) MND(reload_all_gpu_data, METH_NOARGS) MND(resize, METH_VARARGS) MND(ignore_bells_for, METH_VARARGS) MND(set_margins, METH_VARARGS) MND(detect_url, METH_VARARGS) MND(rescale_images, METH_NOARGS) MND(current_key_encoding_flags, METH_NOARGS) MND(text_for_selection, METH_VARARGS) MND(text_for_marked_url, METH_VARARGS) MND(is_rectangle_select, METH_NOARGS) MND(scroll, METH_VARARGS) MND(scroll_to_prompt, METH_VARARGS) MND(set_last_visited_prompt, METH_VARARGS) MND(send_escape_code_to_child, METH_VARARGS) MND(pause_rendering, METH_VARARGS) MND(hyperlink_at, METH_VARARGS) MND(toggle_alt_screen, METH_NOARGS) MND(reset_callbacks, METH_NOARGS) MND(paste, METH_O) MND(paste_bytes, METH_O) MND(focus_changed, METH_O) MND(has_focus, METH_NOARGS) MND(has_activity_since_last_focus, METH_NOARGS) MND(copy_colors_from, METH_O) MND(set_marker, METH_VARARGS) MND(marked_cells, METH_NOARGS) MND(scroll_to_next_mark, METH_VARARGS) MND(update_only_line_graphics_data, METH_NOARGS) MND(bell, METH_NOARGS) MND(current_selections, METH_NOARGS) {"select_graphic_rendition", (PyCFunction)_select_graphic_rendition, METH_VARARGS, ""}, {NULL} /* Sentinel */ }; static PyGetSetDef getsetters[] = { GETSET(in_bracketed_paste_mode) GETSET(color_preference_notification) GETSET(auto_repeat_enabled) GETSET(focus_tracking_enabled) GETSET(in_band_resize_notification) GETSET(cursor_visible) GETSET(cursor_key_mode) GETSET(disable_ligatures) GETSET(render_unfocused_cursor) {NULL} /* Sentinel */ }; #if UINT_MAX == UINT32_MAX #define T_COL T_UINT #elif ULONG_MAX == UINT32_MAX #define T_COL T_ULONG #else #error Neither int nor long is 4-bytes in size #endif static PyMemberDef members[] = { {"callbacks", T_OBJECT_EX, offsetof(Screen, callbacks), 0, "callbacks"}, {"cursor", T_OBJECT_EX, offsetof(Screen, cursor), READONLY, "cursor"}, {"vt_parser", T_OBJECT_EX, offsetof(Screen, vt_parser), READONLY, "vt_parser"}, {"last_reported_cwd", T_OBJECT, offsetof(Screen, last_reported_cwd), READONLY, "last_reported_cwd"}, {"grman", T_OBJECT_EX, offsetof(Screen, grman), READONLY, "grman"}, {"color_profile", T_OBJECT_EX, offsetof(Screen, color_profile), READONLY, "color_profile"}, {"linebuf", T_OBJECT_EX, offsetof(Screen, linebuf), READONLY, "linebuf"}, {"main_linebuf", T_OBJECT_EX, offsetof(Screen, main_linebuf), READONLY, "main_linebuf"}, {"historybuf", T_OBJECT_EX, offsetof(Screen, historybuf), READONLY, "historybuf"}, {"scrolled_by", T_UINT, offsetof(Screen, scrolled_by), READONLY, "scrolled_by"}, {"lines", T_UINT, offsetof(Screen, lines), READONLY, "lines"}, {"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"}, {"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"}, {"margin_bottom", T_UINT, offsetof(Screen, margin_bottom), READONLY, "margin_bottom"}, {"history_line_added_count", T_UINT, offsetof(Screen, history_line_added_count), 0, "history_line_added_count"}, {NULL} }; PyTypeObject Screen_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.Screen", .tp_basicsize = sizeof(Screen), .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "Screen", .tp_methods = methods, .tp_members = members, .tp_new = new_screen_object, .tp_getset = getsetters, }; static PyMethodDef module_methods[] = { {"is_emoji_presentation_base", (PyCFunction)screen_is_emoji_presentation_base, METH_O, ""}, {"truncate_point_for_length", (PyCFunction)screen_truncate_point_for_length, METH_VARARGS, ""}, {"test_ch_and_idx", test_ch_and_idx, METH_O, ""}, {NULL} /* Sentinel */ }; INIT_TYPE(Screen) // }}} kitty-0.41.1/kitty/screen.h0000664000175000017510000003043014773370543015103 0ustar nileshnilesh/* * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "vt-parser.h" #include "graphics.h" #include "monotonic.h" #include "line-buf.h" #include "history.h" typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } ScrollType; typedef struct { bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM, mDECCKM, mCOLOR_PREFERENCE_NOTIFICATION, mBRACKETED_PASTE, mFOCUS_TRACKING, mDECSACE, mHANDLE_TERMIOS_SIGNALS, mINBAND_RESIZE_NOTIFICATION; MouseTrackingMode mouse_tracking_mode; MouseTrackingProtocol mouse_tracking_protocol; } ScreenModes; typedef struct { unsigned int x, y; bool in_left_half_of_cell; } SelectionBoundary; typedef enum SelectionExtendModes { EXTEND_CELL, EXTEND_WORD, EXTEND_LINE, EXTEND_LINE_FROM_POINT, EXTEND_WORD_AND_LINE_FROM_POINT } SelectionExtendMode; typedef struct { index_type x, x_limit; } XRange; typedef struct { int y, y_limit; XRange first, body, last; } IterationData; typedef struct { SelectionBoundary start, end, input_start, input_current; unsigned int start_scrolled_by, end_scrolled_by; bool rectangle_select, adjusting_start, is_hyperlink; IterationData last_rendered; int sort_y, sort_x; struct { SelectionBoundary start, end; unsigned int scrolled_by; } initial_extent; } Selection; typedef struct { Selection *items; size_t count, capacity, last_rendered_count; bool in_progress, extension_in_progress; SelectionExtendMode extend_mode; } Selections; #define SAVEPOINTS_SZ 256 typedef struct CharsetState { uint32_t *zero, *one, *current, current_num; } CharsetState; typedef struct { Cursor cursor; bool mDECOM, mDECAWM, mDECSCNM; CharsetState charset; bool is_valid; } Savepoint; typedef struct { PyObject *overlay_text; CPUCell *cpu_cells; GPUCell *gpu_cells; index_type xstart, ynum, xnum, cursor_x, text_len; bool is_active; bool is_dirty; struct { CPUCell *cpu_cells; GPUCell *gpu_cells; Cursor cursor; } original_line; struct { index_type x, y; } last_ime_pos; } OverlayLine; typedef struct { PyObject_HEAD unsigned int columns, lines, margin_top, margin_bottom, scrolled_by; double pending_scroll_pixels_x, pending_scroll_pixels_y; CellPixelSize cell_size; OverlayLine overlay_line; id_type window_id; Selections selections, url_ranges; struct { unsigned int cursor_x, cursor_y, scrolled_by; index_type lines, columns; color_type cursor_bg; } last_rendered; bool is_dirty, scroll_changed, reload_all_gpu_data; Cursor *cursor; Savepoint main_savepoint, alt_savepoint; PyObject *callbacks, *test_child; TextCache *text_cache; LineBuf *linebuf, *main_linebuf, *alt_linebuf; GraphicsManager *grman, *main_grman, *alt_grman; HistoryBuf *historybuf; unsigned int history_line_added_count; bool *tabstops, *main_tabstops, *alt_tabstops; ScreenModes modes, saved_modes; ColorProfile *color_profile; monotonic_t start_visual_bell_at; uint8_t *write_buf; size_t write_buf_sz, write_buf_used; pthread_mutex_t write_buf_lock; CursorRenderInfo cursor_render_info; DisableLigature disable_ligatures; PyObject *marker; bool has_focus; bool has_activity_since_last_focus; hyperlink_id_type active_hyperlink_id; HYPERLINK_POOL_HANDLE hyperlink_pool; ANSIBuf as_ansi_buf; char_type last_graphic_char; uint8_t main_key_encoding_flags[8], alt_key_encoding_flags[8], *key_encoding_flags; struct { monotonic_t start, duration; } ignore_bells; union { struct { unsigned int redraws_prompts_at_all: 1; unsigned int uses_special_keys_for_cursor_movement: 1; unsigned int supports_click_events: 1; }; unsigned int val; } prompt_settings; char display_window_char; struct { char ch; uint8_t *canvas; size_t requested_height, width_px, height_px; } last_rendered_window_char; struct { unsigned int scrolled_by; index_type y; bool is_set; } last_visited_prompt; PyObject *last_reported_cwd; struct { hyperlink_id_type id; index_type x, y; } current_hyperlink_under_mouse; struct { uint8_t stack[16], count; } main_pointer_shape_stack, alternate_pointer_shape_stack; Parser *vt_parser; struct { monotonic_t expires_at; Cursor cursor; ColorProfile color_profile; bool inverted, cell_data_updated, cursor_visible; unsigned int scrolled_by; LineBuf *linebuf; GraphicsManager *grman; Selections selections, url_ranges; } paused_rendering; CharsetState charset; ListOfChars *lc; monotonic_t parsing_at; } Screen; void screen_align(Screen*); void screen_restore_cursor(Screen *); void screen_save_cursor(Screen *); void screen_restore_modes(Screen *); void screen_restore_mode(Screen *, unsigned int); void screen_save_modes(Screen *); void screen_save_mode(Screen *, unsigned int); bool write_escape_code_to_child(Screen *self, unsigned char which, const char *data); void screen_cursor_position(Screen*, unsigned int, unsigned int); void screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/); void screen_erase_in_line(Screen *, unsigned int, bool); void screen_erase_in_display(Screen *, unsigned int, bool); void screen_draw_text(Screen *self, const uint32_t *chars, size_t num_chars); void screen_ensure_bounds(Screen *self, bool use_margins, bool cursor_was_within_margins); void screen_toggle_screen_buffer(Screen *self, bool, bool); void screen_normal_keypad_mode(Screen *self); void screen_alternate_keypad_mode(Screen *self); void screen_change_default_color(Screen *self, unsigned int which, uint32_t col); void screen_alignment_display(Screen *self); void screen_reverse_index(Screen *self); void screen_index(Screen *self); void screen_scroll(Screen *self, unsigned int count); void screen_reverse_scroll(Screen *self, unsigned int count); void screen_reverse_scroll_and_fill_from_scrollback(Screen *self, unsigned int count); void screen_reset(Screen *self); void screen_set_tab_stop(Screen *self); void screen_tab(Screen *self); void screen_backtab(Screen *self, unsigned int); void screen_clear_tab_stop(Screen *self, unsigned int how); void screen_set_mode(Screen *self, unsigned int mode); void screen_reset_mode(Screen *self, unsigned int mode); void screen_decsace(Screen *self, unsigned int); void screen_xtversion(Screen *self, unsigned int); void screen_insert_characters(Screen *self, unsigned int count); void screen_cursor_up(Screen *self, unsigned int count/*=1*/, bool do_carriage_return/*=false*/, int move_direction/*=-1*/); void screen_set_cursor(Screen *self, unsigned int mode, uint8_t secondary); void screen_cursor_to_column(Screen *self, unsigned int column); void screen_cursor_down(Screen *self, unsigned int count/*=1*/); void screen_cursor_forward(Screen *self, unsigned int count/*=1*/); void screen_cursor_down1(Screen *self, unsigned int count/*=1*/); void screen_cursor_up1(Screen *self, unsigned int count/*=1*/); void screen_cursor_to_line(Screen *screen, unsigned int line); MouseShape screen_pointer_shape(Screen *self); void screen_insert_lines(Screen *self, unsigned int count/*=1*/); void screen_delete_lines(Screen *self, unsigned int count/*=1*/); void screen_repeat_character(Screen *self, unsigned int count); void screen_delete_characters(Screen *self, unsigned int count); void screen_erase_characters(Screen *self, unsigned int count); void screen_set_margins(Screen *self, unsigned int top, unsigned int bottom); void screen_push_colors(Screen *, unsigned int); void screen_pop_colors(Screen *, unsigned int); void screen_report_color_stack(Screen *); void screen_handle_kitty_dcs(Screen *, const char *callback_name, PyObject *cmd); void set_title(Screen *self, PyObject*); void desktop_notify(Screen *self, unsigned int, PyObject*); void set_icon(Screen *self, PyObject*); void set_dynamic_color(Screen *self, unsigned int code, PyObject*); void color_control(Screen *self, unsigned int code, PyObject*); void clipboard_control(Screen *self, int code, PyObject*); void shell_prompt_marking(Screen *self, char *buf); void file_transmission(Screen *self, PyObject*); void set_color_table_color(Screen *self, unsigned int code, PyObject*); void process_cwd_notification(Screen *self, unsigned int code, const char*, size_t); void screen_request_capabilities(Screen *, char, const char *); void report_device_attributes(Screen *self, unsigned int UNUSED mode, char start_modifier); void select_graphic_rendition(Screen *self, int *params, unsigned int count, bool is_group, Region *r); void report_device_status(Screen *self, unsigned int which, bool UNUSED); void report_mode_status(Screen *self, unsigned int which, bool); void screen_apply_selection(Screen *self, void *address, size_t size); bool screen_is_selection_dirty(Screen *self); bool screen_has_selection(Screen*); bool screen_invert_colors(Screen *self); void screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE, bool cursor_has_moved); bool screen_is_cursor_visible(const Screen *self); bool screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end); bool screen_selection_range_for_word(Screen *self, const index_type x, const index_type y, index_type *, index_type *, index_type *start, index_type *end, bool); void screen_start_selection(Screen *self, index_type x, index_type y, bool, bool, SelectionExtendMode); typedef struct SelectionUpdate { bool ended, start_extended_selection, set_as_nearest_extend; } SelectionUpdate; void screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_half, SelectionUpdate upd); bool screen_history_scroll(Screen *self, int amt, bool upwards); PyObject* as_text_history_buf(HistoryBuf *self, PyObject *args, ANSIBuf *output); Line* screen_visual_line(Screen *self, index_type y); void screen_mark_url(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y); void set_active_hyperlink(Screen*, char*, char*); hyperlink_id_type screen_mark_hyperlink(Screen*, index_type, index_type); void screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const uint8_t *payload); void screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const uint8_t *payload); bool screen_open_url(Screen*); bool screen_set_last_visited_prompt(Screen*, index_type); bool screen_select_cmd_output(Screen*, index_type); void screen_dirty_sprite_positions(Screen *self); void screen_rescale_images(Screen *self); void screen_report_size(Screen *, unsigned int which); void screen_manipulate_title_stack(Screen *, unsigned int op, unsigned int which); bool screen_is_overlay_active(Screen *self); void screen_update_overlay_text(Screen *self, const char *utf8_text); void screen_set_key_encoding_flags(Screen *self, uint32_t val, uint32_t how); void screen_push_key_encoding_flags(Screen *self, uint32_t val); void screen_pop_key_encoding_flags(Screen *self, uint32_t num); uint8_t screen_current_key_encoding_flags(Screen *self); void screen_modify_other_keys(Screen *self, unsigned int); void screen_report_key_encoding_flags(Screen *self); int screen_detect_url(Screen *screen, unsigned int x, unsigned int y); int screen_cursor_at_a_shell_prompt(const Screen *); bool screen_prompt_supports_click_events(const Screen *); bool screen_fake_move_cursor_to_position(Screen *, index_type x, index_type y); bool screen_send_signal_for_key(Screen *, char key); bool get_line_edge_colors(Screen *self, color_type *left, color_type *right); bool parse_sgr(Screen *screen, const uint8_t *buf, unsigned int num, const char *report_name, bool is_deccara); bool screen_pause_rendering(Screen *self, bool pause, int for_in_ms); void screen_check_pause_rendering(Screen *self, monotonic_t now); void screen_designate_charset(Screen *self, uint32_t which, uint32_t as); #define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen); DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(backspace) DECLARE_CH_SCREEN_HANDLER(tab) DECLARE_CH_SCREEN_HANDLER(linefeed) DECLARE_CH_SCREEN_HANDLER(carriage_return) #undef DECLARE_CH_SCREEN_HANDLER kitty-0.41.1/kitty/search_query_parser.py0000664000175000017510000002317414773370543020102 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2022, Kovid Goyal import re from collections.abc import Callable, Iterator, Sequence from enum import Enum from functools import lru_cache from gettext import gettext as _ from typing import NamedTuple, TypeVar from .types import run_once class ParseException(Exception): hide_traceback = True @property def msg(self) -> str: if len(self.args) > 0: return str(self.args[0]) return "" class ExpressionType(Enum): OR = 1 AND = 2 NOT = 3 TOKEN = 4 class TokenType(Enum): OPCODE = 1 WORD = 2 QUOTED_WORD = 3 EOF = 4 T = TypeVar('T') GetMatches = Callable[[str, str, set[T]], set[T]] class SearchTreeNode: type = ExpressionType.OR def __init__(self, type: ExpressionType) -> None: self.type = type def search(self, universal_set: set[T], get_matches: GetMatches[T]) -> set[T]: return self(universal_set, get_matches) def __call__(self, candidates: set[T], get_matches: GetMatches[T]) -> set[T]: return set() def iter_token_nodes(self) -> Iterator['TokenNode']: return iter(()) class OrNode(SearchTreeNode): def __init__(self, lhs: SearchTreeNode, rhs: SearchTreeNode) -> None: self.lhs = lhs self.rhs = rhs def __call__(self, candidates: set[T], get_matches: GetMatches[T]) -> set[T]: lhs = self.lhs(candidates, get_matches) return lhs.union(self.rhs(candidates.difference(lhs), get_matches)) def iter_token_nodes(self) -> Iterator['TokenNode']: yield from self.lhs.iter_token_nodes() yield from self.rhs.iter_token_nodes() class AndNode(SearchTreeNode): type = ExpressionType.AND def __init__(self, lhs: SearchTreeNode, rhs: SearchTreeNode) -> None: self.lhs = lhs self.rhs = rhs def __call__(self, candidates: set[T], get_matches: GetMatches[T]) -> set[T]: lhs = self.lhs(candidates, get_matches) return self.rhs(lhs, get_matches) def iter_token_nodes(self) -> Iterator['TokenNode']: yield from self.lhs.iter_token_nodes() yield from self.rhs.iter_token_nodes() class NotNode(SearchTreeNode): type = ExpressionType.NOT def __init__(self, rhs: SearchTreeNode) -> None: self.rhs = rhs def __call__(self, candidates: set[T], get_matches: GetMatches[T]) -> set[T]: return candidates.difference(self.rhs(candidates, get_matches)) def iter_token_nodes(self) -> Iterator['TokenNode']: yield from self.rhs.iter_token_nodes() class TokenNode(SearchTreeNode): type = ExpressionType.TOKEN def __init__(self, location: str, query: str) -> None: self.location = location self.query = query def __call__(self, candidates: set[T], get_matches: GetMatches[T]) -> set[T]: return get_matches(self.location, self.query, candidates) def iter_token_nodes(self) -> Iterator['TokenNode']: yield self class Token(NamedTuple): type: TokenType val: str @run_once def lex_scanner() -> Callable[[str], tuple[list[Token], str]]: return getattr(re, 'Scanner')([ # type: ignore (r'[()]', lambda x, t: Token(TokenType.OPCODE, t)), (r'@.+?:[^")\s]+', lambda x, t: Token(TokenType.WORD, str(t))), (r'[^"()\s]+', lambda x, t: Token(TokenType.WORD, str(t))), (r'".*?((? tuple[tuple[str, str], ...]: return tuple(('\\' + x, chr(i + 1)) for i, x in enumerate('\\"()')) class NoLocation(ParseException): def __init__(self, tt: str): a, sep, b = tt.partition(':') if sep == ':': super().__init__(f'{a} is not a recognized location in {tt}') else: super().__init__(f'No location specified before {tt}') class Parser: def __init__(self, allow_no_location: bool = False) -> None: self.current_token = 0 self.tokens: list[Token] = [] self.allow_no_location = allow_no_location def token(self, advance: bool = False) -> str | None: if self.is_eof(): return None res = self.tokens[self.current_token].val if advance: self.current_token += 1 return res def lcase_token(self, advance: bool = False) -> str | None: if self.is_eof(): return None res = self.tokens[self.current_token].val if advance: self.current_token += 1 return res.lower() def token_type(self) -> TokenType: if self.is_eof(): return TokenType.EOF return self.tokens[self.current_token].type def is_eof(self) -> bool: return self.current_token >= len(self.tokens) def advance(self) -> None: self.current_token += 1 def tokenize(self, expr: str) -> list[Token]: # Strip out escaped backslashes, quotes and parens so that the # lex scanner doesn't get confused. We put them back later. for k, v in replacements(): expr = expr.replace(k, v) tokens, leftover = lex_scanner()(expr) if leftover: raise ParseException(_('Extra characters at end of search')) def unescape(x: str) -> str: for k, v in replacements(): x = x.replace(v, k[1:]) return x return [ Token(tt, unescape(tv) if tt in (TokenType.WORD, TokenType.QUOTED_WORD) else tv) for tt, tv in tokens ] def parse(self, expr: str, locations: Sequence[str]) -> SearchTreeNode: self.locations = locations self.tokens = self.tokenize(expr) self.current_token = 0 prog = self.or_expression() if not self.is_eof(): raise ParseException(_('Extra characters at end of search')) return prog def or_expression(self) -> SearchTreeNode: lhs = self.and_expression() if self.lcase_token() == 'or': self.advance() return OrNode(lhs, self.or_expression()) return lhs def and_expression(self) -> SearchTreeNode: lhs = self.not_expression() if self.lcase_token() == 'and': self.advance() return AndNode(lhs, self.and_expression()) # Account for the optional 'and' if ((self.token_type() in (TokenType.WORD, TokenType.QUOTED_WORD) or self.token() == '(') and self.lcase_token() != 'or'): return AndNode(lhs, self.and_expression()) return lhs def not_expression(self) -> SearchTreeNode: if self.lcase_token() == 'not': self.advance() return NotNode(self.not_expression()) return self.location_expression() def location_expression(self) -> SearchTreeNode: if self.token_type() == TokenType.OPCODE and self.token() == '(': self.advance() res = self.or_expression() if self.token_type() != TokenType.OPCODE or self.token(advance=True) != ')': raise ParseException(_('missing )')) return res if self.token_type() not in (TokenType.WORD, TokenType.QUOTED_WORD): raise ParseException(_('Invalid syntax. Expected a lookup name or a word')) return self.base_token() def base_token(self) -> SearchTreeNode: if self.token_type() is TokenType.QUOTED_WORD: tt = self.token(advance=True) assert tt is not None if self.allow_no_location: return TokenNode('all', tt) raise NoLocation(tt) tt = self.token(advance=True) assert tt is not None words = tt.split(':') # The complexity here comes from having colon-separated search # values. That forces us to check that the first "word" in a colon- # separated group is a valid location. If not, then the token must # be reconstructed. We also have the problem that locations can be # followed by quoted strings that appear as the next token. and that # tokens can be a sequence of colons. # We have a location if there is more than one word and the first # word is in locations. This check could produce a "wrong" answer if # the search string is something like 'author: "foo"' because it # will be interpreted as 'author:"foo"'. I am choosing to accept the # possible error. The expression should be written '"author:" foo' if len(words) > 1 and words[0].lower() in self.locations: loc = words[0].lower() words = words[1:] if len(words) == 1 and self.token_type() == TokenType.QUOTED_WORD: tt = self.token(advance=True) assert tt is not None return TokenNode(loc, tt) return TokenNode(loc.lower(), ':'.join(words)) if self.allow_no_location: return TokenNode('all', ':'.join(words)) raise NoLocation(tt) @lru_cache(maxsize=64) def build_tree(query: str, locations: str | tuple[str, ...], allow_no_location: bool = False) -> SearchTreeNode: if isinstance(locations, str): locations = tuple(locations.split()) p = Parser(allow_no_location) try: return p.parse(query, locations) except RuntimeError as e: raise ParseException(f'Failed to parse {query!r}, too much recursion required') from e def search( query: str, locations: str | tuple[str, ...], universal_set: set[T], get_matches: GetMatches[T], allow_no_location: bool = False, ) -> set[T]: return build_tree(query, locations, allow_no_location).search(universal_set, get_matches) kitty-0.41.1/kitty/session.py0000664000175000017510000002471414773370543015520 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal import os import shlex import sys from collections.abc import Callable, Generator, Iterator, Mapping from contextlib import suppress from functools import partial from typing import TYPE_CHECKING, Optional, Union from .cli_stub import CLIOptions from .layout.interface import all_layouts from .options.types import Options from .options.utils import resize_window, to_layout_names, window_size from .os_window_size import WindowSize, WindowSizeData, WindowSizes from .typing import SpecialWindowInstance from .utils import expandvars, log_error, resolve_custom_file, resolved_shell, shlex_split if TYPE_CHECKING: from .launch import LaunchSpec from .window import CwdRequest def get_os_window_sizing_data(opts: Options, session: Optional['Session'] = None) -> WindowSizeData: if session is None or session.os_window_size is None: sizes = WindowSizes(WindowSize(*opts.initial_window_width), WindowSize(*opts.initial_window_height)) else: sizes = session.os_window_size return WindowSizeData( sizes, opts.remember_window_size, opts.single_window_margin_width, opts.window_margin_width, opts.single_window_padding_width, opts.window_padding_width) ResizeSpec = tuple[str, int] class WindowSpec: def __init__(self, launch_spec: Union['LaunchSpec', 'SpecialWindowInstance']): self.launch_spec = launch_spec self.resize_spec: ResizeSpec | None = None self.focus_matching_window_spec: str = '' self.is_background_process = False if hasattr(launch_spec, 'opts'): # LaunchSpec from .launch import LaunchSpec assert isinstance(launch_spec, LaunchSpec) self.is_background_process = launch_spec.opts.type == 'background' class Tab: def __init__(self, opts: Options, name: str): self.windows: list[WindowSpec] = [] self.pending_resize_spec: ResizeSpec | None = None self.pending_focus_matching_window: str = '' self.name = name.strip() self.active_window_idx = 0 self.enabled_layouts = opts.enabled_layouts self.layout = (self.enabled_layouts or ['tall'])[0] self.cwd: str | None = None self.next_title: str | None = None @property def has_non_background_processes(self) -> bool: for w in self.windows: if not w.is_background_process: return True return False class Session: def __init__(self, default_title: str | None = None): self.tabs: list[Tab] = [] self.active_tab_idx = 0 self.default_title = default_title self.os_window_size: WindowSizes | None = None self.os_window_class: str | None = None self.os_window_name: str | None = None self.os_window_state: str | None = None self.focus_os_window: bool = False @property def has_non_background_processes(self) -> bool: for t in self.tabs: if t.has_non_background_processes: return True return False def add_tab(self, opts: Options, name: str = '') -> None: if self.tabs and not self.tabs[-1].windows: del self.tabs[-1] self.tabs.append(Tab(opts, name)) def set_next_title(self, title: str) -> None: self.tabs[-1].next_title = title.strip() def set_layout(self, val: str) -> None: if val.partition(':')[0] not in all_layouts: raise ValueError(f'{val} is not a valid layout') self.tabs[-1].layout = val def add_window(self, cmd: None | str | list[str], expand: Callable[[str], str] = lambda x: x) -> None: from .launch import parse_launch_args needs_expandvars = False if isinstance(cmd, str) and cmd: needs_expandvars = True cmd = list(shlex_split(cmd)) spec = parse_launch_args(cmd) if needs_expandvars: assert isinstance(cmd, list) limit = len(cmd) if len(spec.args): with suppress(ValueError): limit = cmd.index(spec.args[0]) cmd = [(expand(x) if i < limit else x) for i, x in enumerate(cmd)] spec = parse_launch_args(cmd) t = self.tabs[-1] if t.next_title and not spec.opts.window_title: spec.opts.window_title = t.next_title spec.opts.cwd = spec.opts.cwd or t.cwd t.windows.append(WindowSpec(spec)) t.next_title = None if t.pending_resize_spec is not None: t.windows[-1].resize_spec = t.pending_resize_spec t.pending_resize_spec = None if t.pending_focus_matching_window: t.windows[-1].focus_matching_window_spec = t.pending_focus_matching_window t.pending_focus_matching_window = '' def resize_window(self, args: list[str]) -> None: s = resize_window('resize_window', shlex.join(args))[1] spec: ResizeSpec = s[0], s[1] t = self.tabs[-1] if t.windows: t.windows[-1].resize_spec = spec else: t.pending_resize_spec = spec def focus_matching_window(self, spec: str) -> None: t = self.tabs[-1] if t.windows: t.windows[-1].focus_matching_window_spec = spec else: t.pending_focus_matching_window = spec def add_special_window(self, sw: 'SpecialWindowInstance') -> None: self.tabs[-1].windows.append(WindowSpec(sw)) def focus(self) -> None: self.active_tab_idx = max(0, len(self.tabs) - 1) self.tabs[-1].active_window_idx = max(0, len(self.tabs[-1].windows) - 1) def set_enabled_layouts(self, raw: str) -> None: self.tabs[-1].enabled_layouts = to_layout_names(raw) if self.tabs[-1].layout not in self.tabs[-1].enabled_layouts: self.tabs[-1].layout = self.tabs[-1].enabled_layouts[0] def set_cwd(self, val: str) -> None: self.tabs[-1].cwd = val def parse_session(raw: str, opts: Options, environ: Mapping[str, str] | None = None) -> Generator[Session, None, None]: def finalize_session(ans: Session) -> Session: from .tabs import SpecialWindow for t in ans.tabs: if not t.windows: t.windows.append(WindowSpec(SpecialWindow(cmd=resolved_shell(opts)))) return ans if environ is None: environ = os.environ expand = partial(expandvars, env=environ, fallback_to_os_env=False) ans = Session() ans.add_tab(opts) for line in raw.splitlines(): line = line.strip() if line and not line.startswith('#'): parts = line.split(maxsplit=1) if len(parts) == 1: cmd, rest = parts[0], '' else: cmd, rest = parts cmd, rest = cmd.strip(), rest.strip() if cmd != 'launch': rest = expand(rest) if cmd == 'new_tab': ans.add_tab(opts, rest) elif cmd == 'new_os_window': yield finalize_session(ans) ans = Session() ans.add_tab(opts, rest) elif cmd == 'layout': ans.set_layout(rest) elif cmd == 'launch': ans.add_window(rest, expand) elif cmd == 'focus': ans.focus() elif cmd == 'focus_os_window': ans.focus_os_window = True elif cmd == 'enabled_layouts': ans.set_enabled_layouts(rest) elif cmd == 'cd': ans.set_cwd(rest) elif cmd == 'title': ans.set_next_title(rest) elif cmd == 'os_window_size': w, h = map(window_size, rest.split(maxsplit=1)) ans.os_window_size = WindowSizes(WindowSize(*w), WindowSize(*h)) elif cmd == 'os_window_class': ans.os_window_class = rest elif cmd == 'os_window_name': ans.os_window_name = rest elif cmd == 'os_window_state': ans.os_window_state = rest elif cmd == 'resize_window': ans.resize_window(rest.split()) elif cmd == 'focus_matching_window': ans.focus_matching_window(rest) else: raise ValueError(f'Unknown command in session file: {cmd}') yield finalize_session(ans) class PreReadSession(str): def __new__(cls, val: str, associated_environ: Mapping[str, str]) -> 'PreReadSession': ans: PreReadSession = str.__new__(cls, val) ans.pre_read = True # type: ignore ans.associated_environ = associated_environ # type: ignore return ans def create_sessions( opts: Options, args: CLIOptions | None = None, special_window: Optional['SpecialWindowInstance'] = None, cwd_from: Optional['CwdRequest'] = None, respect_cwd: bool = False, default_session: str | None = None, ) -> Iterator[Session]: if args and args.session: if args.session == "none": default_session = "none" else: environ: Mapping[str, str] | None = None if isinstance(args.session, PreReadSession): session_data = '' + str(args.session) environ = args.session.associated_environ # type: ignore else: if args.session == '-': f = sys.stdin else: f = open(resolve_custom_file(args.session)) with f: session_data = f.read() yield from parse_session(session_data, opts, environ=environ) return if default_session and default_session != 'none' and not getattr(args, 'args', None): try: with open(default_session) as f: session_data = f.read() except OSError: log_error(f'Failed to read from session file, ignoring: {default_session}') else: yield from parse_session(session_data, opts) return ans = Session() current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall' ans.add_tab(opts) ans.tabs[-1].layout = current_layout if special_window is None: cmd = args.args if args and args.args else resolved_shell(opts) from kitty.tabs import SpecialWindow cwd: str | None = args.directory if respect_cwd and args else None special_window = SpecialWindow(cmd, cwd_from=cwd_from, cwd=cwd, hold=bool(args and args.hold)) ans.add_special_window(special_window) yield ans kitty-0.41.1/kitty/shaders.c0000664000175000017510000020370114773370543015253 0ustar nileshnilesh/* * shaders.c * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "fonts.h" #include "gl.h" #include "cleanup.h" #include "colors.h" #include #include "window_logo.h" #include "srgb_gamma.h" #include "uniforms_generated.h" #define BLEND_ONTO_OPAQUE glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // blending onto opaque colors #define BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); // blending onto opaque colors with final color having alpha 1 #define BLEND_PREMULT glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // blending of pre-multiplied colors enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_ALPHA_MASK_PROGRAM, BGIMAGE_PROGRAM, TINT_PROGRAM, TRAIL_PROGRAM, NUM_PROGRAMS }; enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, BGIMAGE_UNIT, SPRITE_DECORATIONS_MAP_UNIT }; // Sprites {{{ typedef struct { int xnum, ynum, x, y, z, last_num_of_layers, last_ynum; GLuint texture_id; GLint max_texture_size, max_array_texture_layers; struct decorations_map { GLuint texture_id; unsigned width, height; size_t count; } decorations_map; } SpriteMap; static const SpriteMap NEW_SPRITE_MAP = { .xnum = 1, .ynum = 1, .last_num_of_layers = 1, .last_ynum = -1 }; static GLint max_texture_size = 0, max_array_texture_layers = 0; static GLfloat srgb_color(uint8_t color) { return srgb_lut[color]; } static void color_vec3(GLint location, color_type color) { glUniform3f(location, srgb_lut[(color >> 16) & 0xFF], srgb_lut[(color >> 8) & 0xFF], srgb_lut[color & 0xFF]); } static void color_vec4_premult(GLint location, color_type color, GLfloat alpha) { glUniform4f(location, srgb_lut[(color >> 16) & 0xFF]*alpha, srgb_lut[(color >> 8) & 0xFF]*alpha, srgb_lut[color & 0xFF]*alpha, alpha); } SPRITE_MAP_HANDLE alloc_sprite_map(void) { if (!max_texture_size) { glGetIntegerv(GL_MAX_TEXTURE_SIZE, &(max_texture_size)); glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &(max_array_texture_layers)); #ifdef __APPLE__ // Since on Apple we could have multiple GPUs, with different capabilities, // upper bound the values according to the data from https://developer.apple.com/graphicsimaging/opengl/capabilities/ max_texture_size = MIN(8192, max_texture_size); max_array_texture_layers = MIN(512, max_array_texture_layers); #endif sprite_tracker_set_limits(max_texture_size, max_array_texture_layers); } SpriteMap *ans = calloc(1, sizeof(SpriteMap)); if (!ans) fatal("Out of memory allocating a sprite map"); *ans = NEW_SPRITE_MAP; ans->max_texture_size = max_texture_size; ans->max_array_texture_layers = max_array_texture_layers; return (SPRITE_MAP_HANDLE)ans; } void free_sprite_data(FONTS_DATA_HANDLE fg) { SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; if (sprite_map) { if (sprite_map->texture_id) free_texture(&sprite_map->texture_id); if (sprite_map->decorations_map.texture_id) free_texture(&sprite_map->texture_id); free(sprite_map); fg->sprite_map = NULL; } } static void copy_32bit_texture(GLuint old_texture, GLuint new_texture, GLenum texture_type) { // requires new texture to be at least as big as old texture. Assumes textures are 32bits per pixel GLint width, height, layers; glBindTexture(texture_type, old_texture); glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_WIDTH, &width); glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_HEIGHT, &height); glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_DEPTH, &layers); if (GLAD_GL_ARB_copy_image) { glCopyImageSubData(old_texture, texture_type, 0, 0, 0, 0, new_texture, texture_type, 0, 0, 0, 0, width, height, layers); return; } static bool copy_image_warned = false; // ARB_copy_image not available, do a slow roundtrip copy if (!copy_image_warned) { copy_image_warned = true; log_error("WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation"); } GLint internal_format; glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format); GLenum format, type; switch(internal_format) { case GL_R8UI: case GL_R8I: case GL_R16UI: case GL_R16I: case GL_R32UI: case GL_R32I: case GL_RG8UI: case GL_RG8I: case GL_RG16UI: case GL_RG16I: case GL_RG32UI: case GL_RG32I: case GL_RGB8UI: case GL_RGB8I: case GL_RGB16UI: case GL_RGB16I: case GL_RGB32UI: case GL_RGB32I: case GL_RGBA8UI: case GL_RGBA8I: case GL_RGBA16UI: case GL_RGBA16I: case GL_RGBA32UI: case GL_RGBA32I: format = GL_RED_INTEGER; type = GL_UNSIGNED_INT; break; default: format = GL_RGBA; type = GL_UNSIGNED_INT_8_8_8_8; break; } glPixelStorei(GL_UNPACK_ALIGNMENT, 4); RAII_ALLOC(uint8_t, pixels, malloc((size_t)width * height * layers * 4u)); if (!pixels) fatal("Out of memory"); glGetTexImage(texture_type, 0, format, type, pixels); glBindTexture(texture_type, new_texture); glPixelStorei(GL_PACK_ALIGNMENT, 4); if (texture_type == GL_TEXTURE_2D_ARRAY) glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, layers, format, type, pixels); else glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, pixels); } static GLuint setup_new_sprites_texture(GLenum texture_type) { GLuint tex; glGenTextures(1, &tex); glBindTexture(texture_type, tex); // We use GL_NEAREST otherwise glyphs that touch the edge of the cell // often show a border between cells glTexParameteri(texture_type, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(texture_type, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(texture_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(texture_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); return tex; } static void realloc_sprite_decorations_texture_if_needed(FONTS_DATA_HANDLE fg) { #define dm (sm->decorations_map) SpriteMap *sm = (SpriteMap*)fg->sprite_map; size_t current_capacity = (size_t)dm.width * dm.height; if (dm.count < current_capacity && dm.texture_id) return; GLint new_capacity = dm.count + 256; GLint width = new_capacity, height = 1; if (new_capacity > sm->max_texture_size) { width = sm->max_texture_size; height = 1 + new_capacity / width; } if (height > sm->max_texture_size) fatal("Max texture size too small for sprite decorations map, maybe switch to using a GL_TEXTURE_2D_ARRAY"); const GLenum texture_type = GL_TEXTURE_2D; GLuint tex = setup_new_sprites_texture(texture_type); glTexImage2D(texture_type, 0, GL_R32UI, width, height, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, NULL); if (dm.texture_id) { // copy data from old texture copy_32bit_texture(dm.texture_id, tex, texture_type); glDeleteTextures(1, &dm.texture_id); } glBindTexture(texture_type, 0); dm.texture_id = tex; dm.width = width; dm.height = height; #undef dm } static void realloc_sprite_texture(FONTS_DATA_HANDLE fg) { unsigned int xnum, ynum, z, znum, width, height; sprite_tracker_current_layout(fg, &xnum, &ynum, &z); znum = z + 1; SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; width = xnum * fg->fcm.cell_width; height = ynum * (fg->fcm.cell_height + 1); const GLenum texture_type = GL_TEXTURE_2D_ARRAY; GLuint tex = setup_new_sprites_texture(texture_type); glTexStorage3D(texture_type, 1, GL_SRGB8_ALPHA8, width, height, znum); if (sprite_map->texture_id) { // copy old texture data into new texture copy_32bit_texture(sprite_map->texture_id, tex, texture_type); glDeleteTextures(1, &sprite_map->texture_id); } glBindTexture(texture_type, 0); sprite_map->last_num_of_layers = znum; sprite_map->last_ynum = ynum; sprite_map->texture_id = tex; } static void ensure_sprite_map(FONTS_DATA_HANDLE fg) { SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; if (!sprite_map->texture_id) realloc_sprite_texture(fg); if (!sprite_map->decorations_map.texture_id) realloc_sprite_decorations_texture_if_needed(fg); // We have to rebind since we don't know if the texture was ever bound // in the context of the current OSWindow glActiveTexture(GL_TEXTURE0 + SPRITE_DECORATIONS_MAP_UNIT); glBindTexture(GL_TEXTURE_2D, sprite_map->decorations_map.texture_id); glActiveTexture(GL_TEXTURE0 + SPRITE_MAP_UNIT); glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id); } void send_sprite_to_gpu(FONTS_DATA_HANDLE fg, sprite_index idx, pixel *buf, sprite_index decoration_idx) { SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; unsigned int xnum, ynum, znum, x, y, z; #define dm (sprite_map->decorations_map) if (idx >= dm.count) dm.count = idx + 1; realloc_sprite_decorations_texture_if_needed(fg); div_t d = div(idx, dm.width); x = d.rem; y = d.quot; glActiveTexture(GL_TEXTURE0 + SPRITE_DECORATIONS_MAP_UNIT); glBindTexture(GL_TEXTURE_2D, dm.texture_id); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &decoration_idx); #undef dm sprite_tracker_current_layout(fg, &xnum, &ynum, &znum); if ((int)znum >= sprite_map->last_num_of_layers || (znum == 0 && (int)ynum > sprite_map->last_ynum)) { realloc_sprite_texture(fg); sprite_tracker_current_layout(fg, &xnum, &ynum, &znum); } glActiveTexture(GL_TEXTURE0 + SPRITE_MAP_UNIT); glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); sprite_index_to_pos(idx, xnum, ynum, &x, &y, &z); x *= fg->fcm.cell_width; y *= (fg->fcm.cell_height + 1); glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, fg->fcm.cell_width, fg->fcm.cell_height + 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buf); } void send_image_to_gpu(GLuint *tex_id, const void* data, GLsizei width, GLsizei height, bool is_opaque, bool is_4byte_aligned, bool linear, RepeatStrategy repeat) { if (!(*tex_id)) { glGenTextures(1, tex_id); } glBindTexture(GL_TEXTURE_2D, *tex_id); glPixelStorei(GL_UNPACK_ALIGNMENT, is_4byte_aligned ? 4 : 1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, linear ? GL_LINEAR : GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear ? GL_LINEAR : GL_NEAREST); RepeatStrategy r; switch (repeat) { case REPEAT_MIRROR: r = GL_MIRRORED_REPEAT; break; case REPEAT_CLAMP: { static const GLfloat border_color[4] = {0}; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color); r = GL_CLAMP_TO_BORDER; break; } default: r = GL_REPEAT; } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, r); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, r); glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA, width, height, 0, is_opaque ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, data); } // }}} // Cell {{{ typedef struct CellRenderData { struct { GLfloat xstart, ystart, dx, dy, width, height; } gl; float x_ratio, y_ratio; } CellRenderData; typedef struct { UniformBlock render_data; ArrayInformation color_table; CellUniforms uniforms; } CellProgramLayout; static CellProgramLayout cell_program_layouts[NUM_PROGRAMS]; typedef struct { GraphicsUniforms uniforms; } GraphicsProgramLayout; static GraphicsProgramLayout graphics_program_layouts[NUM_PROGRAMS]; typedef struct { BgimageUniforms uniforms; } BGImageProgramLayout; static BGImageProgramLayout bgimage_program_layout; typedef struct { TintUniforms uniforms; } TintProgramLayout; static TintProgramLayout tint_program_layout; static void init_cell_program(void) { for (int i = CELL_PROGRAM; i < BORDERS_PROGRAM; i++) { cell_program_layouts[i].render_data.index = block_index(i, "CellRenderData"); cell_program_layouts[i].render_data.size = block_size(i, cell_program_layouts[i].render_data.index); cell_program_layouts[i].color_table.size = get_uniform_information(i, "color_table[0]", GL_UNIFORM_SIZE); cell_program_layouts[i].color_table.offset = get_uniform_information(i, "color_table[0]", GL_UNIFORM_OFFSET); cell_program_layouts[i].color_table.stride = get_uniform_information(i, "color_table[0]", GL_UNIFORM_ARRAY_STRIDE); get_uniform_locations_cell(i, &cell_program_layouts[i].uniforms); bind_program(i); glUniform1fv(cell_program_layouts[i].uniforms.gamma_lut, arraysz(srgb_lut), srgb_lut); } // Sanity check to ensure the attribute location binding worked #define C(p, name, expected) { int aloc = attrib_location(p, #name); if (aloc != expected && aloc != -1) fatal("The attribute location for %s is %d != %d in program: %d", #name, aloc, expected, p); } for (int p = CELL_PROGRAM; p < BORDERS_PROGRAM; p++) { C(p, colors, 0); C(p, sprite_idx, 1); C(p, is_selected, 2); C(p, decorations_sprite_map, 3); } #undef C for (int i = GRAPHICS_PROGRAM; i <= GRAPHICS_ALPHA_MASK_PROGRAM; i++) { get_uniform_locations_graphics(i, &graphics_program_layouts[i].uniforms); } get_uniform_locations_bgimage(BGIMAGE_PROGRAM, &bgimage_program_layout.uniforms); get_uniform_locations_tint(TINT_PROGRAM, &tint_program_layout.uniforms); } #define CELL_BUFFERS enum { cell_data_buffer, selection_buffer, uniform_buffer }; ssize_t create_cell_vao(void) { ssize_t vao_idx = create_vao(); #define A(name, size, dtype, offset, stride) \ add_attribute_to_vao(CELL_PROGRAM, vao_idx, #name, \ /*size=*/size, /*dtype=*/dtype, /*stride=*/stride, /*offset=*/offset, /*divisor=*/1); #define A1(name, size, dtype, offset) A(name, size, dtype, (void*)(offsetof(GPUCell, offset)), sizeof(GPUCell)) add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER); A1(sprite_idx, 2, GL_UNSIGNED_INT, sprite_idx); A1(colors, 3, GL_UNSIGNED_INT, fg); add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER); A(is_selected, 1, GL_UNSIGNED_BYTE, NULL, 0); size_t bufnum = add_buffer_to_vao(vao_idx, GL_UNIFORM_BUFFER); alloc_vao_buffer(vao_idx, cell_program_layouts[CELL_PROGRAM].render_data.size, bufnum, GL_STREAM_DRAW); return vao_idx; #undef A #undef A1 } ssize_t create_graphics_vao(void) { ssize_t vao_idx = create_vao(); add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER); add_attribute_to_vao(GRAPHICS_PROGRAM, vao_idx, "src", 4, GL_FLOAT, 0, NULL, 0); return vao_idx; } #define IS_SPECIAL_COLOR(name) (screen->color_profile->overridden.name.type == COLOR_IS_SPECIAL || (screen->color_profile->overridden.name.type == COLOR_NOT_SET && screen->color_profile->configured.name.type == COLOR_IS_SPECIAL)) static void pick_cursor_color(Line *line, const ColorProfile *color_profile, color_type cell_fg, color_type cell_bg, index_type cell_color_x, color_type *cursor_fg, color_type *cursor_bg, color_type default_fg, color_type default_bg) { ARGB32 fg, bg, dfg, dbg; (void) line; (void) color_profile; (void) cell_color_x; fg.rgb = cell_fg; bg.rgb = cell_bg; *cursor_fg = cell_bg; *cursor_bg = cell_fg; double cell_contrast = rgb_contrast(fg, bg); if (cell_contrast < 2.5) { dfg.rgb = default_fg; dbg.rgb = default_bg; if (rgb_contrast(dfg, dbg) > cell_contrast) { *cursor_fg = default_bg; *cursor_bg = default_fg; } } } static void cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, const CellRenderData *crd, CursorRenderInfo *cursor, OSWindow *os_window) { struct GPUCellRenderData { GLfloat xstart, ystart, dx, dy, use_cell_bg_for_selection_fg, use_cell_fg_for_selection_color, use_cell_for_selection_bg; GLuint default_fg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted; GLuint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx, cell_height; GLuint cursor_x1, cursor_x2, cursor_y1, cursor_y2; GLfloat cursor_opacity; GLuint bg_colors0, bg_colors1, bg_colors2, bg_colors3, bg_colors4, bg_colors5, bg_colors6, bg_colors7; GLfloat bg_opacities0, bg_opacities1, bg_opacities2, bg_opacities3, bg_opacities4, bg_opacities5, bg_opacities6, bg_opacities7; }; // Send the uniform data struct GPUCellRenderData *rd = (struct GPUCellRenderData*)map_vao_buffer(vao_idx, uniform_buffer, GL_WRITE_ONLY); ColorProfile *cp = screen->paused_rendering.expires_at ? &screen->paused_rendering.color_profile : screen->color_profile; if (UNLIKELY(cp->dirty || screen->reload_all_gpu_data)) { copy_color_table_to_buffer(cp, (GLuint*)rd, cell_program_layouts[CELL_PROGRAM].color_table.offset / sizeof(GLuint), cell_program_layouts[CELL_PROGRAM].color_table.stride / sizeof(GLuint)); } #define COLOR(name) colorprofile_to_color(cp, cp->overridden.name, cp->configured.name).rgb rd->default_fg = COLOR(default_fg); rd->highlight_fg = COLOR(highlight_fg); rd->highlight_bg = COLOR(highlight_bg); rd->bg_colors0 = COLOR(default_bg); rd->bg_opacities0 = os_window->is_semi_transparent ? os_window->background_opacity : 1.0f; #define SETBG(which) colorprofile_to_transparent_color(cp, which - 1, &rd->bg_colors##which, &rd->bg_opacities##which) SETBG(1); SETBG(2); SETBG(3); SETBG(4); SETBG(5); SETBG(6); SETBG(7); #undef SETBG // selection if (IS_SPECIAL_COLOR(highlight_fg)) { if (IS_SPECIAL_COLOR(highlight_bg)) { rd->use_cell_bg_for_selection_fg = 1.f; rd->use_cell_fg_for_selection_color = 0.f; } else { rd->use_cell_bg_for_selection_fg = 0.f; rd->use_cell_fg_for_selection_color = 1.f; } } else { rd->use_cell_bg_for_selection_fg = 0.f; rd->use_cell_fg_for_selection_color = 0.f; } rd->use_cell_for_selection_bg = IS_SPECIAL_COLOR(highlight_bg) ? 1. : 0.; // Cursor position enum { BLOCK_IDX = 0, BEAM_IDX = 2, UNDERLINE_IDX = 3, UNFOCUSED_IDX = 4 }; Line *line_for_cursor = NULL; if (cursor->opacity > 0) { rd->cursor_x1 = cursor->x, rd->cursor_y1 = cursor->y; rd->cursor_x2 = cursor->x, rd->cursor_y2 = cursor->y; rd->cursor_opacity = cursor->opacity; CursorShape cs = (cursor->is_focused || OPT(cursor_shape_unfocused) == NO_CURSOR_SHAPE) ? cursor->shape : OPT(cursor_shape_unfocused); switch(cs) { case CURSOR_BEAM: rd->cursor_fg_sprite_idx = BEAM_IDX; break; case CURSOR_UNDERLINE: rd->cursor_fg_sprite_idx = UNDERLINE_IDX; break; case CURSOR_BLOCK: case NUM_OF_CURSOR_SHAPES: case NO_CURSOR_SHAPE: rd->cursor_fg_sprite_idx = BLOCK_IDX; break; case CURSOR_HOLLOW: rd->cursor_fg_sprite_idx = UNFOCUSED_IDX; break; }; color_type cell_fg = rd->default_fg, cell_bg = rd->bg_colors0; index_type cell_color_x = cursor->x; bool reversed = false; if (cursor->x < screen->columns && cursor->y < screen->lines) { if (screen->paused_rendering.expires_at) { linebuf_init_line(screen->paused_rendering.linebuf, cursor->y); line_for_cursor = screen->paused_rendering.linebuf->line; } else { linebuf_init_line(screen->linebuf, cursor->y); line_for_cursor = screen->linebuf->line; } } if (line_for_cursor) { colors_for_cell(line_for_cursor, cp, &cell_color_x, &cell_fg, &cell_bg, &reversed); const CPUCell *cursor_cell; const bool large_cursor = ((cursor_cell = &line_for_cursor->cpu_cells[cursor->x])->is_multicell) && cursor_cell->x == 0 && cursor_cell->y == 0; if (large_cursor) { switch(cs) { case CURSOR_BEAM: rd->cursor_y2 += cursor_cell->scale - 1; break; case CURSOR_UNDERLINE: rd->cursor_y1 += cursor_cell->scale - 1; rd->cursor_y2 = rd->cursor_y1; rd->cursor_x2 += mcd_x_limit(cursor_cell) - 1; break; case CURSOR_BLOCK: rd->cursor_y2 += cursor_cell->scale - 1; rd->cursor_x2 += mcd_x_limit(cursor_cell) - 1; break; case CURSOR_HOLLOW: case NUM_OF_CURSOR_SHAPES: case NO_CURSOR_SHAPE: break; }; } } if (IS_SPECIAL_COLOR(cursor_color)) { if (line_for_cursor) pick_cursor_color(line_for_cursor, cp, cell_fg, cell_bg, cell_color_x, &rd->cursor_fg, &rd->cursor_bg, rd->default_fg, rd->bg_colors0); else { rd->cursor_fg = rd->bg_colors0; rd->cursor_bg = rd->default_fg; } if (cell_bg == cell_fg) { rd->cursor_fg = rd->bg_colors0; rd->cursor_bg = rd->default_fg; } else { rd->cursor_fg = cell_bg; rd->cursor_bg = cell_fg; } } else { rd->cursor_bg = COLOR(cursor_color); if (IS_SPECIAL_COLOR(cursor_text_color)) rd->cursor_fg = cell_bg; else rd->cursor_fg = COLOR(cursor_text_color); } // store last rendered cursor color for trail rendering screen->last_rendered.cursor_bg = rd->cursor_bg; } else { rd->cursor_x1 = screen->columns + 1; rd->cursor_x2 = screen->columns; rd->cursor_y1 = screen->lines + 1; rd->cursor_y2 = screen->lines; } rd->xnum = screen->columns; rd->ynum = screen->lines; rd->xstart = crd->gl.xstart; rd->ystart = crd->gl.ystart; rd->dx = crd->gl.dx; rd->dy = crd->gl.dy; unsigned int x, y, z; sprite_tracker_current_layout(os_window->fonts_data, &x, &y, &z); rd->sprites_xnum = x; rd->sprites_ynum = y; rd->inverted = screen_invert_colors(screen) ? 1 : 0; rd->cell_height = os_window->fonts_data->fcm.cell_height; #undef COLOR rd->url_color = OPT(url_color); rd->url_style = OPT(url_style); unmap_vao_buffer(vao_idx, uniform_buffer); rd = NULL; } static bool cell_prepare_to_render(ssize_t vao_idx, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, FONTS_DATA_HANDLE fonts_data) { size_t sz; CELL_BUFFERS; void *address; bool changed = false; ensure_sprite_map(fonts_data); const Cursor *cursor = screen->paused_rendering.expires_at ? &screen->paused_rendering.cursor : screen->cursor; bool cursor_pos_changed = cursor->x != screen->last_rendered.cursor_x || cursor->y != screen->last_rendered.cursor_y; bool disable_ligatures = screen->disable_ligatures == DISABLE_LIGATURES_CURSOR; bool screen_resized = screen->last_rendered.columns != screen->columns || screen->last_rendered.lines != screen->lines; #define update_cell_data { \ sz = sizeof(GPUCell) * screen->lines * screen->columns; \ address = alloc_and_map_vao_buffer(vao_idx, sz, cell_data_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY); \ screen_update_cell_data(screen, address, fonts_data, disable_ligatures && cursor_pos_changed); \ unmap_vao_buffer(vao_idx, cell_data_buffer); address = NULL; \ changed = true; \ } if (screen->paused_rendering.expires_at) { if (!screen->paused_rendering.cell_data_updated) update_cell_data; } else if (screen->reload_all_gpu_data || screen->scroll_changed || screen->is_dirty || screen_resized || (disable_ligatures && cursor_pos_changed)) update_cell_data; if (cursor_pos_changed) { screen->last_rendered.cursor_x = cursor->x; screen->last_rendered.cursor_y = cursor->y; } #define update_selection_data { \ sz = (size_t)screen->lines * screen->columns; \ address = alloc_and_map_vao_buffer(vao_idx, sz, selection_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY); \ screen_apply_selection(screen, address, sz); \ unmap_vao_buffer(vao_idx, selection_buffer); address = NULL; \ changed = true; \ } #define update_graphics_data(grman) \ grman_update_layers(grman, screen->scrolled_by, xstart, ystart, dx, dy, screen->columns, screen->lines, screen->cell_size) if (screen->paused_rendering.expires_at) { if (!screen->paused_rendering.cell_data_updated) { update_selection_data; update_graphics_data(screen->paused_rendering.grman); } screen->paused_rendering.cell_data_updated = true; screen->last_rendered.scrolled_by = screen->paused_rendering.scrolled_by; } else { if (screen->reload_all_gpu_data || screen_resized || screen_is_selection_dirty(screen)) update_selection_data; if (update_graphics_data(screen->grman)) changed = true; screen->last_rendered.scrolled_by = screen->scrolled_by; } #undef update_selection_data #undef update_cell_data screen->last_rendered.columns = screen->columns; screen->last_rendered.lines = screen->lines; return changed; } static void draw_background_image(OSWindow *w) { blank_canvas(w->is_semi_transparent ? OPT(background_opacity) : 1.0f, OPT(background)); bind_program(BGIMAGE_PROGRAM); glUniform1i(bgimage_program_layout.uniforms.image, BGIMAGE_UNIT); glUniform1f(bgimage_program_layout.uniforms.opacity, OPT(background_opacity)); #ifdef __APPLE__ int window_width = w->window_width, window_height = w->window_height; #else int window_width = w->viewport_width, window_height = w->viewport_height; #endif GLfloat iwidth = (GLfloat)w->bgimage->width; GLfloat iheight = (GLfloat)w->bgimage->height; GLfloat vwidth = (GLfloat)window_width; GLfloat vheight = (GLfloat)window_height; if (CENTER_SCALED == OPT(background_image_layout)) { GLfloat ifrac = iwidth / iheight; if (ifrac > (vwidth / vheight)) { iheight = vheight; iwidth = iheight * ifrac; } else { iwidth = vwidth; iheight = iwidth / ifrac; } } glUniform4f(bgimage_program_layout.uniforms.sizes, vwidth, vheight, iwidth, iheight); glUniform1f(bgimage_program_layout.uniforms.premult, w->is_semi_transparent ? 1.f : 0.f); GLfloat tiled = 0.f;; GLfloat left = -1.0, top = 1.0, right = 1.0, bottom = -1.0; switch (OPT(background_image_layout)) { case TILING: case MIRRORED: case CLAMPED: tiled = 1.f; break; case SCALED: break; case CENTER_CLAMPED: case CENTER_SCALED: { GLfloat wfrac = (vwidth - iwidth) / vwidth; GLfloat hfrac = (vheight - iheight) / vheight; left += wfrac; right -= wfrac; top -= hfrac; bottom += hfrac; } break; } glUniform1f(bgimage_program_layout.uniforms.tiled, tiled); glUniform4f(bgimage_program_layout.uniforms.positions, left, top, right, bottom); glActiveTexture(GL_TEXTURE0 + BGIMAGE_UNIT); glBindTexture(GL_TEXTURE_2D, w->bgimage->texture_id); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); unbind_program(); } static void draw_graphics(int program, ssize_t vao_idx, ImageRenderData *data, GLuint start, GLuint count, ImageRect viewport) { bind_program(program); glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT); GraphicsUniforms *u = &graphics_program_layouts[program].uniforms; glUniform4f(u->viewport, viewport.left, viewport.top, viewport.right, viewport.bottom); glEnable(GL_CLIP_DISTANCE0); glEnable(GL_CLIP_DISTANCE1); glEnable(GL_CLIP_DISTANCE2); glEnable(GL_CLIP_DISTANCE3); for (GLuint i=0; i < count;) { ImageRenderData *group = data + start + i; glBindTexture(GL_TEXTURE_2D, group->texture_id); if (group->group_count == 0) { i++; continue; } for (GLuint k=0; k < group->group_count; k++, i++) { ImageRenderData *rd = data + start + i; glUniform4f(u->src_rect, rd->src_rect.left, rd->src_rect.top, rd->src_rect.right, rd->src_rect.bottom); glUniform4f(u->dest_rect, rd->dest_rect.left, rd->dest_rect.top, rd->dest_rect.right, rd->dest_rect.bottom); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } } glDisable(GL_CLIP_DISTANCE0); glDisable(GL_CLIP_DISTANCE1); glDisable(GL_CLIP_DISTANCE2); glDisable(GL_CLIP_DISTANCE3); bind_vertex_array(vao_idx); } static ImageRenderData* load_alpha_mask_texture(size_t width, size_t height, uint8_t *canvas) { static ImageRenderData data = {.group_count=1}; if (!data.texture_id) { glGenTextures(1, &data.texture_id); } glBindTexture(GL_TEXTURE_2D, data.texture_id); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, canvas); return &data; } static void gpu_data_for_centered_image(ImageRenderData *ans, unsigned int screen_width_px, unsigned int screen_height_px, unsigned int width, unsigned int height) { float width_frac = 2 * MIN(1, width / (float)screen_width_px), height_frac = 2 * MIN(1, height / (float)screen_height_px); float hmargin = (2 - width_frac) / 2; float vmargin = (2 - height_frac) / 2; gpu_data_for_image(ans, -1 + hmargin, 1 - vmargin, -1 + hmargin + width_frac, 1 - vmargin - height_frac); } void draw_centered_alpha_mask(OSWindow *os_window, size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas, float background_opacity) { ImageRenderData *data = load_alpha_mask_texture(width, height, canvas); gpu_data_for_centered_image(data, screen_width, screen_height, width, height); bind_program(GRAPHICS_ALPHA_MASK_PROGRAM); glUniform1i(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.image, GRAPHICS_UNIT); color_vec3(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.amask_fg, OPT(foreground)); color_vec4_premult(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.amask_bg_premult, OPT(background), background_opacity); glEnable(GL_BLEND); if (os_window->is_semi_transparent) { BLEND_PREMULT; } else { BLEND_ONTO_OPAQUE; } draw_graphics(GRAPHICS_ALPHA_MASK_PROGRAM, 0, data, 0, 1, (ImageRect){-1, 1, 1, -1}); glDisable(GL_BLEND); } static ImageRect viewport_for_cells(const CellRenderData *crd) { return (ImageRect){crd->gl.xstart, crd->gl.ystart, crd->gl.xstart + crd->gl.width, crd->gl.ystart - crd->gl.height}; } static void draw_cells_simple(ssize_t vao_idx, Screen *screen, const CellRenderData *crd, GraphicsRenderData grd, bool is_semi_transparent) { bind_program(CELL_PROGRAM); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); if (grd.count) { glEnable(GL_BLEND); int program = GRAPHICS_PROGRAM; if (is_semi_transparent) { BLEND_PREMULT; program = GRAPHICS_PREMULT_PROGRAM; } else { BLEND_ONTO_OPAQUE; } draw_graphics(program, vao_idx, grd.images, 0, grd.count, viewport_for_cells(crd)); glDisable(GL_BLEND); } } static bool has_bgimage(OSWindow *w) { return w->bgimage && w->bgimage->texture_id > 0; } static void draw_tint(bool premult, Screen *screen, const CellRenderData *crd) { if (premult) { BLEND_PREMULT } else { BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT } bind_program(TINT_PROGRAM); color_type window_bg = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.default_bg, screen->color_profile->configured.default_bg).rgb; #define C(shift) srgb_color((window_bg >> shift) & 0xFF) * premult_factor GLfloat premult_factor = premult ? OPT(background_tint) : 1.0f; glUniform4f(tint_program_layout.uniforms.tint_color, C(16), C(8), C(0), OPT(background_tint)); #undef C glUniform4f(tint_program_layout.uniforms.edges, crd->gl.xstart, crd->gl.ystart - crd->gl.height, crd->gl.xstart + crd->gl.width, crd->gl.ystart); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } static bool draw_scroll_indicator(bool premult, Screen *screen, const CellRenderData *crd) { if (OPT(scrollback_indicator_opacity) <= 0 || screen->linebuf != screen->main_linebuf || !screen->scrolled_by) return false; glEnable(GL_BLEND); if (premult) { BLEND_PREMULT } else { BLEND_ONTO_OPAQUE } bind_program(TINT_PROGRAM); const color_type bar_color = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg).rgb; GLfloat alpha = OPT(scrollback_indicator_opacity); float frac = (float)screen->scrolled_by / (float)screen->historybuf->count; const GLfloat bar_height = crd->gl.dy; GLfloat bottom = (crd->gl.ystart - crd->gl.height); bottom += MAX(0, crd->gl.height - bar_height) * frac; #define C(shift) srgb_color((bar_color >> shift) & 0xFF) * premult_factor GLfloat premult_factor = premult ? alpha : 1.0f; glUniform4f(tint_program_layout.uniforms.tint_color, C(16), C(8), C(0), alpha); #undef C GLfloat width = 0.5f * crd->gl.dx; GLfloat left = (GLfloat)(crd->gl.xstart + (screen->columns * crd->gl.dx - width)); glUniform4f(tint_program_layout.uniforms.edges, left, bottom, left + width, bottom + bar_height); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDisable(GL_BLEND); return true; } static float prev_inactive_text_alpha = -1; static void set_cell_uniforms(float current_inactive_text_alpha, bool force) { static bool constants_set = false; if (!constants_set || force) { float text_contrast = 1.0f + OPT(text_contrast) * 0.01f; float text_gamma_adjustment = OPT(text_gamma_adjustment) < 0.01f ? 1.0f : 1.0f / OPT(text_gamma_adjustment); for (int i = GRAPHICS_PROGRAM; i <= GRAPHICS_PREMULT_PROGRAM; i++) { bind_program(i); glUniform1i(graphics_program_layouts[i].uniforms.image, GRAPHICS_UNIT); } for (int i = CELL_PROGRAM; i <= CELL_FG_PROGRAM; i++) { bind_program(i); const CellUniforms *cu = &cell_program_layouts[i].uniforms; switch(i) { case CELL_PROGRAM: case CELL_FG_PROGRAM: glUniform1i(cu->sprites, SPRITE_MAP_UNIT); glUniform1i(cu->sprite_decorations_map, SPRITE_DECORATIONS_MAP_UNIT); glUniform1f(cu->dim_opacity, OPT(dim_opacity)); glUniform1f(cu->text_contrast, text_contrast); glUniform1f(cu->text_gamma_adjustment, text_gamma_adjustment); break; } } constants_set = true; } if (current_inactive_text_alpha != prev_inactive_text_alpha || force) { prev_inactive_text_alpha = current_inactive_text_alpha; for (int i = GRAPHICS_PROGRAM; i <= GRAPHICS_PREMULT_PROGRAM; i++) { bind_program(i); glUniform1f(graphics_program_layouts[i].uniforms.inactive_text_alpha, current_inactive_text_alpha); } #define S(prog, loc) bind_program(prog); glUniform1f(cell_program_layouts[prog].uniforms.inactive_text_alpha, current_inactive_text_alpha); S(CELL_PROGRAM, cploc); S(CELL_FG_PROGRAM, cfploc); #undef S } } static GLfloat render_a_bar(OSWindow *os_window, Screen *screen, const CellRenderData *crd, WindowBarData *bar, PyObject *title, bool along_bottom) { GLfloat left = os_window->viewport_width * (crd->gl.xstart + 1.f) / 2.f; GLfloat right = left + os_window->viewport_width * crd->gl.width / 2.f; unsigned bar_height = os_window->fonts_data->fcm.cell_height + 2; if (!bar_height || right <= left) return 0; unsigned bar_width = (unsigned)ceilf(right - left); if (!bar->buf || bar->width != bar_width || bar->height != bar_height) { free(bar->buf); bar->buf = malloc((size_t)4 * bar_width * bar_height); if (!bar->buf) return 0; bar->height = bar_height; bar->width = bar_width; bar->needs_render = true; } if (bar->last_drawn_title_object_id != title || bar->needs_render) { static char titlebuf[2048] = {0}; if (!title) return 0; snprintf(titlebuf, arraysz(titlebuf), " %s", PyUnicode_AsUTF8(title)); #define RGBCOL(which, fallback) ( 0xff000000 | colorprofile_to_color_with_fallback(screen->color_profile, screen->color_profile->overridden.which, screen->color_profile->configured.which, screen->color_profile->overridden.fallback, screen->color_profile->configured.fallback)) if (!draw_window_title(os_window, titlebuf, RGBCOL(highlight_fg, default_fg), RGBCOL(highlight_bg, default_bg), bar->buf, bar_width, bar_height)) return 0; #undef RGBCOL Py_CLEAR(bar->last_drawn_title_object_id); bar->last_drawn_title_object_id = title; Py_INCREF(bar->last_drawn_title_object_id); } static ImageRenderData data = {.group_count=1}; GLfloat xstart, ystart; xstart = clamp_position_to_nearest_pixel(crd->gl.xstart, os_window->viewport_width); GLfloat height_gl = gl_size(bar_height, os_window->viewport_height); if (along_bottom) ystart = crd->gl.ystart - crd->gl.height + height_gl; else ystart = clamp_position_to_nearest_pixel(crd->gl.ystart, os_window->viewport_height); gpu_data_for_image(&data, xstart, ystart, xstart + crd->gl.width, ystart - height_gl); if (!data.texture_id) { glGenTextures(1, &data.texture_id); } glBindTexture(GL_TEXTURE_2D, data.texture_id); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA, bar_width, bar_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bar->buf); set_cell_uniforms(1.f, false); bind_program(GRAPHICS_PROGRAM); glEnable(GL_BLEND); if (os_window->is_semi_transparent) { BLEND_PREMULT; } else { BLEND_ONTO_OPAQUE; } draw_graphics(GRAPHICS_PROGRAM, 0, &data, 0, 1, viewport_for_cells(crd)); glDisable(GL_BLEND); return height_gl; } static void draw_hyperlink_target(OSWindow *os_window, Screen *screen, const CellRenderData *crd, Window *window) { WindowBarData *bd = &window->url_target_bar_data; if (bd->hyperlink_id_for_title_object != screen->current_hyperlink_under_mouse.id) { bd->hyperlink_id_for_title_object = screen->current_hyperlink_under_mouse.id; Py_CLEAR(bd->last_drawn_title_object_id); const char *url = get_hyperlink_for_id(screen->hyperlink_pool, bd->hyperlink_id_for_title_object, true); if (url == NULL) url = ""; bd->last_drawn_title_object_id = PyObject_CallMethod(global_state.boss, "sanitize_url_for_dispay_to_user", "s", url); if (bd->last_drawn_title_object_id == NULL) { PyErr_Print(); return; } bd->needs_render = true; } if (bd->last_drawn_title_object_id == NULL) return; const bool along_bottom = screen->current_hyperlink_under_mouse.y < 3; PyObject *ref = bd->last_drawn_title_object_id; Py_INCREF(ref); render_a_bar(os_window, screen, crd, &window->title_bar_data, bd->last_drawn_title_object_id, along_bottom); Py_DECREF(ref); } static void draw_window_logo(ssize_t vao_idx, OSWindow *os_window, const WindowLogoRenderData *wl, const CellRenderData *crd) { if (os_window->live_resize.in_progress) return; BLEND_PREMULT; GLfloat logo_width_gl = gl_size(wl->instance->width, os_window->viewport_width); GLfloat logo_height_gl = gl_size(wl->instance->height, os_window->viewport_height); if (OPT(window_logo_scale.width) > 0 || OPT(window_logo_scale.height) > 0) { unsigned int scaled_wl_width = os_window->viewport_width; unsigned int scaled_wl_height = os_window->viewport_height; // [sx] Scales logo to sx % of the viewports shortest dimension, preserving aspect ratio if (OPT(window_logo_scale.height) < 0) { if (os_window->viewport_height < os_window->viewport_width) { scaled_wl_height = (int)(os_window->viewport_height * OPT(window_logo_scale.width) / 100); scaled_wl_width = wl->instance->width * scaled_wl_height / wl->instance->height; } else { scaled_wl_width = (int)(os_window->viewport_width * OPT(window_logo_scale.width) / 100); scaled_wl_height = wl->instance->height * scaled_wl_width / wl->instance->width; } } // [0 sy] Scales logo's y dimension to sy % of viewporty keeping original x dimension else if (OPT(window_logo_scale.width) == 0.0) { scaled_wl_height = (int)(scaled_wl_height * OPT(window_logo_scale.height) / 100); scaled_wl_width = wl->instance->width; } // [sx 0] Scales logo's x dimension to sx % of viewportx keeping original y dimension else if (OPT(window_logo_scale.height) == 0.0) { scaled_wl_width = (int)(scaled_wl_width * OPT(window_logo_scale.width) / 100); scaled_wl_height = wl->instance->height; } // [sx sy] Scales logo's x and y dimension to sx and sy % of viewportx and viewporty respectively else { scaled_wl_height = (int)(scaled_wl_height * OPT(window_logo_scale.height) / 100); scaled_wl_width = (int)(scaled_wl_width * OPT(window_logo_scale.width) / 100); } logo_height_gl = gl_size(scaled_wl_height, os_window->viewport_height); logo_width_gl = gl_size(scaled_wl_width, os_window->viewport_width); } GLfloat logo_left_gl = clamp_position_to_nearest_pixel( crd->gl.xstart + crd->gl.width * wl->position.canvas_x - logo_width_gl * wl->position.image_x, os_window->viewport_width); GLfloat logo_top_gl = clamp_position_to_nearest_pixel( crd->gl.ystart - crd->gl.height * wl->position.canvas_y + logo_height_gl * wl->position.image_y, os_window->viewport_height); static ImageRenderData ird = {.group_count=1}; ird.texture_id = wl->instance->texture_id; gpu_data_for_image(&ird, logo_left_gl, logo_top_gl, logo_left_gl + logo_width_gl, logo_top_gl - logo_height_gl); bind_program(GRAPHICS_PREMULT_PROGRAM); glUniform1f(graphics_program_layouts[GRAPHICS_PREMULT_PROGRAM].uniforms.inactive_text_alpha, prev_inactive_text_alpha * wl->alpha); draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, &ird, 0, 1, viewport_for_cells(crd)); glUniform1f(graphics_program_layouts[GRAPHICS_PREMULT_PROGRAM].uniforms.inactive_text_alpha, prev_inactive_text_alpha); } static void draw_window_number(OSWindow *os_window, Screen *screen, const CellRenderData *crd, Window *window) { GLfloat left = os_window->viewport_width * (crd->gl.xstart + 1.f) / 2.f; GLfloat right = left + os_window->viewport_width * crd->gl.width / 2.f; GLfloat title_bar_height = 0; size_t requested_height = (size_t)(os_window->viewport_height * crd->gl.height / 2.f); if (window->title && PyUnicode_Check(window->title) && (requested_height > (os_window->fonts_data->fcm.cell_height + 1) * 2)) { title_bar_height = render_a_bar(os_window, screen, crd, &window->title_bar_data, window->title, false); } GLfloat ystart = crd->gl.ystart, height = crd->gl.height, xstart = crd->gl.xstart, width = crd->gl.width; if (title_bar_height > 0) { ystart -= title_bar_height; height -= title_bar_height; } ystart -= crd->gl.dy / 2.f; height -= crd->gl.dy; // top and bottom margins xstart += crd->gl.dx / 2.f; width -= crd->gl.dx; // left and right margins GLfloat height_gl = MIN(MIN(12 * crd->gl.dy, height), width); requested_height = (size_t)(os_window->viewport_height * height_gl / 2.f); if (requested_height < 4) return; #define lr screen->last_rendered_window_char if (!lr.canvas || lr.ch != screen->display_window_char || lr.requested_height != requested_height) { free(lr.canvas); lr.canvas = NULL; lr.requested_height = requested_height; lr.height_px = requested_height; lr.ch = 0; lr.canvas = draw_single_ascii_char(screen->display_window_char, &lr.width_px, &lr.height_px); if (lr.height_px < 4 || lr.width_px < 4 || !lr.canvas) return; lr.ch = screen->display_window_char; } GLfloat width_gl = gl_size(lr.width_px, os_window->viewport_width); height_gl = gl_size(lr.height_px, os_window->viewport_height); left = xstart + (width - width_gl) / 2.f; left = clamp_position_to_nearest_pixel(left, os_window->viewport_width); right = left + width_gl; GLfloat top = ystart - (height - height_gl) / 2.f; top = clamp_position_to_nearest_pixel(top, os_window->viewport_height); GLfloat bottom = top - height_gl; bind_program(GRAPHICS_ALPHA_MASK_PROGRAM); ImageRenderData *ird = load_alpha_mask_texture(lr.width_px, lr.height_px, lr.canvas); #undef lr gpu_data_for_image(ird, left, top, right, bottom); glEnable(GL_BLEND); BLEND_PREMULT; glUniform1i(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.image, GRAPHICS_UNIT); color_type digit_color = colorprofile_to_color_with_fallback(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg, screen->color_profile->overridden.default_fg, screen->color_profile->configured.default_fg); color_vec3(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.amask_fg, digit_color); glUniform4f(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.amask_bg_premult, 0.f, 0.f, 0.f, 0.f); draw_graphics(GRAPHICS_ALPHA_MASK_PROGRAM, 0, ird, 0, 1, viewport_for_cells(crd)); glDisable(GL_BLEND); } static void draw_visual_bell_flash(GLfloat intensity, const CellRenderData *crd, Screen *screen) { glEnable(GL_BLEND); // BLEND_PREMULT glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); bind_program(TINT_PROGRAM); GLfloat attenuation = 0.4f; #define COLOR(name, fallback) colorprofile_to_color_with_fallback(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name, screen->color_profile->overridden.fallback, screen->color_profile->configured.fallback) const color_type flash = !IS_SPECIAL_COLOR(highlight_bg) ? COLOR(visual_bell_color, highlight_bg) : COLOR(visual_bell_color, default_fg); #undef COLOR #define C(shift) srgb_color((flash >> shift) & 0xFF) const GLfloat r = C(16), g = C(8), b = C(0); const GLfloat max_channel = r > g ? (r > b ? r : b) : (g > b ? g : b); #undef C #define C(x) (x * intensity * attenuation) if (max_channel > 0.45) attenuation = 0.6f; // light color glUniform4f(tint_program_layout.uniforms.tint_color, C(r), C(g), C(b), C(1)); #undef C glUniform4f(tint_program_layout.uniforms.edges, crd->gl.xstart, crd->gl.ystart - crd->gl.height, crd->gl.xstart + crd->gl.width, crd->gl.ystart); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDisable(GL_BLEND); } static void draw_cells_interleaved(ssize_t vao_idx, Screen *screen, OSWindow *w, const CellRenderData *crd, GraphicsRenderData grd, const WindowLogoRenderData *wl) { glEnable(GL_BLEND); BLEND_ONTO_OPAQUE; // draw background for all cells if (!has_bgimage(w)) { bind_program(CELL_BG_PROGRAM); glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].uniforms.draw_bg_bitfield, 3); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); } else if (OPT(background_tint) > 0) { draw_tint(false, screen, crd); BLEND_ONTO_OPAQUE; } if (grd.num_of_below_refs || has_bgimage(w) || wl) { if (wl) { draw_window_logo(vao_idx, w, wl, crd); BLEND_ONTO_OPAQUE; } if (grd.num_of_below_refs) draw_graphics( GRAPHICS_PROGRAM, vao_idx, grd.images, 0, grd.num_of_below_refs, viewport_for_cells(crd)); bind_program(CELL_BG_PROGRAM); // draw background for non-default bg cells glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].uniforms.draw_bg_bitfield, 2); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); } if (grd.num_of_negative_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, grd.images, grd.num_of_below_refs, grd.num_of_negative_refs, viewport_for_cells(crd)); bind_program(CELL_SPECIAL_PROGRAM); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); bind_program(CELL_FG_PROGRAM); BLEND_PREMULT; glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); BLEND_ONTO_OPAQUE; if (grd.num_of_positive_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, grd.images, grd.num_of_negative_refs + grd.num_of_below_refs, grd.num_of_positive_refs, viewport_for_cells(crd)); glDisable(GL_BLEND); } static void draw_cells_interleaved_premult(ssize_t vao_idx, Screen *screen, OSWindow *os_window, const CellRenderData *crd, GraphicsRenderData grd, const WindowLogoRenderData *wl) { if (OPT(background_tint) > 0.f) { glEnable(GL_BLEND); draw_tint(true, screen, crd); glDisable(GL_BLEND); } bind_program(CELL_BG_PROGRAM); if (!has_bgimage(os_window)) { // draw background for all cells glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].uniforms.draw_bg_bitfield, 3); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); } glEnable(GL_BLEND); BLEND_PREMULT; if (grd.num_of_below_refs || has_bgimage(os_window) || wl) { if (wl) { draw_window_logo(vao_idx, os_window, wl, crd); BLEND_PREMULT; } if (grd.num_of_below_refs) draw_graphics( GRAPHICS_PREMULT_PROGRAM, vao_idx, grd.images, 0, grd.num_of_below_refs, viewport_for_cells(crd)); bind_program(CELL_BG_PROGRAM); // Draw background for non-default bg cells glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].uniforms.draw_bg_bitfield, 2); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); } else { // Apply background_opacity glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].uniforms.draw_bg_bitfield, 0); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); } if (grd.num_of_negative_refs) { draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, grd.images, grd.num_of_below_refs, grd.num_of_negative_refs, viewport_for_cells(crd)); } bind_program(CELL_SPECIAL_PROGRAM); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); bind_program(CELL_FG_PROGRAM); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); if (grd.num_of_positive_refs) draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, grd.images, grd.num_of_negative_refs + grd.num_of_below_refs, grd.num_of_positive_refs, viewport_for_cells(crd)); glDisable(GL_BLEND); } void blank_canvas(float background_opacity, color_type color) { // See https://github.com/glfw/glfw/issues/1538 for why we use pre-multiplied alpha #define C(shift) srgb_color((color >> shift) & 0xFF) glClearColor(C(16), C(8), C(0), background_opacity); #undef C glClear(GL_COLOR_BUFFER_BIT); } bool send_cell_data_to_gpu(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, OSWindow *os_window) { bool changed = false; if (os_window->fonts_data) { if (cell_prepare_to_render(vao_idx, screen, xstart, ystart, dx, dy, os_window->fonts_data)) changed = true; } return changed; } static Animation *default_visual_bell_animation = NULL; static float get_visual_bell_intensity(Screen *screen) { if (screen->start_visual_bell_at > 0) { if (!default_visual_bell_animation) { default_visual_bell_animation = alloc_animation(); if (!default_visual_bell_animation) fatal("Out of memory"); add_cubic_bezier_animation(default_visual_bell_animation, 0, 1, EASE_IN_OUT); add_cubic_bezier_animation(default_visual_bell_animation, 1, 0, EASE_IN_OUT); } const monotonic_t progress = monotonic() - screen->start_visual_bell_at; const monotonic_t duration = OPT(visual_bell_duration) / 2; if (progress <= duration) { Animation *a = animation_is_valid(OPT(animation.visual_bell)) ? OPT(animation.visual_bell) : default_visual_bell_animation; return (float)apply_easing_curve(a, progress / (double)duration, duration); } screen->start_visual_bell_at = 0; } return 0.0f; } void draw_cells(ssize_t vao_idx, const WindowRenderData *srd, OSWindow *os_window, bool is_active_window, bool is_tab_bar, bool is_single_window, Window *window) { float x_ratio = 1., y_ratio = 1.; if (os_window->live_resize.in_progress) { x_ratio = (float) os_window->viewport_width / (float) os_window->live_resize.width; y_ratio = (float) os_window->viewport_height / (float) os_window->live_resize.height; } Screen *screen = srd->screen; CELL_BUFFERS; CellRenderData crd = { .gl={.xstart = srd->xstart, .ystart = srd->ystart, .dx = srd->dx * x_ratio, .dy = srd->dy * y_ratio}, .x_ratio=x_ratio, .y_ratio=y_ratio }; crd.gl.width = crd.gl.dx * screen->columns; crd.gl.height = crd.gl.dy * screen->lines; cell_update_uniform_block(vao_idx, screen, uniform_buffer, &crd, &screen->cursor_render_info, os_window); bind_vao_uniform_buffer(vao_idx, uniform_buffer, cell_program_layouts[CELL_PROGRAM].render_data.index); bind_vertex_array(vao_idx); // We draw with inactive text alpha if: // - We're not drawing the tab bar // - There's only a single window and the os window is not focused // - There are multiple windows and the current window is not active float current_inactive_text_alpha = is_tab_bar || (!is_single_window && is_active_window) || (is_single_window && screen->cursor_render_info.is_focused) ? 1.0f : (float)OPT(inactive_text_alpha); set_cell_uniforms(current_inactive_text_alpha, screen->reload_all_gpu_data); screen->reload_all_gpu_data = false; bool has_underlying_image = has_bgimage(os_window); WindowLogoRenderData *wl; if (window && (wl = &window->window_logo) && wl->id && (wl->instance = find_window_logo(global_state.all_window_logos, wl->id)) && wl->instance && wl->instance->load_from_disk_ok) { has_underlying_image = true; set_on_gpu_state(window->window_logo.instance, true); } else wl = NULL; ImageRenderData *scaled_render_data = NULL; GraphicsManager *grman = screen->paused_rendering.expires_at && screen->paused_rendering.grman ? screen->paused_rendering.grman : screen->grman; GraphicsRenderData grd = grman_render_data(grman); if (os_window->live_resize.in_progress && grd.count && (crd.x_ratio != 1 || crd.y_ratio != 1)) { scaled_render_data = malloc(sizeof(scaled_render_data[0]) * grd.count); if (scaled_render_data) { memcpy(scaled_render_data, grd.images, sizeof(scaled_render_data[0]) * grd.count); grd.images = scaled_render_data; for (size_t i = 0; i < grd.count; i++) scale_rendered_graphic(grd.images + i, srd->xstart, srd->ystart, crd.x_ratio, crd.y_ratio); } } bool use_premult = false; has_underlying_image |= grd.num_of_below_refs > 0 || grd.num_of_negative_refs > 0; if (os_window->is_semi_transparent) { if (has_underlying_image) { draw_cells_interleaved_premult(vao_idx, screen, os_window, &crd, grd, wl); use_premult = true; } else draw_cells_simple(vao_idx, screen, &crd, grd, os_window->is_semi_transparent); } else { if (has_underlying_image) draw_cells_interleaved(vao_idx, screen, os_window, &crd, grd, wl); else draw_cells_simple(vao_idx, screen, &crd, grd, os_window->is_semi_transparent); } draw_scroll_indicator(use_premult, screen, &crd); if (screen->start_visual_bell_at) { GLfloat intensity = get_visual_bell_intensity(screen); if (intensity > 0.0f) draw_visual_bell_flash(intensity, &crd, screen); } if (window && screen->display_window_char) draw_window_number(os_window, screen, &crd, window); if (OPT(show_hyperlink_targets) && window && screen->current_hyperlink_under_mouse.id && !is_mouse_hidden(os_window)) draw_hyperlink_target(os_window, screen, &crd, window); free(scaled_render_data); } // }}} // Borders {{{ typedef struct BorderProgramLayout { BorderUniforms uniforms; } BorderProgramLayout; static BorderProgramLayout border_program_layout; static void init_borders_program(void) { get_uniform_locations_border(BORDERS_PROGRAM, &border_program_layout.uniforms); bind_program(BORDERS_PROGRAM); glUniform1fv(border_program_layout.uniforms.gamma_lut, 256, srgb_lut); } ssize_t create_border_vao(void) { ssize_t vao_idx = create_vao(); add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER); add_attribute_to_vao(BORDERS_PROGRAM, vao_idx, "rect", /*size=*/4, /*dtype=*/GL_FLOAT, /*stride=*/sizeof(BorderRect), /*offset=*/(void*)offsetof(BorderRect, left), /*divisor=*/1); add_attribute_to_vao(BORDERS_PROGRAM, vao_idx, "rect_color", /*size=*/1, /*dtype=*/GL_UNSIGNED_INT, /*stride=*/sizeof(BorderRect), /*offset=*/(void*)(offsetof(BorderRect, color)), /*divisor=*/1); return vao_idx; } void draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_buf, bool rect_data_is_dirty, uint32_t viewport_width, uint32_t viewport_height, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg, OSWindow *w) { float background_opacity = w->is_semi_transparent ? w->background_opacity: 1.0f; float tint_opacity = background_opacity; float tint_premult = background_opacity; bind_vertex_array(vao_idx); if (has_bgimage(w)) { glEnable(GL_BLEND); BLEND_ONTO_OPAQUE; draw_background_image(w); BLEND_ONTO_OPAQUE; background_opacity = 1.0f; tint_opacity = OPT(background_tint) * OPT(background_tint_gaps); tint_premult = w->is_semi_transparent ? OPT(background_tint) : 1.0f; } if (num_border_rects) { bind_program(BORDERS_PROGRAM); if (rect_data_is_dirty) { const size_t sz = sizeof(BorderRect) * num_border_rects; void *borders_buf_address = alloc_and_map_vao_buffer(vao_idx, sz, 0, GL_STATIC_DRAW, GL_WRITE_ONLY); if (borders_buf_address) memcpy(borders_buf_address, rect_buf, sz); unmap_vao_buffer(vao_idx, 0); } color_type default_bg = (num_visible_windows > 1 && !all_windows_have_same_bg) ? OPT(background) : active_window_bg; GLuint colors[9] = { default_bg, OPT(active_border_color), OPT(inactive_border_color), 0, OPT(bell_border_color), OPT(tab_bar_background), OPT(tab_bar_margin_color), w->tab_bar_edge_color.left, w->tab_bar_edge_color.right }; glUniform1uiv(border_program_layout.uniforms.colors, arraysz(colors), colors); glUniform1f(border_program_layout.uniforms.background_opacity, background_opacity); glUniform1f(border_program_layout.uniforms.tint_opacity, tint_opacity); glUniform1f(border_program_layout.uniforms.tint_premult, tint_premult); glUniform2ui(border_program_layout.uniforms.viewport, viewport_width, viewport_height); if (has_bgimage(w)) { if (w->is_semi_transparent) { BLEND_PREMULT; } else { BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT; } } glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, num_border_rects); unbind_program(); } unbind_vertex_array(); if (has_bgimage(w)) glDisable(GL_BLEND); } // }}} // Cursor Trail {{{ typedef struct { TrailUniforms uniforms; } TrailProgramLayout; static TrailProgramLayout trail_program_layout; static void init_trail_program(void) { get_uniform_locations_trail(TRAIL_PROGRAM, &trail_program_layout.uniforms); } void draw_cursor_trail(CursorTrail *trail, Window *active_window) { bind_program(TRAIL_PROGRAM); glEnable(GL_BLEND); BLEND_ONTO_OPAQUE; glUniform4fv(trail_program_layout.uniforms.x_coords, 1, trail->corner_x); glUniform4fv(trail_program_layout.uniforms.y_coords, 1, trail->corner_y); glUniform2fv(trail_program_layout.uniforms.cursor_edge_x, 1, trail->cursor_edge_x); glUniform2fv(trail_program_layout.uniforms.cursor_edge_y, 1, trail->cursor_edge_y); color_vec3(trail_program_layout.uniforms.trail_color, active_window ? active_window->render_data.screen->last_rendered.cursor_bg : OPT(foreground)); glUniform1fv(trail_program_layout.uniforms.trail_opacity, 1, &trail->opacity); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDisable(GL_BLEND); unbind_program(); } // }}} // Python API {{{ static bool attach_shaders(PyObject *sources, GLuint program_id, GLenum shader_type) { RAII_ALLOC(const GLchar*, c_sources, calloc(PyTuple_GET_SIZE(sources), sizeof(GLchar*))); for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(sources); i++) { PyObject *temp = PyTuple_GET_ITEM(sources, i); if (!PyUnicode_Check(temp)) { PyErr_SetString(PyExc_TypeError, "shaders must be strings"); return false; } c_sources[i] = PyUnicode_AsUTF8(temp); } GLuint shader_id = compile_shaders(shader_type, PyTuple_GET_SIZE(sources), c_sources); if (shader_id == 0) return false; glAttachShader(program_id, shader_id); glDeleteShader(shader_id); return true; } static PyObject* compile_program(PyObject UNUSED *self, PyObject *args) { PyObject *vertex_shaders, *fragment_shaders; int which, allow_recompile = 0; if (!PyArg_ParseTuple(args, "iO!O!|p", &which, &PyTuple_Type, &vertex_shaders, &PyTuple_Type, &fragment_shaders, &allow_recompile)) return NULL; if (which < 0 || which >= NUM_PROGRAMS) { PyErr_Format(PyExc_ValueError, "Unknown program: %d", which); return NULL; } Program *program = program_ptr(which); if (program->id != 0) { if (allow_recompile) { glDeleteProgram(program->id); program->id = 0; } else { PyErr_SetString(PyExc_ValueError, "program already compiled"); return NULL; } } #define fail_compile() { glDeleteProgram(program->id); return NULL; } program->id = glCreateProgram(); if (!attach_shaders(vertex_shaders, program->id, GL_VERTEX_SHADER)) fail_compile(); if (!attach_shaders(fragment_shaders, program->id, GL_FRAGMENT_SHADER)) fail_compile(); glLinkProgram(program->id); GLint ret = GL_FALSE; glGetProgramiv(program->id, GL_LINK_STATUS, &ret); if (ret != GL_TRUE) { GLsizei len; static char glbuf[4096]; glGetProgramInfoLog(program->id, sizeof(glbuf), &len, glbuf); PyErr_Format(PyExc_ValueError, "Failed to link GLSL shaders:\n%s", glbuf); fail_compile(); } #undef fail_compile init_uniforms(which); return Py_BuildValue("I", program->id); } #define PYWRAP0(name) static PyObject* py##name(PYNOARG) #define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args) #define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL; #define ONE_INT(name) PYWRAP1(name) { name(PyLong_AsSsize_t(args)); Py_RETURN_NONE; } #define TWO_INT(name) PYWRAP1(name) { int a, b; PA("ii", &a, &b); name(a, b); Py_RETURN_NONE; } #define NO_ARG(name) PYWRAP0(name) { name(); Py_RETURN_NONE; } #define NO_ARG_INT(name) PYWRAP0(name) { return PyLong_FromSsize_t(name()); } ONE_INT(bind_program) NO_ARG(unbind_program) PYWRAP0(create_vao) { int ans = create_vao(); if (ans < 0) return NULL; return Py_BuildValue("i", ans); } ONE_INT(bind_vertex_array) NO_ARG(unbind_vertex_array) TWO_INT(unmap_vao_buffer) NO_ARG(init_borders_program) NO_ARG(init_cell_program) NO_ARG(init_trail_program) static PyObject* sprite_map_set_limits(PyObject UNUSED *self, PyObject *args) { unsigned int w, h; if(!PyArg_ParseTuple(args, "II", &w, &h)) return NULL; sprite_tracker_set_limits(w, h); max_texture_size = w; max_array_texture_layers = h; Py_RETURN_NONE; } #define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL} #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} static PyMethodDef module_methods[] = { M(compile_program, METH_VARARGS), M(sprite_map_set_limits, METH_VARARGS), MW(create_vao, METH_NOARGS), MW(bind_vertex_array, METH_O), MW(unbind_vertex_array, METH_NOARGS), MW(unmap_vao_buffer, METH_VARARGS), MW(bind_program, METH_O), MW(unbind_program, METH_NOARGS), MW(init_borders_program, METH_NOARGS), MW(init_cell_program, METH_NOARGS), MW(init_trail_program, METH_NOARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; static void finalize(void) { default_visual_bell_animation = free_animation(default_visual_bell_animation); } bool init_shaders(PyObject *module) { #define C(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return false; } C(CELL_PROGRAM); C(CELL_BG_PROGRAM); C(CELL_SPECIAL_PROGRAM); C(CELL_FG_PROGRAM); C(BORDERS_PROGRAM); C(GRAPHICS_PROGRAM); C(GRAPHICS_PREMULT_PROGRAM); C(GRAPHICS_ALPHA_MASK_PROGRAM); C(BGIMAGE_PROGRAM); C(TINT_PROGRAM); C(TRAIL_PROGRAM); C(GLSL_VERSION); C(GL_VERSION); C(GL_VENDOR); C(GL_SHADING_LANGUAGE_VERSION); C(GL_RENDERER); C(GL_TRIANGLE_FAN); C(GL_TRIANGLE_STRIP); C(GL_TRIANGLES); C(GL_LINE_LOOP); C(GL_COLOR_BUFFER_BIT); C(GL_VERTEX_SHADER); C(GL_FRAGMENT_SHADER); C(GL_TRUE); C(GL_FALSE); C(GL_COMPILE_STATUS); C(GL_LINK_STATUS); C(GL_TEXTURE0); C(GL_TEXTURE1); C(GL_TEXTURE2); C(GL_TEXTURE3); C(GL_TEXTURE4); C(GL_TEXTURE5); C(GL_TEXTURE6); C(GL_TEXTURE7); C(GL_TEXTURE8); C(GL_MAX_ARRAY_TEXTURE_LAYERS); C(GL_TEXTURE_BINDING_BUFFER); C(GL_MAX_TEXTURE_BUFFER_SIZE); C(GL_MAX_TEXTURE_SIZE); C(GL_TEXTURE_2D_ARRAY); C(GL_LINEAR); C(GL_CLAMP_TO_EDGE); C(GL_NEAREST); C(GL_TEXTURE_MIN_FILTER); C(GL_TEXTURE_MAG_FILTER); C(GL_TEXTURE_WRAP_S); C(GL_TEXTURE_WRAP_T); C(GL_UNPACK_ALIGNMENT); C(GL_R8); C(GL_RED); C(GL_UNSIGNED_BYTE); C(GL_UNSIGNED_SHORT); C(GL_R32UI); C(GL_RGB32UI); C(GL_RGBA); C(GL_TEXTURE_BUFFER); C(GL_STATIC_DRAW); C(GL_STREAM_DRAW); C(GL_DYNAMIC_DRAW); C(GL_SRC_ALPHA); C(GL_ONE_MINUS_SRC_ALPHA); C(GL_WRITE_ONLY); C(GL_READ_ONLY); C(GL_READ_WRITE); C(GL_BLEND); C(GL_FLOAT); C(GL_UNSIGNED_INT); C(GL_ARRAY_BUFFER); C(GL_UNIFORM_BUFFER); #undef C if (PyModule_AddFunctions(module, module_methods) != 0) return false; register_at_exit_cleanup_func(SHADERS_CLEANUP_FUNC, finalize); return true; } // }}} kitty-0.41.1/kitty/shaders.py0000664000175000017510000002036414773370543015463 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2023, Kovid Goyal import re from collections.abc import Callable, Iterator from functools import lru_cache, partial from itertools import count from typing import Any, Literal, NamedTuple, Optional from .constants import read_kitty_resource from .fast_data_types import ( BGIMAGE_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, CELL_PROGRAM, CELL_SPECIAL_PROGRAM, DECORATION, DECORATION_MASK, DIM, GLSL_VERSION, GRAPHICS_ALPHA_MASK_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM, MARK, MARK_MASK, REVERSE, STRIKETHROUGH, TINT_PROGRAM, TRAIL_PROGRAM, compile_program, get_options, init_cell_program, init_trail_program, ) def identity(x: str) -> str: return x class CompileError(ValueError): pass class Program: include_pat: Optional['re.Pattern[str]'] = None filename_number_base: int = 7893000 def __init__(self, name: str, vertex_name: str = '', fragment_name: str = '') -> None: self.name = name self.filename_number_counter = count(self.filename_number_base + 1) self.filename_map: dict[str, int] = {} if Program.include_pat is None: Program.include_pat = re.compile(r'^#pragma\s+kitty_include_shader\s+<(.+?)>', re.MULTILINE) self.vertex_name = vertex_name or f'{name}_vertex.glsl' self.fragment_name = fragment_name or f'{name}_fragment.glsl' self.original_vertex_sources = tuple(self._load_sources(self.vertex_name, set())) self.original_fragment_sources = tuple(self._load_sources(self.fragment_name, set())) self.vertex_sources = self.original_vertex_sources self.fragment_sources = self.original_fragment_sources def _load_sources(self, name: str, seen: set[str], level: int = 0) -> Iterator[str]: if level == 0: yield f'#version {GLSL_VERSION}\n' if name in seen: return seen.add(name) self.filename_map[name] = fnum = next(self.filename_number_counter) src = read_kitty_resource(name).decode('utf-8') pos = 0 lnum = 0 assert Program.include_pat is not None for m in Program.include_pat.finditer(src): prefix = src[pos:m.start()] if prefix: yield f'\n#line {lnum} {fnum}\n{prefix}' lnum += prefix.count('\n') iname = m.group(1) yield from self._load_sources(iname, seen, level+1) pos = m.end() if pos < len(src): yield f'\n#line {lnum} {fnum}\n{src[pos:]}' def apply_to_sources(self, vertex: Callable[[str], str] = identity, frag: Callable[[str], str] = identity) -> None: self.vertex_sources = self.original_vertex_sources if vertex is identity else tuple(map(vertex, self.original_vertex_sources)) self.fragment_sources = self.original_fragment_sources if frag is identity else tuple(map(frag, self.original_fragment_sources)) def compile(self, program_id: int, allow_recompile: bool = False) -> None: cerr: CompileError = CompileError() try: compile_program(program_id, self.vertex_sources, self.fragment_sources, allow_recompile) return except ValueError as err: lines = str(err).splitlines() msg = [] pat = re.compile(r'\b(' + str(self.filename_number_base).replace('0', r'\d') + r')\b') rmap = {str(v): k for k, v in self.filename_map.items()} def sub(m: 're.Match[str]') -> str: return rmap.get(m.group(1), m.group(1)) for line in lines: msg.append(pat.sub(sub, line)) cerr = CompileError('\n'.join(msg)) raise cerr @lru_cache(maxsize=64) def program_for(name: str) -> Program: return Program(name) class MultiReplacer: pat: Optional['re.Pattern[str]'] = None def __init__(self, **replacements: Any): self.replacements = {k: str(v) for k, v in replacements.items()} if MultiReplacer.pat is None: MultiReplacer.pat = re.compile(r'\{([A-Z_]+)\}') def _sub(self, m: 're.Match[str]') -> str: return self.replacements.get(m.group(1), m.group(1)) def __call__(self, src: str) -> str: assert self.pat is not None return self.pat.sub(self._sub, src) null_replacer = MultiReplacer() class TextFgOverrideThreshold(NamedTuple): value: float = 0 unit: Literal['%', 'ratio'] = '%' scaled_value: float = 0 class LoadShaderPrograms: text_fg_override_threshold: TextFgOverrideThreshold = TextFgOverrideThreshold() text_old_gamma: bool = False semi_transparent: bool = False cell_program_replacer: MultiReplacer = null_replacer @property def needs_recompile(self) -> bool: opts = get_options() return ( opts.text_fg_override_threshold != (self.text_fg_override_threshold.value, self.text_fg_override_threshold.unit) or (opts.text_composition_strategy == 'legacy') != self.text_old_gamma ) def recompile_if_needed(self) -> None: if self.needs_recompile: self(self.semi_transparent, allow_recompile=True) def __call__(self, semi_transparent: bool = False, allow_recompile: bool = False) -> None: self.semi_transparent = semi_transparent opts = get_options() self.text_old_gamma = opts.text_composition_strategy == 'legacy' text_fg_override_threshold: float = opts.text_fg_override_threshold[0] match opts.text_fg_override_threshold[1]: case '%': text_fg_override_threshold = max(0, min(text_fg_override_threshold, 100.0)) * 0.01 case 'ratio': text_fg_override_threshold = max(0, min(text_fg_override_threshold, 21.0)) self.text_fg_override_threshold = TextFgOverrideThreshold( opts.text_fg_override_threshold[0], opts.text_fg_override_threshold[1], text_fg_override_threshold) cell = program_for('cell') if self.cell_program_replacer is null_replacer: self.cell_program_replacer = MultiReplacer( REVERSE_SHIFT=REVERSE, STRIKE_SHIFT=STRIKETHROUGH, DIM_SHIFT=DIM, DECORATION_SHIFT=DECORATION, MARK_SHIFT=MARK, MARK_MASK=MARK_MASK, DECORATION_MASK=DECORATION_MASK, ) def resolve_cell_defines(which: str, src: str) -> str: r = self.cell_program_replacer.replacements r['WHICH_PHASE'] = f'PHASE_{which}' r['TRANSPARENT'] = '1' if semi_transparent else '0' r['DO_FG_OVERRIDE'] = '1' if self.text_fg_override_threshold.scaled_value else '0' r['FG_OVERRIDE_ALGO'] = '1' if self.text_fg_override_threshold.unit == '%' else '2' r['FG_OVERRIDE_THRESHOLD'] = str(self.text_fg_override_threshold.scaled_value) r['TEXT_NEW_GAMMA'] = '0' if self.text_old_gamma else '1' return self.cell_program_replacer(src) for which, p in { 'BOTH': CELL_PROGRAM, 'BACKGROUND': CELL_BG_PROGRAM, 'SPECIAL': CELL_SPECIAL_PROGRAM, 'FOREGROUND': CELL_FG_PROGRAM, }.items(): cell.apply_to_sources( vertex=partial(resolve_cell_defines, which), frag=partial(resolve_cell_defines, which), ) cell.compile(p, allow_recompile) graphics = program_for('graphics') def resolve_graphics_fragment_defines(which: str, f: str) -> str: return f.replace('#define ALPHA_TYPE', f'#define {which}', 1) for which, p in { 'SIMPLE': GRAPHICS_PROGRAM, 'PREMULT': GRAPHICS_PREMULT_PROGRAM, 'ALPHA_MASK': GRAPHICS_ALPHA_MASK_PROGRAM, }.items(): graphics.apply_to_sources(frag=partial(resolve_graphics_fragment_defines, which)) graphics.compile(p, allow_recompile) program_for('bgimage').compile(BGIMAGE_PROGRAM, allow_recompile) program_for('tint').compile(TINT_PROGRAM, allow_recompile) init_cell_program() program_for('trail').compile(TRAIL_PROGRAM, allow_recompile) init_trail_program() load_shader_programs = LoadShaderPrograms() kitty-0.41.1/kitty/shell_integration.py0000664000175000017510000001752514773370543017551 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import os import subprocess from collections.abc import Callable from contextlib import suppress from .constants import shell_integration_dir from .fast_data_types import get_options from .options.types import Options, defaults from .utils import log_error, which def setup_fish_env(env: dict[str, str], argv: list[str]) -> None: val = env.get('XDG_DATA_DIRS') env['KITTY_FISH_XDG_DATA_DIR'] = shell_integration_dir if not val: env['XDG_DATA_DIRS'] = shell_integration_dir else: dirs = list(filter(None, val.split(os.pathsep))) dirs.insert(0, shell_integration_dir) env['XDG_DATA_DIRS'] = os.pathsep.join(dirs) def is_new_zsh_install(env: dict[str, str], zdotdir: str | None) -> bool: # if ZDOTDIR is empty, zsh will read user rc files from / # if there aren't any, it'll run zsh-newuser-install # the latter will bail if there are rc files in $HOME if not zdotdir: zdotdir = env.get('HOME', os.path.expanduser('~')) assert isinstance(zdotdir, str) if zdotdir == '~': return True for q in ('.zshrc', '.zshenv', '.zprofile', '.zlogin'): if os.path.exists(os.path.join(zdotdir, q)): return False return True def get_zsh_zdotdir_from_global_zshenv(env: dict[str, str], argv: list[str]) -> str | None: exe = which(argv[0], only_system=True) or 'zsh' with suppress(Exception): return subprocess.check_output([exe, '--norcs', '--interactive', '-c', 'echo -n $ZDOTDIR'], env=env).decode('utf-8') return None def setup_zsh_env(env: dict[str, str], argv: list[str]) -> None: zdotdir = env.get('ZDOTDIR') if is_new_zsh_install(env, zdotdir): if zdotdir is None: # Try to get ZDOTDIR from /etc/zshenv, when all startup files are not present zdotdir = get_zsh_zdotdir_from_global_zshenv(env, argv) if zdotdir is None or is_new_zsh_install(env, zdotdir): return else: # dont prevent zsh-newuser-install from running # zsh-newuser-install never runs as root but we assume that it does return if zdotdir is not None: env['KITTY_ORIG_ZDOTDIR'] = zdotdir else: # KITTY_ORIG_ZDOTDIR can be set at this point if, for example, the global # zshenv overrides ZDOTDIR; we try to limit the damage in this case env.pop('KITTY_ORIG_ZDOTDIR', None) env['ZDOTDIR'] = os.path.join(shell_integration_dir, 'zsh') def setup_bash_env(env: dict[str, str], argv: list[str]) -> None: inject = {'1'} posix_env = rcfile = '' remove_args = set() expecting_multi_chars_opt = True expecting_option_arg = False interactive_opt = False expecting_file_arg = False file_arg_set = False for i in range(1, len(argv)): arg = argv[i] if expecting_file_arg: file_arg_set = True break if expecting_option_arg: expecting_option_arg = False continue if arg in ('-', '--'): if not expecting_file_arg: expecting_file_arg = True continue elif len(arg) > 1 and arg[1] != '-' and (arg[0] == '-' or arg.startswith('+O')): expecting_multi_chars_opt = False options = arg.lstrip('-+') # shopt option if 'O' in options: t = options.split('O', maxsplit=1) if not t[1]: expecting_option_arg = True options = t[0] # command string if 'c' in options: # non-interactive shell # also skip `bash -ic` interactive mode with command string return # read from stdin and follow with args if 's' in options: break # interactive option if 'i' in options: interactive_opt = True elif arg.startswith('--') and expecting_multi_chars_opt: if arg == '--posix': inject.add('posix') posix_env = env.get('ENV', '') remove_args.add(i) elif arg == '--norc': inject.add('no-rc') remove_args.add(i) elif arg == '--noprofile': inject.add('no-profile') remove_args.add(i) elif arg in ('--rcfile', '--init-file') and i + 1 < len(argv): expecting_option_arg = True rcfile = argv[i+1] remove_args |= {i, i+1} else: file_arg_set = True break if file_arg_set and not interactive_opt: # non-interactive shell return env['ENV'] = os.path.join(shell_integration_dir, 'bash', 'kitty.bash') env['KITTY_BASH_INJECT'] = ' '.join(inject) if posix_env: env['KITTY_BASH_POSIX_ENV'] = posix_env if rcfile: env['KITTY_BASH_RCFILE'] = rcfile for i in sorted(remove_args, reverse=True): del argv[i] if 'HISTFILE' not in env and 'posix' not in inject: # In POSIX mode the default history file is ~/.sh_history instead of ~/.bash_history env['HISTFILE'] = os.path.expanduser('~/.bash_history') env['KITTY_BASH_UNEXPORT_HISTFILE'] = '1' argv.insert(1, '--posix') def as_str_literal(x: str) -> str: parts = x.split("'") return '"\'"'.join(f"'{x}'" for x in parts) def as_fish_str_literal(x: str) -> str: x = x.replace('\\', '\\\\').replace("'", "\\'") return f"'{x}'" def posix_serialize_env(env: dict[str, str], prefix: str = 'builtin export', sep: str = '=') -> str: ans = [] for k, v in env.items(): ans.append(f'{prefix} {as_str_literal(k)}{sep}{as_str_literal(v)}') return '\n'.join(ans) def fish_serialize_env(env: dict[str, str]) -> str: ans = [] for k, v in env.items(): ans.append(f'set -gx {as_fish_str_literal(k)} {as_fish_str_literal(v)}') return '\n'.join(ans) ENV_MODIFIERS = { 'fish': setup_fish_env, 'zsh': setup_zsh_env, 'bash': setup_bash_env, } ENV_SERIALIZERS: dict[str, Callable[[dict[str, str]], str]] = { 'zsh': posix_serialize_env, 'bash': posix_serialize_env, 'fish': fish_serialize_env, } def get_supported_shell_name(path: str) -> str | None: name = os.path.basename(path) if name.lower().endswith('.exe'): name = name.rpartition('.')[0] if name.startswith('-'): name = name[1:] return name if name in ENV_MODIFIERS else None def shell_integration_allows_rc_modification(opts: Options) -> bool: return not (opts.shell_integration & {'disabled', 'no-rc'}) def serialize_env(path: str, env: dict[str, str]) -> str: if not env: return '' name = get_supported_shell_name(path) if not name: raise ValueError(f'{path} is not a supported shell') return ENV_SERIALIZERS[name](env) def get_effective_ksi_env_var(opts: Options | None = None) -> str: opts = opts or get_options() if 'disabled' in opts.shell_integration: return '' # Use the default when shell_integration is empty due to misconfiguration if 'invalid' in opts.shell_integration: return ' '.join(defaults.shell_integration) return ' '.join(opts.shell_integration) def modify_shell_environ(opts: Options, env: dict[str, str], argv: list[str]) -> None: shell = get_supported_shell_name(argv[0]) ksi = get_effective_ksi_env_var(opts) if shell is None or not ksi: return env['KITTY_SHELL_INTEGRATION'] = ksi if not shell_integration_allows_rc_modification(opts): return f = ENV_MODIFIERS.get(shell) if f is not None: try: f(env, argv) except Exception: import traceback traceback.print_exc() log_error(f'Failed to setup shell integration for: {shell}') kitty-0.41.1/kitty/shlex.c0000664000175000017510000002012714773370543014744 0ustar nileshnilesh/* * shlex.c * Copyright (C) 2023 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "data-types.h" typedef enum { NORMAL, WORD, STRING_WITHOUT_ESCAPES, STRING_WITH_ESCAPES, ANSI_C_QUOTED } State; typedef struct { PyObject_HEAD PyObject *src; Py_UCS4 *buf; Py_ssize_t src_sz, src_pos, word_start, buf_pos; int kind, support_ansi_c_quoting; void *src_data; State state; } Shlex; static PyObject * new_shlex_object(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { Shlex *self; self = (Shlex *)type->tp_alloc(type, 0); if (self) { PyObject *src; if (!PyArg_ParseTuple(args, "U|p", &src, &self->support_ansi_c_quoting)) return NULL; self->src_sz = PyUnicode_GET_LENGTH(src); self->buf = malloc(sizeof(Py_UCS4) * self->src_sz); if (self->buf) { self->src = src; Py_INCREF(src); self->kind = PyUnicode_KIND(src); self->src_data = PyUnicode_DATA(src); } else { Py_CLEAR(self); PyErr_NoMemory(); } } return (PyObject*) self; } static void dealloc(Shlex* self) { Py_CLEAR(self->src); free(self->buf); Py_TYPE(self)->tp_free((PyObject*)self); } #define WHITESPACE ' ': case '\n': case '\t': case '\r' #define STRING_WITH_ESCAPES_DELIM '"' #define STRING_WITHOUT_ESCAPES_DELIM '\'' #define ESCAPE_CHAR '\\' static void start_word(Shlex *self) { self->word_start = self->src_pos - 1; self->buf_pos = 0; } static void write_ch(Shlex *self, Py_UCS4 ch) { self->buf[self->buf_pos++] = ch; } static PyObject* get_word(Shlex *self) { Py_ssize_t pos = self->buf_pos; self->buf_pos = 0; return Py_BuildValue("nN", self->word_start, PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, self->buf, pos)); } static Py_UCS4 read_ch(Shlex *self) { Py_UCS4 nch = PyUnicode_READ(self->kind, self->src_data, self->src_pos); self->src_pos++; return nch; } static bool write_escape_ch(Shlex *self) { if (self->src_pos < self->src_sz) { Py_UCS4 nch = read_ch(self); write_ch(self, nch); return true; } return false; } static bool write_control_ch(Shlex *self) { if (self->src_pos >= self->src_sz) { PyErr_SetString(PyExc_ValueError, "Trailing \\c escape at end of input data"); return false; } Py_UCS4 ch = read_ch(self); write_ch(self, ch & 0x1f); return true; } static void read_valid_digits(Shlex *self, int max, char *output, bool(*is_valid)(Py_UCS4 ch)) { for (int i = 0; i < max && self->src_pos < self->src_sz; i++, output++) { Py_UCS4 ch = read_ch(self); if (!is_valid(ch)) { self->src_pos--; break; } *output = ch; } } static bool is_octal_digit(Py_UCS4 ch) { return '0' <= ch && ch <= '7'; } static bool is_hex_digit(Py_UCS4 ch) { return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F'); } static void write_octal_ch(Shlex *self, Py_UCS4 ch) { char chars[4] = {ch, 0, 0, 0}; read_valid_digits(self, 2, chars + 1, is_octal_digit); write_ch(self, strtol(chars, NULL, 8)); } static bool write_unicode_ch(Shlex *self, int max) { char chars[16] = {0}; read_valid_digits(self, max, chars, is_hex_digit); if (!chars[0]) { PyErr_SetString(PyExc_ValueError, "Trailing unicode escape at end of input data"); return false; } write_ch(self, strtol(chars, NULL, 16)); return true; } static bool write_ansi_escape_ch(Shlex *self) { if (self->src_pos >= self->src_sz) { PyErr_SetString(PyExc_ValueError, "Trailing backslash at end of input data"); return false; } Py_UCS4 ch = read_ch(self); switch(ch) { case 'a': write_ch(self, '\a'); return true; case 'b': write_ch(self, '\b'); return true; case 'e': case 'E': write_ch(self, 0x1b); return true; case 'f': write_ch(self, '\f'); return true; case 'n': write_ch(self, '\n'); return true; case 'r': write_ch(self, '\r'); return true; case 't': write_ch(self, '\t'); return true; case 'v': write_ch(self, '\v'); return true; case '\\': write_ch(self, '\\'); return true; case '\'': write_ch(self, '\''); return true; case '\"': write_ch(self, '\"'); return true; case '\?': write_ch(self, '\?'); return true; case 'c': return write_control_ch(self); case 'x': return write_unicode_ch(self, 2); case 'u': return write_unicode_ch(self, 4); case 'U': return write_unicode_ch(self, 8); START_ALLOW_CASE_RANGE case '0' ... '7': write_octal_ch(self, ch); return true; END_ALLOW_CASE_RANGE default: write_ch(self, ch); return true; } } static void set_state(Shlex *self, State s) { self->state = s; } static PyObject* next_word(Shlex *self, PyObject *args UNUSED) { #define write_escaped_or_fail() if (!write_escape_ch(self)) { PyErr_SetString(PyExc_ValueError, "Trailing backslash at end of input data"); return NULL; } Py_UCS4 prev_word_ch = 0; while (self->src_pos < self->src_sz) { Py_UCS4 ch = read_ch(self); switch(self->state) { case NORMAL: switch(ch) { case WHITESPACE: break; case STRING_WITH_ESCAPES_DELIM: set_state(self, STRING_WITH_ESCAPES); start_word(self); break; case STRING_WITHOUT_ESCAPES_DELIM: set_state(self, STRING_WITHOUT_ESCAPES); start_word(self); break; case ESCAPE_CHAR: start_word(self); write_escaped_or_fail(); set_state(self, WORD); break; default: set_state(self, WORD); start_word(self); write_ch(self, ch); prev_word_ch = ch; break; } break; case WORD: switch(ch) { case WHITESPACE: set_state(self, NORMAL); if (self->buf_pos) return get_word(self); break; case STRING_WITH_ESCAPES_DELIM: set_state(self, STRING_WITH_ESCAPES); break; case STRING_WITHOUT_ESCAPES_DELIM: if (self->support_ansi_c_quoting && prev_word_ch == '$') { self->buf_pos--; set_state(self, ANSI_C_QUOTED); } else set_state(self, STRING_WITHOUT_ESCAPES); break; case ESCAPE_CHAR: write_escaped_or_fail(); break; default: write_ch(self, ch); prev_word_ch = ch; break; } break; case STRING_WITHOUT_ESCAPES: switch(ch) { case STRING_WITHOUT_ESCAPES_DELIM: set_state(self, WORD); break; default: write_ch(self, ch); break; } break; case STRING_WITH_ESCAPES: switch(ch) { case STRING_WITH_ESCAPES_DELIM: set_state(self, WORD); break; case ESCAPE_CHAR: write_escaped_or_fail(); break; default: write_ch(self, ch); break; } break; case ANSI_C_QUOTED: switch(ch) { case STRING_WITHOUT_ESCAPES_DELIM: set_state(self, WORD); break; case ESCAPE_CHAR: if (!write_ansi_escape_ch(self)) return NULL; break; default: write_ch(self, ch); break; } break; } } switch (self->state) { case WORD: self->state = NORMAL; if (self->buf_pos) return get_word(self); break; case STRING_WITH_ESCAPES: case STRING_WITHOUT_ESCAPES: case ANSI_C_QUOTED: PyErr_SetString(PyExc_ValueError, "Unterminated string at the end of input"); self->state = NORMAL; return NULL; case NORMAL: break; } return Py_BuildValue("is", -1, ""); #undef write_escaped_or_fail } static PyMethodDef methods[] = { METHODB(next_word, METH_NOARGS), {NULL} /* Sentinel */ }; PyTypeObject Shlex_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.Shlex", .tp_basicsize = sizeof(Shlex), .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "Lexing like a shell", .tp_methods = methods, .tp_new = new_shlex_object, }; INIT_TYPE(Shlex) kitty-0.41.1/kitty/shm.py0000664000175000017510000001325514773370543014622 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2022, Kovid Goyal # This is present in the python stdlib (version 3.7) in # multiprocessing.shared_memory. However, it is crippled in various ways, most # notably using extremely small filenames. import errno import mmap import os import secrets import stat import struct from kitty.fast_data_types import SHM_NAME_MAX, shm_open, shm_unlink def make_filename(prefix: str) -> str: "Create a random filename for the shared memory object." # number of random bytes to use for name. Use a largeish value # to make double unlink safe. if not prefix.startswith('/'): # FreeBSD requires name to start with / prefix = '/' + prefix plen = len(prefix.encode('utf-8')) safe_length = min(plen + 64, SHM_NAME_MAX) if safe_length - plen < 2: raise OSError(errno.ENAMETOOLONG, f'SHM filename prefix {prefix} is too long') nbytes = (safe_length - plen) // 2 name = prefix + secrets.token_hex(nbytes) return name class SharedMemory: ''' Create or access randomly named shared memory. To create call with empty name and specific size. To access call with name only. WARNING: The actual size of the shared memory may be larger than the requested size. ''' _fd: int = -1 _name: str = '' _mmap: mmap.mmap | None = None _size: int = 0 size_fmt = '!I' num_bytes_for_size = struct.calcsize(size_fmt) def __init__( self, name: str = '', size: int = 0, readonly: bool = False, mode: int = stat.S_IREAD | stat.S_IWRITE, prefix: str = 'kitty-', unlink_on_exit: bool = False, ignore_close_failure: bool = False ): self.unlink_on_exit = unlink_on_exit self.ignore_close_failure = ignore_close_failure if size < 0: raise TypeError("'size' must be a non-negative integer") if size and name: raise TypeError('Cannot specify both name and size') if not name: flags = os.O_CREAT | os.O_EXCL if not size: raise TypeError("'size' must be > 0") else: flags = 0 flags |= os.O_RDONLY if readonly else os.O_RDWR tries = 30 while not name and tries > 0: tries -= 1 q = make_filename(prefix) try: self._fd = shm_open(q, flags, mode) name = q except FileExistsError: continue if tries <= 0: raise OSError(f'Failed to create a uniquely named SHM file, try shortening the prefix from: {prefix}') if self._fd < 0: self._fd = shm_open(name, flags, mode) self._name = name try: if flags & os.O_CREAT and size: if hasattr(os, 'posix_fallocate'): os.posix_fallocate(self._fd, 0, size) else: os.ftruncate(self._fd, size) self.stats = os.fstat(self._fd) size = self.stats.st_size self._mmap = mmap.mmap(self._fd, size, access=mmap.ACCESS_READ if readonly else mmap.ACCESS_WRITE) except OSError: self.unlink() raise self._size = size def read(self, sz: int = 0) -> bytes: if sz <= 0: sz = self.size return self.mmap.read(sz) def write(self, data: bytes) -> None: self.mmap.write(data) def tell(self) -> int: return self.mmap.tell() def seek(self, pos: int, whence: int = os.SEEK_SET) -> None: self.mmap.seek(pos, whence) def flush(self) -> None: self.mmap.flush() def write_data_with_size(self, data: str | bytes) -> None: if isinstance(data, str): data = data.encode('utf-8') sz = struct.pack(self.size_fmt, len(data)) self.write(sz) self.write(data) def read_data_with_size(self) -> bytes: sz = struct.unpack(self.size_fmt, self.read(self.num_bytes_for_size))[0] return self.read(sz) def __del__(self) -> None: try: self.close() except OSError: pass def __enter__(self) -> 'SharedMemory': return self def __exit__(self, *a: object) -> None: self.close() if self.unlink_on_exit: self.unlink() @property def size(self) -> int: return self._size @property def name(self) -> str: return self._name @property def mmap(self) -> mmap.mmap: ans = self._mmap if ans is None: raise RuntimeError('Cannot access the mmap of a closed shared memory object') return ans def fileno(self) -> int: return self._fd def __repr__(self) -> str: return f'{self.__class__.__name__}({self.name!r}, size={self.size})' def close(self) -> None: """Closes access to the shared memory from this instance but does not destroy the shared memory block.""" if self._mmap is not None: try: self._mmap.close() except BufferError: if not self.ignore_close_failure: raise self._mmap = None if self._fd >= 0: os.close(self._fd) self._fd = -1 def unlink(self) -> None: """Requests that the underlying shared memory block be destroyed. In order to ensure proper cleanup of resources, unlink should be called once (and only once) across all processes which have access to the shared memory block.""" if self._name: try: shm_unlink(self._name) except FileNotFoundError: pass self._name = '' kitty-0.41.1/kitty/short_uuid.py0000664000175000017510000000445014773370543016215 0ustar nileshnilesh#!/usr/bin/env python # License: GPLv3 Copyright: 2021, Kovid Goyal import math import string import uuid as _uuid from collections.abc import Sequence def num_to_string(number: int, alphabet: Sequence[str], alphabet_len: int, pad_to_length: int | None = None) -> str: ans = [] number = max(0, number) while number: number, digit = divmod(number, alphabet_len) ans.append(alphabet[digit]) if pad_to_length is not None and pad_to_length > len(ans): ans.append(alphabet[0] * (pad_to_length - len(ans))) return ''.join(ans) def string_to_num(string: str, alphabet_map: dict[str, int], alphabet_len: int) -> int: ans = 0 for char in reversed(string): ans = ans * alphabet_len + alphabet_map[char] return ans escape_code_safe_alphabet = string.ascii_letters + string.digits + string.punctuation + ' ' human_alphabet = (string.digits + string.ascii_letters)[2:] class ShortUUID: def __init__(self, alphabet: str = human_alphabet): self.alphabet = tuple(sorted(alphabet)) self.alphabet_len = len(self.alphabet) self.alphabet_map = {c: i for i, c in enumerate(self.alphabet)} self.uuid_pad_len = int(math.ceil(math.log(1 << 128, self.alphabet_len))) def uuid4(self, pad_to_length: int | None = None) -> str: if pad_to_length is None: pad_to_length = self.uuid_pad_len return num_to_string(_uuid.uuid4().int, self.alphabet, self.alphabet_len, pad_to_length) def uuid5(self, namespace: _uuid.UUID, name: str, pad_to_length: int | None = None) -> str: if pad_to_length is None: pad_to_length = self.uuid_pad_len return num_to_string(_uuid.uuid5(namespace, name).int, self.alphabet, self.alphabet_len, pad_to_length) def decode(self, encoded: str) -> _uuid.UUID: return _uuid.UUID(int=string_to_num(encoded, self.alphabet_map, self.alphabet_len)) _global_instance = ShortUUID() uuid4 = _global_instance.uuid4 uuid5 = _global_instance.uuid5 decode = _global_instance.decode _escape_code_instance: ShortUUID | None = None def uuid4_for_escape_code() -> str: global _escape_code_instance if _escape_code_instance is None: _escape_code_instance = ShortUUID(escape_code_safe_alphabet) return _escape_code_instance.uuid4() kitty-0.41.1/kitty/simd-string-128.c0000664000175000017510000000030714773370543016367 0ustar nileshnilesh/* * simd-string-128.c * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #define KITTY_SIMD_LEVEL 128 #include "simd-string-impl.h" kitty-0.41.1/kitty/simd-string-256.c0000664000175000017510000000030714773370543016371 0ustar nileshnilesh/* * simd-string-128.c * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #define KITTY_SIMD_LEVEL 256 #include "simd-string-impl.h" kitty-0.41.1/kitty/simd-string-impl.h0000664000175000017510000010343014773370543017024 0ustar nileshnilesh/* * Copyright (C) 2023 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" #include "simd-string.h" #include #ifndef KITTY_SIMD_LEVEL #define KITTY_SIMD_LEVEL 128 #endif #define CONCAT(A, B) A##B #define CONCAT_EXPAND(A, B) CONCAT(A,B) #define FUNC(name) CONCAT_EXPAND(name##_, KITTY_SIMD_LEVEL) #ifdef KITTY_NO_SIMD #define NOSIMD { fatal("No SIMD implementations for this CPU"); } bool FUNC(utf8_decode_to_esc)(UTF8Decoder *d UNUSED, const uint8_t *src UNUSED, size_t src_sz UNUSED) NOSIMD const uint8_t* FUNC(find_either_of_two_bytes)(const uint8_t *haystack UNUSED, const size_t sz UNUSED, const uint8_t a UNUSED, const uint8_t b UNUSED) NOSIMD void FUNC(xor_data64)(const uint8_t key[64] UNUSED, uint8_t* data UNUSED, const size_t data_sz UNUSED) NOSIMD #undef NOSIMD #else #include "charsets.h" // Boilerplate {{{ START_IGNORE_DIAGNOSTIC("-Wfloat-conversion") START_IGNORE_DIAGNOSTIC("-Wpedantic") #if defined(__clang__) && __clang_major__ > 13 _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wbitwise-instead-of-logical\"") #endif #include #include #if defined(__clang__) && __clang_major__ > 13 _Pragma("clang diagnostic pop") #endif END_IGNORE_DIAGNOSTIC END_IGNORE_DIAGNOSTIC #ifndef _MM_SHUFFLE #define _MM_SHUFFLE(z, y, x, w) (((z) << 6) | ((y) << 4) | ((x) << 2) | (w)) #endif #define integer_t CONCAT_EXPAND(CONCAT_EXPAND(simde__m, KITTY_SIMD_LEVEL), i) #define shift_right_by_bytes128 simde_mm_srli_si128 #define is_zero FUNC(is_zero) #if KITTY_SIMD_LEVEL == 128 #define set1_epi8(x) simde_mm_set1_epi8((char)(x)) #define set_epi8 simde_mm_set_epi8 #define add_epi8 simde_mm_add_epi8 #define load_unaligned simde_mm_loadu_si128 #define load_aligned(x) simde_mm_load_si128((const integer_t*)(x)) #define store_unaligned simde_mm_storeu_si128 #define store_aligned(dest, vec) simde_mm_store_si128((integer_t*)dest, vec) #define cmpeq_epi8 simde_mm_cmpeq_epi8 #define cmplt_epi8 simde_mm_cmplt_epi8 #define cmpgt_epi8 simde_mm_cmpgt_epi8 #define or_si simde_mm_or_si128 #define and_si simde_mm_and_si128 #define xor_si simde_mm_xor_si128 #define andnot_si simde_mm_andnot_si128 #define movemask_epi8 simde_mm_movemask_epi8 #define extract_lower_quarter_as_chars simde_mm_cvtepu8_epi32 #define blendv_epi8 simde_mm_blendv_epi8 #define shift_left_by_bits16 simde_mm_slli_epi16 #define shift_right_by_bits32 simde_mm_srli_epi32 #define shuffle_epi8 simde_mm_shuffle_epi8 #define numbered_bytes() set_epi8(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0) #define reverse_numbered_bytes() simde_mm_setr_epi8(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0) // output[i] = MAX(0, a[i] - b[1i]) #define subtract_saturate_epu8 simde_mm_subs_epu8 #define subtract_epi8 simde_mm_sub_epi8 #define create_zero_integer simde_mm_setzero_si128 #define create_all_ones_integer() simde_mm_set1_epi64x(-1) #define sum_bytes sum_bytes_128 #define zero_upper() static inline int FUNC(is_zero)(const integer_t a) { return simde_mm_testz_si128(a, a); } #define GA(LA) LA(1) LA(2) LA(3) LA(4) LA(5) LA(6) LA(7) LA(8) LA(9) LA(10) LA(11) LA(12) LA(13) LA(14) LA(15) #define L(n) case n: return simde_mm_srli_si128(A, n); #define R(n) case n: return simde_mm_slli_si128(A, n); #define shift_left_by_bytes_macro(A, n) { switch(n) { default: return A; GA(L) } } #define shift_right_by_bytes_macro(A, n) { switch(n) { default: return A; GA(R) } } static inline integer_t shift_right_by_bytes(const integer_t A, unsigned n) { shift_right_by_bytes_macro(A, n) } static inline integer_t shift_left_by_bytes(const integer_t A, unsigned n) { shift_left_by_bytes_macro(A, n) } #define w(dir, word, num) static inline integer_t shift_##dir##_by_##word(const integer_t A) { shift_##dir##_by_bytes_macro(A, num); } w(right, one_byte, 1) w(right, two_bytes, 2) w(right, four_bytes, 4) w(right, eight_bytes, 8) w(right, sixteen_bytes, 16) w(left, one_byte, 1) w(left, two_bytes, 2) w(left, four_bytes, 4) w(left, eight_bytes, 8) w(left, sixteen_bytes, 16) #undef w #undef GA #undef L #undef R #undef shift_right_by_bytes_macro #undef shift_left_by_bytes_macro #else #if defined(SIMDE_ARCH_AMD64) || defined(SIMDE_ARCH_X86) #define zero_upper _mm256_zeroupper #else #define zero_upper() #endif #define set1_epi8(x) simde_mm256_set1_epi8((char)(x)) #define set_epi8 simde_mm256_set_epi8 #define add_epi8 simde_mm256_add_epi8 #define load_unaligned simde_mm256_loadu_si256 #define load_aligned(x) simde_mm256_load_si256((const integer_t*)(x)) #define store_unaligned simde_mm256_storeu_si256 #define store_aligned(dest, vec) simde_mm256_store_si256((integer_t*)dest, vec) #define cmpeq_epi8 simde_mm256_cmpeq_epi8 #define cmpgt_epi8 simde_mm256_cmpgt_epi8 #define cmplt_epi8(a, b) cmpgt_epi8(b, a) #define or_si simde_mm256_or_si256 #define and_si simde_mm256_and_si256 #define xor_si simde_mm256_xor_si256 #define andnot_si simde_mm256_andnot_si256 #define movemask_epi8 simde_mm256_movemask_epi8 #define extract_lower_half_as_chars simde_mm256_cvtepu8_epi32 #define blendv_epi8 simde_mm256_blendv_epi8 #define subtract_saturate_epu8 simde_mm256_subs_epu8 #define subtract_epi8 simde_mm256_sub_epi8 #define shift_left_by_bits16 simde_mm256_slli_epi16 #define shift_right_by_bits32 simde_mm256_srli_epi32 #define create_zero_integer simde_mm256_setzero_si256 #define create_all_ones_integer() simde_mm256_set1_epi64x(-1) #define numbered_bytes() set_epi8(31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0) #define reverse_numbered_bytes() simde_mm256_setr_epi8(31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0) static inline int FUNC(is_zero)(const integer_t a) { return simde_mm256_testz_si256(a, a); } #define GA(LA) LA(1) LA(2) LA(3) LA(4) LA(5) LA(6) LA(7) LA(8) LA(9) LA(10) LA(11) LA(12) LA(13) LA(14) LA(15) #define GB(LA) LA(17) LA(18) LA(19) LA(20) LA(21) LA(22) LA(23) LA(24) LA(25) LA(26) LA(27) LA(28) LA(29) LA(30) LA(31) #define RA(n) case n: return simde_mm256_alignr_epi8(A, simde_mm256_permute2x128_si256(A, A, _MM_SHUFFLE(0, 0, 2, 0)), 16 - n); #define RB(n) case n: return simde_mm256_slli_si256(simde_mm256_permute2x128_si256(A, A, _MM_SHUFFLE(0, 0, 2, 0)), n - 16); \ #define shift_right_by_bytes_macro(A, n) { \ switch(n) { \ default: return A; \ GA(RA) \ case 16: return simde_mm256_permute2x128_si256(A, A, _MM_SHUFFLE(0, 0, 2, 0)); \ GB(RB) \ } \ } #define LA(n) case n: return simde_mm256_alignr_epi8(simde_mm256_permute2x128_si256(A, A, _MM_SHUFFLE(2, 0, 0, 1)), A, n); #define LB(n) case n: return simde_mm256_srli_si256(simde_mm256_permute2x128_si256(A, A, _MM_SHUFFLE(2, 0, 0, 1)), n - 16); #define shift_left_by_bytes_macro(A, n) { \ switch(n) { \ default: return A; \ GA(LA) \ case 16: return simde_mm256_permute2x128_si256(A, A, _MM_SHUFFLE(2, 0, 0, 1)); \ GB(LB) \ } \ } static inline integer_t shift_right_by_bytes(const integer_t A, unsigned n) { shift_right_by_bytes_macro(A, n) } static inline integer_t shift_left_by_bytes(const integer_t A, unsigned n) { shift_left_by_bytes_macro(A, n) } #define w(dir, word, num) static inline integer_t shift_##dir##_by_##word(const integer_t A) { shift_##dir##_by_bytes_macro(A, num); } w(right, one_byte, 1) w(right, two_bytes, 2) w(right, four_bytes, 4) w(right, eight_bytes, 8) w(right, sixteen_bytes, 16) w(left, one_byte, 1) w(left, two_bytes, 2) w(left, four_bytes, 4) w(left, eight_bytes, 8) w(left, sixteen_bytes, 16) #undef LA #undef LB #undef GA #undef GB #undef RA #undef RB #undef w #undef shift_right_by_bytes_macro #undef shift_left_by_bytes_macro static inline integer_t shuffle_impl256(const integer_t value, const integer_t shuffle) { #define K0 simde_mm256_setr_epi8( \ 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, \ -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16) #define K1 simde_mm256_setr_epi8( \ -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, \ 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70) return or_si( simde_mm256_shuffle_epi8(value, add_epi8(shuffle, K0)), simde_mm256_shuffle_epi8(simde_mm256_permute4x64_epi64(value, 0x4E), simde_mm256_add_epi8(shuffle, K1)) ); #undef K0 #undef K1 } #define shuffle_epi8 shuffle_impl256 #define sum_bytes(x) (sum_bytes_128(simde_mm256_extracti128_si256(x, 0)) + sum_bytes_128(simde_mm256_extracti128_si256(x, 1))) #endif #define print_register_as_bytes(r) { \ printf("%s:\n", #r); \ alignas(64) uint8_t data[sizeof(r)]; \ store_unaligned((integer_t*)data, r); \ for (unsigned i = 0; i < sizeof(integer_t); i++) { \ uint8_t ch = data[i]; \ if (' ' <= ch && ch < 0x7f) printf("_%c ", ch); else printf("%.2x ", ch); \ } \ printf("\n"); \ } #if 0 #define debug_register print_register_as_bytes #define debug printf #else #define debug_register(...) #define debug(...) #endif #if (defined(__arm64__) && defined(__APPLE__)) || defined(__aarch64__) // See https://community.arm.com/arm-community-blogs/b/infrastructure-solutions-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon static inline uint64_t movemask_arm128(const simde__m128i vec) { simde_uint8x8_t res = simde_vshrn_n_u16(simde_vreinterpretq_u16_u8((simde_uint8x16_t)vec), 4); return simde_vget_lane_u64(simde_vreinterpret_u64_u8(res), 0); } #if KITTY_SIMD_LEVEL == 128 static inline int bytes_to_first_match(const integer_t vec) { const uint64_t m = movemask_arm128(vec); return m ? (__builtin_ctzll(m) >> 2) : -1; } static inline int bytes_to_first_match_ignoring_leading_n(const integer_t vec, uintptr_t num_ignored) { uint64_t m = movemask_arm128(vec); m >>= num_ignored << 2; return m ? (__builtin_ctzll(m) >> 2) : -1; } #else static inline int bytes_to_first_match(const integer_t vec) { if (is_zero(vec)) return -1; simde__m128i v = simde_mm256_extracti128_si256(vec, 0); if (!simde_mm_testz_si128(v, v)) return __builtin_ctzll(movemask_arm128(v)) >> 2; v = simde_mm256_extracti128_si256(vec, 1); return 16 + (__builtin_ctzll(movemask_arm128(v)) >> 2); } static inline int bytes_to_first_match_ignoring_leading_n(const integer_t vec, uintptr_t num_ignored) { uint64_t m; int offset; if (num_ignored < 16) { m = ((uint64_t)movemask_arm128(simde_mm256_extracti128_si256(vec, 0))) >> (num_ignored << 2); if (m) return (__builtin_ctzll(m) >> 2); offset = 16 - num_ignored; num_ignored = 0; } else { num_ignored -= 16; offset = 0; } m = ((uint64_t)movemask_arm128(simde_mm256_extracti128_si256(vec, 1))) >> (num_ignored << 2); return m ? (offset + (__builtin_ctzll(m) >> 2)) : -1; } #endif #else static inline int bytes_to_first_match(const integer_t vec) { return is_zero(vec) ? -1 : __builtin_ctz(movemask_epi8(vec)); } static inline int bytes_to_first_match_ignoring_leading_n(const integer_t vec, const uintptr_t num_ignored) { uint32_t mask = movemask_epi8(vec); mask >>= num_ignored; return mask ? __builtin_ctz(mask) : -1; } #endif // }}} static inline integer_t zero_last_n_bytes(const integer_t vec, const char n) { integer_t mask = create_all_ones_integer(); mask = shift_left_by_bytes(mask, n); return and_si(mask, vec); } #define KEY_SIZE 64 void FUNC(xor_data64)(const uint8_t key[KEY_SIZE], uint8_t* data, const size_t data_sz) { // First process unaligned bytes at the start of data const uintptr_t unaligned_bytes = KEY_SIZE - ((uintptr_t)data & (KEY_SIZE - 1)); if (data_sz <= unaligned_bytes) { for (unsigned i = 0; i < data_sz; i++) data[i] ^= key[i]; return; } for (unsigned i = 0; i < unaligned_bytes; i++) data[i] ^= key[i]; // Rotate the key by unaligned_bytes alignas(sizeof(integer_t)) char aligned_key[KEY_SIZE]; memcpy(aligned_key, key + unaligned_bytes, KEY_SIZE - unaligned_bytes); memcpy(aligned_key + KEY_SIZE - unaligned_bytes, key, unaligned_bytes); const integer_t v1 = load_aligned(aligned_key), v2 = load_aligned(aligned_key + sizeof(integer_t)); #if KITTY_SIMD_LEVEL == 128 const integer_t v3 = load_aligned(aligned_key + 2*sizeof(integer_t)), v4 = load_aligned(aligned_key + 3 * sizeof(integer_t)); #endif // Process KEY_SIZE aligned chunks using SIMD integer_t d; uint8_t *p = data + unaligned_bytes, *limit = data + data_sz; const uintptr_t trailing_bytes = (uintptr_t)limit & (KEY_SIZE - 1); limit -= trailing_bytes; // p is aligned to first KEY_SIZE boundary >= data and limit is aligned to first KEY_SIZE boundary <= (data + data_sz) #define do_one(which) d = load_aligned(p); store_aligned(p, xor_si(which, d)); p += sizeof(integer_t); while (p < limit) { do_one(v1); do_one(v2); #if KITTY_SIMD_LEVEL == 128 do_one(v3); do_one(v4); #endif } #undef do_one // Process remaining trailing_bytes for (unsigned i = 0; i < trailing_bytes; i++) limit[i] ^= aligned_key[i]; zero_upper(); return; } #undef KEY_SIZE #define check_chunk() if (n > -1) { \ const uint8_t *ans = haystack + n; \ zero_upper(); \ return ans < limit ? ans : NULL; \ } #define find_match(haystack, sz, get_test_vec) { \ const uint8_t* limit = haystack + sz; \ integer_t chunk; int n; \ \ { /* first chunk which is possibly unaligned */ \ const uintptr_t addr = (uintptr_t)haystack; \ const uintptr_t unaligned_bytes = addr & (sizeof(integer_t) - 1); \ chunk = load_aligned(haystack - unaligned_bytes); /* this is an aligned load from the first aligned pos before haystack */ \ n = bytes_to_first_match_ignoring_leading_n(get_test_vec(chunk), unaligned_bytes); \ check_chunk(); \ haystack += sizeof(integer_t) - unaligned_bytes; \ } \ \ /* Iterate over aligned chunks */ \ for (; haystack < limit; haystack += sizeof(integer_t)) { \ chunk = load_aligned(haystack); \ n = bytes_to_first_match(get_test_vec(chunk)); \ check_chunk(); \ } \ zero_upper(); \ return NULL;\ } const uint8_t* FUNC(find_either_of_two_bytes)(const uint8_t *haystack, const size_t sz, const uint8_t a, const uint8_t b) { if (!sz) return NULL; const integer_t a_vec = set1_epi8(a), b_vec = set1_epi8(b); #define get_test_from_chunk(chunk) (or_si(cmpeq_epi8(chunk, a_vec), cmpeq_epi8(chunk, b_vec))) find_match(haystack, sz, get_test_from_chunk); #undef get_test_from_chunk } #undef check_chunk #define output_increment sizeof(integer_t)/sizeof(uint32_t) static inline void FUNC(output_plain_ascii)(UTF8Decoder *d, integer_t vec, size_t src_sz) { utf8_decoder_ensure_capacity(d, src_sz); #if KITTY_SIMD_LEVEL == 128 for (const uint32_t *p = d->output.storage + d->output.pos, *limit = p + src_sz; p < limit; p += output_increment) { const integer_t unpacked = extract_lower_quarter_as_chars(vec); store_unaligned((integer_t*)p, unpacked); vec = shift_right_by_bytes128(vec, output_increment); } #else const uint32_t *p = d->output.storage + d->output.pos, *limit = p + src_sz; simde__m128i x = simde_mm256_extracti128_si256(vec, 0); integer_t unpacked = extract_lower_half_as_chars(x); store_unaligned((integer_t*)p, unpacked); p += output_increment; if (p < limit) { x = shift_right_by_bytes128(x, output_increment); unpacked = extract_lower_half_as_chars(x); store_unaligned((integer_t*)p, unpacked); p += output_increment; if (p < limit) { x = simde_mm256_extracti128_si256(vec, 1); unpacked = extract_lower_half_as_chars(x); store_unaligned((integer_t*)p, unpacked); p += output_increment; if (p < limit) { x = shift_right_by_bytes128(x, output_increment); unpacked = extract_lower_half_as_chars(x); store_unaligned((integer_t*)p, unpacked); p += output_increment; } } } #endif d->output.pos += src_sz; } static inline void FUNC(output_unicode)(UTF8Decoder *d, integer_t output1, integer_t output2, integer_t output3, const size_t num_codepoints) { utf8_decoder_ensure_capacity(d, 64); #if KITTY_SIMD_LEVEL == 128 for (const uint32_t *p = d->output.storage + d->output.pos, *limit = p + num_codepoints; p < limit; p += output_increment) { const integer_t unpacked1 = extract_lower_quarter_as_chars(output1); const integer_t unpacked2 = shift_right_by_one_byte(extract_lower_quarter_as_chars(output2)); const integer_t unpacked3 = shift_right_by_two_bytes(extract_lower_quarter_as_chars(output3)); const integer_t unpacked = or_si(or_si(unpacked1, unpacked2), unpacked3); store_unaligned((integer_t*)p, unpacked); output1 = shift_right_by_bytes128(output1, output_increment); output2 = shift_right_by_bytes128(output2, output_increment); output3 = shift_right_by_bytes128(output3, output_increment); } #else uint32_t *p = d->output.storage + d->output.pos; const uint32_t *limit = p + num_codepoints; simde__m128i x1, x2, x3; #define chunk() { \ const integer_t unpacked1 = extract_lower_half_as_chars(x1); \ const integer_t unpacked2 = shift_right_by_one_byte(extract_lower_half_as_chars(x2)); \ const integer_t unpacked3 = shift_right_by_two_bytes(extract_lower_half_as_chars(x3)); \ store_unaligned((integer_t*)p, or_si(or_si(unpacked1, unpacked2), unpacked3)); \ p += output_increment; \ } #define extract(which) x1 = simde_mm256_extracti128_si256(output1, which); x2 = simde_mm256_extracti128_si256(output2, which); x3 = simde_mm256_extracti128_si256(output3, which); #define shift() x1 = shift_right_by_bytes128(x1, output_increment); x2 = shift_right_by_bytes128(x2, output_increment); x3 = shift_right_by_bytes128(x3, output_increment); extract(0); chunk(); if (p < limit) { shift(); chunk(); if (p < limit) { extract(1); chunk(); if (p < limit) { shift(); chunk(); } } } #undef chunk #undef extract #undef shift #endif d->output.pos += num_codepoints; } #undef output_increment static inline unsigned sum_bytes_128(simde__m128i v) { // Use _mm_sad_epu8 to perform a sum of absolute differences against zero // This sums up all 8-bit integers in the 128-bit vector and packs the result into a 64-bit integer simde__m128i sum = simde_mm_sad_epu8(v, simde_mm_setzero_si128()); // At this point, the sum of the first half is in the lower 64 bits, and the sum of the second half is in the upper 64 bits. // Extract the lower and upper 64-bit sums and add them together. const unsigned lower_sum = simde_mm_cvtsi128_si32(sum); // Extracts the lower 32 bits const unsigned upper_sum = simde_mm_cvtsi128_si32(simde_mm_srli_si128(sum, 8)); // Extracts the upper 32 bits return lower_sum + upper_sum; // Final sum of all bytes } #define do_one_byte \ const uint8_t ch = src[pos++]; \ switch (decode_utf8(&d->state.cur, &d->state.codep, ch)) { \ case UTF8_ACCEPT: \ d->output.storage[d->output.pos++] = d->state.codep; \ break; \ case UTF8_REJECT: { \ const bool prev_was_accept = d->state.prev == UTF8_ACCEPT; \ zero_at_ptr(&d->state); \ d->output.storage[d->output.pos++] = 0xfffd; \ if (!prev_was_accept) { \ pos--; \ continue; /* so that prev is correct */ \ } \ } break; \ } \ d->state.prev = d->state.cur; static inline size_t scalar_decode_to_accept(UTF8Decoder *d, const uint8_t *src, size_t src_sz) { size_t pos = 0; utf8_decoder_ensure_capacity(d, src_sz); while (pos < src_sz && d->state.cur != UTF8_ACCEPT) { do_one_byte } return pos; } static inline size_t scalar_decode_all(UTF8Decoder *d, const uint8_t *src, size_t src_sz) { size_t pos = 0; utf8_decoder_ensure_capacity(d, src_sz); while (pos < src_sz) { do_one_byte } return pos; } #undef do_one_byte bool FUNC(utf8_decode_to_esc)(UTF8Decoder *d, const uint8_t *src_data, size_t src_len) { // Based on the algorithm described in: https://woboq.com/blog/utf-8-processing-using-simd.html #ifdef compare_with_scalar UTF8Decoder debugdec ={0}; memcpy(&debugdec.state, &d->state, sizeof(debugdec.state)); bool scalar_sentinel_found = utf8_decode_to_esc_scalar(&debugdec, src_data, src_len); #endif d->output.pos = 0; d->num_consumed = 0; if (d->state.cur != UTF8_ACCEPT) { // Finish the trailing sequence only d->num_consumed = scalar_decode_to_accept(d, src_data, src_len); src_data += d->num_consumed; src_len -= d->num_consumed; } const integer_t esc_vec = set1_epi8(0x1b); const integer_t zero = create_zero_integer(), one = set1_epi8(1), two = set1_epi8(2), three = set1_epi8(3), numbered = numbered_bytes(); const uint8_t *limit = src_data + src_len, *p = src_data, *start_of_current_chunk = src_data; bool sentinel_found = false; unsigned chunk_src_sz = 0; unsigned num_of_trailing_bytes = 0; while (p < limit && !sentinel_found) { chunk_src_sz = MIN((size_t)(limit - p), sizeof(integer_t)); integer_t vec = load_unaligned((integer_t*)p); start_of_current_chunk = p; p += chunk_src_sz; const integer_t esc_cmp = cmpeq_epi8(vec, esc_vec); int num_of_bytes_to_first_esc = bytes_to_first_match(esc_cmp); if (num_of_bytes_to_first_esc > -1 && (unsigned)num_of_bytes_to_first_esc < chunk_src_sz) { sentinel_found = true; chunk_src_sz = num_of_bytes_to_first_esc; d->num_consumed += chunk_src_sz + 1; // esc is also consumed if (!chunk_src_sz) continue; } else d->num_consumed += chunk_src_sz; if (chunk_src_sz < sizeof(integer_t)) vec = zero_last_n_bytes(vec, sizeof(integer_t) - chunk_src_sz); num_of_trailing_bytes = 0; bool check_for_trailing_bytes = !sentinel_found; debug_register(vec); int32_t ascii_mask; #define abort_with_invalid_utf8() { \ scalar_decode_all(d, start_of_current_chunk, chunk_src_sz + num_of_trailing_bytes); \ d->num_consumed += num_of_trailing_bytes; \ break; \ } #define handle_trailing_bytes() if (num_of_trailing_bytes) { \ if (p >= limit) { \ scalar_decode_all(d, p - num_of_trailing_bytes, num_of_trailing_bytes); \ d->num_consumed += num_of_trailing_bytes; \ break; \ } \ p -= num_of_trailing_bytes; \ } start_classification: // Check if we have pure ASCII and use fast path ascii_mask = movemask_epi8(vec); if (!ascii_mask) { // no bytes with high bit (0x80) set, so just plain ASCII FUNC(output_plain_ascii)(d, vec, chunk_src_sz); handle_trailing_bytes(); continue; } // Classify the bytes integer_t state = set1_epi8(0x80); const integer_t vec_signed = add_epi8(vec, state); // needed because cmplt_epi8 works only on signed chars const integer_t bytes_indicating_start_of_two_byte_sequence = cmplt_epi8(set1_epi8(0xc0 - 1 - 0x80), vec_signed); state = blendv_epi8(state, set1_epi8(0xc2), bytes_indicating_start_of_two_byte_sequence); // state now has 0xc2 on all bytes that start a 2 or more byte sequence and 0x80 on the rest const integer_t bytes_indicating_start_of_three_byte_sequence = cmplt_epi8(set1_epi8(0xe0 - 1 - 0x80), vec_signed); state = blendv_epi8(state, set1_epi8(0xe3), bytes_indicating_start_of_three_byte_sequence); const integer_t bytes_indicating_start_of_four_byte_sequence = cmplt_epi8(set1_epi8(0xf0 - 1 - 0x80), vec_signed); state = blendv_epi8(state, set1_epi8(0xf4), bytes_indicating_start_of_four_byte_sequence); // state now has 0xc2 on all bytes that start a 2 byte sequence, 0xe3 on start of 3-byte, 0xf4 on 4-byte start and 0x80 on rest debug_register(state); const integer_t mask = and_si(state, set1_epi8(0xf8)); // keep upper 5 bits of state debug_register(mask); const integer_t count = and_si(state, set1_epi8(0x7)); // keep lower 3 bits of state debug_register(count); // count contains the number of bytes in the sequence for the start byte of every sequence and zero elsewhere // shift 02 bytes by 1 and subtract 1 const integer_t count_subs1 = subtract_saturate_epu8(count, one); integer_t counts = add_epi8(count, shift_right_by_one_byte(count_subs1)); // shift 03 and 04 bytes by 2 and subtract 2 counts = add_epi8(counts, shift_right_by_two_bytes(subtract_saturate_epu8(counts, two))); // counts now contains the number of bytes remaining in each utf-8 sequence of 2 or more bytes debug_register(counts); // check for an incomplete trailing utf8 sequence if (check_for_trailing_bytes && !is_zero(cmplt_epi8(one, and_si(counts, cmpeq_epi8(numbered, set1_epi8(chunk_src_sz - 1)))))) { // The value of counts at the last byte is > 1 indicating we have a trailing incomplete sequence check_for_trailing_bytes = false; if (start_of_current_chunk[chunk_src_sz-1] >= 0xc0) num_of_trailing_bytes = 1; // 2-, 3- and 4-byte characters with only 1 byte left else if (chunk_src_sz > 1 && start_of_current_chunk[chunk_src_sz-2] >= 0xe0) num_of_trailing_bytes = 2; // 3- and 4-byte characters with only 1 byte left else if (chunk_src_sz > 2 && start_of_current_chunk[chunk_src_sz-3] >= 0xf0) num_of_trailing_bytes = 3; // 4-byte characters with only 3 bytes left chunk_src_sz -= num_of_trailing_bytes; d->num_consumed -= num_of_trailing_bytes; if (!chunk_src_sz) { abort_with_invalid_utf8(); } vec = zero_last_n_bytes(vec, sizeof(integer_t) - chunk_src_sz); goto start_classification; } // Only ASCII chars should have corresponding byte of counts == 0 if (ascii_mask != movemask_epi8(cmpgt_epi8(counts, zero))) { abort_with_invalid_utf8(); } // The difference between a byte in counts and the next one should be negative, // zero, or one. Any other value means there is not enough continuation bytes. if (!is_zero(cmpgt_epi8(subtract_epi8(shift_right_by_one_byte(counts), counts), one))) { abort_with_invalid_utf8(); } // Process the bytes storing the three resulting bytes that make up the unicode codepoint // mask all control bits so that we have only useful bits left vec = andnot_si(mask, vec); debug_register(vec); // Now calculate the three output vectors // The lowest byte is made up of 6 bits from locations with counts == 1 and the lowest two bits from locations with count == 2 // In addition, the ASCII bytes are copied unchanged from vec const integer_t vec_non_ascii = andnot_si(cmpeq_epi8(counts, zero), vec); debug_register(vec_non_ascii); integer_t output1 = blendv_epi8(vec, or_si( // there are no count == 1 locations without a count == 2 location to its left so we dont need to AND with count2_locations vec, and_si(shift_left_by_bits16(shift_right_by_one_byte(vec_non_ascii), 6), set1_epi8(0xc0)) ), cmpeq_epi8(counts, one) ); debug_register(output1); // The next byte is made up of 4 bits (5, 4, 3, 2) from locations with count == 2 and the first 4 bits from locations with count == 3 const integer_t count2_locations = cmpeq_epi8(counts, two), count3_locations = cmpeq_epi8(counts, three); integer_t output2 = and_si(vec, count2_locations); output2 = shift_right_by_bits32(output2, 2); // selects the bits 5, 4, 3, 2 // select the first 4 bits from locs with count == 3 by shifting count 3 locations right by one byte and left by 4 bits output2 = or_si(output2, and_si(set1_epi8(0xf0), shift_left_by_bits16(shift_right_by_one_byte(and_si(count3_locations, vec_non_ascii)), 4) ) ); output2 = and_si(output2, count2_locations); // keep only the count2 bytes output2 = shift_right_by_one_byte(output2); debug_register(output2); // The last byte is made up of bits 5 and 6 from count == 3 and 3 bits from count == 4 integer_t output3 = and_si(three, shift_right_by_bits32(vec, 4)); // bits 5 and 6 from count == 3 const integer_t count4_locations = cmpeq_epi8(counts, set1_epi8(4)); // 3 bits from count == 4 locations, placed at count == 3 locations shifted left by 2 bits output3 = or_si(output3, and_si(set1_epi8(0xfc), shift_left_by_bits16(shift_right_by_one_byte(and_si(count4_locations, vec_non_ascii)), 2) ) ); output3 = and_si(output3, count3_locations); // keep only count3 bytes output3 = shift_right_by_two_bytes(output3); debug_register(output3); // Shuffle bytes to remove continuation bytes integer_t shifts = count_subs1; // number of bytes we need to skip for each UTF-8 sequence // propagate the shifts to all subsequent bytes by shift and add shifts = add_epi8(shifts, shift_right_by_one_byte(shifts)); shifts = add_epi8(shifts, shift_right_by_two_bytes(shifts)); shifts = add_epi8(shifts, shift_right_by_four_bytes(shifts)); shifts = add_epi8(shifts, shift_right_by_eight_bytes(shifts)); #if KITTY_SIMD_LEVEL == 256 shifts = add_epi8(shifts, shift_right_by_sixteen_bytes(shifts)); #endif // zero the shifts for discarded continuation bytes shifts = and_si(shifts, cmplt_epi8(counts, two)); // now we need to convert shifts into a mask for the shuffle. The mask has each byte of the // form 0000xxxx the lower four bits indicating the destination location for the byte. For 256 bit shuffle we use lower 5 bits. // First we move the numbers in shifts to discard the unwanted UTF-8 sequence bytes. We note that the numbers // are bounded by sizeof(integer_t) and so we need at most 4 (for 128 bit) or 5 (for 256 bit) moves. The numbers are // monotonic from left to right and change value only at the end of a UTF-8 sequence. We move them leftwards, accumulating the // moves bit-by-bit. #define move(shifts, amt, which_bit) blendv_epi8(shifts, shift_left_by_##amt(shifts), shift_left_by_##amt(shift_left_by_bits16(shifts, 8 - which_bit))) shifts = move(shifts, one_byte, 1); shifts = move(shifts, two_bytes, 2); shifts = move(shifts, four_bytes, 3); shifts = move(shifts, eight_bytes, 4); #if KITTY_SIMD_LEVEL == 256 shifts = move(shifts, sixteen_bytes, 5); #endif #undef move // convert the shifts into a suitable mask for shuffle by adding the byte number to each byte shifts = add_epi8(shifts, numbered); debug_register(shifts); output1 = shuffle_epi8(output1, shifts); output2 = shuffle_epi8(output2, shifts); output3 = shuffle_epi8(output3, shifts); debug_register(output1); debug_register(output2); debug_register(output3); const unsigned num_of_discarded_bytes = sum_bytes(count_subs1); const unsigned num_codepoints = chunk_src_sz - num_of_discarded_bytes; debug("num_of_discarded_bytes: %u num_codepoints: %u\n", num_of_discarded_bytes, num_codepoints); FUNC(output_unicode)(d, output1, output2, output3, num_codepoints); handle_trailing_bytes(); } #ifdef compare_with_scalar if (debugdec.output.pos != d->output.pos || debugdec.num_consumed != d->num_consumed || memcmp(d->output.storage, debugdec.output.storage, d->output.pos * sizeof(d->output.storage[0])) != 0 || sentinel_found != scalar_sentinel_found || debugdec.state.cur != d->state.cur ) { fprintf(stderr, "vector decode output differs from scalar: input_sz=%zu consumed=(%u %u) output_sz=(%u %u) sentinel=(%d %d) state_changed: %d output_different: %d\n", src_len, debugdec.num_consumed, d->num_consumed, debugdec.output.pos, d->output.pos, scalar_sentinel_found, sentinel_found, debugdec.state.cur != d->state.cur, memcmp(d->output.storage, debugdec.output.storage, MIN(d->output.pos, debugdec.output.pos) * sizeof(d->output.storage[0])) ); fprintf(stderr, "\""); for (unsigned i = 0; i < src_len; i++) { if (32 <= src_data[i] && src_data[i] < 0x7f && src_data[i] != '"') fprintf(stderr, "%c", src_data[i]); else fprintf(stderr, "\\x%x", src_data[i]); } fprintf(stderr, "\"\n"); } utf8_decoder_free(&debugdec); #endif zero_upper(); return sentinel_found; #undef abort_with_invalid_utf8 #undef handle_trailing_bytes } #undef FUNC #undef integer_t #undef set1_epi8 #undef set_epi8 #undef load_unaligned #undef load_aligned #undef store_unaligned #undef store_aligned #undef cmpeq_epi8 #undef cmplt_epi8 #undef cmpgt_epi8 #undef or_si #undef and_si #undef xor_si #undef andnot_si #undef movemask_epi8 #undef CONCAT #undef CONCAT_EXPAND #undef KITTY_SIMD_LEVEL #undef shift_right_by_one_byte #undef shift_right_by_two_bytes #undef shift_right_by_four_bytes #undef shift_right_by_eight_bytes #undef shift_right_by_sixteen_bytes #undef shift_left_by_one_byte #undef shift_left_by_two_bytes #undef shift_left_by_four_bytes #undef shift_left_by_eight_bytes #undef shift_left_by_sixteen_bytes #undef shift_left_by_bits16 #undef shift_right_by_bits32 #undef shift_right_by_bytes128 #undef extract_lower_quarter_as_chars #undef extract_lower_half_as_chars #undef blendv_epi8 #undef add_epi8 #undef subtract_saturate_epu8 #undef subtract_epi8 #undef create_zero_integer #undef create_all_ones_integer #undef shuffle_epi8 #undef numbered_bytes #undef reverse_numbered_bytes #undef sum_bytes #undef is_zero #undef zero_upper #undef print_register_as_bytes #endif // KITTY_NO_SIMD kitty-0.41.1/kitty/simd-string.c0000664000175000017510000002207014773370543016060 0ustar nileshnilesh/* * simd-string.c * Copyright (C) 2023 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "data-types.h" #include "charsets.h" #include "simd-string.h" static bool has_sse4_2 = false, has_avx2 = false; // xor_data64 {{{ static void xor_data64_scalar(const uint8_t key[64], uint8_t* data, const size_t data_sz) { for (size_t i = 0; i < data_sz; i++) data[i] ^= key[i & 63]; } static void (*xor_data64_impl)(const uint8_t key[64], uint8_t* data, const size_t data_sz) = xor_data64_scalar; void xor_data64(const uint8_t key[64], uint8_t* data, const size_t data_sz) { xor_data64_impl(key, data, data_sz); } // }}} // find_either_of_two_bytes {{{ static const uint8_t* find_either_of_two_bytes_scalar(const uint8_t *haystack, const size_t sz, const uint8_t x, const uint8_t y) { for (const uint8_t *limit = haystack + sz; haystack < limit; haystack++) { if (*haystack == x || *haystack == y) return haystack; } return NULL; } static const uint8_t* (*find_either_of_two_bytes_impl)(const uint8_t*, const size_t, const uint8_t, const uint8_t) = find_either_of_two_bytes_scalar; const uint8_t* find_either_of_two_bytes(const uint8_t *haystack, const size_t sz, const uint8_t a, const uint8_t b) { return (uint8_t*)find_either_of_two_bytes_impl(haystack, sz, a, b); } // }}} // UTF-8 {{{ bool utf8_decode_to_esc_scalar(UTF8Decoder *d, const uint8_t *src, const size_t src_sz) { d->output.pos = 0; d->num_consumed = 0; utf8_decoder_ensure_capacity(d, src_sz); while (d->num_consumed < src_sz) { const uint8_t ch = src[d->num_consumed++]; if (ch == 0x1b) { if (d->state.cur != UTF8_ACCEPT) d->output.storage[d->output.pos++] = 0xfffd; zero_at_ptr(&d->state); return true; } else { switch(decode_utf8(&d->state.cur, &d->state.codep, ch)) { case UTF8_ACCEPT: d->output.storage[d->output.pos++] = d->state.codep; break; case UTF8_REJECT: { const bool prev_was_accept = d->state.prev == UTF8_ACCEPT; zero_at_ptr(&d->state); d->output.storage[d->output.pos++] = 0xfffd; if (!prev_was_accept && d->num_consumed) { d->num_consumed--; continue; // so that prev is correct } } break; } } d->state.prev = d->state.cur; } return false; } static bool (*utf8_decode_to_esc_impl)(UTF8Decoder *d, const uint8_t *src, size_t src_sz) = utf8_decode_to_esc_scalar; bool utf8_decode_to_esc(UTF8Decoder *d, const uint8_t *src, size_t src_sz) { return utf8_decode_to_esc_impl(d, src, src_sz); } // }}} // Boilerplate {{{ static PyObject* test_utf8_decode_to_sentinel(PyObject *self UNUSED, PyObject *args) { const uint8_t *src; Py_ssize_t src_sz; int which_function = 0; static UTF8Decoder d = {0}; if (!PyArg_ParseTuple(args, "s#|i", &src, &src_sz, &which_function)) return NULL; bool found_sentinel = false; bool(*func)(UTF8Decoder*, const uint8_t*, size_t sz) = utf8_decode_to_esc; switch (which_function) { case -1: zero_at_ptr(&d); Py_RETURN_NONE; case 1: func = utf8_decode_to_esc_scalar; break; case 2: func = utf8_decode_to_esc_128; break; case 3: func = utf8_decode_to_esc_256; break; } RAII_PyObject(ans, PyUnicode_FromString("")); ssize_t p = 0; while (p < src_sz && !found_sentinel) { found_sentinel = func(&d, src + p, src_sz - p); p += d.num_consumed; if (d.output.pos) { RAII_PyObject(temp, PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, d.output.storage, d.output.pos)); PyObject *t = PyUnicode_Concat(ans, temp); Py_DECREF(ans); ans = t; } } utf8_decoder_free(&d); return Py_BuildValue("OOi", found_sentinel ? Py_True : Py_False, ans, p); } static PyObject* test_find_either_of_two_bytes(PyObject *self UNUSED, PyObject *args) { RAII_PY_BUFFER(buf); int which_function = 0, align_offset = 0; const uint8_t*(*func)(const uint8_t*, const size_t sz, const uint8_t, const uint8_t) = find_either_of_two_bytes; unsigned char a, b; if (!PyArg_ParseTuple(args, "s*BB|ii", &buf, &a, &b, &which_function, &align_offset)) return NULL; switch (which_function) { case 1: func = find_either_of_two_bytes_scalar; break; case 2: func = find_either_of_two_bytes_128; break; case 3: func = find_either_of_two_bytes_256; break; case 0: break; default: PyErr_SetString(PyExc_ValueError, "Unknown which_function"); return NULL; } uint8_t *abuf; if (posix_memalign((void**)&abuf, 64, 256 + buf.len) != 0) { return PyErr_NoMemory(); } uint8_t *p = abuf; memset(p, '<', 64 + align_offset); p += 64 + align_offset; memcpy(p, buf.buf, buf.len); memset(p + buf.len, '>', 64); const uint8_t *ans = func(p, buf.len, a, b); free(abuf); if (ans == NULL) return PyLong_FromLong(-1); unsigned long long n = ans - p; return PyLong_FromUnsignedLongLong(n); } static PyObject* test_xor64(PyObject *self UNUSED, PyObject *args) { RAII_PY_BUFFER(buf); RAII_PY_BUFFER(key); int which_function = 0, align_offset = 0; void (*func)(const uint8_t key[64], uint8_t* data, const size_t data_sz) = xor_data64; if (!PyArg_ParseTuple(args, "s*s*|ii", &key, &buf, &which_function, &align_offset)) return NULL; switch (which_function) { case 1: func = xor_data64_scalar; break; case 2: func = xor_data64_128; break; case 3: func = xor_data64_256; break; case 0: break; default: PyErr_SetString(PyExc_ValueError, "Unknown which_function"); return NULL; } uint8_t *abuf; if (posix_memalign((void**)&abuf, 64, 256 + buf.len) != 0) { return PyErr_NoMemory(); } uint8_t *p = abuf; memset(p, '<', 64 + align_offset); p += 64 + align_offset; memcpy(p, buf.buf, buf.len); memset(p + buf.len, '>', 64); func(key.buf, p, buf.len); PyObject *ans = NULL; for (int i = 0; i < 64 + align_offset; i++) if (abuf[i] != '<') { PyErr_SetString(PyExc_SystemError, "xor wrote before start of data region"); } for (int i = 0; i < 64; i++) if (p[i + buf.len] != '>') { PyErr_SetString(PyExc_SystemError, "xor wrote after end of data region"); } if (!PyErr_Occurred()) ans = PyBytes_FromStringAndSize((const char*)p, buf.len); free(abuf); return ans; } // }}} static PyMethodDef module_methods[] = { METHODB(test_utf8_decode_to_sentinel, METH_VARARGS), METHODB(test_find_either_of_two_bytes, METH_VARARGS), METHODB(test_xor64, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_simd(void *x) { PyObject *module = (PyObject*)x; if (PyModule_AddFunctions(module, module_methods) != 0) return false; #define A(x, val) { Py_INCREF(Py_##val); if (0 != PyModule_AddObject(module, #x, Py_##val)) return false; } #define do_check() { has_sse4_2 = __builtin_cpu_supports("sse4.2") != 0; has_avx2 = __builtin_cpu_supports("avx2") != 0; } #ifdef __APPLE__ #ifdef __arm64__ // simde takes care of NEON on Apple Silicon // ARM has only 128 bit registers but using the avx2 code is still slightly faster has_sse4_2 = true; has_avx2 = true; #else do_check(); // On GitHub actions there are some weird macOS machines which report avx2 not available but sse4.2 is available and then // SIGILL when using basic sse instructions if (!has_avx2 && has_sse4_2) { const char *ci = getenv("CI"); if (ci && strcmp(ci, "true") == 0) has_sse4_2 = false; } #endif #else #ifdef __aarch64__ // no idea how to probe ARM cpu for NEON support. This file uses pretty // basic AVX2 and SSE4.2 intrinsics, so hopefully they work on ARM // ARM has only 128 bit registers but using the avx2 code is still slightly faster has_sse4_2 = true; has_avx2 = true; #elif !defined(KITTY_NO_SIMD) do_check(); #endif #endif const char *simd_env = getenv("KITTY_SIMD"); if (simd_env) { has_sse4_2 = strcmp(simd_env, "128") == 0; has_avx2 = strcmp(simd_env, "256") == 0; } #undef do_check if (has_avx2) { A(has_avx2, True); find_either_of_two_bytes_impl = find_either_of_two_bytes_256; utf8_decode_to_esc_impl = utf8_decode_to_esc_256; xor_data64_impl = xor_data64_256; } else { A(has_avx2, False); } if (has_sse4_2) { A(has_sse4_2, True); if (find_either_of_two_bytes_impl == find_either_of_two_bytes_scalar) find_either_of_two_bytes_impl = find_either_of_two_bytes_128; if (utf8_decode_to_esc_impl == utf8_decode_to_esc_scalar) utf8_decode_to_esc_impl = utf8_decode_to_esc_128; if (xor_data64_impl == xor_data64_scalar) xor_data64_impl = xor_data64_128; } else { A(has_sse4_2, False); } #undef A return true; } kitty-0.41.1/kitty/simd-string.h0000664000175000017510000000462714773370543016075 0ustar nileshnilesh/* * Copyright (C) 2023 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" #include #include typedef void (*control_byte_callback)(void *data, uint8_t ch); typedef void (*output_chars_callback)(void *data, const uint32_t *chars, unsigned count); typedef struct UTF8Decoder { struct { uint32_t *storage; unsigned pos, capacity; } output; struct { uint32_t cur, prev, codep; } state; unsigned num_consumed; } UTF8Decoder; static inline void utf8_decoder_reset(UTF8Decoder *self) { zero_at_ptr(&self->state); } bool utf8_decode_to_esc(UTF8Decoder *d, const uint8_t *src, size_t src_sz); bool utf8_decode_to_esc_scalar(UTF8Decoder *d, const uint8_t *src, const size_t src_sz); static inline void utf8_decoder_ensure_capacity(UTF8Decoder *d, unsigned sz) { if (d->output.pos + sz > d->output.capacity) { d->output.capacity = d->output.pos + sz + 4096; // allow for overwrite of upto 64 bytes d->output.storage = realloc(d->output.storage, d->output.capacity * sizeof(d->output.storage[0]) + 64); if (!d->output.storage) fatal("Out of memory for UTF8Decoder output buffer at capacity: %u", d->output.capacity); } } static inline void utf8_decoder_free(UTF8Decoder *d) { free(d->output.storage); zero_at_ptr(&(d->output)); } // Pass a PyModule PyObject* as the argument. Must be called once at application startup bool init_simd(void* module); // Returns pointer to first position in haystack that contains either of the // two chars or NULL if not found. const uint8_t* find_either_of_two_bytes(const uint8_t *haystack, const size_t sz, const uint8_t a, const uint8_t b); // XOR data with the 64 byte key void xor_data64(const uint8_t key[64], uint8_t* data, const size_t data_sz); // SIMD implementations, internal use bool utf8_decode_to_esc_128(UTF8Decoder *d, const uint8_t *src, size_t src_sz); bool utf8_decode_to_esc_256(UTF8Decoder *d, const uint8_t *src, size_t src_sz); const uint8_t* find_either_of_two_bytes_128(const uint8_t *haystack, const size_t sz, const uint8_t a, const uint8_t b); const uint8_t* find_either_of_two_bytes_256(const uint8_t *haystack, const size_t sz, const uint8_t a, const uint8_t b); void xor_data64_128(const uint8_t key[64], uint8_t* data, const size_t data_sz); void xor_data64_256(const uint8_t key[64], uint8_t* data, const size_t data_sz); kitty-0.41.1/kitty/srgb_gamma.h0000664000175000017510000000522614773370543015730 0ustar nileshnilesh// Generated by gen-srgb-lut.py DO NOT edit static const GLfloat srgb_lut[256] = { 0.00000f, 0.00030f, 0.00061f, 0.00091f, 0.00121f, 0.00152f, 0.00182f, 0.00212f, 0.00243f, 0.00273f, 0.00304f, 0.00335f, 0.00368f, 0.00402f, 0.00439f, 0.00478f, 0.00518f, 0.00561f, 0.00605f, 0.00651f, 0.00700f, 0.00750f, 0.00802f, 0.00857f, 0.00913f, 0.00972f, 0.01033f, 0.01096f, 0.01161f, 0.01229f, 0.01298f, 0.01370f, 0.01444f, 0.01521f, 0.01600f, 0.01681f, 0.01764f, 0.01850f, 0.01938f, 0.02029f, 0.02122f, 0.02217f, 0.02315f, 0.02416f, 0.02519f, 0.02624f, 0.02732f, 0.02843f, 0.02956f, 0.03071f, 0.03190f, 0.03310f, 0.03434f, 0.03560f, 0.03689f, 0.03820f, 0.03955f, 0.04092f, 0.04231f, 0.04374f, 0.04519f, 0.04667f, 0.04817f, 0.04971f, 0.05127f, 0.05286f, 0.05448f, 0.05613f, 0.05781f, 0.05951f, 0.06125f, 0.06301f, 0.06480f, 0.06663f, 0.06848f, 0.07036f, 0.07227f, 0.07421f, 0.07619f, 0.07819f, 0.08022f, 0.08228f, 0.08438f, 0.08650f, 0.08866f, 0.09084f, 0.09306f, 0.09531f, 0.09759f, 0.09990f, 0.10224f, 0.10462f, 0.10702f, 0.10946f, 0.11193f, 0.11444f, 0.11697f, 0.11954f, 0.12214f, 0.12477f, 0.12744f, 0.13014f, 0.13287f, 0.13563f, 0.13843f, 0.14126f, 0.14413f, 0.14703f, 0.14996f, 0.15293f, 0.15593f, 0.15896f, 0.16203f, 0.16513f, 0.16827f, 0.17144f, 0.17465f, 0.17789f, 0.18116f, 0.18447f, 0.18782f, 0.19120f, 0.19462f, 0.19807f, 0.20156f, 0.20508f, 0.20864f, 0.21223f, 0.21586f, 0.21953f, 0.22323f, 0.22697f, 0.23074f, 0.23455f, 0.23840f, 0.24228f, 0.24620f, 0.25016f, 0.25415f, 0.25818f, 0.26225f, 0.26636f, 0.27050f, 0.27468f, 0.27889f, 0.28315f, 0.28744f, 0.29177f, 0.29614f, 0.30054f, 0.30499f, 0.30947f, 0.31399f, 0.31855f, 0.32314f, 0.32778f, 0.33245f, 0.33716f, 0.34191f, 0.34670f, 0.35153f, 0.35640f, 0.36131f, 0.36625f, 0.37124f, 0.37626f, 0.38133f, 0.38643f, 0.39157f, 0.39676f, 0.40198f, 0.40724f, 0.41254f, 0.41789f, 0.42327f, 0.42869f, 0.43415f, 0.43966f, 0.44520f, 0.45079f, 0.45641f, 0.46208f, 0.46778f, 0.47353f, 0.47932f, 0.48515f, 0.49102f, 0.49693f, 0.50289f, 0.50888f, 0.51492f, 0.52100f, 0.52712f, 0.53328f, 0.53948f, 0.54572f, 0.55201f, 0.55834f, 0.56471f, 0.57112f, 0.57758f, 0.58408f, 0.59062f, 0.59720f, 0.60383f, 0.61050f, 0.61721f, 0.62396f, 0.63076f, 0.63760f, 0.64448f, 0.65141f, 0.65837f, 0.66539f, 0.67244f, 0.67954f, 0.68669f, 0.69387f, 0.70110f, 0.70838f, 0.71569f, 0.72306f, 0.73046f, 0.73791f, 0.74540f, 0.75294f, 0.76052f, 0.76815f, 0.77582f, 0.78354f, 0.79130f, 0.79910f, 0.80695f, 0.81485f, 0.82279f, 0.83077f, 0.83880f, 0.84687f, 0.85499f, 0.86316f, 0.87137f, 0.87962f, 0.88792f, 0.89627f, 0.90466f, 0.91310f, 0.92158f, 0.93011f, 0.93869f, 0.94731f, 0.95597f, 0.96469f, 0.97345f, 0.98225f, 0.99110f, 1.00000f }; kitty-0.41.1/kitty/state.c0000664000175000017510000015736414773370543014757 0ustar nileshnilesh/* * state.c * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "cleanup.h" #include "options/to-c-generated.h" #include #include GlobalState global_state = {{0}}; #define REMOVER(array, qid, count, destroy, capacity) { \ for (size_t i = 0; i < count; i++) { \ if (array[i].id == qid) { \ destroy(array + i); \ zero_at_i(array, i); \ remove_i_from_array(array, i, count); \ break; \ } \ }} #define WITH_OS_WINDOW(os_window_id) \ for (size_t o = 0; o < global_state.num_os_windows; o++) { \ OSWindow *os_window = global_state.os_windows + o; \ if (os_window->id == os_window_id) { #define END_WITH_OS_WINDOW break; }} #define WITH_TAB(os_window_id, tab_id) \ for (size_t o = 0, tab_found = 0; o < global_state.num_os_windows && !tab_found; o++) { \ OSWindow *osw = global_state.os_windows + o; \ if (osw->id == os_window_id) { \ for (size_t t = 0; t < osw->num_tabs; t++) { \ if (osw->tabs[t].id == tab_id) { \ Tab *tab = osw->tabs + t; #define END_WITH_TAB tab_found = 1; break; }}}} #define WITH_WINDOW(os_window_id, tab_id, window_id) \ for (size_t o = 0, window_found = 0; o < global_state.num_os_windows && !window_found; o++) { \ OSWindow *osw = global_state.os_windows + o; \ if (osw->id == os_window_id) { \ for (size_t t = 0; t < osw->num_tabs && !window_found; t++) { \ if (osw->tabs[t].id == tab_id) { \ Tab *tab = osw->tabs + t; \ for (size_t w = 0; w < tab->num_windows; w++) { \ if (tab->windows[w].id == window_id) { \ Window *window = tab->windows + w; #define END_WITH_WINDOW window_found = 1; break; }}}}}} #define WITH_OS_WINDOW_REFS \ id_type cb_window_id = 0, focused_window_id = 0; \ if (global_state.callback_os_window) cb_window_id = global_state.callback_os_window->id; \ #define END_WITH_OS_WINDOW_REFS \ if (cb_window_id || focused_window_id) { \ global_state.callback_os_window = NULL; \ for (size_t wn = 0; wn < global_state.num_os_windows; wn++) { \ OSWindow *wp = global_state.os_windows + wn; \ if (wp->id == cb_window_id && cb_window_id) global_state.callback_os_window = wp; \ }} static double dpi_for_os_window(OSWindow *os_window) { double dpi = (os_window->fonts_data->logical_dpi_x + os_window->fonts_data->logical_dpi_y) / 2.; if (dpi == 0) dpi = (global_state.default_dpi.x + global_state.default_dpi.y) / 2.; return dpi; } static double dpi_for_os_window_id(id_type os_window_id) { double dpi = 0; if (os_window_id) { WITH_OS_WINDOW(os_window_id) dpi = dpi_for_os_window(os_window); END_WITH_OS_WINDOW } if (dpi == 0) { dpi = (global_state.default_dpi.x + global_state.default_dpi.y) / 2.; } return dpi; } static long pt_to_px_for_os_window(double pt, OSWindow *w) { const double dpi = dpi_for_os_window(w); return ((long)round((pt * (dpi / 72.0)))); } static long pt_to_px(double pt, id_type os_window_id) { const double dpi = dpi_for_os_window_id(os_window_id); return ((long)round((pt * (dpi / 72.0)))); } OSWindow* current_os_window(void) { if (global_state.callback_os_window) return global_state.callback_os_window; for (size_t i = 0; i < global_state.num_os_windows; i++) { if (global_state.os_windows[i].is_focused) return global_state.os_windows + i; } return global_state.os_windows; } static id_type last_focused_os_window_id(void) { id_type ans = 0, max_fc_count = 0; for (size_t i = 0; i < global_state.num_os_windows; i++) { OSWindow *w = &global_state.os_windows[i]; if (w->last_focused_counter > max_fc_count) { ans = w->id; max_fc_count = w->last_focused_counter; } } return ans; } static id_type current_focused_os_window_id(void) { for (size_t i = 0; i < global_state.num_os_windows; i++) { OSWindow *w = &global_state.os_windows[i]; if (w->is_focused) { return w->id; } } return 0; } OSWindow* os_window_for_id(id_type os_window_id) { for (size_t i = 0; i < global_state.num_os_windows; i++) { OSWindow *w = global_state.os_windows + i; if (w->id == os_window_id) return w; } return NULL; } OSWindow* os_window_for_kitty_window(id_type kitty_window_id) { for (size_t i = 0; i < global_state.num_os_windows; i++) { OSWindow *w = global_state.os_windows + i; for (size_t t = 0; t < w->num_tabs; t++) { Tab *tab = w->tabs + t; for (size_t c = 0; c < tab->num_windows; c++) { if (tab->windows[c].id == kitty_window_id) return w; } } } return NULL; } Window* window_for_window_id(id_type kitty_window_id) { for (size_t i = 0; i < global_state.num_os_windows; i++) { OSWindow *w = global_state.os_windows + i; for (size_t t = 0; t < w->num_tabs; t++) { Tab *tab = w->tabs + t; for (size_t c = 0; c < tab->num_windows; c++) { if (tab->windows[c].id == kitty_window_id) return tab->windows + c; } } } return NULL; } static void free_bgimage_bitmap(BackgroundImage *bgimage) { if (!bgimage->bitmap) return; if (bgimage->mmap_size) { if (munmap(bgimage->bitmap, bgimage->mmap_size) != 0) log_error("Failed to unmap BackgroundImage with error: %s", strerror(errno)); } else free(bgimage->bitmap); bgimage->bitmap = NULL; bgimage->mmap_size = 0; } static void send_bgimage_to_gpu(BackgroundImageLayout layout, BackgroundImage *bgimage) { RepeatStrategy r = REPEAT_DEFAULT; switch (layout) { case SCALED: case CLAMPED: case CENTER_CLAMPED: case CENTER_SCALED: r = REPEAT_CLAMP; break; case MIRRORED: r = REPEAT_MIRROR; break; case TILING: r = REPEAT_DEFAULT; break; } bgimage->texture_id = 0; size_t delta = bgimage->mmap_size ? bgimage->mmap_size - ((size_t)4) * bgimage->width * bgimage->height : 0; send_image_to_gpu(&bgimage->texture_id, bgimage->bitmap + delta, bgimage->width, bgimage->height, false, true, OPT(background_image_linear), r); free_bgimage_bitmap(bgimage); } static void free_bgimage(BackgroundImage **bgimage, bool release_texture) { if (*bgimage && (*bgimage)->refcnt) { (*bgimage)->refcnt--; if ((*bgimage)->refcnt == 0) { free_bgimage_bitmap(*bgimage); if (release_texture) free_texture(&(*bgimage)->texture_id); free(*bgimage); } } bgimage = NULL; } OSWindow* add_os_window(void) { WITH_OS_WINDOW_REFS ensure_space_for(&global_state, os_windows, OSWindow, global_state.num_os_windows + 1, capacity, 1, true); OSWindow *ans = global_state.os_windows + global_state.num_os_windows++; zero_at_ptr(ans); ans->id = ++global_state.os_window_id_counter; ans->tab_bar_render_data.vao_idx = create_cell_vao(); ans->background_opacity = OPT(background_opacity); ans->created_at = monotonic(); bool wants_bg = OPT(background_image) && OPT(background_image)[0] != 0; if (wants_bg) { if (!global_state.bgimage) { global_state.bgimage = calloc(1, sizeof(BackgroundImage)); if (!global_state.bgimage) fatal("Out of memory allocating the global bg image object"); global_state.bgimage->refcnt++; if (image_path_to_bitmap(OPT(background_image), &global_state.bgimage->bitmap, &global_state.bgimage->width, &global_state.bgimage->height, &global_state.bgimage->mmap_size)) { send_bgimage_to_gpu(OPT(background_image_layout), global_state.bgimage); } } if (global_state.bgimage->texture_id) { ans->bgimage = global_state.bgimage; ans->bgimage->refcnt++; } } END_WITH_OS_WINDOW_REFS return ans; } static id_type add_tab(id_type os_window_id) { WITH_OS_WINDOW(os_window_id) make_os_window_context_current(os_window); ensure_space_for(os_window, tabs, Tab, os_window->num_tabs + 1, capacity, 1, true); zero_at_i(os_window->tabs, os_window->num_tabs); os_window->tabs[os_window->num_tabs].id = ++global_state.tab_id_counter; os_window->tabs[os_window->num_tabs].border_rects.vao_idx = create_border_vao(); return os_window->tabs[os_window->num_tabs++].id; END_WITH_OS_WINDOW return 0; } static void create_gpu_resources_for_window(Window *w) { w->render_data.vao_idx = create_cell_vao(); } static void release_gpu_resources_for_window(Window *w) { if (w->render_data.vao_idx > -1) remove_vao(w->render_data.vao_idx); w->render_data.vao_idx = -1; } static bool set_window_logo(Window *w, const char *path, const ImageAnchorPosition pos, float alpha, bool is_default, char *png_data, size_t png_data_size) { bool ok = false; if (path && path[0]) { window_logo_id_t wl = find_or_create_window_logo(global_state.all_window_logos, path, png_data, png_data_size); if (wl) { if (w->window_logo.id) decref_window_logo(global_state.all_window_logos, w->window_logo.id); w->window_logo.id = wl; w->window_logo.position = pos; w->window_logo.alpha = alpha; ok = true; } } else { if (w->window_logo.id) { decref_window_logo(global_state.all_window_logos, w->window_logo.id); w->window_logo.id = 0; } ok = true; } w->window_logo.using_default = is_default; if (ok && w->render_data.screen) w->render_data.screen->is_dirty = true; return ok; } static void initialize_window(Window *w, PyObject *title, bool init_gpu_resources) { w->id = ++global_state.window_id_counter; w->visible = true; w->title = title; Py_XINCREF(title); if (!set_window_logo(w, OPT(default_window_logo), OPT(window_logo_position), OPT(window_logo_alpha), true, NULL, 0)) { log_error("Failed to load default window logo: %s", OPT(default_window_logo)); if (PyErr_Occurred()) PyErr_Print(); } if (init_gpu_resources) create_gpu_resources_for_window(w); else { w->render_data.vao_idx = -1; } } static id_type add_window(id_type os_window_id, id_type tab_id, PyObject *title) { WITH_TAB(os_window_id, tab_id); ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true); make_os_window_context_current(osw); zero_at_i(tab->windows, tab->num_windows); initialize_window(tab->windows + tab->num_windows, title, true); return tab->windows[tab->num_windows++].id; END_WITH_TAB; return 0; } static void update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyObject *title) { WITH_WINDOW(os_window_id, tab_id, window_id) Py_CLEAR(window->title); window->title = title; Py_XINCREF(window->title); END_WITH_WINDOW; } void set_os_window_title_from_window(Window *w, OSWindow *os_window) { if (os_window->disallow_title_changes || os_window->title_is_overriden) return; if (w->title && w->title != os_window->window_title) { Py_XDECREF(os_window->window_title); os_window->window_title = w->title; Py_INCREF(os_window->window_title); set_os_window_title(os_window, PyUnicode_AsUTF8(w->title)); } } void update_os_window_title(OSWindow *os_window) { if (os_window->num_tabs) { Tab *tab = os_window->tabs + os_window->active_tab; if (tab->num_windows) { Window *w = tab->windows + tab->active_window; set_os_window_title_from_window(w, os_window); } } } static void destroy_window(Window *w) { free(w->pending_clicks.clicks); zero_at_ptr(&w->pending_clicks); free(w->buffered_keys.key_data); zero_at_ptr(&w->buffered_keys); Py_CLEAR(w->render_data.screen); Py_CLEAR(w->title); Py_CLEAR(w->title_bar_data.last_drawn_title_object_id); free(w->title_bar_data.buf); w->title_bar_data.buf = NULL; Py_CLEAR(w->url_target_bar_data.last_drawn_title_object_id); free(w->url_target_bar_data.buf); w->url_target_bar_data.buf = NULL; release_gpu_resources_for_window(w); if (w->window_logo.id) { decref_window_logo(global_state.all_window_logos, w->window_logo.id); w->window_logo.id = 0; } } static void remove_window_inner(Tab *tab, id_type id) { id_type active_window_id = 0; if (tab->active_window < tab->num_windows) active_window_id = tab->windows[tab->active_window].id; REMOVER(tab->windows, id, tab->num_windows, destroy_window, tab->capacity); if (active_window_id) { for (unsigned int w = 0; w < tab->num_windows; w++) { if (tab->windows[w].id == active_window_id) { tab->active_window = w; break; } } } } static void remove_window(id_type os_window_id, id_type tab_id, id_type id) { WITH_TAB(os_window_id, tab_id); make_os_window_context_current(osw); remove_window_inner(tab, id); END_WITH_TAB; } typedef struct { unsigned int num_windows, capacity; Window *windows; } DetachedWindows; static DetachedWindows detached_windows = {0}; static void add_detached_window(Window *w) { ensure_space_for(&detached_windows, windows, Window, detached_windows.num_windows + 1, capacity, 8, true); memcpy(detached_windows.windows + detached_windows.num_windows++, w, sizeof(Window)); } static void detach_window(id_type os_window_id, id_type tab_id, id_type id) { WITH_TAB(os_window_id, tab_id); for (size_t i = 0; i < tab->num_windows; i++) { if (tab->windows[i].id == id) { make_os_window_context_current(osw); release_gpu_resources_for_window(&tab->windows[i]); add_detached_window(tab->windows + i); zero_at_i(tab->windows, i); remove_i_from_array(tab->windows, i, tab->num_windows); break; } } END_WITH_TAB; } static void resize_screen(OSWindow *os_window, Screen *screen, bool has_graphics) { if (screen) { screen->cell_size.width = os_window->fonts_data->fcm.cell_width; screen->cell_size.height = os_window->fonts_data->fcm.cell_height; screen_dirty_sprite_positions(screen); if (has_graphics) screen_rescale_images(screen); } } static void attach_window(id_type os_window_id, id_type tab_id, id_type id) { WITH_TAB(os_window_id, tab_id); for (size_t i = 0; i < detached_windows.num_windows; i++) { if (detached_windows.windows[i].id == id) { ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true); Window *w = tab->windows + tab->num_windows++; memcpy(w, detached_windows.windows + i, sizeof(Window)); zero_at_i(detached_windows.windows, i); remove_i_from_array(detached_windows.windows, i, detached_windows.num_windows); make_os_window_context_current(osw); create_gpu_resources_for_window(w); if ( w->render_data.screen->cell_size.width != osw->fonts_data->fcm.cell_width || w->render_data.screen->cell_size.height != osw->fonts_data->fcm.cell_height ) resize_screen(osw, w->render_data.screen, true); else screen_dirty_sprite_positions(w->render_data.screen); w->render_data.screen->reload_all_gpu_data = true; break; } } END_WITH_TAB; } static void destroy_tab(Tab *tab) { for (size_t i = tab->num_windows; i > 0; i--) remove_window_inner(tab, tab->windows[i - 1].id); remove_vao(tab->border_rects.vao_idx); free(tab->border_rects.rect_buf); tab->border_rects.rect_buf = NULL; free(tab->windows); tab->windows = NULL; } static void remove_tab_inner(OSWindow *os_window, id_type id) { id_type active_tab_id = 0; if (os_window->active_tab < os_window->num_tabs) active_tab_id = os_window->tabs[os_window->active_tab].id; make_os_window_context_current(os_window); REMOVER(os_window->tabs, id, os_window->num_tabs, destroy_tab, os_window->capacity); if (active_tab_id) { for (unsigned int i = 0; i < os_window->num_tabs; i++) { if (os_window->tabs[i].id == active_tab_id) { os_window->active_tab = i; break; } } } } static void remove_tab(id_type os_window_id, id_type id) { WITH_OS_WINDOW(os_window_id) remove_tab_inner(os_window, id); END_WITH_OS_WINDOW } static void destroy_os_window_item(OSWindow *w) { for (size_t t = w->num_tabs; t > 0; t--) { Tab *tab = w->tabs + t - 1; remove_tab_inner(w, tab->id); } Py_CLEAR(w->window_title); Py_CLEAR(w->tab_bar_render_data.screen); remove_vao(w->tab_bar_render_data.vao_idx); free(w->tabs); w->tabs = NULL; free_bgimage(&w->bgimage, true); w->bgimage = NULL; } bool remove_os_window(id_type os_window_id) { bool found = false; WITH_OS_WINDOW(os_window_id) found = true; make_os_window_context_current(os_window); END_WITH_OS_WINDOW if (found) { WITH_OS_WINDOW_REFS REMOVER(global_state.os_windows, os_window_id, global_state.num_os_windows, destroy_os_window_item, global_state.capacity); END_WITH_OS_WINDOW_REFS update_os_window_references(); } return found; } static void mark_os_window_dirty(id_type os_window_id) { WITH_OS_WINDOW(os_window_id) os_window->needs_render = true; END_WITH_OS_WINDOW } static void set_active_tab(id_type os_window_id, unsigned int idx) { WITH_OS_WINDOW(os_window_id) os_window->active_tab = idx; os_window->needs_render = true; END_WITH_OS_WINDOW } static void set_active_window(id_type os_window_id, id_type tab_id, id_type window_id) { WITH_WINDOW(os_window_id, tab_id, window_id) (void)window; tab->active_window = w; osw->needs_render = true; set_os_window_chrome(osw); END_WITH_WINDOW; } static bool buffer_keys_in_window(id_type os_window_id, id_type tab_id, id_type window_id, bool enable) { WITH_WINDOW(os_window_id, tab_id, window_id) window->buffered_keys.enabled = enable; if (!enable) dispatch_buffered_keys(window); return true; END_WITH_WINDOW; return false; } static bool set_redirect_keys_to_overlay(id_type os_window_id, id_type tab_id, id_type window_id, id_type overlay_id) { WITH_WINDOW(os_window_id, tab_id, window_id) window->redirect_keys_to_overlay = overlay_id; return true; END_WITH_WINDOW; return false; } static void swap_tabs(id_type os_window_id, unsigned int a, unsigned int b) { WITH_OS_WINDOW(os_window_id) Tab t = os_window->tabs[b]; os_window->tabs[b] = os_window->tabs[a]; os_window->tabs[a] = t; END_WITH_OS_WINDOW } static void add_borders_rect(id_type os_window_id, id_type tab_id, uint32_t left, uint32_t top, uint32_t right, uint32_t bottom, uint32_t color) { WITH_TAB(os_window_id, tab_id) BorderRects *br = &tab->border_rects; br->is_dirty = true; if (!left && !top && !right && !bottom) { br->num_border_rects = 0; return; } ensure_space_for(br, rect_buf, BorderRect, br->num_border_rects + 1, capacity, 32, false); BorderRect *r = br->rect_buf + br->num_border_rects++; r->left = gl_pos_x(left, osw->viewport_width); r->top = gl_pos_y(top, osw->viewport_height); r->right = r->left + gl_size(right - left, osw->viewport_width); r->bottom = r->top - gl_size(bottom - top, osw->viewport_height); r->color = color; END_WITH_TAB } void os_window_regions(OSWindow *os_window, Region *central, Region *tab_bar) { if (!OPT(tab_bar_hidden) && os_window->num_tabs >= OPT(tab_bar_min_tabs)) { long margin_outer = pt_to_px_for_os_window(OPT(tab_bar_margin_height.outer), os_window); long margin_inner = pt_to_px_for_os_window(OPT(tab_bar_margin_height.inner), os_window); switch(OPT(tab_bar_edge)) { case TOP_EDGE: central->left = 0; central->right = os_window->viewport_width - 1; central->top = os_window->fonts_data->fcm.cell_height + margin_inner + margin_outer; central->bottom = os_window->viewport_height - 1; central->top = MIN(central->top, central->bottom); tab_bar->top = margin_outer; break; default: central->left = 0; central->top = 0; central->right = os_window->viewport_width - 1; long bottom = os_window->viewport_height - os_window->fonts_data->fcm.cell_height - 1 - margin_inner - margin_outer; central->bottom = MAX(0, bottom); tab_bar->top = central->bottom + 1 + margin_inner; break; } tab_bar->left = central->left; tab_bar->right = central->right; tab_bar->bottom = tab_bar->top + os_window->fonts_data->fcm.cell_height - 1; } else { zero_at_ptr(tab_bar); central->left = 0; central->top = 0; central->right = os_window->viewport_width - 1; central->bottom = os_window->viewport_height - 1; } } void mark_os_window_for_close(OSWindow* w, CloseRequest cr) { global_state.has_pending_closes = true; w->close_request = cr; } static bool owners_for_window_id(id_type window_id, OSWindow **os_window, Tab **tab) { if (os_window) *os_window = NULL; if (tab) *tab = NULL; for (size_t o = 0; o < global_state.num_os_windows; o++) { OSWindow *osw = global_state.os_windows + o; for (size_t t = 0; t < osw->num_tabs; t++) { Tab *qtab = osw->tabs + t; for (size_t w = 0; w < qtab->num_windows; w++) { Window *window = qtab->windows + w; if (window->id == window_id) { if (os_window) *os_window = osw; if (tab) *tab = qtab; return true; }}}} return false; } bool make_window_context_current(id_type window_id) { OSWindow *os_window; if (owners_for_window_id(window_id, &os_window, NULL)) { make_os_window_context_current(os_window); return true; } return false; } void dispatch_pending_clicks(id_type timer_id UNUSED, void *data UNUSED) { bool dispatched = false; do { // dispatching a click can cause windows/tabs/etc to close so do it one at a time. const monotonic_t now = monotonic(); dispatched = false; for (size_t o = 0; o < global_state.num_os_windows && !dispatched; o++) { OSWindow *osw = global_state.os_windows + o; for (size_t t = 0; t < osw->num_tabs && !dispatched; t++) { Tab *qtab = osw->tabs + t; for (size_t w = 0; w < qtab->num_windows && !dispatched; w++) { Window *window = qtab->windows + w; for (size_t i = 0; i < window->pending_clicks.num && !dispatched; i++) { if (now - window->pending_clicks.clicks[i].at >= OPT(click_interval)) { dispatched = true; send_pending_click_to_window(window, i); } } } } } } while (dispatched); } bool update_ime_position_for_window(id_type window_id, bool force, int update_focus) { for (size_t o = 0; o < global_state.num_os_windows; o++) { OSWindow *osw = global_state.os_windows + o; for (size_t t = 0; t < osw->num_tabs; t++) { Tab *qtab = osw->tabs + t; for (size_t w = 0; w < qtab->num_windows; w++) { Window *window = qtab->windows + w; if (window->id == window_id) { // The screen may not be ready after the new window is created and focused, and still needs to enable IME. if ((window->render_data.screen && (force || osw->is_focused)) || update_focus > 0) { OSWindow *orig = global_state.callback_os_window; global_state.callback_os_window = osw; if (update_focus) update_ime_focus(osw, update_focus > 0); if (update_focus >= 0 && window->render_data.screen) { update_ime_position(window, window->render_data.screen); } global_state.callback_os_window = orig; return true; } return false; } } } } return false; } // Python API {{{ #define PYWRAP0(name) static PyObject* py##name(PYNOARG) #define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args) #define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL; #define ONE_UINT(name) PYWRAP1(name) { name((unsigned int)PyLong_AsUnsignedLong(args)); Py_RETURN_NONE; } #define TWO_UINT(name) PYWRAP1(name) { unsigned int a, b; PA("II", &a, &b); name(a, b); Py_RETURN_NONE; } #define THREE_UINT(name) PYWRAP1(name) { unsigned int a, b, c; PA("III", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; } #define TWO_ID(name) PYWRAP1(name) { id_type a, b; PA("KK", &a, &b); name(a, b); Py_RETURN_NONE; } #define THREE_ID(name) PYWRAP1(name) { id_type a, b, c; PA("KKK", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; } #define THREE_ID_OBJ(name) PYWRAP1(name) { id_type a, b, c; PyObject *o; PA("KKKO", &a, &b, &c, &o); name(a, b, c, o); Py_RETURN_NONE; } #define K(name) PYWRAP1(name) { id_type a; PA("K", &a); name(a); Py_RETURN_NONE; } #define KI(name) PYWRAP1(name) { id_type a; unsigned int b; PA("KI", &a, &b); name(a, b); Py_RETURN_NONE; } #define KII(name) PYWRAP1(name) { id_type a; unsigned int b, c; PA("KII", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; } #define KKI(name) PYWRAP1(name) { id_type a, b; unsigned int c; PA("KKI", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; } #define KKK(name) PYWRAP1(name) { id_type a, b, c; PA("KKK", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; } #define KKII(name) PYWRAP1(name) { id_type a, b; unsigned int c, d; PA("KKII", &a, &b, &c, &d); name(a, b, c, d); Py_RETURN_NONE; } #define KKKK(name) PYWRAP1(name) { id_type a, b, c, d; PA("KKKK", &a, &b, &c, &d); name(a, b, c, d); Py_RETURN_NONE; } #define KK5I(name) PYWRAP1(name) { id_type a, b; unsigned int c, d, e, f, g; PA("KKIIIII", &a, &b, &c, &d, &e, &f, &g); name(a, b, c, d, e, f, g); Py_RETURN_NONE; } #define BOOL_SET(name) PYWRAP1(set_##name) { global_state.name = PyObject_IsTrue(args); Py_RETURN_NONE; } #define dict_iter(d) { \ PyObject *key, *value; Py_ssize_t pos = 0; \ while (PyDict_Next(d, &pos, &key, &value)) PYWRAP1(update_ime_position_for_window) { id_type window_id; int force = 0; int update_focus = 0; PA("K|pi", &window_id, &force, &update_focus); if (update_ime_position_for_window(window_id, force, update_focus)) Py_RETURN_TRUE; Py_RETURN_FALSE; } PYWRAP0(next_window_id) { return PyLong_FromUnsignedLongLong(global_state.window_id_counter + 1); } PYWRAP0(last_focused_os_window_id) { return PyLong_FromUnsignedLongLong(last_focused_os_window_id()); } PYWRAP0(current_focused_os_window_id) { return PyLong_FromUnsignedLongLong(current_focused_os_window_id()); } PYWRAP1(handle_for_window_id) { id_type os_window_id; PA("K", &os_window_id); WITH_OS_WINDOW(os_window_id) return PyLong_FromVoidPtr(os_window->handle); END_WITH_OS_WINDOW PyErr_SetString(PyExc_ValueError, "No such window"); return NULL; } PYWRAP0(get_options) { if (!global_state.options_object) { PyErr_SetString(PyExc_RuntimeError, "Must call set_options() before using get_options()"); return NULL; } Py_INCREF(global_state.options_object); return global_state.options_object; } PYWRAP1(set_options) { PyObject *opts; int is_wayland = 0, debug_rendering = 0, debug_font_fallback = 0; PA("O|ppp", &opts, &is_wayland, &debug_rendering, &debug_font_fallback); if (opts == Py_None) { Py_CLEAR(global_state.options_object); Py_RETURN_NONE; } global_state.is_wayland = is_wayland ? true : false; #ifdef __APPLE__ global_state.has_render_frames = true; #endif if (global_state.is_wayland) global_state.has_render_frames = true; global_state.debug_rendering = debug_rendering ? true : false; global_state.debug_font_fallback = debug_font_fallback ? true : false; if (!convert_opts_from_python_opts(opts, &global_state.opts)) return NULL; global_state.options_object = opts; Py_INCREF(global_state.options_object); Py_RETURN_NONE; } PYWRAP1(set_ignore_os_keyboard_processing) { set_ignore_os_keyboard_processing(PyObject_IsTrue(args)); Py_RETURN_NONE; } static void init_window_render_data(OSWindow *osw, const WindowGeometry *g, WindowRenderData *d) { d->dx = gl_size(osw->fonts_data->fcm.cell_width, osw->viewport_width); d->dy = gl_size(osw->fonts_data->fcm.cell_height, osw->viewport_height); d->xstart = gl_pos_x(g->left, osw->viewport_width); d->ystart = gl_pos_y(g->top, osw->viewport_height); } PYWRAP1(set_tab_bar_render_data) { WindowRenderData d = {0}; WindowGeometry g = {0}; id_type os_window_id; PA("KOIIII", &os_window_id, &d.screen, &g.left, &g.top, &g.right, &g.bottom); WITH_OS_WINDOW(os_window_id) Py_CLEAR(os_window->tab_bar_render_data.screen); d.vao_idx = os_window->tab_bar_render_data.vao_idx; init_window_render_data(os_window, &g, &d); os_window->tab_bar_render_data = d; Py_INCREF(os_window->tab_bar_render_data.screen); END_WITH_OS_WINDOW Py_RETURN_NONE; } static PyTypeObject RegionType; static PyStructSequence_Field region_fields[] = { {"left", ""}, {"top", ""}, {"right", ""}, {"bottom", ""}, {"width", ""}, {"height", ""}, {NULL, NULL} }; static PyStructSequence_Desc region_desc = {"Region", NULL, region_fields, 6}; static PyObject* wrap_region(Region *r) { PyObject *ans = PyStructSequence_New(&RegionType); if (ans) { PyStructSequence_SET_ITEM(ans, 0, PyLong_FromUnsignedLong(r->left)); PyStructSequence_SET_ITEM(ans, 1, PyLong_FromUnsignedLong(r->top)); PyStructSequence_SET_ITEM(ans, 2, PyLong_FromUnsignedLong(r->right)); PyStructSequence_SET_ITEM(ans, 3, PyLong_FromUnsignedLong(r->bottom)); PyStructSequence_SET_ITEM(ans, 4, PyLong_FromUnsignedLong(r->right - r->left + 1)); PyStructSequence_SET_ITEM(ans, 5, PyLong_FromUnsignedLong(r->bottom - r->top + 1)); } return ans; } PYWRAP1(viewport_for_window) { id_type os_window_id; int vw = 100, vh = 100; unsigned int cell_width = 1, cell_height = 1; PA("K", &os_window_id); Region central = {0}, tab_bar = {0}; WITH_OS_WINDOW(os_window_id) os_window_regions(os_window, ¢ral, &tab_bar); vw = os_window->viewport_width; vh = os_window->viewport_height; cell_width = os_window->fonts_data->fcm.cell_width; cell_height = os_window->fonts_data->fcm.cell_height; goto end; END_WITH_OS_WINDOW end: return Py_BuildValue("NNiiII", wrap_region(¢ral), wrap_region(&tab_bar), vw, vh, cell_width, cell_height); } PYWRAP1(cell_size_for_window) { id_type os_window_id; unsigned int cell_width = 0, cell_height = 0; PA("K", &os_window_id); WITH_OS_WINDOW(os_window_id) cell_width = os_window->fonts_data->fcm.cell_width; cell_height = os_window->fonts_data->fcm.cell_height; goto end; END_WITH_OS_WINDOW end: return Py_BuildValue("II", cell_width, cell_height); } PYWRAP1(os_window_has_background_image) { id_type os_window_id; PA("K", &os_window_id); WITH_OS_WINDOW(os_window_id) if (os_window->bgimage && os_window->bgimage->texture_id > 0) { Py_RETURN_TRUE; } END_WITH_OS_WINDOW Py_RETURN_FALSE; } PYWRAP1(mark_os_window_for_close) { id_type os_window_id; CloseRequest cr = IMPERATIVE_CLOSE_REQUESTED; PA("K|i", &os_window_id, &cr); WITH_OS_WINDOW(os_window_id) mark_os_window_for_close(os_window, cr); Py_RETURN_TRUE; END_WITH_OS_WINDOW Py_RETURN_FALSE; } PYWRAP1(set_application_quit_request) { CloseRequest cr = IMPERATIVE_CLOSE_REQUESTED; PA("|i", &cr); global_state.quit_request = cr; global_state.has_pending_closes = true; request_tick_callback(); Py_RETURN_NONE; } PYWRAP0(current_application_quit_request) { return Py_BuildValue("i", global_state.quit_request); } PYWRAP1(focus_os_window) { id_type os_window_id; int also_raise = 1; const char *activation_token = NULL; PA("K|pz", &os_window_id, &also_raise, &activation_token); WITH_OS_WINDOW(os_window_id) if (!os_window->is_focused || (activation_token && activation_token[0])) focus_os_window(os_window, also_raise, activation_token); Py_RETURN_TRUE; END_WITH_OS_WINDOW Py_RETURN_FALSE; } PYWRAP1(run_with_activation_token) { for (size_t o = 0; o < global_state.num_os_windows; o++) { OSWindow *os_window = global_state.os_windows + o; if (os_window->is_focused) { run_with_activation_token_in_os_window(os_window, args); Py_RETURN_TRUE; } } id_type os_window_id = last_focused_os_window_id(); if (!os_window_id) { if (!global_state.num_os_windows) Py_RETURN_FALSE; os_window_id = global_state.os_windows[0].id; } for (size_t o = 0; o < global_state.num_os_windows; o++) { OSWindow *os_window = global_state.os_windows + o; if (os_window->id == os_window_id) { run_with_activation_token_in_os_window(os_window, args); Py_RETURN_TRUE; } } Py_RETURN_FALSE; } PYWRAP1(set_os_window_chrome) { id_type os_window_id; PA("K", &os_window_id); WITH_OS_WINDOW(os_window_id) set_os_window_chrome(os_window); Py_RETURN_TRUE; END_WITH_OS_WINDOW Py_RETURN_FALSE; } PYWRAP1(mark_tab_bar_dirty) { id_type os_window_id = PyLong_AsUnsignedLongLong(args); if (PyErr_Occurred()) return NULL; WITH_OS_WINDOW(os_window_id) os_window->tab_bar_data_updated = false; END_WITH_OS_WINDOW Py_RETURN_NONE; } PYWRAP1(change_background_opacity) { id_type os_window_id; float opacity; PA("Kf", &os_window_id, &opacity); WITH_OS_WINDOW(os_window_id) os_window->background_opacity = opacity; if (!os_window->redraw_count) os_window->redraw_count++; Py_RETURN_TRUE; END_WITH_OS_WINDOW Py_RETURN_FALSE; } PYWRAP1(background_opacity_of) { id_type os_window_id = PyLong_AsUnsignedLongLong(args); if (PyErr_Occurred()) return NULL; WITH_OS_WINDOW(os_window_id) return PyFloat_FromDouble((double)os_window->background_opacity); END_WITH_OS_WINDOW Py_RETURN_NONE; } PYWRAP1(set_window_padding) { id_type os_window_id, tab_id, window_id; unsigned int left, top, right, bottom; PA("KKKIIII", &os_window_id, &tab_id, &window_id, &left, &top, &right, &bottom); WITH_WINDOW(os_window_id, tab_id, window_id); window->padding.left = left; window->padding.top = top; window->padding.right = right; window->padding.bottom = bottom; END_WITH_WINDOW; Py_RETURN_NONE; } PYWRAP1(set_window_render_data) { #define A(name) &(d.name) #define B(name) &(g.name) id_type os_window_id, tab_id, window_id; WindowRenderData d = {0}; WindowGeometry g = {0}; PA("KKKOIIII", &os_window_id, &tab_id, &window_id, A(screen), B(left), B(top), B(right), B(bottom)); WITH_WINDOW(os_window_id, tab_id, window_id); Py_CLEAR(window->render_data.screen); d.vao_idx = window->render_data.vao_idx; init_window_render_data(osw, &g, &d); window->render_data = d; window->geometry = g; Py_INCREF(window->render_data.screen); END_WITH_WINDOW; Py_RETURN_NONE; #undef A #undef B } PYWRAP1(update_window_visibility) { id_type os_window_id, tab_id, window_id; int visible; PA("KKKp", &os_window_id, &tab_id, &window_id, &visible); WITH_WINDOW(os_window_id, tab_id, window_id); bool was_visible = window->visible & 1; window->visible = visible & 1; if (!was_visible && window->visible) global_state.check_for_active_animated_images = true; END_WITH_WINDOW; Py_RETURN_NONE; } PYWRAP1(sync_os_window_title) { id_type os_window_id; PA("K", &os_window_id); WITH_OS_WINDOW(os_window_id) update_os_window_title(os_window); END_WITH_OS_WINDOW Py_RETURN_NONE; } PYWRAP1(set_os_window_title) { id_type os_window_id; PyObject *title; PA("KU", &os_window_id, &title); WITH_OS_WINDOW(os_window_id) if (os_window->disallow_title_changes) break; if (PyUnicode_GetLength(title)) { os_window->title_is_overriden = true; Py_XDECREF(os_window->window_title); os_window->window_title = title; Py_INCREF(title); set_os_window_title(os_window, PyUnicode_AsUTF8(title)); } else { os_window->title_is_overriden = false; if (os_window->window_title) set_os_window_title(os_window, PyUnicode_AsUTF8(os_window->window_title)); update_os_window_title(os_window); } END_WITH_OS_WINDOW Py_RETURN_NONE; } PYWRAP1(get_os_window_title) { id_type os_window_id; PA("K", &os_window_id); WITH_OS_WINDOW(os_window_id) if (os_window->window_title) return Py_BuildValue("O", os_window->window_title); END_WITH_OS_WINDOW Py_RETURN_NONE; } PYWRAP1(os_window_is_invisible) { id_type os_window_id = PyLong_AsUnsignedLongLong(args); if (PyErr_Occurred()) return NULL; WITH_OS_WINDOW(os_window_id) if (should_os_window_be_rendered(os_window)) { Py_RETURN_FALSE; } Py_RETURN_TRUE; END_WITH_OS_WINDOW Py_RETURN_FALSE; } PYWRAP1(pt_to_px) { double pt; id_type os_window_id = 0; PA("d|K", &pt, &os_window_id); return PyLong_FromLong(pt_to_px(pt, os_window_id)); } PYWRAP1(global_font_size) { double set_val = -1; PA("|d", &set_val); if (set_val > 0) OPT(font_size) = set_val; return Py_BuildValue("d", OPT(font_size)); } PYWRAP1(os_window_font_size) { id_type os_window_id; int force = 0; double new_sz = -1; PA("K|dp", &os_window_id, &new_sz, &force); WITH_OS_WINDOW(os_window_id) if (new_sz > 0 && (force || new_sz != os_window->fonts_data->font_sz_in_pts)) { double xdpi, ydpi; float xscale, yscale; get_os_window_content_scale(os_window, &xdpi, &ydpi, &xscale, &yscale); os_window->fonts_data = load_fonts_data(new_sz, xdpi, ydpi); send_prerendered_sprites_for_window(os_window); resize_screen(os_window, os_window->tab_bar_render_data.screen, false); for (size_t ti = 0; ti < os_window->num_tabs; ti++) { Tab *tab = os_window->tabs + ti; for (size_t wi = 0; wi < tab->num_windows; wi++) { Window *w = tab->windows + wi; resize_screen(os_window, w->render_data.screen, true); } } os_window_update_size_increments(os_window); // On Wayland with CSD title needs to be re-rendered in a different font size if (os_window->window_title && global_state.is_wayland) set_os_window_title(os_window, NULL); } return Py_BuildValue("d", os_window->fonts_data->font_sz_in_pts); END_WITH_OS_WINDOW return Py_BuildValue("d", 0.0); } PYWRAP1(set_os_window_size) { id_type os_window_id; int width, height; PA("Kii", &os_window_id, &width, &height); WITH_OS_WINDOW(os_window_id) set_os_window_size(os_window, width, height); Py_RETURN_TRUE; END_WITH_OS_WINDOW Py_RETURN_FALSE; } PYWRAP1(get_os_window_size) { id_type os_window_id; PA("K", &os_window_id); WITH_OS_WINDOW(os_window_id) double xdpi, ydpi; float xscale, yscale; int width, height, fw, fh; get_os_window_size(os_window, &width, &height, &fw, &fh); get_os_window_content_scale(os_window, &xdpi, &ydpi, &xscale, &yscale); unsigned int cell_width = os_window->fonts_data->fcm.cell_width, cell_height = os_window->fonts_data->fcm.cell_height; return Py_BuildValue("{si si si si sf sf sd sd sI sI}", "width", width, "height", height, "framebuffer_width", fw, "framebuffer_height", fh, "xscale", xscale, "yscale", yscale, "xdpi", xdpi, "ydpi", ydpi, "cell_width", cell_width, "cell_height", cell_height); END_WITH_OS_WINDOW Py_RETURN_NONE; } PYWRAP1(get_os_window_pos) { id_type os_window_id; PA("K", &os_window_id); WITH_OS_WINDOW(os_window_id) int x, y; get_os_window_pos(os_window, &x, &y); return Py_BuildValue("ii", x, y); END_WITH_OS_WINDOW Py_RETURN_NONE; } PYWRAP1(set_os_window_pos) { id_type os_window_id; int x, y; PA("Kii", &os_window_id, &x, &y); WITH_OS_WINDOW(os_window_id) set_os_window_pos(os_window, x, y); END_WITH_OS_WINDOW Py_RETURN_NONE; } PYWRAP1(set_boss) { Py_CLEAR(global_state.boss); global_state.boss = args; Py_INCREF(global_state.boss); Py_RETURN_NONE; } PYWRAP0(get_boss) { if (global_state.boss) { Py_INCREF(global_state.boss); return global_state.boss; } Py_RETURN_NONE; } PYWRAP0(apply_options_update) { for (size_t o = 0; o < global_state.num_os_windows; o++) { OSWindow *os_window = global_state.os_windows + o; get_platform_dependent_config_values(os_window->handle); os_window->background_opacity = OPT(background_opacity); if (!os_window->redraw_count) os_window->redraw_count++; for (size_t t = 0; t < os_window->num_tabs; t++) { Tab *tab = os_window->tabs + t; for (size_t w = 0; w < tab->num_windows; w++) { Window *window = tab->windows + w; if (window->window_logo.using_default) { set_window_logo(window, OPT(default_window_logo), OPT(window_logo_position), OPT(window_logo_alpha), true, NULL, 0); } } } } Py_RETURN_NONE; } PYWRAP1(patch_global_colors) { PyObject *spec; int configured; if (!PyArg_ParseTuple(args, "Op", &spec, &configured)) return NULL; #define P(name) { \ PyObject *val = PyDict_GetItemString(spec, #name); \ if (val) { \ if (val == Py_None) OPT(name) = 0; \ else if (PyLong_Check(val)) OPT(name) = PyLong_AsLong(val); \ } \ } P(active_border_color); P(inactive_border_color); P(bell_border_color); P(tab_bar_background); P(tab_bar_margin_color); if (configured) { P(background); P(url_color); } if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } PYWRAP1(update_tab_bar_edge_colors) { id_type os_window_id; PA("K", &os_window_id); WITH_OS_WINDOW(os_window_id) if (os_window->tab_bar_render_data.screen) { if (get_line_edge_colors(os_window->tab_bar_render_data.screen, &os_window->tab_bar_edge_color.left, &os_window->tab_bar_edge_color.right)) { Py_RETURN_TRUE; } } END_WITH_OS_WINDOW Py_RETURN_FALSE; } static PyObject* pyset_background_image(PyObject *self UNUSED, PyObject *args) { const char *path; PyObject *layout_name = NULL; PyObject *os_window_ids; int configured = 0; char *png_data = NULL; Py_ssize_t png_data_size = 0; PA("zO!|pOy#", &path, &PyTuple_Type, &os_window_ids, &configured, &layout_name, &png_data, &png_data_size); size_t size; BackgroundImageLayout layout = PyUnicode_Check(layout_name) ? bglayout(layout_name) : OPT(background_image_layout); BackgroundImage *bgimage = NULL; if (path) { bgimage = calloc(1, sizeof(BackgroundImage)); if (!bgimage) return PyErr_NoMemory(); bool ok; if (png_data && png_data_size) { ok = png_from_data(png_data, png_data_size, path, &bgimage->bitmap, &bgimage->width, &bgimage->height, &size); } else { ok = image_path_to_bitmap(path, &bgimage->bitmap, &bgimage->width, &bgimage->height, &bgimage->mmap_size); } if (!ok) { PyErr_Format(PyExc_ValueError, "Failed to load image from: %s", path); free(bgimage); return NULL; } send_bgimage_to_gpu(layout, bgimage); bgimage->refcnt++; } if (configured) { free_bgimage(&global_state.bgimage, true); global_state.bgimage = bgimage; if (bgimage) bgimage->refcnt++; OPT(background_image_layout) = layout; } for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(os_window_ids); i++) { id_type os_window_id = PyLong_AsUnsignedLongLong(PyTuple_GET_ITEM(os_window_ids, i)); WITH_OS_WINDOW(os_window_id) make_os_window_context_current(os_window); free_bgimage(&os_window->bgimage, true); os_window->bgimage = bgimage; os_window->render_calls = 0; if (bgimage) bgimage->refcnt++; END_WITH_OS_WINDOW } if (bgimage) free_bgimage(&bgimage, true); Py_RETURN_NONE; } PYWRAP0(destroy_global_data) { Py_CLEAR(global_state.boss); free(global_state.os_windows); global_state.os_windows = NULL; Py_RETURN_NONE; } PYWRAP0(wakeup_main_loop) { wakeup_main_loop(); Py_RETURN_NONE; } static void destroy_mock_window(PyObject *capsule) { Window *w = PyCapsule_GetPointer(capsule, "Window"); if (w) { destroy_window(w); PyMem_Free(w); } } static PyObject* pycreate_mock_window(PyObject *self UNUSED, PyObject *args) { Screen *screen; PyObject *title = NULL; if (!PyArg_ParseTuple(args, "O|U", &screen, &title)) return NULL; Window *w = PyMem_Calloc(sizeof(Window), 1); if (!w) return NULL; Py_INCREF(screen); PyObject *ans = PyCapsule_New(w, "Window", destroy_mock_window); if (ans != NULL) { initialize_window(w, title, false); w->render_data.screen = screen; } return ans; } static bool click_mouse_url(id_type os_window_id, id_type tab_id, id_type window_id) { bool clicked = false; WITH_WINDOW(os_window_id, tab_id, window_id); clicked = mouse_open_url(window); END_WITH_WINDOW; return clicked; } static bool click_mouse_cmd_output(id_type os_window_id, id_type tab_id, id_type window_id, int select_cmd_output) { bool handled = false; WITH_WINDOW(os_window_id, tab_id, window_id); handled = mouse_set_last_visited_cmd_output(window); if (select_cmd_output && handled) handled = mouse_select_cmd_output(window); END_WITH_WINDOW; return handled; } static bool move_cursor_to_mouse_if_in_prompt(id_type os_window_id, id_type tab_id, id_type window_id) { bool moved = false; WITH_WINDOW(os_window_id, tab_id, window_id); moved = move_cursor_to_mouse_if_at_shell_prompt(window); END_WITH_WINDOW; return moved; } static PyObject* pyupdate_pointer_shape(PyObject *self UNUSED, PyObject *args) { id_type os_window_id; PA("K", &os_window_id); WITH_OS_WINDOW(os_window_id); OSWindow *orig = global_state.callback_os_window; global_state.callback_os_window = os_window; update_mouse_pointer_shape(); global_state.callback_os_window = orig; END_WITH_OS_WINDOW; Py_RETURN_NONE; } static PyObject* pymouse_selection(PyObject *self UNUSED, PyObject *args) { id_type os_window_id, tab_id, window_id; int code, button; PA("KKKii", &os_window_id, &tab_id, &window_id, &code, &button); WITH_WINDOW(os_window_id, tab_id, window_id); mouse_selection(window, code, button); END_WITH_WINDOW; Py_RETURN_NONE; } PYWRAP1(set_window_logo) { id_type os_window_id, tab_id, window_id; const char *path; PyObject *position; float alpha = 0.5; char *png_data = NULL; Py_ssize_t png_data_size = 0; PA("KKKsUf|y#", &os_window_id, &tab_id, &window_id, &path, &position, &alpha, &png_data, &png_data_size); bool ok = false; WITH_WINDOW(os_window_id, tab_id, window_id); ok = set_window_logo(window, path, PyObject_IsTrue(position) ? bganchor(position) : OPT(window_logo_position), (0 <= alpha && alpha <= 1) ? alpha : OPT(window_logo_alpha), false, png_data, png_data_size); END_WITH_WINDOW; if (ok) Py_RETURN_TRUE; Py_RETURN_FALSE; } PYWRAP1(click_mouse_url) { id_type a, b, c; PA("KKK", &a, &b, &c); if (click_mouse_url(a, b, c)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } PYWRAP1(click_mouse_cmd_output) { id_type a, b, c; int d; PA("KKKp", &a, &b, &c, &d); if (click_mouse_cmd_output(a, b, c, d)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } PYWRAP1(move_cursor_to_mouse_if_in_prompt) { id_type a, b, c; PA("KKK", &a, &b, &c); if (move_cursor_to_mouse_if_in_prompt(a, b, c)) Py_RETURN_TRUE; Py_RETURN_FALSE; } PYWRAP1(redirect_mouse_handling) { global_state.redirect_mouse_handling = PyObject_IsTrue(args) ? true : false; Py_RETURN_NONE; } PYWRAP1(buffer_keys_in_window) { int enabled = 1; id_type a, b, c; PA("KKK|p", &a, &b, &c, &enabled); if (buffer_keys_in_window(a, b, c, enabled)) Py_RETURN_TRUE; Py_RETURN_FALSE; } THREE_ID_OBJ(update_window_title) THREE_ID(remove_window) THREE_ID(detach_window) THREE_ID(attach_window) PYWRAP1(add_tab) { return PyLong_FromUnsignedLongLong(add_tab(PyLong_AsUnsignedLongLong(args))); } PYWRAP1(add_window) { PyObject *title; id_type a, b; PA("KKO", &a, &b, &title); return PyLong_FromUnsignedLongLong(add_window(a, b, title)); } PYWRAP0(current_os_window) { OSWindow *w = current_os_window(); if (!w) Py_RETURN_NONE; return PyLong_FromUnsignedLongLong(w->id); } TWO_ID(remove_tab) KI(set_active_tab) K(mark_os_window_dirty) KKK(set_active_window) KII(swap_tabs) KK5I(add_borders_rect) KKKK(set_redirect_keys_to_overlay) static PyObject* os_window_focus_counters(PyObject *self UNUSED, PyObject *args UNUSED) { RAII_PyObject(ans, PyDict_New()); for (size_t i = 0; i < global_state.num_os_windows; i++) { OSWindow *w = &global_state.os_windows[i]; RAII_PyObject(key, PyLong_FromUnsignedLongLong(w->id)); RAII_PyObject(val, PyLong_FromUnsignedLongLong(w->last_focused_counter)); if (!key || !val) return NULL; if (PyDict_SetItem(ans, key, val) != 0) return NULL; } Py_INCREF(ans); return ans; } static PyObject* get_mouse_data_for_window(PyObject *self UNUSED, PyObject *args) { id_type os_window_id, tab_id, window_id; PA("KKK", &os_window_id, &tab_id, &window_id); WITH_WINDOW(os_window_id, tab_id, window_id) return Py_BuildValue("{sI sI sO}", "cell_x", window->mouse_pos.cell_x, "cell_y", window->mouse_pos.cell_y, "in_left_half_of_cell", window->mouse_pos.in_left_half_of_cell ? Py_True: Py_False); END_WITH_WINDOW Py_RETURN_NONE; } #define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL} #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} static PyMethodDef module_methods[] = { M(os_window_focus_counters, METH_NOARGS), M(get_mouse_data_for_window, METH_VARARGS), MW(update_pointer_shape, METH_VARARGS), MW(current_os_window, METH_NOARGS), MW(next_window_id, METH_NOARGS), MW(last_focused_os_window_id, METH_NOARGS), MW(current_focused_os_window_id, METH_NOARGS), MW(set_options, METH_VARARGS), MW(get_options, METH_NOARGS), MW(click_mouse_url, METH_VARARGS), MW(click_mouse_cmd_output, METH_VARARGS), MW(move_cursor_to_mouse_if_in_prompt, METH_VARARGS), MW(redirect_mouse_handling, METH_O), MW(mouse_selection, METH_VARARGS), MW(set_window_logo, METH_VARARGS), MW(set_ignore_os_keyboard_processing, METH_O), MW(handle_for_window_id, METH_VARARGS), MW(update_ime_position_for_window, METH_VARARGS), MW(pt_to_px, METH_VARARGS), MW(add_tab, METH_O), MW(add_window, METH_VARARGS), MW(update_window_title, METH_VARARGS), MW(remove_tab, METH_VARARGS), MW(remove_window, METH_VARARGS), MW(detach_window, METH_VARARGS), MW(attach_window, METH_VARARGS), MW(set_active_tab, METH_VARARGS), MW(mark_os_window_dirty, METH_VARARGS), MW(set_redirect_keys_to_overlay, METH_VARARGS), MW(buffer_keys_in_window, METH_VARARGS), MW(set_active_window, METH_VARARGS), MW(swap_tabs, METH_VARARGS), MW(add_borders_rect, METH_VARARGS), MW(set_tab_bar_render_data, METH_VARARGS), MW(set_window_render_data, METH_VARARGS), MW(set_window_padding, METH_VARARGS), MW(viewport_for_window, METH_VARARGS), MW(cell_size_for_window, METH_VARARGS), MW(os_window_has_background_image, METH_VARARGS), MW(mark_os_window_for_close, METH_VARARGS), MW(set_application_quit_request, METH_VARARGS), MW(current_application_quit_request, METH_NOARGS), MW(set_os_window_chrome, METH_VARARGS), MW(focus_os_window, METH_VARARGS), MW(mark_tab_bar_dirty, METH_O), MW(run_with_activation_token, METH_O), MW(change_background_opacity, METH_VARARGS), MW(background_opacity_of, METH_O), MW(update_window_visibility, METH_VARARGS), MW(sync_os_window_title, METH_VARARGS), MW(get_os_window_title, METH_VARARGS), MW(set_os_window_title, METH_VARARGS), MW(get_os_window_pos, METH_VARARGS), MW(set_os_window_pos, METH_VARARGS), MW(global_font_size, METH_VARARGS), MW(set_background_image, METH_VARARGS), MW(os_window_font_size, METH_VARARGS), MW(set_os_window_size, METH_VARARGS), MW(get_os_window_size, METH_VARARGS), MW(os_window_is_invisible, METH_O), MW(update_tab_bar_edge_colors, METH_VARARGS), MW(set_boss, METH_O), MW(get_boss, METH_NOARGS), MW(apply_options_update, METH_NOARGS), MW(patch_global_colors, METH_VARARGS), MW(create_mock_window, METH_VARARGS), MW(destroy_global_data, METH_NOARGS), MW(wakeup_main_loop, METH_NOARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; static void finalize(void) { while(detached_windows.num_windows--) { destroy_window(&detached_windows.windows[detached_windows.num_windows]); } if (detached_windows.windows) free(detached_windows.windows); detached_windows.capacity = 0; #define F(x) free(OPT(x)); OPT(x) = NULL; F(background_image); F(bell_path); F(bell_theme); F(default_window_logo); #undef F Py_CLEAR(global_state.options_object); free_animation(OPT(animation.cursor)); free_animation(OPT(animation.visual_bell)); // we leak the texture here since it is not guaranteed // that freeing the texture will work during shutdown and // the GPU driver should take care of it when the OpenGL context is // destroyed. free_bgimage(&global_state.bgimage, false); free_window_logo_table(&global_state.all_window_logos); global_state.bgimage = NULL; free_allocs_in_options(&global_state.opts); } bool init_state(PyObject *module) { OPT(font_size) = 11.0; #ifdef __APPLE__ #define DPI 72.0 #else #define DPI 96.0 #endif global_state.default_dpi.x = DPI; global_state.default_dpi.y = DPI; global_state.all_window_logos = alloc_window_logo_table(); if (!global_state.all_window_logos) { PyErr_NoMemory(); return false; } if (PyModule_AddFunctions(module, module_methods) != 0) return false; if (PyStructSequence_InitType2(&RegionType, ®ion_desc) != 0) return false; Py_INCREF((PyObject *) &RegionType); PyModule_AddObject(module, "Region", (PyObject *) &RegionType); PyModule_AddIntConstant(module, "IMPERATIVE_CLOSE_REQUESTED", IMPERATIVE_CLOSE_REQUESTED); PyModule_AddIntConstant(module, "NO_CLOSE_REQUESTED", NO_CLOSE_REQUESTED); PyModule_AddIntConstant(module, "CLOSE_BEING_CONFIRMED", CLOSE_BEING_CONFIRMED); PyModule_AddIntMacro(module, WINDOW_NORMAL); PyModule_AddIntMacro(module, WINDOW_FULLSCREEN); PyModule_AddIntMacro(module, WINDOW_MAXIMIZED); PyModule_AddIntMacro(module, WINDOW_MINIMIZED); PyModule_AddIntMacro(module, TOP_EDGE); PyModule_AddIntMacro(module, BOTTOM_EDGE); register_at_exit_cleanup_func(STATE_CLEANUP_FUNC, finalize); return true; } // }}} kitty-0.41.1/kitty/state.h0000664000175000017510000003675214773370543014761 0ustar nileshnilesh/* * Copyright (C) 2017 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #pragma once #include "data-types.h" #include "animation.h" #include "screen.h" #include "monotonic.h" #include "window_logo.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" #include #pragma GCC diagnostic pop #define OPT(name) global_state.opts.name #define debug_rendering(...) if (global_state.debug_rendering) { timed_debug_print(__VA_ARGS__); } #define debug_input(...) if (OPT(debug_keyboard)) { timed_debug_print(__VA_ARGS__); } #define debug_fonts(...) if (global_state.debug_font_fallback) { timed_debug_print(__VA_ARGS__); } typedef enum { LEFT_EDGE = 1, TOP_EDGE = 2, RIGHT_EDGE = 4, BOTTOM_EDGE = 8 } Edge; typedef enum { REPEAT_MIRROR, REPEAT_CLAMP, REPEAT_DEFAULT } RepeatStrategy; typedef enum { WINDOW_NORMAL, WINDOW_FULLSCREEN, WINDOW_MAXIMIZED, WINDOW_MINIMIZED } WindowState; typedef struct { char_type string[16]; size_t len; } UrlPrefix; typedef enum AdjustmentUnit { POINT = 0, PERCENT = 1, PIXEL = 2 } AdjustmentUnit; typedef enum UnderlineHyperlinks { UNDERLINE_ON_HOVER = 0, UNDERLINE_ALWAYS = 1, UNDERLINE_NEVER = 2 } UnderlineHyperlinks; struct MenuItem { const char* *location; size_t location_count; const char *definition; }; typedef struct { monotonic_t visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval; double wheel_scroll_multiplier, touch_scroll_multiplier; int wheel_scroll_min_lines; bool enable_audio_bell; CursorShape cursor_shape, cursor_shape_unfocused; float cursor_beam_thickness; float cursor_underline_thickness; monotonic_t cursor_trail; float cursor_trail_decay_fast; float cursor_trail_decay_slow; float cursor_trail_start_threshold; unsigned int url_style; unsigned int scrollback_pager_history_size; bool scrollback_fill_enlarged_window; char_type *select_by_word_characters; char_type *select_by_word_characters_forward; color_type url_color, background, foreground, active_border_color, inactive_border_color, bell_border_color, tab_bar_background, tab_bar_margin_color; monotonic_t repaint_delay, input_delay; bool focus_follows_mouse; unsigned int hide_window_decorations; bool macos_hide_from_tasks, macos_quit_when_last_window_closed, macos_window_resizable, macos_traditional_fullscreen; unsigned int macos_option_as_alt; float macos_thicken_font; WindowTitleIn macos_show_window_title_in; char *bell_path, *bell_theme; float background_opacity, dim_opacity, scrollback_indicator_opacity; float text_contrast, text_gamma_adjustment; bool text_old_gamma; char *background_image, *default_window_logo; BackgroundImageLayout background_image_layout; ImageAnchorPosition window_logo_position; bool background_image_linear; float background_tint, background_tint_gaps, window_logo_alpha; struct { float width, height; } window_logo_scale; bool dynamic_background_opacity; float inactive_text_alpha; Edge tab_bar_edge; unsigned long tab_bar_min_tabs; DisableLigature disable_ligatures; bool force_ltr; bool resize_in_steps; bool sync_to_monitor; bool close_on_child_death; bool window_alert_on_bell; bool debug_keyboard; bool allow_hyperlinks; struct { monotonic_t on_end, on_pause; } resize_debounce_time; MouseShape pointer_shape_when_grabbed; MouseShape default_pointer_shape; MouseShape pointer_shape_when_dragging, pointer_shape_when_dragging_rectangle; struct { UrlPrefix *values; size_t num, max_prefix_len; } url_prefixes; char_type *url_excluded_characters; bool detect_urls; bool tab_bar_hidden; double font_size; struct { double outer, inner; } tab_bar_margin_height; long macos_menubar_title_max_length; int macos_colorspace; struct { float val; AdjustmentUnit unit; } underline_position, underline_thickness, strikethrough_position, strikethrough_thickness, cell_width, cell_height, baseline; bool show_hyperlink_targets; UnderlineHyperlinks underline_hyperlinks; int background_blur; long macos_titlebar_color; unsigned long wayland_titlebar_color; struct { struct MenuItem *entries; size_t count; } global_menu; bool wayland_enable_ime; struct { size_t num; struct { const char *psname; size_t num; hb_feature_t *features; } *entries; } font_features; struct { Animation *cursor, *visual_bell; } animation; unsigned undercurl_style; struct { float thickness; int unit; } underline_exclusion; float box_drawing_scale[4]; } Options; typedef struct WindowLogoRenderData { window_logo_id_t id; WindowLogo *instance; ImageAnchorPosition position; float alpha; bool using_default; } WindowLogoRenderData; typedef struct { ssize_t vao_idx; float xstart, ystart, dx, dy; Screen *screen; } WindowRenderData; typedef struct { unsigned int left, top, right, bottom; } WindowGeometry; typedef struct { monotonic_t at; int button, modifiers; double x, y; unsigned long num; } Click; #define CLICK_QUEUE_SZ 3 typedef struct { Click clicks[CLICK_QUEUE_SZ]; unsigned int length; } ClickQueue; typedef struct MousePosition { unsigned int cell_x, cell_y; double global_x, global_y; bool in_left_half_of_cell; } MousePosition; typedef struct PendingClick { id_type window_id; int button, count, modifiers; bool grabbed; monotonic_t at; MousePosition mouse_pos; unsigned long press_num; double radius_for_multiclick; } PendingClick; typedef struct WindowBarData { unsigned width, height; uint8_t *buf; PyObject *last_drawn_title_object_id; hyperlink_id_type hyperlink_id_for_title_object; bool needs_render; } WindowBarData; typedef struct { id_type id; bool visible; float cursor_opacity_at_last_render; CursorShape last_cursor_shape; PyObject *title; WindowRenderData render_data; WindowLogoRenderData window_logo; MousePosition mouse_pos; struct { unsigned int left, top, right, bottom; } padding; WindowGeometry geometry; ClickQueue click_queues[8]; monotonic_t last_drag_scroll_at; uint32_t last_special_key_pressed; WindowBarData title_bar_data, url_target_bar_data; id_type redirect_keys_to_overlay; struct { bool enabled; void *key_data; size_t count, capacity; } buffered_keys; struct { PendingClick *clicks; size_t num, capacity; } pending_clicks; } Window; typedef struct { float left, top, right, bottom; uint32_t color; } BorderRect; typedef struct { BorderRect *rect_buf; unsigned int num_border_rects, capacity; bool is_dirty; ssize_t vao_idx; } BorderRects; typedef struct { bool needs_render; monotonic_t updated_at; float opacity; float corner_x[4]; float corner_y[4]; float cursor_edge_x[2]; float cursor_edge_y[2]; } CursorTrail; typedef struct { id_type id; unsigned int active_window, num_windows, capacity; Window *windows; BorderRects border_rects; CursorTrail cursor_trail; } Tab; enum RENDER_STATE { RENDER_FRAME_NOT_REQUESTED, RENDER_FRAME_REQUESTED, RENDER_FRAME_READY }; typedef enum { NO_CLOSE_REQUESTED, CONFIRMABLE_CLOSE_REQUESTED, CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED } CloseRequest; typedef struct { monotonic_t last_resize_event_at; bool in_progress; bool from_os_notification; bool os_says_resize_complete; unsigned int width, height, num_of_resize_events; } LiveResizeInfo; typedef struct WindowChromeState { color_type color; bool use_system_color; unsigned system_color; int background_blur; unsigned hide_window_decorations; bool show_title_in_titlebar; bool resizable; int macos_colorspace; float background_opacity; } WindowChromeState; typedef struct { void *handle; id_type id; monotonic_t created_at; struct { int x, y, w, h; bool is_set, was_maximized; } before_fullscreen; int viewport_width, viewport_height, window_width, window_height; double viewport_x_ratio, viewport_y_ratio; Tab *tabs; BackgroundImage *bgimage; unsigned int active_tab, num_tabs, capacity, last_active_tab, last_num_tabs, last_active_window_id; bool focused_at_last_render, needs_render; WindowRenderData tab_bar_render_data; struct { color_type left, right; } tab_bar_edge_color; bool tab_bar_data_updated; bool is_focused; monotonic_t cursor_blink_zero_time, last_mouse_activity_at; bool has_received_cursor_pos_event; double mouse_x, mouse_y; bool mouse_button_pressed[32]; PyObject *window_title; bool disallow_title_changes, title_is_overriden; bool viewport_size_dirty, viewport_updated_at_least_once; monotonic_t viewport_resized_at; LiveResizeInfo live_resize; bool has_pending_resizes, is_semi_transparent, shown_once, ignore_resize_events; unsigned int clear_count, redraw_count; WindowChromeState last_window_chrome; float background_opacity; FONTS_DATA_HANDLE fonts_data; id_type temp_font_group_id; enum RENDER_STATE render_state; monotonic_t last_render_frame_received_at; uint64_t render_calls; id_type last_focused_counter; CloseRequest close_request; } OSWindow; typedef struct { Options opts; id_type os_window_id_counter, tab_id_counter, window_id_counter; PyObject *boss; BackgroundImage *bgimage; OSWindow *os_windows; size_t num_os_windows, capacity; OSWindow *callback_os_window; bool is_wayland; bool has_render_frames; bool debug_rendering, debug_font_fallback; bool has_pending_resizes, has_pending_closes; bool check_for_active_animated_images; struct { double x, y; } default_dpi; id_type active_drag_in_window, tracked_drag_in_window; int active_drag_button, tracked_drag_button; CloseRequest quit_request; bool redirect_mouse_handling; WindowLogoTable *all_window_logos; int gl_version; PyObject *options_object; } GlobalState; extern GlobalState global_state; #define call_boss(name, ...) if (global_state.boss) { \ PyObject *cret_ = PyObject_CallMethod(global_state.boss, #name, __VA_ARGS__); \ if (cret_ == NULL) { PyErr_Print(); } \ else Py_DECREF(cret_); \ } static inline void sprite_index_to_pos(unsigned idx, unsigned xnum, unsigned ynum, unsigned *x, unsigned *y, unsigned *z) { div_t r = div(idx & 0x7fffffff, ynum * xnum), r2 = div(r.rem, xnum); *z = r.quot; *y = r2.quot; *x = r2.rem; } void gl_init(void); void remove_vao(ssize_t vao_idx); bool remove_os_window(id_type os_window_id); void* make_os_window_context_current(OSWindow *w); void set_os_window_size(OSWindow *os_window, int x, int y); void get_os_window_size(OSWindow *os_window, int *w, int *h, int *fw, int *fh); void get_os_window_pos(OSWindow *os_window, int *x, int *y); void set_os_window_pos(OSWindow *os_window, int x, int y); void get_os_window_content_scale(OSWindow *os_window, double *xdpi, double *ydpi, float *xscale, float *yscale); void update_os_window_references(void); void mark_os_window_for_close(OSWindow* w, CloseRequest cr); void update_os_window_viewport(OSWindow *window, bool notify_boss); bool should_os_window_be_rendered(OSWindow* w); void wakeup_main_loop(void); void swap_window_buffers(OSWindow *w); bool make_window_context_current(id_type); void hide_mouse(OSWindow *w); bool is_mouse_hidden(OSWindow *w); void destroy_os_window(OSWindow *w); void focus_os_window(OSWindow *w, bool also_raise, const char *activation_token); void run_with_activation_token_in_os_window(OSWindow *w, PyObject *callback); void set_os_window_title(OSWindow *w, const char *title); OSWindow* os_window_for_kitty_window(id_type); OSWindow* os_window_for_id(id_type); OSWindow* add_os_window(void); OSWindow* current_os_window(void); void os_window_regions(OSWindow*, Region *main, Region *tab_bar); bool drag_scroll(Window *, OSWindow*); void draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_buf, bool rect_data_is_dirty, uint32_t viewport_width, uint32_t viewport_height, color_type, unsigned int, bool, OSWindow *w); ssize_t create_cell_vao(void); ssize_t create_graphics_vao(void); ssize_t create_border_vao(void); bool send_cell_data_to_gpu(ssize_t, float, float, float, float, Screen *, OSWindow *); void draw_cells(ssize_t, const WindowRenderData*, OSWindow *, bool, bool, bool, Window*); void draw_centered_alpha_mask(OSWindow *w, size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas, float); void draw_cursor_trail(CursorTrail *trail, Window *active_window); bool update_cursor_trail(CursorTrail *ct, Window *w, monotonic_t now, OSWindow *os_window); void update_surface_size(int, int, uint32_t); void free_texture(uint32_t*); void free_framebuffer(uint32_t*); void send_image_to_gpu(uint32_t*, const void*, int32_t, int32_t, bool, bool, bool, RepeatStrategy); void send_sprite_to_gpu(FONTS_DATA_HANDLE fg, sprite_index, pixel*, sprite_index); void blank_canvas(float, color_type); void blank_os_window(OSWindow *); void set_os_window_chrome(OSWindow *w); FONTS_DATA_HANDLE load_fonts_data(double, double, double); void send_prerendered_sprites_for_window(OSWindow *w); #ifdef __APPLE__ #include "cocoa_window.h" #endif void request_frame_render(OSWindow *w); void request_tick_callback(void); typedef void (* timer_callback_fun)(id_type, void*); typedef void (* tick_callback_fun)(void*); id_type add_main_loop_timer(monotonic_t interval, bool repeats, timer_callback_fun callback, void *callback_data, timer_callback_fun free_callback); void remove_main_loop_timer(id_type timer_id); void update_main_loop_timer(id_type timer_id, monotonic_t interval, bool enabled); void run_main_loop(tick_callback_fun, void*); void stop_main_loop(void); void os_window_update_size_increments(OSWindow *window); void set_os_window_title_from_window(Window *w, OSWindow *os_window); void update_os_window_title(OSWindow *os_window); void fake_scroll(Window *w, int amount, bool upwards); Window* window_for_window_id(id_type kitty_window_id); bool mouse_open_url(Window *w); bool mouse_set_last_visited_cmd_output(Window *w); bool mouse_select_cmd_output(Window *w); bool move_cursor_to_mouse_if_at_shell_prompt(Window *w); void mouse_selection(Window *w, int code, int button); const char* format_mods(unsigned mods); void dispatch_pending_clicks(id_type, void*); void send_pending_click_to_window(Window*, int); void get_platform_dependent_config_values(void *glfw_window); bool draw_window_title(OSWindow *window, const char *text, color_type fg, color_type bg, uint8_t *output_buf, size_t width, size_t height); uint8_t* draw_single_ascii_char(const char ch, size_t *result_width, size_t *result_height); bool is_os_window_fullscreen(OSWindow *); void update_ime_focus(OSWindow* osw, bool focused); void update_ime_position(Window* w, Screen *screen); bool update_ime_position_for_window(id_type window_id, bool force, int update_focus); void set_ignore_os_keyboard_processing(bool enabled); void update_menu_bar_title(PyObject *title UNUSED); void change_live_resize_state(OSWindow*, bool); bool render_os_window(OSWindow *w, monotonic_t now, bool ignore_render_frames, bool scan_for_animated_images); void update_mouse_pointer_shape(void); void adjust_window_size_for_csd(OSWindow *w, int width, int height, int *adjusted_width, int *adjusted_height); void dispatch_buffered_keys(Window *w); kitty-0.41.1/kitty/systemd.c0000664000175000017510000002047614773370543015320 0ustar nileshnilesh/* * systemd.c * Copyright (C) 2024 Kovid Goyal * * Distributed under terms of the GPL3 license. */ #include "data-types.h" #include "cleanup.h" #include #define FUNC(name, restype, ...) typedef restype (*name##_func)(__VA_ARGS__); static name##_func name = NULL #define LOAD_FUNC(name) {\ *(void **) (&name) = dlsym(systemd.lib, #name); \ if (!name) { \ const char* error = dlerror(); \ if (error != NULL) { \ log_error("Failed to load the function %s with error: %s", #name, error); return; \ } \ } \ } typedef struct sd_bus sd_bus; static struct { void *lib; sd_bus *user_bus; bool initialized, functions_loaded, ok; } systemd = {0}; typedef struct { const char *name; const char *message; int _need_free; int64_t filler; // just in case systemd ever increases the size of this struct } sd_bus_error; typedef struct sd_bus_message sd_bus_message; FUNC(sd_bus_default_user, int, sd_bus**); FUNC(sd_bus_message_unref, sd_bus_message*, sd_bus_message*); FUNC(sd_bus_error_free, void, sd_bus_error*); FUNC(sd_bus_unref, sd_bus*, sd_bus*); FUNC(sd_bus_message_new_method_call, int, sd_bus *, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member); FUNC(sd_bus_message_append, int, sd_bus_message *m, const char *types, ...); FUNC(sd_bus_message_open_container, int, sd_bus_message *m, char type, const char *contents); FUNC(sd_bus_message_close_container, int, sd_bus_message *m); FUNC(sd_pid_get_user_slice, int, pid_t pid, char **slice); FUNC(sd_bus_call, int, sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply); static void ensure_initialized(void) { if (systemd.initialized) return; systemd.initialized = true; const char* libnames[] = { #if defined(_KITTY_SYSTEMD_LIBRARY) _KITTY_SYSTEMD_LIBRARY, #else "libsystemd.so", // some installs are missing the .so symlink, so try the full name "libsystemd.so.0", "libsystemd.so.0.38.0", #endif NULL }; for (int i = 0; libnames[i]; i++) { systemd.lib = dlopen(libnames[i], RTLD_LAZY); if (systemd.lib) break; } if (systemd.lib == NULL) { log_error("Failed to load %s with error: %s\n", libnames[0], dlerror()); return; } LOAD_FUNC(sd_bus_default_user); LOAD_FUNC(sd_bus_message_unref); LOAD_FUNC(sd_bus_error_free); LOAD_FUNC(sd_bus_unref); LOAD_FUNC(sd_bus_message_new_method_call); LOAD_FUNC(sd_bus_message_append); LOAD_FUNC(sd_bus_message_open_container); LOAD_FUNC(sd_bus_message_close_container); LOAD_FUNC(sd_pid_get_user_slice); LOAD_FUNC(sd_bus_call); systemd.functions_loaded = true; int ret = sd_bus_default_user(&systemd.user_bus); if (ret < 0) { log_error("Failed to open systemd user bus with error: %s", strerror(-ret)); return; } systemd.ok = true; } static inline void err_cleanup(sd_bus_error *p) { sd_bus_error_free(p); } #define RAII_bus_error(name) __attribute__((cleanup(err_cleanup))) sd_bus_error name = {0}; static inline void msg_cleanup(sd_bus_message **p) { sd_bus_message_unref(*p); } #define RAII_message(name) __attribute__((cleanup(msg_cleanup))) sd_bus_message *name = NULL; #define SYSTEMD_DESTINATION "org.freedesktop.systemd1" #define SYSTEMD_PATH "/org/freedesktop/systemd1" #define SYSTEMD_INTERFACE "org.freedesktop.systemd1.Manager" static bool set_systemd_error(int r, const char *msg) { RAII_PyObject(m, PyUnicode_FromFormat("Failed to %s: %s", msg, strerror(-r))); if (m) { RAII_PyObject(e, Py_BuildValue("(iO)", -r, m)); if (e) PyErr_SetObject(PyExc_OSError, e); } return false; } static bool set_reply_error(const char* func_name, int r, const sd_bus_error *err) { RAII_PyObject(m, PyUnicode_FromFormat("Failed to call %s: %s: %s", func_name, err->name, err->message)); if (m) { RAII_PyObject(e, Py_BuildValue("(iO)", -r, m)); if (e) PyErr_SetObject(PyExc_OSError, e); } return false; } static bool move_pid_into_new_scope(pid_t pid, const char* scope_name, const char *description) { pid_t parent_pid = getpid(); RAII_bus_error(err); RAII_message(m); RAII_message(reply); int r; #define checked_call(func, ...) if ((r = func(__VA_ARGS__)) < 0) { return set_systemd_error(r, #func); } checked_call(sd_bus_message_new_method_call, systemd.user_bus, &m, SYSTEMD_DESTINATION, SYSTEMD_PATH, SYSTEMD_INTERFACE, "StartTransientUnit"); // mode is "fail" which means it will fail if a unit with scope_name already exists checked_call(sd_bus_message_append, m, "ss", scope_name, "fail"); checked_call(sd_bus_message_open_container, m, 'a', "(sv)"); if (description && description[0]) { checked_call(sd_bus_message_append, m, "(sv)", "Description", "s", description); } RAII_ALLOC(char, slice, NULL); if (sd_pid_get_user_slice(parent_pid, &slice) >= 0) { checked_call(sd_bus_message_append, m, "(sv)", "Slice", "s", slice); } else { // Fallback checked_call(sd_bus_message_append, m, "(sv)", "Slice", "s", "kitty.slice"); } // Add the PID to this scope checked_call(sd_bus_message_open_container, m, 'r', "sv"); checked_call(sd_bus_message_append, m, "s", "PIDs"); checked_call(sd_bus_message_open_container, m, 'v', "au"); checked_call(sd_bus_message_open_container, m, 'a', "u"); checked_call(sd_bus_message_append, m, "u", pid); checked_call(sd_bus_message_close_container, m); // au checked_call(sd_bus_message_close_container, m); // v checked_call(sd_bus_message_close_container, m); // (sv) // If something in this process group is OOMkilled dont kill the rest of // the process group. Since typically the shell is not causing the OOM // something being run inside it is. checked_call(sd_bus_message_append, m, "(sv)", "OOMPolicy", "s", "continue"); // Make sure shells are terminated with SIGHUP not just SIGTERM checked_call(sd_bus_message_append, m, "(sv)", "SendSIGHUP", "b", true); // Unload this unit in failed state as well checked_call(sd_bus_message_append, m, "(sv)", "CollectMode", "s", "inactive-or-failed"); // Only kill the main process on stop checked_call(sd_bus_message_append, m, "(sv)", "KillMode", "s", "process"); checked_call(sd_bus_message_close_container, m); // End properties a(sv) // checked_call(sd_bus_message_append, m, "a(sa(sv))", 0); // No auxiliary units // if ((r=sd_bus_call(systemd.user_bus, m, 0 /* timeout default */, &err, &reply)) < 0) return set_reply_error("StartTransientUnit", r, &err); return true; #undef checked_call } static void finalize(void) { if (systemd.user_bus) sd_bus_unref(systemd.user_bus); if (systemd.lib) dlclose(systemd.lib); memset(&systemd, 0, sizeof(systemd)); } static bool ensure_initialized_and_useable(void) { ensure_initialized(); if (!systemd.ok) { if (!systemd.lib) PyErr_SetString(PyExc_NotImplementedError, "Could not load libsystemd"); else if (!systemd.functions_loaded) PyErr_SetString(PyExc_NotImplementedError, "Could not load libsystemd functions"); else PyErr_SetString(PyExc_NotImplementedError, "Could not connect to systemd user bus"); return false; } return true; } static PyObject* systemd_move_pid_into_new_scope(PyObject *self UNUSED, PyObject *args) { long pid; const char *scope_name, *description; if (!PyArg_ParseTuple(args, "lss", &pid, &scope_name, &description)) return NULL; #ifdef __APPLE__ (void)ensure_initialized_and_useable; (void)move_pid_into_new_scope; PyErr_SetString(PyExc_NotImplementedError, "not supported on this platform"); #else if (!ensure_initialized_and_useable()) return NULL; move_pid_into_new_scope(pid, scope_name, description); #endif if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } static PyMethodDef module_methods[] = { METHODB(systemd_move_pid_into_new_scope, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; bool init_systemd_module(PyObject *module) { register_at_exit_cleanup_func(SYSTEMD_CLEANUP_FUNC, finalize); if (PyModule_AddFunctions(module, module_methods) != 0) return false; return true; } kitty-0.41.1/kitty/tab_bar.py0000664000175000017510000006643014773370543015430 0ustar nileshnilesh#!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal import os import re from collections.abc import Callable, Sequence from functools import lru_cache, partial, wraps from string import Formatter as StringFormatter from typing import ( Any, NamedTuple, ) from .borders import Border, BorderColor from .constants import config_dir from .fast_data_types import ( BOTTOM_EDGE, DECAWM, Color, Region, Screen, cell_size_for_window, get_boss, get_options, pt_to_px, set_tab_bar_render_data, update_tab_bar_edge_colors, viewport_for_window, wcswidth, ) from .progress import ProgressState from .rgb import alpha_blend, color_as_sgr, color_from_int, to_color from .types import WindowGeometry, run_once from .typing import EdgeLiteral, PowerlineStyle from .utils import color_as_int, log_error, sgr_sanitizer_pat class TabBarData(NamedTuple): title: str is_active: bool needs_attention: bool tab_id: int num_windows: int num_window_groups: int layout_name: str has_activity_since_last_focus: bool active_fg: int | None active_bg: int | None inactive_fg: int | None inactive_bg: int | None num_of_windows_with_progress: int total_progress: int last_focused_window_with_progress_id: int class DrawData(NamedTuple): leading_spaces: int sep: str trailing_spaces: int bell_on_tab: str alpha: Sequence[float] active_fg: Color active_bg: Color inactive_fg: Color inactive_bg: Color default_bg: Color title_template: str active_title_template: str | None tab_activity_symbol: str powerline_style: PowerlineStyle tab_bar_edge: EdgeLiteral max_tab_title_length: int def tab_fg(self, tab: TabBarData) -> int: if tab.is_active: if tab.active_fg is not None: return tab.active_fg return int(self.active_fg) if tab.inactive_fg is not None: return tab.inactive_fg return int(self.inactive_fg) def tab_bg(self, tab: TabBarData) -> int: if tab.is_active: if tab.active_bg is not None: return tab.active_bg return int(self.active_bg) if tab.inactive_bg is not None: return tab.inactive_bg return int(self.inactive_bg) def as_rgb(x: int) -> int: return (x << 8) | 2 @lru_cache def report_template_failure(template: str, e: str) -> None: log_error(f'Invalid tab title template: "{template}" with error: {e}') @lru_cache def compile_template(template: str) -> Any: try: return compile('f"""' + template + '"""', '